diff --git a/src/components/clinical-dashboard/medication-prescribing-workspace.tsx b/src/components/clinical-dashboard/medication-prescribing-workspace.tsx index e9fea8a11..85a5aa68b 100644 --- a/src/components/clinical-dashboard/medication-prescribing-workspace.tsx +++ b/src/components/clinical-dashboard/medication-prescribing-workspace.tsx @@ -535,7 +535,7 @@ function MedicationResults({ - + {result.indication} {showMatchBadge || result.match !== "Exact clinical fit" || row.badges.length > 0 ? ( @@ -548,13 +548,13 @@ function MedicationResults({ ) : null} - + {result.dose} - {result.action} + {result.action} {result.href ? (

-

{result.indication}

+

+ {result.indication} +

{showMatchBadge || result.match !== "Exact clinical fit" || row.badges.length > 0 ? (
@@ -627,10 +629,10 @@ function MedicationResults({
) : null}
- {result.dose} + {result.dose}
-

+

{result.action}

diff --git a/src/components/clinical-dashboard/medication-record-page.tsx b/src/components/clinical-dashboard/medication-record-page.tsx index dc1aa5526..1b4f02e85 100644 --- a/src/components/clinical-dashboard/medication-record-page.tsx +++ b/src/components/clinical-dashboard/medication-record-page.tsx @@ -14,14 +14,15 @@ import { Gauge, Lock, Pill, + ShieldAlert, ShieldCheck, - Sparkles, + Timer, type LucideIcon, } from "lucide-react"; import Link from "next/link"; import { useMemo, useRef, useState, type CSSProperties, type KeyboardEvent as ReactKeyboardEvent } from "react"; -import { BadgeCluster, clinicalBadgeToneClass } from "@/components/clinical-dashboard/clinical-badge"; +import { BadgeCluster } from "@/components/clinical-dashboard/clinical-badge"; import { MedicationConsiderations } from "@/components/clinical-dashboard/medication-considerations"; import { PatientProfilePanel } from "@/components/clinical-dashboard/patient-profile-panel"; import { useMedicationDetail } from "@/components/clinical-dashboard/use-medication-catalog"; @@ -30,11 +31,27 @@ import { medicationAccessFields, medicationIdentityBadges, medicationRowBadges, - medicationStatTone, type MedicationGovernance, } from "@/lib/medication-badges"; -import { medicationDetailTiles, type MedicationRecord, type MedicationSection } from "@/lib/medications"; -import { cn, EmptyState, LoadingPanel, pageContainer, toneDanger } from "@/components/ui-primitives"; +import { + medicationHeroMetrics, + medicationIndication, + type MedicationHeroMetric, + type MedicationQuickRow, + type MedicationRecord, + type MedicationSection, +} from "@/lib/medications"; +import type { SemanticTone } from "@/lib/semantic-tone"; +import { + cn, + EmptyState, + LoadingPanel, + pageContainer, + toneDanger, + toneInfo, + toneSuccess, + toneWarning, +} from "@/components/ui-primitives"; const sectionIcons: Record = { dose: CalendarDays, @@ -74,83 +91,79 @@ function medicationAccentStyle(accent: string | undefined): CSSProperties { } as CSSProperties; } -// Icon + categorical chip per detail tile (index-aligned with medicationDetailTiles: -// Prescribing answer / Dosing / Dose ceiling / Avoid). The danger tile is toned -// separately below. -const detailTileDecor: Array<{ icon: LucideIcon; chip: string }> = [ - { - icon: Sparkles, - chip: "border-[color:var(--type-source-border)] bg-[color:var(--type-source-soft)] text-[color:var(--type-source)]", +// Tone-driven hero metric tile. Colour comes only from the metric's semantic tone +// (medicationStatTone, via medicationHeroMetrics) so it honours the #659 contract: +// green = success, amber = caution, red = safety, teal = primary/evidence. The +// value stays in a high-contrast heading colour on every tone so text never sits +// on a same-hue wash (the readability lesson from #659) — the border, soft fill, +// label and icon chip carry the colour. +const heroToneTile: Record = { + clinical: { + card: "border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent-soft)]", + chip: "border-[color:var(--clinical-accent)]/25 bg-[color:var(--surface)] text-[color:var(--clinical-accent)]", + label: "text-[color:var(--clinical-accent)]", + value: "text-[color:var(--text-heading)]", }, - { - icon: CalendarDays, - chip: "border-[color:var(--type-document-border)] bg-[color:var(--type-document-soft)] text-[color:var(--type-document)]", + danger: { + card: toneDanger, + chip: "border-[color:var(--danger)]/25 bg-[color:var(--surface)] text-[color:var(--danger)]", + label: "text-[color:var(--danger)]", + value: "text-[color:var(--danger-text)]", }, - { - icon: Gauge, - chip: "border-[color:var(--type-service-border)] bg-[color:var(--type-service-soft)] text-[color:var(--type-service)]", + warning: { + card: toneWarning, + chip: "border-[color:var(--warning)]/25 bg-[color:var(--surface)] text-[color:var(--warning)]", + label: "text-[color:var(--warning)]", + value: "text-[color:var(--text-heading)]", }, - { icon: Ban, chip: "border-[color:var(--danger)]/25 bg-[color:var(--danger-soft)] text-[color:var(--danger)]" }, -]; - -function DetailTile({ - label, - value, - meta, - danger = false, - icon: Icon, - chip, -}: { - label: string; - value: string; - meta?: string; - danger?: boolean; - icon: LucideIcon; - chip: string; -}) { + success: { + card: toneSuccess, + chip: "border-[color:var(--success)]/25 bg-[color:var(--surface)] text-[color:var(--success)]", + label: "text-[color:var(--success)]", + value: "text-[color:var(--text-heading)]", + }, + info: { + card: toneInfo, + chip: "border-[color:var(--info-border)] bg-[color:var(--surface)] text-[color:var(--info)]", + label: "text-[color:var(--info)]", + value: "text-[color:var(--text-heading)]", + }, + neutral: { + card: "border-[color:var(--border)] bg-[color:var(--surface-raised)]", + chip: "border-[color:var(--border)] bg-[color:var(--surface-subtle)] text-[color:var(--text-muted)]", + label: "text-[color:var(--text-muted)]", + value: "text-[color:var(--text-heading)]", + }, +}; + +// Icon keyed to the metric's meaning: a gauge for the dose ceiling, a timer for +// time-based metrics (half-life / onset / duration), and a tone-appropriate shield +// or alert for everything else (risk & caution flags). Rendered directly (rather +// than assigning the component to a local) so it stays a static component. +function HeroMetricIcon({ metric }: { metric: MedicationHeroMetric }) { + const label = metric.label.toLowerCase(); + const iconClass = "h-3.5 w-3.5"; + if (/dose|ceiling|\bmax\b/.test(label)) return