feat: Appliquer le pattern optimistic update sur les pages admin

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.
This commit is contained in:
2026-02-19 19:02:41 +01:00
parent 1b8bd6cd78
commit cfbe96ccf8
4 changed files with 87 additions and 7 deletions

View File

@@ -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';

View File

@@ -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 = '';

View File

@@ -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);

View File

@@ -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 {