/* ============================================================ Report panel + assessor cards + score + detail drawer ============================================================ */ const StatusIcon = ({ status, size }) => { const s = PC_STATUS[status] || PC_STATUS.unknown; return {s.glyph}; }; const StatusPill = ({ status }) => { const s = PC_STATUS[status] || PC_STATUS.unknown; return {s.glyph}{s.label}; }; const ConfBadge = ({ level }) => { if (!level) return null; const n = PC_CONF_LEVEL[level] || 0; return ( {[0, 1, 2].map(i => )} {level} ); }; function verdictFor(score) { if (score == null) return 'Awaiting assessment'; if (score >= 80) return 'Strong prospect'; if (score >= 65) return 'Promising'; if (score >= 50) return 'Marginal'; return 'Weak prospect'; } const ScoreDial = ({ score, status }) => { const r = 32, c = 2 * Math.PI * r; const pct = score == null ? 0 : score / 100; const col = { green: 'var(--st-green)', amber: 'var(--st-amber)', red: 'var(--st-red)', unknown: 'var(--st-unknown)' }[status] || 'var(--st-unknown)'; return (
{score == null ? '–' : score} / 100
); }; const ValueRows = ({ values }) => { const entries = Object.entries(values || {}); if (!entries.length) return null; return (
{entries.map(([k, v]) => (
{k}{v}
))}
); }; const AssessorCard = ({ result, onOpen, clickable }) => { const meta = PC_ASSESSOR_META[result.assessor_key] || { name: result.assessor_key }; const isUnknown = result.status === 'unknown'; return (
onOpen(result) : undefined}>
{meta.name}
{result.headline}
{clickable && !isUnknown && {PCI.chevron}}
{!isUnknown && } {isUnknown ? (
{PCI.clock}Not yet implemented — this check will fill in automatically once the assessor is online.
) : (
{result.source} {result.source_retrieved_at && · retrieved {result.source_retrieved_at}}
)}
); }; const ReportSection = ({ section, results, onOpen, clickable, headerAction }) => { const inSection = section.keys .map(k => results.find(r => r.assessor_key === k)) .filter(Boolean); if (!inSection.length) return null; const pending = inSection.filter(r => r.status === 'unknown').length; return (

{section.label}

{pending ? `${pending} pending` : `${inSection.length} checked`} {headerAction && {headerAction}}
{inSection.map(r => )}
); }; /** Compact on/off pill — used by section headers (e.g. "Show on map"). */ const SectionToggle = ({ on, disabled, onChange, label }) => ( ); const EmptyPanel = () => (

No site selected

Draw a boundary or search an address to run a fast-screen assessment. Or pick a site from the pipeline.

); const PC_LOADING_STEPS = [ 'Matching boundary to LINZ parcels', 'Resolving land tenure & titles', 'Querying grid proximity', 'Modelling solar resource', 'Reading terrain & slope', 'Checking planning constraints', ]; /* Inline toast. Accepts a string (simple notification) or an object { msg, kind, actions: [{label, primary, onClick}] } for action toasts (e.g. Save / Discard after a boundary edit). */ const Toast = ({ msg }) => { if (!msg) return null; const t = typeof msg === 'string' ? { msg } : msg; return (
{t.msg} {(t.actions || []).map((a, i) => ( ))}
); }; const ErrorPanel = ({ error, onClose }) => (

Assessment failed

{error || 'The backend could not complete the assessment.'}

Check that Start-Prospecting.ps1 launched the backend on port 8766, then try again.

); const LoadingPanel = ({ step }) => (

Running assessment

Fast-screening the boundary against every assessor.

{PC_LOADING_STEPS.map((label, i) => (
{i < step ? {PCI.check} : i === step ? : } {label}
))}
); const ReportPanel = ({ site, onClose, onExport, onOpenAssessor, clickable, gridSectionToggle }) => { const pending = site.assessors.filter(a => a.status === 'unknown').length; const total = site.assessors.length; return ( <>
Site report · {site.id}

{site.name}

{site.address}
{site.area_ha} ha · {site.shape_descriptor}
Fast-screen score
{verdictFor(site.overall_score)}
{pending} of {total} assessors pending — score reflects geometry & tenure only.
{PC_SECTIONS.map(sec => { const headerAction = sec.id === 'grid' && gridSectionToggle ? ( ) : null; return ( ); })}
); }; const DetailDrawer = ({ assessor, open, onClose, width }) => { const a = assessor; const meta = a ? (PC_ASSESSOR_META[a.assessor_key] || { name: a.assessor_key }) : {}; return ( <>
); }; Object.assign(window, { StatusIcon, StatusPill, ConfBadge, ScoreDial, AssessorCard, ReportSection, SectionToggle, ReportPanel, LoadingPanel, EmptyPanel, ErrorPanel, DetailDrawer, Toast, verdictFor, PC_LOADING_STEPS, });