From 6f072a81668901c499202d74033b41ac9e944996 Mon Sep 17 00:00:00 2001 From: Vincent de Graaf Date: Wed, 20 Dec 2023 13:14:55 +0100 Subject: [PATCH] [feat-5588]: refactor containers in detail panel (#2788) * Refactor assetSelectableObject logic * Fix TSLint warnings * Fix tests * Fix get-feture-type test * Cleanup code * Add public lights case to mapper\ * PR feedback --- src/components/IconList/IconList.tsx | 3 +- .../Asset/AssetList/AssetList.test.tsx | 31 +-- .../Asset/AssetList/AssetList.tsx | 15 +- .../AssetList/AssetListItemSelectable.tsx | 5 +- .../hooks/useSelectionProps.test.tsx | 34 +-- .../AssetList/hooks/useSelectionProps.tsx | 72 ++---- .../form/MapSelectors/Asset/AssetSelect.tsx | 11 +- .../Selector/StatusLayer/StatusLayer.tsx | 2 +- .../WfsLayer/AssetLayer/AssetLayer.tsx | 2 +- .../DetailPanel/styled.tsx | 2 +- .../StatusLayer/StatusLayer.tsx | 2 +- .../WfsLayer/AssetLayer/AssetLayer.tsx | 2 +- .../WfsLayer/WfsLayer.tsx | 21 +- .../WfsLayer/utils/get-feature-type.test.ts | 13 + .../WfsLayer/utils/get-feature-type.ts | 13 + .../WfsLayer/utils/get-object-type.test.ts | 28 +++ .../WfsLayer/utils/get-object-type.ts | 28 +++ .../WfsLayer/utils/index.ts | 1 + .../map-data-to-selectable-feature.test.ts | 55 ++++ .../utils/map-data-to-selectable-feature.ts | 56 +++++ .../WfsLayer/utils/test/mock-feature-types.ts | 236 ++++++++++++++++++ .../WfsLayer/utils/test/mock-objects.ts | 132 ++++++++++ .../form/MapSelectors/Asset/types.ts | 8 +- .../components/form/MapSelectors/types.ts | 25 +- 24 files changed, 675 insertions(+), 122 deletions(-) create mode 100644 src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/get-feature-type.test.ts create mode 100644 src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/get-feature-type.ts create mode 100644 src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/get-object-type.test.ts create mode 100644 src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/get-object-type.ts create mode 100644 src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/index.ts create mode 100644 src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/map-data-to-selectable-feature.test.ts create mode 100644 src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/map-data-to-selectable-feature.ts create mode 100644 src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/test/mock-feature-types.ts create mode 100644 src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/test/mock-objects.ts diff --git a/src/components/IconList/IconList.tsx b/src/components/IconList/IconList.tsx index edbd74dce9..2a8d83ed97 100644 --- a/src/components/IconList/IconList.tsx +++ b/src/components/IconList/IconList.tsx @@ -14,6 +14,7 @@ import type { import { makeSelectMaxAssetWarning } from 'signals/incident/containers/IncidentContainer/selectors' import { StyledListItem, StyledImg, StatusIcon } from './styled' +import type { SelectableFeature } from '../../signals/incident/components/form/MapSelectors/types' import Checkbox from '../Checkbox' export interface IconListItemProps { @@ -24,7 +25,7 @@ export interface IconListItemProps { featureStatusType?: FeatureStatusType children: ReactNode onClick?: (item: Item) => void - item?: Item + item?: SelectableFeature | Item checkboxDisabled?: boolean checked?: boolean } diff --git a/src/signals/incident/components/form/MapSelectors/Asset/AssetList/AssetList.test.tsx b/src/signals/incident/components/form/MapSelectors/Asset/AssetList/AssetList.test.tsx index 18b0bf57ed..2327e574f0 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/AssetList/AssetList.test.tsx +++ b/src/signals/incident/components/form/MapSelectors/Asset/AssetList/AssetList.test.tsx @@ -111,27 +111,16 @@ describe('AssetList', () => { coordinates: { lat: 1, lng: 2 }, }, ], - selectableFeatures: { - type: 'FeatureCollection', - features: [ - { - type: 'Feature', - id: '123', - properties: { - fractie_omschrijving: 'Rest', - id: '123', - type: 'Rest', - status: FeatureStatus.REPORTED, - label: 'Rest container - 123', - }, - geometry: { - type: 'Point', - coordinates: [1, 2], - }, - }, - ], - }, - objectTypePlural: 'objecten', + selectableFeatures: [ + { + type: 'Feature', + id: '123', + coordinates: { lat: 1, lng: 2 }, + status: FeatureStatus.REPORTED, + label: 'Rest container - 123', + description: 'Rest', + }, + ], } const reportedProps: AssetListProps = { diff --git a/src/signals/incident/components/form/MapSelectors/Asset/AssetList/AssetList.tsx b/src/signals/incident/components/form/MapSelectors/Asset/AssetList/AssetList.tsx index 6412bf05b6..61bd8f8fa1 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/AssetList/AssetList.tsx +++ b/src/signals/incident/components/form/MapSelectors/Asset/AssetList/AssetList.tsx @@ -2,16 +2,14 @@ // Copyright (C) 2021 - 2023 Gemeente Amsterdam import type { FunctionComponent } from 'react' -import type { FeatureCollection } from 'geojson' - import IconList from 'components/IconList/IconList' import { capitalize } from 'shared/services/date-utils' import { AssetListItem } from './AssetListItem' import { AssetListItemSelectable } from './AssetListItemSelectable' import { ListDescription, ListHeading } from './styled' -import type { Feature } from '../../types' import type { FeatureStatusType, FeatureType, Item } from '../../types' +import type { SelectableFeature } from '../../types' export interface AssetListProps { className?: string @@ -20,7 +18,7 @@ export interface AssetListProps { objectTypePlural?: string remove?: (item: Item) => void selection?: Item[] - selectableFeatures?: FeatureCollection + selectableFeatures?: SelectableFeature[] zoomLevel?: number } @@ -36,8 +34,7 @@ const AssetList: FunctionComponent = ({ }) => { const selectableComponents = selectableFeatures && - selectableFeatures?.features?.map((feat: any) => { - const feature = feat as Feature + selectableFeatures?.map((feature) => { return ( = ({ ) && ( {`Er zijn geen ${ - objectTypePlural || 'Objecten' + objectTypePlural || 'objecten' } in de buurt. Versleep de kaart om de ${ - objectTypePlural || 'Objecten' - } te zien.`} + objectTypePlural || 'objecten' + } te zien.`} )} 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 bc25625040..c5258ed2bd 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/AssetList/AssetListItemSelectable.tsx +++ b/src/signals/incident/components/form/MapSelectors/Asset/AssetList/AssetListItemSelectable.tsx @@ -3,12 +3,13 @@ import { useSelectionProps } from './hooks/useSelectionProps' import { StyledLabel, ListItem } from './styled' import { IconListItem } from '../../../../../../../components/IconList' -import type { Feature, FeatureType, Item, FeatureStatusType } from '../../types' +import type { FeatureType, Item, FeatureStatusType } from '../../types' +import type { SelectableFeature } from '../../types' export type Props = { featureTypes: FeatureType[] featureStatusTypes: FeatureStatusType[] - feature: Feature + feature: SelectableFeature selection?: Item[] } diff --git a/src/signals/incident/components/form/MapSelectors/Asset/AssetList/hooks/useSelectionProps.test.tsx b/src/signals/incident/components/form/MapSelectors/Asset/AssetList/hooks/useSelectionProps.test.tsx index 00240e1015..c126b0cc41 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/AssetList/hooks/useSelectionProps.test.tsx +++ b/src/signals/incident/components/form/MapSelectors/Asset/AssetList/hooks/useSelectionProps.test.tsx @@ -70,26 +70,16 @@ describe('useSelectionProps', () => { coordinates: { lat: 1, lng: 2 }, }, ], - selectableFeatures: { - type: 'FeatureCollection', - features: [ - { - type: 'Feature', - id: '123', - properties: { - fractie_omschrijving: 'Rest', - id: '123', - type: 'Rest', - status: FeatureStatus.REPORTED, - label: 'Rest container - 123', - }, - geometry: { - type: 'Point', - coordinates: [1, 2], - }, - }, - ], - }, + selectableFeatures: [ + { + type: 'Feature', + id: '123', + coordinates: { lat: 2, lng: 1 }, + status: FeatureStatus.REPORTED, + label: 'Rest container - 123', + description: 'Rest', + }, + ], } beforeEach(() => { @@ -107,7 +97,7 @@ describe('useSelectionProps', () => { useSelectionProps({ featureTypes: props.featureTypes, featureStatusTypes: props.featureStatusTypes, - feature: props?.selectableFeatures?.features[0] as any, + feature: props?.selectableFeatures?.[0] as any, selection: [], }) ) @@ -126,7 +116,7 @@ describe('useSelectionProps', () => { useSelectionProps({ featureTypes: props.featureTypes, featureStatusTypes: props.featureStatusTypes, - feature: props?.selectableFeatures?.features[0] as any, + feature: props?.selectableFeatures?.[0] as any, selection: [ { description: '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 1d23f9412b..69ba3a57d3 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 @@ -4,27 +4,26 @@ import { useContext } from 'react' import { useSelector } from 'react-redux' +import reverseGeocoderService from 'shared/services/reverse-geocoder' import { makeSelectMaxAssetWarning } from 'signals/incident/containers/IncidentContainer/selectors' +import type { Location } from 'types/incident' -import { featureToCoordinates } from '../../../../../../../../shared/services/map-location' -import reverseGeocoderService from '../../../../../../../../shared/services/reverse-geocoder' import type { - Geometrie, - Location, -} from '../../../../../../../../types/incident' -import { - isTemplateString, - parseTemplateString, -} from '../../../../../../../../utils/parseTemplateString' -import type { Feature, FeatureType, Item } from '../../../types' + FeatureStatusType, + FeatureType, + Item, + SelectableFeature, +} from '../../../types' import { FeatureStatus } from '../../../types' import AssetSelectContext from '../../context' -import type { Props } from '../AssetListItemSelectable' -const getFeatureType = (feature: Feature, featureTypes: FeatureType[]) => { - return featureTypes.find( - ({ typeField, typeValue }) => feature.properties[typeField] === typeValue - ) + +type Props = { + featureTypes: FeatureType[] + featureStatusTypes: FeatureStatusType[] + feature: SelectableFeature + selection?: Item[] } + export const useSelectionProps = ({ featureTypes, featureStatusTypes, @@ -33,50 +32,25 @@ export const useSelectionProps = ({ }: Props) => { const { setItem } = useContext(AssetSelectContext) const { maxAssetWarning } = useSelector(makeSelectMaxAssetWarning) - - const coordinates = featureToCoordinates(feature.geometry as Geometrie) - - const featureType = getFeatureType(feature, featureTypes) - if (!featureType) return null - - const { description, typeValue, idField } = featureType - const id = feature.properties[idField] || '' - const featureStatusType = featureStatusTypes.find( ({ typeValue }) => typeValue === status ) - - const label = isTemplateString(description) - ? parseTemplateString(description, feature.properties) - : [description, id].filter(Boolean).join(' - ') - const item: Item = { - id: `${coordinates.lat}.${coordinates.lng}.${feature.properties.created_at}`, - label: label, - description: featureType.description, - type: featureType.typeValue, - coordinates, + ...feature, + address: undefined, + status: featureStatusType?.typeValue, } - if (selection?.find((item) => item.id === id)) return null + if (selection?.find((item) => item.id === feature.id)) return null const { icon }: Partial = - featureTypes?.find(({ typeValue }) => typeValue === item.type) ?? {} + featureTypes?.find(({ typeValue }) => typeValue === feature.type) ?? {} const onClick = async () => { - if (typeValue !== FeatureStatus.REPORTED && !maxAssetWarning) { - const location: Location = { coordinates } - - const item: Item = { - id, - type: typeValue, - description, - status: featureStatusType?.typeValue, - label, - coordinates, - } + if (feature.type !== FeatureStatus.REPORTED && !maxAssetWarning) { + const location: Location = { coordinates: feature.coordinates } - const response = await reverseGeocoderService(coordinates) + const response = await reverseGeocoderService(feature.coordinates) if (response) { location.address = response.data.address @@ -87,5 +61,5 @@ export const useSelectionProps = ({ } } - return { id, item, featureStatusType, icon, onClick } + return { id: feature.id, item: feature, featureStatusType, icon, onClick } } diff --git a/src/signals/incident/components/form/MapSelectors/Asset/AssetSelect.tsx b/src/signals/incident/components/form/MapSelectors/Asset/AssetSelect.tsx index 6df4329acb..f95e2a1663 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/AssetSelect.tsx +++ b/src/signals/incident/components/form/MapSelectors/Asset/AssetSelect.tsx @@ -4,7 +4,6 @@ import type { FC } from 'react' import { useEffect } from 'react' import { useCallback, useState } from 'react' -import type { FeatureCollection } from 'geojson' import type { LatLngLiteral } from 'leaflet' import { useDispatch, useSelector } from 'react-redux' @@ -20,7 +19,13 @@ import Selector from './Selector' import SelectorV2 from './Selector_v2_removeafterfinishepic5440' import configuration from '../../../../../../shared/services/configuration/configuration' import { UNKNOWN_TYPE, UNREGISTERED_TYPE } from '../constants' -import type { FeatureStatusType, FeatureType, Item, Meta } from '../types' +import type { + FeatureStatusType, + FeatureType, + Item, + Meta, + SelectableFeature, +} from '../types' const defaultIconConfig: FeatureType['icon'] = { options: { @@ -66,7 +71,7 @@ const AssetSelect: FC = ({ value, layer, meta, parent }) => { const { selection, location } = value || {} const [message, setMessage] = useState() const [selectableFeatures, setSelectableFeatures] = useState< - FeatureCollection | undefined + SelectableFeature[] | undefined >(undefined) const { mapActive } = useSelector(makeSelectIncidentContainer) const [featureTypes, setFeatureTypes] = useState([]) diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector/StatusLayer/StatusLayer.tsx b/src/signals/incident/components/form/MapSelectors/Asset/Selector/StatusLayer/StatusLayer.tsx index 10981f26e9..acfbf37a01 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/Selector/StatusLayer/StatusLayer.tsx +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector/StatusLayer/StatusLayer.tsx @@ -44,7 +44,7 @@ const StatusLayer: FC = ({ return ( { const location: Location = { coordinates } const item: Item = { - id, + id: id.toString(), type: typeValue, description, status: featureStatusType?.typeValue, diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/DetailPanel/styled.tsx b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/DetailPanel/styled.tsx index 5721368264..9fb49bca04 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/DetailPanel/styled.tsx +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/DetailPanel/styled.tsx @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MPL-2.0 -// Copyright (C) 2022-2023 Gemeente Amsterdam +// Copyright (C) 2022 - 2023 Gemeente Amsterdam import { Button, themeSpacing, themeColor, breakpoint } from '@amsterdam/asc-ui' import styled, { css } from 'styled-components' diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/StatusLayer/StatusLayer.tsx b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/StatusLayer/StatusLayer.tsx index 10981f26e9..acfbf37a01 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/StatusLayer/StatusLayer.tsx +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/StatusLayer/StatusLayer.tsx @@ -44,7 +44,7 @@ const StatusLayer: FC = ({ return ( { const location: Location = { coordinates } const item: Item = { - id, + id: id.toString(), type: typeValue, description, status: featureStatusType?.typeValue, diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/WfsLayer.tsx b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/WfsLayer.tsx index fd05256163..186a5c0fa9 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/WfsLayer.tsx +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/WfsLayer.tsx @@ -11,6 +11,7 @@ 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 useBoundingBox from '../../../hooks/useBoundingBox' import useLayerVisible from '../../../hooks/useLayerVisible' @@ -59,7 +60,7 @@ const WfsLayer: FunctionComponent = ({ useEffect(() => { if (!layerVisible) { setData(NO_DATA) - setSelectableFeatures(NO_DATA) + setSelectableFeatures([]) return } @@ -82,7 +83,13 @@ const WfsLayer: FunctionComponent = ({ console.error('Unhandled Error in wfs call', result.error.message) } else { setData(result) - setSelectableFeatures(result) + + const mappedResult = mapDataToSelectableFeature( + result.features, + meta.featureTypes + ) + + setSelectableFeatures(mappedResult) } return null }) @@ -101,7 +108,15 @@ const WfsLayer: FunctionComponent = ({ return () => { controller.abort() } - }, [bbox, wfsUrl, layerVisible, setMessage, filter, setSelectableFeatures]) + }, [ + bbox, + wfsUrl, + layerVisible, + setMessage, + filter, + setSelectableFeatures, + meta.featureTypes, + ]) return {children} } diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/get-feature-type.test.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/get-feature-type.test.ts new file mode 100644 index 0000000000..d32d2e8089 --- /dev/null +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/get-feature-type.test.ts @@ -0,0 +1,13 @@ +// 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' + +describe('getFeatureType', () => { + it('should return the correct feature type', () => { + const result = getFeatureType(mockGlasContainer, mockContainerFeatureTypes) + + expect(result).toEqual(mockContainerFeatureTypes[2]) + }) +}) diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/get-feature-type.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/get-feature-type.ts new file mode 100644 index 0000000000..7b4a603c0f --- /dev/null +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/get-feature-type.ts @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (C) 2023 Gemeente Amsterdam +import type { Feature } from 'geojson' + +import type { FeatureType } from '../../../../../../form/MapSelectors/types' + +export const getFeatureType = ( + feature: Feature, + featureTypes: FeatureType[] +): FeatureType => + featureTypes.find( + ({ typeField, typeValue }) => feature.properties?.[typeField] === typeValue + ) as FeatureType diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/get-object-type.test.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/get-object-type.test.ts new file mode 100644 index 0000000000..48c0bfdb67 --- /dev/null +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/get-object-type.test.ts @@ -0,0 +1,28 @@ +// 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 '../../../../../../form/MapSelectors/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) + }) +}) diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/get-object-type.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/get-object-type.ts new file mode 100644 index 0000000000..b80df333a5 --- /dev/null +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/get-object-type.ts @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (C) 2023 Gemeente Amsterdam +import type { Feature } from 'geojson' + +import { FeatureTypes } from '../../../../../../form/MapSelectors/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 && + 'species' in feature.properties + ) { + return FeatureTypes.CATERPILLAR + } + + return null +} diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/index.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/index.ts new file mode 100644 index 0000000000..5c3875fcc4 --- /dev/null +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/index.ts @@ -0,0 +1 @@ +export { mapDataToSelectableFeature } from './map-data-to-selectable-feature' diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/map-data-to-selectable-feature.test.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/map-data-to-selectable-feature.test.ts new file mode 100644 index 0000000000..91296197ff --- /dev/null +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/map-data-to-selectable-feature.test.ts @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (C) 2023 Gemeente Amsterdam +import { mapDataToSelectableFeature } from './map-data-to-selectable-feature' +import { + mockContainerFeatureTypes, + mockPublicLightsFeatureTypes, +} from './test/mock-feature-types' +import { + mockGlasContainer, + mockPaperContainer, + mockPublicLight, +} from './test/mock-objects' + +describe('mapDataToSelectableFeature', () => { + it('should map container features to selectable features correctly', () => { + const selectableFeatures = mapDataToSelectableFeature( + [mockGlasContainer, mockPaperContainer], + mockContainerFeatureTypes + ) + + 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', + type: 'Papier', + }, + ]) + }) + + it('should map public lights features to selectable features correctly', () => { + const selectableFeatures = mapDataToSelectableFeature( + [mockPublicLight], + mockPublicLightsFeatureTypes + ) + + expect(selectableFeatures).toEqual([ + { + coordinates: { lat: 52.372935004142086, lng: 4.901763001239158 }, + description: 'Overig lichtpunt', + id: '000067', + label: 'Overig lichtpunt - 000067', + type: '4', + }, + ]) + }) +}) diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/map-data-to-selectable-feature.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/map-data-to-selectable-feature.ts new file mode 100644 index 0000000000..f58836608d --- /dev/null +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/map-data-to-selectable-feature.ts @@ -0,0 +1,56 @@ +// 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 { 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 '../../../../../../form/MapSelectors/types' +import type { + FeatureType, + SelectableFeature, +} from '../../../../../../form/MapSelectors/types' + +export const mapDataToSelectableFeature = ( + features: Feature[], + featureTypes: FeatureType[] +): 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, + } + }) + default: + return [] + } +} diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/test/mock-feature-types.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/test/mock-feature-types.ts new file mode 100644 index 0000000000..41cba0b631 --- /dev/null +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/test/mock-feature-types.ts @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (C) 2023 Gemeente Amsterdam +import type { FeatureType } from '../../../../../../../form/MapSelectors/types' + +export const mockContainerFeatureTypes: FeatureType[] = [ + { + label: 'Restafval', + description: 'Restafval container', + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/afval/rest.svg', + }, + idField: 'id_nummer', + typeField: 'fractie_omschrijving', + typeValue: 'Rest', + }, + { + label: 'Papier', + description: 'Papier container', + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/afval/paper.svg', + }, + idField: 'id_nummer', + typeField: 'fractie_omschrijving', + typeValue: 'Papier', + }, + { + label: 'Glas', + description: 'Glas container', + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/afval/glas.svg', + }, + idField: 'id_nummer', + typeField: 'fractie_omschrijving', + typeValue: 'Glas', + }, + { + label: 'Plastic', + description: 'Plastic container', + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/afval/plastic.svg', + }, + idField: 'id_nummer', + typeField: 'fractie_omschrijving', + typeValue: 'Plastic', + }, + { + label: 'Textiel', + description: 'Textiel container', + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/afval/textile.svg', + }, + idField: 'id_nummer', + typeField: 'fractie_omschrijving', + typeValue: 'Textiel', + }, + { + label: 'Groente- fruit- en tuinafval', + description: 'Groente- fruit- en tuinafval container', + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/afval/gft.svg', + }, + idField: 'id_nummer', + typeField: 'fractie_omschrijving', + typeValue: 'GFT', + }, + { + label: 'Brood', + description: 'Brood container', + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/afval/bread.svg', + }, + idField: 'id_nummer', + typeField: 'fractie_omschrijving', + typeValue: 'Brood', + }, + { + description: 'De container staat niet op de kaart', + label: 'Onbekend', + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/feature-unknown-marker.svg', + }, + idField: 'id', + typeField: 'type', + typeValue: 'not-on-map', + }, +] + +export const mockPublicLightsFeatureTypes: FeatureType[] = [ + { + label: 'Grachtmast', + description: 'Grachtmast', + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/openbare_verlichting/grachtmast.svg', + }, + idField: 'objectnummer', + typeField: 'objecttype', + typeValue: '5', + }, + { + label: 'Overspanning', + description: 'Overspanning', + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/openbare_verlichting/overspanning.svg', + }, + idField: 'objectnummer', + typeField: 'objecttype', + typeValue: '2', + }, + { + label: 'Gevelarmatuur', + description: 'Gevelarmatuur', + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/openbare_verlichting/gevelarmatuur.svg', + }, + idField: 'objectnummer', + typeField: 'objecttype', + typeValue: '3', + }, + { + label: 'Schijnwerper', + description: 'Schijnwerper', + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/openbare_verlichting/schijnwerper.svg', + }, + idField: 'objectnummer', + typeField: 'objecttype', + typeValue: '10', + }, + { + label: 'Overig lichtpunt', + description: 'Overig lichtpunt', + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/openbare_verlichting/overig.svg', + }, + idField: 'objectnummer', + typeField: 'objecttype', + typeValue: '4', + }, + { + description: 'Het lichtpunt staat niet op de kaart', + label: 'Onbekend', + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/feature-unknown-marker.svg', + }, + idField: 'id', + typeField: 'type', + typeValue: 'not-on-map', + }, +] + +export const mockCaterpillarFeatureTypes: FeatureType[] = [ + { + label: 'Eikenboom', + description: 'Eikenboom', + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/groen_water/oak.svg', + }, + idField: 'OBJECTID', + typeValue: 'Eikenboom', + typeField: '', + }, + { + idField: 'UNKNOWN', + label: 'Onbekend', + description: 'De boom staat niet op de kaart', + icon: { + options: { + className: 'object-marker', + iconSize: [40, 40], + }, + iconUrl: '/assets/images/feature-unknown-marker.svg', + }, + typeValue: 'not-on-map', + typeField: '', + }, +] diff --git a/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/test/mock-objects.ts b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/test/mock-objects.ts new file mode 100644 index 0000000000..75edf718bc --- /dev/null +++ b/src/signals/incident/components/form/MapSelectors/Asset/Selector_v2_removeafterfinishepic5440/WfsLayer/utils/test/mock-objects.ts @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (C) 2023 Gemeente Amsterdam +import type { Feature } from '../../../../../../../form/MapSelectors/types' + +export const mockGlasContainer: Feature = { + type: 'Feature', + id: 'container.16154', + geometry_name: '16154', + geometry: { + type: 'Point', + coordinates: [52.37209240253326, 4.900003434199737], + }, + 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 mockPaperContainer: Feature = { + type: 'Feature', + id: 'container.16156', + geometry_name: '16156', + geometry: { + type: 'Point', + coordinates: [52.37210126045667, 4.900010236862614], + }, + 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, + }, +} + +export const mockPublicLight: 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 mockCaterpillar: Feature = { + id: 4108613, + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [52.38632248, 4.87543579], + }, + properties: { + species: 'Quercus robur', + }, +} diff --git a/src/signals/incident/components/form/MapSelectors/Asset/types.ts b/src/signals/incident/components/form/MapSelectors/Asset/types.ts index b755bf3be1..2b1d3ac101 100644 --- a/src/signals/incident/components/form/MapSelectors/Asset/types.ts +++ b/src/signals/incident/components/form/MapSelectors/Asset/types.ts @@ -2,7 +2,6 @@ // Copyright (C) 2021 - 2023 Gemeente Amsterdam import type { FC } from 'react' -import type { FeatureCollection } from 'geojson' import type { LatLngLiteral } from 'leaflet' import type { FormFieldProps } from 'components/FormField/FormField' @@ -10,8 +9,7 @@ import type { Address } from 'types/address' import type { Location } from 'types/incident' import type { FormOptions } from 'types/reactive-form' -import type { Meta, Item, FeatureType } from '../types' - +import type { SelectableFeature, Meta, Item, FeatureType } from '../types' export interface AssetSelectValue { address?: Address coordinates?: LatLngLiteral @@ -20,12 +18,12 @@ export interface AssetSelectValue { message?: string meta: Meta removeItem: (item?: Item) => void - selectableFeatures?: FeatureCollection selection?: Item[] setItem: (item: Item, location?: Location) => void setLocation: (location: Location) => void setMessage: (message?: string) => void - setSelectableFeatures: (features?: FeatureCollection) => void + setSelectableFeatures: (features?: SelectableFeature[]) => void + selectableFeatures?: SelectableFeature[] } export interface AssetSelectRendererProps extends FormFieldProps { diff --git a/src/signals/incident/components/form/MapSelectors/types.ts b/src/signals/incident/components/form/MapSelectors/types.ts index 09c606778d..63baa06ccd 100644 --- a/src/signals/incident/components/form/MapSelectors/types.ts +++ b/src/signals/incident/components/form/MapSelectors/types.ts @@ -92,5 +92,26 @@ export interface Meta extends Record { validators?: Record } -export type FeatureProps = Record -export type Feature = GeoJSONFeature +export enum FeatureTypes { + CONTAINER = 'container', + PUBLIC_LIGHTS = 'public_lights', + CATERPILLAR = 'caterpillar', +} + +export interface SelectableFeature { + coordinates: LatLngLiteral + description: string + id: string + label: string + type: string + status?: any +} + +export type FeatureProps = Record< + string, + string | number | undefined | null | boolean +> + +export type Feature = GeoJSONFeature & { + geometry_name?: string +}