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 `