diff --git a/src/components/medications/MedicationCardHeader.tsx b/src/components/medications/MedicationCardHeader.tsx new file mode 100644 index 000000000..df201dcb2 --- /dev/null +++ b/src/components/medications/MedicationCardHeader.tsx @@ -0,0 +1,64 @@ +"use client"; + +import { type ReactNode } from "react"; + +import { Badge } from "@/components/ui/badge"; +import { CardHeader, CardTitle } from "@/components/ui/card"; + +/** + * v1.4.28 FB-G1 — shared medication-list row header. + * + * The generic `` and the `` carry + * the same two-line row shape on the medications list page. Both rows + * route their title + dose + drug class through this primitive so the + * surface stays one consistent shape: + * + * Line 1: `{name} {dose}` — bold, `text-lg` + * Line 2: `{categoryLabel}` outline badge + optional state badges + * + * The trailing `actions` slot carries the icon-button cluster (history + + * edit) on the right of the row. State badges (without-notification, + * paused-since, inactive) ride on line 2 alongside the class label so a + * narrow viewport keeps a single inline run. + * + * The GLP-1 row previously surfaced a `` glyph + middle-dot + * separator on line 1 and demoted the dose to a muted inline span. This + * primitive folds it into the canonical two-line shape; the syringe icon + * and the dot separator are gone from the list row. + */ +export interface MedicationCardHeaderProps { + name: string; + dose: string; + categoryLabel: string; + stateBadges?: ReactNode; + actions?: ReactNode; +} + +export function MedicationCardHeader({ + name, + dose, + categoryLabel, + stateBadges, + actions, +}: MedicationCardHeaderProps) { + return ( + +
+
+ + {name} {dose} + +
+ + {categoryLabel} + + {stateBadges} +
+
+ {actions ? ( +
{actions}
+ ) : null} +
+
+ ); +} diff --git a/src/components/medications/__tests__/medication-card-glp1.test.tsx b/src/components/medications/__tests__/medication-card-glp1.test.tsx index 363c0282c..15fb9d0a9 100644 --- a/src/components/medications/__tests__/medication-card-glp1.test.tsx +++ b/src/components/medications/__tests__/medication-card-glp1.test.tsx @@ -178,8 +178,10 @@ describe(" — GLP-1 variant rendering", () => { ); expect(html).toContain("GLP-1 injection"); - // The Syringe lucide icon renders an SVG with this class. - expect(html).toMatch(/lucide-syringe/i); + // v1.4.28 FB-G1 — the Syringe glyph + middle-dot separator on the + // list row are gone. The list row reads as the canonical two-line + // shape: `{name} {dose}` on line 1, class label on line 2. + expect(html).not.toMatch(/lucide-syringe/i); }); it("renders the default MedicationCard when treatmentClass is null/undefined (back-compat)", () => { @@ -204,7 +206,7 @@ describe(" — GLP-1 variant rendering", () => { expect(html).not.toContain("Dose history"); }); - it("shows drug name + current dose ('Mounjaro · 7.5 mg')", () => { + it("shows drug name + current dose on line 1 ('Mounjaro 7.5 mg')", () => { const client = makeClient(); seedCompliance(client, med7p5.id); seedGlp1Details(client, med7p5.id, {}); @@ -216,9 +218,10 @@ describe(" — GLP-1 variant rendering", () => { expect(html).toContain("Mounjaro"); expect(html).toContain("7.5 mg"); - // Card title joins the two with the middle-dot separator the - // glp1Headline copy uses. - expect(html).toContain("·"); + // v1.4.28 FB-G1 — the GLP-1 row drops the middle-dot separator and + // surfaces `{name} {dose}` together on line 1 via the shared + // `` primitive. The dot separator is gone. + expect(html).toContain("Mounjaro 7.5 mg"); }); it("shows last + next injection labels with localised weekday", () => { diff --git a/src/components/medications/glp1-medication-card.tsx b/src/components/medications/glp1-medication-card.tsx index 2018fb08e..47da29613 100644 --- a/src/components/medications/glp1-medication-card.tsx +++ b/src/components/medications/glp1-medication-card.tsx @@ -11,12 +11,12 @@ import { Pencil, SkipForward, Stethoscope, - Syringe, } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Card, CardContent } from "@/components/ui/card"; +import { MedicationCardHeader } from "@/components/medications/MedicationCardHeader"; import { Progress } from "@/components/ui/progress"; import { useTranslations, useFormatters } from "@/lib/i18n/context"; import { invalidateKeys, medicationDependentKeys } from "@/lib/query-keys"; @@ -241,60 +241,57 @@ export function Glp1MedicationCard({ }); } + const stateBadges = ( + <> + {!medication.notificationsEnabled && ( + + {t("medications.withoutNotification")} + + )} + {!medication.active && ( + + {medication.pausedAt + ? `${t("medications.pausedSince")} ${formatDateTime(medication.pausedAt)}` + : t("medications.inactive")} + + )} + + ); + + const headerActions = ( + <> + + + + ); + return ( - -
-
- - - {medication.name} - - · {medication.dose} - - -
- - {t("medications.treatmentClassGlp1")} - - {!medication.notificationsEnabled && ( - - {t("medications.withoutNotification")} - - )} - {!medication.active && ( - - {medication.pausedAt - ? `${t("medications.pausedSince")} ${formatDateTime(medication.pausedAt)}` - : t("medications.inactive")} - - )} -
-
-
- - -
-
-
+ {/* Injection state — last + next */} diff --git a/src/components/medications/medication-card.tsx b/src/components/medications/medication-card.tsx index 183806899..f4e7083ef 100644 --- a/src/components/medications/medication-card.tsx +++ b/src/components/medications/medication-card.tsx @@ -5,7 +5,8 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Progress } from "@/components/ui/progress"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Card, CardContent } from "@/components/ui/card"; +import { MedicationCardHeader } from "@/components/medications/MedicationCardHeader"; import { parseScheduleRecurrence } from "@/lib/medication-schedule"; import { formatTimeWindowRange } from "@/lib/time-window-format"; import { formatDateTime, formatTime } from "@/lib/format"; @@ -367,59 +368,61 @@ export function MedicationCard({ medication, onEdit }: MedicationCardProps) { return formatDateTime(value); } + const stateBadges = ( + <> + {!medication.notificationsEnabled && ( + + {t("medications.withoutNotification")} + + )} + {!medication.active && ( + + {medication.pausedAt + ? `${t("medications.pausedSince")} ${formatDateTime(medication.pausedAt)}` + : t("medications.inactive")} + + )} + + ); + + const headerActions = ( + <> + {/* Phase A5: bumped from `h-8 w-8` (32px) to `min-h-11 + min-w-11` (44px) so these meet the WCAG 2.5.5 minimum + tap-target on mobile without the icon glyph itself + changing size. */} + + + + ); + return ( - -
-
- {medication.name} -
- {medication.dose} - - {categoryLabel} - - {!medication.notificationsEnabled && ( - - {t("medications.withoutNotification")} - - )} - {!medication.active && ( - - {medication.pausedAt - ? `${t("medications.pausedSince")} ${formatDateTime(medication.pausedAt)}` - : t("medications.inactive")} - - )} -
-
-
- {/* Phase A5: bumped from `h-8 w-8` (32px) to `min-h-11 - min-w-11` (44px) so these meet the WCAG 2.5.5 minimum - tap-target on mobile without the icon glyph itself - changing size. */} - - -
-
-
+ {/* Status, last & next intake info */}