From 18e36e32266a2b21ef79c573631f16634a66541a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 5 Jun 2026 12:28:38 +0200 Subject: [PATCH] insights(medications): drop the GLP-1 dose-listing timeline and densify the compliance tiles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the chronological GLP-1 therapy-event list from the medications insight page — it duplicated data already on the per-medication detail page and added no read on this surface. The page spine collapses to compliance grid -> target -> assessment. Route each per-medication compliance card header through the canonical TileHeader (Pill glyph + med name as the heading) and fold the dose, category, and streak into one compact muted line. Tighten the card gap, header padding, progress-bar height, and content spacing so the same data (7d/30d bars, taken/skipped/missed, heatmap, per-med sentence) reads in noticeably less vertical space, consistent with the rest of the harmonised Insights tiles. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app/insights/medications/page.tsx | 60 +++--- src/components/insights/therapy-timeline.tsx | 213 ------------------- 2 files changed, 29 insertions(+), 244 deletions(-) delete mode 100644 src/components/insights/therapy-timeline.tsx diff --git a/src/app/insights/medications/page.tsx b/src/app/insights/medications/page.tsx index 5c5e3227e..7af41c192 100644 --- a/src/app/insights/medications/page.tsx +++ b/src/app/insights/medications/page.tsx @@ -10,7 +10,7 @@ import { queryKeys } from "@/lib/query-keys"; import { useTranslations } from "@/lib/i18n/context"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { Card, CardContent } from "@/components/ui/card"; +import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { EmptyState } from "@/components/ui/empty-state"; import { Progress } from "@/components/ui/progress"; import { ComplianceHeatmap } from "@/components/charts/compliance-heatmap"; @@ -18,8 +18,7 @@ import { CoachLaunchButton } from "@/components/insights/coach-launch-button"; import { InsightStatusCard } from "@/components/insights/insight-status-card"; import { MetricTargetSummary } from "@/components/insights/metric-target-summary"; import { SubPageShell } from "@/components/insights/sub-page-shell"; -import { TherapyTimeline } from "@/components/insights/therapy-timeline"; -import { MedicationCardHeader } from "@/components/medications/MedicationCardHeader"; +import { TileHeader } from "@/components/insights/tile-header"; /** * v1.4.25 W4 — `/insights/medications`. @@ -203,33 +202,37 @@ export default function InsightsMedikamentePage() { > {medications.map((med) => { const medicationSummary = medicationSummaryById.get(med.id); - // UI-H3 — route every medication-list row (medications page - // + insights/medications) through `` - // so the surface stays one shape: `{name} {dose}` on line 1 - // + outline category badge on line 2. The streak chip rides - // the `stateBadges` slot so a strong streak still surfaces. + // v1.12.6 — the per-medication compliance card harmonises with + // the rest of the Insights tiles: its header runs through the + // canonical `` (Pill glyph + med name as the white + // heading), and the dose + category + streak fold into one + // compact muted line below it. Every datum the previous, taller + // card carried (7d/30d bars, taken/skipped/missed, heatmap, + // per-med assistant sentence) stays — only the vertical rhythm + // tightens. const categoryLabel = med.category === "BLOOD_PRESSURE" ? t("medications.categoryBloodPressure") : med.category === "VITAMIN" ? t("medications.categoryVitamin") : t("medications.categoryOther"); - const streakBadge = - med.streak > 0 ? ( - - {t("insights.dayStreak", { count: med.streak })} - - ) : null; return ( - - - -
+ + + +
+ {med.dose} + + {categoryLabel} + {med.streak > 0 ? ( + + {t("insights.dayStreak", { count: med.streak })} + + ) : null} +
+
+ +
{t("insights.compliance7d")} {med.compliance7}% @@ -238,18 +241,18 @@ export default function InsightsMedikamentePage() { accessible name (Lighthouse a11y 91/94). */}
-
+
{t("insights.compliance30d")} {med.compliance30}%
@@ -283,11 +286,6 @@ export default function InsightsMedikamentePage() { - {/* v1.4.25 W4d — GLP-1 therapy timeline. Self-hides for users - without an active GLP-1 medication, so the page collapses - back to the compliance grid for everyone else. */} - - {/* v1.12.2 — the assessment is the LAST block on every bespoke metric-detail page, matching the canonical spine the generic scaffold renders. This card reads the medication-compliance diff --git a/src/components/insights/therapy-timeline.tsx b/src/components/insights/therapy-timeline.tsx deleted file mode 100644 index 4e4a765ab..000000000 --- a/src/components/insights/therapy-timeline.tsx +++ /dev/null @@ -1,213 +0,0 @@ -"use client"; - -import { useQuery } from "@tanstack/react-query"; -import { ArrowDown, ArrowUp, Pill, Syringe } from "lucide-react"; - -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { useTranslations, useFormatters } from "@/lib/i18n/context"; -import { - describeInjectionSite, - type InjectionSiteKey, -} from "@/lib/medications/injection-sites"; - -/** - * v1.4.25 W4d — therapy-timeline view for /insights/medications. - * - * Renders a chronological list of GLP-1 therapy events: dose changes, - * injections (with site if recorded), inventory events, side-effect tag - * occurrences. Pure list view — no chart — so the medications-page - * "no chart on the medication card" rule stays consistent across all - * GLP-1 surfaces; the dose curve lives on the Dashboard tile only. - * - * Self-hides for users with no GLP-1 medications (the parent route does - * the gating via a non-200 from the API endpoint). - */ - -interface TimelineEntry { - date: string; - kind: "dose-change" | "injection" | "inventory" | "side-effect"; - medicationName?: string; - doseValue?: number; - doseUnit?: string; - doseDelta?: "up" | "down" | null; - note?: string | null; - injectionSite?: InjectionSiteKey | null; - inventoryDelta?: number; - reason?: string; - tags?: string[]; -} - -interface TherapyTimelineResponse { - hasGlp1: boolean; - entries: TimelineEntry[]; -} - -interface TherapyTimelineProps { - /** Optional cap — defaults to the API's default (60 entries). */ - limit?: number; -} - -export function TherapyTimeline({ limit }: TherapyTimelineProps) { - const { t } = useTranslations(); - const fmt = useFormatters(); - - const { data, isLoading } = useQuery({ - queryKey: ["insights", "glp1-timeline", limit ?? "default"], - queryFn: async () => { - const url = limit - ? `/api/insights/glp1-timeline?limit=${limit}` - : "/api/insights/glp1-timeline"; - const res = await fetch(url); - if (!res.ok) return null; - const json = await res.json(); - return json.data as TherapyTimelineResponse; - }, - staleTime: 60 * 1000, - }); - - if (isLoading || !data || !data.hasGlp1) return null; - if (data.entries.length === 0) { - return ( - - - - - {t("insights.therapyTimeline.title")} - - - -

- {t("insights.therapyTimeline.empty")} -

-
-
- ); - } - - return ( - - - - - {t("insights.therapyTimeline.title")} - -

- {t("insights.therapyTimeline.description")} -

-
- -
    - {data.entries.map((entry) => ( -
  1. -
  2. - ))} -
-
-
- ); -} - -function colourForKind(kind: TimelineEntry["kind"]): string { - switch (kind) { - case "dose-change": - return "var(--dracula-purple)"; - case "injection": - return "var(--dracula-cyan)"; - case "inventory": - return "var(--dracula-yellow)"; - case "side-effect": - return "var(--dracula-orange)"; - } -} - -function renderEntryLine( - entry: TimelineEntry, - t: (key: string, params?: Record) => string, -): React.ReactNode { - const drug = entry.medicationName ?? ""; - switch (entry.kind) { - case "dose-change": { - const arrow = - entry.doseDelta === "up" ? ( -