/** * NACHVERFOLGUNG: "Nachverfolgungsinformationen" — read-only modal showing the * data provenance of an imported gerbil (which source files contributed, how many * raw records were merged, how the parents were derived, and human-readable notes). * * The provenance arrives as a JSON string on the Gerbil DTO (produced by the Python * merge step). We parse it here defensively; anything unparseable / absent is shown * as "Keine Herkunftsdaten vorhanden." Mirrors the ReportErrorDialog modal pattern. */ import { useEffect } from 'react' import { de } from '../strings/de' import type { EntityProvenance } from '../api/types' import './provenanceDialog.css' interface ProvenanceDialogProps { open: boolean onClose: () => void /** Raw provenance JSON string from the entity (Gerbil/Contact/Litter; null = no import data). */ provenance?: string | null /** Optional entity label (e.g. "dieses Tiers", "dieses Kontakts") for the intro line. */ entityLabel?: string } /** Parse the JSON provenance string defensively; null on absence / parse error. */ function parseProvenance(raw?: string | null): EntityProvenance | null { if (!raw || !raw.trim()) return null try { const p = JSON.parse(raw) as Partial return { sourceFiles: Array.isArray(p.sourceFiles) ? p.sourceFiles : [], mergedRecordCount: typeof p.mergedRecordCount === 'number' ? p.mergedRecordCount : 0, fromWurfchronik: Boolean(p.fromWurfchronik), parentMethod: p.parentMethod, parentConfidence: p.parentConfidence, notes: Array.isArray(p.notes) ? p.notes : [], } } catch { return null } } export default function ProvenanceDialog({ open, onClose, provenance, entityLabel }: ProvenanceDialogProps) { // Close on Escape (only while mounted). useEffect(() => { if (!open) return const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() } window.addEventListener('keydown', onKey) return () => window.removeEventListener('keydown', onKey) }, [open, onClose]) if (!open) return null const t = de.provenance const p = parseProvenance(provenance) const method = p?.parentMethod ? (t.methods[p.parentMethod] ?? p.parentMethod) : null const confidence = p?.parentConfidence ? (t.confidences[p.parentConfidence] ?? p.parentConfidence) : null return (
e.stopPropagation()} >

{t.dialogTitle}

{!p ? (

{t.empty}

) : ( <>

{entityLabel ? t.introFor(entityLabel) : t.intro}

{t.mergedTitle}

{t.mergedCount(p.mergedRecordCount)}

{p.fromWurfchronik && (

📖 {t.fromWurfchronik}

)}
{t.sourceFilesTitle} — {t.sourceFilesCount(p.sourceFiles.length)}
{p.sourceFiles.length === 0 ? (

{t.empty}

) : (
    {p.sourceFiles.map((f) => (
  • {f}
  • ))}
)}
{(method || confidence) && (
{t.parentsTitle}
{method && (
{t.parentMethodLabel}
{method}
)} {confidence && (
{t.parentConfidenceLabel}
{confidence}
)}
)} {p.notes.length > 0 && (
{t.notesTitle}
    {p.notes.map((n) => (
  • {n}
  • ))}
)} )}
) }