"use client" import { type ColumnDef, type ColumnFiltersState, type ExpandedState, flexRender, getCoreRowModel, getExpandedRowModel, getFilteredRowModel, getSortedRowModel, type SortingState, useReactTable, } from "@tanstack/react-table" import dayjs from "dayjs" import relativeTime from "dayjs/plugin/relativeTime" import { Check, ChevronDown, ChevronRight, Filter, ImageIcon, Search, SortDesc, X } from "lucide-react" import * as React from "react" import { SubmissionDetails } from "@/components/submission-details" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { UserDisplay } from "@/components/user-display" import { pb, type Submission } from "@/lib/pb" import { cn } from "@/lib/utils" // Initialize dayjs relative time plugin dayjs.extend(relativeTime) // Utility function to get display name with priority: username > email > created_by field const getDisplayName = (submission: Submission, expandedData?: any): string => { // Check if we have expanded user data if (expandedData && expandedData.created_by) { const user = expandedData.created_by // Priority: username > email if (user.username) { return user.username } if (user.email) { return user.email } } // Fallback to created_by field (could be user ID or username) return submission.created_by } interface SubmissionsDataTableProps { data: Submission[] isAdmin: boolean currentUserId: string onApprove: (id: string) => void onReject: (id: string) => void isApproving?: boolean isRejecting?: boolean } // Group submissions by status with priority order const groupAndSortSubmissions = (submissions: Submission[]): Submission[] => { const statusPriority = { pending: 0, approved: 1, added_to_collection: 2, rejected: 3 } return [...submissions].sort((a, b) => { // First, sort by status priority const statusDiff = statusPriority[a.status] - statusPriority[b.status] if (statusDiff !== 0) return statusDiff // Within same status, sort by updated time (most recent first) return new Date(b.updated).getTime() - new Date(a.updated).getTime() }) } const getStatusColor = (status: Submission["status"]) => { switch (status) { case "approved": return "bg-blue-500/10 text-blue-400 font-bold border-blue-500/20" case "rejected": return "bg-red-500/10 text-red-500 border-red-500/20" case "pending": return "bg-yellow-500/10 text-yellow-500 border-yellow-500/20" case "added_to_collection": return "bg-green-500/10 text-green-500 border-green-500/20" default: return "bg-gray-500/10 text-gray-500 border-gray-500/20" } } const getStatusDisplayName = (status: Submission["status"]) => { switch (status) { case "pending": return "Pending" case "approved": return "Approved" case "rejected": return "Rejected" case "added_to_collection": return "Added to Collection" default: return status } } export function SubmissionsDataTable({ data, isAdmin, currentUserId, onApprove, onReject, isApproving, isRejecting, }: SubmissionsDataTableProps) { const [sorting, setSorting] = React.useState([]) const [expanded, setExpanded] = React.useState({}) const [columnFilters, setColumnFilters] = React.useState([]) const [globalFilter, setGlobalFilter] = React.useState("") const [userFilter, setUserFilter] = React.useState<{ userId: string; displayName: string } | null>(null) // Handle row expansion - only one row can be expanded at a time const handleRowToggle = React.useCallback((rowId: string, isExpanded: boolean) => { setExpanded(isExpanded ? {} : { [rowId]: true }) }, []) // Group and sort data by status and updated time const groupedData = React.useMemo(() => { return groupAndSortSubmissions(data) }, [data]) // Handle user filter - filter by user ID but display username const handleUserFilter = React.useCallback( (userId: string, displayName: string) => { if (userFilter?.userId === userId) { setUserFilter(null) setColumnFilters((prev) => prev.filter((filter) => filter.id !== "created_by")) } else { setUserFilter({ userId, displayName }) setColumnFilters((prev) => [...prev.filter((filter) => filter.id !== "created_by"), { id: "created_by", value: userId }]) } }, [userFilter], ) const columns: ColumnDef[] = React.useMemo(() => [ { id: "expander", header: () => null, cell: ({ row }) => { return ( ) }, }, { accessorKey: "name", header: ({ column }) => { return ( ) }, cell: ({ row }) =>
{row.getValue("name")}
, }, { accessorKey: "status", header: ({ column }) => { return ( ) }, cell: ({ row }) => { const status = row.getValue("status") as Submission["status"] return ( {getStatusDisplayName(status)} ) }, }, { accessorKey: "created_by", header: ({ column }) => { return ( ) }, cell: ({ row }) => { const submission = row.original const expandedData = (submission as any).expand const displayName = getDisplayName(submission, expandedData) const userId = submission.created_by return (
{userFilter?.userId === userId && }
) }, }, { accessorKey: "updated", header: ({ column }) => { return ( ) }, cell: ({ row }) => { const date = row.getValue("updated") as string return (
{dayjs(date).fromNow()}
) }, }, { accessorKey: "assets", header: "Preview", cell: ({ row }) => { const assets = row.getValue("assets") as string[] const name = row.getValue("name") as string if (assets.length > 0) { return (
{name}
) } return (
) }, }, ], [handleRowToggle, handleUserFilter, userFilter]) const table = useReactTable({ data: groupedData, columns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getExpandedRowModel: getExpandedRowModel(), getFilteredRowModel: getFilteredRowModel(), onSortingChange: setSorting, onExpandedChange: setExpanded, onColumnFiltersChange: setColumnFilters, onGlobalFilterChange: setGlobalFilter, state: { sorting, expanded, columnFilters, globalFilter, }, getRowCanExpand: () => true, globalFilterFn: (row, columnId, value) => { const searchValue = value.toLowerCase() const name = row.getValue("name") as string const status = row.getValue("status") as string const submission = row.original const expandedData = (submission as any).expand const displayName = getDisplayName(submission, expandedData) return ( name.toLowerCase().includes(searchValue) || status.toLowerCase().includes(searchValue) || displayName.toLowerCase().includes(searchValue) ) }, }) return (
{/* Search and Filters */}
setGlobalFilter(String(event.target.value))} className="pl-10" />
{userFilter && (
User: {userFilter.displayName}
)}
{/* Table */}
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { return ( {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} ) })} ))} {table.getRowModel().rows?.length ? ( (() => { let lastStatus: string | null = null return table.getRowModel().rows.map((row, index) => { const currentStatus = row.original.status const showStatusHeader = currentStatus !== lastStatus lastStatus = currentStatus return ( {showStatusHeader && (
{getStatusDisplayName(currentStatus)} {table.getRowModel().rows.filter((r) => r.original.status === currentStatus).length} {table.getRowModel().rows.filter((r) => r.original.status === currentStatus).length === 1 ? " submission" : " submissions"}
)} handleRowToggle(row.id, row.getIsExpanded())} > {row.getVisibleCells().map((cell) => ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} {row.getIsExpanded() && ( onApprove(row.original.id) : undefined} onReject={row.original.status === "pending" && isAdmin ? () => onReject(row.original.id) : undefined} isApproving={isApproving} isRejecting={isRejecting} /> )}
) }) })() ) : ( {globalFilter || userFilter ? "No submissions found matching your search" : "No submissions found"} )}
) }