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
@@ -1,5 +1,6 @@
import type { Dispatch, FormEvent, SetStateAction } from 'react'
import type { AreaRead, DatasetCreateResponse } from '../../types'
import { getDatasetDisplayName } from '../../lib/datasetDisplay'
interface DatasetFormState {
datasetType: string
@@ -70,25 +71,25 @@ function normalizeDatasetRole(dataset: DatasetCreateResponse): 'reference' | 'ca
function datasetRoleLabel(role: string): string {
if (role === 'reference') {
return 'Reference'
return 'Officiële referentie'
}
if (role === 'candidate') {
return 'Candidate'
return 'Analyse-resultaat'
}
return 'Source'
return 'Eigen bron'
}
function datasetActionHint(dataset: DatasetCreateResponse, role: string): string {
if (role === 'reference') {
return 'QA reference layer. Select it when comparing candidates against trusted geometry.'
return 'Officiële laag voor kaartanalyse en kwaliteitscontrole.'
}
if (role === 'candidate') {
return 'QA candidate layer. Open on the map or send to Export / QA.'
return 'Bewaard resultaat dat opnieuw op de kaart, in kwaliteit of als download kan worden gebruikt.'
}
if (dataset.dataset_type === 'raster') {
return 'Raster source. Use the raster tools in the inspector after selecting it.'
return 'Luchtbeeld of rasterbron voor kaart- en beeldanalyse.'
}
return 'Source dataset. Inspect details before using it in downstream workflows.'
return 'Ingeladen gegevensbron voor verdere analyse.'
}
export function DatasetPanel({
@@ -111,30 +112,30 @@ export function DatasetPanel({
const roleSummaries = [
{
key: 'selected',
label: 'Selected',
label: 'Geselecteerd',
count: selectedDatasetId ? 1 : 0,
hint: 'Active',
hint: 'Actief',
className: 'dataset-role-selected',
},
{
key: 'reference',
label: 'Reference',
label: 'Referentie',
count: datasets.filter((dataset) => normalizeDatasetRole(dataset) === 'reference').length,
hint: 'QA truth',
hint: 'Officieel',
className: 'dataset-role-reference',
},
{
key: 'candidate',
label: 'Candidate',
label: 'Resultaat',
count: datasets.filter((dataset) => normalizeDatasetRole(dataset) === 'candidate').length,
hint: 'QA candidate',
hint: 'Afgeleid',
className: 'dataset-role-candidate',
},
{
key: 'source',
label: 'Source',
label: 'Eigen bron',
count: datasets.filter((dataset) => normalizeDatasetRole(dataset) === 'source').length,
hint: 'Uploads',
hint: 'Ingeladen',
className: 'dataset-role-source',
},
]
@@ -143,23 +144,23 @@ export function DatasetPanel({
<section className="workspace-panel" data-testid="dataset-panel">
<div className="panel-title-row">
<div>
<p className="eyebrow">Data catalog</p>
<h2>Datasets</h2>
<p className="eyebrow">Ingeladen gegevens</p>
<h2>Bronnen</h2>
</div>
<span className="count-pill">{readyDatasets}/{datasets.length} ready</span>
<span className="count-pill">{readyDatasets}/{datasets.length} beschikbaar</span>
</div>
<div className="data-selection-summary data-selection-summary-dataset">
<span>Selected dataset</span>
<strong>{selectedDataset?.name ?? 'No dataset selected'}</strong>
<small>{selectedDataset ? `${datasetRoleLabel(normalizeDatasetRole(selectedDataset))} / ${selectedDataset.status}` : 'Inspect a dataset to load details and tools.'}</small>
<span>Actieve bron</span>
<strong>{selectedDataset ? getDatasetDisplayName(selectedDataset) : 'Geen bron geselecteerd'}</strong>
<small>{selectedDataset ? `${datasetRoleLabel(normalizeDatasetRole(selectedDataset))} · ${selectedDataset.status === 'ready' ? 'beschikbaar' : selectedDataset.status}` : 'Kies een bron om deze op de kaart te openen.'}</small>
</div>
<details className="dataset-upload-block data-panel-form-block">
<summary>Upload source data</summary>
<summary>Eigen bronbestand toevoegen</summary>
<form className="dataset-upload-form" onSubmit={onUploadDataset}>
<label>
Type
Bestandstype
<select
value={datasetForm.datasetType}
onChange={(event) => onDatasetFormChange((previous) => ({ ...previous, datasetType: event.target.value }))}
@@ -170,7 +171,7 @@ export function DatasetPanel({
</select>
</label>
<label>
Source
Bronnaam
<input
value={datasetForm.source}
onChange={(event) => onDatasetFormChange((previous) => ({ ...previous, source: event.target.value }))}
@@ -178,12 +179,12 @@ export function DatasetPanel({
/>
</label>
<label>
AOI
Gebied
<select
value={datasetForm.areaId}
onChange={(event) => onDatasetFormChange((previous) => ({ ...previous, areaId: event.target.value }))}
>
<option value="">No area</option>
<option value="">Volledige bron</option>
{areas.map((area) => (
<option key={area.id} value={area.id}>
{area.name}
@@ -192,7 +193,7 @@ export function DatasetPanel({
</select>
</label>
<label className="file-input-label">
File
Bestand
<input
type="file"
accept=".geojson,.json,.tif,.tiff,.geotiff"
@@ -200,7 +201,7 @@ export function DatasetPanel({
/>
</label>
<button className="primary-action" type="submit" disabled={!selectedProjectId}>
Upload dataset
Bron inladen
</button>
</form>
</details>
@@ -215,15 +216,15 @@ export function DatasetPanel({
))}
</div>
{loadingDatasets ? <p>Loading datasets...</p> : null}
{loadingDatasets ? <p>Bronnen laden...</p> : null}
{datasets.length === 0 ? (
<div className="empty-state">
<strong>No datasets yet</strong>
<p>Upload vector, GeoJSON or raster data after selecting a project.</p>
<strong>Nog geen bronnen beschikbaar</strong>
<p>Voeg een vector-, GeoJSON- of rasterbestand toe aan deze werkruimte.</p>
</div>
) : null}
<div className="data-panel-list-block dataset-catalog-block">
<p className="data-section-label">Available datasets</p>
<p className="data-section-label">Beschikbare bronnen</p>
<ul className="dataset-list">
{datasets.map((dataset) => {
const datasetRole = normalizeDatasetRole(dataset)
@@ -236,22 +237,16 @@ export function DatasetPanel({
<span className={`dataset-role-badge dataset-role-${datasetRole}`}>{roleLabel}</span>
<span>{dataset.dataset_type}</span>
</div>
<strong className="dataset-card-title">{dataset.name}</strong>
<div className="entity-meta">
<span>Source: {dataset.source_name ?? dataset.source}</span>
{dataset.reference_layer_name ? <span>Layer: {dataset.reference_layer_name}</span> : null}
<span>CRS: {dataset.crs ?? dataset.vector_summary?.crs ?? 'n/a'}</span>
<strong className="dataset-card-title">{getDatasetDisplayName(dataset)}</strong>
<div className="entity-meta dataset-card-primary-meta">
<span>{(dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0).toLocaleString('nl-BE')} objecten</span>
<span>{dataset.source_name ?? dataset.source}</span>
</div>
</div>
<span className={dataset.status === 'ready' ? 'status-badge status-badge-ready' : 'status-badge'}>
{dataset.status === 'ready' ? 'ready' : dataset.status === 'failed' ? 'failed' : 'pending'}
{dataset.status === 'ready' ? 'beschikbaar' : dataset.status === 'failed' ? 'mislukt' : 'bezig'}
</span>
</div>
<div className="dataset-metrics data-density-grid">
<span>size: {formatBytes(dataset.size_bytes)}</span>
<span>features: {dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 'n/a'}</span>
<span>bbox: {formatBounds(dataset.bounds_json ?? dataset.vector_summary?.bounds_json)}</span>
</div>
<p className="dataset-action-hint">{datasetActionHint(dataset, datasetRole)}</p>
<div className="dataset-action-grid">
<button
@@ -261,17 +256,17 @@ export function DatasetPanel({
onClick={() => onLoadDatasetDetails(selectedProjectId ?? '', dataset)}
data-testid={`dataset-select-${dataset.id}`}
>
<span>Inspect</span>
<small>Select / details</small>
<span>Bekijken</span>
<small>Details en acties</small>
</button>
<button
className="secondary-action dataset-action-button"
type="button"
onClick={() => onOpenDatasetInMap(dataset)}
aria-label="Open in map"
aria-label="Open op kaart"
>
<span>Map</span>
<small>Open layer</small>
<span>Kaart</span>
<small>Laag openen</small>
</button>
<button
className="secondary-action dataset-action-button"
@@ -279,20 +274,33 @@ export function DatasetPanel({
onClick={() => onOpenDatasetExport(dataset)}
disabled={!(dataset.dataset_type === 'vector' || dataset.dataset_type === 'geojson')}
>
<span>Export / QA</span>
<small>{dataset.dataset_type === 'vector' || dataset.dataset_type === 'geojson' ? 'Compare or export' : 'Vector/GeoJSON only'}</small>
<span>Downloaden</span>
<small>{dataset.dataset_type === 'vector' || dataset.dataset_type === 'geojson' ? 'GeoJSON of rapport' : 'Enkel vectorlagen'}</small>
</button>
</div>
<details className="dataset-technical-details">
<summary>Broninformatie</summary>
<div className="entity-meta">
<span>Bestand: {dataset.name}</span>
<span>Bron: {dataset.source_name ?? dataset.source}</span>
{dataset.reference_layer_name ? <span>Laag: {dataset.reference_layer_name}</span> : null}
<span>CRS: {dataset.crs ?? dataset.vector_summary?.crs ?? 'onbekend'}</span>
</div>
<div className="dataset-metrics data-density-grid">
<span>Grootte: {formatBytes(dataset.size_bytes)}</span>
<span>Objecten: {dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 'onbekend'}</span>
<span>Begrenzing: {formatBounds(dataset.bounds_json ?? dataset.vector_summary?.bounds_json)}</span>
</div>
<button
className="secondary-action dataset-action-button"
className="secondary-action"
type="button"
onClick={() => onRefreshMetadata(dataset.id)}
disabled={dataset.dataset_type === 'raster'}
aria-label="Refresh metadata"
aria-label="Bronmetadata vernieuwen"
>
<span>Metadata</span>
<small>{dataset.dataset_type === 'raster' ? 'Raster metadata is automatic' : 'Refresh vector stats'}</small>
Metadata vernieuwen
</button>
</div>
</details>
</li>
)
})}