/// import { action, computed, Default, equals, NAME, navigateTo, pattern, Stream, UI, type VNode, Writable, } from "commontools"; import EventDetail, { type EventPiece } from "./event-detail.tsx"; // Re-export for consumers and tests export type { EventPiece }; interface CalendarInput { events?: Writable>; } interface CalendarOutput { [NAME]: string; [UI]: VNode; events: EventPiece[]; sortedEvents: EventPiece[]; mentionable: EventPiece[]; todayDate: string; summary: string; addEvent: Stream<{ title: string; date: string; time: string }>; removeEvent: Stream<{ event: EventPiece }>; } const getTodayDate = (): string => { const now = new Date(); return now.toISOString().split("T")[0]; }; const formatDate = (date: string): string => { if (!date) return ""; const d = new Date(date + "T00:00:00"); return d.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric", }); }; const isToday = (date: string): boolean => date === getTodayDate(); const isPast = (date: string): boolean => date < getTodayDate(); export default pattern(({ events }) => { const todayDate = getTodayDate(); const newTitle = Writable.of(""); const newDate = Writable.of(todayDate); const newTime = Writable.of(""); const eventCount = computed(() => events.get().length); const sortedEvents = computed((): EventPiece[] => { const all = events.get(); if (!Array.isArray(all)) return []; return [...all].sort((a, b) => { const aDate = a.date; const bDate = b.date; if (aDate !== bDate) return aDate.localeCompare(bDate); return (a.time || "").localeCompare(b.time || ""); }); }); const summary = computed(() => { const sorted = sortedEvents; return sorted .map((e) => `${e.date} ${e.time || ""} ${e.title}`.trim()) .join(", "); }); const addEvent = action( ({ title, date, time }: { title: string; date: string; time: string }) => { const trimmed = title.trim(); if (trimmed && date) { events.push(EventDetail({ title: trimmed, date, time })); newTitle.set(""); newTime.set(""); } }, ); const removeEvent = action(({ event }: { event: EventPiece }) => { const current = events.get(); const idx = current.findIndex((e) => equals(event, e)); if (idx >= 0) { events.set(current.toSpliced(idx, 1)); } }); return { [NAME]: "Calendar", [UI]: ( Calendar ({eventCount}) {todayDate} {computed(() => { const sorted = sortedEvents; if (sorted.length === 0) { return (
No events yet. Add one below!
); } let lastDate = ""; return sorted.flatMap((event) => { const date = event.date; const items = []; if (date !== lastDate) { lastDate = date; const dateIsToday = isToday(date); const dateIsPast = isPast(date); items.push( {formatDate(date)} {dateIsToday ? ( Today ) : null} , ); } items.push( {event.time && ( {event.time} )} {event.title || "(untitled)"} {event.notes && ( {event.notes} )} navigateTo(event)} > Edit removeEvent.send({ event })} > × , ); return items; }); })}
addEvent.send({ title: newTitle.get(), date: newDate.get(), time: newTime.get(), })} > Add
), events, sortedEvents, mentionable: events, todayDate, summary, addEvent, removeEvent, }; });