feat(reports): collapse/expand sub-categories in comparable reports (#254) #261
No reviewers
Labels
No labels
autopilot:pending-human
source:analyste
source:defenseur
source:human
source:medic
status:approved
status:blocked
status:in-progress
status:needs-clarification
status:needs-fix
status:ready
status:review
status:triage
type:bug
type:feature
type:infra
type:refactor
type:schema
type:security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: maximus/Simpl-Resultat#261
Loading…
Reference in a new issue
No description provided.
Delete branch "issue-254-collapse-expand"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Resolves #254
Résumé
Ajoute le repli/dépli des sous-catégories dans les deux rapports comparables hiérarchiques de la page Compare : réel-vs-réel (
ComparePeriodTable) et réel-vs-budget (BudgetVsActualTable).Décisions de design (à valider)
is_parent/depth), tous deux sous la page Compare.CategoryOverTimeTable(Tendances → par catégorie), évoqué ailleurs comme 2ᵉ rapport, est une liste plate top-N sans hiérarchie de sous-catégories → rien à replier, laissé hors scope.userPreferenceServicedu Bilan. Synchrone (pas de flash), pur UI. On persiste l'ensemble des groupes dépliés (exceptions au défaut replié) → ensemble vide = tout replié.depth 0; les déplier révèle tout le sous-arbre. Pas de repli indépendant des parents intermédiaires (v1 simple, cohérent avec « replier les sous-catégories sous une catégorie »).Implémentation
src/utils/collapsibleRows.ts(filtre de visibilité, extraction des clés de groupe, (dé)sérialisation de l'ensemble déplié) — 12 tests vitest.src/hooks/useCollapsibleGroups.ts: state + persistance localStorage, bâti sur le module pur.depthOf/isParent/keyOf) qui reflètent la logique de rendu existante de chaque tableau → les lignes masquées sont exactement les descendants indentés d'un groupe.reports.collapse.{expandAll,collapseAll}(FR « Tout déplier/replier » + EN), CHANGELOG FR/EN.Validation locale
npm run build(tsc + vite) vertnpm testvert — 713 tests (701 baseline + 12 nouveaux)/pr-reviewPR #261 — collapse/expand des sous-catégoriesVerdict : APPROVE ✅
Résumé
Feature propre et bien découpée : un module pur testé (
collapsibleRows.ts, 12 tests), un hook mince de state + persistance (useCollapsibleGroups.ts), et des accessors par tableau branchés sur les deux tables hiérarchiques (ComparePeriodTable,BudgetVsActualTable). Le repli/dépli est purement visuel — vérifié : les sous-totaux de section, le total général et les lignes de résultat sont calculés sur les lignes brutes (sumLeaves/computeResults/section.rows.filter), jamais sur les lignes visibles → replier ne modifie aucun total.Points vérifiés
depthOf/isParent/keyOfreproduisent exactement les expressions de rendu de chaque table (depth ?? 0côté Compare,depth ?? (parent_id !== null && !is_parent ? 1 : 0)côté BVA). Confirmé côté services :budgetServiceetreportServicepeuplent toujoursdepthexplicitement à chaque niveau et émettent[subtotal, …enfants](ordre parent-first) → les fallbacks??ne se déclenchent jamais en pratique et l'ordre depth-first attendu parvisibleRowsest garanti. Les lignes masquées sont donc exactement les descendants indentés du groupe.reorderRows(groups.visible(rows), subtotalsOnTop)saine.visiblefiltre des sous-arbres entiers en préservant l'ordre relatif, puisreorderRowsréordonne le reste. Tracé sur les cas mixte / tout-replié / sous-totaux-en-bas → sortie correcte, pas de régression du toggle sous-totaux voisin.CategoryOverTimeTable(Tendances → par catégorie) est bien une liste plate (section.categories: string[], aucunis_parent/depth) → rien à replier, exclusion justifiée. Les deux tables retenues sont exactement les deux rapports hiérarchiques nommés dans #254.parseStoredExpandedgèrenull/""/JSON corrompu/non-array → set vide (défaut replié), et filtre les membres non-string. Try/catch autour dusetItem. Clés localStorage distinctes (reports-bva-expanded,reports-compare-expanded) — pas de collision avec les clés*subtotals-positionexistantes.reports.collapse.{expandAll,collapseAll}), aucune chaîne en dur. CHANGELOG FR + EN sous[Unreleased]. a11y :aria-expandedprésent, le bouton porte le nom de catégorie comme nom accessible. Aucune migration DB. Commit conventional unique référençant #254.Suggestions (non bloquantes)
ComparePeriodTableetBudgetVsActualTable(~20 lignes identiques modulocategoryColor/categoryNamevscategory_color/category_name). Un petit composant partagéCollapsibleCategoryCellserait plus DRY — mais vu que les deux tables dupliquent déjà beaucoup de structure avec des noms de champs divergents, l'inlining reste un choix défendable.subtotalsOnBottom, le chevron du groupe est rendu là où se pose le sous-total (en bas du groupe une fois déplié) ; déplier fait apparaître les enfants au-dessus de la ligne cliquée. Fonctionnellement correct, légèrement inhabituel — à surveiller si un retour utilisateur remonte.keyOfcôté Compare =String(row.categoryId)→"null"sicategoryIdétait null ; en pratique les parents de premier niveau ont toujours un id réel, donc pas de collision atteignable. Note défensive seulement.Build + 713 vitest annoncés verts ; PR ciblant
maindonc couverte parcheck.yml.