aboutsummaryrefslogtreecommitdiff
path: root/src/lib/shared/routine-metrics.ts
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-10-05 07:37:35 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-10-05 07:37:35 +0200
commit0bab369a6bc6a64bfd8fedd577e69eb27bfb1178 (patch)
treeac63289164efb0a9bd68097a5ec541b404825a2b /src/lib/shared/routine-metrics.ts
parenta725d4c7d28fee701b6997cc46cffddefbcd26bf (diff)
Show time since done or lateness instead of the last-done date
Diffstat (limited to 'src/lib/shared/routine-metrics.ts')
-rw-r--r--src/lib/shared/routine-metrics.ts27
1 files changed, 27 insertions, 0 deletions
diff --git a/src/lib/shared/routine-metrics.ts b/src/lib/shared/routine-metrics.ts
index d3c1470..4417539 100644
--- a/src/lib/shared/routine-metrics.ts
+++ b/src/lib/shared/routine-metrics.ts
@@ -38,6 +38,15 @@ function maxInstant(instants: Temporal.Instant[]): Temporal.Instant | null {
);
}
+/** Whole UTC-calendar days from an instant to now, floored at 0. */
+function daysAgo(then: Temporal.Instant, now: Temporal.Instant): number {
+ const days = then
+ .toZonedDateTimeISO(UTC)
+ .toPlainDate()
+ .until(now.toZonedDateTimeISO(UTC).toPlainDate()).days;
+ return Math.max(0, days);
+}
+
function parseCompletions(
routineId: number,
completions: CompletionInput[],
@@ -75,6 +84,8 @@ export interface RoutineState {
/** Spec text, as stored. */
frequency: string;
lastDoneAt: string | null;
+ /** Whole UTC-calendar days since lastDoneAt (null when never done). */
+ daysSinceDone: number | null;
/** When the pending obligation must be done (window end for quotas). */
dueAt: string;
/** Days past dueAt (0 while the deadline is still ahead). */
@@ -131,6 +142,7 @@ export function computeRoutineState(
name: routine.name,
frequency: routine.frequency,
lastDoneAt: lastDone !== null ? iso(lastDone) : null,
+ daysSinceDone: lastDone !== null ? daysAgo(lastDone, now) : null,
dueAt: iso(due),
overdueDays: Math.floor(overdueRatio * days),
overdueRatio,
@@ -223,6 +235,7 @@ function quotaState(
name: routine.name,
frequency: routine.frequency,
lastDoneAt: lastDone !== null ? iso(lastDone) : null,
+ daysSinceDone: lastDone !== null ? daysAgo(lastDone, now) : null,
dueAt: iso(due),
overdueDays: Math.max(
0,
@@ -396,3 +409,17 @@ export const STATUS_LABELS: Record<RoutineStatus, string> = {
late: 'En retard',
veryLate: 'Très en retard',
};
+
+/** Meta stamp next to the frequency: lateness past due, else time since done. */
+export function formatDoneAge(
+ state: Pick<RoutineState, 'daysSinceDone' | 'overdueDays'>,
+): string {
+ if (state.overdueDays > 0) {
+ const n = state.overdueDays;
+ return `${n} jour${n > 1 ? 's' : ''} de retard`;
+ }
+ if (state.daysSinceDone === null) return 'jamais fait';
+ if (state.daysSinceDone === 0) return "fait aujourd'hui";
+ if (state.daysSinceDone === 1) return 'fait hier';
+ return `fait il y a ${state.daysSinceDone} jours`;
+}