Skip to content

Commit

Permalink
Merge pull request #96 from Amsterdam/feature/128109-search-vve
Browse files Browse the repository at this point in the history
128109 Added VVE search by address
  • Loading branch information
remyvdwereld authored Oct 14, 2024
2 parents 16b75f2 + 367f12a commit 7cee627
Show file tree
Hide file tree
Showing 18 changed files with 355 additions and 26 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
18 changes: 18 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"
20 changes: 20 additions & 0 deletions src/app/pages/AddressPage/AddressPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { HousingIcon } from "@amsterdam/design-system-react-icons"
import { useParams } from "react-router-dom"
import { PanoramaPreview, PageHeading } from "app/components"
import HoaDescription from "./HoaDescription"


export const AddressPage: React.FC = () => {
const { bagId } = useParams<{ bagId: string }>()

return (
<>
<PageHeading label="Adresoverzicht" icon={ HousingIcon } />
{ bagId && <PanoramaPreview bagId={ bagId } aspect={ 4 } fov={ 120 } />}
{ bagId && <HoaDescription bagId={ bagId } /> }
</>
)
}

export default AddressPage

47 changes: 47 additions & 0 deletions src/app/pages/AddressPage/HoaDescription.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Button } from "@amsterdam/design-system-react"
import { styled } from "styled-components"
import { useNavigate } from "react-router-dom"
import { PageSpinner, Descriptions } from "app/components"
import { useHomeownerAssociation } from "app/state/rest"


type Props = {
bagId: string
}

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

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

if (isBusy) {
return <PageSpinner />
}
if (data?.message) {
return <p>Er zijn geen VVE-gegevens gevonden voor dit adres.</p>
}
if (data?.id) {
const items = [
{ label: "VVE statutaire naam", children: data?.name },
{ label: "Bouwjaar", children: data?.build_year },
{ label: "Aantal appartementen", children: data?.number_of_appartments }
]
return (
<>
<Wrapper>
<Descriptions items={ items } />
</Wrapper>
<Button onClick={ () => navigate(`/vve/${ data.id }/zaken/nieuw`)} >
Nieuwe zaak aanmaken
</Button>
</>
)
}
return null
}

export default HoaDescription

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
})
}
17 changes: 15 additions & 2 deletions 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"
const MUNICIPALITY_FILTER = "gemeentenaam:(amsterdam)"
const ADDRESS_FILTER = "AND (type:adres) AND (adrestype: hoofdadres)"
const DEFAULT_SORT = "score desc, weergavenaam asc"
Expand All @@ -29,7 +29,20 @@ export const useBagPdok = (searchString?: string, options?: Options) => {
const query = constructQuery(searchString)

return useApiRequest<BAGPdokResponse>({
url: `${ PDOK_URL }${ query }`,
url: `${ PDOK_URL }/suggest${ query }`,
lazy: searchString === undefined,
...options,
groupName: "bagPdok",
handleError
})
}

export const useBagPdokByBagId = (searchString?: string, options?: Options) => {
const handleError = useErrorHandler()
const query = constructQuery(searchString)

return useApiRequest<BAGPdokResponse>({
url: `${ PDOK_URL }/free${ query }`,
lazy: searchString === undefined,
...options,
groupName: "bagPdok",
Expand Down
Loading

0 comments on commit 7cee627

Please sign in to comment.