Appearance
[UX] members — B-Active memberships
Draft from /ux-audit on 2026-07-30 (unattended batch run). Not filed. Repo: bcc-nancy/members · Branch:
develop@2c22f5a· Files reviewed: 5 Patterns: forms/search-field (data-display/table & data-display/filter-panel via baseline)
Summary
The B-Active memberships view (bactive-adhesions) is a thin wrapper over the shared TableData + FilterDrawer components plus a "Comparaison" sub-view, so most of what it does — sorting, filtering, error-as-empty rendering — inherits the project-level TableData defects rather than being feature-specific. Two things are specific and worth attention: this view is the caller that turns the row-removal action into an unconfirmed "Désactiver" (no confirm dialog, unlike the delete path), and its new-membership template hard-codes Annee: 2026 while the surrounding dates are computed from the current year.
Findings
1. "Désactiver" mutates a membership with no confirmation — Medium · MSG-05
Where: admin/src/client/views/BActive/Adhesions.vue:13 (:archive-mode="true") → admin/src/client/components/app/tables/TableData.vue:428-429What: Because this view passes archive-mode, the row context-menu item becomes Désactiver, whose command calls props.data?.update.mutateAsync({ ...ctxRow, status: "inactive" })immediately on click. The non-archive branch (:431) routes through requestDelete → the "Êtes-vous sûr ? Cette action est irréversible." BccDialog (:191-214). So the destructive path that does have a confirmation is the one this feature never uses; the path it does use has none. Why it matters: A single right-click + click deactivates a member's B-Active membership, which then drops out of the default Est_membre=true list (Adhesions.vue:184). There is no confirmation, no toast, and no visible undo, so a mis-click silently removes a member from the working set. Non-tech-savvy staff are the stated audience. Fix: Route Désactiver through the same confirm step as Supprimer (reuse the BccDialog, with copy naming the effect — "Cette adhésion sera désactivée"), or surface an undo toast after the mutation.
2. New-membership template hard-codes Annee: 2026 — Medium · proposed DATA-TEMPORAL-DEFAULT
Where: admin/src/client/views/BActive/Adhesions.vue:187-194What: newAdhesion sets Annee: 2026 as a literal, while Debut and Fin on the very next lines are derived from new Date() / new Date().getFullYear(). The year field and the date range are computed from different clocks. Why it matters: It is correct only during calendar 2026. From 1 Jan 2027, the "Ajouter" modal pre-fills Annee = 2026 alongside Debut/Fin that fall in 2027, so a membership created without the operator noticing is saved with a year that contradicts its own dates — a silent data-integrity defect on the money/records path, not something the UI flags. Fix: Derive Annee from new Date().getFullYear() like the dates, or from a single shared "current membership year" source so all three fields agree.
3. Search and range filter inputs are placeholder-only — Medium · FORM-01
Where: admin/src/client/components/app/tables/TableData.vue:44-51 (search); admin/src/client/components/app/tables/FilterDrawer.vue:106-147 (Min/Max, De/À) What: The list search BccInput has only placeholder="Rechercher…" — no <label> and no aria-label. In the drawer, the number/date range inputs render <label>Min</label> / De etc. as plain text next to the BccInput with no for/id association, so they are visual-only. The UX-Patterns search-field anatomy shows an explicit <label for=…> as a required part, not an optional one. Why it matters: A screen-reader user reaching the search field hears "recherche, éditer le texte" with no stated purpose; the Min/Max/De/À controls announce with no accessible name at all, so range filtering is unusable without sight. Placeholder text also vanishes on input. Fix: Give the search input an aria-label="Rechercher"; associate each drawer range label with its control via for/id (or wrap the input in the <label>, as the enum options already do at FilterDrawer.vue:79-96).
4. Tab and list state are not reflected in the URL — Low · proposed NAV-URL-STATE
Where: admin/src/client/views/BActive/Adhesions.vue:69 (tab); TableData.vue:350,384-390 (query/pagination/filters as local refs) What: The Adhésions↔Comparaison tab, the search query, active filters and the current page all live in component-local refs with no URL sync and no KeepAlive. Opening a member modal then closing, or navigating away and back, resets them. Why it matters: Staff re-apply filters and re-find their page after every detour; the current view can't be linked or bookmarked. This is the members instance of the cross-project "list state is component-local everywhere" theme recorded for customer-portal. Fix: Sync tab + filters + page to query params (or wrap the routed view in KeepAlive).
5. Empty-state copy typo « Aucun élement » — Low · CONTENT (copy quality)
Where: admin/src/client/components/app/tables/TableData.vue:136What: The empty-state string is "Aucun élement" — missing the accent on "élément". Shared string, so it shows across every list including this one. Fix: "Aucun élément".
Referenced project-level defects (not re-filed — see PROJECT-LEVEL.md)
- MSG-06 —
TableData.vue:118-140never readsisError, so a failedbactive.membersload renders as "Aucun élement", indistinguishable from an empty result. Applies to this view; one fix inTableData. - A11Y-03 — sort headers (
TableData.vue:96), rows (:151) and theDésactiveraction (right-click only,:437-444) are mouse-only; keyboard users cannot sort, open a row, or deactivate. - NAV-03 — no per-route document title; static « BCC Nancy Admin » for all routes.
- CONTENT-01 — not-applicable; single-locale French by design (see project-level i18n finding).
Offboarding / revocation check (per PROJECT-LEVEL hypothesis)
Désactiver here sets a membership record's status to inactive — a data/display flag on Adhesions_B_Active, not an account or auth state. It does not gate sign-in or capabilities, so the "does archive actually revoke access?" hypothesis does not apply to this surface. Real access control for this repo lives in the capabilities store / #25 Users & roles, which is where that check belongs.
Unverified
- A11Y-01 (contrast) — the
text-neutral-400/-500faceted counts, empty-state and placeholder text against white need a contrast tool; not decidable from code. - A11Y-06 (responsive / short viewport) — the right-slide
FilterDrawer(max-w-sm, full height) and thetable-layout: fixedtable with ~11 columns need a rendered viewport (and a mobile keyboard open) to judge.
Baseline additions
- DATA-TEMPORAL-DEFAULT — form defaults that encode "now" (year, date, season) must be derived from the clock at open time, never hard-coded literals, and all co-dependent temporal fields must derive from one source so they cannot disagree.
- NAV-URL-STATE — a list view's tab/mode, search, filters and page must survive navigation (URL sync or
KeepAlive); component-local-only state that resets on return is a defect. (Reconcile with the separately-proposed NAV-06 variants.)
Cross-project note
- FORM-01 placeholder-only search/range inputs and NAV-URL-STATE live in the shared
TableData/FilterDrawer, so they affect every members admin list (queue rows #9–#29), and the URL-state gap is already confirmed in customer-portal; worth checking tt-time-tracker's TanStack tables too. - MSG-05 unconfirmed destructive action: check every other members caller that passes
archive-mode(e.g. #14 BCC memberships) — sameDésactiver-with-no-confirm behaviour. - DATA-TEMPORAL-DEFAULT: grep the other three projects' "new item" templates for hard-coded years/dates alongside computed ones.