/* Resultaten screen */ function ReviewTooltip({ r }) { const [zichtbaar, setZichtbaar] = React.useState(false); const ref = React.useRef(null); const [links, setLinks] = React.useState(false); function toonTooltip() { if (ref.current) { const rect = ref.current.getBoundingClientRect(); setLinks(rect.right > window.innerWidth - 260); } setZichtbaar(true); } if (!r.heeft_review) return ; const ikoon = r.beoordeling === 'correct' ? '✅' : r.beoordeling === 'onzeker' ? '❓' : '❌'; const kleur = r.beoordeling === 'correct' ? 'var(--color-success)' : r.beoordeling === 'onzeker' ? 'var(--color-warning)' : 'var(--color-danger)'; const label = r.beoordeling === 'correct' ? 'Correct' : r.beoordeling === 'onzeker' ? 'Onzeker' : 'Incorrect'; return (
setZichtbaar(false)}> {ikoon} {zichtbaar && (
Medewerkersbeoordeling
Uitkomst {ikoon} {label}
Luchtfoto {r.brondocument || '—'}
AI-model {r.ai_model || '—'}
Pipeline datum {fmtDateTime(r.geanalyseerd_op)}
Beoordeeld op {fmtDateTime(r.beoordeeld_op)}
)}
); } function AanwezigBadge({ r }) { if (r.aanwezig === true) return Ja; if (r.aanwezig === false) return Nee; return Onbekend; } function AnalyseStatusBadge({ r }) { const status = r.analyse_status || 'ok'; if (status === 'ok') return ok; const label = r.fout_code || status; return {label}; } function SchermResultaten({ gemeenten, defaultGemeente, onNaarPand }) { const [gemeente, setGemeente] = useState(() => defaultGemeente || ''); const [detectieType, setDetectieType] = useState('contour'); const [resultaten, setResultaten] = useState([]); const [stats, setStats] = useState(null); const [laden, setLaden] = useState(false); // Kolomfilters (client-side) const [fPandId, setFPandId] = useState(''); const [fTaak, setFTaak] = useState(''); const [fAanwezig, setFAanwezig] = useState('alle'); const [fZekerheid, setFZekerheid] = useState('alle'); const [fToelichting, setFToelichting] = useState(''); const [fLuchtfoto, setFLuchtfoto] = useState('alle'); const [fModel, setFModel] = useState('alle'); const [fDatumVan, setFDatumVan] = useState(''); const [fDatumTot, setFDatumTot] = useState(''); const [fReview, setFReview] = useState('alle'); const eersteGemeente = gemeenten.find(g => g.status === 'klaar'); const actieveGemeente = gemeente || defaultGemeente || (eersteGemeente ? eersteGemeente.code : ''); const isPerceelObjecten = detectieType === 'perceel_objecten'; useEffect(() => { if (!gemeente && actieveGemeente) setGemeente(actieveGemeente); }, [gemeente, actieveGemeente]); async function laad() { if (!actieveGemeente) return; setLaden(true); if (isPerceelObjecten) { const params = new URLSearchParams({ gemeente_code: actieveGemeente }); const objecten = await fetch(`${API}/perceel-objecten?${params}`).then(r => r.json()).catch(() => []); const lijst = Array.isArray(objecten) ? objecten : []; setResultaten(lijst); setStats({ totaal: lijst.length, aanwezig: lijst.filter(o => o.object_type === 'contourafwijking').length, hoog: lijst.filter(o => o.object_type === 'zonnepanelen').length, laag: lijst.filter(o => o.object_type === 'natuurlijk').length, }); } else { const params = new URLSearchParams({ gemeente_code: actieveGemeente, detectie_type: detectieType }); const rijen = await fetch(`${API}/resultaten?${params}`).then(r => r.json()).catch(() => []); setResultaten(Array.isArray(rijen) ? rijen : []); const statsLijst = await fetch(`${API}/resultaten/stats?gemeente_code=${actieveGemeente}`).then(r => r.json()).catch(() => []); const s = Array.isArray(statsLijst) ? statsLijst.find(x => x.detectie_type === detectieType) : null; setStats(s ? { totaal: s.totaal, aanwezig: s.aanwezig_count, hoog: s.hoog_count, laag: s.laag_count } : null); } setLaden(false); } useEffect(() => { laad(); }, [actieveGemeente, detectieType]); async function verwijderResultaat(r) { if (!confirm(`Detectieresultaat #${r.id} (pand ${r.pand_identificatie}) verwijderen? Bijbehorende vlakken, review en afbeelding gaan ook weg.`)) return; const resp = await fetch(`${API}/resultaten/${r.id}`, { method: 'DELETE' }); if (resp.ok) laad(); else alert('Verwijderen mislukt.'); } const exportCSV = async () => { if (!actieveGemeente || isPerceelObjecten) return; window.location = `${API}/resultaten/export?gemeente_code=${actieveGemeente}&detectie_type=${detectieType}`; }; function resetFilters() { setFPandId(''); setFTaak(''); setFAanwezig('alle'); setFZekerheid('alle'); setFToelichting(''); setFLuchtfoto('alle'); setFModel('alle'); setFDatumVan(''); setFDatumTot(''); setFReview('alle'); } const uniekeModellen = [...new Set(resultaten.map(r => r.ai_model).filter(Boolean))].sort(); const uniekeLuchtfotos = [...new Set(resultaten.map(r => r.kaartmateriaal_naam).filter(Boolean))].sort(); const actieveFilters = fPandId || fTaak || fAanwezig !== 'alle' || fZekerheid !== 'alle' || fToelichting || fLuchtfoto !== 'alle' || fModel !== 'alle' || fDatumVan || fDatumTot || fReview !== 'alle'; const gefilterd = resultaten.filter(r => { const idVeld = isPerceelObjecten ? `${r.gekoppeld_pand_id || ''} ${r.perceel_id || ''}` : (r.pand_identificatie || ''); if (fPandId && !idVeld.toLowerCase().includes(fPandId.toLowerCase())) return false; if (fTaak && !String(r.taak_id || '').includes(fTaak.replace(/^#/, ''))) return false; if (isPerceelObjecten && fAanwezig === 'ja' && r.object_type !== 'contourafwijking') return false; if (isPerceelObjecten && fAanwezig === 'nee' && r.object_type === 'contourafwijking') return false; if (!isPerceelObjecten && fAanwezig === 'ja' && r.aanwezig !== true) return false; if (!isPerceelObjecten && fAanwezig === 'nee' && r.aanwezig !== false) return false; if (fZekerheid !== 'alle' && r.zekerheid !== fZekerheid) return false; if (fToelichting && !r.toelichting?.toLowerCase().includes(fToelichting.toLowerCase())) return false; if (fLuchtfoto !== 'alle' && r.kaartmateriaal_naam !== fLuchtfoto) return false; if (fModel !== 'alle' && r.ai_model !== fModel) return false; if (fDatumVan && r.geanalyseerd_op && r.geanalyseerd_op < fDatumVan) return false; if (fDatumTot && r.geanalyseerd_op && r.geanalyseerd_op.slice(0, 10) > fDatumTot) return false; if (fReview === 'geen' && r.heeft_review) return false; if (fReview !== 'alle' && fReview !== 'geen' && r.beoordeling !== fReview) return false; return true; }); const thFilterStyle = { padding: '4px 8px', background: 'var(--color-primary-light)', borderBottom: '2px solid var(--color-primary)', }; const filterInputStyle = { width: '100%', padding: '3px 6px', fontSize: 'var(--font-size-xs)', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'var(--surface-card)', color: 'var(--text-body)', boxSizing: 'border-box', }; return (

Resultaten

Bekijk detectieresultaten per gemeente en type.

{actieveFilters && Filters wissen} Export CSV
{['contour','zonnepanelen','dakkapel'].map(t => ( ))}
{stats && (
)}

{isPerceelObjecten ? 'Perceel-objecten' : 'Detectieresultaten'}

{actieveFilters ? `${gefilterd.length} van ${resultaten.length} resultaten` : `${resultaten.length} resultaten`}
{!actieveGemeente ? ( ) : laden ? ( ) : resultaten.length === 0 ? ( ) : (
{isPerceelObjecten && ( {gefilterd.length === 0 ? ( ) : gefilterd.slice(0, 500).map((r, i) => ( ))}
Perceel Gekoppeld pand Type Subtype Zekerheid Oppervlak Hoogte Afstand pand Review Aangemaakt
setFPandId(e.target.value)} />
Geen perceel-objecten met huidige filters.
{r.perceel_id || '-'} {r.gekoppeld_pand_id && onNaarPand ? : (r.gekoppeld_pand_id || '-')} {r.object_type || '-'} {r.subtype || '-'} {r.zekerheid || '-'} {r.oppervlakte_m2 != null ? `${Number(r.oppervlakte_m2).toFixed(1)} m2` : '-'} {r.hoogte_m != null ? `${Number(r.hoogte_m).toFixed(1)} m` : '-'} {r.afstand_tot_pand_m != null ? `${Number(r.afstand_tot_pand_m).toFixed(1)} m` : '-'} {r.review_status || 'nieuw'} {fmtDateTime(r.aangemaakt_op)}
)} {!isPerceelObjecten && ( {gefilterd.length === 0 ? ( ) : gefilterd.slice(0, 500).map((r, i) => ( ))}
Taak Pand-id Aanwezig Status Zekerheid Toelichting Luchtfoto Model Prompt Datum analyse Rev.
setFTaak(e.target.value)} /> setFPandId(e.target.value)} /> setFToelichting(e.target.value)} />
setFDatumVan(e.target.value)} /> setFDatumTot(e.target.value)} />
Geen resultaten met de huidige filters.
{r.taak_id ? `#${r.taak_id}` : '—'} {onNaarPand ? : {r.pand_identificatie}} {r.zekerheid || '—'} {Array.isArray(r.cluster_subtypes) && r.cluster_subtypes.length > 0 && (
{r.cluster_subtypes.map((cs, ci) => ( {(CONTOUR_SUBTYPE_LABEL[cs.subtype] || cs.subtype)}{cs.n > 1 ? ` ×${cs.n}` : ''} ))}
)} {r.toelichting}
{r.kaartmateriaal_naam ? 🛰 {r.kaartmateriaal_naam} : }
{r.ai_model || '—'}
{r.finish_reason &&
finish: {r.finish_reason}
}
{r.prompt_naam || (r.prompt_id ? `#${r.prompt_id}` : '—')} {fmtDateTime(r.geanalyseerd_op)}
)}
)}
); } function StatCard({ label, value, hint, icon, kind = 'neutral', info }) { return (
{label} {info && }
{value}
{hint &&
{hint}
}
); } window.SchermResultaten = SchermResultaten;