feat(balance): 2-step onboarding card on /balance empty state
Replace empty BalanceOverviewCard with BalanceOnboardingCard showing two steps: 1. Create an account 2. Enter a snapshot Step 2 is grayed out until at least one account exists; the entire card is replaced by BalanceOverviewCard once a snapshot is recorded. Hide "+ New snapshot" button when 0 accounts (it lives inside the overview card, which is now hidden in that state). Improve SnapshotEditPage noAccounts copy to clarify account vs snapshot semantics. Resolves #178
This commit is contained in:
parent
50b119121f
commit
eac2a516b5
8 changed files with 316 additions and 6 deletions
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
## [Non publié]
|
## [Non publié]
|
||||||
|
|
||||||
|
### Modifié
|
||||||
|
|
||||||
|
- Bilan : remplacement de l'état vide de /balance par une carte d'onboarding à 2 étapes (Créer un compte → Saisir un snapshot) pour éviter l'écran « aucun snapshot » déroutant avant qu'un compte n'existe. Le bouton « + Nouveau snapshot » est masqué tant qu'aucun compte n'existe. La copie de l'état vide de /balance/snapshot clarifie la différence entre un compte et un snapshot (#178).
|
||||||
|
|
||||||
### Corrigé
|
### Corrigé
|
||||||
|
|
||||||
- Bilan : correction de l'erreur SQLite « misuse of aggregate function MIN() » au chargement de /balance avec des snapshots existants ; remplacement du pattern aggregate-in-WHERE par une window function ROW_NUMBER() dans getAccountsPeriodAnchor (#175).
|
- Bilan : correction de l'erreur SQLite « misuse of aggregate function MIN() » au chargement de /balance avec des snapshots existants ; remplacement du pattern aggregate-in-WHERE par une window function ROW_NUMBER() dans getAccountsPeriodAnchor (#175).
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Bilan: replaced empty /balance state with a 2-step onboarding card (Create an account → Enter a snapshot) so users no longer see a confusing "no snapshot" screen before any account exists. The "+ New snapshot" button is hidden until at least one account exists. The /balance/snapshot empty-state copy now clarifies what an account is vs. what a snapshot is (#178).
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Bilan: fix SQLite "misuse of aggregate function MIN()" error when loading /balance with existing snapshots; replaced aggregate-in-WHERE pattern with ROW_NUMBER() window function in getAccountsPeriodAnchor (#175).
|
- Bilan: fix SQLite "misuse of aggregate function MIN()" error when loading /balance with existing snapshots; replaced aggregate-in-WHERE pattern with ROW_NUMBER() window function in getAccountsPeriodAnchor (#175).
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ simpl-resultat/
|
||||||
├── src/ # Frontend React/TypeScript
|
├── src/ # Frontend React/TypeScript
|
||||||
│ ├── components/ # 58 composants organisés par domaine
|
│ ├── components/ # 58 composants organisés par domaine
|
||||||
│ │ ├── adjustments/ # 3 composants
|
│ │ ├── adjustments/ # 3 composants
|
||||||
│ │ ├── balance/ # 7 composants Bilan (AccountForm, BalanceAccountsTable, BalanceEvolutionChart, BalanceOverviewCard, LinkTransfersModal, SnapshotEditor, SnapshotLineRow)
|
│ │ ├── balance/ # 8 composants Bilan (AccountForm, BalanceAccountsTable, BalanceEvolutionChart, BalanceOnboardingCard, BalanceOverviewCard, LinkTransfersModal, SnapshotEditor, SnapshotLineRow)
|
||||||
│ │ ├── budget/ # 5 composants
|
│ │ ├── budget/ # 5 composants
|
||||||
│ │ ├── categories/ # 5 composants
|
│ │ ├── categories/ # 5 composants
|
||||||
│ │ ├── dashboard/ # 2 composants
|
│ │ ├── dashboard/ # 2 composants
|
||||||
|
|
|
||||||
41
src/components/balance/BalanceOnboardingCard.test.tsx
Normal file
41
src/components/balance/BalanceOnboardingCard.test.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
// BalanceOnboardingCard — unit tests (issue #178)
|
||||||
|
//
|
||||||
|
// NOTE: This project does not have @testing-library/react or jsdom configured
|
||||||
|
// (logged as MEDIUM in autopilot decisions-log). Tests cover the pure
|
||||||
|
// `deriveOnboardingSteps` helper that drives the visual state of each step.
|
||||||
|
// All React rendering is bypassed.
|
||||||
|
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { deriveOnboardingSteps } from "./BalanceOnboardingCard";
|
||||||
|
|
||||||
|
describe("BalanceOnboardingCard — deriveOnboardingSteps", () => {
|
||||||
|
it("0 accounts, 0 snapshots → step1 active, step2 disabled", () => {
|
||||||
|
const r = deriveOnboardingSteps(0, 0);
|
||||||
|
expect(r.step1).toBe("active");
|
||||||
|
expect(r.step2).toBe("disabled");
|
||||||
|
});
|
||||||
|
|
||||||
|
it(">=1 account, 0 snapshots → step1 done, step2 active", () => {
|
||||||
|
const r = deriveOnboardingSteps(1, 0);
|
||||||
|
expect(r.step1).toBe("done");
|
||||||
|
expect(r.step2).toBe("active");
|
||||||
|
|
||||||
|
const r2 = deriveOnboardingSteps(5, 0);
|
||||||
|
expect(r2.step1).toBe("done");
|
||||||
|
expect(r2.step2).toBe("active");
|
||||||
|
});
|
||||||
|
|
||||||
|
it(">=1 account, >=1 snapshot → both done (defensive — card normally hidden)", () => {
|
||||||
|
const r = deriveOnboardingSteps(2, 3);
|
||||||
|
expect(r.step1).toBe("done");
|
||||||
|
expect(r.step2).toBe("done");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("guard: 0 accounts but >=1 snapshot (anomaly) → step1 active, step2 done", () => {
|
||||||
|
// This combination should not happen in practice (a snapshot requires at
|
||||||
|
// least one account), but the helper handles it conservatively.
|
||||||
|
const r = deriveOnboardingSteps(0, 1);
|
||||||
|
expect(r.step1).toBe("active");
|
||||||
|
expect(r.step2).toBe("done");
|
||||||
|
});
|
||||||
|
});
|
||||||
210
src/components/balance/BalanceOnboardingCard.tsx
Normal file
210
src/components/balance/BalanceOnboardingCard.tsx
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
// BalanceOnboardingCard — empty-state onboarding for /balance.
|
||||||
|
//
|
||||||
|
// Issue #178. Replaces the BalanceOverviewCard when the user has no accounts
|
||||||
|
// or no snapshots yet. Two vertical steps:
|
||||||
|
// 1. Create an account → /balance/accounts
|
||||||
|
// 2. Enter a snapshot → /balance/snapshot
|
||||||
|
//
|
||||||
|
// Each step has 3 states:
|
||||||
|
// - "active": primary CTA, currently the next thing to do
|
||||||
|
// - "done": marked with a checkmark, no CTA
|
||||||
|
// - "disabled": grayed out (e.g. step 2 when 0 accounts), CTA disabled
|
||||||
|
//
|
||||||
|
// The whole card is replaced by BalanceOverviewCard once at least one
|
||||||
|
// snapshot exists, so step 2 in practice is rendered as "active" or
|
||||||
|
// "disabled"; the "done" branch is supported for completeness/tests.
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import type { TFunction } from "i18next";
|
||||||
|
import { Wallet, FileText, Check, ArrowRight } from "lucide-react";
|
||||||
|
|
||||||
|
interface BalanceOnboardingCardProps {
|
||||||
|
/** Number of active (non-archived) accounts. */
|
||||||
|
accountsCount: number;
|
||||||
|
/** Number of snapshots saved (any date). */
|
||||||
|
snapshotsCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StepState = "active" | "done" | "disabled";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pure helper exposed for unit tests — derives the state of each onboarding
|
||||||
|
* step from the (accountsCount, snapshotsCount) pair.
|
||||||
|
*
|
||||||
|
* - Step 1 is "done" once at least one account exists, "active" otherwise.
|
||||||
|
* - Step 2 is "done" once any snapshot exists, "active" once at least one
|
||||||
|
* account exists, "disabled" otherwise. In practice the parent guard on
|
||||||
|
* /balance only renders this card when snapshotsCount === 0, so the
|
||||||
|
* "done" branch for step 2 is mostly defensive.
|
||||||
|
*/
|
||||||
|
export function deriveOnboardingSteps(
|
||||||
|
accountsCount: number,
|
||||||
|
snapshotsCount: number
|
||||||
|
): { step1: StepState; step2: StepState } {
|
||||||
|
const step1: StepState = accountsCount >= 1 ? "done" : "active";
|
||||||
|
const step2: StepState =
|
||||||
|
snapshotsCount >= 1
|
||||||
|
? "done"
|
||||||
|
: accountsCount >= 1
|
||||||
|
? "active"
|
||||||
|
: "disabled";
|
||||||
|
return { step1, step2 };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BalanceOnboardingCard({
|
||||||
|
accountsCount,
|
||||||
|
snapshotsCount,
|
||||||
|
}: BalanceOnboardingCardProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { step1: step1State, step2: step2State } = deriveOnboardingSteps(
|
||||||
|
accountsCount,
|
||||||
|
snapshotsCount
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-[var(--card)] rounded-xl border border-[var(--border)] p-6">
|
||||||
|
<h2 className="text-lg font-semibold mb-1">
|
||||||
|
{t("balance.onboarding.title")}
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-[var(--muted-foreground)] mb-5">
|
||||||
|
{t("balance.onboarding.subtitle")}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ol className="space-y-3">
|
||||||
|
<Step
|
||||||
|
number={1}
|
||||||
|
state={step1State}
|
||||||
|
icon={<Wallet size={18} />}
|
||||||
|
title={t("balance.onboarding.step1.title")}
|
||||||
|
description={t("balance.onboarding.step1.description")}
|
||||||
|
ctaLabel={t("balance.onboarding.step1.cta")}
|
||||||
|
ctaHref="/balance/accounts"
|
||||||
|
t={t}
|
||||||
|
/>
|
||||||
|
<Step
|
||||||
|
number={2}
|
||||||
|
state={step2State}
|
||||||
|
icon={<FileText size={18} />}
|
||||||
|
title={t("balance.onboarding.step2.title")}
|
||||||
|
description={t("balance.onboarding.step2.description")}
|
||||||
|
ctaLabel={t("balance.onboarding.step2.cta")}
|
||||||
|
ctaHref="/balance/snapshot"
|
||||||
|
disabledHint={t("balance.onboarding.step2.disabledHint")}
|
||||||
|
t={t}
|
||||||
|
/>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Internal — single step row
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
interface StepProps {
|
||||||
|
number: number;
|
||||||
|
state: StepState;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
ctaLabel: string;
|
||||||
|
ctaHref: string;
|
||||||
|
disabledHint?: string;
|
||||||
|
t: TFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Step({
|
||||||
|
number,
|
||||||
|
state,
|
||||||
|
icon,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
ctaLabel,
|
||||||
|
ctaHref,
|
||||||
|
disabledHint,
|
||||||
|
t,
|
||||||
|
}: StepProps) {
|
||||||
|
const isDone = state === "done";
|
||||||
|
const isActive = state === "active";
|
||||||
|
const isDisabled = state === "disabled";
|
||||||
|
|
||||||
|
// Number bubble: green check when done, primary bg when active, muted when disabled.
|
||||||
|
const bubbleClass = isDone
|
||||||
|
? "bg-[var(--positive)] text-white"
|
||||||
|
: isActive
|
||||||
|
? "bg-[var(--primary)] text-white"
|
||||||
|
: "bg-[var(--muted)] text-[var(--muted-foreground)]";
|
||||||
|
|
||||||
|
const titleClass = isDisabled
|
||||||
|
? "text-[var(--muted-foreground)]"
|
||||||
|
: "text-[var(--foreground)]";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
data-testid={`balance-onboarding-step-${number}`}
|
||||||
|
data-state={state}
|
||||||
|
className={`flex items-start gap-4 p-4 rounded-lg border ${
|
||||||
|
isDisabled
|
||||||
|
? "border-[var(--border)] opacity-60"
|
||||||
|
: "border-[var(--border)]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-sm font-semibold ${bubbleClass}`}
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
{isDone ? <Check size={16} /> : number}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="flex items-center gap-2 mb-1">
|
||||||
|
<span className="text-[var(--muted-foreground)]" aria-hidden="true">
|
||||||
|
{icon}
|
||||||
|
</span>
|
||||||
|
<h3 className={`text-sm font-semibold ${titleClass}`}>{title}</h3>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-[var(--muted-foreground)]">{description}</p>
|
||||||
|
{isDisabled && disabledHint && (
|
||||||
|
<p className="text-xs text-[var(--muted-foreground)] italic mt-1">
|
||||||
|
{disabledHint}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="shrink-0 self-center">
|
||||||
|
{isDone ? (
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center gap-1 text-xs text-[var(--positive)] font-medium"
|
||||||
|
data-testid={`balance-onboarding-step-${number}-done-badge`}
|
||||||
|
>
|
||||||
|
<Check size={14} />
|
||||||
|
{t("balance.onboarding.doneBadge")}
|
||||||
|
</span>
|
||||||
|
) : isActive ? (
|
||||||
|
<Link
|
||||||
|
to={ctaHref}
|
||||||
|
data-testid={`balance-onboarding-step-${number}-cta`}
|
||||||
|
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-[var(--primary)] text-white text-sm font-medium hover:opacity-90"
|
||||||
|
>
|
||||||
|
{ctaLabel}
|
||||||
|
<ArrowRight size={14} />
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled
|
||||||
|
data-testid={`balance-onboarding-step-${number}-cta`}
|
||||||
|
aria-disabled="true"
|
||||||
|
title={disabledHint}
|
||||||
|
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg border border-[var(--border)] text-[var(--muted-foreground)] text-sm font-medium cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{ctaLabel}
|
||||||
|
<ArrowRight size={14} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1537,6 +1537,22 @@
|
||||||
"stacked": "Stacked by category"
|
"stacked": "Stacked by category"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"onboarding": {
|
||||||
|
"title": "Get started with your balance sheet",
|
||||||
|
"subtitle": "Two steps to start tracking your net worth.",
|
||||||
|
"doneBadge": "Done",
|
||||||
|
"step1": {
|
||||||
|
"title": "Create an account",
|
||||||
|
"description": "An account is where you keep money: chequing, TFSA, RRSP, stocks, crypto, and so on.",
|
||||||
|
"cta": "Create an account"
|
||||||
|
},
|
||||||
|
"step2": {
|
||||||
|
"title": "Enter a snapshot",
|
||||||
|
"description": "A snapshot is the picture, at a given date, of the balance in each account. Enter one a month to track changes over time.",
|
||||||
|
"cta": "Enter a snapshot",
|
||||||
|
"disabledHint": "Create an account first to unlock this step."
|
||||||
|
}
|
||||||
|
},
|
||||||
"sidebar": "Balance sheet",
|
"sidebar": "Balance sheet",
|
||||||
"accountsPage": {
|
"accountsPage": {
|
||||||
"title": "Balance accounts",
|
"title": "Balance accounts",
|
||||||
|
|
@ -1643,8 +1659,8 @@
|
||||||
"dateLabel": "Snapshot date",
|
"dateLabel": "Snapshot date",
|
||||||
"dateImmutable": "An existing snapshot date cannot be changed. To change the date, delete this snapshot and create a new one.",
|
"dateImmutable": "An existing snapshot date cannot be changed. To change the date, delete this snapshot and create a new one.",
|
||||||
"total": "Entered total",
|
"total": "Entered total",
|
||||||
"noAccounts": "You need to create at least one balance account first.",
|
"noAccounts": "To enter a snapshot, first create at least one account. An account = where you keep money (chequing, TFSA, RRSP, stocks, etc.). A snapshot = the picture of how much was in each account on a given date.",
|
||||||
"goToAccounts": "Go to accounts",
|
"goToAccounts": "Create an account",
|
||||||
"prefill": "Prefill from previous",
|
"prefill": "Prefill from previous",
|
||||||
"prefillTooltip": "Copy values from the snapshot dated {{date}}",
|
"prefillTooltip": "Copy values from the snapshot dated {{date}}",
|
||||||
"prefillNoPrevious": "No earlier snapshot available.",
|
"prefillNoPrevious": "No earlier snapshot available.",
|
||||||
|
|
|
||||||
|
|
@ -1537,6 +1537,22 @@
|
||||||
"stacked": "Empilé par catégorie"
|
"stacked": "Empilé par catégorie"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"onboarding": {
|
||||||
|
"title": "Premiers pas avec le bilan",
|
||||||
|
"subtitle": "Deux étapes pour commencer à suivre votre valeur nette.",
|
||||||
|
"doneBadge": "Fait",
|
||||||
|
"step1": {
|
||||||
|
"title": "Créer un compte",
|
||||||
|
"description": "Un compte représente l'endroit où vous tenez votre argent : compte chèque, CELI, REER, actions, crypto, etc.",
|
||||||
|
"cta": "Créer un compte"
|
||||||
|
},
|
||||||
|
"step2": {
|
||||||
|
"title": "Saisir un snapshot",
|
||||||
|
"description": "Un snapshot est la photo, à une date donnée, du solde de chaque compte. Saisissez-en un par mois pour suivre l'évolution.",
|
||||||
|
"cta": "Saisir un snapshot",
|
||||||
|
"disabledHint": "Créez d'abord un compte pour activer cette étape."
|
||||||
|
}
|
||||||
|
},
|
||||||
"sidebar": "Bilan",
|
"sidebar": "Bilan",
|
||||||
"accountsPage": {
|
"accountsPage": {
|
||||||
"title": "Comptes du bilan",
|
"title": "Comptes du bilan",
|
||||||
|
|
@ -1643,8 +1659,8 @@
|
||||||
"dateLabel": "Date du snapshot",
|
"dateLabel": "Date du snapshot",
|
||||||
"dateImmutable": "La date d'un snapshot existant ne peut pas être modifiée. Pour changer la date, supprimez ce snapshot et créez-en un nouveau.",
|
"dateImmutable": "La date d'un snapshot existant ne peut pas être modifiée. Pour changer la date, supprimez ce snapshot et créez-en un nouveau.",
|
||||||
"total": "Total saisi",
|
"total": "Total saisi",
|
||||||
"noAccounts": "Vous devez d'abord créer au moins un compte de bilan.",
|
"noAccounts": "Pour saisir un snapshot, créez d'abord au moins un compte. Un compte = où vous tenez votre argent (chèque, CELI, REER, actions, etc.). Un snapshot = la photo de combien il y avait dans chaque compte à une date donnée.",
|
||||||
"goToAccounts": "Aller aux comptes",
|
"goToAccounts": "Créer un compte",
|
||||||
"prefill": "Pré-remplir depuis le précédent",
|
"prefill": "Pré-remplir depuis le précédent",
|
||||||
"prefillTooltip": "Copier les valeurs du snapshot du {{date}}",
|
"prefillTooltip": "Copier les valeurs du snapshot du {{date}}",
|
||||||
"prefillNoPrevious": "Aucun snapshot antérieur disponible.",
|
"prefillNoPrevious": "Aucun snapshot antérieur disponible.",
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import {
|
||||||
import { getAllCategories } from "../services/transactionService";
|
import { getAllCategories } from "../services/transactionService";
|
||||||
import type { Category, BalanceAccountTransferWithTransaction } from "../shared/types";
|
import type { Category, BalanceAccountTransferWithTransaction } from "../shared/types";
|
||||||
import BalanceOverviewCard from "../components/balance/BalanceOverviewCard";
|
import BalanceOverviewCard from "../components/balance/BalanceOverviewCard";
|
||||||
|
import BalanceOnboardingCard from "../components/balance/BalanceOnboardingCard";
|
||||||
import BalanceEvolutionChart from "../components/balance/BalanceEvolutionChart";
|
import BalanceEvolutionChart from "../components/balance/BalanceEvolutionChart";
|
||||||
import BalanceAccountsTable from "../components/balance/BalanceAccountsTable";
|
import BalanceAccountsTable from "../components/balance/BalanceAccountsTable";
|
||||||
import LinkTransfersModal from "../components/balance/LinkTransfersModal";
|
import LinkTransfersModal from "../components/balance/LinkTransfersModal";
|
||||||
|
|
@ -127,7 +128,25 @@ export default function BalancePage() {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<BalanceOverviewCard totals={state.evolutionTotals} />
|
{(() => {
|
||||||
|
// Issue #178 — show a 2-step onboarding card while the user has no
|
||||||
|
// accounts or no snapshots yet. We probe accountsLatest for ANY
|
||||||
|
// snapshot date so the empty-state guard is independent of the
|
||||||
|
// active period filter (state.period).
|
||||||
|
const accountsCount = state.accountsLatest.length;
|
||||||
|
const hasAnySnapshot = state.accountsLatest.some(
|
||||||
|
(a) => a.latest_snapshot_date != null
|
||||||
|
);
|
||||||
|
if (accountsCount === 0 || !hasAnySnapshot) {
|
||||||
|
return (
|
||||||
|
<BalanceOnboardingCard
|
||||||
|
accountsCount={accountsCount}
|
||||||
|
snapshotsCount={hasAnySnapshot ? 1 : 0}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <BalanceOverviewCard totals={state.evolutionTotals} />;
|
||||||
|
})()}
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
||||||
{/* Period selector */}
|
{/* Period selector */}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue