mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
Ten client mutation sites forgot a consumer: a coach-accepted checkup stayed invisible for up to five minutes, a threshold or target edit left the dashboard bands stale, a score-config save marked the daily reads stale but never refetched them, a booked visit missed the Today rail, a source-priority reorder kept every chart on the old canonical picks, an adopted self-context note never reached the Settings read. Each site now invalidates what its write changed and forces the unmounted snapshot + digest reads to refetch through refetchInactiveDailyReads. New illnessDependentKeys bundle carries the daily reads (Rest Mode frames both), dailyDigest joins encounterDependentKeys, and the chart-data prefix gains a factory accessor for whole-cache flushes. The daily-reads guard learns the cycle / encounter / vaccination / illness bundle families and a second matcher for direct snapshot/digest invalidateQueries without a forced refetch, each with a frozen reasoned allowlist. Both matchers were proven live by reverting a fix and watching the guard go red (see the test doc comment).
81 lines
2.4 KiB
TypeScript
81 lines
2.4 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* Data hooks for the account's own address book of doctors and practices.
|
|
*
|
|
* The search runs in SQL over the two plaintext columns rather than in the
|
|
* browser over a downloaded list: the columns are plaintext precisely so the
|
|
* picker can search and sort them there, and a client-side filter would need
|
|
* the whole book in memory to do it.
|
|
*/
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
|
|
import { apiDelete, apiGet, apiPatch, apiPost } from "@/lib/api/api-fetch";
|
|
import {
|
|
encounterDependentKeys,
|
|
invalidateKeys,
|
|
queryKeys,
|
|
refetchInactiveDailyReads,
|
|
} from "@/lib/query-keys";
|
|
import type { PractitionerDTO } from "@/lib/practitioners/dto";
|
|
|
|
export type Practitioner = PractitionerDTO;
|
|
|
|
export interface PractitionerWriteBody {
|
|
name?: string;
|
|
specialty?: string | null;
|
|
practice?: string | null;
|
|
location?: string | null;
|
|
phone?: string | null;
|
|
note?: string | null;
|
|
}
|
|
|
|
const BASE = "/api/practitioners";
|
|
|
|
export function usePractitioners(q?: string, enabled = true) {
|
|
const term = q?.trim() || undefined;
|
|
return useQuery({
|
|
queryKey: queryKeys.practitionerList(term),
|
|
queryFn: () =>
|
|
apiGet<Practitioner[]>(
|
|
term ? `${BASE}?q=${encodeURIComponent(term)}` : BASE,
|
|
),
|
|
enabled,
|
|
});
|
|
}
|
|
|
|
export function usePractitionerMutations() {
|
|
const qc = useQueryClient();
|
|
// Renaming a practice changes the label a rail appointment renders under,
|
|
// and the daily reads are unmounted from the address book — pair the
|
|
// bundle invalidation with the forced inactive refetch.
|
|
const invalidate = () =>
|
|
Promise.all([
|
|
invalidateKeys(qc, encounterDependentKeys),
|
|
refetchInactiveDailyReads(qc),
|
|
]);
|
|
|
|
const create = useMutation({
|
|
mutationKey: queryKeys.practitionerCreate(),
|
|
mutationFn: (body: PractitionerWriteBody) =>
|
|
apiPost<Practitioner>(BASE, body),
|
|
onSuccess: invalidate,
|
|
});
|
|
|
|
const update = useMutation({
|
|
mutationKey: queryKeys.practitionerUpdate(),
|
|
mutationFn: ({ id, body }: { id: string; body: PractitionerWriteBody }) =>
|
|
apiPatch<Practitioner>(`${BASE}/${id}`, body),
|
|
onSuccess: invalidate,
|
|
});
|
|
|
|
const remove = useMutation({
|
|
mutationKey: queryKeys.practitionerDelete(),
|
|
mutationFn: (id: string) =>
|
|
apiDelete<{ deleted: boolean }>(`${BASE}/${id}`),
|
|
onSuccess: invalidate,
|
|
});
|
|
|
|
return { create, update, remove };
|
|
}
|