mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
feat(dashboard): make the spotlight heading and the verdict sentence the links
The briefing spotlight carried a separate underlined 'open Insights' text link that thickened the card; every signal row below already deep-links. Drop it and make the Tagesbriefing heading itself the link — quiet, no underline, hover lift only — targeting the briefing card directly via a new #daily-briefing anchor on the insights section (scroll-mt-28, the app-wide offset token). After the broad Insights button left the hero, the scoreDrop and briefing verdict sentences were dead ends. Link-CTA verdicts without a button label now make the sentence itself clickable (same quiet treatment): scoreDrop routes to /insights, the briefing sentence deep-links to the picked finding's metric sub-page via the METRIC_HREF map (fallback /insights). Verdicts with their own action keep their button.
This commit is contained in:
1 parent
8c22d66dd9
commit
e32e27d27f
12 files changed
+74
-36
No files matched your search
+1
-2
@@ -340,8 +340,7 @@
|
||||
"logMeasurement": "Messung erfassen"
|
||||
},
|
||||
"briefing": {
|
||||
"title": "Tagesbriefing",
|
||||
"viewAll": "Insights öffnen"
|
||||
"title": "Tagesbriefing"
|
||||
}
|
||||
},
|
||||
"greetingSalutation": "Hallo, {name}",
|
||||
|
||||
+1
-2
@@ -340,8 +340,7 @@
|
||||
"logMeasurement": "Log measurement"
|
||||
},
|
||||
"briefing": {
|
||||
"title": "Today’s briefing",
|
||||
"viewAll": "Open Insights"
|
||||
"title": "Today’s briefing"
|
||||
}
|
||||
},
|
||||
"greetingSalutation": "Hi, {name}",
|
||||
|
||||
+1
-2
@@ -340,8 +340,7 @@
|
||||
"logMeasurement": "Registrar medición"
|
||||
},
|
||||
"briefing": {
|
||||
"title": "Resumen de hoy",
|
||||
"viewAll": "Abrir Insights"
|
||||
"title": "Resumen de hoy"
|
||||
}
|
||||
},
|
||||
"greetingSalutation": "Hola, {name}",
|
||||
|
||||
+1
-2
@@ -340,8 +340,7 @@
|
||||
"logMeasurement": "Saisir une mesure"
|
||||
},
|
||||
"briefing": {
|
||||
"title": "Briefing du jour",
|
||||
"viewAll": "Ouvrir Insights"
|
||||
"title": "Briefing du jour"
|
||||
}
|
||||
},
|
||||
"greetingSalutation": "Bonjour, {name}",
|
||||
|
||||
+1
-2
@@ -340,8 +340,7 @@
|
||||
"logMeasurement": "Registra misurazione"
|
||||
},
|
||||
"briefing": {
|
||||
"title": "Briefing di oggi",
|
||||
"viewAll": "Apri Insights"
|
||||
"title": "Briefing di oggi"
|
||||
}
|
||||
},
|
||||
"greetingSalutation": "Ciao, {name}",
|
||||
|
||||
+1
-2
@@ -340,8 +340,7 @@
|
||||
"logMeasurement": "Zapisz pomiar"
|
||||
},
|
||||
"briefing": {
|
||||
"title": "Briefing dnia",
|
||||
"viewAll": "Otwórz Insights"
|
||||
"title": "Briefing dnia"
|
||||
}
|
||||
},
|
||||
"greetingSalutation": "Cześć, {name}",
|
||||
|
||||
@@ -117,7 +117,10 @@ describe("<BriefingSpotlight>", () => {
|
||||
});
|
||||
expect(html).toContain('data-slot="dashboard-briefing-spotlight"');
|
||||
expect(html).toContain("Short on sleep this week");
|
||||
expect(html).toContain('href="/insights"');
|
||||
// The heading itself links to the briefing card anchor; the former
|
||||
// separate "view all" text link is gone.
|
||||
expect(html).toContain('href="/insights#daily-briefing"');
|
||||
expect(html).not.toContain('data-slot="dashboard-briefing-spotlight-link"');
|
||||
expect(html).toContain("-1.2 h");
|
||||
// Signals win over key findings when present.
|
||||
expect(html).not.toContain("should not appear when signals exist");
|
||||
|
||||
@@ -152,18 +152,17 @@ export function BriefingSpotlight({
|
||||
|
||||
return (
|
||||
<div data-slot="dashboard-briefing-spotlight" className="space-y-2">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<p className="text-muted-foreground text-[11px] font-semibold tracking-wide uppercase">
|
||||
{t("dashboard.hero.briefing.title")}
|
||||
</p>
|
||||
<Link
|
||||
href="/insights"
|
||||
data-slot="dashboard-briefing-spotlight-link"
|
||||
className="text-muted-foreground hover:text-foreground text-[11px] underline underline-offset-2"
|
||||
>
|
||||
{t("dashboard.hero.briefing.viewAll")}
|
||||
</Link>
|
||||
</div>
|
||||
{/* The heading itself routes to the briefing card on /insights —
|
||||
quiet (no underline, hover lift only). The former separate
|
||||
"open Insights" text link is gone: it thickened the card while
|
||||
every signal row below already deep-links. */}
|
||||
<Link
|
||||
href="/insights#daily-briefing"
|
||||
data-slot="dashboard-briefing-spotlight-title"
|
||||
className="text-muted-foreground hover:text-foreground inline-block text-[11px] font-semibold tracking-wide uppercase transition-colors"
|
||||
>
|
||||
{t("dashboard.hero.briefing.title")}
|
||||
</Link>
|
||||
<div className="space-y-2">
|
||||
{rows.map((row, index) => {
|
||||
const Icon = METRIC_ICON[row.sourceMetric];
|
||||
|
||||
@@ -53,6 +53,7 @@ import { ScoreRing } from "@/components/insights/derived/score-ring";
|
||||
import type { RingHue } from "@/components/insights/derived/ring-hues";
|
||||
import { RestModeBanner } from "@/components/insights/rest-mode-banner";
|
||||
import { BriefingSpotlight } from "@/components/dashboard/hero/briefing-spotlight";
|
||||
import { METRIC_HREF } from "@/components/insights/daily-briefing";
|
||||
import {
|
||||
resolveDashboardVerdict,
|
||||
type DashboardVerdictVariant,
|
||||
@@ -199,6 +200,23 @@ export function DashboardHero({
|
||||
const cta = verdict.cta;
|
||||
const ctaLabelKey = CTA_LABEL_KEY[verdict.variant];
|
||||
|
||||
// Link-CTA verdicts WITHOUT a button label (scoreDrop, briefing) make
|
||||
// the sentence itself the link — after the broad "open Insights"
|
||||
// button was dropped the sentence was a dead end. The briefing
|
||||
// sentence deep-links to the picked finding's metric sub-page (the
|
||||
// METRIC_HREF map the briefing rows use); metrics without a routed
|
||||
// sub-page fall back to the resolver's `/insights` href. Verdicts
|
||||
// with their own action (take dose, view BP, …) keep the button.
|
||||
const sentenceHref =
|
||||
cta !== null && cta.kind === "link" && !ctaLabelKey
|
||||
? verdict.variant === "briefing" &&
|
||||
typeof verdict.values.sourceMetric === "string"
|
||||
? (METRIC_HREF[
|
||||
verdict.values.sourceMetric as keyof typeof METRIC_HREF
|
||||
] ?? cta.href)
|
||||
: cta.href
|
||||
: null;
|
||||
|
||||
// ── Provisional score copy ──────────────────────────────────────────
|
||||
// The score's four pillars are weight, BP, mood, and medication
|
||||
// compliance. When ANY of them already carries data, a null score is
|
||||
@@ -250,13 +268,24 @@ export function DashboardHero({
|
||||
{welcomeText}
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<p
|
||||
data-slot="dashboard-hero-verdict"
|
||||
data-verdict-variant={verdict.variant}
|
||||
className="text-foreground/90 text-base font-medium"
|
||||
>
|
||||
{sentence}
|
||||
</p>
|
||||
{sentenceHref ? (
|
||||
<Link
|
||||
href={sentenceHref}
|
||||
data-slot="dashboard-hero-verdict"
|
||||
data-verdict-variant={verdict.variant}
|
||||
className="text-foreground/90 hover:text-foreground text-base font-medium transition-colors"
|
||||
>
|
||||
{sentence}
|
||||
</Link>
|
||||
) : (
|
||||
<p
|
||||
data-slot="dashboard-hero-verdict"
|
||||
data-verdict-variant={verdict.variant}
|
||||
className="text-foreground/90 text-base font-medium"
|
||||
>
|
||||
{sentence}
|
||||
</p>
|
||||
)}
|
||||
{cta !== null && ctaLabelKey ? (
|
||||
cta.kind === "link" ? (
|
||||
<Button
|
||||
|
||||
@@ -379,9 +379,13 @@ export function DailyBriefing({
|
||||
// straight on its content and the surface reclaims the vertical
|
||||
// space the in-card header used to occupy.
|
||||
<section
|
||||
// Anchor target for the dashboard spotlight's heading link
|
||||
// (`/insights#daily-briefing`); `scroll-mt-28` is the app-wide
|
||||
// sticky-header offset token.
|
||||
id="daily-briefing"
|
||||
data-slot="daily-briefing-section"
|
||||
aria-label={t("insights.dailyBriefing.title")}
|
||||
className="space-y-3"
|
||||
className="scroll-mt-28 space-y-3"
|
||||
>
|
||||
<SectionHeading
|
||||
icon={Sparkles}
|
||||
|
||||
@@ -606,7 +606,10 @@ describe("rung 8 — briefing", () => {
|
||||
NOW,
|
||||
);
|
||||
expect(verdict.variant).toBe("briefing");
|
||||
expect(verdict.values).toEqual({ headline: WATCH_FINDING.headline });
|
||||
expect(verdict.values).toEqual({
|
||||
headline: WATCH_FINDING.headline,
|
||||
sourceMetric: WATCH_FINDING.sourceMetric,
|
||||
});
|
||||
expect(verdict.cta).toEqual({ kind: "link", href: "/insights" });
|
||||
});
|
||||
|
||||
@@ -623,7 +626,10 @@ describe("rung 8 — briefing", () => {
|
||||
NOW,
|
||||
);
|
||||
expect(verdict.variant).toBe("briefing");
|
||||
expect(verdict.values).toEqual({ headline: FRESH_FINDING.headline });
|
||||
expect(verdict.values).toEqual({
|
||||
headline: FRESH_FINDING.headline,
|
||||
sourceMetric: FRESH_FINDING.sourceMetric,
|
||||
});
|
||||
});
|
||||
|
||||
it("skips a STALE briefing even in ready-shaped delivery", () => {
|
||||
|
||||
@@ -273,7 +273,10 @@ export function resolveDashboardVerdict(
|
||||
const picked = findings.find((f) => f.tone === "watch") ?? findings[0];
|
||||
return {
|
||||
variant: "briefing",
|
||||
values: { headline: picked.headline },
|
||||
// `sourceMetric` rides along so the hero can deep-link the
|
||||
// sentence to the picked finding's metric sub-page (METRIC_HREF);
|
||||
// renderers that don't know the field ignore it.
|
||||
values: { headline: picked.headline, sourceMetric: picked.sourceMetric },
|
||||
cta: { kind: "link", href: "/insights" },
|
||||
};
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user