diff --git a/src/hooks/__tests__/use-measurement-reminders-refetch.test.ts b/src/hooks/__tests__/use-measurement-reminders-refetch.test.ts index 4c4f01db1..35d13d042 100644 --- a/src/hooks/__tests__/use-measurement-reminders-refetch.test.ts +++ b/src/hooks/__tests__/use-measurement-reminders-refetch.test.ts @@ -1,7 +1,11 @@ import { describe, it, expect, vi } from "vitest"; import { QueryClient, QueryObserver } from "@tanstack/react-query"; -import { invalidateReminderReads } from "@/hooks/use-measurement-reminders"; +import { + invalidateReminderReads, + putReminderRow, + type MeasurementReminder, +} from "@/hooks/use-measurement-reminders"; import { queryKeys } from "@/lib/query-keys"; /** @@ -71,3 +75,23 @@ describe("invalidateReminderReads — the Vorsorge daily-rail freshness contract client.clear(); }); }); + +describe("putReminderRow", () => { + it("shows the answered row at once, so it no longer reads as due", () => { + const client = new QueryClient(); + const due = { + id: "r1", + nextDueAt: "2026-11-11T08:00:00.000Z", + } as MeasurementReminder; + const other = { id: "r2" } as MeasurementReminder; + client.setQueryData(queryKeys.measurementReminders(), [due, other]); + + const done = { ...due, nextDueAt: "2027-11-11T08:00:00.000Z" }; + putReminderRow(client, done); + + expect(client.getQueryData(queryKeys.measurementReminders())).toEqual([ + done, + other, + ]); + }); +}); diff --git a/src/hooks/use-measurement-reminders.ts b/src/hooks/use-measurement-reminders.ts index d60766302..1dfe06611 100644 --- a/src/hooks/use-measurement-reminders.ts +++ b/src/hooks/use-measurement-reminders.ts @@ -115,10 +115,29 @@ export function useMeasurementReminders(enabled = true) { }); } +/** + * Write the row a satisfy, skip or snooze answered with into the cached list. + * The button re-enables as the call settles, and with only the refetch behind + * it the row still read as due, so a second tap marked it done twice. + */ +export function putReminderRow( + qc: QueryClient, + reminder: MeasurementReminder, +): void { + qc.setQueryData( + queryKeys.measurementReminders(), + (rows) => rows?.map((row) => (row.id === reminder.id ? reminder : row)), + ); +} + export function useMeasurementReminderMutations() { const qc = useQueryClient(); // Not returned: a mutation would wait for every refetch before it settles. const invalidate = () => void invalidateReminderReads(qc); + const answered = (reminder: MeasurementReminder) => { + putReminderRow(qc, reminder); + invalidate(); + }; const create = useMutation({ mutationKey: queryKeys.measurementReminderCreate(), @@ -150,7 +169,7 @@ export function useMeasurementReminderMutations() { mutationKey: queryKeys.measurementReminderSatisfy(), mutationFn: (id: string) => apiPost(`${BASE}/${id}/satisfy`), - onSuccess: invalidate, + onSuccess: answered, }); // v1.37.20 (#223) — honest skip: the interval restarts from the skip @@ -161,7 +180,7 @@ export function useMeasurementReminderMutations() { apiPost<{ skipped: boolean; reminder: MeasurementReminder }>( `${BASE}/${id}/skip`, ), - onSuccess: invalidate, + onSuccess: ({ reminder }) => answered(reminder), }); // v1.37.20 (#223) — snooze to a calendar day; the server resolves it to @@ -172,7 +191,7 @@ export function useMeasurementReminderMutations() { apiPost<{ reminder: MeasurementReminder }>(`${BASE}/${id}/snooze`, { until, }), - onSuccess: invalidate, + onSuccess: ({ reminder }) => answered(reminder), }); return { create, update, remove, satisfy, skip, snooze };