From ea8a07f81c2e08d6683e1431db99be320692c6b5 Mon Sep 17 00:00:00 2001 From: Vincent de Graaf Date: Tue, 30 Apr 2024 16:22:03 +0200 Subject: [PATCH] [feat-5691]: wfs objects detail panel (#2842) * refactor map-data-to-selectable-feature * Fix test case * add mapper for caterpillar object data * add test cases * fix test * add test case map-to-selectable-feature * add test cases * remove selectable component when feature is selected * show description of status in detail panel * fix status on detail panel * re-add datapunt key * fix typo * Fix types --- app.amsterdam.json | 29 +- src/shared/services/map-location/index.ts | 1 + .../services/map-location/map-location.ts | 2 +- .../AssetList/AssetListItemSelectable.tsx | 11 +- .../AssetList/hooks/useSelectionProps.tsx | 5 +- .../Asset/Selector/WfsLayer/WfsLayer.test.tsx | 26 +- .../Asset/Selector/WfsLayer/WfsLayer.tsx | 23 +- .../WfsLayer/utils/get-feature-type.test.ts | 22 +- .../WfsLayer/utils/get-feature-type.ts | 15 +- .../WfsLayer/utils/get-object-type.test.ts | 40 --- .../WfsLayer/utils/get-object-type.ts | 29 -- .../utils/is-caterpillar-category.test.ts | 27 ++ .../WfsLayer/utils/is-caterpillar-category.ts | 10 + .../utils/map-caterpillar-features.test.ts | 72 +++++ .../utils/map-caterpillar-features.ts | 35 +++ .../map-data-to-selectable-feature.test.ts | 120 ++++++-- .../utils/map-data-to-selectable-feature.ts | 88 ++---- .../utils/map-to-selectable-feature.test.ts | 64 +++++ .../utils/map-to-selectable-feature.ts | 37 +++ .../WfsLayer/utils/test/mock-feature-types.ts | 19 +- .../WfsLayer/utils/test/mock-objects.ts | 272 ++++++++++-------- .../CaterpillarLayer/CaterpillarLayer.tsx | 2 +- .../eikenprocessierups.ts | 6 +- 23 files changed, 620 insertions(+), 335 deletions(-) delete mode 100644 src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/get-object-type.test.ts delete mode 100644 src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/get-object-type.ts create mode 100644 src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/is-caterpillar-category.test.ts create mode 100644 src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/is-caterpillar-category.ts create mode 100644 src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-caterpillar-features.test.ts create mode 100644 src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-caterpillar-features.ts create mode 100644 src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-to-selectable-feature.test.ts create mode 100644 src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-to-selectable-feature.ts diff --git a/app.amsterdam.json b/app.amsterdam.json index 25c7f540da..7c978b85c0 100644 --- a/app.amsterdam.json +++ b/app.amsterdam.json @@ -24,7 +24,6 @@ "showVulaanControls": true, "useProjectenSignalType": false, "showContactEdit": true - }, "head": { "androidIcon": "/icon_192x192.png", @@ -71,19 +70,10 @@ "municipality": "amsterdam \"ouder-amstel\" weesp", "options": { "crs": "EPSG:28992", - "center": [ - 52.3731081, - 4.8932945 - ], + "center": [52.3731081, 4.8932945], "maxBounds": [ - [ - 52.25168, - 4.64034 - ], - [ - 52.50536, - 5.10737 - ] + [52.25168, 4.64034], + [52.50536, 5.10737] ], "maxNumberOfAssets": { "straatverlichting": 3, @@ -100,22 +90,15 @@ "minZoom": 7, "zoom": 7 }, - "optionsAreaMap": { + "optionsAreaMap": { "focusRadiusMeters": 100, "zoom": 13 }, "tiles": { - "args": [ - "https://{s}.data.amsterdam.nl/topo_rd/{z}/{x}/{y}.png" - ], + "args": ["https://{s}.data.amsterdam.nl/topo_rd/{z}/{x}/{y}.png"], "options": { "attribution": "", - "subdomains": [ - "t1", - "t2", - "t3", - "t4" - ], + "subdomains": ["t1", "t2", "t3", "t4"], "tms": true } }, diff --git a/src/shared/services/map-location/index.ts b/src/shared/services/map-location/index.ts index c0ee1e5ffe..2c032e6a3d 100644 --- a/src/shared/services/map-location/index.ts +++ b/src/shared/services/map-location/index.ts @@ -8,6 +8,7 @@ export { pointWithinBounds, serviceResultToAddress, wktPointToLocation, + sanitizeCoordinates, } from './map-location' export type { PdokResponse, diff --git a/src/shared/services/map-location/map-location.ts b/src/shared/services/map-location/map-location.ts index fbaf7c41f9..c5b1d9e6b6 100644 --- a/src/shared/services/map-location/map-location.ts +++ b/src/shared/services/map-location/map-location.ts @@ -8,7 +8,7 @@ import type { Incident } from 'types/api/incident' import type { Geometrie, Location } from 'types/incident' import type { RevGeo, Doc } from 'types/pdok/revgeo' -const sanitizeCoordinates = (coordinates: LatLngTuple): LatLngTuple => +export const sanitizeCoordinates = (coordinates: LatLngTuple): LatLngTuple => coordinates.sort((a, b) => (a > b ? 1 : -1)).reverse() as LatLngTuple export const coordinatesToFeature = ({ diff --git a/src/signals/incident/components/form/MapSelectors/Asset/AssetList/AssetListItemSelectable.tsx b/src/signals/incident/components/form/MapSelectors/Asset/AssetList/AssetListItemSelectable.tsx index c5258ed2bd..913f87591c 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/AssetList/AssetListItemSelectable.tsx +++ b/src/signals/incident/components/form/MapSelectors/Asset/AssetList/AssetListItemSelectable.tsx @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright (C) 2023 Gemeente Amsterdam import { useSelectionProps } from './hooks/useSelectionProps' -import { StyledLabel, ListItem } from './styled' +import { StyledLabel, ListItem, StyledStatusDescription } from './styled' import { IconListItem } from '../../../../../../../components/IconList' import type { FeatureType, Item, FeatureStatusType } from '../../types' import type { SelectableFeature } from '../../types' @@ -38,7 +38,14 @@ export const AssetListItemSelectable = ({ item={item} checked={false} > - {item.label} + + {item.label} + {featureStatusType?.description && ( + + {featureStatusType?.description} + + )} + ) diff --git a/src/signals/incident/components/form/MapSelectors/Asset/AssetList/hooks/useSelectionProps.tsx b/src/signals/incident/components/form/MapSelectors/Asset/AssetList/hooks/useSelectionProps.tsx index 6bc9d245fb..99130afd82 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/AssetList/hooks/useSelectionProps.tsx +++ b/src/signals/incident/components/form/MapSelectors/Asset/AssetList/hooks/useSelectionProps.tsx @@ -33,7 +33,7 @@ export const useSelectionProps = ({ const { setItem } = useContext(AssetSelectContext) const { maxAssetWarning } = useSelector(makeSelectMaxAssetWarning) const featureStatusType = featureStatusTypes.find( - ({ typeValue }) => typeValue === status + ({ typeValue }) => typeValue === feature.status ) const item: Item = { ...feature, @@ -41,7 +41,8 @@ export const useSelectionProps = ({ status: featureStatusType?.typeValue, } - if (selection?.find((item) => item.id === feature.id)) return null + if (selection?.find((item) => item.id?.toString() === feature.id.toString())) + return null const { icon }: Partial = featureTypes?.find(({ typeValue }) => typeValue === feature.type) ?? {} diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/WfsLayer.test.tsx b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/WfsLayer.test.tsx index e076288dfa..a16556cdbe 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/WfsLayer.test.tsx +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/WfsLayer.test.tsx @@ -4,13 +4,14 @@ import type { FunctionComponent, ReactNode } from 'react' import { useContext } from 'react' import { Map } from '@amsterdam/react-maps' -import { act, render, screen } from '@testing-library/react' +import { act, render, screen, waitFor } from '@testing-library/react' import type { FeatureCollection } from 'geojson' import type { FetchMock } from 'jest-fetch-mock' -import type { MapOptions } from 'leaflet' +import type { LatLngTuple, MapOptions } from 'leaflet' import configuration from 'shared/services/configuration/configuration' import MAP_OPTIONS from 'shared/services/configuration/map-options' +import { sanitizeCoordinates } from 'shared/services/map-location' import assetsJson from 'utils/__tests__/fixtures/assets.json' import WfsDataContext, { NO_DATA } from './context' @@ -100,9 +101,24 @@ describe('src/signals/incident/components/form/AssetSelect/WfsLayer', () => { ) ) - await screen.findByTestId('map-test') - expect(setContextData).toHaveBeenCalledWith(assetsJson) - expect(fetchMock).toHaveBeenCalledTimes(1) + const sanitizedAssetsJson = { + ...assetsJson, + features: assetsJson.features.map((feature) => { + return { + ...feature, + geometry: { + ...feature.geometry, + coordinates: sanitizeCoordinates( + feature.geometry.coordinates as LatLngTuple + ), + }, + } + }), + } + + await waitFor(() => { + expect(setContextData).toHaveBeenCalledWith(sanitizedAssetsJson) + }) }) it('should not render when an AbortError occurs in the wfs call', () => { diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/WfsLayer.tsx b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/WfsLayer.tsx index 69c2a7aab1..d566ab3eec 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/WfsLayer.tsx +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/WfsLayer.tsx @@ -12,9 +12,12 @@ import AssetSelectContext from 'signals/incident/components/form/MapSelectors/As import type { DataLayerProps } from 'signals/incident/components/form/MapSelectors/types' import { NO_DATA, WfsDataProvider } from './context' -import { mapDataToSelectableFeature } from './utils' +import { isCaterpillarCategory } from './utils/is-caterpillar-category' +import { mapCaterpillarFeatures } from './utils/map-caterpillar-features' +import { mapDataToSelectableFeature } from './utils/map-data-to-selectable-feature' import useBoundingBox from '../../../hooks/useBoundingBox' import useLayerVisible from '../../../hooks/useLayerVisible' + export const SRS_NAME = 'EPSG:4326' export interface WfsLayerProps { @@ -89,14 +92,21 @@ const WfsLayer: FunctionComponent = ({ // eslint-disable-next-line no-console console.error('Unhandled Error in wfs call', result.error.message) } else { - setData(result) + let mappedResult = result + + if (isCaterpillarCategory(result.features[0])) { + mappedResult = mapCaterpillarFeatures(result) + } + + setData(mappedResult) - const mappedResult = mapDataToSelectableFeature( - result.features, - meta.featureTypes + const selectableFeatures = mapDataToSelectableFeature( + mappedResult.features, + meta.featureTypes, + meta.featureStatusTypes ) - setSelectableFeatures(mappedResult) + setSelectableFeatures(selectableFeatures) } return null }) @@ -123,6 +133,7 @@ const WfsLayer: FunctionComponent = ({ filter, setSelectableFeatures, meta.featureTypes, + meta.featureStatusTypes, ]) return {children} diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/get-feature-type.test.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/get-feature-type.test.ts index d32d2e8089..c092ca87c3 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/get-feature-type.test.ts +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/get-feature-type.test.ts @@ -1,13 +1,25 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright (C) 2023 Gemeente Amsterdam import { getFeatureType } from './get-feature-type' -import { mockContainerFeatureTypes } from './test/mock-feature-types' -import { mockGlasContainer } from './test/mock-objects' +import { + mockContainerFeatureTypes, + mockCaterpillarFeatureTypes, +} from './test/mock-feature-types' +import { mockContainers, mockCaterpillarFeature } from './test/mock-objects' describe('getFeatureType', () => { - it('should return the correct feature type', () => { - const result = getFeatureType(mockGlasContainer, mockContainerFeatureTypes) + it('should return the container feature type', () => { + const result = getFeatureType(mockContainers[0], mockContainerFeatureTypes) - expect(result).toEqual(mockContainerFeatureTypes[2]) + expect(result).toEqual(mockContainerFeatureTypes[1]) + }) + + it('should return the caterpillar feature type', () => { + const result = getFeatureType( + mockCaterpillarFeature[0], + mockCaterpillarFeatureTypes + ) + + expect(result).toEqual(mockCaterpillarFeatureTypes[0]) }) }) diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/get-feature-type.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/get-feature-type.ts index f4c13cdd02..dd39c59220 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/get-feature-type.ts +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/get-feature-type.ts @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MPL-2.0 -// Copyright (C) 2023 Gemeente Amsterdam -import type { Feature } from 'geojson' +// Copyright (C) 2024 Gemeente Amsterdam +import type { Feature } from 'signals/incident/components/form/MapSelectors/types' import type { FeatureType } from '../../../../types' -export const getFeatureType = ( - feature: Feature, - featureTypes: FeatureType[] -): FeatureType => - featureTypes.find( - ({ typeField, typeValue }) => feature.properties?.[typeField] === typeValue - ) as FeatureType +export const getFeatureType = (feature: Feature, featureTypes: FeatureType[]) => + featureTypes.find(({ typeField, typeValue }) => { + return feature.properties?.[typeField] === typeValue + }) diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/get-object-type.test.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/get-object-type.test.ts deleted file mode 100644 index b492aea4f5..0000000000 --- a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/get-object-type.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (C) 2023 Gemeente Amsterdam -import { getObjectType } from './get-object-type' -import { - mockGlasContainer, - mockPublicLight, - mockCaterpillar, -} from './test/mock-objects' -import { FeatureTypes } from '../../../../types' - -describe('getObjectType', () => { - it('should return container type', () => { - const result = getObjectType([mockGlasContainer]) - - expect(result).toEqual(FeatureTypes.CONTAINER) - }) - - it('should return public lights type', () => { - const result = getObjectType([mockPublicLight]) - - expect(result).toEqual(FeatureTypes.PUBLIC_LIGHTS) - }) - - it('should return caterpillar type', () => { - const result = getObjectType([mockCaterpillar]) - - expect(result).toEqual(FeatureTypes.CATERPILLAR) - }) - - it('should return caterpillar type when properties is an array with species', () => { - const caterpillarWithArrayProperties = { - ...mockCaterpillar, - properties: [mockCaterpillar.properties], - } - - const result = getObjectType([caterpillarWithArrayProperties]) - - expect(result).toEqual(FeatureTypes.CATERPILLAR) - }) -}) diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/get-object-type.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/get-object-type.ts deleted file mode 100644 index cb23c82e01..0000000000 --- a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/get-object-type.ts +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (C) 2023 Gemeente Amsterdam -import type { Feature } from 'geojson' - -import { FeatureTypes } from '../../../../types' - -export const getObjectType = (features: Feature[]) => { - if (!features[0].id) return null - - const feature = features[0] - - if (typeof feature.id === 'string' && feature.id?.startsWith('container')) { - return FeatureTypes.CONTAINER - } else if ( - typeof feature.id === 'string' && - feature.id.startsWith('openbareverlichting') - ) { - return FeatureTypes.PUBLIC_LIGHTS - } else if ( - typeof feature.id === 'number' && - feature.properties && - // properties is an object or an array with a species - ('species' in feature.properties || 'species' in feature.properties[0]) - ) { - return FeatureTypes.CATERPILLAR - } - - return null -} diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/is-caterpillar-category.test.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/is-caterpillar-category.test.ts new file mode 100644 index 0000000000..408a1975e0 --- /dev/null +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/is-caterpillar-category.test.ts @@ -0,0 +1,27 @@ +import { isCaterpillarCategory } from './is-caterpillar-category' +import { mockCaterpillarFeatureGeo } from './test/mock-objects' + +describe('isCaterpillarCategory', () => { + it('should return true when feature id is a number and species is in properties', () => { + expect(isCaterpillarCategory(mockCaterpillarFeatureGeo[0])).toBe(true) + }) + + it('should return true when feature id is a number and species is in properties array', () => { + expect(isCaterpillarCategory(mockCaterpillarFeatureGeo[1])).toBe(true) + }) + + it('should return false when feature id is not a number', () => { + expect( + isCaterpillarCategory({ ...mockCaterpillarFeatureGeo[0], id: undefined }) + ).toBe(false) + }) + + it('should return false when species is not in properties', () => { + expect( + isCaterpillarCategory({ + ...mockCaterpillarFeatureGeo[0], + properties: [{ type: 'tree' }], + }) + ).toBe(false) + }) +}) diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/is-caterpillar-category.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/is-caterpillar-category.ts new file mode 100644 index 0000000000..2ff2aa0ee7 --- /dev/null +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/is-caterpillar-category.ts @@ -0,0 +1,10 @@ +import type { Feature as FeatureGeo } from 'geojson' + +export const isCaterpillarCategory = (feature: FeatureGeo) => { + return ( + typeof feature.id === 'number' && + feature.properties && + // properties is an object or an array with a species + ('species' in feature.properties || 'species' in feature.properties[0]) + ) +} diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-caterpillar-features.test.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-caterpillar-features.test.ts new file mode 100644 index 0000000000..f373a6c044 --- /dev/null +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-caterpillar-features.test.ts @@ -0,0 +1,72 @@ +import { UNREGISTERED_TYPE } from 'signals/incident/components/form/MapSelectors/constants' + +import { mapCaterpillarFeatures } from './map-caterpillar-features' +import { mockCaterpillarFeatureGeo } from './test/mock-objects' + +describe('mapDataToSelectableFeature', () => { + it('should return an array of SelectableFeatures', () => { + const result = mapCaterpillarFeatures({ + features: mockCaterpillarFeatureGeo, + }) + + expect(result).toEqual({ + features: [ + { + id: 4108613, + type: 'Feature', + geometry: { type: 'Point', coordinates: [52.38632248, 4.87543579] }, + properties: { + species: 'Quercus robur', + type: 'Eikenboom', + id: 4108613, + }, + }, + { + id: 4108614, + type: 'Feature', + geometry: { type: 'Point', coordinates: [52.3863225, 4.8754357] }, + properties: { + species: 'Quercus robur', + type: 'Eikenboom', + id: 4108614, + }, + }, + ], + }) + }) + + it('should not map when type property already exists', () => { + const result = mapCaterpillarFeatures({ + features: [ + { + id: 4108613, + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [52.38632248, 4.87543579], + }, + properties: { + species: 'Quercus robur', + id: 4108613, + type: UNREGISTERED_TYPE, + }, + }, + ], + }) + + expect(result).toEqual({ + features: [ + { + geometry: { coordinates: [52.38632248, 4.87543579], type: 'Point' }, + id: 4108613, + properties: { + id: 4108613, + species: 'Quercus robur', + type: 'not-on-map', + }, + type: 'Feature', + }, + ], + }) + }) +}) diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-caterpillar-features.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-caterpillar-features.ts new file mode 100644 index 0000000000..eeedc6081b --- /dev/null +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-caterpillar-features.ts @@ -0,0 +1,35 @@ +import type { Feature as FeatuesGeo } from 'geojson' + +import type { Feature } from 'signals/incident/components/form/MapSelectors/types' + +interface Result { + features: FeatuesGeo[] +} + +export const mapCaterpillarFeatures = (result: Result) => { + const mappedFeatures = result.features.map((feature) => { + if (Array.isArray(feature.properties)) { + if (!feature.properties[0].type) { + return { + ...feature, + properties: { + ...feature.properties[0], + id: feature.id, + type: 'Eikenboom', + }, + } + } else return feature + } else if (!feature.properties?.type) { + return { + ...feature, + properties: { + ...feature.properties, + id: feature.id, + type: 'Eikenboom', + }, + } + } else return feature + }) as Feature[] + + return { ...result, features: mappedFeatures } +} diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-data-to-selectable-feature.test.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-data-to-selectable-feature.test.ts index fa86c6a24c..5d261a45d5 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-data-to-selectable-feature.test.ts +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-data-to-selectable-feature.test.ts @@ -1,74 +1,132 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (C) 2023 Gemeente Amsterdam +import type { + FeatureStatusType, + FeatureType, +} from 'signals/incident/components/form/MapSelectors/types' + import { mapDataToSelectableFeature } from './map-data-to-selectable-feature' import { mockContainerFeatureTypes, mockPublicLightsFeatureTypes, mockCaterpillarFeatureTypes, + mockPublicLightsFeatureTypesDenHaag, } from './test/mock-feature-types' import { - mockGlasContainer, - mockPaperContainer, - mockPublicLight, - mockCaterpillar, + mockContainers, + mockCaterpillarFeature, + mockFeaturesDenHaag, + mockPublicLights, } from './test/mock-objects' +const mockFeatureStatusTypes = undefined describe('mapDataToSelectableFeature', () => { - it('should map container features to selectable features correctly', () => { + it('should map containers to selectable features correctly', () => { const selectableFeatures = mapDataToSelectableFeature( - [mockGlasContainer, mockPaperContainer], - mockContainerFeatureTypes + mockContainers, + mockContainerFeatureTypes, + mockFeatureStatusTypes ) expect(selectableFeatures).toEqual([ { - coordinates: { lat: 52.37209240253326, lng: 4.900003434199737 }, - description: 'Glas container', - id: 'GLA00144', - label: 'Glas container - GLA00144', - type: 'Glas', - }, - { - coordinates: { lat: 52.37210126045667, lng: 4.900010236862614 }, - description: 'Papier container', - id: 'PAA00092', - label: 'Papier container - PAA00092', + id: 'PAA00210', type: 'Papier', + description: 'Papier container', + status: undefined, + label: 'Papier container - PAA00210', + coordinates: { lat: 52.37585547675836, lng: 4.89321686975306 }, }, ]) }) - it('should map public lights features to selectable features correctly', () => { + it('should map public lights to selectable features correctly', () => { const selectableFeatures = mapDataToSelectableFeature( - [mockPublicLight], - mockPublicLightsFeatureTypes + mockPublicLights, + mockPublicLightsFeatureTypes, + mockFeatureStatusTypes ) expect(selectableFeatures).toEqual([ { - coordinates: { lat: 52.372935004142086, lng: 4.901763001239158 }, - description: 'Overig lichtpunt', id: '000067', - label: 'Overig lichtpunt - 000067', type: '4', + description: 'Overig lichtpunt', + status: undefined, + label: 'Overig lichtpunt - 000067', + coordinates: { lat: 52.372935004142086, lng: 4.901763001239158 }, }, ]) }) - it('should map caterpillar features to selectable features correctly', () => { + it('should map caterpillar objects to selectable features correctly', () => { const selectableFeatures = mapDataToSelectableFeature( - [mockCaterpillar], - mockCaterpillarFeatureTypes + mockCaterpillarFeature, + mockCaterpillarFeatureTypes, + mockFeatureStatusTypes ) expect(selectableFeatures).toEqual([ { - id: 4108613, + id: '4108613', type: 'Eikenboom', description: 'Eikenboom', - coordinates: { lat: 52.38632248, lng: 4.87543579 }, + status: undefined, label: 'Eikenboom - 4108613', + coordinates: { lat: 52.38632248, lng: 4.87543579 }, + }, + { + id: '4108614', + type: 'Eikenboom', + description: 'Eikenboom', + status: undefined, + label: 'Eikenboom - 4108614', + coordinates: { lat: 52.3863225, lng: 4.8754357 }, }, ]) }) + + it('should map features to selectable features correctly with Den Haag light objects', () => { + const selectableFeatures = mapDataToSelectableFeature( + mockFeaturesDenHaag, + mockPublicLightsFeatureTypesDenHaag as FeatureType[], + [] + ) + + expect(selectableFeatures).toEqual([ + { + id: '230281', + type: undefined, + description: 'Lichtpunt {{ MastCode }} ', + status: undefined, + label: 'Lichtpunt Koningskade-0542 ', + coordinates: { lat: 52.08410811, lng: 4.31817273 }, + }, + ]) + }) + + it('should create label correctly when description is a template string', () => { + const mockFeatureTypesWithTemplateString = [ + { + label: 'Papier', + description: 'Papier container - {{ id_nummer }}', + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/afval/paper.svg', + }, + idField: 'id_nummer', + typeField: 'fractie_omschrijving', + typeValue: 'Papier', + }, + ] as FeatureStatusType[] + + const selectableFeatures = mapDataToSelectableFeature( + mockContainers, + mockFeatureTypesWithTemplateString, + mockFeatureStatusTypes + ) + + expect(selectableFeatures[0].label).toEqual('Papier container - PAA00210') + }) }) diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-data-to-selectable-feature.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-data-to-selectable-feature.ts index 2101f82e04..c7a166941b 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-data-to-selectable-feature.ts +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-data-to-selectable-feature.ts @@ -1,68 +1,40 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (C) 2023 Gemeente Amsterdam -import type { Feature } from 'geojson' - import { featureToCoordinates } from 'shared/services/map-location' +import type { + Feature, + FeatureStatusType, +} from 'signals/incident/components/form/MapSelectors/types' import type { Geometrie } from 'types/incident' -import { - isTemplateString, - parseTemplateString, -} from 'utils/parseTemplateString' import { getFeatureType } from './get-feature-type' -import { getObjectType } from './get-object-type' -import { FeatureTypes } from '../../../../types' +import { mapToSelectableFeature } from './map-to-selectable-feature' import type { FeatureType, SelectableFeature } from '../../../../types' +import { getFeatureStatusType } from '../../StatusLayer/utils' export const mapDataToSelectableFeature = ( features: Feature[], - featureTypes: FeatureType[] + featureTypes: FeatureType[], + featureStatusTypes: FeatureStatusType[] = [] ): SelectableFeature[] => { - if (!features || features.length === 0) return [] - - const objectType = getObjectType(features) - - switch (objectType) { - case FeatureTypes.CONTAINER: - case FeatureTypes.PUBLIC_LIGHTS: - return features.map((feature) => { - const { idField, description, typeValue } = getFeatureType( - feature, - featureTypes - ) - - const id_number = - (feature.properties && feature.properties[idField]) || '' - const label = isTemplateString(description) - ? parseTemplateString(description, feature.properties) - : [description, id_number].filter(Boolean).join(' - ') - - const coordinates = featureToCoordinates(feature?.geometry as Geometrie) - - return { - coordinates, - description: description, - id: id_number, - label: label, - type: typeValue, - } - }) - case FeatureTypes.CATERPILLAR: - return features.map((feature) => { - const { description, typeValue } = featureTypes[0] - - const coordinates = featureToCoordinates(feature?.geometry as Geometrie) - - return { - id: feature.id || '', - type: typeValue, - description, - coordinates, - label: [description, feature.id].filter(Boolean).join(' - '), - } - }) - - default: - return [] - } + const selectableFeatures = features + .map((feature) => { + const coordinates = featureToCoordinates(feature?.geometry as Geometrie) + const featureType = getFeatureType(feature, featureTypes) + const featureStatusType = getFeatureStatusType( + feature, + featureStatusTypes + ) + + if (!featureType) return null + + return mapToSelectableFeature( + feature, + featureType, + coordinates, + featureStatusType + ) + }) + // filter out null values + .flatMap((f) => (f ? [f] : [])) + + return selectableFeatures } diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-to-selectable-feature.test.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-to-selectable-feature.test.ts new file mode 100644 index 0000000000..6158794e8d --- /dev/null +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-to-selectable-feature.test.ts @@ -0,0 +1,64 @@ +import { mapToSelectableFeature } from './map-to-selectable-feature' +import { + mockContainerFeatureTypes, + mockPublicLightsFeatureTypes, + mockCaterpillarFeatureTypes, +} from './test/mock-feature-types' +import { + mockContainers, + mockCaterpillarFeature, + mockPublicLights, +} from './test/mock-objects' + +describe('mapToSelectableFeature', () => { + it('should map container to a selectable feature', () => { + const result = mapToSelectableFeature( + mockContainers[0], + mockContainerFeatureTypes[1], + { lat: 52.37585547675836, lng: 4.89321686975306 } + ) + + expect(result).toEqual({ + id: 'PAA00210', + type: 'Papier', + description: 'Papier container', + status: undefined, + label: 'Papier container - PAA00210', + coordinates: { lat: 52.37585547675836, lng: 4.89321686975306 }, + }) + }) + + it('should map public light to a selectable feature', () => { + const result = mapToSelectableFeature( + mockPublicLights[0], + mockPublicLightsFeatureTypes[0], + { lat: 52.372935004142086, lng: 4.901763001239158 } + ) + + expect(result).toEqual({ + id: '000067', + type: '5', + description: 'Grachtmast', + status: undefined, + label: 'Grachtmast - 000067', + coordinates: { lat: 52.372935004142086, lng: 4.901763001239158 }, + }) + }) + + it('should map caterpillar tree to a selectable feature', () => { + const result = mapToSelectableFeature( + mockCaterpillarFeature[0], + mockCaterpillarFeatureTypes[0], + { lat: 52.38632248, lng: 4.87543579 } + ) + + expect(result).toEqual({ + id: '4108613', + type: 'Eikenboom', + description: 'Eikenboom', + status: undefined, + label: 'Eikenboom - 4108613', + coordinates: { lat: 52.38632248, lng: 4.87543579 }, + }) + }) +}) diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-to-selectable-feature.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-to-selectable-feature.ts new file mode 100644 index 0000000000..2e989a0f0e --- /dev/null +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/map-to-selectable-feature.ts @@ -0,0 +1,37 @@ +import type { LatLngLiteral } from 'leaflet' + +import type { + Feature, + FeatureStatusType, +} from 'signals/incident/components/form/MapSelectors/types' +import { + isTemplateString, + parseTemplateString, +} from 'utils/parseTemplateString' + +import type { FeatureType, SelectableFeature } from '../../../../types' + +export const mapToSelectableFeature = ( + feature: Feature, + featureType: FeatureType, + coordinates: LatLngLiteral, + featureStatusType?: FeatureStatusType +): SelectableFeature => { + const { description, typeValue, idField } = featureType + const id = feature.properties[idField] || '' + + const label = isTemplateString(description) + ? parseTemplateString(description, feature.properties) + : [description, id].filter(Boolean).join(' - ') + + const selectableFeature = { + id: id.toString(), + type: typeValue, + description, + status: featureStatusType?.typeValue, + label, + coordinates, + } + + return selectableFeature +} diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/test/mock-feature-types.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/test/mock-feature-types.ts index 278590807b..6562092f3e 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/test/mock-feature-types.ts +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/test/mock-feature-types.ts @@ -215,9 +215,9 @@ export const mockCaterpillarFeatureTypes: FeatureType[] = [ }, iconUrl: '/assets/images/groen_water/oak.svg', }, - idField: 'OBJECTID', + idField: 'id', typeValue: 'Eikenboom', - typeField: '', + typeField: 'type', }, { idField: 'UNKNOWN', @@ -234,3 +234,18 @@ export const mockCaterpillarFeatureTypes: FeatureType[] = [ typeField: '', }, ] + +export const mockPublicLightsFeatureTypesDenHaag = [ + { + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/openbare_verlichting/overig.svg', + }, + label: 'Straatverlichting', + idField: 'LumiId', + description: 'Lichtpunt {{ MastCode }} ', + }, +] diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/test/mock-objects.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/test/mock-objects.ts index f73ce209ab..92f9519547 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/test/mock-objects.ts +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector/WfsLayer/utils/test/mock-objects.ts @@ -1,132 +1,168 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright (C) 2023 Gemeente Amsterdam +import type { Feature as FeaturesGeo } from 'geojson' + import type { Feature } from '../../../../../types' -export const mockGlasContainer: Feature = { - type: 'Feature', - id: 'container.16154', - geometry_name: '16154', - geometry: { - type: 'Point', - coordinates: [52.37209240253326, 4.900003434199737], +export const mockContainers: Feature[] = [ + { + type: 'Feature', + id: 'container.184574', + geometry_name: '184574', + geometry: { + type: 'Point', + coordinates: [52.37585547675836, 4.89321686975306], + }, + properties: { + id: '184574', + id_nummer: 'PAA00210', + serienummer: 'HBD.2022.2853', + cluster_id: '121361.350|487667.794', + eigenaar_id: '112', + eigenaar_naam: 'A Centrum', + status: 1, + fractie_code: '3', + fractie_omschrijving: 'Papier', + datum_creatie: '2022-03-15', + datum_plaatsing: '2023-02-01', + datum_operationeel: '2023-12-12', + datum_aflopen_garantie: '2032-12-01', + datum_oplevering: '2022-12-01', + wijzigingsdatum_dp: '2024-03-28T22:00:00+00:00', + verwijderd_dp: false, + geadopteerd_ind: false, + locatie_id: '184575', + type_id: '5666', + bag_hoofdadres_verblijfsobject_id: '0363010001027867', + gbd_buurt_id: '03630980000044', + bag_openbareruimte_id: '0363300000004690', + bag_nummeraanduiding_id: '0363200000516676', + container_ral_kleur_naam: null, + container_ral_kleur_code: null, + container_ral_kleur_hexcode: null, + container_chip_nummber: null, + container_unit_card_lezer_id: null, + container_kleur: 'NCS7000', + container_mark: 10, + container_datum_vervanging: '2032-02-01', + container_datum_wijziging: '2023-12-11T23:00:00+00:00', + container_end_of_life: null, + container_eigenaarschap: 'Eigendom', + container_eigenaarschap_opmerking: + 'Container is in eigendom van de klant', + container_opmerking: null, + }, }, - properties: { - id: '16154', - id_nummer: 'GLA00144', - serienummer: '660512', - cluster_id: '121821.017|487246.299', - eigenaar_id: '112', - eigenaar_naam: 'A Centrum', - status: 1, - fractie_code: '2', - fractie_omschrijving: 'Glas', - datum_creatie: '2012-03-29', - datum_plaatsing: '2013-02-01', - datum_operationeel: '2013-02-01', - datum_aflopen_garantie: '2020-01-01', - datum_oplevering: '2013-01-01', - wijzigingsdatum_dp: '2023-12-05T18:09:17.602034+00:00', - verwijderd_dp: false, - geadopteerd_ind: true, - locatie_id: '16153', - type_id: '356', - bag_hoofdadres_verblijfsobject_id: '0363010002005059', - gbd_buurt_id: '03630980000060', - bag_openbareruimte_id: '0363300000003878', - bag_nummeraanduiding_id: '0363200002004858', - container_ral_kleur_naam: null, - container_ral_kleur_code: null, - container_ral_kleur_hexcode: null, - container_chip_nummber: null, - container_unit_card_lezer_id: null, - container_kleur: 'Gris 900 sable', - container_mark: 3, - container_datum_vervanging: '2023-01-01', - container_datum_wijziging: '2023-07-02T22:00:00+00:00', - container_end_of_life: null, - container_eigenaarschap: 'Eigendom', - container_eigenaarschap_opmerking: 'Container is in eigendom van de klant', - container_opmerking: null, +] + +export const mockPublicLights: Feature[] = [ + { + type: 'Feature', + id: 'openbareverlichting.43', + geometry_name: '000067', + geometry: { + type: 'Point', + coordinates: [52.372935004142086, 4.901763001239158], + }, + properties: { + id: 43, + object_id: '55', + objecttype: '4', + objecttype_omschrijving: 'LSD Objecten', + objectnummer: '000067', + breedtegraad: 52.3729346862489, + lengtegraad: 4.90176284379253, + storingstatus: 0, + meldingstatus: 0, + }, }, -} +] -export const mockPaperContainer: Feature = { - type: 'Feature', - id: 'container.16156', - geometry_name: '16156', - geometry: { - type: 'Point', - coordinates: [52.37210126045667, 4.900010236862614], +export const mockCaterpillarFeatureGeo: FeaturesGeo[] = [ + { + id: 4108613, + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [52.38632248, 4.87543579], + }, + properties: { + species: 'Quercus robur', + id: 4108613, + }, }, - properties: { - id: '16156', - id_nummer: 'PAA00092', - serienummer: '660436', - cluster_id: '121821.017|487246.299', - eigenaar_id: '112', - eigenaar_naam: 'A Centrum', - status: 1, - fractie_code: '3', - fractie_omschrijving: 'Papier', - datum_creatie: '2012-03-29', - datum_plaatsing: '2012-02-01', - datum_operationeel: '2013-02-01', - datum_aflopen_garantie: '2020-01-01', - datum_oplevering: '2013-01-01', - wijzigingsdatum_dp: '2023-12-05T18:09:17.602034+00:00', - verwijderd_dp: false, - geadopteerd_ind: true, - locatie_id: '16155', - type_id: '357', - bag_hoofdadres_verblijfsobject_id: '0363010002005059', - gbd_buurt_id: '03630980000060', - bag_openbareruimte_id: '0363300000003878', - bag_nummeraanduiding_id: '0363200002004858', - container_ral_kleur_naam: null, - container_ral_kleur_code: null, - container_ral_kleur_hexcode: null, - container_chip_nummber: null, - container_unit_card_lezer_id: null, - container_kleur: 'Gris 900 sable', - container_mark: 1, - container_datum_vervanging: '2022-02-01', - container_datum_wijziging: '2023-07-02T22:00:00+00:00', - container_end_of_life: null, - container_eigenaarschap: 'Eigendom', - container_eigenaarschap_opmerking: 'Container is in eigendom van de klant', - container_opmerking: null, + { + id: 4108614, + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [52.3863225, 4.8754357], + }, + properties: [ + { + species: 'Quercus robur', + id: 4108614, + }, + ], }, -} +] -export const mockPublicLight: Feature = { - type: 'Feature', - id: 'openbareverlichting.43', - geometry_name: '000067', - geometry: { - type: 'Point', - coordinates: [52.372935004142086, 4.901763001239158], +export const mockCaterpillarFeature: Feature[] = [ + { + id: 4108613, + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [52.38632248, 4.87543579], + }, + properties: { + species: 'Quercus robur', + id: 4108613, + type: 'Eikenboom', + }, }, - properties: { - id: 43, - object_id: '55', - objecttype: '4', - objecttype_omschrijving: 'LSD Objecten', - objectnummer: '000067', - breedtegraad: 52.3729346862489, - lengtegraad: 4.90176284379253, - storingstatus: 0, - meldingstatus: 0, + { + id: 4108614, + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [52.3863225, 4.8754357], + }, + properties: { + species: 'Quercus robur', + id: 4108614, + type: 'Eikenboom', + }, }, -} +] -export const mockCaterpillar: Feature = { - id: 4108613, - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [52.38632248, 4.87543579], - }, - properties: { - species: 'Quercus robur', +export const mockFeaturesDenHaag: Feature[] = [ + { + type: 'Feature', + id: 'mast.23661', + geometry: { + type: 'Point', + coordinates: [52.08410811, 4.31817273], + }, + geometry_name: 'the_geom', + properties: { + LumiId: 230281, + KastCode: 'Z508', + MastCode: 'Koningskade-0542', + MastNr: '0542', + MastHoogte: '32', + MastGroep: 'Z508-11', + MastPlaats: '2009-12-02T23:00:00Z', + MastMateri: '', + UithouderT: '', + Straat: 'Koningskade', + MastType: 'HAAGSE MAST', + ArmType: 'PHILIPS MONTMARTRE IJS CPO 45', + ObjectType: 'mast', + MastVerfSo: '', + ArmatuurHo: '', + MastSchild: '2023-11-15T23:00:00Z', + MastRempla: null, + }, }, -} +] diff --git a/src/signals/incident/components/form/MapSelectors/Caterpillar/CaterpillarLayer/CaterpillarLayer.tsx b/src/signals/incident/components/form/MapSelectors/Caterpillar/CaterpillarLayer/CaterpillarLayer.tsx index 7bade5bd54..fac7fafd50 100644 --- a/src/signals/incident/components/form/MapSelectors/Caterpillar/CaterpillarLayer/CaterpillarLayer.tsx +++ b/src/signals/incident/components/form/MapSelectors/Caterpillar/CaterpillarLayer/CaterpillarLayer.tsx @@ -35,7 +35,7 @@ export const CaterpillarLayer: FC = () => { const featureId = feature.id const isSelected = Boolean( - selection?.find((item) => item.id === featureId) + selection?.find((item) => item.id?.toString() === featureId?.toString()) ) const featureStatusType = getFeatureStatusType( diff --git a/src/signals/incident/definitions/wizard-step-2-vulaan/eikenprocessierups.ts b/src/signals/incident/definitions/wizard-step-2-vulaan/eikenprocessierups.ts index 24743b8a3a..50b639cc26 100644 --- a/src/signals/incident/definitions/wizard-step-2-vulaan/eikenprocessierups.ts +++ b/src/signals/incident/definitions/wizard-step-2-vulaan/eikenprocessierups.ts @@ -45,9 +45,9 @@ export const controls = { options, iconUrl: '/assets/images/groen_water/oak.svg', }, - idField: 'OBJECTID', + idField: 'id', typeValue: 'Eikenboom', - typeField: '', + typeField: 'type', }, { label: 'Onbekend', @@ -57,7 +57,7 @@ export const controls = { iconUrl: '/assets/images/feature-unknown-marker.svg', }, typeValue: UNREGISTERED_TYPE, - typeField: '', + typeField: 'type', }, ], featureStatusTypes: [