Files
Sharang Parnerkar 9096aad693 refactor(admin): split audit-checklist, cookie-banner, escalations pages
Each page.tsx was 750-780 LOC. Extracted React components to _components/
and custom hooks to _hooks/ next to each page.tsx. All three pages are now
under 215 LOC (well within the 500 LOC hard cap). Zero behavior changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 13:06:45 +02:00

153 lines
6.6 KiB
TypeScript

'use client'
import React, { useState } from 'react'
import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader'
import { useEscalations } from './_hooks/useEscalations'
import { EscalationCard } from './_components/EscalationCard'
import { EscalationCreateModal } from './_components/EscalationCreateModal'
import { EscalationDetailDrawer } from './_components/EscalationDetailDrawer'
import { Escalation } from './_components/types'
export default function EscalationsPage() {
const [filter, setFilter] = useState<string>('all')
const [showCreateModal, setShowCreateModal] = useState(false)
const [selectedEscalation, setSelectedEscalation] = useState<Escalation | null>(null)
const { escalations, stats, loading, loadAll } = useEscalations(filter)
const criticalCount = stats?.by_priority?.critical ?? 0
const escalatedCount = stats?.by_status?.escalated ?? 0
const openCount = stats?.by_status?.open ?? 0
const activeCount = stats?.active ?? 0
const stepInfo = STEP_EXPLANATIONS['escalations']
return (
<div className="space-y-6">
<StepHeader
stepId="escalations"
title={stepInfo.title}
description={stepInfo.description}
explanation={stepInfo.explanation}
tips={stepInfo.tips}
>
<button
onClick={() => setShowCreateModal(true)}
className="flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 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="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
Eskalation erstellen
</button>
</StepHeader>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-white rounded-xl border border-gray-200 p-6">
<div className="text-sm text-gray-500">Gesamt aktiv</div>
<div className="text-3xl font-bold text-gray-900">{loading ? '…' : activeCount}</div>
</div>
<div className="bg-white rounded-xl border border-red-200 p-6">
<div className="text-sm text-red-600">Kritisch</div>
<div className="text-3xl font-bold text-red-600">{loading ? '…' : criticalCount}</div>
</div>
<div className="bg-white rounded-xl border border-orange-200 p-6">
<div className="text-sm text-orange-600">Eskaliert</div>
<div className="text-3xl font-bold text-orange-600">{loading ? '…' : escalatedCount}</div>
</div>
<div className="bg-white rounded-xl border border-blue-200 p-6">
<div className="text-sm text-blue-600">Offen</div>
<div className="text-3xl font-bold text-blue-600">{loading ? '…' : openCount}</div>
</div>
</div>
{criticalCount > 0 && (
<div className="bg-red-50 border border-red-200 rounded-xl p-4 flex items-center gap-4">
<div className="w-10 h-10 bg-red-100 rounded-full flex items-center justify-center flex-shrink-0">
<svg className="w-5 h-5 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<div>
<h4 className="font-medium text-red-800">{criticalCount} kritische Eskalation(en) erfordern sofortige Aufmerksamkeit</h4>
<p className="text-sm text-red-600">Priorisieren Sie diese Vorfaelle zur Vermeidung von Schaeden.</p>
</div>
</div>
)}
<div className="flex items-center gap-2 flex-wrap">
<span className="text-sm text-gray-500">Filter:</span>
{[
{ key: 'all', label: 'Alle' },
{ key: 'open', label: 'Offen' },
{ key: 'in_progress', label: 'In Bearbeitung' },
{ key: 'escalated', label: 'Eskaliert' },
{ key: 'critical', label: 'Kritisch' },
{ key: 'high', label: 'Hoch' },
{ key: 'resolved', label: 'Geloest' },
].map(f => (
<button
key={f.key}
onClick={() => setFilter(f.key)}
className={`px-3 py-1 text-sm rounded-full transition-colors ${
filter === f.key
? 'bg-purple-600 text-white'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`}
>
{f.label}
</button>
))}
</div>
{loading ? (
<div className="text-center py-12 text-gray-500 text-sm">Lade Eskalationen</div>
) : (
<div className="space-y-4">
{escalations
.sort((a, b) => {
const priorityOrder: Record<string, number> = { critical: 0, high: 1, medium: 2, low: 3 }
const statusOrder: Record<string, number> = { escalated: 0, open: 1, in_progress: 2, resolved: 3, closed: 4 }
const pd = (priorityOrder[a.priority] ?? 9) - (priorityOrder[b.priority] ?? 9)
if (pd !== 0) return pd
return (statusOrder[a.status] ?? 9) - (statusOrder[b.status] ?? 9)
})
.map(esc => (
<EscalationCard
key={esc.id}
escalation={esc}
onClick={() => setSelectedEscalation(esc)}
/>
))}
{escalations.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-gray-100 rounded-full flex items-center justify-center mb-4">
<svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h3 className="text-lg font-semibold text-gray-900">Keine Eskalationen gefunden</h3>
<p className="mt-2 text-gray-500">Passen Sie den Filter an oder erstellen Sie eine neue Eskalation.</p>
</div>
)}
</div>
)}
{showCreateModal && (
<EscalationCreateModal
onClose={() => setShowCreateModal(false)}
onCreated={loadAll}
/>
)}
{selectedEscalation && (
<EscalationDetailDrawer
escalation={selectedEscalation}
onClose={() => setSelectedEscalation(null)}
onUpdated={loadAll}
/>
)}
</div>
)
}