diff --git a/src/app/(app)/equipe/AddEmployeeForm.tsx b/src/app/(app)/equipe/AddEmployeeForm.tsx index b23668d..3978a38 100644 --- a/src/app/(app)/equipe/AddEmployeeForm.tsx +++ b/src/app/(app)/equipe/AddEmployeeForm.tsx @@ -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 ( -
+ ); } diff --git a/src/app/(app)/equipe/[id]/DocumentsPanel.tsx b/src/app/(app)/equipe/[id]/DocumentsPanel.tsx index cd23aae..0bee07b 100644 --- a/src/app/(app)/equipe/[id]/DocumentsPanel.tsx +++ b/src/app/(app)/equipe/[id]/DocumentsPanel.tsx @@ -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 ? ( - + ) : null} {uploadState.error ? ( diff --git a/src/app/(app)/reglages/etablissements/AddLocationForm.tsx b/src/app/(app)/reglages/etablissements/AddLocationForm.tsx index 85f9ef6..1b67ce4 100644 --- a/src/app/(app)/reglages/etablissements/AddLocationForm.tsx +++ b/src/app/(app)/reglages/etablissements/AddLocationForm.tsx @@ -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 ( - + ); } diff --git a/src/app/(app)/reglages/etablissements/AddTeamForm.tsx b/src/app/(app)/reglages/etablissements/AddTeamForm.tsx index f55d572..7aede9e 100644 --- a/src/app/(app)/reglages/etablissements/AddTeamForm.tsx +++ b/src/app/(app)/reglages/etablissements/AddTeamForm.tsx @@ -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 ( - + ); } diff --git a/src/app/(app)/reglages/registre/AddEntryForm.tsx b/src/app/(app)/reglages/registre/AddEntryForm.tsx index a4fdc76..dab5846 100644 --- a/src/app/(app)/reglages/registre/AddEntryForm.tsx +++ b/src/app/(app)/reglages/registre/AddEntryForm.tsx @@ -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 ( - + ); } diff --git a/src/components/absences/RequestForm.tsx b/src/components/absences/RequestForm.tsx index 3bede0b..2d20237 100644 --- a/src/components/absences/RequestForm.tsx +++ b/src/components/absences/RequestForm.tsx @@ -3,6 +3,7 @@ import { useActionState } from 'react'; import { Button } from '@/components/ui/Button'; +import { PersistentForm } from '@/components/ui/PersistentForm'; import { requestTimeOffAction, type AbsenceActionState, @@ -33,7 +34,8 @@ export function RequestForm({ ); return ( - + ); } diff --git a/src/components/hours/ActualHoursForm.tsx b/src/components/hours/ActualHoursForm.tsx index 939f7ea..ba42e81 100644 --- a/src/components/hours/ActualHoursForm.tsx +++ b/src/components/hours/ActualHoursForm.tsx @@ -3,6 +3,7 @@ import { useActionState } from 'react'; import { Button } from '@/components/ui/Button'; +import { PersistentForm } from '@/components/ui/PersistentForm'; import { saveActualHoursAction, validateHoursAction, @@ -37,7 +38,11 @@ export function ActualHoursForm({ shift }: { shift: HoursShiftRow }) { } return ( - + ); } diff --git a/src/components/payroll/MappingForm.tsx b/src/components/payroll/MappingForm.tsx index a45070c..9f80fc7 100644 --- a/src/components/payroll/MappingForm.tsx +++ b/src/components/payroll/MappingForm.tsx @@ -3,6 +3,7 @@ import { useActionState } from 'react'; import { Button } from '@/components/ui/Button'; +import { PersistentForm } from '@/components/ui/PersistentForm'; import { saveMappingAction, type PayrollActionState, @@ -31,7 +32,8 @@ export function MappingForm({ const listId = `codes-${row.key}`; return ( - + ); } diff --git a/src/components/planning/TeamSection.tsx b/src/components/planning/TeamSection.tsx index 48c8230..c5e7894 100644 --- a/src/components/planning/TeamSection.tsx +++ b/src/components/planning/TeamSection.tsx @@ -6,6 +6,7 @@ import { AlertPanel } from '@/components/planning/AlertPanel'; import { WeekGrid } from '@/components/planning/WeekGrid'; import { Badge } from '@/components/ui/Badge'; import { Button } from '@/components/ui/Button'; +import { PersistentForm } from '@/components/ui/PersistentForm'; import { formatMinutes } from '@/domain/counters/week'; import { displayName, @@ -16,11 +17,15 @@ import { createShiftAction, deleteShiftAction, duplicateWeekAction, - publishWeekAction, - unpublishWeekAction, + setWeekPublicationAction, updateShiftAction, type PlanningActionState, } from '@/server/planning/actions'; +import { + PUBLICATION_INTENT_FIELD, + PUBLISH_INTENT, + UNPUBLISH_INTENT, +} from '@/domain/planning/publication'; import type { BoardLabel, BoardSection } from '@/server/planning/queries'; export interface TeamSectionProps { @@ -222,12 +227,15 @@ function PublishControl({ pendingWarnings: number; }) { const [state, formAction, pending] = useActionState( - published ? unpublishWeekAction : publishWeekAction, + setWeekPublicationAction, empty, ); return ( - + ); } diff --git a/src/components/settings/EmailSettingsForm.tsx b/src/components/settings/EmailSettingsForm.tsx index 05317ad..0239900 100644 --- a/src/components/settings/EmailSettingsForm.tsx +++ b/src/components/settings/EmailSettingsForm.tsx @@ -3,6 +3,7 @@ import { useActionState } from 'react'; import { Button } from '@/components/ui/Button'; +import { PersistentForm } from '@/components/ui/PersistentForm'; import { saveEmailSettingsAction, sendTestEmailAction, @@ -42,8 +43,9 @@ export function EmailSettingsForm({ ); return ( - + ); } @@ -160,7 +162,7 @@ export function TestEmailForm({ defaultTo }: { defaultTo: string }) { ); return ( - + ); } diff --git a/src/components/settings/RetentionForms.tsx b/src/components/settings/RetentionForms.tsx index c1e4e6e..5422832 100644 --- a/src/components/settings/RetentionForms.tsx +++ b/src/components/settings/RetentionForms.tsx @@ -3,6 +3,7 @@ import { useActionState } from 'react'; import { Button } from '@/components/ui/Button'; +import { PersistentForm } from '@/components/ui/PersistentForm'; import { START_POINT_LABELS, START_POINTS, @@ -23,7 +24,11 @@ export function RetentionPolicyForm() { ); return ( - + ); } diff --git a/src/components/ui/PersistentForm.tsx b/src/components/ui/PersistentForm.tsx index a101336..e00735c 100644 --- a/src/components/ui/PersistentForm.tsx +++ b/src/components/ui/PersistentForm.tsx @@ -7,13 +7,13 @@ import { useEffect, useRef, type FormHTMLAttributes, type ReactNode } from 'reac * * React 19 **vide les champs non contrôlés dès que l'action se termine**, sans * distinguer le succès de l'échec. Sur un refus, l'utilisateur retrouve donc un - * formulaire vierge et doit tout ressaisir — sur l'écran de première - * installation, huit champs pour un mot de passe trop court. + * formulaire vierge et doit tout ressaisir. * * Pire que l'agacement : les champs vidés portent `required`, si bien que le * clic suivant est arrêté par la validation du navigateur **avant** d'émettre * un `submit`. Le formulaire paraît alors mort — le bouton répond, et rien ne - * part. + * part. Il faut ressaisir *tous* les champs requis pour qu'un second envoi + * puisse seulement atteindre le serveur. * * Le remède : photographier la saisie à l'envoi, et la rétablir quand React * remet le formulaire à zéro. Les mots de passe sont exclus de la photo — les @@ -21,54 +21,105 @@ import { useEffect, useRef, type FormHTMLAttributes, type ReactNode } from 'reac * extension qui la lit. */ +type Restorable = + | HTMLInputElement + | HTMLSelectElement + | HTMLTextAreaElement; + +interface Captured { + name: string; + value: string; + checked: boolean; + /** Sélection multiple : `value` seul ne la décrirait pas. */ + selected?: string[]; +} + export interface PersistentFormProps extends Omit