feat(front) : add usePaginatedList composable + paginate all admin lists via MalioDataTable
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 2m1s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m7s

- frontend/shared/composables/usePaginatedList.ts : composable generique de liste paginee serveur (Hydra), branche directement sur MalioDataTable
- 22 tests Vitest (navigation, bornes, parse Hydra, hors-borne, reset, filtres, tri, swallow erreur)
- Migration des pages admin existantes : sites, users, roles, categories
- Refactor de useCategoriesAdmin pour ne porter que le referentiel CategoryType (charge en une fois via ?pagination=false)
- Etat page/tri/filtre 100% local dans le composable (respect regle ABSOLUE n°6 — pas de persistance URL)
- Section dediee dans .claude/rules/frontend.md documentant le pattern obligatoire pour toute nouvelle liste

ERP-73 — volet front de la pagination, depend du back ERP-72 deja merge.
This commit is contained in:
2026-05-29 16:40:00 +02:00
parent 0c6919201e
commit 43c3220873
9 changed files with 899 additions and 272 deletions
+47
View File
@@ -53,6 +53,53 @@ Tout affichage LISTE tabulaire (donnees metier paginees, CRUD admin) doit passer
**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 :
```ts
const {
items,
totalItems,
currentPage,
itemsPerPage,
itemsPerPageOptions,
fetch: loadList,
goToPage,
setItemsPerPage,
} = usePaginatedList<MyEntity>({ url: '/my-resources' })
onMounted(loadList)
```
```vue
<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.