Files
Starseed/.claude/rules/frontend.md
T
tristan ad20d1f4c9
Auto Tag Develop / tag (push) Successful in 8s
[ERP-73] Paginer toutes les listes côté front + composable de liste paginée réutilisable (#30)
## Contexte
Ticket Lesstime : #73 (id 492) — volet front de la pagination (groupe Transversal).
Dépend du back ERP-72 (déjà mergé sur develop). Pas de spec docs/specs ; référence = description #73 + .claude/rules/frontend.md.

## Implémentation
- Composable réutilisable `usePaginatedList` (`frontend/shared/composables/`) générique, branché directement sur `MalioDataTable` (props page/perPage/totalItems + events update:page/update:per-page).
- Force `Accept: application/ld+json` (sans Accept, API Platform renvoie un tableau plat sans pagination).
- Migration des pages admin existantes (M0 catégories, Sites, Utilisateurs, Rôles) vers le composable.
- Refactor de `useCategoriesAdmin` : ne porte plus la liste paginée (déplacée vers `usePaginatedList<Category>` dans la page) et concentre son rôle sur le référentiel `CategoryType` (chargé en une fois via `?pagination=false`, échappatoire prévue par `pagination_client_enabled: true` côté back).
- Cas limites couverts : liste vide (pas de contrôle pagination affiché), page hors borne après filtre (retombe sur la dernière page valide), items/page 10/25/50, reset filtres/tri, swallow erreur réseau.
- Pattern « liste paginée » documenté dans `.claude/rules/frontend.md` (section dédiée + exemple).

## Décision URL
Le ticket suggérait « idéalement page/tri/filtre dans l'URL » — arbitré explicitement par Tristan en faveur de la règle ABSOLUE n°6 du CLAUDE.md (state local uniquement, jamais persisté dans l'URL). Aucun reflet URL implémenté ; comportement homogène entre toutes les listes migrées.

## Tests
- `make nuxt-test` : 101/101 OK (22 nouveaux tests sur `usePaginatedList`, 6 anciens tests `useCategoriesAdmin.fetchAll` retirés en cohérence avec la refacto).
- Vérification manuelle dans le navigateur (`make dev-nuxt`) : Sites, Utilisateurs, Rôles, Catégories affichent le sélecteur `Lignes : 10` et les boutons Prev/Next ; audit-log (non migré, composable spécifique) intact avec ses 3 pages.
- Aucun test E2E ajouté (règle d'or projet).
- Pre-commit hook : ESLint + PHPUnit 322/322 OK.

## Hors périmètre
- `audit-log.vue` non migré : composable `useAuditLog` spécifique (cache partagé page/timeline, filtres complexes, persistance URL préexistante). Refactor risqué et net-zéro pour ERP-73.
- M1 répertoire clients : pas encore livré sur develop (seules les specs sont mergées via #23). Le futur écran consommera `usePaginatedList` dès sa création.

Reviewed-on: #30
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-01 09:54:54 +00:00

5.3 KiB

Frontend — Regles Nuxt 4 / Vue 3 / @malio/layer-ui

Base

  • TypeScript strict
  • 4 espaces d'indentation
  • Commentaires (JSDoc, inline, bloc) en francais ; code (variables, types) en anglais
  • Chaque module front = un layer Nuxt auto-detecte (frontend/modules/*/nuxt.config.ts minimal)

Appels API

  • Toujours useApi() — jamais $fetch, ofetch, axios en direct
  • useApi() gere : cookies JWT, erreurs, toasts i18n, parsing Hydra

Stores (Pinia)

  • useAuthStore pour l'authentification
  • useUiStore pour l'etat UI global (sidebar, modales, etc.)
  • Composables avec state singleton (refs module-level) : exposer une fonction reset*() et la rappeler au logout (ex: useSidebar().resetSidebar())

Middlewares globaux

  • auth.global.ts protege les routes + charge la sidebar apres login
  • modules.global.ts redirige si la route demandee est dans disabledRoutes

i18n et sidebar

  • Labels de sidebar = cles i18n sidebar.<module>.*, jamais du texte brut
  • Le layout default.vue applique t() sur les labels retournes par /api/sidebar
  • Traductions dans frontend/i18n/locales/

Composants formulaires — @malio/layer-ui obligatoire

Tout champ de formulaire / filtre doit utiliser les composants Malio* plutot que <input> / <select> bruts :

  • MalioInputText, MalioInputNumber, MalioInputAmount, MalioInputPassword, MalioInputTextArea
  • MalioSelect, MalioSelectCheckbox, MalioCheckbox, MalioRadioButton
  • MalioInputUpload, MalioTime
  • MalioButton, MalioButtonIcon

Exceptions autorisees (commenter un // TODO pour migrer quand la lib couvrira le cas) :

  1. Type non couvert : datetime-local, date, color picker, file drag & drop
  2. Bug connu bloquant (ex: MalioSelect avec options string) — documenter le bug en commentaire

Toute autre exception requiert validation avant merge.

Tableaux de donnees — MalioDataTable obligatoire

Tout affichage LISTE tabulaire (donnees metier paginees, CRUD admin) doit passer par MalioDataTable :

  • Pagination integree
  • Slots #header-* pour filtres, #cell-* pour rendu custom
  • Pas de <table> brut avec pagination custom

Exception : tableaux purement presentationnels non paginables (diff field/old/new, grille de comparaison, matrice RBAC d'admin, etc.) peuvent rester en <table> HTML brut.

Listes paginees (standard) — usePaginatedList obligatoire

Toute liste qui consomme une GetCollection API doit passer par usePaginatedList (frontend/shared/composables/usePaginatedList.ts). Le composable est le pendant front de la regle ABSOLUE n°13 (« toute collection est paginee cote back ») : il consomme l'envelope Hydra (member / totalItems / view) et expose un etat reactif a brancher directement sur MalioDataTable.

Pattern de reference :

const {
    items,
    totalItems,
    currentPage,
    itemsPerPage,
    itemsPerPageOptions,
    fetch: loadList,
    goToPage,
    setItemsPerPage,
} = usePaginatedList<MyEntity>({ url: '/my-resources' })

onMounted(loadList)
<MalioDataTable
    :columns="columns"
    :items="rows"
    :total-items="totalItems"
    :page="currentPage"
    :per-page="itemsPerPage"
    :per-page-options="itemsPerPageOptions"
    :empty-message="t('foo.empty')"
    @update:page="goToPage"
    @update:per-page="setItemsPerPage"
/>

Garanties offertes par le composable :

  • Force Accept: application/ld+json → API Platform 4 renvoie bien member / totalItems (sans Accept, retour tableau plat sans pagination).
  • Defaut 10 items/page, choix client 10 / 25 / 50, aligne sur le defaut serveur.
  • Mutation setFilters / setSort / setItemsPerPage → retombe systematiquement en page 1.
  • Cas limite « page hors borne apres filtre » : retombe automatiquement sur la derniere page valide (tests/usePaginatedList.test.ts).
  • Etat 100 % local (refs internes a l'instance) — jamais reflete dans l'URL, conformement a la regle « Etat des tableaux — pas de persistance URL » ci-dessous.

A NE PAS faire :

  • Charger une collection complete via ?itemsPerPage=999 pour bypasser la pagination. Le seul cas legitime de retour complet est l'alimentation d'un <select> sur un referentiel ≤ quelques dizaines d'entrees, et il passe par ?pagination=false (echappatoire prevue par pagination_client_enabled: true).
  • Reimplementer la pagination prev/next a la main au-dessus de MalioDataTable — le composant porte deja le selecteur items/page et les boutons Prev/Next.
  • Persister page/tri/filtre dans la query string — meme regle que pour <MalioDataTable> brut (cf. section suivante).

Etat des tableaux — pas de persistance URL

Interdit de persister l'etat d'un tableau (filtres, pagination, tri par colonne, selection, ligne active, scroll) dans la query string ou de le reinjecter depuis route.query au montage.

  • L'etat vit uniquement dans le composant (reactive, ref locales)
  • Seuls les deep links "de navigation metier" (ex: ouvrir un detail precis /users/42) sont dans l'URL
  • Exceptions autorisees sur demande explicite de l'utilisateur

Interdits

  • modules-loader.ts, .module.ts — le scan des layers est automatique
  • Hardcode de la sidebar cote front — elle vient de /api/sidebar
  • Edition manuelle de extends dans frontend/nuxt.config.ts — les layers sont scannes
  • Import d'un module front depuis un autre module — passer par frontend/shared/