diff --git a/frontend/cypress/e2e/side_window/mission_list/filters.spec.ts b/frontend/cypress/e2e/side_window/mission_list/filters.spec.ts index 35a781a786..7fb975b731 100644 --- a/frontend/cypress/e2e/side_window/mission_list/filters.spec.ts +++ b/frontend/cypress/e2e/side_window/mission_list/filters.spec.ts @@ -54,12 +54,12 @@ context('Side Window > Mission List > Filter Bar', () => { cy.wrap(row).should('contain', 'CACEM') }) + + cy.fill('Administration', undefined) }) it('Should filter missions by administrations', () => { - cy.getDataCy('select-administration-filter').click().wait(100) - cy.get('.rs-picker-search-bar-input').type('DDTM').wait(100) - cy.get('[data-key="DDTM"]').click().wait(100).clickOutside() + cy.fill('Administration', ['DDTM']) cy.get('.Table-SimpleTable tr').should('have.length.to.be.greaterThan', 0) cy.get('.Table-SimpleTable tr').each((row, index) => { @@ -69,12 +69,11 @@ context('Side Window > Mission List > Filter Bar', () => { cy.wrap(row).should('contain', 'DDTM') }) + cy.fill('Administration', undefined) }) it('Should filter missions by units', () => { - cy.getDataCy('select-units-filter').click().wait(100) - cy.get('.rs-picker-search-bar-input').type('BSN').wait(100) - cy.get('[data-key="10015"]').click().wait(100).clickOutside() + cy.fill('Unité', ['BSN']) cy.get('.Table-SimpleTable tr').should('have.length.to.be.greaterThan', 0) cy.get('.Table-SimpleTable tr').each((row, index) => { @@ -84,11 +83,12 @@ context('Side Window > Mission List > Filter Bar', () => { cy.wrap(row).should('contain', 'BSN Ste Maxime') }) + + cy.fill('Unité', undefined) }) it('Should filter missions by types', () => { - cy.getDataCy('select-types-filter').click().wait(100) - cy.get('[data-key="SEA"]').click().wait(100).clickOutside() + cy.fill('Type de mission', ['Mer']) cy.get('.Table-SimpleTable tr').should('have.length.to.be.greaterThan', 0) cy.get('.Table-SimpleTable tr').each((row, index) => { @@ -98,11 +98,12 @@ context('Side Window > Mission List > Filter Bar', () => { cy.wrap(row).should('contain', 'Mer') }) + + cy.fill('Type de mission', undefined) }) it('Should filter missions by sea fronts', () => { - cy.getDataCy('select-seaFronts-filter').click().wait(100) - cy.get('[data-key="MED"]').click().wait(100).clickOutside() + cy.fill('Facade', ['MED']) cy.get('.Table-SimpleTable tr').should('have.length.to.be.greaterThan', 0) cy.get('.Table-SimpleTable tr').each((row, index) => { @@ -112,11 +113,12 @@ context('Side Window > Mission List > Filter Bar', () => { cy.wrap(row).should('contain', 'MED') }) + + cy.fill('Facade', undefined) }) it('Should filter missions by statuses', () => { - cy.getDataCy('select-statuses-filter').click().wait(100) - cy.get('[data-key="PENDING"]').click().wait(100).clickOutside() + cy.fill('Statut', ['En cours']) cy.get('.Table-SimpleTable tr').should('have.length.to.be.greaterThan', 0) cy.get('.Table-SimpleTable tr').each((row, index) => { @@ -126,12 +128,12 @@ context('Side Window > Mission List > Filter Bar', () => { cy.wrap(row).should('contain', 'En cours') }) + + cy.fill('Statut', undefined) }) it('Should filter missions by themes', () => { - cy.getDataCy('select-theme-filter').click().wait(100) - cy.get('.rs-picker-search-bar-input').type('Police').wait(100) - cy.get('[data-key="Police des activités de cultures marines"]').click().wait(100).clickOutside() + cy.fill('Thématique', ['Police des activités de cultures marines']) cy.get('.Table-SimpleTable tr').should('have.length.to.be.greaterThan', 0) cy.get('.Table-SimpleTable tr').each((row, index) => { @@ -141,5 +143,7 @@ context('Side Window > Mission List > Filter Bar', () => { cy.wrap(row).should('contain', 'Police des activités de cultures marines') }) + + cy.fill('Thématique', undefined) }) }) diff --git a/frontend/cypress/e2e/side_window/mission_list/missions.spec.ts b/frontend/cypress/e2e/side_window/mission_list/missions.spec.ts index 4b88d4f1f2..ba64bd51d5 100644 --- a/frontend/cypress/e2e/side_window/mission_list/missions.spec.ts +++ b/frontend/cypress/e2e/side_window/mission_list/missions.spec.ts @@ -40,16 +40,12 @@ context('Missions', () => { cy.log('Units should be filtered') cy.get('*[data-cy="edit-mission-38"]').should('exist') - cy.get('*[data-cy="select-units-filter"]').click() - cy.get('div[role="option"]').find('label').contains('PAM Themis').click({ force: true }) + cy.fill('Unité', ['PAM Themis']) cy.get('*[data-cy="edit-mission-48"]').should('exist') cy.get('*[data-cy="edit-mission-38"]').should('not.exist') cy.log('Units filter should be clear') - cy.get('*[data-cy="Missions-numberOfDisplayedMissions"]').click('topLeft') - cy.get('*[data-cy="select-units-filter"]').get('[title="Clear"]').click({ - force: true - }) + cy.fill('Unité', undefined) cy.get('*[data-cy="edit-mission-38"]').should('exist') }) @@ -57,8 +53,7 @@ context('Missions', () => { cy.visit(`/side_window`).wait(1000) cy.log('Should filter by theme') - cy.get('*[data-cy="select-theme-filter"]').click() - cy.get('div[role="option"]').find('label').contains('Police des épaves').click({ force: true }) + cy.fill('Thématique', ['Police des épaves']) cy.get('*[data-cy="cell-envactions-themes"]') .eq(0) .contains( diff --git a/frontend/cypress/e2e/side_window/reporting/filters.spec.ts b/frontend/cypress/e2e/side_window/reporting/filters.spec.ts new file mode 100644 index 0000000000..6ade00f5f7 --- /dev/null +++ b/frontend/cypress/e2e/side_window/reporting/filters.spec.ts @@ -0,0 +1,58 @@ +import { ReportingSourceLabels } from '../../../../src/domain/entities/reporting' +import { SeaFrontLabel } from '../../../../src/domain/entities/seaFrontType' + +context('Reportings', () => { + beforeEach(() => { + cy.viewport(1280, 1024) + cy.visit(`/side_window`) + cy.intercept('GET', '/bff/v1/reportings*').as('getReportings') + cy.clickButton('signalements') + cy.wait('@getReportings') + }) + + it('Reportings should be displayed in Reportings Table and filterable', () => { + cy.log('A default period filter should be set') + cy.fill('Période', '24 dernières heures') + cy.get('*[data-cy="totalReportings"]').contains('5') + + cy.log('Source type should be filtered') + cy.fill('Type de source', [ReportingSourceLabels.SEMAPHORE]) + cy.getDataCy('reportings-filter-tags').find('.Component-SingleTag > span').contains('Type Sémaphore') + // here we test if the clear button worked correctly + cy.fill('Type de source', undefined) + + cy.log('Source should be filtered') + cy.fill('Source', ['Sémaphore de Fécamp']) + cy.getDataCy('reportings-filter-tags').find('.Component-SingleTag > span').contains('Source Sémaphore de Fécamp') + cy.fill('Source', undefined) + + cy.log('Reporting type should be filtered') + cy.fill('Type de signalement', 'Observation') + cy.getDataCy('totalReportings').contains('5') + + cy.log('Themes should be filtered') + cy.fill('Thématiques', ['Rejets illicites', 'Police des mouillages']) + cy.getDataCy('reportings-filter-tags').find('.Component-SingleTag > span').contains('Thème Rejets illicites') + cy.getDataCy('reportings-filter-tags').find('.Component-SingleTag > span').contains('Thème Police des mouillages') + cy.fill('Thématiques', undefined) + + cy.log('Sub-themes should be filtered') + cy.fill('Sous-thématiques', ['Arrêté municipal']) + cy.getDataCy('reportings-filter-tags').find('.Component-SingleTag > span').contains('Sous-thème Arrêté municipal') + cy.fill('Sous-thématiques', undefined) + + cy.log('Sea fronts should be filtered') + cy.fill('Facade', [SeaFrontLabel.MARTINIQUE, SeaFrontLabel.SOUTH_INDIAN_OCEAN]) + cy.getDataCy('reportings-filter-tags').find('.Component-SingleTag > span').contains('Facade Martinique') + cy.getDataCy('reportings-filter-tags').find('.Component-SingleTag > span').contains('Facade Sud Océan Indien') + cy.fill('Facade', undefined) + + cy.wait('@getReportings') + cy.getDataCy('reinitialize-filters').click() + cy.getDataCy('totalReportings').contains('5') + + cy.getDataCy('status-filter-Archivés').click() + cy.fill('Période', '30 derniers jours') + cy.getDataCy('totalReportings').contains('8') + }) +}) diff --git a/frontend/cypress/e2e/side_window/reporting/reportings.spec.ts b/frontend/cypress/e2e/side_window/reporting/reportings.spec.ts index cfc6d7734b..9b145754da 100644 --- a/frontend/cypress/e2e/side_window/reporting/reportings.spec.ts +++ b/frontend/cypress/e2e/side_window/reporting/reportings.spec.ts @@ -1,4 +1,4 @@ -context('Missions', () => { +context('Reportings', () => { beforeEach(() => { cy.viewport(1280, 1024) cy.visit(`/side_window`) @@ -7,31 +7,6 @@ context('Missions', () => { cy.wait('@getReportings') }) - it('Reportings should be displayed in Reportings Table and filterable', () => { - cy.log('A default period filter should be set') - cy.fill('Période', '24 dernières heures') - cy.get('*[data-cy="totalReportings"]').contains('5') - - cy.log('Source type should be filtered') - cy.get('*[data-cy="select-source-type-filter"]').click() - cy.get('div[role="option"]').find('label').contains('Sémaphore').click() - cy.get('*[data-cy="totalReportings"]').contains('2') - - cy.log('Source should be filtered') - cy.get('*[data-cy="select-source-filter"]').click() - cy.get('div[role="option"]').find('label').contains('Sémaphore de Fécamp').click() - cy.get('*[data-cy="totalReportings"]').click('topLeft') - cy.get('*[data-cy="totalReportings"]').contains('2') - - cy.wait('@getReportings') - cy.get('*[data-cy="reinitialize-filters"]').click() - cy.get('*[data-cy="totalReportings"]').contains('5') - - cy.get('*[data-cy="status-filter-Archivés"]').click() - cy.fill('Période', '30 derniers jours') - cy.get('*[data-cy="totalReportings"]').contains('8') - }) - it('Reportings should be archived in Reportings Table', () => { cy.intercept('PUT', '/bff/v1/reportings/5').as('archiveReporting') cy.get('*[data-cy="status-filter-Archivés"]').click() diff --git a/frontend/src/api/missionsAPI.ts b/frontend/src/api/missionsAPI.ts index c09bd220b8..01f562523b 100644 --- a/frontend/src/api/missionsAPI.ts +++ b/frontend/src/api/missionsAPI.ts @@ -8,7 +8,7 @@ type MissionsFilter = { missionSource?: string missionStatus?: string[] missionTypes?: string[] - seaFronts: string[] + seaFronts?: string[] startedAfterDateTime?: string startedBeforeDateTime?: string } diff --git a/frontend/src/api/reportingsAPI.ts b/frontend/src/api/reportingsAPI.ts index fa38713665..f8895d61dc 100644 --- a/frontend/src/api/reportingsAPI.ts +++ b/frontend/src/api/reportingsAPI.ts @@ -7,7 +7,7 @@ import type { Reporting, ReportingDetailed } from '../domain/entities/reporting' type ReportingsFilter = { reportingType: string | undefined - seaFronts: string[] + seaFronts?: string[] sourcesType?: string[] startedAfterDateTime?: string startedBeforeDateTime?: string diff --git a/frontend/src/domain/entities/missions.ts b/frontend/src/domain/entities/missions.ts index e58d090412..741d0bc519 100644 --- a/frontend/src/domain/entities/missions.ts +++ b/frontend/src/domain/entities/missions.ts @@ -45,6 +45,12 @@ export const missionTypeEnum = { } } +export enum MissionTypeLabel { + AIR = 'Air', + LAND = 'Terre', + SEA = 'Mer' +} + export enum InfractionTypeEnum { WAITING = 'WAITING', WITHOUT_REPORT = 'WITHOUT_REPORT', @@ -182,6 +188,14 @@ export enum MissionStatusEnum { PENDING = 'PENDING', UPCOMING = 'UPCOMING' } + +export enum MissionStatusLabel { + CLOSED = 'Cloturée', + ENDED = 'Terminée', + PENDING = 'En cours', + UPCOMING = 'À venir' +} + export const missionStatusLabels = { CLOSED: { borderColor: THEME.color.slateGray, @@ -224,6 +238,11 @@ export const missionSourceEnum = { } } +export enum MissionSourceLabel { + MONITORENV = 'CACEM', + MONITORFISH = 'CNSP' +} + export const THEME_REQUIRE_PROTECTED_SPECIES = ['Police des espèces protégées et de leurs habitats (faune et flore)'] export const relevantCourtEnum = { diff --git a/frontend/src/domain/shared_slices/MissionFilters.ts b/frontend/src/domain/shared_slices/MissionFilters.ts index 1b2c225aae..024e6938eb 100644 --- a/frontend/src/domain/shared_slices/MissionFilters.ts +++ b/frontend/src/domain/shared_slices/MissionFilters.ts @@ -1,5 +1,6 @@ import { customDayjs as dayjs } from '@mtes-mct/monitor-ui' import { createSlice, type PayloadAction } from '@reduxjs/toolkit' +import { isEqual, omit } from 'lodash' import { persistReducer } from 'redux-persist' import storage from 'redux-persist/lib/storage' @@ -22,14 +23,14 @@ export enum MissionFiltersEnum { type MissionFilterValues = { hasFilters: boolean - selectedAdministrationNames: string[] - selectedControlUnitIds: number[] + selectedAdministrationNames: string[] | undefined + selectedControlUnitIds: number[] | undefined selectedMissionSource: string | undefined - selectedMissionTypes: string[] + selectedMissionTypes: string[] | undefined selectedPeriod: string - selectedSeaFronts: string[] - selectedStatuses: string[] - selectedThemes: string[] + selectedSeaFronts: string[] | undefined + selectedStatuses: string[] | undefined + selectedThemes: string[] | undefined startedAfter?: string startedBefore?: string } @@ -42,14 +43,14 @@ export type MissionFiltersState = { const INITIAL_STATE: MissionFiltersState = { hasFilters: false, - selectedAdministrationNames: [], - selectedControlUnitIds: [], + selectedAdministrationNames: undefined, + selectedControlUnitIds: undefined, selectedMissionSource: undefined, - selectedMissionTypes: [], + selectedMissionTypes: undefined, selectedPeriod: DATE_RANGE_LABEL.WEEK.value, - selectedSeaFronts: [], - selectedStatuses: [], - selectedThemes: [], + selectedSeaFronts: undefined, + selectedStatuses: undefined, + selectedThemes: undefined, startedAfter: SEVEN_DAYS_AGO, startedBefore: undefined } @@ -79,15 +80,14 @@ const missionFiltersSlice = createSlice({ return { ...state, [action.payload.key]: action.payload.value, - hasFilters: - (action.payload.value && action.payload.value.length > 0) || - state.selectedPeriod !== DATE_RANGE_LABEL.WEEK.value || - state.selectedAdministrationNames.length > 0 || - state.selectedControlUnitIds.length > 0 || - state.selectedMissionTypes.length > 0 || - state.selectedSeaFronts.length > 0 || - state.selectedStatuses.length > 0 || - state.selectedThemes.length > 0 + hasFilters: !isEqual( + omit(INITIAL_STATE, ['hasFilters', 'startedAfter', 'startedBefore']), + omit({ ...state, [action.payload.key]: action.payload.value }, [ + 'hasFilters', + 'startedAfter', + 'startedBefore' + ]) + ) } } } diff --git a/frontend/src/domain/shared_slices/ReportingsFilters.ts b/frontend/src/domain/shared_slices/ReportingsFilters.ts index 9418aa84ae..c016ca0eb2 100644 --- a/frontend/src/domain/shared_slices/ReportingsFilters.ts +++ b/frontend/src/domain/shared_slices/ReportingsFilters.ts @@ -31,14 +31,14 @@ type ReportingsFiltersSliceType = { actionsFilter?: string[] hasFilters: boolean periodFilter: string - seaFrontFilter: string[] - sourceFilter: SourceFilterProps[] - sourceTypeFilter: string[] + seaFrontFilter: string[] | undefined + sourceFilter: SourceFilterProps[] | undefined + sourceTypeFilter: string[] | undefined startedAfter: string startedBefore?: string statusFilter: string[] - subThemesFilter: string[] - themeFilter: string[] + subThemesFilter: string[] | undefined + themeFilter: string[] | undefined typeFilter?: string | undefined } @@ -46,14 +46,14 @@ const initialState: ReportingsFiltersSliceType = { actionsFilter: [], hasFilters: false, periodFilter: ReportingDateRangeEnum.DAY, - seaFrontFilter: [], - sourceFilter: [], - sourceTypeFilter: [], + seaFrontFilter: undefined, + sourceFilter: undefined, + sourceTypeFilter: undefined, startedAfter: LAST_24_HOURS, startedBefore: undefined, statusFilter: [StatusFilterEnum.IN_PROGRESS], - subThemesFilter: [], - themeFilter: [], + subThemesFilter: undefined, + themeFilter: undefined, typeFilter: undefined } diff --git a/frontend/src/domain/use_cases/missions/filters/isMissionPartOfSelectedAdministrationNames.ts b/frontend/src/domain/use_cases/missions/filters/isMissionPartOfSelectedAdministrationNames.ts index a419eaa4ca..28e5dfdbbe 100644 --- a/frontend/src/domain/use_cases/missions/filters/isMissionPartOfSelectedAdministrationNames.ts +++ b/frontend/src/domain/use_cases/missions/filters/isMissionPartOfSelectedAdministrationNames.ts @@ -2,8 +2,12 @@ import type { Mission } from '../../../entities/missions' export function isMissionPartOfSelectedAdministrationNames( mission: Mission, - selectedAdministrationNames: string[] + selectedAdministrationNames: string[] | undefined ): boolean { + if (!selectedAdministrationNames || selectedAdministrationNames.length === 0) { + return true + } + return selectedAdministrationNames.length ? !!mission.controlUnits.find(controlUnit => selectedAdministrationNames.includes(controlUnit.administration)) : true diff --git a/frontend/src/domain/use_cases/missions/filters/isMissionPartOfSelectedControlUnitIds.ts b/frontend/src/domain/use_cases/missions/filters/isMissionPartOfSelectedControlUnitIds.ts index 3f4f751637..feaa0c34a0 100644 --- a/frontend/src/domain/use_cases/missions/filters/isMissionPartOfSelectedControlUnitIds.ts +++ b/frontend/src/domain/use_cases/missions/filters/isMissionPartOfSelectedControlUnitIds.ts @@ -1,6 +1,13 @@ import type { Mission } from '../../../entities/missions' -export function isMissionPartOfSelectedControlUnitIds(mission: Mission, selectedControlUnitIds: number[]): boolean { +export function isMissionPartOfSelectedControlUnitIds( + mission: Mission, + selectedControlUnitIds: number[] | undefined +): boolean { + if (!selectedControlUnitIds || selectedControlUnitIds.length === 0) { + return true + } + return selectedControlUnitIds.length ? !!mission.controlUnits.find(controlUnit => selectedControlUnitIds.includes(controlUnit.id)) : true diff --git a/frontend/src/domain/use_cases/missions/filters/isMissionPartOfSelectedThemes.ts b/frontend/src/domain/use_cases/missions/filters/isMissionPartOfSelectedThemes.ts index 1431e5b59e..0ae7ba33de 100644 --- a/frontend/src/domain/use_cases/missions/filters/isMissionPartOfSelectedThemes.ts +++ b/frontend/src/domain/use_cases/missions/filters/isMissionPartOfSelectedThemes.ts @@ -1,7 +1,7 @@ import type { Mission } from '../../../entities/missions' -export function isMissionPartOfSelectedThemes(mission: Mission, selectedThemes: string[]) { - if (selectedThemes.length === 0) { +export function isMissionPartOfSelectedThemes(mission: Mission, selectedThemes: string[] | undefined) { + if (!selectedThemes || selectedThemes.length === 0) { return true } if (mission.envActions.length === 0) { diff --git a/frontend/src/domain/use_cases/reporting/filters/sourceFilterFunction.ts b/frontend/src/domain/use_cases/reporting/filters/sourceFilterFunction.ts index 0151f4dc66..6842b05ec4 100644 --- a/frontend/src/domain/use_cases/reporting/filters/sourceFilterFunction.ts +++ b/frontend/src/domain/use_cases/reporting/filters/sourceFilterFunction.ts @@ -1,8 +1,8 @@ import type { ReportingDetailed } from '../../../entities/reporting' import type { SourceFilterProps } from '../../../shared_slices/ReportingsFilters' -export function sourceFilterFunction(reporting: ReportingDetailed, sourceFilter: SourceFilterProps[]) { - if (sourceFilter.length === 0) { +export function sourceFilterFunction(reporting: ReportingDetailed, sourceFilter: SourceFilterProps[] | undefined) { + if (!sourceFilter || sourceFilter.length === 0) { return true } diff --git a/frontend/src/domain/use_cases/reporting/filters/subThemesFilterFunction.ts b/frontend/src/domain/use_cases/reporting/filters/subThemesFilterFunction.ts index 58aa90d8ba..21e8386168 100644 --- a/frontend/src/domain/use_cases/reporting/filters/subThemesFilterFunction.ts +++ b/frontend/src/domain/use_cases/reporting/filters/subThemesFilterFunction.ts @@ -1,7 +1,7 @@ import type { ReportingDetailed } from '../../../entities/reporting' -export function subThemesFilterFunction(reporting: ReportingDetailed, subThemesFilter: string[]) { - if (subThemesFilter.length === 0) { +export function subThemesFilterFunction(reporting: ReportingDetailed, subThemesFilter: string[] | undefined) { + if (!subThemesFilter || subThemesFilter.length === 0) { return true } diff --git a/frontend/src/domain/use_cases/reporting/filters/themeFilterFunction.ts b/frontend/src/domain/use_cases/reporting/filters/themeFilterFunction.ts index f9eb6550cb..09e0cb7905 100644 --- a/frontend/src/domain/use_cases/reporting/filters/themeFilterFunction.ts +++ b/frontend/src/domain/use_cases/reporting/filters/themeFilterFunction.ts @@ -1,7 +1,7 @@ import type { ReportingDetailed } from '../../../entities/reporting' -export function themeFilterFunction(reporting: ReportingDetailed, themeFilter: string[]) { - if (themeFilter.length === 0) { +export function themeFilterFunction(reporting: ReportingDetailed, themeFilter: string[] | undefined) { + if (!themeFilter || themeFilter.length === 0) { return true } diff --git a/frontend/src/features/Reportings/Filters/Map/index.tsx b/frontend/src/features/Reportings/Filters/Map/index.tsx index 634a8721ec..d88d400b2a 100644 --- a/frontend/src/features/Reportings/Filters/Map/index.tsx +++ b/frontend/src/features/Reportings/Filters/Map/index.tsx @@ -1,4 +1,4 @@ -import { DateRangePicker, Checkbox, SingleTag, Accent } from '@mtes-mct/monitor-ui' +import { CheckPicker, DateRangePicker, Checkbox, SingleTag, Accent } from '@mtes-mct/monitor-ui' import { forwardRef, useRef } from 'react' import styled from 'styled-components' @@ -6,7 +6,7 @@ import { ReportingSourceLabels } from '../../../../domain/entities/reporting' import { ReportingsFiltersEnum, reportingsFiltersActions } from '../../../../domain/shared_slices/ReportingsFilters' import { useAppDispatch } from '../../../../hooks/useAppDispatch' import { useAppSelector } from '../../../../hooks/useAppSelector' -import { OptionValue, StyledCheckPicker, StyledSelect, StyledStatusFilter } from '../style' +import { OptionValue, StyledSelect, StyledStatusFilter } from '../style' export function MapReportingsFiltersWithRef( { @@ -98,20 +98,20 @@ export function MapReportingsFiltersWithRef( - updateSourceTypeFilter(value)} + options={sourceTypeOptions} placeholder="Type de source" renderValue={() => sourceTypeFilter && {`Type (${sourceTypeFilter.length})`}} searchable={false} - size="sm" value={sourceTypeFilter} - valueKey="value" /> - {sourceTypeFilter.length > 0 && ( + {sourceTypeFilter && sourceTypeFilter.length > 0 && ( {sourceTypeFilter.map(sourceType => ( - updateSimpleFilter(value, ReportingsFiltersEnum.THEME_FILTER)} + options={themesListAsOptions} placeholder="Thématiques" renderValue={() => themeFilter && {`Thème (${themeFilter.length})`}} - size="sm" + searchable value={themeFilter} - valueKey="value" /> - {themeFilter.length > 0 && ( + {themeFilter && themeFilter.length > 0 && ( {themeFilter.map(theme => ( )} - updateSimpleFilter(value, ReportingsFiltersEnum.SUB_THEMES_FILTER)} + options={subThemesListAsOptions} placeholder="Sous-thématiques" renderValue={() => subThemesFilter && {`Sous-thème (${subThemesFilter.length})`}} - size="sm" + searchable value={subThemesFilter} - valueKey="value" /> - {subThemesFilter.length > 0 && ( + {subThemesFilter && subThemesFilter.length > 0 && ( {subThemesFilter.map(subTheme => ( state.reportingFilters ) const onDeleteTag = (valueToDelete: string | any, filterKey: ReportingsFiltersEnum, reportingFilter) => { const updatedFilter = reportingFilter.filter(unit => unit !== valueToDelete) - dispatch(reportingsFiltersActions.updateFilters({ key: filterKey, value: updatedFilter })) + dispatch( + reportingsFiltersActions.updateFilters({ + key: filterKey, + value: updatedFilter.length === 0 ? undefined : updatedFilter + }) + ) } - const hasNoFilterTags = - sourceTypeFilter.length === 0 && - sourceFilter.length === 0 && - themeFilter.length === 0 && - subThemesFilter.length === 0 && - seaFrontFilter.length === 0 - if (hasNoFilterTags) { + if (!hasFilters) { return null } return ( - - {sourceTypeFilter.length > 0 && + + {sourceTypeFilter && + sourceTypeFilter.length > 0 && sourceTypeFilter.map(sourceType => ( ))} - {sourceFilter.length > 0 && + {sourceFilter && + sourceFilter.length > 0 && sourceFilter.map(source => ( ))} - {themeFilter.length > 0 && + {themeFilter && + themeFilter.length > 0 && themeFilter.map(theme => ( ))} - {subThemesFilter.length > 0 && + {subThemesFilter && + subThemesFilter?.length > 0 && subThemesFilter.map(subTheme => ( ))} - {seaFrontFilter.length > 0 && + {seaFrontFilter && + seaFrontFilter.length > 0 && seaFrontFilter.map(seaFront => ( state.reportingFilters) const { dateRangeOptions, @@ -54,6 +61,32 @@ export function TableReportingsFiltersWithRef( typeOptions } = optionsList + const sourceCustomSearch = useMemo( + () => + new CustomSearch(sourceOptions as Option[], ['label'], { + cacheKey: 'REPORTINGS_LIST', + withCacheInvalidation: true + }), + [sourceOptions] + ) + const themeCustomSearch = useMemo( + () => + new CustomSearch(themesListAsOptions as Option[], ['label'], { + cacheKey: 'REPORTINGS_LIST', + withCacheInvalidation: true + }), + [themesListAsOptions] + ) + + const subThemeCustomSearch = useMemo( + () => + new CustomSearch(subThemesListAsOptions as Option[], ['label'], { + cacheKey: 'REPORTINGS_LIST', + withCacheInvalidation: true + }), + [subThemesListAsOptions] + ) + return ( <> @@ -88,33 +121,34 @@ export function TableReportingsFiltersWithRef( value={periodFilter} /> - updateSourceTypeFilter(value)} + options={sourceTypeOptions} placeholder="Type de source" renderValue={() => sourceTypeFilter && {`Type (${sourceTypeFilter.length})`}} - searchable={false} - size="sm" style={tagPickerStyle} value={sourceTypeFilter} - valueKey="value" /> - updateSimpleFilter(value, ReportingsFiltersEnum.SOURCE_FILTER)} + options={sourceOptions} + optionValueKey={'label' as any} placeholder="Source" renderValue={() => sourceFilter && {`Source (${sourceFilter.length})`}} - size="sm" style={tagPickerStyle} - value={sourceFilter} - valueKey="value" + value={sourceFilter as any} /> - updateSimpleFilter(value, ReportingsFiltersEnum.THEME_FILTER)} + options={themesListAsOptions} placeholder="Thématiques" renderValue={() => themeFilter && {`Thème (${themeFilter.length})`}} - size="sm" style={{ width: 311 }} value={themeFilter} - valueKey="value" /> - updateSimpleFilter(value, ReportingsFiltersEnum.SUB_THEMES_FILTER)} + options={subThemesListAsOptions} placeholder="Sous-thématiques" renderValue={() => subThemesFilter && {`Sous-thème (${subThemesFilter.length})`}} - size="sm" + searchable style={{ width: 311 }} value={subThemesFilter} - valueKey="value" /> - updateSimpleFilter(value, ReportingsFiltersEnum.SEA_FRONT_FILTER)} + options={seaFrontsOptions} placeholder="Facade" renderValue={() => seaFrontFilter && {`Facade (${seaFrontFilter.length})`}} - searchable={false} size="sm" style={tagPickerStyle} value={seaFrontFilter} - valueKey="value" /> diff --git a/frontend/src/features/Reportings/Filters/index.tsx b/frontend/src/features/Reportings/Filters/index.tsx index 69f60429c1..f216bcaf66 100644 --- a/frontend/src/features/Reportings/Filters/index.tsx +++ b/frontend/src/features/Reportings/Filters/index.tsx @@ -80,10 +80,10 @@ export function ReportingsFilters({ context = ReportingFilterContext.TABLE }: { ) const sourceOptions = useMemo(() => { - if (sourceTypeFilter.length === 1 && sourceTypeFilter[0] === ReportingSourceEnum.SEMAPHORE) { + if (sourceTypeFilter && sourceTypeFilter.length === 1 && sourceTypeFilter[0] === ReportingSourceEnum.SEMAPHORE) { return semaphoresAsOptions } - if (sourceTypeFilter.length === 1 && sourceTypeFilter[0] === ReportingSourceEnum.CONTROL_UNIT) { + if (sourceTypeFilter && sourceTypeFilter.length === 1 && sourceTypeFilter[0] === ReportingSourceEnum.CONTROL_UNIT) { return unitListAsOptions } @@ -213,7 +213,7 @@ export function ReportingsFilters({ context = ReportingFilterContext.TABLE }: { const updateSourceTypeFilter = types => { dispatch(reportingsFiltersActions.updateFilters({ key: ReportingsFiltersEnum.SOURCE_TYPE_FILTER, value: types })) - dispatch(reportingsFiltersActions.updateFilters({ key: ReportingsFiltersEnum.SOURCE_FILTER, value: [] })) + dispatch(reportingsFiltersActions.updateFilters({ key: ReportingsFiltersEnum.SOURCE_FILTER, value: undefined })) } const resetFilters = () => { diff --git a/frontend/src/features/Reportings/Filters/style.ts b/frontend/src/features/Reportings/Filters/style.ts index 50d3b7db12..c2675b33e1 100644 --- a/frontend/src/features/Reportings/Filters/style.ts +++ b/frontend/src/features/Reportings/Filters/style.ts @@ -1,5 +1,4 @@ import { Select } from '@mtes-mct/monitor-ui' -import { CheckPicker } from 'rsuite' import styled from 'styled-components' export const StyledStatusFilter = styled.div` @@ -16,11 +15,7 @@ export const StyledSelect = styled(Select)` top: 5px !important; } ` -export const StyledCheckPicker = styled(CheckPicker)` - .rs-picker-toggle-placeholder { - font-size: 13px !important; - } -` + export const StyledTagsContainer = styled.div<{ $withMargin: boolean }>` margin-top: ${p => (p.$withMargin ? '16px' : '0px')}; display: flex; diff --git a/frontend/src/features/layersSelector/search/LayerFilters.tsx b/frontend/src/features/layersSelector/search/LayerFilters.tsx index 21be88c523..1b51654a73 100644 --- a/frontend/src/features/layersSelector/search/LayerFilters.tsx +++ b/frontend/src/features/layersSelector/search/LayerFilters.tsx @@ -1,5 +1,5 @@ -import { Accent, SingleTag } from '@mtes-mct/monitor-ui' -import { Button, CheckPicker } from 'rsuite' +import { Accent, CheckPicker, SingleTag } from '@mtes-mct/monitor-ui' +import { Button } from 'rsuite' import styled from 'styled-components' export function LayerFilters({ @@ -28,10 +28,12 @@ export function LayerFilters({ return ( - filteredRegulatoryThemes && ( @@ -43,8 +45,8 @@ export function LayerFilters({ valueKey="value" /> - {filteredRegulatoryThemes.length > 0 && - filteredRegulatoryThemes.map(theme => ( + {filteredRegulatoryThemes?.length > 0 && + filteredRegulatoryThemes?.map(theme => ( - filteredAmpTypes && {`Type d'AMP (${filteredAmpTypes.length})`}} size="sm" @@ -67,15 +70,15 @@ export function LayerFilters({ valueKey="value" /> - {filteredAmpTypes.length > 0 && - filteredAmpTypes.map(type => ( + {filteredAmpTypes?.length > 0 && + filteredAmpTypes?.map(type => ( {type} ))} - {(filteredRegulatoryThemes?.length > 0 || filteredAmpTypes.length > 0) && ( + {(filteredRegulatoryThemes?.length > 0 || filteredAmpTypes?.length > 0) && ( Réinitialiser les filtres @@ -101,16 +104,6 @@ const ResetFilters = styled(Button)` padding: 0px; ` -const StyledCheckPicker = styled(CheckPicker)` - width: 100%; - .rs-picker-toggle { - background-color: ${p => p.theme.color.white} !important; - } - .rs-picker-toggle-placeholder { - font-size: 13px !important; - } -` - const OptionValue = styled.span` display: flex; overflow: hidden; diff --git a/frontend/src/features/layersSelector/search/index.tsx b/frontend/src/features/layersSelector/search/index.tsx index 6ba7163692..1289ba036a 100644 --- a/frontend/src/features/layersSelector/search/index.tsx +++ b/frontend/src/features/layersSelector/search/index.tsx @@ -86,14 +86,14 @@ export function LayerSearch({ isVisible }) { setTimeout(() => { isSearchThrottled.current = false - if (searchedText.length > 2 || ampTypes.length > 0 || geofilter) { + if (searchedText.length > 2 || ampTypes?.length > 0 || geofilter) { let searchedAMPS let itemSchema - if (searchedText.length > 2 || ampTypes.length > 0) { + if (searchedText.length > 2 || ampTypes?.length > 0) { const filterWithTextExpression = searchedText.length > 0 ? { $path: ['name'], $val: searchedText } : undefined const filterWithType = - ampTypes.length > 0 ? { $or: ampTypes.map(theme => ({ $path: 'type', $val: theme })) } : undefined + ampTypes?.length > 0 ? { $or: ampTypes.map(theme => ({ $path: 'type', $val: theme })) } : undefined const filterExpression = [filterWithTextExpression, filterWithType].filter(f => !!f) as Fuse.Expression[] diff --git a/frontend/src/features/missions/MissionsList/Filters/FilterTags.tsx b/frontend/src/features/missions/MissionsList/Filters/FilterTags.tsx index 82437c2eb6..80208df08a 100644 --- a/frontend/src/features/missions/MissionsList/Filters/FilterTags.tsx +++ b/frontend/src/features/missions/MissionsList/Filters/FilterTags.tsx @@ -38,12 +38,13 @@ export function FilterTags() { const nextSelectedValues = selectedValues.filter(selectedValue => selectedValue !== valueToDelete) as | string[] | number[] - dispatch(updateFilters({ key: filterKey, value: nextSelectedValues })) + dispatch(updateFilters({ key: filterKey, value: nextSelectedValues.length === 0 ? undefined : nextSelectedValues })) } return ( - {selectedAdministrationNames.length > 0 && + {selectedAdministrationNames && + selectedAdministrationNames?.length > 0 && selectedAdministrationNames.map(admin => ( ))} - {selectedControlUnitIds.length > 0 && + {selectedControlUnitIds && + selectedControlUnitIds?.length > 0 && selectedControlUnitIds.map(unit => ( controlUnit.id === unit)?.name || unit}`)} ))} - {selectedMissionTypes.length > 0 && + {selectedMissionTypes && + selectedMissionTypes?.length > 0 && selectedMissionTypes.map(type => ( ))} - {selectedSeaFronts.length > 0 && + {selectedSeaFronts && + selectedSeaFronts?.length > 0 && selectedSeaFronts.map(seaFront => ( ))} - {selectedStatuses.length > 0 && + {selectedStatuses && + selectedStatuses?.length > 0 && selectedStatuses.map(status => ( ))} - {selectedThemes.length > 0 && + {selectedThemes && + selectedThemes?.length > 0 && selectedThemes.map(theme => ( onDeleteTag(theme, MissionFiltersEnum.THEME_FILTER, selectedThemes)}> {String(`Thème ${theme}`)} diff --git a/frontend/src/features/missions/MissionsList/Filters/index.tsx b/frontend/src/features/missions/MissionsList/Filters/index.tsx index 180d9e6642..49d46840f1 100644 --- a/frontend/src/features/missions/MissionsList/Filters/index.tsx +++ b/frontend/src/features/missions/MissionsList/Filters/index.tsx @@ -4,10 +4,12 @@ import { DateRangePicker, type DateAsStringRange, useNewWindow, - getOptionsFromIdAndName + getOptionsFromIdAndName, + CheckPicker, + getOptionsFromLabelledEnum, + CustomSearch } from '@mtes-mct/monitor-ui' import { type MutableRefObject, useMemo, useRef, useState } from 'react' -import { CheckPicker } from 'rsuite' import styled from 'styled-components' import { FilterTags } from './FilterTags' @@ -16,7 +18,7 @@ import { RTK_DEFAULT_QUERY_OPTIONS } from '../../../../api/constants' import { useGetControlThemesQuery } from '../../../../api/controlThemesAPI' import { useGetLegacyControlUnitsQuery } from '../../../../api/legacyControlUnitsAPI' import { DateRangeEnum, DATE_RANGE_LABEL } from '../../../../domain/entities/dateRange' -import { missionSourceEnum, missionStatusLabels, missionTypeEnum } from '../../../../domain/entities/missions' +import { MissionSourceLabel, MissionTypeLabel, MissionStatusLabel } from '../../../../domain/entities/missions' import { seaFrontLabels } from '../../../../domain/entities/seaFrontType' import { MissionFiltersEnum, resetMissionFilters, updateFilters } from '../../../../domain/shared_slices/MissionFilters' import { useAppDispatch } from '../../../../hooks/useAppDispatch' @@ -46,26 +48,42 @@ export function MissionsTableFilters() { const unitPickerRef = useRef() as MutableRefObject const { data: administrations } = useGetAdministrationsQuery(undefined, RTK_DEFAULT_QUERY_OPTIONS) - const { data: legacyControlUnits } = useGetLegacyControlUnitsQuery(undefined, RTK_DEFAULT_QUERY_OPTIONS) + const { data: legacyControlUnits, isLoading } = useGetLegacyControlUnitsQuery(undefined, RTK_DEFAULT_QUERY_OPTIONS) const { data: controlThemes } = useGetControlThemesQuery() - const activeAdministrations = useMemo(() => (administrations || []).filter(isNotArchived), [administrations]) + const activeAdministrations = useMemo( + () => + (administrations || []).filter(isNotArchived).map(admin => ({ + label: admin.name, + value: admin.name + })), + [administrations] + ) const themesAsOptions = useMemo(() => getThemesAsListOptions(controlThemes), [controlThemes]) + const themeCustomSearch = useMemo(() => new CustomSearch(themesAsOptions, ['label']), [themesAsOptions]) + const controlUnitsAsOptions = useMemo(() => { const activeControlUnits = (legacyControlUnits || []).filter(isNotArchived) - const selectableControlUnits = activeControlUnits.filter(activeControlUnit => - selectedAdministrationNames.length ? selectedAdministrationNames.includes(activeControlUnit.administration) : true + const selectableControlUnits = activeControlUnits?.filter(activeControlUnit => + selectedAdministrationNames?.length + ? selectedAdministrationNames.includes(activeControlUnit.administration) + : true ) return getOptionsFromIdAndName(selectableControlUnits) || [] }, [legacyControlUnits, selectedAdministrationNames]) + const controlUnitCustomSearch = useMemo( + () => new CustomSearch(controlUnitsAsOptions, ['label']), + [controlUnitsAsOptions] + ) + const dateRangesAsOptions = Object.values(DATE_RANGE_LABEL) - const missionStatusesAsOptions = Object.values(missionStatusLabels) - const missionTypesAsOptions = Object.values(missionTypeEnum) - const missionSourcesAsOptions = Object.values(missionSourceEnum) + const missionStatusesAsOptions = getOptionsFromLabelledEnum(MissionStatusLabel) + const missionTypesAsOptions = getOptionsFromLabelledEnum(MissionTypeLabel) + const missionSourcesAsOptions = getOptionsFromLabelledEnum(MissionSourceLabel) const seaFrontsAsOptions = Object.values(seaFrontLabels) const onUpdatePeriodFilter = (nextDateRange: DateRangeEnum | undefined) => { @@ -143,6 +161,9 @@ export function MissionsTableFilters() { setIsCustomPeriodVisible(false) dispatch(resetMissionFilters()) } + if (isLoading) { + return
Chargement
+ } return ( <> @@ -173,12 +194,15 @@ export function MissionsTableFilters() { style={tagPickerStyle} value={selectedMissionSource} /> - selectedAdministrationNames && ( @@ -186,83 +210,83 @@ export function MissionsTableFilters() { ) } searchable - size="sm" style={tagPickerStyle} value={selectedAdministrationNames} - valueKey="name" /> - onUpdateSimpleFilter(value, MissionFiltersEnum.UNIT_FILTER)} + options={controlUnitsAsOptions as any} placeholder="Unité" renderValue={() => selectedControlUnitIds && {`Unité (${selectedControlUnitIds.length})`} } - searchable size="sm" style={tagPickerStyle} value={selectedControlUnitIds} - valueKey="value" /> - onUpdateSimpleFilter(value, MissionFiltersEnum.TYPE_FILTER)} + options={missionTypesAsOptions} placeholder="Type de mission" renderValue={() => selectedMissionTypes && {`Type (${selectedMissionTypes.length})`} } - searchable={false} size="sm" style={tagPickerStyle} value={selectedMissionTypes} - valueKey="code" /> - onUpdateSimpleFilter(value, MissionFiltersEnum.SEA_FRONT_FILTER)} + options={seaFrontsAsOptions} placeholder="Facade" renderValue={() => selectedSeaFronts && {`Facade (${selectedSeaFronts.length})`}} - searchable={false} size="sm" style={tagPickerStyle} value={selectedSeaFronts} - valueKey="value" /> - onUpdateSimpleFilter(value, MissionFiltersEnum.STATUS_FILTER)} + options={missionStatusesAsOptions} placeholder="Statut" renderValue={() => selectedStatuses && {`Statut (${selectedStatuses.length})`}} - searchable={false} size="sm" style={tagPickerStyle} value={selectedStatuses} - valueKey="code" /> - onUpdateSimpleFilter(value, MissionFiltersEnum.THEME_FILTER)} + options={themesAsOptions} placeholder="Thématique" renderValue={() => selectedThemes && {`Theme (${selectedThemes.length})`}} size="sm" style={tagPickerStyle} value={selectedThemes} - valueKey="value" /> @@ -320,11 +344,7 @@ const StyledSelect = styled(Select)` top: 5px !important; } ` -const StyledCheckPicker = styled(CheckPicker)` - .rs-picker-toggle-placeholder { - font-size: 13px !important; - } -` + const StyledTagsContainer = styled.div` align-items: baseline; display: flex; diff --git a/frontend/src/hooks/useGetFilteredMissionsQuery.ts b/frontend/src/hooks/useGetFilteredMissionsQuery.ts index 55c524ed1d..e5f62ba32f 100644 --- a/frontend/src/hooks/useGetFilteredMissionsQuery.ts +++ b/frontend/src/hooks/useGetFilteredMissionsQuery.ts @@ -43,9 +43,9 @@ export const useGetFilteredMissionsQuery = () => { } if ( - selectedAdministrationNames.length === 0 && - selectedControlUnitIds.length === 0 && - selectedThemes.length === 0 + selectedAdministrationNames?.length === 0 && + selectedControlUnitIds?.length === 0 && + selectedThemes?.length === 0 ) { return missions }