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

+42
View File
@@ -0,0 +1,42 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { queryKeys } from "@/lib/query-keys";
import {
resolveDashboardLayout,
type DashboardLayout,
} from "@/lib/dashboard-layout";
/**
* v1.4.25 W4 — shared reader for the dashboard-layout payload across
* the insights surfaces.
*
* The mother page and every sub-page need the persisted
* `comparisonBaseline` so the dashboard / insights chart-cog stays
* in sync. The shape repeats the same `useQuery({ … queryKey:
* queryKeys.dashboardWidgets() … })` four-line block that lived inline
* on the mother page — extracted here so a new sub-page consumer drops
* the boilerplate and so the TanStack-Query cache lookup stays
* authoritative for every consumer.
*
* Returns the resolved `comparisonBaseline` (defaults to `"none"`)
* directly so consumers don't have to remember the `resolveDashboardLayout`
* fallback dance. `enabled` controls whether the query fires — the
* sub-pages gate this on `useAuth().isAuthenticated`.
*/
export function useInsightsLayoutPrefs(enabled: boolean) {
const { data } = useQuery({
queryKey: queryKeys.dashboardWidgets(),
queryFn: async () => {
const res = await fetch("/api/dashboard/widgets");
if (!res.ok) throw new Error("Failed to load dashboard layout");
const json = await res.json();
return json.data as DashboardLayout;
},
enabled,
});
const compareBaseline =
resolveDashboardLayout(data).comparisonBaseline ?? "none";
return { layout: data ?? null, compareBaseline } as const;
}