'use client' import React, { useState, useEffect, useRef } from 'react' import { useSDK, Evidence as SDKEvidence, EvidenceType } from '@/lib/sdk' import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader' import { ConfidenceLevelBadge, TruthStatusBadge, GenerationModeBadge, ApprovalStatusBadge, } from './components/anti-fake-badges' // ============================================================================= // TYPES // ============================================================================= type DisplayEvidenceType = 'document' | 'screenshot' | 'log' | 'audit-report' | 'certificate' type DisplayFormat = 'pdf' | 'image' | 'text' | 'json' type DisplayStatus = 'valid' | 'expired' | 'pending-review' interface DisplayEvidence { id: string name: string description: string displayType: DisplayEvidenceType format: DisplayFormat controlId: string linkedRequirements: string[] linkedControls: string[] uploadedBy: string uploadedAt: Date validFrom: Date validUntil: Date | null status: DisplayStatus fileSize: string fileUrl: string | null // Anti-Fake-Evidence Phase 2 confidenceLevel: string | null truthStatus: string | null generationMode: string | null approvalStatus: string | null requiresFourEyes: boolean } // ============================================================================= // HELPER FUNCTIONS // ============================================================================= function mapEvidenceTypeToDisplay(type: EvidenceType): DisplayEvidenceType { switch (type) { case 'DOCUMENT': return 'document' case 'SCREENSHOT': return 'screenshot' case 'LOG': return 'log' case 'CERTIFICATE': return 'certificate' case 'AUDIT_REPORT': return 'audit-report' default: return 'document' } } function getEvidenceStatus(validUntil: Date | null): DisplayStatus { if (!validUntil) return 'pending-review' const now = new Date() if (validUntil < now) return 'expired' return 'valid' } // ============================================================================= // FALLBACK TEMPLATES // ============================================================================= interface EvidenceTemplate { id: string name: string description: string type: EvidenceType displayType: DisplayEvidenceType format: DisplayFormat controlId: string linkedRequirements: string[] linkedControls: string[] uploadedBy: string validityDays: number fileSize: string } const evidenceTemplates: EvidenceTemplate[] = [ { id: 'ev-dse-001', name: 'Datenschutzerklaerung v2.3', description: 'Aktuelle Datenschutzerklaerung fuer Website und App', type: 'DOCUMENT', displayType: 'document', format: 'pdf', controlId: 'ctrl-org-001', linkedRequirements: ['req-gdpr-13', 'req-gdpr-14'], linkedControls: ['ctrl-org-001'], uploadedBy: 'DSB', validityDays: 365, fileSize: '245 KB', }, { id: 'ev-pentest-001', name: 'Penetrationstest Report Q4/2024', description: 'Externer Penetrationstest durch Security-Partner', type: 'AUDIT_REPORT', displayType: 'audit-report', format: 'pdf', controlId: 'ctrl-tom-001', linkedRequirements: ['req-gdpr-32', 'req-iso-a12'], linkedControls: ['ctrl-tom-001', 'ctrl-tom-002', 'ctrl-det-001'], uploadedBy: 'IT Security Team', validityDays: 365, fileSize: '2.1 MB', }, { id: 'ev-iso-cert', name: 'ISO 27001 Zertifikat', description: 'Zertifizierung des ISMS', type: 'CERTIFICATE', displayType: 'certificate', format: 'pdf', controlId: 'ctrl-tom-001', linkedRequirements: ['req-iso-4.1', 'req-iso-5.1'], linkedControls: [], uploadedBy: 'QM Abteilung', validityDays: 365, fileSize: '156 KB', }, { id: 'ev-schulung-001', name: 'Schulungsnachweis Datenschutz 2024', description: 'Teilnehmerliste und Schulungsinhalt', type: 'DOCUMENT', displayType: 'document', format: 'pdf', controlId: 'ctrl-org-001', linkedRequirements: ['req-gdpr-39'], linkedControls: ['ctrl-org-001'], uploadedBy: 'HR Team', validityDays: 365, fileSize: '890 KB', }, { id: 'ev-rbac-001', name: 'Access Control Screenshot', description: 'Nachweis der RBAC-Konfiguration', type: 'SCREENSHOT', displayType: 'screenshot', format: 'image', controlId: 'ctrl-tom-001', linkedRequirements: ['req-gdpr-32'], linkedControls: ['ctrl-tom-001'], uploadedBy: 'Admin', validityDays: 0, fileSize: '1.2 MB', }, { id: 'ev-log-001', name: 'Audit Log Export', description: 'Monatlicher Audit-Log Export', type: 'LOG', displayType: 'log', format: 'json', controlId: 'ctrl-det-001', linkedRequirements: ['req-gdpr-32'], linkedControls: ['ctrl-det-001'], uploadedBy: 'System', validityDays: 90, fileSize: '4.5 MB', }, ] // ============================================================================= // COMPONENTS // ============================================================================= // ============================================================================= // CONFIDENCE FILTER COLORS (matching anti-fake-badges) // ============================================================================= const confidenceFilterColors: Record = { E0: 'bg-red-200 text-red-800', E1: 'bg-yellow-200 text-yellow-800', E2: 'bg-blue-200 text-blue-800', E3: 'bg-green-200 text-green-800', E4: 'bg-emerald-200 text-emerald-800', } // ============================================================================= // REVIEW MODAL // ============================================================================= function ReviewModal({ evidence, onClose, onSuccess }: { evidence: DisplayEvidence; onClose: () => void; onSuccess: () => void }) { const [confidenceLevel, setConfidenceLevel] = useState(evidence.confidenceLevel || 'E1') const [truthStatus, setTruthStatus] = useState(evidence.truthStatus || 'uploaded') const [reviewedBy, setReviewedBy] = useState('') const [submitting, setSubmitting] = useState(false) const [error, setError] = useState(null) const handleSubmit = async () => { if (!reviewedBy.trim()) { setError('Bitte E-Mail-Adresse angeben'); return } setSubmitting(true) setError(null) try { const res = await fetch(`/api/sdk/v1/compliance/evidence/${evidence.id}/review`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ confidence_level: confidenceLevel, truth_status: truthStatus, reviewed_by: reviewedBy }), }) if (!res.ok) { const err = await res.json().catch(() => ({ detail: 'Review fehlgeschlagen' })) throw new Error(typeof err.detail === 'string' ? err.detail : JSON.stringify(err.detail)) } onSuccess() } catch (err) { setError(err instanceof Error ? err.message : 'Unbekannter Fehler') } finally { setSubmitting(false) } } const confidenceLevels = [ { value: 'E0', label: 'E0 — Generiert' }, { value: 'E1', label: 'E1 — Manuell' }, { value: 'E2', label: 'E2 — Intern validiert' }, { value: 'E3', label: 'E3 — System-beobachtet' }, { value: 'E4', label: 'E4 — Extern auditiert' }, ] const truthStatuses = [ { value: 'generated', label: 'Generiert' }, { value: 'uploaded', label: 'Hochgeladen' }, { value: 'observed', label: 'Beobachtet' }, { value: 'validated', label: 'Validiert' }, { value: 'audited', label: 'Auditiert' }, ] return (
e.stopPropagation()}>

Evidence Reviewen

{evidence.name}

{/* Current values */}
Aktuelles Confidence-Level: {evidence.confidenceLevel || '—'}
Aktueller Truth-Status: {evidence.truthStatus || '—'}
{/* New confidence level */}
{/* New truth status */}
{/* Reviewed by */}
setReviewedBy(e.target.value)} placeholder="reviewer@unternehmen.de" className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
{/* Four-eyes warning */} {evidence.requiresFourEyes && evidence.approvalStatus !== 'approved' && (

4-Augen-Prinzip aktiv

Dieser Nachweis erfordert eine zusaetzliche Freigabe durch einen zweiten Reviewer.

)} {/* Error */} {error && (
{error}
)} {/* Actions */}
) } // ============================================================================= // REJECT MODAL // ============================================================================= function RejectModal({ evidence, onClose, onSuccess }: { evidence: DisplayEvidence; onClose: () => void; onSuccess: () => void }) { const [reviewedBy, setReviewedBy] = useState('') const [rejectionReason, setRejectionReason] = useState('') const [submitting, setSubmitting] = useState(false) const [error, setError] = useState(null) const handleSubmit = async () => { if (!reviewedBy.trim()) { setError('Bitte E-Mail-Adresse angeben'); return } if (!rejectionReason.trim()) { setError('Bitte Ablehnungsgrund angeben'); return } setSubmitting(true) setError(null) try { const res = await fetch(`/api/sdk/v1/compliance/evidence/${evidence.id}/reject`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ reviewed_by: reviewedBy, rejection_reason: rejectionReason }), }) if (!res.ok) { const err = await res.json().catch(() => ({ detail: 'Ablehnung fehlgeschlagen' })) throw new Error(typeof err.detail === 'string' ? err.detail : JSON.stringify(err.detail)) } onSuccess() } catch (err) { setError(err instanceof Error ? err.message : 'Unbekannter Fehler') } finally { setSubmitting(false) } } return (
e.stopPropagation()}>

Evidence Ablehnen

{evidence.name}

{/* Reviewed by */}
setReviewedBy(e.target.value)} placeholder="reviewer@unternehmen.de" className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-red-500 focus:border-transparent" />
{/* Rejection reason */}