feat(insights): unify range controls and part set across bespoke metric pages

Mount MetricRangeControls on the measurement-series bespoke pages (weight,
blood-pressure, pulse, bmi, sleep) so the range selector no longer vanishes
when navigating from a HealthKit metric. Mood, workouts, and medications are
event-driven rather than MeasurementType series, so they document the
omission inline.

Apply the canonical part set where the data supports it: blood-pressure and
sleep gain the diversity nudge + show-all-values foot link. Blood pressure
omits the single-series stat strip (it is sys+dia) and bmi omits the strip /
nudge / show-all entry (it is derived from WEIGHT), each with an inline note.
This commit is contained in:
Marc-André Bombeck committed 2026-06-03 08:56:20 +02:00
1 parent 73255f817d
commit 692b091637
8 files changed
+59

No files matched your search

+17
View File
@@ -11,7 +11,9 @@ import { useInsightsLayoutPrefs } from "@/hooks/use-insights-layout-prefs";
import { Button } from "@/components/ui/button";
import { HealthChartDynamic } from "@/components/charts/health-chart-dynamic";
import { InsightStatusCard } from "@/components/insights/insight-status-card";
import { MeasurementDiversityNudge } from "@/components/insights/measurement-diversity-nudge";
import { MetricEmptyState } from "@/components/insights/metric-empty-state";
import { MetricRangeControls } from "@/components/insights/metric-range-controls";
import { MetricTargetSummary } from "@/components/insights/metric-target-summary";
import { SubPageShell } from "@/components/insights/sub-page-shell";
import { getBpTargets } from "@/lib/analytics/bp-targets";
@@ -101,7 +103,22 @@ export default function InsightsBlutdruckPage() {
description={t("insights.subPage.blutdruckDescription")}
explainerMetric="bloodPressure"
coachLaunch
diversityNudge={
<MeasurementDiversityNudge
measurementType="BLOOD_PRESSURE_SYS"
metricLabel={t("insights.bloodPressureSectionTitle")}
timeZone={user?.timezone ?? undefined}
/>
}
showAllValuesType="BLOOD_PRESSURE_SYS"
/* No `statStrip`: `<MetricStatStrip>` is single-series, but blood
pressure is two series (systolic + diastolic). A single min / max /
median / mean strip would silently drop the diastolic half, so the
page omits it rather than mislead. */
>
{/* The period-over-period delta keys on the systolic series — the
canonical BP figure the targets + status surfaces lead with. */}
<MetricRangeControls measurementType="BLOOD_PRESSURE_SYS" enabled={!isEmpty} />
<HealthChartDynamic
chartKey="bp"
types={["BLOOD_PRESSURE_SYS", "BLOOD_PRESSURE_DIA"]}
+10
View File
@@ -14,6 +14,7 @@ import { HealthChartDynamic } from "@/components/charts/health-chart-dynamic";
import { CoachLaunchButton } from "@/components/insights/coach-launch-button";
import { InsightStatusCard } from "@/components/insights/insight-status-card";
import { MetricEmptyState } from "@/components/insights/metric-empty-state";
import { MetricRangeControls } from "@/components/insights/metric-range-controls";
import { MetricTargetSummary } from "@/components/insights/metric-target-summary";
import { SubPageShell } from "@/components/insights/sub-page-shell";
@@ -112,6 +113,15 @@ export default function InsightsBmiPage() {
explainerMetric="bmi"
coachLaunch
>
{/* No `statStrip` / `diversityNudge` / `showAllValuesType`: BMI is a
derived metric (computed from the WEIGHT series + the user's height),
so it has no first-class summary or raw-reading rows of its own. A
stat strip would have nothing to read, and a diversity nudge / "show
all values" entry would point at WEIGHT and duplicate the weight
page's controls.
BMI is derived from WEIGHT, so the period-over-period delta keys on
the underlying weight series the chart reuses. */}
<MetricRangeControls measurementType="WEIGHT" enabled={!isEmpty} />
<HealthChartDynamic
chartKey="bmi"
types={["WEIGHT"]}
+3
View File
@@ -193,6 +193,9 @@ export default function InsightsMedikamentePage() {
explainerMetric="medications"
coachLaunch
>
{/* No `<MetricRangeControls>` here: medication compliance is
event-driven, not a MeasurementType series, so the period-over-period
range read has nothing to aggregate. */}
<div
className={
medications.length >= 2 ? "grid gap-4 sm:grid-cols-2" : "grid gap-4"
+4
View File
@@ -100,6 +100,10 @@ export default function InsightsStimmungPage() {
explainerMetric="mood"
coachLaunch
>
{/* No `<MetricRangeControls>` here: mood is event-driven, not a
MeasurementType series, so the period-over-period range read
(`/api/analytics/range`, keyed on a MeasurementType enum) has
nothing to aggregate. */}
<MoodChart
chartKey="mood"
compareBaseline={compareBaseline}
+2
View File
@@ -14,6 +14,7 @@ import { InsightStatusCard } from "@/components/insights/insight-status-card";
import { MetricEmptyState } from "@/components/insights/metric-empty-state";
import { MetricStatStrip } from "@/components/insights/metric-stat-strip";
import { MeasurementDiversityNudge } from "@/components/insights/measurement-diversity-nudge";
import { MetricRangeControls } from "@/components/insights/metric-range-controls";
import { MetricTargetSummary } from "@/components/insights/metric-target-summary";
import { SubPageShell } from "@/components/insights/sub-page-shell";
import { Vo2MaxChartRow } from "@/components/insights/vo2-max-chart-row";
@@ -131,6 +132,7 @@ export default function InsightsPulsPage() {
coachLaunch
showAllValuesType="PULSE"
>
<MetricRangeControls measurementType="PULSE" enabled={!isEmpty} />
<HealthChartDynamic
chartKey="pulse"
types={["PULSE"]}
+18
View File
@@ -3,11 +3,14 @@
import Link from "next/link";
import { Moon } from "lucide-react";
import { useAuth } from "@/hooks/use-auth";
import { useInsightsAnalytics } from "@/hooks/use-insights-analytics";
import { useTranslations } from "@/lib/i18n/context";
import { Button } from "@/components/ui/button";
import { MeasurementDiversityNudge } from "@/components/insights/measurement-diversity-nudge";
import { MetricStatusCard } from "@/components/insights/metric-status-card";
import { MetricEmptyState } from "@/components/insights/metric-empty-state";
import { MetricRangeControls } from "@/components/insights/metric-range-controls";
import { MetricTargetSummary } from "@/components/insights/metric-target-summary";
import { SleepOverview } from "@/components/insights/sleep-overview";
import { SubPageShell } from "@/components/insights/sub-page-shell";
@@ -30,6 +33,7 @@ import { SubPageShell } from "@/components/insights/sub-page-shell";
*/
export default function InsightsSchlafPage() {
const { t } = useTranslations();
const { user } = useAuth();
const { isEmpty } = useInsightsAnalytics("SLEEP_DURATION");
@@ -63,7 +67,21 @@ export default function InsightsSchlafPage() {
description={t("insights.sleep.description")}
explainerMetric="sleep"
coachLaunch
diversityNudge={
<MeasurementDiversityNudge
measurementType="SLEEP_DURATION"
metricLabel={t("insights.sleep.title")}
timeZone={user?.timezone ?? undefined}
/>
}
showAllValuesType="SLEEP_DURATION"
/* No `statStrip`: `<SleepOverview>` already leads with the average
nightly total + per-stage breakdown, so a duration-in-minutes
min / max / median / mean strip would duplicate it and read in an
awkward unit. */
>
<MetricRangeControls measurementType="SLEEP_DURATION" enabled={!isEmpty} />
<SleepOverview />
<MetricTargetSummary slug="sleep" />
+2
View File
@@ -14,6 +14,7 @@ import { InsightStatusCard } from "@/components/insights/insight-status-card";
import { MetricEmptyState } from "@/components/insights/metric-empty-state";
import { MetricStatStrip } from "@/components/insights/metric-stat-strip";
import { MeasurementDiversityNudge } from "@/components/insights/measurement-diversity-nudge";
import { MetricRangeControls } from "@/components/insights/metric-range-controls";
import { MetricTargetSummary } from "@/components/insights/metric-target-summary";
import { SubPageShell } from "@/components/insights/sub-page-shell";
import { buildWeightBandsFromHeight } from "@/lib/analytics/value-bands";
@@ -88,6 +89,7 @@ export default function InsightsGewichtPage() {
coachLaunch
showAllValuesType="WEIGHT"
>
<MetricRangeControls measurementType="WEIGHT" enabled={!isEmpty} />
<HealthChartDynamic
chartKey="weight"
types={["WEIGHT"]}
+3
View File
@@ -37,6 +37,9 @@ export default function InsightsWorkoutsPage() {
explainerMetric="workouts"
coachLaunch
>
{/* No `<MetricRangeControls>` here: workouts are session records, not a
MeasurementType series, so the period-over-period range read has
nothing to aggregate. */}
{isLoading ? (
<div data-slot="workouts-loading" className="space-y-2">
<Skeleton className="h-14 w-full rounded-lg" />