feat: Pagination et recherche des sections admin
Les listes admin (utilisateurs, classes, matières, affectations) chargeaient toutes les données d'un coup, ce qui dégradait l'expérience avec un volume croissant. La pagination côté serveur existait dans la config API Platform mais aucun Provider ne l'exploitait. Cette implémentation ajoute la pagination serveur (30 items/page, max 100) avec recherche textuelle sur toutes les sections, des composants frontend réutilisables (Pagination + SearchInput avec debounce), et la synchronisation URL pour le partage de liens filtrés. Les Query valident leurs paramètres (clamp page/limit, trim search) pour éviter les abus. Les affectations utilisent des lookup maps pour résoudre les noms sans N+1 queries. Les pages admin gèrent les race conditions via AbortController.
This commit is contained in:
@@ -41,7 +41,7 @@ test.describe('User Blocking', () => {
|
||||
await page.locator('#email').fill(ADMIN_EMAIL);
|
||||
await page.locator('#password').fill(ADMIN_PASSWORD);
|
||||
await Promise.all([
|
||||
page.waitForURL(/\/dashboard/, { timeout: 10000 }),
|
||||
page.waitForURL(/\/dashboard/, { timeout: 30000 }),
|
||||
page.getByRole('button', { name: /se connecter/i }).click()
|
||||
]);
|
||||
}
|
||||
@@ -53,6 +53,12 @@ test.describe('User Blocking', () => {
|
||||
// Wait for users table to load
|
||||
await expect(page.locator('.users-table')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Search for the target user (pagination may hide them beyond page 1)
|
||||
const searchInput = page.locator('input[type="search"]');
|
||||
await searchInput.fill(TARGET_EMAIL);
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Find the target user row
|
||||
const targetRow = page.locator('tr', { has: page.locator(`text=${TARGET_EMAIL}`) });
|
||||
await expect(targetRow).toBeVisible();
|
||||
@@ -86,6 +92,12 @@ test.describe('User Blocking', () => {
|
||||
|
||||
await expect(page.locator('.users-table')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Search for the target user (pagination may hide them beyond page 1)
|
||||
const searchInput = page.locator('input[type="search"]');
|
||||
await searchInput.fill(TARGET_EMAIL);
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Find the suspended target user row
|
||||
const targetRow = page.locator('tr', { has: page.locator(`text=${TARGET_EMAIL}`) });
|
||||
await expect(targetRow).toBeVisible();
|
||||
@@ -111,6 +123,12 @@ test.describe('User Blocking', () => {
|
||||
await page.goto(`${ALPHA_URL}/admin/users`);
|
||||
await expect(page.locator('.users-table')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Search for the target user (pagination may hide them beyond page 1)
|
||||
const searchInput = page.locator('input[type="search"]');
|
||||
await searchInput.fill(TARGET_EMAIL);
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const targetRow = page.locator('tr', { has: page.locator(`text=${TARGET_EMAIL}`) });
|
||||
await expect(async () => {
|
||||
await targetRow.getByRole('button', { name: /bloquer/i }).click();
|
||||
@@ -141,6 +159,12 @@ test.describe('User Blocking', () => {
|
||||
|
||||
await expect(page.locator('.users-table')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Search for the admin user (pagination may hide them beyond page 1)
|
||||
const searchInput = page.locator('input[type="search"]');
|
||||
await searchInput.fill(ADMIN_EMAIL);
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Find the admin's own row
|
||||
const adminRow = page.locator('tr', { has: page.locator(`text=${ADMIN_EMAIL}`) });
|
||||
await expect(adminRow).toBeVisible();
|
||||
|
||||
Reference in New Issue
Block a user