Skip to content

Commit

Permalink
[Missions] MAJ du fonctionnement des zones de mission (#3027)
Browse files Browse the repository at this point in the history
## Linked issues

- [x] Resolve #2986
- [x] Resolve #2803
- remplacer le titre de la pop-up "Supprimer la mission" par
"Suppression impossible"
- [x] Resolve #2712 

----

- [ ] Tests E2E (Cypress)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

- **New Features**
- Enhanced mission form to support re-computation or deletion of mission
geometry upon removing the last action.
- Introduced a function to initialize mission location in various
mission form components.
- **Bug Fixes**
- Updated success message content for mission zone modifications to
reflect the current operation accurately.
- Added logic to handle control actions with geometry data more
effectively.
- **Tests**
- Added and updated test cases to verify the updated success messages
and the absence of specific messages post-operation in mission form
scenarios.
- **Documentation**
- Updated notification messages within the mission form to clarify the
modification of mission zones.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
louptheron committed Mar 20, 2024
2 parents 8dbc337 + 26839bc commit 67643fc
Show file tree
Hide file tree
Showing 14 changed files with 169 additions and 30 deletions.
57 changes: 57 additions & 0 deletions frontend/cypress/e2e/side_window/mission_form/action_list.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,61 @@ context('Side Window > Mission Form > Action List', () => {
'[title="23581 - Taille de maille non réglementaire, 23588 - Chalutage dans la zone des 3 milles, 23584 - Défaut AIS"]'
).should('exist')
})

it('Should re-compute or delete the mission geometry when the action is the last one', () => {
editSideWindowMissionListMissionWithId(34, SeaFrontGroup.MEMN)

cy.intercept('POST', '/api/v1/missions/34', {
body: {
id: 1
},
statusCode: 201
}).as('updateMission34')
cy.intercept('DELETE', '/bff/v1/mission_actions/9', {
body: {
id: 1
},
statusCode: 200
}).as('deleteMissionAction9')

// We add another control
cy.clickButton('Ajouter')
cy.clickButton('Ajouter un contrôle en mer')
cy.fill('Navire inconnu', true)

cy.intercept('POST', '/bff/v1/mission_actions', {
body: {
id: 1
},
statusCode: 201
}).as('createMissionAction')

cy.get('*[data-cy="mission-main-form-location"]').should(
'contain',
'Actuellement, la zone de mission ' +
'est automatiquement calculée selon le point ou la zone de la dernière action rapportée par l’unité.'
)

cy.wait(250)
cy.clickButton('Supprimer l’action').eq(0)

// There is still a valid control with a geometry that could be used for the mission zone
cy.get('.Toastify__toast--success').contains(
'Une zone de mission a été modifiée à partir des contrôles de la mission'
)
cy.get('*[data-cy="mission-main-form-location"]').should(
'contain',
'Actuellement, la zone de mission ' +
'est automatiquement calculée selon le point ou la zone de la dernière action rapportée par l’unité.'
)

cy.wait(250)
cy.clickButton('Supprimer l’action')

cy.get('*[data-cy="mission-main-form-location"]').should(
'not.contain',
'Actuellement, la zone de mission ' +
'est automatiquement calculée selon le point ou la zone de la dernière action rapportée par l’unité.'
)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ context('Side Window > Mission Form > Land Control', () => {

// A mission zone should be automatically added
cy.get('.Toastify__toast--success').contains(
'Une zone de mission a été ajoutée à partir des contrôles de la mission'
'Une zone de mission a été modifiée à partir des contrôles de la mission'
)
cy.get('*[data-cy="mission-main-form-location"]').should(
'contain',
Expand Down Expand Up @@ -345,7 +345,7 @@ context('Side Window > Mission Form > Land Control', () => {

// The mission zone should be automatically updated
cy.get('.Toastify__toast--success').contains(
'Une zone de mission a été ajoutée à partir des contrôles de la mission'
'Une zone de mission a été modifiée à partir des contrôles de la mission'
)

cy.get('*[data-cy="mission-main-form-location"]').should(
Expand Down Expand Up @@ -464,5 +464,12 @@ context('Side Window > Mission Form > Land Control', () => {
)
.its('response.statusCode')
.should('eq', 201)
cy.wait(250)
cy.fill('Port de contrôle', undefined)
cy.get('*[data-cy="mission-main-form-location"]').should(
'not.contain',
'Actuellement, la zone de mission ' +
'est automatiquement calculée selon le point ou la zone de la dernière action rapportée par l’unité.'
)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ context('Side Window > Mission Form > Sea Control', () => {

// A mission zone should be automatically added (because of the stubbed coordinates update when IS_CYPRESS LocalSorage key is "true")
cy.get('.Toastify__toast--success').contains(
'Une zone de mission a été ajoutée à partir des contrôles de la mission'
'Une zone de mission a été modifiée à partir des contrôles de la mission'
)
cy.get('*[data-cy="mission-main-form-location"]').should(
'contain',
Expand Down Expand Up @@ -559,7 +559,7 @@ context('Side Window > Mission Form > Sea Control', () => {

// The mission zone should be automatically updated (because of the stubbed coordinates update when IS_CYPRESS LocalSorage key is "true")
cy.get('.Toastify__toast--success').contains(
'Une zone de mission a été ajoutée à partir des contrôles de la mission'
'Une zone de mission a été modifiée à partir des contrôles de la mission'
)
cy.get('*[data-cy="mission-main-form-location"]').should(
'contain',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function FormikCoordinatesPicker() {
const coordinatesFormat = useMainAppSelector(state => state.map.coordinatesFormat)
const listener = useMainAppSelector(state => state.draw.listener)

const { updateFAOAreasAndSegments, updateMissionLocation } = useGetMissionActionFormikUsecases()
const { initMissionLocation, updateFAOAreasAndSegments, updateMissionLocation } = useGetMissionActionFormikUsecases()
const { values } = useFormikContext<MissionActionFormValues>()
const [{ value: longitudeValue }, longitudeMeta, longitudeHelpers] =
useField<MissionActionFormValues['longitude']>('longitude')
Expand Down Expand Up @@ -135,6 +135,7 @@ export function FormikCoordinatesPicker() {

longitudeHelpers.setValue(undefined)
latitudeHelpers.setValue(undefined)
initMissionLocation()
},

// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { Option } from '@mtes-mct/monitor-ui'

export function FormikPortSelect() {
const { errors, setFieldValue, values } = useFormikContext<MissionActionFormValues>()
const { updateFAOAreasAndSegments, updateMissionLocation } = useGetMissionActionFormikUsecases()
const { initMissionLocation, updateFAOAreasAndSegments, updateMissionLocation } = useGetMissionActionFormikUsecases()

const getPortsApiQuery = useGetPortsQuery()

Expand All @@ -36,6 +36,7 @@ export function FormikPortSelect() {

if (!nextPortLocode) {
setFieldValue('portLocode', undefined)
initMissionLocation()

return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { AutoSaveTag } from './shared/AutoSaveTag'

export function Loader() {
const dispatch = useMainAppDispatch()
const missionIdFromPath = useMainAppSelector(store => store.sideWindow.selectedPath.id)
const missionFormError = useMainAppSelector(state => state.displayedError.missionFormError)

const goToMissionList = useCallback(async () => {
Expand Down Expand Up @@ -52,11 +51,9 @@ export function Loader() {
</Body>

<Footer>
{!!missionIdFromPath && (
<Button accent={Accent.SECONDARY} disabled Icon={Icon.Delete}>
Supprimer la mission
</Button>
)}
<Button accent={Accent.SECONDARY} disabled Icon={Icon.Delete}>
Supprimer la mission
</Button>

<RightButtonsContainer>
<AutoSaveTag />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,11 @@ export function FormikLocationPicker() {
setFieldValue('geom', geometryComputedFromControls)

window.document.dispatchEvent(
new NotificationEvent('Une zone de mission a été ajoutée à partir des contrôles de la mission', 'success', true)
new NotificationEvent(
'Une zone de mission a été modifiée à partir des contrôles de la mission',
'success',
true
)
)

dispatch(missionFormActions.unsetGeometryComputedFromControls())
Expand Down Expand Up @@ -157,7 +161,7 @@ export function FormikLocationPicker() {
/>
</Row>
))}
{values.isGeometryComputedFromControls && (
{values.isGeometryComputedFromControls && !!polygons.length && (
<ZoneComputedFromActions>
Actuellement, la zone de mission est <b>automatiquement calculée</b> selon le point ou la zone de la
dernière action rapportée par l’unité.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ import { vesselApi } from '@features/Vessel/apis'
import { FrontendError } from '@libs/FrontendError'
import { MissionAction } from 'domain/types/missionAction'
import { getFleetSegments } from 'domain/use_cases/vessel/getFleetSegments'
import { MultiPolygon } from 'ol/geom'

import { PAMControlUnitIds } from './constants'
import { convertToGeoJSONGeometryObject } from '../../../../domain/entities/layers'

import type { MissionActionFormValues, MissionMainFormValues } from './types'
import type { Option } from '@mtes-mct/monitor-ui'
import type { MainRootState } from '@store'
import type { RiskFactor } from 'domain/entities/vessel/riskFactor/types'
import type { Gear } from 'domain/types/Gear'
import type { GeoJSON } from 'domain/types/GeoJSON'
import type { Port } from 'domain/types/port'
import type { AnyAction } from 'redux'
import type { ThunkDispatch } from 'redux-thunk'
Expand Down Expand Up @@ -157,6 +160,16 @@ const updateMissionLocation =
dispatch(missionFormActions.setGeometryComputedFromControls(nextMissionGeometry))
}

const initMissionLocation = dispatch => async (isGeometryComputedFromControls: boolean | undefined) => {
if (!isGeometryComputedFromControls) {
return
}

const emptyMissionGeometry = convertToGeoJSONGeometryObject(new MultiPolygon([])) as GeoJSON.MultiPolygon

dispatch(missionFormActions.setGeometryComputedFromControls(emptyMissionGeometry))
}

const updateOtherControlsCheckboxes =
dispatch => async (mission: MissionMainFormValues, previousIsControlUnitPAM: boolean) => {
const isControlUnitPAM = mission.controlUnits?.some(
Expand All @@ -174,6 +187,7 @@ const updateOtherControlsCheckboxes =
}

export const formikUsecase = {
initMissionLocation,
updateFAOAreas,
updateGearsOnboard,
updateMissionLocation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,14 @@ export function useGetMissionActionFormikUsecases() {
missionActionValues
)

/**
* When updating the mission location from an action, we use the `RTK-Query` cache object to access the `mission` form.
*/
const initMissionLocation = () =>
formikUsecase.initMissionLocation(dispatch)(draft?.mainFormValues.isGeometryComputedFromControls)

return {
initMissionLocation,
updateFAOAreasAndSegments,
updateFieldsControlledByVessel,
updateMissionLocation,
Expand Down
39 changes: 25 additions & 14 deletions frontend/src/features/Mission/components/MissionForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { autoSaveMissionAction } from '@features/Mission/useCases/autoSaveMissio
import { deleteMission } from '@features/Mission/useCases/deleteMission'
import { deleteMissionAction } from '@features/Mission/useCases/deleteMissionAction'
import { saveMissionAndMissionActionsByDiff } from '@features/Mission/useCases/saveMissionAndMissionActionsByDiff'
import { cleanMissionForm } from '@features/SideWindow/useCases/cleanMissionForm'
import { openSideWindowPath } from '@features/SideWindow/useCases/openSideWindowPath'
import { useMainAppDispatch } from '@hooks/useMainAppDispatch'
import { useMainAppSelector } from '@hooks/useMainAppSelector'
Expand Down Expand Up @@ -167,8 +168,7 @@ export function MissionForm() {
const goToMissionList = useCallback(async () => {
const canExit = await dispatch(openSideWindowPath({ menu: SideWindowMenuKey.MISSION_LIST }))
if (canExit) {
dispatch(missionFormActions.resetMissionForm())
dispatch(missionFormActions.unsetSelectedMissionGeoJSON())
dispatch(cleanMissionForm())
}
}, [dispatch])

Expand Down Expand Up @@ -276,7 +276,12 @@ export function MissionForm() {
}

const nextActionsFormValues = await dispatch(
deleteMissionAction(actionsFormValues, actionIndex, isAutoSaveEnabled)
deleteMissionAction(
actionsFormValues,
actionIndex,
isAutoSaveEnabled,
mainFormValues.isGeometryComputedFromControls
)
)

setActionsFormValues(nextActionsFormValues)
Expand All @@ -289,6 +294,7 @@ export function MissionForm() {
dispatch,
updateEditedActionFormValues,
updateReduxSliceDraft,
mainFormValues.isGeometryComputedFromControls,
actionsFormValues,
editedActionIndex,
isAutoSaveEnabled
Expand Down Expand Up @@ -418,7 +424,7 @@ export function MissionForm() {
const response = dispatch(monitorenvMissionApi.endpoints.canDeleteMission.initiate(missionIdRef.current))
const canDeleteMissionResponse = await response.unwrap()
if (canDeleteMissionResponse.canDelete) {
setIsDeletionConfirmationDialogOpen(!isDeletionConfirmationDialogOpen)
setIsDeletionConfirmationDialogOpen(true)

return
}
Expand All @@ -433,7 +439,7 @@ export function MissionForm() {
userMessage: "Nous n'avons pas pu vérifier si cette mission est supprimable."
})
}
}, [isDeletionConfirmationDialogOpen, dispatch])
}, [dispatch])

useEffect(() => {
if (!missionEvent) {
Expand Down Expand Up @@ -495,14 +501,16 @@ export function MissionForm() {
</FrontendErrorBoundary>
</Body>
<Footer>
<DeleteButton
accent={Accent.SECONDARY}
disabled={isSaving || mainFormValues.missionSource !== Mission.MissionSource.MONITORFISH}
Icon={Icon.Delete}
onClick={toggleDeletionConfirmationDialog}
>
Supprimer la mission
</DeleteButton>
{missionIdRef.current && (
<DeleteButton
accent={Accent.SECONDARY}
disabled={isSaving || mainFormValues.missionSource !== Mission.MissionSource.MONITORFISH}
Icon={Icon.Delete}
onClick={toggleDeletionConfirmationDialog}
>
Supprimer la mission
</DeleteButton>
)}

<Separator />

Expand Down Expand Up @@ -570,7 +578,10 @@ export function MissionForm() {
</Wrapper>

{isDeletionConfirmationDialogOpen && (
<DeletionConfirmationDialog onCancel={toggleDeletionConfirmationDialog} onConfirm={handleDelete} />
<DeletionConfirmationDialog
onCancel={() => setIsDeletionConfirmationDialogOpen(false)}
onConfirm={handleDelete}
/>
)}
{isDraftCancellationConfirmationDialogOpen && (
<DraftCancellationConfirmationDialog isAutoSaveEnabled={isAutoSaveEnabled} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function ExternalActionsDialog({ onClose, sources }: ExternalActionsModal

return (
<Dialog data-cy="external-actions-modal" isAbsolute>
<Dialog.Title>Supprimer la mission</Dialog.Title>
<Dialog.Title>Suppression impossible</Dialog.Title>
<Dialog.Body>
<Alert>
<Icon.Attention color={THEME.color.maximumRed} size={30} />
Expand Down
Loading

0 comments on commit 67643fc

Please sign in to comment.