GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
245 lines
9.5 KiB
TypeScript
245 lines
9.5 KiB
TypeScript
import { useMemo, useState } from 'react'
|
|
import type {
|
|
AreaRead,
|
|
DetectionRead,
|
|
DetectionRunRead,
|
|
ExportCreateResponse,
|
|
ExportRead,
|
|
ProjectRead,
|
|
QualityCheckRead,
|
|
SegmentationRead,
|
|
SegmentationRunRead,
|
|
} from '../../types'
|
|
import { DatasetDetailPanel, type DatasetDetailPanelProps } from '../datasets/DatasetDetailPanel'
|
|
|
|
type InspectorTab = 'context' | 'dataset' | 'quality' | 'ai'
|
|
|
|
interface WorkbenchInspectorProps {
|
|
selectedProject: ProjectRead | null
|
|
selectedArea: AreaRead | null
|
|
selectedMapFeature: GeoJSON.Feature | null
|
|
areas: AreaRead[]
|
|
datasetsCount: number
|
|
qualityChecks: QualityCheckRead[]
|
|
exports: ExportRead[]
|
|
latestExport: ExportCreateResponse | null
|
|
detectionRuns: DetectionRunRead[]
|
|
selectedDetectionRunId: string
|
|
detectionItems: DetectionRead[]
|
|
segmentationRuns: SegmentationRunRead[]
|
|
selectedSegmentationRunId: string
|
|
segmentationItems: SegmentationRead[]
|
|
datasetDetailProps: DatasetDetailPanelProps
|
|
onOpenDataWorkspace: () => void
|
|
onOpenMapWorkspace: () => void
|
|
onOpenQualityWorkspace: () => void
|
|
onOpenExportsWorkspace: () => void
|
|
onOpenAiWorkspace: () => void
|
|
onClose: () => void
|
|
}
|
|
|
|
function formatValue(value: string | number | null | undefined): string {
|
|
if (value === null || value === undefined || value === '') {
|
|
return 'n.v.t.'
|
|
}
|
|
return String(value)
|
|
}
|
|
|
|
function InspectorField({ label, value }: { label: string; value: string | number | null | undefined }): JSX.Element {
|
|
return (
|
|
<div className="inspector-field">
|
|
<span>{label}</span>
|
|
<strong>{formatValue(value)}</strong>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function WorkbenchInspector({
|
|
selectedProject,
|
|
selectedArea,
|
|
selectedMapFeature,
|
|
areas,
|
|
datasetsCount,
|
|
qualityChecks,
|
|
exports,
|
|
latestExport,
|
|
detectionRuns,
|
|
selectedDetectionRunId,
|
|
detectionItems,
|
|
segmentationRuns,
|
|
selectedSegmentationRunId,
|
|
segmentationItems,
|
|
datasetDetailProps,
|
|
onOpenDataWorkspace,
|
|
onOpenMapWorkspace,
|
|
onOpenQualityWorkspace,
|
|
onOpenExportsWorkspace,
|
|
onOpenAiWorkspace,
|
|
onClose,
|
|
}: WorkbenchInspectorProps): JSX.Element {
|
|
const [activeTab, setActiveTab] = useState<InspectorTab>('context')
|
|
const selectedDetectionRun = useMemo(
|
|
() => detectionRuns.find((run) => run.id === selectedDetectionRunId) ?? null,
|
|
[detectionRuns, selectedDetectionRunId],
|
|
)
|
|
const selectedSegmentationRun = useMemo(
|
|
() => segmentationRuns.find((run) => run.id === selectedSegmentationRunId) ?? null,
|
|
[segmentationRuns, selectedSegmentationRunId],
|
|
)
|
|
const latestQualityCheck = qualityChecks[0] ?? null
|
|
const latestPersistedExport = exports[0] ?? null
|
|
const activeTabId = `inspector-tab-${activeTab}`
|
|
const activePanelId = `inspector-panel-${activeTab}`
|
|
|
|
const tabs: Array<{ key: InspectorTab; label: string }> = [
|
|
{ key: 'context', label: 'Context' },
|
|
{ key: 'dataset', label: 'Databron' },
|
|
{ key: 'quality', label: 'Kwaliteit en downloads' },
|
|
{ key: 'ai', label: 'Beeldanalyse' },
|
|
]
|
|
|
|
return (
|
|
<section className="workbench-inspector-panel" data-testid="workbench-inspector-panel">
|
|
<div className="inspector-header">
|
|
<div>
|
|
<p className="eyebrow">Context</p>
|
|
<h2>Details van de selectie</h2>
|
|
</div>
|
|
<button type="button" className="inspector-close" onClick={onClose} aria-label="Sluit details van de selectie">
|
|
Sluiten
|
|
</button>
|
|
</div>
|
|
<div className="inspector-tabs" role="tablist" aria-label="Onderdelen van de selectie">
|
|
{tabs.map((tab) => (
|
|
<button
|
|
key={tab.key}
|
|
type="button"
|
|
className={activeTab === tab.key ? 'inspector-tab inspector-tab-active' : 'inspector-tab'}
|
|
onClick={() => setActiveTab(tab.key)}
|
|
role="tab"
|
|
aria-selected={activeTab === tab.key}
|
|
aria-controls={`inspector-panel-${tab.key}`}
|
|
id={`inspector-tab-${tab.key}`}
|
|
data-testid={`inspector-tab-${tab.key}`}
|
|
>
|
|
{tab.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{activeTab === 'context' ? (
|
|
<div className="inspector-tab-panel" role="tabpanel" id={activePanelId} aria-labelledby={activeTabId}>
|
|
<div className="inspector-card">
|
|
<h3>Werkruimte</h3>
|
|
<InspectorField label="Naam" value={selectedProject?.name} />
|
|
<InspectorField label="Regio" value={selectedProject?.region} />
|
|
<InspectorField label="Status" value={selectedProject?.status} />
|
|
<InspectorField label="Gebieden" value={areas.length} />
|
|
<InspectorField label="Databronnen" value={datasetsCount} />
|
|
<div className="button-row">
|
|
<button type="button" className="secondary-action" onClick={onOpenDataWorkspace}>
|
|
Gegevens beheren
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="inspector-card">
|
|
<h3>Actief werkgebied</h3>
|
|
<InspectorField label="Naam" value={selectedArea?.name} />
|
|
<InspectorField label="Oppervlakte m²" value={selectedArea?.area_m2} />
|
|
<InspectorField label="Coördinatenstelsel" value={selectedArea?.original_crs} />
|
|
<InspectorField label="Geometrietype" value={selectedArea?.geometry?.type} />
|
|
<div className="button-row">
|
|
<button type="button" className="secondary-action" onClick={onOpenMapWorkspace}>
|
|
Kaart openen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="inspector-card">
|
|
<h3>Geselecteerd kaartobject</h3>
|
|
{selectedMapFeature ? (
|
|
<pre className="job-result">{JSON.stringify(selectedMapFeature.properties ?? {}, null, 2)}</pre>
|
|
) : (
|
|
<p className="muted">Er is geen kaartobject geselecteerd.</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
|
|
{activeTab === 'dataset' ? (
|
|
<div className="inspector-tab-panel" role="tabpanel" id={activePanelId} aria-labelledby={activeTabId}>
|
|
<div className="inspector-action-bar">
|
|
<button type="button" className="secondary-action" onClick={onOpenDataWorkspace}>
|
|
Gegevenscatalogus
|
|
</button>
|
|
<button type="button" className="secondary-action" onClick={onOpenMapWorkspace}>
|
|
Kaartlaag
|
|
</button>
|
|
<button type="button" className="secondary-action" onClick={onOpenExportsWorkspace}>
|
|
Download
|
|
</button>
|
|
</div>
|
|
<DatasetDetailPanel {...datasetDetailProps} />
|
|
</div>
|
|
) : null}
|
|
|
|
{activeTab === 'quality' ? (
|
|
<div className="inspector-tab-panel" role="tabpanel" id={activePanelId} aria-labelledby={activeTabId}>
|
|
<div className="inspector-card">
|
|
<h3>Laatste kwaliteitscontrole</h3>
|
|
<InspectorField label="Type controle" value={latestQualityCheck?.check_type?.replaceAll('_', ' ')} />
|
|
<InspectorField label="Status" value={latestQualityCheck?.status} />
|
|
<InspectorField label="Score" value={latestQualityCheck?.score} />
|
|
<InspectorField label="Meetwaarden" value={latestQualityCheck?.metrics?.length} />
|
|
<div className="button-row">
|
|
<button type="button" className="secondary-action" onClick={onOpenQualityWorkspace}>
|
|
Kwaliteit openen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="inspector-card">
|
|
<h3>Laatste download</h3>
|
|
<InspectorField label="Zojuist aangemaakt" value={latestExport?.export_type} />
|
|
<InspectorField label="Bewaard type" value={latestPersistedExport?.export_type} />
|
|
<InspectorField label="Status" value={latestPersistedExport?.status ?? latestExport?.status} />
|
|
<InspectorField label="Aantal downloads" value={exports.length} />
|
|
<div className="button-row">
|
|
<button type="button" className="secondary-action" onClick={onOpenExportsWorkspace}>
|
|
Downloads openen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
|
|
{activeTab === 'ai' ? (
|
|
<div className="inspector-tab-panel" role="tabpanel" id={activePanelId} aria-labelledby={activeTabId}>
|
|
<div className="inspector-card">
|
|
<h3>Gebouwdetectie</h3>
|
|
<InspectorField label="Status" value={selectedDetectionRun?.status} />
|
|
<InspectorField label="Model" value={selectedDetectionRun?.model_name} />
|
|
<InspectorField label="Gevonden objecten" value={detectionItems.length} />
|
|
<div className="button-row">
|
|
<button type="button" className="secondary-action" onClick={onOpenAiWorkspace}>
|
|
Beeldanalyse openen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="inspector-card">
|
|
<h3>Segmentatie</h3>
|
|
<InspectorField label="Status" value={selectedSegmentationRun?.status} />
|
|
<InspectorField label="Model" value={selectedSegmentationRun?.model_name} />
|
|
<InspectorField label="Herkende vlakken" value={segmentationItems.length} />
|
|
<details className="technical-inline-details">
|
|
<summary>Technische analyseruns</summary>
|
|
<div className="entity-meta">
|
|
<span>Detectierun-ID: {selectedDetectionRunId || 'n.v.t.'}</span>
|
|
<span>Segmentatierun-ID: {selectedSegmentationRunId || 'n.v.t.'}</span>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</section>
|
|
)
|
|
}
|