'use strict';
// ---- Family: one start page for the household: the announcement, today's events, chores, the shopping list, shared notes, chats and tasks. Each card loads on its own and
// only shows if the person has that part of the hub, so one slow or missing piece never blanks the page.
const fmCard = (id, title, href, sub) => ` Loading…${esc(title)}
${href ? `${esc(sub || 'Open')}` : ''}
${esc(e && e.message || 'Could not load this')}
`; }; function viewFamily() { const h = new Date().getHours(), greet = h < 5 ? 'Good night' : h < 12 ? 'Good morning' : h < 18 ? 'Good afternoon' : 'Good evening', today = new Date().toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric' }); const cards = [['cal', allowed('calendar'), fmCard('fmcal', 'Today and tomorrow', '#/calendar', 'Calendar')], ['chores', allowed('chores'), fmCard('fmch', 'Chores today', '#/chores')], ['shop', allowed('recipes'), fmCard('fmsh', 'Shopping list', '#/recipes/shop')], ['notes', allowed('notes'), fmCard('fmnt', 'Family notes and lists', '#/notes')], ['chat', allowed('chat'), fmCard('fmct', 'Chat', '#/chat')], ['tasks', allowed('tasks'), fmCard('fmtk', 'Your tasks', '#/tasks')]].filter(c => c[1]); $('#view').innerHTML = `${today}
Nothing here yet. An admin can turn on Calendar, Chores, Notes, Chat and more for you.
'}Nothing left for you today. 🎉
'} ${per.length ? `The shopping list is not set up yet.
'; return; } const l = await jfetch('/api/recipes/shopping'), b = fmBody('fmsh'); if (!b) return; const open = l.items.filter(i => !i.checked); b.innerHTML = `${open.slice(0, 8).map(i => ``).join('') || 'Nothing to buy.
'}${open.length > 8 ? `+ ${open.length - 8} more` : ''} `; b.querySelectorAll('input[data-id]').forEach(c => c.onchange = async () => { try { await jfetch('/api/recipes/shopping/check', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id: c.dataset.id, checked: true }) }); fmShopping(); } catch (e) { toast(e.message); c.checked = false; } }); b.querySelector('#fmshf').onsubmit = async e => { e.preventDefault(); const i = b.querySelector('#fmshi'), t = i.value.trim(); if (!t) return; try { await jfetch('/api/recipes/shopping/add', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text: t }) }); fmShopping(); } catch (x) { toast(x.message); } }; } async function fmNotes() { const r = await jfetch('/api/notes'), b = fmBody('fmnt'); if (!b) return; const l = r.notes.filter(n => !n.archived && n.share !== 'me').slice(0, 4); b.innerHTML = l.map(n => `${esc(n.title || (n.type === 'list' ? 'Checklist' : 'Note'))}${n.pinned ? ' 📌' : ''}${n.type === 'list' ? ntDone(n) + ' of ' + n.items.length + ' done' + (n.items.find(i => !i.done) ? ' · next: ' + esc(n.items.find(i => !i.done).text) : '') : esc(n.body.slice(0, 70))}`).join('') || 'No shared notes yet.
'; } async function fmChat() { const r = await jfetch('/api/chat/convs'), b = fmBody('fmct'); if (!b) return; const l = r.convs.filter(c => !c.archived).slice(0, 4); b.innerHTML = l.map(c => `${c.type === 'group' ? icon('chat') : esc(sgInitial(c.name))}${esc(c.name)}${esc(c.preview || '')}${c.unread ? `${c.unread}` : ''}`).join('') || 'No chats yet.
'; } async function fmTasks() { const r = await jfetch('/api/tasks/upcoming?today=' + crYmd(new Date())), b = fmBody('fmtk'); if (!b) return; b.innerHTML = r.tasks.slice(0, 5).map(t => `${esc(t.title)}${t.due === crYmd(new Date()) ? 'Today' : esc(t.due)}`).join('') || 'Nothing due this week. 🎉
'; }