/* Data-scherm: zoeken op panden/percelen + detailview Redesigned - Thema Centric DutchGovernmentModern */ /* ================================================================ ZOEKSCHERM ================================================================ */ function DataZoekScherm({ onSelectPand }) { const [zoekterm, setZoekterm] = React.useState(''); const [resultaten, setResultaten] = React.useState([]); const [laden, setLaden] = React.useState(false); const [fout, setFout] = React.useState(null); async function zoek(q) { if (!q || q.length < 2) { setResultaten([]); return; } setLaden(true); setFout(null); try { const r = await fetch(`${API}/data/zoek?q=${encodeURIComponent(q)}`); if (!r.ok) throw new Error(`HTTP ${r.status}`); setResultaten(await r.json()); } catch(e) { setFout(e.message); } finally { setLaden(false); } } React.useEffect(() => { const t = setTimeout(() => zoek(zoekterm), 300); return () => clearTimeout(t); }, [zoekterm]); return (

Panden zoeken

Zoek op adres, straatnaam, BAG ID, VBO ID of perceelnummer

setZoekterm(e.target.value)} autoFocus /> {laden && } {zoekterm && }
{fout && (
Fout bij ophalen: {fout}
)} {!laden && zoekterm.length >= 2 && resultaten.length === 0 && !fout && ( )} {zoekterm.length < 2 && (
Voer een zoekopdracht in
Minimaal 2 tekens om te beginnen met zoeken
)} {resultaten.length > 0 && ( <>
{resultaten.map(r => ( onSelectPand(r.pand_id)} style={{ cursor: 'pointer' }}> ))}
Adres Pand-ID Perceel-ID Gemeente Runs
{r.adres || '-'}
{r.postcode &&
{r.postcode}
}
{r.pand_id} {r.perceel_id || '-'} {r.gemeente_naam || r.gemeente_code} {r.pipeline_runs > 0 ? {r.pipeline_runs} : -}
{resultaten.length === 100 && (
Maximaal 100 resultaten getoond - verfijn uw zoekopdracht voor nauwkeurigere resultaten.
)} )}
); } /* ================================================================ DETAILSCHERM ================================================================ */ function InfoRij({ label, waarde, mono }) { return (
{label} {waarde || '-'}
); } function BoolRij({ label, waarde }) { return (
{label}
); } function ZekerheidBalk({ zekerheid }) { const pct = zekerheid === 'hoog' ? 90 : zekerheid === 'midden' ? 55 : 25; const kleur = zekerheid === 'hoog' ? 'var(--color-success)' : zekerheid === 'midden' ? 'var(--color-warning)' : 'var(--color-error)'; return (
{zekerheid || '-'}
); } function fmtM2(waarde) { const n = Number(waarde); if (!Number.isFinite(n)) return null; return `${n.toFixed(n >= 10 ? 1 : 2)} m2`; } function ResultaatClusters({ clusters }) { const lijst = Array.isArray(clusters) ? clusters : []; if (lijst.length === 0) return null; return (
Gevonden componenten
{lijst.map((cluster, idx) => { const extra = cluster.extra || {}; const subtype = cluster.subtype || cluster.type || 'niet_geclassificeerd'; const label = CONTOUR_SUBTYPE_LABEL[subtype] || subtype; const area = fmtM2(extra.component_area_m2); const nummer = extra.nummer || idx + 1; const toelichting = extra.toelichting; return (
#{nummer} {label} {area && {area}} {cluster.zekerheid && {cluster.zekerheid}} {extra.ai_status && extra.ai_status !== 'ok' && {extra.ai_status}}
{toelichting && (
{toelichting}
)}
); })}
); } function AfbeeldingViewer({ resultaatId }) { const [status, setStatus] = React.useState('laden'); // laden | ok | geen const [variant, setVariant] = React.useState('default'); // default | dsm const [dsmBeschikbaar, setDsmBeschikbaar] = React.useState(false); React.useEffect(() => { setDsmBeschikbaar(false); fetch(`${API}/afbeelding/${resultaatId}?variant=dsm`, { method: 'HEAD' }) .then(r => setDsmBeschikbaar(r.ok)) .catch(() => setDsmBeschikbaar(false)); }, [resultaatId]); return (
{dsmBeschikbaar && (
)} {status === 'laden' && ( Laden... )} {status === 'geen' && (
Geen afbeelding beschikbaar
)} {variant setStatus('ok')} onError={() => setStatus('geen')} style={{ display: status === 'ok' ? 'block' : 'none', maxWidth: 'min(800px, 80vw)', maxHeight: '70vh', borderRadius: 'var(--radius-sm)', objectFit: 'contain', }} />
); } /* Lagen-config voor de Data-detailkaart — gespiegeld op Kaart-pagina (kaart.jsx). id's komen overeen met laagInstellingen-keys; mapLayers/sourceId zijn de detail-specifieke MapLibre-id's. */ const DETAIL_LAGEN_CONFIG = [ { id: 'panden', label: 'Panden rondom', swatch: '#3b82f6', mapLayers: ['detail-panden-fill', 'detail-panden-outline'], sourceId: 'detail-panden' }, ...RESULTAAT_LAGEN.map(l => ({ id: l.id, label: l.label, swatch: l.swatch, mapLayers: [l.fillId, l.lineId], sourceId: l.sourceId })), ]; function detailBoundsVanSource(map, sourceId) { const src = map.getSource(sourceId); if (!src || !src._data?.features?.length) return null; const bounds = new maplibregl.LngLatBounds(); const flat = (coords) => { if (!coords?.length) return; if (typeof coords[0] === 'number') { bounds.extend(coords); return; } coords.forEach(flat); }; src._data.features.forEach(f => { if (f.geometry) flat(f.geometry.coordinates); }); return bounds.isEmpty() ? null : bounds; } function DetailLaagRij({ laag, inst, updateLaag, mapInstanceRef, onDragStart, onDragOver, onDrop, isDragOver }) { const aan = !!inst?.aan; const [expanded, setExpanded] = React.useState(false); const opPct = Math.round((inst?.opacity ?? 1) * 10) * 10; function stap(delta) { const huidig = Math.round((inst?.opacity ?? 1) * 10) * 10; updateLaag(laag.id, { opacity: Math.min(100, Math.max(10, huidig + delta)) / 100 }); } return (
{ e.dataTransfer.effectAllowed = 'move'; onDragStart(laag.id); }} onDragOver={e => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; onDragOver(laag.id); }} onDrop={e => { e.preventDefault(); onDrop(laag.id); }} onDragEnd={() => onDrop(null)}>
{ e.stopPropagation(); setExpanded(v => !v); }}> updateLaag(laag.id, { aan: !aan })}> {aan && } updateLaag(laag.id, { aan: !aan })}>{laag.label} {laag.sourceId && ( )}
{expanded && (
Helderheid
{opPct}%
)}
); } function DataDetailKaart({ data, kaartmateriaal = [], onOpenPand }) { const mapRef = React.useRef(null); const mapInstanceRef = React.useRef(null); const dataRef = React.useRef(data); const laagInstellingenRef = React.useRef(null); const achtergrondIdRef = React.useRef(null); const [lagenOpen, setLagenOpen] = React.useState(true); const [laagInstellingen, setLaagInstellingen] = React.useState({ achtergrond: { aan: true, opacity: 1 }, panden: { aan: true, opacity: 0.65 }, zonnepanelen: { aan: false, opacity: 1 }, dakkapellen: { aan: false, opacity: 1 }, contourafwijking: { aan: true, opacity: 1 }, }); const [achtergrondId, setAchtergrondId] = React.useState(null); const [lagenVolgorde, setLagenVolgorde] = React.useState(() => DETAIL_LAGEN_CONFIG.map(l => l.id)); const [dragBron, setDragBron] = React.useState(null); const [dragDoel, setDragDoel] = React.useState(null); const [nietAanwezigZichtbaar, setNietAanwezigZichtbaar] = React.useState(false); dataRef.current = data; laagInstellingenRef.current = laagInstellingen; achtergrondIdRef.current = achtergrondId; const heeftDetectieAan = RESULTAAT_LAGEN.some(l => laagInstellingen[l.id]?.aan); function legeCollectie() { return { type: 'FeatureCollection', features: [] }; } function eersteRing(geom) { if (!geom?.coordinates) return null; if (geom.type === 'Polygon') return geom.coordinates[0]; if (geom.type === 'MultiPolygon') return geom.coordinates[0]?.[0]; return null; } function boundsVoorGeom(geom) { const bounds = new maplibregl.LngLatBounds(); function voegToe(coords) { if (!coords?.length) return; if (typeof coords[0] === 'number') { bounds.extend(coords); return; } coords.forEach(voegToe); } voegToe(geom?.coordinates); return bounds.isEmpty() ? null : bounds; } function updateLaag(id, patch) { setLaagInstellingen(prev => { const next = { ...prev, [id]: { ...prev[id], ...patch } }; laagInstellingenRef.current = next; return next; }); } function handleDetailDrag(doelId) { if (!dragBron || !doelId || dragBron === doelId) { setDragDoel(null); return; } const volgorde = [...lagenVolgorde]; const vanIdx = volgorde.indexOf(dragBron); const naarIdx = volgorde.indexOf(doelId); if (vanIdx === -1 || naarIdx === -1) return; volgorde.splice(vanIdx, 1); volgorde.splice(naarIdx, 0, dragBron); setLagenVolgorde(volgorde); setDragBron(null); setDragDoel(null); const map = mapInstanceRef.current; if (!map) return; // Lijst boven = voorgrond → MapLibre van achter naar voren plaatsen [...volgorde].reverse().forEach((id, i, omg) => { const lc = DETAIL_LAGEN_CONFIG.find(l => l.id === id); if (!lc) return; const volgendConf = DETAIL_LAGEN_CONFIG.find(l => l.id === omg[i + 1]); const beforeId = volgendConf ? volgendConf.mapLayers[0] : undefined; lc.mapLayers.forEach(lid => { try { map.moveLayer(lid, beforeId); } catch (e) {} }); }); } async function laadPandenRondom(map) { if (!dataRef.current?.gemeente_code || !map?.getBounds) return; const b = map.getBounds(); const url = `${API}/gemeenten/${dataRef.current.gemeente_code}/panden?minlng=${b.getWest()}&minlat=${b.getSouth()}&maxlng=${b.getEast()}&maxlat=${b.getNorth()}`; const geojson = await fetch(url).then(r => r.json()).catch(() => null); if (geojson && map.getSource('detail-panden')) { map.getSource('detail-panden').setData(geojson); } } async function laadResultaten(map) { if (!dataRef.current?.gemeente_code) return; for (const laag of RESULTAAT_LAGEN) { const zichtbaar = !!laagInstellingenRef.current?.[laag.id]?.aan; const geojson = await fetch(`${API}/gemeenten/${dataRef.current.gemeente_code}/resultaten-geojson?detectie_type=${laag.type}`) .then(r => r.json()) .catch(() => legeCollectie()); voegResultaatLagenToe(map, laag, geojson, zichtbaar); } } function toonPandPopup(map, lngLat, pandId) { const container = document.createElement('div'); const titel = document.createElement('strong'); const regel = document.createElement('div'); const knop = document.createElement('button'); titel.textContent = 'Pand'; knop.className = 'bd-popup-link bd-mono'; knop.textContent = pandId; knop.type = 'button'; knop.style.border = '0'; knop.style.background = 'transparent'; knop.style.color = '#004494'; knop.style.padding = '2px 0 0'; knop.style.cursor = 'pointer'; knop.style.fontFamily = 'var(--font-family-mono)'; knop.onclick = () => { if (onOpenPand) onOpenPand(pandId); }; container.appendChild(titel); regel.appendChild(knop); container.appendChild(regel); new maplibregl.Popup().setLngLat(lngLat).setDOMContent(container).addTo(map); } function toonResultaatPopup(map, lngLat, laag, props) { const subtype = props.subtype || props.type || laag.label || 'Resultaat'; const zekerheid = props.zekerheid || '?'; const oppervlak = props.component_area_m2 ?? props.oppervlak_m2 ?? '?'; const kandidaat = props.kandidaat_pand === true || props.kandidaat_pand === 'true'; new maplibregl.Popup() .setLngLat(lngLat) .setHTML(` ${subtype.toString().toUpperCase()}
Zekerheid: ${zekerheid}
Oppervlak: ${oppervlak} m2 ${kandidaat ? '
⚠ Vrijstaand — mogelijk ontbrekend pand' : ''} `) .addTo(map); } React.useEffect(() => { if (!data?.geom_wgs84 || mapInstanceRef.current) return undefined; const bounds = boundsVoorGeom(data.geom_wgs84); const ring = eersteRing(data.geom_wgs84); if (!bounds || !ring?.length) return undefined; const center = bounds.getCenter(); const map = new maplibregl.Map({ container: mapRef.current, style: { version: 8, sources: {}, layers: [], glyphs: 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf' }, center: [center.lng, center.lat], zoom: 18, attributionControl: false, }); map.addControl(new maplibregl.AttributionControl({ compact: true }), 'bottom-right'); map.on('load', async () => { const achtergrond = kaartmateriaal.find(k => k.id === achtergrondIdRef.current) || kaartmateriaal.find(k => k.voor_kaart && (k.naam || '').toLowerCase().includes('luchtfoto')) || kaartmateriaal.find(k => k.voor_kaart) || kaartmateriaal.find(k => (k.naam || '').toLowerCase().includes('brt')); map.addSource('detail-achtergrond', { type: 'raster', tiles: [tileUrlVoorKaartmateriaal(achtergrond) || BRT_WMTS_TEMPLATE], tileSize: 256 }); map.addLayer({ id: 'detail-achtergrond', type: 'raster', source: 'detail-achtergrond' }); map.addSource('detail-panden', { type: 'geojson', data: legeCollectie() }); map.addLayer({ id: 'detail-panden-fill', type: 'fill', source: 'detail-panden', paint: { 'fill-color': ['case', ['==', ['get', 'inactief'], true], '#888', '#3b82f6'], 'fill-opacity': laagInstellingenRef.current.panden.opacity * 0.28, }, }); map.addLayer({ id: 'detail-panden-outline', type: 'line', source: 'detail-panden', paint: { 'line-color': ['case', ['==', ['get', 'inactief'], true], '#e74c3c', '#fff'], 'line-width': 0.75, 'line-opacity': laagInstellingenRef.current.panden.opacity, }, }); map.addSource('detail-pand-selected', { type: 'geojson', data: { type: 'Feature', geometry: data.geom_wgs84, properties: { identificatie: data.pand_id } }, }); map.addLayer({ id: 'detail-pand-selected-fill', type: 'fill', source: 'detail-pand-selected', paint: { 'fill-color': '#004494', 'fill-opacity': 0.25 }, }); map.addLayer({ id: 'detail-pand-selected-line', type: 'line', source: 'detail-pand-selected', paint: { 'line-color': '#004494', 'line-width': 2 }, }); map.on('click', 'detail-panden-fill', e => { const id = e.features?.[0]?.properties?.identificatie; if (!id || id === dataRef.current?.pand_id) return; toonPandPopup(map, e.lngLat, id); }); map.on('mouseenter', 'detail-panden-fill', () => { map.getCanvas().style.cursor = 'pointer'; }); map.on('mouseleave', 'detail-panden-fill', () => { map.getCanvas().style.cursor = ''; }); await laadPandenRondom(map); await laadResultaten(map); RESULTAAT_LAGEN.forEach(laag => { map.on('click', laag.fillId, e => { const props = e.features?.[0]?.properties || {}; toonResultaatPopup(map, e.lngLat, laag, props); }); map.on('mouseenter', laag.fillId, () => { map.getCanvas().style.cursor = 'pointer'; }); map.on('mouseleave', laag.fillId, () => { map.getCanvas().style.cursor = ''; }); }); map.fitBounds(bounds, { padding: 80, animate: false, maxZoom: 19 }); }); map.on('moveend', () => { laadPandenRondom(map); }); function fitPand() { const b = boundsVoorGeom(dataRef.current?.geom_wgs84); if (b) map.fitBounds(b, { padding: 80, animate: true, maxZoom: 19 }); } mapInstanceRef.current = map; window.addEventListener('bd-detail-map-fit-pand', fitPand); return () => { window.removeEventListener('bd-detail-map-fit-pand', fitPand); map.remove(); mapInstanceRef.current = null; }; }, [data?.pand_id]); React.useEffect(() => { const map = mapInstanceRef.current; if (!map || !map.isStyleLoaded()) return; setMapLayerVisibility(map, ['detail-achtergrond'], laagInstellingen.achtergrond.aan); setMapLayerVisibility(map, ['detail-panden-fill', 'detail-panden-outline'], laagInstellingen.panden.aan); RESULTAAT_LAGEN.forEach(laag => setMapLayerVisibility(map, [laag.fillId, laag.lineId], laagInstellingen[laag.id]?.aan)); try { map.setPaintProperty('detail-panden-fill', 'fill-opacity', laagInstellingen.panden.opacity * 0.28); map.setPaintProperty('detail-panden-outline', 'line-opacity', laagInstellingen.panden.opacity); RESULTAAT_LAGEN.forEach(laag => { map.setPaintProperty(laag.fillId, 'fill-opacity', (laagInstellingen[laag.id]?.opacity || 1) * 0.4); map.setPaintProperty(laag.lineId, 'line-opacity', laagInstellingen[laag.id]?.opacity || 1); }); } catch (e) {} }, [laagInstellingen]); // Standaard-achtergrond kiezen zodra kaartmateriaal geladen is (zoals Kaart-pagina) React.useEffect(() => { if (kaartmateriaal.length > 0 && achtergrondId === null) { const achter = kaartmateriaal.filter(k => k.voor_kaart); const standaard = achter.find(k => (k.naam || '').toLowerCase().includes('luchtfoto')) || achter.find(k => k.type === 'wmts') || achter[0]; if (standaard) setAchtergrondId(standaard.id); } }, [kaartmateriaal]); // Achtergrondlaag wisselen bij selectie React.useEffect(() => { const map = mapInstanceRef.current; if (!map || achtergrondId === null || !map.isStyleLoaded()) return; const km = kaartmateriaal.find(k => k.id === achtergrondId); const tileUrl = tileUrlVoorKaartmateriaal(km); if (!tileUrl || !map.getSource('detail-achtergrond')) return; if (map.getLayer('detail-achtergrond')) map.removeLayer('detail-achtergrond'); map.removeSource('detail-achtergrond'); map.addSource('detail-achtergrond', { type: 'raster', tiles: [tileUrl], tileSize: 256 }); const onderlaag = map.getLayer('detail-panden-fill') ? 'detail-panden-fill' : undefined; map.addLayer({ id: 'detail-achtergrond', type: 'raster', source: 'detail-achtergrond' }, onderlaag); setMapLayerVisibility(map, ['detail-achtergrond'], laagInstellingenRef.current.achtergrond.aan); }, [achtergrondId, kaartmateriaal]); // "Niet aanwezig"-detecties tonen/verbergen (filter op zekerheid-resultaatlagen) React.useEffect(() => { const map = mapInstanceRef.current; if (!map || !map.isStyleLoaded()) return; RESULTAAT_LAGEN.forEach(laag => { [laag.fillId, laag.lineId].forEach(lid => { if (!map.getLayer(lid)) return; try { map.setFilter(lid, nietAanwezigZichtbaar ? null : ['==', ['get', 'aanwezig'], true]); } catch (e) {} }); }); }, [nietAanwezigZichtbaar]); return (
{lagenOpen && (
Kaartlagen
{/* Achtergrondlagen uit kaartmateriaal (Databeheer) */} {kaartmateriaal.filter(k => k.voor_kaart).length > 0 && (
Achtergrond
{kaartmateriaal.filter(k => k.voor_kaart).map(k => (
setAchtergrondId(k.id)}> {achtergrondId === k.id && } {k.naam}{k.jaar ? ` (${k.jaar})` : ''}
))}
)} {/* Lagen — sleep om volgorde te wijzigen (boven = voorgrond) */} {lagenVolgorde.map(id => { const laag = DETAIL_LAGEN_CONFIG.find(l => l.id === id); if (!laag) return null; return ( ); })} {/* Zekerheidslegenda — identiek aan Kaart-pagina */} {heeftDetectieAan && (
Zekerheid detectie
Aanwezig
{[{ k: '#1a9e4a', l: 'Hoog' }, { k: '#f1c40f', l: 'Midden' }, { k: '#e67e22', l: 'Laag' }].map(({ k, l }) => (
{l}
))}
Niet aanwezig
{[{ k: '#c0392b', l: 'Hoog' }, { k: '#e74c3c', l: 'Midden' }, { k: '#f1948a', l: 'Laag' }].map(({ k, l }) => (
{l}
))}
)}
)}
); } function DataDetailScherm({ pandId, onTerug, kaartmateriaal = [], onOpenPand, onNaarReview }) { const [data, setData] = React.useState(null); const [laden, setLaden] = React.useState(true); const [fout, setFout] = React.useState(null); const [vboOpen, setVboOpen] = React.useState(false); const [afbeeldingModal, setAfbeeldingModal] = React.useState(null); // resultaat_id // Sleepbaar paneel const PANEL_MIN = 260; const PANEL_MAX = 620; const [panelWidth, setPanelWidth] = React.useState(() => { const saved = parseInt(localStorage.getItem('bd-data-panel-width')); return (saved >= PANEL_MIN && saved <= PANEL_MAX) ? saved : 380; }); const panelWidthRef = React.useRef(panelWidth); panelWidthRef.current = panelWidth; const draggingRef = React.useRef(false); const startXRef = React.useRef(0); const startWRef = React.useRef(0); function onDragStart(e) { draggingRef.current = true; startXRef.current = e.clientX; startWRef.current = panelWidthRef.current; document.body.style.userSelect = 'none'; document.body.style.cursor = 'col-resize'; function onMove(e) { if (!draggingRef.current) return; const delta = e.clientX - startXRef.current; const newW = Math.max(PANEL_MIN, Math.min(PANEL_MAX, startWRef.current + delta)); setPanelWidth(newW); } function onUp() { draggingRef.current = false; document.body.style.userSelect = ''; document.body.style.cursor = ''; localStorage.setItem('bd-data-panel-width', panelWidthRef.current); document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); } document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp); } React.useEffect(() => { setLaden(true); setFout(null); Promise.all([ fetch(`${API}/data/pand/${encodeURIComponent(pandId)}`) .then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); }) , fetch(`${API}/perceel-objecten?gekoppeld_pand_id=${encodeURIComponent(pandId)}`) .then(r => r.ok ? r.json() : []) .catch(() => []), ]) .then(([pandData, perceelObjecten]) => setData({...pandData, perceel_objecten: Array.isArray(perceelObjecten) ? perceelObjecten : []})) .catch(e => setFout(e.message)) .finally(() => setLaden(false)); }, [pandId]); async function verwijderResultaat(res) { if (!confirm(`Detectieresultaat #${res.id} verwijderen? Bijbehorende vlakken, review en afbeelding gaan ook weg.`)) return; const r = await fetch(`${API}/resultaten/${res.id}`, { method: 'DELETE' }); if (r.ok) { const vernieuwd = await fetch(`${API}/data/pand/${encodeURIComponent(pandId)}`).then(r => r.json()); setData(prev => ({...vernieuwd, perceel_objecten: prev?.perceel_objecten || []})); } else { alert('Verwijderen mislukt.'); } } if (laden) return (
Pandgegevens laden...
); if (fout) return (
Fout bij ophalen pandgegevens: {fout}
); if (!data) return null; const heeftResultaten = data.resultaten && data.resultaten.length > 0; const heeftPerceelObjecten = data.perceel_objecten && data.perceel_objecten.length > 0; return (
{/* AFBEELDING MODAL */} {afbeeldingModal !== null && (
setAfbeeldingModal(null)} style={{ position: 'fixed', inset: 0, zIndex: 1000, background: 'rgba(0,0,0,0.75)', display: 'flex', alignItems: 'center', justifyContent: 'center', }} >
e.stopPropagation()} style={{ background: 'var(--surface-card)', borderRadius: 'var(--radius-md)', padding: 'var(--space-4)', maxWidth: '90vw', maxHeight: '90vh', display: 'flex', flexDirection: 'column', gap: 'var(--space-3)', boxShadow: '0 8px 32px rgba(0,0,0,0.4)', }} >
Pipeline-afbeelding
)} {/* LEFT PANEL */}
{/* Panel header */}
Pand
{data.pand_id}
{data.rd_x != null && data.rd_y != null && (
)} {data.status || 'Onbekend'}
{/* Scrollable content */}
{/* Sectie: Pandgegevens */}
Pandgegevens
{data.perceel_id && }
{/* Sectie: VBO's */} {data.vbos && data.vbos.length > 0 && (
{vboOpen && (
{data.vbos.map(v => (
{v.vbo_id}
{v.adres &&
{v.adres}
} {v.postcode &&
{v.postcode} {v.woonplaats}
} {v.gebruiksdoel &&
{v.gebruiksdoel}
}
))}
)}
)} {/* Sectie: Perceel-objecten */}
Perceel-objecten {heeftPerceelObjecten && {data.perceel_objecten.length}}
{!heeftPerceelObjecten ? (
Geen gekoppelde perceel-objecten voor dit pand.
) : (
{data.perceel_objecten.map(obj => (
{obj.object_type || 'object'} {CONTOUR_SUBTYPE_LABEL[obj.subtype] || obj.subtype || 'Onbekend'} {obj.zekerheid || '-'}
Perceel: {obj.perceel_id || '-'} Pand: {obj.gekoppeld_pand_id || '-'} Oppervlak: {obj.oppervlakte_m2 != null ? `${Number(obj.oppervlakte_m2).toFixed(1)} m2` : '-'} Afstand: {obj.afstand_tot_pand_m != null ? `${Number(obj.afstand_tot_pand_m).toFixed(1)} m` : '-'}
))}
)}
{/* Sectie: Detectieresultaten */}
Detectieresultaten {heeftResultaten && {data.resultaten.length}}
{!heeftResultaten ? (
Geen detectieresultaten voor dit pand.
) : (
{data.resultaten.map(res => { const analyseStatus = res.analyse_status || 'ok'; const aanwezigKind = res.aanwezig === true ? 'success' : res.aanwezig === false ? 'error' : analyseStatus !== 'ok' ? 'warning' : 'neutral'; return (
{/* Result header */}
{DETECTIE_LABEL[res.detectie_type] || res.detectie_type} {res.aanwezig === true ? 'Aanwezig' : res.aanwezig === false ? 'Niet aanwezig' : 'Onbekend'} {analyseStatus !== 'ok' && {res.fout_code || analyseStatus}}
{/* Zekerheid */} {res.zekerheid && (
)} {/* Toelichting */} {res.toelichting && (
{res.toelichting}
)} {/* Meta */}
{res.run_datum && {fmtDate(res.run_datum)}} {res.ai_model && {res.ai_model}} {res.prompt_naam && {res.prompt_naam}} {res.finish_reason && finish: {res.finish_reason}} {res.brondocument && {res.brondocument}}
{/* Afbeelding knop */}
{/* Ruwe AI-tekst (uitklapbaar) */} {res.ai_ruw_tekst && (
Ruwe AI-tekst
{res.ai_ruw_tekst}
)} {/* Acties */}
{res.beoordeling && ( {res.beoordeling} )}
); })}
)}
{/* DRAG HANDLE */}
{/* RIGHT PANEL: MAP */}
Kaart {data.pand_id}
Legenda
Geselecteerd pand
Zekerheid:
Hoog
Midden
Laag
PDOK BRT / Kadastrale kaart v5
); } /* ================================================================ ROOT COMPONENT ================================================================ */ function SchermData({ kaartmateriaal, pandId, onPandChange, onNaarReview }) { if (pandId) { return onPandChange(null)} />; } return ; } Object.assign(window, { SchermData, DataDetailScherm, DataZoekScherm });