From c6045482c3e2dac2211d42f9cd8b67aa27e27da0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 14 Jun 2026 23:33:37 +0200 Subject: [PATCH] =?UTF-8?q?feat(reminders):=20preventive-care=20surface=20?= =?UTF-8?q?=E2=80=94=20wann,=20was,=20wo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the Vorsorge page and section: an upcoming-reminders list sorted by server-computed next-due, with a create form (measurement-linked or free-text, cadence, notify hour, location) and a per-card Erledigt and delete action. Cards stay neutral-coloured regardless of due state — the status reads through a discreet badge only, never an alarming tint. Copy ships in all six locales. --- messages/de.json | 51 +++ messages/en.json | 51 +++ messages/es.json | 51 +++ messages/fr.json | 51 +++ messages/it.json | 51 +++ messages/pl.json | 51 +++ src/app/vorsorge/page.tsx | 31 ++ .../vorsorge-section.tsx | 369 ++++++++++++++++++ src/hooks/use-measurement-reminders.ts | 97 +++++ src/lib/query-keys/index.ts | 2 + src/lib/query-keys/measurement-reminders.ts | 21 + 11 files changed, 826 insertions(+) create mode 100644 src/app/vorsorge/page.tsx create mode 100644 src/components/measurement-reminders/vorsorge-section.tsx create mode 100644 src/hooks/use-measurement-reminders.ts create mode 100644 src/lib/query-keys/measurement-reminders.ts diff --git a/messages/de.json b/messages/de.json index 01784ec8b..28f7422d6 100644 --- a/messages/de.json +++ b/messages/de.json @@ -6208,5 +6208,56 @@ "deleteN": "Löschen ({count})", "clearSelection": "Auswahl aufheben", "selectionBarLabel": "Auswahlaktionen" + }, + "measurementReminders": { + "sectionTitle": "Vorsorge", + "sectionDescription": "Erinnerungen, was wann und wo zu messen oder zu prüfen ist.", + "addButton": "Erinnerung hinzufügen", + "markDone": "Erledigt", + "delete": "Löschen", + "disabledBadge": "Aus", + "nextDue": { + "none": "Nicht geplant", + "today": "Heute fällig", + "tomorrow": "Morgen fällig", + "inDays": "In {days} Tagen" + }, + "overdueByDays": "Überfällig seit {days} Tagen", + "cadence": { + "everyNDays": "Alle {days} Tage", + "custom": "Eigener Zeitplan" + }, + "location": { + "prefix": "Bei: {location}" + }, + "types": { + "WEIGHT": "Gewicht", + "BLOOD_PRESSURE_SYS": "Blutdruck", + "PULSE": "Puls", + "BLOOD_GLUCOSE": "Blutzucker", + "OXYGEN_SATURATION": "Sauerstoffsättigung", + "BODY_FAT": "Körperfett", + "BODY_TEMPERATURE": "Körpertemperatur" + }, + "empty": { + "title": "Noch keine Erinnerungen", + "description": "Füge eine Erinnerung hinzu, um z. B. regelmäßig den Blutdruck zu messen oder ein jährliches Blutbild zu planen." + }, + "form": { + "label": "Bezeichnung", + "labelPlaceholder": "z. B. Blutdruck messen", + "kind": "Art", + "kindType": "Mit einer Messung verknüpft", + "kindFreeText": "Freitext-Prüfung", + "measurementType": "Messwert", + "cadence": "Wiederholung", + "notifyHour": "Erinnern um", + "location": "Wo (optional)", + "locationPlaceholder": "z. B. Hausarzt, Labor", + "save": "Speichern" + }, + "pushTitle": "Vorsorge-Erinnerung", + "pushBody": "Fällig: {label}", + "pushLocation": "(bei {location})" } } diff --git a/messages/en.json b/messages/en.json index a78cc1a24..88748e36d 100644 --- a/messages/en.json +++ b/messages/en.json @@ -6208,5 +6208,56 @@ "deleteN": "Delete ({count})", "clearSelection": "Clear selection", "selectionBarLabel": "Selection actions" + }, + "measurementReminders": { + "sectionTitle": "Preventive care", + "sectionDescription": "Reminders for what to measure or check, when, and where.", + "addButton": "Add reminder", + "markDone": "Done", + "delete": "Delete", + "disabledBadge": "Off", + "nextDue": { + "none": "Not scheduled", + "today": "Due today", + "tomorrow": "Due tomorrow", + "inDays": "In {days} days" + }, + "overdueByDays": "Overdue by {days} days", + "cadence": { + "everyNDays": "Every {days} days", + "custom": "Custom schedule" + }, + "location": { + "prefix": "At: {location}" + }, + "types": { + "WEIGHT": "Weight", + "BLOOD_PRESSURE_SYS": "Blood pressure", + "PULSE": "Pulse", + "BLOOD_GLUCOSE": "Blood glucose", + "OXYGEN_SATURATION": "Oxygen saturation", + "BODY_FAT": "Body fat", + "BODY_TEMPERATURE": "Body temperature" + }, + "empty": { + "title": "No reminders yet", + "description": "Add a reminder to measure your blood pressure on a cadence, or to schedule an annual blood panel." + }, + "form": { + "label": "Label", + "labelPlaceholder": "e.g. Measure blood pressure", + "kind": "Type", + "kindType": "Linked to a measurement", + "kindFreeText": "Free-text check", + "measurementType": "Measurement", + "cadence": "Repeat", + "notifyHour": "Remind at", + "location": "Where (optional)", + "locationPlaceholder": "e.g. GP, Lab", + "save": "Save" + }, + "pushTitle": "Preventive-care reminder", + "pushBody": "Time for: {label}", + "pushLocation": "(at {location})" } } diff --git a/messages/es.json b/messages/es.json index 07ef722cb..d8c7e315e 100644 --- a/messages/es.json +++ b/messages/es.json @@ -6208,5 +6208,56 @@ "deleteN": "Eliminar ({count})", "clearSelection": "Borrar selección", "selectionBarLabel": "Acciones de selección" + }, + "measurementReminders": { + "sectionTitle": "Prevención", + "sectionDescription": "Recordatorios de qué medir o revisar, cuándo y dónde.", + "addButton": "Añadir recordatorio", + "markDone": "Hecho", + "delete": "Eliminar", + "disabledBadge": "Apagado", + "nextDue": { + "none": "Sin programar", + "today": "Vence hoy", + "tomorrow": "Vence mañana", + "inDays": "En {days} días" + }, + "overdueByDays": "Vencido hace {days} días", + "cadence": { + "everyNDays": "Cada {days} días", + "custom": "Programación personalizada" + }, + "location": { + "prefix": "En: {location}" + }, + "types": { + "WEIGHT": "Peso", + "BLOOD_PRESSURE_SYS": "Presión arterial", + "PULSE": "Pulso", + "BLOOD_GLUCOSE": "Glucosa en sangre", + "OXYGEN_SATURATION": "Saturación de oxígeno", + "BODY_FAT": "Grasa corporal", + "BODY_TEMPERATURE": "Temperatura corporal" + }, + "empty": { + "title": "Aún no hay recordatorios", + "description": "Añade un recordatorio para medir la presión arterial con regularidad o programar un análisis de sangre anual." + }, + "form": { + "label": "Etiqueta", + "labelPlaceholder": "p. ej. Medir la presión arterial", + "kind": "Tipo", + "kindType": "Vinculado a una medición", + "kindFreeText": "Comprobación de texto libre", + "measurementType": "Medición", + "cadence": "Repetir", + "notifyHour": "Recordar a las", + "location": "Dónde (opcional)", + "locationPlaceholder": "p. ej. Médico, Laboratorio", + "save": "Guardar" + }, + "pushTitle": "Recordatorio de prevención", + "pushBody": "Toca: {label}", + "pushLocation": "(en {location})" } } diff --git a/messages/fr.json b/messages/fr.json index acec48c77..3831846ad 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -6208,5 +6208,56 @@ "deleteN": "Supprimer ({count})", "clearSelection": "Effacer la sélection", "selectionBarLabel": "Actions de sélection" + }, + "measurementReminders": { + "sectionTitle": "Prévention", + "sectionDescription": "Rappels de ce qu'il faut mesurer ou vérifier, quand et où.", + "addButton": "Ajouter un rappel", + "markDone": "Terminé", + "delete": "Supprimer", + "disabledBadge": "Désactivé", + "nextDue": { + "none": "Non planifié", + "today": "À faire aujourd'hui", + "tomorrow": "À faire demain", + "inDays": "Dans {days} jours" + }, + "overdueByDays": "En retard de {days} jours", + "cadence": { + "everyNDays": "Tous les {days} jours", + "custom": "Planning personnalisé" + }, + "location": { + "prefix": "Chez : {location}" + }, + "types": { + "WEIGHT": "Poids", + "BLOOD_PRESSURE_SYS": "Tension artérielle", + "PULSE": "Pouls", + "BLOOD_GLUCOSE": "Glycémie", + "OXYGEN_SATURATION": "Saturation en oxygène", + "BODY_FAT": "Masse grasse", + "BODY_TEMPERATURE": "Température corporelle" + }, + "empty": { + "title": "Aucun rappel pour l'instant", + "description": "Ajoutez un rappel pour mesurer régulièrement votre tension ou planifier un bilan sanguin annuel." + }, + "form": { + "label": "Libellé", + "labelPlaceholder": "ex. Mesurer la tension", + "kind": "Type", + "kindType": "Lié à une mesure", + "kindFreeText": "Vérification libre", + "measurementType": "Mesure", + "cadence": "Répétition", + "notifyHour": "Rappeler à", + "location": "Où (facultatif)", + "locationPlaceholder": "ex. Médecin, Laboratoire", + "save": "Enregistrer" + }, + "pushTitle": "Rappel de prévention", + "pushBody": "C'est l'heure : {label}", + "pushLocation": "(chez {location})" } } diff --git a/messages/it.json b/messages/it.json index e825a1770..6c959630c 100644 --- a/messages/it.json +++ b/messages/it.json @@ -6208,5 +6208,56 @@ "deleteN": "Elimina ({count})", "clearSelection": "Cancella selezione", "selectionBarLabel": "Azioni di selezione" + }, + "measurementReminders": { + "sectionTitle": "Prevenzione", + "sectionDescription": "Promemoria su cosa misurare o controllare, quando e dove.", + "addButton": "Aggiungi promemoria", + "markDone": "Fatto", + "delete": "Elimina", + "disabledBadge": "Disattivato", + "nextDue": { + "none": "Non pianificato", + "today": "In scadenza oggi", + "tomorrow": "In scadenza domani", + "inDays": "Tra {days} giorni" + }, + "overdueByDays": "In ritardo di {days} giorni", + "cadence": { + "everyNDays": "Ogni {days} giorni", + "custom": "Pianificazione personalizzata" + }, + "location": { + "prefix": "Presso: {location}" + }, + "types": { + "WEIGHT": "Peso", + "BLOOD_PRESSURE_SYS": "Pressione sanguigna", + "PULSE": "Battito", + "BLOOD_GLUCOSE": "Glicemia", + "OXYGEN_SATURATION": "Saturazione di ossigeno", + "BODY_FAT": "Grasso corporeo", + "BODY_TEMPERATURE": "Temperatura corporea" + }, + "empty": { + "title": "Ancora nessun promemoria", + "description": "Aggiungi un promemoria per misurare regolarmente la pressione o pianificare un esame del sangue annuale." + }, + "form": { + "label": "Etichetta", + "labelPlaceholder": "es. Misurare la pressione", + "kind": "Tipo", + "kindType": "Collegato a una misurazione", + "kindFreeText": "Controllo a testo libero", + "measurementType": "Misurazione", + "cadence": "Ripeti", + "notifyHour": "Ricorda alle", + "location": "Dove (facoltativo)", + "locationPlaceholder": "es. Medico, Laboratorio", + "save": "Salva" + }, + "pushTitle": "Promemoria di prevenzione", + "pushBody": "È ora di: {label}", + "pushLocation": "(presso {location})" } } diff --git a/messages/pl.json b/messages/pl.json index b551d7b0b..cb4719c43 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -6208,5 +6208,56 @@ "deleteN": "Usuń ({count})", "clearSelection": "Wyczyść zaznaczenie", "selectionBarLabel": "Akcje zaznaczenia" + }, + "measurementReminders": { + "sectionTitle": "Profilaktyka", + "sectionDescription": "Przypomnienia, co, kiedy i gdzie zmierzyć lub sprawdzić.", + "addButton": "Dodaj przypomnienie", + "markDone": "Gotowe", + "delete": "Usuń", + "disabledBadge": "Wył.", + "nextDue": { + "none": "Niezaplanowane", + "today": "Termin dzisiaj", + "tomorrow": "Termin jutro", + "inDays": "Za {days} dni" + }, + "overdueByDays": "Zaległe od {days} dni", + "cadence": { + "everyNDays": "Co {days} dni", + "custom": "Własny harmonogram" + }, + "location": { + "prefix": "U: {location}" + }, + "types": { + "WEIGHT": "Waga", + "BLOOD_PRESSURE_SYS": "Ciśnienie krwi", + "PULSE": "Tętno", + "BLOOD_GLUCOSE": "Glukoza we krwi", + "OXYGEN_SATURATION": "Saturacja tlenem", + "BODY_FAT": "Tkanka tłuszczowa", + "BODY_TEMPERATURE": "Temperatura ciała" + }, + "empty": { + "title": "Brak przypomnień", + "description": "Dodaj przypomnienie, aby regularnie mierzyć ciśnienie krwi lub zaplanować coroczne badanie krwi." + }, + "form": { + "label": "Etykieta", + "labelPlaceholder": "np. Zmierz ciśnienie krwi", + "kind": "Typ", + "kindType": "Powiązane z pomiarem", + "kindFreeText": "Sprawdzenie tekstowe", + "measurementType": "Pomiar", + "cadence": "Powtarzaj", + "notifyHour": "Przypomnij o", + "location": "Gdzie (opcjonalnie)", + "locationPlaceholder": "np. Lekarz, Laboratorium", + "save": "Zapisz" + }, + "pushTitle": "Przypomnienie profilaktyczne", + "pushBody": "Czas na: {label}", + "pushLocation": "(u {location})" } } diff --git a/src/app/vorsorge/page.tsx b/src/app/vorsorge/page.tsx new file mode 100644 index 000000000..6873b7851 --- /dev/null +++ b/src/app/vorsorge/page.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { Loader2 } from "lucide-react"; + +import { useAuth } from "@/hooks/use-auth"; +import { useMounted } from "@/hooks/use-mounted"; +import { VorsorgeSection } from "@/components/measurement-reminders/vorsorge-section"; + +/** + * v1.17.1 — Vorsorge (preventive-care) reminders page. The dedicated + * feature surface for "wann muss ich was wo machen". Auth-gated; the + * section component owns the list + create flow. + */ +export default function VorsorgePage() { + const { isAuthenticated, isLoading } = useAuth(); + const mounted = useMounted(); + + if (!mounted || isLoading) { + return ( +
+ +
+ ); + } + + return ( +
+ +
+ ); +} diff --git a/src/components/measurement-reminders/vorsorge-section.tsx b/src/components/measurement-reminders/vorsorge-section.tsx new file mode 100644 index 000000000..b86900975 --- /dev/null +++ b/src/components/measurement-reminders/vorsorge-section.tsx @@ -0,0 +1,369 @@ +"use client"; + +/** + * v1.17.1 — Vorsorge (preventive-care / measurement) reminder surface. + * + * Answers "wann muss ich was wo machen": a list of upcoming reminders + * sorted by server-computed next-due, each card showing the label, the + * cadence, the location, and a relative next-due badge. Per the project + * rule the card stays NEUTRAL regardless of due state — no alarming + * red/green tint; status reads through a discreet badge only. + * + * The server is authoritative for `nextDueAt`; this component renders it + * relative to "now" but never recomputes the cadence. + */ +import { useState } from "react"; +import { CheckCircle2, Plus, Trash2 } from "lucide-react"; + +import { useTranslations } from "@/lib/i18n/context"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { EmptyState } from "@/components/ui/empty-state"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { NativeSelect } from "@/components/ui/native-select"; +import { + useMeasurementReminders, + useMeasurementReminderMutations, + type MeasurementReminder, +} from "@/hooks/use-measurement-reminders"; + +const TYPE_OPTIONS = [ + "WEIGHT", + "BLOOD_PRESSURE_SYS", + "PULSE", + "BLOOD_GLUCOSE", + "OXYGEN_SATURATION", + "BODY_FAT", + "BODY_TEMPERATURE", +] as const; + +const INTERVAL_PRESETS = [7, 14, 30, 90, 180, 365] as const; +const DAY_MS = 24 * 60 * 60 * 1000; + +function relativeDueKey( + nextDueAt: string | null, + 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); + 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 }; + return { key: "nextDue.inDays", days: deltaDays }; +} + +export function VorsorgeSection({ enabled = true }: { enabled?: boolean }) { + const { t } = useTranslations(); + const { data: reminders, isLoading } = useMeasurementReminders(enabled); + const { create, remove, satisfy } = useMeasurementReminderMutations(); + const [showForm, setShowForm] = useState(false); + + // Form state. + const [label, setLabel] = useState(""); + const [kind, setKind] = useState<"type" | "freeText">("type"); + const [measurementType, setMeasurementType] = + useState("BLOOD_PRESSURE_SYS"); + const [intervalDays, setIntervalDays] = useState(7); + const [notifyHour, setNotifyHour] = useState(9); + const [location, setLocation] = useState(""); + + // Wall-clock anchor for the relative-due labels, captured once at mount + // via a lazy state initializer so the impure Date.now() stays out of + // render (the repo's purity + set-state-in-effect rules both reject the + // alternatives). The relative labels are coarse (day granularity), so a + // mount-time snapshot is plenty fresh. + const [now] = useState(() => Date.now()); + + function resetForm() { + setLabel(""); + setKind("type"); + setMeasurementType("BLOOD_PRESSURE_SYS"); + setIntervalDays(7); + setNotifyHour(9); + setLocation(""); + } + + function submit() { + const trimmed = label.trim(); + if (!trimmed) return; + create.mutate( + { + label: trimmed, + measurementType: kind === "type" ? measurementType : null, + intervalDays, + notifyHour, + location: location.trim() || null, + }, + { + onSuccess: () => { + resetForm(); + setShowForm(false); + }, + }, + ); + } + + return ( +
+
+
+

+ {t("measurementReminders.sectionTitle")} +

+

+ {t("measurementReminders.sectionDescription")} +

+
+ +
+ + {showForm && ( + + +
+ + setLabel(e.target.value)} + /> +
+ +
+ + + setKind(e.target.value as "type" | "freeText") + } + > + + + +
+ + {kind === "type" && ( +
+ + setMeasurementType(e.target.value)} + > + {TYPE_OPTIONS.map((type) => ( + + ))} + +
+ )} + +
+
+ + setIntervalDays(Number(e.target.value))} + > + {INTERVAL_PRESETS.map((days) => ( + + ))} + +
+
+ + setNotifyHour(Number(e.target.value))} + > + {Array.from({ length: 24 }, (_, h) => ( + + ))} + +
+
+ +
+ + setLocation(e.target.value)} + /> +
+ +
+ + +
+
+
+ )} + + {!isLoading && (reminders?.length ?? 0) === 0 && !showForm && ( + + )} + +
    + {reminders?.map((reminder) => ( + satisfy.mutate(reminder.id)} + onRemove={() => remove.mutate(reminder.id)} + busy={satisfy.isPending || remove.isPending} + /> + ))} +
+
+ ); +} + +function VorsorgeCard({ + reminder, + now, + onSatisfy, + onRemove, + busy, +}: { + reminder: MeasurementReminder; + now: number; + onSatisfy: () => void; + onRemove: () => void; + busy: boolean; +}) { + const { t } = useTranslations(); + const due = relativeDueKey(reminder.nextDueAt, now); + const cadence = + reminder.intervalDays != null + ? t("measurementReminders.cadence.everyNDays", { + days: reminder.intervalDays, + }) + : reminder.rrule + ? t("measurementReminders.cadence.custom") + : ""; + + return ( +
  • + {/* NEUTRAL card — no status-driven colour. The due state reads only + through the discreet badge below. */} + + +
    +
    +

    {reminder.label}

    + {!reminder.enabled && ( + + {t("measurementReminders.disabledBadge")} + + )} +
    +

    + + {t(`measurementReminders.${due.key}`, { days: due.days })} + + {cadence} +

    + {reminder.location && ( +

    + {t("measurementReminders.location.prefix", { + location: reminder.location, + })} +

    + )} +
    +
    + + +
    +
    +
    +
  • + ); +} diff --git a/src/hooks/use-measurement-reminders.ts b/src/hooks/use-measurement-reminders.ts new file mode 100644 index 000000000..243efe90f --- /dev/null +++ b/src/hooks/use-measurement-reminders.ts @@ -0,0 +1,97 @@ +"use client"; + +/** + * v1.17.1 — data hook for Vorsorge (measurement) reminders. + * + * Wraps the `/api/measurement-reminders` CRUD + satisfy surface behind + * TanStack Query. Every mutation invalidates the one + * `measurementReminders()` root so the section list and the dashboard + * tile repaint in lockstep. The DTO is the server-authoritative shape — + * the client renders `nextDueAt` as-is, never recomputing. + */ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; + +import { apiDelete, apiGet, apiPatch, apiPost } from "@/lib/api/api-fetch"; +import { queryKeys } from "@/lib/query-keys"; + +export interface MeasurementReminder { + id: string; + label: string; + measurementType: string | null; + intervalDays: number | null; + rrule: string | null; + anchorDate: string | null; + notifyHour: number; + location: string | null; + nextDueAt: string | null; + lastSatisfiedAt: string | null; + enabled: boolean; + createdAt: string; + updatedAt: string; +} + +export interface CreateMeasurementReminderBody { + label: string; + measurementType?: string | null; + intervalDays?: number | null; + rrule?: string | null; + anchorDate?: string | null; + notifyHour?: number; + location?: string | null; + enabled?: boolean; +} + +export type UpdateMeasurementReminderBody = Partial< + CreateMeasurementReminderBody +>; + +const BASE = "/api/measurement-reminders"; + +export function useMeasurementReminders(enabled = true) { + return useQuery({ + queryKey: queryKeys.measurementReminders(), + queryFn: () => apiGet(BASE), + enabled, + }); +} + +export function useMeasurementReminderMutations() { + const qc = useQueryClient(); + const invalidate = () => + qc.invalidateQueries({ queryKey: queryKeys.measurementReminders() }); + + const create = useMutation({ + mutationKey: queryKeys.measurementReminderCreate(), + mutationFn: (body: CreateMeasurementReminderBody) => + apiPost(BASE, body), + onSuccess: invalidate, + }); + + const update = useMutation({ + mutationKey: queryKeys.measurementReminderUpdate(), + mutationFn: ({ + id, + body, + }: { + id: string; + body: UpdateMeasurementReminderBody; + }) => apiPatch(`${BASE}/${id}`, body), + onSuccess: invalidate, + }); + + const remove = useMutation({ + mutationKey: queryKeys.measurementReminderDelete(), + mutationFn: (id: string) => + apiDelete<{ deleted: boolean }>(`${BASE}/${id}`), + onSuccess: invalidate, + }); + + const satisfy = useMutation({ + mutationKey: queryKeys.measurementReminderSatisfy(), + mutationFn: (id: string) => + apiPost(`${BASE}/${id}/satisfy`), + onSuccess: invalidate, + }); + + return { create, update, remove, satisfy }; +} diff --git a/src/lib/query-keys/index.ts b/src/lib/query-keys/index.ts index d864a42bd..d619681a0 100644 --- a/src/lib/query-keys/index.ts +++ b/src/lib/query-keys/index.ts @@ -22,6 +22,7 @@ import { dashboardKeys } from "./dashboard"; import { insightsKeys } from "./insights"; import { integrationKeys } from "./integrations"; import { measurementKeys } from "./measurements"; +import { measurementReminderKeys } from "./measurement-reminders"; import { medicationKeys } from "./medications"; import { moodKeys } from "./mood"; import { settingsKeys } from "./settings"; @@ -40,6 +41,7 @@ export const queryKeys = { ...workoutKeys, ...settingsKeys, ...cycleKeys, + ...measurementReminderKeys, }; /** diff --git a/src/lib/query-keys/measurement-reminders.ts b/src/lib/query-keys/measurement-reminders.ts new file mode 100644 index 000000000..af4d7b6c2 --- /dev/null +++ b/src/lib/query-keys/measurement-reminders.ts @@ -0,0 +1,21 @@ +/** + * Query keys — Vorsorge (measurement) reminders (v1.17.1). + * Part of the centralized factory; aggregated in `./index.ts`. + */ +export const measurementReminderKeys = { + /** + * The owner's Vorsorge reminder list. A create / edit / delete / satisfy + * mutation invalidates this root so the section + the dashboard tile + * repaint in lockstep. + */ + measurementReminders: () => ["measurement-reminders"] as const, + /** Mutation keys — kept in the factory so no bare array reaches a call site. */ + measurementReminderCreate: () => + ["measurement-reminders", "create"] as const, + measurementReminderUpdate: () => + ["measurement-reminders", "update"] as const, + measurementReminderDelete: () => + ["measurement-reminders", "delete"] as const, + measurementReminderSatisfy: () => + ["measurement-reminders", "satisfy"] as const, +};