mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 14:40:52 +00:00
fix(dashboard): link metric tiles to Insights detail + unify tile header
- Dashboard metric tiles link to their /insights/<slug> detail page (weight, blood pressure, pulse, glucose, mood, sleep, steps, VO2 max); tiles with no dedicated sub-page stay un-linked rather than misroute. - Daily-briefing key findings now route hrv, resting HR, active energy, flights, distance, body temperature and recovery to their sub-pages — the METRIC_HREF map had gone stale as those routes shipped. - RecentWorkoutsTile adopts the canonical TileHeader (foreground icon + title) instead of a muted hand-rolled row. - Hero-strip header glyph moves off text-primary to the foreground tier.
This commit is contained in:
1 parent
6ad2dc858a
commit
2dc9947de2
5 files changed
+124
-47
No files matched your search
@@ -15,6 +15,7 @@ import {
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { useWorkouts, type WorkoutListEntry } from "@/hooks/use-workouts";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { TileHeader } from "@/components/insights/tile-header";
|
||||
|
||||
/**
|
||||
* v1.4.32 — `<RecentWorkoutsTile>`.
|
||||
@@ -107,23 +108,23 @@ export function RecentWorkoutsTile() {
|
||||
"bg-card border-border space-y-3 rounded-xl border p-4 md:p-6",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Activity
|
||||
className="text-muted-foreground size-4"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<h2 className="text-sm font-semibold">
|
||||
{t("dashboard.recentWorkouts.title")}
|
||||
</h2>
|
||||
</div>
|
||||
<Link
|
||||
href="/insights/workouts"
|
||||
className="text-muted-foreground hover:text-foreground inline-flex min-h-11 items-center text-xs"
|
||||
>
|
||||
{t("dashboard.recentWorkouts.viewAll")}
|
||||
</Link>
|
||||
</div>
|
||||
{/* Canonical tile header (foreground icon + title) — the same
|
||||
`TileHeader` contract the Insights tiles use, so the dashboard
|
||||
stops speaking a separate header language. `size="sm"` matches the
|
||||
compact dashboard tile. */}
|
||||
<TileHeader
|
||||
icon={Activity}
|
||||
size="sm"
|
||||
title={t("dashboard.recentWorkouts.title")}
|
||||
right={
|
||||
<Link
|
||||
href="/insights/workouts"
|
||||
className="text-muted-foreground hover:text-foreground inline-flex min-h-11 items-center text-xs"
|
||||
>
|
||||
{t("dashboard.recentWorkouts.viewAll")}
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
|
||||
{isLoading ? (
|
||||
// v1.4.43 W11-L6 — reserve roughly the loaded tile height
|
||||
|
||||
@@ -95,6 +95,36 @@ describe("<DailyBriefing>", () => {
|
||||
expect(html).toContain("Weight down 30 d");
|
||||
});
|
||||
|
||||
it("links the key-finding rows for metrics whose Insights sub-page exists", () => {
|
||||
// Regression guard for the stale METRIC_HREF map: hrv / resting_hr /
|
||||
// active_energy / flights / distance / body_temp / recovery all have
|
||||
// dedicated Insights sub-pages now, so their briefing rows must be
|
||||
// tappable `<a>` links, not inert rows.
|
||||
const html = render(
|
||||
<DailyBriefing
|
||||
briefing={{
|
||||
...baseBriefing,
|
||||
keyFindings: [
|
||||
{ ...baseBriefing.keyFindings[2], sourceMetric: "hrv" },
|
||||
{ ...baseBriefing.keyFindings[2], sourceMetric: "resting_hr" },
|
||||
{ ...baseBriefing.keyFindings[2], sourceMetric: "active_energy" },
|
||||
{ ...baseBriefing.keyFindings[2], sourceMetric: "flights" },
|
||||
{ ...baseBriefing.keyFindings[2], sourceMetric: "distance" },
|
||||
{ ...baseBriefing.keyFindings[2], sourceMetric: "body_temp" },
|
||||
{ ...baseBriefing.keyFindings[2], sourceMetric: "recovery" },
|
||||
],
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
expect(html).toContain('href="/insights/hrv"');
|
||||
expect(html).toContain('href="/insights/resting-pulse"');
|
||||
expect(html).toContain('href="/insights/active-energy"');
|
||||
expect(html).toContain('href="/insights/flights-climbed"');
|
||||
expect(html).toContain('href="/insights/walking-distance"');
|
||||
expect(html).toContain('href="/insights/body-temperature"');
|
||||
expect(html).toContain('href="/insights/recovery"');
|
||||
});
|
||||
|
||||
it("renders the delta string when supplied", () => {
|
||||
const html = render(<DailyBriefing briefing={baseBriefing} />);
|
||||
expect(html).toMatch(/data-slot="daily-briefing-delta"/);
|
||||
|
||||
@@ -53,28 +53,28 @@ export const METRIC_HREF: Record<
|
||||
pulse: "/insights/pulse",
|
||||
mood: "/insights/mood",
|
||||
compliance: "/insights/medications",
|
||||
// Apple Health / GLP-1 additive metrics — no dedicated sub-page yet.
|
||||
// They render as static rows; v1.4.28 can wire each as they ship.
|
||||
hrv: null,
|
||||
// Apple Health / GLP-1 additive metrics — each now routes to its
|
||||
// dedicated sub-page that shipped since the original map was written.
|
||||
hrv: "/insights/hrv",
|
||||
sleep: "/insights/sleep",
|
||||
resting_hr: null,
|
||||
resting_hr: "/insights/resting-pulse",
|
||||
// v1.12.4 — steps has no dedicated metric sub-page, so it routes to the
|
||||
// generic readings view keyed to its `MeasurementType` (the same target
|
||||
// the category pages use to drill into a single type). Every other
|
||||
// briefing finding with a real destination is already tappable; steps
|
||||
// was the lone static row.
|
||||
steps: "/insights/steps",
|
||||
active_energy: null,
|
||||
flights: null,
|
||||
distance: null,
|
||||
active_energy: "/insights/active-energy",
|
||||
flights: "/insights/flights-climbed",
|
||||
distance: "/insights/walking-distance",
|
||||
vo2_max: "/insights/pulse",
|
||||
body_temp: null,
|
||||
body_temp: "/insights/body-temperature",
|
||||
glp1_plateau: "/insights/medications",
|
||||
// ── v1.10.0 derived-wellness additive ──
|
||||
// Readiness has a score-anatomy detail page; recovery is a read-only
|
||||
// ring with no dedicated sub-page yet (renders as a static row).
|
||||
// Readiness has a score-anatomy detail page; recovery has its own
|
||||
// read-only sub-page.
|
||||
readiness: "/insights/scores/readiness",
|
||||
recovery: null,
|
||||
recovery: "/insights/recovery",
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -284,8 +284,11 @@ export function HeroStrip({
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-5">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Header glyph stays in the foreground tier — UI-STANDARDS §1
|
||||
reserves primary/accent for actions, not decorative header
|
||||
icons. */}
|
||||
<Sparkles
|
||||
className="text-primary h-5 w-5 shrink-0"
|
||||
className="text-foreground h-5 w-5 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<h1
|
||||
|
||||
Reference in new issue
Block a user