fix(balance): hide period selector, chart and table on empty /balance (S2)
Before this commit, /balance rendered the BalanceOnboardingCard plus the period selector + evolution chart + accounts table whenever the user had no accounts or no snapshot. The lower three components surfaced their own empty states, producing 3 stacked "no data" messages under the onboarding card. Lifts the (accountsCount, hasAnySnapshot) computation out of the inline IIFE and uses a single isEmpty branch: empty profiles see only the BalanceOnboardingCard; populated profiles see the full overview. No logic change — only JSX restructuring. Tests covering useBalanceOverview and BalanceOnboardingCard remain green (61 tests passing). Suggestion S2 from PR #184 review (#187). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
445822b792
commit
372a785834
1 changed files with 89 additions and 81 deletions
|
|
@ -173,96 +173,104 @@ export default function BalancePage() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="space-y-6">
|
{/* Issue #178 — empty-state guard. We probe accountsLatest for ANY
|
||||||
{(() => {
|
snapshot date so the guard is independent of the active period
|
||||||
// Issue #178 — show a 2-step onboarding card while the user has no
|
filter (state.period). When empty, we render only the onboarding
|
||||||
// accounts or no snapshots yet. We probe accountsLatest for ANY
|
card — period selector, chart and accounts table would all show
|
||||||
// snapshot date so the empty-state guard is independent of the
|
empty states stacked under it (S2 from #187). */}
|
||||||
// active period filter (state.period).
|
{(() => {
|
||||||
const accountsCount = state.accountsLatest.length;
|
const accountsCount = state.accountsLatest.length;
|
||||||
const hasAnySnapshot = state.accountsLatest.some(
|
const hasAnySnapshot = state.accountsLatest.some(
|
||||||
(a) => a.latest_snapshot_date != null
|
(a) => a.latest_snapshot_date != null
|
||||||
);
|
);
|
||||||
if (accountsCount === 0 || !hasAnySnapshot) {
|
const isEmpty = accountsCount === 0 || !hasAnySnapshot;
|
||||||
return (
|
|
||||||
|
if (isEmpty) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
<BalanceOnboardingCard
|
<BalanceOnboardingCard
|
||||||
accountsCount={accountsCount}
|
accountsCount={accountsCount}
|
||||||
snapshotsCount={hasAnySnapshot ? 1 : 0}
|
snapshotsCount={hasAnySnapshot ? 1 : 0}
|
||||||
/>
|
/>
|
||||||
);
|
</div>
|
||||||
}
|
);
|
||||||
return <BalanceOverviewCard totals={state.evolutionTotals} />;
|
}
|
||||||
})()}
|
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
return (
|
||||||
{/* Period selector */}
|
<div className="space-y-6">
|
||||||
<div
|
<BalanceOverviewCard totals={state.evolutionTotals} />
|
||||||
role="group"
|
|
||||||
aria-label={t("balance.period.legend")}
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
||||||
className="inline-flex rounded-lg border border-[var(--border)] overflow-hidden"
|
{/* Period selector */}
|
||||||
>
|
<div
|
||||||
{PERIOD_OPTIONS.map((p) => (
|
role="group"
|
||||||
<button
|
aria-label={t("balance.period.legend")}
|
||||||
key={p}
|
className="inline-flex rounded-lg border border-[var(--border)] overflow-hidden"
|
||||||
type="button"
|
|
||||||
onClick={() => setPeriod(p)}
|
|
||||||
className={`px-3 py-1.5 text-sm font-medium ${
|
|
||||||
state.period === p
|
|
||||||
? "bg-[var(--primary)] text-white"
|
|
||||||
: "bg-[var(--card)] text-[var(--foreground)] hover:bg-[var(--muted)]/40"
|
|
||||||
}`}
|
|
||||||
aria-pressed={state.period === p}
|
|
||||||
>
|
>
|
||||||
{t(`balance.period.${p}`)}
|
{PERIOD_OPTIONS.map((p) => (
|
||||||
</button>
|
<button
|
||||||
))}
|
key={p}
|
||||||
</div>
|
type="button"
|
||||||
|
onClick={() => setPeriod(p)}
|
||||||
|
className={`px-3 py-1.5 text-sm font-medium ${
|
||||||
|
state.period === p
|
||||||
|
? "bg-[var(--primary)] text-white"
|
||||||
|
: "bg-[var(--card)] text-[var(--foreground)] hover:bg-[var(--muted)]/40"
|
||||||
|
}`}
|
||||||
|
aria-pressed={state.period === p}
|
||||||
|
>
|
||||||
|
{t(`balance.period.${p}`)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Chart mode toggle */}
|
{/* Chart mode toggle */}
|
||||||
<div
|
<div
|
||||||
role="group"
|
role="group"
|
||||||
aria-label={t("balance.chart.modeLegend")}
|
aria-label={t("balance.chart.modeLegend")}
|
||||||
className="inline-flex rounded-lg border border-[var(--border)] overflow-hidden"
|
className="inline-flex rounded-lg border border-[var(--border)] overflow-hidden"
|
||||||
>
|
|
||||||
{(["line", "stacked"] as BalanceChartMode[]).map((mode) => (
|
|
||||||
<button
|
|
||||||
key={mode}
|
|
||||||
type="button"
|
|
||||||
onClick={() => setChartMode(mode)}
|
|
||||||
className={`px-3 py-1.5 text-sm font-medium ${
|
|
||||||
state.chartMode === mode
|
|
||||||
? "bg-[var(--primary)] text-white"
|
|
||||||
: "bg-[var(--card)] text-[var(--foreground)] hover:bg-[var(--muted)]/40"
|
|
||||||
}`}
|
|
||||||
aria-pressed={state.chartMode === mode}
|
|
||||||
>
|
>
|
||||||
{t(`balance.chart.mode.${mode}`)}
|
{(["line", "stacked"] as BalanceChartMode[]).map((mode) => (
|
||||||
</button>
|
<button
|
||||||
))}
|
key={mode}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setChartMode(mode)}
|
||||||
|
className={`px-3 py-1.5 text-sm font-medium ${
|
||||||
|
state.chartMode === mode
|
||||||
|
? "bg-[var(--primary)] text-white"
|
||||||
|
: "bg-[var(--card)] text-[var(--foreground)] hover:bg-[var(--muted)]/40"
|
||||||
|
}`}
|
||||||
|
aria-pressed={state.chartMode === mode}
|
||||||
|
>
|
||||||
|
{t(`balance.chart.mode.${mode}`)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BalanceEvolutionChart
|
||||||
|
mode={state.chartMode}
|
||||||
|
totals={state.evolutionTotals}
|
||||||
|
byCategory={state.evolutionByCategory}
|
||||||
|
categoryLabels={categoryLabels}
|
||||||
|
transferMarkers={allTransferMarkers}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold mb-3">
|
||||||
|
{t("balance.overview.accountsTitle")}
|
||||||
|
</h2>
|
||||||
|
<BalanceAccountsTable
|
||||||
|
accounts={state.accountsLatest}
|
||||||
|
periodAnchor={state.accountsPeriodAnchor}
|
||||||
|
sinceCreationDate={earliestSnapshotDate}
|
||||||
|
onArchiveAccount={(acc) => handleArchiveAccount(acc.account_id)}
|
||||||
|
onLinkTransfers={(acc) => setLinkTarget(acc)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
|
})()}
|
||||||
<BalanceEvolutionChart
|
|
||||||
mode={state.chartMode}
|
|
||||||
totals={state.evolutionTotals}
|
|
||||||
byCategory={state.evolutionByCategory}
|
|
||||||
categoryLabels={categoryLabels}
|
|
||||||
transferMarkers={allTransferMarkers}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-lg font-semibold mb-3">
|
|
||||||
{t("balance.overview.accountsTitle")}
|
|
||||||
</h2>
|
|
||||||
<BalanceAccountsTable
|
|
||||||
accounts={state.accountsLatest}
|
|
||||||
periodAnchor={state.accountsPeriodAnchor}
|
|
||||||
sinceCreationDate={earliestSnapshotDate}
|
|
||||||
onArchiveAccount={(acc) => handleArchiveAccount(acc.account_id)}
|
|
||||||
onLinkTransfers={(acc) => setLinkTarget(acc)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<StarterAccountsModal
|
<StarterAccountsModal
|
||||||
isOpen={showStarterModal}
|
isOpen={showStarterModal}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue