feat(dashboard): GLP-1 status tile with next-injection + weight-delta caption

Renders a dedicated tile for users on a GLP-1 receptor agonist
(Mounjaro, Ozempic, Wegovy, Zepbound, …) — the new W4d schema enum
`Medication.treatmentClass = GLP1` is the discriminator. Tile self-
hides when no active GLP-1 med exists so accounts without one see no
visual change.

The tile surfaces:
  - Drug name + current dose ("Mounjaro 7.5mg") — sourced from the
    `MedicationDoseChange` titration history (latest row wins), so a
    Mounjaro user titrating up 2.5 → 5 → 7.5 → 10 mg sees their
    current step, not the starting dose.
  - Last injection date + weekday ("Sun, 05/10")
  - Next injection date + weekday + countdown
    ("Sun, 05/17 (in 3 days)") — predicted from the existing schedule
    cadence
  - Weight delta since starting the therapy ("−4.2 kg since start"
    / "−4,2 kg seit Beginn") — green chip on loss, orange on gain,
    muted minus on flat (within ±0.1 kg)
  - A compact weight chart with vertical injection-day markers via
    the new `<HealthChart verticalMarkers={…} />` prop

New server endpoint `GET /api/dashboard/glp1` aggregates the data:
the heavy lifting (current dose / last + next injection / titration
history / inventory math) is delegated to the existing
`buildGlp1SnapshotBlock` so the Coach's GLP-1-aware reply path and
the dashboard tile read from the same shape. The route adds the UI-
facing extras (weight series since first dose-change, current vs
start, injection date keys for the markers) on top.

Mount: dashboard mother page (`src/app/page.tsx`) mounts the tile
below the AI insights preview and above the chart row — high enough
that a Mounjaro user sees their dose-response without scrolling past
BP / pulse, but below the AI preview which stays the scroll-stop
hero anchor.

Constraint respected (Marc directive 2026-05-14): the chart lives on
the Dashboard tile and the Insights /medikamente sub-page only; the
Medications page itself stays chart-free.

i18n keys: dashboard.glp1.{title, lastInjection, nextInjection,
weightDelta, inDays, empty.title, empty.description} in EN + DE.

Tests pin 7 scenarios: hidden when no active GLP-1, hidden when meds
array is empty, skeleton during fetch, full render for a Mounjaro 7.5
mg weekly profile, latest titration step wins (10 mg, not 2.5 mg),
fallback to name-only when no dose history exists, German copy under
the de locale.
This commit is contained in:
Marc-André Bombeck committed 2026-05-14 13:37:29 +02:00
1 parent b8a1c1833b
commit 88efd42755
7 files changed
+882 -2

No files matched your search

+12 -1
View File
@@ -270,7 +270,18 @@
"layoutChartColumn": "Chart",
"emptyTitle": "Dein Dashboard ist leer",
"emptyDescription": "Erfasse den ersten Messwert oder Stimmungseintrag. Kacheln und Charts erscheinen automatisch, sobald Daten vorhanden sind.",
"emptyAddMeasurement": "Ersten Messwert erfassen"
"emptyAddMeasurement": "Ersten Messwert erfassen",
"glp1": {
"title": "GLP-1-Therapie",
"lastInjection": "Letzte Injektion",
"nextInjection": "Nächste Injektion",
"weightDelta": "seit Beginn",
"inDays": "in {count} Tagen",
"empty": {
"title": "Kein aktives GLP-1-Medikament",
"description": "Lege einen GLP-1-Rezeptoragonisten an (Mounjaro, Ozempic, Wegovy, …), um Dosis, Zeitplan und Gewichtsverlauf hier zu sehen."
}
}
},
"measurements": {
"title": "Messungen",
+12 -1
View File
@@ -270,7 +270,18 @@
"layoutChartColumn": "Chart",
"emptyTitle": "Your dashboard is empty",
"emptyDescription": "Log your first measurement or mood entry. Tiles and charts appear automatically as data lands.",
"emptyAddMeasurement": "Add your first measurement"
"emptyAddMeasurement": "Add your first measurement",
"glp1": {
"title": "GLP-1 therapy",
"lastInjection": "Last injection",
"nextInjection": "Next injection",
"weightDelta": "since start",
"inDays": "in {count} days",
"empty": {
"title": "No active GLP-1 medication",
"description": "Add a GLP-1 receptor agonist (Mounjaro, Ozempic, Wegovy, …) to surface dose, schedule, and weight response here."
}
}
},
"measurements": {
"title": "Measurements",