'use client' import React, { useState } from 'react' import { useRouter } from 'next/navigation' import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader' import { mapControlTypeToDisplay, mapStatusToDisplay, DisplayControl } from './_types' import { ControlCard } from './_components/ControlCard' import { AddControlForm } from './_components/AddControlForm' import { LoadingSkeleton } from './_components/LoadingSkeleton' import { StatsCards } from './_components/StatsCards' import { FilterBar } from './_components/FilterBar' import { RAGPanel } from './_components/RAGPanel' import { useControlsData } from './_hooks/useControlsData' import { useRAGSuggestions } from './_hooks/useRAGSuggestions' // --------------------------------------------------------------------------- // Transition Error Banner // --------------------------------------------------------------------------- function TransitionErrorBanner({ controlId, violations, onDismiss, }: { controlId: string violations: string[] onDismiss: () => void }) { return (

Status-Transition blockiert ({controlId})

    {violations.map((v, i) => (
  • {v}
  • ))}
Evidence hinzufuegen →
) } // --------------------------------------------------------------------------- // Main Page // --------------------------------------------------------------------------- export default function ControlsPage() { const router = useRouter() const [filter, setFilter] = useState('all') const [showAddForm, setShowAddForm] = useState(false) const [transitionError, setTransitionError] = useState<{ controlId: string; violations: string[] } | null>(null) const { state, dispatch, loading, error, setError, effectivenessMap, evidenceMap, handleStatusChange: _handleStatusChange, handleEffectivenessChange, handleAddControl, } = useControlsData() const { ragLoading, ragSuggestions, showRagPanel, setShowRagPanel, selectedRequirementId, setSelectedRequirementId, suggestControlsFromRAG, addSuggestedControl, } = useRAGSuggestions(setError) // Wrap status change to capture 409 transition errors const handleStatusChange = async (controlId: string, newStatus: import('@/lib/sdk').ImplementationStatus) => { const oldControl = state.controls.find(c => c.id === controlId) const oldStatus = oldControl?.implementationStatus dispatch({ type: 'UPDATE_CONTROL', payload: { id: controlId, data: { implementationStatus: newStatus } } }) try { const res = await fetch(`/api/sdk/v1/compliance/controls/${controlId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ implementation_status: newStatus }), }) if (!res.ok) { if (oldStatus) dispatch({ type: 'UPDATE_CONTROL', payload: { id: controlId, data: { implementationStatus: oldStatus } } }) const err = await res.json().catch(() => ({ detail: 'Status-Aenderung fehlgeschlagen' })) if (res.status === 409 && err.detail?.violations) { setTransitionError({ controlId, violations: err.detail.violations }) } else { const msg = typeof err.detail === 'string' ? err.detail : err.detail?.error || 'Status-Aenderung fehlgeschlagen' setError(msg) } } else if (transitionError?.controlId === controlId) { setTransitionError(null) } } catch { if (oldStatus) dispatch({ type: 'UPDATE_CONTROL', payload: { id: controlId, data: { implementationStatus: oldStatus } } }) setError('Netzwerkfehler bei Status-Aenderung') } } // Build display controls const displayControls: DisplayControl[] = state.controls.map(ctrl => { const effectivenessPercent = effectivenessMap[ctrl.id] ?? (ctrl.implementationStatus === 'IMPLEMENTED' ? 85 : ctrl.implementationStatus === 'PARTIAL' ? 50 : 0) return { id: ctrl.id, name: ctrl.name, description: ctrl.description, type: ctrl.type, category: ctrl.category, implementationStatus: ctrl.implementationStatus, evidence: ctrl.evidence, owner: ctrl.owner, dueDate: ctrl.dueDate, code: ctrl.id, displayType: 'preventive' as import('./_types').DisplayControlType, displayCategory: mapControlTypeToDisplay(ctrl.type), displayStatus: mapStatusToDisplay(ctrl.implementationStatus), effectivenessPercent, linkedRequirements: [], linkedEvidence: evidenceMap[ctrl.id] || [], lastReview: new Date(), } }) const filteredControls = filter === 'all' ? displayControls : displayControls.filter(c => c.displayStatus === filter || c.displayType === filter || c.displayCategory === filter) const implementedCount = displayControls.filter(c => c.displayStatus === 'implemented').length const avgEffectiveness = displayControls.length > 0 ? Math.round(displayControls.reduce((sum, c) => sum + c.effectivenessPercent, 0) / displayControls.length) : 0 const partialCount = displayControls.filter(c => c.displayStatus === 'partial').length const stepInfo = STEP_EXPLANATIONS['controls'] return (
{showAddForm && ( { handleAddControl(data); setShowAddForm(false) }} onCancel={() => setShowAddForm(false)} /> )} {showRagPanel && ( setShowRagPanel(false)} /> )} {error && (
{error}
)} {transitionError && ( setTransitionError(null)} /> )} {state.requirements.length === 0 && !loading && (

Keine Anforderungen definiert

Bitte definieren Sie zuerst Anforderungen, um die zugehoerigen Kontrollen zu laden.

)} {loading && } {!loading && (
{filteredControls.map(control => ( handleStatusChange(control.id, status)} onEffectivenessChange={(effectiveness) => handleEffectivenessChange(control.id, effectiveness)} onLinkEvidence={() => router.push(`/sdk/evidence?control_id=${control.id}`)} /> ))}
)} {!loading && filteredControls.length === 0 && state.requirements.length > 0 && (

Keine Kontrollen gefunden

Passen Sie den Filter an oder fuegen Sie neue Kontrollen hinzu.

)}
) }