Skip to content

Commit

Permalink
Add filter tags in prior notification list
Browse files Browse the repository at this point in the history
  • Loading branch information
ivangabriele committed Mar 22, 2024
1 parent 215f7d9 commit b5cdbe2
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 34 deletions.
8 changes: 4 additions & 4 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"dependencies": {
"@dnd-kit/core": "6.1.0",
"@dnd-kit/modifiers": "6.0.1",
"@mtes-mct/monitor-ui": "13.7.3",
"@mtes-mct/monitor-ui": "13.8.0",
"@reduxjs/toolkit": "1.9.6",
"@sentry/browser": "7.55.2",
"@sentry/react": "7.52.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import {
DateRangePicker,
Icon,
MultiCascader,
MultiSelect,
RichBoolean,
RichBooleanCheckbox,
Select,
Size,
TextInput,
type DateAsStringRange
type DateAsStringRange,
CheckPicker
} from '@mtes-mct/monitor-ui'
import { assertNotNullish } from '@utils/assertNotNullish'
import { useCallback } from 'react'
Expand All @@ -38,13 +38,13 @@ export type FilterBarProps = {
}
export function FilterBar() {
const listFilterValues = useMainAppSelector(store => store.priorNotification.listFilterValues)
const dispatch = useMainAppDispatch()

const { fleetSegmentsAsOptions } = useGetFleetSegmentsAsOptions()
const { gearsAsTreeOptions } = useGetGearsAsTreeOptions()
const { portsAsTreeOptions } = useGetPortsAsTreeOptions()
const { speciesAsOptions } = useGetSpeciesAsOptions()
const { priorNotificationTypesAsOptions } = useGetPriorNotificationTypesAsOptions()
const dispatch = useMainAppDispatch()

const updateCountryCodes = (nextCountryCodes: string[] | undefined) => {
dispatch(priorNotificationActions.setListFilterValues({ countryCodes: nextCountryCodes }))
Expand Down Expand Up @@ -123,20 +123,23 @@ export function FilterBar() {
</Row>

<Row>
<MultiSelect
<CheckPicker
isLabelHidden
isTransparent
label="Nationalité"
label="Nationalités"
name="countryCodes"
onChange={updateCountryCodes}
options={COUNTRIES_AS_ALPHA3_OPTIONS}
placeholder="Nationalité"
popupWidth={240}
renderValue={(_, items) =>
items.length > 0 ? <SelectValue>Nationalités ({items.length})</SelectValue> : <></>
}
searchable
value={listFilterValues.countryCodes}
virtualized
/>
<MultiSelect
<CheckPicker
disabled={!fleetSegmentsAsOptions}
isLabelHidden
isTransparent
Expand All @@ -146,11 +149,14 @@ export function FilterBar() {
options={fleetSegmentsAsOptions ?? []}
placeholder="Segments de flotte"
popupWidth={320}
renderValue={(_, items) =>
items.length > 0 ? <SelectValue>Segments de flotte ({items.length})</SelectValue> : <></>
}
searchable
value={listFilterValues.fleetSegmentSegments}
virtualized
/>
<MultiSelect
<CheckPicker
disabled={!speciesAsOptions}
isLabelHidden
isTransparent
Expand All @@ -160,6 +166,9 @@ export function FilterBar() {
options={speciesAsOptions ?? []}
placeholder="Espèces à bord"
popupWidth={320}
renderValue={(_, items) =>
items.length > 0 ? <SelectValue>Espèces à bord ({items.length})</SelectValue> : <></>
}
searchable
value={listFilterValues.specyCodes}
virtualized
Expand All @@ -174,6 +183,9 @@ export function FilterBar() {
options={gearsAsTreeOptions ?? []}
placeholder="Engins utilisés"
popupWidth={500}
renderValue={(_, items) =>
items.length > 0 ? <SelectValue>Engins utilisés ({items.length})</SelectValue> : <></>
}
searchable
value={listFilterValues.gearCodes}
/>
Expand Down Expand Up @@ -225,10 +237,13 @@ export function FilterBar() {
options={portsAsTreeOptions ?? []}
placeholder="Ports d’arrivée"
popupWidth={500}
renderValue={(_, items) =>
items.length > 0 ? <SelectValue>Ports d’arrivée ({items.length})</SelectValue> : <></>
}
searchable
value={listFilterValues.portLocodes}
/>
<MultiSelect
<CheckPicker
disabled={!priorNotificationTypesAsOptions}
isLabelHidden
isTransparent
Expand All @@ -238,6 +253,9 @@ export function FilterBar() {
options={priorNotificationTypesAsOptions ?? []}
placeholder="Types de préavis"
popupWidth={240}
renderValue={(_, items) =>
items.length > 0 ? <SelectValue>Types de préavis ({items.length})</SelectValue> : <></>
}
searchable
value={listFilterValues.priorNotificationTypes}
virtualized
Expand Down Expand Up @@ -304,3 +322,11 @@ const Row = styled.div`
min-width: 320px;
}
`

const SelectValue = styled.span`
display: flex;
overflow: hidden;
pointer-events: none;
text-overflow: ellipsis;
white-space: nowrap;
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { COUNTRIES_AS_ALPHA3_OPTIONS } from '@constants/index'
import { useGetPriorNotificationTypesAsOptions } from '@features/PriorNotification/hooks/useGetPriorNotificationTypesAsOptions'
import { priorNotificationActions } from '@features/PriorNotification/slice'
import { useGetFleetSegmentsAsOptions } from '@hooks/useGetFleetSegmentsAsOptions'
import { useGetGearsAsTreeOptions } from '@hooks/useGetGearsAsTreeOptions'
import { useGetPortsAsTreeOptions } from '@hooks/useGetPortsAsTreeOptions'
import { useGetSpeciesAsOptions } from '@hooks/useGetSpeciesAsOptions'
import { useMainAppDispatch } from '@hooks/useMainAppDispatch'
import { useMainAppSelector } from '@hooks/useMainAppSelector'
import { SingleTag, getSelectedOptionFromOptionValueInTree } from '@mtes-mct/monitor-ui'
import styled from 'styled-components'

import type { ListFilter } from './types'

export function FilterTags() {
const listFilterValues = useMainAppSelector(store => store.priorNotification.listFilterValues)
const dispatch = useMainAppDispatch()

const { fleetSegmentsAsOptions } = useGetFleetSegmentsAsOptions()
const { gearsAsTreeOptions } = useGetGearsAsTreeOptions()
const { portsAsTreeOptions } = useGetPortsAsTreeOptions()
const { speciesAsOptions } = useGetSpeciesAsOptions()
const { priorNotificationTypesAsOptions } = useGetPriorNotificationTypesAsOptions()

const remove = (key: keyof ListFilter, value: string) => {
const filterValue = listFilterValues[key]

if (!filterValue) {
throw new Error('`filterValue` is undefined.')
}

const nextFilterValue = Array.isArray(filterValue) ? filterValue.filter(v => v !== value) : undefined
const normalizedNextFilterValue =
Array.isArray(nextFilterValue) && !nextFilterValue.length ? undefined : nextFilterValue
const nextListFilterValues = { ...listFilterValues, [key]: normalizedNextFilterValue }

dispatch(priorNotificationActions.setListFilterValues(nextListFilterValues))
}

const removeAll = () => {
dispatch(priorNotificationActions.resetListFilterValues())
}

return (
<Wrapper>
<Row>
{!!listFilterValues.countryCodes &&
listFilterValues.countryCodes.map(countryCode => (
<SingleTag key={`countryCodes-${countryCode}`} onDelete={() => remove('countryCodes', countryCode)}>
{String(COUNTRIES_AS_ALPHA3_OPTIONS.find(option => option.value === countryCode)?.label)}
</SingleTag>
))}

{!!listFilterValues.fleetSegmentSegments &&
!!fleetSegmentsAsOptions &&
listFilterValues.fleetSegmentSegments.map(fleetSegmentSegment => (
<SingleTag
key={`fleetSegmentSegments-${fleetSegmentSegment}`}
onDelete={() => remove('fleetSegmentSegments', fleetSegmentSegment)}
>
{String(fleetSegmentsAsOptions.find(option => option.value === fleetSegmentSegment)?.label)}
</SingleTag>
))}

{!!listFilterValues.specyCodes &&
!!speciesAsOptions &&
listFilterValues.specyCodes.map(specyCode => (
<SingleTag key={`specyCodes-${specyCode}`} onDelete={() => remove('specyCodes', specyCode)}>
{String(speciesAsOptions.find(option => option.value === specyCode)?.label)}
</SingleTag>
))}

{!!listFilterValues.gearCodes &&
!!gearsAsTreeOptions &&
listFilterValues.gearCodes.map(gearCode => (
<SingleTag key={`gearCodes-${gearCode}`} onDelete={() => remove('gearCodes', gearCode)}>
{getSelectedOptionFromOptionValueInTree(gearsAsTreeOptions, gearCode)?.label}
</SingleTag>
))}

{!!listFilterValues.portLocodes &&
!!portsAsTreeOptions &&
listFilterValues.portLocodes.map(portLocode => (
<SingleTag key={`portLocodes-${portLocode}`} onDelete={() => remove('portLocodes', portLocode)}>
{getSelectedOptionFromOptionValueInTree(portsAsTreeOptions, portLocode)?.label}
</SingleTag>
))}

{!!listFilterValues.priorNotificationTypes &&
!!priorNotificationTypesAsOptions &&
listFilterValues.priorNotificationTypes.map(priorNotificationType => (
<SingleTag
key={`priorNotificationTypes-${priorNotificationType}`}
onDelete={() => remove('priorNotificationTypes', priorNotificationType)}
>
{String(priorNotificationTypesAsOptions.find(option => option.value === priorNotificationType)?.label)}
</SingleTag>
))}
</Row>

<Row>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<Link onClick={removeAll}>Réinitialiser les filtres</Link>
</Row>
</Wrapper>
)
}

const Wrapper = styled.div`
display: flex;
flex-direction: column;
flex-wrap: wrap;
margin-bottom: 24px;
> div:not(:first-child) {
margin-top: 12px;
}
`

const Row = styled.div`
align-items: center;
display: flex;
flex-wrap: wrap;
> .Component-SingleTag {
margin: 0 8px 8px 0;
}
`

const Link = styled.a`
align-items: center;
color: ${p => p.theme.color.charcoal};
cursor: pointer;
line-height: 1;
text-decoration: underline;
`
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import styled from 'styled-components'

import { PRIOR_NOTIFICATION_TABLE_COLUMNS, SUB_MENUS_AS_OPTIONS } from './constants'
import { FilterBar } from './FilterBar'
import { FilterTags } from './FilterTags'
import {
countPriorNotificationsForSeaFrontGroup,
getApiFilterFromListFilter,
Expand Down Expand Up @@ -121,6 +122,7 @@ export function PriorNotificationList() {

<Body>
<FilterBar />
<FilterTags />

<TableWrapper ref={tableContainerRef}>
{isError && <div>Une erreur est survenue.</div>}
Expand Down
23 changes: 23 additions & 0 deletions frontend/src/features/PriorNotification/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { RichBoolean } from '@mtes-mct/monitor-ui'

import { ExpectedArrivalPeriod } from './components/PriorNotificationList/constants'
import { SeaFrontGroup } from '../../domain/entities/seaFront/constants'

import type { ListFilter } from './components/PriorNotificationList/types'

export const DEFAULT_LIST_FILTER_VALUES: ListFilter = {
countryCodes: undefined,
expectedArrivalCustomPeriod: undefined,
expectedArrivalPeriod: ExpectedArrivalPeriod.IN_LESS_THAN_FOUR_HOURS,
fleetSegmentSegments: undefined,
gearCodes: undefined,
hasOneOrMoreReportings: RichBoolean.BOTH,
isLessThanTwelveMetersVessel: RichBoolean.BOTH,
isSent: undefined,
lastControlPeriod: undefined,
portLocodes: undefined,
priorNotificationTypes: undefined,
seaFrontGroup: SeaFrontGroup.ALL,
searchQuery: undefined,
specyCodes: undefined
}
25 changes: 6 additions & 19 deletions frontend/src/features/PriorNotification/slice.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,24 @@
import { RichBoolean } from '@mtes-mct/monitor-ui'
import { createSlice, type PayloadAction } from '@reduxjs/toolkit'

import { ExpectedArrivalPeriod } from './components/PriorNotificationList/constants'
import { SeaFrontGroup } from '../../domain/entities/seaFront/constants'
import { DEFAULT_LIST_FILTER_VALUES } from './constants'

import type { ListFilter } from './components/PriorNotificationList/types'

interface PriorNotificationState {
listFilterValues: ListFilter
}
const INITIAL_STATE: PriorNotificationState = {
listFilterValues: {
countryCodes: undefined,
expectedArrivalCustomPeriod: undefined,
expectedArrivalPeriod: ExpectedArrivalPeriod.IN_LESS_THAN_FOUR_HOURS,
fleetSegmentSegments: undefined,
gearCodes: undefined,
hasOneOrMoreReportings: RichBoolean.BOTH,
isLessThanTwelveMetersVessel: RichBoolean.BOTH,
isSent: undefined,
lastControlPeriod: undefined,
portLocodes: undefined,
priorNotificationTypes: undefined,
seaFrontGroup: SeaFrontGroup.ALL,
searchQuery: undefined,
specyCodes: undefined
}
listFilterValues: DEFAULT_LIST_FILTER_VALUES
}

const priorNotificationSlice = createSlice({
initialState: INITIAL_STATE,
name: 'priorNotification',
reducers: {
resetListFilterValues(state) {
state.listFilterValues = DEFAULT_LIST_FILTER_VALUES
},

setListFilterValues(state, action: PayloadAction<Partial<ListFilter>>) {
state.listFilterValues = {
...state.listFilterValues,
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/features/SideWindow/MissionList/FilterBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ import { missionListActions } from '../../Mission/components/MissionList/slice'
import type { FilterValues } from './types'
import type { Promisable } from 'type-fest'

export type FilterBarProps = {
export type FilterBarProps = Readonly<{
onQueryChange: (nextQuery: string | undefined) => Promisable<void>
searchQuery: string | undefined
}
}>
export function FilterBar({ onQueryChange, searchQuery }: FilterBarProps) {
const listFilterValues = useMainAppSelector(store => store.missionList.listFilterValues)

Expand Down

0 comments on commit b5cdbe2

Please sign in to comment.