diff --git a/app/statistics/page.tsx b/app/statistics/page.tsx index 3dbc0e7..0e48d39 100644 --- a/app/statistics/page.tsx +++ b/app/statistics/page.tsx @@ -17,13 +17,21 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { AccountFilterCombobox } from "@/components/ui/account-filter-combobox"; +import { CategoryFilterCombobox } from "@/components/ui/category-filter-combobox"; +import { Card, CardContent } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { CategoryIcon } from "@/components/ui/category-icon"; +import { Filter, X, Wallet, CircleSlash, Calendar } from "lucide-react"; +import type { Account, Category } from "@/lib/types"; type Period = "3months" | "6months" | "12months" | "all"; export default function StatisticsPage() { const { data, isLoading } = useBankingData(); const [period, setPeriod] = useState("6months"); - const [selectedAccount, setSelectedAccount] = useState("all"); + const [selectedAccounts, setSelectedAccounts] = useState(["all"]); + const [selectedCategories, setSelectedCategories] = useState(["all"]); const stats = useMemo(() => { if (!data) return null; @@ -49,12 +57,24 @@ export default function StatisticsPage() { (t) => new Date(t.date) >= startDate ); - if (selectedAccount !== "all") { + // Filter by accounts + if (!selectedAccounts.includes("all")) { transactions = transactions.filter( - (t) => t.accountId === selectedAccount + (t) => selectedAccounts.includes(t.accountId) ); } + // Filter by categories + if (!selectedCategories.includes("all")) { + if (selectedCategories.includes("uncategorized")) { + transactions = transactions.filter((t) => !t.categoryId); + } else { + transactions = transactions.filter( + (t) => t.categoryId && selectedCategories.includes(t.categoryId) + ); + } + } + // Monthly breakdown const monthlyData = new Map(); transactions.forEach((t) => { @@ -119,17 +139,14 @@ export default function StatisticsPage() { const avgMonthlyExpenses = monthlyData.size > 0 ? totalExpenses / monthlyData.size : 0; - // Balance evolution - Aggregated - const allTransactionsForBalance = data.transactions.filter( - (t) => new Date(t.date) >= startDate - ); - const sortedAllTransactions = [...allTransactionsForBalance].sort( + // Balance evolution - Aggregated (using filtered transactions) + const sortedFilteredTransactions = [...transactions].sort( (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime() ); let runningBalance = 0; const aggregatedBalanceByDate = new Map(); - sortedAllTransactions.forEach((t) => { + sortedFilteredTransactions.forEach((t) => { runningBalance += t.amount; aggregatedBalanceByDate.set(t.date, runningBalance); }); @@ -144,7 +161,7 @@ export default function StatisticsPage() { solde: Math.round(balance), })); - // Balance evolution - Per account + // Balance evolution - Per account (using filtered transactions) const accountBalances = new Map>(); data.accounts.forEach((account) => { accountBalances.set(account.id, new Map()); @@ -156,7 +173,7 @@ export default function StatisticsPage() { accountRunningBalances.set(account.id, 0); }); - sortedAllTransactions.forEach((t) => { + sortedFilteredTransactions.forEach((t) => { const currentBalance = accountRunningBalances.get(t.accountId) || 0; const newBalance = currentBalance + t.amount; accountRunningBalances.set(t.accountId, newBalance); @@ -209,7 +226,7 @@ export default function StatisticsPage() { perAccountBalanceData, transactionCount: transactions.length, }; - }, [data, period, selectedAccount]); + }, [data, period, selectedAccounts, selectedCategories]); const formatCurrency = (amount: number) => { return new Intl.NumberFormat("fr-FR", { @@ -227,21 +244,26 @@ export default function StatisticsPage() { - + /> + + + +
+ + + + - - } - /> +
+ + { + const newAccounts = selectedAccounts.filter((a) => a !== id); + setSelectedAccounts(newAccounts.length > 0 ? newAccounts : ["all"]); + }} + onClearAccounts={() => setSelectedAccounts(["all"])} + selectedCategories={selectedCategories} + onRemoveCategory={(id) => { + const newCategories = selectedCategories.filter((c) => c !== id); + setSelectedCategories(newCategories.length > 0 ? newCategories : ["all"]); + }} + onClearCategories={() => setSelectedCategories(["all"])} + period={period} + onClearPeriod={() => setPeriod("all")} + accounts={data.accounts} + categories={data.categories} + /> +
+
); } + +function ActiveFilters({ + selectedAccounts, + onRemoveAccount, + onClearAccounts, + selectedCategories, + onRemoveCategory, + onClearCategories, + period, + onClearPeriod, + accounts, + categories, +}: { + selectedAccounts: string[]; + onRemoveAccount: (id: string) => void; + onClearAccounts: () => void; + selectedCategories: string[]; + onRemoveCategory: (id: string) => void; + onClearCategories: () => void; + period: Period; + onClearPeriod: () => void; + accounts: Account[]; + categories: Category[]; +}) { + const hasAccounts = !selectedAccounts.includes("all"); + const hasCategories = !selectedCategories.includes("all"); + const hasPeriod = period !== "all"; + + const hasActiveFilters = hasAccounts || hasCategories || hasPeriod; + + if (!hasActiveFilters) return null; + + const selectedAccs = accounts.filter((a) => selectedAccounts.includes(a.id)); + const selectedCats = categories.filter((c) => selectedCategories.includes(c.id)); + const isUncategorized = selectedCategories.includes("uncategorized"); + + const getPeriodLabel = (p: Period) => { + switch (p) { + case "3months": + return "3 mois"; + case "6months": + return "6 mois"; + case "12months": + return "12 mois"; + default: + return "Tout"; + } + }; + + const clearAll = () => { + onClearAccounts(); + onClearCategories(); + onClearPeriod(); + }; + + return ( +
+ + + {selectedAccs.map((acc) => ( + + + {acc.name} + + + ))} + + {isUncategorized && ( + + + Non catégorisé + + + )} + + {selectedCats.map((cat) => ( + + + {cat.name} + + + ))} + + {hasPeriod && ( + + + {getPeriodLabel(period)} + + + )} + + +
+ ); +}