mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 14:40:52 +00:00
feat(insights): page hero with gradient header + generation timestamp + personal-baseline indicator
Wraps the /insights heading in a Dracula-gradient hero band with the "Generated <relative-time>" caption, "Based on your last 90 days" personal-baseline indicator, and a regenerate slot. Pure shell so the page only passes timestamps in. Animation uses the existing animate-insight-in keyframes which already gates on prefers-reduced-motion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
c8e76399ba
commit
4d7d0748fa
5 files changed
+296
-6
No files matched your search
@@ -42,6 +42,7 @@ import {
|
||||
TrendingUp,
|
||||
} from "lucide-react";
|
||||
import { InsightStatusCard } from "@/components/insights/insight-status-card";
|
||||
import { InsightsPageHero } from "@/components/insights/insights-page-hero";
|
||||
// Recharts is ~108 KiB Brotli — defer-load it via a self-contained scatter
|
||||
// wrapper so the bundle only lands once a correlation card actually renders.
|
||||
// Every scatter card sits inside a `length >= 5` gate and below the fold,
|
||||
@@ -270,6 +271,29 @@ function getRangeHint(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* v1.4.16 phase B1b — pick the freshest ISO timestamp from a list so the
|
||||
* page hero's "Generated …" caption surfaces a single representative
|
||||
* value across the per-section caches. Returns null when nothing's
|
||||
* been generated yet so the caption hides cleanly.
|
||||
*/
|
||||
function freshestUpdatedAt(
|
||||
candidates: Array<string | null | undefined>,
|
||||
): string | null {
|
||||
let freshest: number | null = null;
|
||||
let freshestIso: string | null = null;
|
||||
for (const iso of candidates) {
|
||||
if (!iso) continue;
|
||||
const ms = new Date(iso).getTime();
|
||||
if (Number.isNaN(ms)) continue;
|
||||
if (freshest === null || ms > freshest) {
|
||||
freshest = ms;
|
||||
freshestIso = iso;
|
||||
}
|
||||
}
|
||||
return freshestIso;
|
||||
}
|
||||
|
||||
type HealthBandState = "green" | "orange" | "red";
|
||||
|
||||
function classifyRangeValue(
|
||||
@@ -831,14 +855,23 @@ export default function InsightsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
// Wire the hero's "Generated …" caption from the freshest of the per-
|
||||
// section caches so the user gets a single timestamp without having to
|
||||
// expand every card. Falls back to null when no section has shipped a
|
||||
// payload yet (the hero just hides the caption).
|
||||
const heroUpdatedAt = freshestUpdatedAt([
|
||||
generalStatus?.updatedAt,
|
||||
bloodPressureStatus?.updatedAt,
|
||||
weightStatus?.updatedAt,
|
||||
pulseStatus?.updatedAt,
|
||||
bmiStatus?.updatedAt,
|
||||
moodStatus?.updatedAt,
|
||||
medicationComplianceStatus?.updatedAt,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">{t("insights.title")}</h1>
|
||||
<p className="text-muted-foreground hidden text-sm sm:block">
|
||||
{t("insights.overviewSubtitle")}
|
||||
</p>
|
||||
</div>
|
||||
<InsightsPageHero updatedAt={heroUpdatedAt} />
|
||||
|
||||
<InsightsSectionNav />
|
||||
|
||||
|
||||
Reference in new issue
Block a user