'use client' import { useEffect, useState } from 'react' interface BulkDiffStep { from: string from_version: string | null to: string to_version: string | null created_at: string | null kind: 'text' | 'binary' added_lines: number removed_lines: number metadata_diff_fields: string[] } interface BulkDiffResponse { cid_latest: string cid_baseline: string versions: number steps: BulkDiffStep[] totals: { added_lines: number removed_lines: number metadata_fields_changed: number binary_steps: number } note?: string } interface Props { cid: string onClose: () => void } function shorten(cid: string): string { if (cid.length <= 14) return cid return cid.slice(0, 8) + '…' + cid.slice(-6) } export default function BulkDiffPanel({ cid, onClose }: Props) { const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { let cancel = false setLoading(true) setError(null) fetch(`/api/sdk/v1/dsms/documents/${encodeURIComponent(cid)}/bulk-diff`) .then(async (r) => { if (!r.ok) throw new Error(`HTTP ${r.status}`) const json = (await r.json()) as BulkDiffResponse if (!cancel) setData(json) }) .catch((e) => { if (!cancel) setError(e?.message || 'Fehler beim Laden') }) .finally(() => { if (!cancel) setLoading(false) }) return () => { cancel = true } }, [cid]) return (

Aggregierter Diff: V1 → V_latest

{loading &&
Bulk-Diff wird berechnet…
} {error &&
{error}
} {!loading && !error && data && ( <>
{data.totals.binary_steps > 0 && (
{data.totals.binary_steps} von {data.steps.length} Schritten binaer — Text-Diff nicht moeglich.
)} {data.steps.length === 0 ? (
{data.note || 'Keine Vorgaengerversion vorhanden.'}
) : (
{data.steps.map((step, i) => ( ))}
Schritt Datum Typ + Metadaten-Felder
V{step.from_version || '?'} → V{step.to_version || '?'}
{shorten(step.from)} → {shorten(step.to)}
{step.created_at ? new Date(step.created_at).toLocaleDateString('de-DE') : '—'} {step.kind === 'binary' ? 'binaer' : 'text'} {step.kind === 'binary' ? '—' : step.added_lines} {step.kind === 'binary' ? '—' : step.removed_lines} {step.metadata_diff_fields.length === 0 ? '—' : step.metadata_diff_fields.slice(0, 3).join(', ') + (step.metadata_diff_fields.length > 3 ? ` (+${step.metadata_diff_fields.length - 3})` : '')}
)} )}
) } function Stat({ label, value, tone }: { label: string; value: number; tone: 'positive' | 'negative' | 'neutral' }) { const color = tone === 'positive' ? 'text-emerald-700 dark:text-emerald-400' : tone === 'negative' ? 'text-red-700 dark:text-red-400' : 'text-gray-800 dark:text-gray-200' return (
{value.toLocaleString('de-DE')}
{label}
) }