mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
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:
1 parent
73255f817d
commit
692b091637
8 files changed
+59
No files matched your search
@@ -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"]}
|
||||
|
||||
@@ -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"]}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"]}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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"]}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in new issue
Block a user