'use client' import { useState, useEffect, useMemo, useCallback } from 'react' import { Shield, Search, ChevronRight, ArrowLeft, ExternalLink, Filter, AlertTriangle, CheckCircle2, Info, Lock, FileText, BookOpen, Scale, Plus, Pencil, Trash2, Save, X, Zap, BarChart3, Eye, RefreshCw, Clock, } from 'lucide-react' // ============================================================================= // TYPES // ============================================================================= interface OpenAnchor { framework: string ref: string url: string } interface EvidenceItem { type: string description: string } interface CanonicalControl { id: string framework_id: string control_id: string title: string objective: string rationale: string scope: { platforms?: string[] components?: string[] data_classes?: string[] } requirements: string[] test_procedure: string[] evidence: EvidenceItem[] severity: string risk_score: number | null implementation_effort: string | null evidence_confidence: number | null open_anchors: OpenAnchor[] release_state: string tags: string[] license_rule?: number | null source_original_text?: string | null source_citation?: Record | null customer_visible?: boolean generation_metadata?: Record | null created_at: string updated_at: string } interface Framework { id: string framework_id: string name: string version: string description: string release_state: string } // ============================================================================= // CONSTANTS // ============================================================================= const SEVERITY_CONFIG: Record }> = { critical: { bg: 'bg-red-100 text-red-800', label: 'Kritisch', icon: AlertTriangle }, high: { bg: 'bg-orange-100 text-orange-800', label: 'Hoch', icon: AlertTriangle }, medium: { bg: 'bg-yellow-100 text-yellow-800', label: 'Mittel', icon: Info }, low: { bg: 'bg-green-100 text-green-800', label: 'Niedrig', icon: CheckCircle2 }, } const EFFORT_LABELS: Record = { s: 'Klein (S)', m: 'Mittel (M)', l: 'Gross (L)', xl: 'Sehr gross (XL)', } const BACKEND_URL = '/api/sdk/v1/canonical' const EMPTY_CONTROL = { framework_id: 'bp_security_v1', control_id: '', title: '', objective: '', rationale: '', scope: { platforms: [] as string[], components: [] as string[], data_classes: [] as string[] }, requirements: [''], test_procedure: [''], evidence: [{ type: '', description: '' }], severity: 'medium', risk_score: null as number | null, implementation_effort: 'm' as string | null, open_anchors: [{ framework: '', ref: '', url: '' }], release_state: 'draft', tags: [] as string[], } // ============================================================================= // HELPERS // ============================================================================= function SeverityBadge({ severity }: { severity: string }) { const config = SEVERITY_CONFIG[severity] || SEVERITY_CONFIG.medium const Icon = config.icon return ( {config.label} ) } function StateBadge({ state }: { state: string }) { const config: Record = { draft: 'bg-gray-100 text-gray-600', review: 'bg-blue-100 text-blue-700', approved: 'bg-green-100 text-green-700', deprecated: 'bg-red-100 text-red-600', needs_review: 'bg-yellow-100 text-yellow-800', too_close: 'bg-red-100 text-red-700', duplicate: 'bg-orange-100 text-orange-700', } const labels: Record = { needs_review: 'Review noetig', too_close: 'Zu aehnlich', duplicate: 'Duplikat', } return ( {labels[state] || state} ) } function LicenseRuleBadge({ rule }: { rule: number | null | undefined }) { if (!rule) return null const config: Record = { 1: { bg: 'bg-green-100 text-green-700', label: 'Free Use' }, 2: { bg: 'bg-blue-100 text-blue-700', label: 'Zitation' }, 3: { bg: 'bg-amber-100 text-amber-700', label: 'Reformuliert' }, } const c = config[rule] if (!c) return null return {c.label} } function getDomain(controlId: string): string { return controlId.split('-')[0] || '' } // ============================================================================= // CONTROL FORM COMPONENT // ============================================================================= function ControlForm({ initial, onSave, onCancel, saving, }: { initial: typeof EMPTY_CONTROL onSave: (data: typeof EMPTY_CONTROL) => void onCancel: () => void saving: boolean }) { const [form, setForm] = useState(initial) const [tagInput, setTagInput] = useState(initial.tags.join(', ')) const [platformInput, setPlatformInput] = useState((initial.scope.platforms || []).join(', ')) const [componentInput, setComponentInput] = useState((initial.scope.components || []).join(', ')) const [dataClassInput, setDataClassInput] = useState((initial.scope.data_classes || []).join(', ')) const handleSave = () => { const data = { ...form, tags: tagInput.split(',').map(t => t.trim()).filter(Boolean), scope: { platforms: platformInput.split(',').map(t => t.trim()).filter(Boolean), components: componentInput.split(',').map(t => t.trim()).filter(Boolean), data_classes: dataClassInput.split(',').map(t => t.trim()).filter(Boolean), }, requirements: form.requirements.filter(r => r.trim()), test_procedure: form.test_procedure.filter(r => r.trim()), evidence: form.evidence.filter(e => e.type.trim() || e.description.trim()), open_anchors: form.open_anchors.filter(a => a.framework.trim() || a.ref.trim()), } onSave(data) } return (

{initial.control_id ? `Control ${initial.control_id} bearbeiten` : 'Neues Control erstellen'}

{/* Basic fields */}
setForm({ ...form, control_id: e.target.value.toUpperCase() })} placeholder="AUTH-003" disabled={!!initial.control_id} className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:outline-none disabled:bg-gray-100" />

Format: DOMAIN-NNN (z.B. AUTH-003, NET-005)

setForm({ ...form, title: e.target.value })} placeholder="Control-Titel" className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:outline-none" />
setForm({ ...form, risk_score: e.target.value ? parseFloat(e.target.value) : null })} className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg" />
{/* Objective & Rationale */}