'use strict'; // ---- Settings > Family: the family's settings in one place (chat, your PIN, chores and rewards, who is on the family board, shared screens). // Each row opens the same sheet that used to live inside that part of the hub. const famRow = (id, ic, title, sub) => ``; async function famManageChores() { try { cr.data = await crApi('/?date=' + crYmd(new Date())); } catch (e) { return toast(e.message); } cr.afterEdit = famManageChores; crManage(); } async function famSettings(box) { if (!box) return; const admin = ME && ME.role === 'admin', can = t => typeof allowed === 'function' && allowed(t); box.innerHTML = `

You

${can('chat') ? famRow('fs-chat', 'chat', 'Chat', 'Quiet hours for phone alerts' + (admin ? ', and the family announcement' : '')) : ''}
${admin && can('chores') ? `

Chores

${famRow('fs-chores', 'check', 'Chores and weekly rewards', 'Make chores, choose who does them, set the points and the reward tiers')}
` : ''} ${admin ? '

Children

What each child did this week, and Plex.

Loading…
' + famRow('fs-words', 'search', 'Blocked search words', 'A child\'s search only uses Simple English Wikipedia and a built-in list of blocked words. Add your own words here.') + '
' : ''} ${admin ? '

Family board

Who has a name tile on the shared family screen and shows in its chore progress.

Loading…
' : ''}`; const c = box.querySelector('#fs-chat'); if (c) c.onclick = () => chSettings(); const k = box.querySelector('#fs-chores'); if (k) k.onclick = famManageChores; if (typeof devSettings === 'function') devSettings(box.querySelector('#fs-dev')); if (admin) { famRequests(box.querySelector('#fs-req')); famKids(box.querySelector('#fs-kids')); const w = box.querySelector('#fs-words'); if (w) w.onclick = famWords; } if (admin) try { const r = await jfetch('/api/users'), b = box.querySelector('#fs-board'); if (!b) return; b.innerHTML = r.users.filter(u => !u.disabled).map(u => ``).join(''); b.querySelectorAll('input[data-u]').forEach(i => i.onchange = async () => { try { await jfetch('/api/users/' + encodeURIComponent(i.dataset.u), { method: 'PUT', body: JSON.stringify({ board: i.checked }) }); } catch (e) { toast(e.message); i.checked = !i.checked; } }); } catch (e) { const b = box.querySelector('#fs-board'); if (b) b.innerHTML = `
${esc(e.message)}
`; } } // ---- Requests from children: what they asked for, with Yes / No. async function famRequests(box) { if (!box) return; let r; try { r = await jfetch('/api/kid/requests'); } catch { box.innerHTML = ''; return; } const open = r.requests.filter(x => x.status === 'open'); if (!open.length) { box.innerHTML = ''; return; } const when = t => new Date(t).toLocaleString([], { weekday: 'short', hour: 'numeric', minute: '2-digit' }); box.innerHTML = `

Asking you (${open.length})

${open.map(q => `
${esc(q.userName)}: ${esc(q.text)}${when(q.at)}${q.tab ? ' · wants ' + esc((TABS.find(t => t[0] === q.tab) || [0, q.tab])[1]) : ''}${q.minutes ? ' · +' + q.minutes + ' min' : ''}${q.time ? [15, 30, 60].map(m => ``).join('') : ``}
`).join('')}
`; box.querySelectorAll('[data-q]').forEach(b => b.onclick = async () => { const yes = !!b.dataset.y; try { await jfetch('/api/kid/requests/' + b.dataset.q, { method: 'POST', body: JSON.stringify({ approve: yes, minutes: b.dataset.m ? +b.dataset.m : undefined }) }); toast(yes ? 'Said yes' : 'Said no'); famRequests(box); } catch (e) { toast(e.message); } }); } // ---- One row per child: their week (minutes by area, what they played, searched and tried), and Plex locking. async function famKids(box) { if (!box) return; let r; try { r = await jfetch('/api/users'); } catch (e) { box.innerHTML = `
${esc(e.message)}
`; return; } const kids = r.users.filter(u => u.kid && u.kid.on && !u.disabled); box.innerHTML = kids.map(u => ``).join('') || '
No child accounts yet. Turn on This is a child for a person under Admin → People.
'; box.querySelectorAll('[data-k]').forEach(b => b.onclick = () => famChild(kids.find(u => u.id === b.dataset.k))); } async function famChild(u) { const el = openSheet(u.name, 'Last 7 days', '

Loading…

'); let a, px; try { [a, px] = await Promise.all([jfetch('/api/kid/activity?user=' + encodeURIComponent(u.id) + '&days=7'), jfetch('/api/kid/plex-lock?user=' + encodeURIComponent(u.id))]); } catch (e) { el.querySelector('#fcb').innerHTML = `

${esc(e.message)}

`; return; } const h = m => m >= 60 ? Math.floor(m / 60) + ' h ' + (m % 60) + ' min' : m + ' min', total = a.days.reduce((n, d) => n + d.minutes, 0), by = {}; a.days.forEach(d => Object.entries(d.areas).forEach(([k, m]) => { by[k] = (by[k] || 0) + m; })); const icn = { play: '🎵', watch: '🎬', listen: '🎧', read: '📖', search: '🔎', signin: '🔑', chore: '✅', add: '➕', blocked: '⛔' }, max = Math.max(1, ...a.days.map(d => d.minutes)); const day = d => new Date(d + 'T12:00:00').toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' }); el.querySelector('#fcb').innerHTML = `
${h(total)} in the app${Object.entries(by).filter(([k, m]) => m > 0 && a.labels[k]).map(([k, m]) => esc(a.labels[k]) + ' ' + h(m)).join(' · ') || 'No time recorded yet'}
${a.days.map(d => `
${esc(day(d.day))}
${h(d.minutes)}
`).join('')}

Plex

${px.connected ? (px.profile ? 'Using Plex as ' + esc(px.profile) : 'Connected to Plex') : 'Not connected to Plex'}${px.locked ? 'Locked to this profile: they cannot switch profiles or servers.' : 'They can switch Plex profiles if the other profile has no PIN. Lock it once you are on their managed profile.'}

What they did

${a.recent.map(e => `
${icn[e.kind] || '•'}${esc(e.title || e.kind)}${esc(new Date(e.t).toLocaleString([], { weekday: 'short', hour: 'numeric', minute: '2-digit' }))}
`).join('') || '

Nothing yet.

'}
`; el.querySelector('#fcpl').onclick = async () => { try { await jfetch('/api/kid/plex-lock', { method: 'POST', body: JSON.stringify({ user: u.id, locked: !px.locked }) }); toast(px.locked ? 'Unlocked' : 'Locked to this profile'); closeSheet(); famChild(u); } catch (e) { toast(e.message); } }; } // ---- The words a parent blocks from a child's search (on top of a built-in list). async function famWords() { let r; try { r = await jfetch('/api/kidsearch/words'); } catch (e) { return toast(e.message); } const el = openSheet('Blocked search words', 'A child searches Simple English Wikipedia only. Any search or result with one of these words (or a built-in blocked word) is hidden. One per line or separated by commas.', `
`); el.querySelector('#fwx').onclick = closeSheet; el.querySelector('#fwf').onsubmit = async e => { e.preventDefault(); try { await jfetch('/api/kidsearch/words', { method: 'POST', body: JSON.stringify({ words: el.querySelector('#fww').value }) }); closeSheet(); toast('Saved'); } catch (x) { toast(x.message); } }; }