feat(reports): shared FilterPanel component (temporal slot + account multi-select) #282
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#282
Loading…
Reference in a new issue
No description provided.
Delete branch "issue-274-filter-panel"
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?
Generated autonomously by /autopilot run of 2026-07-11
Resolves #274
Summary
Adds the shared
<FilterPanel>component (src/components/reports/FilterPanel.tsx) for the report pages:temporalControl: ReactNodeprop and renders it as-is (notemporalModeenum — rejected during planning to avoid mixing state sources; each page keeps owning its own temporal state).accounts: ImportSource[](import sources), controlled viaaccountIds: number[]+onAccountIdsChange. EmptyaccountIds= no filter (all sources).toggleAccountId(accountIds, id)helper for direct unit testing.reports.filters.accounts.{label,hint}. Copy deliberately says "sources"/"sources d'import" everywhere (mirroring the existingtransactions.filters.allSourcesvocabulary), never "comptes"/"accounts", so the filter reads as distinct from the Bilan module'sbalance_accounts("Compte"/"Account" throughout that module's own UI) — this was an explicit acceptance criterion.Scope is intentionally tight per the issue: this creates the standalone shared component only. Wiring it into the actual report pages (Trends/ByCategory/Compare/Budget) is left to follow-up issues #275/#276.
Testing approach note (caveat)
This project has no
@testing-library/react/jsdom configured (seePriceFetchControl.test.tsx's precedent, which bypasses rendering entirely and re-simulates logic inline). SinceFilterPanelhas no internal state/hooks besidesuseTranslation(mocked to a plain function in the test), the test file calls the component directly as a plain function and walks the returned React-element tree (a plain object graph — JSX creation never touches the DOM) to assert render output and simulate checkbox clicks by invoking.props.onChange()directly. This exercises the real shipped component code rather than re-implementing its logic in the test, which is a stronger form of coverage than the established precedent while still requiring no jsdom.Decisions (MEDIUM ambiguity, noted per instructions)
accountIdsis empty: all checkboxes render unchecked (no accounts explicitly selected = no active filter = all accounts shown). Checking boxes progressively narrows the filter to just the checked ids; unchecking the last one returns to[]. This is the literal, lowest-risk reading of the acceptance criterion ("empty selection → accountIds = [] = all") and needs no extra pseudo-"All" toggle state.reports.filters.accounts.hint, i18n'd) clarifying "no selection = all sources", since the checkbox-list has no explicit "All" affordance.Test plan
npm run build(tsc + vite) greennpm test— 773 ��� 788 (added 15, zero regressions)### Added)en.json/fr.json/pr-review— PR #282 ·<FilterPanel>partagé (slot temporel + multi-select comptes)Verdict : APPROVE
Résumé
Composant d'affichage partagé propre, contrôlé (aucune donnée fetchée en propre), au scope volontairement serré : le composant standalone seul, le câblage dans les pages reste pour #275/#276. Bien testé, conventions respectées, aucun blocage.
Vérifications
ImportSource(src/shared/types/index.ts) correspond exactement aux fixtures du test ;accountIds: number[]cohérent avec la fondation posée en #272/#273.reports.filters.accounts.{label,hint}ajoutées dans les deux locales (en.json/fr.json), placées sous le bon namespacereports.filters; aucun texte en dur, tout passe part().balance_accounts). Aligné sur le vocabulaire existanttransactions.filters.allSources.bg-[var(--card)] rounded-xl border border-[var(--border)]etaccent-[var(--primary)]sur la checkbox conformes aux composants reports existants (CategoryZoomHeader:63, cartes de charts).role="group"+aria-label(pattern déjà utilisé parViewModeToggle/TrendsChartTypeToggle), checkboxes enveloppées dans un<label>(association implicite, cible cliquable correcte).toggleAccountIdimmuable, ordre préservé au retrait, testé y compris la non-mutation. Câblagechecked/onChangecorrect.toggleAccountId+ 7 render + 3 wiring), aucun.only/.skip, cohérent avec le compte annoncé 773→788. L'approche (appel direct du composant comme fonction pure + walk de l'arbre d'éléments) exerce le vrai code livré plutôt que d'en ré-implémenter la logique — plus fort que le précédentPriceFetchControl.test.tsx.### Added, référence #274.feat(reports): …).Suggestions (non bloquantes)
FilterPanel.test.tsx:191) — le test « checking a second account preserves the first » rend le composant une seule fois avecaccountIds: [], donc les deux handlersonChangecapturent le même[]figé ; les assertions[1]puis[2]sont correctes pour cette simulation, mais le test ne prouve pas l'accumulation[1]→[1,2](qui exigerait un re-render avec la prop mise à jour). L'accumulation est bien couverte par les tests purstoggleAccountId([1,2],3)=[1,2,3]— le nom du test surestime juste ce qu'il vérifie.accountsvide — quand la liste est vide, le panneau affiche quand même le label « Sources d'import », l'icône et le hint, sans aucune case. Le câblage (#275/#276) pourrait masquer le bloc comptes siaccounts.length === 0.accountIds.length === 0.Inbox— connote habituellement un état vide ; une icône type filtre/entrée de fichier lirait peut-être plus clairement « sources d'import ». Purement cosmétique.FilterPanel(props)casserait si le composant gagnait plus tard un vrai hook d'état (useState/useEffect). Documenté dans le commentaire, acceptable — à garder en tête pour les PRs de câblage si le composant devient stateful.Note pile stackée
CI
check.ymlne se déclenche pas sur ce maillon (base =issue-273-…, pasmain) — il afficheno-status. Valider le tip cumulé en local (tsc && vite build,vitest run,cargo check/test) avant de merger la pile #272→#276.Merged into
mainvia local fast-forward (commitfe9ae01) as a linear stack. Forgejo doesn't flag non-API merges as merged; closing manually.Pull request closed