const BLOCK_ICONS = { morning: '🌅', school: '🎒', after: '⚽', evening: '🌙' }; const WEEKDAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; const WEEKEND = ['Saturday', 'Sunday']; function debounce(fn, ms) { let t; return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); }; } // Renders the weekly board. `editable` gates every mutation affordance // (contenteditable, delete buttons, add-task buttons) — the kiosk view passes // editable:false and gets a board with nothing but working checkboxes. function renderCalendar({ calendar, editable, weekdayBoard, weekendWrap, handlers, todayName }) { weekdayBoard.innerHTML = ''; weekendWrap.innerHTML = ''; const currentLabels = {}; calendar.blocks.forEach((b) => { currentLabels[b.key] = b.label; }); function syncLabel(key, sourceEl) { document.querySelectorAll(`.label[data-block="${key}"]`).forEach((el) => { if (el !== sourceEl) el.textContent = sourceEl.textContent; }); document.querySelectorAll(`.legend-label[data-block="${key}"]`).forEach((el) => { el.textContent = sourceEl.textContent; }); } function makeTask(dayName, blockKey, task) { const row = document.createElement('div'); row.className = 'task' + (task.done ? ' done' : '') + (editable ? '' : ' kiosk'); row.dataset.taskId = task.id; const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = task.done; cb.onchange = () => { row.classList.toggle('done', cb.checked); handlers.onToggleDone(task.id, cb.checked); }; const span = document.createElement('div'); span.className = 'task-text'; span.textContent = task.text; row.appendChild(cb); row.appendChild(span); if (editable) { span.contentEditable = 'true'; span.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); span.blur(); } }); const debouncedEdit = debounce(() => { if (span.textContent.trim()) handlers.onTextEdit(task.id, span.textContent.trim()); }, 500); span.addEventListener('input', debouncedEdit); span.addEventListener('blur', () => { if (!span.textContent.trim()) span.textContent = task.text; }); const del = document.createElement('button'); del.className = 'del'; del.textContent = '✕'; del.onclick = () => { row.remove(); handlers.onDeleteTask(task.id); }; row.appendChild(del); } return row; } function makeDayCard(dayName) { const card = document.createElement('div'); card.className = 'day-card' + (dayName === todayName ? ' today' : ''); const head = document.createElement('div'); head.className = 'day-head'; head.innerHTML = `
${dayName}
` + (dayName === todayName ? `
TODAY
` : ''); card.appendChild(head); calendar.blocks.forEach((block) => { const blockEl = document.createElement('div'); blockEl.className = 'block ' + block.key; const title = document.createElement('div'); title.className = 'block-title'; const iconSpan = document.createElement('span'); iconSpan.className = 'icon'; iconSpan.textContent = BLOCK_ICONS[block.key] || ''; const labelSpan = document.createElement('span'); labelSpan.className = 'label'; labelSpan.dataset.block = block.key; labelSpan.textContent = currentLabels[block.key]; if (editable) { labelSpan.contentEditable = 'true'; labelSpan.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); labelSpan.blur(); } }); const debouncedLabel = debounce(() => { if (labelSpan.textContent.trim()) { currentLabels[block.key] = labelSpan.textContent.trim(); handlers.onLabelEdit(block.key, labelSpan.textContent.trim()); } }, 500); labelSpan.addEventListener('input', () => { syncLabel(block.key, labelSpan); debouncedLabel(); }); } title.appendChild(iconSpan); title.appendChild(labelSpan); blockEl.appendChild(title); const list = document.createElement('div'); list.className = 'task-list'; (calendar.days[dayName][block.key] || []).forEach((task) => { list.appendChild(makeTask(dayName, block.key, task)); }); blockEl.appendChild(list); if (editable) { const addBtn = document.createElement('button'); addBtn.className = 'add-task'; addBtn.textContent = '+ add task'; addBtn.onclick = async () => { const newTask = await handlers.onAddTask(dayName, block.key, 'New task'); const row = makeTask(dayName, block.key, newTask); list.appendChild(row); const span = row.querySelector('.task-text'); span.focus(); document.execCommand('selectAll', false, null); }; blockEl.appendChild(addBtn); } card.appendChild(blockEl); }); return card; } WEEKDAYS.forEach((d) => weekdayBoard.appendChild(makeDayCard(d))); WEEKEND.forEach((d) => weekendWrap.appendChild(makeDayCard(d))); return { currentLabels }; } function renderLegend(legendEl, calendar) { legendEl.innerHTML = ''; calendar.blocks.forEach((block) => { const item = document.createElement('span'); item.className = 'item'; item.innerHTML = `` + `${block.label}`; legendEl.appendChild(item); }); }