/* Shared primitives: Icon, Badge, Button, crumbs, helpers, constants */ const { useState, useEffect, useRef, useCallback } = React; const API = '/api'; /* Labels voor contour-subtypes (gedeeld door data-scherm, review-canvas, resultaten) */ const CONTOUR_SUBTYPE_LABEL = { serre: 'Serre', aanbouw: 'Aanbouw', schuur: 'Schuur', overkapping: 'Overkapping', boom: 'Boom', tuinafscheiding: 'Tuinafscheiding', vegetatie: 'Vegetatie', geen_bouwwerk: 'Geen bouwwerk', anders: 'Anders', niet_geclassificeerd: 'Niet geclassificeerd', }; /* ---------- Icon: thin wrapper around Lucide ---------- */ function Icon({ name, size, color, style, className, strokeWidth }) { const ref = React.useRef(null); React.useEffect(() => { if (ref.current && window.lucide) { ref.current.innerHTML = ''; const el = document.createElement('i'); el.setAttribute('data-lucide', name); ref.current.appendChild(el); try { window.lucide.createIcons({ nameAttr: 'data-lucide', icons: window.lucide.icons }); } catch(e) {} } }, [name]); const s = { display: 'inline-flex', width: size || 16, height: size || 16, color: color || 'currentColor', ...(style||{}) }; return ; } /* ---------- Badge ---------- */ function Badge({ kind = 'neutral', icon, children, style }) { return ( {icon ? : } {children} ); } /* ---------- Button ---------- */ function Btn({ kind = 'secondary', size, icon, iconRight, children, className, ...rest }) { const cls = [ 'tc-btn', 'tc-btn--' + kind, size === 'sm' ? 'tc-btn--sm' : '', (icon && !children) ? 'tc-btn--icon-only' : '', className || '', ].filter(Boolean).join(' '); return ( ); } /* ---------- Alert ---------- */ function Alert({ kind = 'info', icon, children }) { const def = { error: 'alert-circle', success: 'check-circle-2', info: 'info', warning: 'alert-triangle' }; return (
{children}
); } /* ---------- Empty ---------- */ function Empty({ icon, title, hint }) { return (
{title}
{hint &&
{hint}
}
); } /* ---------- Field ---------- */ function Field({ label, hint, children, style }) { return (
{label && } {children} {hint &&
{hint}
}
); } /* ---------- Constants shared across screens ---------- */ const MODELLEN_DEFAULTS = { anthropic: ['claude-opus-4-7', 'claude-sonnet-4-6', 'claude-sonnet-4-5', 'claude-haiku-4-5'], openai: ['gpt-5.5', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-4o', 'gpt-4.1', 'gpt-4o-mini'], google: ['gemini-2.0-flash', 'gemini-2.5-pro'], bedrock: ['qwen.qwen3-vl-235b-a22b', 'qwen.qwen3-vl-235b-a22b-instruct-v1:0', 'anthropic.claude-3-5-sonnet-20241022-v2:0'], nebul: ['Qwen/Qwen3-VL-235B-A22B-Thinking', 'Qwen/Qwen3-VL-235B-A22B-Instruct'], }; let MODELLEN = { ...MODELLEN_DEFAULTS }; async function laadModellen() { try { const d = await fetch(`${API}/config/providers/models`).then(r => r.ok ? r.json() : null); if (d) { const merged = {}; for (const p of Object.keys(MODELLEN_DEFAULTS)) { merged[p] = (d[p] && d[p].length > 0) ? d[p] : MODELLEN_DEFAULTS[p]; } MODELLEN = merged; window.MODELLEN = MODELLEN; } } catch (e) {} } const DETECTIE_LABEL = { zonnepanelen: 'Zonnepanelen', dakkapel: 'Dakkapellen', contour: 'Contourafwijking', contour_perceel: 'Perceel-scan', }; const DETECTIE_ICON = { zonnepanelen: 'sun', dakkapel: 'home', contour: 'triangle-alert', contour_perceel: 'land-plot', }; const ZEKERHEID_KIND = { hoog: 'success', midden: 'warning', laag: 'error' }; function fmtDate(s) { if (!s) return '—'; try { return new Date(s).toLocaleDateString('nl-NL'); } catch(e) { return '—'; } } function fmtDateTime(s) { if (!s) return '—'; try { return new Date(s).toLocaleString('nl-NL'); } catch(e) { return '—'; } } function fmtNum(n) { if (n == null) return '—'; return Number(n).toLocaleString('nl-NL'); } Object.assign(window, { useState, useEffect, useRef, useCallback, API, Icon, Badge, Btn, Alert, Empty, Field, MODELLEN, laadModellen, DETECTIE_LABEL, DETECTIE_ICON, ZEKERHEID_KIND, fmtDate, fmtDateTime, fmtNum });