Skip to content

Commit

Permalink
128109 Added VVE search by address
Browse files Browse the repository at this point in the history
  • Loading branch information
remyvdwereld committed Oct 11, 2024
1 parent 16b75f2 commit 0fb48ce
Show file tree
Hide file tree
Showing 17 changed files with 317 additions and 25 deletions.
3 changes: 2 additions & 1 deletion scripts/src/generateSwaggerSchema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { exec } from "child_process"

const url = "https://api.zwd.woon-o.azure.amsterdam.nl/api/schema/?format=json"
// const url = "https://api.zwd.woon-o.azure.amsterdam.nl/api/schema/?format=json"
const url = "http://localhost:8081/api/schema/?format=json"

exec(`dtsgen -o ./src/__generated__/apiSchema.d.ts --url ${ url }`,
(error, stdout, stderr) => {
Expand Down
17 changes: 17 additions & 0 deletions src/__generated__/apiSchema.d.ts

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

28 changes: 28 additions & 0 deletions src/app/components/Descriptions/Descriptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { DescriptionList } from "@amsterdam/design-system-react"
import { Fragment } from "react"

type DescriptionItem = {
label: string;
children: React.ReactNode
}

type DescriptionsProps = {
items: DescriptionItem[]
}

export const Descriptions: React.FC<DescriptionsProps> = ({ items }) => (
<DescriptionList>
{items.map((item, index) => (
<Fragment key={ index }>
<DescriptionList.Term>
{item.label}
</DescriptionList.Term>
<DescriptionList.Details>
{item.children}
</DescriptionList.Details>
</Fragment>
))}
</DescriptionList>
)

export default Descriptions
56 changes: 56 additions & 0 deletions src/app/components/Panorama/PanoramaPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useRef } from "react"
import styled from "styled-components"
import { usePanoramaByBagId } from "app/state/rest/custom/usePanoramaByBagId"
import useRect from "./hooks/useRect"

type Props = {
bagId: string
width?: number
aspect?: number
radius?: number
fov?: number
}

const Div = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 100%;
.skeleton {
width: 100%;
height: 100%;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
`
const Img = styled.img`
width: 100%;
`

export const PanoramaPreview: React.FC<Props> = ({ bagId, width: w, aspect = 1.5, radius = 180, fov = 80 }) => {
const ref = useRef<HTMLDivElement>(null)
const rect = useRect(ref, 100)
const width = w ?? rect.width
const height = width !== undefined ? width / aspect : undefined
const [data] = usePanoramaByBagId(bagId, width, aspect, radius, fov)

return (
<Div ref={ ref } style={ { height } }>
{ data ? (
<Img src={ data.url } alt={ `Panorama preview voor BAG: ${ bagId }` } />
) : <div className="skeleton"></div>
}
</Div>
)
}

export default PanoramaPreview
60 changes: 60 additions & 0 deletions src/app/components/Panorama/hooks/useRect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useLayoutEffect, useCallback, useState } from "react"
import debounce from "lodash.debounce"

type RectResult = {
bottom: number
height: number
left: number
right: number
top: number
width: number
};

const getRect = <T extends HTMLElement>(element?: T): RectResult => {
let rect: RectResult = {
bottom: 0,
height: 0,
left: 0,
right: 0,
top: 0,
width: 0
}
if (element) rect = element.getBoundingClientRect()
return rect
}

export default <T extends HTMLElement>(
ref: React.RefObject<T>,
delay = 0
): RectResult => {
const [rect, setRect] = useState<RectResult>(
ref?.current ? getRect(ref.current) : getRect()
)

const handleResize = useCallback(() => {
if (ref.current == null) return
setRect(getRect(ref.current)) // Update client rect
}, [ref])

useLayoutEffect(() => {
const element = ref.current
if (element == null) return

handleResize()

const debounced = debounce(handleResize, delay)

if (typeof ResizeObserver === "function") {
const resizeObserver = new ResizeObserver(debounced)
resizeObserver.observe(element)
return () => {
resizeObserver.disconnect()
}
} else {
window.addEventListener("resize", debounced) // Browser support, remove freely
return () => window.removeEventListener("resize", debounced)
}
}, [ref, delay, handleResize])

return rect
}
8 changes: 5 additions & 3 deletions src/app/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
export * from "./Descriptions/Descriptions"
export * from "./DefaultLayout/DefaultLayout"
export * from "./DetailsList/DetailsList"
export * from "./TimelineEvents/TimelineEvents"
export * from "./icons/icons"
export * from "./Modal/Modal"
export * from "./LinkButton/LinkButton"
export * from "./Modal/hooks/useModal"
export * from "./Modal/Modal"
export * from "./NavMenu/NavMenu"
export * from "./PageHeading/PageHeading"
export * from "./LinkButton/LinkButton"
export * from "./Panorama/PanoramaPreview"
export * from "./SmallSkeleton/SmallSkeleton"
export * from "./Spinner/Spinner"
export * from "./Table/Table"
export * from "./Table/types"
export * from "./TimelineEvents/TimelineEvents"
export * from "./User/User"
44 changes: 44 additions & 0 deletions src/app/pages/AddressPage/AddressPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Button } from "@amsterdam/design-system-react"
import { HousingIcon } from "@amsterdam/design-system-react-icons"
import { styled } from "styled-components"
import { useNavigate, useParams } from "react-router-dom"
import { PanoramaPreview, PageHeading, PageSpinner, Descriptions } from "app/components"
import { useHomeownerAssociation } from "app/state/rest"


const Wrapper = styled.div`
margin: 24px 0;
`

export const AddressPage: React.FC = () => {
const { bagId } = useParams<{ bagId: string }>()
const [data, { isBusy }] = useHomeownerAssociation(bagId)
const navigate = useNavigate()

const items = [
{ label: "VVE statutaire naam", children: data?.name },
{ label: "Bouwjaar", children: data?.build_year },
{ label: "Aantal appartementen", children: data?.number_of_appartments }
]

return (
<>
<PageHeading label="Adresoverzicht" icon={ HousingIcon } />
{ bagId && <PanoramaPreview bagId={ bagId } aspect={ 4 } fov={ 120 } />}
{ isBusy && <PageSpinner /> }
{ data && data.id && (
<>
<Wrapper>
<Descriptions items={ items } />
</Wrapper>
<Button onClick={ () => navigate(`/vve/${ data.id }/zaken/nieuw`)} >
Nieuwe zaak aanmaken
</Button>
</>
)}
</>
)
}

export default AddressPage

20 changes: 9 additions & 11 deletions src/app/pages/SearchPage/SearchResults/SearchResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,15 @@ const SearchResults: React.FC<Props> = ({ searchString }) => {
const dataSource = bagData?.response?.docs?.filter((obj) => obj.adresseerbaarobject_id) || []

return (
isValid ? (
<Table
columns={ columns }
data={ dataSource }
loading={ loading }
numLoadingRows={ 1 }
onClickRow={({ adresseerbaarobject_id }) => navigate(`vve/${ adresseerbaarobject_id }/zaken/nieuw`)}
emptyPlaceholder="Er zijn geen adressen gevonden"
pagination={ false }
/>
) : null
<Table
columns={ columns }
data={ dataSource }
loading={ loading }
numLoadingRows={ 1 }
onClickRow={({ adresseerbaarobject_id }) => navigate(`adres/${ adresseerbaarobject_id }`)}
emptyPlaceholder={ isValid ? "Geen resultaten gevonden" : "Voer minimaal 3 karakters in" }
pagination={ false }
/>
)
}

Expand Down
2 changes: 1 addition & 1 deletion src/app/pages/SearchVvePage/SearchResultsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const SearchResultsTable: React.FC = () => {
columns={ columns }
data={ vveList }
loading={ false }
onClickRow={(_, id) => navigate(`vve/${ id }/zaken/nieuw`)}
onClickRow={(_, id) => navigate(`/vve/${ id }/zaken/nieuw`)}
/>
</>
)
Expand Down
1 change: 1 addition & 0 deletions src/app/pages/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./AuthPage/AuthPage"
export * from "./AddressPage/AddressPage"
export * from "./CaseCreatePage/CaseCreatePage"
export * from "./CaseDetailsPage/CaseDetailsPage"
export * from "./CasesPage/CasesPage"
Expand Down
8 changes: 6 additions & 2 deletions src/app/routing/router.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DefaultLayout } from "app/components"
import {
AuthPage, CaseCreatePage, CaseDetailsPage, CasesPage,
NotFoundPage, SearchPage, TasksPage, BpmnPage, SearchVvePage
AuthPage, AddressPage, CaseCreatePage, CaseDetailsPage, CasesPage,
NotFoundPage, SearchPage, TasksPage, BpmnPage, SearchVvePage
} from "app/pages"
import { createBrowserRouter } from "react-router-dom"

Expand All @@ -15,6 +15,10 @@ const router = createBrowserRouter([
path: "/",
element: <SearchPage />
},
{
path: "adres/:bagId",
element: <AddressPage />
},
{
path: "bpmn",
element: <BpmnPage />
Expand Down
16 changes: 16 additions & 0 deletions src/app/state/rest/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Options } from "."
import { makeApiUrl, useErrorHandler } from "./hooks/utils"
import useApiRequest from "./hooks/useApiRequest"


export const useHomeownerAssociation = (bagId?: string ,options?: Options) => {
const handleError = useErrorHandler()
return useApiRequest<Components.Schemas.HomeownerAssociation>({
...options,
url: `${ makeApiUrl("address", bagId, "homeowner-association") }`,
lazy: bagId === undefined,
groupName: "address",
handleError,
isProtected: true
})
}
2 changes: 1 addition & 1 deletion src/app/state/rest/bagPdok.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import useApiRequest from "./hooks/useApiRequest"
import qs from "qs"

// Constants
const PDOK_URL = "https://api.pdok.nl/bzk/locatieserver/search/v3_1/suggest"
const PDOK_URL = "https://api.pdok.nl/bzk/locatieserver/search/v3_1/free"
const MUNICIPALITY_FILTER = "gemeentenaam:(amsterdam)"
const ADDRESS_FILTER = "AND (type:adres) AND (adrestype: hoofdadres)"
const DEFAULT_SORT = "score desc, weergavenaam asc"
Expand Down
36 changes: 36 additions & 0 deletions src/app/state/rest/custom/usePanoramaByBagId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useBagPdok, usePanorama } from "app/state/rest"

const extractLatLng = (point?: BAGPdokAddress["centroide_ll"]) => {
// Ensure the string starts with "POINT(" and ends with ")"
if (point && point.startsWith("POINT(") && point.endsWith(")")) {
// Remove "POINT(" from the start and ")" from the end
const coordinates = point.slice(6, -1)
// Split the coordinates by space
const [lng, lat] = coordinates.split(" ")
// Parse the coordinates to floats
return {
lat: parseFloat(lat),
lng: parseFloat(lng)
}
}
return null
}

export const usePanoramaByBagId = (bagId: string, width: number | undefined, aspect: number | undefined, radius: number, fov: number | undefined) => {
const [data] = useBagPdok(bagId)
const docs = data?.response?.docs
const foundAddress = docs && docs[0] ? docs[0] : undefined
const latLng = extractLatLng(foundAddress?.centroide_ll)

return usePanorama(
latLng?.lat,
latLng?.lng,
width,
aspect,
radius,
fov,
{ lazy: foundAddress === undefined || width === undefined }
)
}

export default usePanoramaByBagId
15 changes: 15 additions & 0 deletions src/app/state/rest/dataPunt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import qs from "qs"
import type { Options } from "./"
import { useSuppressErrorHandler } from "./hooks/utils"
import useApiRequest from "./hooks/useApiRequest"

export const usePanorama = (lat?: number, lon?: number, width?: number, aspect?: number, radius?: number, fov?: number, options?: Options) => {
const handleError = useSuppressErrorHandler()
const queryString = qs.stringify({ lat, lon, width, fov, aspect, radius }, { addQueryPrefix: true })
return useApiRequest<{ url: string }>({
...options,
url: `https://api.data.amsterdam.nl/panorama/thumbnail/${ queryString }`,
groupName: "dataPunt",
handleError
})
}
Loading

0 comments on commit 0fb48ce

Please sign in to comment.