From d79060e26b60152ecfca4ca786bb78e899b3f0e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 11 Nov 2026 22:56:32 +0100 Subject: [PATCH] fix(checkups): show a marked-done check-up as done before the refetch The "Mark done" button re-enables as soon as the satisfy call settles, while the row kept reading as due until the list refetch landed, so a second tap stored a second satisfied entry; skip and snooze behaved the same. The row each of the three calls answers with is now written into the cached list on success, so the row moves to its next due date at once, and the refetch follows as before. --- .../use-measurement-reminders-refetch.test.ts | 26 ++++++++++++++++++- src/hooks/use-measurement-reminders.ts | 25 +++++++++++++++--- 2 files changed, 47 insertions(+), 4 deletions(-) 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 };