From 1951bb1228e452cdd253ee8e5d4faad05bc105c9 Mon Sep 17 00:00:00 2001 From: medic-bot Date: Sun, 8 Mar 2026 13:06:01 -0400 Subject: [PATCH] fix: sort level 4 categories under their parent in dashboard and budget tables (#23) - Fix sub-group sorting so depth-2 children stay grouped with their intermediate parent instead of falling to the bottom of the section - Reduce pie chart height and show labels only on hover - Adjust grid layout to give more room to the budget vs actual table Co-Authored-By: Claude Opus 4.6 --- CHANGELOG.fr.md | 8 +++ CHANGELOG.md | 8 +++ src/components/dashboard/CategoryPieChart.tsx | 6 +-- src/hooks/useBudget.ts | 26 ++++++++-- src/pages/DashboardPage.tsx | 2 +- src/services/budgetService.ts | 50 +++++++++++++------ 6 files changed, 77 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.fr.md b/CHANGELOG.fr.md index 7066584..30c6a4d 100644 --- a/CHANGELOG.fr.md +++ b/CHANGELOG.fr.md @@ -5,6 +5,14 @@ ### Ajouté - Tableau de budget : colonne du total de l'année précédente affichée comme première colonne de données pour servir de référence (#16) +### Corrigé +- Tableau de bord : les catégories de niveau 4 s'affichent maintenant sous leur parent au lieu d'en bas de la section (#23) +- Tableau de budget : les catégories de niveau 4 s'affichent maintenant sous leur parent au lieu d'en bas de la section (#23) + +### Modifié +- Tableau de bord : les étiquettes du graphique circulaire n'apparaissent maintenant qu'au survol pour économiser de l'espace (#23) +- Tableau de bord : disposition du graphique circulaire et du tableau budget vs réel ajustée pour donner plus d'espace au tableau (#23) + ## [0.6.3] ### Ajouté diff --git a/CHANGELOG.md b/CHANGELOG.md index edd44d6..1ecfb3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ ### Added - Budget table: previous year total column displayed as first data column for baseline reference (#16) +### Fixed +- Dashboard: level 4 categories now display under their parent instead of at the bottom of the section (#23) +- Budget table: level 4 categories now display under their parent instead of at the bottom of the section (#23) + +### Changed +- Dashboard: pie chart labels now only appear on hover to save space (#23) +- Dashboard: pie chart and budget vs actual table layout adjusted to give more room to the table (#23) + ## [0.6.3] ### Added diff --git a/src/components/dashboard/CategoryPieChart.tsx b/src/components/dashboard/CategoryPieChart.tsx index 2fe8195..803b82f 100644 --- a/src/components/dashboard/CategoryPieChart.tsx +++ b/src/components/dashboard/CategoryPieChart.tsx @@ -43,7 +43,7 @@ export default function CategoryPieChart({ } return ( -
+
{hiddenCategories.size > 0 && (
{t("charts.hiddenCategories")}: @@ -67,7 +67,7 @@ export default function CategoryPieChart({ )}
- +
-
+
{data.map((item, index) => { const isHidden = hiddenCategories.has(item.category_name); return ( diff --git a/src/hooks/useBudget.ts b/src/hooks/useBudget.ts index 9ee0899..ce70a5f 100644 --- a/src/hooks/useBudget.ts +++ b/src/hooks/useBudget.ts @@ -317,6 +317,7 @@ export function useBudget() { } // Sort by type, then within each type: keep hierarchy groups together + // Sub-group logic: depth-2 children must stay under their intermediate parent function getTopGroupId(r: BudgetYearRow): number { if ((r.depth ?? 0) === 0) return r.category_id; if (r.is_parent && r.parent_id === null) return r.category_id; @@ -328,6 +329,11 @@ export function useBudget() { } return r.category_id; } + function getSubGroupId(r: BudgetYearRow): number { + if ((r.depth ?? 0) === 2 && r.parent_id !== null) return r.parent_id; + if ((r.depth ?? 0) === 1 && r.is_parent) return r.category_id; + return r.category_id; + } rows.sort((a, b) => { const typeA = TYPE_ORDER[a.category_type] ?? 9; @@ -343,9 +349,23 @@ export function useBudget() { if (orderA !== orderB) return orderA - orderB; return (catA?.name ?? "").localeCompare(catB?.name ?? ""); } - // Same group: sort by depth, then parent before children at same depth - if (a.is_parent !== b.is_parent && (a.depth ?? 0) === (b.depth ?? 0)) return a.is_parent ? -1 : 1; - if ((a.depth ?? 0) !== (b.depth ?? 0)) return (a.depth ?? 0) - (b.depth ?? 0); + // Within same group: depth-0 parent always first + if (a.is_parent && (a.depth ?? 0) === 0) return -1; + if (b.is_parent && (b.depth ?? 0) === 0) return 1; + // "(direct)" of top-level parent always second + if (!a.is_parent && (a.depth ?? 0) === 1 && a.parent_id === a.category_id) return -1; + if (!b.is_parent && (b.depth ?? 0) === 1 && b.parent_id === b.category_id) return 1; + // Sub-group: keep depth-2 children with their intermediate parent + const subA = getSubGroupId(a); + const subB = getSubGroupId(b); + if (subA !== subB) { + const nameA = catById.get(subA)?.name ?? ""; + const nameB = catById.get(subB)?.name ?? ""; + return nameA.localeCompare(nameB); + } + // Same sub-group: parent before children + if (a.is_parent !== b.is_parent) return a.is_parent ? -1 : 1; + // "(direct)" within sub-group first if (a.parent_id && a.category_id === a.parent_id) return -1; if (b.parent_id && b.category_id === b.parent_id) return 1; return a.category_name.localeCompare(b.category_name); diff --git a/src/pages/DashboardPage.tsx b/src/pages/DashboardPage.tsx index d484fbf..e6ccfe9 100644 --- a/src/pages/DashboardPage.tsx +++ b/src/pages/DashboardPage.tsx @@ -126,7 +126,7 @@ export default function DashboardPage() { ))}
-
+

{t("dashboard.expensesByCategory")}

{ const typeA = TYPE_ORDER[a.category_type] ?? 9; const typeB = TYPE_ORDER[b.category_type] ?? 9; if (typeA !== typeB) return typeA - typeB; - // Find the top-level group id - function getGroupId(r: BudgetVsActualRow): number { - if (r.depth === 0) return r.category_id; - if (r.is_parent && r.parent_id === null) return r.category_id; - // Walk up to find the root - let pid = r.parent_id; - while (pid !== null) { - const pCat = catById.get(pid); - if (!pCat || !pCat.parent_id) return pid; - pid = pCat.parent_id; - } - return r.category_id; - } const groupA = getGroupId(a); const groupB = getGroupId(b); if (groupA !== groupB) { @@ -442,9 +446,23 @@ export async function getBudgetVsActualData( if (orderA !== orderB) return orderA - orderB; return (catA?.name ?? "").localeCompare(catB?.name ?? ""); } - // Within same group: sort by depth, then parent before children - if (a.is_parent !== b.is_parent && (a.depth ?? 0) === (b.depth ?? 0)) return a.is_parent ? -1 : 1; - if ((a.depth ?? 0) !== (b.depth ?? 0)) return (a.depth ?? 0) - (b.depth ?? 0); + // Within same group: depth-0 parent always first + if (a.is_parent && (a.depth ?? 0) === 0) return -1; + if (b.is_parent && (b.depth ?? 0) === 0) return 1; + // "(direct)" of top-level parent always second + if (!a.is_parent && (a.depth ?? 0) === 1 && a.parent_id === a.category_id) return -1; + if (!b.is_parent && (b.depth ?? 0) === 1 && b.parent_id === b.category_id) return 1; + // Sub-group: keep depth-2 children with their intermediate parent + const subA = getSubGroupId(a); + const subB = getSubGroupId(b); + if (subA !== subB) { + const nameA = catById.get(subA)?.name ?? ""; + const nameB = catById.get(subB)?.name ?? ""; + return nameA.localeCompare(nameB); + } + // Same sub-group: parent before children + if (a.is_parent !== b.is_parent) return a.is_parent ? -1 : 1; + // "(direct)" within sub-group first if (a.parent_id && a.category_id === a.parent_id) return -1; if (b.parent_id && b.category_id === b.parent_id) return 1; return a.category_name.localeCompare(b.category_name);