From 29e34d26d1a61d23e3f58c7f9b3b345d2f6c24b5 Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Thu, 13 Jun 2024 18:27:12 +0200 Subject: [PATCH 1/8] [feat] keep form open when switching tab --- .../domain/use_cases/missions/switchTab.ts | 2 +- .../SideWindow/SideWindowLauncher.tsx | 6 +-- frontend/src/features/SideWindow/index.tsx | 12 +++--- .../ActionForm/ControlForm/index.tsx | 23 +++++------ .../MissionForm/ActionForm/NoteForm.tsx | 8 ++-- .../ActionForm/ReportingForm/index.tsx | 4 +- .../ActionForm/SurveillanceForm/index.tsx | 17 ++++---- .../missions/MissionForm/ActionForm/index.tsx | 36 +++++++---------- .../ActionCard/EnvActions/ReportingCard.tsx | 6 +-- .../ActionCard/EnvActions/index.tsx | 6 +-- .../ActionsTimeLine/ActionCard/index.tsx | 6 +-- .../MissionForm/ActionsTimeLine/index.tsx | 36 ++++++++--------- .../missions/MissionForm/MissionForm.tsx | 21 +++++----- .../hooks/useSyncFormValuesWithRedux.ts | 13 ++++-- .../features/missions/MissionForm/index.tsx | 6 +++ .../features/missions/MissionForm/slice.ts | 10 +++++ .../missions/MissionsList/MissionsTable.tsx | 2 +- .../src/features/missions/MissionsNavBar.tsx | 40 ++++++++----------- 18 files changed, 128 insertions(+), 126 deletions(-) diff --git a/frontend/src/domain/use_cases/missions/switchTab.ts b/frontend/src/domain/use_cases/missions/switchTab.ts index e0cd45452..95a00276d 100644 --- a/frontend/src/domain/use_cases/missions/switchTab.ts +++ b/frontend/src/domain/use_cases/missions/switchTab.ts @@ -5,7 +5,7 @@ import { sideWindowActions } from '../../../features/SideWindow/slice' import { getIdTyped } from '../../../utils/getIdTyped' import { getMissionPageRoute } from '../../../utils/routes' -export const switchTab = path => async (dispatch, getState) => { +export const switchTab = (path: string) => async (dispatch, getState) => { const { missions } = getState().missionForms const routeParams = getMissionPageRoute(path) diff --git a/frontend/src/features/SideWindow/SideWindowLauncher.tsx b/frontend/src/features/SideWindow/SideWindowLauncher.tsx index 2cc20ecd3..82c728b0a 100644 --- a/frontend/src/features/SideWindow/SideWindowLauncher.tsx +++ b/frontend/src/features/SideWindow/SideWindowLauncher.tsx @@ -28,7 +28,7 @@ export function SideWindowLauncher() { }, [forceUpdate]) const onChangeFocus = useCallback( - isFocused => { + (isFocused: boolean) => { const nextStatus = isFocused ? SideWindowStatus.VISIBLE : SideWindowStatus.HIDDEN dispatch(sideWindowActions.onChangeStatus(nextStatus)) }, @@ -36,12 +36,12 @@ export function SideWindowLauncher() { ) const hasAtLeastOneMissionFormDirty = useMemo( - () => !!Object.values(missions).find(mission => mission.isFormDirty), + () => Object.values(missions).some(mission => mission.isFormDirty), [missions] ) const hasAtLeastOneReportingFormDirty = useMemo( - () => !!reportingsOpenOnSideWindow.find(reporting => reporting.isFormDirty), + () => reportingsOpenOnSideWindow.some(reporting => reporting.isFormDirty), [reportingsOpenOnSideWindow] ) diff --git a/frontend/src/features/SideWindow/index.tsx b/frontend/src/features/SideWindow/index.tsx index a096cc23b..11c3372be 100644 --- a/frontend/src/features/SideWindow/index.tsx +++ b/frontend/src/features/SideWindow/index.tsx @@ -31,7 +31,7 @@ export function SideWindow() { const dispatch = useAppDispatch() const wrapperRef = useRef(null) const currentPath = useAppSelector(state => state.sideWindow.currentPath) - const [isFirstRender, setIsFirstRender] = useState(true) + const [isMounted, setIsMounted] = useState(false) const missionEvent = useListenMissionEventUpdates() const reportingEvent = useListenReportingEventUpdates() @@ -60,7 +60,7 @@ export function SideWindow() { dispatch(reportingActions.updateUnactiveReporting(omit(reportingEvent, REPORTING_EVENT_UNSYNCHRONIZED_PROPERTIES))) }, [dispatch, reportingEvent]) - const navigate = nextPath => { + const navigate = (nextPath: string) => { if (!nextPath) { return } @@ -79,11 +79,11 @@ export function SideWindow() { : { current: window.document.createElement('div') } }), // eslint-disable-next-line react-hooks/exhaustive-deps - [isFirstRender] + [isMounted] ) useEffect(() => { - setIsFirstRender(false) + setIsMounted(true) }, []) return ( @@ -98,13 +98,13 @@ export function SideWindow() { Icon={Icon.MissionAction} isActive={isMissionButtonIsActive} onClick={() => navigate(generatePath(sideWindowPaths.MISSIONS))} - title="missions" + title="Missions et contrôles" /> navigate(generatePath(sideWindowPaths.REPORTINGS))} - title="signalements" + title="Signalements" /> diff --git a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/index.tsx b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/index.tsx index ca286d93b..2f4331704 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/index.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/index.tsx @@ -56,13 +56,13 @@ import { import { ActionTheme } from '../Themes/ActionTheme' export function ControlForm({ - currentActionIndex, + currentActionId, removeControlAction, - setCurrentActionIndex + setCurrentActionId }: { - currentActionIndex: string + currentActionId: string removeControlAction: () => void - setCurrentActionIndex: (string) => void + setCurrentActionId: (string) => void }) { const { newWindowContainerRef } = useNewWindow() const { @@ -74,7 +74,7 @@ export function ControlForm({ const { actionsMissingFields } = useMissionAndActionsCompletion() - const envActionIndex = envActions.findIndex(envAction => envAction.id === String(currentActionIndex)) + const envActionIndex = envActions.findIndex(envAction => envAction.id === String(currentActionId)) const currentAction = envActions[envActionIndex] const actionDate = envActions[envActionIndex]?.actionStartDateTimeUtc ?? (startDateTimeUtc || new Date().toISOString()) @@ -170,7 +170,7 @@ export function ControlForm({ } const handleRemoveAction = () => { - setCurrentActionIndex(undefined) + setCurrentActionId(undefined) removeControlAction() } @@ -286,16 +286,16 @@ export function ControlForm({ } useEffect(() => { - if (actionsMissingFields[currentActionIndex] === 0 && currentAction?.completion === CompletionStatus.TO_COMPLETE) { + if (actionsMissingFields[currentActionId] === 0 && currentAction?.completion === CompletionStatus.TO_COMPLETE) { setFieldValue(`envActions[${envActionIndex}].completion`, CompletionStatus.COMPLETED) return } - if (actionsMissingFields[currentActionIndex] > 0 && currentAction?.completion === CompletionStatus.COMPLETED) { + if (actionsMissingFields[currentActionId] > 0 && currentAction?.completion === CompletionStatus.COMPLETED) { setFieldValue(`envActions[${envActionIndex}].completion`, CompletionStatus.TO_COMPLETE) } - }, [actionsMissingFields, setFieldValue, currentActionIndex, currentAction?.completion, envActionIndex]) + }, [actionsMissingFields, setFieldValue, currentActionId, currentAction?.completion, envActionIndex]) return ( <> @@ -322,10 +322,7 @@ export function ControlForm({ - +
('envActions') const { setFieldValue, values: { envActions } } = useFormikContext>() - const envActionIndex = actionsFields.value.findIndex(envAction => envAction.id === String(currentActionIndex)) + const envActionIndex = actionsFields.value.findIndex(envAction => envAction.id === String(currentActionId)) const currentAction = envActions[envActionIndex] const handleRemoveAction = () => { - setCurrentActionIndex(undefined) - remove(currentActionIndex) + setCurrentActionId(undefined) + remove(currentActionId) } const duplicateNote = useCallback(() => { diff --git a/frontend/src/features/missions/MissionForm/ActionForm/ReportingForm/index.tsx b/frontend/src/features/missions/MissionForm/ActionForm/ReportingForm/index.tsx index 0132e0642..aa47c523a 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/ReportingForm/index.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/ReportingForm/index.tsx @@ -24,10 +24,10 @@ const EMPTY_VALUE = '--' export function ReportingForm({ reportingActionIndex, - setCurrentActionIndex + setCurrentActionId: setCurrentActionIndex }: { reportingActionIndex: number - setCurrentActionIndex: (string) => void + setCurrentActionId: (string) => void }) { const dispatch = useAppDispatch() const { subThemes, themes } = useGetControlPlans() diff --git a/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm/index.tsx b/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm/index.tsx index d4e0d2316..bc89683de 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm/index.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm/index.tsx @@ -50,7 +50,7 @@ import { } from '../style' import { SurveillanceThemes } from '../Themes/SurveillanceThemes' -export function SurveillanceForm({ currentActionIndex, remove, setCurrentActionIndex }) { +export function SurveillanceForm({ currentActionId, remove, setCurrentActionId }) { const { newWindowContainerRef } = useNewWindow() const { @@ -62,7 +62,7 @@ export function SurveillanceForm({ currentActionIndex, remove, setCurrentActionI const { actionsMissingFields } = useMissionAndActionsCompletion() const [actionsFields] = useField('envActions') - const envActionIndex = actionsFields.value.findIndex(envAction => envAction.id === String(currentActionIndex)) + const envActionIndex = actionsFields.value.findIndex(envAction => envAction.id === String(currentActionId)) const currentAction = envActions[envActionIndex] const actionDate = envActions[envActionIndex]?.actionStartDateTimeUtc ?? startDateTimeUtc ?? new Date().toISOString() @@ -146,7 +146,7 @@ export function SurveillanceForm({ currentActionIndex, remove, setCurrentActionI ) const handleRemoveAction = () => { - setCurrentActionIndex(undefined) + setCurrentActionId(undefined) remove(envActionIndex) } @@ -173,16 +173,16 @@ export function SurveillanceForm({ currentActionIndex, remove, setCurrentActionI } useEffect(() => { - if (actionsMissingFields[currentActionIndex] === 0 && currentAction?.completion === CompletionStatus.TO_COMPLETE) { + if (actionsMissingFields[currentActionId] === 0 && currentAction?.completion === CompletionStatus.TO_COMPLETE) { setFieldValue(`envActions[${envActionIndex}].completion`, CompletionStatus.COMPLETED) return } - if (actionsMissingFields[currentActionIndex] > 0 && currentAction?.completion === CompletionStatus.COMPLETED) { + if (actionsMissingFields[currentActionId] > 0 && currentAction?.completion === CompletionStatus.COMPLETED) { setFieldValue(`envActions[${envActionIndex}].completion`, CompletionStatus.TO_COMPLETE) } - }, [actionsMissingFields, setFieldValue, currentActionIndex, currentAction?.completion, envActionIndex]) + }, [actionsMissingFields, setFieldValue, currentActionId, currentAction?.completion, envActionIndex]) return ( <> @@ -209,10 +209,7 @@ export function SurveillanceForm({ currentActionIndex, remove, setCurrentActionI - +
void + currentActionId: string | undefined + setCurrentActionId: (actionId: string | undefined) => void } -export function ActionForm({ currentActionIndex, setCurrentActionIndex }: ActionFormProps) { +export function ActionForm({ currentActionId, setCurrentActionId }: ActionFormProps) { const [attachedReportingsField] = useField('attachedReportings') const reportingActionIndex = (attachedReportingsField.value ?? []).findIndex( - reporting => String(reporting.id) === currentActionIndex + reporting => String(reporting.id) === currentActionId ) const [reportingField] = useField(`attachedReportings.${reportingActionIndex}`) const [envActionsField, , envActionsHelper] = useField('envActions') - const envActionIndex = (envActionsField.value ?? []).findIndex(envAction => envAction.id === currentActionIndex) + const envActionIndex = (envActionsField.value ?? []).findIndex(envAction => envAction.id === currentActionId) const [actionTypeField] = useField(`envActions.${envActionIndex}.actionType`) - const [actionIdField] = useField(`envActions.${envActionIndex}.id`) const removeAction = useCallback(() => { const actionsToUpdate = [...(envActionsField.value || [])] actionsToUpdate.splice(envActionIndex, 1) envActionsHelper.setValue(actionsToUpdate) - setCurrentActionIndex(undefined) - }, [envActionIndex, envActionsField, envActionsHelper, setCurrentActionIndex]) + setCurrentActionId(undefined) + }, [envActionIndex, envActionsField, envActionsHelper, setCurrentActionId]) - if (currentActionIndex === undefined) { + if (currentActionId === undefined) { return ( Ajouter ou sélectionner une action @@ -47,7 +46,7 @@ export function ActionForm({ currentActionIndex, setCurrentActionIndex }: Action ) @@ -59,10 +58,9 @@ export function ActionForm({ currentActionIndex, setCurrentActionIndex }: Action return ( ) @@ -70,22 +68,16 @@ export function ActionForm({ currentActionIndex, setCurrentActionIndex }: Action return ( ) case ActionTypeEnum.NOTE: return ( - + ) diff --git a/frontend/src/features/missions/MissionForm/ActionsTimeLine/ActionCard/EnvActions/ReportingCard.tsx b/frontend/src/features/missions/MissionForm/ActionsTimeLine/ActionCard/EnvActions/ReportingCard.tsx index 0259ba5f2..dd2cecdf8 100644 --- a/frontend/src/features/missions/MissionForm/ActionsTimeLine/ActionCard/EnvActions/ReportingCard.tsx +++ b/frontend/src/features/missions/MissionForm/ActionsTimeLine/ActionCard/EnvActions/ReportingCard.tsx @@ -20,10 +20,10 @@ import { Accented, ControlContainer, ReportingDate, SummaryContent, SummaryConte export function ReportingCard({ action, - setCurrentActionIndex + setCurrentActionId }: { action: ReportingForTimeline - setCurrentActionIndex: (string) => void + setCurrentActionId: (actionId: string) => void }) { const { themes } = useGetControlPlans() const { setFieldValue, values } = useFormikContext>() @@ -103,7 +103,7 @@ export function ReportingCard({ }) setFieldValue('envActions', [newControl, ...(values?.envActions ?? [])]) - setCurrentActionIndex(newControl.id) + setCurrentActionId(newControl.id) const reportingToUpdateIndex = values?.attachedReportings ? values?.attachedReportings?.findIndex(reporting => Number(reporting.id) === Number(action.id)) : -1 diff --git a/frontend/src/features/missions/MissionForm/ActionsTimeLine/ActionCard/EnvActions/index.tsx b/frontend/src/features/missions/MissionForm/ActionsTimeLine/ActionCard/EnvActions/index.tsx index 6f5939a31..6816d2b1b 100644 --- a/frontend/src/features/missions/MissionForm/ActionsTimeLine/ActionCard/EnvActions/index.tsx +++ b/frontend/src/features/missions/MissionForm/ActionsTimeLine/ActionCard/EnvActions/index.tsx @@ -30,7 +30,7 @@ type EnvActionsProps = { hasError: boolean removeAction: MouseEventHandler selected: boolean - setCurrentActionIndex: (string) => void + setCurrentActionId: (id: string) => void } export function EnvActions({ @@ -39,7 +39,7 @@ export function EnvActions({ hasError, removeAction, selected, - setCurrentActionIndex + setCurrentActionId }: EnvActionsProps) { return ( <> @@ -53,7 +53,7 @@ export function EnvActions({ {action.actionType === ActionTypeEnum.SURVEILLANCE && } {action.actionType === ActionTypeEnum.NOTE && } {action.actionType === ActionTypeEnum.REPORTING && ( - + )} {action.actionType !== ActionTypeEnum.REPORTING && ( <> diff --git a/frontend/src/features/missions/MissionForm/ActionsTimeLine/ActionCard/index.tsx b/frontend/src/features/missions/MissionForm/ActionsTimeLine/ActionCard/index.tsx index eb80ed01d..71a689469 100644 --- a/frontend/src/features/missions/MissionForm/ActionsTimeLine/ActionCard/index.tsx +++ b/frontend/src/features/missions/MissionForm/ActionsTimeLine/ActionCard/index.tsx @@ -21,7 +21,7 @@ type ActionCardProps = { removeAction: MouseEventHandler selectAction: MouseEventHandler selected: boolean - setCurrentActionIndex: (string) => void + setCurrentActionId: (actionId: string) => void } export function ActionCard({ @@ -31,7 +31,7 @@ export function ActionCard({ removeAction, selectAction, selected, - setCurrentActionIndex + setCurrentActionId }: ActionCardProps) { const onClickCard = id => { if (action.actionSource !== ActionSource.MONITORENV) { @@ -54,7 +54,7 @@ export function ActionCard({ hasError={hasError} removeAction={removeAction} selected={selected} - setCurrentActionIndex={setCurrentActionIndex} + setCurrentActionId={setCurrentActionId} /> )} {action.actionSource === ActionSource.MONITORFISH && } diff --git a/frontend/src/features/missions/MissionForm/ActionsTimeLine/index.tsx b/frontend/src/features/missions/MissionForm/ActionsTimeLine/index.tsx index cb6011c06..2b35b9e29 100644 --- a/frontend/src/features/missions/MissionForm/ActionsTimeLine/index.tsx +++ b/frontend/src/features/missions/MissionForm/ActionsTimeLine/index.tsx @@ -19,7 +19,7 @@ import { FormTitle, Separator } from '../style' import type { DetachedReporting, Reporting } from '../../../../domain/entities/reporting' import type { FishMissionAction } from '../../fishActions.types' -export function ActionsTimeLine({ currentActionIndex, setCurrentActionIndex }) { +export function ActionsTimeLine({ currentActionId, setCurrentActionId }) { const actionTimelineRef = useRef(null) const actionTimelineHeight = Number(actionTimelineRef.current?.clientHeight) - 40 || undefined const { errors, setFieldValue, values } = useFormikContext>() @@ -71,11 +71,11 @@ export function ActionsTimeLine({ currentActionIndex, setCurrentActionIndex }) { }) }) setFieldValue('envActions', [newSurveillance, ...(envActions || [])]) - setCurrentActionIndex(newSurveillance.id) + setCurrentActionId(newSurveillance.id) }, [ envActions, isFirstSurveillanceAction, - setCurrentActionIndex, + setCurrentActionId, setFieldValue, values?.endDateTimeUtc, values?.startDateTimeUtc @@ -84,20 +84,20 @@ export function ActionsTimeLine({ currentActionIndex, setCurrentActionIndex }) { const handleAddControlAction = useCallback(() => { const newControl = actionFactory({ actionType: ActionTypeEnum.CONTROL }) setFieldValue('envActions', [newControl, ...(envActions || [])]) - setCurrentActionIndex(newControl.id) - }, [envActions, setCurrentActionIndex, setFieldValue]) + setCurrentActionId(newControl.id) + }, [envActions, setCurrentActionId, setFieldValue]) const handleAddNoteAction = useCallback(() => { const newNote = actionFactory({ actionType: ActionTypeEnum.NOTE }) setFieldValue('envActions', [newNote, ...(envActions || [])]) - setCurrentActionIndex(newNote.id) - }, [envActions, setCurrentActionIndex, setFieldValue]) + setCurrentActionId(newNote.id) + }, [envActions, setCurrentActionId, setFieldValue]) const handleSelectAction = useCallback( id => { - setCurrentActionIndex(actions && Object.keys(actions).find(key => key === String(id))) + setCurrentActionId(actions && Object.keys(actions).find(key => key === String(id))) }, - [actions, setCurrentActionIndex] + [actions, setCurrentActionId] ) const handleRemoveAction = useCallback( @@ -112,9 +112,9 @@ export function ActionsTimeLine({ currentActionIndex, setCurrentActionIndex }) { actionsToUpdate.splice(actionToDeleteIndex, 1) setFieldValue('envActions', actionsToUpdate) } - setCurrentActionIndex(undefined) + setCurrentActionId(undefined) }, - [envActions, setCurrentActionIndex, setFieldValue] + [envActions, setCurrentActionId, setFieldValue] ) const handleDuplicateAction = useCallback( @@ -124,11 +124,10 @@ export function ActionsTimeLine({ currentActionIndex, setCurrentActionIndex }) { if (envAction) { const duplicatedAction = actionFactory(envAction) setFieldValue('envActions', [duplicatedAction, ...(envActions || [])]) - - setCurrentActionIndex(0) + setCurrentActionId(undefined) } }, - [envActions, setFieldValue, setCurrentActionIndex] + [envActions, setFieldValue, setCurrentActionId] ) return ( @@ -161,7 +160,7 @@ export function ActionsTimeLine({ currentActionIndex, setCurrentActionIndex }) { {sortedActions ? ( <> - {sortedActions.map((action, index) => { + {sortedActions.map(action => { const envActionsIndex = envActions?.findIndex(a => a.id === action.id) const envActionsErrors = errors?.envActions && @@ -171,15 +170,14 @@ export function ActionsTimeLine({ currentActionIndex, setCurrentActionIndex }) { return ( handleDuplicateAction(action.id)} hasError={!!envActionsErrors} removeAction={() => handleRemoveAction(action.id)} selectAction={() => handleSelectAction(action.id)} - selected={String(action.id) === String(currentActionIndex)} - setCurrentActionIndex={setCurrentActionIndex} + selected={String(action.id) === String(currentActionId)} + setCurrentActionId={setCurrentActionId} /> ) })} diff --git a/frontend/src/features/missions/MissionForm/MissionForm.tsx b/frontend/src/features/missions/MissionForm/MissionForm.tsx index 3ac58afbf..ee846a3a1 100644 --- a/frontend/src/features/missions/MissionForm/MissionForm.tsx +++ b/frontend/src/features/missions/MissionForm/MissionForm.tsx @@ -55,13 +55,20 @@ enum ModalTypes { type ModalProps = ModalTypes.ACTIONS | ModalTypes.DELETE type MissionFormProps = { + activeActionId: string | undefined engagedControlUnit: ControlUnit.EngagedControlUnit | undefined id: number | string isNewMission: boolean selectedMission: AtLeast, 'id'> | Partial | undefined } -export function MissionForm({ engagedControlUnit, id, isNewMission, selectedMission }: MissionFormProps) { +export function MissionForm({ + activeActionId, + engagedControlUnit, + id, + isNewMission, + selectedMission +}: MissionFormProps) { const dispatch = useAppDispatch() const sideWindow = useAppSelector(state => state.sideWindow) @@ -97,7 +104,6 @@ export function MissionForm({ engagedControlUnit, id, isNewMission, selectedMiss useUpdateSurveillance() useUpdateOtherControlTypes() - const [currentActionIndex, setCurrentActionIndex] = useState(undefined) const [openModal, setOpenModal] = useState(undefined) const [actionsSources, setActionsSources] = useState([]) @@ -112,8 +118,8 @@ export function MissionForm({ engagedControlUnit, id, isNewMission, selectedMiss } }, [attachedReportingIds, values?.attachedReportingIds?.length, setFieldValue, attachedReportings]) - const handleSetCurrentActionIndex = index => { - setCurrentActionIndex(index) + const handleSetCurrentActionId = (actionId: string | undefined) => { + dispatch(missionFormsActions.setActiveActionId(actionId)) } const returnToEdition = () => { @@ -258,13 +264,10 @@ export function MissionForm({ engagedControlUnit, id, isNewMission, selectedMiss - + - + diff --git a/frontend/src/features/missions/MissionForm/hooks/useSyncFormValuesWithRedux.ts b/frontend/src/features/missions/MissionForm/hooks/useSyncFormValuesWithRedux.ts index a90693e2f..d69b4b8ed 100644 --- a/frontend/src/features/missions/MissionForm/hooks/useSyncFormValuesWithRedux.ts +++ b/frontend/src/features/missions/MissionForm/hooks/useSyncFormValuesWithRedux.ts @@ -1,13 +1,13 @@ +import { useAppDispatch } from '@hooks/useAppDispatch' +import { useAppSelector } from '@hooks/useAppSelector' import { useFormikContext } from 'formik' import { useEffect } from 'react' import { useDebouncedCallback } from 'use-debounce' -import { useAppDispatch } from '../../../../hooks/useAppDispatch' -import { useAppSelector } from '../../../../hooks/useAppSelector' import { missionFormsActions } from '../slice' import { getIsMissionFormValid } from '../utils' -import type { Mission } from '../../../../domain/entities/missions' +import type { Mission } from 'domain/entities/missions' export function useSyncFormValuesWithRedux(isAutoSaveEnabled: boolean) { const dispatch = useAppDispatch() @@ -19,6 +19,9 @@ export function useSyncFormValuesWithRedux(isAutoSaveEnabled: boolean) { const engagedControlUnit = useAppSelector(state => activeMissionId ? state.missionForms.missions[activeMissionId]?.engagedControlUnit : undefined ) + const activeActionId = useAppSelector(state => + activeMissionId ? state.missionForms.missions[activeMissionId]?.activeActionId : undefined + ) const dispatchFormUpdate = useDebouncedCallback(async (newValues: Mission) => { if (!newValues || newValues.id !== activeMissionId) { @@ -27,7 +30,9 @@ export function useSyncFormValuesWithRedux(isAutoSaveEnabled: boolean) { const isFormDirty = isMissionFormDirty() - dispatch(missionFormsActions.setMission({ engagedControlUnit, isFormDirty, missionForm: newValues })) + dispatch( + missionFormsActions.setMission({ activeActionId, engagedControlUnit, isFormDirty, missionForm: newValues }) + ) }, 350) /** diff --git a/frontend/src/features/missions/MissionForm/index.tsx b/frontend/src/features/missions/MissionForm/index.tsx index d9c52701a..5c6a6748f 100644 --- a/frontend/src/features/missions/MissionForm/index.tsx +++ b/frontend/src/features/missions/MissionForm/index.tsx @@ -14,6 +14,7 @@ import type { Mission as MissionType, NewMission } from '../../../domain/entitie export function MissionFormWrapper() { const activeMissionId = useAppSelector(state => state.missionForms.activeMissionId) + const selectedMission = useAppSelector(state => activeMissionId ? state.missionForms.missions[activeMissionId] : undefined ) @@ -21,6 +22,10 @@ export function MissionFormWrapper() { activeMissionId ? state.missionForms.missions[activeMissionId]?.engagedControlUnit : undefined ) + const activeActionId = useAppSelector(state => + activeMissionId ? state.missionForms.missions[activeMissionId]?.activeActionId : undefined + ) + const missionIsNewMission = useMemo(() => isNewMission(activeMissionId), [activeMissionId]) const missionValues: Partial = useMemo(() => { @@ -69,6 +74,7 @@ export function MissionFormWrapper() { >
) { + if (state.activeMissionId) { + const activeMission = state.missions[state.activeMissionId] + if (activeMission) { + activeMission.activeActionId = action.payload + } + } + }, setActiveMissionId(state, action: PayloadAction) { state.activeMissionId = action.payload }, diff --git a/frontend/src/features/missions/MissionsList/MissionsTable.tsx b/frontend/src/features/missions/MissionsList/MissionsTable.tsx index 657db2add..ac81b6432 100644 --- a/frontend/src/features/missions/MissionsList/MissionsTable.tsx +++ b/frontend/src/features/missions/MissionsList/MissionsTable.tsx @@ -93,7 +93,7 @@ export function MissionsTable({ isLoading, missions }: { isLoading: boolean; mis {row?.getVisibleCells().map(cell => ( - } - onItemRemove={removeTab} - onSelect={selectTab} - removable - > - {tabs.map((item, index) => ( - - {item.label} - - ))} - -
+ } + onItemRemove={removeTab} + onSelect={selectTab} + removable + > + {tabs.map((item, index) => ( + + {item.label} + + ))} + ) } @@ -95,6 +88,7 @@ const StyledResponsiveNav = styled(ResponsiveNav)` display: flex; box-shadow: 0px 3px 4px #7077854d; height: 48px; + width: 100%; > .rs-nav-item { width: 360px; From a7a2ed5861a9883bcfe966137139e636e345e1d1 Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Mon, 17 Jun 2024 09:20:34 +0200 Subject: [PATCH 2/8] feat: add e2e test and modifying button label --- .../mission_form/delete_mission.spec.ts | 2 +- .../mission_form/main_form.spec.ts | 34 ++++++++++++++++++ .../mission_form/mission_actions.spec.ts | 35 +++++++++++++++++++ .../mission_list/missions_navigation.spec.ts | 8 ++--- .../reporting/create_reporting.spec.ts | 4 +-- .../e2e/side_window/reporting/filters.spec.ts | 2 +- .../side_window/reporting/reportings.spec.ts | 2 +- ...nWithAttachedReportingAndAttachedAction.ts | 4 +-- .../cypress/e2e/utils/createPendingMission.ts | 2 +- .../e2e/utils/createReportingOnSideWindow.ts | 2 +- 10 files changed, 82 insertions(+), 13 deletions(-) diff --git a/frontend/cypress/e2e/side_window/mission_form/delete_mission.spec.ts b/frontend/cypress/e2e/side_window/mission_form/delete_mission.spec.ts index ca85f626e..9d06e4c6e 100644 --- a/frontend/cypress/e2e/side_window/mission_form/delete_mission.spec.ts +++ b/frontend/cypress/e2e/side_window/mission_form/delete_mission.spec.ts @@ -124,7 +124,7 @@ context('Side Window > Mission Form > Delete Mission', () => { cy.intercept('GET', '/bff/v1/reportings*').as('getReportings') - cy.clickButton('signalements') + cy.clickButton('Signalements') cy.wait('@getReportings') cy.getDataCy(`edit-reporting-${attachedReportingId}`).click({ force: true }) 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 ffbd79326..81577f753 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 @@ -229,6 +229,40 @@ context('Side Window > Mission Form > Main Form', () => { }) }) + it('An user can cancel mission creation if control unit already engaged and be redirected to filtered mission list', () => { + // Given + visitSideWindow() + cy.wait(200) + const { asDatePickerDate: expectedStartDate } = getUtcDateInMultipleFormats( + getPreviousMonthUTC(2, customDayjs().utc()) + ) + const { asDatePickerDate: expectedEndDate } = getUtcDateInMultipleFormats(todayUTC()) + + cy.fill('Période', 'Période spécifique') + cy.fill('Période spécifique', [expectedStartDate, expectedEndDate]) + const controlUnit = 'PAM Jeanne Barret' + cy.fill('Unité', [controlUnit]) + cy.fill('Statut de mission', ['En cours']) + + cy.get('.Table-SimpleTable tr').then(rows => { + cy.intercept('GET', '/api/v1/missions/engaged_control_units').as('getEngagedControlUnits') + + // When + cy.get('*[data-cy="add-mission"]').click({ force: true }) + cy.fill('Unité 1', controlUnit) + cy.wait('@getEngagedControlUnits') + + // Then + cy.get('body').contains('Une autre mission, ouverte par le CACEM, est en cours avec cette unité.') + cy.clickButton("Non, l'abandonner") + + cy.intercept('GET', '/bff/v1/missions*').as('getMissions') + + // table should have the same number of rows than before + cy.get('.Table-SimpleTable tr').should('have.length', rows.length) + }) + }) + it('An user can create mission even if control unit already engaged', () => { visitSideWindow() cy.wait(200) diff --git a/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts b/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts index 80bf22fc2..dd77834ff 100644 --- a/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts +++ b/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts @@ -544,4 +544,39 @@ context('Side Window > Mission Form > Mission actions', () => { }) }) }) + + it('Should keep pending action when switching tabs', () => { + createPendingMission().then(({ body }) => { + const mission = body + + cy.intercept('PUT', `/bff/v1/missions/${mission.id}`).as('updateMission') + + // Add a control + cy.clickButton('Ajouter') + cy.clickButton('Ajouter des contrôles') + cy.wait(500) + + cy.fill('Nb total de contrôles', 1) + cy.fill('Type de cible', 'Véhicule') + cy.fill('Type de véhicule', 'Navire') + + cy.clickButton('+ Ajouter un contrôle avec infraction') + cy.fill('MMSI', '123456789') + cy.fill('Nom du navire', 'BALTIK') + cy.fill('IMO', 'IMO123') + cy.fill('Nom du capitaine', 'John Doe') + + cy.getDataCy('mission-0').first().click({ force: true }) + cy.getDataCy('mission-1').first().click({ force: true }) + cy.clickButton('Editer') + cy.get('input[name="envActions[0].infractions[0].mmsi"]').should('have.value', '123456789') + cy.get('input[name="envActions[0].infractions[0].vesselName"]').should('have.value', 'BALTIK') + cy.get('input[name="envActions[0].infractions[0].imo"]').should('have.value', 'IMO123') + cy.get('input[name="envActions[0].infractions[0].controlledPersonIdentity"]').should('have.value', 'John Doe') + + // delete created mission + cy.clickButton('Supprimer la mission') + cy.clickButton('Confirmer la suppression') + }) + }) }) diff --git a/frontend/cypress/e2e/side_window/mission_list/missions_navigation.spec.ts b/frontend/cypress/e2e/side_window/mission_list/missions_navigation.spec.ts index 82a1c753c..1ef6354e6 100644 --- a/frontend/cypress/e2e/side_window/mission_list/missions_navigation.spec.ts +++ b/frontend/cypress/e2e/side_window/mission_list/missions_navigation.spec.ts @@ -38,8 +38,8 @@ context('Mission', () => { cy.clickButton("Retourner à l'édition") // navigate between reportings and missions - cy.clickButton('signalements') - cy.clickButton('missions') + cy.clickButton('Signalements') + cy.clickButton('Missions et contrôles') // we want to check whether the second mission has been saved correctly cy.get('[data-cy="mission-2"]').first().forceClick().wait(250) @@ -78,8 +78,8 @@ context('Mission', () => { cy.clickButton("Retourner à l'édition") // navigate between reportings and missions - cy.clickButton('signalements') - cy.clickButton('missions') + cy.clickButton('Signalements') + cy.clickButton('Missions et contrôles') // we want to check whether the second mission has been saved correctly cy.get('[data-cy="mission-2"]').first().forceClick().wait(250) diff --git a/frontend/cypress/e2e/side_window/reporting/create_reporting.spec.ts b/frontend/cypress/e2e/side_window/reporting/create_reporting.spec.ts index fca09f248..256dda079 100644 --- a/frontend/cypress/e2e/side_window/reporting/create_reporting.spec.ts +++ b/frontend/cypress/e2e/side_window/reporting/create_reporting.spec.ts @@ -14,8 +14,8 @@ context('Reportings', () => { Cypress.env('CYPRESS_REPORTING_FORM_AUTO_SAVE_ENABLED', 'true') } }) - cy.intercept('GET', '/bff/v1/reportings*').as('getReportings') - cy.clickButton('signalements') + cy.intercept('GET', '/bff/v1/rhe mission form Should be updated When a miportings*').as('getReportings') + cy.clickButton('Signalements') cy.wait('@getReportings') }) diff --git a/frontend/cypress/e2e/side_window/reporting/filters.spec.ts b/frontend/cypress/e2e/side_window/reporting/filters.spec.ts index a35a8f402..b40b52125 100644 --- a/frontend/cypress/e2e/side_window/reporting/filters.spec.ts +++ b/frontend/cypress/e2e/side_window/reporting/filters.spec.ts @@ -6,7 +6,7 @@ context('Reportings', () => { cy.viewport(1280, 1024) cy.visit(`/side_window`) cy.intercept('GET', '/bff/v1/reportings*').as('getReportings') - cy.clickButton('signalements') + cy.clickButton('Signalements') cy.wait('@getReportings') }) afterEach(() => { diff --git a/frontend/cypress/e2e/side_window/reporting/reportings.spec.ts b/frontend/cypress/e2e/side_window/reporting/reportings.spec.ts index 053f34fd8..987646f6f 100644 --- a/frontend/cypress/e2e/side_window/reporting/reportings.spec.ts +++ b/frontend/cypress/e2e/side_window/reporting/reportings.spec.ts @@ -7,7 +7,7 @@ context('Reportings', () => { } }) cy.intercept('GET', '/bff/v1/reportings*').as('getReportings') - cy.clickButton('signalements') + cy.clickButton('Signalements') cy.wait('@getReportings') }) diff --git a/frontend/cypress/e2e/utils/createMissionWithAttachedReportingAndAttachedAction.ts b/frontend/cypress/e2e/utils/createMissionWithAttachedReportingAndAttachedAction.ts index 94eda806e..e73f70c3d 100644 --- a/frontend/cypress/e2e/utils/createMissionWithAttachedReportingAndAttachedAction.ts +++ b/frontend/cypress/e2e/utils/createMissionWithAttachedReportingAndAttachedAction.ts @@ -10,7 +10,7 @@ const dispatch = action => cy.window().its('store').invoke('dispatch', action) export function createMissionWithAttachedReportingAndAttachedAction() { cy.intercept('GET', '/bff/v1/reportings*').as('getReportings') - cy.clickButton('signalements') + cy.clickButton('Signalements') cy.wait('@getReportings') cy.wait(1000) @@ -56,7 +56,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('Missions et contrôles') cy.clickButton('Ajouter une nouvelle mission') const endDate = getFutureDate(7, 'day') diff --git a/frontend/cypress/e2e/utils/createPendingMission.ts b/frontend/cypress/e2e/utils/createPendingMission.ts index 26e565d2b..def38fb8f 100644 --- a/frontend/cypress/e2e/utils/createPendingMission.ts +++ b/frontend/cypress/e2e/utils/createPendingMission.ts @@ -3,7 +3,7 @@ import { getFutureDate } from './getFutureDate' export function createPendingMission() { cy.intercept('GET', '/bff/v1/missions*').as('getMissions') cy.intercept('PUT', '/bff/v1/missions').as('createMission') - cy.clickButton('missions') + cy.clickButton('Missions et contrôles') cy.clickButton('Ajouter une nouvelle mission') const endDate = getFutureDate(7, 'day') diff --git a/frontend/cypress/e2e/utils/createReportingOnSideWindow.ts b/frontend/cypress/e2e/utils/createReportingOnSideWindow.ts index d9f967d9a..0482107f1 100644 --- a/frontend/cypress/e2e/utils/createReportingOnSideWindow.ts +++ b/frontend/cypress/e2e/utils/createReportingOnSideWindow.ts @@ -5,7 +5,7 @@ import type { GeoJSON } from 'domain/types/GeoJSON' const dispatch = action => cy.window().its('store').invoke('dispatch', action) export function createReportingOnSideWindow() { - cy.clickButton('signalements') + cy.clickButton('Signalements') cy.clickButton('Ajouter un nouveau signalement') cy.intercept('PUT', '/bff/v1/reportings').as('createReporting') cy.wait(500) From 51b023adf312b49a5bbac4cd2ba07f4c916dce00 Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Tue, 18 Jun 2024 09:05:49 +0200 Subject: [PATCH 3/8] [FIX] e2e tests --- .../mission_form/mission_actions.spec.ts | 1 + .../side_window/reporting/create_reporting.spec.ts | 2 +- .../ControlForm/InfractionForm/InfractionForm.tsx | 13 +++++++------ .../MissionForm/ActionForm/ControlForm/index.tsx | 4 ++-- .../missions/MissionForm/ActionForm/NoteForm.tsx | 2 +- .../ActionForm/SurveillanceForm/index.tsx | 2 +- .../missions/MissionForm/ActionsTimeLine/index.tsx | 2 +- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts b/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts index dd77834ff..9894239f6 100644 --- a/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts +++ b/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts @@ -565,6 +565,7 @@ context('Side Window > Mission Form > Mission actions', () => { cy.fill('Nom du navire', 'BALTIK') cy.fill('IMO', 'IMO123') cy.fill('Nom du capitaine', 'John Doe') + cy.clickOutside() cy.getDataCy('mission-0').first().click({ force: true }) cy.getDataCy('mission-1').first().click({ force: true }) diff --git a/frontend/cypress/e2e/side_window/reporting/create_reporting.spec.ts b/frontend/cypress/e2e/side_window/reporting/create_reporting.spec.ts index 256dda079..fe23f937c 100644 --- a/frontend/cypress/e2e/side_window/reporting/create_reporting.spec.ts +++ b/frontend/cypress/e2e/side_window/reporting/create_reporting.spec.ts @@ -14,7 +14,7 @@ context('Reportings', () => { Cypress.env('CYPRESS_REPORTING_FORM_AUTO_SAVE_ENABLED', 'true') } }) - cy.intercept('GET', '/bff/v1/rhe mission form Should be updated When a miportings*').as('getReportings') + cy.intercept('GET', '/bff/v1/reportings*').as('getReportings') cy.clickButton('Signalements') cy.wait('@getReportings') }) diff --git a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionForm.tsx b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionForm.tsx index 623d58ba0..dc9e44d90 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionForm.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionForm.tsx @@ -40,12 +40,13 @@ export function InfractionForm({ )} - - + {actionTargetField.value !== TargetTypeEnum.VEHICLE && ( + + )} void - setCurrentActionId: (string) => void + setCurrentActionId: (actionId: string | undefined) => void }) { const { newWindowContainerRef } = useNewWindow() const { @@ -74,7 +74,7 @@ export function ControlForm({ const { actionsMissingFields } = useMissionAndActionsCompletion() - const envActionIndex = envActions.findIndex(envAction => envAction.id === String(currentActionId)) + const envActionIndex = envActions.findIndex(envAction => envAction.id === currentActionId) const currentAction = envActions[envActionIndex] const actionDate = envActions[envActionIndex]?.actionStartDateTimeUtc ?? (startDateTimeUtc || new Date().toISOString()) diff --git a/frontend/src/features/missions/MissionForm/ActionForm/NoteForm.tsx b/frontend/src/features/missions/MissionForm/ActionForm/NoteForm.tsx index 51391092f..c62f29f50 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/NoteForm.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/NoteForm.tsx @@ -13,7 +13,7 @@ export function NoteForm({ currentActionId, remove, setCurrentActionId }) { setFieldValue, values: { envActions } } = useFormikContext>() - const envActionIndex = actionsFields.value.findIndex(envAction => envAction.id === String(currentActionId)) + const envActionIndex = actionsFields.value.findIndex(envAction => envAction.id === currentActionId) const currentAction = envActions[envActionIndex] const handleRemoveAction = () => { diff --git a/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm/index.tsx b/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm/index.tsx index bc89683de..3482a6d6c 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm/index.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm/index.tsx @@ -62,7 +62,7 @@ export function SurveillanceForm({ currentActionId, remove, setCurrentActionId } const { actionsMissingFields } = useMissionAndActionsCompletion() const [actionsFields] = useField('envActions') - const envActionIndex = actionsFields.value.findIndex(envAction => envAction.id === String(currentActionId)) + const envActionIndex = actionsFields.value.findIndex(envAction => envAction.id === currentActionId) const currentAction = envActions[envActionIndex] const actionDate = envActions[envActionIndex]?.actionStartDateTimeUtc ?? startDateTimeUtc ?? new Date().toISOString() diff --git a/frontend/src/features/missions/MissionForm/ActionsTimeLine/index.tsx b/frontend/src/features/missions/MissionForm/ActionsTimeLine/index.tsx index 2b35b9e29..29df57ad0 100644 --- a/frontend/src/features/missions/MissionForm/ActionsTimeLine/index.tsx +++ b/frontend/src/features/missions/MissionForm/ActionsTimeLine/index.tsx @@ -176,7 +176,7 @@ export function ActionsTimeLine({ currentActionId, setCurrentActionId }) { hasError={!!envActionsErrors} removeAction={() => handleRemoveAction(action.id)} selectAction={() => handleSelectAction(action.id)} - selected={String(action.id) === String(currentActionId)} + selected={String(action.id) === currentActionId} setCurrentActionId={setCurrentActionId} /> ) From a1eb84bdfbb7074124a55725aa159dd28d0be306 Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Wed, 19 Jun 2024 09:22:08 +0200 Subject: [PATCH 4/8] [feat] disable natinf validation if form is invalid --- .../InfractionForm/InfractionForm.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionForm.tsx b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionForm.tsx index dc9e44d90..c44a7590d 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionForm.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionForm.tsx @@ -1,13 +1,13 @@ import { Accent, Button, FormikCheckbox, FormikMultiRadio, FormikTextarea, FormikTextInput } from '@mtes-mct/monitor-ui' -import { useField } from 'formik' +import { formalNoticeLabels, infractionTypeLabels, type EnvActionControl, type Mission } from 'domain/entities/missions' +import { TargetTypeEnum } from 'domain/entities/targetType' +import { useField, useFormikContext, type FormikErrors } from 'formik' import styled from 'styled-components' import { InfractionFormHeaderCompany } from './InfractionFormHeaderCompany' import { InfractionFormHeaderVehicle } from './InfractionFormHeaderVehicle' import { NatinfSelector } from './NatinfSelector' import { RelevantCourtSelector } from './RelevantCourtSelector' -import { infractionTypeLabels, formalNoticeLabels } from '../../../../../../domain/entities/missions' -import { TargetTypeEnum } from '../../../../../../domain/entities/targetType' import type { MouseEventHandler } from 'react' @@ -30,6 +30,9 @@ export function InfractionForm({ const [actionTargetField] = useField(`envActions.${envActionIndex}.actionTargetType`) + const { errors } = useFormikContext() + const isInvalid = isInfractionFormInvalid(errors) + return ( {actionTargetField.value === TargetTypeEnum.VEHICLE && ( @@ -78,12 +81,19 @@ export function InfractionForm({ - ) + + function isInfractionFormInvalid(errorsForm: FormikErrors) { + const envActionErrors = (!!errorsForm.envActions && + errorsForm.envActions[envActionIndex]) as FormikErrors + + return envActionErrors && !!envActionErrors.infractions + } } const FormWrapper = styled.div` From 27b38c3352ce3b40f3f08c7c19e90802c290f53a Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Wed, 19 Jun 2024 09:24:39 +0200 Subject: [PATCH 5/8] [feat] natinf display identification information depending on targetType and information entered --- .../mission_form/main_form.spec.ts | 34 ----- .../mission_form/mission_actions.spec.ts | 77 +++++++++++ frontend/src/domain/entities/missions.ts | 6 +- .../ActionForm/ControlForm/InfractionCard.tsx | 123 +++++++++++++----- 4 files changed, 171 insertions(+), 69 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 81577f753..ffbd79326 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 @@ -229,40 +229,6 @@ context('Side Window > Mission Form > Main Form', () => { }) }) - it('An user can cancel mission creation if control unit already engaged and be redirected to filtered mission list', () => { - // Given - visitSideWindow() - cy.wait(200) - const { asDatePickerDate: expectedStartDate } = getUtcDateInMultipleFormats( - getPreviousMonthUTC(2, customDayjs().utc()) - ) - const { asDatePickerDate: expectedEndDate } = getUtcDateInMultipleFormats(todayUTC()) - - cy.fill('Période', 'Période spécifique') - cy.fill('Période spécifique', [expectedStartDate, expectedEndDate]) - const controlUnit = 'PAM Jeanne Barret' - cy.fill('Unité', [controlUnit]) - cy.fill('Statut de mission', ['En cours']) - - cy.get('.Table-SimpleTable tr').then(rows => { - cy.intercept('GET', '/api/v1/missions/engaged_control_units').as('getEngagedControlUnits') - - // When - cy.get('*[data-cy="add-mission"]').click({ force: true }) - cy.fill('Unité 1', controlUnit) - cy.wait('@getEngagedControlUnits') - - // Then - cy.get('body').contains('Une autre mission, ouverte par le CACEM, est en cours avec cette unité.') - cy.clickButton("Non, l'abandonner") - - cy.intercept('GET', '/bff/v1/missions*').as('getMissions') - - // table should have the same number of rows than before - cy.get('.Table-SimpleTable tr').should('have.length', rows.length) - }) - }) - it('An user can create mission even if control unit already engaged', () => { visitSideWindow() cy.wait(200) diff --git a/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts b/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts index 9894239f6..30bb4944b 100644 --- a/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts +++ b/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts @@ -545,6 +545,83 @@ context('Side Window > Mission Form > Mission actions', () => { }) }) + it('Should display MMSI > IMO > Immatriculation and identity > vessel type when adding an infraction', () => { + createPendingMission().then(({ body }) => { + const mission = body + + cy.intercept('PUT', `/bff/v1/missions/${mission.id}`).as('updateMission') + + // Add a control + cy.clickButton('Ajouter') + cy.clickButton('Ajouter des contrôles') + cy.wait(500) + + cy.fill('Nb total de contrôles', 1) + cy.fill('Type de cible', 'Personne morale') + cy.clickButton('+ Ajouter un contrôle avec infraction') + // Fill mandatory fields + cy.fill("Type d'infraction", 'Avec PV') + cy.fill('Mise en demeure', 'Oui') + cy.fill('NATINF', ["1508 - Execution d'un travail dissimule"]) + cy.clickButton("Valider l'infraction") + + // cases without identification + cy.getDataCy('infraction-0-identification').contains('Personne morale') + + cy.fill('Type de cible', 'Personne physique') + cy.getDataCy('infraction-0-identification').contains('Personne physique') + + cy.fill('Type de cible', 'Véhicule') + cy.getDataCy('infraction-0-identification').contains('Véhicule - Type non renseigné') + + cy.fill('Type de véhicule', 'Navire') + cy.getDataCy('infraction-0-identification').contains('Véhicule - Navire') + + // cases with identification + // Company + cy.fill('Type de cible', 'Personne morale') + cy.clickButton('Editer') + cy.fill('Identité de la personne contrôlée', 'John Doe') + cy.clickButton("Valider l'infraction") + cy.getDataCy('infraction-0-identification').contains('John Doe') + + cy.clickButton('Editer') + cy.fill('Nom de la personne morale', 'World company') + cy.clickButton("Valider l'infraction") + cy.getDataCy('infraction-0-identification').contains('World company - John Doe') + + // Individual + cy.fill('Type de cible', 'Personne physique') + cy.getDataCy('infraction-0-identification').contains('John Doe') + + // Vehicle + cy.fill('Type de cible', 'Véhicule') + cy.fill('Type de véhicule', 'Navire') + + cy.clickButton('Editer') + cy.fill('Immatriculation', 'ABC123') + cy.clickButton("Valider l'infraction") + cy.getDataCy('infraction-0-identification').contains('ABC123') + + cy.clickButton('Editer') + cy.fill('IMO', 'IMO123') + cy.clickButton("Valider l'infraction") + cy.getDataCy('infraction-0-identification').contains('IMO123') + + cy.clickButton('Editer') + cy.fill('MMSI', '123456789') + cy.clickButton("Valider l'infraction") + cy.getDataCy('infraction-0-identification').contains('123456789') + + cy.fill('Type de véhicule', 'Autre véhicule marin') + cy.getDataCy('infraction-0-identification').contains('ABC123') + + // clean + cy.clickButton('Supprimer la mission') + cy.clickButton('Confirmer la suppression') + }) + }) + it('Should keep pending action when switching tabs', () => { createPendingMission().then(({ body }) => { const mission = body diff --git a/frontend/src/domain/entities/missions.ts b/frontend/src/domain/entities/missions.ts index ee346c407..6414c2046 100644 --- a/frontend/src/domain/entities/missions.ts +++ b/frontend/src/domain/entities/missions.ts @@ -291,11 +291,7 @@ export type EnvActionSurveillance = EnvActionCommonProperties & { reportingIds: number[] } -export type ControlOrSurveillance = - | EnvActionControl - | EnvActionSurveillance - | NewEnvActionControl - | EnvActionSurveillance +export type ControlOrSurveillance = EnvActionControl | EnvActionSurveillance export type EnvActionNote = EnvActionCommonProperties & { actionType: ActionTypeEnum.NOTE diff --git a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionCard.tsx b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionCard.tsx index 6bf4fe26b..eaf54a413 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionCard.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionCard.tsx @@ -1,17 +1,16 @@ import { Accent, Button, Icon, IconButton, Tag } from '@mtes-mct/monitor-ui' -import { vesselTypeLabel, type VesselTypeEnum } from 'domain/entities/vesselType' -import { useField } from 'formik' -import styled, { css } from 'styled-components' - import { FormalNoticeEnum, InfractionTypeEnum, infractionTypeLabels, type EnvActionControl, type Infraction -} from '../../../../../domain/entities/missions' -import { TargetTypeEnum, TargetTypeLabels } from '../../../../../domain/entities/targetType' -import { vehicleTypeLabels, VehicleTypeEnum } from '../../../../../domain/entities/vehicleType' +} from 'domain/entities/missions' +import { TargetTypeEnum, TargetTypeLabels } from 'domain/entities/targetType' +import { VehicleTypeEnum, vehicleTypeLabels } from 'domain/entities/vehicleType' +import { useField } from 'formik' +import styled, { css } from 'styled-components' + import { StyledDeleteIconButton } from '../style' export function InfractionCard({ @@ -28,16 +27,94 @@ export function InfractionCard({ `envActions.${envActionIndex}.actionTargetType` ) const [vehicleTypeField] = useField(`envActions.${envActionIndex}.vehicleType`) - const [vesselType] = useField(`${infractionPath}.vesselType`) + const [mmsi] = useField(`${infractionPath}.mmsi`) + const [imo] = useField(`${infractionPath}.imo`) const [registrationNumber] = useField(`${infractionPath}.registrationNumber`) + const [companyName] = useField(`${infractionPath}.companyName`) + const [controlledPersonIdentity] = useField( `${infractionPath}.controlledPersonIdentity` ) - const [companyName] = useField(`${infractionPath}.companyName`) const [infractionType] = useField(`${infractionPath}.infractionType`) const [formalNotice] = useField(`${infractionPath}.formalNotice`) const [natinf] = useField(`${infractionPath}.natinf`) + const displayIdentification = () => { + const identification: string[] = [] + + const addDefaultVehicleIdentification = () => { + identification.push(TargetTypeLabels[targetTypeField.value]) + identification.push(vehicleTypeLabels[vehicleTypeField.value].label) + } + + const addVehicleIdentification = () => { + if (registrationNumber.value) { + identification.push(registrationNumber.value) + } else { + addDefaultVehicleIdentification() + } + } + + const addVesselIdentification = () => { + if (mmsi.value) { + identification.push(mmsi.value) + } else if (imo.value) { + identification.push(imo.value) + } else if (registrationNumber.value) { + identification.push(registrationNumber.value) + } else if (controlledPersonIdentity.value) { + identification.push(controlledPersonIdentity.value) + } else { + addDefaultVehicleIdentification() + } + } + + switch (targetTypeField.value) { + case TargetTypeEnum.VEHICLE: + switch (vehicleTypeField.value) { + case VehicleTypeEnum.OTHER_SEA: + case VehicleTypeEnum.VEHICLE_AIR: + case VehicleTypeEnum.VEHICLE_LAND: + addVehicleIdentification() + break + case VehicleTypeEnum.VESSEL: + addVesselIdentification() + break + default: + identification.push(TargetTypeLabels[targetTypeField.value]) + identification.push('Type non renseigné') + break + } + break + + case TargetTypeEnum.COMPANY: + if (companyName.value) { + identification.push(companyName.value) + } + if (controlledPersonIdentity.value) { + identification.push(controlledPersonIdentity.value) + } + if (!companyName.value && !controlledPersonIdentity.value) { + identification.push(TargetTypeLabels[targetTypeField.value]) + } + break + + case TargetTypeEnum.INDIVIDUAL: + if (controlledPersonIdentity.value) { + identification.push(controlledPersonIdentity.value) + } else { + identification.push(TargetTypeLabels[targetTypeField.value]) + } + break + + default: + identification.push('Cible non renseignée') + break + } + + return identification.join(' - ') + } + let libelleInfractionType switch (infractionType?.value) { case undefined: @@ -57,23 +134,9 @@ export function InfractionCard({ return ( - {targetTypeField.value === TargetTypeEnum.VEHICLE && ( - - {/* TODO Fix the type here: `label` is a `string` but can be undefined? */} - {vehicleTypeLabels[vehicleTypeField?.value]?.label ?? 'Non Renseigné'} - {vehicleTypeField?.value === VehicleTypeEnum.VESSEL - ? ` – ${vesselTypeLabel[vesselType?.value] ?? 'Type non défini'}` - : ''} -  –  - - )} - {targetTypeField.value === TargetTypeEnum.VEHICLE ? ( - {registrationNumber?.value ?? ' sans immatriculation'} - ) : ( - - {companyName?.value ?? controlledPersonIdentity?.value ?? TargetTypeLabels[targetTypeField.value]} - - )} + + {displayIdentification()} + {libelleInfractionType} {formalNotice?.value === FormalNoticeEnum.YES && MED} @@ -128,10 +191,10 @@ const ButtonsWrapper = styled.div` justify-content: space-between; ` -const VehicleType = styled.span` - font-weight: 500; - color: ${p => p.theme.color.gunMetal}; -` +// const VehicleType = styled.span` +// font-weight: 500; +// color: ${p => p.theme.color.gunMetal}; +// ` const Identification = styled.span` font-weight: 500; From 938be3f957a35296a9ca22ca5bfc2cff8a0bc807 Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Wed, 19 Jun 2024 12:41:59 +0200 Subject: [PATCH 6/8] [feat] natinf display identification information depending on targetType and information entered --- frontend/.env.example | 2 +- .../e2e/side_window/mission_form/mission_actions.spec.ts | 4 ++-- .../MissionForm/ActionForm/ControlForm/InfractionCard.tsx | 5 ----- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/frontend/.env.example b/frontend/.env.example index 55cec802b..56bd676f7 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -38,7 +38,7 @@ FRONTEND_SHOM_KEY= FRONTEND_MISSION_FORM_AUTO_UPDATE=true FRONTEND_MISSION_FORM_AUTO_SAVE_ENABLED=true FRONTEND_REPORTING_FORM_AUTO_SAVE_ENABLED=true -FRONTEND_REPORTING_FORM_AUTO_UPDATE=true +# FRONTEND_REPORTING_FORM_AUTO_UPDATE=true ################################################################################ # Version diff --git a/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts b/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts index 30bb4944b..24277dc6e 100644 --- a/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts +++ b/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts @@ -545,7 +545,7 @@ context('Side Window > Mission Form > Mission actions', () => { }) }) - it('Should display MMSI > IMO > Immatriculation and identity > vessel type when adding an infraction', () => { + it('Should display target type if there are no identity informations when adding an infraction', () => { createPendingMission().then(({ body }) => { const mission = body @@ -616,7 +616,7 @@ context('Side Window > Mission Form > Mission actions', () => { cy.fill('Type de véhicule', 'Autre véhicule marin') cy.getDataCy('infraction-0-identification').contains('ABC123') - // clean + // delete created mission cy.clickButton('Supprimer la mission') cy.clickButton('Confirmer la suppression') }) diff --git a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionCard.tsx b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionCard.tsx index eaf54a413..ad97b73cc 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionCard.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionCard.tsx @@ -191,11 +191,6 @@ const ButtonsWrapper = styled.div` justify-content: space-between; ` -// const VehicleType = styled.span` -// font-weight: 500; -// color: ${p => p.theme.color.gunMetal}; -// ` - const Identification = styled.span` font-weight: 500; color: ${p => p.theme.color.gunMetal}; From 21b9de919cac86f70f17962e534ec7be11123eaa Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Wed, 19 Jun 2024 15:45:10 +0200 Subject: [PATCH 7/8] [review] rollback on keys and add maxlength=3 to openby fields --- frontend/.env.example | 2 +- .../components/ReportingForm/FormContent.tsx | 2 +- .../InfractionForm/InfractionForm.tsx | 24 +++++++------------ .../InfractionFormHeaderVehicle.tsx | 18 ++++++++++---- .../ActionForm/ControlForm/index.tsx | 1 + .../ActionForm/ReportingForm/index.tsx | 2 +- .../ActionForm/SurveillanceForm/index.tsx | 1 + .../missions/MissionForm/ActionForm/index.tsx | 10 +++++++- 8 files changed, 36 insertions(+), 24 deletions(-) diff --git a/frontend/.env.example b/frontend/.env.example index 56bd676f7..55cec802b 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -38,7 +38,7 @@ FRONTEND_SHOM_KEY= FRONTEND_MISSION_FORM_AUTO_UPDATE=true FRONTEND_MISSION_FORM_AUTO_SAVE_ENABLED=true FRONTEND_REPORTING_FORM_AUTO_SAVE_ENABLED=true -# FRONTEND_REPORTING_FORM_AUTO_UPDATE=true +FRONTEND_REPORTING_FORM_AUTO_UPDATE=true ################################################################################ # Version diff --git a/frontend/src/features/Reportings/components/ReportingForm/FormContent.tsx b/frontend/src/features/Reportings/components/ReportingForm/FormContent.tsx index f64137acf..db332db78 100644 --- a/frontend/src/features/Reportings/components/ReportingForm/FormContent.tsx +++ b/frontend/src/features/Reportings/components/ReportingForm/FormContent.tsx @@ -358,7 +358,7 @@ export function FormContent({ reducedReportingsOnContext, selectedReporting }: F - + diff --git a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionForm.tsx b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionForm.tsx index c44a7590d..b2583c3a6 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionForm.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionForm.tsx @@ -1,4 +1,4 @@ -import { Accent, Button, FormikCheckbox, FormikMultiRadio, FormikTextarea, FormikTextInput } from '@mtes-mct/monitor-ui' +import { Accent, Button, FormikCheckbox, FormikMultiRadio, FormikTextarea } from '@mtes-mct/monitor-ui' import { formalNoticeLabels, infractionTypeLabels, type EnvActionControl, type Mission } from 'domain/entities/missions' import { TargetTypeEnum } from 'domain/entities/targetType' import { useField, useFormikContext, type FormikErrors } from 'formik' @@ -31,6 +31,14 @@ export function InfractionForm({ const [actionTargetField] = useField(`envActions.${envActionIndex}.actionTargetType`) const { errors } = useFormikContext() + + function isInfractionFormInvalid(errorsForm: FormikErrors) { + const envActionErrors = (!!errorsForm.envActions && + errorsForm.envActions[envActionIndex]) as FormikErrors + + return envActionErrors && !!envActionErrors.infractions + } + const isInvalid = isInfractionFormInvalid(errors) return ( @@ -43,13 +51,6 @@ export function InfractionForm({ )} - {actionTargetField.value !== TargetTypeEnum.VEHICLE && ( - - )} ) - - function isInfractionFormInvalid(errorsForm: FormikErrors) { - const envActionErrors = (!!errorsForm.envActions && - errorsForm.envActions[envActionIndex]) as FormikErrors - - return envActionErrors && !!envActionErrors.infractions - } } const FormWrapper = styled.div` diff --git a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionFormHeaderVehicle.tsx b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionFormHeaderVehicle.tsx index 4832c7913..26365b5f7 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionFormHeaderVehicle.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionFormHeaderVehicle.tsx @@ -13,11 +13,19 @@ export function InfractionFormHeaderVehicle({ envActionIndex, infractionPath }) return ( {vehicleTypeField?.value !== VehicleTypeEnum.VESSEL && ( - + <> + + + + )} {vehicleTypeField?.value === VehicleTypeEnum.VESSEL && ( diff --git a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/index.tsx b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/index.tsx index 700efb1cd..98c8e8f9d 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/index.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/index.tsx @@ -438,6 +438,7 @@ export function ControlForm({ isLight isRequired label="Ouvert par" + maxLength={3} name={`envActions[${envActionIndex}].openBy`} /> Le signalement nécessite un contrôle - +
diff --git a/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm/index.tsx b/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm/index.tsx index 3482a6d6c..583a32006 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm/index.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/SurveillanceForm/index.tsx @@ -308,6 +308,7 @@ export function SurveillanceForm({ currentActionId, remove, setCurrentActionId } isLight isRequired label="Ouvert par" + maxLength={3} name={`envActions[${envActionIndex}].openBy`} /> ('envActions') const envActionIndex = (envActionsField.value ?? []).findIndex(envAction => envAction.id === currentActionId) const [actionTypeField] = useField(`envActions.${envActionIndex}.actionType`) + const [actionIdField] = useField(`envActions.${envActionIndex}.id`) const removeAction = useCallback(() => { const actionsToUpdate = [...(envActionsField.value || [])] @@ -58,6 +59,7 @@ export function ActionForm({ currentActionId, setCurrentActionId }: ActionFormPr return ( - + ) From 8629daed63e56e6e4a065c2b3127e8bcfc6c0c28 Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Wed, 19 Jun 2024 19:31:38 +0200 Subject: [PATCH 8/8] [feat] natinf display identification information depending on targetType and information entered --- .../ControlForm/InfractionForm/InfractionForm.tsx | 5 +++++ .../InfractionForm/InfractionFormHeaderCompany.tsx | 12 +++++++++++- .../InfractionFormHeaderIndividual.tsx | 11 +++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionFormHeaderIndividual.tsx diff --git a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionForm.tsx b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionForm.tsx index b2583c3a6..33b6b86cf 100644 --- a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionForm.tsx +++ b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionForm.tsx @@ -5,6 +5,7 @@ import { useField, useFormikContext, type FormikErrors } from 'formik' import styled from 'styled-components' import { InfractionFormHeaderCompany } from './InfractionFormHeaderCompany' +import { InfractionFormHeaderIndividual } from './InfractionFormHeaderIndividual' import { InfractionFormHeaderVehicle } from './InfractionFormHeaderVehicle' import { NatinfSelector } from './NatinfSelector' import { RelevantCourtSelector } from './RelevantCourtSelector' @@ -51,6 +52,10 @@ export function InfractionForm({ )} + {actionTargetField.value === TargetTypeEnum.INDIVIDUAL && ( + + )} + + return ( + <> + + + + + ) } diff --git a/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionFormHeaderIndividual.tsx b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionFormHeaderIndividual.tsx new file mode 100644 index 000000000..82a894ad2 --- /dev/null +++ b/frontend/src/features/missions/MissionForm/ActionForm/ControlForm/InfractionForm/InfractionFormHeaderIndividual.tsx @@ -0,0 +1,11 @@ +import { FormikTextInput } from '@mtes-mct/monitor-ui' + +export function InfractionFormHeaderIndividual({ infractionPath }) { + return ( + + ) +}