mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
fix(medications): refresh the dashboard the moment an intake is recorded
The intake write invalidates the dashboard snapshot, but the default active refetch type only refetches mounted queries — and the snapshot is unmounted while the user is on the medication card or detail page. On navigating back it remounts under refetchOnMount:false, so the pre-write cache was served and the due prompt lingered until a hard reload. Force the inactive snapshot to refetch after every intake write so the dashboard clears as soon as the dose is taken.
This commit is contained in:
1 parent
6a99f72384
commit
59307760c5
2 files changed
+61
-4
No files matched your search
@@ -7,6 +7,7 @@ import {
|
||||
runRecordIntake,
|
||||
runUndoIntake,
|
||||
} from "@/components/medications/use-medication-intake";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
|
||||
/**
|
||||
* v1.12.2 — the shared intake orchestration consumed by both the generic
|
||||
@@ -144,6 +145,38 @@ describe("runRecordIntake — shared C1 failure toast + C2 Undo", () => {
|
||||
expect(onRecorded).toHaveBeenCalledWith("evt-99", false);
|
||||
});
|
||||
|
||||
it("forces the inactive dashboard snapshot to refetch so the due prompt clears without a reload", async () => {
|
||||
// The dashboard snapshot is unmounted while the user is on the medication
|
||||
// card/detail, so a default ("active") invalidation marks it stale but
|
||||
// never refetches it; on navigating back, refetchOnMount:false serves the
|
||||
// pre-write cache and the "due" prompt lingers until a hard reload.
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: vi.fn().mockResolvedValue({ data: { id: "evt-1" } }),
|
||||
text: vi
|
||||
.fn()
|
||||
.mockResolvedValue(JSON.stringify({ data: { id: "evt-1" } })),
|
||||
}),
|
||||
);
|
||||
const queryClient = fakeQueryClient();
|
||||
|
||||
await runRecordIntake({
|
||||
medication,
|
||||
skipped: false,
|
||||
t,
|
||||
queryClient,
|
||||
setIntakeLoading: vi.fn(),
|
||||
undoIntake: vi.fn(),
|
||||
});
|
||||
|
||||
expect(queryClient.invalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: queryKeys.dashboardSnapshot(),
|
||||
refetchType: "inactive",
|
||||
});
|
||||
});
|
||||
|
||||
it("sends the displayed dose's scheduledFor on the POST so the server marks THAT slot (v1.12.3)", async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
|
||||
@@ -5,9 +5,33 @@ import { useQueryClient, type QueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { invalidateKeys, medicationDependentKeys } from "@/lib/query-keys";
|
||||
import {
|
||||
invalidateKeys,
|
||||
medicationDependentKeys,
|
||||
queryKeys,
|
||||
} from "@/lib/query-keys";
|
||||
import { apiDelete, apiPost } from "@/lib/api/api-fetch";
|
||||
|
||||
/**
|
||||
* Invalidate every read that reflects a dose's taken/due state after an
|
||||
* intake write. `invalidateKeys` invalidates with the default
|
||||
* `refetchType: "active"`, which only refetches mounted queries — so the
|
||||
* dashboard snapshot, inactive while the user is on the medication card or
|
||||
* detail page, is marked stale but never refetched. On navigating back the
|
||||
* snapshot remounts under `refetchOnMount: false`, so the pre-write cache is
|
||||
* served and the "due" prompt lingers until a hard reload. Force the inactive
|
||||
* snapshot to refetch so the dashboard clears as soon as the dose is recorded.
|
||||
*/
|
||||
async function invalidateMedicationReads(
|
||||
queryClient: QueryClient,
|
||||
): Promise<void> {
|
||||
await invalidateKeys(queryClient, medicationDependentKeys);
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.dashboardSnapshot(),
|
||||
refetchType: "inactive",
|
||||
});
|
||||
}
|
||||
|
||||
type Translator = (
|
||||
key: string,
|
||||
params?: Record<string, string | number>,
|
||||
@@ -136,7 +160,7 @@ export async function runRecordIntake(deps: {
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
await invalidateKeys(queryClient, medicationDependentKeys);
|
||||
await invalidateMedicationReads(queryClient);
|
||||
onRecorded?.(eventId, skipped);
|
||||
} catch {
|
||||
toast.error(t("medications.intakeToastFailed", { name: medication.name }));
|
||||
@@ -188,7 +212,7 @@ export async function runLogIntake(deps: {
|
||||
if (scheduledFor) body.scheduledFor = scheduledFor;
|
||||
if (!skipped && doseTaken) body.doseTaken = doseTaken;
|
||||
await apiPost(`/api/medications/${medication.id}/intake`, body);
|
||||
await invalidateKeys(queryClient, medicationDependentKeys);
|
||||
await invalidateMedicationReads(queryClient);
|
||||
toast.success(
|
||||
t(
|
||||
skipped
|
||||
@@ -217,7 +241,7 @@ export async function runUndoIntake(deps: {
|
||||
const { medication, eventId, t, queryClient } = deps;
|
||||
try {
|
||||
await apiDelete(`/api/medications/${medication.id}/intake/${eventId}`);
|
||||
await invalidateKeys(queryClient, medicationDependentKeys);
|
||||
await invalidateMedicationReads(queryClient);
|
||||
toast.success(t("medications.intakeUndone"));
|
||||
} catch {
|
||||
toast.error(t("medications.intakeUndoFailed"));
|
||||
|
||||
Reference in new issue
Block a user