diff --git a/app/statistics/page.tsx b/app/statistics/page.tsx
index 5df3931..f05fc67 100644
--- a/app/statistics/page.tsx
+++ b/app/statistics/page.tsx
@@ -360,22 +360,53 @@ export default function StatisticsPage() {
})
.sort((a, b) => b.value - a.value);
- // Top expenses - deduplicate by ID and sort by amount (most negative first)
+ // Top expenses by top parent categories - deduplicate by ID
const uniqueTransactions = Array.from(
new Map(transactions.map((t) => [t.id, t])).values(),
);
- const topExpenses = uniqueTransactions
- .filter((t) => t.amount < 0)
- .sort((a, b) => {
- // Sort by amount (most negative first)
- if (a.amount !== b.amount) {
- return a.amount - b.amount;
- }
- // If same amount, sort by date (most recent first) for stable sorting
- return new Date(b.date).getTime() - new Date(a.date).getTime();
- })
+ const expenses = uniqueTransactions.filter((t) => t.amount < 0);
+
+ // Get top 5 parent categories by total expenses
+ const topParentCategories = Array.from(categoryTotalsByParent.entries())
+ .map(([groupId, total]) => ({
+ groupId: groupId === "uncategorized" ? null : groupId,
+ total,
+ }))
+ .sort((a, b) => b.total - a.total)
.slice(0, 5);
+ // Get top 10 expenses per top parent category (from all its subcategories)
+ const topExpensesByCategory = topParentCategories.map(({ groupId }) => {
+ const categoryExpenses = expenses
+ .filter((t) => {
+ if (groupId === null) {
+ return !t.categoryId;
+ }
+ // Check if transaction belongs to this parent category or its subcategories
+ const category = data.categories.find((c) => c.id === t.categoryId);
+ if (!category) {
+ return false;
+ }
+ // Use parent category ID if exists, otherwise use the category itself
+ const transactionGroupId = category.parentId || category.id;
+ return transactionGroupId === groupId;
+ })
+ .sort((a, b) => {
+ // Sort by amount (most negative first)
+ if (a.amount !== b.amount) {
+ return a.amount - b.amount;
+ }
+ // If same amount, sort by date (most recent first) for stable sorting
+ return new Date(b.date).getTime() - new Date(a.date).getTime();
+ })
+ .slice(0, 10);
+
+ return {
+ categoryId: groupId,
+ expenses: categoryExpenses,
+ };
+ });
+
// Summary
const totalIncome = transactions
.filter((t) => t.amount >= 0)
@@ -653,7 +684,7 @@ export default function StatisticsPage() {
monthlyChartData,
categoryChartData,
categoryChartDataByParent,
- topExpenses,
+ topExpensesByCategory,
totalIncome,
totalExpenses,
avgMonthlyExpenses,
@@ -1200,7 +1231,7 @@ export default function StatisticsPage() {
diff --git a/components/statistics/top-expenses-list.tsx b/components/statistics/top-expenses-list.tsx
index c921d44..bfd3538 100644
--- a/components/statistics/top-expenses-list.tsx
+++ b/components/statistics/top-expenses-list.tsx
@@ -4,86 +4,156 @@ import Link from "next/link";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { CategoryIcon } from "@/components/ui/category-icon";
import { Badge } from "@/components/ui/badge";
+import {
+ Tabs,
+ TabsList,
+ TabsTrigger,
+ TabsContent,
+} from "@/components/ui/tabs";
import { useIsMobile } from "@/hooks/use-mobile";
import type { Transaction, Category } from "@/lib/types";
interface TopExpensesListProps {
- expenses: Transaction[];
+ expensesByCategory: Array<{
+ categoryId: string | null;
+ expenses: Transaction[];
+ }>;
categories: Category[];
formatCurrency: (amount: number) => string;
}
export function TopExpensesList({
- expenses,
+ expensesByCategory,
categories,
formatCurrency,
}: TopExpensesListProps) {
const isMobile = useIsMobile();
+ // Filtrer les catégories qui ont des dépenses
+ const categoriesWithExpenses = expensesByCategory.filter(
+ (group) => group.expenses.length > 0,
+ );
+
+ const hasExpenses = categoriesWithExpenses.length > 0;
+
+ // Déterminer la valeur par défaut du premier onglet
+ const defaultTabValue =
+ categoriesWithExpenses.length > 0
+ ? categoriesWithExpenses[0].categoryId || "uncategorized"
+ : "";
+
return (
- Top 5 dépenses
+
+ Top 10 dépenses par top 5 catégories parentes
+
- {expenses.length > 0 ? (
-
- {expenses.map((expense, index) => {
- const category = categories.find(
- (c) => c.id === expense.categoryId,
- );
+ {hasExpenses ? (
+
+
+ {categoriesWithExpenses.map(({ categoryId, expenses }) => {
+ const category = categoryId
+ ? categories.find((c) => c.id === categoryId)
+ : null;
+ const tabValue = categoryId || "uncategorized";
+
+ return (
+
+ {category && (
+
+ )}
+
+ {category?.name || "Non catégorisé"}
+
+
+ );
+ })}
+
+ {categoriesWithExpenses.map(({ categoryId, expenses }) => {
+ const category = categoryId
+ ? categories.find((c) => c.id === categoryId)
+ : null;
+ const tabValue = categoryId || "uncategorized";
+
return (
-
-
- {index + 1}
-
-
-
-
- {expense.description}
-
-
- {formatCurrency(expense.amount)}
-
-
-
-
- {new Date(expense.date).toLocaleDateString("fr-FR")}
-
- {category && (
-
-
-
-
- {category.name}
+
+
+ {expenses.map((expense, index) => (
+
+
+ {index + 1}
+
+
+
+
+ {expense.description}
+
+
+ {formatCurrency(expense.amount)}
+
+
+
+
+ {new Date(expense.date).toLocaleDateString(
+ "fr-FR",
+ )}
-
-
- )}
-
+ {expense.categoryId && (() => {
+ const expenseCategory = categories.find(
+ (c) => c.id === expense.categoryId,
+ );
+ // Afficher seulement si c'est une sous-catégorie (a un parentId)
+ if (expenseCategory?.parentId) {
+ return (
+
+
+
+
+ {expenseCategory.name}
+
+
+
+ );
+ }
+ return null;
+ })()}
+
+
+
+ ))}
-
+
);
})}
-
+
) : (
Pas de dépenses pour cette période