Repeats, estimates, weekly review: completing a repeating task spawns the next occurrence; estimate chips + quick-add tokens (~30m, monthly); review screen with one-tap triage; server gains repeat column and applies its idempotent schema at boot
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
51
app/src/lib/dates.ts
Normal file
51
app/src/lib/dates.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
// Date helpers for human-friendly display. All task dates are local ISO
|
||||
// "YYYY-MM-DD" strings; comparisons are lexicographic.
|
||||
|
||||
const DAY_MS = 86_400_000;
|
||||
|
||||
export function todayIso(): string {
|
||||
const d = new Date();
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
export function fromIso(s: string): Date {
|
||||
const [y, m, d] = s.split("-").map(Number);
|
||||
return new Date(y, m - 1, d);
|
||||
}
|
||||
|
||||
export function toIso(d: Date): string {
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
/** Deferred = hidden from action lists until the date arrives. */
|
||||
export function isDeferred(deferDate: string | null, today = todayIso()): boolean {
|
||||
return !!deferDate && deferDate > today;
|
||||
}
|
||||
|
||||
export function daysFromToday(dateIso: string): number {
|
||||
return Math.round((fromIso(dateIso).getTime() - fromIso(todayIso()).getTime()) / DAY_MS);
|
||||
}
|
||||
|
||||
/** "Today" / "Tomorrow" / "Mon 18 Aug" — no ISO date math for the reader. */
|
||||
export function friendlyDate(dateIso: string): string {
|
||||
const diff = daysFromToday(dateIso);
|
||||
if (diff === 0) return "Today";
|
||||
if (diff === 1) return "Tomorrow";
|
||||
if (diff === -1) return "Yesterday";
|
||||
const d = fromIso(dateIso);
|
||||
const wd = d.toLocaleDateString("en-AU", { weekday: "short" });
|
||||
const mo = d.toLocaleDateString("en-AU", { month: "short" });
|
||||
const yr = d.getFullYear() !== new Date().getFullYear() ? ` ${d.getFullYear()}` : "";
|
||||
return `${wd} ${d.getDate()} ${mo}${yr}`;
|
||||
}
|
||||
|
||||
export type DueTone = "overdue" | "today" | "later";
|
||||
|
||||
/** Row label for a due date, with a tone the UI can color. */
|
||||
export function dueMeta(dueIso: string): { label: string; tone: DueTone } {
|
||||
const diff = daysFromToday(dueIso);
|
||||
if (diff < 0) return { label: diff === -1 ? "1d overdue" : `${-diff}d overdue`, tone: "overdue" };
|
||||
if (diff === 0) return { label: "due today", tone: "today" };
|
||||
if (diff === 1) return { label: "due tomorrow", tone: "later" };
|
||||
return { label: `due ${friendlyDate(dueIso)}`, tone: "later" };
|
||||
}
|
||||
Reference in New Issue
Block a user