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 <noreply@anthropic.com>
This commit is contained in:
parent
8742c25945
commit
1951bb1228
6 changed files with 77 additions and 23 deletions
|
|
@ -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é
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export default function CategoryPieChart({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="bg-[var(--card)] rounded-xl p-6 border border-[var(--border)]">
|
||||
<div className="bg-[var(--card)] rounded-xl p-6 border border-[var(--border)] group">
|
||||
{hiddenCategories.size > 0 && (
|
||||
<div className="flex flex-wrap items-center gap-2 mb-3">
|
||||
<span className="text-xs text-[var(--muted-foreground)]">{t("charts.hiddenCategories")}:</span>
|
||||
|
|
@ -67,7 +67,7 @@ export default function CategoryPieChart({
|
|||
)}
|
||||
|
||||
<div onContextMenu={handleContextMenu}>
|
||||
<ResponsiveContainer width="100%" height={280}>
|
||||
<ResponsiveContainer width="100%" height={220}>
|
||||
<PieChart>
|
||||
<ChartPatternDefs
|
||||
prefix="cat-pie"
|
||||
|
|
@ -110,7 +110,7 @@ export default function CategoryPieChart({
|
|||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-x-4 gap-y-1 mt-2">
|
||||
<div className="flex flex-wrap gap-x-4 gap-y-1 mt-2 max-h-0 overflow-hidden opacity-0 group-hover:max-h-96 group-hover:opacity-100 transition-all duration-300">
|
||||
{data.map((item, index) => {
|
||||
const isHidden = hiddenCategories.has(item.category_name);
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ export default function DashboardPage() {
|
|||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-[1fr_2fr] gap-4 mb-6">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold mb-3">{t("dashboard.expensesByCategory")}</h2>
|
||||
<CategoryPieChart
|
||||
|
|
|
|||
|
|
@ -415,15 +415,10 @@ export async function getBudgetVsActualData(
|
|||
}
|
||||
|
||||
// Sort by type, then within same type keep parent+children groups together
|
||||
rows.sort((a, b) => {
|
||||
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
|
||||
// Sub-group logic: depth-2 children must stay under their intermediate parent
|
||||
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);
|
||||
|
|
@ -432,6 +427,15 @@ export async function getBudgetVsActualData(
|
|||
}
|
||||
return r.category_id;
|
||||
}
|
||||
function getSubGroupId(r: BudgetVsActualRow): 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;
|
||||
const typeB = TYPE_ORDER[b.category_type] ?? 9;
|
||||
if (typeA !== typeB) return typeA - typeB;
|
||||
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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue