mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
feat(insights,dashboard): empty states for first-run views
Closes the last two surfaces from the C5 audit. - /insights — Top-level "no data" path replaces a bare "No data available" sentence with EmptyState (TrendingUp icon) + "Add a measurement" CTA into /measurements. The BMI section's height-not-set fallback gets a compact plain-variant EmptyState (Ruler icon) + "Open account settings" CTA instead of a single grey line that looked like a bug. - / (dashboard) — When trendCards.length === 0 AND charts.length === 0 (a brand-new account with no data and the Getting-started checklist already dismissed) the dashboard rendered an empty 0-px tile strip below the welcome banner. Now paints an EmptyState with the same quick-entry dialog wired to the CTA so the user can log their first measurement without leaving the page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1d65f3b925
commit
65faf1d156
2 files changed
+64
-4
No files matched your search
@@ -7,7 +7,10 @@ import { useTranslations, useFormatters } from "@/lib/i18n/context";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import Link from "next/link";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const HealthChart = dynamic(
|
||||
@@ -809,10 +812,20 @@ export default function InsightsPage() {
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
// v1.4.15 phase-C5: replace bare "no data" text with EmptyState +
|
||||
// CTA into /measurements so the user has a single-click path out
|
||||
// of the empty insights view.
|
||||
return (
|
||||
<div className="text-muted-foreground py-20 text-center">
|
||||
{t("common.noData")}
|
||||
</div>
|
||||
<EmptyState
|
||||
icon={<TrendingUp className="size-6" />}
|
||||
title={t("insights.emptyTitle")}
|
||||
description={t("insights.emptyDescription")}
|
||||
action={
|
||||
<Button size="sm" asChild>
|
||||
<Link href="/measurements">{t("insights.emptyAddMeasurement")}</Link>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1491,7 +1504,24 @@ export default function InsightsPage() {
|
||||
valueBands={bmiBands}
|
||||
/>
|
||||
) : (
|
||||
<p className="text-muted-foreground text-sm">{t("common.noData")}</p>
|
||||
// v1.4.15 phase-C5: explicit empty state when the user
|
||||
// hasn't set a height yet — without it the BMI card looked
|
||||
// broken. Plain variant so the dashed border doesn't double
|
||||
// up inside the section card.
|
||||
<EmptyState
|
||||
variant="plain"
|
||||
size="compact"
|
||||
icon={<Ruler className="size-5" />}
|
||||
title={t("insights.bmiEmptyTitle")}
|
||||
description={t("insights.bmiEmptyDescription")}
|
||||
action={
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link href="/settings/account">
|
||||
{t("insights.bmiEmptyAction")}
|
||||
</Link>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<InsightStatusCard
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
import type { DataSummary as DataSummaryType } from "@/lib/analytics/trends";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -950,6 +951,35 @@ export default function DashboardPage() {
|
||||
|
||||
charts.sort((a, b) => a.order - b.order);
|
||||
|
||||
// v1.4.15 phase-C5: dashboard fully-empty state. When no tile
|
||||
// and no chart has data the dashboard would otherwise paint a
|
||||
// 0-px tile strip with the welcome banner above it — visually
|
||||
// looked like a half-broken page. Render an EmptyState that
|
||||
// re-uses the existing quick-entry dialog so the user has a
|
||||
// single click into "Log measurement" without leaving the page.
|
||||
// The GettingStartedChecklist above renders its own self-gated
|
||||
// surface for very-new accounts; this empty state covers the
|
||||
// case where the checklist has been dismissed but no data was
|
||||
// logged afterwards.
|
||||
if (trendCards.length === 0 && charts.length === 0) {
|
||||
return (
|
||||
<EmptyState
|
||||
icon={<Activity className="size-6" />}
|
||||
title={t("dashboard.emptyTitle")}
|
||||
description={t("dashboard.emptyDescription")}
|
||||
action={
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => setQuickEntryDialog("measurement")}
|
||||
>
|
||||
<Plus className="mr-1 h-4 w-4" />
|
||||
{t("dashboard.emptyAddMeasurement")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* v1.4: dashboard tiles are *always* a single row.
|
||||
|
||||
Reference in new issue
Block a user