feat: 7 Analyse-Module auf 100% — Backend-Endpoints, DB-Model, Frontend-Persistenz

Alle 7 Analyse-Module (Requirements → Report) von ~80% auf 100% gebracht:
- Modul 1 (Requirements): POST/DELETE Endpoints + Frontend-Anbindung + Rollback
- Modul 2 (Controls): Evidence-Linking UI mit Validity-Badge
- Modul 3 (Evidence): Pagination (Frontend + Backend)
- Modul 4 (Risk Matrix): Mitigation-UI, Residual Risk, Status-Workflow
- Modul 5 (AI Act): AISystemDB Model, 6 CRUD-Endpoints, Backend-Persistenz
- Modul 6 (Audit Checklist): PDF-Download + Session-History
- Modul 7 (Audit Report): Detail-Seite mit Checklist Sign-Off + Navigation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-02 15:52:23 +01:00
parent d079886819
commit d48ebc5211
14 changed files with 1452 additions and 70 deletions

View File

@@ -1,6 +1,7 @@
'use client'
import React, { useState, useEffect } from 'react'
import { useRouter } from 'next/navigation'
import { useSDK, Control as SDKControl, ControlType, ImplementationStatus } from '@/lib/sdk'
import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader'
@@ -28,6 +29,7 @@ interface DisplayControl {
displayStatus: DisplayStatus
effectivenessPercent: number
linkedRequirements: string[]
linkedEvidence: { id: string; title: string; status: string }[]
lastReview: Date
}
@@ -153,10 +155,12 @@ function ControlCard({
control,
onStatusChange,
onEffectivenessChange,
onLinkEvidence,
}: {
control: DisplayControl
onStatusChange: (status: ImplementationStatus) => void
onEffectivenessChange: (effectivenessPercent: number) => void
onLinkEvidence: () => void
}) {
const [showEffectivenessSlider, setShowEffectivenessSlider] = useState(false)
@@ -279,6 +283,33 @@ function ControlCard({
{statusLabels[control.displayStatus]}
</span>
</div>
{/* Linked Evidence */}
{control.linkedEvidence.length > 0 && (
<div className="mt-3 pt-3 border-t border-gray-100">
<span className="text-xs text-gray-500 mb-1 block">Nachweise:</span>
<div className="flex items-center gap-1 flex-wrap">
{control.linkedEvidence.map(ev => (
<span key={ev.id} className={`px-2 py-0.5 text-xs rounded ${
ev.status === 'valid' ? 'bg-green-50 text-green-700' :
ev.status === 'expired' ? 'bg-red-50 text-red-700' :
'bg-yellow-50 text-yellow-700'
}`}>
{ev.title}
</span>
))}
</div>
</div>
)}
<div className="mt-3 pt-3 border-t border-gray-100">
<button
onClick={onLinkEvidence}
className="text-sm text-purple-600 hover:text-purple-700 font-medium"
>
Evidence verknuepfen
</button>
</div>
</div>
)
}
@@ -400,6 +431,7 @@ function LoadingSkeleton() {
export default function ControlsPage() {
const { state, dispatch } = useSDK()
const router = useRouter()
const [filter, setFilter] = useState<string>('all')
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
@@ -407,6 +439,33 @@ export default function ControlsPage() {
// Track effectiveness locally as it's not in the SDK state type
const [effectivenessMap, setEffectivenessMap] = useState<Record<string, number>>({})
// Track linked evidence per control
const [evidenceMap, setEvidenceMap] = useState<Record<string, { id: string; title: string; status: string }[]>>({})
const fetchEvidenceForControls = async (controlIds: string[]) => {
try {
const res = await fetch('/api/sdk/v1/compliance/evidence')
if (res.ok) {
const data = await res.json()
const allEvidence = data.evidence || data
if (Array.isArray(allEvidence)) {
const map: Record<string, { id: string; title: string; status: string }[]> = {}
for (const ev of allEvidence) {
const ctrlId = ev.control_id || ''
if (!map[ctrlId]) map[ctrlId] = []
map[ctrlId].push({
id: ev.id,
title: ev.title || ev.name || 'Nachweis',
status: ev.status || 'pending',
})
}
setEvidenceMap(map)
}
}
} catch {
// Silently fail
}
}
// Fetch controls from backend on mount
useEffect(() => {
@@ -432,6 +491,8 @@ export default function ControlsPage() {
}))
dispatch({ type: 'SET_STATE', payload: { controls: mapped } })
setError(null)
// Fetch evidence for all controls
fetchEvidenceForControls(mapped.map(c => c.id))
return
}
}
@@ -494,6 +555,7 @@ export default function ControlsPage() {
displayStatus: mapStatusToDisplay(ctrl.implementationStatus),
effectivenessPercent,
linkedRequirements: template?.linkedRequirements || [],
linkedEvidence: evidenceMap[ctrl.id] || [],
lastReview: new Date(),
}
})
@@ -673,6 +735,7 @@ export default function ControlsPage() {
control={control}
onStatusChange={(status) => handleStatusChange(control.id, status)}
onEffectivenessChange={(effectiveness) => handleEffectivenessChange(control.id, effectiveness)}
onLinkEvidence={() => router.push(`/sdk/evidence?control_id=${control.id}`)}
/>
))}
</div>