Polish export handoff artifacts
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-06-21 13:08:42 +02:00
parent 91567d2365
commit 0a9054c9ec
7 changed files with 232 additions and 0 deletions
@@ -51,6 +51,17 @@ function formatExportType(value: string): string {
.join(' ')
}
function formatExportCreated(value?: string | null): string {
if (!value) {
return 'created time unavailable'
}
return value
}
function getLatestExportByType(exports: ExportRead[], exportType: string): ExportRead | null {
return exports.find((item) => item.export_type === exportType) ?? null
}
function exportMatchesSearch(item: ExportRead, query: string): boolean {
const normalizedQuery = query.trim().toLowerCase()
if (!normalizedQuery) {
@@ -90,6 +101,41 @@ export function ExportCenter({
const handoffStatus = selectedProjectId ? 'project selected' : 'select project'
const exportTypes = useMemo(() => Array.from(new Set(exports.map((item) => item.export_type))).sort(), [exports])
const exportStatuses = useMemo(() => Array.from(new Set(exports.map((item) => item.status))).sort(), [exports])
const latestHandoffArtifacts = useMemo(
() => [
{
key: 'project_report_html',
label: 'Project report',
detail: 'HTML report for human review',
item: getLatestExportByType(exports, 'project_report_html'),
},
{
key: 'project_metadata_json',
label: 'Project metadata',
detail: 'Machine-readable project inventory',
item: getLatestExportByType(exports, 'project_metadata_json'),
},
{
key: 'dataset_geojson',
label: 'Dataset GeoJSON',
detail: 'Selected vector/reference layer',
item: getLatestExportByType(exports, 'dataset_geojson'),
},
{
key: 'detection_geojson',
label: 'Detection GeoJSON',
detail: 'Persisted detection geometries',
item: getLatestExportByType(exports, 'detection_geojson'),
},
{
key: 'segmentation_geojson',
label: 'Segmentation GeoJSON',
detail: 'Persisted segmentation polygons',
item: getLatestExportByType(exports, 'segmentation_geojson'),
},
],
[exports],
)
const filteredExports = useMemo(
() =>
exports.filter((item) => {
@@ -161,6 +207,42 @@ export function ExportCenter({
</div>
</div>
<div className="latest-artifact-surface" aria-label="Latest handoff artifacts">
<div>
<h3>Latest handoff artifacts</h3>
<p className="muted">Open the newest artifact of each handoff type without searching the full export history.</p>
</div>
<div className="latest-artifact-grid">
{latestHandoffArtifacts.map((artifact) => {
const item = artifact.item
return (
<div className={item ? 'latest-artifact-card' : 'latest-artifact-card latest-artifact-card-empty'} key={artifact.key}>
<span>{artifact.label}</span>
<strong>{item ? item.status : 'No artifact yet'}</strong>
<p>{item ? compactPath(item.storage_path) : artifact.detail}</p>
{item ? (
<>
<small>{formatExportCreated(item.created_at)}</small>
<div className="latest-artifact-actions">
{canPreviewJson(item) ? (
<button type="button" className="secondary-action" onClick={() => onPreviewContent(item.id)}>
Preview
</button>
) : (
<span className="status-badge">download-only</span>
)}
<button type="button" className="primary-action" onClick={() => onDownload(item.id)}>
Download
</button>
</div>
</>
) : null}
</div>
)
})}
</div>
</div>
<div className="export-actions-surface" aria-label="Export artifact actions">
<div className="panel-title-row">
<div>