diff --git a/messages/de.json b/messages/de.json index 42965e6ba..73959128c 100644 --- a/messages/de.json +++ b/messages/de.json @@ -955,7 +955,9 @@ "gastroparesisLike": "Verlangsamte Magenentleerung", "dysgeusia": "Geschmacksveränderung", "gallbladderDiscomfort": "Gallenblasen-Beschwerden" - } + }, + "deleteConfirmTitle": "Eintrag löschen?", + "deleteConfirmBody": "Dieser Nebenwirkungs-Eintrag wird dauerhaft entfernt." }, "scheduling": { "naturalLanguage": { diff --git a/messages/en.json b/messages/en.json index 0deafe73f..e620e98bb 100644 --- a/messages/en.json +++ b/messages/en.json @@ -955,7 +955,9 @@ "gastroparesisLike": "Slow stomach emptying", "dysgeusia": "Taste change", "gallbladderDiscomfort": "Gallbladder discomfort" - } + }, + "deleteConfirmTitle": "Delete this entry?", + "deleteConfirmBody": "This side-effect entry will be permanently removed." }, "scheduling": { "naturalLanguage": { diff --git a/messages/es.json b/messages/es.json index bf2109424..4c4e978ac 100644 --- a/messages/es.json +++ b/messages/es.json @@ -955,7 +955,9 @@ "gastroparesisLike": "Vaciado gástrico lento", "dysgeusia": "Alteración del gusto", "gallbladderDiscomfort": "Molestia en la vesícula biliar" - } + }, + "deleteConfirmTitle": "¿Eliminar esta entrada?", + "deleteConfirmBody": "Esta entrada de efecto secundario se eliminará de forma permanente." }, "scheduling": { "naturalLanguage": { diff --git a/messages/fr.json b/messages/fr.json index 3a211914e..ec0d5ccb3 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -955,7 +955,9 @@ "gastroparesisLike": "Vidange gastrique lente", "dysgeusia": "Altération du goût", "gallbladderDiscomfort": "Inconfort de la vésicule biliaire" - } + }, + "deleteConfirmTitle": "Supprimer cette entrée ?", + "deleteConfirmBody": "Cette entrée d’effet secondaire sera définitivement supprimée." }, "scheduling": { "naturalLanguage": { diff --git a/messages/it.json b/messages/it.json index 51dee5cd3..34a14bc39 100644 --- a/messages/it.json +++ b/messages/it.json @@ -955,7 +955,9 @@ "gastroparesisLike": "Svuotamento gastrico lento", "dysgeusia": "Alterazione del gusto", "gallbladderDiscomfort": "Fastidio alla cistifellea" - } + }, + "deleteConfirmTitle": "Eliminare questa voce?", + "deleteConfirmBody": "Questa voce di effetto collaterale verrà rimossa definitivamente." }, "scheduling": { "naturalLanguage": { diff --git a/messages/pl.json b/messages/pl.json index 50016525c..8830afebe 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -955,7 +955,9 @@ "gastroparesisLike": "Spowolnione opróżnianie żołądka", "dysgeusia": "Zmiana smaku", "gallbladderDiscomfort": "Dolegliwości pęcherzyka żółciowego" - } + }, + "deleteConfirmTitle": "Usunąć ten wpis?", + "deleteConfirmBody": "Ten wpis o skutku ubocznym zostanie trwale usunięty." }, "scheduling": { "naturalLanguage": { diff --git a/src/app/medications/page.tsx b/src/app/medications/page.tsx index bc2121edf..bc8a5b0e2 100644 --- a/src/app/medications/page.tsx +++ b/src/app/medications/page.tsx @@ -26,6 +26,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { EmptyState } from "@/components/ui/empty-state"; +import { QueryErrorCard } from "@/components/ui/query-error-card"; import { Skeleton } from "@/components/ui/skeleton"; import { CheckCheck, @@ -426,20 +427,11 @@ export default function MedicationsPage() { )} ) : isError ? ( -
-
-

- {t("medications.loadFailed")} -

- -
-
+ void refetchMedications()} + /> ) : !medications?.length ? ( // v1.4.15 phase-C5: refactor the inline icon+text+button block // to the shared EmptyState primitive so the empty path matches diff --git a/src/components/custom-metrics/custom-metric-detail.tsx b/src/components/custom-metrics/custom-metric-detail.tsx index 1f170814c..b809e8412 100644 --- a/src/components/custom-metrics/custom-metric-detail.tsx +++ b/src/components/custom-metrics/custom-metric-detail.tsx @@ -15,6 +15,7 @@ import { toast } from "sonner"; import { DeleteButton } from "@/components/data-list"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; +import { QueryErrorCard } from "@/components/ui/query-error-card"; import { EmptyState } from "@/components/ui/empty-state"; import { ResponsiveSheet } from "@/components/ui/responsive-sheet"; import { Skeleton } from "@/components/ui/skeleton"; @@ -178,23 +179,13 @@ export function CustomMetricDetail({ {description}

- - -

- {t("customMetrics.loadError")} -

- -
-
+ { + if (metricError) void refetchMetric(); + if (listError) void refetchList(); + }} + /> ); } diff --git a/src/components/custom-metrics/custom-metric-list.tsx b/src/components/custom-metrics/custom-metric-list.tsx index e9492c6df..b7669cc0c 100644 --- a/src/components/custom-metrics/custom-metric-list.tsx +++ b/src/components/custom-metrics/custom-metric-list.tsx @@ -2,11 +2,13 @@ import { useState } from "react"; import Link from "next/link"; +import { useRouter } from "next/navigation"; import { useQuery } from "@tanstack/react-query"; import { ChevronRight, Gauge, Plus } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; +import { QueryErrorCard } from "@/components/ui/query-error-card"; import { ResponsiveSheet } from "@/components/ui/responsive-sheet"; import { Skeleton } from "@/components/ui/skeleton"; import { apiGet } from "@/lib/api/api-fetch"; @@ -27,10 +29,11 @@ import type { CustomMetricDto, CustomMetricListResponse } from "./types"; */ export function CustomMetricList() { const { t } = useTranslations(); + const router = useRouter(); const [addOpen, setAddOpen] = useState(false); const [addFooterEl, setAddFooterEl] = useState(null); - const { data, isLoading, isError } = useQuery({ + const { data, isLoading, isError, refetch } = useQuery({ queryKey: queryKeys.customMetrics(), queryFn: () => apiGet("/api/custom-metrics"), }); @@ -40,10 +43,8 @@ export function CustomMetricList() { function afterAdd(saved: CustomMetricDto) { setAddOpen(false); // Land the user on the new metric's detail page so the next step (logging - // a value) is one tap away. - if (typeof window !== "undefined") { - window.location.assign(`/custom-metrics/${saved.id}`); - } + // a value) is one tap away — client nav, not a full-page reload. + router.push(`/custom-metrics/${saved.id}`); } return ( @@ -80,9 +81,10 @@ export function CustomMetricList() { ) : isError ? ( -

- {t("customMetrics.loadError")} -

+ refetch()} + /> ) : metrics.length === 0 ? ( diff --git a/src/components/medications/side-effects-section.tsx b/src/components/medications/side-effects-section.tsx index 834437e05..f28e71e0a 100644 --- a/src/components/medications/side-effects-section.tsx +++ b/src/components/medications/side-effects-section.tsx @@ -8,6 +8,16 @@ import { Loader2, Plus, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { ResponsiveSheet } from "@/components/ui/responsive-sheet"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { @@ -113,6 +123,7 @@ export function SideEffectsSection({ medicationId }: SideEffectsSectionProps) { // into the `` footer slot. The submit button keeps // its `
` association via the HTML `form` attribute. const [footerEl, setFooterEl] = useState(null); + const [pendingDeleteId, setPendingDeleteId] = useState(null); const formId = useId(); const listKey = queryKeys.medicationSideEffects(medicationId); @@ -330,14 +341,7 @@ export function SideEffectsSection({ medicationId }: SideEffectsSectionProps) { size="icon" className="text-muted-foreground hover:text-destructive size-11 shrink-0" aria-label={t("medications.sideEffects.deleteCta")} - onClick={() => { - if ( - typeof window === "undefined" || - window.confirm(t("medications.sideEffects.deleteConfirm")) - ) { - deleteMutation.mutate(row.id); - } - }} + onClick={() => setPendingDeleteId(row.id)} disabled={deleteMutation.isPending} > @@ -348,6 +352,43 @@ export function SideEffectsSection({ medicationId }: SideEffectsSectionProps) { )} + {/* Themed confirm — replaces the native window.confirm so the delete + gate matches every other destructive action in the app. */} + { + if (!o) setPendingDeleteId(null); + }} + > + + + + {t("medications.sideEffects.deleteConfirmTitle")} + + + {t("medications.sideEffects.deleteConfirmBody")} + + + + + {t("common.cancel")} + + { + e.preventDefault(); + if (pendingDeleteId) deleteMutation.mutate(pendingDeleteId); + setPendingDeleteId(null); + }} + disabled={deleteMutation.isPending} + aria-busy={deleteMutation.isPending || undefined} + className="bg-destructive dark:bg-destructive/60 text-white hover:bg-destructive/90" + > + {t("medications.sideEffects.deleteCta")} + + + + +