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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user