Files
breakpilot-compliance/admin-compliance/app/sdk/notfallplan/_components/ApiSections.tsx
Sharang Parnerkar ef8284dff5 refactor(admin): split dsfa/[id] and notfallplan page.tsx into colocated components
dsfa/[id]/page.tsx (1893 LOC -> 350 LOC) split into 9 components:
Section1-5Editor, SDMCoverageOverview, RAGSearchPanel, AddRiskModal,
AddMitigationModal. Page is now a thin orchestrator.

notfallplan/page.tsx (1890 LOC -> 435 LOC) split into 8 modules:
types.ts, ConfigTab, IncidentsTab, TemplatesTab, ExercisesTab, Modals,
ApiSections. All under the 500-line hard cap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 18:51:54 +02:00

138 lines
6.9 KiB
TypeScript

'use client'
import React from 'react'
import type { ApiContact, ApiScenario, ApiExercise } from './types'
export function ApiContactsSection({ apiContacts, apiLoading, onAdd, onDelete }: {
apiContacts: ApiContact[]; apiLoading: boolean; onAdd: () => void; onDelete: (id: string) => void
}) {
return (
<div className="bg-white rounded-lg border p-5">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-base font-semibold">Notfallkontakte (Datenbank)</h3>
<p className="text-sm text-gray-500">Zentral gespeicherte Kontakte fuer den Notfall</p>
</div>
<button onClick={onAdd} className="px-3 py-1.5 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700">
+ Kontakt hinzufuegen
</button>
</div>
{apiLoading ? (
<div className="text-sm text-gray-500 py-4 text-center">Lade Kontakte...</div>
) : apiContacts.length === 0 ? (
<div className="text-sm text-gray-400 py-4 text-center">Noch keine Kontakte hinterlegt</div>
) : (
<div className="space-y-2">
{apiContacts.map(contact => (
<div key={contact.id} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div className="flex items-center gap-3">
{contact.is_primary && <span className="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full font-medium">Primaer</span>}
{contact.available_24h && <span className="text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded-full font-medium">24/7</span>}
<div>
<span className="font-medium text-sm">{contact.name}</span>
{contact.role && <span className="text-gray-500 text-sm ml-2">({contact.role})</span>}
<div className="text-xs text-gray-400">{contact.email} {contact.phone && `| ${contact.phone}`}</div>
</div>
</div>
<button onClick={() => onDelete(contact.id)} className="text-xs text-red-500 hover:text-red-700 px-2 py-1 rounded hover:bg-red-50">
Loeschen
</button>
</div>
))}
</div>
)}
</div>
)
}
export function ApiScenariosSection({ apiScenarios, apiLoading, onAdd, onDelete }: {
apiScenarios: ApiScenario[]; apiLoading: boolean; onAdd: () => void; onDelete: (id: string) => void
}) {
return (
<div className="bg-white rounded-lg border p-5">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-base font-semibold">Notfallszenarien (Datenbank)</h3>
<p className="text-sm text-gray-500">Definierte Szenarien und Reaktionsplaene</p>
</div>
<button onClick={onAdd} className="px-3 py-1.5 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700">
+ Szenario hinzufuegen
</button>
</div>
{apiLoading ? (
<div className="text-sm text-gray-500 py-4 text-center">Lade Szenarien...</div>
) : apiScenarios.length === 0 ? (
<div className="text-sm text-gray-400 py-4 text-center">Noch keine Szenarien definiert</div>
) : (
<div className="space-y-2">
{apiScenarios.map(scenario => (
<div key={scenario.id} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div>
<span className="font-medium text-sm">{scenario.title}</span>
<div className="flex items-center gap-2 mt-1">
{scenario.category && <span className="text-xs bg-slate-100 text-slate-600 px-2 py-0.5 rounded">{scenario.category}</span>}
<span className={`text-xs px-2 py-0.5 rounded ${
scenario.severity === 'critical' ? 'bg-red-100 text-red-700' :
scenario.severity === 'high' ? 'bg-orange-100 text-orange-700' :
scenario.severity === 'medium' ? 'bg-yellow-100 text-yellow-700' :
'bg-green-100 text-green-700'
}`}>{scenario.severity}</span>
</div>
{scenario.description && <p className="text-xs text-gray-500 mt-1 truncate max-w-lg">{scenario.description}</p>}
</div>
<button onClick={() => onDelete(scenario.id)} className="text-xs text-red-500 hover:text-red-700 px-2 py-1 rounded hover:bg-red-50">
Loeschen
</button>
</div>
))}
</div>
)}
</div>
)
}
export function ApiExercisesSection({ apiExercises, apiLoading, onAdd, onDelete }: {
apiExercises: ApiExercise[]; apiLoading: boolean; onAdd: () => void; onDelete: (id: string) => void
}) {
return (
<div className="bg-white rounded-lg border p-5">
<div className="flex items-center justify-between mb-4">
<h3 className="text-base font-semibold">Uebungen (Datenbank)</h3>
<div className="flex items-center gap-3">
<span className="text-sm text-gray-500">{apiExercises.length} Eintraege</span>
<button onClick={onAdd} className="px-3 py-1.5 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors">
+ Neue Uebung
</button>
</div>
</div>
{apiExercises.length === 0 ? (
<div className="text-sm text-gray-400 py-2 text-center">Noch keine Uebungen in der Datenbank</div>
) : (
<div className="space-y-2">
{apiExercises.map(ex => (
<div key={ex.id} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div>
<span className="font-medium text-sm">{ex.title}</span>
<div className="flex items-center gap-2 mt-1">
<span className="text-xs bg-slate-100 text-slate-600 px-2 py-0.5 rounded">{ex.exercise_type}</span>
{ex.exercise_date && <span className="text-xs text-gray-400">{new Date(ex.exercise_date).toLocaleDateString('de-DE')}</span>}
{ex.outcome && <span className={`text-xs px-2 py-0.5 rounded ${ex.outcome === 'passed' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>{ex.outcome}</span>}
</div>
</div>
<button
onClick={() => { if (window.confirm(`Uebung "${ex.title}" loeschen?`)) onDelete(ex.id) }}
className="p-1.5 text-red-400 hover:text-red-600 hover:bg-red-50 rounded transition-colors"
title="Loeschen"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
))}
</div>
)}
</div>
)
}