'use client' import { useState, useEffect } from 'react' import { ArrowLeft, CheckCircle2, Trash2, Pencil, SkipForward, ChevronLeft, Scale, BookOpen, ExternalLink, AlertTriangle, FileText, Clock, } from 'lucide-react' import { CanonicalControl, BACKEND_URL, SeverityBadge, StateBadge, LicenseRuleBadge, CategoryBadge, TargetAudienceBadge, } from './helpers' // ============================================================================= // Compact Control Panel (used on both sides of the comparison) // ============================================================================= function ControlPanel({ ctrl, label, highlight }: { ctrl: CanonicalControl; label: string; highlight?: boolean }) { return (
{/* Panel Header */}
{label}
{ctrl.control_id}

{ctrl.title}

{/* Panel Content */}
{/* Objective */}

Ziel

{ctrl.objective}

{/* Rationale */} {ctrl.rationale && (

Begruendung

{ctrl.rationale}

)} {/* Source Citation (Rule 1+2) */} {ctrl.source_citation && (
Gesetzliche Grundlage
{ctrl.source_citation.source && (

{ctrl.source_citation.source} {ctrl.source_citation.article && ` — ${ctrl.source_citation.article}`} {ctrl.source_citation.paragraph && ` ${ctrl.source_citation.paragraph}`}

)}
)} {/* Requirements */} {ctrl.requirements.length > 0 && (

Anforderungen

    {ctrl.requirements.map((r, i) => (
  1. {r}
  2. ))}
)} {/* Test Procedure */} {ctrl.test_procedure.length > 0 && (

Pruefverfahren

    {ctrl.test_procedure.map((s, i) => (
  1. {s}
  2. ))}
)} {/* Open Anchors */} {ctrl.open_anchors.length > 0 && (
Referenzen ({ctrl.open_anchors.length})
{ctrl.open_anchors.map((a, i) => (
{a.framework} {a.ref}
))}
)} {/* Tags */} {ctrl.tags.length > 0 && (
{ctrl.tags.map(t => ( {t} ))}
)}
) } // ============================================================================= // ReviewCompare — Side-by-Side Duplicate Comparison // ============================================================================= interface ReviewCompareProps { ctrl: CanonicalControl onBack: () => void onReview: (controlId: string, action: string) => void onEdit: () => void reviewIndex: number reviewTotal: number onReviewPrev: () => void onReviewNext: () => void } export function ReviewCompare({ ctrl, onBack, onReview, onEdit, reviewIndex, reviewTotal, onReviewPrev, onReviewNext, }: ReviewCompareProps) { const [suspectedDuplicate, setSuspectedDuplicate] = useState(null) const [loading, setLoading] = useState(false) const [similarity, setSimilarity] = useState(null) // Load the suspected duplicate from generation_metadata.similar_controls useEffect(() => { const loadDuplicate = async () => { const similarControls = ctrl.generation_metadata?.similar_controls as Array<{ control_id: string; title: string; similarity: number }> | undefined if (!similarControls || similarControls.length === 0) { setSuspectedDuplicate(null) setSimilarity(null) return } const suspect = similarControls[0] setSimilarity(suspect.similarity) setLoading(true) try { const res = await fetch(`${BACKEND_URL}?endpoint=control&id=${encodeURIComponent(suspect.control_id)}`) if (res.ok) { const data = await res.json() setSuspectedDuplicate(data) } else { setSuspectedDuplicate(null) } } catch { setSuspectedDuplicate(null) } finally { setLoading(false) } } loadDuplicate() }, [ctrl.control_id, ctrl.generation_metadata]) return (
{/* Header */}
Duplikat-Vergleich {similarity !== null && ( {(similarity * 100).toFixed(1)}% Aehnlichkeit )}
{/* Navigation */}
{reviewIndex + 1} / {reviewTotal}
{/* Actions */}
{/* Side-by-Side Panels */}
{/* Left: Control to review */}
{/* Right: Suspected duplicate */}
{loading ? (
) : suspectedDuplicate ? ( ) : (
Kein Duplikat-Kandidat gefunden
)}
) }