Add governed GRB refresh workflow
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-16 17:59:00 +02:00
parent bbf56d762c
commit eed6ee796e
21 changed files with 1313 additions and 1 deletions
@@ -1,4 +1,7 @@
import type {
GrbRefreshLayerPlan,
GrbRefreshLayerStatus,
GrbRefreshPlan,
SourceCatalogComparisonStatus,
SourceCatalogProbeItem,
SourceCatalogProbeReport,
@@ -17,6 +20,9 @@ interface SourceFreshnessPanelProps {
catalogLoading: boolean
catalogError: string | null
onProbeCatalogs: (force: boolean) => void
grbRefreshPlan: GrbRefreshPlan | null
grbRefreshPlanLoading: boolean
grbRefreshPlanError: string | null
}
function statusLabel(status: SourceFreshnessStatus): string {
@@ -46,6 +52,35 @@ function formatDate(value?: string | null): string {
return new Intl.DateTimeFormat('nl-BE', { dateStyle: 'medium' }).format(new Date(value))
}
function formatCount(value?: number | null): string {
return value == null ? 'nog niet bekend' : value.toLocaleString('nl-BE')
}
function grbLayerStatusLabel(status: GrbRefreshLayerStatus): string {
if (status === 'current') return 'actueel'
if (status === 'update_available') return 'update beschikbaar'
if (status === 'not_loaded') return 'nog niet ingeladen'
if (status === 'remote_unavailable') return 'bron niet bereikbaar'
return 'beoordeling nodig'
}
function GrbRefreshLayerRow({ item, remoteEdition }: { item: GrbRefreshLayerPlan; remoteEdition?: string | null }): JSX.Element {
return (
<div className={`grb-refresh-layer grb-refresh-layer-${item.status}`}>
<div>
<strong>{item.display_name}</strong>
<span>{grbLayerStatusLabel(item.status)}</span>
</div>
<p>{item.action_message}</p>
<div className="source-freshness-meta">
<span>Lokaal: {item.local_source_version ?? 'niet aanwezig'}</span>
<span>Officieel: {remoteEdition ?? 'onbekend'}</span>
<span>{formatCount(item.local_feature_count)} objecten lokaal</span>
</div>
</div>
)
}
function integrityIssueCount(item: SourceFreshnessItem): number {
return Object.values(item.integrity).reduce((total, value) => total + value, 0)
}
@@ -102,6 +137,9 @@ export function SourceFreshnessPanel({
catalogLoading,
catalogError,
onProbeCatalogs,
grbRefreshPlan,
grbRefreshPlanLoading,
grbRefreshPlanError,
}: SourceFreshnessPanelProps): JSX.Element {
const attentionItems = report?.items.filter((item) => item.status === 'due' || item.status === 'review_required') ?? []
const summary = report?.summary
@@ -180,6 +218,27 @@ export function SourceFreshnessPanel({
</p>
</>
) : null}
{grbRefreshPlanLoading ? <p className="source-catalog-empty">Veilig vernieuwingsplan wordt opgebouwd...</p> : null}
{grbRefreshPlanError ? <p className="inline-error">{grbRefreshPlanError}</p> : null}
{grbRefreshPlan ? (
<div className="grb-refresh-plan">
<div className="grb-refresh-plan-heading">
<div>
<strong>Veilige GRB-vernieuwing</strong>
<span>{grbRefreshPlan.message}</span>
</div>
<span>{grbRefreshPlan.summary.update_available_count} updates</span>
</div>
<div className="grb-refresh-layer-list">
{grbRefreshPlan.layers.map((item) => (
<GrbRefreshLayerRow item={item} remoteEdition={grbRefreshPlan.remote_edition_date} key={item.theme} />
))}
</div>
<p className="source-freshness-limitation">
Een serveroperator controleert eerst exacte aantallen en checksums. Bestaande snapshots blijven altijd behouden.
</p>
</div>
) : null}
</div>
</>
) : null}