diff --git a/frontend/src/features/Dashboard/components/Layers/ExportLayer.tsx b/frontend/src/features/Dashboard/components/Layers/ExportLayer.tsx index b35f6abc8..d1384f945 100644 --- a/frontend/src/features/Dashboard/components/Layers/ExportLayer.tsx +++ b/frontend/src/features/Dashboard/components/Layers/ExportLayer.tsx @@ -1,10 +1,22 @@ -import { dashboardActions } from '@features/Dashboard/slice' -import { CENTERED_ON_FRANCE, type BaseMapChildrenProps } from '@features/map/BaseMap' +import { useGetAMPsQuery } from '@api/ampsAPI' +import { useGetRegulatoryLayersQuery } from '@api/regulatoryLayersAPI' +import { useGetReportingsByIdsQuery } from '@api/reportingsAPI' +import { useGetVigilanceAreasQuery } from '@api/vigilanceAreasAPI' +import { getDashboardById } from '@features/Dashboard/slice' +import { Dashboard } from '@features/Dashboard/types' +import { CENTERED_ON_FRANCE } from '@features/map/BaseMap' +import { getAMPFeature } from '@features/map/layers/AMP/AMPGeometryHelpers' +import { getRegulatoryFeature } from '@features/map/layers/Regulatory/regulatoryGeometryHelpers' +import { measurementStyle, measurementStyleWithCenter } from '@features/map/layers/styles/measurement.style' +import { getReportingZoneFeature } from '@features/Reportings/components/ReportingLayer/Reporting/reportingsGeometryHelpers' +import { getVigilanceAreaZoneFeature } from '@features/VigilanceArea/components/VigilanceAreaLayer/vigilanceAreaGeometryHelper' import { useAppDispatch } from '@hooks/useAppDispatch' import { useAppSelector } from '@hooks/useAppSelector' import { OPENLAYERS_PROJECTION, WSG84_PROJECTION } from '@mtes-mct/monitor-ui' +import { getFeature } from '@utils/getFeature' import { Layers } from 'domain/entities/layers/constants' import { Feature, View } from 'ol' +import { createEmpty, extend, type Extent } from 'ol/extent' import TileLayer from 'ol/layer/Tile' import VectorLayer from 'ol/layer/Vector' import OpenLayerMap from 'ol/Map' @@ -13,14 +25,14 @@ import { XYZ } from 'ol/source' import VectorSource from 'ol/source/Vector' import { useEffect, useRef, type MutableRefObject } from 'react' -import { wait } from '../../../../../puppeteer/e2e/utils' +import { getDashboardStyle } from './style' import type { VectorLayerWithName } from 'domain/types/layer' +import type { Geometry } from 'ol/geom' const initialMap = new OpenLayerMap({ layers: [ new TileLayer({ - className: Layers.BASE_LAYER.code, source: new XYZ({ crossOrigin: 'anonymous', maxZoom: 19, @@ -40,39 +52,69 @@ const initialMap = new OpenLayerMap({ }) }) -export function ExportLayer({ map }: BaseMapChildrenProps) { +export type ExportImageType = { + featureId: string | number | undefined + image: string +} + +type ExportLayerProps = { + onImagesReady: (images: ExportImageType[]) => void + shouldLoadImages: boolean +} +export function ExportLayer({ onImagesReady, shouldLoadImages }: ExportLayerProps) { const mapRef = useRef(null) as MutableRefObject const dispatch = useAppDispatch() const activeDashboardId = useAppSelector(state => state.dashboard.activeDashboardId) - const isGeneratingBrief = useAppSelector(state => - activeDashboardId ? state.dashboard.dashboards?.[activeDashboardId]?.isGeneratingBrief : false - ) + const dashboard = useAppSelector(state => getDashboardById(state.dashboard, activeDashboardId)) + const { data: reportings } = useGetReportingsByIdsQuery(dashboard?.dashboard.reportingIds ?? [], { + skip: !dashboard + }) + const { data: regulatoryLayers } = useGetRegulatoryLayersQuery(undefined, { skip: !dashboard }) + const { data: ampLayers } = useGetAMPsQuery(undefined, { skip: !dashboard }) + const { data: vigilanceAreas } = useGetVigilanceAreasQuery(undefined, { skip: !dashboard }) + + const activeDashboard = dashboard?.dashboard + + const layersVectorSourceRef = useRef(new VectorSource()) as React.MutableRefObject>> + const layersVectorLayerRef = useRef( + new VectorLayer({ + renderBuffer: 7, + renderOrder: (a, b) => b.get('area') - a.get('area'), + source: layersVectorSourceRef.current, + style: feature => getDashboardStyle(feature), + zIndex: Layers.DASHBOARD.zIndex + }) + ) as React.MutableRefObject + layersVectorLayerRef.current.name = 'EXPORT_PDF' - const zoomToFeature = (feature: Feature) => { - const inMemoryMap = mapRef.current - const geometry = feature.getGeometry() + const zoomToFeatures = async (features: Feature[]) => + new Promise(resolve => { + const inMemoryMap = mapRef.current + const extent = combineExtent(features) + if (!extent || !inMemoryMap) { + return + } - if (!geometry || !inMemoryMap) { - return - } + const view = inMemoryMap.getView() - const extent = geometry.getExtent() - const view = inMemoryMap.getView() + view.fit(extent, { padding: [10, 10, 10, 10] }) - // Zoom et centrage avec animation - view.fit(extent) - } + // admission of weakness... + setTimeout(() => { + resolve() + }, 100) + }) useEffect(() => { const hiddenDiv = document.createElement('div') hiddenDiv.style.width = '800px' hiddenDiv.style.height = '600px' - hiddenDiv.style.position = 'absolute' // Hors du flux DOM visible - hiddenDiv.style.visibility = 'hidden' // Invisible mais dans le DOM - document.body.appendChild(hiddenDiv) // Nécessaire pour permettre le rendu + hiddenDiv.style.position = 'absolute' + hiddenDiv.style.visibility = 'hidden' + document.body.appendChild(hiddenDiv) const memoryMap = initialMap @@ -85,152 +127,165 @@ export function ExportLayer({ map }: BaseMapChildrenProps) { }, []) useEffect(() => { - if (mapRef.current && isGeneratingBrief === 'loading') { - // console.log('and again') - - mapRef.current.renderSync() - - const dashboardFeatures = map - .getLayers() - .getArray() - .find((layer): layer is VectorLayerWithName => 'name' in layer && layer.name === Layers.DASHBOARD.code) - ?.getSource() - ?.getFeatures() - - mapRef.current.renderSync() - - // dashboardFeatures?.forEach(feat => console.log(feat.getProperties())) - - const layerVector = new VectorLayer({ - source: new VectorSource({ - features: dashboardFeatures - }) - }) as VectorLayerWithName - - mapRef.current.renderSync() - - layerVector.name = 'EXPORT_PDF' - - mapRef.current.getLayers().push(layerVector) + if (mapRef.current) { + mapRef.current.getLayers().push(layersVectorLayerRef.current) + } - mapRef.current.renderSync() + return () => { + if (mapRef.current) { + // eslint-disable-next-line react-hooks/exhaustive-deps + mapRef.current.removeLayer(layersVectorLayerRef.current) + } + } + }, []) + useEffect(() => { + if (mapRef.current && shouldLoadImages) { // Exporter le canvas en image - const allImages: string[] = [] + const allImages: ExportImageType[] = [] + const allFeatures: Feature[] = [] + let dashboardAreaFeature const mapCanvas = mapRef.current.getViewport().querySelector('canvas')! const mapContext = mapCanvas.getContext('2d') - mapRef.current.renderSync() - - const feature = mapRef.current - .getLayers() - .getArray() - .find((layer): layer is VectorLayerWithName => 'name' in layer && layer.name === 'EXPORT_PDF') - ?.getSource() - ?.getFeatureById('testgeo')! - // mapRef.current.getView().fit(feature?.getGeometry()?.getExtent()!) - zoomToFeature(feature) // Attendre la fin du zoom - - mapRef.current - .getViewport() - .querySelectorAll('canvas') - .forEach(canvas => { - allImages.push(canvas.toDataURL('image/png')) - // mapContext?.drawImage(canvas, 0, 0) - }) - - // dispatch(dashboardActions.setBriefImages([mapCanvas.toDataURL('image/png')])) - dispatch(dashboardActions.setBriefImages(allImages)) - dispatch(dashboardActions.setIsGeneratingBrief('imagesToUpdate')) + layersVectorSourceRef.current.clear(true) + + if (activeDashboard) { + // Regulatory Areas + if (regulatoryLayers?.entities) { + const regulatoryLayersIds = activeDashboard.regulatoryAreaIds + // we don't want to display the area twice + regulatoryLayersIds.forEach(layerId => { + const layer = regulatoryLayers.entities[layerId] + + if (layer && layer?.geom && layer?.geom?.coordinates.length > 0) { + const feature = getRegulatoryFeature({ + code: Dashboard.featuresCode.DASHBOARD_REGULATORY_AREAS, + isolatedLayer: undefined, + layer + }) + if (feature) { + allFeatures.push(feature) + } + } + }) + } + + // AMP + if (ampLayers?.entities) { + const ampLayerIds = activeDashboard.ampIds + + ampLayerIds?.forEach(layerId => { + const layer = ampLayers.entities[layerId] + + if (layer && layer?.geom && layer?.geom?.coordinates.length > 0) { + const feature = getAMPFeature({ + code: Dashboard.featuresCode.DASHBOARD_AMP, + isolatedLayer: undefined, + layer + }) + + if (feature) { + allFeatures.push(feature) + } + } + }) + } + + // Vigilance Areas + if (vigilanceAreas?.entities) { + const vigilanceAreaLayersIds = activeDashboard.vigilanceAreaIds + vigilanceAreaLayersIds.forEach(layerId => { + const layer = vigilanceAreas.entities[layerId] + if (layer && layer?.geom && layer?.geom?.coordinates.length > 0) { + const feature = getVigilanceAreaZoneFeature( + layer, + Dashboard.featuresCode.DASHBOARD_VIGILANCE_AREAS, + undefined + ) + if (feature) { + allFeatures.push(feature) + } + } + }) + } + + // Reportings + if (reportings) { + Object.values(reportings?.entities ?? []).forEach(reporting => { + if (reporting.geom) { + const feature = getReportingZoneFeature(reporting, Dashboard.featuresCode.DASHBOARD_REPORTINGS) + allFeatures.push(feature) + } + }) + } + } + + if (dashboard?.dashboard.geom) { + dashboardAreaFeature = getFeature(dashboard.dashboard.geom) + if (!dashboardAreaFeature) { + return + } + dashboardAreaFeature?.setStyle([measurementStyle, measurementStyleWithCenter]) + } + + const generateImages = async () => { + if (!mapRef.current || allFeatures.length === 0) { + onImagesReady([]) + } + + // eslint-disable-next-line no-restricted-syntax + for (const feature of allFeatures) { + layersVectorSourceRef.current.clear(true) + layersVectorSourceRef.current.addFeature(feature) + // layersVectorSourceRef.current.addFeature(dashboardAreaFeature) + + // eslint-disable-next-line no-await-in-loop + await zoomToFeatures([feature]) + + mapRef.current + ?.getTargetElement() + .querySelectorAll('canvas') + .forEach(canvas => { + mapContext?.drawImage(canvas, 0, 0) + allImages.push({ + featureId: feature.getId(), + image: mapCanvas.toDataURL('image/png') + }) + mapContext?.reset() + }) + } + + onImagesReady(allImages) + } + + generateImages() } - }, [dispatch, isGeneratingBrief, map]) - - // useEffect(() => { - // if (isGeneratingBrief === 'loading') { - // const activeDashboardSource = map - // .getLayers() - // .getArray() - // .find((layer): layer is VectorLayerWithName => 'name' in layer && layer.name === Layers.DASHBOARD.code) - // ?.getSource() - // const memoryMap = new OpenLayerMap({ - // layers: [ - // new TileLayer({ - // source: new XYZ({ - // crossOrigin: 'anonymous', - // maxZoom: 19, - // urls: ['a', 'b', 'c', 'd'].map( - // subdomain => `https://${subdomain}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png` - // ) - // }), - // zIndex: 0 - // }), - // new VectorLayer({ - // source: new VectorSource({ - // features: activeDashboardSource?.getFeatures() - // }) - // }) - // ], - // target: undefined - // }) - - // memoryMap.renderSync() - - // // Zoom sur la région spécifique - // // map.getView().fit(targetExtent, { size: [800, 600] }) // Taille arbitraire du canvas - - // // Forcer le rendu et récupérer le canvas - // const canvas = memoryMap.getViewport().querySelector('canvas') - // if (canvas) { - // dispatch(dashboardActions.setBriefImages([canvas.toDataURL('image/png')])) - // dispatch(dashboardActions.setIsGeneratingBrief('ready')) - // } - - // // Déclencher le rendu forcé - - // // return () => { - // // // inmemoryMap.setTarget(undefined) // Nettoyer la carte - // // } - // } - - // // return undefined - // }, [dispatch, isGeneratingBrief, map]) - - // useEffect(() => { - // if (isGeneratingBrief === 'loading') { - // const width = Math.round((297 * 72) / 25.4) - // const height = Math.round((210 * 72) / 25.4) - // const mapCanvas = document.createElement('canvas') - // mapCanvas.width = width - // mapCanvas.height = height - // const mapContext = mapCanvas.getContext('2d') - // const allCanvas = map.getViewport().querySelectorAll('canvas') - // const activeDashboardSource = map - // .getLayers() - // .getArray() - // .find((layer): layer is VectorLayerWithName => 'name' in layer && layer.name === Layers.DASHBOARD.code) - // ?.getSource() - - // if (activeDashboardSource) { - // activeDashboardSource.forEachFeature(feature => { - // const extent = feature.getGeometry()?.getExtent() - // const center = extent && getCenter(extent) - // const centerLatLon = center && transform(center, OPENLAYERS_PROJECTION, WSG84_PROJECTION) - - // if (centerLatLon) { - // dispatch(setFitToExtent(extent)) - // } - // }) - - // // if (allCanvas) { - // // allCanvas.forEach(canvas => { - // // mapContext?.drawImage(canvas, 0, 0) - // // }) - // dispatch(dashboardActions.setBriefImages([mapCanvas.toDataURL('image/png')])) - // dispatch(dashboardActions.setIsGeneratingBrief('ready')) - // // } - // } - // } - // }, [map, isGeneratingBrief, dispatch]) + }, [ + activeDashboard, + ampLayers?.entities, + dashboard?.dashboard.geom, + dispatch, + onImagesReady, + regulatoryLayers?.entities, + reportings, + shouldLoadImages, + vigilanceAreas?.entities + ]) return null } + +function combineExtent(features: Feature[]): Extent { + const combinedExtent = createEmpty() + + // Étendre l'étendue pour inclure chaque feature + features.forEach(feature => { + const geometry = feature.getGeometry() + if (geometry) { + extend(combinedExtent, geometry.getExtent()) + } + }) + + return combinedExtent +} diff --git a/frontend/src/features/Dashboard/components/Layers/ExportLayer2.tsx b/frontend/src/features/Dashboard/components/Layers/ExportLayer2.tsx deleted file mode 100644 index a940212dc..000000000 --- a/frontend/src/features/Dashboard/components/Layers/ExportLayer2.tsx +++ /dev/null @@ -1,410 +0,0 @@ -import { useGetAMPsQuery } from '@api/ampsAPI' -import { useGetRegulatoryLayersQuery } from '@api/regulatoryLayersAPI' -import { useGetReportingsByIdsQuery } from '@api/reportingsAPI' -import { useGetVigilanceAreasQuery } from '@api/vigilanceAreasAPI' -import { getDashboardById } from '@features/Dashboard/slice' -import { Dashboard } from '@features/Dashboard/types' -import { CENTERED_ON_FRANCE } from '@features/map/BaseMap' -import { getAMPFeature } from '@features/map/layers/AMP/AMPGeometryHelpers' -import { getRegulatoryFeature } from '@features/map/layers/Regulatory/regulatoryGeometryHelpers' -import { measurementStyle, measurementStyleWithCenter } from '@features/map/layers/styles/measurement.style' -import { getReportingZoneFeature } from '@features/Reportings/components/ReportingLayer/Reporting/reportingsGeometryHelpers' -import { getVigilanceAreaZoneFeature } from '@features/VigilanceArea/components/VigilanceAreaLayer/vigilanceAreaGeometryHelper' -import { useAppDispatch } from '@hooks/useAppDispatch' -import { useAppSelector } from '@hooks/useAppSelector' -import { OPENLAYERS_PROJECTION, WSG84_PROJECTION } from '@mtes-mct/monitor-ui' -import { getFeature } from '@utils/getFeature' -import { Layers } from 'domain/entities/layers/constants' -import { Feature, View } from 'ol' -import TileLayer from 'ol/layer/Tile' -import VectorLayer from 'ol/layer/Vector' -import OpenLayerMap from 'ol/Map' -import { transform } from 'ol/proj' -import { XYZ } from 'ol/source' -import VectorSource from 'ol/source/Vector' -import { useCallback, useEffect, useRef, type MutableRefObject } from 'react' - -import { getDashboardStyle } from './style' - -import type { VectorLayerWithName } from 'domain/types/layer' -import type { Geometry } from 'ol/geom' - -const initialMap = new OpenLayerMap({ - layers: [ - new TileLayer({ - source: new XYZ({ - crossOrigin: 'anonymous', - maxZoom: 19, - urls: ['a', 'b', 'c', 'd'].map( - subdomain => `https://${subdomain}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png` - ) - }), - zIndex: 0 - }) - ], - - view: new View({ - center: transform(CENTERED_ON_FRANCE, WSG84_PROJECTION, OPENLAYERS_PROJECTION), - minZoom: 3, - projection: OPENLAYERS_PROJECTION, - zoom: 6 - }) -}) - -type ExportLayerProps = { - onImagesReady: (images: string[]) => void - shouldLoadImage: boolean -} -export function ExportLayer2({ onImagesReady, shouldLoadImage }: ExportLayerProps) { - const mapRef = useRef(null) as MutableRefObject - - const dispatch = useAppDispatch() - - const activeDashboardId = useAppSelector(state => state.dashboard.activeDashboardId) - - const dashboard = useAppSelector(state => getDashboardById(state.dashboard, activeDashboardId)) - const { data: reportings } = useGetReportingsByIdsQuery(dashboard?.dashboard.reportingIds ?? [], { - skip: !dashboard - }) - const { data: regulatoryLayers } = useGetRegulatoryLayersQuery(undefined, { skip: !dashboard }) - const { data: ampLayers } = useGetAMPsQuery(undefined, { skip: !dashboard }) - const { data: vigilanceAreas } = useGetVigilanceAreasQuery(undefined, { skip: !dashboard }) - - const activeDashboard = dashboard?.dashboard - - const layersVectorSourceRef = useRef(new VectorSource()) as React.MutableRefObject>> - const layersVectorLayerRef = useRef( - new VectorLayer({ - renderBuffer: 7, - renderOrder: (a, b) => b.get('area') - a.get('area'), - source: layersVectorSourceRef.current, - style: feature => getDashboardStyle(feature), - zIndex: Layers.DASHBOARD.zIndex - }) - ) as React.MutableRefObject - layersVectorLayerRef.current.name = 'EXPORT_PDF' - - const zoomToFeature = async (feature: Feature) => - new Promise(resolve => { - const inMemoryMap = mapRef.current - const geometry = feature.getGeometry() - - if (!geometry || !inMemoryMap) { - return - } - - const extent = geometry.getExtent() - const view = inMemoryMap.getView() - - const onMoveEnd = () => { - inMemoryMap.un('moveend', onMoveEnd) // Supprimer l'écouteur une fois appelé - resolve() // Terminer la promesse - } - - inMemoryMap.on('moveend', onMoveEnd) - - // Zoom et centrage avec animation - view.fit(extent) - }) - - useEffect(() => { - const hiddenDiv = document.createElement('div') - hiddenDiv.style.width = '800px' - hiddenDiv.style.height = '600px' - hiddenDiv.style.position = 'absolute' // Hors du flux DOM visible - hiddenDiv.style.visibility = 'hidden' // Invisible mais dans le DOM - document.body.appendChild(hiddenDiv) // Nécessaire pour permettre le rendu - - const memoryMap = initialMap - - memoryMap.setTarget(hiddenDiv) - mapRef.current = memoryMap - - return () => { - document.body.removeChild(hiddenDiv) - } - }, []) - - // const getFeatures = useCallback(() => { - // if (mapRef.current) { - // layersVectorSourceRef.current.clear(true) - - // if (activeDashboard) { - // // Regulatory Areas - // if (regulatoryLayers?.entities) { - // const regulatoryLayersIds = activeDashboard.regulatoryAreaIds - // // we don't want to display the area twice - // const features = regulatoryLayersIds.reduce((feats: Feature[], layerId) => { - // const layer = regulatoryLayers.entities[layerId] - - // if (layer && layer?.geom && layer?.geom?.coordinates.length > 0) { - // const feature = getRegulatoryFeature({ - // code: Dashboard.featuresCode.DASHBOARD_REGULATORY_AREAS, - // isolatedLayer: undefined, - // layer - // }) - // if (!feature) { - // return feats - // } - // feats.push(feature) - // } - - // return feats - // }, []) - - // layersVectorSourceRef.current.addFeatures(features) - // } - - // // AMP - // if (ampLayers?.entities) { - // const ampLayerIds = activeDashboard.ampIds - - // const features = ampLayerIds?.reduce((feats: Feature[], layerId) => { - // const layer = ampLayers.entities[layerId] - - // if (layer && layer?.geom && layer?.geom?.coordinates.length > 0) { - // const feature = getAMPFeature({ - // code: Dashboard.featuresCode.DASHBOARD_AMP, - // isolatedLayer: undefined, - // layer - // }) - - // if (!feature) { - // return feats - // } - - // feats.push(feature) - // } - - // return feats - // }, []) - - // layersVectorSourceRef.current.addFeatures(features) - // } - - // // Vigilance Areas - // if (vigilanceAreas?.entities) { - // const vigilanceAreaLayersIds = activeDashboard.vigilanceAreaIds - // const features = vigilanceAreaLayersIds.reduce((feats: Feature[], layerId) => { - // const layer = vigilanceAreas.entities[layerId] - // if (layer && layer?.geom && layer?.geom?.coordinates.length > 0) { - // const feature = getVigilanceAreaZoneFeature( - // layer, - // Dashboard.featuresCode.DASHBOARD_VIGILANCE_AREAS, - // undefined - // ) - // feats.push(feature) - // } - - // return feats - // }, []) - - // layersVectorSourceRef.current.addFeatures(features) - // } - - // // Reportings - // if (reportings) { - // const features = Object.values(reportings?.entities ?? []).reduce((feats: Feature[], reporting) => { - // if (reporting.geom) { - // const feature = getReportingZoneFeature(reporting, Dashboard.featuresCode.DASHBOARD_REPORTINGS) - // feats.push(feature) - // } - - // return feats - // }, []) - - // layersVectorSourceRef.current.addFeatures(features) - // } - // } - - // if (dashboard?.dashboard.geom) { - // const dashboardAreaFeature = getFeature(dashboard.dashboard.geom) - // if (!dashboardAreaFeature) { - // return - // } - // dashboardAreaFeature.setId('testgeo') - // dashboardAreaFeature?.setStyle([measurementStyle, measurementStyleWithCenter]) - - // layersVectorSourceRef.current.addFeature(dashboardAreaFeature) - // } - // } - // }, [ - // activeDashboard, - // ampLayers?.entities, - // dashboard?.dashboard.geom, - // regulatoryLayers?.entities, - // reportings, - // vigilanceAreas?.entities - // ]) - - useEffect(() => { - if (mapRef.current) { - mapRef.current.getLayers().push(layersVectorLayerRef.current) - } - - return () => { - if (mapRef.current) { - // eslint-disable-next-line react-hooks/exhaustive-deps - mapRef.current.removeLayer(layersVectorLayerRef.current) - } - } - }, []) - - useEffect(() => { - if (mapRef.current?.isRendered && shouldLoadImage) { - // Exporter le canvas en image - const allImages: string[] = [] - const mapCanvas = mapRef.current.getViewport().querySelector('canvas')! - const mapContext = mapCanvas.getContext('2d') - - layersVectorSourceRef.current.clear(true) - - if (activeDashboard) { - // Regulatory Areas - if (regulatoryLayers?.entities) { - const regulatoryLayersIds = activeDashboard.regulatoryAreaIds - // we don't want to display the area twice - regulatoryLayersIds.forEach(layerId => { - const layer = regulatoryLayers.entities[layerId] - - if (layer && layer?.geom && layer?.geom?.coordinates.length > 0) { - const feature = getRegulatoryFeature({ - code: Dashboard.featuresCode.DASHBOARD_REGULATORY_AREAS, - isolatedLayer: undefined, - layer - }) - layersVectorSourceRef.current.clear(true) - if (feature) { - layersVectorSourceRef.current.addFeature(feature) - zoomToFeature(feature) - setTimeout(() => { - mapRef.current - ?.getViewport() - .querySelectorAll('canvas') - .forEach(canvas => { - // allImages.push(canvas.toDataURL('image/png')) - mapContext?.drawImage(canvas, 0, 0) - allImages.push(mapCanvas.toDataURL('image/png')) - mapContext?.reset() - }) - - onImagesReady(allImages) - }, 2000) - } - } - }) - } - - // AMP - if (ampLayers?.entities) { - const ampLayerIds = activeDashboard.ampIds - - const features = ampLayerIds?.reduce((feats: Feature[], layerId) => { - const layer = ampLayers.entities[layerId] - - if (layer && layer?.geom && layer?.geom?.coordinates.length > 0) { - const feature = getAMPFeature({ - code: Dashboard.featuresCode.DASHBOARD_AMP, - isolatedLayer: undefined, - layer - }) - - if (!feature) { - return feats - } - - feats.push(feature) - } - - return feats - }, []) - - layersVectorSourceRef.current.addFeatures(features) - } - - // Vigilance Areas - if (vigilanceAreas?.entities) { - const vigilanceAreaLayersIds = activeDashboard.vigilanceAreaIds - const features = vigilanceAreaLayersIds.reduce((feats: Feature[], layerId) => { - const layer = vigilanceAreas.entities[layerId] - if (layer && layer?.geom && layer?.geom?.coordinates.length > 0) { - const feature = getVigilanceAreaZoneFeature( - layer, - Dashboard.featuresCode.DASHBOARD_VIGILANCE_AREAS, - undefined - ) - feats.push(feature) - } - - return feats - }, []) - - layersVectorSourceRef.current.addFeatures(features) - } - - // Reportings - if (reportings) { - const features = Object.values(reportings?.entities ?? []).reduce((feats: Feature[], reporting) => { - if (reporting.geom) { - const feature = getReportingZoneFeature(reporting, Dashboard.featuresCode.DASHBOARD_REPORTINGS) - feats.push(feature) - } - - return feats - }, []) - - layersVectorSourceRef.current.addFeatures(features) - } - } - - if (dashboard?.dashboard.geom) { - const dashboardAreaFeature = getFeature(dashboard.dashboard.geom) - if (!dashboardAreaFeature) { - return - } - dashboardAreaFeature.setId('testgeo') - dashboardAreaFeature?.setStyle([measurementStyle, measurementStyleWithCenter]) - - layersVectorSourceRef.current.addFeature(dashboardAreaFeature) - } - - // to do faire la boucle ici - - // const feature = mapRef.current - // .getLayers() - // .getArray() - // .find((layer): layer is VectorLayerWithName => 'name' in layer && layer.name === 'EXPORT_PDF') - // ?.getSource() - // ?.getFeatureById('testgeo')! - - // zoomToFeature(feature) - - // TODO: refacto to wait for the zoom to end - // setTimeout(() => { - // mapRef.current - // ?.getViewport() - // .querySelectorAll('canvas') - // .forEach(canvas => { - // // allImages.push(canvas.toDataURL('image/png')) - // mapContext?.drawImage(canvas, 0, 0) - // allImages.push(mapCanvas.toDataURL('image/png')) - // mapContext?.reset() - // }) - - // onImagesReady(allImages) - // }, 200) - } - }, [ - activeDashboard, - ampLayers?.entities, - dashboard?.dashboard.geom, - dispatch, - onImagesReady, - regulatoryLayers?.entities, - reportings, - shouldLoadImage, - vigilanceAreas?.entities - ]) - - return null -} diff --git a/frontend/src/features/Dashboard/components/Pdf/Amps/index.tsx b/frontend/src/features/Dashboard/components/Pdf/Amps/index.tsx index 1f9c1c72a..862a446ca 100644 --- a/frontend/src/features/Dashboard/components/Pdf/Amps/index.tsx +++ b/frontend/src/features/Dashboard/components/Pdf/Amps/index.tsx @@ -1,12 +1,25 @@ +import { Dashboard } from '@features/Dashboard/types' import { THEME } from '@mtes-mct/monitor-ui' -import { Link, Text, View } from '@react-pdf/renderer' +import { Image, Link, Text, View } from '@react-pdf/renderer' import { getTitle } from 'domain/entities/layers/utils' import { areaStyle, layoutStyle } from '../style' +import type { ExportImageType } from '../../Layers/ExportLayer' import type { AMPFromAPI } from 'domain/entities/AMPs' -export function Amps({ amps }: { amps: AMPFromAPI[] }) { +export function Amps({ amps, images }: { amps: AMPFromAPI[]; images: ExportImageType[] }) { + function getImage(amp: AMPFromAPI): string | undefined { + return images.find(image => { + if (!image.featureId) { + return false + } + const [type, id] = `${image.featureId}`.split(':') + + return type === Dashboard.featuresCode[Dashboard.Layer.DASHBOARD_AMP] && id && amp.id === +id + })?.image + } + return ( <> @@ -15,38 +28,40 @@ export function Amps({ amps }: { amps: AMPFromAPI[] }) { {amps.map(amp => ( - - - {getTitle(amp.name)} - - - - - Nature d'AMP - - - {amp.designation || '-'} - + + + + {getTitle(amp.name)} - - {amp.url_legicem && ( - - - Résumé réglementaire sur Légicem - - - - + + + + Nature d'AMP - - {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} - - {amp.ref_reg} - + + {amp.designation || '-'} - )} + {amp.url_legicem && ( + + + Résumé réglementaire sur Légicem + + + + + + + + {amp.ref_reg} + + + + + )} + + {getImage(amp) && } ))} diff --git a/frontend/src/features/Dashboard/components/Pdf/Brief.tsx b/frontend/src/features/Dashboard/components/Pdf/Brief.tsx index 3b7557617..0cf15e03f 100644 --- a/frontend/src/features/Dashboard/components/Pdf/Brief.tsx +++ b/frontend/src/features/Dashboard/components/Pdf/Brief.tsx @@ -1,8 +1,4 @@ -/* eslint-disable import/no-absolute-path */ - -// TODO (04/11/2024) : use monitor-ui fonts instead of imported/duplicated ones - -import { Document, Image, Page, View } from '@react-pdf/renderer' +import { Document, Page, View } from '@react-pdf/renderer' import { Amps } from './Amps' import { Comments } from './Comments' @@ -40,20 +36,19 @@ export function Brief({ brief }: BriefProps) { - + - + - {/* eslint-disable-next-line react/no-array-index-key */} - {brief.images && brief.images.map((image, index) => )} ) diff --git a/frontend/src/features/Dashboard/components/Pdf/GeneratePdfButton.tsx b/frontend/src/features/Dashboard/components/Pdf/GeneratePdfButton.tsx index d645eb4cf..7ccae5c21 100644 --- a/frontend/src/features/Dashboard/components/Pdf/GeneratePdfButton.tsx +++ b/frontend/src/features/Dashboard/components/Pdf/GeneratePdfButton.tsx @@ -12,7 +12,7 @@ import { useEffect, useMemo, useState } from 'react' import styled from 'styled-components' import { Brief } from './Brief' -import { ExportLayer2 } from '../Layers/ExportLayer2' +import { ExportLayer, type ExportImageType } from '../Layers/ExportLayer' import type { Dashboard } from '@features/Dashboard/types' @@ -89,18 +89,11 @@ export function GeneratePdfButton({ dashboard }: GeneratePdfButtonProps) { setShouldLoadImage(true) } - const updateBrief = (imagesToUpdate: string[]) => { + const updateBrief = (imagesToUpdate: ExportImageType[]) => { update() setShouldLoadImage(false) } - // useEffect(() => { - // if (isGeneratingBrief === 'imagesToUpdate' && !pdf.loading && pdf.blob && pdf.url) { - // dispatch(dashboardActions.setIsGeneratingBrief('ready')) - // setIsGenerating(true) - // } - // }, [brief, dispatch, images, isGeneratingBrief, pdf.blob, pdf.loading, pdf.url]) - useEffect(() => { if (isGenerating && !shouldLoadImage && !pdf.loading && pdf.blob && pdf.url) { setIsGenerating(false) @@ -114,7 +107,7 @@ export function GeneratePdfButton({ dashboard }: GeneratePdfButtonProps) { return ( <> - + { + if (!image.featureId) { + return false + } + const [type, id] = `${image.featureId}`.split(':') + + return ( + type === Dashboard.featuresCode[Dashboard.Layer.DASHBOARD_REGULATORY_AREAS] && id && regulatoryArea.id === +id + ) + })?.image + } + return ( <> @@ -15,58 +36,60 @@ export function RegulatoryAreas({ regulatoryAreas }: { regulatoryAreas: Regulato {regulatoryAreas.map(regulatoryArea => ( - - - {getTitle(regulatoryArea.layer_name)} - - - - - Entité - - - {regulatoryArea.entity_name || 'AUCUN NOM'} - + + + + {getTitle(regulatoryArea.layer_name)} - - - Ensemble reg. + + + + Entité + + + {regulatoryArea.entity_name || 'AUCUN NOM'} + - - {regulatoryArea.type || '-'} + + + Ensemble reg. + + + {regulatoryArea.type || '-'} + - - - - Thématique + + + Thématique + + + {regulatoryArea.thematique || '-'} + - - {regulatoryArea.thematique || '-'} + + + Façade + + + {regulatoryArea.facade || '-'} + - - - Façade - - - {regulatoryArea.facade || '-'} - - - - - - Résumé réglementaire sur Légicem - - - - + + + Résumé réglementaire sur Légicem - - {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} - {regulatoryArea.ref_reg} + + + + + + {regulatoryArea.ref_reg} + + {getImage(regulatoryArea) && } ))} diff --git a/frontend/src/features/Dashboard/components/Pdf/VigilanceAreas/index.tsx b/frontend/src/features/Dashboard/components/Pdf/VigilanceAreas/index.tsx index a44bc43e2..005b0db7f 100644 --- a/frontend/src/features/Dashboard/components/Pdf/VigilanceAreas/index.tsx +++ b/frontend/src/features/Dashboard/components/Pdf/VigilanceAreas/index.tsx @@ -1,23 +1,40 @@ +import { Dashboard } from '@features/Dashboard/types' import { EMPTY_VALUE } from '@features/VigilanceArea/constants' import { VigilanceArea } from '@features/VigilanceArea/types' import { endingOccurenceText, frequencyText } from '@features/VigilanceArea/utils' import { customDayjs, THEME } from '@mtes-mct/monitor-ui' -import { Link, Text, View } from '@react-pdf/renderer' +import { Image, Link, Text, View } from '@react-pdf/renderer' import { areaStyle, layoutStyle } from '../style' +import type { ExportImageType } from '../../Layers/ExportLayer' import type { AMPFromAPI } from 'domain/entities/AMPs' import type { RegulatoryLayerWithMetadata } from 'domain/entities/regulatory' export function VigilanceAreas({ + images, linkedAMPs, linkedRegulatoryAreas, vigilanceAreas }: { + images: ExportImageType[] linkedAMPs: AMPFromAPI[] linkedRegulatoryAreas: RegulatoryLayerWithMetadata[] vigilanceAreas: VigilanceArea.VigilanceArea[] }) { + function getImage(vigilanceArea: VigilanceArea.VigilanceArea): string | undefined { + return images.find(image => { + if (!image.featureId) { + return false + } + const [type, id] = `${image.featureId}`.split(':') + + return ( + type === Dashboard.featuresCode[Dashboard.Layer.DASHBOARD_VIGILANCE_AREAS] && id && vigilanceArea.id === +id + ) + })?.image + } + return ( <> @@ -39,87 +56,89 @@ export function VigilanceAreas({ ) return ( - - - {vigilanceArea.name} - - - - - Période - - - - {formattedStartPeriod ? `Du ${formattedStartPeriod} au ${formattedEndPeriod}` : EMPTY_VALUE} - - {frequencyText(vigilanceArea?.frequency)} - {endingOccurenceText(vigilanceArea?.endingCondition, vigilanceArea?.computedEndDate)} - + + + + {vigilanceArea.name} - - - Thématique - - - {vigilanceArea.themes ? vigilanceArea?.themes.join(', ') : EMPTY_VALUE} - - - - - Visibilité + + + + Période + + + + {formattedStartPeriod ? `Du ${formattedStartPeriod} au ${formattedEndPeriod}` : EMPTY_VALUE} + + {frequencyText(vigilanceArea?.frequency)} + {endingOccurenceText(vigilanceArea?.endingCondition, vigilanceArea?.computedEndDate)} + - - - {vigilanceArea.visibility - ? VigilanceArea.VisibilityLabel[vigilanceArea?.visibility] - : EMPTY_VALUE} - + + + Thématique + + + {vigilanceArea.themes ? vigilanceArea?.themes.join(', ') : EMPTY_VALUE} + - - - - - Commentaires - {vigilanceArea.comments} - - - {regulatoryAreas.length > 0 && ( - - - Réglementations en lien - {regulatoryAreas.map(linkedRegulatoryArea => ( - - {linkedRegulatoryArea.entity_name} + + + Visibilité + + + + {vigilanceArea.visibility + ? VigilanceArea.VisibilityLabel[vigilanceArea?.visibility] + : EMPTY_VALUE} - ))} + - )} - {amps.length > 0 && ( - AMP en lien - {amps.map(linkedAmp => ( - - {linkedAmp.name} - - ))} + Commentaires + {vigilanceArea.comments} - )} - {vigilanceArea.links && vigilanceArea.links?.length > 0 && ( - - - Liens utiles - {vigilanceArea.links.map(link => ( - // eslint-disable-next-line jsx-a11y/anchor-is-valid - - {link.linkText} - - ))} + {regulatoryAreas.length > 0 && ( + + + Réglementations en lien + {regulatoryAreas.map(linkedRegulatoryArea => ( + + {linkedRegulatoryArea.entity_name} + + ))} + - - )} + )} + {amps.length > 0 && ( + + + AMP en lien + {amps.map(linkedAmp => ( + + {linkedAmp.name} + + ))} + + + )} + {vigilanceArea.links && vigilanceArea.links?.length > 0 && ( + + + Liens utiles + {vigilanceArea.links.map(link => ( + + {link.linkText} + + ))} + + + )} + + {getImage(vigilanceArea) && } ) })} diff --git a/frontend/src/features/Dashboard/components/Pdf/style.ts b/frontend/src/features/Dashboard/components/Pdf/style.ts index 2b01aae47..fdaca2b7b 100644 --- a/frontend/src/features/Dashboard/components/Pdf/style.ts +++ b/frontend/src/features/Dashboard/components/Pdf/style.ts @@ -72,7 +72,6 @@ export const layoutStyle = StyleSheet.create({ fontStyle: 'italic' }, page: { - color: THEME.color.gunMetal, fontFamily: 'Mariane', fontWeight: 'normal', padding: '20 22' @@ -120,6 +119,12 @@ export const areaStyle = StyleSheet.create({ header: { backgroundColor: THEME.color.gainsboro, minHeight: 12.4, - padding: '2 14' + padding: '2' + }, + wrapper: { + display: 'flex', + flexDirection: 'row', + gap: 13, + width: '100%' } }) diff --git a/frontend/src/features/Dashboard/slice.ts b/frontend/src/features/Dashboard/slice.ts index 6ebfe312e..8c703a948 100644 --- a/frontend/src/features/Dashboard/slice.ts +++ b/frontend/src/features/Dashboard/slice.ts @@ -12,7 +12,6 @@ import type { GeoJSON } from 'domain/types/GeoJSON' export const initialDashboard: DashboardType = { ampIdsToDisplay: [], - briefImages: [], dashboard: { ampIds: [], controlUnitIds: [], @@ -28,7 +27,6 @@ export const initialDashboard: DashboardType = { extractedArea: undefined, isCancelEditModalOpen: false, isEditingTabName: false, - isGeneratingBrief: 'waiting', openPanel: undefined, regulatoryIdsToDisplay: [], reportingToDisplay: undefined, @@ -44,14 +42,12 @@ type OpenPanel = { export type DashboardType = { ampIdsToDisplay: number[] - briefImages: string[] dashboard: Dashboard.Dashboard defaultName: string | undefined displayGeometry: boolean extractedArea?: Dashboard.ExtractedArea isCancelEditModalOpen: boolean isEditingTabName: boolean - isGeneratingBrief: 'waiting' | 'loading' | 'imagesToUpdate' | 'ready' openPanel: OpenPanel | undefined regulatoryIdsToDisplay: number[] reportingToDisplay: Reporting | undefined @@ -276,17 +272,6 @@ export const dashboardSlice = createSlice({ setActiveDashboardId(state, action: PayloadAction) { state.activeDashboardId = action.payload }, - setBriefImages(state, action: PayloadAction) { - const id = state.activeDashboardId - - if (!id) { - return - } - - if (state.dashboards[id]) { - state.dashboards[id].briefImages = action.payload - } - }, setComments(state, action: PayloadAction<{ comments: string | undefined; key: string }>) { const id = action.payload.key @@ -347,17 +332,6 @@ export const dashboardSlice = createSlice({ state.dashboards[id].isEditingTabName = action.payload.isEditing }, - setIsGeneratingBrief(state, action: PayloadAction<'waiting' | 'loading' | 'imagesToUpdate' | 'ready'>) { - const id = state.activeDashboardId - - if (!id) { - return - } - - if (state.dashboards[id]) { - state.dashboards[id].isGeneratingBrief = action.payload - } - }, setName(state, action: PayloadAction<{ key: string; name: string }>) { const id = action.payload.key diff --git a/frontend/src/features/Dashboard/types.ts b/frontend/src/features/Dashboard/types.ts index 222fac93b..44ca64196 100644 --- a/frontend/src/features/Dashboard/types.ts +++ b/frontend/src/features/Dashboard/types.ts @@ -1,3 +1,4 @@ +import type { ExportImageType } from './components/Layers/ExportLayer' import type { AMP, AMPFromAPI } from '../../domain/entities/AMPs' import type { RegulatoryLayerCompact, @@ -48,7 +49,7 @@ export namespace Dashboard { amps: AMPFromAPI[] comments?: string controlUnits: ControlUnit.ControlUnit[] - images: string[] | undefined + images: ExportImageType[] | undefined name: string regulatoryAreas: RegulatoryLayerWithMetadata[] reportings: Reporting[] diff --git a/frontend/src/features/SideWindow/slice.ts b/frontend/src/features/SideWindow/slice.ts index 782540898..5eb79261f 100644 --- a/frontend/src/features/SideWindow/slice.ts +++ b/frontend/src/features/SideWindow/slice.ts @@ -25,7 +25,7 @@ export interface SideWindowState { } const INITIAL_STATE: SideWindowState = { bannerStack: bannerStackAdapter.getInitialState(), - currentPath: sideWindowPaths.MISSIONS, + currentPath: sideWindowPaths.DASHBOARDS, hasBeenRenderedOnce: false, showConfirmCancelModal: false, status: SideWindowStatus.CLOSED