Skip to content

Commit

Permalink
Merge pull request #69 from delta10/feat/57-map
Browse files Browse the repository at this point in the history
Feat/57 map
  • Loading branch information
justiandevs authored Nov 6, 2024
2 parents fa59821 + 9216c1b commit a8b8ea4
Show file tree
Hide file tree
Showing 31 changed files with 503 additions and 247 deletions.
22 changes: 21 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
{
"base": {
"municipality": "purmerend"
"municipality": "purmerend",
"style": {
"primaryColor": "#24578f"
},
"map": {
"find_address_in_distance": 30,
"center": [
52.3731081,
4.8932945
],
"maxBounds": [
[
4.64034,
52.25168
],
[
5.10737,
52.50536
]
]
}
}
}
25 changes: 25 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@radix-ui/react-select": "2.1.2",
"@radix-ui/react-slot": "1.1.0",
"@radix-ui/react-visually-hidden": "1.1.0",
"@tabler/icons-react": "3.21.0",
"@types/lodash": "4.17.10",
"axios-retry": "4.5.0",
"class-variance-authority": "0.7.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client'

import { useTranslations } from 'next-intl'
import { useFormStore } from '@/store/form_store'
import { Heading, HeadingGroup, PreHeading } from '@/components'
import { IncidentQuestionsLocationForm } from '@/app/[locale]/incident/add/components/IncidentQuestionsLocationForm'

const currentStep = 2
const maxStep = 4

export const AdditionalInformationPage = () => {
const t = useTranslations('describe-add')
const tGeneral = useTranslations('general.describe_form')
const { loaded } = useFormStore()

// TODO: implement nice loading state, for if loaded if false. Also create a loading state for Stepper store.
if (loaded) {
return (
<div className="flex flex-col gap-4">
<HeadingGroup>
<Heading level={1}>{t('heading')}</Heading>
<PreHeading>
{tGeneral('pre-heading', { current: currentStep, max: maxStep })}
</PreHeading>
</HeadingGroup>
<IncidentQuestionsLocationForm />
</div>
)
}
}
49 changes: 0 additions & 49 deletions src/app/[locale]/incident/add/components/AddressSelect.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { useRouter } from '@/routing/navigation'
import { PublicQuestion } from '@/types/form'
import { Paragraph } from '@/components/index'
import { RenderSingleField } from '@/app/[locale]/incident/add/components/questions/RenderSingleField'
import { LocationSelect } from '@/app/[locale]/incident/add/components/questions/LocationSelect'
import { useTranslations } from 'next-intl'
import { isCoordinates } from '@/lib/utils/map'

export const IncidentQuestionsLocationForm = () => {
const { formState: formStoreState, updateForm } = useFormStore()
Expand All @@ -20,6 +23,7 @@ export const IncidentQuestionsLocationForm = () => {
const { addOneStep, setLastCompletedStep } = useStepperStore()
const router = useRouter()
const methods = useForm()
const t = useTranslations('general.errors')

useEffect(() => {
router.prefetch('/incident/contact')
Expand All @@ -45,6 +49,18 @@ export const IncidentQuestionsLocationForm = () => {
appendAdditionalQuestions()
}, [formStoreState.main_category, formStoreState.sub_category])

useEffect(() => {
if (
isCoordinates(formStoreState.coordinates) &&
formStoreState.coordinates[0] === 0 &&
formStoreState.coordinates[1] === 0
) {
methods.setError('location', { message: t('location_required') })
} else {
methods.clearErrors('location')
}
}, [formStoreState.coordinates])

const onSubmit = (data: any) => {
const questionKeys = Object.keys(data)
const questionsToSubmit = additionalQuestions.filter(
Expand Down Expand Up @@ -117,7 +133,7 @@ export const IncidentQuestionsLocationForm = () => {
/* TODO: Implement nice loading state */
<Paragraph>Laden...</Paragraph>
) : (
<Paragraph>TODO: Laat hier een LocationSelect zien</Paragraph>
<LocationSelect />
)}
<IncidentFormFooter />
</form>
Expand Down
120 changes: 83 additions & 37 deletions src/app/[locale]/incident/add/components/MapDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
import * as Dialog from '@radix-ui/react-dialog'
import { TbPlus } from 'react-icons/tb'
import React, { useState } from 'react'
import React, { useEffect, useState } from 'react'
import Map, {
MapLayerMouseEvent,
Marker,
useMap,
ViewState,
} from 'react-map-gl/maplibre'
import { useTranslations } from 'next-intl'
import { AddressSelect } from '@/app/[locale]/incident/add/components/AddressSelect'
import * as VisuallyHidden from '@radix-ui/react-visually-hidden'
import { useFormStore } from '@/store/form_store'
import { Heading } from '@/components/index'
import { Heading, Icon } from '@/components/index'
import { useConfig } from '@/hooks/useConfig'
import { Button } from '@/components/index'
import { IconMapPinFilled } from '@tabler/icons-react'

type MapDialogProps = {
trigger: React.ReactElement
marker: Array<number>
} & React.HTMLAttributes<HTMLDivElement>

const MapDialog = ({ trigger, marker }: MapDialogProps) => {
const MapDialog = ({ trigger }: MapDialogProps) => {
const t = useTranslations('describe-add.map')
const { updateForm, formState } = useFormStore()
const [marker, setMarker] = useState<[number, number] | []>([])
const { formState, updateForm } = useFormStore()
const { dialogMap } = useMap()
const { loading, config } = useConfig()

const [viewState, setViewState] = useState<ViewState>({
longitude: 5.10448,
latitude: 52.092876,
latitude: 0,
longitude: 0,
zoom: 15,
bearing: 0,
padding: {
Expand All @@ -35,53 +39,95 @@ const MapDialog = ({ trigger, marker }: MapDialogProps) => {
pitch: 0,
})

// Set viewState coordinates to configured ones
useEffect(() => {
if (!loading && config) {
setViewState({
...viewState,
latitude:
formState.coordinates[0] === 0
? config.base.map.center[0]
: formState.coordinates[0],
longitude:
formState.coordinates[1] === 0
? config.base.map.center[1]
: formState.coordinates[1],
})
}
}, [loading, config, formState.coordinates])

// Change marker position on formState.coordinates change
useEffect(() => {
setMarker([formState.coordinates[0], formState.coordinates[1]])
}, [formState.coordinates])

// Handle click on map (flyTo position, after this set the marker position and after this set the view state)
const handleMapClick = (event: MapLayerMouseEvent) => {
updateForm({
...formState,
coordinates: [event.lngLat.lng, event.lngLat.lat],
})
if (dialogMap) {
dialogMap.flyTo({
center: [event.lngLat.lng, event.lngLat.lat],
speed: 0.5,
zoom: 18,
})
}

setViewState({
...viewState,
zoom: 16,
latitude: event.lngLat.lat,
longitude: event.lngLat.lng,
})
setMarker([event.lngLat.lat, event.lngLat.lng])
}

return (
<Dialog.Root>
<Dialog.Trigger asChild>{trigger}</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay />
<Dialog.Content className="fixed inset-0 bg-white z-[1000] grid grid-cols-1 md:grid-cols-3">
<Dialog.Content className="fixed inset-0 bg-white z-[1000] grid grid-cols-1 md:grid-cols-3 utrecht-theme">
<VisuallyHidden.Root>
{/* TODO: Overleggen welke titel hier het meest vriendelijk is voor de gebruiker, multi-language support integreren */}
<Dialog.Title>Locatie kiezen</Dialog.Title>
<Dialog.Description>
Kies een locatie op de kaart voor de locatie van uw melding.
</Dialog.Description>
</VisuallyHidden.Root>
<div className="col-span-1 p-4 flex flex-col gap-4">
<Heading level={1}>{t('map_heading')}</Heading>
<AddressSelect />
</div>
<div className="col-span-1 md:col-span-2">
<Map
{...viewState}
onClick={(e) => handleMapClick(e)}
onMove={(evt) => setViewState(evt.viewState)}
style={{ width: '100%', height: '100%' }}
mapStyle={`${process.env.NEXT_PUBLIC_MAPTILER_MAP}/style.json?key=${process.env.NEXT_PUBLIC_MAPTILER_API_KEY}`}
>
<Marker longitude={marker[0]} latitude={marker[1]}></Marker>
</Map>
<div className="fixed right-0 top-0 p-4 bg-white mt-4 mr-4 w-12 h-12 flex items-center justify-center text-2xl text-black border-border border-2">
<Dialog.Close className="rotate-45">
<TbPlus />
<div className="col-span-1 p-4 flex flex-col justify-between gap-4">
<div>
<Heading level={1}>{t('map_heading')}</Heading>
</div>
<div>
<Dialog.Close
asChild
onClick={() =>
updateForm({ ...formState, coordinates: marker })
}
>
<Button appearance="primary-action-button">Kies locatie</Button>
</Dialog.Close>
</div>
</div>
{/* TODO: Implement state if loading, and no config is found */}
{config && (
<div className="col-span-1 md:col-span-2">
<Map
{...viewState}
id="dialogMap"
onClick={(e) => handleMapClick(e)}
onMove={(evt) => setViewState(evt.viewState)}
style={{ blockSize: '100%', inlineSize: '100%' }}
mapStyle={`${process.env.NEXT_PUBLIC_MAPTILER_MAP}/style.json?key=${process.env.NEXT_PUBLIC_MAPTILER_API_KEY}`}
attributionControl={false}
maxBounds={config.base.map.maxBounds}
>
{marker.length && (
<Marker latitude={marker[0]} longitude={marker[1]}>
<Icon className="map-marker-icon">
<IconMapPinFilled
className="-translate-y-1/2"
color={config.base.style.primaryColor}
/>
</Icon>
</Marker>
)}
</Map>
</div>
)}
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
Expand Down
Loading

0 comments on commit a8b8ea4

Please sign in to comment.