feat: implement top expenses categorization and enhance UI with tabs; display top 10 expenses per top 5 parent categories, improving data organization and user navigation in the statistics page
This commit is contained in:
@@ -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() {
|
||||
</div>
|
||||
<div className="mt-4 md:mt-6">
|
||||
<TopExpensesList
|
||||
expenses={stats.topExpenses}
|
||||
expensesByCategory={stats.topExpensesByCategory}
|
||||
categories={data.categories}
|
||||
formatCurrency={formatCurrency}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user