Close full operational audit findings
This commit is contained in:
+36
-190
@@ -10,11 +10,10 @@ import { ExportCenter } from './components/exports/ExportCenter'
|
||||
import { ExportPreview } from './components/exports/ExportPreview'
|
||||
import { WorkbenchInspector } from './components/inspector/WorkbenchInspector'
|
||||
import { MapWorkspace } from './components/map/MapWorkspace'
|
||||
import { OverviewWorkspace, type WorkspaceKey } from './components/overview/OverviewWorkspace'
|
||||
import { AreaPanel } from './components/project/AreaPanel'
|
||||
import { ProjectPanel } from './components/project/ProjectPanel'
|
||||
import { QualityResultsPanel } from './components/quality/QualityResultsPanel'
|
||||
import { WorkbenchStatusStrip } from './components/WorkbenchStatusStrip'
|
||||
import { SourceFreshnessPanel } from './components/status/SourceFreshnessPanel'
|
||||
import type { DatasetCreateResponse } from './types'
|
||||
import { ProviderPanel } from './components/providers/ProviderPanel'
|
||||
import { SegmentationLab } from './components/segmentation/SegmentationLab'
|
||||
@@ -43,8 +42,6 @@ function isVectorDatasetType(datasetType: string): boolean {
|
||||
return datasetType === 'vector' || datasetType === 'geojson'
|
||||
}
|
||||
|
||||
type WorkspaceKey = 'overview' | 'data' | 'map' | 'assistant' | 'analysis' | 'ai' | 'exports' | 'system'
|
||||
|
||||
const workspaceNavItems: Array<{ key: WorkspaceKey; label: string; description: string }> = [
|
||||
{ key: 'overview', label: 'Status', description: 'Beschikbaarheid en aandachtspunten' },
|
||||
{ key: 'data', label: 'Bronnen', description: 'Gebieden en ingeladen gegevens' },
|
||||
@@ -83,6 +80,7 @@ function App(): JSX.Element {
|
||||
loadingProjects,
|
||||
loadingAreas,
|
||||
loadingDatasets,
|
||||
archivingProjectId,
|
||||
errorMessage,
|
||||
projectForm,
|
||||
areaForm,
|
||||
@@ -90,6 +88,7 @@ function App(): JSX.Element {
|
||||
loadProjectData,
|
||||
createProject,
|
||||
createArea,
|
||||
archiveProject,
|
||||
resetProjectData,
|
||||
setSelectedProjectId,
|
||||
setErrorMessage,
|
||||
@@ -586,24 +585,24 @@ function App(): JSX.Element {
|
||||
return 'Detectierun'
|
||||
}
|
||||
if ((datasetMapContent || viewportVectorLayer.enabled) && selectedDataset) {
|
||||
return `${selectedDataset.dataset_type}-dataset`
|
||||
return selectedDataset.dataset_type === 'raster' ? 'Rasterdatabron' : 'Vectordatabron'
|
||||
}
|
||||
return 'Geen actieve gegevens- of analyselaag'
|
||||
}, [analysisMapLayerActive, changeDetectionResult?.geojson, datasetMapContent, detectionGeoJson, segmentationGeoJson, selectedDataset, viewportVectorLayer.enabled])
|
||||
const mapLayerProvenance = useMemo(() => {
|
||||
if (analysisMapLayerActive && changeDetectionResult?.geojson) {
|
||||
return `source ${changeSourceDatasetId || 'n/a'} -> target ${changeTargetDatasetId || 'n/a'}`
|
||||
return 'Vergelijking van twee bewaarde kaartlagen'
|
||||
}
|
||||
if (analysisMapLayerActive && segmentationGeoJson) {
|
||||
return selectedSegmentationRunId ? `analysis run ${selectedSegmentationRunId}` : 'segmentation results loaded'
|
||||
return selectedSegmentationRunId ? 'Bewaarde segmentatieronde' : 'Segmentatieresultaten geladen'
|
||||
}
|
||||
if (analysisMapLayerActive && detectionGeoJson) {
|
||||
return selectedDetectionRunId ? `analysis run ${selectedDetectionRunId}` : 'detection results loaded'
|
||||
return selectedDetectionRunId ? 'Bewaarde detectieronde' : 'Detectieresultaten geladen'
|
||||
}
|
||||
if ((datasetMapContent || viewportVectorLayer.enabled) && selectedDataset) {
|
||||
return `${selectedDataset.dataset_role ?? 'source'} / ${selectedDataset.source_name ?? selectedDataset.source}`
|
||||
return `Bewaarde ${selectedDataset.dataset_role === 'reference' ? 'referentielaag' : 'databron'}`
|
||||
}
|
||||
return 'Open a dataset, detection run, segmentation run or change result to draw it here.'
|
||||
return 'Open een databron, detectieronde, segmentatieronde of veranderingsresultaat om het hier te tekenen.'
|
||||
}, [
|
||||
analysisMapLayerActive,
|
||||
changeDetectionResult?.geojson,
|
||||
@@ -658,73 +657,11 @@ function App(): JSX.Element {
|
||||
}
|
||||
setActiveWorkspace(target)
|
||||
}
|
||||
const hasAnalysisOutput = qualityChecks.length > 0 || Boolean(changeDetectionResult) || detectionItems.length > 0 || segmentationItems.length > 0
|
||||
const hasMapContext = mapFeatureCount > 0 || areaFeatureCount > 0
|
||||
const workflowGuidanceComplete = Boolean(selectedProjectId) && datasets.length > 0 && hasMapContext && hasAnalysisOutput && exports.length > 0
|
||||
const recommendedWorkflowTarget: WorkspaceKey = !selectedProjectId
|
||||
? 'data'
|
||||
: datasets.length === 0
|
||||
? 'data'
|
||||
: !hasMapContext
|
||||
? 'map'
|
||||
: !hasAnalysisOutput
|
||||
? 'analysis'
|
||||
: exports.length === 0
|
||||
? 'exports'
|
||||
: 'exports'
|
||||
const workflowGuidanceSteps: Array<{
|
||||
step: string
|
||||
title: string
|
||||
detail: string
|
||||
status: string
|
||||
ready: boolean
|
||||
target: WorkspaceKey
|
||||
}> = [
|
||||
{
|
||||
step: '1',
|
||||
title: 'Project & AOI',
|
||||
detail: selectedProjectId ? `${areas.length} AOI record${areas.length === 1 ? '' : 's'} available` : 'Create or load a project context',
|
||||
status: selectedProjectId ? 'ready' : 'next',
|
||||
ready: Boolean(selectedProjectId),
|
||||
target: 'data',
|
||||
},
|
||||
{
|
||||
step: '2',
|
||||
title: 'Data',
|
||||
detail: datasets.length > 0 ? `${datasets.length} dataset${datasets.length === 1 ? '' : 's'} loaded` : 'Upload source and reference datasets',
|
||||
status: datasets.length > 0 ? 'ready' : 'waiting',
|
||||
ready: datasets.length > 0,
|
||||
target: 'data',
|
||||
},
|
||||
{
|
||||
step: '3',
|
||||
title: 'Map',
|
||||
detail: hasMapContext
|
||||
? mapFeatureCount > 0
|
||||
? `${mapFeatureCount} layer feature${mapFeatureCount === 1 ? '' : 's'}${areaFeatureCount > 0 ? ' + AOI' : ''}`
|
||||
: 'AOI loaded'
|
||||
: 'Inspect AOI and selected layer',
|
||||
status: hasMapContext ? 'ready' : 'waiting',
|
||||
ready: hasMapContext,
|
||||
target: 'map',
|
||||
},
|
||||
{
|
||||
step: '4',
|
||||
title: 'QA / AI',
|
||||
detail: hasAnalysisOutput ? 'QA, change, detection or segmentation output exists' : 'Run checks after data is ready',
|
||||
status: hasAnalysisOutput ? 'ready' : 'waiting',
|
||||
ready: hasAnalysisOutput,
|
||||
target: 'analysis',
|
||||
},
|
||||
{
|
||||
step: '5',
|
||||
title: 'Export',
|
||||
detail: exports.length > 0 ? `${exports.length} export artifact${exports.length === 1 ? '' : 's'} recorded` : 'Package validated project outputs',
|
||||
status: exports.length > 0 ? 'ready' : 'waiting',
|
||||
ready: exports.length > 0,
|
||||
target: 'exports',
|
||||
},
|
||||
]
|
||||
const hasAnalysisOutput =
|
||||
qualityChecks.length > 0
|
||||
|| Boolean(changeDetectionResult)
|
||||
|| detectionItems.length > 0
|
||||
|| segmentationItems.length > 0
|
||||
const projectContextLabel = selectedProject?.name === REGIONAL_WORKSPACE_PROJECT_NAME
|
||||
? REGIONAL_WORKSPACE_LABEL
|
||||
: selectedProject?.name ?? 'Geen werkruimte'
|
||||
@@ -759,7 +696,7 @@ function App(): JSX.Element {
|
||||
<p>GeoAI-werkruimte · Mol en de Kempen</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="context-bar" aria-label="Active workbench context">
|
||||
<div className="context-bar" aria-label="Actieve werkcontext">
|
||||
<div>
|
||||
<span>Werkruimte</span>
|
||||
<strong title={projectContextLabel}>{projectContextLabel}</strong>
|
||||
@@ -782,8 +719,8 @@ function App(): JSX.Element {
|
||||
{errorMessage ? <p className="error">{errorMessage}</p> : null}
|
||||
|
||||
<div className="workbench-layout">
|
||||
<aside className="workbench-sidebar" aria-label="Workbench navigation">
|
||||
<nav aria-label="Primary workspaces">
|
||||
<aside className="workbench-sidebar" aria-label="Navigatie van de werkruimte">
|
||||
<nav aria-label="Hoofdonderdelen">
|
||||
{workspaceNavGroups.map((group) => (
|
||||
<div className="nav-group" key={group.label}>
|
||||
<p className="nav-section-label">{group.label}</p>
|
||||
@@ -799,7 +736,7 @@ function App(): JSX.Element {
|
||||
className={item.key === activeWorkspace ? 'nav-item nav-item-active' : 'nav-item'}
|
||||
onClick={() => setActiveWorkspace(item.key)}
|
||||
aria-current={item.key === activeWorkspace ? 'page' : undefined}
|
||||
aria-label={`Open ${item.label} workspace: ${item.description}`}
|
||||
aria-label={`Open ${item.label}: ${item.description}`}
|
||||
data-testid={`workspace-nav-${item.key}`}
|
||||
>
|
||||
<span>{item.label}</span>
|
||||
@@ -833,7 +770,7 @@ function App(): JSX.Element {
|
||||
) : null}
|
||||
</div>
|
||||
</div> : null}
|
||||
<div className="workspace-command-bar" aria-label="Workspace shortcuts">
|
||||
<div className="workspace-command-bar" aria-label="Snelkoppelingen">
|
||||
<div className="workspace-nav-cluster">
|
||||
{workspaceNavItems.slice(0, 5).map((item) => (
|
||||
<button
|
||||
@@ -842,7 +779,7 @@ function App(): JSX.Element {
|
||||
className={item.key === activeWorkspace ? 'command-chip command-chip-active' : 'command-chip'}
|
||||
onClick={() => setActiveWorkspace(item.key)}
|
||||
aria-pressed={item.key === activeWorkspace}
|
||||
aria-label={`Switch to ${item.label} workspace`}
|
||||
aria-label={`Ga naar ${item.label}`}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
@@ -852,112 +789,19 @@ function App(): JSX.Element {
|
||||
</div>
|
||||
|
||||
{activeWorkspace === 'overview' ? (
|
||||
<div className="workspace-stack">
|
||||
<WorkbenchStatusStrip
|
||||
selectedProject={selectedProject}
|
||||
areas={areas}
|
||||
datasets={datasets}
|
||||
qualityChecks={qualityChecks}
|
||||
exports={exports}
|
||||
activeLayerFeatureCount={mapFeatureCount}
|
||||
selectedAreaHasGeometry={Boolean(areaFeatureCollection)}
|
||||
/>
|
||||
<SourceFreshnessPanel
|
||||
report={sourceFreshness.report}
|
||||
loading={sourceFreshness.loading}
|
||||
error={sourceFreshness.error}
|
||||
onRefresh={() => { void sourceFreshness.refresh() }}
|
||||
catalogReport={sourceFreshness.catalogReport}
|
||||
catalogLoading={sourceFreshness.catalogLoading}
|
||||
catalogError={sourceFreshness.catalogError}
|
||||
onProbeCatalogs={(force) => { void sourceFreshness.probeCatalogs(force) }}
|
||||
grbRefreshPlan={sourceFreshness.grbRefreshPlan}
|
||||
grbRefreshPlanLoading={sourceFreshness.grbRefreshPlanLoading}
|
||||
grbRefreshPlanError={sourceFreshness.grbRefreshPlanError}
|
||||
/>
|
||||
<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>
|
||||
<p className="eyebrow">Workbench flow</p>
|
||||
<h2>Project to export path</h2>
|
||||
</div>
|
||||
<span className="status-badge">
|
||||
{workflowGuidanceComplete
|
||||
? 'Ready for handoff'
|
||||
: `Next: ${workspaceNavItems.find((item) => item.key === recommendedWorkflowTarget)?.label ?? 'Overview'}`}
|
||||
</span>
|
||||
</div>
|
||||
<div className="workflow-guidance-steps">
|
||||
{workflowGuidanceSteps.map((step) => (
|
||||
<button
|
||||
key={`${step.step}-${step.title}`}
|
||||
type="button"
|
||||
className={
|
||||
step.target === recommendedWorkflowTarget && !step.ready
|
||||
? 'workflow-guidance-step workflow-guidance-step-active'
|
||||
: step.ready
|
||||
? 'workflow-guidance-step workflow-guidance-step-ready'
|
||||
: 'workflow-guidance-step'
|
||||
}
|
||||
onClick={() => openWorkflowGuidanceStep(step.target)}
|
||||
aria-label={`Open ${step.title} step`}
|
||||
>
|
||||
<span className="workflow-step-status">{step.status}</span>
|
||||
<strong>
|
||||
{step.step}. {step.title}
|
||||
</strong>
|
||||
<small>{step.detail}</small>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<section className="overview-actions">
|
||||
<div className="overview-action-copy">
|
||||
<p className="eyebrow">Quick access</p>
|
||||
<h2>Continue working</h2>
|
||||
</div>
|
||||
<div className="quick-action-grid overview-quick-actions" aria-label="Recommended next workbench actions">
|
||||
<button
|
||||
type="button"
|
||||
className="quick-action-button"
|
||||
onClick={() => setActiveWorkspace('data')}
|
||||
aria-label="Open data setup workspace"
|
||||
>
|
||||
Open data setup
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="quick-action-button"
|
||||
onClick={() => setActiveWorkspace('map')}
|
||||
aria-label="Inspect map workspace"
|
||||
>
|
||||
Inspect map
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="quick-action-button"
|
||||
onClick={() => setActiveWorkspace('analysis')}
|
||||
aria-label="Review QA/QC workspace"
|
||||
>
|
||||
Review QA/QC
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="quick-action-button"
|
||||
onClick={() => setActiveWorkspace('exports')}
|
||||
aria-label="Manage exports workspace"
|
||||
>
|
||||
Manage exports
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</details>
|
||||
</div>
|
||||
<OverviewWorkspace
|
||||
selectedProject={selectedProject}
|
||||
selectedProjectId={selectedProjectId}
|
||||
areas={areas}
|
||||
datasets={datasets}
|
||||
qualityChecks={qualityChecks}
|
||||
exports={exports}
|
||||
activeLayerFeatureCount={mapFeatureCount}
|
||||
selectedAreaHasGeometry={Boolean(areaFeatureCollection)}
|
||||
hasAnalysisOutput={hasAnalysisOutput}
|
||||
sourceFreshness={sourceFreshness}
|
||||
onOpenWorkspace={openWorkflowGuidanceStep}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{activeWorkspace === 'data' ? (
|
||||
@@ -967,12 +811,14 @@ function App(): JSX.Element {
|
||||
projects={projects}
|
||||
selectedProjectId={selectedProjectId}
|
||||
loadingProjects={loadingProjects}
|
||||
archivingProjectId={archivingProjectId}
|
||||
projectForm={projectForm}
|
||||
loadingDemoWorkflow={loadingDemoWorkflow}
|
||||
demoWorkflowMessage={demoWorkflowMessage}
|
||||
onCreateProject={createProject}
|
||||
onUpdateProjectForm={setProjectForm}
|
||||
onSelectProject={selectProject}
|
||||
onArchiveProject={archiveProject}
|
||||
onLoadDemoWorkflow={loadDemoWorkflow}
|
||||
/>
|
||||
|
||||
@@ -1287,7 +1133,7 @@ function App(): JSX.Element {
|
||||
</main>
|
||||
|
||||
{inspectorOpen ? (
|
||||
<aside className="workbench-inspector" id="workbench-inspector" aria-label="Current selection inspector">
|
||||
<aside className="workbench-inspector" id="workbench-inspector" aria-label="Details van de huidige selectie">
|
||||
<WorkbenchInspector
|
||||
selectedProject={selectedProject}
|
||||
selectedArea={selectedArea}
|
||||
|
||||
Reference in New Issue
Block a user