feat(insights): scaffold 6 metric sub-pages with shared layout shell

Carves the v1.4.24 monolithic /insights page into routed sub-pages
under /insights/{blutdruck,gewicht,puls,stimmung,medikamente,bmi}.
Each sub-page renders its metric chart with the per-chart cog
(chartKey from CHART_OVERLAY_KEYS) and the existing AI status card;
empty states surface a CTA where the chart can't compute (BMI without
height, mood with zero entries, medication adherence without a tracked
medication).

The sticky tab strip moves into src/app/insights/layout.tsx via the
new <InsightsLayoutShell>, which also owns the advisor query so the
regenerate affordance stays reachable from every Insights surface.
The tab strip itself is now route-aware (<Link> + usePathname()) and
exposes an "Overview" pill for the mother page. The CoachDrawer stays
in the mother-page body only — per Marc's directive, navigating to a
sub-page must unmount the drawer.

Shared infrastructure:
  - src/lib/insights/sub-page-metric.ts: slug → MeasurementType[] map
    + SUB_PAGE_SLUGS canonical order.
  - src/hooks/use-insights-layout-prefs.ts: reader for the dashboard-
    layout payload so every sub-page picks up the comparisonBaseline
    without re-fetching.
  - src/components/insights/sub-page-shell.tsx: title + optional badge
    + focusable h1 (a11y — screen-reader focus restoration on tab
    navigation).
  - src/components/insights/insights-layout-shell.tsx: the layout's
    client shell that mounts the strip + threads the regenerate handler.

The 7th sub-page (/insights/schlaf) ships in the follow-up W4c commit
together with its sleep-stage chart components.
This commit is contained in:
Marc-André Bombeck committed 2026-05-14 13:06:49 +02:00
1 parent 2668020d92
commit 38bbba110a
13 files changed
+1187 -109

No files matched your search

+61
View File
@@ -0,0 +1,61 @@
/**
* v1.4.25 W4 — slug → metric mapping for the routed insights sub-pages.
*
* The mother page (`/insights`) hosts the overview (hero + briefing +
* trends + advisor). Each named slug below is a sub-route that focuses
* one metric. Charts on the sub-pages still use the dashboard chart-cog
* surface — see `chartKey` strings in `src/lib/dashboard-layout.ts`
* (`CHART_OVERLAY_KEYS`). The slugs themselves are German because the
* product surface is bilingual and German is the primary author voice.
*
* `medikamente` carries no `MeasurementType[]` because medication
* compliance is event-driven (`MedicationIntakeEvent` rows) rather than
* time-series `Measurement` rows; the page fetches that data via the
* existing `/api/insights/comprehensive` consumer.
*
* `bmi` lists both `WEIGHT` + `BODY_HEIGHT` because BMI is derived
* client-side — `WEIGHT / (heightCm/100)^2` — and the chart sets
* `valueMode="bmi"` on `<HealthChart>`. `BODY_HEIGHT` is informational
* only (we don't fetch a height series; the user's height lives on the
* profile).
*/
export type SubPageSlug =
| "blutdruck"
| "gewicht"
| "puls"
| "stimmung"
| "medikamente"
| "bmi"
| "schlaf";
export const SUB_PAGE_SLUGS = [
"blutdruck",
"gewicht",
"puls",
"stimmung",
"medikamente",
"bmi",
"schlaf",
] as const satisfies readonly SubPageSlug[];
export const SUB_PAGE_METRIC: Record<SubPageSlug, string[]> = {
blutdruck: ["BLOOD_PRESSURE_SYS", "BLOOD_PRESSURE_DIA", "PULSE"],
gewicht: ["WEIGHT"],
puls: ["PULSE"],
stimmung: ["MOOD"],
// medication adherence is event-driven; no measurement series.
medikamente: [],
// BMI is derived from WEIGHT + profile height (no separate series).
bmi: ["WEIGHT"],
schlaf: ["SLEEP_DURATION"],
};
/**
* Mother-page route. Kept here as a named constant so call sites
* never have to hard-code the string.
*/
export const INSIGHTS_OVERVIEW_PATH = "/insights" as const;
export function isSubPageSlug(value: string): value is SubPageSlug {
return (SUB_PAGE_SLUGS as readonly string[]).includes(value);
}