Files
breakpilot-compliance/admin-compliance/app/sdk/incidents/_components/IncidentCreateModal.tsx
Sharang Parnerkar 375b34a0d8 refactor(admin): split consent-management, control-library, incidents, training pages
Agent-completed splits committed after agents hit rate limits before
committing their work. All 4 pages now under 500 LOC:

- consent-management: 1303 -> 193 LOC (+ 7 _components, _hooks, _data, _types)
- control-library: 1210 -> 298 LOC (+ _components, _types)
- incidents: 1150 -> 373 LOC (+ _components)
- training: 1127 -> 366 LOC (+ _components)

Verification: next build clean (142 pages generated).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 15:52:45 +02:00

210 lines
8.2 KiB
TypeScript

'use client'
import React, { useState } from 'react'
export function IncidentCreateModal({
onClose,
onSuccess
}: {
onClose: () => void
onSuccess: () => void
}) {
const [title, setTitle] = useState('')
const [category, setCategory] = useState('data_breach')
const [severity, setSeverity] = useState('medium')
const [description, setDescription] = useState('')
const [detectedBy, setDetectedBy] = useState('')
const [affectedSystems, setAffectedSystems] = useState('')
const [estimatedAffectedPersons, setEstimatedAffectedPersons] = useState('0')
const [isSaving, setIsSaving] = useState(false)
const [error, setError] = useState<string | null>(null)
const handleSave = async () => {
if (!title.trim()) {
setError('Titel ist erforderlich.')
return
}
setIsSaving(true)
setError(null)
try {
const res = await fetch('/api/sdk/v1/incidents', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title,
category,
severity,
description,
detectedBy,
affectedSystems: affectedSystems.split(',').map(s => s.trim()).filter(Boolean),
estimatedAffectedPersons: Number(estimatedAffectedPersons)
})
})
if (!res.ok) {
const data = await res.json().catch(() => ({}))
throw new Error(data.detail || data.message || `Fehler: ${res.status}`)
}
onSuccess()
} catch (err: unknown) {
setError(err instanceof Error ? err.message : 'Unbekannter Fehler')
} finally {
setIsSaving(false)
}
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/50"
onClick={onClose}
/>
{/* Modal */}
<div className="relative bg-white rounded-xl shadow-2xl w-full max-w-lg mx-4 p-6 z-10 max-h-[90vh] overflow-y-auto">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-semibold text-gray-900">Neuen Vorfall erfassen</h2>
<button
onClick={onClose}
className="p-1 text-gray-400 hover:text-gray-600 rounded transition-colors"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{error && (
<div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
{error}
</div>
)}
<div className="space-y-4">
{/* Title */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Titel <span className="text-red-500">*</span>
</label>
<input
type="text"
value={title}
onChange={e => setTitle(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 text-sm"
placeholder="Kurze Beschreibung des Vorfalls"
/>
</div>
{/* Category */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Kategorie</label>
<select
value={category}
onChange={e => setCategory(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 text-sm"
>
<option value="data_breach">Datenpanne</option>
<option value="unauthorized_access">Unbefugter Zugriff</option>
<option value="data_loss">Datenverlust</option>
<option value="system_compromise">Systemkompromittierung</option>
<option value="phishing">Phishing</option>
<option value="ransomware">Ransomware</option>
<option value="insider_threat">Insider-Bedrohung</option>
<option value="physical_breach">Physischer Vorfall</option>
<option value="other">Sonstiges</option>
</select>
</div>
{/* Severity */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Schweregrad</label>
<select
value={severity}
onChange={e => setSeverity(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 text-sm"
>
<option value="low">Niedrig (1)</option>
<option value="medium">Mittel (2)</option>
<option value="high">Hoch (3)</option>
<option value="critical">Kritisch (4)</option>
</select>
</div>
{/* Description */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Beschreibung</label>
<textarea
value={description}
onChange={e => setDescription(e.target.value)}
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 text-sm"
placeholder="Detaillierte Beschreibung des Vorfalls"
/>
</div>
{/* Detected By */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Entdeckt von</label>
<input
type="text"
value={detectedBy}
onChange={e => setDetectedBy(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 text-sm"
placeholder="Name / Team / System"
/>
</div>
{/* Affected Systems */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Betroffene Systeme
<span className="ml-1 text-xs text-gray-400">(Kommagetrennt)</span>
</label>
<input
type="text"
value={affectedSystems}
onChange={e => setAffectedSystems(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 text-sm"
placeholder="z.B. CRM, E-Mail-Server, Datenbank"
/>
</div>
{/* Estimated Affected Persons */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Geschaetzte betroffene Personen</label>
<input
type="number"
value={estimatedAffectedPersons}
onChange={e => setEstimatedAffectedPersons(e.target.value)}
min={0}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 text-sm"
/>
</div>
</div>
{/* Buttons */}
<div className="flex items-center justify-end gap-3 mt-6 pt-4 border-t border-gray-200">
<button
onClick={onClose}
className="px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-lg transition-colors"
>
Abbrechen
</button>
<button
onClick={handleSave}
disabled={isSaving}
className="px-4 py-2 text-sm bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
>
{isSaving && (
<svg className="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
)}
Speichern
</button>
</div>
</div>
</div>
)
}