Skip to content

[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-140 never reads isError, so a failed bactive.members load renders as "Aucun élement", indistinguishable from an empty result. Applies to this view; one fix in TableData.
  • A11Y-03 — sort headers (TableData.vue:96), rows (:151) and the Désactiver action (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/-500 faceted 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 the table-layout: fixed table 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) — same Désactiver-with-no-confirm behaviour.
  • DATA-TEMPORAL-DEFAULT: grep the other three projects' "new item" templates for hard-coded years/dates alongside computed ones.