Skip to content

Commit

Permalink
Merge pull request #344 from MTES-MCT/feature/335-saisie-de-control-env
Browse files Browse the repository at this point in the history
feat(control-env): maxNbrControl
  • Loading branch information
lwih authored Sep 17, 2024
2 parents 5765590 + 84faf46 commit aba94c0
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 120 deletions.
Original file line number Diff line number Diff line change
@@ -1,46 +1,55 @@
import React from 'react'
import EnvActionControlPlans from '@common/components/elements/env-action-control-plans.tsx'
import { ControlType } from '@common/types/control-types.ts'
import { actionTargetTypeLabels, EnvActionControl, vehicleTypeLabels } from '@common/types/env-mission-types.ts'
import { extractLatLonFromMultiPoint } from '@common/utils/geometry.ts'
import { CoordinatesFormat, CoordinatesInput, Icon, Label, THEME } from '@mtes-mct/monitor-ui'
import Divider from 'rsuite/Divider'
import { Coordinates } from '@mtes-mct/monitor-ui/types/definitions'
import React, { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'
import { Stack } from 'rsuite'
import Divider from 'rsuite/Divider'
import Text from '../../../../../common/components/ui/text.tsx'
import useActionById from '../../../hooks/use-action-by-id.tsx'
import useIsMissionFinished from '../../../hooks/use-is-mission-finished.tsx'
import ControlsToCompleteTag from '../../ui/controls-to-complete-tag.tsx'
import EnvControlForm from '../controls/env-control-form.tsx'
import { ControlType } from '@common/types/control-types.ts'
import {
actionTargetTypeLabels,
EnvActionControl,
FormattedControlPlan,
vehicleTypeLabels
} from '@common/types/env-mission-types.ts'
import { useParams } from 'react-router-dom'
import EnvInfractionAddNewTarget from '../infractions/env-infraction-add-new-target.tsx'
import EnvInfractionExistingTargets from '../infractions/env-infraction-existing-targets.tsx'
import useActionById from '../../../hooks/use-action-by-id.tsx'
import { extractLatLonFromMultiPoint } from '@common/utils/geometry.ts'
import { Coordinates } from '@mtes-mct/monitor-ui/types/definitions'
import { ActionDetailsProps } from './action-mapping.ts'
import ActionHeader from './action-header.tsx'
import useIsMissionFinished from '../../../hooks/use-is-mission-finished.tsx'
import ActionEnvObservationsUnit from './action-env-observations-unit.tsx'
import ActionEnvDateRange from './action-env-daterange.tsx'
import EnvActionControlPlans from '@common/components/elements/env-action-control-plans.tsx'
import ActionEnvObservationsUnit from './action-env-observations-unit.tsx'
import ActionHeader from './action-header.tsx'
import { ActionDetailsProps } from './action-mapping.ts'

type ActionControlPropsEnv = ActionDetailsProps

const ActionControlEnv: React.FC<ActionControlPropsEnv> = ({ action }) => {
const { missionId, actionId } = useParams()
const isMissionFinished = useIsMissionFinished(missionId)
const [maxNbrControl, setMaxNbrControl] = useState<number>()

const { data: envAction, loading, error } = useActionById(actionId, missionId, action.source, action.type)

useEffect(() => {
if (!envAction?.data) return
const data = envAction.data as unknown as EnvActionControl
if (!data.actionNumberOfControls) return
setMaxNbrControl(
data.actionNumberOfControls -
((data.controlSecurity?.amountOfControls || 0) +
(data.controlGensDeMer?.amountOfControls || 0) +
(data.controlNavigation?.amountOfControls || 0) +
(data.controlAdministrative?.amountOfControls || 0))
)
}, [envAction])

if (loading) {
return <div>Chargement...</div>
}
if (error) {
return <div>error</div>
}
if (envAction) {
const actionData: EnvActionControl = envAction.data
const actionData = envAction.data as unknown as EnvActionControl
return (
<Stack
direction="column"
Expand Down Expand Up @@ -138,33 +147,33 @@ const ActionControlEnv: React.FC<ActionControlPropsEnv> = ({ action }) => {
</Stack.Item>
<Stack.Item style={{ width: '100%' }}>
<EnvControlForm
maxAmountOfControls={maxNbrControl}
controlType={ControlType.ADMINISTRATIVE}
data={actionData?.controlAdministrative}
maxAmountOfControls={actionData?.actionNumberOfControls}
shouldCompleteControl={!!actionData?.controlsToComplete?.includes(ControlType.ADMINISTRATIVE)}
/>
</Stack.Item>
<Stack.Item style={{ width: '100%' }}>
<EnvControlForm
maxAmountOfControls={maxNbrControl}
controlType={ControlType.NAVIGATION}
data={actionData?.controlNavigation}
maxAmountOfControls={actionData?.actionNumberOfControls}
shouldCompleteControl={!!actionData?.controlsToComplete?.includes(ControlType.NAVIGATION)}
/>
</Stack.Item>
<Stack.Item style={{ width: '100%' }}>
<EnvControlForm
maxAmountOfControls={maxNbrControl}
controlType={ControlType.GENS_DE_MER}
data={actionData?.controlGensDeMer}
maxAmountOfControls={actionData?.actionNumberOfControls}
shouldCompleteControl={!!actionData?.controlsToComplete?.includes(ControlType.GENS_DE_MER)}
/>
</Stack.Item>
<Stack.Item style={{ width: '100%' }}>
<EnvControlForm
maxAmountOfControls={maxNbrControl}
controlType={ControlType.SECURITY}
data={actionData?.controlSecurity}
maxAmountOfControls={actionData?.actionNumberOfControls}
shouldCompleteControl={!!actionData?.controlsToComplete?.includes(ControlType.SECURITY)}
/>
</Stack.Item>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ describe('ControlAdministrativeForm', () => {
controlIsChecked: false,
updateControl: updateControlMock,
toggleControl: toogleControlMock,
controlChanged: controlChangedMock
controlChanged: controlChangedMock,
controlEnvChanged: vi.fn()
})
const data = {
id: 'scscss',
Expand All @@ -89,7 +90,8 @@ describe('ControlAdministrativeForm', () => {
controlIsChecked: false,
updateControl: updateControlMock,
toggleControl: toogleControlMock,
controlChanged: controlChangedMock
controlChanged: controlChangedMock,
controlEnvChanged: vi.fn()
})
const wrapper = render(<ControlAdministrativeForm shouldCompleteControl={false} unitShouldConfirm={false} />)
const radio = wrapper.container.querySelectorAll("input[type='radio']")[0] as HTMLInputElement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ describe('ControlGensDeMer', () => {
controlIsChecked: false,
updateControl: updateControlMock,
toggleControl: toogleControlMock,
controlChanged: controlChangedMock
controlChanged: controlChangedMock,
controlEnvChanged: vi.fn()
})
const data = {
id: 'scscss',
Expand All @@ -87,7 +88,8 @@ describe('ControlGensDeMer', () => {
controlIsChecked: false,
updateControl: updateControlMock,
toggleControl: toogleControlMock,
controlChanged: controlChangedMock
controlChanged: controlChangedMock,
controlEnvChanged: vi.fn()
})
const wrapper = render(<ControlGensDeMerForm shouldCompleteControl={false} unitShouldConfirm={false} />)
const radio = wrapper.container.querySelectorAll("input[type='radio']")[0] as HTMLInputElement
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { useControl } from '@features/pam/mission/hooks/control/use-control.tsx'
import { FormikEffect, FormikNumberInput, FormikTextInput } from '@mtes-mct/monitor-ui'
import { Form, Formik } from 'formik'
import { isEqual, isNull, omitBy, pick } from 'lodash'
import { FC, useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'
import { Stack } from 'rsuite'
import {
ControlAdministrative,
Expand All @@ -7,119 +12,96 @@ import {
ControlSecurity,
ControlType
} from '../../../../../common/types/control-types.ts'
import { NumberInput, TextInput } from '@mtes-mct/monitor-ui'
import omit from 'lodash/omit'
import { useParams } from 'react-router-dom'
import ControlTitleCheckbox from '../../ui/control-title-checkbox.tsx'
import useAddOrUpdateControl from '../../../hooks/use-add-update-control.tsx'
import useDeleteControl from '../../../hooks/use-delete-control.tsx'

export type EnvControlFormInput = {
observations?: string
amountOfControls?: number
}

type EnvControl = ControlAdministrative | ControlSecurity | ControlNavigation | ControlGensDeMer

export interface EnvControlFormProps {
controlType: ControlType
data?: ControlAdministrative | ControlSecurity | ControlNavigation | ControlGensDeMer
data?: EnvControl
maxAmountOfControls?: number
shouldCompleteControl?: boolean
}

const EnvControlForm: FC<EnvControlFormProps> = ({ controlType, data, maxAmountOfControls, shouldCompleteControl }) => {
const { missionId, actionId } = useParams()
const [control, setControl] = useState<EnvControlFormInput>()
const { isRequired, toggleControl, controlIsChecked, controlEnvChanged } = useControl(
data,
controlType,
shouldCompleteControl,
3000
)

const [observationsValue, setObservationsValue] = useState<string | undefined>(data?.observations)
const getControlInput = (data?: EnvControl) =>
data ? omitBy(pick(data, 'observations', 'amountOfControls'), isNull) : ({} as EnvControlFormInput)

const handleObservationsChange = (nextValue?: string) => {
setObservationsValue(nextValue)
const getControl = (value?: EnvControlFormInput) => {
if (!value) return
return {
missionId,
actionControlId: actionId,
unitHasConfirmed: undefined,
...value
}
}

useEffect(() => {
setObservationsValue(data?.observations)
setControl(getControlInput(data))
}, [data])

const handleObservationsBlur = async () => {
await onChange('observations', observationsValue)
const handleControlChange = async (value: EnvControlFormInput): Promise<void> => {
if (isEqual(value, control)) return
controlEnvChanged(actionId, getControl(value), !!value.amountOfControls)
}

const [mutateControl] = useAddOrUpdateControl({ controlType: controlType })
const [deleteControl] = useDeleteControl({ controlType: controlType })

const toggleControl = async (isChecked: boolean) =>
isChecked
? onChange()
: await deleteControl({
variables: {
actionId
}
})

const onChange = async (field?: string, value?: any) => {
let updatedData = {
...omit(data, '__typename'),
missionId: missionId,
actionControlId: actionId,
amountOfControls: data?.amountOfControls
}

// do not create a control just because of a blur on observations
if (field === 'observations') {
if (observationsValue === undefined && value === undefined) {
return
}
}

if (!!field && !!value) {
updatedData = {
...updatedData,
[field]: value
}
}

if (field === 'amountOfControls' && (value === 0 || value === undefined)) {
await deleteControl({
variables: {
actionId
}
})
} else {
await mutateControl({ variables: { control: updatedData } })
}
}
const handleToogleControl = async (isChecked: boolean) => toggleControl(isChecked, actionId, getControl(control))

return (
<Stack direction="column" alignItems="flex-start" spacing={'0.5rem'} style={{ width: '100%' }}>
<Stack.Item style={{ width: '100%' }}>
<ControlTitleCheckbox
controlType={controlType}
checked={!!data || shouldCompleteControl}
// disabled={mutationLoading?.loading}
shouldCompleteControl={!!shouldCompleteControl && !data}
onChange={(isChecked: boolean) => toggleControl(isChecked)}
checked={controlIsChecked}
shouldCompleteControl={isRequired}
onChange={(isChecked: boolean) => handleToogleControl(isChecked)}
/>
</Stack.Item>
<Stack.Item style={{ width: '100%' }}>
<Stack direction="row" style={{ width: '100%' }} spacing={'0.5rem'}>
<Stack.Item style={{ width: '33%' }}>
<NumberInput
label="Nb contrôles"
isRequired={shouldCompleteControl}
name="amountOfControls"
// disabled={mutationLoading?.loading}
value={data?.amountOfControls}
// max={maxAmountOfControls}
isLight={true}
onChange={(nextValue?: number) => onChange('amountOfControls', nextValue)}
/>
</Stack.Item>
<Stack.Item style={{ width: '67%' }}>
<TextInput
label="Observations (hors infraction)"
name="observations"
isLight={true}
// disabled={mutationLoading?.loading}
value={observationsValue}
onChange={handleObservationsChange}
onBlur={handleObservationsBlur}
/>
</Stack.Item>
</Stack>
{control !== undefined && (
<Formik
initialValues={control}
validateOnChange={true}
enableReinitialize={true}
onSubmit={handleControlChange}
>
<>
<FormikEffect onChange={handleControlChange} />
<Form>
<Stack direction="row" style={{ width: '100%' }} spacing={'0.5rem'}>
<Stack.Item style={{ width: '33%' }}>
<FormikNumberInput
min={0}
isLight={true}
label="Nb contrôles"
name="amountOfControls"
isRequired={shouldCompleteControl}
max={(data?.amountOfControls || 0) + (maxAmountOfControls || 0)}
/>
</Stack.Item>
<Stack.Item style={{ width: '67%' }}>
<FormikTextInput isLight={true} name="observations" label="Observations (hors infraction)" />
</Stack.Item>
</Stack>
</Form>
</>
</Formik>
)}
</Stack.Item>
</Stack>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,4 +272,18 @@ describe('useControl hook', () => {
control: expect.objectContaining({ unitHasConfirmed: true })
})
})

it('it should override debounce time', async () => {
const debounceTime = 10000
vi.useFakeTimers({ shouldAdvanceTime: true })
const { result } = renderHook(() => useControl(data, ControlType.ADMINISTRATIVE, true, debounceTime), { wrapper })
act(() => {
result.current.controlChanged(actionId, control)
})
expect(addOrUpdateControlMatcher).toHaveBeenCalledTimes(0)
vi.advanceTimersByTime(5000)
expect(addOrUpdateControlMatcher).toHaveBeenCalledTimes(0)
vi.advanceTimersByTime(debounceTime)
expect(addOrUpdateControlMatcher).toHaveBeenCalledTimes(1)
})
})
Loading

0 comments on commit aba94c0

Please sign in to comment.