diff --git a/shop/app/admin/orders/page.tsx b/shop/app/admin/orders/page.tsx
index e54c755..c132b5f 100644
--- a/shop/app/admin/orders/page.tsx
+++ b/shop/app/admin/orders/page.tsx
@@ -7,8 +7,8 @@ export default async function AdminOrdersPage() {
-
Bestellungen
-
Verwalten Sie alle Kundenbestellungen und greifen Sie auf generierte Annfragesbestätigungen zu.
+
Anfragen & Bestellungen
+
Verwalten Sie alle Anfragen, prüfen Sie Details und greifen Sie auf generierte Anfragebestätigungen zu.
diff --git a/shop/components/admin/orders-table.tsx b/shop/components/admin/orders-table.tsx
index 4ce3b8b..4702e5c 100644
--- a/shop/components/admin/orders-table.tsx
+++ b/shop/components/admin/orders-table.tsx
@@ -5,15 +5,42 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
-import { Download, ExternalLink, Search, Loader2 } from "lucide-react";
+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 { updateOrderStatus } from "@/lib/actions/orders";
-import { resolveSupabaseUrl } from "@/lib/utils";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from "@/components/ui/dialog";
+import { updateOrderStatus, rejectOrder } from "@/lib/actions/orders";
interface OrdersTableProps {
initialOrders: any[];
@@ -21,7 +48,10 @@ interface OrdersTableProps {
const statusLabel: Record
= {
pending: 'Eingegangen',
- active: 'In Bearbeitung',
+ pending_approval: 'Wartet auf Freigabe',
+ in_review: 'In Prüfung',
+ approved: 'Freigegeben',
+ active: 'Aktiviert',
completed: 'Abgeschlossen',
cancelled: 'Storniert',
rejected: 'Abgelehnt',
@@ -29,27 +59,51 @@ const statusLabel: Record = {
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-red-500/20 text-red-400 border border-red-500/30',
- rejected: 'bg-rose-500/20 text-rose-400 border border-rose-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);
useEffect(() => {
setOrders(initialOrders);
}, [initialOrders]);
- const handleStatusChange = async (orderId: string, newStatus: 'pending' | 'active' | 'completed' | 'cancelled' | 'rejected') => {
+ 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.");
@@ -58,36 +112,98 @@ export function OrdersTable({ initialOrders }: OrdersTableProps) {
}
};
- const filteredOrders = 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());
+ 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 matchesStatus = statusFilter === "all" || order.status === statusFilter;
+ 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());
- return matchesSearch && matchesStatus;
- });
+ const matchesStatus = statusFilter === "all" || order.status === statusFilter;
+
+ 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 (
- {/* Filterleiste */}
-
-
-
-
setSearch(e.target.value)}
- className="pl-10 bg-slate-950/40 border-white/10 text-white placeholder:text-slate-500"
- />
+ {/* 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.
+
+
+
-
+
+
+
+
-
-
@@ -144,31 +268,31 @@ export function OrdersTable({ initialOrders }: OrdersTableProps) {
- Bestellnr.
- Datum
- Kunde
- Auswahl
- Betrag
- Status
- Aktionen
+ Anfragenr.
+ Datum
+ Kunde / Firma
+ Auswahl / Produkte
+ Gesamtbetrag
+ Status
+ Aktionen
- {filteredOrders.length === 0 ? (
+ {filteredAndSortedOrders.length === 0 ? (
- Keine Bestellungen gefunden.
+ Keine Anfragen gefunden.
) : (
- filteredOrders.map((order) => {
+ 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'
})}
@@ -177,38 +301,43 @@ export function OrdersTable({ initialOrders }: OrdersTableProps) {
{order.customer_data?.company_name || 'Privatkunde'}
-
+
+
{order.customer_data?.first_name} {order.customer_data?.last_name}
-
+
- {items.map((item: any, idx: number) => (
+ {items.slice(0, 2).map((item: any, idx: number) => (
-
- {item.device_name || 'Kasse'}:
+
+ {item.device_name || 'Kasse'}: {item.product_name}
- {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)}
+
+ {new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(order.total_price || 0)}
-
+
{Object.entries(statusLabel).map(([key, label]) => (
@@ -257,6 +389,248 @@ export function OrdersTable({ initialOrders }: OrdersTableProps) {
+
+ {/* Detail-Modal für vollständige Anfragenübersicht & Freigabe-Workflow */}
+ {selectedOrder && (
+
+ )}
+
+ {/* Dialog für Ablehnungsbegründung */}
+ {rejectingOrder && (
+
+ )}
);
}