221 lines
7.7 KiB
TypeScript
221 lines
7.7 KiB
TypeScript
import type { AreaRead, DatasetCreateResponse, ExportRead, ProjectRead, QualityCheckRead } from '../types'
|
|
import {
|
|
FLANDERS_WORKSPACE_LABEL,
|
|
FLANDERS_WORKSPACE_PROJECT_NAME,
|
|
REGIONAL_WORKSPACE_LABEL,
|
|
REGIONAL_WORKSPACE_PROJECT_NAME,
|
|
} from '../config/primaryFocus'
|
|
|
|
interface StatusItem {
|
|
key: string
|
|
label: string
|
|
value: string
|
|
detail: string
|
|
state: 'ready' | 'warning' | 'waiting'
|
|
}
|
|
|
|
interface WorkbenchStatusStripProps {
|
|
selectedProject: ProjectRead | null
|
|
areas: AreaRead[]
|
|
datasets: DatasetCreateResponse[]
|
|
qualityChecks: QualityCheckRead[]
|
|
exports: ExportRead[]
|
|
activeLayerFeatureCount: number
|
|
selectedAreaHasGeometry: boolean
|
|
}
|
|
|
|
function countByDatasetType(datasets: DatasetCreateResponse[], datasetType: string): number {
|
|
return datasets.filter((dataset) => dataset.dataset_type === datasetType).length
|
|
}
|
|
|
|
function countReferenceDatasets(datasets: DatasetCreateResponse[]): number {
|
|
return datasets.filter((dataset) => dataset.dataset_role === 'reference').length
|
|
}
|
|
|
|
const ANALYTICAL_SOURCE_NAMES = new Set([
|
|
'ngi_adminvector',
|
|
'rbins_marine_reporting_units',
|
|
'rbins_msp_2026',
|
|
'grb',
|
|
'department_omgeving_land_use',
|
|
'historical_landuse',
|
|
'department_omgeving_thematic_raster',
|
|
'statbel',
|
|
'inbo_bwk_natura2000',
|
|
'agentschap_landbouw_zeevisserij_agricultural_parcels',
|
|
'dov_soil_map',
|
|
'digitaal_vlaanderen_dhmv',
|
|
'spw_terrain',
|
|
'vmm_flood_hazard',
|
|
'vmm_vha_bathymetry_profiles',
|
|
])
|
|
|
|
const ANALYTICAL_LAYER_NAMES = new Set([
|
|
'administrative',
|
|
'belgium_land_boundary',
|
|
'belgium_regions',
|
|
'belgium_provinces',
|
|
'belgium_municipalities',
|
|
'buildings',
|
|
'roads',
|
|
'water',
|
|
'parcels',
|
|
'population',
|
|
'forest',
|
|
'nature_value',
|
|
'agriculture',
|
|
'soil',
|
|
'bathymetry',
|
|
'flood_hazard',
|
|
'elevation',
|
|
'maritime_planning',
|
|
'marine_environment',
|
|
'marine_legal_scopes',
|
|
'marine_spatial_plan_2026',
|
|
])
|
|
|
|
function isAnalyzableDataset(dataset: DatasetCreateResponse): boolean {
|
|
if (dataset.status !== 'ready') return false
|
|
const sourceName = String(dataset.source_name ?? dataset.source ?? '').toLowerCase()
|
|
const layerName = String(
|
|
dataset.source_metadata?.['theme']
|
|
?? dataset.reference_layer_name
|
|
?? dataset.source_metadata?.['layer_type']
|
|
?? '',
|
|
).toLowerCase()
|
|
return ANALYTICAL_SOURCE_NAMES.has(sourceName) || ANALYTICAL_LAYER_NAMES.has(layerName)
|
|
}
|
|
|
|
function statusLabel(state: StatusItem['state']): string {
|
|
if (state === 'ready') return 'gereed'
|
|
if (state === 'warning') return 'aandacht'
|
|
return 'nog open'
|
|
}
|
|
|
|
function projectLabel(project: ProjectRead | null): string {
|
|
if (!project) return 'Geen werkruimte'
|
|
if (project.name === FLANDERS_WORKSPACE_PROJECT_NAME) return FLANDERS_WORKSPACE_LABEL
|
|
return project.name === REGIONAL_WORKSPACE_PROJECT_NAME ? REGIONAL_WORKSPACE_LABEL : project.name
|
|
}
|
|
|
|
function nextAction(items: StatusItem[]): string {
|
|
if (!items.some((item) => item.key === 'project' && item.state === 'ready')) {
|
|
return 'De regionale werkruimte kon niet worden geopend.'
|
|
}
|
|
if (!items.some((item) => item.key === 'area' && item.state === 'ready')) {
|
|
return 'Kies een gemeente of de volledige regio.'
|
|
}
|
|
if (!items.some((item) => item.key === 'datasets' && item.state === 'ready')) {
|
|
return 'Laad een kaartlaag of luchtbeeld in.'
|
|
}
|
|
if (!items.some((item) => item.key === 'qa' && item.state === 'ready')) {
|
|
return 'Kaart en bronnen zijn klaar. Kwaliteitscontrole is beschikbaar zodra er een analyseresultaat is.'
|
|
}
|
|
if (!items.some((item) => item.key === 'exports' && item.state === 'ready')) {
|
|
return 'De analyse is klaar; maak eventueel een rapport of download.'
|
|
}
|
|
return 'Alle onderdelen van de werkruimte bevatten bewaarde resultaten.'
|
|
}
|
|
|
|
export function WorkbenchStatusStrip({
|
|
selectedProject,
|
|
areas,
|
|
datasets,
|
|
qualityChecks,
|
|
exports,
|
|
activeLayerFeatureCount,
|
|
selectedAreaHasGeometry,
|
|
}: WorkbenchStatusStripProps): JSX.Element {
|
|
const readyDatasets = datasets.filter((dataset) => dataset.status === 'ready').length
|
|
const analyzableDatasets = datasets.filter(isAnalyzableDataset).length
|
|
const vectorDatasets = countByDatasetType(datasets, 'vector') + countByDatasetType(datasets, 'geojson')
|
|
const rasterDatasets = countByDatasetType(datasets, 'raster')
|
|
const referenceDatasets = countReferenceDatasets(datasets)
|
|
const latestQualityCheck = qualityChecks[0]
|
|
const latestExport = exports[0]
|
|
|
|
const items: StatusItem[] = [
|
|
{
|
|
key: 'project',
|
|
label: 'Werkruimte',
|
|
value: projectLabel(selectedProject),
|
|
detail: selectedProject ? selectedProject.region : 'Niet geladen',
|
|
state: selectedProject ? 'ready' : 'waiting',
|
|
},
|
|
{
|
|
key: 'area',
|
|
label: 'Gebieden',
|
|
value: `${areas.length} beschikbaar`,
|
|
detail: selectedAreaHasGeometry ? 'Kaartgrens beschikbaar' : 'Geen kaartgrens geselecteerd',
|
|
state: selectedAreaHasGeometry ? 'ready' : areas.length > 0 ? 'warning' : 'waiting',
|
|
},
|
|
{
|
|
key: 'datasets',
|
|
label: 'Bronnen',
|
|
value: `${analyzableDatasets} analysethema${analyzableDatasets === 1 ? '' : "'s"}`,
|
|
detail: (
|
|
`${readyDatasets}/${datasets.length} bronnen gereed · `
|
|
+ `${vectorDatasets} kaartlagen, ${rasterDatasets} rasters, ${referenceDatasets} officiële referenties`
|
|
),
|
|
state: analyzableDatasets === 0 ? 'waiting' : readyDatasets === datasets.length ? 'ready' : 'warning',
|
|
},
|
|
{
|
|
key: 'map',
|
|
label: 'Kaart',
|
|
value: `${activeLayerFeatureCount} zichtbare objecten`,
|
|
detail: selectedAreaHasGeometry ? 'Gebiedsgrens beschikbaar' : 'Gebiedsgrens ontbreekt',
|
|
state: selectedAreaHasGeometry || activeLayerFeatureCount > 0 ? 'ready' : 'waiting',
|
|
},
|
|
{
|
|
key: 'qa',
|
|
label: 'Kwaliteit',
|
|
value: `${qualityChecks.length} controles`,
|
|
detail: latestQualityCheck ? `${latestQualityCheck.check_type}: ${latestQualityCheck.status}` : 'Nog geen controle uitgevoerd',
|
|
state: qualityChecks.length > 0 ? 'ready' : 'waiting',
|
|
},
|
|
{
|
|
key: 'exports',
|
|
label: 'Downloads',
|
|
value: `${exports.length} bestanden`,
|
|
detail: latestExport ? `${latestExport.export_type}: ${latestExport.status}` : 'Nog geen bestand gemaakt',
|
|
state: exports.length > 0 ? 'ready' : 'waiting',
|
|
},
|
|
]
|
|
const readyCount = items.filter((item) => item.state === 'ready').length
|
|
const coreReady = items.slice(0, 4).every((item) => item.state === 'ready')
|
|
|
|
return (
|
|
<section className="workbench-status-strip" aria-label="Workbench readiness">
|
|
<div className="status-strip-header">
|
|
<div>
|
|
<p className="eyebrow">Platformstatus</p>
|
|
<h2>
|
|
<span className="sr-only">Status werkruimte: </span>
|
|
{coreReady ? 'Kaartwerkruimte is gebruiksklaar' : 'Kaartwerkruimte vraagt aandacht'}
|
|
</h2>
|
|
<p className="status-next-action">{nextAction(items)}</p>
|
|
</div>
|
|
<div className="status-readiness" data-ready={readyCount} aria-label={`${readyCount} van ${items.length} onderdelen gereed`}>
|
|
<span className="status-readiness-orbit" aria-hidden="true" />
|
|
<div className="status-readiness-copy">
|
|
<strong>{readyCount}/{items.length}</strong>
|
|
<span>onderdelen gereed</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="status-strip-grid">
|
|
{items.map((item) => (
|
|
<div className={`status-tile status-tile-${item.state}`} key={item.key}>
|
|
<div className="status-tile-topline">
|
|
<span>{item.label}</span>
|
|
<span className="status-pill">{statusLabel(item.state)}</span>
|
|
</div>
|
|
<strong title={item.value}>{item.value}</strong>
|
|
<p>{item.detail}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|