From 9a6615b75c275d24b6c229753a7a593928b68d7a Mon Sep 17 00:00:00 2001 From: justian Date: Mon, 11 Nov 2024 19:16:37 +0100 Subject: [PATCH 01/27] Feat: show list of features inside Asset Select --- config.json | 8 +- .../incident/add/components/MapDialog.tsx | 33 +++- .../add/components/questions/AssetSelect.tsx | 142 +++++++++++++++++- .../questions/RenderSingleField.tsx | 4 +- src/services/location/features.ts | 17 +++ 5 files changed, 192 insertions(+), 12 deletions(-) create mode 100644 src/services/location/features.ts diff --git a/config.json b/config.json index dccd6340..cf327227 100644 --- a/config.json +++ b/config.json @@ -12,12 +12,12 @@ ], "maxBounds": [ [ - 4.8339104, - 52.4703485 + 5.3309476, + 51.4812962 ], [ - 5.0247001, - 52.6141075 + 5.6965559, + 51.6957026 ] ] } diff --git a/src/app/[locale]/incident/add/components/MapDialog.tsx b/src/app/[locale]/incident/add/components/MapDialog.tsx index ba6d87c9..3b2d4668 100644 --- a/src/app/[locale]/incident/add/components/MapDialog.tsx +++ b/src/app/[locale]/incident/add/components/MapDialog.tsx @@ -1,10 +1,14 @@ import * as Dialog from '@radix-ui/react-dialog' -import React, { useEffect, useRef, useState } from 'react' +import React, { useEffect, useMemo, useRef, useState } from 'react' import Map, { MapLayerMouseEvent, + MapRef, Marker, useMap, ViewState, + Source, + CircleLayer, + Layer, } from 'react-map-gl/maplibre' import { useTranslations } from 'next-intl' import * as VisuallyHidden from '@radix-ui/react-visually-hidden' @@ -30,12 +34,15 @@ import { ButtonGroup } from '@/components' import { isCoordinateInsideMaxBound } from '@/lib/utils/map' import { getSuggestedAddresses } from '@/services/location/address' import { getServerConfig } from '@/services/config/config' +import { FeatureCollection } from 'geojson' type MapDialogProps = { trigger: React.ReactElement + onMapReady?: (map: MapRef) => void + features?: FeatureCollection | null } & React.HTMLAttributes -const MapDialog = ({ trigger }: MapDialogProps) => { +const MapDialog = ({ trigger, onMapReady, features }: MapDialogProps) => { const t = useTranslations('describe-add.map') const [marker, setMarker] = useState<[number, number] | []>([]) const [outsideMaxBoundError, setOutsideMaxBoundError] = useState< @@ -61,6 +68,16 @@ const MapDialog = ({ trigger }: MapDialogProps) => { pitch: 0, }) + const layerStyle: CircleLayer = { + source: '', + id: 'point', + type: 'circle', + paint: { + 'circle-radius': 10, + 'circle-color': '#007cbf', + }, + } + // Set viewState coordinates to configured ones useEffect(() => { if (!loading && config) { @@ -132,6 +149,13 @@ const MapDialog = ({ trigger }: MapDialogProps) => { ) } + // Set dialog map in parent component + useEffect(() => { + if (dialogMap && onMapReady) { + onMapReady(dialogMap) + } + }, [dialogMap, onMapReady]) + return ( {trigger} @@ -225,6 +249,11 @@ const MapDialog = ({ trigger }: MapDialogProps) => { )} + {onMapReady && dialogMap && dialogMap.getZoom() > 17 && ( + + + + )}
+ ) : ( + + Wijzig locatie + + ) + } + /> + +
+ + ) } diff --git a/src/app/[locale]/incident/add/components/questions/RenderSingleField.tsx b/src/app/[locale]/incident/add/components/questions/RenderSingleField.tsx index b7a44e03..a2ece6ce 100644 --- a/src/app/[locale]/incident/add/components/questions/RenderSingleField.tsx +++ b/src/app/[locale]/incident/add/components/questions/RenderSingleField.tsx @@ -9,6 +9,7 @@ import { CheckboxInput } from '@/app/[locale]/incident/add/components/questions/ import { TextAreaInput } from '@/app/[locale]/incident/add/components/questions/TextAreaInput' import { LocationSelect } from '@/app/[locale]/incident/add/components/questions/LocationSelect' import { evaluateConditions } from '@/lib/utils/check-visibility' +import { AssetSelect } from '@/app/[locale]/incident/add/components/questions/AssetSelect' export const RenderSingleField = ({ field }: { field: PublicQuestion }) => { const [shouldRender, setShouldRender] = useState(false) @@ -34,8 +35,7 @@ export const RenderSingleField = ({ field }: { field: PublicQuestion }) => { ), [FieldTypes.ASSET_SELECT]: (field: PublicQuestion) => ( - // TODO: Implement Asset Select - <> + ), [FieldTypes.LOCATION_SELECT]: (field: PublicQuestion) => ( diff --git a/src/services/location/features.ts b/src/services/location/features.ts new file mode 100644 index 00000000..b8bce2e5 --- /dev/null +++ b/src/services/location/features.ts @@ -0,0 +1,17 @@ +import { axiosInstance } from '@/services/client/api-client' +import { AxiosResponse } from 'axios' +import { FeatureCollection } from 'geojson' + +export const getGeoJsonFeatures = async ( + url: string +): Promise => { + const axios = axiosInstance(url) + + try { + const response: AxiosResponse = await axios.get('') + + return response.data + } catch (error) { + throw new Error('Could not fetch suggested addresses. Please try again.') + } +} From 103d05e6d82290edd74b7fbbe57c2b1b689375e3 Mon Sep 17 00:00:00 2001 From: justian Date: Tue, 12 Nov 2024 15:25:32 +0100 Subject: [PATCH 02/27] Feat: show icons per feature, show error if more than maxSelectedFeatures are selected --- config.json | 1 + .../incident/add/components/MapDialog.tsx | 114 +++++++++++++----- .../add/components/questions/AssetSelect.tsx | 1 + src/lib/utils/map.ts | 9 ++ src/types/config.ts | 1 + translations/en.json | 3 +- translations/nl.json | 3 +- 7 files changed, 101 insertions(+), 31 deletions(-) diff --git a/config.json b/config.json index cf327227..4d50157c 100644 --- a/config.json +++ b/config.json @@ -1,6 +1,7 @@ { "base": { "municipality": "purmerend", + "assets_url": "https://meldingen.demo.meierijstad.delta10.cloud", "style": { "primaryColor": "#24578f" }, diff --git a/src/app/[locale]/incident/add/components/MapDialog.tsx b/src/app/[locale]/incident/add/components/MapDialog.tsx index 3b2d4668..f87b2bfd 100644 --- a/src/app/[locale]/incident/add/components/MapDialog.tsx +++ b/src/app/[locale]/incident/add/components/MapDialog.tsx @@ -4,11 +4,9 @@ import Map, { MapLayerMouseEvent, MapRef, Marker, + MarkerEvent, useMap, ViewState, - Source, - CircleLayer, - Layer, } from 'react-map-gl/maplibre' import { useTranslations } from 'next-intl' import * as VisuallyHidden from '@radix-ui/react-visually-hidden' @@ -31,28 +29,40 @@ import { IconPlus, } from '@tabler/icons-react' import { ButtonGroup } from '@/components' -import { isCoordinateInsideMaxBound } from '@/lib/utils/map' +import { + getFeatureIdByCoordinates, + isCoordinateInsideMaxBound, +} from '@/lib/utils/map' import { getSuggestedAddresses } from '@/services/location/address' import { getServerConfig } from '@/services/config/config' -import { FeatureCollection } from 'geojson' +import { Feature, FeatureCollection } from 'geojson' +import { PublicQuestion } from '@/types/form' type MapDialogProps = { trigger: React.ReactElement onMapReady?: (map: MapRef) => void features?: FeatureCollection | null + field?: PublicQuestion } & React.HTMLAttributes -const MapDialog = ({ trigger, onMapReady, features }: MapDialogProps) => { +const MapDialog = ({ + trigger, + onMapReady, + features, + field, +}: MapDialogProps) => { const t = useTranslations('describe-add.map') const [marker, setMarker] = useState<[number, number] | []>([]) - const [outsideMaxBoundError, setOutsideMaxBoundError] = useState< - string | null - >(null) + const [error, setError] = useState(null) const [addressOptions, setAddressOptions] = useState([]) const { formState, updateForm } = useFormStore() const { dialogMap } = useMap() const { loading, config } = useConfig() const dialogRef = useRef(null) + const [selectedFeatureIds, setSelectedFeatureIds] = useState>( + new Set() + ) + const [isMapSelected, setIsMapSelected] = useState(false) const [viewState, setViewState] = useState({ latitude: 0, @@ -68,16 +78,6 @@ const MapDialog = ({ trigger, onMapReady, features }: MapDialogProps) => { pitch: 0, }) - const layerStyle: CircleLayer = { - source: '', - id: 'point', - type: 'circle', - paint: { - 'circle-radius': 10, - 'circle-color': '#007cbf', - }, - } - // Set viewState coordinates to configured ones useEffect(() => { if (!loading && config) { @@ -116,6 +116,35 @@ const MapDialog = ({ trigger, onMapReady, features }: MapDialogProps) => { // Handle click on map const handleMapClick = (event: MapLayerMouseEvent) => { updatePosition(event.lngLat.lat, event.lngLat.lng) + + setIsMapSelected(true) + } + + // Handle click on feature marker + const handleFeatureMarkerClick = (event: MarkerEvent, feature: Feature) => { + // @ts-ignore + const featureId = getFeatureIdByCoordinates(feature.geometry.coordinates) + const maxNumberOfAssets = field?.meta.maxNumberOfAssets || 1 + + if (dialogMap && featureId) { + const newSelectedFeatureIds = new Set([...selectedFeatureIds]) + + if (newSelectedFeatureIds.has(featureId)) { + newSelectedFeatureIds.delete(featureId) + } else { + if (newSelectedFeatureIds.size >= maxNumberOfAssets) { + setError(t('max_number_of_assets_error', { max: maxNumberOfAssets })) + dialogRef.current?.showModal() + + return + } + + newSelectedFeatureIds.add(featureId) + } + + setSelectedFeatureIds(newSelectedFeatureIds) + setTimeout(() => setIsMapSelected(false), 0) + } } // set current location of user @@ -135,15 +164,15 @@ const MapDialog = ({ trigger, onMapReady, features }: MapDialogProps) => { if (isInsideMaxBound) { updatePosition(position.coords.latitude, position.coords.longitude) - setOutsideMaxBoundError(null) + setError(null) return } - setOutsideMaxBoundError(t('outside_max_bound_error')) + setError(t('outside_max_bound_error')) dialogRef.current?.showModal() }, (locationError) => { - setOutsideMaxBoundError(locationError.message) + setError(locationError.message) dialogRef.current?.showModal() } ) @@ -169,7 +198,7 @@ const MapDialog = ({ trigger, onMapReady, features }: MapDialogProps) => {
- {outsideMaxBoundError} + {error} diff --git a/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx b/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx index bef310a5..cb493ce3 100644 --- a/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx +++ b/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx @@ -117,6 +117,7 @@ export const AssetSelect = ({ field }: AssetSelectProps) => { onMapReady={onMapReady} features={features} field={field} + isAssetSelect trigger={ isCoordinates(formStoreState.coordinates) && formStoreState.coordinates[0] === 0 && diff --git a/translations/en.json b/translations/en.json index a9b82cd0..cafe506d 100644 --- a/translations/en.json +++ b/translations/en.json @@ -47,7 +47,8 @@ "close_alert_notification": "Close notification", "address_search_label": "Search by address", "outside_max_bound_error": "Your location is outside the map bounds and is therefore not visible.", - "max_number_of_assets_error": "You can select at most {max, plural, one {# object} other {# objects}}." + "max_number_of_assets_error": "You can select at most {max, plural, one {# object} other {# objects}}.", + "go_further_without_selected_object": "Continue without object" } }, "describe-contact": { diff --git a/translations/nl.json b/translations/nl.json index ed86f64b..008a9a32 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -47,7 +47,8 @@ "outside_max_bound_error": "Uw locatie valt buiten de kaart en is daardoor niet te zien.", "close_alert_notification": "Sluit melding", "address_search_label": "Zoek op adres", - "max_number_of_assets_error": "U kunt maximaal {max, plural, one {# object} other {# objecten}} selecteren." + "max_number_of_assets_error": "U kunt maximaal {max, plural, one {# object} other {# objecten}} selecteren.", + "go_further_without_selected_object": "Ga verder zonder object" } }, "describe-contact": { From 278f5d2eda07d9c50e5088bbeb92b59af82ba01b Mon Sep 17 00:00:00 2001 From: justian Date: Tue, 12 Nov 2024 16:40:32 +0100 Subject: [PATCH 05/27] Fix: use component wrapping tag --- .../incident/add/components/MapDialog.tsx | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/app/[locale]/incident/add/components/MapDialog.tsx b/src/app/[locale]/incident/add/components/MapDialog.tsx index 0e210c58..bb562d21 100644 --- a/src/app/[locale]/incident/add/components/MapDialog.tsx +++ b/src/app/[locale]/incident/add/components/MapDialog.tsx @@ -1,5 +1,5 @@ import * as Dialog from '@radix-ui/react-dialog' -import React, { useEffect, useMemo, useRef, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import Map, { MapLayerMouseEvent, MapRef, @@ -320,14 +320,20 @@ const MapDialog = ({ onClick={(e) => handleFeatureMarkerClick(e, feature)} > {!selectedFeatureIds.has(id) ? ( - + + + ) : ( - + + + )} ) From 3effecc545f674704a4bdb7d3fb14e3678e36688 Mon Sep 17 00:00:00 2001 From: justian Date: Wed, 13 Nov 2024 16:31:52 +0100 Subject: [PATCH 06/27] Feat: show list of all features in view (AssetSelect) sync changes between map and list view --- .../add/components/FeatureListItem.tsx | 103 ++++++++++++++++++ .../incident/add/components/MapDialog.tsx | 27 ++++- src/lib/utils/map.ts | 49 +++++++++ src/types/form.ts | 9 ++ 4 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 src/app/[locale]/incident/add/components/FeatureListItem.tsx diff --git a/src/app/[locale]/incident/add/components/FeatureListItem.tsx b/src/app/[locale]/incident/add/components/FeatureListItem.tsx new file mode 100644 index 00000000..ec3ea088 --- /dev/null +++ b/src/app/[locale]/incident/add/components/FeatureListItem.tsx @@ -0,0 +1,103 @@ +import React, { Dispatch, RefObject, SetStateAction, useMemo } from 'react' +import { + getFeatureDescription, + getFeatureIdByCoordinates, + getFeatureType, +} from '@/lib/utils/map' +import { Feature } from 'geojson' +import { PublicQuestion } from '@/types/form' +import { useConfig } from '@/hooks/useConfig' +import { MapRef } from 'react-map-gl/maplibre' +import { FormField, FormFieldCheckbox, Icon } from '@/components/index' +import { useTranslations } from 'next-intl' + +type FeatureListItemProps = { + feature: Feature + field: PublicQuestion + selectedFeatureIds: Set + setSelectedFeatureIds: Dispatch>> + map: MapRef | undefined + setError: Dispatch> + dialogRef: RefObject +} + +export const FeatureListItem = ({ + feature, + field, + selectedFeatureIds, + map, + setSelectedFeatureIds, + setError, + dialogRef, +}: FeatureListItemProps) => { + const { config } = useConfig() + const t = useTranslations('describe-add.map') + + const featureId = useMemo(() => { + return getFeatureIdByCoordinates( + // @ts-ignore + feature.geometry.coordinates + ) + }, [feature.geometry]) + + const featureType = useMemo(() => { + return getFeatureType(field.meta.featureTypes, feature.properties) + }, [field.meta.featureTypes, feature.properties]) + + const featureDescription = useMemo(() => { + return getFeatureDescription(featureType, feature.properties) + }, [featureType, feature.properties]) + + const maxNumberOfAssets = useMemo(() => { + return field ? field.meta.maxNumberOfAssets : 1 + }, [field]) + + const addOrRemoveFeature = (value: boolean) => { + const newSelectedFeatureIds = new Set([...selectedFeatureIds]) + + if (value) { + if (newSelectedFeatureIds.size >= maxNumberOfAssets) { + setError(t('max_number_of_assets_error', { max: maxNumberOfAssets })) + dialogRef.current?.showModal() + + return + } + + newSelectedFeatureIds.add(featureId) + } else { + newSelectedFeatureIds.delete(featureId) + } + + setSelectedFeatureIds(newSelectedFeatureIds) + } + + // TODO: iets van een label toevoegen zodat voor een SR duidelijk wordt om welke lantaarnpaal, adres etc het gaat? + return featureDescription && config ? ( +
  • + + {!selectedFeatureIds.has(featureId) ? ( + + + + ) : ( + + + + )} + addOrRemoveFeature(e.target.checked)} + /> + +
  • + ) : null +} diff --git a/src/app/[locale]/incident/add/components/MapDialog.tsx b/src/app/[locale]/incident/add/components/MapDialog.tsx index bb562d21..d3216a2e 100644 --- a/src/app/[locale]/incident/add/components/MapDialog.tsx +++ b/src/app/[locale]/incident/add/components/MapDialog.tsx @@ -17,7 +17,6 @@ import { AlertDialog, Paragraph, Button, - FormField, ListboxOptionProps, // SelectCombobox, } from '@/components' @@ -37,6 +36,7 @@ import { getSuggestedAddresses } from '@/services/location/address' import { getServerConfig } from '@/services/config/config' import { Feature, FeatureCollection } from 'geojson' import { PublicQuestion } from '@/types/form' +import { FeatureListItem } from '@/app/[locale]/incident/add/components/FeatureListItem' type MapDialogProps = { trigger: React.ReactElement @@ -188,8 +188,6 @@ const MapDialog = ({ } }, [dialogMap, onMapReady]) - console.log(field) - return ( {trigger} @@ -219,8 +217,8 @@ const MapDialog = ({
    -
    -
    +
    +
    {field?.meta.language.title ? field.meta.language.title @@ -255,6 +253,25 @@ const MapDialog = ({ {/* />*/} {/* }*/} {/*>*/} + {field && ( +
      + {features?.features.map((feature) => ( + + ))} +
    + )}
    { return ( Array.isArray(arg) && @@ -26,3 +29,49 @@ export const getFeatureIdByCoordinates = ( ): number => { return coordinates.reduce((first, second) => first + second) } + +// This function takes a GeoJsonProperties object and a featureType object (which is a field.meta AssetSelect question in Signalen) and returns either null or a formatted string. +// The purpose is to match a feature with its corresponding featureType, as an AssetSelect question can have multiple featureTypes. +// This ensures that the correct icon, title, and other details are displayed. +export const getFeatureType = ( + featureType: FeatureType[], + properties: GeoJsonProperties +): FeatureType | null => { + if (properties) { + const featureTypes = featureType.filter((feature: FeatureType) => + properties.hasOwnProperty(feature.idField) + ) + + if (featureTypes.length) return featureTypes[0] + } + + return null +} + +// This function takes in a GeoJsonProperties object and a featureType object (which is a field.meta AssetSelect question in Signalen) and returns either null or a formatted string. +// The featureType object usually contains a property called description, which might look like "Mastverlichting - {{ nummer }}", for example. +// In the properties field of the GeoJsonProperties object, there is then a corresponding property, such as "Nummer", with a value like "308". +// If these conditions are met, the function will return the string "Mastverlichting - 308". +export const getFeatureDescription = ( + featureType: FeatureType | null, + properties: GeoJsonProperties +): string | null => { + if (properties && featureType) { + const match = featureType.description.match(/{{(.*?)}}/) + + const propertyToReplace = match ? match[0] : null + const propertyInProperties = match ? match[1] : null + + if (propertyToReplace && propertyInProperties) { + const string: string = properties[propertyInProperties.trim()] + + return string + ? featureType.description.replace(propertyToReplace, string) + : null + } + + return null + } + + return null +} diff --git a/src/types/form.ts b/src/types/form.ts index fd56e5c7..561fdf5b 100644 --- a/src/types/form.ts +++ b/src/types/form.ts @@ -18,3 +18,12 @@ export interface PublicQuestion extends Omit { field_type: FieldTypes } + +export type FeatureType = { + icon: { + iconUrl: string + } + label: string + idField: string + description: string +} From fc499fc0adc077ea4ee3581d82579e3881c6c648 Mon Sep 17 00:00:00 2001 From: justian Date: Wed, 13 Nov 2024 17:05:39 +0100 Subject: [PATCH 07/27] Feat: fly to selected asset when toggled in FeatureList --- .../add/components/FeatureListItem.tsx | 24 +++++++++++--- .../incident/add/components/MapDialog.tsx | 33 ++++++++++++------- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/app/[locale]/incident/add/components/FeatureListItem.tsx b/src/app/[locale]/incident/add/components/FeatureListItem.tsx index ec3ea088..2f2596ca 100644 --- a/src/app/[locale]/incident/add/components/FeatureListItem.tsx +++ b/src/app/[locale]/incident/add/components/FeatureListItem.tsx @@ -4,7 +4,7 @@ import { getFeatureIdByCoordinates, getFeatureType, } from '@/lib/utils/map' -import { Feature } from 'geojson' +import { Feature, FeatureCollection } from 'geojson' import { PublicQuestion } from '@/types/form' import { useConfig } from '@/hooks/useConfig' import { MapRef } from 'react-map-gl/maplibre' @@ -19,6 +19,7 @@ type FeatureListItemProps = { map: MapRef | undefined setError: Dispatch> dialogRef: RefObject + features: FeatureCollection | null } export const FeatureListItem = ({ @@ -29,15 +30,14 @@ export const FeatureListItem = ({ setSelectedFeatureIds, setError, dialogRef, + features, }: FeatureListItemProps) => { const { config } = useConfig() const t = useTranslations('describe-add.map') const featureId = useMemo(() => { - return getFeatureIdByCoordinates( - // @ts-ignore - feature.geometry.coordinates - ) + const featureId = feature.id as number + return featureId }, [feature.geometry]) const featureType = useMemo(() => { @@ -54,6 +54,9 @@ export const FeatureListItem = ({ const addOrRemoveFeature = (value: boolean) => { const newSelectedFeatureIds = new Set([...selectedFeatureIds]) + const selectedFeature = features?.features.filter( + (feature) => feature.id === featureId + )[0] if (value) { if (newSelectedFeatureIds.size >= maxNumberOfAssets) { @@ -64,6 +67,17 @@ export const FeatureListItem = ({ } newSelectedFeatureIds.add(featureId) + + if (map && selectedFeature && selectedFeature.properties) { + map.flyTo({ + center: [ + selectedFeature.properties.longitude, + selectedFeature.properties.latitude, + ], + speed: 0.5, + zoom: 18, + }) + } } else { newSelectedFeatureIds.delete(featureId) } diff --git a/src/app/[locale]/incident/add/components/MapDialog.tsx b/src/app/[locale]/incident/add/components/MapDialog.tsx index d3216a2e..75c5e772 100644 --- a/src/app/[locale]/incident/add/components/MapDialog.tsx +++ b/src/app/[locale]/incident/add/components/MapDialog.tsx @@ -65,6 +65,7 @@ const MapDialog = ({ new Set() ) const [isMapSelected, setIsMapSelected] = useState(false) + const [mapFeatures, setMapFeatures] = useState() const [viewState, setViewState] = useState({ latitude: 0, @@ -126,7 +127,7 @@ const MapDialog = ({ // Handle click on feature marker, set selectedFeatures and show error if maxNumberOfAssets is reached const handleFeatureMarkerClick = (event: MarkerEvent, feature: Feature) => { // @ts-ignore - const featureId = getFeatureIdByCoordinates(feature.geometry.coordinates) + const featureId = feature.id as number const maxNumberOfAssets = field?.meta.maxNumberOfAssets || 1 if (dialogMap && featureId) { @@ -188,6 +189,21 @@ const MapDialog = ({ } }, [dialogMap, onMapReady]) + // Set new map features with ID + useEffect(() => { + if (features) { + const featuresWithId = features.features.map((feature) => { + return { + ...feature, + // @ts-ignore + id: getFeatureIdByCoordinates(feature.geometry.coordinates), + } + }) + + setMapFeatures({ ...features, features: featuresWithId }) + } + }, [features]) + return ( {trigger} @@ -255,17 +271,15 @@ const MapDialog = ({ {/*>*/} {field && (
      - {features?.features.map((feature) => ( + {mapFeatures?.features.map((feature) => ( @@ -320,11 +334,8 @@ const MapDialog = ({ {onMapReady && dialogMap && dialogMap.getZoom() > 17 && - features?.features.map((feature) => { - const id = getFeatureIdByCoordinates( - // @ts-ignore - feature.geometry.coordinates - ) + mapFeatures?.features.map((feature) => { + const id = feature.id as number return ( Date: Wed, 13 Nov 2024 17:19:59 +0100 Subject: [PATCH 08/27] Fix: try to fix slowness --- .../[locale]/incident/add/components/FeatureListItem.tsx | 7 +++---- src/app/[locale]/incident/add/components/MapDialog.tsx | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/[locale]/incident/add/components/FeatureListItem.tsx b/src/app/[locale]/incident/add/components/FeatureListItem.tsx index 2f2596ca..d652a18f 100644 --- a/src/app/[locale]/incident/add/components/FeatureListItem.tsx +++ b/src/app/[locale]/incident/add/components/FeatureListItem.tsx @@ -20,6 +20,7 @@ type FeatureListItemProps = { setError: Dispatch> dialogRef: RefObject features: FeatureCollection | null + configUrl?: string } export const FeatureListItem = ({ @@ -31,6 +32,7 @@ export const FeatureListItem = ({ setError, dialogRef, features, + configUrl, }: FeatureListItemProps) => { const { config } = useConfig() const t = useTranslations('describe-add.map') @@ -96,10 +98,7 @@ export const FeatureListItem = ({ ) : ( )} diff --git a/src/app/[locale]/incident/add/components/MapDialog.tsx b/src/app/[locale]/incident/add/components/MapDialog.tsx index 75c5e772..b7cd6906 100644 --- a/src/app/[locale]/incident/add/components/MapDialog.tsx +++ b/src/app/[locale]/incident/add/components/MapDialog.tsx @@ -274,6 +274,7 @@ const MapDialog = ({ {mapFeatures?.features.map((feature) => ( Date: Thu, 14 Nov 2024 12:42:27 +0100 Subject: [PATCH 09/27] Fix: try to fix slowness --- src/app/[locale]/incident/add/components/FeatureListItem.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/[locale]/incident/add/components/FeatureListItem.tsx b/src/app/[locale]/incident/add/components/FeatureListItem.tsx index d652a18f..5cb3c85d 100644 --- a/src/app/[locale]/incident/add/components/FeatureListItem.tsx +++ b/src/app/[locale]/incident/add/components/FeatureListItem.tsx @@ -6,7 +6,6 @@ import { } from '@/lib/utils/map' import { Feature, FeatureCollection } from 'geojson' import { PublicQuestion } from '@/types/form' -import { useConfig } from '@/hooks/useConfig' import { MapRef } from 'react-map-gl/maplibre' import { FormField, FormFieldCheckbox, Icon } from '@/components/index' import { useTranslations } from 'next-intl' @@ -34,7 +33,6 @@ export const FeatureListItem = ({ features, configUrl, }: FeatureListItemProps) => { - const { config } = useConfig() const t = useTranslations('describe-add.map') const featureId = useMemo(() => { @@ -88,7 +86,7 @@ export const FeatureListItem = ({ } // TODO: iets van een label toevoegen zodat voor een SR duidelijk wordt om welke lantaarnpaal, adres etc het gaat? - return featureDescription && config ? ( + return featureDescription ? (
    • {!selectedFeatureIds.has(featureId) ? ( From 253bbb4d65814c338ec933c3cc0323f46def99e5 Mon Sep 17 00:00:00 2001 From: justian Date: Thu, 14 Nov 2024 21:38:18 +0100 Subject: [PATCH 10/27] refactor: remove featureIds set, moved everything to selectedFeatures array --- .../add/components/FeatureListItem.tsx | 39 ++++----- .../incident/add/components/MapDialog.tsx | 79 +++++++++++++------ 2 files changed, 74 insertions(+), 44 deletions(-) diff --git a/src/app/[locale]/incident/add/components/FeatureListItem.tsx b/src/app/[locale]/incident/add/components/FeatureListItem.tsx index 5cb3c85d..aac25389 100644 --- a/src/app/[locale]/incident/add/components/FeatureListItem.tsx +++ b/src/app/[locale]/incident/add/components/FeatureListItem.tsx @@ -13,8 +13,8 @@ import { useTranslations } from 'next-intl' type FeatureListItemProps = { feature: Feature field: PublicQuestion - selectedFeatureIds: Set - setSelectedFeatureIds: Dispatch>> + newSelectedFeatures: Feature[] + setNewSelectedFeatures: Dispatch> map: MapRef | undefined setError: Dispatch> dialogRef: RefObject @@ -25,9 +25,9 @@ type FeatureListItemProps = { export const FeatureListItem = ({ feature, field, - selectedFeatureIds, + newSelectedFeatures, map, - setSelectedFeatureIds, + setNewSelectedFeatures, setError, dialogRef, features, @@ -53,43 +53,44 @@ export const FeatureListItem = ({ }, [field]) const addOrRemoveFeature = (value: boolean) => { - const newSelectedFeatureIds = new Set([...selectedFeatureIds]) - const selectedFeature = features?.features.filter( + const newSelectedFeatureArray = Array.from( + newSelectedFeatures ? newSelectedFeatures : [] + ) + const index = newSelectedFeatureArray.findIndex( (feature) => feature.id === featureId - )[0] + ) if (value) { - if (newSelectedFeatureIds.size >= maxNumberOfAssets) { + if (newSelectedFeatureArray.length >= maxNumberOfAssets) { setError(t('max_number_of_assets_error', { max: maxNumberOfAssets })) dialogRef.current?.showModal() return } - newSelectedFeatureIds.add(featureId) + newSelectedFeatureArray.push(feature) - if (map && selectedFeature && selectedFeature.properties) { + if (map && feature && feature.properties) { map.flyTo({ - center: [ - selectedFeature.properties.longitude, - selectedFeature.properties.latitude, - ], + center: [feature.properties.longitude, feature.properties.latitude], speed: 0.5, zoom: 18, }) } } else { - newSelectedFeatureIds.delete(featureId) + newSelectedFeatureArray.splice(index, 1) // Remove the feature at the found index } - setSelectedFeatureIds(newSelectedFeatureIds) + setNewSelectedFeatures(newSelectedFeatureArray) } // TODO: iets van een label toevoegen zodat voor een SR duidelijk wordt om welke lantaarnpaal, adres etc het gaat? return featureDescription ? (
    • - {!selectedFeatureIds.has(featureId) ? ( + {!newSelectedFeatures.some( + (featureItem) => featureItem.id === featureId + ) ? ( @@ -103,7 +104,9 @@ export const FeatureListItem = ({ featureItem.id === featureId + )} id={featureId.toString()} // @ts-ignore onChange={(e) => addOrRemoveFeature(e.target.checked)} diff --git a/src/app/[locale]/incident/add/components/MapDialog.tsx b/src/app/[locale]/incident/add/components/MapDialog.tsx index b7cd6906..6a29463c 100644 --- a/src/app/[locale]/incident/add/components/MapDialog.tsx +++ b/src/app/[locale]/incident/add/components/MapDialog.tsx @@ -61,11 +61,9 @@ const MapDialog = ({ const { dialogMap } = useMap() const { loading, config } = useConfig() const dialogRef = useRef(null) - const [selectedFeatureIds, setSelectedFeatureIds] = useState>( - new Set() - ) const [isMapSelected, setIsMapSelected] = useState(false) const [mapFeatures, setMapFeatures] = useState() + const [newSelectedFeatures, setNewSelectedFeatures] = useState([]) const [viewState, setViewState] = useState({ latitude: 0, @@ -131,22 +129,28 @@ const MapDialog = ({ const maxNumberOfAssets = field?.meta.maxNumberOfAssets || 1 if (dialogMap && featureId) { - const newSelectedFeatureIds = new Set([...selectedFeatureIds]) + const newSelectedFeatureArray = Array.from( + newSelectedFeatures ? newSelectedFeatures : [] + ) + + const index = newSelectedFeatureArray.findIndex( + (feature) => feature.id === featureId + ) - if (newSelectedFeatureIds.has(featureId)) { - newSelectedFeatureIds.delete(featureId) + if (index !== -1) { + newSelectedFeatureArray.splice(index, 1) // Remove the feature at the found index } else { - if (newSelectedFeatureIds.size >= maxNumberOfAssets) { + if (newSelectedFeatureArray.length >= maxNumberOfAssets) { setError(t('max_number_of_assets_error', { max: maxNumberOfAssets })) dialogRef.current?.showModal() return } - newSelectedFeatureIds.add(featureId) + newSelectedFeatureArray.push(feature) } - setSelectedFeatureIds(newSelectedFeatureIds) + setNewSelectedFeatures(newSelectedFeatureArray) setTimeout(() => setIsMapSelected(false), 0) } } @@ -271,20 +275,41 @@ const MapDialog = ({ {/*>*/} {field && (
        - {mapFeatures?.features.map((feature) => ( - - ))} + {mapFeatures && + newSelectedFeatures.map((feature) => ( + + ))} + + {mapFeatures?.features.map( + (feature) => + !newSelectedFeatures.some( + (featureItem) => featureItem.id === feature.id + ) && ( + + ) + )}
      )}
    @@ -298,10 +323,10 @@ const MapDialog = ({
    - - updateForm({ ...formState, coordinates: marker }) - } - > + closeMapDialog()}>
    {address} + {formStoreState.selectedFeatures.map((feature: any) => ( + {feature.description} + ))} ()( diff --git a/src/types/stores.ts b/src/types/stores.ts index 77f8aded..ec5546b4 100644 --- a/src/types/stores.ts +++ b/src/types/stores.ts @@ -1,3 +1,5 @@ +import { Feature } from 'geojson' + type FormStoreState = { description: string main_category: string @@ -5,6 +7,7 @@ type FormStoreState = { coordinates: number[] email?: string | null phone?: string | null + selectedFeatures: Feature[] sharing_allowed?: boolean extra_properties: Array<{ answer: From a47b8ada592d5198346f83186dd90b05f2ae9c28 Mon Sep 17 00:00:00 2001 From: justian Date: Mon, 18 Nov 2024 15:43:44 +0100 Subject: [PATCH 14/27] refactor: remove old TODO comments --- src/app/[locale]/incident/add/components/MapDialog.tsx | 1 - src/app/[locale]/incident/add/components/questions/PlainText.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/app/[locale]/incident/add/components/MapDialog.tsx b/src/app/[locale]/incident/add/components/MapDialog.tsx index 26962963..1ea4b3ca 100644 --- a/src/app/[locale]/incident/add/components/MapDialog.tsx +++ b/src/app/[locale]/incident/add/components/MapDialog.tsx @@ -227,7 +227,6 @@ const MapDialog = ({ const closeMapDialog = async () => { updateForm({ ...formState, coordinates: marker }) - // Todo: implement asset select logic if (isAssetSelect && field) { const formValues = await Promise.all( formState.selectedFeatures.map(async (feature) => { diff --git a/src/app/[locale]/incident/add/components/questions/PlainText.tsx b/src/app/[locale]/incident/add/components/questions/PlainText.tsx index a95913e4..1eaa1999 100644 --- a/src/app/[locale]/incident/add/components/questions/PlainText.tsx +++ b/src/app/[locale]/incident/add/components/questions/PlainText.tsx @@ -5,7 +5,6 @@ import { Alert } from '@/components' interface PlainTextProps extends QuestionField {} export const PlainText = ({ field }: PlainTextProps) => { - // TODO: Discuss if alert is the only used PlainText type in Signalen, style Markdown return field.meta.value ? ( {field.meta.value} From dbac39e60f178750636e1f457c5422c29c2c89cf Mon Sep 17 00:00:00 2001 From: justian Date: Mon, 18 Nov 2024 15:47:49 +0100 Subject: [PATCH 15/27] feat: use flag "minimal_zoom" in config.json for minimal zoom level to see features --- config.json | 1 + src/app/[locale]/incident/add/components/MapDialog.tsx | 2 +- .../incident/add/components/questions/AssetSelect.tsx | 8 ++++---- src/types/config.ts | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/config.json b/config.json index 60e645b5..c62ab08a 100644 --- a/config.json +++ b/config.json @@ -7,6 +7,7 @@ }, "map": { "find_address_in_distance": 30, + "minimal_zoom": 17, "center": [ 51.6045656, 5.5342026 diff --git a/src/app/[locale]/incident/add/components/MapDialog.tsx b/src/app/[locale]/incident/add/components/MapDialog.tsx index 1ea4b3ca..1f08a4c2 100644 --- a/src/app/[locale]/incident/add/components/MapDialog.tsx +++ b/src/app/[locale]/incident/add/components/MapDialog.tsx @@ -401,7 +401,7 @@ const MapDialog = ({ )} {onMapReady && dialogMap && - dialogMap.getZoom() > 17 && + dialogMap.getZoom() > config.base.map.minimal_zoom && mapFeatures?.features.map((feature) => { const id = feature.id as number diff --git a/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx b/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx index 38104317..f96e4f43 100644 --- a/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx +++ b/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx @@ -61,9 +61,9 @@ export const AssetSelect = ({ field }: AssetSelectProps) => { // Extract and log map bounds on zoom or view change useEffect(() => { - const logMapBounds = async () => { + const setNewFeatures = async () => { const bounds = dialogMap?.getBounds() - const zoom = dialogMap?.getZoom() + const zoom = config?.base.map.minimal_zoom if ( field && @@ -86,12 +86,12 @@ export const AssetSelect = ({ field }: AssetSelectProps) => { } if (dialogMap) { - dialogMap.on('moveend', logMapBounds) + dialogMap.on('moveend', setNewFeatures) } return () => { if (dialogMap) { - dialogMap.off('moveend', logMapBounds) + dialogMap.off('moveend', setNewFeatures) } } }, [dialogMap]) diff --git a/src/types/config.ts b/src/types/config.ts index d0e5ab58..f6e991c0 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -7,6 +7,7 @@ export type AppConfig = { } map: { find_address_in_distance: number + minimal_zoom: number center: [number, number] maxBounds: [[number, number], [number, number]] } From 6dbd3da32f255644212689e620c08bf7453724a6 Mon Sep 17 00:00:00 2001 From: justian Date: Mon, 18 Nov 2024 16:13:10 +0100 Subject: [PATCH 16/27] feat: use flag "minimal_zoom" in config.json for minimal zoom level to see features --- .../incident/add/components/MapDialog.tsx | 60 +++++++++---------- .../add/components/questions/AssetSelect.tsx | 5 +- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/app/[locale]/incident/add/components/MapDialog.tsx b/src/app/[locale]/incident/add/components/MapDialog.tsx index 1f08a4c2..34c51db2 100644 --- a/src/app/[locale]/incident/add/components/MapDialog.tsx +++ b/src/app/[locale]/incident/add/components/MapDialog.tsx @@ -327,37 +327,37 @@ const MapDialog = ({ {/* />*/} {/* }*/} {/*>*/} - {field && ( + {field && dialogMap && config && (
      - {mapFeatures && - formState.selectedFeatures.map((feature: any) => ( - - ))} - - {mapFeatures?.features.map( - (feature: any) => - !formState.selectedFeatures.some( - (featureItem) => featureItem.id === feature.id - ) && ( - - ) - )} + {formState.selectedFeatures.map((feature: any) => ( + + ))} + + {dialogMap.getZoom() > config.base.map.minimal_zoom && + mapFeatures?.features.map( + (feature: any) => + !formState.selectedFeatures.some( + (featureItem) => featureItem.id === feature.id + ) && ( + + ) + )}
    )}
    diff --git a/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx b/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx index f96e4f43..0125d29b 100644 --- a/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx +++ b/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx @@ -63,7 +63,7 @@ export const AssetSelect = ({ field }: AssetSelectProps) => { useEffect(() => { const setNewFeatures = async () => { const bounds = dialogMap?.getBounds() - const zoom = config?.base.map.minimal_zoom + const zoom = dialogMap?.getZoom() if ( field && @@ -71,7 +71,8 @@ export const AssetSelect = ({ field }: AssetSelectProps) => { field.meta && field.meta.endpoint && zoom && - zoom > 17 + config && + zoom > config.base.map.minimal_zoom ) { const endpoint = field.meta.endpoint .replace('{west}', bounds.getWest()) From 1286e6108cc7885af123ca14816d2f4a6297bd4e Mon Sep 17 00:00:00 2001 From: justian Date: Mon, 18 Nov 2024 16:39:06 +0100 Subject: [PATCH 17/27] Feat: show alert dialog if not enough zoomed in --- src/app/[locale]/incident/add/components/MapDialog.tsx | 7 +++++++ translations/en.json | 3 ++- translations/nl.json | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/app/[locale]/incident/add/components/MapDialog.tsx b/src/app/[locale]/incident/add/components/MapDialog.tsx index 34c51db2..846cc828 100644 --- a/src/app/[locale]/incident/add/components/MapDialog.tsx +++ b/src/app/[locale]/incident/add/components/MapDialog.tsx @@ -16,6 +16,7 @@ import { Icon, AlertDialog, Paragraph, + Alert, Button, ListboxOptionProps, // SelectCombobox, @@ -327,6 +328,12 @@ const MapDialog = ({ {/* />*/} {/* }*/} {/*>*/} + {isAssetSelect && + dialogMap && + config && + dialogMap.getZoom() < config.base.map.minimal_zoom && ( + {t('zoom_for_object')} + )} {field && dialogMap && config && (
      {formState.selectedFeatures.map((feature: any) => ( diff --git a/translations/en.json b/translations/en.json index 672c3b7b..3112dbd9 100644 --- a/translations/en.json +++ b/translations/en.json @@ -46,7 +46,8 @@ "address_search_label": "Search by address", "outside_max_bound_error": "Your location is outside the map bounds and is therefore not visible.", "max_number_of_assets_error": "You can select at most {max, plural, one {# object} other {# objects}}.", - "go_further_without_selected_object": "Continue without object" + "go_further_without_selected_object": "Continue without object", + "zoom_for_object": "Zoom in to see the objects" } }, "describe-contact": { diff --git a/translations/nl.json b/translations/nl.json index 44aebafe..5511514b 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -46,7 +46,8 @@ "close_alert_notification": "Sluit melding", "address_search_label": "Zoek op adres", "max_number_of_assets_error": "U kunt maximaal {max, plural, one {# object} other {# objecten}} selecteren.", - "go_further_without_selected_object": "Ga verder zonder object" + "go_further_without_selected_object": "Ga verder zonder object", + "zoom_for_object": "Zoom in om de objecten te zien" } }, "describe-contact": { From 3909c18ae999867cea33a837584dcd5c8ecc5dfd Mon Sep 17 00:00:00 2001 From: justian Date: Tue, 19 Nov 2024 12:25:02 +0100 Subject: [PATCH 18/27] Feat: support multiple feature sorts --- config.json | 8 ++++---- .../incident/add/components/FeatureListItem.tsx | 9 +++++++-- .../incident/add/components/MapDialog.tsx | 15 ++++++++------- .../add/components/questions/AssetSelect.tsx | 1 + src/lib/utils/map.ts | 4 ++-- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/config.json b/config.json index 43585922..55c675fe 100644 --- a/config.json +++ b/config.json @@ -14,12 +14,12 @@ ], "maxBounds": [ [ - 5.3309476, - 51.4812962 + 3.31497114423, + 50.803721015 ], [ - 5.6965559, - 51.6957026 + 7.09205325687, + 53.5104033474 ] ] }, diff --git a/src/app/[locale]/incident/add/components/FeatureListItem.tsx b/src/app/[locale]/incident/add/components/FeatureListItem.tsx index 5d6f43d2..734b4e05 100644 --- a/src/app/[locale]/incident/add/components/FeatureListItem.tsx +++ b/src/app/[locale]/incident/add/components/FeatureListItem.tsx @@ -56,9 +56,14 @@ export const FeatureListItem = ({ newSelectedFeatureArray.push(feature) - if (map && feature && feature.properties) { + if (map && feature && feature.geometry) { map.flyTo({ - center: [feature.properties.longitude, feature.properties.latitude], + center: [ + // @ts-ignore + feature.geometry.coordinates[0], + // @ts-ignore + feature.geometry.coordinates[1], + ], speed: 0.5, zoom: 18, }) diff --git a/src/app/[locale]/incident/add/components/MapDialog.tsx b/src/app/[locale]/incident/add/components/MapDialog.tsx index 846cc828..b0eeb696 100644 --- a/src/app/[locale]/incident/add/components/MapDialog.tsx +++ b/src/app/[locale]/incident/add/components/MapDialog.tsx @@ -372,13 +372,14 @@ const MapDialog = ({ closeMapDialog()}> diff --git a/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx b/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx index 0125d29b..8438b9b5 100644 --- a/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx +++ b/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx @@ -75,6 +75,7 @@ export const AssetSelect = ({ field }: AssetSelectProps) => { zoom > config.base.map.minimal_zoom ) { const endpoint = field.meta.endpoint + .replaceAll('{srsName}', 'EPSG:4326') .replace('{west}', bounds.getWest()) .replace('{north}', bounds.getNorth()) .replace('{south}', bounds.getSouth()) diff --git a/src/lib/utils/map.ts b/src/lib/utils/map.ts index 0453a6ab..e89eb2bc 100644 --- a/src/lib/utils/map.ts +++ b/src/lib/utils/map.ts @@ -58,10 +58,10 @@ export const getFeatureDescription = ( return string ? featureType.description.replace(propertyToReplace, string) - : null + : `${featureType.description} - ${getFeatureId(featureType, properties)}` } - return null + return `${featureType.description} - ${getFeatureId(featureType, properties)}` } return null From a73ac16932ca7830e7198d1d4b04b4062b8c381f Mon Sep 17 00:00:00 2001 From: Yuri Date: Tue, 19 Nov 2024 13:26:53 +0100 Subject: [PATCH 19/27] fix: change scroll class --- src/app/[locale]/incident/add/components/MapDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/[locale]/incident/add/components/MapDialog.tsx b/src/app/[locale]/incident/add/components/MapDialog.tsx index b0eeb696..b3338230 100644 --- a/src/app/[locale]/incident/add/components/MapDialog.tsx +++ b/src/app/[locale]/incident/add/components/MapDialog.tsx @@ -335,7 +335,7 @@ const MapDialog = ({ {t('zoom_for_object')} )} {field && dialogMap && config && ( -
        +
          {formState.selectedFeatures.map((feature: any) => ( Date: Tue, 19 Nov 2024 13:46:24 +0100 Subject: [PATCH 20/27] Fix: bug when maxFeatures is not set in field config --- .../[locale]/incident/add/components/FeatureListItem.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/[locale]/incident/add/components/FeatureListItem.tsx b/src/app/[locale]/incident/add/components/FeatureListItem.tsx index 734b4e05..e9a77351 100644 --- a/src/app/[locale]/incident/add/components/FeatureListItem.tsx +++ b/src/app/[locale]/incident/add/components/FeatureListItem.tsx @@ -30,7 +30,11 @@ export const FeatureListItem = ({ const featureId = feature.id const featureDescription = feature.description - const maxNumberOfAssets = field ? field.meta.maxNumberOfAssets : 1 + const maxNumberOfAssets = field + ? field.meta.maxNumberOfAssets + ? field.meta.maxNumberOfAssets + : 1 + : 1 // Get feature type of asset const featureType = useMemo(() => { From 063b44c9e9c8fbf773457dce4908d48217be3b82 Mon Sep 17 00:00:00 2001 From: justian Date: Tue, 19 Nov 2024 14:14:20 +0100 Subject: [PATCH 21/27] Fix: show shortLabel in admin interface if shortLabel exists on field --- .../incident/add/components/IncidentQuestionsLocationForm.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/[locale]/incident/add/components/IncidentQuestionsLocationForm.tsx b/src/app/[locale]/incident/add/components/IncidentQuestionsLocationForm.tsx index d743f40a..670ad853 100644 --- a/src/app/[locale]/incident/add/components/IncidentQuestionsLocationForm.tsx +++ b/src/app/[locale]/incident/add/components/IncidentQuestionsLocationForm.tsx @@ -107,7 +107,9 @@ export const IncidentQuestionsLocationForm = () => { return { id: question.key, - label: question.meta.label, + label: question.meta.shortLabel + ? question.meta.shortLabel + : question.meta.label, category_url: `/signals/v1/public/terms/categories/${formStoreState.sub_category}/sub_categories/${formStoreState.main_category}`, answer, } From 4c32f71ebbd7604bcc80fbd1915b626797d6b79f Mon Sep 17 00:00:00 2001 From: justian Date: Tue, 19 Nov 2024 14:33:40 +0100 Subject: [PATCH 22/27] Fix: don't require address --- src/app/[locale]/incident/add/components/MapDialog.tsx | 4 +++- src/services/location/address.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/[locale]/incident/add/components/MapDialog.tsx b/src/app/[locale]/incident/add/components/MapDialog.tsx index b3338230..0786dc3c 100644 --- a/src/app/[locale]/incident/add/components/MapDialog.tsx +++ b/src/app/[locale]/incident/add/components/MapDialog.tsx @@ -241,7 +241,9 @@ const MapDialog = ({ return { address: { - ...formatAddressToSignalenInput(address.weergavenaam), + ...formatAddressToSignalenInput( + address ? address.weergavenaam : '' + ), }, id: feature.id?.toString(), coordinates: { diff --git a/src/services/location/address.ts b/src/services/location/address.ts index ab52bda0..b99ba5d6 100644 --- a/src/services/location/address.ts +++ b/src/services/location/address.ts @@ -35,6 +35,6 @@ export const getNearestAddressByCoordinate = async ( (docA, docB) => docA.afstand - docB.afstand )[0] } catch (error) { - throw new Error('Could not fetch address by coordinate. Please try again.') + return null } } From 0b77308dfd9f5bb606b3ff633cbbf4e9cada51cf Mon Sep 17 00:00:00 2001 From: justian Date: Tue, 19 Nov 2024 14:34:12 +0100 Subject: [PATCH 23/27] Refactor: rename value to checked --- src/app/[locale]/incident/add/components/FeatureListItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/[locale]/incident/add/components/FeatureListItem.tsx b/src/app/[locale]/incident/add/components/FeatureListItem.tsx index e9a77351..e0fdfe19 100644 --- a/src/app/[locale]/incident/add/components/FeatureListItem.tsx +++ b/src/app/[locale]/incident/add/components/FeatureListItem.tsx @@ -42,7 +42,7 @@ export const FeatureListItem = ({ }, [field.meta.featureTypes, feature.properties]) // Add or remove feature to / from the newSelectedFeature state declared in DialogMap - const addOrRemoveFeature = (value: boolean) => { + const addOrRemoveFeature = (checked: boolean) => { const newSelectedFeatureArray = Array.from( formState.selectedFeatures ? formState.selectedFeatures : [] ) @@ -50,7 +50,7 @@ export const FeatureListItem = ({ (feature) => feature.id === featureId ) - if (value) { + if (checked) { if (newSelectedFeatureArray.length >= maxNumberOfAssets) { setError(t('max_number_of_assets_error', { max: maxNumberOfAssets })) dialogRef.current?.showModal() From 55f28511c70fc1f25e3106d56c5ab8f6a64bb907 Mon Sep 17 00:00:00 2001 From: justian Date: Tue, 19 Nov 2024 14:36:21 +0100 Subject: [PATCH 24/27] Refactor: move initialisation of index constance to else block --- .../[locale]/incident/add/components/FeatureListItem.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/app/[locale]/incident/add/components/FeatureListItem.tsx b/src/app/[locale]/incident/add/components/FeatureListItem.tsx index e0fdfe19..9cd4480d 100644 --- a/src/app/[locale]/incident/add/components/FeatureListItem.tsx +++ b/src/app/[locale]/incident/add/components/FeatureListItem.tsx @@ -46,9 +46,6 @@ export const FeatureListItem = ({ const newSelectedFeatureArray = Array.from( formState.selectedFeatures ? formState.selectedFeatures : [] ) - const index = newSelectedFeatureArray.findIndex( - (feature) => feature.id === featureId - ) if (checked) { if (newSelectedFeatureArray.length >= maxNumberOfAssets) { @@ -73,6 +70,10 @@ export const FeatureListItem = ({ }) } } else { + const index = newSelectedFeatureArray.findIndex( + (feature) => feature.id === featureId + ) + newSelectedFeatureArray.splice(index, 1) // Remove the feature at the found index } From f3f6f218d42c4b9862f69eea897efd5cd6e44703 Mon Sep 17 00:00:00 2001 From: justian Date: Tue, 19 Nov 2024 14:55:31 +0100 Subject: [PATCH 25/27] Refactor: add comments to make service and utils more clear --- src/lib/utils/map.ts | 48 +++++++++++++++++++++---------- src/services/location/address.ts | 11 +++++++ src/services/location/features.ts | 6 +++- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/lib/utils/map.ts b/src/lib/utils/map.ts index e89eb2bc..328ff46d 100644 --- a/src/lib/utils/map.ts +++ b/src/lib/utils/map.ts @@ -1,6 +1,9 @@ import { FeatureType } from '@/types/form' import { GeoJsonProperties } from 'geojson' +// Validates if the argument is a coordinate pair [longitude, latitude] +// @param {unknown} arg - Input to validate +// @returns {arg is [number, number]} - Type predicate for coordinate tuple export const isCoordinates = (arg: unknown): arg is [number, number] => { return ( Array.isArray(arg) && @@ -10,6 +13,11 @@ export const isCoordinates = (arg: unknown): arg is [number, number] => { ) } +// Checks if coordinates are within specified geographical bounds +// @param {number} lat - Latitude to check +// @param {number} lng - Longitude to check +// @param {[[number, number], [number, number]]} maxBounds - Geographical boundary coordinates +// @returns {boolean} - Whether coordinates are inside bounds export const isCoordinateInsideMaxBound = ( lat: number, lng: number, @@ -21,9 +29,10 @@ export const isCoordinateInsideMaxBound = ( return lat >= minLat && lat <= maxLat && lng >= minLng && lng <= maxLng } -// This function takes a GeoJsonProperties object and a featureType object (which is a field.meta AssetSelect question in Signalen) and returns either null or a formatted string. -// The purpose is to match a feature with its corresponding featureType, as an AssetSelect question can have multiple featureTypes. -// This ensures that the correct icon, title, and other details are displayed. +// Matches a GeoJSON feature with its corresponding feature type +// @param {FeatureType[]} featureType - Array of possible feature types +// @param {GeoJsonProperties} properties - Properties of the GeoJSON feature +// @returns {FeatureType | null} - Matching feature type or null export const getFeatureType = ( featureType: FeatureType[], properties: GeoJsonProperties @@ -39,10 +48,10 @@ export const getFeatureType = ( return null } -// This function takes in a GeoJsonProperties object and a featureType object (which is a field.meta AssetSelect question in Signalen) and returns either null or a formatted string. -// The featureType object usually contains a property called description, which might look like "Mastverlichting - {{ nummer }}", for example. -// In the properties field of the GeoJsonProperties object, there is then a corresponding property, such as "Nummer", with a value like "308". -// If these conditions are met, the function will return the string "Mastverlichting - 308". +// Generates a human-readable description for a geographic feature +// @param {FeatureType | null} featureType - Feature type definition +// @param {GeoJsonProperties} properties - Feature properties +// @returns {string | null} - Formatted feature description export const getFeatureDescription = ( featureType: FeatureType | null, properties: GeoJsonProperties @@ -67,7 +76,10 @@ export const getFeatureDescription = ( return null } -// This function returns the feature id of a feature +// Extracts the unique identifier for a geographic feature +// @param {FeatureType | null} featureType - Feature type definition +// @param {GeoJsonProperties} properties - Feature properties +// @returns {number | undefined} - Feature ID or undefined export const getFeatureId = ( featureType: FeatureType | null, properties: GeoJsonProperties @@ -85,24 +97,30 @@ export const getFeatureId = ( return undefined } -// This function formats a given "PDOK weergavenaam" into a object with seperate postal code, huisnummer, openbare_ruimte (street name) and woonplaats -export const formatAddressToSignalenInput = (input: string) => { +// Parses a PDOK address format into separate components +// @param {string} input - Full address string +// @returns {Object} - Parsed address components +export const formatAddressToSignalenInput = ( + input: string +): { + huisnummer?: string | null + openbare_ruimte?: string | null + postcode?: string | null + woonplaats?: string | null +} => { if (input === '') { return {} } - // Split the main components by "," const [streetAndNumber, postcodeAndCity] = input .split(',') .map((part) => part.trim()) - // Extract street and house number - const streetMatch = streetAndNumber.match(/(.+)\s(\d+)$/) // Matches "N.C.B.-laan" and "17" + const streetMatch = streetAndNumber.match(/(.+)\s(\d+)$/) const openbare_ruimte = streetMatch ? streetMatch[1] : null const huisnummer = streetMatch ? streetMatch[2] : null - // Extract postal code and city - const postcodeMatch = postcodeAndCity.match(/^([0-9A-Z]+)\s(.+)$/) // Matches "5462GA" and "Veghel" + const postcodeMatch = postcodeAndCity.match(/^([0-9A-Z]+)\s(.+)$/) const postcode = postcodeMatch ? postcodeMatch[1] : null const woonplaats = postcodeMatch ? postcodeMatch[2] : null diff --git a/src/services/location/address.ts b/src/services/location/address.ts index b99ba5d6..b9038f0d 100644 --- a/src/services/location/address.ts +++ b/src/services/location/address.ts @@ -2,6 +2,11 @@ import { axiosInstance } from '@/services/client/api-client' import { AxiosResponse } from 'axios' import { AddressCoordinateResponse, AddressSuggestResponse } from '@/types/pdok' +// Fetches suggested addresses from PDOK API based on search query and municipality +// @param {string} searchQuery - Text to search for addresses +// @param {string} municipality - Name of the municipality to filter results +// @returns {Promise} - Promise resolving to suggested addresses +// @throws {Error} - Throws an error if the request fails export const getSuggestedAddresses = async ( searchQuery: string, municipality: string @@ -19,6 +24,12 @@ export const getSuggestedAddresses = async ( } } +// Finds the nearest address to given coordinates within a specified distance +// @param {number} lat - Latitude of the reference point +// @param {number} lng - Longitude of the reference point +// @param {number} distance - Search radius in meters +// @returns {Promise} - Nearest address or null if not found +// @throws {Error} - Returns null if the request fails export const getNearestAddressByCoordinate = async ( lat: number, lng: number, diff --git a/src/services/location/features.ts b/src/services/location/features.ts index b8bce2e5..acd3272d 100644 --- a/src/services/location/features.ts +++ b/src/services/location/features.ts @@ -2,6 +2,10 @@ import { axiosInstance } from '@/services/client/api-client' import { AxiosResponse } from 'axios' import { FeatureCollection } from 'geojson' +// Fetches GeoJSON feature collection from a specified URL +// @param {string} url - Base URL for the GeoJSON endpoint +// @returns {Promise} - Promise resolving to a GeoJSON feature collection +// @throws {Error} - Throws an error if the request fails export const getGeoJsonFeatures = async ( url: string ): Promise => { @@ -12,6 +16,6 @@ export const getGeoJsonFeatures = async ( return response.data } catch (error) { - throw new Error('Could not fetch suggested addresses. Please try again.') + throw new Error('Could not fetch suggested features. Please try again.') } } From 09e5b761fa272a4af71d3d8a4292d9f77cdd09ad Mon Sep 17 00:00:00 2001 From: justian Date: Tue, 19 Nov 2024 15:03:23 +0100 Subject: [PATCH 26/27] Refactor: made comment more clear, about what useEffect in AssetSelect is doing --- .../[locale]/incident/add/components/questions/AssetSelect.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx b/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx index 8438b9b5..a31b4d8a 100644 --- a/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx +++ b/src/app/[locale]/incident/add/components/questions/AssetSelect.tsx @@ -35,7 +35,6 @@ export const AssetSelect = ({ field }: AssetSelectProps) => { const t = useTranslations('describe-add.map') const [dialogMap, setDialogMap] = useState(null) const [features, setFeatures] = useState(null) - const { formState } = useFormStore() useEffect(() => { const getAddress = async () => { @@ -59,7 +58,7 @@ export const AssetSelect = ({ field }: AssetSelectProps) => { setDialogMap(map) } - // Extract and log map bounds on zoom or view change + // Set new features on map move or zoom useEffect(() => { const setNewFeatures = async () => { const bounds = dialogMap?.getBounds() From cae0fefe743f96118cef860bc869130bb1287378 Mon Sep 17 00:00:00 2001 From: justian Date: Tue, 19 Nov 2024 15:17:00 +0100 Subject: [PATCH 27/27] Refactor: remove unneccessary code --- .../incident/add/components/MapDialog.tsx | 38 +------------------ 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/src/app/[locale]/incident/add/components/MapDialog.tsx b/src/app/[locale]/incident/add/components/MapDialog.tsx index 0786dc3c..c3e9c3f9 100644 --- a/src/app/[locale]/incident/add/components/MapDialog.tsx +++ b/src/app/[locale]/incident/add/components/MapDialog.tsx @@ -18,8 +18,6 @@ import { Paragraph, Alert, Button, - ListboxOptionProps, - // SelectCombobox, } from '@/components' import { useConfig } from '@/hooks/useConfig' import { @@ -36,11 +34,7 @@ import { getFeatureType, isCoordinateInsideMaxBound, } from '@/lib/utils/map' -import { - getNearestAddressByCoordinate, - getSuggestedAddresses, -} from '@/services/location/address' -import { getServerConfig } from '@/services/config/config' +import { getNearestAddressByCoordinate } from '@/services/location/address' import { Feature, FeatureCollection } from 'geojson' import { PublicQuestion } from '@/types/form' import { FeatureListItem } from '@/app/[locale]/incident/add/components/FeatureListItem' @@ -64,7 +58,6 @@ const MapDialog = ({ const t = useTranslations('describe-add.map') const [marker, setMarker] = useState<[number, number] | []>([]) const [error, setError] = useState(null) - const [addressOptions, setAddressOptions] = useState([]) const { formState, updateForm } = useFormStore() const { dialogMap } = useMap() const { loading, config } = useConfig() @@ -301,35 +294,6 @@ const MapDialog = ({ ? field.meta.language.title : t('map_heading')} - {/* TODO: add address select */} - {/* {*/} - {/* const municipality = (await getServerConfig())['base'][*/} - {/* 'municipality'*/} - {/* ]*/} - {/* const apiCall = await getSuggestedAddresses(*/} - {/* evt.target.value,*/} - {/* municipality*/} - {/* )*/} - - {/* // TODO: Prevent out-of-order responses showing up*/} - {/* setAddressOptions([])*/} - {/* setAddressOptions(*/} - {/* apiCall.response.docs.map((item) => ({*/} - {/* children: item.weergavenaam,*/} - {/* value: item.weergavenaam,*/} - {/* }))*/} - {/* )*/} - {/* }}*/} - {/* />*/} - {/* }*/} - {/*>*/} {isAssetSelect && dialogMap && config &&