From cfbe96ccf894f9e6159e126a4849be6c33d471fb Mon Sep 17 00:00:00 2001 From: Mathias STRASSER Date: Thu, 19 Feb 2026 19:02:41 +0100 Subject: [PATCH] feat: Appliquer le pattern optimistic update sur les pages admin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chaque action inline (block, unblock, renvoi d'invitation, modification de période, fin de remplacement, ajout de journée pédagogique) déclenchait un rechargement complet de la liste via loadXxx(). Cela causait un reset du scroll, un temps d'attente visible et des requêtes réseau inutiles. Le pattern de mise à jour locale introduit dans la story Droit à l'image est généralisé aux 4 pages admin restantes, en extrayant explicitement les champs utiles de la réponse API pour éviter la pollution JSON-LD. --- .../admin/academic-year/periods/+page.svelte | 24 ++++++++++- .../src/routes/admin/calendar/+page.svelte | 22 +++++++++- .../routes/admin/replacements/+page.svelte | 8 +++- frontend/src/routes/admin/users/+page.svelte | 40 +++++++++++++++++-- 4 files changed, 87 insertions(+), 7 deletions(-) diff --git a/frontend/src/routes/admin/academic-year/periods/+page.svelte b/frontend/src/routes/admin/academic-year/periods/+page.svelte index 6baa45a..5eece68 100644 --- a/frontend/src/routes/admin/academic-year/periods/+page.svelte +++ b/frontend/src/routes/admin/academic-year/periods/+page.svelte @@ -189,7 +189,29 @@ ); } - await loadPeriods(); + const updated = await response.json(); + if (config) { + const updatedPeriod: Period = { + sequence: editingPeriod!.sequence, + label: updated.label ?? editingPeriod!.label, + startDate: updated.startDate ?? editStartDate, + endDate: updated.endDate ?? editEndDate, + isCurrent: updated.isCurrent ?? editingPeriod!.isCurrent, + daysRemaining: updated.daysRemaining ?? editingPeriod!.daysRemaining, + isPast: updated.isPast ?? editingPeriod!.isPast + }; + config = { + ...config, + periods: config.periods.map((p) => + p.sequence === editingPeriod!.sequence ? updatedPeriod : p + ), + currentPeriod: updatedPeriod.isCurrent + ? updatedPeriod + : config.currentPeriod?.sequence === editingPeriod!.sequence + ? null + : config.currentPeriod + }; + } closeEditModal(); } catch (e) { error = e instanceof Error ? e.message : 'Erreur inconnue'; diff --git a/frontend/src/routes/admin/calendar/+page.svelte b/frontend/src/routes/admin/calendar/+page.svelte index 2d86ecc..57abe53 100644 --- a/frontend/src/routes/admin/calendar/+page.svelte +++ b/frontend/src/routes/admin/calendar/+page.svelte @@ -190,7 +190,27 @@ ); } - await loadCalendar(); + const data = await response.json(); + const newEntry: CalendarEntry = { + id: data.id ?? data['@id'] ?? '', + type: data.type ?? 'pedagogical', + startDate: data.startDate ?? data.date ?? pedDate, + endDate: data.endDate ?? data.date ?? pedDate, + label: data.label ?? pedLabel, + description: data.description ?? null + }; + if (calendar) { + const entries = [...calendar.entries, newEntry].sort((a, b) => + a.startDate.localeCompare(b.startDate) + ); + calendar = { ...calendar, entries }; + } else { + calendar = { + academicYearId, + zone: null, + entries: [newEntry] + }; + } showPedagogicalModal = false; pedDate = ''; pedLabel = ''; diff --git a/frontend/src/routes/admin/replacements/+page.svelte b/frontend/src/routes/admin/replacements/+page.svelte index 527d14f..257962a 100644 --- a/frontend/src/routes/admin/replacements/+page.svelte +++ b/frontend/src/routes/admin/replacements/+page.svelte @@ -330,9 +330,15 @@ throw new Error(message); } + replacements = replacements.filter((r) => r.id !== replacementToEnd!.id); + totalItems = Math.max(0, totalItems - 1); + if (replacements.length === 0 && currentPage > 1) { + currentPage -= 1; + updateUrl(); + reloadReplacements(); + } successMessage = 'Remplacement terminé avec succès'; closeEndModal(); - await reloadReplacements(); globalThis.setTimeout(() => { successMessage = null; }, 3000); diff --git a/frontend/src/routes/admin/users/+page.svelte b/frontend/src/routes/admin/users/+page.svelte index 0a62cca..99b4fc6 100644 --- a/frontend/src/routes/admin/users/+page.svelte +++ b/frontend/src/routes/admin/users/+page.svelte @@ -246,8 +246,17 @@ throw new Error(errorMessage); } + const updated = await response.json().catch(() => null); + users = users.map((u) => + u.id === userId + ? { + ...u, + invitedAt: updated?.invitedAt ?? new Date().toISOString(), + invitationExpiree: false + } + : u + ); successMessage = 'L\'invitation a été renvoyée avec succès.'; - await loadUsers(); } catch (e) { error = e instanceof Error ? e.message : 'Erreur lors du renvoi'; } finally { @@ -361,9 +370,19 @@ throw new Error(errorMessage); } + const updated = await response.json(); + users = users.map((u) => + u.id === blockTargetUser!.id + ? { + ...u, + statut: updated.statut ?? 'suspended', + blockedAt: updated.blockedAt ?? new Date().toISOString(), + blockedReason: updated.blockedReason ?? blockReason.trim() + } + : u + ); successMessage = `L'utilisateur ${blockTargetUser.firstName} ${blockTargetUser.lastName} a été bloqué.`; closeBlockModal(); - await loadUsers(); } catch (e) { error = e instanceof Error ? e.message : 'Erreur lors du blocage'; } finally { @@ -401,8 +420,18 @@ throw new Error(errorMessage); } + const updated = await response.json(); + users = users.map((u) => + u.id === user.id + ? { + ...u, + statut: updated.statut ?? 'active', + blockedAt: null, + blockedReason: null + } + : u + ); successMessage = `L'utilisateur ${user.firstName} ${user.lastName} a été débloqué.`; - await loadUsers(); } catch (e) { error = e instanceof Error ? e.message : 'Erreur lors du déblocage'; } finally { @@ -440,9 +469,12 @@ isSavingRoles = true; error = null; await updateUserRoles(rolesTargetUser.id, selectedRoles); + const updatedRoles = [...selectedRoles]; + users = users.map((u) => + u.id === rolesTargetUser!.id ? { ...u, roles: updatedRoles } : u + ); successMessage = `Les rôles de ${rolesTargetUser.firstName} ${rolesTargetUser.lastName} ont été mis à jour.`; closeRolesModal(); - await loadUsers(); } catch (e) { error = e instanceof Error ? e.message : 'Erreur lors de la mise à jour des rôles'; } finally {