fix: sort level-4 categories under their parent in budget vs actual table
Rework the child row sorting in getBudgetVsActualData to preserve parent-child grouping: sub-groups (depth-1 parent + depth-2 children) now stay together instead of being sorted flat alphabetically. Also reduce pie chart size (height 280->200, radii reduced), show legend labels only on hover, and change dashboard grid from 1:1 to 1:2 ratio to give more space to the budget vs actual table. Ref #23 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8742c25945
commit
b0d81522b2
5 changed files with 73 additions and 13 deletions
|
|
@ -5,6 +5,13 @@
|
||||||
### Ajouté
|
### 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)
|
- 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 apparaissent maintenant directement sous leur parent dans le tableau budget vs réel au lieu d'en bas de la section (#23)
|
||||||
|
|
||||||
|
### Modifié
|
||||||
|
- Tableau de bord : graphique circulaire réduit en taille et étiquettes de la légende affichées seulement au survol pour donner plus d'espace au tableau budget vs réel (#23)
|
||||||
|
- Tableau de bord : disposition du graphique circulaire et du tableau budget passée d'un ratio 1:1 à 1:2 pour une meilleure lisibilité du tableau (#23)
|
||||||
|
|
||||||
## [0.6.3]
|
## [0.6.3]
|
||||||
|
|
||||||
### Ajouté
|
### Ajouté
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,13 @@
|
||||||
### Added
|
### Added
|
||||||
- Budget table: previous year total column displayed as first data column for baseline reference (#16)
|
- Budget table: previous year total column displayed as first data column for baseline reference (#16)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Dashboard: level-4 categories now appear directly under their parent in the budget vs actual table instead of at the bottom of the section (#23)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Dashboard: pie chart reduced in size and legend labels only shown on hover to give more space to the budget vs actual table (#23)
|
||||||
|
- Dashboard: pie chart and budget table layout changed from equal 1:1 to 1:2 ratio for better table readability (#23)
|
||||||
|
|
||||||
## [0.6.3]
|
## [0.6.3]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ export default function CategoryPieChart({
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const hoveredRef = useRef<CategoryBreakdownItem | null>(null);
|
const hoveredRef = useRef<CategoryBreakdownItem | null>(null);
|
||||||
const [contextMenu, setContextMenu] = useState<{ x: number; y: number; item: CategoryBreakdownItem } | null>(null);
|
const [contextMenu, setContextMenu] = useState<{ x: number; y: number; item: CategoryBreakdownItem } | null>(null);
|
||||||
|
const [showLegend, setShowLegend] = useState(false);
|
||||||
|
|
||||||
const visibleData = data.filter((d) => !hiddenCategories.has(d.category_name));
|
const visibleData = data.filter((d) => !hiddenCategories.has(d.category_name));
|
||||||
const total = visibleData.reduce((sum, d) => sum + d.total, 0);
|
const total = visibleData.reduce((sum, d) => sum + d.total, 0);
|
||||||
|
|
@ -66,8 +67,12 @@ export default function CategoryPieChart({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div onContextMenu={handleContextMenu}>
|
<div
|
||||||
<ResponsiveContainer width="100%" height={280}>
|
onContextMenu={handleContextMenu}
|
||||||
|
onMouseEnter={() => setShowLegend(true)}
|
||||||
|
onMouseLeave={() => setShowLegend(false)}
|
||||||
|
>
|
||||||
|
<ResponsiveContainer width="100%" height={200}>
|
||||||
<PieChart>
|
<PieChart>
|
||||||
<ChartPatternDefs
|
<ChartPatternDefs
|
||||||
prefix="cat-pie"
|
prefix="cat-pie"
|
||||||
|
|
@ -79,8 +84,8 @@ export default function CategoryPieChart({
|
||||||
nameKey="category_name"
|
nameKey="category_name"
|
||||||
cx="50%"
|
cx="50%"
|
||||||
cy="50%"
|
cy="50%"
|
||||||
innerRadius={50}
|
innerRadius={35}
|
||||||
outerRadius={100}
|
outerRadius={75}
|
||||||
paddingAngle={2}
|
paddingAngle={2}
|
||||||
>
|
>
|
||||||
{visibleData.map((item, index) => (
|
{visibleData.map((item, index) => (
|
||||||
|
|
@ -110,7 +115,11 @@ export default function CategoryPieChart({
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</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 transition-all duration-200 overflow-hidden ${
|
||||||
|
showLegend ? "max-h-96 opacity-100" : "max-h-0 opacity-0"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{data.map((item, index) => {
|
{data.map((item, index) => {
|
||||||
const isHidden = hiddenCategories.has(item.category_name);
|
const isHidden = hiddenCategories.has(item.category_name);
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ export default function DashboardPage() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
|
<div className="grid grid-cols-1 lg:grid-cols-[minmax(0,1fr)_minmax(0,2fr)] gap-4 mb-6">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold mb-3">{t("dashboard.expensesByCategory")}</h2>
|
<h2 className="text-lg font-semibold mb-3">{t("dashboard.expensesByCategory")}</h2>
|
||||||
<CategoryPieChart
|
<CategoryPieChart
|
||||||
|
|
|
||||||
|
|
@ -404,13 +404,50 @@ export async function getBudgetVsActualData(
|
||||||
|
|
||||||
rows.push(parent);
|
rows.push(parent);
|
||||||
|
|
||||||
// Sort: "(direct)" first, then subtotals with their children, then alphabetical leaves
|
// Sort preserving parent-child grouping:
|
||||||
allChildRows.sort((a, b) => {
|
// 1. "(direct)" first
|
||||||
if (a.category_id === cat.id && !a.is_parent) return -1;
|
// 2. Sub-groups (depth-1 parent + its depth-2 children) stay together, sorted by parent name
|
||||||
if (b.category_id === cat.id && !b.is_parent) return 1;
|
// 3. Standalone depth-1 leaves sorted alphabetically
|
||||||
|
const directRow = allChildRows.find((r) => r.category_id === cat.id && !r.is_parent);
|
||||||
|
const subGroups: { parent: BudgetVsActualRow; children: BudgetVsActualRow[] }[] = [];
|
||||||
|
const standaloneLeaves: BudgetVsActualRow[] = [];
|
||||||
|
|
||||||
|
for (const row of allChildRows) {
|
||||||
|
if (row.category_id === cat.id && !row.is_parent) continue; // skip direct row
|
||||||
|
if (row.is_parent && row.depth === 1) {
|
||||||
|
subGroups.push({ parent: row, children: [] });
|
||||||
|
} else if (row.depth === 2 && subGroups.length > 0) {
|
||||||
|
// Find the matching sub-group for this child
|
||||||
|
const matchingGroup = subGroups.find((g) => g.parent.category_id === row.parent_id);
|
||||||
|
if (matchingGroup) {
|
||||||
|
matchingGroup.children.push(row);
|
||||||
|
} else {
|
||||||
|
standaloneLeaves.push(row);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
standaloneLeaves.push(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort sub-groups by parent name, children within each group alphabetically
|
||||||
|
subGroups.sort((a, b) => a.parent.category_name.localeCompare(b.parent.category_name));
|
||||||
|
for (const group of subGroups) {
|
||||||
|
group.children.sort((a, b) => {
|
||||||
|
// "(direct)" entries first within group
|
||||||
|
if (a.category_id === group.parent.category_id) return -1;
|
||||||
|
if (b.category_id === group.parent.category_id) return 1;
|
||||||
return a.category_name.localeCompare(b.category_name);
|
return a.category_name.localeCompare(b.category_name);
|
||||||
});
|
});
|
||||||
rows.push(...allChildRows);
|
}
|
||||||
|
standaloneLeaves.sort((a, b) => a.category_name.localeCompare(b.category_name));
|
||||||
|
|
||||||
|
const sortedChildren: BudgetVsActualRow[] = [];
|
||||||
|
if (directRow) sortedChildren.push(directRow);
|
||||||
|
for (const group of subGroups) {
|
||||||
|
sortedChildren.push(group.parent, ...group.children);
|
||||||
|
}
|
||||||
|
sortedChildren.push(...standaloneLeaves);
|
||||||
|
rows.push(...sortedChildren);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue