Catégorie parent et choix de catégorie du mot-clé : menu déroulant avec saisie (CategoryCombobox) #423
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#423
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Contexte
Deux formulaires choisissent une catégorie dans un
<select>natif, sans saisie au clavier :src/components/categories/CategoryForm.tsx:44-66, 133-146— champ « Catégorie parente » (création et édition). Options : racines et catégories de niveau 1, indentées par espaces insécables, plus « Aucun parent ». Le nom brut (c.name) est affiché, alors que l'arbre de la même page traduit les catégories du seed pari18n_key(CategoryTree.tsx:149).src/components/categories/AddKeywordDialog.tsx:160-175— champ « Catégorie » du dialogue « Ajouter un mot-clé », ouvert par le clic droit sur une transaction (TransactionsPage.tsx:192,ReportsHighlightsPage.tsx:124,ReportsCategoryPage.tsx:102). Options : toutes les catégories actives, à plat, sans hiérarchie, nom brut.La page Transactions utilise
CategoryCombobox(src/components/shared/CategoryCombobox.tsx) : saisie libre, filtrage insensible aux accents, ordre hiérarchique DFS avec indentation (sortHierarchical), navigation clavier, option « extra » en tête de liste (extraOptions/onExtraSelect/activeExtra), nom traduit pari18n_key. Déjà réutilisé dansTransactionTable,TransactionFilterBar,SplitAdjustmentModal(dans un modal),MappingRow,SecurityPickeretCategoryZoomHeader.Analyse du 2026-10-12 (
/analyze, lu surmain8b5b3b1). Prémisse vérifiée : les deux<select>existent tels que décrits.Constats de l'analyse
CategoryComboboxexigeCategory[](shared/types/index.ts:54,parent_id?: number,created_atobligatoire).CategoryFormreçoit desCategoryTreeNode[](index.ts:611,parent_id: number | null, pas decreated_at, pluschildren). Les deux types ne sont pas assignables l'un à l'autre.CategoryFormData(index.ts:626) n'a pas d'id. Exclure la catégorie éditée de la liste des parents demande une prop de plus, fournie parCategoryDetailPanel.tsx:90-98qui tientselectedCategory.transactionService.getAllCategories()(is_active = 1 AND is_inputable = 1,transactionService.ts:328-331) ; le dialogue mot-clé reçoitgetAllCategoriesWithCounts()(categoryService.ts:18-28, actives seulement, saisissables ou non, déjàc.*donc avecparent_id,sort_order,i18n_key: la note d'origine « il faudra charger les colonnes » était fausse, seul le type localCategoryOptionles perd).null: la remise à « aucun » passe par une option extra (modèleTransactionTable.tsx:214-222:extraOptions,activeExtra={value === null ? "" : null},onExtraSelect→null).computeDepthsetsortHierarchicallisentparent_idsur la liste reçue : passer une liste déjà filtrée (racines + niveau 1) garde l'indentation, les parents des niveaux 1 étant présents.CategoryForm.tsx) : exclure la catégorie éditée et toute sa descendance, et les parents qui dépasseraient la profondeur ».updateCategory(categoryService.ts:80-96) n'a aucune garde de cycle.CategoryForminchangé depuis le 3ᵉ niveau (a04813c) ;AddKeywordDialogdepuis #74 ; combobox depuis #126.Décisions (Max, 2026-10-12)
updateCategory), la garde degetCategoryDepthet l'outil de réparation. Commentaire posé sur #393.transactionService.getAllCategories()), cohérent avec l'aide du drapeau (« masquer du budget et des listes de catégories »). Conséquence acceptée : depuisReportsCategoryPage, uninitialCategoryIdnon saisissable n'est pas dans la liste ; le dialogue le remet alors ànull(champ vide, bouton d'application désactivé jusqu'au choix), plutôt que d'envoyer un id que l'utilisateur ne voit pas.Travail à faire
CategoryCombobox: accepter un type structurel minimal (id,name,parent_id?: number | null,sort_order,i18n_key?) plutôt queCategorycomplet, pour queCategoryTreeNode[]passe sans cast.computeDepths,sortHierarchicaletdisplayNamen'utilisent que ces champs.SecurityPickeret les autres appelants ne bougent pas.buildParentOptions(categories, editingId)(par exemplesrc/components/categories/parentOptions.ts) : racines + niveau 1, moins la catégorie éditée, moins sa descendance, moins les niveaux 1 si la catégorie éditée a elle-même des enfants ; ordre laissé au combobox (sortHierarchical). Remplace les lignes 44-66 deCategoryForm.tsx.CategoryForm: propeditingId?: number | null(passée parCategoryDetailPanelen édition, absente en création) ; remplacer le<select>parCategoryComboboxavecextraOptions=[{ value: "", label: t("categories.noParent") }],activeExtra={form.parent_id === null ? "" : null},onExtraSelect→parent_id: null,ariaLabel={t("categories.parent")}(le<label>actuel n'a pas dehtmlFor).AddKeywordDialog: charger partransactionService.getAllCategories()(D2), supprimerCategoryOption; remplacer le<select>parCategoryComboboxavec une option extra « — » (ou le libellé « Aucune catégorie » existant) qui remetcategoryIdànull; à la réception de la liste, siinitialCategoryIdn'y figure pas,setCategoryId(null).canApplyrestecategoryId !== null.absolute z-50dans un conteneuroverflow-y-auto(AddKeywordDialog.tsx:138) ;SplitAdjustmentModalest le précédent. Si la liste est rognée, la placer en bas du bloc ou retireroverflow-y-autodu conteneur.parentOptions.test.ts: racines seules ; niveau 1 inclus ; niveau 2 exclu ; en édition, soi-même exclu, descendance exclue, niveaux 1 exclus quand la catégorie éditée a des enfants ; création (editingIdabsent) = liste d'aujourd'hui.renderToStaticMarkup, fauxtau formatk[...]deTransactionTable.render.test.tsx, voir #389) deCategoryFormet d'AddKeywordDialog: présence d'unrole="combobox", absence de<select>, libellé affiché de la valeur pré-remplie. PourAddKeywordDialog, le chargement est dans unuseEffectque le rendu statique n'exécute pas : mockertransactionServiceet tester le rendu initial, puis la logique «initialCategoryIdhors liste →null» dans une fonction pure testée à part.CategoryCombobox.test.ts: inchangé si le type d'entrée est seulement élargi.categories.parent,categories.noParent,reports.highlights.categoryexistent ; ajouter au besoin un libellé pour l'option vide du mot-clé, dansfr.jsoneten.json.CHANGELOG.mdetCHANGELOG.fr.md: saisie dans les deux champs, ordre hiérarchique dans le mot-clé, noms des catégories du seed traduits dans ces deux champs, liste du mot-clé limitée aux catégories saisissables, parents impossibles retirés du formulaire.docs/guide-utilisateur.mdet clésdocs.*si le guide décrit l'un des deux champs ; pas d'ADR (réutilisation d'un composant existant).Fichiers concernés
src/components/shared/CategoryCombobox.tsx— type d'entrée structurelsrc/components/categories/CategoryForm.tsx— combobox, propeditingId, appel debuildParentOptionssrc/components/categories/parentOptions.ts(nouveau) — module pursrc/components/categories/CategoryDetailPanel.tsx— passereditingId={selectedCategory.id}src/components/categories/AddKeywordDialog.tsx— combobox, source de données, repli deinitialCategoryIdsrc/i18n/locales/fr.json,src/i18n/locales/en.jsonCHANGELOG.md,CHANGELOG.fr.mdsrc/components/categories/parentOptions.test.ts,CategoryForm.render.test.tsx,AddKeywordDialog.render.test.tsx(nouveaux)Surface de test
src/components/shared/CategoryCombobox.test.ts(7 cas sursortHierarchical) ; aucun test surCategoryForm,AddKeywordDialogniCategoryDetailPanel./fix-issue(déduites à la lecture) : inverserparent.parent_id === nulldans le filtre de niveau (les niveaux 2 entrent) ; retirer l'exclusion de la descendance ; retirer le repliinitialCategoryIdhors liste →null; remettregetAllCategoriesWithCounts. Chaque mutation doit rougir au moins un test.Options
Option A — élargir le type d'entrée du combobox (retenue)
CategoryTreeNode→Category, les appelants existants compilent tels quels.CategoryCombobox.tsx.Option B — convertir dans
CategoryForm({ ...node, parent_id: node.parent_id ?? undefined, created_at: "" })created_at, bruit à chaque rendu.Hors portée
getCategoryDepth, outil « Vérifier la hiérarchie » et budget : #393.<select>de catégories de l'app :AdjustmentForm.tsx:124(ajustements) etLinkTransfersModal.tsx:260(bilan). Candidats à une issue de suite si le résultat convient.Critères d'acceptation
nullet reste la valeur par défaut à la création ; les niveaux 2 ne sont jamais proposés.npm run buildetnpm testverts ; tests listés ci-dessus présents et les quatre mutations rouges.Complexité estimée
Medium : trois composants, un type élargi, une prop ajoutée, un module pur et trois fichiers de tests. Aucune migration, aucun changement Rust.
maximus referenced this issue2026-10-12 21:08:57 +00:00