Files
breakpilot-compliance/admin-compliance/app/sdk/process-tasks/_components/TaskDetailModal.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

166 lines
6.8 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import {
ProcessTask, HistoryEntry, API,
CATEGORY_LABELS, CATEGORY_COLORS, PRIORITY_LABELS, PRIORITY_COLORS,
STATUS_LABELS, STATUS_COLORS, FREQUENCY_LABELS,
formatDate, dueLabel, dueLabelColor,
} from './types'
export function TaskDetailModal({
task,
onClose,
onComplete,
onSkip,
onEdit,
onDelete,
}: {
task: ProcessTask
onClose: () => void
onComplete: (task: ProcessTask) => void
onSkip: (task: ProcessTask) => void
onEdit: (task: ProcessTask) => void
onDelete: (id: string) => Promise<void>
}) {
const [history, setHistory] = useState<HistoryEntry[]>([])
const [loadingHistory, setLoadingHistory] = useState(true)
useEffect(() => {
loadHistory()
}, [task.id]) // eslint-disable-line react-hooks/exhaustive-deps
const loadHistory = async () => {
try {
const res = await fetch(`${API}/${task.id}/history`)
if (res.ok) { const data = await res.json(); setHistory(data.history || []) }
} catch { /* ignore */ }
setLoadingHistory(false)
}
const handleDelete = async () => {
if (!confirm('Aufgabe wirklich loeschen?')) return
await onDelete(task.id)
onClose()
}
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-lg max-h-[85vh] overflow-y-auto">
<div className="flex items-start justify-between p-6 border-b gap-4">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2 flex-wrap">
<span className={`px-2 py-0.5 text-xs rounded-full ${CATEGORY_COLORS[task.category] || 'bg-gray-100 text-gray-600'}`}>
{CATEGORY_LABELS[task.category] || task.category}
</span>
<span className={`px-2 py-0.5 text-xs rounded-full ${PRIORITY_COLORS[task.priority]}`}>
{PRIORITY_LABELS[task.priority]}
</span>
<span className={`px-2 py-0.5 text-xs rounded-full ${STATUS_COLORS[task.status]}`}>
{STATUS_LABELS[task.status]}
</span>
</div>
<h2 className="text-base font-semibold text-gray-900">{task.title}</h2>
<p className="text-xs text-gray-400 mt-0.5">{task.task_code}</p>
</div>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 flex-shrink-0">{'\u2715'}</button>
</div>
<div className="p-6 space-y-4 text-sm">
{task.description && <p className="text-gray-700 whitespace-pre-wrap">{task.description}</p>}
<div className="grid grid-cols-2 gap-3">
<div>
<span className="text-gray-500">Frequenz</span>
<p className="font-medium text-gray-900">{FREQUENCY_LABELS[task.frequency] || task.frequency}</p>
</div>
<div>
<span className="text-gray-500">Faellig</span>
<p className={`font-medium ${dueLabelColor(task.next_due_date)}`}>
{task.next_due_date ? `${formatDate(task.next_due_date)} (${dueLabel(task.next_due_date)})` : '\u2014'}
</p>
</div>
<div>
<span className="text-gray-500">Zustaendig</span>
<p className="font-medium text-gray-900">{task.assigned_to || '\u2014'}</p>
</div>
<div>
<span className="text-gray-500">Team</span>
<p className="font-medium text-gray-900">{task.responsible_team || '\u2014'}</p>
</div>
{task.linked_module && (
<div>
<span className="text-gray-500">Modul</span>
<p className="font-medium text-purple-700">{task.linked_module}</p>
</div>
)}
{task.last_completed_at && (
<div>
<span className="text-gray-500">Zuletzt erledigt</span>
<p className="font-medium text-gray-900">{formatDate(task.last_completed_at)}</p>
</div>
)}
</div>
{task.notes && (
<div className="bg-gray-50 rounded-lg p-3">
<span className="text-gray-500 text-xs">Notizen</span>
<p className="text-gray-700 mt-1">{task.notes}</p>
</div>
)}
<div className="border-t pt-4">
<h3 className="font-semibold text-gray-900 mb-2">Verlauf</h3>
{loadingHistory ? (
<div className="animate-pulse space-y-2">
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
</div>
) : history.length === 0 ? (
<p className="text-gray-400 text-sm">Noch keine Eintraege</p>
) : (
<div className="space-y-2 max-h-48 overflow-y-auto">
{history.map(h => (
<div key={h.id} className="flex items-start gap-2 text-xs bg-gray-50 rounded-lg p-2">
<span className={`px-1.5 py-0.5 rounded ${h.status === 'completed' ? 'bg-green-100 text-green-700' : 'bg-yellow-100 text-yellow-700'}`}>
{h.status === 'completed' ? 'Erledigt' : 'Uebersprungen'}
</span>
<div className="flex-1">
<p className="text-gray-600">{formatDate(h.completed_at)} {h.completed_by ? `von ${h.completed_by}` : ''}</p>
{h.result && <p className="text-gray-700 mt-0.5">{h.result}</p>}
{h.notes && <p className="text-gray-500 mt-0.5">{h.notes}</p>}
</div>
</div>
))}
</div>
)}
</div>
</div>
<div className="flex items-center gap-2 p-6 border-t flex-wrap">
{task.status !== 'completed' && (
<>
<button onClick={() => { onClose(); onComplete(task) }}
className="px-3 py-1.5 text-sm bg-green-600 text-white rounded-lg hover:bg-green-700">
Erledigen
</button>
<button onClick={() => { onClose(); onSkip(task) }}
className="px-3 py-1.5 text-sm bg-yellow-500 text-white rounded-lg hover:bg-yellow-600">
Ueberspringen
</button>
</>
)}
<button onClick={() => { onClose(); onEdit(task) }}
className="px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-100 rounded-lg border">
Bearbeiten
</button>
<button onClick={handleDelete}
className="px-3 py-1.5 text-sm text-red-600 hover:bg-red-50 rounded-lg border border-red-200 ml-auto">
Loeschen
</button>
</div>
</div>
</div>
)
}