Add self-hosted kids calendar app
Express + SQLite (node:sqlite, no native build step) family calendar: parent accounts with household invites, per-child calendars with save/duplicate/print, a token-gated read-only kiosk view for tablets, and polling to keep parent and kiosk views in sync. Defaults to port 3007.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
const token = window.location.pathname.split('/').filter(Boolean).pop();
|
||||
|
||||
const weekdayBoard = document.getElementById('weekdayBoard');
|
||||
const weekendWrap = document.getElementById('weekendWrap');
|
||||
|
||||
async function apiGet(path) {
|
||||
const res = await fetch(path, { credentials: 'omit' });
|
||||
if (!res.ok) throw new Error('request failed');
|
||||
return res.json();
|
||||
}
|
||||
async function apiPatch(path, body) {
|
||||
const res = await fetch(path, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!res.ok) throw new Error('request failed');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
const handlers = {
|
||||
onToggleDone: (taskId, done) => apiPatch(`/api/kiosk/${token}/tasks/${taskId}`, { done }).catch(() => {}),
|
||||
};
|
||||
|
||||
function todayName() {
|
||||
return new Date().toLocaleDateString('en-US', { weekday: 'long' });
|
||||
}
|
||||
|
||||
function draw(calendar) {
|
||||
document.getElementById('childName').textContent = calendar.childName || '';
|
||||
document.getElementById('calTitle').textContent = calendar.title || '';
|
||||
|
||||
renderCalendar({
|
||||
calendar,
|
||||
editable: false,
|
||||
weekdayBoard,
|
||||
weekendWrap,
|
||||
handlers,
|
||||
todayName: todayName(),
|
||||
});
|
||||
}
|
||||
|
||||
async function poll() {
|
||||
try {
|
||||
const data = await apiGet(`/api/kiosk/${token}/calendar`);
|
||||
if (!data.calendar) {
|
||||
document.getElementById('emptyState').style.display = 'block';
|
||||
document.getElementById('childName').textContent = data.child ? data.child.name : '';
|
||||
return;
|
||||
}
|
||||
document.getElementById('emptyState').style.display = 'none';
|
||||
draw(data.calendar);
|
||||
} catch {
|
||||
// transient network error — try again next tick
|
||||
}
|
||||
}
|
||||
|
||||
poll();
|
||||
setInterval(poll, 4000);
|
||||
Reference in New Issue
Block a user