From d65ed8b0b655ef680682b32aa53ffe08bbccf2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 20 Jun 2026 16:50:23 +0200 Subject: [PATCH] fix(labs): move delete to the detail view and shrink overview tags Remove the inline trash icon from the lab list and tile rows; delete now lives only on the value detail view, beside Edit, so a stray tap on the overview cannot remove a biomarker. Make the reference-range badge beside each analyte heading quieter on the overview (smaller text, tighter padding, no icon) so it reads as a subtle qualifier instead of competing with the heading. --- src/components/labs/lab-list.tsx | 60 +++++-------------- src/components/labs/reference-range-badge.tsx | 22 ++++++- 2 files changed, 35 insertions(+), 47 deletions(-) diff --git a/src/components/labs/lab-list.tsx b/src/components/labs/lab-list.tsx index e4fcecf23..657d9b07c 100644 --- a/src/components/labs/lab-list.tsx +++ b/src/components/labs/lab-list.tsx @@ -2,17 +2,15 @@ import { useMemo } from "react"; import Link from "next/link"; -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useQuery } from "@tanstack/react-query"; import { ChevronRight, FlaskConical } from "lucide-react"; -import { toast } from "sonner"; -import { DeleteButton } from "@/components/data-list"; import { MedicationCardHeader } from "@/components/medications/medication-card-header"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { EmptyState } from "@/components/ui/empty-state"; import { Skeleton } from "@/components/ui/skeleton"; -import { apiDelete, apiGet } from "@/lib/api/api-fetch"; +import { apiGet } from "@/lib/api/api-fetch"; import { formatDate } from "@/lib/format"; import { formatReferenceRange } from "@/lib/labs/reference-range"; import { formatLabReading, formatLabValue } from "@/lib/labs/format-value"; @@ -77,22 +75,8 @@ function groupReadings(results: LabResultDto[]): MarkerGroup[] { export function LabList({ onAddFirst }: { onAddFirst?: () => void } = {}) { const { t } = useTranslations(); - const queryClient = useQueryClient(); const { prefs } = useModuleListPrefs("labs"); - // Delete a biomarker directly from the list (#41/#3). The endpoint's - // `onDelete: SetNull` FK keeps the readings and unlinks them; both the - // catalog and the result list invalidate so the row drops in the same tick. - const deleteBiomarker = useMutation({ - mutationFn: (id: string) => apiDelete(`/api/biomarkers/${id}`), - onSuccess: () => { - toast.success(t("labs.biomarker.deletedToast")); - queryClient.invalidateQueries({ queryKey: queryKeys.biomarkers() }); - queryClient.invalidateQueries({ queryKey: queryKeys.labResults() }); - }, - onError: () => toast.error(t("labs.biomarker.deleteError")), - }); - const listKey = queryKeys.labResultsList({ biomarkerId: undefined, analyte: undefined, @@ -181,23 +165,9 @@ export function LabList({ onAddFirst }: { onAddFirst?: () => void } = {}) { ); } - // Per-group biomarker delete (#41/#3), surfaced on both views. Only - // catalog-linked groups carry an id the endpoint can delete; legacy - // un-linked rows have no biomarker to remove. - function biomarkerDelete(group: MarkerGroup) { - if (!group.biomarkerId) return null; - const id = group.biomarkerId; - return ( - deleteBiomarker.mutate(id)} - title={t("labs.biomarker.deleteConfirmTitle")} - description={t("labs.biomarker.deleteConfirmDescription")} - confirmLabel={t("labs.biomarker.delete")} - className="size-9" - iconClassName="h-4 w-4" - /> - ); - } + // v1.18.10 (#3) — delete lives ONLY on the value detail view (next to Edit), + // not on these overview rows. The list/tile rows navigate; the trash icon was + // removed here so a stray tap can't delete a biomarker from the overview. // v1.18.6 (MOD-03) — compact list view: one bordered card holding tight // divided rows instead of a card per biomarker. This is the default view @@ -219,7 +189,10 @@ export function LabList({ onAddFirst }: { onAddFirst?: () => void } = {}) { {group.analyte} - +
@@ -229,8 +202,7 @@ export function LabList({ onAddFirst }: { onAddFirst?: () => void } = {}) {
); - // The delete control + chevron sit OUTSIDE the navigating Link - // so the delete confirm never also deep-links into the detail. + // The whole row navigates into the detail (where delete lives). return (
void } = {}) { />
)} - {biomarkerDelete(group)} ); })} @@ -268,9 +239,8 @@ export function LabList({ onAddFirst }: { onAddFirst?: () => void } = {}) { // v1.18.9 (#40) — card/tile view. The tile reuses the medication module's // `MedicationCardHeader` so a lab tile reads identically to a medication or // Vorsorge tile side-by-side: name on line 1, the reference-range badge as - // the line-1 chip, the panel as the category badge, and the delete control - // in the header's `actions` slot (a sibling of the header Link, so deleting - // never also deep-links). + // the line-1 chip, the panel as the category badge. v1.18.10 (#3) — no delete + // control on the tile; the trash icon lives only on the value detail view. return (
    {truncated ? ( @@ -288,13 +258,15 @@ export function LabList({ onAddFirst }: { onAddFirst?: () => void } = {}) { dose="" categoryLabel={group.panel ?? group.unit} nameChip={ - + } href={ group.biomarkerId ? `/labs/${group.biomarkerId}` : undefined } linkLabel={group.analyte} - actions={biomarkerDelete(group)} />
    diff --git a/src/components/labs/reference-range-badge.tsx b/src/components/labs/reference-range-badge.tsx index 2b4aa5a12..e5ceec512 100644 --- a/src/components/labs/reference-range-badge.tsx +++ b/src/components/labs/reference-range-badge.tsx @@ -22,17 +22,33 @@ import type { ReferenceRangeStatus } from "@/lib/validations/labs"; */ export function ReferenceRangeBadge({ status, + compact = false, }: { status: ReferenceRangeStatus; + /** + * v1.18.10 (#4) — a quieter, smaller treatment for the labs overview, where + * the badge sits beside the analyte heading and must not compete with it. + * The hero/detail view keeps the default (un-compact) size. + */ + compact?: boolean; }) { const { t } = useTranslations(); if (status === "unknown") return null; + // Compact: drop the icon, shrink the text and padding so the badge reads as a + // subtle qualifier of the heading rather than a second focal point. + const compactClass = compact + ? "h-auto gap-0.5 px-1.5 py-0 text-[0.625rem] font-normal [&>svg]:size-2.5" + : ""; + if (status === "in-range") { return ( - - + + {compact ? null : } {t("labs.range.inRange")} ); @@ -44,7 +60,7 @@ export function ReferenceRangeBadge({ const label = status === "below" ? t("labs.range.below") : t("labs.range.above"); return ( - + {label}