diff --git a/messages/de.json b/messages/de.json index 988c66016..3e1780f8d 100644 --- a/messages/de.json +++ b/messages/de.json @@ -8527,6 +8527,7 @@ "inDays": "In {days} Tagen" }, "overdueByDays": "Überfällig seit {days} Tagen", + "overdueSinceYesterday": "Überfällig seit gestern", "cadence": { "everyNDays": "Alle {days} Tage", "custom": "Eigener Zeitplan", diff --git a/messages/en.json b/messages/en.json index 5fb158f6f..3e5801c35 100644 --- a/messages/en.json +++ b/messages/en.json @@ -8527,6 +8527,7 @@ "inDays": "In {days} days" }, "overdueByDays": "Overdue by {days} days", + "overdueSinceYesterday": "Overdue since yesterday", "cadence": { "everyNDays": "Every {days} days", "custom": "Custom schedule", diff --git a/messages/es.json b/messages/es.json index 519e3f10b..025be52d0 100644 --- a/messages/es.json +++ b/messages/es.json @@ -8527,6 +8527,7 @@ "inDays": "En {days} días" }, "overdueByDays": "Vencido hace {days} días", + "overdueSinceYesterday": "Vencido desde ayer", "cadence": { "everyNDays": "Cada {days} días", "custom": "Programación personalizada", diff --git a/messages/fr.json b/messages/fr.json index e037193f9..9b79d330c 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -8527,6 +8527,7 @@ "inDays": "Dans {days} jours" }, "overdueByDays": "En retard de {days} jours", + "overdueSinceYesterday": "En retard depuis hier", "cadence": { "everyNDays": "Tous les {days} jours", "custom": "Planning personnalisé", diff --git a/messages/it.json b/messages/it.json index 60a3420d3..a44deebae 100644 --- a/messages/it.json +++ b/messages/it.json @@ -8527,6 +8527,7 @@ "inDays": "Tra {days} giorni" }, "overdueByDays": "In ritardo di {days} giorni", + "overdueSinceYesterday": "In ritardo da ieri", "cadence": { "everyNDays": "Ogni {days} giorni", "custom": "Pianificazione personalizzata", diff --git a/messages/ko.json b/messages/ko.json index 140a39081..789abfd44 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -8527,6 +8527,7 @@ "inDays": "{days}일 후" }, "overdueByDays": "{days}일 지남", + "overdueSinceYesterday": "어제부터 지남", "cadence": { "everyNDays": "{days}일마다", "custom": "사용자 지정 일정", diff --git a/messages/pl.json b/messages/pl.json index 5649bfd1d..dcea730ae 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -8527,6 +8527,7 @@ "inDays": "Za {days} dni" }, "overdueByDays": "Zaległe od {days} dni", + "overdueSinceYesterday": "Zaległe od wczoraj", "cadence": { "everyNDays": "Co {days} dni", "custom": "Własny harmonogram", diff --git a/src/__tests__/vorsorge-due-bucket-single-home-guard.test.ts b/src/__tests__/vorsorge-due-bucket-single-home-guard.test.ts index bc960b9cf..2e94964d1 100644 --- a/src/__tests__/vorsorge-due-bucket-single-home-guard.test.ts +++ b/src/__tests__/vorsorge-due-bucket-single-home-guard.test.ts @@ -52,6 +52,7 @@ const MINTED_ONLY_BY_THE_BUCKET = [ "measurementReminders.nextDue.tomorrow", "measurementReminders.nextDue.inDays", "measurementReminders.nextDue.none", + "measurementReminders.overdueSinceYesterday", ]; /** The hook that yields the zone the surrounding dates are printed in. */ diff --git a/src/components/measurement-reminders/__tests__/vorsorge-section.test.tsx b/src/components/measurement-reminders/__tests__/vorsorge-section.test.tsx index 9a36baad8..7bba97edb 100644 --- a/src/components/measurement-reminders/__tests__/vorsorge-section.test.tsx +++ b/src/components/measurement-reminders/__tests__/vorsorge-section.test.tsx @@ -275,7 +275,9 @@ describe(" loading + empty", () => { } as MeasurementReminder; remindersMock.mockReturnValue({ data: [reminder], isLoading: false }); const html = render(); - expect(html).toContain("Overdue by 1 days"); + // One day over reads "since yesterday": the counted form printed + // "Overdue by 1 days", and the warning hue must follow both phrases. + expect(html).toContain("Overdue since yesterday"); expect(html).toContain("text-warning"); }); diff --git a/src/components/measurement-reminders/vorsorge-section.tsx b/src/components/measurement-reminders/vorsorge-section.tsx index f3fc298ea..e211df9b2 100644 --- a/src/components/measurement-reminders/vorsorge-section.tsx +++ b/src/components/measurement-reminders/vorsorge-section.tsx @@ -47,7 +47,10 @@ import { useDisplayTimezone, } from "@/lib/i18n/context"; import { relativeCalendarDate } from "@/lib/i18n/relative-time"; -import { relativeDueKey } from "@/lib/measurement-reminders/due-day"; +import { + isOverdueDue, + relativeDueKey, +} from "@/lib/measurement-reminders/due-day"; import { cn } from "@/lib/utils"; import { applyOrder, useModuleListPrefs } from "@/lib/module-list-prefs"; import { EncounterSheet } from "@/components/encounters/encounter-sheet"; @@ -899,8 +902,7 @@ function VorsorgeCard({ // routes to the check-in page), never the numeric value-entry wording. const isScreening = isScreeningReminderType(reminder.measurementType); const isDue = - due.key === "measurementReminders.nextDue.today" || - due.key === "measurementReminders.overdueByDays"; + due.key === "measurementReminders.nextDue.today" || isOverdueDue(due); const progress = intervalProgress(reminder, now); // Category-style header badge: the measurement label, or "self-planned" @@ -1361,7 +1363,7 @@ function VorsorgeCard({ // normal muted-resolved date. The card surface stays neutral per the house // rule; only this inline label carries the status hue. const dueIsToday = due.key === "measurementReminders.nextDue.today"; - const dueIsOverdue = due.key === "measurementReminders.overdueByDays"; + const dueIsOverdue = isOverdueDue(due); const nextLastSlot = (
diff --git a/src/lib/measurement-reminders/__tests__/due-day.test.ts b/src/lib/measurement-reminders/__tests__/due-day.test.ts index 62fc66ade..9e09cfa67 100644 --- a/src/lib/measurement-reminders/__tests__/due-day.test.ts +++ b/src/lib/measurement-reminders/__tests__/due-day.test.ts @@ -16,7 +16,7 @@ */ import { describe, it, expect } from "vitest"; -import { relativeDueKey } from "../due-day"; +import { isOverdueDue, relativeDueKey } from "../due-day"; /** * The profile zone under test. July is deliberate: no IANA zone shifts its @@ -84,11 +84,33 @@ describe("relativeDueKey", () => { ).toEqual(TOMORROW); }); - it("counts a checkup from yesterday as one day overdue, however recent", () => { - // Forty-five minutes in the past. It was still yesterday's checkup. + it("calls a checkup from yesterday overdue since yesterday, however recent", () => { + // Forty-five minutes in the past. It was still yesterday's checkup. Its + // own phrase, because "overdue by 1 days" is what the counted form reads + // as on the one day an open check-up is most likely to be looked at. expect( relativeDueKey(zoned(FRIDAY, 23, 30), at(SATURDAY, 0, 15), ZONE), - ).toEqual({ key: "measurementReminders.overdueByDays", days: 1 }); + ).toEqual({ key: "measurementReminders.overdueSinceYesterday", days: 1 }); + }); + + it("counts the days for a checkup overdue longer than that", () => { + expect(relativeDueKey(zoned(FRIDAY, 9), at(FRIDAY + 2, 10), ZONE)).toEqual({ + key: "measurementReminders.overdueByDays", + days: 2, + }); + }); + + it("reads both overdue phrases as overdue, and nothing else", () => { + const now = at(SATURDAY, 12); + expect(isOverdueDue(relativeDueKey(zoned(FRIDAY, 9), now, ZONE))).toBe( + true, + ); + expect(isOverdueDue(relativeDueKey(zoned(FRIDAY - 3, 9), now, ZONE))).toBe( + true, + ); + expect(isOverdueDue(relativeDueKey(zoned(SATURDAY, 9), now, ZONE))).toBe( + false, + ); }); it("counts whole calendar days for a checkup further out", () => { diff --git a/src/lib/measurement-reminders/due-day.ts b/src/lib/measurement-reminders/due-day.ts index 5efa96ed1..8f8da3ee2 100644 --- a/src/lib/measurement-reminders/due-day.ts +++ b/src/lib/measurement-reminders/due-day.ts @@ -86,6 +86,11 @@ export function relativeDueKey( if (Number.isNaN(due.getTime())) return { key: "measurementReminders.nextDue.none", days: 0 }; const deltaDays = calendarDaysUntil(due, new Date(now), timeZone); + // Its own phrase rather than the counted one: "overdue by 1 days" is what + // the counted form reads as, on the one day an open check-up (which since + // v1.39.2 stays due after its reminder) is most likely to be looked at. + if (deltaDays === -1) + return { key: "measurementReminders.overdueSinceYesterday", days: 1 }; if (deltaDays < 0) return { key: "measurementReminders.overdueByDays", @@ -97,3 +102,15 @@ export function relativeDueKey( return { key: "measurementReminders.nextDue.tomorrow", days: 1 }; return { key: "measurementReminders.nextDue.inDays", days: deltaDays }; } + +/** + * Whether a phrase from {@link relativeDueKey} says "overdue". Two keys do — + * "since yesterday" and the counted one — so a surface that colours an + * overdue badge asks here rather than comparing against one of them. + */ +export function isOverdueDue(due: RelativeDue): boolean { + return ( + due.key === "measurementReminders.overdueByDays" || + due.key === "measurementReminders.overdueSinceYesterday" + ); +}