// /opt/erp-system/app/tickets/[id]/page.tsx 'use client'; import { useState, useEffect, useRef } from 'react'; import { useParams, useRouter } from 'next/navigation'; import { useSession } from "next-auth/react"; import { Send, CheckCircle, Clock, ArrowLeft, StickyNote, Activity, User as UserIcon, Paperclip, FileText, Download, AlertTriangle } from 'lucide-react'; import { useToast } from '../../components/ToastProvider'; import { getStatusBadge, getPriorityBadge } from '../../components/AppShell'; export default function TicketDetailPage() { const params = useParams(); const router = useRouter(); const { data: session } = useSession(); const ticketId = params.id; const { toast } = useToast(); const [ticket, setTicket] = useState(null); const [users, setUsers] = useState([]); const [attachments, setAttachments] = useState([]); const [error, setError] = useState(false); const [messageInput, setMessageInput] = useState(''); const [uploading, setUploading] = useState(false); const [showModal, setShowModal] = useState(false); const [modalMode, setModalMode] = useState<'addTime' | 'close' | 'addNote'>('addTime'); const [modalData, setModalData] = useState({ durationMins: 15, description: '', content: '' }); const fileInputRef = useRef(null); useEffect(() => { if (ticketId) { fetchTicket(); fetchAttachments(); if ((session?.user as any)?.userType === 'TEAM') { fetchUsers(); } } }, [ticketId, session]); const fetchTicket = async () => { try { const res = await fetch(`/api/tickets/${ticketId}`); if (res.ok) setTicket(await res.json()); else setError(true); } catch (err) { setError(true); } }; const fetchUsers = async () => { const res = await fetch('/api/users'); if (res.ok) setUsers(await res.json()); }; const fetchAttachments = async () => { const res = await fetch(`/api/tickets/${ticketId}/attachments`); if (res.ok) setAttachments(await res.json()); }; const handleSendMessage = async (e: React.FormEvent) => { e.preventDefault(); if (!messageInput.trim()) return; await fetch(`/api/tickets/${ticketId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'addMessage', content: messageInput }) }); setMessageInput(''); fetchTicket(); }; const handleFileUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setUploading(true); const formData = new FormData(); formData.append('file', file); const res = await fetch(`/api/tickets/${ticketId}/attachments`, { method: 'POST', body: formData }); setUploading(false); if (fileInputRef.current) fileInputRef.current.value = ''; if (res.ok) { fetchAttachments(); fetchTicket(); } else { toast('Fehler beim Hochladen der Datei.', 'error'); } }; const handleAssign = async (userId: string) => { await fetch(`/api/tickets/${ticketId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'assign', userId }) }); fetchTicket(); }; const handleChangePriority = async (priority: string) => { await fetch(`/api/tickets/${ticketId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'changePriority', priority }) }); fetchTicket(); }; const handleModalSubmit = async (e: React.FormEvent) => { e.preventDefault(); let payload = {}; if (modalMode === 'addNote') { payload = { action: 'addNote', content: modalData.content }; } else { const action = modalMode === 'close' ? 'closeTicket' : 'addTimeEntry'; payload = { action, durationMins: modalData.durationMins, description: modalData.description }; } const res = await fetch(`/api/tickets/${ticketId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (res.ok) { setShowModal(false); setModalData({ durationMins: 15, description: '', content: '' }); fetchTicket(); } }; const openModal = (mode: 'addTime' | 'close' | 'addNote') => { setModalMode(mode); setShowModal(true); }; const formatBytes = (bytes: number) => { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; if (error) return
Fehler: Ticket konnte nicht geladen werden.
; if (!ticket) return
Lade Ticketdaten...
; const isClosed = ticket.status === 'RESOLVED' || ticket.status === 'CLOSED'; const userType = (session?.user as any)?.userType; const timelineItems = [ ...(ticket.timeEntries || []).map((entry: any) => ({ ...entry, type: 'time' })), ...(ticket.notes || []).map((note: any) => ({ ...note, type: 'note' })) ].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); return (

#{ticket.id}: {ticket.title}

{getStatusBadge(ticket.status)} {getPriorityBadge(ticket.priority)}

Beschreibung

{ticket.description}

Nachrichtenverlauf
{ticket.messages.length === 0 ? (

Noch keine Nachrichten.

) : ( ticket.messages.map((msg: any) => (

{msg.content}

{new Date(msg.createdAt).toLocaleString('de-DE')}
)) )}
{!isClosed && (
setMessageInput(e.target.value)} placeholder="Antwort schreiben..." className="flex-1 border border-slate-300 p-2.5 rounded-xl focus-ring outline-none transition-all" />
)}

Anhänge ({attachments.length})

{attachments.length > 0 ? (
{attachments.map(att => (

{att.fileName}

{formatBytes(att.fileSize)}

))}
) : (

Keine Dateien vorhanden.

)}
{userType === 'TEAM' && ( <>

Bearbeiter

Priorität

{!isClosed && (

Aktionen

)}

Interne Chronik

{timelineItems.map((item: any) => (

{item.type === 'time' ? `Zeit: ${item.durationMins} Min.` : 'Interne Notiz'}

{item.type === 'time' ? item.description : item.content}

{new Date(item.createdAt).toLocaleString('de-DE')} • {item.user?.firstName}
))}
)}
{showModal && (

{modalMode === 'addNote' ? 'Interne Notiz' : 'Zeit erfassen'}

{modalMode !== 'addNote' && (
setModalData({...modalData, durationMins: parseInt(e.target.value)})} className="w-full border border-slate-300 p-2.5 rounded-xl focus-ring outline-none transition-all" />
)}
{/* HIER WAR DER FEHLER: setModalData statt setFormData */}