feat: Analyse-Module auf 100% Runde 2 — CREATE-Forms, Button-Handler, Persistenz
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 36s
CI / test-python-backend-compliance (push) Successful in 36s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 19s

Requirements: ADD-Form + Details-Panel mit Controls/Status-Anzeige
Controls: ADD-Form + Effectiveness-Persistenz via PUT
Evidence: Anzeigen/Herunterladen-Buttons mit fileUrl + disabled-State
Risks: RiskMatrix Cell-Click filtert Risiko-Liste mit Badge + Reset
AI Act: Mock-Daten entfernt, Loading-Skeleton, Edit/Delete-Handler
Audit Checklist: JSON-Export, debounced Notes-Persistenz, Neue Checkliste
Audit Report: Animiertes Skeleton statt Loading-Text

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-02 13:13:26 +01:00
parent a50a9810ee
commit fc83ebfd82
7 changed files with 607 additions and 96 deletions

View File

@@ -1,6 +1,6 @@
'use client'
import React, { useState } from 'react'
import React, { useState, useEffect } from 'react'
import { useSDK } from '@/lib/sdk'
import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader'
@@ -22,47 +22,32 @@ interface AISystem {
}
// =============================================================================
// INITIAL DATA
// LOADING SKELETON
// =============================================================================
const initialSystems: AISystem[] = [
{
id: 'ai-1',
name: 'Kundenservice Chatbot',
description: 'KI-gestuetzter Chatbot fuer Kundenanfragen',
classification: 'limited-risk',
purpose: 'Automatisierte Beantwortung von Kundenanfragen',
sector: 'Kundenservice',
status: 'classified',
obligations: ['Transparenzpflicht', 'Kennzeichnung als KI-System'],
assessmentDate: new Date('2024-01-15'),
assessmentResult: null,
},
{
id: 'ai-2',
name: 'Bewerber-Screening',
description: 'KI-System zur Vorauswahl von Bewerbungen',
classification: 'high-risk',
purpose: 'Automatisierte Bewertung von Bewerbungsunterlagen',
sector: 'Personal',
status: 'non-compliant',
obligations: ['Risikomanagementsystem', 'Datenlenkung', 'Technische Dokumentation', 'Menschliche Aufsicht', 'Transparenz'],
assessmentDate: new Date('2024-01-10'),
assessmentResult: null,
},
{
id: 'ai-3',
name: 'Empfehlungsalgorithmus',
description: 'Personalisierte Produktempfehlungen',
classification: 'minimal-risk',
purpose: 'Verbesserung der Kundenerfahrung durch personalisierte Empfehlungen',
sector: 'E-Commerce',
status: 'compliant',
obligations: [],
assessmentDate: new Date('2024-01-05'),
assessmentResult: null,
},
]
function LoadingSkeleton() {
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{[1, 2, 3, 4].map(i => (
<div key={i} className="bg-white rounded-xl border border-gray-200 p-6 animate-pulse">
<div className="flex items-center gap-2 mb-3">
<div className="h-5 w-24 bg-gray-200 rounded-full" />
<div className="h-5 w-20 bg-gray-200 rounded-full" />
</div>
<div className="h-6 w-3/4 bg-gray-200 rounded mb-2" />
<div className="h-4 w-full bg-gray-100 rounded mb-4" />
<div className="h-4 w-1/2 bg-gray-100 rounded" />
<div className="mt-4 pt-4 border-t border-gray-100">
<div className="flex gap-2">
<div className="h-8 flex-1 bg-gray-200 rounded-lg" />
<div className="h-8 w-24 bg-gray-200 rounded-lg" />
</div>
</div>
</div>
))}
</div>
)
}
// =============================================================================
// COMPONENTS
@@ -103,23 +88,25 @@ function RiskPyramid({ systems }: { systems: AISystem[] }) {
function AddSystemForm({
onSubmit,
onCancel,
initialData,
}: {
onSubmit: (system: Omit<AISystem, 'id' | 'assessmentDate' | 'assessmentResult'>) => void
onCancel: () => void
initialData?: AISystem | null
}) {
const [formData, setFormData] = useState({
name: '',
description: '',
purpose: '',
sector: '',
classification: 'unclassified' as AISystem['classification'],
status: 'draft' as AISystem['status'],
obligations: [] as string[],
name: initialData?.name || '',
description: initialData?.description || '',
purpose: initialData?.purpose || '',
sector: initialData?.sector || '',
classification: (initialData?.classification || 'unclassified') as AISystem['classification'],
status: (initialData?.status || 'draft') as AISystem['status'],
obligations: initialData?.obligations || [] as string[],
})
return (
<div className="bg-white rounded-xl border border-gray-200 p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Neues KI-System registrieren</h3>
<h3 className="text-lg font-semibold text-gray-900 mb-4">{initialData ? 'KI-System bearbeiten' : 'Neues KI-System registrieren'}</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Name *</label>
@@ -189,7 +176,7 @@ function AddSystemForm({
formData.name ? 'bg-purple-600 text-white hover:bg-purple-700' : 'bg-gray-200 text-gray-400 cursor-not-allowed'
}`}
>
Registrieren
{initialData ? 'Speichern' : 'Registrieren'}
</button>
</div>
</div>
@@ -200,11 +187,13 @@ function AISystemCard({
system,
onAssess,
onEdit,
onDelete,
assessing,
}: {
system: AISystem
onAssess: () => void
onEdit: () => void
onDelete: () => void
assessing: boolean
}) {
const classificationColors = {
@@ -306,6 +295,12 @@ function AISystemCard({
>
Bearbeiten
</button>
<button
onClick={onDelete}
className="px-4 py-2 text-sm text-red-600 hover:bg-red-50 rounded-lg transition-colors"
>
Loeschen
</button>
</div>
</div>
)
@@ -317,23 +312,57 @@ function AISystemCard({
export default function AIActPage() {
const { state } = useSDK()
const [systems, setSystems] = useState<AISystem[]>(initialSystems)
const [systems, setSystems] = useState<AISystem[]>([])
const [filter, setFilter] = useState<string>('all')
const [showAddForm, setShowAddForm] = useState(false)
const [editingSystem, setEditingSystem] = useState<AISystem | null>(null)
const [assessingId, setAssessingId] = useState<string | null>(null)
const [error, setError] = useState<string | null>(null)
const [loading, setLoading] = useState(true)
// Load systems from SDK state on mount
useEffect(() => {
setLoading(true)
// Try to load from SDK state (aiSystems if available)
const sdkState = state as unknown as Record<string, unknown>
if (Array.isArray(sdkState.aiSystems) && sdkState.aiSystems.length > 0) {
setSystems(sdkState.aiSystems as AISystem[])
}
setLoading(false)
}, []) // eslint-disable-line react-hooks/exhaustive-deps
const handleAddSystem = (data: Omit<AISystem, 'id' | 'assessmentDate' | 'assessmentResult'>) => {
const newSystem: AISystem = {
...data,
id: `ai-${Date.now()}`,
assessmentDate: data.classification !== 'unclassified' ? new Date() : null,
assessmentResult: null,
if (editingSystem) {
// Edit existing system
setSystems(prev => prev.map(s =>
s.id === editingSystem.id
? { ...s, ...data }
: s
))
setEditingSystem(null)
} else {
// Create new system
const newSystem: AISystem = {
...data,
id: `ai-${Date.now()}`,
assessmentDate: data.classification !== 'unclassified' ? new Date() : null,
assessmentResult: null,
}
setSystems(prev => [...prev, newSystem])
}
setSystems(prev => [...prev, newSystem])
setShowAddForm(false)
}
const handleDelete = (id: string) => {
if (!confirm('Moechten Sie dieses KI-System wirklich loeschen?')) return
setSystems(prev => prev.filter(s => s.id !== id))
}
const handleEdit = (system: AISystem) => {
setEditingSystem(system)
setShowAddForm(true)
}
const handleAssess = async (systemId: string) => {
const system = systems.find(s => s.id === systemId)
if (!system) return
@@ -420,11 +449,12 @@ export default function AIActPage() {
</div>
)}
{/* Add System Form */}
{/* Add/Edit System Form */}
{showAddForm && (
<AddSystemForm
onSubmit={handleAddSystem}
onCancel={() => setShowAddForm(false)}
onCancel={() => { setShowAddForm(false); setEditingSystem(null) }}
initialData={editingSystem}
/>
)}
@@ -474,20 +504,26 @@ export default function AIActPage() {
))}
</div>
{/* AI Systems List */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{filteredSystems.map(system => (
<AISystemCard
key={system.id}
system={system}
onAssess={() => handleAssess(system.id)}
onEdit={() => {/* Edit handler */}}
assessing={assessingId === system.id}
/>
))}
</div>
{/* Loading */}
{loading && <LoadingSkeleton />}
{filteredSystems.length === 0 && (
{/* AI Systems List */}
{!loading && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{filteredSystems.map(system => (
<AISystemCard
key={system.id}
system={system}
onAssess={() => handleAssess(system.id)}
onEdit={() => handleEdit(system)}
onDelete={() => handleDelete(system.id)}
assessing={assessingId === system.id}
/>
))}
</div>
)}
{!loading && filteredSystems.length === 0 && (
<div className="bg-white rounded-xl border border-gray-200 p-12 text-center">
<div className="w-16 h-16 mx-auto bg-purple-100 rounded-full flex items-center justify-center mb-4">
<svg className="w-8 h-8 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">