feat(admin) : filtres + pagination serveur sur /admin/users/sites/roles

Ajoute le filtrage par colonne et la pagination negociee via query params
sur les 3 DataTables admin existantes. Tout est cote serveur (API Platform
SearchFilter + BooleanFilter) pour scaler naturellement.

Backend :
- api_platform.yaml : scan du mapping Sites + pagination_client_items_per_page
  (avec borne max 100 pour proteger contre les payloads exagerement grands).
- User : SearchFilter username (partial), rbacRoles.code (exact),
  sites.name (exact) + BooleanFilter isAdmin.
- Site : SearchFilter name/city/postalCode (partial).
- Role : SearchFilter label/code (partial), permissions.code (exact).
  (BooleanFilter isSystem deja present.)

Frontend :
- Composable useDataTableServerState (shared) : singleton de page/perPage/
  filters avec debounce 300ms sur les filters, fetch immediat sur page/
  perPage, reset page=1 au changement filter, token anti-race-condition.
- Pages admin : chaque filtre dans un slot #header-{key} (input text avec
  debounce, select mono-selection pour les relations). Font-size 20px sur
  les inputs de filtre.
- /admin/users : colonne Sites + filtre Sites conditionnes par
  useModules().isModuleActive('sites') — preserve l'invariant "module
  desactivable sans casse".

Tests : 215/215 PHPUnit (14 nouveaux filtres/pagination) + 48/48 Vitest
(8 nouveaux useDataTableServerState).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-20 17:00:34 +02:00
parent 296befe187
commit cb6d2d72ec
10 changed files with 875 additions and 88 deletions

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace App\Module\Sites\Domain\Entity;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
@@ -61,6 +63,13 @@ use Symfony\Component\Validator\Constraints as Assert;
normalizationContext: ['groups' => ['site:read']],
denormalizationContext: ['groups' => ['site:write']],
)]
// Filtres cote API pour /admin/sites : recherche partielle (SQL LIKE %x%)
// sur les champs texte saisis dans les headers de la DataTable.
#[ApiFilter(SearchFilter::class, properties: [
'name' => 'partial',
'city' => 'partial',
'postalCode' => 'partial',
])]
#[ORM\Entity(repositoryClass: DoctrineSiteRepository::class)]
#[ORM\Table(name: 'site')]
#[ORM\UniqueConstraint(name: 'uniq_site_name', columns: ['name'])]