Skip to content

Commit

Permalink
feat: add dashboard layer and overlay to map
Browse files Browse the repository at this point in the history
  • Loading branch information
maximeperrault committed Oct 24, 2024
1 parent 27332e0 commit 9613270
Show file tree
Hide file tree
Showing 15 changed files with 577 additions and 29 deletions.
6 changes: 6 additions & 0 deletions frontend/public/icons/Bullseye.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions frontend/src/domain/entities/layers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export enum MonitorEnvLayers {
BASE_LAYER = 'BASE_LAYER',
COMPETENCE_CROSS_AREA = 'COMPETENCE_CROSS_AREA',
DASHBOARD = 'DASHBOARD',
DASHBOARDS = 'DASHBOARDS',
DASHBOARD_PREVIEW = 'DASHBOARD_PREVIEW',
DEPARTMENTS = 'DEPARTMENTS',
DRAW = 'DRAW',
Expand Down Expand Up @@ -291,6 +292,11 @@ export const Layers: Record<MonitorEnvLayers, Layer> = {
code: MonitorEnvLayers.DASHBOARD_PREVIEW,
zIndex: 1300
},

[MonitorEnvLayers.DASHBOARDS]: {
code: MonitorEnvLayers.DASHBOARDS,
zIndex: 1500
},
[MonitorEnvLayers.AERA_ICON]: {
code: MonitorEnvLayers.AERA_ICON
}
Expand Down Expand Up @@ -332,6 +338,7 @@ export const SelectableLayers0To7 = [
MonitorEnvLayers.AMP_LINKED_TO_VIGILANCE_AREA
],
[MonitorEnvLayers.DASHBOARD],
[MonitorEnvLayers.DASHBOARDS],
[MonitorEnvLayers.DASHBOARD_PREVIEW]
]

Expand All @@ -353,6 +360,7 @@ export const SelectableLayers7To26 = [
MonitorEnvLayers.AMP_LINKED_TO_VIGILANCE_AREA
],
[MonitorEnvLayers.DASHBOARD],
[MonitorEnvLayers.DASHBOARDS],
[MonitorEnvLayers.DASHBOARD_PREVIEW]
]

Expand All @@ -373,6 +381,7 @@ export const HoverableLayers0To7 = [
[MonitorEnvLayers.REGULATORY_AREAS_LINKED_TO_VIGILANCE_AREA],
[MonitorEnvLayers.AMP_LINKED_TO_VIGILANCE_AREA],
[MonitorEnvLayers.DASHBOARD],
[MonitorEnvLayers.DASHBOARDS],
[MonitorEnvLayers.DASHBOARD_PREVIEW]
]

Expand All @@ -395,6 +404,7 @@ export const HoverableLayers7To26 = [
MonitorEnvLayers.AMP_LINKED_TO_VIGILANCE_AREA
],
[MonitorEnvLayers.DASHBOARD],
[MonitorEnvLayers.DASHBOARDS],
[MonitorEnvLayers.DASHBOARD_PREVIEW]
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,12 @@ export function ControlUnitAccordion({
controlUnitIdExpanded,
expandUnit
}: {
controlUnit?: ControlUnit.ControlUnit
controlUnit: ControlUnit.ControlUnit
controlUnitIdExpanded: number | undefined
expandUnit: (id: number) => void
}) {
const dispatch = useAppDispatch()

if (!controlUnit) {
return null
}

const isExpanded = controlUnitIdExpanded === controlUnit.id

const removeControlUnit = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,16 @@ export function SelectedControlUnits({
selectedControlUnitIds?.length ?? 0
)} ${pluralize('sélectionnée', selectedControlUnitIds?.length ?? 0)}`}
>
{selectedControlUnitIds?.map(controlUnitId => {
const controlUnit = controlUnits.find(({ id }) => id === controlUnitId)

return (
{controlUnits
.filter(controlUnit => selectedControlUnitIds?.includes(controlUnit.id))
.map(controlUnit => (
<ControlUnitAccordion
key={controlUnit?.id}
key={controlUnit.id}
controlUnit={controlUnit}
controlUnitIdExpanded={controlUnitIdExpanded}
expandUnit={expandedControlUnit}
/>
)
})}
))}
</StyledSelectedAccordion>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const Columns = (regulatoryAreas, controlUnits, legacyFirefoxOffset: numb
accessorFn: row => row.createdAt,
cell: info => <DateCell date={info.getValue()} />,
enableSorting: true,
header: () => 'Créée le ...',
header: () => 'Créé le ...',
id: 'createdAt',
size: 212 + legacyFirefoxOffset
},
Expand Down
139 changes: 139 additions & 0 deletions frontend/src/features/Dashboard/components/Overlays/DashboardCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { useGetDashboardQuery } from '@api/dashboardsAPI'
import { editDashboard } from '@features/Dashboard/useCases/editDashboard'
import { useAppDispatch } from '@hooks/useAppDispatch'
import { Accent, Button, getLocalizedDayjs, Icon, IconButton, pluralize, Size } from '@mtes-mct/monitor-ui'
import { closeAllOverlays } from 'domain/use_cases/map/closeAllOverlays'
import { useEffect, useRef } from 'react'
import styled from 'styled-components'

type DashboardCardProps = {
dashboardId: string
isCardVisible?: boolean
onClose: () => void
selected?: boolean
updateMargins: (margin: number) => void
}

export function DashboardCard({
dashboardId,
isCardVisible = true,
onClose,
selected = false,
updateMargins
}: DashboardCardProps) {
const dispatch = useAppDispatch()
const ref = useRef<HTMLDivElement>(null)

const { data: dashboard } = useGetDashboardQuery(dashboardId)

useEffect(() => {
if (dashboard && ref.current) {
const cardHeight = ref.current.offsetHeight
updateMargins(cardHeight === 0 ? 200 : cardHeight)
}
}, [dashboard, updateMargins])
if (!dashboard) {
return null
}
const creationDate = getLocalizedDayjs(dashboard.createdAt).format('DD MMM YYYY')
const numberOfItemsSelected =
dashboard.amps.length +
dashboard.controlUnits.length +
dashboard.regulatoryAreas.length +
dashboard.vigilanceAreas.length +
dashboard.reportings.length

return (
isCardVisible && (
<Wrapper ref={ref} data-cy="reporting-overlay">
<StyledHeader>
<StyledHeaderFirstLine>
<StyledBoldText>{dashboard.name}</StyledBoldText>
<div>
<StyledGrayText>Créé le {creationDate}</StyledGrayText>
<StyledGrayText>
{numberOfItemsSelected} {pluralize('élément', numberOfItemsSelected)}{' '}
{pluralize('sélectionné', numberOfItemsSelected)}
</StyledGrayText>
</div>
</StyledHeaderFirstLine>

<CloseButton
$isVisible={selected}
accent={Accent.TERTIARY}
data-cy="reporting-overlay-close"
Icon={Icon.Close}
iconSize={14}
onClick={onClose}
/>
</StyledHeader>

<StyledButton
data-cy="map-edit-reporting"
disabled={!selected}
Icon={Icon.Edit}
onClick={() => {
dispatch(editDashboard(dashboard.id))
dispatch(closeAllOverlays())
}}
size={Size.SMALL}
>
Éditer le tableau
</StyledButton>
</Wrapper>
)
)
}

const Wrapper = styled.div`
padding: 10px;
box-shadow: 0px 3px 6px #70778540;
border-radius: 1px;
background-color: ${p => p.theme.color.white};
display: flex;
flex-direction: column;
gap: 16px;
flex-basis: 0 0 345px;
`
const StyledHeader = styled.div`
display: flex;
flex-direction: row;
align-items: start;
justify-content: space-between;
`
const StyledGrayText = styled.span`
color: ${p => p.theme.color.slateGray};
display: flex;
align-items: baseline;
`

const StyledHeaderFirstLine = styled.div`
display: flex;
flex-direction: column;
align-items: start;
gap: 16px;
${StyledGrayText} {
max-width: 190px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
`

const CloseButton = styled(IconButton)<{ $isVisible: boolean }>`
padding: 0px;
margin-left: 8px;
${p => !p.$isVisible && 'visibility: hidden;'};
`

const StyledBoldText = styled.span`
font-weight: 700;
color: ${p => p.theme.color.gunMetal};
`

// TODO delete when Monitor-ui component have good padding
const StyledButton = styled(Button)`
padding: 4px 12px;
align-self: start;
width: inherit;
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { dashboardActions } from '@features/Dashboard/slice'
import { OverlayPositionOnCentroid } from '@features/map/overlays/OverlayPositionOnCentroid'
import { useAppDispatch } from '@hooks/useAppDispatch'
import { useAppSelector } from '@hooks/useAppSelector'
import { Layers } from 'domain/entities/layers/constants'
import { isOverlayOpened } from 'domain/shared_slices/Global'
import { convertToFeature } from 'domain/types/map'
import { useState } from 'react'

import { DashboardCard } from './DashboardCard'

import type { BaseMapChildrenProps } from '@features/map/BaseMap'
import type { VectorLayerWithName } from 'domain/types/layer'

const OPTIONS = {
margins: {
xLeft: 50,
xMiddle: 30,
xRight: -55,
yBottom: 50,
yMiddle: 50,
yTop: -55
}
}
export function DashboardOverlay({ currentFeatureOver, map, mapClickEvent }: BaseMapChildrenProps) {
const dispatch = useAppDispatch()

const [hoveredOptions, setHoveredOptions] = useState(OPTIONS)
const [selectedOptions, setSelectedOptions] = useState(OPTIONS)
const displayDashboardLayer = useAppSelector(state => state.global.displayDashboardLayer)

const selectedDashboardOnMap = useAppSelector(state => state.dashboard.selectedDashboardOnMap)

const feature = map
?.getLayers()
?.getArray()
?.find(
(l): l is VectorLayerWithName =>
Object.prototype.hasOwnProperty.call(l, 'name') && (l as VectorLayerWithName).name === Layers.DASHBOARDS.code
)
?.getSource()
?.getFeatureById(`${Layers.DASHBOARDS.code}:${selectedDashboardOnMap?.id}`)

const canOverlayBeOpened = useAppSelector(state =>
isOverlayOpened(state.global, `${Layers.DASHBOARDS.code}:${selectedDashboardOnMap?.id}`)
)

const hoveredFeature = convertToFeature(currentFeatureOver)

const currentfeatureId = hoveredFeature?.getId()

const displayHoveredFeature =
typeof currentfeatureId === 'string' &&
currentfeatureId.startsWith(Layers.DASHBOARDS.code) &&
currentfeatureId !== `${Layers.DASHBOARDS.code}:${selectedDashboardOnMap?.id}` &&
hoveredFeature?.getProperties().dashboard

const updateSelectedMargins = (cardHeight: number) => {
if (OPTIONS.margins.yTop - cardHeight !== selectedOptions.margins.yTop) {
setSelectedOptions({ margins: { ...selectedOptions.margins, yTop: OPTIONS.margins.yTop - cardHeight } })
}
}
const updateHoveredMargins = (cardHeight: number) => {
if (OPTIONS.margins.yTop - cardHeight !== hoveredOptions.margins.yTop) {
setHoveredOptions({ margins: { ...hoveredOptions.margins, yTop: OPTIONS.margins.yTop - cardHeight } })
}
}

const close = () => {
dispatch(dashboardActions.setSelectedDashboardOnMap(undefined))
}

return (
<>
<OverlayPositionOnCentroid
appClassName="overlay-reporting-selected"
feature={displayDashboardLayer && canOverlayBeOpened ? feature : undefined}
map={map}
mapClickEvent={mapClickEvent}
options={selectedOptions}
zIndex={5000}
>
{selectedDashboardOnMap && (
<DashboardCard
dashboardId={selectedDashboardOnMap?.id}
onClose={close}
selected
updateMargins={updateSelectedMargins}
/>
)}
</OverlayPositionOnCentroid>
<OverlayPositionOnCentroid
appClassName="overlay-reporting-hover"
feature={displayHoveredFeature ? hoveredFeature : undefined}
map={map}
mapClickEvent={mapClickEvent}
options={hoveredOptions}
zIndex={5000}
>
{hoveredFeature?.getProperties().dashboard && (
<DashboardCard
dashboardId={hoveredFeature?.getProperties().dashboard.id}
onClose={close}
updateMargins={updateHoveredMargins}
/>
)}
</OverlayPositionOnCentroid>
</>
)
}
7 changes: 6 additions & 1 deletion frontend/src/features/Dashboard/slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ type DashboardState = {
interactionType: InteractionType
isDrawing: boolean
isGeometryValid: boolean
selectedDashboardOnMap: Dashboard.PopulatedDashboard | undefined
}

const INITIAL_STATE: DashboardState = {
Expand All @@ -102,7 +103,8 @@ const INITIAL_STATE: DashboardState = {
initialGeometry: undefined,
interactionType: InteractionType.CIRCLE,
isDrawing: false,
isGeometryValid: false
isGeometryValid: false,
selectedDashboardOnMap: undefined
}
export const dashboardSlice = createSlice({
initialState: INITIAL_STATE,
Expand Down Expand Up @@ -378,6 +380,9 @@ export const dashboardSlice = createSlice({
state.dashboards[id].reportingFilters = { ...reportingFilters, ...action.payload }
}
},
setSelectedDashboardOnMap(state, action: PayloadAction<Dashboard.PopulatedDashboard | undefined>) {
state.selectedDashboardOnMap = action.payload
},
setSelectedReporting(state, action: PayloadAction<Reporting | undefined>) {
const id = state.activeDashboardId

Expand Down
Loading

0 comments on commit 9613270

Please sign in to comment.