FAQ: solo lectura y bilingüe (ES/EN)
Quita crear/editar/eliminar (la FAQ se gestiona por el webhook). Agrega selector ES/EN con textos de interfaz en ambos idiomas y soporte para variantes por ítem (Question_en/Answer_es) y etiqueta de idioma.
This commit is contained in:
1 parent
399cc1a3a5
commit
06568c36a6
1 file changed
+124
-166
+124
-166
@@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="robots" content="noindex, nofollow, noarchive, nosnippet, noimageindex" />
|
<meta name="robots" content="noindex, nofollow, noarchive, nosnippet, noimageindex" />
|
||||||
<meta name="googlebot" content="noindex, nofollow" />
|
<meta name="googlebot" content="noindex, nofollow" />
|
||||||
<title>FAQ interna — OCA Paraguay</title>
|
<title>FAQ — OCA Paraguay</title>
|
||||||
<link rel="icon" type="image/svg+xml" href="../img/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="../img/favicon.svg" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
@@ -18,54 +18,35 @@
|
|||||||
}
|
}
|
||||||
*{box-sizing:border-box}
|
*{box-sizing:border-box}
|
||||||
body{margin:0;font-family:Inter,system-ui,sans-serif;background:var(--bg);color:var(--ink);line-height:1.55}
|
body{margin:0;font-family:Inter,system-ui,sans-serif;background:var(--bg);color:var(--ink);line-height:1.55}
|
||||||
a{color:var(--violet)}
|
|
||||||
header{background:#fff;border-bottom:1px solid var(--line);position:sticky;top:0;z-index:20}
|
header{background:#fff;border-bottom:1px solid var(--line);position:sticky;top:0;z-index:20}
|
||||||
.wrap{max-width:980px;margin:0 auto;padding:0 20px}
|
.wrap{max-width:920px;margin:0 auto;padding:0 20px}
|
||||||
.head{display:flex;align-items:center;gap:14px;padding:14px 0}
|
.head{display:flex;align-items:center;gap:14px;padding:14px 0;flex-wrap:wrap}
|
||||||
.head img{width:38px;height:38px;border-radius:9px}
|
.head img{width:38px;height:38px;border-radius:9px}
|
||||||
.head h1{font-family:Sora,sans-serif;font-size:1.05rem;margin:0}
|
.head h1{font-family:Sora,sans-serif;font-size:1.05rem;margin:0}
|
||||||
.head h1 small{display:block;font-family:Inter,sans-serif;font-weight:500;font-size:.72rem;color:var(--muted);letter-spacing:.04em;text-transform:uppercase}
|
.head h1 small{display:block;font-family:Inter,sans-serif;font-weight:500;font-size:.72rem;color:var(--muted);letter-spacing:.04em;text-transform:uppercase}
|
||||||
.noidx{margin-left:auto;font-size:.68rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--py-red);border:1px solid currentColor;border-radius:999px;padding:4px 10px}
|
.tools{margin-left:auto;display:flex;align-items:center;gap:10px}
|
||||||
main{padding:28px 0 60px}
|
.noidx{font-size:.64rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--py-red);border:1px solid currentColor;border-radius:999px;padding:3px 9px;white-space:nowrap}
|
||||||
.bar{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:22px}
|
.lang{display:inline-flex;background:#f2eff8;border-radius:999px;padding:3px}
|
||||||
.bar input[type=search]{flex:1 1 240px;padding:11px 14px;border:1px solid var(--line);border-radius:12px;font:inherit;background:#fff}
|
.lang button{border:none;background:transparent;font:inherit;font-size:.76rem;font-weight:700;padding:5px 11px;border-radius:999px;cursor:pointer;color:var(--muted)}
|
||||||
.bar input:focus{outline:2px solid rgba(108,76,224,.25);border-color:var(--violet-soft)}
|
.lang button.on{background:#fff;color:var(--violet);box-shadow:0 1px 3px rgba(0,0,0,.12)}
|
||||||
.btn{font:inherit;font-weight:600;border-radius:12px;border:1px solid transparent;padding:10px 16px;cursor:pointer;display:inline-flex;align-items:center;gap:8px}
|
main{padding:26px 0 60px}
|
||||||
.btn-primary{background:var(--violet);color:#fff}
|
.bar{margin-bottom:22px}
|
||||||
.btn-primary:hover{background:#5b3fd0}
|
.bar input{width:100%;padding:12px 15px;border:1px solid var(--line);border-radius:12px;font:inherit;background:#fff}
|
||||||
.btn-ghost{background:#fff;border-color:var(--line);color:var(--ink)}
|
.bar input:focus{outline:2px solid rgba(108,76,224,.22);border-color:var(--violet-soft)}
|
||||||
.btn-ghost:hover{border-color:var(--violet-soft)}
|
|
||||||
.btn-danger{background:#fff;border-color:#f0c9c4;color:var(--py-red)}
|
|
||||||
.btn-danger:hover{background:#fdf1ef}
|
|
||||||
.msg{padding:12px 16px;border-radius:12px;margin-bottom:18px;font-size:.9rem;display:none}
|
.msg{padding:12px 16px;border-radius:12px;margin-bottom:18px;font-size:.9rem;display:none}
|
||||||
.msg.show{display:block}
|
.msg.show{display:block}
|
||||||
.msg.err{background:#fdf1ef;color:#8f2117;border:1px solid #f0c9c4}
|
.msg.err{background:#fdf1ef;color:#8f2117;border:1px solid #f0c9c4}
|
||||||
.msg.ok{background:#eefaf5;color:#0b6b52;border:1px solid #bfeadd}
|
.area{margin:0 0 26px}
|
||||||
.area{margin:0 0 28px}
|
.area h2{font-family:Sora,sans-serif;font-size:.8rem;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin:0 0 10px}
|
||||||
.area h2{font-family:Sora,sans-serif;font-size:.82rem;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin:0 0 10px}
|
.card{background:#fff;border:1px solid var(--line);border-radius:14px;margin-bottom:10px;overflow:hidden;transition:border-color .18s,box-shadow .18s}
|
||||||
.card{background:#fff;border:1px solid var(--line);border-radius:14px;margin-bottom:10px;overflow:hidden}
|
.card[open]{border-color:var(--violet-soft);box-shadow:0 8px 26px rgba(108,76,224,.09)}
|
||||||
.card summary{cursor:pointer;padding:15px 18px;font-weight:600;list-style:none;display:flex;align-items:center;gap:12px}
|
.card summary{cursor:pointer;padding:15px 18px;font-weight:600;list-style:none;display:flex;align-items:center;gap:12px}
|
||||||
.card summary::-webkit-details-marker{display:none}
|
.card summary::-webkit-details-marker{display:none}
|
||||||
.card summary::after{content:"+";margin-left:auto;color:var(--violet);font-weight:800;font-size:1.1rem}
|
.card summary::after{content:"+";margin-left:auto;color:var(--violet);font-weight:800;font-size:1.15rem;line-height:1}
|
||||||
.card[open] summary::after{content:"–"}
|
.card[open] summary::after{content:"–"}
|
||||||
.card .body{padding:0 18px 16px;color:#41394a}
|
.card .ans{padding:0 18px 17px;margin:0;white-space:pre-line;color:#41394a;font-size:.93rem}
|
||||||
.card .ans{white-space:pre-line;margin:0 0 14px;font-size:.93rem}
|
.empty{color:var(--muted);padding:16px 0}
|
||||||
.acts{display:flex;gap:8px;flex-wrap:wrap}
|
footer{border-top:1px solid var(--line);background:#fff;padding:18px 0;font-size:.76rem;color:var(--muted);text-align:center}
|
||||||
.acts .btn{padding:7px 12px;font-size:.8rem;border-radius:9px}
|
|
||||||
.empty{color:var(--muted);padding:20px 0}
|
|
||||||
dialog{border:none;border-radius:16px;padding:0;max-width:560px;width:calc(100% - 32px);box-shadow:0 30px 80px rgba(0,0,0,.35)}
|
|
||||||
dialog::backdrop{background:rgba(35,27,43,.45)}
|
|
||||||
.dlg-head{padding:18px 22px;border-bottom:1px solid var(--line);display:flex;align-items:center}
|
|
||||||
.dlg-head h3{font-family:Sora,sans-serif;font-size:1rem;margin:0}
|
|
||||||
.dlg-head button{margin-left:auto;background:none;border:none;font-size:1.3rem;cursor:pointer;color:var(--muted)}
|
|
||||||
form{padding:20px 22px 22px}
|
|
||||||
.field{margin-bottom:14px}
|
|
||||||
.field label{display:block;font-size:.78rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--muted);margin-bottom:6px}
|
|
||||||
.field input,.field textarea{width:100%;padding:11px 13px;border:1px solid var(--line);border-radius:10px;font:inherit;background:#fff}
|
|
||||||
.field textarea{min-height:130px;resize:vertical}
|
|
||||||
.field input:focus,.field textarea:focus{outline:2px solid rgba(108,76,224,.22);border-color:var(--violet-soft)}
|
|
||||||
.dlg-foot{display:flex;gap:10px;justify-content:flex-end;margin-top:6px}
|
|
||||||
footer{border-top:1px solid var(--line);background:#fff;padding:18px 0;font-size:.78rem;color:var(--muted);text-align:center}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -73,52 +54,73 @@
|
|||||||
<header>
|
<header>
|
||||||
<div class="wrap head">
|
<div class="wrap head">
|
||||||
<img src="../img/logo.svg" alt="OCA Paraguay" width="38" height="38" />
|
<img src="../img/logo.svg" alt="OCA Paraguay" width="38" height="38" />
|
||||||
<h1>Preguntas frecuentes<small>OCA Paraguay · uso interno</small></h1>
|
<h1><span data-i18n="title">Preguntas frecuentes</span><small data-i18n="subtitle">OCA Paraguay · uso interno</small></h1>
|
||||||
<span class="noidx">no indexada</span>
|
<div class="tools">
|
||||||
|
<span class="noidx" data-i18n="badge">no indexada</span>
|
||||||
|
<span class="lang" role="group" aria-label="Language">
|
||||||
|
<button type="button" data-lang="es">ES</button>
|
||||||
|
<button type="button" data-lang="en">EN</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="wrap">
|
<main class="wrap">
|
||||||
<div class="msg" id="msg"></div>
|
<div class="msg" id="msg"></div>
|
||||||
|
|
||||||
<div class="bar">
|
<div class="bar">
|
||||||
<input type="search" id="q" placeholder="Buscar pregunta, área o respuesta…" aria-label="Buscar" />
|
<input type="search" id="q" data-i18n-placeholder="search" placeholder="Buscar…" aria-label="Buscar" />
|
||||||
<button class="btn btn-primary" id="add">+ Nueva pregunta</button>
|
|
||||||
<button class="btn btn-ghost" id="reload" title="Recargar">↻</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div id="list"><p class="empty" data-i18n="loading">Cargando…</p></div>
|
||||||
<div id="list"><p class="empty">Cargando…</p></div>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>Página interna · datos vía webhook · <span id="count">—</span></footer>
|
<footer><span data-i18n="footer">Página interna · no indexada</span> · <span id="count">—</span></footer>
|
||||||
|
|
||||||
<dialog id="dlg">
|
|
||||||
<div class="dlg-head"><h3 id="dlg-title">Nueva pregunta</h3><button type="button" id="close" aria-label="Cerrar">×</button></div>
|
|
||||||
<form id="form">
|
|
||||||
<input type="hidden" id="f-id" />
|
|
||||||
<div class="field"><label for="f-area">Área</label><input id="f-area" name="area" placeholder="Paraguay Tax" required /></div>
|
|
||||||
<div class="field"><label for="f-q">Pregunta</label><input id="f-q" name="question" placeholder="¿…?" required /></div>
|
|
||||||
<div class="field"><label for="f-a">Respuesta</label><textarea id="f-a" name="answer" placeholder="Respuesta…" required></textarea></div>
|
|
||||||
<div class="dlg-foot">
|
|
||||||
<button type="button" class="btn btn-ghost" id="cancel">Cancelar</button>
|
|
||||||
<button type="submit" class="btn btn-primary" id="save">Guardar</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const API = 'https://n8n.odoo4projects.com/webhook/oca-py/faq';
|
const API = 'https://n8n.odoo4projects.com/webhook/oca-py/faq';
|
||||||
const el = (id) => document.getElementById(id);
|
const el = (id) => document.getElementById(id);
|
||||||
let items = [];
|
|
||||||
|
|
||||||
function flash(text, kind) {
|
const I18N = {
|
||||||
|
es: {
|
||||||
|
title: 'Preguntas frecuentes', subtitle: 'OCA Paraguay · uso interno', badge: 'no indexada',
|
||||||
|
search: 'Buscar pregunta, área o respuesta…', loading: 'Cargando…', empty: 'Sin resultados.',
|
||||||
|
footer: 'Página interna · no indexada', count: (n, total) => n + ' de ' + total + ' preguntas',
|
||||||
|
group: 'General', errRead: 'No se pudo leer el webhook',
|
||||||
|
errData: 'El webhook respondió sin datos',
|
||||||
|
hint: 'En n8n, poné el nodo Webhook en "Respond: When Last Node Finishes".',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: 'Frequently asked questions', subtitle: 'OCA Paraguay · internal use', badge: 'not indexed',
|
||||||
|
search: 'Search question, area or answer…', loading: 'Loading…', empty: 'No results.',
|
||||||
|
footer: 'Internal page · not indexed', count: (n, total) => n + ' of ' + total + ' questions',
|
||||||
|
group: 'General', errRead: 'Could not read the webhook',
|
||||||
|
errData: 'The webhook returned no data',
|
||||||
|
hint: 'In n8n, set the Webhook node to "Respond: When Last Node Finishes".',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
lang: (localStorage.getItem('ocaFaqLang') || (navigator.language || 'es').slice(0, 2)) === 'en' ? 'en' : 'es',
|
||||||
|
items: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const t = (k) => I18N[state.lang][k] ?? k;
|
||||||
|
|
||||||
|
function applyLang() {
|
||||||
|
document.documentElement.lang = state.lang;
|
||||||
|
document.querySelectorAll('[data-i18n]').forEach(n => { n.textContent = t(n.dataset.i18n); });
|
||||||
|
document.querySelectorAll('[data-i18n-placeholder]').forEach(n => { n.placeholder = t(n.dataset.i18nPlaceholder); });
|
||||||
|
document.querySelectorAll('.lang button').forEach(b => b.classList.toggle('on', b.dataset.lang === state.lang));
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
function flash(text) {
|
||||||
const m = el('msg');
|
const m = el('msg');
|
||||||
m.textContent = text;
|
m.textContent = text;
|
||||||
m.className = 'msg show ' + (kind || 'ok');
|
m.className = 'msg show err';
|
||||||
clearTimeout(flash._t);
|
|
||||||
flash._t = setTimeout(() => { m.className = 'msg'; }, 6000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hideMsg() { el('msg').className = 'msg'; }
|
||||||
|
|
||||||
function normalize(payload) {
|
function normalize(payload) {
|
||||||
if (Array.isArray(payload)) {
|
if (Array.isArray(payload)) {
|
||||||
if (payload.length && Array.isArray(payload[0].data)) return payload[0].data;
|
if (payload.length && Array.isArray(payload[0].data)) return payload[0].data;
|
||||||
@@ -128,35 +130,45 @@ function normalize(payload) {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load() {
|
// Optional per-language variants: Question_en / Answer_es / area_en …
|
||||||
try {
|
function pick(it, field) {
|
||||||
const res = await fetch(API, { headers: { Accept: 'application/json' } });
|
const key = Object.keys(it).find(k => k.toLowerCase() === (field + '_' + state.lang));
|
||||||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
if (key && it[key]) return it[key];
|
||||||
const payload = await res.json();
|
return it[field] ?? '';
|
||||||
items = normalize(payload);
|
|
||||||
render();
|
|
||||||
if (!items.length && !Array.isArray(payload)) {
|
|
||||||
flash('El webhook respondió sin datos (' + JSON.stringify(payload) + '). En n8n, poné el nodo Webhook en "Respond: When Last Node Finishes" para que devuelva la lista.', 'err');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
el('list').innerHTML = '';
|
|
||||||
flash('No se pudo leer el webhook (' + err.message + '). Si es CORS, hay que habilitar el origen https://oca-py.org en n8n.', 'err');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optional language tag on an item (lang / language / idioma)
|
||||||
|
function itemLang(it) {
|
||||||
|
const key = Object.keys(it).find(k => ['lang', 'language', 'idioma'].includes(k.toLowerCase()));
|
||||||
|
const v = key ? String(it[key] || '').toLowerCase().slice(0, 2) : '';
|
||||||
|
return (v === 'es' || v === 'en') ? v : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
const term = el('q').value.trim().toLowerCase();
|
|
||||||
const list = el('list');
|
const list = el('list');
|
||||||
const shown = items.filter(it => {
|
if (!state.items.length) {
|
||||||
|
list.innerHTML = '<p class="empty">' + t('empty') + '</p>';
|
||||||
|
el('count').textContent = t('count')(0, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const term = (el('q').value || '').trim().toLowerCase();
|
||||||
|
const shown = state.items.filter(it => {
|
||||||
|
const lang = itemLang(it);
|
||||||
|
if (lang && lang !== state.lang) return false;
|
||||||
if (!term) return true;
|
if (!term) return true;
|
||||||
return [it.area, it.Question, it.Answer].join(' ').toLowerCase().includes(term);
|
return [pick(it, 'area'), pick(it, 'Question'), pick(it, 'Answer')]
|
||||||
|
.join(' ').toLowerCase().includes(term);
|
||||||
});
|
});
|
||||||
el('count').textContent = shown.length + ' de ' + items.length + ' preguntas';
|
|
||||||
|
el('count').textContent = t('count')(shown.length, state.items.length);
|
||||||
list.innerHTML = '';
|
list.innerHTML = '';
|
||||||
if (!shown.length) { list.innerHTML = '<p class="empty">Sin resultados.</p>'; return; }
|
if (!shown.length) { list.innerHTML = '<p class="empty">' + t('empty') + '</p>'; return; }
|
||||||
|
|
||||||
const groups = {};
|
const groups = {};
|
||||||
shown.forEach(it => { (groups[it.area || 'General'] = groups[it.area || 'General'] || []).push(it); });
|
shown.forEach(it => {
|
||||||
|
const area = pick(it, 'area') || t('group');
|
||||||
|
(groups[area] = groups[area] || []).push(it);
|
||||||
|
});
|
||||||
|
|
||||||
Object.keys(groups).sort().forEach(area => {
|
Object.keys(groups).sort().forEach(area => {
|
||||||
const sec = document.createElement('section');
|
const sec = document.createElement('section');
|
||||||
@@ -169,99 +181,45 @@ function render() {
|
|||||||
const card = document.createElement('details');
|
const card = document.createElement('details');
|
||||||
card.className = 'card';
|
card.className = 'card';
|
||||||
const sum = document.createElement('summary');
|
const sum = document.createElement('summary');
|
||||||
sum.textContent = it.Question || '(sin pregunta)';
|
sum.textContent = pick(it, 'Question') || '—';
|
||||||
card.appendChild(sum);
|
|
||||||
|
|
||||||
const body = document.createElement('div');
|
|
||||||
body.className = 'body';
|
|
||||||
const ans = document.createElement('p');
|
const ans = document.createElement('p');
|
||||||
ans.className = 'ans';
|
ans.className = 'ans';
|
||||||
ans.textContent = it.Answer || '';
|
ans.textContent = pick(it, 'Answer') || '';
|
||||||
body.appendChild(ans);
|
card.append(sum, ans);
|
||||||
|
|
||||||
const acts = document.createElement('div');
|
|
||||||
acts.className = 'acts';
|
|
||||||
const edit = document.createElement('button');
|
|
||||||
edit.className = 'btn btn-ghost';
|
|
||||||
edit.textContent = 'Editar';
|
|
||||||
edit.onclick = () => openForm(it);
|
|
||||||
const del = document.createElement('button');
|
|
||||||
del.className = 'btn btn-danger';
|
|
||||||
del.textContent = 'Eliminar';
|
|
||||||
del.onclick = () => remove(it);
|
|
||||||
acts.append(edit, del);
|
|
||||||
|
|
||||||
body.appendChild(acts);
|
|
||||||
card.appendChild(body);
|
|
||||||
sec.appendChild(card);
|
sec.appendChild(card);
|
||||||
});
|
});
|
||||||
list.appendChild(sec);
|
list.appendChild(sec);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function openForm(it) {
|
async function load() {
|
||||||
el('dlg-title').textContent = it ? 'Editar pregunta' : 'Nueva pregunta';
|
try {
|
||||||
el('f-id').value = it ? it.id : '';
|
const res = await fetch(API, { headers: { Accept: 'application/json' } });
|
||||||
el('f-area').value = it ? (it.area || '') : '';
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||||
el('f-q').value = it ? (it.Question || '') : '';
|
const payload = await res.json();
|
||||||
el('f-a').value = it ? (it.Answer || '') : '';
|
state.items = normalize(payload);
|
||||||
el('dlg').showModal();
|
hideMsg();
|
||||||
el(it ? 'f-q' : 'f-area').focus();
|
render();
|
||||||
|
if (!state.items.length && !Array.isArray(payload)) {
|
||||||
|
flash(t('errData') + ' (' + JSON.stringify(payload) + '). ' + t('hint'));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
state.items = [];
|
||||||
|
render();
|
||||||
|
flash(t('errRead') + ' (' + err.message + ').');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save(ev) {
|
document.querySelectorAll('.lang button').forEach(b => {
|
||||||
ev.preventDefault();
|
b.onclick = () => {
|
||||||
const id = el('f-id').value;
|
state.lang = b.dataset.lang;
|
||||||
const payload = {
|
localStorage.setItem('ocaFaqLang', state.lang);
|
||||||
area: el('f-area').value.trim(),
|
applyLang();
|
||||||
Question: el('f-q').value.trim(),
|
|
||||||
Answer: el('f-a').value.trim(),
|
|
||||||
};
|
};
|
||||||
if (id) payload.id = Number(id);
|
|
||||||
const btn = el('save');
|
|
||||||
btn.disabled = true; btn.textContent = 'Guardando…';
|
|
||||||
try {
|
|
||||||
const res = await fetch(API, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
||||||
el('dlg').close();
|
|
||||||
flash(id ? 'Pregunta actualizada.' : 'Pregunta creada.', 'ok');
|
|
||||||
await load();
|
|
||||||
} catch (err) {
|
|
||||||
flash('No se pudo guardar (' + err.message + ').', 'err');
|
|
||||||
} finally {
|
|
||||||
btn.disabled = false; btn.textContent = 'Guardar';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function remove(it) {
|
|
||||||
if (!confirm('¿Eliminar «' + (it.Question || '') + '»?')) return;
|
|
||||||
try {
|
|
||||||
let res = await fetch(API, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ id: it.id }),
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
res = await fetch(API + '?id=' + encodeURIComponent(it.id), { method: 'DELETE' });
|
|
||||||
}
|
|
||||||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
||||||
flash('Pregunta eliminada.', 'ok');
|
|
||||||
await load();
|
|
||||||
} catch (err) {
|
|
||||||
flash('No se pudo eliminar (' + err.message + ').', 'err');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
el('add').onclick = () => openForm(null);
|
|
||||||
el('reload').onclick = load;
|
|
||||||
el('close').onclick = () => el('dlg').close();
|
|
||||||
el('cancel').onclick = () => el('dlg').close();
|
|
||||||
el('form').addEventListener('submit', save);
|
|
||||||
el('q').addEventListener('input', render);
|
el('q').addEventListener('input', render);
|
||||||
|
|
||||||
|
applyLang();
|
||||||
load();
|
load();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in new issue
Block a user