feat: simplify regional end user workbench
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-14 21:40:32 +02:00
parent 1693e4ca1f
commit b5bcd9b041
52 changed files with 1183 additions and 617 deletions
+100 -79
View File
@@ -30,6 +30,8 @@ import { useQualityWorkflow } from './hooks/useQualityWorkflow'
import { useSegmentationWorkflow } from './hooks/useSegmentationWorkflow'
import { useWorkbenchBootstrap } from './hooks/useWorkbenchBootstrap'
import { useViewportVectorLayer } from './hooks/useViewportVectorLayer'
import { getDatasetDisplayName } from './lib/datasetDisplay'
import { REGIONAL_WORKSPACE_LABEL, REGIONAL_WORKSPACE_PROJECT_NAME } from './config/primaryFocus'
function isVectorDatasetType(datasetType: string): boolean {
return datasetType === 'vector' || datasetType === 'geojson'
@@ -38,19 +40,20 @@ function isVectorDatasetType(datasetType: string): boolean {
type WorkspaceKey = 'overview' | 'data' | 'map' | 'analysis' | 'ai' | 'exports' | 'system'
const workspaceNavItems: Array<{ key: WorkspaceKey; label: string; description: string }> = [
{ key: 'overview', label: 'Overview', description: 'V1 readiness and next action' },
{ key: 'data', label: 'Data', description: 'Projects, AOI and datasets' },
{ key: 'map', label: 'Map', description: 'Layer review and inspection' },
{ key: 'analysis', label: 'QA/QC', description: 'Quality checks and change detection' },
{ key: 'ai', label: 'AI Labs', description: 'Detection and segmentation' },
{ key: 'exports', label: 'Exports', description: 'Artifacts and downloads' },
{ key: 'system', label: 'System', description: 'Provider capabilities' },
{ key: 'overview', label: 'Status', description: 'Beschikbaarheid en aandachtspunten' },
{ key: 'data', label: 'Bronnen', description: 'Gebieden en ingeladen gegevens' },
{ key: 'map', label: 'Kaart', description: 'Selecteren, uitlezen en vergelijken' },
{ key: 'analysis', label: 'Kwaliteit', description: 'Resultaten controleren' },
{ key: 'ai', label: 'Beeldanalyse', description: 'Gebouwen herkennen op luchtbeelden' },
{ key: 'exports', label: 'Downloads', description: 'Resultaten bewaren en delen' },
{ key: 'system', label: 'Beheer', description: 'Databronnen en systeemstatus' },
]
const workspaceNavGroups: Array<{ label: string; keys: WorkspaceKey[] }> = [
{ label: 'Workspace', keys: ['overview', 'data', 'map'] },
{ label: 'Analyze', keys: ['analysis', 'ai'] },
{ label: 'Deliver', keys: ['exports', 'system'] },
{ label: 'Verkennen', keys: ['map', 'data'] },
{ label: 'Analyseren', keys: ['analysis', 'ai'] },
{ label: 'Afronden', keys: ['exports'] },
{ label: 'Beheer', keys: ['overview', 'system'] },
]
function App(): JSX.Element {
@@ -659,43 +662,45 @@ function App(): JSX.Element {
target: 'exports',
},
]
const projectContextLabel = selectedProject?.name ?? 'No project'
const areaContextLabel = selectedArea?.name ?? (areas.length > 0 ? 'Select area' : 'No AOI')
const datasetContextLabel = selectedDataset?.name ?? (datasets.length > 0 ? 'Select dataset' : 'No dataset')
const projectContextLabel = selectedProject?.name === REGIONAL_WORKSPACE_PROJECT_NAME
? REGIONAL_WORKSPACE_LABEL
: selectedProject?.name ?? 'Geen werkruimte'
const areaContextLabel = selectedArea?.name ?? (areas.length > 0 ? 'Kies een gebied' : 'Geen gebied')
const datasetContextLabel = selectedDataset ? getDatasetDisplayName(selectedDataset) : datasets.length > 0 ? 'Kies een bron' : 'Geen bron'
const layerContextLabel = mapFeatureCollection
? `${mapFeatureCount} features`
: viewportVectorLayerActive
? 'Viewport layer selected'
: 'No active layer'
? 'Kaartlaag gekozen'
: 'Geen actieve laag'
return (
<div className="app-shell workbench-shell">
<a className="skip-link" href="#workspace-main">
Skip to workspace
Ga naar de werkruimte
</a>
<header className="workbench-topbar">
<div className="brand-block">
<span className="brand-mark" aria-hidden="true">GI</span>
<div>
<h1>GeoIntel</h1>
<p>GeoAI Workbench · Mol / Kempen</p>
<p>GeoAI-werkruimte · Mol en de Kempen</p>
</div>
</div>
<div className="context-bar" aria-label="Active workbench context">
<div>
<span>Project</span>
<span>Werkruimte</span>
<strong title={projectContextLabel}>{projectContextLabel}</strong>
</div>
<div>
<span>AOI</span>
<span>Gebied</span>
<strong title={areaContextLabel}>{areaContextLabel}</strong>
</div>
<div>
<span>Dataset</span>
<span>Bron</span>
<strong title={datasetContextLabel}>{datasetContextLabel}</strong>
</div>
<div>
<span>Layer</span>
<span>Kaartlaag</span>
<strong title={layerContextLabel}>{layerContextLabel}</strong>
</div>
</div>
@@ -770,7 +775,7 @@ function App(): JSX.Element {
</button>
))}
</div>
<span>{selectedProject ? `Active project: ${selectedProject.name}` : 'Start with a project or load the demo workflow.'}</span>
<span>{selectedProject ? `Actief bereik: ${projectContextLabel}` : 'De regionale gegevens worden ingeladen.'}</span>
</div>
{activeWorkspace === 'overview' ? (
@@ -784,6 +789,11 @@ function App(): JSX.Element {
activeLayerFeatureCount={mapFeatureCount}
selectedAreaHasGeometry={Boolean(areaFeatureCollection)}
/>
<details className="status-details-disclosure">
<summary>
<span>Volledige workflowstatus</span>
<strong>{workflowGuidanceComplete ? 'voltooid' : 'optionele stappen open'}</strong>
</summary>
<section className="workflow-guidance-panel" aria-label="V1 workflow guidance">
<div className="panel-title-row">
<div>
@@ -860,6 +870,7 @@ function App(): JSX.Element {
</button>
</div>
</section>
</details>
</div>
) : null}
@@ -958,7 +969,6 @@ function App(): JSX.Element {
selectedMapDatasetId={selectedDataset && isVectorDatasetType(selectedDataset.dataset_type) ? selectedDataset.id : ''}
selectedFeature={selectedMapFeature}
onSelectMapArea={setSelectedMapAreaId}
onSelectProject={selectProject}
onOpenDatasetInMap={openDatasetInMap}
onSetAreaLayerVisible={setAreaLayerVisible}
onSetAreaLayerOpacity={setAreaLayerOpacity}
@@ -981,22 +991,6 @@ function App(): JSX.Element {
{activeWorkspace === 'analysis' ? (
<div className="workspace-grid workspace-grid-analysis">
<ChangeDetectionPanel
vectorDatasets={availableVectorDatasets}
sourceDatasetId={changeSourceDatasetId}
targetDatasetId={changeTargetDatasetId}
iouThreshold={changeIouThreshold}
includeUnchanged={changeIncludeUnchanged}
running={runningChangeDetection}
result={changeDetectionResult}
error={changeDetectionError}
onSourceDatasetChange={setChangeSourceDatasetId}
onTargetDatasetChange={setChangeTargetDatasetId}
onIouThresholdChange={setChangeIouThreshold}
onIncludeUnchangedChange={setChangeIncludeUnchanged}
onRun={runChangeDetection}
/>
<QualityResultsPanel
selectedProjectId={selectedProjectId}
qualityChecks={qualityChecks}
@@ -1008,6 +1002,27 @@ function App(): JSX.Element {
evidenceLoading={qualityEvidenceLoading}
evidenceError={qualityEvidenceError}
/>
<details className="secondary-analysis-disclosure">
<summary>
<span>Historische vectorlagen vergelijken</span>
<strong>Geavanceerd</strong>
</summary>
<ChangeDetectionPanel
vectorDatasets={availableVectorDatasets}
sourceDatasetId={changeSourceDatasetId}
targetDatasetId={changeTargetDatasetId}
iouThreshold={changeIouThreshold}
includeUnchanged={changeIncludeUnchanged}
running={runningChangeDetection}
result={changeDetectionResult}
error={changeDetectionError}
onSourceDatasetChange={setChangeSourceDatasetId}
onTargetDatasetChange={setChangeTargetDatasetId}
onIouThresholdChange={setChangeIouThreshold}
onIncludeUnchangedChange={setChangeIncludeUnchanged}
onRun={runChangeDetection}
/>
</details>
</div>
) : null}
@@ -1069,46 +1084,52 @@ function App(): JSX.Element {
onApplyOperatorProfile={applyDetectionOperatorProfile}
/>
<SegmentationLab
segmentationModels={segmentationModels}
loadingSegmentationModels={loadingSegmentationModels}
segmentationModelError={segmentationModelError}
selectedSegmentationDatasetId={selectedSegmentationDatasetId}
selectedSegmentationModelId={selectedSegmentationModelId}
segmentationTileManifestPath={segmentationTileManifestPath}
segmentationConfidenceThreshold={segmentationConfidenceThreshold}
runningSegmentation={runningSegmentation}
segmentationRunResult={segmentationRunResult}
segmentationRunError={segmentationRunError}
segmentationRuns={segmentationRuns}
selectedSegmentationRunId={selectedSegmentationRunId}
segmentationItems={segmentationItems}
segmentationClassFilter={segmentationClassFilter}
segmentationMinConfidenceFilter={segmentationMinConfidenceFilter}
loadingSegmentationResults={loadingSegmentationResults}
segmentationReferenceDatasetId={segmentationReferenceDatasetId}
segmentationQaResult={segmentationQaResult}
segmentationQaError={segmentationQaError}
runningSegmentationQa={runningSegmentationQa}
selectedProjectId={selectedProjectId}
rasterDatasets={rasterDatasets}
referenceDatasets={referenceDatasets}
selectedSegmentationModelConfigured={Boolean(selectedSegmentationModel?.configured)}
selectedSegmentationModelLimitation={selectedSegmentationModel?.limitation_message ?? null}
onLoadModels={loadSegmentationModels}
onSelectDataset={setSelectedSegmentationDatasetId}
onSelectModel={setSelectedSegmentationModelId}
onSetTileManifestPath={setSegmentationTileManifestPath}
onSetConfidenceThreshold={setSegmentationConfidenceThreshold}
onRunSegmentation={runSegmentation}
onLoadRuns={() => loadSegmentationRuns()}
onSelectRun={setSelectedSegmentationRunId}
onSetClassFilter={setSegmentationClassFilter}
onSetMinConfidenceFilter={setSegmentationMinConfidenceFilter}
onLoadResults={() => loadSegmentationResults()}
onSelectReferenceDataset={setSegmentationReferenceDatasetId}
onRunQa={runSegmentationQa}
/>
<details className="secondary-analysis-disclosure segmentation-disclosure">
<summary>
<span>Segmentatie van beeldvlakken</span>
<strong>Nog niet geconfigureerd</strong>
</summary>
<SegmentationLab
segmentationModels={segmentationModels}
loadingSegmentationModels={loadingSegmentationModels}
segmentationModelError={segmentationModelError}
selectedSegmentationDatasetId={selectedSegmentationDatasetId}
selectedSegmentationModelId={selectedSegmentationModelId}
segmentationTileManifestPath={segmentationTileManifestPath}
segmentationConfidenceThreshold={segmentationConfidenceThreshold}
runningSegmentation={runningSegmentation}
segmentationRunResult={segmentationRunResult}
segmentationRunError={segmentationRunError}
segmentationRuns={segmentationRuns}
selectedSegmentationRunId={selectedSegmentationRunId}
segmentationItems={segmentationItems}
segmentationClassFilter={segmentationClassFilter}
segmentationMinConfidenceFilter={segmentationMinConfidenceFilter}
loadingSegmentationResults={loadingSegmentationResults}
segmentationReferenceDatasetId={segmentationReferenceDatasetId}
segmentationQaResult={segmentationQaResult}
segmentationQaError={segmentationQaError}
runningSegmentationQa={runningSegmentationQa}
selectedProjectId={selectedProjectId}
rasterDatasets={rasterDatasets}
referenceDatasets={referenceDatasets}
selectedSegmentationModelConfigured={Boolean(selectedSegmentationModel?.configured)}
selectedSegmentationModelLimitation={selectedSegmentationModel?.limitation_message ?? null}
onLoadModels={loadSegmentationModels}
onSelectDataset={setSelectedSegmentationDatasetId}
onSelectModel={setSelectedSegmentationModelId}
onSetTileManifestPath={setSegmentationTileManifestPath}
onSetConfidenceThreshold={setSegmentationConfidenceThreshold}
onRunSegmentation={runSegmentation}
onLoadRuns={() => loadSegmentationRuns()}
onSelectRun={setSelectedSegmentationRunId}
onSetClassFilter={setSegmentationClassFilter}
onSetMinConfidenceFilter={setSegmentationMinConfidenceFilter}
onLoadResults={() => loadSegmentationResults()}
onSelectReferenceDataset={setSegmentationReferenceDatasetId}
onRunQa={runSegmentationQa}
/>
</details>
</div>
) : null}