Files
breakpilot-compliance/admin-compliance/app/sdk/process-tasks/_components/TaskFormModal.tsx
Sharang Parnerkar 1fcd8244b1 refactor(admin): split evidence, process-tasks, iace/hazards pages
Extract components and hooks into _components/ and _hooks/ subdirectories
to reduce each page.tsx to under 500 LOC (was 1545/1383/1316).

Final line counts: evidence=213, process-tasks=304, hazards=157.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 17:12:15 +02:00

147 lines
7.3 KiB
TypeScript

'use client'
import { useState } from 'react'
import { EMPTY_FORM, CATEGORY_LABELS, PRIORITY_LABELS, FREQUENCY_LABELS } from './types'
import type { TaskFormData } from './types'
export function TaskFormModal({
initial,
onClose,
onSave,
}: {
initial?: Partial<TaskFormData>
onClose: () => void
onSave: (data: TaskFormData) => Promise<void>
}) {
const [form, setForm] = useState<TaskFormData>({ ...EMPTY_FORM, ...initial })
const [saving, setSaving] = useState(false)
const [error, setError] = useState<string | null>(null)
const update = (field: keyof TaskFormData, value: string | number) =>
setForm(prev => ({ ...prev, [field]: value }))
const handleSave = async () => {
if (!form.title.trim()) { setError('Titel ist erforderlich'); return }
if (!form.task_code.trim()) { setError('Task-Code ist erforderlich'); return }
setSaving(true)
setError(null)
try {
await onSave(form)
onClose()
} catch (e) {
setError(e instanceof Error ? e.message : 'Fehler beim Speichern')
} finally {
setSaving(false)
}
}
return (
<div className="fixed inset-0 bg-black/40 z-50 flex items-center justify-center p-4" onClick={e => e.target === e.currentTarget && onClose()}>
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-xl max-h-[90vh] overflow-y-auto">
<div className="flex items-center justify-between p-6 border-b">
<h2 className="text-lg font-semibold text-gray-900">
{initial?.title ? 'Aufgabe bearbeiten' : 'Neue Aufgabe erstellen'}
</h2>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 text-xl">{'\u2715'}</button>
</div>
<div className="p-6 space-y-4">
{error && <div className="text-red-600 text-sm bg-red-50 rounded-lg p-3">{error}</div>}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Task-Code *</label>
<input type="text" value={form.task_code} onChange={e => update('task_code', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500"
placeholder="z.B. DSGVO-VVT-REVIEW" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Kategorie</label>
<select value={form.category} onChange={e => update('category', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
{Object.entries(CATEGORY_LABELS).map(([k, v]) => <option key={k} value={k}>{v}</option>)}
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Titel *</label>
<input type="text" value={form.title} onChange={e => update('title', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500"
placeholder="z.B. VVT-Review und Aktualisierung" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Beschreibung</label>
<textarea value={form.description} onChange={e => update('description', e.target.value)} rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500"
placeholder="Detaillierte Beschreibung..." />
</div>
<div className="grid grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Prioritaet</label>
<select value={form.priority} onChange={e => update('priority', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
{Object.entries(PRIORITY_LABELS).map(([k, v]) => <option key={k} value={k}>{v}</option>)}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Frequenz</label>
<select value={form.frequency} onChange={e => update('frequency', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
{Object.entries(FREQUENCY_LABELS).map(([k, v]) => <option key={k} value={k}>{v}</option>)}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Faellig am</label>
<input type="date" value={form.next_due_date} onChange={e => update('next_due_date', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm" />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Zustaendig</label>
<input type="text" value={form.assigned_to} onChange={e => update('assigned_to', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm" placeholder="z.B. DSB, CISO" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Team</label>
<input type="text" value={form.responsible_team} onChange={e => update('responsible_team', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm" placeholder="z.B. IT-Sicherheit" />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Verknuepftes Modul</label>
<input type="text" value={form.linked_module} onChange={e => update('linked_module', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm" placeholder="z.B. vvt, tom, dsfa" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Erinnerung (Tage vorher)</label>
<input type="number" value={form.due_reminder_days}
onChange={e => update('due_reminder_days', parseInt(e.target.value) || 14)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm" min={0} max={90} />
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Notizen</label>
<textarea value={form.notes} onChange={e => update('notes', e.target.value)} rows={2}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
placeholder="Interne Hinweise..." />
</div>
</div>
<div className="flex justify-end gap-3 p-6 border-t">
<button onClick={onClose} className="px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg">Abbrechen</button>
<button onClick={handleSave} disabled={saving}
className="px-5 py-2 text-sm bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50">
{saving ? 'Speichern...' : 'Speichern'}
</button>
</div>
</div>
</div>
)
}