/* Página del trabajador: registro + calendario de la ronda. */ (() => { 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 selected = new Set(); // fechas elegidas (con cambios sin guardar) let savedDates = new Set(); // ---------- 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')}`; // ---------- 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 ---------- 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); const today = new Date(); const todayIso = iso(today.getFullYear(), today.getMonth(), today.getDate()); 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 = `

${MONTHS[m]} ${round.year}

` + `
${DOW.map((d) => `${d}`).join('')}
`; const days = document.createElement('div'); days.className = 'days'; const first = new Date(round.year, m, 1); const blanks = (first.getDay() + 6) % 7; // semana empieza en lunes for (let i = 0; i < blanks; i++) days.appendChild(document.createElement('span')); const total = new Date(round.year, m + 1, 0).getDate(); let monthHasCounts = false; for (let d = 1; d <= total; d++) { const date = iso(round.year, m, d); const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'day'; btn.dataset.date = date; btn.textContent = d; if (date < todayIso) btn.classList.add('past'); if (state.counts[date]?.[me.role]) monthHasCounts = true; if (!open) btn.disabled = true; else btn.addEventListener('click', () => toggle(date, btn)); days.appendChild(btn); } month.appendChild(days); // Minileyenda: solo si este mes tiene días ya pedidos por compañeros de tu cargo. if (monthHasCounts) { const legend = document.createElement('p'); legend.className = 'month-legend'; legend.innerHTML = 'N compañeros de tu mismo cargo que ya han pedido ese día'; month.appendChild(legend); } cal.appendChild(month); } paint(); } function toggle(date, btn) { if (selected.has(date)) selected.delete(date); else selected.add(date); btn.classList.toggle('selected', selected.has(date)); updateBars(); } // Pinta selección y contadores de compañeros del mismo cargo. function paint() { const role = state.me.role; document.querySelectorAll('.day').forEach((btn) => { const date = btn.dataset.date; btn.classList.toggle('selected', selected.has(date)); btn.querySelector('.count')?.remove(); const n = state.counts[date]?.[role]; if (n) { const b = document.createElement('span'); b.className = 'count'; b.textContent = n; btn.appendChild(b); } }); updateBars(); } function updateBars() { $('me-count').textContent = selected.size; const dirty = selected.size !== savedDates.size || [...selected].some((d) => !savedDates.has(d)); const bar = $('savebar'); bar.classList.toggle('show', dirty && state.round.status === 'open'); $('save-label').textContent = selected.size === 1 ? '1 día elegido' : `${selected.size} días elegidos`; } $('save-btn').addEventListener('click', async () => { const btn = $('save-btn'); btn.disabled = true; try { const data = await fetchJSON(api('/requests'), { method: 'PUT', body: { dates: [...selected] }, }); savedDates = new Set(data.dates); selected = new Set(data.dates); state.counts = data.counts; paint(); toast('✓ Petición guardada'); } catch (ex) { toast(ex.message, true); } finally { btn.disabled = false; } }); // ---------- carga inicial ---------- async function load() { try { state = await fetchJSON(api('')); } catch { $('round-title').textContent = 'Ronda no encontrada'; return; } $('round-title').innerHTML = `${state.round.name} ${state.round.year}`; $('round-sub').textContent = state.me ? 'Marca en el calendario los días que quieres pedir' : 'Pide tus días de vacaciones en un minuto'; document.title = `Vacaciones · ${state.round.name} ${state.round.year}`; if (state.me) { savedDates = new Set(state.me.dates); selected = new Set(state.me.dates); renderCalendar(); } else if (state.round.status !== 'open') { $('closed-banner').classList.remove('hidden'); $('round-sub').textContent = 'Esta ronda ya no admite peticiones.'; } else { renderJoin(); } } load(); })();