"use client"; import { useState, useEffect } from "react"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Download, ExternalLink, Search, Loader2, Eye, ArrowUpDown, Building2, User, Mail, MapPin, Package, FileText, Calendar, Layers, Sparkles, XCircle, CheckCircle, Edit3 } from "lucide-react"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from "@/components/ui/dialog"; import { updateOrderStatus, rejectOrder, updateOrderPayload } from "@/lib/actions/orders"; interface OrdersTableProps { initialOrders: any[]; } const statusLabel: Record = { pending: 'Eingegangen', pending_approval: 'Wartet auf Freigabe', in_review: 'In Prüfung', approved: 'Freigegeben', active: 'Aktiviert', completed: 'Abgeschlossen', cancelled: 'Storniert', rejected: 'Abgelehnt', }; const statusClass: Record = { pending: 'bg-amber-500/20 text-amber-400 border border-amber-500/30', pending_approval: 'bg-amber-600/20 text-amber-300 border border-amber-500/40', in_review: 'bg-purple-500/20 text-purple-400 border border-purple-500/30', approved: 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30', active: 'bg-blue-500/20 text-blue-400 border border-blue-500/30', completed: 'bg-green-500/20 text-green-400 border border-green-500/30', cancelled: 'bg-slate-500/20 text-slate-400 border border-slate-500/30', rejected: 'bg-red-500/20 text-red-400 border border-red-500/30', }; type SortField = 'date_desc' | 'date_asc' | 'price_desc' | 'price_asc' | 'order_num'; export function OrdersTable({ initialOrders }: OrdersTableProps) { const [orders, setOrders] = useState(initialOrders); const [search, setSearch] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [sortBy, setSortBy] = useState("date_desc"); const [updatingId, setUpdatingId] = useState(null); const [selectedOrder, setSelectedOrder] = useState(null); // Rejection Dialog State const [rejectingOrder, setRejectingOrder] = useState(null); const [rejectionReason, setRejectionReason] = useState(""); const [isSubmittingReject, setIsSubmittingReject] = useState(false); // Preiskorrektur States const [isEditingSnapshot, setIsEditingSnapshot] = useState(false); const [editedSnapshot, setEditedSnapshot] = useState(null); const [isSavingSnapshot, setIsSavingSnapshot] = useState(false); // Initialize editedSnapshot when selectedOrder changes useEffect(() => { if (selectedOrder) { setEditedSnapshot(JSON.parse(JSON.stringify(selectedOrder.order_data || {}))); setIsEditingSnapshot(false); } else { setEditedSnapshot(null); setIsEditingSnapshot(false); } }, [selectedOrder]); const updateItemBasePrice = (itemIdx: number, newPrice: number) => { if (!editedSnapshot) return; const updatedItems = [...editedSnapshot.items]; const item = { ...updatedItems[itemIdx] }; item.base_price = newPrice; const moduleTotal = (item.selected_modules || []).reduce((acc: number, m: any) => acc + (m.total_price ?? (m.price * (m.quantity || 1))), 0); item.item_total = newPrice + moduleTotal; updatedItems[itemIdx] = item; recalculateSnapshot(updatedItems); }; const updateModulePriceOrQty = (itemIdx: number, modIdx: number, field: 'price' | 'quantity', value: number) => { if (!editedSnapshot) return; const updatedItems = [...editedSnapshot.items]; const item = { ...updatedItems[itemIdx] }; const updatedModules = [...(item.selected_modules || [])]; const mod = { ...updatedModules[modIdx] }; if (field === 'price') { mod.price = value; } else { mod.quantity = value; } mod.total_price = mod.price * (mod.quantity || 1); updatedModules[modIdx] = mod; item.selected_modules = updatedModules; const moduleTotal = updatedModules.reduce((acc: number, m: any) => acc + (m.total_price ?? (m.price * (m.quantity || 1))), 0); item.item_total = (item.base_price || 0) + moduleTotal; updatedItems[itemIdx] = item; recalculateSnapshot(updatedItems); }; const recalculateSnapshot = (updatedItems: any[]) => { if (!editedSnapshot) return; const taxRate = editedSnapshot.tax_rate ?? 19; const subtotal = updatedItems.reduce((acc: number, item: any) => acc + (item.item_total || 0), 0); const taxAmount = Math.round(subtotal * (taxRate / 100) * 100) / 100; const total = Math.round((subtotal + taxAmount) * 100) / 100; setEditedSnapshot({ ...editedSnapshot, items: updatedItems, subtotal, tax_amount: taxAmount, total }); }; const handleSaveSnapshot = async () => { if (!editedSnapshot || !selectedOrder) return; setIsSavingSnapshot(true); try { const updated = await updateOrderPayload(selectedOrder.id, { orderSnapshot: editedSnapshot }); setOrders(prev => prev.map(o => o.id === selectedOrder.id ? { ...o, order_data: editedSnapshot, total_price: editedSnapshot.total } : o)); setSelectedOrder({ ...selectedOrder, order_data: editedSnapshot, total_price: editedSnapshot.total }); setIsEditingSnapshot(false); } catch (error: any) { console.error(error); alert("Fehler beim Speichern der Änderungen: " + error.message); } finally { setIsSavingSnapshot(false); } }; // Badge-Counter berechnen const countAll = orders.length; const countOffen = orders.filter(o => ["pending", "pending_approval", "in_review"].includes(o.status)).length; const countApproved = orders.filter(o => o.status === "approved").length; const countActive = orders.filter(o => ["active", "completed"].includes(o.status)).length; const countRejected = orders.filter(o => ["rejected", "cancelled"].includes(o.status)).length; const displaySnapshot = editedSnapshot || selectedOrder?.order_data || {}; const displayItems = displaySnapshot.items || []; useEffect(() => { setOrders(initialOrders); }, [initialOrders]); const handleStatusChange = async (orderId: string, newStatus: 'pending' | 'pending_approval' | 'in_review' | 'approved' | 'active' | 'completed' | 'cancelled' | 'rejected') => { if (newStatus === 'rejected') { const target = orders.find(o => o.id === orderId); if (target) { setRejectingOrder(target); setRejectionReason(""); return; } } setUpdatingId(orderId); try { await updateOrderStatus(orderId, newStatus); setOrders(prev => prev.map(o => o.id === orderId ? { ...o, status: newStatus } : o)); if (selectedOrder?.id === orderId) { setSelectedOrder((prev: any) => prev ? { ...prev, status: newStatus } : null); } } catch (error) { console.error(error); alert("Fehler beim Ändern des Status."); } finally { setUpdatingId(null); } }; const handleConfirmReject = async () => { if (!rejectingOrder) return; setIsSubmittingReject(true); try { await rejectOrder(rejectingOrder.id, rejectionReason || "Vom Administrator abgelehnt."); setOrders(prev => prev.map(o => o.id === rejectingOrder.id ? { ...o, status: 'rejected' } : o)); if (selectedOrder?.id === rejectingOrder.id) { setSelectedOrder((prev: any) => prev ? { ...prev, status: 'rejected' } : null); } setRejectingOrder(null); setRejectionReason(""); } catch (error) { console.error(error); alert("Fehler beim Ablehnen der Anfrage."); } finally { setIsSubmittingReject(false); } }; const filteredAndSortedOrders = orders .filter((order) => { const orderNum = (order.order_number || "").toLowerCase(); const company = (order.customer_data?.company_name || "").toLowerCase(); const contact = `${order.customer_data?.first_name || ""} ${order.customer_data?.last_name || ""}`.toLowerCase(); const matchesSearch = orderNum.includes(search.toLowerCase()) || company.includes(search.toLowerCase()) || contact.includes(search.toLowerCase()); let matchesStatus = false; if (statusFilter === "all") matchesStatus = true; else if (statusFilter === "offen") matchesStatus = ["pending", "pending_approval", "in_review"].includes(order.status); else if (statusFilter === "approved") matchesStatus = order.status === "approved"; else if (statusFilter === "active") matchesStatus = ["active", "completed"].includes(order.status); else if (statusFilter === "rejected") matchesStatus = ["rejected", "cancelled"].includes(order.status); return matchesSearch && matchesStatus; }) .sort((a, b) => { if (sortBy === 'date_desc') return new Date(b.created_at).getTime() - new Date(a.created_at).getTime(); if (sortBy === 'date_asc') return new Date(a.created_at).getTime() - new Date(b.created_at).getTime(); if (sortBy === 'price_desc') return (b.total_price || 0) - (a.total_price || 0); if (sortBy === 'price_asc') return (a.total_price || 0) - (b.total_price || 0); if (sortBy === 'order_num') return (a.order_number || a.id).localeCompare(b.order_number || b.id); return 0; }); return (
{/* Filter- & Sortierleiste */}
setSearch(e.target.value)} className="pl-10 bg-slate-950/40 border-white/10 text-white placeholder:text-slate-500" />
setSortBy('date_desc')} className="text-xs cursor-pointer"> Datum (Neueste zuerst) setSortBy('date_asc')} className="text-xs cursor-pointer"> Datum (Älteste zuerst) setSortBy('price_desc')} className="text-xs cursor-pointer"> Betrag (Höchster zuerst) setSortBy('price_asc')} className="text-xs cursor-pointer"> Betrag (Niedrigster zuerst) setSortBy('order_num')} className="text-xs cursor-pointer"> Anfragenr.
{/* Tabelle */}
Anfragenr. Datum Kunde / Firma Auswahl / Produkte Gesamtbetrag Status Aktionen {filteredAndSortedOrders.length === 0 ? ( Keine Anfragen gefunden. ) : ( filteredAndSortedOrders.map((order) => { const items = order.order_data?.items || []; return ( #{order.order_number || order.id.slice(0, 8)} {new Date(order.created_at).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })}
{order.customer_data?.company_name || 'Privatkunde'}
{order.customer_data?.first_name} {order.customer_data?.last_name}
{items.slice(0, 2).map((item: any, idx: number) => (
{item.device_name || 'Kasse'}: {item.product_name} {item.selected_modules?.length > 0 && ( + {item.selected_modules.map((m: any) => m.module_name || m.name).join(', ')} )}
))} {items.length > 2 && ( + {items.length - 2} weitere Kassen )}
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(order.total_price || 0)} {Object.entries(statusLabel).map(([key, label]) => ( handleStatusChange(order.id, key as any)} className={`text-xs cursor-pointer hover:bg-white/10 transition-colors focus:bg-white/10 focus:text-white ${order.status === key ? 'font-bold text-primary' : ''}`} > {label} ))}
); }) )}
{/* Detail-Modal für vollständige Anfragenübersicht & Freigabe-Workflow */} {selectedOrder && ( !open && setSelectedOrder(null)}>
Anfragenr. #{selectedOrder.order_number || selectedOrder.id.slice(0, 8)} {statusLabel[selectedOrder.status] ?? selectedOrder.status}
Erstellt am {new Date(selectedOrder.created_at).toLocaleString('de-DE')}
{isEditingSnapshot ? "Vorschau Summe" : "Gesamtsumme"}
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format( isEditingSnapshot ? (editedSnapshot?.total || 0) : (selectedOrder.total_price || 0) )}
{/* Kunden & Stammdaten */}
Kunden- & Firmendaten
{selectedOrder.customer_data?.company_name || 'Keine Firma angegeben'}
{selectedOrder.customer_data?.first_name} {selectedOrder.customer_data?.last_name}
{selectedOrder.customer_data?.email && (
{selectedOrder.customer_data.email}
)}
Adresse & Standort
{selectedOrder.customer_data?.street || 'Keine Straße'}
{selectedOrder.customer_data?.zip} {selectedOrder.customer_data?.city}
{/* Geräte & Produktauswahl (Snapshot Aufteilung) */}
Konfigurierte Kassen & Module ({displayItems.length})
{selectedOrder.status === 'in_review' && !isEditingSnapshot && ( )}
{isEditingSnapshot && (
Sie befinden sich im Preiskorrektur-Modus.
)}
{displayItems.map((item: any, idx: number) => (
{item.device_name || `Kasse #${idx + 1}`} {item.product_name}
{isEditingSnapshot ? (
updateItemBasePrice(idx, parseFloat(e.target.value) || 0)} className="w-20 h-7 bg-slate-950 border-white/10 text-white text-xs px-2" />
) : ( {new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(item.base_price || 0)} )}
{/* Ausgewählte Module & Lizenzen */} {item.selected_modules?.length > 0 && (
Zusatzmodule ({item.selected_modules.length}):
{item.selected_modules.map((mod: any, mIdx: number) => (
{mod.module_name || mod.name} {isEditingSnapshot ? (
updateModulePriceOrQty(idx, mIdx, 'price', parseFloat(e.target.value) || 0)} className="w-14 h-6 bg-slate-900 border-white/10 text-white text-[10px] px-1" /> x updateModulePriceOrQty(idx, mIdx, 'quantity', parseInt(e.target.value) || 1)} className="w-10 h-6 bg-slate-900 border-white/10 text-white text-[10px] px-1" />
) : ( +{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(mod.price * (mod.quantity || 1))} )}
))}
)}
))}
{/* Workflow Freigabe & Aktionen */}
Freigabe-Workflow & Verwalter-Aktionen
)} {/* Dialog für Ablehnungsbegründung */} {rejectingOrder && ( !open && setRejectingOrder(null)}> Anfrage Ablehnen Bitte geben Sie einen Grund für die Ablehnung der Anfrage #{rejectingOrder.order_number || rejectingOrder.id.slice(0, 8)} an.
setRejectionReason(e.target.value)} className="bg-slate-900 border-white/10 text-white text-xs" />
)}
); }