/* Página del trabajador: registro + calendario de la ronda con estados. */ (() => { const token = location.pathname.split('/').pop(); const api = (p) => `/api/round/${token}${p}`; const $ = (id) => document.getElementById(id); const MONTHS = ['Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre']; const DOW = ['L','M','X','J','V','S','D']; let state = null; // respuesta del servidor let todayIso = ''; // hoy según el servidor (zona de España) let myStatus = new Map(); // fecha → 'approved' | 'rejected' | 'pending' (guardado) let savedPending = new Set();// pendientes ya guardadas en el servidor let pending = new Set(); // pendientes en edición (con cambios sin guardar) // ---------- utilidades ---------- 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 && { ...opts, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(opts.body), }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error || 'Error de conexión'); return data; }; const iso = (y, m, d) => `${y}-${String(m + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`; // Reconstruye el estado local a partir de las solicitudes del servidor. function setRequests(requests) { myStatus = new Map(requests.map((r) => [r.date, r.status])); savedPending = new Set(requests.filter((r) => r.status === 'pending').map((r) => r.date)); pending = new Set(savedPending); } // ---------- registro ---------- function renderJoin() { $('join-card').classList.remove('hidden'); const grid = $('role-grid'); grid.innerHTML = ''; for (const [value, label] of Object.entries(state.roles)) { const opt = document.createElement('label'); opt.className = 'role-option'; opt.innerHTML = `${label}`; grid.appendChild(opt); } $('join-form').addEventListener('submit', async (e) => { e.preventDefault(); const err = $('join-error'); err.classList.remove('show'); try { const role = document.querySelector('input[name=role]:checked')?.value; await fetchJSON(api('/join'), { method: 'POST', body: { name: $('name').value, role } }); await load(); } catch (ex) { err.textContent = ex.message; err.classList.add('show'); } }); } // ---------- calendario ---------- // Calcula cómo se ve y se comporta un día concreto para este trabajador. function dayMeta(date) { const role = state.me.role; const status = myStatus.get(date); const inPending = pending.has(date); const others = state.counts[date]?.[role] || 0; const limit = state.limits[role]; const full = limit ? others >= limit : false; const past = date <= todayIso; const open = state.round.status === 'open'; let cls = 'day'; let disabled = !open; let clickable = false; if (status === 'approved') { cls += ' approved'; disabled = true; } else if (status === 'rejected') { cls += ' rejected'; disabled = true; } else if (inPending && savedPending.has(date)) { cls += ' pending'; if (past) disabled = true; else clickable = !disabled; } else if (inPending) { cls += ' choosing'; clickable = !disabled; } else { // Día libre para este trabajador. if (past) { cls += ' past'; disabled = true; } else if (full) { cls += ' full'; disabled = true; } else clickable = !disabled; } return { cls, disabled, clickable, others, full }; } // Aplica el aspecto y el badge de compañeros a un botón de día. function styleDay(btn) { const date = btn.dataset.date; const m = dayMeta(date); btn.className = m.cls; btn.disabled = m.disabled; btn.querySelector('.count')?.remove(); if (m.others) { const b = document.createElement('span'); b.className = 'count' + (m.full ? ' is-full' : ''); b.textContent = m.others; btn.appendChild(b); } } function renderCalendar() { $('join-card').classList.add('hidden'); $('calendar-section').classList.remove('hidden'); const { me, round } = state; const open = round.status === 'open'; $('me-name').textContent = me.name; $('me-role').textContent = state.roles[me.role] || me.role; $('closed-banner').classList.toggle('hidden', open); $('calendar-hint').classList.toggle('hidden', !open); $('calendar-legend').classList.remove('hidden'); const cal = $('calendar'); cal.innerHTML = ''; for (let m = 0; m < 12; m++) { const month = document.createElement('section'); month.className = 'month'; month.style.animationDelay = `${m * 40}ms`; month.innerHTML = `