feat: improve balance calculations in statistics page and charts with enhanced formatting for large values

This commit is contained in:
Julien Froidefond
2025-11-30 12:19:34 +01:00
parent 184a073bb1
commit ebf842cf6d
3 changed files with 87 additions and 17 deletions

View File

@@ -181,7 +181,7 @@ export default function StatisticsPage() {
.map(([month, values]) => ({
month: new Date(month + "-01").toLocaleDateString("fr-FR", {
month: "short",
year: "2-digit",
year: "numeric",
}),
revenus: Math.round(values.income),
depenses: Math.round(values.expenses),
@@ -232,18 +232,40 @@ export default function StatisticsPage() {
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
);
// Start with sum of initial balances for filtered accounts
// Calculate starting balance: initialBalance + transactions before startDate
let runningBalance = 0;
if (selectedAccounts.includes("all")) {
runningBalance = data.accounts.reduce(
const accountsToUse = selectedAccounts.includes("all")
? data.accounts
: data.accounts.filter((acc) => selectedAccounts.includes(acc.id));
// Start with initial balances
runningBalance = accountsToUse.reduce(
(sum, acc) => sum + (acc.initialBalance || 0),
0,
);
// Add all transactions before the start date for these accounts
const accountsToUseIds = new Set(accountsToUse.map((a) => a.id));
data.transactions
.filter((t) => {
// Filter by account
if (!accountsToUseIds.has(t.accountId)) return false;
// Filter by category if needed
if (!selectedCategories.includes("all")) {
if (selectedCategories.includes("uncategorized")) {
if (t.categoryId) return false;
} else {
runningBalance = data.accounts
.filter((acc) => selectedAccounts.includes(acc.id))
.reduce((sum, acc) => sum + (acc.initialBalance || 0), 0);
if (!t.categoryId || !selectedCategories.includes(t.categoryId))
return false;
}
}
// Only transactions before startDate
const transactionDate = new Date(t.date);
return transactionDate < startDate;
})
.forEach((t) => {
runningBalance += t.amount;
});
const aggregatedBalanceByDate = new Map<string, number>();
sortedFilteredTransactions.forEach((t) => {
@@ -257,6 +279,7 @@ export default function StatisticsPage() {
date: new Date(date).toLocaleDateString("fr-FR", {
day: "2-digit",
month: "short",
year: "numeric",
}),
solde: Math.round(balance),
}));
@@ -267,10 +290,31 @@ export default function StatisticsPage() {
accountBalances.set(account.id, new Map());
});
// Calculate running balance per account (start with initialBalance)
// Calculate running balance per account
// Start with initialBalance + transactions before startDate
const accountRunningBalances = new Map<string, number>();
data.accounts.forEach((account) => {
accountRunningBalances.set(account.id, account.initialBalance || 0);
let accountBalance = account.initialBalance || 0;
// Add transactions before startDate for this account
data.transactions
.filter((t) => {
if (t.accountId !== account.id) return false;
// Filter by category if needed
if (!selectedCategories.includes("all")) {
if (selectedCategories.includes("uncategorized")) {
if (t.categoryId) return false;
} else {
if (!t.categoryId || !selectedCategories.includes(t.categoryId))
return false;
}
}
const transactionDate = new Date(t.date);
return transactionDate < startDate;
})
.forEach((t) => {
accountBalance += t.amount;
});
accountRunningBalances.set(account.id, accountBalance);
});
sortedFilteredTransactions.forEach((t) => {
@@ -292,8 +336,10 @@ export default function StatisticsPage() {
const sortedDates = Array.from(allDates).sort();
const lastBalances = new Map<string, number>();
// Initialize with the starting balance (initialBalance + transactions before startDate)
data.accounts.forEach((account) => {
lastBalances.set(account.id, account.initialBalance || 0);
const startingBalance = accountRunningBalances.get(account.id) || 0;
lastBalances.set(account.id, startingBalance);
});
const perAccountBalanceData = sortedDates.map((date) => {
@@ -301,6 +347,7 @@ export default function StatisticsPage() {
date: new Date(date).toLocaleDateString("fr-FR", {
day: "2-digit",
month: "short",
year: "numeric",
}),
};

View File

@@ -90,7 +90,18 @@ export function BalanceLineChart({
className="text-xs"
interval="preserveStartEnd"
/>
<YAxis className="text-xs" tickFormatter={(v) => `${v}`} />
<YAxis
className="text-xs"
width={80}
tickFormatter={(v) => {
// Format compact pour les grandes valeurs
if (Math.abs(v) >= 1000) {
return `${(v / 1000).toFixed(1)}k€`;
}
return `${Math.round(v)}`;
}}
tick={{ fill: "var(--muted-foreground)" }}
/>
<Tooltip
formatter={(value: number) => formatCurrency(value)}
contentStyle={{

View File

@@ -37,9 +37,21 @@ export function MonthlyChart({ data, formatCurrency }: MonthlyChartProps) {
<BarChart data={data}>
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
<XAxis dataKey="month" className="text-xs" />
<YAxis className="text-xs" tickFormatter={(v) => `${v}`} />
<YAxis
className="text-xs"
width={80}
tickFormatter={(v) => {
// Format compact pour les grandes valeurs
if (Math.abs(v) >= 1000) {
return `${(v / 1000).toFixed(1)}k€`;
}
return `${Math.round(v)}`;
}}
tick={{ fill: "var(--muted-foreground)" }}
/>
<Tooltip
formatter={(value: number) => formatCurrency(value)}
labelFormatter={(label) => label}
contentStyle={{
backgroundColor: "var(--card)",
border: "1px solid var(--border)",