mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
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.
This commit is contained in:
1 parent
f751f006de
commit
02d527720a
7 files changed
+151
-8
No files matched your search
+8
-1
@@ -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",
|
||||
|
||||
+8
-1
@@ -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",
|
||||
|
||||
+8
-1
@@ -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",
|
||||
|
||||
+8
-1
@@ -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",
|
||||
|
||||
+8
-1
@@ -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",
|
||||
|
||||
+8
-1
@@ -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",
|
||||
|
||||
@@ -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<HTMLDivElement | null>(null);
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
const [editFooterEl, setEditFooterEl] = useState<HTMLDivElement | null>(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 (
|
||||
<p className="text-destructive py-8 text-center text-sm">
|
||||
@@ -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"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="min-h-11 min-w-11 sm:min-h-9 sm:min-w-9"
|
||||
onClick={openRange}
|
||||
disabled={!marker}
|
||||
aria-label={t("labs.biomarker.targetRange.title")}
|
||||
>
|
||||
<SlidersHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setAddOpen(true)}
|
||||
// v1.18.10 (W10) — on the narrowest phones the h1 + Edit + Delete +
|
||||
@@ -333,6 +378,62 @@ export function LabBiomarkerDetail({ biomarkerId }: { biomarkerId: string }) {
|
||||
/>
|
||||
) : null}
|
||||
</ResponsiveSheet>
|
||||
|
||||
<ResponsiveSheet
|
||||
open={rangeOpen}
|
||||
onOpenChange={setRangeOpen}
|
||||
title={t("labs.biomarker.targetRange.title")}
|
||||
description={t("labs.biomarker.targetRange.description")}
|
||||
footer={
|
||||
<div className="flex w-full justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setRangeOpen(false)}
|
||||
disabled={saveRange.isPending}
|
||||
>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => saveRange.mutate()}
|
||||
disabled={saveRange.isPending}
|
||||
>
|
||||
{t("labs.biomarker.targetRange.save")}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="biomarker-lower-bound">
|
||||
{t("labs.biomarker.form.lowerBound")}
|
||||
</Label>
|
||||
<Input
|
||||
id="biomarker-lower-bound"
|
||||
type="number"
|
||||
inputMode="decimal"
|
||||
value={lowerInput}
|
||||
onChange={(e) => setLowerInput(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="biomarker-upper-bound">
|
||||
{t("labs.biomarker.form.upperBound")}
|
||||
</Label>
|
||||
<Input
|
||||
id="biomarker-upper-bound"
|
||||
type="number"
|
||||
inputMode="decimal"
|
||||
value={upperInput}
|
||||
onChange={(e) => setUpperInput(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{marker?.unit ? (
|
||||
<p className="text-muted-foreground mt-2 text-xs">
|
||||
{t("labs.biomarker.form.rangeHint")}
|
||||
</p>
|
||||
) : null}
|
||||
</ResponsiveSheet>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user