Files
geointel-Public/frontend/src/components/project/AreaPanel.tsx
T

196 lines
7.0 KiB
TypeScript

import { useEffect, useMemo, useState, type FormEvent } from 'react'
import type { AreaRead, ProjectRead } from '../../types'
const AREA_CATALOG_PAGE_SIZE = 12
function projectDisplayName(project: ProjectRead | null): string {
if (project?.name === 'Belgium and North Sea Workbench') {
return 'België en Belgische Noordzee'
}
if (project?.name === 'Kempen Regional Workbench') {
return 'Kempen · volledige regio'
}
if (project?.name === 'Mol Municipality Workbench') {
return 'Mol · lokale historische werkruimte'
}
return project?.name ?? 'Geen werkruimte actief'
}
interface AreaFormState {
name: string
geometry: string
crs: string
}
interface AreaPanelProps {
areas: AreaRead[]
selectedProject: ProjectRead | null
selectedProjectId: string | null
loadingAreas: boolean
areaForm: AreaFormState
selectedMapAreaId: string
onCreateArea: (event: FormEvent<HTMLFormElement>) => void
onUpdateAreaForm: (areaForm: AreaFormState) => void
onSelectMapArea: (areaId: string) => void
}
export function AreaPanel({
areas,
selectedProject,
selectedProjectId,
loadingAreas,
areaForm,
selectedMapAreaId,
onCreateArea,
onUpdateAreaForm,
onSelectMapArea,
}: AreaPanelProps): JSX.Element {
const [catalogOpen, setCatalogOpen] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
const [page, setPage] = useState(1)
const selectedArea = areas.find((area) => area.id === selectedMapAreaId)
const filteredAreas = useMemo(() => {
const query = searchQuery.trim().toLocaleLowerCase('nl-BE')
if (!query) return areas
return areas.filter((area) => area.name.toLocaleLowerCase('nl-BE').includes(query))
}, [areas, searchQuery])
const pageCount = Math.max(1, Math.ceil(filteredAreas.length / AREA_CATALOG_PAGE_SIZE))
const visibleAreas = filteredAreas.slice(
(page - 1) * AREA_CATALOG_PAGE_SIZE,
page * AREA_CATALOG_PAGE_SIZE,
)
useEffect(() => {
setPage((current) => Math.min(current, pageCount))
}, [pageCount])
useEffect(() => {
setSearchQuery('')
setPage(1)
setCatalogOpen(false)
}, [selectedProjectId])
return (
<section data-testid="area-panel">
<div className="panel-title-row">
<div>
<p className="eyebrow">Geografisch bereik</p>
<h2>Gebieden</h2>
</div>
<span className="count-pill">{areas.length}</span>
</div>
<p className="muted">{projectDisplayName(selectedProject)}</p>
<div className="data-selection-summary data-selection-summary-area">
<span>Getoond op de kaart</span>
<strong>{selectedArea?.name ?? 'Geen gebied geselecteerd'}</strong>
<small>{selectedArea?.area_m2 ? `${(selectedArea.area_m2 / 1_000_000).toLocaleString('nl-BE', { maximumFractionDigits: 2 })} km2` : 'Kies een gemeente, gewest, zeezone of teken een eigen selectie.'}</small>
</div>
<details className="data-panel-form-block">
<summary>Eigen gebied toevoegen (GeoJSON)</summary>
<form className="compact-form" onSubmit={onCreateArea}>
<label>
Naam
<input
value={areaForm.name}
onChange={(event) => onUpdateAreaForm({ ...areaForm, name: event.target.value })}
placeholder="Naam van het gebied"
/>
</label>
<label>
CRS
<input
value={areaForm.crs}
onChange={(event) => onUpdateAreaForm({ ...areaForm, crs: event.target.value })}
placeholder="EPSG:4326"
/>
</label>
<label>
GeoJSON-geometrie
<textarea
value={areaForm.geometry}
onChange={(event) => onUpdateAreaForm({ ...areaForm, geometry: event.target.value })}
rows={4}
/>
</label>
<button className="primary-action" type="submit" disabled={!selectedProjectId}>
Gebied toevoegen
</button>
</form>
</details>
{loadingAreas ? <p>Gebieden laden...</p> : null}
{areas.length === 0 ? <p>Nog geen gebieden beschikbaar.</p> : null}
<details
className="data-panel-list-block area-catalog-disclosure"
onToggle={(event) => setCatalogOpen(event.currentTarget.open)}
>
<summary>{areas.length} beschikbare gemeenten en regiogrenzen</summary>
{catalogOpen ? (
<>
<div className="catalog-browser-toolbar">
<label className="catalog-search-field">
Zoek gemeente of regio
<input
type="search"
value={searchQuery}
onChange={(event) => {
setSearchQuery(event.target.value)
setPage(1)
}}
placeholder="Bijvoorbeeld Brussel, Namen of Noordzee"
/>
</label>
<div className="catalog-pagination" aria-label="Paginering van gebieden">
<span>{filteredAreas.length} gevonden</span>
<button
className="secondary-action"
type="button"
disabled={page === 1}
onClick={() => setPage((current) => Math.max(1, current - 1))}
>
Vorige
</button>
<strong>{page} / {pageCount}</strong>
<button
className="secondary-action"
type="button"
disabled={page === pageCount}
onClick={() => setPage((current) => Math.min(pageCount, current + 1))}
>
Volgende
</button>
</div>
</div>
{visibleAreas.length > 0 ? (
<ul className="entity-list">
{visibleAreas.map((area) => (
<li className={selectedMapAreaId === area.id ? 'entity-card entity-card-active' : 'entity-card'} key={area.id}>
<strong>{area.name}</strong>
<div className="entity-meta">
<span>{area.area_m2 ? `${(area.area_m2 / 1_000_000).toLocaleString('nl-BE', { maximumFractionDigits: 2 })} km2` : 'Oppervlakte onbekend'}</span>
<span>{area.geometry?.type ?? area.geometry_type ?? 'Geometrie onbekend'}</span>
</div>
<button
className="secondary-action"
type="button"
onClick={() => onSelectMapArea(area.id)}
disabled={!area.geometry}
data-testid={`area-show-${area.id}`}
>
{selectedMapAreaId === area.id ? 'Op kaart' : 'Toon op kaart'}
</button>
</li>
))}
</ul>
) : (
<p className="muted">Geen gebied gevonden voor deze zoekterm.</p>
)}
</>
) : null}
</details>
</section>
)
}