fix(vorsorge): key due status on the local calendar day

This commit is contained in:
Marc-André Bombeck committed 2026-06-20 12:53:41 +02:00
1 parent a1f5ed625c
commit 90a9407195
2 files changed
+74 -3

No files matched your search

@@ -136,6 +136,68 @@ describe("<VorsorgeSection> loading + empty", () => {
expect(html).not.toContain("Log value");
});
it("reads a same-day due time as 'today' (calendar-day delta, not rolling 24h)", () => {
// v1.18.9 recon — a reminder whose nextDue falls on the SAME local
// calendar day must read "Due today" with the green status hue, even when
// the wall-clock gap to now exceeds the 24h a rolling delta would key on.
// Anchor the due instant to local noon today so the floored calendar-day
// delta is 0 regardless of the hour the suite runs.
const todayNoon = new Date();
todayNoon.setHours(12, 0, 0, 0);
const reminder: MeasurementReminder = {
id: "due-today",
label: "Blood pressure check",
measurementType: "BLOOD_PRESSURE_SYS",
intervalDays: 7,
rrule: null,
anchorDate: null,
endsOn: null,
origin: "VORSORGE",
nextDueAt: todayNoon.toISOString(),
notifyHour: 9,
location: null,
lastSatisfiedAt: null,
enabled: true,
createdAt: "2030-01-01T00:00:00.000Z",
updatedAt: "2030-01-01T00:00:00.000Z",
} as MeasurementReminder;
remindersMock.mockReturnValue({ data: [reminder], isLoading: false });
const html = render(<VorsorgeSection />);
expect(html).toContain("Due today");
// The status hue is the discreet success colour, never a card-bg tint.
expect(html).toContain("text-success");
});
it("reads a prior-day due time as 'overdue' once the calendar day has passed", () => {
// A reminder whose due CALENDAR day is before today reads "Overdue", with
// the warning hue — a rolling-24h delta would mis-bucket a yesterday-evening
// due viewed this morning as "today".
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
yesterday.setHours(20, 0, 0, 0);
const reminder: MeasurementReminder = {
id: "overdue",
label: "Annual physical",
measurementType: null,
intervalDays: 365,
rrule: null,
anchorDate: null,
endsOn: null,
origin: "VORSORGE",
nextDueAt: yesterday.toISOString(),
notifyHour: 9,
location: null,
lastSatisfiedAt: null,
enabled: true,
createdAt: "2030-01-01T00:00:00.000Z",
updatedAt: "2030-01-01T00:00:00.000Z",
} as MeasurementReminder;
remindersMock.mockReturnValue({ data: [reminder], isLoading: false });
const html = render(<VorsorgeSection />);
expect(html).toContain("Overdue by 1 days");
expect(html).toContain("text-warning");
});
it("translates a COACH-origin label i18n key and shows the neutral badge", () => {
const reminder: MeasurementReminder = {
id: "c1",
@@ -40,6 +40,7 @@ import {
import { useFormatters, useTranslations } from "@/lib/i18n/context";
import { relativeCalendarDate } from "@/lib/i18n/relative-time";
import { startOfLocalDayInTz } from "@/lib/tz/local-day";
import { cn } from "@/lib/utils";
import { applyOrder, useModuleListPrefs } from "@/lib/module-list-prefs";
import { SettingsCardHeader } from "@/components/settings/_card-header";
@@ -124,9 +125,17 @@ function relativeDueKey(
now: number,
): { key: string; days: number } {
if (!nextDueAt) return { key: "nextDue.none", days: 0 };
const due = new Date(nextDueAt).getTime();
// Compare calendar-day deltas so "today" / "in 1 day" read cleanly.
const deltaDays = Math.round((due - now) / DAY_MS);
const due = new Date(nextDueAt);
if (Number.isNaN(due.getTime())) return { key: "nextDue.none", days: 0 };
// v1.18.9 (recon) — compare CALENDAR-day deltas in the user's local zone,
// not a rolling-24h delta. A reminder due at 09:00 viewed the same evening
// at 20:00 must still read "heute", and one whose due calendar-day is before
// today must read "überfällig" — a raw `(due - now) / DAY_MS` round would
// mis-bucket both. Floor each instant to its local day start (host-local zone
// = the browser user's zone for this client component) before differencing.
const dueDay = startOfLocalDayInTz(due, undefined).getTime();
const nowDay = startOfLocalDayInTz(new Date(now), undefined).getTime();
const deltaDays = Math.round((dueDay - nowDay) / DAY_MS);
if (deltaDays < 0) return { key: "overdueByDays", days: Math.abs(deltaDays) };
if (deltaDays === 0) return { key: "nextDue.today", days: 0 };
if (deltaDays === 1) return { key: "nextDue.tomorrow", days: 1 };