feat(records): complete eAkte reference journey
Module Package Release / publish-packages (push) Successful in 12s
Module Package Release / publish-packages (push) Successful in 12s
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@govoplan/records-webui",
|
||||
"version": "0.1.19",
|
||||
"version": "0.1.20",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -89,9 +89,15 @@ export type RecordItem = {
|
||||
content_sha256?: string | null;
|
||||
content_type?: string | null;
|
||||
size_bytes?: number | null;
|
||||
source_valid_from?: string | null;
|
||||
source_valid_to?: string | null;
|
||||
source_recorded_at?: string | null;
|
||||
launch_url?: string | null;
|
||||
filed_at: string;
|
||||
filed_by?: string | null;
|
||||
institutional_context: Record<string, unknown>;
|
||||
source_metadata: Record<string, unknown>;
|
||||
filing_metadata: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type RecordChronology = {
|
||||
@@ -147,10 +153,15 @@ export type RecordTransferPackage = {
|
||||
provider_id: string;
|
||||
profile: string;
|
||||
status: string;
|
||||
authority_mode: string;
|
||||
manifest: Record<string, unknown>;
|
||||
manifest_sha256: string;
|
||||
receipt: Record<string, unknown>;
|
||||
receipt_sha256?: string | null;
|
||||
external_reference?: string | null;
|
||||
recovery_operation_id?: string | null;
|
||||
simulated: boolean;
|
||||
institutional_context: Record<string, unknown>;
|
||||
recorded_at: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -314,7 +314,7 @@ function LifecycleEvidence({ detail, onRelease, canWrite }: { detail: RecordDeta
|
||||
<div><dt>Appraisal</dt><dd>{record.appraisal_state ? humanize(record.appraisal_state) : "Not appraised"}</dd></div>
|
||||
</dl>
|
||||
{detail.volumes.length > 0 && <div className="records-evidence-list"><strong>Volumes</strong>{detail.volumes.map((volume) => <div key={volume.volume_id}><span>{volume.sequence}. {volume.label}</span><StatusBadge status={volume.state === "open" ? "active" : "neutral"} label={humanize(volume.state)} /></div>)}</div>}
|
||||
{detail.holds.length > 0 && <div className="records-evidence-list"><strong>Holds</strong>{detail.holds.map((hold) => <div key={hold.hold_id}><span><b>{hold.authority}</b> · {hold.reason}</span><span className="records-evidence-actions"><StatusBadge status={hold.status === "active" ? "danger" : "neutral"} label={humanize(hold.status)} />{hold.status === "active" && <Button type="button" variant="ghost" iconOnly aria-label="Release hold" title="Release hold" onClick={() => onRelease(hold.hold_id)} disabledReason={!canWrite ? "Your account may not release holds." : undefined}><Ban size={15} aria-hidden="true" /></Button>}</span></div>)}</div>}
|
||||
{detail.holds.length > 0 && <div className="records-evidence-list"><strong>Holds</strong>{detail.holds.map((hold) => <div key={hold.hold_id}><span><b>{hold.authority}</b> · {hold.reason}{hold.policy_refs.length > 0 ? ` · policy ${hold.policy_refs.join(", ")}` : ""}</span><span className="records-evidence-actions"><StatusBadge status={hold.status === "active" ? "danger" : "neutral"} label={humanize(hold.status)} />{hold.status === "active" && <Button type="button" variant="ghost" iconOnly aria-label="Release hold" title="Release hold" onClick={() => onRelease(hold.hold_id)} disabledReason={!canWrite ? "Your account may not release holds." : undefined}><Ban size={15} aria-hidden="true" /></Button>}</span></div>)}</div>}
|
||||
{detail.dispositions.map((item) => <DispositionEvidence key={item.disposition_id} item={item} />)}
|
||||
{detail.transfer_packages.map((item) => <TransferEvidence key={item.package_id} item={item} />)}
|
||||
</div>
|
||||
@@ -322,11 +322,20 @@ function LifecycleEvidence({ detail, onRelease, canWrite }: { detail: RecordDeta
|
||||
}
|
||||
|
||||
function DispositionEvidence({ item }: { item: RecordDisposition }) {
|
||||
return <div className="records-evidence-list"><strong>Disposition</strong><div><span><b>{humanize(item.action)}</b> · {item.reason}</span><StatusBadge status={item.status === "approved" ? "active" : item.status === "review_unavailable" ? "danger" : "warning"} label={humanize(item.status)} /></div>{item.approval_request_id && <small>Approval: {item.approval_request_id}</small>}</div>;
|
||||
return <div className="records-evidence-list"><strong>Disposition</strong><div><span><b>{humanize(item.action)}</b> · {item.reason}</span><StatusBadge status={item.status === "approved" ? "active" : item.status === "review_unavailable" ? "danger" : "warning"} label={humanize(item.status)} /></div>{item.policy_refs.length > 0 && <small>Policy: {item.policy_refs.join(", ")}</small>}{item.approval_request_id && <small>Approval: {item.approval_request_id}</small>}</div>;
|
||||
}
|
||||
|
||||
function TransferEvidence({ item }: { item: RecordTransferPackage }) {
|
||||
return <div className="records-evidence-list"><strong>Transfer package</strong><div><span><b>{item.profile}</b> · {item.manifest_sha256.slice(0, 12)}</span><StatusBadge status={item.status === "simulated_accepted" ? "warning" : item.status === "accepted" ? "active" : "neutral"} label={humanize(item.status)} /></div>{item.simulated && <small>Simulation only; no archival custody.</small>}</div>;
|
||||
const receiptMetadata = typeof item.receipt.metadata === "object" && item.receipt.metadata != null
|
||||
? item.receipt.metadata as Record<string, unknown>
|
||||
: {};
|
||||
const custodyTransferred = receiptMetadata.custody_transferred === true;
|
||||
const custodySummary = item.simulated
|
||||
? "Simulation only; no archival custody."
|
||||
: custodyTransferred
|
||||
? "Archive custody was transferred."
|
||||
: "Archive custody was not transferred.";
|
||||
return <div className="records-evidence-list"><strong>Transfer package</strong><div><span><b>{item.profile}</b> · {item.manifest_sha256.slice(0, 12)}</span><StatusBadge status={item.status === "simulated_accepted" ? "warning" : item.status === "accepted" ? "active" : "neutral"} label={humanize(item.status)} /></div><small>{custodySummary} Authority: {humanize(item.authority_mode)}.</small></div>;
|
||||
}
|
||||
|
||||
function RecoveryResult({ value, loading }: { value: Record<string, unknown> | null; loading: boolean }) {
|
||||
|
||||
@@ -464,6 +464,7 @@ function RecordDetailPanel({
|
||||
onChanged: () => void;
|
||||
}) {
|
||||
const record = detail.record;
|
||||
const governanceContext = recordContextFacts(record.institutional_context);
|
||||
return (
|
||||
<PageScrollViewport className="records-detail-scroll">
|
||||
<div className="records-detail-header">
|
||||
@@ -496,6 +497,17 @@ function RecordDetailPanel({
|
||||
|
||||
{record.description && <p className="records-description">{record.description}</p>}
|
||||
|
||||
{governanceContext.length > 0 && (
|
||||
<section className="records-detail-section" data-help-context-id="records.governance-context">
|
||||
<div className="records-section-heading"><h2>Governance context</h2></div>
|
||||
<dl className="records-context-facts">
|
||||
{governanceContext.map(([label, value]) => (
|
||||
<div key={label}><dt>{label}</dt><dd>{value}</dd></div>
|
||||
))}
|
||||
</dl>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<RecordLifecyclePanel
|
||||
detail={detail}
|
||||
archiveProviders={archiveProviders}
|
||||
@@ -525,6 +537,15 @@ function RecordDetailPanel({
|
||||
<time>{formatDateTime(item.filed_at)}</time>
|
||||
</div>
|
||||
<p>{item.filing_reason}</p>
|
||||
<dl className="records-item-evidence">
|
||||
<div><dt>Authority</dt><dd>{humanize(item.authority_mode)}</dd></div>
|
||||
<div><dt>Relationship</dt><dd>{humanize(item.relationship)}</dd></div>
|
||||
<div><dt>Purpose</dt><dd>{item.purpose}</dd></div>
|
||||
<div><dt>Source recorded</dt><dd>{formatDateTime(item.source_recorded_at)}</dd></div>
|
||||
{typeof item.source_metadata.evidence_role === "string" && (
|
||||
<div><dt>Evidence role</dt><dd>{humanize(item.source_metadata.evidence_role)}</dd></div>
|
||||
)}
|
||||
</dl>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -882,6 +903,27 @@ function formatBytes(value?: number | null): string {
|
||||
return `${(value / 1024 / 1024).toFixed(1)} MiB`;
|
||||
}
|
||||
|
||||
function recordContextFacts(context: Record<string, unknown>): Array<[string, string]> {
|
||||
const fields: Array<[string, string]> = [
|
||||
["Service", "service_id"],
|
||||
["Case", "case_id"],
|
||||
["Applicant", "applicant_party_id"],
|
||||
["Representative", "representative_party_id"],
|
||||
["Representation", "representation_id"],
|
||||
["Authority", "authority_id"],
|
||||
["Responsible function", "responsible_function_id"],
|
||||
["Mandate", "mandate_id"],
|
||||
["Legal basis", "legal_basis"],
|
||||
["Retention policy", "retention_policy_ref"]
|
||||
];
|
||||
return fields.flatMap(([label, key]) => {
|
||||
const value = context[key];
|
||||
if (typeof value === "string" && value.trim()) return [[label, value]];
|
||||
if (Array.isArray(value) && value.length > 0) return [[label, value.map(String).join(", ")]];
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
function humanize(value: string): string {
|
||||
return value.replace(/[_:.\-]+/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
|
||||
}
|
||||
|
||||
@@ -41,6 +41,16 @@ const en = {
|
||||
"Retention input": "Retention input",
|
||||
"Not classified": "Not classified",
|
||||
"Not configured": "Not configured",
|
||||
"Governance context": "Governance context",
|
||||
"Service": "Service",
|
||||
"Case": "Case",
|
||||
"Applicant": "Applicant",
|
||||
"Representative": "Representative",
|
||||
"Representation": "Representation",
|
||||
"Responsible function": "Responsible function",
|
||||
"Mandate": "Mandate",
|
||||
"Legal basis": "Legal basis",
|
||||
"Retention policy": "Retention policy",
|
||||
"Contents": "Contents",
|
||||
"No items have been filed in this temporal view.": "No items have been filed in this temporal view.",
|
||||
"Chronology": "Chronology",
|
||||
@@ -66,6 +76,9 @@ const en = {
|
||||
"Source object ID": "Source object ID",
|
||||
"Exact source revision": "Exact source revision",
|
||||
"Filing reason": "Filing reason",
|
||||
"Relationship": "Relationship",
|
||||
"Source recorded": "Source recorded",
|
||||
"Evidence role": "Evidence role",
|
||||
"Not set": "Not set",
|
||||
"Size unavailable": "Size unavailable",
|
||||
"Your account may view records but may not create them.": "Your account may view records but may not create them.",
|
||||
@@ -121,6 +134,8 @@ const en = {
|
||||
"Disposition": "Disposition",
|
||||
"Transfer package": "Transfer package",
|
||||
"Simulation only; no archival custody.": "Simulation only; no archival custody.",
|
||||
"Archive custody was transferred.": "Archive custody was transferred.",
|
||||
"Archive custody was not transferred.": "Archive custody was not transferred.",
|
||||
"Volume label": "Volume label",
|
||||
"Authority": "Authority",
|
||||
"Explicit retention trigger": "Explicit retention trigger",
|
||||
@@ -194,6 +209,16 @@ const de: Record<keyof typeof en, string> = {
|
||||
"Retention input": "Aufbewahrungsvorgabe",
|
||||
"Not classified": "Nicht klassifiziert",
|
||||
"Not configured": "Nicht konfiguriert",
|
||||
"Governance context": "Governance-Kontext",
|
||||
"Service": "Leistung",
|
||||
"Case": "Vorgang",
|
||||
"Applicant": "Antragstellende Person",
|
||||
"Representative": "Vertretende Person",
|
||||
"Representation": "Vertretung",
|
||||
"Responsible function": "Zuständige Funktion",
|
||||
"Mandate": "Mandat",
|
||||
"Legal basis": "Rechtsgrundlage",
|
||||
"Retention policy": "Aufbewahrungsrichtlinie",
|
||||
"Contents": "Inhalt",
|
||||
"No items have been filed in this temporal view.": "In dieser temporalen Ansicht sind keine Objekte veraktet.",
|
||||
"Chronology": "Chronologie",
|
||||
@@ -219,6 +244,9 @@ const de: Record<keyof typeof en, string> = {
|
||||
"Source object ID": "ID des Quellobjekts",
|
||||
"Exact source revision": "Exakte Quellrevision",
|
||||
"Filing reason": "Veraktungsbegründung",
|
||||
"Relationship": "Beziehung",
|
||||
"Source recorded": "Quelle erfasst am",
|
||||
"Evidence role": "Nachweisrolle",
|
||||
"Not set": "Nicht gesetzt",
|
||||
"Size unavailable": "Größe nicht verfügbar",
|
||||
"Your account may view records but may not create them.": "Ihr Konto darf Akten einsehen, aber nicht anlegen.",
|
||||
@@ -274,6 +302,8 @@ const de: Record<keyof typeof en, string> = {
|
||||
"Disposition": "Aussonderung",
|
||||
"Transfer package": "Übergabepaket",
|
||||
"Simulation only; no archival custody.": "Nur Simulation; keine Archivverwahrung.",
|
||||
"Archive custody was transferred.": "Die Archivverwahrung wurde übertragen.",
|
||||
"Archive custody was not transferred.": "Die Archivverwahrung wurde nicht übertragen.",
|
||||
"Volume label": "Bandbezeichnung",
|
||||
"Authority": "Anordnende Stelle",
|
||||
"Explicit retention trigger": "Expliziter Aufbewahrungsbeginn",
|
||||
|
||||
@@ -253,6 +253,42 @@
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.records-context-facts,
|
||||
.records-item-evidence {
|
||||
display: grid;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.records-context-facts {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1px;
|
||||
overflow: hidden;
|
||||
border: var(--border-line);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--line);
|
||||
}
|
||||
|
||||
.records-context-facts > div {
|
||||
min-width: 0;
|
||||
padding: 9px 10px;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.records-context-facts dt,
|
||||
.records-item-evidence dt {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.records-context-facts dd,
|
||||
.records-item-evidence dd {
|
||||
overflow-wrap: anywhere;
|
||||
margin: 3px 0 0;
|
||||
color: var(--text-strong);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.records-lifecycle-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -424,6 +460,17 @@
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.records-item-evidence {
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.records-item-evidence > div {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.records-chronology > div {
|
||||
position: relative;
|
||||
display: grid;
|
||||
@@ -564,6 +611,8 @@
|
||||
}
|
||||
|
||||
.records-facts,
|
||||
.records-context-facts,
|
||||
.records-item-evidence,
|
||||
.records-lifecycle-evidence > dl,
|
||||
.records-recovery-result dl,
|
||||
.records-access-explanation dl,
|
||||
|
||||
Reference in New Issue
Block a user