mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
feat(insights): adopt the canonical per-metric detail template and terminology
Bring the web per-metric Insights detail pages to structural parity with the canonical metric template: a single top-to-bottom spine, every block self-suppressing when its data is absent. New spine (above the chart): header (title + target gear + Coach circle) → intro → primary tile → Letzte Messung → stat strip; then the chart, the range controls + period delta, the per-metric correlation card, and the Einschätzung assessment last. - Add the primary tile (headline value + 30-day average + an In-range bar shown only when the metric carries a per-metric in-target pct). Blood pressure keeps its richer target panel as its primary tile. - Add the Letzte Messung card (labeled heading + last-reading date), reading the last-seen timestamp the slim analytics slice already carries — no extra fetch. - Relocate the owning correlation onto its metric page: weight × weekday on Weight, mood × pulse on Pulse. The card reads the correlations already on the analytics payload and self-gates below the surfacing bar; it is now the canonical home for those relationships. - Move the headline value, 30-day average and in-target share out of the target reference panel so each figure appears in exactly one place; the panel stays the band reference (range, status, positional bar, consistency strip, source). Blood pressure keeps its stitched S/D 30-day average inline. - Order the Einschätzung last on every page. Terminology: adopt the canonical labels — rename the mood patterns surface to "Was auffällt", pluralise the deviation-count card to "Signale des Tages", and align the settings section labels to "Über diese App" and "Quellen-Priorität". New In-range / 30-day-average / Letzte Messung strings land in all six locales. Web charts are unchanged — Recharts and the existing chart tokens stay; the iOS monochrome chart doctrine is not applied.
This commit is contained in:
1 parent
6ad0a8a5cf
commit
d5eb887757
19 files changed
+564
-78
No files matched your search
@@ -13,6 +13,7 @@ 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 { MetricLastMeasurementCard } from "@/components/insights/metric-last-measurement-card";
|
||||
import { MetricRangeControls } from "@/components/insights/metric-range-controls";
|
||||
import { MetricTargetSummary } from "@/components/insights/metric-target-summary";
|
||||
import { SubPageShell } from "@/components/insights/sub-page-shell";
|
||||
@@ -46,7 +47,10 @@ export default function InsightsBlutdruckPage() {
|
||||
const { data: status, isLoading: isStatusLoading } =
|
||||
useInsightStatus("blood-pressure");
|
||||
|
||||
const { isEmpty } = useInsightsAnalytics("BLOOD_PRESSURE_SYS");
|
||||
const { data: analytics, isEmpty } =
|
||||
useInsightsAnalytics("BLOOD_PRESSURE_SYS");
|
||||
const bpLastSeenAt =
|
||||
analytics?.lastSeenByType?.BLOOD_PRESSURE_SYS?.lastSeenAt ?? null;
|
||||
|
||||
if (isEmpty) {
|
||||
return (
|
||||
@@ -103,6 +107,15 @@ export default function InsightsBlutdruckPage() {
|
||||
description={t("insights.subPage.blutdruckDescription")}
|
||||
explainerMetric="bloodPressure"
|
||||
coachLaunch
|
||||
primary={
|
||||
<>
|
||||
{/* Blood pressure's primary tile IS its richer target panel
|
||||
(ESH classification + sys/dia band + 30-day S/D average),
|
||||
promoted above the chart per the canonical spine. */}
|
||||
<MetricTargetSummary slug="blood-pressure" />
|
||||
<MetricLastMeasurementCard lastSeenAt={bpLastSeenAt} />
|
||||
</>
|
||||
}
|
||||
diversityNudge={
|
||||
<MeasurementDiversityNudge
|
||||
measurementType="BLOOD_PRESSURE_SYS"
|
||||
@@ -132,8 +145,8 @@ export default function InsightsBlutdruckPage() {
|
||||
{/* v1.12.0 — range pills + period-over-period delta below the chart. */}
|
||||
<MetricRangeControls measurementType="BLOOD_PRESSURE_SYS" enabled={!isEmpty} />
|
||||
|
||||
<MetricTargetSummary slug="blood-pressure" />
|
||||
|
||||
{/* v1.12.0 — Einschätzung is the last block on the canonical
|
||||
metric-detail spine. */}
|
||||
<InsightStatusCard
|
||||
title={t("insights.assessmentTitle")}
|
||||
icon={<HeartPulse className="h-5 w-5" />}
|
||||
|
||||
@@ -13,6 +13,9 @@ import { HealthChartDynamic } from "@/components/charts/health-chart-dynamic";
|
||||
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 { MetricPrimaryTile } from "@/components/insights/metric-primary-tile";
|
||||
import { MetricLastMeasurementCard } from "@/components/insights/metric-last-measurement-card";
|
||||
import { MetricCorrelationCard } from "@/components/insights/metric-correlation-card";
|
||||
import { MeasurementDiversityNudge } from "@/components/insights/measurement-diversity-nudge";
|
||||
import { MetricRangeControls } from "@/components/insights/metric-range-controls";
|
||||
import { MetricTargetSummary } from "@/components/insights/metric-target-summary";
|
||||
@@ -52,6 +55,8 @@ export default function InsightsPulsPage() {
|
||||
// the same key).
|
||||
const { data: analytics, isEmpty } = useInsightsAnalytics("PULSE");
|
||||
const vo2Summary = analytics?.summaries?.VO2_MAX ?? null;
|
||||
const pulseSummary = analytics?.summaries?.PULSE ?? null;
|
||||
const pulseLastSeenAt = analytics?.lastSeenByType?.PULSE?.lastSeenAt ?? null;
|
||||
|
||||
// v1.4.27 F17 — gate the sub-page on at least one pulse observation.
|
||||
// Brand-new accounts (no manual logs, no Apple-Health upload yet)
|
||||
@@ -116,12 +121,13 @@ export default function InsightsPulsPage() {
|
||||
title={t("insights.pulseSectionTitle")}
|
||||
description={t("insights.subPage.pulsDescription")}
|
||||
explainerMetric="pulse"
|
||||
statStrip={
|
||||
<MetricStatStrip
|
||||
summary={analytics?.summaries?.PULSE ?? null}
|
||||
unit="bpm"
|
||||
/>
|
||||
primary={
|
||||
<>
|
||||
<MetricPrimaryTile summary={pulseSummary} unit="bpm" slug="pulse" />
|
||||
<MetricLastMeasurementCard lastSeenAt={pulseLastSeenAt} />
|
||||
</>
|
||||
}
|
||||
statStrip={<MetricStatStrip summary={pulseSummary} unit="bpm" />}
|
||||
diversityNudge={
|
||||
<MeasurementDiversityNudge
|
||||
measurementType="PULSE"
|
||||
@@ -147,16 +153,6 @@ export default function InsightsPulsPage() {
|
||||
|
||||
<MetricTargetSummary slug="pulse" />
|
||||
|
||||
<InsightStatusCard
|
||||
title={t("insights.assessmentTitle")}
|
||||
icon={<Heart className="h-5 w-5" />}
|
||||
text={status?.text ?? null}
|
||||
hasProvider={status?.hasProvider ?? false}
|
||||
updatedAt={status?.updatedAt ?? null}
|
||||
loading={isStatusLoading}
|
||||
preparing={status?.preparing ?? false}
|
||||
/>
|
||||
|
||||
{/* v1.4.25 W16a — VO2 max sits on the cardio sub-page because it
|
||||
is a cardio-fitness metric (Apple's Health app surfaces it
|
||||
under "Heart"). The chart-row stays mounted even at zero
|
||||
@@ -168,6 +164,22 @@ export default function InsightsPulsPage() {
|
||||
compareBaseline={compareBaseline}
|
||||
userTimezone={user?.timezone}
|
||||
/>
|
||||
|
||||
{/* v1.12.0 — Pulse owns the mood × pulse correlation (relocated off
|
||||
the overview onto its metric page). */}
|
||||
<MetricCorrelationCard slug="pulse" />
|
||||
|
||||
{/* v1.12.0 — Einschätzung is the last block on the canonical
|
||||
metric-detail spine. */}
|
||||
<InsightStatusCard
|
||||
title={t("insights.assessmentTitle")}
|
||||
icon={<Heart className="h-5 w-5" />}
|
||||
text={status?.text ?? null}
|
||||
hasProvider={status?.hasProvider ?? false}
|
||||
updatedAt={status?.updatedAt ?? null}
|
||||
loading={isStatusLoading}
|
||||
preparing={status?.preparing ?? false}
|
||||
/>
|
||||
</SubPageShell>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ 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 { MetricLastMeasurementCard } from "@/components/insights/metric-last-measurement-card";
|
||||
import { MetricRangeControls } from "@/components/insights/metric-range-controls";
|
||||
import { MetricTargetSummary } from "@/components/insights/metric-target-summary";
|
||||
import { SleepOverview } from "@/components/insights/sleep-overview";
|
||||
@@ -35,7 +36,10 @@ export default function InsightsSchlafPage() {
|
||||
const { t } = useTranslations();
|
||||
const { user } = useAuth();
|
||||
|
||||
const { isEmpty } = useInsightsAnalytics("SLEEP_DURATION");
|
||||
const { data: analytics, isEmpty } =
|
||||
useInsightsAnalytics("SLEEP_DURATION");
|
||||
const sleepLastSeenAt =
|
||||
analytics?.lastSeenByType?.SLEEP_DURATION?.lastSeenAt ?? null;
|
||||
|
||||
if (isEmpty) {
|
||||
return (
|
||||
@@ -67,6 +71,7 @@ export default function InsightsSchlafPage() {
|
||||
description={t("insights.sleep.description")}
|
||||
explainerMetric="sleep"
|
||||
coachLaunch
|
||||
primary={<MetricLastMeasurementCard lastSeenAt={sleepLastSeenAt} />}
|
||||
diversityNudge={
|
||||
<MeasurementDiversityNudge
|
||||
measurementType="SLEEP_DURATION"
|
||||
|
||||
@@ -13,6 +13,9 @@ import { HealthChartDynamic } from "@/components/charts/health-chart-dynamic";
|
||||
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 { MetricPrimaryTile } from "@/components/insights/metric-primary-tile";
|
||||
import { MetricLastMeasurementCard } from "@/components/insights/metric-last-measurement-card";
|
||||
import { MetricCorrelationCard } from "@/components/insights/metric-correlation-card";
|
||||
import { MeasurementDiversityNudge } from "@/components/insights/measurement-diversity-nudge";
|
||||
import { MetricRangeControls } from "@/components/insights/metric-range-controls";
|
||||
import { MetricTargetSummary } from "@/components/insights/metric-target-summary";
|
||||
@@ -42,6 +45,8 @@ export default function InsightsGewichtPage() {
|
||||
|
||||
const { data: analytics, isEmpty } = useInsightsAnalytics("WEIGHT");
|
||||
const weightSummary = analytics?.summaries?.WEIGHT ?? null;
|
||||
const weightLastSeenAt =
|
||||
analytics?.lastSeenByType?.WEIGHT?.lastSeenAt ?? null;
|
||||
|
||||
if (isEmpty) {
|
||||
return (
|
||||
@@ -79,6 +84,12 @@ export default function InsightsGewichtPage() {
|
||||
title={t("insights.weightSectionTitle")}
|
||||
description={t("insights.subPage.gewichtDescription")}
|
||||
explainerMetric="weight"
|
||||
primary={
|
||||
<>
|
||||
<MetricPrimaryTile summary={weightSummary} unit="kg" slug="weight" />
|
||||
<MetricLastMeasurementCard lastSeenAt={weightLastSeenAt} />
|
||||
</>
|
||||
}
|
||||
statStrip={<MetricStatStrip summary={weightSummary} unit="kg" />}
|
||||
diversityNudge={
|
||||
<MeasurementDiversityNudge
|
||||
@@ -113,6 +124,10 @@ export default function InsightsGewichtPage() {
|
||||
compact
|
||||
/>
|
||||
|
||||
{/* v1.12.0 — Weight owns the weight × weekday correlation (relocated
|
||||
off the overview onto its metric page). */}
|
||||
<MetricCorrelationCard slug="weight" />
|
||||
|
||||
<InsightStatusCard
|
||||
title={t("insights.assessmentTitle")}
|
||||
icon={<Scale className="h-5 w-5" />}
|
||||
|
||||
Reference in new issue
Block a user