/* Panel de administración: login, rondas, Excel. */ (() => { const $ = (id) => document.getElementById(id); const toast = (msg, isError = false) => { const el = $('toast'); el.textContent = msg; el.classList.toggle('error', isError); el.classList.add('show'); clearTimeout(el._t); el._t = setTimeout(() => el.classList.remove('show'), 2600); }; const fetchJSON = async (url, opts) => { const res = await fetch(url, opts && { method: opts.method, headers: opts.body ? { 'Content-Type': 'application/json' } : undefined, body: opts.body ? JSON.stringify(opts.body) : undefined, }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error || 'Error de conexión'); return data; }; function show(view) { $('login-card').classList.toggle('hidden', view !== 'login'); $('panel').classList.toggle('hidden', view !== 'panel'); } // ---------- login ---------- $('login-form').addEventListener('submit', async (e) => { e.preventDefault(); const err = $('login-error'); err.classList.remove('show'); try { await fetchJSON('/api/admin/login', { method: 'POST', body: { password: $('password').value } }); $('password').value = ''; show('panel'); loadRounds(); } catch (ex) { err.textContent = ex.message; err.classList.add('show'); } }); $('logout-btn').addEventListener('click', async () => { await fetchJSON('/api/admin/logout', { method: 'POST' }).catch(() => {}); show('login'); }); // ---------- rondas ---------- $('round-year').value = new Date().getFullYear() + 1; $('create-form').addEventListener('submit', async (e) => { e.preventDefault(); const err = $('create-error'); err.classList.remove('show'); try { const roles = $('round-roles').value.split(',').map((s) => s.trim()).filter(Boolean); const { round } = await fetchJSON('/api/admin/rounds', { method: 'POST', body: { name: $('round-name').value, year: Number($('round-year').value), roles }, }); $('round-name').value = ''; await copyUrl(round.token, false); toast('✓ Ronda creada y URL copiada'); loadRounds(); } catch (ex) { err.textContent = ex.message; err.classList.add('show'); } }); const roundUrl = (token) => `${location.origin}/r/${token}`; async function copyUrl(token, notify = true) { try { await navigator.clipboard.writeText(roundUrl(token)); if (notify) toast('✓ URL copiada al portapapeles'); } catch { if (notify) toast('No se pudo copiar; mantén pulsado el enlace', true); } } async function loadRounds() { const list = $('rounds-list'); let rounds; try { ({ rounds } = await fetchJSON('/api/admin/rounds')); } catch { show('login'); return; } list.innerHTML = ''; if (!rounds.length) { list.innerHTML = '
Todavía no hay rondas. Crea la primera arriba.
'; return; } for (const r of rounds) { const open = r.status === 'open'; const item = document.createElement('div'); item.className = 'round-item'; item.innerHTML = ` `; item.querySelector('[data-act=copy]').addEventListener('click', () => copyUrl(r.token)); item.querySelector('[data-act=status]').addEventListener('click', async () => { if (open && !confirm(`¿Cerrar la ronda "${r.name} ${r.year}"? Nadie podrá pedir ni modificar días.`)) return; try { await fetchJSON(`/api/admin/rounds/${r.id}/status`, { method: 'POST', body: { status: open ? 'closed' : 'open' }, }); toast(open ? 'Ronda cerrada' : 'Ronda reabierta'); loadRounds(); } catch (ex) { toast(ex.message, true); } }); list.appendChild(item); } } const esc = (s) => s.replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); // ---------- arranque ---------- fetchJSON('/api/admin/me') .then(() => { show('panel'); loadRounds(); }) .catch(() => show('login')); })();