Polish data workspace density
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-06-20 06:45:54 +02:00
parent fc3ddf3c49
commit 5a00d3525b
9 changed files with 400 additions and 220 deletions
+129 -116
View File
@@ -105,6 +105,7 @@ export function DatasetPanel({
onOpenDatasetExport,
}: DatasetPanelProps) {
const readyDatasets = datasets.filter((dataset) => dataset.status === 'ready').length
const selectedDataset = datasets.find((dataset) => dataset.id === selectedDatasetId)
const roleSummaries = [
{
key: 'selected',
@@ -146,52 +147,61 @@ export function DatasetPanel({
<span className="count-pill">{readyDatasets}/{datasets.length} ready</span>
</div>
<form className="dataset-upload-form" onSubmit={onUploadDataset}>
<label>
Type
<select
value={datasetForm.datasetType}
onChange={(event) => onDatasetFormChange((previous) => ({ ...previous, datasetType: event.target.value }))}
>
<option value="vector">vector</option>
<option value="geojson">geojson</option>
<option value="raster">raster</option>
</select>
</label>
<label>
Source
<input
value={datasetForm.source}
onChange={(event) => onDatasetFormChange((previous) => ({ ...previous, source: event.target.value }))}
placeholder="user_upload"
/>
</label>
<label>
AOI
<select
value={datasetForm.areaId}
onChange={(event) => onDatasetFormChange((previous) => ({ ...previous, areaId: event.target.value }))}
>
<option value="">No area</option>
{areas.map((area) => (
<option key={area.id} value={area.id}>
{area.name}
</option>
))}
</select>
</label>
<label className="file-input-label">
File
<input
type="file"
accept=".geojson,.json,.tif,.tiff,.geotiff"
onChange={(event) => onDatasetFormChange((previous) => ({ ...previous, file: event.target.files?.[0] ?? null }))}
/>
</label>
<button className="primary-action" type="submit" disabled={!selectedProjectId}>
Upload dataset
</button>
</form>
<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>
</div>
<div className="dataset-upload-block data-panel-form-block">
<p className="data-section-label">Upload source data</p>
<form className="dataset-upload-form" onSubmit={onUploadDataset}>
<label>
Type
<select
value={datasetForm.datasetType}
onChange={(event) => onDatasetFormChange((previous) => ({ ...previous, datasetType: event.target.value }))}
>
<option value="vector">vector</option>
<option value="geojson">geojson</option>
<option value="raster">raster</option>
</select>
</label>
<label>
Source
<input
value={datasetForm.source}
onChange={(event) => onDatasetFormChange((previous) => ({ ...previous, source: event.target.value }))}
placeholder="user_upload"
/>
</label>
<label>
AOI
<select
value={datasetForm.areaId}
onChange={(event) => onDatasetFormChange((previous) => ({ ...previous, areaId: event.target.value }))}
>
<option value="">No area</option>
{areas.map((area) => (
<option key={area.id} value={area.id}>
{area.name}
</option>
))}
</select>
</label>
<label className="file-input-label">
File
<input
type="file"
accept=".geojson,.json,.tif,.tiff,.geotiff"
onChange={(event) => onDatasetFormChange((previous) => ({ ...previous, file: event.target.files?.[0] ?? null }))}
/>
</label>
<button className="primary-action" type="submit" disabled={!selectedProjectId}>
Upload dataset
</button>
</form>
</div>
<div className="dataset-role-summary-grid" aria-label="Dataset role summary">
{roleSummaries.map((summary) => (
@@ -210,78 +220,81 @@ export function DatasetPanel({
<p>Upload vector, GeoJSON or raster data after selecting a project.</p>
</div>
) : null}
<ul className="dataset-list">
{datasets.map((dataset) => {
const datasetRole = normalizeDatasetRole(dataset)
const roleLabel = datasetRoleLabel(datasetRole)
return (
<li className={dataset.id === selectedDatasetId ? 'dataset-card dataset-card-active' : 'dataset-card'} key={dataset.id}>
<div className="dataset-card-header">
<div>
<div className="dataset-card-kicker">
<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>
<div className="data-panel-list-block dataset-catalog-block">
<p className="data-section-label">Available datasets</p>
<ul className="dataset-list">
{datasets.map((dataset) => {
const datasetRole = normalizeDatasetRole(dataset)
const roleLabel = datasetRoleLabel(datasetRole)
return (
<li className={dataset.id === selectedDatasetId ? 'dataset-card dataset-card-active' : 'dataset-card'} key={dataset.id}>
<div className="dataset-card-header">
<div>
<div className="dataset-card-kicker">
<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>
</div>
</div>
<span className={dataset.status === 'ready' ? 'status-badge status-badge-ready' : 'status-badge'}>
{dataset.status === 'ready' ? 'ready' : dataset.status === 'failed' ? 'failed' : 'pending'}
</span>
</div>
<span className={dataset.status === 'ready' ? 'status-badge status-badge-ready' : 'status-badge'}>
{dataset.status === 'ready' ? 'ready' : dataset.status === 'failed' ? 'failed' : 'pending'}
</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
className="primary-action dataset-action-button"
type="button"
onClick={() => onLoadDatasetDetails(selectedProjectId ?? '', dataset)}
data-testid={`dataset-select-${dataset.id}`}
>
<span>Inspect</span>
<small>Select / details</small>
</button>
<button
className="secondary-action dataset-action-button"
type="button"
onClick={() => onOpenDatasetInMap(dataset)}
aria-label="Open in map"
>
<span>Map</span>
<small>Open layer</small>
</button>
<button
className="secondary-action dataset-action-button"
type="button"
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>
</button>
<button
className="secondary-action dataset-action-button"
type="button"
onClick={() => onRefreshMetadata(dataset.id)}
disabled={dataset.dataset_type === 'raster'}
aria-label="Refresh metadata"
>
<span>Metadata</span>
<small>{dataset.dataset_type === 'raster' ? 'Raster metadata is automatic' : 'Refresh vector stats'}</small>
</button>
</div>
</li>
)
})}
</ul>
<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
className="primary-action dataset-action-button"
type="button"
onClick={() => onLoadDatasetDetails(selectedProjectId ?? '', dataset)}
data-testid={`dataset-select-${dataset.id}`}
>
<span>Inspect</span>
<small>Select / details</small>
</button>
<button
className="secondary-action dataset-action-button"
type="button"
onClick={() => onOpenDatasetInMap(dataset)}
aria-label="Open in map"
>
<span>Map</span>
<small>Open layer</small>
</button>
<button
className="secondary-action dataset-action-button"
type="button"
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>
</button>
<button
className="secondary-action dataset-action-button"
type="button"
onClick={() => onRefreshMetadata(dataset.id)}
disabled={dataset.dataset_type === 'raster'}
aria-label="Refresh metadata"
>
<span>Metadata</span>
<small>{dataset.dataset_type === 'raster' ? 'Raster metadata is automatic' : 'Refresh vector stats'}</small>
</button>
</div>
</li>
)
})}
</ul>
</div>
</section>
)
}