"use client"; import { useState, useEffect, useCallback } from "react"; import React from "react"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Eye, Loader2, CheckCircle2, XCircle, ChevronLeft, ChevronRight, ArrowUpDown, Truck, MessageCircle } from "lucide-react"; import Link from "next/link"; import { clientFetch } from '@/lib/client-utils'; import { toast } from "sonner"; import { Checkbox } from "@/components/ui/checkbox"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; interface Order { _id: string; orderId: string; status: string; totalPrice: number; orderDate: Date; telegramUsername?: string; telegramBuyerId?: string; } type SortableColumns = "orderId" | "totalPrice" | "status" | "orderDate"; interface StatusConfig { icon: React.ElementType; color: string; bgColor: string; animate?: string; } type OrderStatus = "paid" | "unpaid" | "acknowledged" | "shipped" | "completed" | "cancelled" | "confirming"; // Create a StatusFilter component to replace the missing component const StatusFilter = ({ currentStatus, onChange }: { currentStatus: string, onChange: (value: string) => void }) => { return ( ); }; // Create a PageSizeSelector component const PageSizeSelector = ({ currentSize, onChange, options }: { currentSize: number, onChange: (value: string) => void, options: number[] }) => { return (
Show:
); }; export default function OrderTable() { const [orders, setOrders] = useState([]); const [loading, setLoading] = useState(true); const [statusFilter, setStatusFilter] = useState("all"); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [totalOrders, setTotalOrders] = useState(0); const [sortConfig, setSortConfig] = useState<{ column: SortableColumns; direction: "asc" | "desc"; }>({ column: "orderDate", direction: "desc" }); const [selectedOrders, setSelectedOrders] = useState>(new Set()); const [isShipping, setIsShipping] = useState(false); const [itemsPerPage, setItemsPerPage] = useState(20); const pageSizeOptions = [5, 10, 15, 20, 25, 50, 75, 100]; // Fetch orders with server-side pagination const fetchOrders = useCallback(async () => { try { setLoading(true); const queryParams = new URLSearchParams({ page: currentPage.toString(), limit: itemsPerPage.toString(), sortBy: sortConfig.column, sortOrder: sortConfig.direction, ...(statusFilter !== "all" && { status: statusFilter }), }); const data = await clientFetch(`/orders?${queryParams}`); setOrders(data.orders || []); setTotalPages(data.totalPages || 1); setTotalOrders(data.totalOrders || 0); } catch (error) { toast.error("Failed to fetch orders"); console.error("Fetch error:", error); } finally { setLoading(false); } }, [currentPage, statusFilter, itemsPerPage, sortConfig]); useEffect(() => { fetchOrders(); }, [fetchOrders]); // Reset to first page when filter changes useEffect(() => { setCurrentPage(1); }, [statusFilter]); const handlePageChange = (newPage: number) => { setCurrentPage(newPage); }; const handleItemsPerPageChange = (e: React.ChangeEvent) => { const newSize = parseInt(e.target.value, 10); setItemsPerPage(newSize); setCurrentPage(1); // Reset to first page when changing items per page }; // Derived data calculations const filteredOrders = orders.filter( (order) => statusFilter === "all" || order.status === statusFilter ); // Use the orders directly as they're already sorted by the server const paginatedOrders = filteredOrders; // Handlers const handleSort = (column: SortableColumns) => { setSortConfig(prev => ({ column, direction: prev.column === column && prev.direction === "asc" ? "desc" : "asc" })); setCurrentPage(1); // Reset to first page when changing sort order }; const toggleSelection = (orderId: string) => { setSelectedOrders(prev => { const newSet = new Set(prev); newSet.has(orderId) ? newSet.delete(orderId) : newSet.add(orderId); return newSet; }); }; const toggleAll = () => { if (selectedOrders.size === paginatedOrders.length) { setSelectedOrders(new Set()); } else { setSelectedOrders(new Set(paginatedOrders.map(o => o._id))); } }; const markAsShipped = async () => { if (selectedOrders.size === 0) { toast.warning("Please select orders to mark as shipped"); return; } try { setIsShipping(true); const response = await clientFetch("/orders/mark-shipped", { method: "POST", body: JSON.stringify({ orderIds: Array.from(selectedOrders) }) }); // Only update orders that were successfully marked as shipped if (response.success && response.success.orders) { const successfulOrderIds = new Set(response.success.orders.map((o: any) => o.id)); setOrders(prev => prev.map(order => successfulOrderIds.has(order._id) ? { ...order, status: "shipped" } : order ) ); if (response.failed && response.failed.count > 0) { toast.warning(`${response.failed.count} orders could not be marked as shipped`); } if (response.success.count > 0) { toast.success(`${response.success.count} orders marked as shipped`); } } setSelectedOrders(new Set()); } catch (error) { toast.error("Failed to update orders"); console.error("Shipping error:", error); } finally { setIsShipping(false); } }; const statusConfig: Record = { acknowledged: { icon: CheckCircle2, color: "text-white", bgColor: "bg-purple-600" }, paid: { icon: CheckCircle2, color: "text-white", bgColor: "bg-emerald-600" }, unpaid: { icon: XCircle, color: "text-white", bgColor: "bg-red-500" }, confirming: { icon: Loader2, color: "text-white", bgColor: "bg-yellow-500", animate: "animate-spin" }, shipped: { icon: Truck, color: "text-white", bgColor: "bg-blue-600" }, completed: { icon: CheckCircle2, color: "text-white", bgColor: "bg-green-600" }, cancelled: { icon: XCircle, color: "text-white", bgColor: "bg-gray-500" } }; return (
{/* Filters header */}
handleItemsPerPageChange({ target: { value } } as React.ChangeEvent)} options={pageSizeOptions} />
Mark Orders as Shipped Are you sure you want to mark {selectedOrders.size} order{selectedOrders.size !== 1 ? 's' : ''} as shipped? This action cannot be undone. Cancel Confirm
{/* Table */}
{loading && (
)}
0} onCheckedChange={toggleAll} /> handleSort("orderId")}> Order ID handleSort("totalPrice")}> Total handleSort("status")}> Status handleSort("orderDate")}> Date Buyer Actions {paginatedOrders.map((order) => { const StatusIcon = statusConfig[order.status as keyof typeof statusConfig]?.icon || XCircle; return ( toggleSelection(order._id)} disabled={order.status !== "paid" && order.status !== "acknowledged"} /> #{order.orderId} £{order.totalPrice.toFixed(2)}
{React.createElement(statusConfig[order.status as OrderStatus]?.icon || XCircle, { className: `h-4 w-4 ${statusConfig[order.status as OrderStatus]?.animate || ""}` })} {order.status.charAt(0).toUpperCase() + order.status.slice(1)}
{new Date(order.orderDate).toLocaleDateString("en-GB")} {order.telegramUsername ? `@${order.telegramUsername}` : "-"}
{(order.telegramBuyerId || order.telegramUsername) && ( )}
); })}
{/* Pagination */}
Page {currentPage} of {totalPages} ({totalOrders} total)
); }