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>
153 lines
6.6 KiB
TypeScript
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>
|
|
)
|
|
}
|