From 02d527720a5df6b10936b58c7b695eb2da05c0d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 28 Jun 2026 13:30:19 +0200 Subject: [PATCH] feat(labs): adjust a biomarker target range from its detail page Add a control beside the marker actions that opens a focused sheet for the reference low and high only, so a user can tune the band without the full marker editor. Persists through the existing biomarker PUT and invalidates the detail, catalog, and result reads. --- messages/de.json | 9 +- messages/en.json | 9 +- messages/es.json | 9 +- messages/fr.json | 9 +- messages/it.json | 9 +- messages/pl.json | 9 +- src/components/labs/lab-biomarker-detail.tsx | 105 ++++++++++++++++++- 7 files changed, 151 insertions(+), 8 deletions(-) diff --git a/messages/de.json b/messages/de.json index 097118112..a5e5f1469 100644 --- a/messages/de.json +++ b/messages/de.json @@ -6979,7 +6979,14 @@ "unhiddenToast": "Biomarker wiederhergestellt", "hideError": "Biomarker konnte nicht aktualisiert werden", "allHidden": "Alle Biomarker sind ausgeblendet.", - "hiddenSection": "Ausgeblendet" + "hiddenSection": "Ausgeblendet", + "targetRange": { + "title": "Zielbereich anpassen", + "description": "Lege die untere und obere Referenz für diesen Marker fest. Lass ein Feld leer für eine offene Grenze.", + "save": "Bereich speichern", + "savedToast": "Zielbereich aktualisiert", + "saveError": "Zielbereich konnte nicht aktualisiert werden" + } }, "catalog": { "total-cholesterol": "Gesamtcholesterin", diff --git a/messages/en.json b/messages/en.json index dd62e0249..c6704f0c4 100644 --- a/messages/en.json +++ b/messages/en.json @@ -6979,7 +6979,14 @@ "unhiddenToast": "Biomarker restored", "hideError": "Could not update the biomarker", "allHidden": "Every biomarker is hidden.", - "hiddenSection": "Hidden" + "hiddenSection": "Hidden", + "targetRange": { + "title": "Adjust target range", + "description": "Set the reference low and high for this marker. Leave a field empty for an open-ended bound.", + "save": "Save range", + "savedToast": "Target range updated", + "saveError": "Could not update the target range" + } }, "catalog": { "total-cholesterol": "Total cholesterol", diff --git a/messages/es.json b/messages/es.json index fddcd2093..458ffb5f1 100644 --- a/messages/es.json +++ b/messages/es.json @@ -6979,7 +6979,14 @@ "unhiddenToast": "Biomarcador restaurado", "hideError": "No se pudo actualizar el biomarcador", "allHidden": "Todos los biomarcadores están ocultos.", - "hiddenSection": "Ocultos" + "hiddenSection": "Ocultos", + "targetRange": { + "title": "Ajustar el rango objetivo", + "description": "Define la referencia mínima y máxima de este marcador. Deja un campo vacío para un límite abierto.", + "save": "Guardar rango", + "savedToast": "Rango objetivo actualizado", + "saveError": "No se pudo actualizar el rango objetivo" + } }, "catalog": { "total-cholesterol": "Colesterol total", diff --git a/messages/fr.json b/messages/fr.json index f4cb7352d..48f8b1b65 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -6979,7 +6979,14 @@ "unhiddenToast": "Biomarqueur restauré", "hideError": "Impossible de mettre à jour le biomarqueur", "allHidden": "Tous les biomarqueurs sont masqués.", - "hiddenSection": "Masqués" + "hiddenSection": "Masqués", + "targetRange": { + "title": "Ajuster la plage cible", + "description": "Définis la référence basse et haute de ce marqueur. Laisse un champ vide pour une limite ouverte.", + "save": "Enregistrer la plage", + "savedToast": "Plage cible mise à jour", + "saveError": "Impossible de mettre à jour la plage cible" + } }, "catalog": { "total-cholesterol": "Cholestérol total", diff --git a/messages/it.json b/messages/it.json index bb20e327f..acee72e93 100644 --- a/messages/it.json +++ b/messages/it.json @@ -6979,7 +6979,14 @@ "unhiddenToast": "Biomarcatore ripristinato", "hideError": "Impossibile aggiornare il biomarcatore", "allHidden": "Tutti i biomarcatori sono nascosti.", - "hiddenSection": "Nascosti" + "hiddenSection": "Nascosti", + "targetRange": { + "title": "Regola l'intervallo obiettivo", + "description": "Imposta il riferimento minimo e massimo per questo marcatore. Lascia un campo vuoto per un limite aperto.", + "save": "Salva intervallo", + "savedToast": "Intervallo obiettivo aggiornato", + "saveError": "Impossibile aggiornare l'intervallo obiettivo" + } }, "catalog": { "total-cholesterol": "Colesterolo totale", diff --git a/messages/pl.json b/messages/pl.json index 4129312fd..458028f3a 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -6979,7 +6979,14 @@ "unhiddenToast": "Biomarker przywrócony", "hideError": "Nie udało się zaktualizować biomarkera", "allHidden": "Wszystkie biomarkery są ukryte.", - "hiddenSection": "Ukryte" + "hiddenSection": "Ukryte", + "targetRange": { + "title": "Dostosuj zakres docelowy", + "description": "Ustaw dolną i górną wartość referencyjną dla tego markera. Pozostaw pole puste, aby granica była otwarta.", + "save": "Zapisz zakres", + "savedToast": "Zaktualizowano zakres docelowy", + "saveError": "Nie udało się zaktualizować zakresu docelowego" + } }, "catalog": { "total-cholesterol": "Cholesterol całkowity", diff --git a/src/components/labs/lab-biomarker-detail.tsx b/src/components/labs/lab-biomarker-detail.tsx index 3353791c2..06994ed7d 100644 --- a/src/components/labs/lab-biomarker-detail.tsx +++ b/src/components/labs/lab-biomarker-detail.tsx @@ -3,16 +3,18 @@ import { useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { FlaskConical, Pencil, Plus } from "lucide-react"; +import { FlaskConical, Pencil, Plus, SlidersHorizontal } from "lucide-react"; import { toast } from "sonner"; import { DeleteButton } from "@/components/data-list"; 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 { ResponsiveSheet } from "@/components/ui/responsive-sheet"; import { Skeleton } from "@/components/ui/skeleton"; -import { apiDelete, apiGet } from "@/lib/api/api-fetch"; +import { apiDelete, apiGet, apiPut } from "@/lib/api/api-fetch"; import { BIOMARKER_CATALOG } from "@/lib/labs/biomarker-catalog"; import { classifyReferenceRange } from "@/lib/labs/reference-range"; import { formatLabValue } from "@/lib/labs/format-value"; @@ -83,6 +85,12 @@ export function LabBiomarkerDetail({ biomarkerId }: { biomarkerId: string }) { const [addFooterEl, setAddFooterEl] = useState(null); const [editOpen, setEditOpen] = useState(false); const [editFooterEl, setEditFooterEl] = useState(null); + // v1.24 — focused "adjust target range" sheet. Edits only the reference + // bounds (the full marker editor lives behind the pencil); seeded from the + // marker each time it opens. + const [rangeOpen, setRangeOpen] = useState(false); + const [lowerInput, setLowerInput] = useState(""); + const [upperInput, setUpperInput] = useState(""); const { data: marker, isError: markerError } = useQuery({ queryKey: queryKeys.biomarkerDetail(biomarkerId), @@ -151,6 +159,33 @@ export function LabBiomarkerDetail({ biomarkerId }: { biomarkerId: string }) { onError: () => toast.error(t("labs.biomarker.deleteError")), }); + function openRange() { + setLowerInput(marker?.lowerBound != null ? String(marker.lowerBound) : ""); + setUpperInput(marker?.upperBound != null ? String(marker.upperBound) : ""); + setRangeOpen(true); + } + + const saveRange = useMutation({ + mutationFn: () => { + const lower = lowerInput.trim() === "" ? null : Number(lowerInput); + const upper = upperInput.trim() === "" ? null : Number(upperInput); + return apiPut(`/api/biomarkers/${biomarkerId}`, { + lowerBound: lower, + upperBound: upper, + }); + }, + onSuccess: () => { + toast.success(t("labs.biomarker.targetRange.savedToast")); + setRangeOpen(false); + queryClient.invalidateQueries({ + queryKey: queryKeys.biomarkerDetail(biomarkerId), + }); + queryClient.invalidateQueries({ queryKey: queryKeys.biomarkers() }); + queryClient.invalidateQueries({ queryKey: queryKeys.labResults() }); + }, + onError: () => toast.error(t("labs.biomarker.targetRange.saveError")), + }); + if (markerError || listError) { return (

@@ -202,6 +237,16 @@ export function LabBiomarkerDetail({ biomarkerId }: { biomarkerId: string }) { className="min-h-11 min-w-11 sm:min-h-9 sm:min-w-9" iconClassName="h-4 w-4" /> + + + + } + > +

+
+ + setLowerInput(e.target.value)} + /> +
+
+ + setUpperInput(e.target.value)} + /> +
+
+ {marker?.unit ? ( +

+ {t("labs.biomarker.form.rangeHint")} +

+ ) : null} + ); }