From 2676edb731baa053a601df623cb796d802b59bff Mon Sep 17 00:00:00 2001 From: Claire Dagan Date: Fri, 19 Apr 2024 10:03:01 +0200 Subject: [PATCH 1/9] [Mission] clean missionEvent context --- frontend/src/context/MissionEventContext.ts | 5 ---- frontend/src/context/MissionEventContext.tsx | 25 +++++++++++++++++++ .../src/context/useMissionEventContext.ts | 12 +++++++++ .../domain/use_cases/missions/saveMission.ts | 8 +++--- .../SideWindow/SideWindowLauncher.tsx | 5 +++- frontend/src/features/SideWindow/index.tsx | 5 +--- .../MissionForm/FormikSyncMissionFields.tsx | 10 ++++++-- .../missions/MissionForm/MissionForm.tsx | 6 +++-- .../missions/MissionForm/constants.ts | 4 +-- .../hooks/useListenMissionEventUpdates.ts | 17 ++++++------- .../hooks/useListenMissionEventUpdatesById.ts | 11 -------- .../features/missions/MissionForm/utils.ts | 3 ++- .../src/features/missions/Missions.helpers.ts | 2 +- 13 files changed, 71 insertions(+), 42 deletions(-) delete mode 100644 frontend/src/context/MissionEventContext.ts create mode 100644 frontend/src/context/MissionEventContext.tsx create mode 100644 frontend/src/context/useMissionEventContext.ts delete mode 100644 frontend/src/features/missions/MissionForm/hooks/useListenMissionEventUpdatesById.ts diff --git a/frontend/src/context/MissionEventContext.ts b/frontend/src/context/MissionEventContext.ts deleted file mode 100644 index f51d9bba0..000000000 --- a/frontend/src/context/MissionEventContext.ts +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react' - -import type { Mission } from '../domain/entities/missions' - -export const MissionEventContext = React.createContext(undefined) diff --git a/frontend/src/context/MissionEventContext.tsx b/frontend/src/context/MissionEventContext.tsx new file mode 100644 index 000000000..99e238842 --- /dev/null +++ b/frontend/src/context/MissionEventContext.tsx @@ -0,0 +1,25 @@ +import { createContext, useState, useMemo } from 'react' + +import type { Mission } from 'domain/entities/missions' + +type MissionEventContextProps = { + contextMissionEvent: Mission | undefined + getMissionEventById: (missionId: number | string | undefined) => Mission | undefined + setMissionEventInContext: React.Dispatch> +} +export const MissionEventContext = createContext(undefined) + +export function MissionEventProvider({ children }) { + const [contextMissionEvent, setMissionEventInContext] = useState(undefined) + const contextValue = useMemo( + () => ({ + contextMissionEvent, + getMissionEventById: (missionId: number | string | undefined) => + missionId && contextMissionEvent?.id === missionId ? (contextMissionEvent as Mission | undefined) : undefined, + setMissionEventInContext + }), + [contextMissionEvent, setMissionEventInContext] + ) + + return {children} +} diff --git a/frontend/src/context/useMissionEventContext.ts b/frontend/src/context/useMissionEventContext.ts new file mode 100644 index 000000000..bbe128791 --- /dev/null +++ b/frontend/src/context/useMissionEventContext.ts @@ -0,0 +1,12 @@ +import { useContext } from 'react' + +import { MissionEventContext } from './MissionEventContext' + +export const useMissionEventContext = () => { + const onboardingContext = useContext(MissionEventContext) + if (onboardingContext === undefined) { + throw new Error('useOnboardingContext must be inside a MissionEventContext') + } + + return onboardingContext +} diff --git a/frontend/src/domain/use_cases/missions/saveMission.ts b/frontend/src/domain/use_cases/missions/saveMission.ts index 09c242ebd..de04d272f 100644 --- a/frontend/src/domain/use_cases/missions/saveMission.ts +++ b/frontend/src/domain/use_cases/missions/saveMission.ts @@ -36,10 +36,6 @@ export const saveMission = if ('data' in response) { const missionUpdated = response.data - setTimeout(async () => { - await dispatch(missionFormsActions.setIsListeningToEvents(true)) - }, 500) - // We save the new properties : `id`, `createdAt`, `updatedAt` after a mission creation/update if (missionIsNewMission) { await dispatch( @@ -68,6 +64,10 @@ export const saveMission = ) } + setTimeout(async () => { + await dispatch(missionFormsActions.setIsListeningToEvents(true)) + }, 500) + if (reopen || !quitAfterSave) { return } diff --git a/frontend/src/features/SideWindow/SideWindowLauncher.tsx b/frontend/src/features/SideWindow/SideWindowLauncher.tsx index 2cc20ecd3..40acbfae1 100644 --- a/frontend/src/features/SideWindow/SideWindowLauncher.tsx +++ b/frontend/src/features/SideWindow/SideWindowLauncher.tsx @@ -1,4 +1,5 @@ import { useForceUpdate, NewWindow } from '@mtes-mct/monitor-ui' +import { MissionEventProvider } from 'context/MissionEventContext' import { useCallback, useEffect, useMemo } from 'react' import { SideWindow } from '.' @@ -72,7 +73,9 @@ export function SideWindowLauncher() { showPrompt={hasAtLeastOneMissionFormDirty || hasAtLeastOneReportingFormDirty} title="MonitorEnv" > - + + + ) } diff --git a/frontend/src/features/SideWindow/index.tsx b/frontend/src/features/SideWindow/index.tsx index 5230e9b5a..23c3cc761 100644 --- a/frontend/src/features/SideWindow/index.tsx +++ b/frontend/src/features/SideWindow/index.tsx @@ -9,7 +9,6 @@ import { Route } from './Route' import { sideWindowActions } from './slice' import { StyledRouteContainer, Wrapper } from './style' import { ErrorBoundary } from '../../components/ErrorBoundary' -import { MissionEventContext } from '../../context/MissionEventContext' import { sideWindowPaths } from '../../domain/entities/sideWindow' import { ReportingContext } from '../../domain/shared_slices/Global' import { switchTab } from '../../domain/use_cases/missions/switchTab' @@ -98,9 +97,7 @@ export function SideWindow() { } path={sideWindowPaths.REPORTINGS} /> } path={[sideWindowPaths.MISSIONS, sideWindowPaths.MISSION]} /> } path={sideWindowPaths.MISSIONS} /> - - } path={sideWindowPaths.MISSION} /> - + } path={sideWindowPaths.MISSION} /> {isReportingsButtonIsActive && ( diff --git a/frontend/src/features/missions/MissionForm/FormikSyncMissionFields.tsx b/frontend/src/features/missions/MissionForm/FormikSyncMissionFields.tsx index bdddcaeaa..f80ec01b6 100644 --- a/frontend/src/features/missions/MissionForm/FormikSyncMissionFields.tsx +++ b/frontend/src/features/missions/MissionForm/FormikSyncMissionFields.tsx @@ -1,10 +1,10 @@ +import { useMissionEventContext } from 'context/useMissionEventContext' import { diff } from 'deep-object-diff' import { useFormikContext } from 'formik' import { omit } from 'lodash' import { useEffect } from 'react' import { MISSION_EVENT_UNSYNCHRONIZED_PROPERTIES_IN_FORM } from './constants' -import { useListenMissionEventUpdatesById } from './hooks/useListenMissionEventUpdatesById' import type { Mission } from '../../../domain/entities/missions' @@ -16,7 +16,8 @@ type FormikSyncMissionFormProps = { */ export function FormikSyncMissionFields({ missionId }: FormikSyncMissionFormProps) { const { setFieldValue, values } = useFormikContext() - const missionEvent = useListenMissionEventUpdatesById(missionId) + const { getMissionEventById, setMissionEventInContext } = useMissionEventContext() + const missionEvent = getMissionEventById(missionId) useEffect( () => { @@ -37,6 +38,11 @@ export function FormikSyncMissionFields({ missionId }: FormikSyncMissionFormProp console.log(`SSE: setting form key "${key}" to "${JSON.stringify(missionEvent[key])}"`) setFieldValue(key, missionEvent[key]) }) + + // we need to wait for the form to be updated before removing the mission event from the context + setTimeout(() => { + setMissionEventInContext(undefined) + }, 500) }, // We don't want to trigger infinite re-renders since `setFieldValue` changes after each rendering diff --git a/frontend/src/features/missions/MissionForm/MissionForm.tsx b/frontend/src/features/missions/MissionForm/MissionForm.tsx index e413de933..74f8b9d19 100644 --- a/frontend/src/features/missions/MissionForm/MissionForm.tsx +++ b/frontend/src/features/missions/MissionForm/MissionForm.tsx @@ -1,4 +1,5 @@ import { customDayjs, FormikEffect, usePrevious } from '@mtes-mct/monitor-ui' +import { useMissionEventContext } from 'context/useMissionEventContext' import { useFormikContext } from 'formik' import { isEmpty } from 'lodash' import { useEffect, useMemo, useState } from 'react' @@ -13,7 +14,6 @@ import { DeleteModal } from './DeleteModal' import { ExternalActionsModal } from './ExternalActionsModal' import { FormikSyncMissionFields } from './FormikSyncMissionFields' import { GeneralInformationsForm } from './GeneralInformationsForm' -import { useListenMissionEventUpdatesById } from './hooks/useListenMissionEventUpdatesById' import { useMissionAndActionsCompletion } from './hooks/useMissionAndActionsCompletion' import { useSyncFormValuesWithRedux } from './hooks/useSyncFormValuesWithRedux' import { useUpdateOtherControlTypes } from './hooks/useUpdateOtherControlTypes' @@ -62,7 +62,9 @@ export function MissionForm({ const attachedReportingIds = useAppSelector(state => state.attachReportingToMission.attachedReportingIds) const attachedReportings = useAppSelector(state => state.attachReportingToMission.attachedReportings) const selectedMissions = useAppSelector(state => state.missionForms.missions) - const missionEvent = useListenMissionEventUpdatesById(id) + const { getMissionEventById } = useMissionEventContext() + const missionEvent = getMissionEventById(id) + const { dirty, errors: formErrors, diff --git a/frontend/src/features/missions/MissionForm/constants.ts b/frontend/src/features/missions/MissionForm/constants.ts index e4bf5e614..4de5017e7 100644 --- a/frontend/src/features/missions/MissionForm/constants.ts +++ b/frontend/src/features/missions/MissionForm/constants.ts @@ -19,8 +19,8 @@ export const MISSION_EVENT_UNSYNCHRONIZED_PROPERTIES_IN_FORM = [ 'updatedAtUtc', // We do not update this field as it is not used by the form 'createdAtUtc', - // TODO add the update of the env actions - 'envActions' + // TODO to delete when fish and rapportNav are removed `closedBy`in their forms + 'completedBy' ] export const HIDDEN_ERROR = 'HIDDEN_ERROR' diff --git a/frontend/src/features/missions/MissionForm/hooks/useListenMissionEventUpdates.ts b/frontend/src/features/missions/MissionForm/hooks/useListenMissionEventUpdates.ts index 18362d48b..2579efebc 100644 --- a/frontend/src/features/missions/MissionForm/hooks/useListenMissionEventUpdates.ts +++ b/frontend/src/features/missions/MissionForm/hooks/useListenMissionEventUpdates.ts @@ -1,18 +1,17 @@ -import { useEffect, useRef, useState } from 'react' +import { useMissionEventContext } from 'context/useMissionEventContext' +import { useEffect, useRef } from 'react' import { useAppSelector } from '../../../../hooks/useAppSelector' import { missionEventListener } from '../sse' -import type { Mission } from '../../../../domain/entities/missions' - const MISSION_UPDATES_URL = `/api/v1/missions/sse` const MISSION_UPDATE_EVENT = `MISSION_UPDATE` export function useListenMissionEventUpdates() { const isListeningToEvents = useAppSelector(state => state.missionForms.isListeningToEvents) const eventSourceRef = useRef() - const [missionEvent, setMissionEvent] = useState() - const listener = useRef(missionEventListener(mission => setMissionEvent(mission))) + const { contextMissionEvent, setMissionEventInContext } = useMissionEventContext() + const listener = useRef(missionEventListener(mission => setMissionEventInContext(mission))) useEffect(() => { eventSourceRef.current = new EventSource(MISSION_UPDATES_URL) @@ -30,13 +29,13 @@ export function useListenMissionEventUpdates() { useEffect(() => { if (!isListeningToEvents) { eventSourceRef.current?.removeEventListener(MISSION_UPDATE_EVENT, listener.current) + setMissionEventInContext(undefined) return } - - listener.current = missionEventListener(mission => setMissionEvent(mission)) + listener.current = missionEventListener(mission => setMissionEventInContext(mission)) eventSourceRef.current?.addEventListener(MISSION_UPDATE_EVENT, listener.current) - }, [isListeningToEvents]) + }, [isListeningToEvents, setMissionEventInContext]) - return missionEvent + return contextMissionEvent } diff --git a/frontend/src/features/missions/MissionForm/hooks/useListenMissionEventUpdatesById.ts b/frontend/src/features/missions/MissionForm/hooks/useListenMissionEventUpdatesById.ts deleted file mode 100644 index b156f0240..000000000 --- a/frontend/src/features/missions/MissionForm/hooks/useListenMissionEventUpdatesById.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useContext } from 'react' - -import { MissionEventContext } from '../../../../context/MissionEventContext' - -import type { Mission } from '../../../../domain/entities/missions' - -export function useListenMissionEventUpdatesById(missionId: number | string | undefined) { - const missionEvent = useContext(MissionEventContext) - - return missionId && missionEvent?.id === missionId ? (missionEvent as Mission | undefined) : undefined -} diff --git a/frontend/src/features/missions/MissionForm/utils.ts b/frontend/src/features/missions/MissionForm/utils.ts index 8fb3f2a81..ff887fcf2 100644 --- a/frontend/src/features/missions/MissionForm/utils.ts +++ b/frontend/src/features/missions/MissionForm/utils.ts @@ -89,7 +89,8 @@ export const validateBeforeOnChange = debounce( if (engagedControlUnit) { return } + dispatch(saveMission(nextValues, false, false)) }, - 500 + 400 ) diff --git a/frontend/src/features/missions/Missions.helpers.ts b/frontend/src/features/missions/Missions.helpers.ts index 5eb9a7332..38fc4def1 100644 --- a/frontend/src/features/missions/Missions.helpers.ts +++ b/frontend/src/features/missions/Missions.helpers.ts @@ -183,7 +183,7 @@ type ActionsForTimeLine = Record envActions?.reduce((newEnvActionsCollection, action) => { - if (action.actionType === ActionTypeEnum.CONTROL && action.reportingIds.length === 1) { + if (action.actionType === ActionTypeEnum.CONTROL && action.reportingIds?.length === 1) { const attachedReporting = attachedReportings?.find(reporting => reporting.id === action.reportingIds[0]) return { From 7a1403f7bbebb1fcc0807fb04a1a081c3bc6383c Mon Sep 17 00:00:00 2001 From: Claire Dagan Date: Fri, 19 Apr 2024 10:05:52 +0200 Subject: [PATCH 2/9] [Missions] don't get fish actions in getAllMissions --- .../domain/use_cases/missions/GetFullMissions.kt | 12 +----------- .../bff/outputs/missions/MissionsDataOutput.kt | 8 -------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/GetFullMissions.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/GetFullMissions.kt index b6e2cfca3..b9e835058 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/GetFullMissions.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/GetFullMissions.kt @@ -39,16 +39,6 @@ class GetFullMissions( logger.info("Found ${missions.size} mission(s)") - return missions.map { missionAndFishActions -> - val mission = missionAndFishActions.mission - - try { - val fishActions = - monitorFishMissionActionsRepository.findFishMissionActionsById(mission.id!!) - MissionDTO(mission = mission, fishActions = fishActions) - } catch (e: Exception) { - MissionDTO(mission = mission, fishActions = listOf()) - } - } + return missions } } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/missions/MissionsDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/missions/MissionsDataOutput.kt index ef04ce4c1..a76ab48ca 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/missions/MissionsDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/missions/MissionsDataOutput.kt @@ -4,7 +4,6 @@ import fr.gouv.cacem.monitorenv.domain.entities.controlUnit.LegacyControlUnitEnt import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionSourceEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionTypeEnum import fr.gouv.cacem.monitorenv.domain.use_cases.missions.dtos.MissionDTO -import fr.gouv.cacem.monitorenv.infrastructure.monitorfish.adapters.MonitorFishMissionActionDataOutput import org.locationtech.jts.geom.MultiPolygon import java.time.ZonedDateTime @@ -21,7 +20,6 @@ data class MissionsDataOutput( val startDateTimeUtc: ZonedDateTime, val endDateTimeUtc: ZonedDateTime? = null, val envActions: List? = null, - val fishActions: List? = listOf(), val missionSource: MissionSourceEnum, val hasMissionOrder: Boolean, val isUnderJdp: Boolean, @@ -52,12 +50,6 @@ data class MissionsDataOutput( dto.envActionsAttachedToReportingIds, ) }, - fishActions = - dto.fishActions?.map { - MonitorFishMissionActionDataOutput.fromMonitorFishMissionActionEntity( - it, - ) - }, missionSource = dto.mission.missionSource, hasMissionOrder = dto.mission.hasMissionOrder, isUnderJdp = dto.mission.isUnderJdp, From 56a4ec12aa1473c69ca24cb29e14f9835693e3ce Mon Sep 17 00:00:00 2001 From: Claire Dagan Date: Fri, 19 Apr 2024 10:48:43 +0200 Subject: [PATCH 3/9] [Mission] UI changes after code review --- .../side_window/mission_list/filters.spec.ts | 8 ++-- .../e2e/side_window/reporting/filters.spec.ts | 4 +- frontend/src/App.tsx | 18 +++++---- .../Reportings/Filters/Table/FilterTags.tsx | 2 +- .../Reportings/Filters/Table/index.tsx | 6 +-- .../SideWindow/SideWindowLauncher.tsx | 5 +-- .../MissionForm/ActionsTimeLine/index.tsx | 39 ++++++++++++------- .../missions/MissionForm/constants.ts | 6 ++- .../MissionsList/Filters/FilterTags.tsx | 4 +- .../missions/MissionsList/Filters/index.tsx | 8 ++-- .../components/MissionStatusLabel.tsx | 2 +- .../missions/components/MissionStatusTag.tsx | 2 +- 12 files changed, 60 insertions(+), 44 deletions(-) diff --git a/frontend/cypress/e2e/side_window/mission_list/filters.spec.ts b/frontend/cypress/e2e/side_window/mission_list/filters.spec.ts index c4a1ecbdc..ed1696dec 100644 --- a/frontend/cypress/e2e/side_window/mission_list/filters.spec.ts +++ b/frontend/cypress/e2e/side_window/mission_list/filters.spec.ts @@ -143,7 +143,7 @@ context('Side Window > Mission List > Filter Bar', () => { }) it('Should filter missions by sea fronts', () => { - cy.fill('Facade', ['MED']) + cy.fill('Façade', ['MED']) cy.get('.Table-SimpleTable tr').should('have.length.to.be.greaterThan', 0) cy.get('.Table-SimpleTable tr').each((row, index) => { @@ -154,7 +154,7 @@ context('Side Window > Mission List > Filter Bar', () => { cy.wrap(row).should('contain', 'MED') }) - cy.fill('Facade', undefined) + cy.fill('Façade', undefined) }) it('Should filter missions by statuses', () => { @@ -217,7 +217,7 @@ context('Side Window > Mission List > Filter Bar', () => { }) it('Should filter missions with env actions', () => { - cy.fill('Missions avec actions env.', true) + cy.fill('Missions avec actions CACEM', true) cy.wait('@getMissions') cy.get('.Table-SimpleTable tr').should('have.length', 6) @@ -226,6 +226,6 @@ context('Side Window > Mission List > Filter Bar', () => { cy.getDataCy('edit-mission-22').should('not.exist') cy.getDataCy('edit-mission-43').should('not.exist') - cy.fill('Missions avec actions env.', false) + cy.fill('Missions avec actions CACEM', false) }) }) diff --git a/frontend/cypress/e2e/side_window/reporting/filters.spec.ts b/frontend/cypress/e2e/side_window/reporting/filters.spec.ts index d371fd49e..62acc5bfe 100644 --- a/frontend/cypress/e2e/side_window/reporting/filters.spec.ts +++ b/frontend/cypress/e2e/side_window/reporting/filters.spec.ts @@ -133,7 +133,7 @@ context('Reportings', () => { }) it('Should filter reportings by sea-fronts', () => { - cy.fill('Facade', [SeaFrontLabel.NAMO]) + cy.fill('Façade', [SeaFrontLabel.NAMO]) cy.getDataCy('reportings-filter-tags').find('.Component-SingleTag > span').contains('Facade NAMO') cy.wait('@getReportings') @@ -147,7 +147,7 @@ context('Reportings', () => { cy.wrap(row).should('contain', 'NAMO') }) - cy.fill('Facade', undefined) + cy.fill('Façade', undefined) }) it('Should filter reportings by status', () => { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7d7cdcfef..0136691c8 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -8,6 +8,7 @@ import { HomePage } from '@pages/HomePage' import { homeStore } from '@store/index' import { isBrowserSupported } from '@utils/isBrowserSupported' import { isCypress } from '@utils/isCypress' +import { MissionEventProvider } from 'context/MissionEventContext' import { Provider as ReduxProvider } from 'react-redux' import { BrowserRouter as Router, Route, Routes } from 'react-router-dom' import { persistStore } from 'redux-persist' @@ -36,14 +37,17 @@ export function App() { - - - } path="/backoffice/*" /> - } path="/side_window" /> - } path="/" /> - - + + + + } path="/backoffice/*" /> + } path="/side_window" /> + + } path="/" /> + + + diff --git a/frontend/src/features/Reportings/Filters/Table/FilterTags.tsx b/frontend/src/features/Reportings/Filters/Table/FilterTags.tsx index 58437be96..17643c512 100644 --- a/frontend/src/features/Reportings/Filters/Table/FilterTags.tsx +++ b/frontend/src/features/Reportings/Filters/Table/FilterTags.tsx @@ -110,7 +110,7 @@ export function FilterTags() { accent={Accent.SECONDARY} onDelete={() => onDeleteTag(seaFront, ReportingsFiltersEnum.SEA_FRONT_FILTER, seaFrontFilter)} > - {String(`Facade ${seaFront}`)} + {String(`Façade ${seaFront}`)} ))} diff --git a/frontend/src/features/Reportings/Filters/Table/index.tsx b/frontend/src/features/Reportings/Filters/Table/index.tsx index 7463e3708..b0c81b2c2 100644 --- a/frontend/src/features/Reportings/Filters/Table/index.tsx +++ b/frontend/src/features/Reportings/Filters/Table/index.tsx @@ -234,12 +234,12 @@ export function TableReportingsFiltersWithRef( /> updateSimpleFilter(value, ReportingsFiltersEnum.SEA_FRONT_FILTER)} options={seaFrontsOptions} - placeholder="Facade" - renderValue={() => seaFrontFilter && {`Facade (${seaFrontFilter.length})`}} + placeholder="Façade" + renderValue={() => seaFrontFilter && {`Façade (${seaFrontFilter.length})`}} size="sm" style={tagPickerStyle} value={seaFrontFilter} diff --git a/frontend/src/features/SideWindow/SideWindowLauncher.tsx b/frontend/src/features/SideWindow/SideWindowLauncher.tsx index 40acbfae1..2cc20ecd3 100644 --- a/frontend/src/features/SideWindow/SideWindowLauncher.tsx +++ b/frontend/src/features/SideWindow/SideWindowLauncher.tsx @@ -1,5 +1,4 @@ import { useForceUpdate, NewWindow } from '@mtes-mct/monitor-ui' -import { MissionEventProvider } from 'context/MissionEventContext' import { useCallback, useEffect, useMemo } from 'react' import { SideWindow } from '.' @@ -73,9 +72,7 @@ export function SideWindowLauncher() { showPrompt={hasAtLeastOneMissionFormDirty || hasAtLeastOneReportingFormDirty} title="MonitorEnv" > - - - + ) } diff --git a/frontend/src/features/missions/MissionForm/ActionsTimeLine/index.tsx b/frontend/src/features/missions/MissionForm/ActionsTimeLine/index.tsx index b1e0aabe4..e24f6e072 100644 --- a/frontend/src/features/missions/MissionForm/ActionsTimeLine/index.tsx +++ b/frontend/src/features/missions/MissionForm/ActionsTimeLine/index.tsx @@ -129,19 +129,21 @@ export function ActionsTimeLine({ currentActionIndex, setCurrentActionIndex }) { Actions réalisées en mission - - - Ajouter des contrôles - - - Ajouter une surveillance - - - Ajouter une note libre - - - - + + + + Ajouter des contrôles + + + Ajouter une surveillance + + + Ajouter une note libre + + + + + @@ -192,6 +194,17 @@ const TitleWrapper = styled.div` display: flex; justify-content: space-between; ` +const ButtonsWrapper = styled.div` + display: flex; + gap: 8px; + + // TODO update this in monitor-ui - Dropdown component + > div { + > button { + padding: 5px 12px 6px !important; + } + } +` const ActionsTimeline = styled.div` flex: 1; diff --git a/frontend/src/features/missions/MissionForm/constants.ts b/frontend/src/features/missions/MissionForm/constants.ts index 4de5017e7..002d2f7d9 100644 --- a/frontend/src/features/missions/MissionForm/constants.ts +++ b/frontend/src/features/missions/MissionForm/constants.ts @@ -19,8 +19,10 @@ export const MISSION_EVENT_UNSYNCHRONIZED_PROPERTIES_IN_FORM = [ 'updatedAtUtc', // We do not update this field as it is not used by the form 'createdAtUtc', - // TODO to delete when fish and rapportNav are removed `closedBy`in their forms - 'completedBy' + // TODO to delete this three fields when fish and rapportNav are removed `closedBy`in their forms + 'completedBy', + 'closedBy', + 'isClosed' ] export const HIDDEN_ERROR = 'HIDDEN_ERROR' diff --git a/frontend/src/features/missions/MissionsList/Filters/FilterTags.tsx b/frontend/src/features/missions/MissionsList/Filters/FilterTags.tsx index bd85302e3..4666f0c42 100644 --- a/frontend/src/features/missions/MissionsList/Filters/FilterTags.tsx +++ b/frontend/src/features/missions/MissionsList/Filters/FilterTags.tsx @@ -53,7 +53,7 @@ export function FilterTags() { key={seaFront} onDelete={() => onDeleteTag(seaFront, MissionFiltersEnum.SEA_FRONT_FILTER, selectedSeaFronts)} > - {String(`Facade ${seaFront}`)} + {String(`Façade ${seaFront}`)} ))} {selectedAdministrationNames && @@ -112,7 +112,7 @@ export function FilterTags() { onDeleteTag(completionStatus, MissionFiltersEnum.COMPLETION_STATUS_FILTER, selectedCompletionStatus) } > - {String(`données ${FrontCompletionStatusLabel[completionStatus]}`)} + {String(`Données ${FrontCompletionStatusLabel[completionStatus].toLowerCase()}`)} ))} diff --git a/frontend/src/features/missions/MissionsList/Filters/index.tsx b/frontend/src/features/missions/MissionsList/Filters/index.tsx index bd1c54418..cc8205d9d 100644 --- a/frontend/src/features/missions/MissionsList/Filters/index.tsx +++ b/frontend/src/features/missions/MissionsList/Filters/index.tsx @@ -193,12 +193,12 @@ export function MissionsTableFilters() { data-cy="select-seaFronts-filter" isLabelHidden isTransparent - label="Facade" + label="Façade" name="seaFront" onChange={(value: any) => onUpdateSimpleFilter(value, MissionFiltersEnum.SEA_FRONT_FILTER)} options={seaFrontsAsOptions} - placeholder="Facade" - renderValue={() => selectedSeaFronts && {`Facade (${selectedSeaFronts.length})`}} + placeholder="Façade" + renderValue={() => selectedSeaFronts && {`Façade (${selectedSeaFronts.length})`}} style={tagPickerStyle} value={selectedSeaFronts} /> @@ -318,7 +318,7 @@ export function MissionsTableFilters() { /> onUpdateSimpleFilter(value ?? false, MissionFiltersEnum.WITH_ENV_ACTIONS_FILTER)} /> diff --git a/frontend/src/features/missions/components/MissionStatusLabel.tsx b/frontend/src/features/missions/components/MissionStatusLabel.tsx index 8d6874b40..21a9e04b3 100644 --- a/frontend/src/features/missions/components/MissionStatusLabel.tsx +++ b/frontend/src/features/missions/components/MissionStatusLabel.tsx @@ -23,7 +23,7 @@ export function MissionStatusLabel({ missionStatus }) { case missionStatusLabels.UPCOMING.code: return ( - + {missionStatusLabels.UPCOMING.libelle} ) diff --git a/frontend/src/features/missions/components/MissionStatusTag.tsx b/frontend/src/features/missions/components/MissionStatusTag.tsx index b747f403f..538f16938 100644 --- a/frontend/src/features/missions/components/MissionStatusTag.tsx +++ b/frontend/src/features/missions/components/MissionStatusTag.tsx @@ -35,7 +35,7 @@ export function MissionStatusTag({ status }: { status: string }) { From 51ef30f88ce25c63266029685fe967abb69f3ba3 Mon Sep 17 00:00:00 2001 From: Claire Dagan Date: Fri, 19 Apr 2024 12:31:43 +0200 Subject: [PATCH 4/9] [Mission] update columns size in mission list and clean tests --- .../mission_form/main_form.spec.ts | 21 ++++++-- .../side_window/mission_list/filters.spec.ts | 2 +- .../e2e/side_window/reporting/filters.spec.ts | 2 +- .../missions/MissionsList/Columns/index.tsx | 48 ++++++++++++++----- .../missions/MissionsList/getDateCell.tsx | 4 +- .../MissionsList/getMissionTypeCell.tsx | 2 +- .../MissionsList/getNumberOfControlsCell.tsx | 2 +- 7 files changed, 58 insertions(+), 23 deletions(-) diff --git a/frontend/cypress/e2e/side_window/mission_form/main_form.spec.ts b/frontend/cypress/e2e/side_window/mission_form/main_form.spec.ts index d8f944f99..61ca9e866 100644 --- a/frontend/cypress/e2e/side_window/mission_form/main_form.spec.ts +++ b/frontend/cypress/e2e/side_window/mission_form/main_form.spec.ts @@ -313,11 +313,13 @@ context('Side Window > Mission Form > Main Form', () => { attachedReportingIds: [], attachedReportings: [], // Changed field - completedBy: 'LTH', + // TODO : uncomment this field when Fish andRapportNav have deleted closedBy field + // completedBy: 'LTH', controlUnits: [ { administration: 'Gendarmerie Nationale', - contact: null, + // Changed field + contact: 'contact', id: 10020, isArchived: false, name: 'BN Toulon', @@ -346,7 +348,7 @@ context('Side Window > Mission Form > Main Form', () => { ) }) - cy.wait(1500) + cy.wait(500) cy.get('[name="missionTypes1"]').click({ force: true }) cy.wait(250) @@ -354,7 +356,18 @@ context('Side Window > Mission Form > Main Form', () => { '@updateMission', { body: { - completedBy: 'LTH', + controlUnits: [ + { + administration: 'Gendarmerie Nationale', + contact: 'contact', + id: 10020, + isArchived: false, + name: 'BN Toulon', + resources: [] + } + ], + // TODO : uncomment this field when Fish andRapportNav have deleted closedBy field + // completedBy: 'LTH', missionTypes: ['SEA', 'LAND'], observationsCnsp: 'Encore une observation', openBy: 'LTH' diff --git a/frontend/cypress/e2e/side_window/mission_list/filters.spec.ts b/frontend/cypress/e2e/side_window/mission_list/filters.spec.ts index ed1696dec..d32694d27 100644 --- a/frontend/cypress/e2e/side_window/mission_list/filters.spec.ts +++ b/frontend/cypress/e2e/side_window/mission_list/filters.spec.ts @@ -34,7 +34,7 @@ context('Side Window > Mission List > Filter Bar', () => { cy.fill('Etat des données', ['Complétées']) cy.wait('@getMissions') - cy.getDataCy('missions-filter-tags').find('.Component-SingleTag > span').contains('données Complétées') + cy.getDataCy('missions-filter-tags').find('.Component-SingleTag > span').contains('Données complétées') cy.get('.Table-SimpleTable tr').should('have.length.to.be.greaterThan', 0) cy.get('.Table-SimpleTable tr').each((row, index) => { if (index === 0) { diff --git a/frontend/cypress/e2e/side_window/reporting/filters.spec.ts b/frontend/cypress/e2e/side_window/reporting/filters.spec.ts index 62acc5bfe..cc22bb13d 100644 --- a/frontend/cypress/e2e/side_window/reporting/filters.spec.ts +++ b/frontend/cypress/e2e/side_window/reporting/filters.spec.ts @@ -134,7 +134,7 @@ context('Reportings', () => { it('Should filter reportings by sea-fronts', () => { cy.fill('Façade', [SeaFrontLabel.NAMO]) - cy.getDataCy('reportings-filter-tags').find('.Component-SingleTag > span').contains('Facade NAMO') + cy.getDataCy('reportings-filter-tags').find('.Component-SingleTag > span').contains('Façade NAMO') cy.wait('@getReportings') diff --git a/frontend/src/features/missions/MissionsList/Columns/index.tsx b/frontend/src/features/missions/MissionsList/Columns/index.tsx index 55b3731d4..41c53d1ca 100644 --- a/frontend/src/features/missions/MissionsList/Columns/index.tsx +++ b/frontend/src/features/missions/MissionsList/Columns/index.tsx @@ -15,7 +15,9 @@ export const Columns = [ enableSorting: true, header: () => 'Début', id: 'startDate', - size: 140 + maxSize: 109, + minSize: 109, + size: 109 // +24(padding) + 1(border) = 134 }, { accessorFn: row => row.endDateTimeUtc, @@ -23,15 +25,19 @@ export const Columns = [ enableSorting: true, header: () => 'Fin', id: 'endDate', - size: 140 + maxSize: 109, + minSize: 109, + size: 109 // +24(padding) + 1(border) = 134 }, { accessorFn: row => row.facade, cell: info => info.getValue(), enableSorting: true, - header: () => 'Facade', + header: () => 'Façade', id: 'seaFront', - size: 96 + maxSize: 71, + minSize: 71, + size: 71 // +24(padding) + 1(border) = 96 }, { accessorFn: row => row.missionTypes, @@ -39,7 +45,9 @@ export const Columns = [ enableSorting: false, header: () => 'Type', id: 'type', - size: 120 + maxSize: 95, + minSize: 95, + size: 95 // +24(padding) + 1(border) = 120 }, { accessorFn: row => row.controlUnits, @@ -47,7 +55,9 @@ export const Columns = [ enableSorting: false, header: () => 'Unité (Administration)', id: 'unitAndAdministration', - size: 310 + maxSize: 231, + minSize: 231, + size: 231 // +24(padding) + 1(border) = 256 }, { @@ -56,15 +66,19 @@ export const Columns = [ enableSorting: false, header: () => 'Thématiques', id: 'themes', - size: 544 + maxSize: 367, + minSize: 367, + size: 367 // +24(padding) + 1(border) = 392 }, { accessorFn: row => row.envActions, cell: info => getNumberOfControlsCell(info.getValue()), enableSorting: false, - header: () => 'Contrôles', + header: () => 'Ctr.', id: 'controls', - size: 100 + maxSize: 41, + minSize: 41, + size: 41 // +24(padding) + 1(border) = 66 }, { accessorFn: row => row, @@ -72,7 +86,9 @@ export const Columns = [ enableSorting: false, header: () => 'Statut', id: 'status', - size: 120 + maxSize: 82, + minSize: 82, + size: 82 // +24(padding) + 1(border) = 107 }, { accessorFn: row => row, @@ -80,7 +96,9 @@ export const Columns = [ enableSorting: false, header: () => 'État données', id: 'completion', - size: 136 + maxSize: 102, + minSize: 102, + size: 102 // +24(padding) + 1(border) = 127 }, { accessorFn: row => row.geom, @@ -88,7 +106,9 @@ export const Columns = [ enableSorting: false, header: () => '', id: 'geom', - size: 55 + maxSize: 28, + minSize: 28, + size: 28 }, { accessorFn: row => row.id, @@ -96,6 +116,8 @@ export const Columns = [ enableSorting: false, header: () => '', id: 'edit', - size: 55 + maxSize: 34, + minSize: 34, + size: 34 } ] diff --git a/frontend/src/features/missions/MissionsList/getDateCell.tsx b/frontend/src/features/missions/MissionsList/getDateCell.tsx index 705f5aebf..ef62c75df 100644 --- a/frontend/src/features/missions/MissionsList/getDateCell.tsx +++ b/frontend/src/features/missions/MissionsList/getDateCell.tsx @@ -1,9 +1,9 @@ -import { getDateAsLocalizedStringCompact } from '../../../utils/getDateAsLocalizedString' +import { getDateAsLocalizedStringVeryCompact } from '../../../utils/getDateAsLocalizedString' export function getDateCell(date: string) { if (!date) { return 'Non saisie' } - return getDateAsLocalizedStringCompact(date) + return getDateAsLocalizedStringVeryCompact(date) } diff --git a/frontend/src/features/missions/MissionsList/getMissionTypeCell.tsx b/frontend/src/features/missions/MissionsList/getMissionTypeCell.tsx index b6b1722ee..77ee357dd 100644 --- a/frontend/src/features/missions/MissionsList/getMissionTypeCell.tsx +++ b/frontend/src/features/missions/MissionsList/getMissionTypeCell.tsx @@ -1,7 +1,7 @@ import { MissionTypeEnum, missionTypeEnum } from '../../../domain/entities/missions' export function getMissionTypeCell(missionTypes: MissionTypeEnum[]) { - const missionTypesAsText = missionTypes?.map(t => missionTypeEnum[t]?.libelle).join(' / ') + const missionTypesAsText = missionTypes?.map(t => missionTypeEnum[t]?.libelle).join(', ') return missionTypesAsText } diff --git a/frontend/src/features/missions/MissionsList/getNumberOfControlsCell.tsx b/frontend/src/features/missions/MissionsList/getNumberOfControlsCell.tsx index 2f7371dcf..ea6fb04be 100644 --- a/frontend/src/features/missions/MissionsList/getNumberOfControlsCell.tsx +++ b/frontend/src/features/missions/MissionsList/getNumberOfControlsCell.tsx @@ -3,5 +3,5 @@ import _ from 'lodash' export function getNumberOfControlsCell(envActions) { const numberOfControls = _.reduce(envActions, (sum, action) => sum + (action.actionNumberOfControls || 0), 0) - return numberOfControls + return numberOfControls > 0 ? numberOfControls : '-' } From 4a2c2f02a66d96511c4cc6850723db761a7642c2 Mon Sep 17 00:00:00 2001 From: Claire Dagan Date: Fri, 19 Apr 2024 14:24:23 +0200 Subject: [PATCH 5/9] [Mission] add sorting function for controls, status and completion columns --- .../layers/Missions/missionGeometryHelpers.ts | 4 +-- .../MissionsList/getNumberOfControlsCell.tsx | 4 +-- .../features/missions/MissionsList/utils.ts | 36 +++++++++++++++++++ frontend/src/features/missions/utils.test.ts | 10 +++++- frontend/src/features/missions/utils.ts | 15 ++++---- 5 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 frontend/src/features/missions/MissionsList/utils.ts diff --git a/frontend/src/features/map/layers/Missions/missionGeometryHelpers.ts b/frontend/src/features/map/layers/Missions/missionGeometryHelpers.ts index dd44348e8..141fcb665 100644 --- a/frontend/src/features/map/layers/Missions/missionGeometryHelpers.ts +++ b/frontend/src/features/map/layers/Missions/missionGeometryHelpers.ts @@ -42,8 +42,8 @@ export const getMissionZoneFeature = (mission: Partial, la missionStatus: getMissionStatus(mission), missionTypes: mission.missionTypes, numberOfActions: mission.envActions?.length ?? 0, - numberOfControls: getTotalOfControls(mission), - numberOfSurveillance: getTotalOfSurveillances(mission), + numberOfControls: getTotalOfControls(mission.envActions ?? []), + numberOfSurveillance: getTotalOfSurveillances(mission.envActions ?? []), overlayCoordinates: undefined, startDateTimeUtc: mission.startDateTimeUtc }) diff --git a/frontend/src/features/missions/MissionsList/getNumberOfControlsCell.tsx b/frontend/src/features/missions/MissionsList/getNumberOfControlsCell.tsx index ea6fb04be..56ff86b4f 100644 --- a/frontend/src/features/missions/MissionsList/getNumberOfControlsCell.tsx +++ b/frontend/src/features/missions/MissionsList/getNumberOfControlsCell.tsx @@ -1,7 +1,7 @@ -import _ from 'lodash' +import { getTotalOfControls } from '../utils' export function getNumberOfControlsCell(envActions) { - const numberOfControls = _.reduce(envActions, (sum, action) => sum + (action.actionNumberOfControls || 0), 0) + const numberOfControls = getTotalOfControls(envActions) return numberOfControls > 0 ? numberOfControls : '-' } diff --git a/frontend/src/features/missions/MissionsList/utils.ts b/frontend/src/features/missions/MissionsList/utils.ts new file mode 100644 index 000000000..7c803378f --- /dev/null +++ b/frontend/src/features/missions/MissionsList/utils.ts @@ -0,0 +1,36 @@ +import { FrontCompletionStatus, FrontCompletionStatusLabel, getMissionStatus } from 'domain/entities/missions' + +import { getMissionCompletionStatus, getTotalOfControls } from '../utils' + +import type { Row } from '@tanstack/react-table' + +export function sortNumberOfControls(rowA: Row, rowB: Row, columnId: string) { + return getTotalOfControls(rowA.original[columnId]) - getTotalOfControls(rowB.original[columnId]) +} + +export function sortStatus(rowA: Row, rowB: Row) { + return getMissionStatus(rowA.original).localeCompare(getMissionStatus(rowB.original)) +} + +export function sortCompletion(rowA: Row, rowB: Row) { + const statusA: string = getMissionCompletionStatus(rowA.original) ?? '' + const statusB: string = getMissionCompletionStatus(rowB.original) ?? '' + + let statusALabel = statusA + if (statusA !== '') { + statusALabel = + statusA === FrontCompletionStatus.TO_COMPLETE_MISSION_ENDED + ? FrontCompletionStatusLabel[FrontCompletionStatus.TO_COMPLETE] + : FrontCompletionStatusLabel[statusA] + } + + let statusBLabel = statusB + if (statusB !== '') { + statusBLabel = + statusB === FrontCompletionStatus.TO_COMPLETE_MISSION_ENDED + ? FrontCompletionStatusLabel[FrontCompletionStatus.TO_COMPLETE] + : FrontCompletionStatusLabel[statusB] + } + + return statusALabel.localeCompare(statusBLabel) +} diff --git a/frontend/src/features/missions/utils.test.ts b/frontend/src/features/missions/utils.test.ts index 9d1eea5a7..9f3a6569f 100644 --- a/frontend/src/features/missions/utils.test.ts +++ b/frontend/src/features/missions/utils.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from '@jest/globals' import { customDayjs } from '@mtes-mct/monitor-ui' -import { getMissionCompletionStatus } from './utils' +import { getIsMissionEnded, getMissionCompletionStatus } from './utils' import { FrontCompletionStatus } from '../../domain/entities/missions' const pendingMission = { @@ -80,4 +80,12 @@ describe('mission utils', () => { const missionCompletionUpcoming = getMissionCompletionStatus(missionUpcoming) expect(missionCompletionUpcoming).toBe(undefined) }) + + it('getIsMissionEnded Should return true if the mission is ended', async () => { + const isMissionEnded = getIsMissionEnded(endedMission.endDateTimeUtc) + expect(isMissionEnded).toBe(true) + + const isMissionNotEnded = getIsMissionEnded(pendingMission.endDateTimeUtc) + expect(isMissionNotEnded).toBe(false) + }) }) diff --git a/frontend/src/features/missions/utils.ts b/frontend/src/features/missions/utils.ts index 49ed03b33..8a898a32f 100644 --- a/frontend/src/features/missions/utils.ts +++ b/frontend/src/features/missions/utils.ts @@ -7,26 +7,23 @@ import { FrontCompletionStatus, getMissionStatus, MissionStatusEnum, - type Mission, - type NewMission + type EnvAction } from '../../domain/entities/missions' -export const getTotalOfControls = (mission: Partial) => +export const getTotalOfControls = (envActions: Array>) => sum( - mission.envActions?.map( - control => (control.actionType === ActionTypeEnum.CONTROL && control.actionNumberOfControls) || 0 - ) + envActions?.map(control => (control.actionType === ActionTypeEnum.CONTROL && control.actionNumberOfControls) || 0) ) -export const getTotalOfSurveillances = (mission: Partial) => - mission.envActions?.filter(action => action.actionType === ActionTypeEnum.SURVEILLANCE).length +export const getTotalOfSurveillances = (envActions: Array>) => + envActions?.filter(action => action.actionType === ActionTypeEnum.SURVEILLANCE).length export function getVesselName(vesselName) { return vesselName === 'UNKNOWN' ? 'Navire inconnu' : vesselName } export function hasAtLeastOnUncompletedEnvAction(envActions): boolean { - return !!envActions.find( + return !!envActions?.find( action => (action.actionType === ActionTypeEnum.SURVEILLANCE || action.actionType === ActionTypeEnum.CONTROL) && action.completion === CompletionStatus.TO_COMPLETE From f61dfe7b1706746d204eac332df257fc8f87745b Mon Sep 17 00:00:00 2001 From: Claire Dagan Date: Fri, 19 Apr 2024 14:44:47 +0200 Subject: [PATCH 6/9] [Mission] add completedBy prop in mission public api --- .../inputs/CreateOrUpdateMissionDataInput.kt | 6 +- .../publicapi/outputs/MissionDataOutput.kt | 7 +- .../publicapi/ApiLegacyMissionsITests.kt | 106 ++++++++++-------- .../missions/MissionsList/Columns/index.tsx | 17 +-- 4 files changed, 78 insertions(+), 58 deletions(-) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/inputs/CreateOrUpdateMissionDataInput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/inputs/CreateOrUpdateMissionDataInput.kt index e046a9d30..18fe73996 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/inputs/CreateOrUpdateMissionDataInput.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/inputs/CreateOrUpdateMissionDataInput.kt @@ -12,7 +12,8 @@ data class CreateOrUpdateMissionDataInput( val missionTypes: List, val controlUnits: List = listOf(), val openBy: String? = null, - val closedBy: String? = null, + val closedBy: String? = null, // TODO delete when Fish and RapportNav are removed + val completedBy: String? = null, val observationsCacem: String? = null, val observationsCnsp: String? = null, val facade: String? = null, @@ -31,7 +32,8 @@ data class CreateOrUpdateMissionDataInput( missionTypes = this.missionTypes, controlUnits = this.controlUnits, openBy = this.openBy, - completedBy = this.closedBy, + // TODO delete condition and closedBy when Fish and RapportNav are removed + completedBy = this.completedBy ?: this.closedBy, observationsCacem = this.observationsCacem, observationsCnsp = this.observationsCnsp, facade = this.facade, diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/outputs/MissionDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/outputs/MissionDataOutput.kt index ef4f43f29..c8dac34f7 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/outputs/MissionDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/outputs/MissionDataOutput.kt @@ -12,7 +12,8 @@ data class MissionDataOutput( val missionTypes: List, val controlUnits: List? = listOf(), val openBy: String? = null, - val closedBy: String? = null, + val closedBy: String? = null, // TODO delete when Fish and RapportNav are removed + val completedBy: String? = null, val observationsCacem: String? = null, val observationsCnsp: String? = null, val facade: String? = null, @@ -37,7 +38,9 @@ data class MissionDataOutput( missionTypes = mission.missionTypes, controlUnits = mission.controlUnits, openBy = mission.openBy, - closedBy = mission.completedBy, + closedBy = + mission.completedBy, // TODO delete when Fish and RapportNav are removed + completedBy = mission.completedBy, observationsCacem = mission.observationsCacem, observationsCnsp = mission.observationsCnsp, facade = mission.facade, diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ApiLegacyMissionsITests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ApiLegacyMissionsITests.kt index de90cb656..fe3dba05b 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ApiLegacyMissionsITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/ApiLegacyMissionsITests.kt @@ -49,7 +49,8 @@ class ApiLegacyMissionsITests { @MockBean private lateinit var deleteMission: DeleteMission - @MockBean private lateinit var bypassActionCheckAndDeleteMission: BypassActionCheckAndDeleteMission + @MockBean + private lateinit var bypassActionCheckAndDeleteMission: BypassActionCheckAndDeleteMission @MockBean private lateinit var canDeleteMission: CanDeleteMission @@ -282,15 +283,13 @@ class ApiLegacyMissionsITests { @Test fun `Should delete mission`() { - mockMvc.perform(delete("/api/v1/missions/20")) - .andExpect(status().isOk) + mockMvc.perform(delete("/api/v1/missions/20")).andExpect(status().isOk) Mockito.verify(bypassActionCheckAndDeleteMission).execute(20) } @Test fun `Should delete mission with api v2`() { - mockMvc.perform(delete("/api/v2/missions/20?source=MONITORFISH")) - .andExpect(status().isOk) + mockMvc.perform(delete("/api/v2/missions/20?source=MONITORFISH")).andExpect(status().isOk) Mockito.verify(deleteMission).execute(20, MissionSourceEnum.MONITORFISH) } @@ -313,7 +312,12 @@ class ApiLegacyMissionsITests { val source = MissionSourceEnum.MONITORFISH given(canDeleteMission.execute(missionId = missionId, source = source)) - .willReturn(CanDeleteMissionResponse(canDelete = false, sources = listOf(MissionSourceEnum.MONITORENV))) + .willReturn( + CanDeleteMissionResponse( + canDelete = false, + sources = listOf(MissionSourceEnum.MONITORENV), + ), + ) mockMvc.perform(get("/api/v1/missions/$missionId/can_delete?source=$source")) .andExpect(status().isOk) @@ -323,20 +327,21 @@ class ApiLegacyMissionsITests { @Test fun `Should get all engaged control units`() { // Given - given(getEngagedControlUnits.execute()).willReturn( - listOf( - Pair( - LegacyControlUnitEntity( - id = 123, - administration = "Admin", - resources = listOf(), - isArchived = false, - name = "Control Unit Name", + given(getEngagedControlUnits.execute()) + .willReturn( + listOf( + Pair( + LegacyControlUnitEntity( + id = 123, + administration = "Admin", + resources = listOf(), + isArchived = false, + name = "Control Unit Name", + ), + listOf(MissionSourceEnum.MONITORFISH), ), - listOf(MissionSourceEnum.MONITORFISH), ), - ), - ) + ) // When mockMvc.perform(get("/api/v1/missions/engaged_control_units")) @@ -353,22 +358,26 @@ class ApiLegacyMissionsITests { val multipolygonString = "MULTIPOLYGON (((-4.54877817 48.30555988, -4.54997332 48.30597601, -4.54998501 48.30718823, -4.5487929 48.30677461, -4.54877817 48.30555988)))" val polygon = wktReader.read(multipolygonString) as MultiPolygon - val updateMissionEvent = UpdateMissionEvent( - mission = MissionEntity( - id = 132, - missionTypes = listOf(MissionTypeEnum.SEA), - facade = "Outre-Mer", - geom = polygon, - observationsCnsp = null, - startDateTimeUtc = ZonedDateTime.parse("2022-01-15T04:50:09Z"), - endDateTimeUtc = ZonedDateTime.parse("2022-01-23T20:29:03Z"), - isDeleted = false, - missionSource = MissionSourceEnum.MONITORFISH, - hasMissionOrder = false, - isUnderJdp = false, - isGeometryComputedFromControls = false, - ), - ) + val updateMissionEvent = + UpdateMissionEvent( + mission = + MissionEntity( + id = 132, + missionTypes = listOf(MissionTypeEnum.SEA), + facade = "Outre-Mer", + geom = polygon, + observationsCnsp = null, + startDateTimeUtc = + ZonedDateTime.parse("2022-01-15T04:50:09Z"), + endDateTimeUtc = + ZonedDateTime.parse("2022-01-23T20:29:03Z"), + isDeleted = false, + missionSource = MissionSourceEnum.MONITORFISH, + hasMissionOrder = false, + isUnderJdp = false, + isGeometryComputedFromControls = false, + ), + ) // When we send an event from another thread object : Thread() { @@ -380,22 +389,25 @@ class ApiLegacyMissionsITests { println(ex) } } - }.start() + } + .start() // Then - val missionUpdateEvent = mockMvc.perform(get("/api/v1/missions/sse")) - .andExpect(status().isOk) - .andExpect(request().asyncStarted()) - .andExpect(request().asyncResult(nullValue())) - .andExpect(header().string("Content-Type", "text/event-stream")) - .andDo(MockMvcResultHandlers.log()) - .andReturn() - .response - .contentAsString + val missionUpdateEvent = + mockMvc.perform(get("/api/v1/missions/sse")) + .andExpect(status().isOk) + .andExpect(request().asyncStarted()) + .andExpect(request().asyncResult(nullValue())) + .andExpect(header().string("Content-Type", "text/event-stream")) + .andDo(MockMvcResultHandlers.log()) + .andReturn() + .response + .contentAsString assertThat(missionUpdateEvent).contains("event:MISSION_UPDATE") - assertThat(missionUpdateEvent).contains( - "data:{\"id\":132,\"missionTypes\":[\"SEA\"],\"controlUnits\":[],\"openBy\":null,\"closedBy\":null,\"observationsCacem\":null,\"observationsCnsp\":null,\"facade\":\"Outre-Mer\",\"geom\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-4.54877817,48.30555988],[-4.54997332,48.30597601],[-4.54998501,48.30718823],[-4.5487929,48.30677461],[-4.54877817,48.30555988]]]]},\"startDateTimeUtc\":\"2022-01-15T04:50:09Z\",\"endDateTimeUtc\":\"2022-01-23T20:29:03Z\",\"createdAtUtc\":null,\"updatedAtUtc\":null,\"envActions\":[],\"missionSource\":\"MONITORFISH\",\"isClosed\":false,\"hasMissionOrder\":false,\"isUnderJdp\":false,\"isGeometryComputedFromControls\":false}", - ) + assertThat(missionUpdateEvent) + .contains( + "data:{\"id\":132,\"missionTypes\":[\"SEA\"],\"controlUnits\":[],\"openBy\":null,\"closedBy\":null,\"completedBy\":null,\"observationsCacem\":null,\"observationsCnsp\":null,\"facade\":\"Outre-Mer\",\"geom\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-4.54877817,48.30555988],[-4.54997332,48.30597601],[-4.54998501,48.30718823],[-4.5487929,48.30677461],[-4.54877817,48.30555988]]]]},\"startDateTimeUtc\":\"2022-01-15T04:50:09Z\",\"endDateTimeUtc\":\"2022-01-23T20:29:03Z\",\"createdAtUtc\":null,\"updatedAtUtc\":null,\"envActions\":[],\"missionSource\":\"MONITORFISH\",\"isClosed\":false,\"hasMissionOrder\":false,\"isUnderJdp\":false,\"isGeometryComputedFromControls\":false}", + ) } } diff --git a/frontend/src/features/missions/MissionsList/Columns/index.tsx b/frontend/src/features/missions/MissionsList/Columns/index.tsx index 41c53d1ca..303060a45 100644 --- a/frontend/src/features/missions/MissionsList/Columns/index.tsx +++ b/frontend/src/features/missions/MissionsList/Columns/index.tsx @@ -7,6 +7,9 @@ import { getDateCell } from '../getDateCell' import { getMissionTypeCell } from '../getMissionTypeCell' import { getNumberOfControlsCell } from '../getNumberOfControlsCell' import { getResourcesCell } from '../getResourcesCell' +import { sortCompletion, sortNumberOfControls, sortStatus } from '../utils' + +import type { Row } from '@tanstack/react-table' export const Columns = [ { @@ -73,32 +76,32 @@ export const Columns = [ { accessorFn: row => row.envActions, cell: info => getNumberOfControlsCell(info.getValue()), - enableSorting: false, header: () => 'Ctr.', - id: 'controls', + id: 'envActions', maxSize: 41, minSize: 41, - size: 41 // +24(padding) + 1(border) = 66 + size: 41, // +24(padding) + 1(border) = 66 + sortingFn: (rowA: Row, rowB: Row, columnId: string) => sortNumberOfControls(rowA, rowB, columnId) }, { accessorFn: row => row, cell: ({ row }) => , - enableSorting: false, header: () => 'Statut', id: 'status', maxSize: 82, minSize: 82, - size: 82 // +24(padding) + 1(border) = 107 + size: 82, // +24(padding) + 1(border) = 107 + sortingFn: (rowA: Row, rowB: Row) => sortStatus(rowA, rowB) }, { accessorFn: row => row, cell: ({ row }) => , - enableSorting: false, header: () => 'État données', id: 'completion', maxSize: 102, minSize: 102, - size: 102 // +24(padding) + 1(border) = 127 + size: 102, // +24(padding) + 1(border) = 127 + sortingFn: (rowA: Row, rowB: Row) => sortCompletion(rowA, rowB) }, { accessorFn: row => row.geom, From 53849e6ccbd90dd4f07720f3e2dcd4f44f862922 Mon Sep 17 00:00:00 2001 From: Claire Dagan Date: Mon, 22 Apr 2024 10:04:22 +0200 Subject: [PATCH 7/9] [Mission] add banner when the mission is created and when the mission is ended and has missing fields --- .../V666.10__insert_dummy_env_actions.sql | 9 ++-- .../mission_form/main_form.spec.ts | 11 ++++- frontend/package-lock.json | 8 ++-- frontend/package.json | 2 +- .../domain/use_cases/missions/saveMission.ts | 9 +++- .../missions/MissionForm/MissionForm.tsx | 43 ++++++++++++++++--- .../MissionForm/MissionFormBottomBar.tsx | 2 +- .../features/missions/MissionForm/index.tsx | 24 +++++++++++ .../features/missions/MissionForm/slice.ts | 13 +++++- 9 files changed, 102 insertions(+), 19 deletions(-) diff --git a/backend/src/main/resources/db/testdata/V666.10__insert_dummy_env_actions.sql b/backend/src/main/resources/db/testdata/V666.10__insert_dummy_env_actions.sql index 362ef2b89..b1c669ed6 100644 --- a/backend/src/main/resources/db/testdata/V666.10__insert_dummy_env_actions.sql +++ b/backend/src/main/resources/db/testdata/V666.10__insert_dummy_env_actions.sql @@ -11,7 +11,8 @@ INSERT INTO public.env_actions (id, mission_id, action_type, value, action_start ('6d4b7d0a-79ce-47cf-ac26-2024d2b27f28', 49, 'CONTROL' , '{"themes": [{"theme": "AMP sans réglementation particulière", "subThemes": ["Contrôle dans une AMP sans réglementation particulière"], "protectedSpecies": []}], "infractions": [{"id": "e56648c1-6ca3-4d5e-a5d2-114aa7c17126", "natinf": ["10231", "10228"], "toProcess": true, "vesselSize": 11, "vesselType": null, "companyName": null, "formalNotice": "PENDING", "observations": "RAS", "relevantCourt": "PRE", "infractionType": "WAITING", "registrationNumber": null, "controlledPersonIdentity": "M DURAND"}], "vehicleType": null, "actionTargetType": "INDIVIDUAL", "actionNumberOfControls": 1}', NULL, '0104000020E61000000100000001010000003B0DADC6D4BB01C0A8387A2964714740', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'ABC', null), ('c52c6f20-e495-4b29-b3df-d7edfb67fdd7', 34, 'SURVEILLANCE', '{"themes": [{"theme": "Police des espèces protégées et de leurs habitats (faune et flore)", "subThemes": ["Destruction, capture, arrachage", "Atteinte aux habitats d''espèces protégées"], "protectedSpecies": ["FLORA", "BIRDS"]}, {"theme": "Police des mouillages", "subThemes": ["Mouillage individuel", "ZMEL"], "protectedSpecies": []}], "duration": 0.0, "observations": "RAS", "protectedSpecies": []}', '2022-07-16 10:03:12.588693', '0106000020E61000000100000001030000000100000009000000AD0812BCE168E4BFCCDEEA3227BD4840BE63AEABD812E4BF1C5E8873F8AC484044BD156CA117DABF84C0E2AF49AC48408E16A14DE463CCBFBC9F7168A2A5484008BF4C12D0F97B3F9494F5EA3CAB4840399BF9438A28B43FDC4BF050D9BB48404BAA02B73C2CCCBF24A79C8362CD4840BC46F7A9D24DE1BFA0238D36B2D04840AD0812BCE168E4BFCCDEEA3227BD4840', 'MEMN', NULL, '2022-07-16 12:03:12.588693', NULL, NULL, NULL, NULL, 'ABC', 'DEF'), ('b8007c8a-5135-4bc3-816f-c69c7b75d807', 34, 'CONTROL' , '{"themes": [{"theme": "Police des mouillages", "subThemes": ["Mouillage individuel", "ZMEL"], "protectedSpecies": []}], "observations": "RAS", "infractions": [{"id": "5d5b7829-68cd-4436-8c0b-1cc8db7788a0", "natinf": ["10038","10231"], "toProcess": false, "vesselSize": 45, "vesselType": "COMMERCIAL", "companyName": null, "formalNotice": "PENDING", "observations": "Pas d''observations", "relevantCourt": "LOCAL_COURT", "infractionType": "WITH_REPORT", "registrationNumber": "BALTIK", "controlledPersonIdentity": "John Doe"}], "vehicleType": "VESSEL", "actionTargetType": "VEHICLE", "actionNumberOfControls": 1}', '2022-07-16 09:01:12.588693', '0104000020E610000001000000010100000047A07E6651E3DEBF044620AB65C54840', NULL, NULL, '2022-07-16 12:03:12.588693', NULL, NULL, NULL, NULL, 'ABC', NULL), -('4d9a3139-6c60-49a5-b443-0e6238a6a120', 41, 'CONTROL' , '{"themes": [{"theme": "Police des mouillages", "subThemes": ["Contrôle administratif"], "protectedSpecies": []}], "infractions": [], "vehicleType": null, "observations": "", "actionTargetType": null, "actionNumberOfControls": null}','2022-07-01 02:44:16.588693', NULL, NULL, NULL, NULL, TRUE, TRUE, TRUE, TRUE, 'ABC', 'DEF') +('4d9a3139-6c60-49a5-b443-0e6238a6a120', 41, 'CONTROL' , '{"themes": [{"theme": "Police des mouillages", "subThemes": ["Contrôle administratif"], "protectedSpecies": []}], "infractions": [], "vehicleType": null, "observations": "", "actionTargetType": null, "actionNumberOfControls": null}',NULL, NULL, NULL, NULL, NULL, TRUE, TRUE, TRUE, TRUE, 'ABC', 'DEF'), +('5865b619-3280-4c67-94ca-9f15da7d5aa7', 27, 'CONTROL' , '{"infractions": [], "vehicleType": "VESSEL", "observations": "", "actionTargetType": "VEHICLE", "actionNumberOfControls": 1}','2022-07-01 02:44:16.588693', NULL, NULL, NULL, NULL, FALSE, FALSE, FALSE, FALSE, 'ABC', 'EFG') ; @@ -35,7 +36,8 @@ INSERT INTO public.env_actions_control_plan_themes (env_action_id, theme_id) VAL ('f3e90d3a-6ba4-4bb3-805e-d391508aa46d', 15), ('e2257638-ddef-4611-960c-7675a3254c38', 9), ('4d9a3139-6c60-49a5-b443-0e6238a6a120', 12), -('6d4b7d0a-79ce-47cf-ac26-2024d2b27f28', 1) +('6d4b7d0a-79ce-47cf-ac26-2024d2b27f28', 1), +('5865b619-3280-4c67-94ca-9f15da7d5aa7', 3) ; INSERT INTO public.env_actions_control_plan_sub_themes(env_action_id, subtheme_id) VALUES @@ -49,7 +51,8 @@ INSERT INTO public.env_actions_control_plan_sub_themes(env_action_id, subtheme_i ('c52c6f20-e495-4b29-b3df-d7edfb67fdd7', 117), ('c52c6f20-e495-4b29-b3df-d7edfb67fdd7', 118), ('b8007c8a-5135-4bc3-816f-c69c7b75d807', 102), -('4d9a3139-6c60-49a5-b443-0e6238a6a120', 42) +('4d9a3139-6c60-49a5-b443-0e6238a6a120', 42), +('5865b619-3280-4c67-94ca-9f15da7d5aa7', 5) ; INSERT INTO public.env_actions_control_plan_tags(env_action_id, tag_id) VALUES diff --git a/frontend/cypress/e2e/side_window/mission_form/main_form.spec.ts b/frontend/cypress/e2e/side_window/mission_form/main_form.spec.ts index 61ca9e866..27dff705c 100644 --- a/frontend/cypress/e2e/side_window/mission_form/main_form.spec.ts +++ b/frontend/cypress/e2e/side_window/mission_form/main_form.spec.ts @@ -27,7 +27,7 @@ context('Side Window > Mission Form > Main Form', () => { cy.getDataCy('mission-status-tag-pending').should('exist') cy.getDataCy('completion-mission-status-tag-to-completed').should('exist') - cy.get('div').contains('Mission non enregistrée.') + cy.get('div').contains('Mission non créée.') cy.get('.Element-Tag').contains('Enregistrement auto. actif') // When cy.fill('Date de début (UTC)', [2024, 5, 26, 12, 0]) @@ -71,6 +71,7 @@ context('Side Window > Mission Form > Main Form', () => { .should('eq', 200) cy.get('div').contains('Mission créée par le') cy.get('div').contains('Dernière modification enregistrée') + cy.get('.Component-Banner').contains('La mission a bien été créée') }) it('A mission should be created When auto-save is not enabled', () => { @@ -484,4 +485,12 @@ context('Side Window > Mission Form > Main Form', () => { cy.getDataCy('add-control-unit').find('.rs-picker-toggle-value').contains('Unité archivée') cy.getDataCy('add-control-administration').find('.rs-picker-toggle-value').contains('Administration Archivée 2') }) + + it('Should display missing fields banner if mission is ended and has missing fields', () => { + visitSideWindow() + cy.fill('Période', 'Un mois') + cy.wait(500) + cy.getDataCy('edit-mission-27').scrollIntoView().click({ force: true }) + cy.get('.Component-Banner').contains('euillez compléter ou corriger les éléments en rouge') + }) }) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index dddc68bda..0f6ee7d7d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "license": "AGPL-3.0", "dependencies": { - "@mtes-mct/monitor-ui": "14.0.0", + "@mtes-mct/monitor-ui": "14.3.0", "@reduxjs/toolkit": "1.9.7", "@rsuite/responsive-nav": "5.0.1", "@sentry/browser": "7.73.0", @@ -3666,9 +3666,9 @@ "integrity": "sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA==" }, "node_modules/@mtes-mct/monitor-ui": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@mtes-mct/monitor-ui/-/monitor-ui-14.0.0.tgz", - "integrity": "sha512-jPasjFYgi+A4vmjG53jfCHGYY/lXA7B3/Ul4L2fDm7cRVdJhGOmeszlCxIBdBDcETou0W2N1GVFD3Kj4IKo8/w==", + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@mtes-mct/monitor-ui/-/monitor-ui-14.3.0.tgz", + "integrity": "sha512-UODW1oq6tRn6IV7Mj1mGouimoR53FFTZuzn5lRDwLDPjMLOIzS15VTCAUU33gbImXQzNNs1jkTsAymX+iZuc8A==", "dependencies": { "@babel/runtime": "7.24.4", "@tanstack/react-table": "8.9.7", diff --git a/frontend/package.json b/frontend/package.json index a0ac1cf77..d155a7b9d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,7 +26,7 @@ "test:unit:watch": "npm run test:unit -- --watch" }, "dependencies": { - "@mtes-mct/monitor-ui": "14.0.0", + "@mtes-mct/monitor-ui": "14.3.0", "@reduxjs/toolkit": "1.9.7", "@rsuite/responsive-nav": "5.0.1", "@sentry/browser": "7.73.0", diff --git a/frontend/src/domain/use_cases/missions/saveMission.ts b/frontend/src/domain/use_cases/missions/saveMission.ts index de04d272f..10af05877 100644 --- a/frontend/src/domain/use_cases/missions/saveMission.ts +++ b/frontend/src/domain/use_cases/missions/saveMission.ts @@ -52,13 +52,20 @@ export const saveMission = await dispatch(missionActions.setSelectedMissionIdOnMap(missionUpdated.id)) const nextPath = generatePath(sideWindowPaths.MISSION, { id: missionUpdated.id }) await dispatch(sideWindowActions.setCurrentPath(nextPath)) + + // wait for the mission to be updated in the form before displaying the banner + setTimeout(async () => { + await dispatch(missionFormsActions.setShowCreatedBanner({ id: missionUpdated.id, showBanner: true })) + }, 250) } else { // for a mission already created we want to update the `updatedAt` value with the new one const mission = selectedMissions[values.id] dispatch( missionFormsActions.setMission({ ...mission, - isFormDirty: false, // since mission has just been saved, it's not dirty anymore + displayCreatedMissionBanner: false, + isFormDirty: false, + // since mission has just been saved, it's not dirty anymore missionForm: missionUpdated }) ) diff --git a/frontend/src/features/missions/MissionForm/MissionForm.tsx b/frontend/src/features/missions/MissionForm/MissionForm.tsx index 74f8b9d19..a75a0f57d 100644 --- a/frontend/src/features/missions/MissionForm/MissionForm.tsx +++ b/frontend/src/features/missions/MissionForm/MissionForm.tsx @@ -1,4 +1,4 @@ -import { customDayjs, FormikEffect, usePrevious } from '@mtes-mct/monitor-ui' +import { Banner, customDayjs, FormikEffect, Icon, Level, THEME, usePrevious } from '@mtes-mct/monitor-ui' import { useMissionEventContext } from 'context/useMissionEventContext' import { useFormikContext } from 'formik' import { isEmpty } from 'lodash' @@ -22,7 +22,12 @@ import { MissionFormBottomBar } from './MissionFormBottomBar' import { missionFormsActions } from './slice' import { isMissionAutoSaveEnabled, validateBeforeOnChange } from './utils' import { missionsAPI } from '../../../api/missionsAPI' -import { type Mission, MissionSourceEnum, type NewMission } from '../../../domain/entities/missions' +import { + FrontCompletionStatus, + type Mission, + MissionSourceEnum, + type NewMission +} from '../../../domain/entities/missions' import { sideWindowPaths } from '../../../domain/entities/sideWindow' import { setToast } from '../../../domain/shared_slices/Global' import { deleteMissionAndGoToMissionsList } from '../../../domain/use_cases/missions/deleteMission' @@ -30,6 +35,7 @@ import { saveMission } from '../../../domain/use_cases/missions/saveMission' import { useAppDispatch } from '../../../hooks/useAppDispatch' import { useAppSelector } from '../../../hooks/useAppSelector' import { sideWindowActions } from '../../SideWindow/slice' +import { getIsMissionEnded } from '../utils' import type { ControlUnit } from '../../../domain/entities/controlUnit' import type { AtLeast } from '../../../types' @@ -58,10 +64,12 @@ export function MissionForm({ setShouldValidateOnChange }: MissionFormProps) { const dispatch = useAppDispatch() + const sideWindow = useAppSelector(state => state.sideWindow) const attachedReportingIds = useAppSelector(state => state.attachReportingToMission.attachedReportingIds) const attachedReportings = useAppSelector(state => state.attachReportingToMission.attachedReportings) const selectedMissions = useAppSelector(state => state.missionForms.missions) + const { getMissionEventById } = useMissionEventContext() const missionEvent = getMissionEventById(id) @@ -79,12 +87,10 @@ export function MissionForm({ if (!isMissionAutoSaveEnabled()) { return false } + + const isMissionEnded = getIsMissionEnded(selectedMission?.endDateTimeUtc) const now = customDayjs() - if ( - selectedMission?.endDateTimeUtc && - now.isAfter(selectedMission?.endDateTimeUtc) && - customDayjs(selectedMission?.endDateTimeUtc) < customDayjs(selectedMission?.endDateTimeUtc).add(2, 'days') - ) { + if (isMissionEnded && selectedMission && now.subtract(48, 'hours').isAfter(selectedMission.endDateTimeUtc)) { return false } @@ -222,6 +228,23 @@ export function MissionForm({ return ( + {missionCompletionFrontStatus === FrontCompletionStatus.TO_COMPLETE_MISSION_ENDED && ( + + + + Veuillez compléter ou corriger les éléments en rouge + + + )} + validateBeforeOnChange( @@ -279,6 +302,12 @@ const StyledFormContainer = styled.div` flex-direction: column; flex: 1; ` +const MissionEndedText = styled.div` + align-items: center; + display: flex; + gap: 8px; + justify-content: center; +` const Wrapper = styled.div` height: calc(100vh - 116px); diff --git a/frontend/src/features/missions/MissionForm/MissionFormBottomBar.tsx b/frontend/src/features/missions/MissionForm/MissionFormBottomBar.tsx index c88e9c8bf..476da4f03 100644 --- a/frontend/src/features/missions/MissionForm/MissionFormBottomBar.tsx +++ b/frontend/src/features/missions/MissionForm/MissionFormBottomBar.tsx @@ -53,7 +53,7 @@ export function MissionFormBottomBar({ - {!values?.createdAtUtc && <>Mission non enregistrée.} + {!values?.createdAtUtc && <>Mission non créée.} {values?.createdAtUtc && ( <> Mission créée par le {missionSourceEnum[values?.missionSource]?.label} le{' '} diff --git a/frontend/src/features/missions/MissionForm/index.tsx b/frontend/src/features/missions/MissionForm/index.tsx index 17fad1958..408ec6a46 100644 --- a/frontend/src/features/missions/MissionForm/index.tsx +++ b/frontend/src/features/missions/MissionForm/index.tsx @@ -1,3 +1,4 @@ +import { Banner, Icon, Level, THEME } from '@mtes-mct/monitor-ui' import { Form, Formik } from 'formik' import { noop } from 'lodash' import { useMemo, useState } from 'react' @@ -44,6 +45,22 @@ export function MissionFormWrapper() { return ( + {selectedMission?.displayCreatedMissionBanner && ( + + + + La mission a bien été créée + + + )} + , 'id'> | Partial @@ -57,7 +58,10 @@ const missionFormsSlice = createSlice({ const missionWithPreviousId = state.missions[previousId] if (missionWithPreviousId) { delete state.missions[previousId] - state.missions = { ...state.missions, [createdMissionId]: action.payload.createdMission } + state.missions = { + ...state.missions, + [createdMissionId]: action.payload.createdMission + } } state.activeMissionId = createdMissionId }, @@ -88,6 +92,13 @@ const missionFormsSlice = createSlice({ } state.activeMissionId = id }, + setShowCreatedBanner(state, action: PayloadAction<{ id: number; showBanner: boolean }>) { + const { id, showBanner } = action.payload + const mission = state.missions[id] + if (mission) { + mission.displayCreatedMissionBanner = showBanner + } + }, updateUnactiveMission(state, action: PayloadAction, 'id'>>) { const { id } = action.payload From 88c72dc9c3d4c06bc297b93032c32442455298ee Mon Sep 17 00:00:00 2001 From: Claire Dagan Date: Mon, 22 Apr 2024 18:26:26 +0200 Subject: [PATCH 8/9] [Tech] upgrade monitor-ui --- frontend/package-lock.json | 8 ++++---- frontend/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0f6ee7d7d..310464b59 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "license": "AGPL-3.0", "dependencies": { - "@mtes-mct/monitor-ui": "14.3.0", + "@mtes-mct/monitor-ui": "14.3.1", "@reduxjs/toolkit": "1.9.7", "@rsuite/responsive-nav": "5.0.1", "@sentry/browser": "7.73.0", @@ -3666,9 +3666,9 @@ "integrity": "sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA==" }, "node_modules/@mtes-mct/monitor-ui": { - "version": "14.3.0", - "resolved": "https://registry.npmjs.org/@mtes-mct/monitor-ui/-/monitor-ui-14.3.0.tgz", - "integrity": "sha512-UODW1oq6tRn6IV7Mj1mGouimoR53FFTZuzn5lRDwLDPjMLOIzS15VTCAUU33gbImXQzNNs1jkTsAymX+iZuc8A==", + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@mtes-mct/monitor-ui/-/monitor-ui-14.3.1.tgz", + "integrity": "sha512-B3/32REynPCvhfSaBSmiOAiHR7leNt57Ns62ooPQ3PrJRCn82LLYGrwo1UwazKHDjyrPpHzzpWqcP5EH+5DcJQ==", "dependencies": { "@babel/runtime": "7.24.4", "@tanstack/react-table": "8.9.7", diff --git a/frontend/package.json b/frontend/package.json index d155a7b9d..3a76d6f37 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,7 +26,7 @@ "test:unit:watch": "npm run test:unit -- --watch" }, "dependencies": { - "@mtes-mct/monitor-ui": "14.3.0", + "@mtes-mct/monitor-ui": "14.3.1", "@reduxjs/toolkit": "1.9.7", "@rsuite/responsive-nav": "5.0.1", "@sentry/browser": "7.73.0", From 2b60815b053d106a2e94f3fe118865ef3e18a471 Mon Sep 17 00:00:00 2001 From: Claire Dagan Date: Tue, 23 Apr 2024 10:02:34 +0200 Subject: [PATCH 9/9] [Tech] changes after code review --- frontend/src/context/useMissionEventContext.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/context/useMissionEventContext.ts b/frontend/src/context/useMissionEventContext.ts index bbe128791..3ed46ae3e 100644 --- a/frontend/src/context/useMissionEventContext.ts +++ b/frontend/src/context/useMissionEventContext.ts @@ -3,10 +3,10 @@ import { useContext } from 'react' import { MissionEventContext } from './MissionEventContext' export const useMissionEventContext = () => { - const onboardingContext = useContext(MissionEventContext) - if (onboardingContext === undefined) { - throw new Error('useOnboardingContext must be inside a MissionEventContext') + const missionEventContext = useContext(MissionEventContext) + if (missionEventContext === undefined) { + throw new Error('useMissionEventContext must be inside a MissionEventContext') } - return onboardingContext + return missionEventContext }