Skip to content

Commit

Permalink
feat(control): refactor security and navigation
Browse files Browse the repository at this point in the history
 - on security and navigation control use hook useControl
 - on security and navigation control Refactor form with formik and FormikEffect
 - On administrative and gensDeMer, remove control already in useControl hook
 - Add tests
  • Loading branch information
xtiannyeto committed Sep 4, 2024
1 parent e36d577 commit b5d980b
Show file tree
Hide file tree
Showing 6 changed files with 333 additions and 163 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ const ControlAdministrativeForm: FC<ControlAdministrativeFormProps> = ({

const getControl = (value?: ControlAdministrativeFormInput) => {
if (!value) return
if (!_.isBoolean(value.unitHasConfirmed)) value.unitHasConfirmed = true
return {
...omit(data, 'infractions'),
missionId,
Expand All @@ -83,10 +82,7 @@ const ControlAdministrativeForm: FC<ControlAdministrativeFormProps> = ({
controlChanged(actionId, getControl(value))
}

const handleToogleControl = async (isChecked: boolean) => {
if (controlIsChecked === isChecked) return
toggleControl(isChecked, actionId, getControl(control))
}
const handleToogleControl = async (isChecked: boolean) => toggleControl(isChecked, actionId, getControl(control))

return (
<Panel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ const ControlGensDeMerForm: FC<ControlGensDeMerFormProps> = ({ data, shouldCompl

const getControl = (value?: ControlGensDeMerFormInput) => {
if (!value) return
//TODO: unitConfrimed by default? if (!_.isBoolean(value.unitHasConfirmed)) value.unitHasConfirmed = true
return {
...omit(data, 'infractions'),
missionId,
Expand All @@ -79,10 +78,7 @@ const ControlGensDeMerForm: FC<ControlGensDeMerFormProps> = ({ data, shouldCompl
controlChanged(actionId, getControl(value))
}

const handleToogleControl = async (isChecked: boolean) => {
if (controlIsChecked === isChecked) return
toggleControl(isChecked, actionId, getControl(control))
}
const handleToogleControl = async (isChecked: boolean) => toggleControl(isChecked, actionId, getControl(control))

return (
<Panel
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { ControlAdministrative, ControlGensDeMer, ControlNavigation } from '@common/types/control-types'
import { Params } from 'react-router-dom'
import { vi } from 'vitest'
import { fireEvent, render, screen } from '../../../../../../test-utils'
import * as useControlHook from '../../../hooks/control/use-control'
import ControlNavigationForm from './control-navigation-form'

const updateControlMock = vi.fn()
const toogleControlMock = vi.fn()
const controlChangedMock = vi.fn()

vi.mock('react-router-dom', async importOriginal => {
const actual = await importOriginal()
return {
...actual,
useParams: (): Readonly<Params<string>> => ({ missionId: '761', actionId: '3434' })
}
})

describe('ControlNavigationForm', () => {
afterEach(() => {
vi.clearAllMocks()
vi.resetAllMocks()
})

it('should render control navigation form', () => {
const data = {
id: '',
amountOfControls: 0
} as ControlGensDeMer
render(<ControlNavigationForm data={data} />)
expect(screen.getByText('Respect des règles de navigation')).toBeInTheDocument()
})

it('it should have control navigation title check when should complete control is true', () => {
const data = {
id: '',
amountOfControls: 0
} as ControlGensDeMer
const wrapper = render(<ControlNavigationForm data={data} shouldCompleteControl={false} />)
const checkbox = wrapper.container.querySelectorAll("input[type='checkbox']")[0] as HTMLInputElement
expect(checkbox).toBeChecked()
})

it('it should display required red ', () => {
const wrapper = render(<ControlNavigationForm shouldCompleteControl={true} />)
expect(wrapper.getAllByTestId('control-title-required-control')).not.toBeNull()
})

it('it should show unit should confirm toogle', () => {
const data = {
id: '',
amountOfControls: 0
} as ControlAdministrative
render(<ControlNavigationForm data={data} unitShouldConfirm={true} />)
expect(screen.getByText('Contrôle confirmé par l’unité')).toBeInTheDocument()
})

it('it should trigger delete control', async () => {
const useControlSpy = vi.spyOn(useControlHook, 'useControl')
useControlSpy.mockReturnValue({
isRequired: false,
controlIsChecked: false,
updateControl: updateControlMock,
toggleControl: toogleControlMock,
controlChanged: controlChangedMock
})
const data = {
id: 'scscss',
observations: 'myObservations'
} as ControlNavigation

const wrapper = render(<ControlNavigationForm data={data} shouldCompleteControl={true} unitShouldConfirm={false} />)
const checkbox = wrapper.container.querySelectorAll("input[type='checkbox']")[0] as HTMLInputElement
fireEvent.click(checkbox)
expect(toogleControlMock).toHaveBeenCalledTimes(1)
})

it('it should update form and trigger use control controlChanged', async () => {
const useControlSpy = vi.spyOn(useControlHook, 'useControl')
useControlSpy.mockReturnValue({
isRequired: true,
controlIsChecked: false,
updateControl: updateControlMock,
toggleControl: toogleControlMock,
controlChanged: controlChangedMock
})
render(<ControlNavigationForm shouldCompleteControl={false} unitShouldConfirm={false} />)
const observations = screen.getByLabelText('Observations (hors infraction) sur les règles de navigation')
fireEvent.change(observations, { target: { value: 'my observations' } })
expect(controlChangedMock).toHaveBeenCalled() //TODO: check number of times
})
})
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { Panel, Stack, Toggle } from 'rsuite'
import { ControlNavigation, ControlType } from '../../../../../common/types/control-types.ts'
import { Label, Textarea, THEME } from '@mtes-mct/monitor-ui'
import { useControl } from '@features/pam/mission/hooks/control/use-control.tsx'
import { FormikEffect, FormikTextarea, FormikToggle, Label, THEME } from '@mtes-mct/monitor-ui'
import { Form, Formik } from 'formik'
import _ from 'lodash'
import omit from 'lodash/omit'
import { FC, useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'
import { Panel, Stack } from 'rsuite'
import { ControlNavigation, ControlType } from '../../../../../common/types/control-types.ts'
import ControlTitleCheckbox from '../../ui/control-title-checkbox.tsx'
import ControlInfraction from '../infractions/infraction-for-control.tsx'
import { FC, useEffect, useState } from 'react'
import useAddOrUpdateControl from '../../../hooks/use-add-update-control.tsx'
import useDeleteControl from '../../../hooks/use-delete-control.tsx'

export type ControlNavigationFormInput = {
observations?: string
unitHasConfirmed?: boolean
}

interface ControlNavigationFormProps {
data?: ControlNavigation
Expand All @@ -17,99 +23,88 @@ interface ControlNavigationFormProps {

const ControlNavigationForm: FC<ControlNavigationFormProps> = ({ data, shouldCompleteControl, unitShouldConfirm }) => {
const { missionId, actionId } = useParams()
const [control, setControl] = useState<ControlNavigationFormInput>()
const { isRequired, controlChanged, toggleControl, controlIsChecked } = useControl(
data,
ControlType.NAVIGATION,
shouldCompleteControl
)

const [observationsValue, setObservationsValue] = useState<string | undefined>(data?.observations)

const handleObservationsChange = (nextValue?: string) => {
setObservationsValue(nextValue)
}
const getControlInput = (data?: ControlNavigationFormInput) =>
data ? _.omitBy(_.pick(data, 'observations', 'unitHasConfirmed'), _.isNull) : ({} as ControlNavigationFormInput)

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

const handleObservationsBlur = async () => {
if (observationsValue !== data?.observations) {
await onChange('observations', observationsValue)
}
}

const [mutateControl, { loading: mutationLoading }] = useAddOrUpdateControl({ controlType: ControlType.NAVIGATION })
const [deleteControl] = useDeleteControl({ controlType: ControlType.NAVIGATION })

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

const onChange = async (field?: string, value?: any) => {
let updatedData = {
...omit(data, '__typename', 'infractions'),
id: data?.id,
missionId: missionId,
actionControlId: actionId,
const getControl = (value?: ControlNavigationFormInput) => {
if (!value) return
return {
...omit(data, 'infractions'),
missionId,
unitShouldConfirm,
amountOfControls: 1,
unitShouldConfirm: unitShouldConfirm
actionControlId: actionId,
...value
}
}

if (!!field && value !== undefined) {
updatedData = {
...updatedData,
[field]: value
}
}
await mutateControl({ variables: { control: updatedData } })
const handleControlChange = async (value: ControlNavigationFormInput): Promise<void> => {
if (value === control) return
controlChanged(actionId, getControl(value))
}

const handleToogleControl = async (isChecked: boolean) => toggleControl(isChecked, actionId, getControl(control))
return (
<Panel
header={
<ControlTitleCheckbox
checked={controlIsChecked}
shouldCompleteControl={isRequired}
controlType={ControlType.NAVIGATION}
checked={!!data || shouldCompleteControl}
disabled={mutationLoading}
shouldCompleteControl={!!shouldCompleteControl && !!!data}
onChange={(isChecked: boolean) => toggleControl(isChecked)}
onChange={(isChecked: boolean) => handleToogleControl(isChecked)}
/>
}
// collapsible
// defaultExpanded={controlIsEnabled(data)}
style={{ backgroundColor: THEME.color.white, borderRadius: 0 }}
>
<Stack direction="column" alignItems="flex-start" spacing="1rem" style={{ width: '100%' }}>
{unitShouldConfirm && (
<Stack.Item style={{ width: '100%' }}>
<Stack direction="row" alignItems="center" spacing={'0.5rem'}>
<Stack.Item>
{/* TODO add Toggle component to monitor-ui */}
<Toggle
checked={!!data?.unitHasConfirmed}
size="sm"
onChange={(checked: boolean) => onChange('unitHasConfirmed', checked)}
disabled={mutationLoading}
/>
</Stack.Item>
<Stack.Item alignSelf="flex-end">
<Label style={{ marginBottom: 0 }}>
<b>Contrôle confirmé par l’unité</b>
</Label>
</Stack.Item>
</Stack>
</Stack.Item>
{control !== undefined && (
<Formik
initialValues={control}
onSubmit={handleControlChange}
validateOnChange={true}
enableReinitialize={true}
>
<>
<FormikEffect onChange={handleControlChange} />
<Form>
{unitShouldConfirm && (
<Stack.Item style={{ width: '100%' }}>
<Stack direction="row" alignItems="center" spacing={'0.5rem'}>
<Stack.Item>
<FormikToggle label="" size="sm" name="unitHasConfirmed" />
</Stack.Item>
<Stack.Item alignSelf="flex-end">
<Label style={{ marginBottom: 0 }}>
<b>Contrôle confirmé par l’unité</b>
</Label>
</Stack.Item>
</Stack>
</Stack.Item>
)}
<Stack.Item style={{ width: '100%' }}>
<FormikTextarea
name="observations"
label="Observations (hors infraction) sur les règles de navigation"
/>
</Stack.Item>
</Form>
</>
</Formik>
)}
<Stack.Item style={{ width: '100%' }}>
<Textarea
name="observations"
label="Observations (hors infraction) sur les règles de navigation"
value={observationsValue}
onChange={handleObservationsChange}
onBlur={handleObservationsBlur}
disabled={mutationLoading}
/>
</Stack.Item>

<Stack.Item style={{ width: '100%' }}>
<ControlInfraction
controlId={data?.id}
Expand Down
Loading

0 comments on commit b5d980b

Please sign in to comment.