'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 (
)
}
// ---------------------------------------------------------------------------
// 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.
)}
)
}