Étendre la préservation des formulaires, et corriger la publication
Suite de PR #31. `PersistentForm` couvre désormais les formulaires où la saisie compte : ajout d'un salarié, d'un établissement, d'une équipe, d'une entrée de registre, demande d'absence, heures réelles, correspondances de paie, réglages d'envoi, conservation, dépôt de pièce. Le composant gère `select`, `textarea`, cases et boutons radio, et reçoit `resetAfter` : passé l'état retourné par l'action en cas de succès, le formulaire revient aux valeurs rendues par le serveur — vide pour un ajout, la valeur enregistrée pour un champ d'édition. C'est ce qui rend visible une normalisation faite côté serveur. Les champs cachés sont exclus. Ils ne portent jamais une saisie mais l'état de l'application, et les rétablir écrase ce que le serveur vient de renvoyer. Le cas s'est produit sur `expectedVersion`, le verrou optimiste d'une semaine de planning. En éprouvant cela, un défaut plus grave est apparu, antérieur et sans rapport avec la préservation : le formulaire de publication recevait tantôt l'action de publication, tantôt celle de dépublication selon l'état, et cessait de suivre le changement. Lecture de l'en-tête `Next-Action` sur trois envois consécutifs : 1er envoi 60b8097e (dépublier) → brouillon 2e envoi 6012d40f (publier) → publiée 3e envoi 6012d40f (publier) → publiée Le bouton affichait « Dépublier » et republiait, sans message d'erreur puisque l'action réellement exécutée réussissait. Une seule action reçoit maintenant l'intention, portée par le bouton déclencheur — un champ caché serait remis à sa valeur d'origine par la réinitialisation que React applique après chaque action, un bouton ne l'est jamais. Le test du planning fait désormais deux allers-retours dans le même chargement : le premier envoi d'une page emploie toujours la bonne action, si bien qu'un seul aller-retour laissait passer le défaut selon l'état où la semaine avait été trouvée. Vérifié : le test échoue sur l'ancien câblage, passe sur le nouveau. 450 tests unitaires et d'intégration, 77 de bout en bout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv
This commit is contained in:
15 files changed
+254
-69
No files matched your search
@@ -3,6 +3,7 @@
|
||||
import { useActionState } from 'react';
|
||||
|
||||
import { Field, FormError, SubmitButton } from '@/components/ui/Form';
|
||||
import { PersistentForm } from '@/components/ui/PersistentForm';
|
||||
import {
|
||||
createEmployeeAction,
|
||||
type ActionState,
|
||||
@@ -15,7 +16,11 @@ export function AddEmployeeForm() {
|
||||
);
|
||||
|
||||
return (
|
||||
<form action={formAction} className="flex flex-col gap-3">
|
||||
<PersistentForm
|
||||
action={formAction}
|
||||
resetAfter={state.ok ? state : null}
|
||||
className="flex flex-col gap-3"
|
||||
>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Field label="Prénom" name="firstName" required />
|
||||
<Field label="Nom" name="lastName" required />
|
||||
@@ -37,6 +42,6 @@ export function AddEmployeeForm() {
|
||||
<span className="text-xs text-ok-soft-ink">Salarié ajouté.</span>
|
||||
) : null}
|
||||
</div>
|
||||
</form>
|
||||
</PersistentForm>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { useActionState } from 'react';
|
||||
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { PersistentForm } from '@/components/ui/PersistentForm';
|
||||
import {
|
||||
ACCEPTED_MIME_TYPES,
|
||||
CATEGORY_LABELS,
|
||||
@@ -114,8 +115,9 @@ export function DocumentsPanel({
|
||||
)}
|
||||
|
||||
{canManage ? (
|
||||
<form
|
||||
<PersistentForm
|
||||
action={upload}
|
||||
resetAfter={uploadState.ok ? uploadState : null}
|
||||
className="flex flex-wrap items-end gap-3 border-t border-line-1 pt-4"
|
||||
>
|
||||
<input type="hidden" name="membershipId" value={membershipId} />
|
||||
@@ -157,7 +159,7 @@ export function DocumentsPanel({
|
||||
<Button type="submit" variant="primary" disabled={uploading}>
|
||||
Déposer
|
||||
</Button>
|
||||
</form>
|
||||
</PersistentForm>
|
||||
) : null}
|
||||
|
||||
{uploadState.error ? (
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useActionState } from 'react';
|
||||
|
||||
import { Field, FormError, SubmitButton } from '@/components/ui/Form';
|
||||
import { PersistentForm } from '@/components/ui/PersistentForm';
|
||||
import {
|
||||
createLocationAction,
|
||||
type ActionState,
|
||||
@@ -15,7 +16,11 @@ export function AddLocationForm() {
|
||||
);
|
||||
|
||||
return (
|
||||
<form action={formAction} className="flex flex-col gap-3">
|
||||
<PersistentForm
|
||||
action={formAction}
|
||||
resetAfter={state.ok ? state : null}
|
||||
className="flex flex-col gap-3"
|
||||
>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Field label="Nom" name="name" required placeholder="Nantes Atlantis" />
|
||||
<Field
|
||||
@@ -52,6 +57,6 @@ export function AddLocationForm() {
|
||||
<span className="text-xs text-ok-soft-ink">Établissement créé.</span>
|
||||
) : null}
|
||||
</div>
|
||||
</form>
|
||||
</PersistentForm>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useActionState } from 'react';
|
||||
|
||||
import { Field, FormError, SubmitButton } from '@/components/ui/Form';
|
||||
import { PersistentForm } from '@/components/ui/PersistentForm';
|
||||
import {
|
||||
createTeamAction,
|
||||
type ActionState,
|
||||
@@ -15,13 +16,17 @@ export function AddTeamForm({ locationId }: { locationId: string }) {
|
||||
);
|
||||
|
||||
return (
|
||||
<form action={formAction} className="flex flex-wrap items-end gap-2">
|
||||
<PersistentForm
|
||||
action={formAction}
|
||||
resetAfter={state.ok ? state : null}
|
||||
className="flex flex-wrap items-end gap-2"
|
||||
>
|
||||
<input type="hidden" name="locationId" value={locationId} />
|
||||
<Field label="Nouvelle équipe" name="name" placeholder="Caisse" required />
|
||||
<SubmitButton size="md">Ajouter</SubmitButton>
|
||||
<div className="w-full">
|
||||
<FormError>{state.error}</FormError>
|
||||
</div>
|
||||
</form>
|
||||
</PersistentForm>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useActionState } from 'react';
|
||||
|
||||
import { Field, FormError, SubmitButton } from '@/components/ui/Form';
|
||||
import { PersistentForm } from '@/components/ui/PersistentForm';
|
||||
import { LEGAL_DOMAINS } from '@/domain/legal/domains';
|
||||
import {
|
||||
addLegalEntryAction,
|
||||
@@ -16,7 +17,11 @@ export function AddEntryForm() {
|
||||
);
|
||||
|
||||
return (
|
||||
<form action={formAction} className="flex flex-col gap-3">
|
||||
<PersistentForm
|
||||
action={formAction}
|
||||
resetAfter={state.ok ? state : null}
|
||||
className="flex flex-col gap-3"
|
||||
>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<label className="flex min-w-0 flex-1 flex-col gap-1.5">
|
||||
<span className="text-sm font-medium">Domaine</span>
|
||||
@@ -78,6 +83,6 @@ export function AddEntryForm() {
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</form>
|
||||
</PersistentForm>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user