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

@@ -257,12 +257,14 @@ function AddRequirementForm({
function RequirementCard({
requirement,
onStatusChange,
onDelete,
expanded,
onToggleDetails,
linkedControls,
}: {
requirement: DisplayRequirement
onStatusChange: (status: RequirementStatus) => void
onDelete: () => void
expanded: boolean
onToggleDetails: () => void
linkedControls: { id: string; name: string }[]
@@ -345,19 +347,27 @@ function RequirementCard({
<p className="text-sm text-gray-400">Keine Kontrollen zugeordnet</p>
)}
</div>
<div>
<h4 className="text-sm font-medium text-gray-700 mb-1">Status-Historie</h4>
<div className="flex items-center gap-2 text-sm text-gray-500">
<span className={`px-2 py-0.5 text-xs rounded-full ${
requirement.displayStatus === 'compliant' ? 'bg-green-100 text-green-700' :
requirement.displayStatus === 'partial' ? 'bg-yellow-100 text-yellow-700' :
'bg-red-100 text-red-700'
}`}>
{requirement.status === 'NOT_STARTED' ? 'Nicht begonnen' :
requirement.status === 'IN_PROGRESS' ? 'In Bearbeitung' :
requirement.status === 'IMPLEMENTED' ? 'Implementiert' : 'Verifiziert'}
</span>
<div className="flex items-center justify-between">
<div>
<h4 className="text-sm font-medium text-gray-700 mb-1">Status-Historie</h4>
<div className="flex items-center gap-2 text-sm text-gray-500">
<span className={`px-2 py-0.5 text-xs rounded-full ${
requirement.displayStatus === 'compliant' ? 'bg-green-100 text-green-700' :
requirement.displayStatus === 'partial' ? 'bg-yellow-100 text-yellow-700' :
'bg-red-100 text-red-700'
}`}>
{requirement.status === 'NOT_STARTED' ? 'Nicht begonnen' :
requirement.status === 'IN_PROGRESS' ? 'In Bearbeitung' :
requirement.status === 'IMPLEMENTED' ? 'Implementiert' : 'Verifiziert'}
</span>
</div>
</div>
<button
onClick={onDelete}
className="px-3 py-1 text-sm text-red-600 hover:bg-red-50 border border-red-200 rounded-lg transition-colors"
>
Loeschen
</button>
</div>
</div>
)}
@@ -493,6 +503,7 @@ export default function RequirementsPage() {
const nonCompliantCount = displayRequirements.filter(r => r.displayStatus === 'non-compliant').length
const handleStatusChange = async (requirementId: string, status: RequirementStatus) => {
const previousStatus = state.requirements.find(r => r.id === requirementId)?.status
dispatch({
type: 'UPDATE_REQUIREMENT',
payload: { id: requirementId, data: { status } },
@@ -500,17 +511,94 @@ export default function RequirementsPage() {
// Persist to backend
try {
await fetch(`/api/sdk/v1/compliance/requirements/${requirementId}`, {
const res = await fetch(`/api/sdk/v1/compliance/requirements/${requirementId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status }),
body: JSON.stringify({ implementation_status: status.toLowerCase() }),
})
if (!res.ok) {
// Rollback on failure
if (previousStatus) {
dispatch({ type: 'UPDATE_REQUIREMENT', payload: { id: requirementId, data: { status: previousStatus } } })
}
setError('Status-Aenderung konnte nicht gespeichert werden')
}
} catch {
// Silently fail — SDK state is already updated
if (previousStatus) {
dispatch({ type: 'UPDATE_REQUIREMENT', payload: { id: requirementId, data: { status: previousStatus } } })
}
setError('Backend nicht erreichbar — Aenderung zurueckgesetzt')
}
}
const handleAddRequirement = (data: { regulation: string; article: string; title: string; description: string; criticality: RiskSeverity }) => {
const handleDeleteRequirement = async (requirementId: string) => {
if (!confirm('Anforderung wirklich loeschen?')) return
try {
const res = await fetch(`/api/sdk/v1/compliance/requirements/${requirementId}`, {
method: 'DELETE',
})
if (res.ok) {
dispatch({ type: 'SET_STATE', payload: { requirements: state.requirements.filter(r => r.id !== requirementId) } })
} else {
setError('Loeschen fehlgeschlagen')
}
} catch {
setError('Backend nicht erreichbar')
}
}
const handleAddRequirement = async (data: { regulation: string; article: string; title: string; description: string; criticality: RiskSeverity }) => {
// Try to resolve regulation_id from backend
let regulationId = ''
try {
const regRes = await fetch(`/api/sdk/v1/compliance/regulations/${data.regulation}`)
if (regRes.ok) {
const regData = await regRes.json()
regulationId = regData.id
}
} catch {
// Regulation not found — still add locally
}
const priorityMap: Record<string, number> = { LOW: 1, MEDIUM: 2, HIGH: 3, CRITICAL: 4 }
if (regulationId) {
try {
const res = await fetch('/api/sdk/v1/compliance/requirements', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
regulation_id: regulationId,
article: data.article,
title: data.title,
description: data.description,
priority: priorityMap[data.criticality] || 2,
}),
})
if (res.ok) {
const created = await res.json()
const newReq: SDKRequirement = {
id: created.id,
regulation: data.regulation,
article: data.article,
title: data.title,
description: data.description,
criticality: data.criticality,
applicableModules: [],
status: 'NOT_STARTED',
controls: [],
}
dispatch({ type: 'ADD_REQUIREMENT', payload: newReq })
setShowAddForm(false)
return
}
} catch {
// Fall through to local-only add
}
}
// Fallback: add locally only
const newReq: SDKRequirement = {
id: `req-${Date.now()}`,
regulation: data.regulation,
@@ -651,6 +739,7 @@ export default function RequirementsPage() {
key={requirement.id}
requirement={requirement}
onStatusChange={(status) => handleStatusChange(requirement.id, status)}
onDelete={() => handleDeleteRequirement(requirement.id)}
expanded={expandedId === requirement.id}
onToggleDetails={() => setExpandedId(expandedId === requirement.id ? null : requirement.id)}
linkedControls={linkedControls}