diff --git a/frontend/src/features/Dashboard/components/DashboardForm/Accordion.tsx b/frontend/src/features/Dashboard/components/DashboardForm/Accordion.tsx index c3ac94eb7c..cb0d73aea0 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/Accordion.tsx +++ b/frontend/src/features/Dashboard/components/DashboardForm/Accordion.tsx @@ -1,4 +1,4 @@ -import { Accent, Icon, IconButton, Size } from '@mtes-mct/monitor-ui' +import { Accent, Icon, IconButton } from '@mtes-mct/monitor-ui' import styled from 'styled-components' type AccordionProps = { @@ -22,7 +22,6 @@ export function Accordion({ children, headerButton, isExpanded, setExpandedAccor accent={Accent.TERTIARY} Icon={Icon.Chevron} onClick={setExpandedAccordion} - size={Size.SMALL} /> @@ -39,7 +38,7 @@ const StyledIconButton = styled(IconButton)<{ $isExpanded: boolean }>` transform: ${({ $isExpanded }) => ($isExpanded ? 'rotate(180deg)' : 'rotate(0deg)')}; transition: transform 0.3s; ` -const AccordionHeader = styled.div` +const AccordionHeader = styled.header` display: flex; justify-content: space-between; padding: 24px; diff --git a/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/Layer.tsx b/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/Layer.tsx index 1857d0911b..5dc9d40be4 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/Layer.tsx +++ b/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/Layer.tsx @@ -7,6 +7,7 @@ import { Accent, Icon, IconButton, THEME } from '@mtes-mct/monitor-ui' import { transformExtent } from 'ol/proj' import Projection from 'ol/proj/Projection' import { createRef } from 'react' +import styled from 'styled-components' import { useGetRegulatoryLayersQuery } from '../../../../../api/regulatoryLayersAPI' import { MonitorEnvLayers } from '../../../../../domain/entities/layers/constants' @@ -16,10 +17,11 @@ import { useAppDispatch } from '../../../../../hooks/useAppDispatch' type RegulatoryLayerProps = { dashboardId: number + isSelected: boolean layerId: number } -export function Layer({ dashboardId, layerId }: RegulatoryLayerProps) { +export function Layer({ dashboardId, isSelected, layerId }: RegulatoryLayerProps) { const dispatch = useAppDispatch() const ref = createRef() @@ -54,16 +56,17 @@ export function Layer({ dashboardId, layerId }: RegulatoryLayerProps) { } } + const removeZone = e => { + e.stopPropagation() + dispatch(dashboardActions.removeItems({ itemIds: [layerId], type: Dashboard.Block.REGULATORY_AREAS })) + } + const toggleZoneMetadata = () => { dispatch(dashboardActions.setDashboardPanel({ id: layerId, type: Dashboard.Block.REGULATORY_AREAS })) } return ( - + - + {isSelected ? ( + + ) : ( + + )} - + ) } + +const StyledLayer = styled(LayerSelector.Layer)` + padding-left: 24px; + padding-right: 24px; +` diff --git a/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/ListLayerGroup.tsx b/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/ListLayerGroup.tsx index a59ef66080..152b05d3cf 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/ListLayerGroup.tsx +++ b/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/ListLayerGroup.tsx @@ -8,15 +8,17 @@ import { Accent, Icon, IconButton, THEME } from '@mtes-mct/monitor-ui' import { getTitle } from 'domain/entities/layers/utils' import { includes, intersection } from 'lodash' import { useState } from 'react' +import styled from 'styled-components' import { Layer } from './Layer' type ResultListLayerGroupProps = { dashboardId: number groupName: string + isSelected?: boolean layerIds: number[] } -export function ListLayerGroup({ dashboardId, groupName, layerIds }: ResultListLayerGroupProps) { +export function ListLayerGroup({ dashboardId, groupName, isSelected = false, layerIds }: ResultListLayerGroupProps) { const dispatch = useAppDispatch() const [zonesAreOpen, setZonesAreOpen] = useState(false) @@ -40,34 +42,55 @@ export function ListLayerGroup({ dashboardId, groupName, layerIds }: ResultListL } } + const removeAllZones = e => { + e.stopPropagation() + const payload = { itemIds: layerIds, type: Dashboard.Block.REGULATORY_AREAS } + dispatch(dashboardActions.removeItems(payload)) + } + const clickOnGroupZones = () => { setZonesAreOpen(!zonesAreOpen) } return ( <> - + {getTitle(groupName) ?? ''} {`${layerIds.length}/${totalNumberOfZones}`} - - + {isSelected ? ( + + ) : ( + + )} - + {layerIds?.map(layerId => ( - + ))} ) } + +const StyledGroupWrapper = styled(LayerSelector.GroupWrapper)` + padding-left: 24px; + padding-right: 24px; +` diff --git a/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/Panel.tsx b/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/Panel.tsx index 13dd2ae9e5..db8948b73d 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/Panel.tsx +++ b/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/Panel.tsx @@ -9,12 +9,13 @@ import { Accent, Icon, IconButton } from '@mtes-mct/monitor-ui' import { skipToken } from '@reduxjs/toolkit/query' import { MonitorEnvLayers } from 'domain/entities/layers/constants' import { getTitle } from 'domain/entities/layers/utils' +import { useEffect } from 'react' import { FingerprintSpinner } from 'react-epic-spinners' import styled from 'styled-components' const FOUR_HOURS = 4 * 60 * 60 * 1000 -export function RegulatoryPanel({ dashboardId }: { dashboardId: number }) { +export function RegulatoryPanel({ $marginLeft, dashboardId }: { $marginLeft: number; dashboardId: number }) { const dispatch = useAppDispatch() const openPanel = useAppSelector(state => state.dashboard.dashboards?.[dashboardId]?.openPanel) @@ -26,8 +27,18 @@ export function RegulatoryPanel({ dashboardId }: { dashboardId: number }) { dispatch(dashboardActions.setDashboardPanel()) } + useEffect( + () => () => { + dispatch(dashboardActions.setDashboardPanel()) + }, + [dispatch] + ) + if (!openPanel) { + return null + } + return ( - + {regulatoryMetadata ? ( <>
@@ -63,16 +74,16 @@ export function RegulatoryPanel({ dashboardId }: { dashboardId: number }) { ) } -const Wrapper = styled.div<{ $isOpen: boolean }>` +const Wrapper = styled.div<{ $isOpen: boolean; $marginLeft: number }>` background-color: ${p => p.theme.color.white}; box-shadow: 0px 3px 5px #70778540; - display: block; - margin-left: 37px; - opacity: ${p => (p.$isOpen ? 1 : 0)}; position: absolute; - transform: translateX(100%); - transition: all 0.5s; width: 400px; + z-index: 1; + left: ${p => + `calc( + ${p.$marginLeft}px + 40px + 64px + 20px + )`}; // 40px is the padding, 64px is the width of the lsidebar, 20px is the margin ` const RegulatoryZoneName = styled.span` @@ -86,7 +97,7 @@ const RegulatoryZoneName = styled.span` margin-right: 5px; ` -const Header = styled.div` +const Header = styled.header` background-color: ${p => p.theme.color.gainsboro}; color: ${p => p.theme.color.gunMetal}; text-align: left; diff --git a/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/index.tsx b/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/index.tsx index 29242f01db..778aa91292 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/index.tsx +++ b/frontend/src/features/Dashboard/components/DashboardForm/RegulatoryAreas/index.tsx @@ -1,11 +1,16 @@ import { useGetRegulatoryLayersQuery } from '@api/regulatoryLayersAPI' +import { Dashboard } from '@features/Dashboard/types' import { LayerSelector } from '@features/layersSelector/utils/LayerSelector.style' +import { useAppSelector } from '@hooks/useAppSelector' +import { pluralize } from '@mtes-mct/monitor-ui' import { groupBy } from 'lodash' +import { useRef, useState } from 'react' import styled from 'styled-components' import { ListLayerGroup } from './ListLayerGroup' import { RegulatoryPanel } from './Panel' import { Accordion } from '../Accordion' +import { SmallAccordion } from '../SmallAccordion' type RegulatoriesAreasProps = { dashboardId: number @@ -13,16 +18,33 @@ type RegulatoriesAreasProps = { setExpandedAccordion: () => void } export function RegulatoryAreas({ dashboardId, isExpanded, setExpandedAccordion }: RegulatoriesAreasProps) { + const ref = useRef(null) + const width = ref.current?.clientWidth + + const selectedLayerIds = useAppSelector( + state => state.dashboard.dashboards?.[dashboardId]?.[Dashboard.Block.REGULATORY_AREAS] + ) + + const [isExpandedSmallAccordion, setExpandedSmallAccordion] = useState(false) const { data: regulatoryLayers } = useGetRegulatoryLayersQuery() const regulatoryAreasByLayerName = groupBy( Object.values(regulatoryLayers?.ids ?? {}), - r => regulatoryLayers?.entities[r]?.layer_name + (r: number) => regulatoryLayers?.entities[r]?.layer_name + ) + + const selectedRegulatoryAreaIds = Object.values(regulatoryLayers?.ids ?? {}).filter(id => + selectedLayerIds?.includes(id) + ) + const selectedRegulatoryAreasByLayerName = groupBy( + selectedRegulatoryAreaIds, + (r: number) => regulatoryLayers?.entities[r]?.layer_name ) return ( -
- +
+ + + setExpandedSmallAccordion(!isExpandedSmallAccordion)} + title={`${selectedLayerIds?.length ?? 0} ${pluralize('zone', selectedLayerIds?.length ?? 0)} ${pluralize( + 'sélectionée', + selectedLayerIds?.length ?? 0 + )}`} + > + {Object.entries(selectedRegulatoryAreasByLayerName).map(([layerGroupName, layerIdsInGroup]) => ( + + ))} +
) } diff --git a/frontend/src/features/Dashboard/components/DashboardForm/SmallAccordion.tsx b/frontend/src/features/Dashboard/components/DashboardForm/SmallAccordion.tsx new file mode 100644 index 0000000000..b2715b3046 --- /dev/null +++ b/frontend/src/features/Dashboard/components/DashboardForm/SmallAccordion.tsx @@ -0,0 +1,76 @@ +import { Accent, Icon, IconButton } from '@mtes-mct/monitor-ui' +import styled from 'styled-components' + +type AccordionProps = { + children: React.ReactNode + isExpanded: boolean + isReadOnly?: boolean + setExpandedAccordion: () => void + title: string +} + +export function SmallAccordion({ + children, + isExpanded, + isReadOnly = false, + setExpandedAccordion, + title +}: AccordionProps) { + return ( + + + + {title} + + {!isReadOnly && ( + + )} + + + {children} + + ) +} + +const AccordionContainer = styled.div` + background-color: ${p => p.theme.color.blueGray25}; + box-shadow: 0px 3px 6px #70778540; + cursor: pointer; +` +const StyledIconButton = styled(IconButton)<{ $isExpanded: boolean }>` + transform: ${({ $isExpanded }) => ($isExpanded ? 'rotate(180deg)' : 'rotate(0deg)')}; + transition: transform 0.3s; +` +const AccordionHeader = styled.header` + color: ${p => p.theme.color.blueYonder}; + display: flex; + font-weight: 500; + justify-content: space-between; + padding: 8px 24px; +` +const TitleContainer = styled.div` + align-items: center; + display: flex; + gap: 16px; +` +const Title = styled.span` + font-size: 16px; + font-weight: 500; +` + +const HeaderSeparator = styled.div` + border-bottom: 2px solid ${p => p.theme.color.gainsboro}; + padding: -24px; +` +const AccordionContent = styled.div<{ $isExpanded: boolean }>` + display: flex; + flex-direction: column; + max-height: ${({ $isExpanded }) => ($isExpanded ? '100vh' : '0px')}; + overflow-x: hidden; + transition: 0.5s max-height; +` diff --git a/frontend/src/features/Dashboard/components/DashboardForm/index.tsx b/frontend/src/features/Dashboard/components/DashboardForm/index.tsx index 04f627775f..f564c5f508 100644 --- a/frontend/src/features/Dashboard/components/DashboardForm/index.tsx +++ b/frontend/src/features/Dashboard/components/DashboardForm/index.tsx @@ -1,8 +1,8 @@ import { Dashboard } from '@features/Dashboard/types' import { SideWindowContent } from '@features/SideWindow/style' import { useAppDispatch } from '@hooks/useAppDispatch' -import { useAppSelector } from '@hooks/useAppSelector' import { Accent, Icon, IconButton } from '@mtes-mct/monitor-ui' +import { useState } from 'react' import styled from 'styled-components' import { Accordion } from './Accordion' @@ -12,18 +12,35 @@ import { dashboardActions } from '../../slice' export function DashboardForm() { const dispatch = useAppDispatch() const dashboardId = 1 // TODO replace with real value - const expandedAccordion = useAppSelector(state => - dashboardId ? state.dashboard.dashboards?.[dashboardId]?.openAccordion : undefined + const [expandedAccordionFirstColumn, setExpandedAccordionFirstColumn] = useState( + undefined + ) + const [expandedAccordionSecondColumn, setExpandedAccordionSecondColumn] = useState( + undefined + ) + const [expandedAccordionThirdColumn, setExpandedAccordionThirdColumn] = useState( + undefined ) const handleAccordionClick = (type: Dashboard.Block) => { - if (expandedAccordion === type) { - dispatch(dashboardActions.setDashboardAccordion()) - - return + switch (type) { + case Dashboard.Block.REGULATORY_AREAS: + case Dashboard.Block.AMP: + case Dashboard.Block.VIGILANCE_AREAS: + setExpandedAccordionFirstColumn(expandedAccordionFirstColumn === type ? undefined : type) + dispatch(dashboardActions.setDashboardPanel()) + break + case Dashboard.Block.TERRITORIAL_PRESSURE: + case Dashboard.Block.REPORTINGS: + setExpandedAccordionSecondColumn(expandedAccordionSecondColumn === type ? undefined : type) + break + case Dashboard.Block.CONTROL_UNITS: + case Dashboard.Block.COMMENTS: + setExpandedAccordionThirdColumn(expandedAccordionThirdColumn === type ? undefined : type) + break + default: + break } - dispatch(dashboardActions.setDashboardAccordion(type)) - dispatch(dashboardActions.setDashboardPanel()) } const clickOnEye = () => {} @@ -34,12 +51,12 @@ export function DashboardForm() { handleAccordionClick(Dashboard.Block.REGULATORY_AREAS)} /> handleAccordionClick(Dashboard.Block.AMP)} title="Zones AMP" > @@ -57,7 +74,7 @@ export function DashboardForm() {
TEST
handleAccordionClick(Dashboard.Block.VIGILANCE_AREAS)} title="Zones de vigilance" > @@ -69,7 +86,7 @@ export function DashboardForm() {
handleAccordionClick(Dashboard.Block.TERRITORIAL_PRESSURE)} title="Pression territoriale des contrôles et surveillances" > @@ -88,7 +105,7 @@ export function DashboardForm() { } - isExpanded={expandedAccordion === Dashboard.Block.REPORTINGS} + isExpanded={expandedAccordionSecondColumn === Dashboard.Block.REPORTINGS} setExpandedAccordion={() => handleAccordionClick(Dashboard.Block.REPORTINGS)} title="Signalements" > @@ -100,7 +117,7 @@ export function DashboardForm() { handleAccordionClick(Dashboard.Block.CONTROL_UNITS)} title="Unités" > @@ -118,7 +135,7 @@ export function DashboardForm() {
TEST
handleAccordionClick(Dashboard.Block.COMMENTS)} title="Commentaires" > @@ -134,7 +151,6 @@ export function DashboardForm() { const Container = styled(SideWindowContent)` display: flex; - overflow-y: hidden; flex-direction: row; ` @@ -142,8 +158,9 @@ const Column = styled.div` display: flex; flex: 1; flex-direction: column; - height: 100vh; - overflow-y: auto; + height: calc(100vh- 48px - 40px); // 48px = navbar height, 40px = padding + overflow-y: scroll; + overflow-x: visible; + box-sizing: content-box; padding: 12px; - padding-bottom: 100px; ` diff --git a/frontend/src/features/Dashboard/slice.ts b/frontend/src/features/Dashboard/slice.ts index 450397a56b..3a83030d0b 100644 --- a/frontend/src/features/Dashboard/slice.ts +++ b/frontend/src/features/Dashboard/slice.ts @@ -9,7 +9,6 @@ type OpenPanel = { type DashboardType = { dashboard: any - openAccordion: Dashboard.Block | undefined openPanel: OpenPanel | undefined [Dashboard.Block.REGULATORY_AREAS]: number[] } @@ -29,7 +28,6 @@ const INITIAL_STATE: DashboardState = { 1: { // TODO: it's just for testing to delete dashboard: {}, - openAccordion: undefined, openPanel: undefined, [Dashboard.Block.REGULATORY_AREAS]: [] } @@ -64,17 +62,6 @@ export const dashboardSlice = createSlice({ state.dashboards[id][type] = selectedItems.filter(item => !itemIds.includes(item)) } }, - setDashboardAccordion(state, action: PayloadAction) { - const id = state.activeDashboardId - - if (!id) { - return - } - - if (state.dashboards[id]) { - state.dashboards[id].openAccordion = action.payload - } - }, setDashboardPanel(state, action: PayloadAction) { const id = state.activeDashboardId diff --git a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/ImageViewer.tsx b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/ImageViewer.tsx index e9a0a990bc..e20805b429 100644 --- a/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/ImageViewer.tsx +++ b/frontend/src/features/VigilanceArea/components/VigilanceAreaForm/ImageViewer.tsx @@ -1,6 +1,7 @@ import { useEscapeKey } from '@hooks/useEscapeKey' import { Accent, Icon, IconButton, THEME } from '@mtes-mct/monitor-ui' import { useCallback, useState } from 'react' +import { createPortal } from 'react-dom' import styled from 'styled-components' interface ImageViewerProps { @@ -29,7 +30,7 @@ export function ImageViewer({ currentIndex, images, onClose }: ImageViewerProps) onEscape: () => onClose() }) - return ( + return createPortal( <> @@ -72,7 +73,8 @@ export function ImageViewer({ currentIndex, images, onClose }: ImageViewerProps) )} - + , + document.body as HTMLElement ) }