import type { ResetTiming } from "../../shared/time.ts"; import { CATCH_UP_DAYS, catchUpDays, dailySummary, msUntilReset, type DailySummary, } from "../../shared/daily.ts"; import type { LaneId } from "../../shared/custom.ts"; import { DayPip } from "./DayPip.tsx"; import { goalCaption, type GoalStatus } from "../../shared/goal.ts"; import type { Region } from "../../shared/schema.ts"; import { formatRemaining } from "../../shared/time.ts"; /** * The checklist for an event you have to come back to every day. * * A single "done" tick is the wrong shape for these: the job is not finished * or unfinished, it is finished *today*, and yesterday's is gone whatever you * do now. So this shows the whole run as a strip of days — what you got, what * you missed, and how many chances are left — with today's tick as the one * prominent control. * * Past days stay clickable on purpose. People log in and tick up later, and a * checklist that cannot be corrected stops being trusted after the first * mistake. */ export function DailyChecklist({ startsMs, endsMs, region, game, now, logged, onToggleDay, goal, timing, }: { timing?: ResetTiming | undefined; startsMs: number; endsMs: number | null; region: Region; /** Whose reset clock the days are counted on — not every game shares one. */ game: LaneId; now: number; logged: string[]; onToggleDay: (day: string) => void; /** * The reader's check-in goal, when set. The caption then counts against it * instead of reporting missed days: an event that never wanted every day has * not been failed on the ones left unticked. */ goal?: GoalStatus | undefined; }) { const summary = dailySummary({ startsMs, endsMs, region, game, now, logged, ...(timing === undefined ? {} : { timing }) }); const { days, today, doneToday, todayInWindow } = summary; const started = now >= startsMs; return (

Daily checklist

resets in {formatRemaining(msUntilReset(now, region, game, timing))}

{days === null ? ( <> {/* No published end means no run to draw, but the days already gone are just as claimable as an announced event's — and until this existed they were unreachable, so a reader who forgot to tick yesterday could never say so. Only the past, and only as far back as `catchUpDays` goes. */}
{catchUpDays(now, region, game, startsMs, undefined, timing).map((day) => ( onToggleDay(day)} /> ))}
{goal !== undefined && (

{goalCaption(goal)}

)}

The source hasn't announced an end date, so how many days are left is unknown. Your ticks are still counted — {summary.logged} so far, and the last {CATCH_UP_DAYS} days are above if you did one and forgot to say.

) : ( <>
{days.map((day) => ( onToggleDay(day)} /> ))}

{goal !== undefined ? goalCaption(goal) : caption(summary, todayInWindow)}

)}
); } function caption(summary: DailySummary, todayInWindow: boolean): string { const { days, logged, remaining, missed } = summary; if (days === null || remaining === null) return `${logged} days ticked off.`; const parts = [`${logged} of ${days.length} days`]; if (remaining > 0) { parts.push( todayInWindow ? `${remaining} left including today` : `${remaining} to come`, ); } else { parts.push("the run is over"); } if (missed !== null && missed > 0) parts.push(`${missed} missed`); return `${parts.join(" · ")}.`; }