mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
feat(labs): describe each biomarker under its heading
Surface a short, factual subtitle under every catalog-linked lab marker on the Labs page, mirroring the descriptive line the Insights overview carries under its metric headings. The text states what each biomarker measures, sourced from the seed catalog via a new labs.catalog.desc.<slug> i18n map in all six locales. A reading's analyte resolves back to its catalog slug by a case- and whitespace-insensitive match against the localised catalog name; free-text markers with no catalog match carry no subtitle rather than a fabricated one. Rendered in both the compact list view and the card/tile view.
This commit is contained in:
1 parent
6d990b769c
commit
3caf95160d
8 files changed
+266
-51
No files matched your search
@@ -11,6 +11,7 @@ import { Card, CardContent } from "@/components/ui/card";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { apiGet } from "@/lib/api/api-fetch";
|
||||
import { resolveCatalogSlug } from "@/lib/labs/biomarker-catalog";
|
||||
import { formatDate } from "@/lib/format";
|
||||
import { formatReferenceRange } from "@/lib/labs/reference-range";
|
||||
import { formatLabReading, formatLabValue } from "@/lib/labs/format-value";
|
||||
@@ -77,6 +78,14 @@ export function LabList({ onAddFirst }: { onAddFirst?: () => void } = {}) {
|
||||
const { t } = useTranslations();
|
||||
const { prefs } = useModuleListPrefs("labs");
|
||||
|
||||
// v1.22 — a short, factual line under each marker heading describing what the
|
||||
// biomarker measures, sourced from the catalog via i18n. Free-text markers
|
||||
// (no catalog match) carry no subtitle rather than a fabricated one.
|
||||
const describeMarker = (analyte: string): string | null => {
|
||||
const slug = resolveCatalogSlug(analyte, (s) => t(`labs.catalog.${s}`));
|
||||
return slug ? t(`labs.catalog.desc.${slug}`) : null;
|
||||
};
|
||||
|
||||
const listKey = queryKeys.labResultsList({
|
||||
biomarkerId: undefined,
|
||||
analyte: undefined,
|
||||
@@ -183,6 +192,7 @@ export function LabList({ onAddFirst }: { onAddFirst?: () => void } = {}) {
|
||||
<Card>
|
||||
<CardContent className="divide-border divide-y p-0">
|
||||
{groups.map((group) => {
|
||||
const description = describeMarker(group.analyte);
|
||||
const inner = (
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex flex-wrap items-baseline gap-x-2 text-sm">
|
||||
@@ -194,6 +204,11 @@ export function LabList({ onAddFirst }: { onAddFirst?: () => void } = {}) {
|
||||
compact
|
||||
/>
|
||||
</div>
|
||||
{description ? (
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{description}
|
||||
</p>
|
||||
) : null}
|
||||
<div className="text-muted-foreground flex flex-wrap items-center gap-x-2 text-xs">
|
||||
<span className="text-foreground font-semibold tabular-nums">
|
||||
{formatLabReading(group.latest)}
|
||||
@@ -250,61 +265,69 @@ export function LabList({ onAddFirst }: { onAddFirst?: () => void } = {}) {
|
||||
</p>
|
||||
</li>
|
||||
) : null}
|
||||
{groups.map((group) => (
|
||||
<li key={group.key} className="contents">
|
||||
<Card className="h-full gap-3">
|
||||
<MedicationCardHeader
|
||||
name={group.analyte}
|
||||
dose=""
|
||||
categoryLabel={group.panel ?? group.unit}
|
||||
nameChip={
|
||||
<ReferenceRangeBadge
|
||||
status={group.latest.rangeStatus}
|
||||
compact
|
||||
/>
|
||||
}
|
||||
href={
|
||||
group.biomarkerId ? `/labs/${group.biomarkerId}` : undefined
|
||||
}
|
||||
linkLabel={group.analyte}
|
||||
/>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="text-muted-foreground flex flex-wrap items-center gap-x-2 text-sm">
|
||||
<span className="text-foreground font-semibold tabular-nums">
|
||||
{formatLabReading(group.latest)}
|
||||
</span>
|
||||
{group.latest.value !== null &&
|
||||
(group.latest.referenceLow !== null ||
|
||||
group.latest.referenceHigh !== null) ? (
|
||||
<span className="text-xs">
|
||||
{t("labs.referenceLabel")}{" "}
|
||||
{formatReferenceRange(
|
||||
group.latest.referenceLow,
|
||||
group.latest.referenceHigh,
|
||||
formatLabValue,
|
||||
)}
|
||||
{groups.map((group) => {
|
||||
const description = describeMarker(group.analyte);
|
||||
return (
|
||||
<li key={group.key} className="contents">
|
||||
<Card className="h-full gap-3">
|
||||
<MedicationCardHeader
|
||||
name={group.analyte}
|
||||
dose=""
|
||||
categoryLabel={group.panel ?? group.unit}
|
||||
nameChip={
|
||||
<ReferenceRangeBadge
|
||||
status={group.latest.rangeStatus}
|
||||
compact
|
||||
/>
|
||||
}
|
||||
href={
|
||||
group.biomarkerId ? `/labs/${group.biomarkerId}` : undefined
|
||||
}
|
||||
linkLabel={group.analyte}
|
||||
/>
|
||||
<CardContent>
|
||||
{description ? (
|
||||
<p className="text-muted-foreground mb-2 text-sm">
|
||||
{description}
|
||||
</p>
|
||||
) : null}
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="text-muted-foreground flex flex-wrap items-center gap-x-2 text-sm">
|
||||
<span className="text-foreground font-semibold tabular-nums">
|
||||
{formatLabReading(group.latest)}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="text-xs">
|
||||
{formatDate(group.latest.takenAt)}
|
||||
</span>
|
||||
{group.readings.length > 1 ? (
|
||||
{group.latest.value !== null &&
|
||||
(group.latest.referenceLow !== null ||
|
||||
group.latest.referenceHigh !== null) ? (
|
||||
<span className="text-xs">
|
||||
{t("labs.referenceLabel")}{" "}
|
||||
{formatReferenceRange(
|
||||
group.latest.referenceLow,
|
||||
group.latest.referenceHigh,
|
||||
formatLabValue,
|
||||
)}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="text-xs">
|
||||
{t("labs.readingsCount", {
|
||||
count: group.readings.length,
|
||||
})}
|
||||
{formatDate(group.latest.takenAt)}
|
||||
</span>
|
||||
) : null}
|
||||
{group.readings.length > 1 ? (
|
||||
<span className="text-xs">
|
||||
{t("labs.readingsCount", {
|
||||
count: group.readings.length,
|
||||
})}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<LabTrendSparkline
|
||||
values={group.readings.map((r) => r.value)}
|
||||
/>
|
||||
</div>
|
||||
<LabTrendSparkline
|
||||
values={group.readings.map((r) => r.value)}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</li>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user