Skip to content

Commit

Permalink
[Mission] Pouvoir sauvegarder une mission terminée même si des champs…
Browse files Browse the repository at this point in the history
… sont manquants (#1377)

## Related Pull Requests & Issues

- Resolve #1363

----

- [ ] Tests E2E (Cypress)
  • Loading branch information
claire2212 authored May 16, 2024
2 parents b3d1756 + cad1c28 commit fcd6157
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export function createMissionWithAttachedReportingAndAttachedAction() {

// Attach the reporting to a mission
cy.intercept('GET', '/bff/v1/missions*').as('getMissions')
cy.intercept('PUT', '/bff/v1/missions').as('createMission')
cy.clickButton('missions')
cy.clickButton('Ajouter une nouvelle mission')

Expand All @@ -61,8 +62,6 @@ export function createMissionWithAttachedReportingAndAttachedAction() {
cy.get('[name="missionTypes0"]').click({ force: true })
cy.fill('Unité 1', 'BN Toulon')

cy.intercept('PUT', '/bff/v1/missions').as('createMission')

return cy.waitForLastRequest(
'@createMission',
{
Expand Down
41 changes: 35 additions & 6 deletions frontend/src/features/missions/MissionForm/CancelEditModal.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,49 @@
import { Accent, Button, Dialog, THEME } from '@mtes-mct/monitor-ui'
import { useMemo } from 'react'
import styled from 'styled-components'

type CancelEditModalProps = {
isAutoSaveEnabled: boolean
isDirty: boolean
isMissionFormValid: boolean
onCancel: () => void
onConfirm: () => void
open: boolean
}
export function CancelEditModal({ onCancel, onConfirm, open }: CancelEditModalProps) {
export function CancelEditModal({
isAutoSaveEnabled,
isDirty,
isMissionFormValid,
onCancel,
onConfirm,
open
}: CancelEditModalProps) {
const isMissionUnsaved = !isAutoSaveEnabled && isDirty && isMissionFormValid
const title = isMissionUnsaved ? 'Enregistrer les modifications' : 'Enregistrement impossible'

const body = useMemo(() => {
if (isMissionUnsaved) {
return (
<>
<p>Vous êtes en train d&apos;abandonner l&apos;édition de la mission.</p>
<p>Voulez-vous enregistrer les modifications avant de quitter ?</p>
</>
)
}

return (
<>
<p>Vous êtes en train d&apos;abandonner l&apos;édition de la mission.</p>
<Bold>Si vous souhaitez enregistrer les modifications, merci de corriger les champs en erreur.</Bold>
</>
)
}, [isMissionUnsaved])

return (
open && (
<Dialog data-cy="cancel-edit-modal" isAbsolute>
<Dialog.Title>Enregistrement impossible</Dialog.Title>
<Dialog.Body $color={THEME.color.gunMetal}>
<p>Vous êtes en train d&apos;abandonner l&apos;édition de la mission.</p>
<Bold>Si vous souhaitez enregistrer les modifications, merci de corriger les champs en erreur.</Bold>
</Dialog.Body>
<Dialog.Title>{title}</Dialog.Title>
<Dialog.Body $color={THEME.color.gunMetal}>{body}</Dialog.Body>

<Dialog.Action>
<Button accent={Accent.SECONDARY} onClick={onConfirm}>
Expand Down
27 changes: 0 additions & 27 deletions frontend/src/features/missions/MissionForm/CloseEditModal.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ export function GeneralInformationsForm({
name="controlUnits"
/* eslint-disable-next-line react/jsx-props-no-spreading */
render={props => <ControlUnitsForm {...props} />}
validateOnChange={false}
/>
</StyledUnitsContainer>

Expand Down
66 changes: 24 additions & 42 deletions frontend/src/features/missions/MissionForm/MissionForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
} from '@mtes-mct/monitor-ui'
import { useMissionEventContext } from 'context/useMissionEventContext'
import { useFormikContext } from 'formik'
import { isEmpty } from 'lodash'
import { useEffect, useMemo, useState } from 'react'
import { generatePath } from 'react-router'
import styled from 'styled-components'
Expand All @@ -19,7 +18,6 @@ import { useDebouncedCallback } from 'use-debounce'
import { ActionForm } from './ActionForm'
import { ActionsTimeLine } from './ActionsTimeLine'
import { CancelEditModal } from './CancelEditModal'
import { CloseEditModal } from './CloseEditModal'
import { DeleteModal } from './DeleteModal'
import { ExternalActionsModal } from './ExternalActionsModal'
import { FormikSyncMissionFields } from './FormikSyncMissionFields'
Expand All @@ -29,9 +27,8 @@ import { useSyncFormValuesWithRedux } from './hooks/useSyncFormValuesWithRedux'
import { useUpdateOtherControlTypes } from './hooks/useUpdateOtherControlTypes'
import { useUpdateSurveillance } from './hooks/useUpdateSurveillance'
import { MissionFormBottomBar } from './MissionFormBottomBar'
import { NewMissionSchema } from './Schemas'
import { missionFormsActions } from './slice'
import { isMissionAutoSaveEnabled, shouldSaveMission } from './utils'
import { getIsMissionFormValid, isMissionAutoSaveEnabled, shouldSaveMission } from './utils'
import { missionsAPI } from '../../../api/missionsAPI'
import {
FrontCompletionStatus,
Expand All @@ -52,11 +49,10 @@ import type { AtLeast } from '../../../types'

enum ModalTypes {
ACTIONS = 'ACTIONS',
CLOSE = 'CLOSE',
DELETE = 'DELETE'
}

type ModalProps = ModalTypes.ACTIONS | ModalTypes.DELETE | ModalTypes.CLOSE
type ModalProps = ModalTypes.ACTIONS | ModalTypes.DELETE

type MissionFormProps = {
engagedControlUnit: ControlUnit.EngagedControlUnit | undefined
Expand All @@ -76,13 +72,7 @@ export function MissionForm({ engagedControlUnit, id, isNewMission, selectedMiss
const { getMissionEventById } = useMissionEventContext()
const missionEvent = getMissionEventById(id)

const {
dirty,
errors: formErrors,
setFieldValue,
validateForm,
values
} = useFormikContext<Partial<Mission | NewMission>>()
const { dirty, setFieldValue, values } = useFormikContext<Partial<Mission | NewMission>>()

const previousEngagedControlUnit = usePrevious(engagedControlUnit)

Expand Down Expand Up @@ -111,6 +101,8 @@ export function MissionForm({ engagedControlUnit, id, isNewMission, selectedMiss
const [openModal, setOpenModal] = useState<ModalProps | undefined>(undefined)
const [actionsSources, setActionsSources] = useState<MissionSourceEnum[]>([])

const isMissionFormValid = useMemo(() => getIsMissionFormValid(values), [values])

// the form listens to the redux store to update the attached reportings
// because of the map interaction to attach reportings
useEffect(() => {
Expand Down Expand Up @@ -162,50 +154,34 @@ export function MissionForm({ engagedControlUnit, id, isNewMission, selectedMiss
await dispatch(missionFormsActions.deleteSelectedMission(id))
}

const submitMission = () => {
validateForm().then(errors => {
if (isEmpty(errors)) {
dispatch(saveMission(values, false, true))

return
}
dispatch(sideWindowActions.setShowConfirmCancelModal(true))
})
}

const confirmFormCancelation = () => {
// when auto save is disabled, and form has changes we want to display specific modal
if (!isAutoSaveEnabled && dirty && isEmpty(formErrors)) {
setOpenModal(ModalTypes.CLOSE)
const submitMission = async () => {
if (isMissionFormValid) {
dispatch(saveMission(values, false, true))

return
}

if (isFormDirty) {
dispatch(sideWindowActions.setShowConfirmCancelModal(true))
}

const confirmFormCancelation = () => {
if ((!isAutoSaveEnabled && dirty && isMissionFormValid) || isFormDirty) {
dispatch(sideWindowActions.setShowConfirmCancelModal(true))
} else {
cancelForm()
}
}

const validateBeforeOnChange = useDebouncedCallback(async (nextValues, forceSave) => {
if (!isAutoSaveEnabled) {
if (!isAutoSaveEnabled || engagedControlUnit || !isMissionFormValid) {
return
}

if (engagedControlUnit) {
if (!shouldSaveMission(selectedMission, missionEvent, nextValues) && !forceSave) {
return
}

try {
NewMissionSchema.validateSync(values, { abortEarly: false })
if (!shouldSaveMission(selectedMission, missionEvent, nextValues) && !forceSave) {
return
}

dispatch(saveMission(nextValues, false, false))
// eslint-disable-next-line no-empty
} catch (e: any) {}
dispatch(saveMission(nextValues, false, false))
}, 300)

useEffect(() => {
Expand Down Expand Up @@ -259,8 +235,14 @@ export function MissionForm({ engagedControlUnit, id, isNewMission, selectedMiss

<FormikEffect onChange={nextValues => validateBeforeOnChange(nextValues, false)} />
<FormikSyncMissionFields missionId={id} />
<CancelEditModal onCancel={returnToEdition} onConfirm={cancelForm} open={sideWindow.showConfirmCancelModal} />
<CloseEditModal onCancel={returnToEdition} onConfirm={cancelForm} open={openModal === ModalTypes.CLOSE} />
<CancelEditModal
isAutoSaveEnabled={isAutoSaveEnabled}
isDirty={dirty}
isMissionFormValid={isMissionFormValid}
onCancel={returnToEdition}
onConfirm={cancelForm}
open={sideWindow.showConfirmCancelModal}
/>
<DeleteModal
onCancel={returnToEdition}
onConfirm={validateDeleteMission}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { useDebouncedCallback } from 'use-debounce'

import { useAppDispatch } from '../../../../hooks/useAppDispatch'
import { useAppSelector } from '../../../../hooks/useAppSelector'
import { NewMissionSchema } from '../Schemas'
import { missionFormsActions } from '../slice'
import { getIsMissionFormValid } from '../utils'

import type { Mission } from '../../../../domain/entities/missions'

Expand Down Expand Up @@ -49,13 +49,9 @@ export function useSyncFormValuesWithRedux(isAutoSaveEnabled: boolean) {
return activeMission?.isFormDirty ?? false
}

try {
NewMissionSchema.validateSync(values, { abortEarly: false })
const isMissionFormValid = getIsMissionFormValid(values)

return false
} catch (e: any) {
return true
}
return !isMissionFormValid
}

useEffect(() => {
Expand Down
1 change: 0 additions & 1 deletion frontend/src/features/missions/MissionForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ export function MissionFormWrapper() {
enableReinitialize
initialValues={missionValues}
onSubmit={noop}
validateOnBlur={false}
validateOnMount
validationSchema={MissionSchema}
>
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/features/missions/MissionForm/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isEqual, omit } from 'lodash'

import { MISSION_EVENT_UNSYNCHRONIZED_PROPERTIES_IN_FORM } from './constants'
import { NewMissionSchema } from './Schemas'
import { isCypress } from '../../../utils/isCypress'

import type { Mission, NewMission } from '../../../domain/entities/missions'
Expand Down Expand Up @@ -62,3 +63,13 @@ export function shouldSaveMission(
function filterActionsFormInternalProperties(values: Partial<Mission | NewMission>) {
return values.envActions?.map(envAction => omit(envAction, 'durationMatchesMission')) ?? []
}

export function getIsMissionFormValid(mission: Partial<Mission | NewMission>): boolean {
try {
NewMissionSchema.validateSync(mission, { abortEarly: false })

return true
} catch (e: any) {
return false
}
}

0 comments on commit fcd6157

Please sign in to comment.