diff --git a/frontend/cypress/e2e/side_window/dashboard/edit_dashboard.spec.ts b/frontend/cypress/e2e/side_window/dashboard/edit_dashboard.spec.ts index 6a481d0ee..7097c8ba9 100644 --- a/frontend/cypress/e2e/side_window/dashboard/edit_dashboard.spec.ts +++ b/frontend/cypress/e2e/side_window/dashboard/edit_dashboard.spec.ts @@ -110,4 +110,31 @@ context('Side Window > Dashboard > Edit Dashboard', () => { // Selected controlUnits should be visible cy.getDataCy('dashboard-control-unit-accordion-10002').contains('DML 2A - DDTM').should('be.visible') }) + + it('Should select/deselect all in each bloc', () => { + const id = 'e1e99b92-1e61-4f9f-9cbf-8cfae2395d41' + cy.getDataCy(`edit-dashboard-${id}`).click({ force: true }) + + cy.intercept('GET', `/bff/v1/dashboards/${id}`).as('editDashboard') + + // Tab should be visible + cy.getDataCy('dashboard-1').contains('Dashboard 1') + + cy.wait(250) + + // from partially selection to fully selected + cy.get('h2').contains('Zones réglementaires').parent().clickButton('Tout sélectionner', { withoutScroll: true }) + // no button if there is no area + cy.get('h2').contains('Zones AMP').parent().get('Tout sélectionner').should('not.exist') + cy.get('h2').contains('Zones de vigilance').parent().clickButton('Tout désélectionner', { withoutScroll: true }) + cy.get('h2').contains('Signalements').parent().clickButton('Tout sélectionner', { withoutScroll: true }) + + cy.clickButton('Prévisualiser la sélection', { withoutScroll: true }) + cy.wait(250) + // because result list have a separator so we need to multiply the results by 2 + cy.getDataCy('dashboard-selected-regulatory-result-group').should('have.length', 9) + cy.getDataCy('dashboard-selected-amp-result-group').should('not.exist') + cy.get('[data-cy^="dashboard-selected-vigilance-areas-zone-"]').should('not.exist') + cy.get('[data-cy^="dashboard-selected-reporting-"]').should('have.length', 3) + }) }) diff --git a/frontend/src/features/Dashboard/components/DashboardForm/Accordion.tsx b/frontend/src/features/Dashboard/components/DashboardForm/Accordion.tsx index 1001ff64c..7dd5ec3be 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/Accordion.tsx +++ b/frontend/src/features/Dashboard/components/DashboardForm/Accordion.tsx @@ -53,7 +53,7 @@ export const AccordionHeader = styled.header` export const TitleContainer = styled.div` align-items: center; display: flex; - gap: 16px; + gap: 10px; ` export const Title = styled.h2` font-size: 16px; diff --git a/frontend/src/features/Dashboard/components/DashboardForm/Amps/ListLayerGroup.tsx b/frontend/src/features/Dashboard/components/DashboardForm/Amps/ListLayerGroup.tsx index 673bfedf3..8febe7385 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/Amps/ListLayerGroup.tsx +++ b/frontend/src/features/Dashboard/components/DashboardForm/Amps/ListLayerGroup.tsx @@ -11,6 +11,7 @@ import { useState } from 'react' import styled from 'styled-components' import { Layer } from './Layer' +import { getPinIcon, getSelectionState } from '../ToggleSelectAll/utils' type ResultListLayerGroupProps = { groupName: string @@ -25,13 +26,13 @@ export function ListLayerGroup({ groupName, isSelected = false, layerIds, select const totalNumberOfZones = useAppSelector(state => getNumberOfAMPByGroupName(state, groupName)) const zonesSelected = intersection(selectedAmpIds, layerIds) - const allTopicZonesAreChecked = zonesSelected?.length === layerIds?.length + const topicSelectionState = getSelectionState(zonesSelected, layerIds) const handleCheckAllZones = e => { e.stopPropagation() const payload = { itemIds: layerIds, type: Dashboard.Block.AMP } - if (allTopicZonesAreChecked) { + if (topicSelectionState === 'ALL') { dispatch(dashboardActions.removeItems(payload)) } else { dispatch(dashboardActions.addItems(payload)) @@ -69,14 +70,7 @@ export function ListLayerGroup({ groupName, isSelected = false, layerIds, select title="Supprimer la/les zone(s)" /> ) : ( - + getPinIcon(topicSelectionState, handleCheckAllZones) )} diff --git a/frontend/src/features/Dashboard/components/DashboardForm/Amps/index.tsx b/frontend/src/features/Dashboard/components/DashboardForm/Amps/index.tsx index 16cc92104..408734e9f 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/Amps/index.tsx +++ b/frontend/src/features/Dashboard/components/DashboardForm/Amps/index.tsx @@ -1,7 +1,6 @@ import { useGetAMPsQuery } from '@api/ampsAPI' import { dashboardActions, getOpenedPanel } from '@features/Dashboard/slice' import { Dashboard } from '@features/Dashboard/types' -import { getSelectionState, handleSelection } from '@features/Dashboard/utils' import { useAppDispatch } from '@hooks/useAppDispatch' import { useAppSelector } from '@hooks/useAppSelector' import { pluralize } from '@mtes-mct/monitor-ui' @@ -11,9 +10,11 @@ import styled from 'styled-components' import { Accordion, Title, TitleContainer } from '../Accordion' import { SelectedAccordion } from '../SelectedAccordion' -import { SelectedLayerList, StyledLayerList } from '../style' +import { ResultNumber, SelectedLayerList, StyledLayerList } from '../style' +import { StyledToggleSelectAll } from '../ToggleSelectAll' import { ListLayerGroup } from './ListLayerGroup' import { AmpPanel } from './Panel' +import { getSelectionState, handleSelection } from '../ToggleSelectAll/utils' import type { AMP, AMPFromAPI } from 'domain/entities/AMPs' @@ -67,8 +68,9 @@ export const Amps = forwardRef( title={ Zones AMP - {amps.length !== 0 && ( - {`(${amps.length} ${pluralize('résultat', amps.length)})`} + {(amps.length !== 0 || selectedAmpIds.length !== 0) && ( + handleSelection({ allIds: amps.map(amp => amp.id), diff --git a/frontend/src/features/Dashboard/components/DashboardForm/Comments/index.tsx b/frontend/src/features/Dashboard/components/DashboardForm/Comments/index.tsx index 2521d0cb3..eb72fc45b 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/Comments/index.tsx +++ b/frontend/src/features/Dashboard/components/DashboardForm/Comments/index.tsx @@ -5,7 +5,7 @@ import { debounce } from 'lodash' import { forwardRef, useCallback, useMemo, useState } from 'react' import styled from 'styled-components' -import { Accordion } from '../Accordion' +import { Accordion, Title, TitleContainer } from '../Accordion' type CommentsProps = { comments: string | undefined @@ -39,7 +39,11 @@ export const Comments = forwardRef( + Commentaires + + } titleRef={ref} > ( return (
- + + Unités + + } + titleRef={ref} + > getNumberOfRegulatoryLayerZonesByGroupName(state, groupName)) const zonesSelected = intersection(selectedRegulatoryAreaIds, layerIds) - const allTopicZonesAreChecked = zonesSelected?.length === layerIds?.length + const topicSelectionState = getSelectionState(zonesSelected, layerIds) const handleCheckAllZones = e => { e.stopPropagation() const payload = { itemIds: layerIds, type: Dashboard.Block.REGULATORY_AREAS } - if (allTopicZonesAreChecked) { + if (topicSelectionState === 'ALL') { dispatch(dashboardActions.removeItems(payload)) } else { dispatch(dashboardActions.addItems(payload)) @@ -73,14 +74,7 @@ export function ListLayerGroup({ title="Supprimer la/les zone(s)" /> ) : ( - + getPinIcon(topicSelectionState, handleCheckAllZones) )} diff --git a/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/index.tsx b/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/index.tsx index dad196cb4..1dc74c076 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/index.tsx +++ b/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/index.tsx @@ -1,7 +1,6 @@ import { useGetRegulatoryLayersQuery } from '@api/regulatoryLayersAPI' import { dashboardActions, getOpenedPanel } from '@features/Dashboard/slice' import { Dashboard } from '@features/Dashboard/types' -import { getSelectionState, handleSelection } from '@features/Dashboard/utils' import { useAppDispatch } from '@hooks/useAppDispatch' import { useAppSelector } from '@hooks/useAppSelector' import { pluralize } from '@mtes-mct/monitor-ui' @@ -11,10 +10,11 @@ import styled from 'styled-components' import { Accordion, Title, TitleContainer } from '../Accordion' import { SelectedAccordion } from '../SelectedAccordion' -import { SelectedLayerList, StyledLayerList } from '../style' -import { ToggleSelectAll } from '../ToggleSelectAll' +import { ResultNumber, SelectedLayerList, StyledLayerList } from '../style' import { ListLayerGroup } from './ListLayerGroup' import { RegulatoryPanel } from './Panel' +import { StyledToggleSelectAll } from '../ToggleSelectAll' +import { getSelectionState, handleSelection } from '../ToggleSelectAll/utils' import type { RegulatoryLayerCompactFromAPI } from 'domain/entities/regulatory' @@ -78,8 +78,12 @@ export const RegulatoryAreas = forwardRef Zones réglementaires - {regulatoryAreas.length !== 0 && ( - {`(${regulatoryAreas.length} ${pluralize( + 'résultat', + regulatoryAreas.length + )})`} + {(regulatoryAreas.length !== 0 || selectedRegulatoryAreaIds.length !== 0) && ( + handleSelection({ allIds: regulatoryAreas.map(amp => amp.id), diff --git a/frontend/src/features/Dashboard/components/DashboardForm/Reportings/index.tsx b/frontend/src/features/Dashboard/components/DashboardForm/Reportings/index.tsx index 196db5480..cb9e91c95 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/Reportings/index.tsx +++ b/frontend/src/features/Dashboard/components/DashboardForm/Reportings/index.tsx @@ -1,7 +1,6 @@ import { useGetReportingsByIdsQuery } from '@api/reportingsAPI' import { dashboardActions } from '@features/Dashboard/slice' import { Dashboard } from '@features/Dashboard/types' -import { getSelectionState, handleSelection } from '@features/Dashboard/utils' import { useAppDispatch } from '@hooks/useAppDispatch' import { pluralize } from '@mtes-mct/monitor-ui' import { forwardRef, useEffect, useMemo, useState } from 'react' @@ -9,9 +8,11 @@ import styled from 'styled-components' import { Accordion, Title, TitleContainer } from '../Accordion' import { SelectedAccordion } from '../SelectedAccordion' +import { ResultNumber } from '../style' import { Filters } from './Filters' import { Layer } from './Layer' -import { ToggleSelectAll } from '../ToggleSelectAll' +import { StyledToggleSelectAll } from '../ToggleSelectAll' +import { getSelectionState, handleSelection } from '../ToggleSelectAll/utils' import type { Reporting } from 'domain/entities/reporting' @@ -52,8 +53,9 @@ export const Reportings = forwardRef( title={ Signalements - {reportings.length !== 0 && ( - {`(${reportings.length} ${pluralize('résultat', reportings.length)})`} + {(reportings.length !== 0 || selectedReportingIds.length !== 0) && ( + handleSelection({ allIds: reportings.map(reporting => +reporting.id), diff --git a/frontend/src/features/Dashboard/components/DashboardForm/TerritorialPressure/index.tsx b/frontend/src/features/Dashboard/components/DashboardForm/TerritorialPressure/index.tsx index 9772d4d11..cf58ae41a 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/TerritorialPressure/index.tsx +++ b/frontend/src/features/Dashboard/components/DashboardForm/TerritorialPressure/index.tsx @@ -5,7 +5,7 @@ import { Icon } from '@mtes-mct/monitor-ui' import { forwardRef, useMemo } from 'react' import styled from 'styled-components' -import { Accordion } from '../Accordion' +import { Accordion, Title } from '../Accordion' // Id like 199, 216, 197 can be changes if dashboards in metabase changes const AMP_LINK = '/dashboard/199-effort-de-surveillance-et-de-controle-en-amp?' @@ -58,7 +58,7 @@ export const TerritorialPressure = forwardRef - Pression territoriale des contrôles et surveillances + Pression territoriale des contrôles et surveillances Les liens suivants envoient vers des tableaux Metabase montrant la pression territoriale sur les zones REG, les AMP ou à l’échelle du département. diff --git a/frontend/src/features/Dashboard/components/DashboardForm/ToggleSelectAll/index.tsx b/frontend/src/features/Dashboard/components/DashboardForm/ToggleSelectAll/index.tsx index d59b64d7e..bebec9adc 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/ToggleSelectAll/index.tsx +++ b/frontend/src/features/Dashboard/components/DashboardForm/ToggleSelectAll/index.tsx @@ -1,35 +1,37 @@ import { Icon, THEME } from '@mtes-mct/monitor-ui' +import styled from 'styled-components' -import { SelectedPinButton } from '../style' +import { SelectedPinButton } from './style' export type SelectionState = 'PARTIAL' | 'ALL' | 'NONE' type ToggleSelectAllProps = { + className?: string onSelection: () => void selectionState: SelectionState } -export function ToggleSelectAll({ onSelection, selectionState }: ToggleSelectAllProps) { +export function ToggleSelectAll({ className, onSelection, selectionState }: ToggleSelectAllProps) { const getIcon = () => { switch (selectionState) { case 'ALL': return ( <> - - Désélectionner tous + + Tout désélectionner ) case 'PARTIAL': return ( <> - - Sélectionner tous + + Tout sélectionner ) case 'NONE': return ( <> - - Sélectionner tous + + Tout sélectionner ) default: @@ -39,6 +41,7 @@ export function ToggleSelectAll({ onSelection, selectionState }: ToggleSelectAll return ( { e.stopPropagation() onSelection() @@ -49,3 +52,7 @@ export function ToggleSelectAll({ onSelection, selectionState }: ToggleSelectAll ) } + +export const StyledToggleSelectAll = styled(ToggleSelectAll)` + margin-left: 6px; +` diff --git a/frontend/src/features/Dashboard/components/DashboardForm/ToggleSelectAll/style.ts b/frontend/src/features/Dashboard/components/DashboardForm/ToggleSelectAll/style.ts new file mode 100644 index 000000000..cb43edc79 --- /dev/null +++ b/frontend/src/features/Dashboard/components/DashboardForm/ToggleSelectAll/style.ts @@ -0,0 +1,9 @@ +import styled from 'styled-components' + +export const SelectedPinButton = styled.button` + background-color: ${p => p.theme.color.white}; + color: ${p => p.theme.color.slateGray}; + display: flex; + gap: 4px; + text-decoration: underline; +` diff --git a/frontend/src/features/Dashboard/components/DashboardForm/ToggleSelectAll/utils.tsx b/frontend/src/features/Dashboard/components/DashboardForm/ToggleSelectAll/utils.tsx new file mode 100644 index 000000000..5d16d0338 --- /dev/null +++ b/frontend/src/features/Dashboard/components/DashboardForm/ToggleSelectAll/utils.tsx @@ -0,0 +1,86 @@ +import { Accent, Icon, IconButton, THEME } from '@mtes-mct/monitor-ui' +import { difference, intersection } from 'lodash' + +import type { SelectionState } from '.' +import type { Dashboard } from '@features/Dashboard/types' + +export function getSelectionState(selectedIds: number[], allIds: number[]) { + if ( + allIds.length === 0 || + (selectedIds.length > 0 && + selectedIds.length >= allIds.length && + selectedIds.some(selectedId => allIds.includes(selectedId))) + ) { + return 'ALL' + } + + if (selectedIds.some(selectedId => allIds.includes(selectedId))) { + return 'PARTIAL' + } + + return 'NONE' +} + +type SelectionParams = { + allIds: number[] + onRemove: (payload: { itemIds: number[]; type: Dashboard.Block }) => void + onSelect: (payload: { itemIds: number[]; type: Dashboard.Block }) => void + selectedIds: number[] + selectionState: SelectionState + type: Dashboard.Block +} + +export function handleSelection({ allIds, onRemove, onSelect, selectedIds, selectionState, type }: SelectionParams) { + const itemIds = selectionState === 'ALL' ? intersection(allIds, selectedIds) : difference(allIds, selectedIds) + + const payload = { itemIds, type } + + if (selectionState === 'ALL') { + onRemove(payload) + } else { + onSelect(payload) + } +} + +export const getPinIcon = ( + topicSelectionState: SelectionState, + onClick: (event: React.MouseEvent) => void +) => { + switch (topicSelectionState) { + case 'ALL': + return ( + + ) + case 'PARTIAL': + return ( + + ) + case 'NONE': + return ( + + ) + default: + return undefined + } +} diff --git a/frontend/src/features/Dashboard/components/DashboardForm/Toolbar/Filters/index.tsx b/frontend/src/features/Dashboard/components/DashboardForm/Toolbar/Filters/index.tsx index 7b89afde7..fa8ace8da 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/Toolbar/Filters/index.tsx +++ b/frontend/src/features/Dashboard/components/DashboardForm/Toolbar/Filters/index.tsx @@ -17,7 +17,7 @@ import { useMemo } from 'react' import styled from 'styled-components' import { dashboardFiltersActions } from '../../slice' -import { SelectedPinButton } from '../../style' +import { SelectedPinButton } from '../../ToggleSelectAll/style' type FiltersProps = { dashboard: DashboardType diff --git a/frontend/src/features/Dashboard/components/DashboardForm/VigilanceAreas/index.tsx b/frontend/src/features/Dashboard/components/DashboardForm/VigilanceAreas/index.tsx index 804642e64..b2b0f7142 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/VigilanceAreas/index.tsx +++ b/frontend/src/features/Dashboard/components/DashboardForm/VigilanceAreas/index.tsx @@ -1,7 +1,6 @@ import { useGetVigilanceAreasQuery } from '@api/vigilanceAreasAPI' import { dashboardActions, getOpenedPanel } from '@features/Dashboard/slice' import { Dashboard } from '@features/Dashboard/types' -import { getSelectionState, handleSelection } from '@features/Dashboard/utils' import { VigilanceArea } from '@features/VigilanceArea/types' import { useAppDispatch } from '@hooks/useAppDispatch' import { useAppSelector } from '@hooks/useAppSelector' @@ -11,10 +10,11 @@ import styled from 'styled-components' import { Accordion, Title, TitleContainer } from '../Accordion' import { SelectedAccordion } from '../SelectedAccordion' -import { SelectedLayerList, StyledLayerList } from '../style' -import { ToggleSelectAll } from '../ToggleSelectAll' +import { ResultNumber, SelectedLayerList, StyledLayerList } from '../style' +import { StyledToggleSelectAll } from '../ToggleSelectAll' import { Layer } from './Layer' import { Panel } from './Panel' +import { getSelectionState, handleSelection } from '../ToggleSelectAll/utils' type VigilanceAreasProps = { columnWidth: number @@ -73,11 +73,18 @@ export const VigilanceAreas = forwardRef( title={ Zones de vigilance - {vigilanceAreas.length !== 0 && ( - {`(${vigilanceAreas.length} ${pluralize( + 'résultat', + vigilanceAreas.length + )})`} + {(vigilanceAreas.length !== 0 || selectedVigilanceAreaIds.length !== 0) && ( + handleSelection({ - allIds: vigilanceAreas.map(vigilanceArea => vigilanceArea.id), + allIds: vigilanceAreas.map( + (vigilanceArea: VigilanceArea.VigilanceAreaFromApi | VigilanceArea.VigilanceAreaLayer) => + vigilanceArea.id + ), onRemove: payload => dispatch(dashboardActions.removeItems(payload)), onSelect: payload => dispatch(dashboardActions.addItems(payload)), selectedIds: selectedVigilanceAreaIds, diff --git a/frontend/src/features/Dashboard/components/DashboardForm/style.ts b/frontend/src/features/Dashboard/components/DashboardForm/style.ts index 8b5649408..a1ad5104f 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/style.ts +++ b/frontend/src/features/Dashboard/components/DashboardForm/style.ts @@ -1,4 +1,5 @@ import { LayerSelector } from '@features/layersSelector/utils/LayerSelector.style' +import { THEME } from '@mtes-mct/monitor-ui' import styled from 'styled-components' export const SelectedLayerList = styled.ul` @@ -7,12 +8,11 @@ export const SelectedLayerList = styled.ul` max-height: 100%; color: ${p => p.theme.color.slateGray}; ` -export const SelectedPinButton = styled.button` - background-color: ${p => p.theme.color.white}; - color: ${p => p.theme.color.slateGray}; - display: flex; - gap: 4px; - text-decoration: underline; + +export const ResultNumber = styled.small` + font-size: 13px; + font-weight: regular; + color: ${THEME.color.slateGray}; ` export const StyledLayerList = styled(LayerSelector.LayerList)` diff --git a/frontend/src/features/Dashboard/components/DashboardsList/index.tsx b/frontend/src/features/Dashboard/components/DashboardsList/index.tsx index 7b5df3a9f..d029b6503 100644 --- a/frontend/src/features/Dashboard/components/DashboardsList/index.tsx +++ b/frontend/src/features/Dashboard/components/DashboardsList/index.tsx @@ -14,7 +14,7 @@ export function DashboardsList() { Tableaux de bord - + {dashboards?.length ?? '0'} Tableau{dashboards && dashboards.length > 1 ? 'x' : ''} diff --git a/frontend/src/features/Dashboard/components/MenuButton.tsx b/frontend/src/features/Dashboard/components/MenuButton.tsx index acc2db21e..7f78bc648 100644 --- a/frontend/src/features/Dashboard/components/MenuButton.tsx +++ b/frontend/src/features/Dashboard/components/MenuButton.tsx @@ -76,7 +76,7 @@ export function DashboardMenuButton() { ) : ( - + diff --git a/frontend/src/features/Dashboard/utils.ts b/frontend/src/features/Dashboard/utils.tsx similarity index 64% rename from frontend/src/features/Dashboard/utils.ts rename to frontend/src/features/Dashboard/utils.tsx index 51d8b4137..1ddd469a0 100644 --- a/frontend/src/features/Dashboard/utils.ts +++ b/frontend/src/features/Dashboard/utils.tsx @@ -3,9 +3,7 @@ import { regulatoryLayersAPI } from '@api/regulatoryLayersAPI' import { reportingsAPI } from '@api/reportingsAPI' import { vigilanceAreasAPI } from '@api/vigilanceAreasAPI' import { isCypress } from '@utils/isCypress' -import { difference } from 'lodash' -import type { SelectionState } from './components/DashboardForm/ToggleSelectAll' import type { Dashboard } from './types' import type { HomeRootState } from '@store/index' import type { Action } from 'redux' @@ -44,40 +42,3 @@ export async function populateExtractAreaFromApi( ) } } - -export function getSelectionState(selectedIds: number[], allIds: number[]) { - if ( - selectedIds.length > 0 && - selectedIds.length >= allIds.length && - selectedIds.every(selectedId => allIds.includes(selectedId)) - ) { - return 'ALL' - } - - if (selectedIds.some(selectedId => allIds.includes(selectedId))) { - return 'PARTIAL' - } - - return 'NONE' -} - -type SelectionParams = { - allIds: number[] - onRemove: (payload: { itemIds: number[]; type: Dashboard.Block }) => void - onSelect: (payload: { itemIds: number[]; type: Dashboard.Block }) => void - selectedIds: number[] - selectionState: SelectionState - type: Dashboard.Block -} - -export function handleSelection({ allIds, onRemove, onSelect, selectedIds, selectionState, type }: SelectionParams) { - const itemIds = selectionState === 'ALL' ? selectedIds : difference(allIds, selectedIds) - - const payload = { itemIds, type } - - if (selectionState === 'ALL') { - onRemove(payload) - } else { - onSelect(payload) - } -}