'use client'; import { useState, useEffect, useRef } from 'react'; import { useParams, useRouter } from 'next/navigation'; import { ArrowLeft, Download, PenTool, Send, Check, X, Printer, FilePlus2 } from 'lucide-react'; import { useToast } from '../../components/ToastProvider'; import { useSession } from 'next-auth/react'; const typeLabels: Record = { QUOTE: 'Angebot', ORDER_CONFIRMATION: 'Auftragsbestätigung', DELIVERY_NOTE: 'Lieferschein', INVOICE: 'Rechnung', CREDIT_NOTE: 'Rechnungskorrektur' }; const statusLabels: Record = { DRAFT: 'Entwurf', SENT: 'Gesendet', ACCEPTED: 'Angenommen', REJECTED: 'Abgelehnt', DELIVERED: 'Geliefert', PAID: 'Bezahlt', CANCELLED: 'Storniert', ARCHIVED: 'Archiviert' }; const statusColors: Record = { DRAFT: 'bg-slate-100 text-slate-600', SENT: 'bg-blue-100 text-blue-700', ACCEPTED: 'bg-emerald-100 text-emerald-700', REJECTED: 'bg-red-100 text-red-700', DELIVERED: 'bg-amber-100 text-amber-700', PAID: 'bg-emerald-100 text-emerald-700', CANCELLED: 'bg-slate-200 text-slate-500', ARCHIVED: 'bg-slate-100 text-slate-400' }; export default function SalesDocDetailPage() { const params = useParams(); const router = useRouter(); const { toast, confirm } = useToast(); const { data: session } = useSession(); const perms = (session?.user as any)?.permissions || []; const canManage = perms.includes('SALES_MANAGE'); const [doc, setDoc] = useState(null); const [loading, setLoading] = useState(true); const [showSignature, setShowSignature] = useState(false); const canvasRef = useRef(null); const isDrawing = useRef(false); useEffect(() => { fetchDoc(); }, [params.id]); const fetchDoc = async () => { const res = await fetch(`/api/sales/${params.id}`); if (res.ok) setDoc(await res.json()); setLoading(false); }; const updateStatus = async (status: string) => { const res = await fetch(`/api/sales/${params.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status }) }); if (res.ok) { const data = await res.json(); // If quote was accepted, auto-AB was created → show info if (data.followUpId) { toast(`Auftragsbestätigung ${data.followUpNumber} wurde automatisch erstellt & versendet. Angebot archiviert.`, 'success'); } else { toast(`Status: ${statusLabels[status]}`, 'success'); } fetchDoc(); } }; const createFollowUp = async () => { const followUpLabels: Record = { ORDER_CONFIRMATION: 'Lieferschein', DELIVERY_NOTE: 'Rechnung', INVOICE: 'Rechnungskorrektur' }; const nextLabel = followUpLabels[doc.type] || 'Folgebeleg'; const msg = doc.type === 'INVOICE' ? `Rechnungskorrektur erstellen? Die Rechnung ${doc.number} wird storniert.` : `Aus dieser ${typeLabels[doc.type]} einen ${nextLabel} erstellen?`; const ok = await confirm({ title: `${nextLabel} erstellen`, message: msg, danger: doc.type === 'INVOICE' }); if (!ok) return; const res = await fetch(`/api/sales/${params.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'CREATE_FOLLOWUP' }) }); if (res.ok) { const newDoc = await res.json(); toast(`${nextLabel} ${newDoc.number} erstellt.`, 'success'); router.push(`/sales/${newDoc.id}`); } else { toast('Fehler beim Erstellen', 'error'); } }; // Signature Canvas const startDraw = (e: React.MouseEvent | React.TouchEvent) => { isDrawing.current = true; const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; const rect = canvas.getBoundingClientRect(); const x = 'touches' in e ? e.touches[0].clientX - rect.left : e.clientX - rect.left; const y = 'touches' in e ? e.touches[0].clientY - rect.top : e.clientY - rect.top; ctx.beginPath(); ctx.moveTo(x, y); }; const draw = (e: React.MouseEvent | React.TouchEvent) => { if (!isDrawing.current) return; const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; const rect = canvas.getBoundingClientRect(); const x = 'touches' in e ? e.touches[0].clientX - rect.left : e.clientX - rect.left; const y = 'touches' in e ? e.touches[0].clientY - rect.top : e.clientY - rect.top; ctx.lineWidth = 2; ctx.lineCap = 'round'; ctx.strokeStyle = '#1e293b'; ctx.lineTo(x, y); ctx.stroke(); }; const endDraw = () => { isDrawing.current = false; }; const clearSignature = () => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); ctx?.clearRect(0, 0, canvas.width, canvas.height); }; const saveSignature = async () => { const canvas = canvasRef.current; if (!canvas) return; const dataUrl = canvas.toDataURL('image/png'); const res = await fetch(`/api/sales/${params.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ signatureData: dataUrl }) }); if (res.ok) { toast('Unterschrift gespeichert', 'success'); setShowSignature(false); fetchDoc(); } }; if (loading) return
Lade...
; if (!doc) return
Beleg nicht gefunden.
; const nextActions: Record = { DRAFT: [{ label: 'Als gesendet markieren', status: 'SENT', icon: Send }], SENT: doc.type === 'ORDER_CONFIRMATION' ? [] : doc.type === 'INVOICE' ? [{ label: 'Bezahlt', status: 'PAID', icon: Check }] : doc.type === 'CREDIT_NOTE' ? [] : [{ label: 'Angenommen', status: 'ACCEPTED', icon: Check }, { label: 'Abgelehnt', status: 'REJECTED', icon: X }], ACCEPTED: doc.type === 'DELIVERY_NOTE' ? [{ label: 'Geliefert', status: 'DELIVERED', icon: Check }] : [], }; const actions = nextActions[doc.status] || []; // Can create follow-up? AB → LS, LS → RE, RE → RK const canCreateFollowUp = canManage && ( (doc.type === 'ORDER_CONFIRMATION' && doc.status === 'SENT') || (doc.type === 'DELIVERY_NOTE' && ['SENT', 'ACCEPTED', 'DELIVERED'].includes(doc.status)) || (doc.type === 'INVOICE' && ['SENT', 'PAID'].includes(doc.status)) ); return (

{typeLabels[doc.type]} {doc.number}

{statusLabels[doc.status]} {doc.previousStatus && ['ARCHIVED', 'CANCELLED'].includes(doc.status) && ( vorher: {statusLabels[doc.previousStatus] || doc.previousStatus} )} {new Date(doc.createdAt).toLocaleDateString('de-DE')}
{canManage && !doc.signatureData && !['INVOICE', 'ORDER_CONFIRMATION'].includes(doc.type) && !['CANCELLED', 'ARCHIVED'].includes(doc.status) && ( )} {canManage && actions.map(a => ( ))} {canCreateFollowUp && ( )}
{/* Document Body (print-friendly) */}
{/* Sidebar Info */}

Details

Erstellt von{doc.createdBy?.firstName} {doc.createdBy?.lastName}
Typ{typeLabels[doc.type]}
Positionen{doc.items?.length}
{doc.signedAt &&
Unterschrieben
}
{canManage && !['CANCELLED', 'PAID', 'ARCHIVED'].includes(doc.status) && ( )} {doc.status === 'ARCHIVED' && (

Dieser Beleg wurde archiviert.

)}
{/* Signature Modal */} {showSignature && (

Digitale Unterschrift

Bitte unterschreiben Sie im Feld unten.

)}
); }