Add governed source freshness audit
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import type { SourceFreshnessItem, SourceFreshnessReport, SourceFreshnessStatus } from '../../types'
|
||||
|
||||
interface SourceFreshnessPanelProps {
|
||||
report: SourceFreshnessReport | null
|
||||
loading: boolean
|
||||
error: string | null
|
||||
onRefresh: () => void
|
||||
}
|
||||
|
||||
function statusLabel(status: SourceFreshnessStatus): string {
|
||||
if (status === 'current') return 'actueel'
|
||||
if (status === 'due') return 'controle nodig'
|
||||
if (status === 'review_required') return 'nakijken'
|
||||
return 'lokaal'
|
||||
}
|
||||
|
||||
function formatDate(value?: string | null): string {
|
||||
if (!value) return 'geen datum'
|
||||
return new Intl.DateTimeFormat('nl-BE', { dateStyle: 'medium' }).format(new Date(value))
|
||||
}
|
||||
|
||||
function integrityIssueCount(item: SourceFreshnessItem): number {
|
||||
return Object.values(item.integrity).reduce((total, value) => total + value, 0)
|
||||
}
|
||||
|
||||
function SourceRow({ item }: { item: SourceFreshnessItem }): JSX.Element {
|
||||
const issueCount = integrityIssueCount(item)
|
||||
return (
|
||||
<div className={`source-freshness-row source-freshness-row-${item.status}`}>
|
||||
<div className="source-freshness-main">
|
||||
<div>
|
||||
<strong>{item.display_name}</strong>
|
||||
<span>{item.dataset_count} datasets · {item.version_count} versies</span>
|
||||
</div>
|
||||
<span className="source-freshness-status">{statusLabel(item.status)}</span>
|
||||
</div>
|
||||
<p>{item.reason}</p>
|
||||
<div className="source-freshness-meta">
|
||||
<span>Laatste import: {formatDate(item.latest_imported_at)}</span>
|
||||
{item.latest_source_version ? <span>Editie: {item.latest_source_version}</span> : null}
|
||||
{item.historical_series ? <span>Historische reeks</span> : null}
|
||||
{issueCount ? <span>{issueCount} integriteitsafwijking{issueCount === 1 ? '' : 'en'}</span> : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function SourceFreshnessPanel({ report, loading, error, onRefresh }: SourceFreshnessPanelProps): JSX.Element {
|
||||
const attentionItems = report?.items.filter((item) => item.status === 'due' || item.status === 'review_required') ?? []
|
||||
const summary = report?.summary
|
||||
|
||||
return (
|
||||
<section className="source-freshness-panel" aria-label="Bronactualiteit en versie-integriteit">
|
||||
<div className="source-freshness-header">
|
||||
<div>
|
||||
<p className="eyebrow">Bronbeheer</p>
|
||||
<h2>Actualiteit en versiecontrole</h2>
|
||||
<p>Controleert lokale publicaties, versies en bestanden zonder externe bronnen automatisch te wijzigen.</p>
|
||||
</div>
|
||||
<button className="secondary-action" type="button" onClick={onRefresh} disabled={loading}>
|
||||
{loading ? 'Controleren...' : 'Opnieuw controleren'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{error ? <p className="inline-error">{error}</p> : null}
|
||||
{!report && !error ? <p className="empty-state">{loading ? 'Bronstatus wordt gecontroleerd...' : 'Geen projectbronstatus beschikbaar.'}</p> : null}
|
||||
{report && summary ? (
|
||||
<>
|
||||
<div className="source-freshness-summary" aria-label="Samenvatting broncontrole">
|
||||
<span><strong>{summary.current_count}</strong> actueel</span>
|
||||
<span><strong>{summary.due_count}</strong> controle nodig</span>
|
||||
<span><strong>{summary.review_required_count}</strong> nakijken</span>
|
||||
<span><strong>{summary.integrity_issue_count}</strong> integriteitsfouten</span>
|
||||
</div>
|
||||
{attentionItems.length ? (
|
||||
<div className="source-freshness-attention" aria-label="Bronnen die aandacht vragen">
|
||||
{attentionItems.map((item) => <SourceRow item={item} key={item.source_name} />)}
|
||||
</div>
|
||||
) : (
|
||||
<p className="source-freshness-ok">Alle externe bronpublicaties hebben geldige lokale versie-evidentie.</p>
|
||||
)}
|
||||
<details className="source-freshness-details">
|
||||
<summary>
|
||||
<span>Alle {summary.source_count} bronnen bekijken</span>
|
||||
<strong>gecontroleerd {formatDate(report.generated_at)}</strong>
|
||||
</summary>
|
||||
<div className="source-freshness-list">
|
||||
{report.items.map((item) => <SourceRow item={item} key={item.source_name} />)}
|
||||
</div>
|
||||
<p className="source-freshness-limitation">{report.limitations.join(' ')}</p>
|
||||
</details>
|
||||
</>
|
||||
) : null}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user