"use client"; import { ColumnDef, SortingState, flexRender, getCoreRowModel, useReactTable, } from "@tanstack/react-table"; import { ArrowUpDown, Check, ChevronDown, ChevronUp, MoreHorizontal, Pencil, Trash2, } from "lucide-react"; import Link from "next/link"; import * as React from "react"; import { deleteCommissionRequest } from "@/actions/commissions/deleteCommissionRequest"; import { getCommissionRequestsTablePage } from "@/actions/commissions/getCommissionRequestsTablePage"; import { setCommissionRequestStatus } from "@/actions/commissions/setCommissionRequestStatus"; import type { CommissionRequestTableRow, CommissionStatus, } from "@/schemas/commissions/tableSchema"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; type TriState = "any" | "true" | "false"; function useDebouncedValue(value: T, delayMs: number) { const [debounced, setDebounced] = React.useState(value); React.useEffect(() => { const t = setTimeout(() => setDebounced(value), delayMs); return () => clearTimeout(t); }, [value, delayMs]); return debounced; } function SortHeader(props: { title: string; column: any }) { const sorted = props.column.getIsSorted() as false | "asc" | "desc"; return ( ); } function StatusBadge({ status }: { status: CommissionStatus }) { const variant = status === "COMPLETED" ? "default" : status === "REJECTED" || status === "SPAM" ? "destructive" : "secondary"; return ( {status} ); } function TriSelectInline(props: { value: TriState; onChange: (v: TriState) => void }) { return ( ); } const STATUS_OPTIONS: CommissionStatus[] = [ "NEW", "REVIEWING", "ACCEPTED", "REJECTED", "COMPLETED", "SPAM", ]; type Filters = { q: string; email: string; status: "any" | CommissionStatus; hasFiles: TriState; }; export function CommissionRequestsTable() { const [sorting, setSorting] = React.useState([ { id: "createdAt", desc: true }, ]); const [pageIndex, setPageIndex] = React.useState(0); const [pageSize, setPageSize] = React.useState(25); const [filters, setFilters] = React.useState({ q: "", email: "", status: "any", hasFiles: "any", }); const debouncedQ = useDebouncedValue(filters.q, 300); const debouncedEmail = useDebouncedValue(filters.email, 300); const [rows, setRows] = React.useState([]); const [total, setTotal] = React.useState(0); const [isPending, startTransition] = React.useTransition(); // Delete dialog const [deleteOpen, setDeleteOpen] = React.useState(false); const [deleteTarget, setDeleteTarget] = React.useState<{ id: string; label: string; } | null>(null); const pageCount = Math.max(1, Math.ceil(total / pageSize)); const refresh = React.useCallback(() => { startTransition(async () => { const res = await getCommissionRequestsTablePage({ pagination: { pageIndex, pageSize }, sorting, filters: { q: debouncedQ || undefined, email: debouncedEmail || undefined, status: filters.status, hasFiles: filters.hasFiles, }, }); setRows(res.rows); setTotal(res.total); }); }, [ pageIndex, pageSize, sorting, debouncedQ, debouncedEmail, filters.status, filters.hasFiles, ]); React.useEffect(() => { refresh(); }, [refresh]); const columns = React.useMemo[]>( () => [ { accessorKey: "createdAt", header: ({ column }) => , cell: ({ row }) => ( {new Date(row.original.createdAt).toLocaleString()} ), }, { accessorKey: "filesCount", header: ({ column }) => , cell: ({ row }) => ( {row.original.filesCount} ), }, { id: "requestor", header: "Requestor", enableSorting: false, cell: ({ row }) => { const r = row.original; return (
{r.customerName}
{r.customerEmail} {r.customerSocials ? ` · ${r.customerSocials}` : ""}
); }, }, { accessorKey: "status", header: ({ column }) => , cell: ({ row }) => , }, { id: "complete", header: "", enableSorting: false, cell: ({ row }) => { const r = row.original; const isCompleted = r.status === "COMPLETED"; return (
); }, }, { id: "actions", header: "", enableSorting: false, cell: ({ row }) => { const r = row.original; return (
{/* status changes */}
Set status
{STATUS_OPTIONS.filter((s) => s !== "COMPLETED").map((s) => ( { e.preventDefault(); startTransition(async () => { await setCommissionRequestStatus({ id: r.id, status: s }); refresh(); }); }} > {s} ))} Edit { e.preventDefault(); setDeleteTarget({ id: r.id, label: `${r.customerName} (${r.customerEmail})`, }); setDeleteOpen(true); }} > Delete
); }, }, ], [isPending, refresh], ); const table = useReactTable({ data: rows, columns, state: { sorting }, manualPagination: true, manualSorting: true, pageCount, onSortingChange: (updater) => { setSorting((prev) => (typeof updater === "function" ? updater(prev) : updater)); setPageIndex(0); }, getCoreRowModel: getCoreRowModel(), }); const headerGroup = table.getHeaderGroups()[0]; return (
{headerGroup.headers.map((header) => ( {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} ))} {/* filter row */} {headerGroup.headers.map((header) => { const colId = header.column.id; return ( {colId === "createdAt" ? (
) : colId === "filesCount" ? ( { setFilters((f) => ({ ...f, hasFiles: v })); setPageIndex(0); }} /> ) : colId === "requestor" ? (
{ setFilters((f) => ({ ...f, q: e.target.value })); setPageIndex(0); }} /> { setFilters((f) => ({ ...f, email: e.target.value })); setPageIndex(0); }} />
) : colId === "status" ? ( ) : (
)} ); })} {rows.length === 0 ? (
{isPending ? "Loading…" : "No results."}
Adjust filters or change page size.
) : ( table.getRowModel().rows.map((r, idx) => ( {r.getVisibleCells().map((cell) => ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} )) )}
{/* pagination */}
{isPending ? "Updating…" : null} Total: {total}
Page {pageIndex + 1} / {pageCount}
{/* delete confirmation */} Delete request? This will delete{" "} {deleteTarget?.label}. This action cannot be undone. Cancel { const target = deleteTarget; if (!target) return; startTransition(async () => { await deleteCommissionRequest(target.id); setDeleteOpen(false); setDeleteTarget(null); // If current page becomes empty after delete, clamp page index. setPageIndex((p) => (p > 0 && rows.length === 1 ? p - 1 : p)); refresh(); }); }} > Delete
); }