Add tabbed workbench inspector
This commit is contained in:
@@ -10,7 +10,7 @@ import type {
|
||||
import { RasterControls } from './RasterControls'
|
||||
import { VectorControls } from './VectorControls'
|
||||
|
||||
interface DatasetDetailPanelProps {
|
||||
export interface DatasetDetailPanelProps {
|
||||
areas: AreaRead[]
|
||||
availableVectorTargets: DatasetCreateResponse[]
|
||||
selectedDatasetId: string | null
|
||||
@@ -147,7 +147,7 @@ export function DatasetDetailPanel({
|
||||
onPickDerivedDataset,
|
||||
}: DatasetDetailPanelProps) {
|
||||
return (
|
||||
<section>
|
||||
<section className="dataset-detail-panel">
|
||||
<h2>Dataset details</h2>
|
||||
{selectedDatasetId ? <p>Selected dataset: {selectedDatasetId}</p> : <p>No dataset selected</p>}
|
||||
{selectedDataset ? (
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import type {
|
||||
AreaRead,
|
||||
DetectionRead,
|
||||
DetectionRunRead,
|
||||
ExportCreateResponse,
|
||||
ExportRead,
|
||||
ProjectRead,
|
||||
QualityCheckRead,
|
||||
SegmentationRead,
|
||||
SegmentationRunRead,
|
||||
} from '../../types'
|
||||
import { DatasetDetailPanel, type DatasetDetailPanelProps } from '../datasets/DatasetDetailPanel'
|
||||
|
||||
type InspectorTab = 'context' | 'dataset' | 'quality' | 'ai'
|
||||
|
||||
interface WorkbenchInspectorProps {
|
||||
selectedProject: ProjectRead | null
|
||||
selectedArea: AreaRead | null
|
||||
selectedMapFeature: GeoJSON.Feature | null
|
||||
areas: AreaRead[]
|
||||
datasetsCount: number
|
||||
qualityChecks: QualityCheckRead[]
|
||||
exports: ExportRead[]
|
||||
latestExport: ExportCreateResponse | null
|
||||
detectionRuns: DetectionRunRead[]
|
||||
selectedDetectionRunId: string
|
||||
detectionItems: DetectionRead[]
|
||||
segmentationRuns: SegmentationRunRead[]
|
||||
selectedSegmentationRunId: string
|
||||
segmentationItems: SegmentationRead[]
|
||||
datasetDetailProps: DatasetDetailPanelProps
|
||||
}
|
||||
|
||||
function formatValue(value: string | number | null | undefined): string {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return 'n/a'
|
||||
}
|
||||
return String(value)
|
||||
}
|
||||
|
||||
function InspectorField({ label, value }: { label: string; value: string | number | null | undefined }): JSX.Element {
|
||||
return (
|
||||
<div className="inspector-field">
|
||||
<span>{label}</span>
|
||||
<strong>{formatValue(value)}</strong>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function WorkbenchInspector({
|
||||
selectedProject,
|
||||
selectedArea,
|
||||
selectedMapFeature,
|
||||
areas,
|
||||
datasetsCount,
|
||||
qualityChecks,
|
||||
exports,
|
||||
latestExport,
|
||||
detectionRuns,
|
||||
selectedDetectionRunId,
|
||||
detectionItems,
|
||||
segmentationRuns,
|
||||
selectedSegmentationRunId,
|
||||
segmentationItems,
|
||||
datasetDetailProps,
|
||||
}: WorkbenchInspectorProps): JSX.Element {
|
||||
const [activeTab, setActiveTab] = useState<InspectorTab>('context')
|
||||
const selectedDetectionRun = useMemo(
|
||||
() => detectionRuns.find((run) => run.id === selectedDetectionRunId) ?? null,
|
||||
[detectionRuns, selectedDetectionRunId],
|
||||
)
|
||||
const selectedSegmentationRun = useMemo(
|
||||
() => segmentationRuns.find((run) => run.id === selectedSegmentationRunId) ?? null,
|
||||
[segmentationRuns, selectedSegmentationRunId],
|
||||
)
|
||||
const latestQualityCheck = qualityChecks[0] ?? null
|
||||
const latestPersistedExport = exports[0] ?? null
|
||||
|
||||
const tabs: Array<{ key: InspectorTab; label: string }> = [
|
||||
{ key: 'context', label: 'Context' },
|
||||
{ key: 'dataset', label: 'Dataset' },
|
||||
{ key: 'quality', label: 'QA/Exports' },
|
||||
{ key: 'ai', label: 'AI Runs' },
|
||||
]
|
||||
|
||||
return (
|
||||
<section className="workbench-inspector-panel" data-testid="workbench-inspector-panel">
|
||||
<div className="inspector-header">
|
||||
<div>
|
||||
<p className="eyebrow">Inspector</p>
|
||||
<h2>Selection details</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="inspector-tabs" role="tablist" aria-label="Inspector detail tabs">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.key}
|
||||
type="button"
|
||||
className={activeTab === tab.key ? 'inspector-tab inspector-tab-active' : 'inspector-tab'}
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
role="tab"
|
||||
aria-selected={activeTab === tab.key}
|
||||
data-testid={`inspector-tab-${tab.key}`}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{activeTab === 'context' ? (
|
||||
<div className="inspector-tab-panel">
|
||||
<div className="inspector-card">
|
||||
<h3>Project context</h3>
|
||||
<InspectorField label="Project" value={selectedProject?.name} />
|
||||
<InspectorField label="Region" value={selectedProject?.region} />
|
||||
<InspectorField label="Status" value={selectedProject?.status} />
|
||||
<InspectorField label="AOIs" value={areas.length} />
|
||||
<InspectorField label="Datasets" value={datasetsCount} />
|
||||
</div>
|
||||
<div className="inspector-card">
|
||||
<h3>Active AOI</h3>
|
||||
<InspectorField label="Name" value={selectedArea?.name} />
|
||||
<InspectorField label="Area m2" value={selectedArea?.area_m2} />
|
||||
<InspectorField label="CRS" value={selectedArea?.original_crs} />
|
||||
<InspectorField label="Geometry" value={selectedArea?.geometry?.type} />
|
||||
</div>
|
||||
<div className="inspector-card">
|
||||
<h3>Map feature</h3>
|
||||
{selectedMapFeature ? (
|
||||
<pre className="job-result">{JSON.stringify(selectedMapFeature.properties ?? {}, null, 2)}</pre>
|
||||
) : (
|
||||
<p className="muted">No map feature selected.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{activeTab === 'dataset' ? (
|
||||
<div className="inspector-tab-panel">
|
||||
<DatasetDetailPanel {...datasetDetailProps} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{activeTab === 'quality' ? (
|
||||
<div className="inspector-tab-panel">
|
||||
<div className="inspector-card">
|
||||
<h3>Latest QA/QC</h3>
|
||||
<InspectorField label="Check type" value={latestQualityCheck?.check_type} />
|
||||
<InspectorField label="Status" value={latestQualityCheck?.status} />
|
||||
<InspectorField label="Score" value={latestQualityCheck?.score} />
|
||||
<InspectorField label="Metrics" value={latestQualityCheck?.metrics.length} />
|
||||
<InspectorField label="Reference" value={latestQualityCheck?.reference_dataset_id} />
|
||||
</div>
|
||||
<div className="inspector-card">
|
||||
<h3>Latest export</h3>
|
||||
<InspectorField label="Created now" value={latestExport?.export_type} />
|
||||
<InspectorField label="Persisted type" value={latestPersistedExport?.export_type} />
|
||||
<InspectorField label="Status" value={latestPersistedExport?.status ?? latestExport?.status} />
|
||||
<InspectorField label="Path" value={latestPersistedExport?.storage_path ?? latestExport?.path} />
|
||||
<InspectorField label="Export count" value={exports.length} />
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{activeTab === 'ai' ? (
|
||||
<div className="inspector-tab-panel">
|
||||
<div className="inspector-card">
|
||||
<h3>Detection run</h3>
|
||||
<InspectorField label="Selected run" value={selectedDetectionRunId} />
|
||||
<InspectorField label="Status" value={selectedDetectionRun?.status} />
|
||||
<InspectorField label="Model" value={selectedDetectionRun?.model_name} />
|
||||
<InspectorField label="Detections loaded" value={detectionItems.length} />
|
||||
</div>
|
||||
<div className="inspector-card">
|
||||
<h3>Segmentation run</h3>
|
||||
<InspectorField label="Selected run" value={selectedSegmentationRunId} />
|
||||
<InspectorField label="Status" value={selectedSegmentationRun?.status} />
|
||||
<InspectorField label="Model" value={selectedSegmentationRun?.model_name} />
|
||||
<InspectorField label="Segmentations loaded" value={segmentationItems.length} />
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user