diff --git a/frontend/src/features/missions/MissionForm/MissionForm.tsx b/frontend/src/features/missions/MissionForm/MissionForm.tsx index 8ea6857c60..ee846a3a1c 100644 --- a/frontend/src/features/missions/MissionForm/MissionForm.tsx +++ b/frontend/src/features/missions/MissionForm/MissionForm.tsx @@ -1,12 +1,12 @@ import { Banner, + type ControlUnit, + customDayjs, FormikEffect, Icon, Level, THEME, - customDayjs, - usePrevious, - type ControlUnit + usePrevious } from '@mtes-mct/monitor-ui' import { useMissionEventContext } from 'context/mission/useMissionEventContext' import { useFormikContext } from 'formik' @@ -23,6 +23,7 @@ import { ExternalActionsModal } from './ExternalActionsModal' import { FormikSyncMissionFields } from './FormikSyncMissionFields' import { GeneralInformationsForm } from './GeneralInformationsForm' import { useMissionAndActionsCompletion } from './hooks/useMissionAndActionsCompletion' +import { useSyncFormValuesWithRedux } from './hooks/useSyncFormValuesWithRedux' import { useUpdateOtherControlTypes } from './hooks/useUpdateOtherControlTypes' import { useUpdateSurveillance } from './hooks/useUpdateSurveillance' import { MissionFormBottomBar } from './MissionFormBottomBar' @@ -31,8 +32,8 @@ import { getIsMissionFormValid, isMissionAutoSaveEnabled, shouldSaveMission } fr import { missionsAPI } from '../../../api/missionsAPI' import { FrontCompletionStatus, - MissionSourceEnum, type Mission, + MissionSourceEnum, type NewMission } from '../../../domain/entities/missions' import { sideWindowPaths } from '../../../domain/entities/sideWindow' @@ -98,6 +99,7 @@ export function MissionForm({ const isFormDirty = useMemo(() => selectedMissions[id]?.isFormDirty ?? false, [id, selectedMissions]) + useSyncFormValuesWithRedux(isAutoSaveEnabled) const { missionCompletionFrontStatus } = useMissionAndActionsCompletion() useUpdateSurveillance() useUpdateOtherControlTypes() @@ -177,14 +179,6 @@ export function MissionForm({ } const validateBeforeOnChange = useDebouncedCallback(async (nextValues, forceSave) => { - dispatch( - missionFormsActions.setMission({ - engagedControlUnit, - isFormDirty, - missionForm: nextValues - }) - ) - if (!isAutoSaveEnabled || engagedControlUnit || !isMissionFormValid) { return } @@ -194,7 +188,7 @@ export function MissionForm({ } dispatch(saveMission(nextValues, false, false)) - }, 150) + }, 300) useEffect(() => { if (isNewMission && !engagedControlUnit && previousEngagedControlUnit !== engagedControlUnit) { diff --git a/frontend/src/features/missions/MissionForm/hooks/useSyncFormValuesWithRedux.ts b/frontend/src/features/missions/MissionForm/hooks/useSyncFormValuesWithRedux.ts new file mode 100644 index 0000000000..bba1b86142 --- /dev/null +++ b/frontend/src/features/missions/MissionForm/hooks/useSyncFormValuesWithRedux.ts @@ -0,0 +1,66 @@ +import { useAppDispatch } from '@hooks/useAppDispatch' +import { useAppSelector } from '@hooks/useAppSelector' +import { useFormikContext } from 'formik' +import { useEffect } from 'react' +import { useDebouncedCallback } from 'use-debounce' + +import { missionFormsActions } from '../slice' +import { getIsMissionFormValid } from '../utils' + +import type { Mission } from 'domain/entities/missions' + +export function useSyncFormValuesWithRedux(isAutoSaveEnabled: boolean) { + const dispatch = useAppDispatch() + const { dirty, values } = useFormikContext() + const activeMissionId = useAppSelector(state => state.missionForms.activeMissionId) + const activeMission = useAppSelector(state => + activeMissionId ? state.missionForms.missions[activeMissionId] : undefined + ) + const engagedControlUnit = useAppSelector(state => + activeMissionId ? state.missionForms.missions[activeMissionId]?.engagedControlUnit : undefined + ) + + const dispatchFormUpdate = useDebouncedCallback(async (newValues: Mission) => { + if (!newValues || newValues.id !== activeMissionId) { + return + } + + const isFormDirty = isMissionFormDirty() + + dispatch( + missionFormsActions.setMission({ + engagedControlUnit, + isFormDirty, + missionForm: newValues + }) + ) + }, 350) + + /** + * The form is dirty if: + * - In auto-save mode, an error is found (hence the form is not saved) + * - In manual save mode, values have been modified (using the `dirty` props of Formik) + */ + function isMissionFormDirty() { + if (!isAutoSaveEnabled) { + if (dirty) { + return dirty + } + + /** + * If the form was already dirty and still open, the new `dirty` property is not valid anymore as Formik + * has been re-instantiated with the saved values. + * We use the last `isFormDirty` value instead of `dirty`. + */ + return activeMission?.isFormDirty ?? false + } + + const isMissionFormValid = getIsMissionFormValid(values) + + return !isMissionFormValid + } + + useEffect(() => { + dispatchFormUpdate(values) + }, [values, dispatchFormUpdate]) +}