'use client'; import { useState, useEffect, useRef } from 'react'; import { useParams, useRouter } from 'next/navigation'; import { ArrowLeft, Save, Building2, Mail, Key, Users, Plus, Trash2, Activity, FileText, FileBadge, Download, CheckCircle, Ticket, Phone, MapPin, Eye, EyeOff, RefreshCw, Copy } from 'lucide-react'; import { useToast } from '../../components/ToastProvider'; import { getStatusBadge } from '../../components/AppShell'; import { useSession } from 'next-auth/react'; type Tab = 'OVERVIEW' | 'TICKETS' | 'CONTACTS' | 'CONTRACTS' | 'MASTER_DATA' | 'CREDENTIALS' | 'DOCUMENTS' | 'SALES_DOCS'; export default function CustomerDashboard() { const params = useParams(); const router = useRouter(); const customerId = params.id; const { toast, confirm } = useToast(); const { data: session } = useSession(); const perms = (session?.user as any)?.permissions || []; const canEdit = perms.includes('CUSTOMERS_MANAGE') || perms.includes('CUSTOMERS_EDIT'); const [customer, setCustomer] = useState(null); const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState('OVERVIEW'); // Credentials State const [saving, setSaving] = useState(false); const [formData, setFormData] = useState({}); const [additionalEmails, setAdditionalEmails] = useState([]); const [visiblePasswords, setVisiblePasswords] = useState>({}); const [newEmailInput, setNewEmailInput] = useState(''); // Modals const [showContactModal, setShowContactModal] = useState(false); const [contactForm, setContactForm] = useState({ firstName: '', lastName: '', email: '', phone: '' }); const [showCredentialModal, setShowCredentialModal] = useState(false); const [credentialForm, setCredentialForm] = useState({ title: '', username: '', password: '', description: '' }); const [showTicketModal, setShowTicketModal] = useState(false); const [ticketForm, setTicketForm] = useState({ title: '', description: '', priority: 'MEDIUM' }); const [salesDocs, setSalesDocs] = useState([]); const generatePassword = (len = 16) => { const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789!@#$%&*'; return Array.from({ length: len }, () => chars[Math.floor(Math.random() * chars.length)]).join(''); }; const fileInputRef = useRef(null); const [uploading, setUploading] = useState(false); useEffect(() => { fetchCustomer(); }, [customerId]); const fetchCustomer = async () => { const res = await fetch(`/api/customers/${customerId}`); if (res.ok) { const data = await res.json(); setCustomer(data); setFormData({ companyName: data.companyName || '', firstName: data.firstName || '', lastName: data.lastName || '', email: data.email || '', phone: data.phone || '', address: data.address || '', zipCode: data.zipCode || '', city: data.city || '' }); setAdditionalEmails(data.additionalEmails || []); } setLoading(false); }; useEffect(() => { if (customerId) fetch(`/api/sales?customerId=${customerId}`).then(r => r.json()).then(setSalesDocs).catch(() => {}); }, [customerId]); const handleCreateTicket = async (e: React.FormEvent) => { e.preventDefault(); const res = await fetch('/api/tickets', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...ticketForm, customerId }) }); if (res.ok) { setShowTicketModal(false); setTicketForm({ title: '', description: '', priority: 'MEDIUM' }); fetchCustomer(); toast('Ticket erstellt', 'success'); } }; // ── STAMMDATEN ── const handleSaveCustomer = async (e?: React.FormEvent) => { if (e) e.preventDefault(); if (!canEdit) { toast('Keine Berechtigung', 'error'); return; } setSaving(true); const res = await fetch(`/api/customers/${customerId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...formData, additionalEmails }) }); if (res.ok) { toast('Kundendaten erfolgreich gespeichert.', 'success'); fetchCustomer(); } else { const data = await res.json(); toast(data.error || 'Fehler beim Speichern', 'error'); } setSaving(false); }; // ── CONTACTS ── const handleAddContact = async (e: React.FormEvent) => { e.preventDefault(); const res = await fetch(`/api/customers/${customerId}/contacts`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(contactForm) }); if (res.ok) { setShowContactModal(false); setContactForm({ firstName: '', lastName: '', email: '', phone: '' }); fetchCustomer(); } }; const handleDeleteContact = async (contactId: number) => { const isConfirmed = await confirm({ title: 'Mitarbeiter löschen', message: 'Mitarbeiter wirklich löschen?', danger: true }); if (!isConfirmed) return; await fetch(`/api/customers/${customerId}/contacts?contactId=${contactId}`, { method: 'DELETE' }); fetchCustomer(); }; // ── CREDENTIALS ── const handleAddCredential = async (e: React.FormEvent) => { e.preventDefault(); const res = await fetch(`/api/customers/${customerId}/credentials`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentialForm) }); if (res.ok) { setShowCredentialModal(false); setCredentialForm({ title: '', username: '', password: '', description: '' }); fetchCustomer(); toast('Zugangsdaten gespeichert', 'success'); } }; const handleDeleteCredential = async (credId: number) => { const isConfirmed = await confirm({ title: 'Löschen', message: 'Zugangsdaten wirklich löschen?', danger: true }); if (!isConfirmed) return; await fetch(`/api/customers/${customerId}/credentials?credId=${credId}`, { method: 'DELETE' }); fetchCustomer(); }; // ── DOCUMENTS ── const handleFileUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setUploading(true); const uploadData = new FormData(); uploadData.append('file', file); const res = await fetch(`/api/customers/${customerId}/documents`, { method: 'POST', body: uploadData }); setUploading(false); if (fileInputRef.current) fileInputRef.current.value = ''; if (res.ok) { toast('Dokument hochgeladen', 'success'); fetchCustomer(); } else { toast('Fehler beim Hochladen', 'error'); } }; const handleDeleteDocument = async (docId: number) => { const isConfirmed = await confirm({ title: 'Dokument löschen', message: 'Dokument wirklich löschen?', danger: true }); if (!isConfirmed) return; await fetch(`/api/customers/${customerId}/documents?docId=${docId}`, { method: 'DELETE' }); fetchCustomer(); }; 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 (loading) return
Lade Kundendaten...
; if (!customer) return
Kunde nicht gefunden.
; const openTickets = customer.tickets?.filter((t:any) => t.status === 'OPEN' || t.status === 'IN_PROGRESS').length || 0; const totalTickets = customer.tickets?.length || 0; const activeContracts = customer.contracts?.filter((c:any) => c.status === 'ACTIVE').length || 0; const tabs: { id: Tab, label: string, icon: any }[] = [ { id: 'OVERVIEW', label: 'Alles', icon: Activity }, { id: 'TICKETS', label: `Tickets (${totalTickets})`, icon: Ticket }, { id: 'CONTACTS', label: 'Mitarbeiter', icon: Users }, { id: 'CONTRACTS', label: 'Abos & Verträge', icon: FileBadge }, { id: 'SALES_DOCS', label: `Belege (${salesDocs.length})`, icon: FileText }, { id: 'MASTER_DATA', label: 'Stammdaten', icon: Building2 }, { id: 'CREDENTIALS', label: 'Zugangsdaten', icon: Key }, { id: 'DOCUMENTS', label: 'Dokumente', icon: FileText }, ]; return (
{/* Header */}

{customer.companyName || `${customer.firstName} ${customer.lastName}`}

{customer.companyName &&

{customer.firstName} {customer.lastName}

}
{customer.email} {customer.phone && {customer.phone}} {(customer.address || customer.city) && ( {[customer.address, customer.zipCode, customer.city].filter(Boolean).join(', ')} )}
{/* Quick Actions */}
{activeTab === 'MASTER_DATA' && canEdit && ( )} {activeTab === 'TICKETS' && ( )} {activeTab === 'CONTACTS' && canEdit && ( )} {activeTab === 'CREDENTIALS' && canEdit && ( )} {activeTab === 'DOCUMENTS' && (
)}
{/* Status Bar */}
0 ? 'bg-amber-100 text-amber-700' : 'bg-emerald-100 text-emerald-700'}`}> {openTickets > 0 ? `${openTickets} offene Tickets` : 'Keine offenen Tickets'} {totalTickets} Tickets gesamt {activeContracts} aktive Verträge {customer.contacts?.length || 0} Mitarbeiter {customer.credentials?.length || 0} Zugangsdaten
{/* Tabs */}
{tabs.map(tab => { const Icon = tab.icon; const isActive = activeTab === tab.id; return ( ); })}
{/* Tab Content */}
{/* ── OVERVIEW ── */} {activeTab === 'OVERVIEW' && (

Letzte Aktivitäten

{customer.tickets?.length === 0 ? (

Bisher keine Tickets vorhanden.

) : ( customer.tickets?.slice(0, 5).map((t: any) => (
router.push(`/tickets/${t.id}`)} className="flex items-start justify-between p-4 bg-slate-50 hover:bg-indigo-50/50 rounded-xl border border-slate-100 cursor-pointer transition-colors group">

{t.title}

{t.description}

{new Date(t.createdAt).toLocaleDateString('de-DE')}

{getStatusBadge(t.status)}
)) )}
{/* Quick Stats Sidebar */}

Tickets gesamt

{customer.tickets?.length || 0}

Aktive Verträge

{customer.contracts?.filter((c:any) => c.status === 'ACTIVE').length || 0}

)} {/* ── TICKETS ── */} {activeTab === 'TICKETS' && (
{customer.tickets?.length === 0 ? (
Keine Tickets vorhanden.
) : ( {customer.tickets.map((t: any) => ( router.push(`/tickets/${t.id}`)} className="hover:bg-indigo-50/40 transition-colors cursor-pointer"> ))}
ID Titel Status Erstellt
#{t.id.toString().padStart(4,'0')} {t.title} {getStatusBadge(t.status)} {new Date(t.createdAt).toLocaleDateString('de-DE')}
)}
)} {/* ── CONTACTS ── */} {activeTab === 'CONTACTS' && (
{customer.contacts?.length === 0 ? (
Keine Mitarbeiter hinterlegt.
) : ( {customer.contacts.map((contact: any) => ( ))}
Name E-Mail Telefon Aktion
{contact.firstName} {contact.lastName} {contact.email} {contact.phone || '-'}
)}
)} {/* ── CONTRACTS ── */} {activeTab === 'CONTRACTS' && (
{customer.contracts?.length === 0 ? (
Keine Verträge vorhanden.
) : (
{customer.contracts?.map((contract: any) => (

{contract.title}

{contract.status === 'ACTIVE' ? 'Aktiv' : 'Inaktiv'}
{contract.description &&

{contract.description}

}

Gültig ab

{new Date(contract.startDate).toLocaleDateString('de-DE')}

Preis / Monat

{contract.monthlyPrice.toFixed(2)} €

))}
)}
)} {/* ── MASTER DATA ── */} {activeTab === 'MASTER_DATA' && (

Stammdaten

setFormData({...formData, companyName: e.target.value})} />
setFormData({...formData, firstName: e.target.value})} />
setFormData({...formData, lastName: e.target.value})} />
setFormData({...formData, address: e.target.value})} />
setFormData({...formData, zipCode: e.target.value})} />
setFormData({...formData, city: e.target.value})} />
setFormData({...formData, phone: e.target.value})} />

Portal-Zugang

setFormData({...formData, email: e.target.value})} />

Ticket E-Mails

setNewEmailInput(e.target.value)} onKeyDown={e => e.key === 'Enter' && setAdditionalEmails([...additionalEmails, newEmailInput])} />
{additionalEmails.map((email, idx) => (
{email}
))}
)} {/* ── CREDENTIALS ── */} {activeTab === 'CREDENTIALS' && (
{customer.credentials?.length === 0 ? (
Keine Zugangsdaten hinterlegt.
) : ( {customer.credentials.map((cred: any) => ( ))}
Bezeichnung Benutzername Passwort Aktion
{cred.title}
{visiblePasswords[cred.id] ? cred.password : '••••••••'}
)}
)} {/* ── DOCUMENTS ── */} {activeTab === 'DOCUMENTS' && (
{customer.documents?.length === 0 ? (
Keine Dokumente hinterlegt.
) : ( {customer.documents.map((doc: any) => ( ))}
Dateiname Größe Hochgeladen Aktionen
{doc.fileName} {formatBytes(doc.fileSize)} {new Date(doc.createdAt).toLocaleDateString('de-DE')}
)}
)} {/* ── SALES DOCS ── */} {activeTab === 'SALES_DOCS' && (
{salesDocs.length === 0 ? (
Keine Belege vorhanden.
) : ( {salesDocs.map((d: any) => ( router.push(`/sales/${d.id}`)} className="hover:bg-indigo-50/40 transition-colors cursor-pointer"> ))}
Nummer Typ Status Betrag Datum
{d.number} {{'QUOTE':'Angebot','ORDER_CONFIRMATION':'AB','DELIVERY_NOTE':'Lieferschein','INVOICE':'Rechnung','CREDIT_NOTE':'RK'}[d.type as string]} {{'DRAFT':'Entwurf','SENT':'Gesendet','ACCEPTED':'Angenommen','REJECTED':'Abgelehnt','DELIVERED':'Geliefert','PAID':'Bezahlt','CANCELLED':'Storniert','ARCHIVED':'Archiviert'}[d.status as string]} {d.total.toFixed(2)} € {new Date(d.createdAt).toLocaleDateString('de-DE')}
)}
)}
{/* MODAL: Mitarbeiter */} {showContactModal && (

Mitarbeiter hinzufügen

setContactForm({...contactForm, firstName: e.target.value})} />
setContactForm({...contactForm, lastName: e.target.value})} />
setContactForm({...contactForm, email: e.target.value})} />
setContactForm({...contactForm, phone: e.target.value})} />
)} {/* MODAL: Zugangsdaten */} {showCredentialModal && (

Zugangsdaten anlegen

setCredentialForm({...credentialForm, title: e.target.value})} />
setCredentialForm({...credentialForm, username: e.target.value})} />
setCredentialForm({...credentialForm, description: e.target.value})} />
setCredentialForm({...credentialForm, password: e.target.value})} />
)} {/* MODAL: Neues Ticket */} {showTicketModal && (

Neues Ticket erstellen

setTicketForm({...ticketForm, title: e.target.value})} />