"use client"; import { type Column, type ColumnDef, ColumnFiltersState, flexRender, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, RowSelectionState, type SortingState, type Table as TableType, useReactTable, VisibilityState, } from "@tanstack/react-table"; import { ArrowDown, ArrowUp, CheckCheckIcon, CheckCircle, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, ChevronsUpDown, Clock, Footprints, LoaderIcon, Package, PackageIcon, PieChart, RefreshCcw, Shirt, ShoppingBag, Smartphone, } from "lucide-react"; import * as React from "react"; import { z } from "zod"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; 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"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { cn } from "@/lib/utils"; const formatNumber = (value: number) => value.toLocaleString("fa-IR"); type UseDataTableOptions = { data: Array; columns: Array>; getRowId?: (row: TData) => string; initialSorting?: SortingState; initialGlobalFilter?: string; initialFilters?: ColumnFiltersState; initialVisibility?: VisibilityState; initialSelection?: RowSelectionState; enableRowSelection?: boolean; }; export function useDataTable(options: UseDataTableOptions) { const { data, columns, getRowId, initialSorting = [], initialFilters = [], initialGlobalFilter = "", initialVisibility = {}, initialSelection = {}, enableRowSelection = true, } = options; const [sorting, setSorting] = React.useState(initialSorting); const [columnFilters, setColumnFilters] = React.useState(initialFilters); const [globalFilter, setGlobalFilter] = React.useState(initialGlobalFilter); const [columnVisibility, setColumnVisibility] = React.useState(initialVisibility); const [rowSelection, setRowSelection] = React.useState(initialSelection); const table = useReactTable({ data, columns, getRowId, state: { sorting, columnFilters, globalFilter, columnVisibility, rowSelection, }, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, onRowSelectionChange: setRowSelection, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), onGlobalFilterChange: setGlobalFilter, onColumnVisibilityChange: setColumnVisibility, getFacetedRowModel: getFacetedRowModel(), getFacetedUniqueValues: getFacetedUniqueValues(), enableRowSelection, }); return { table, columnFilters, setColumnFilters, sorting, setSorting, globalFilter, setGlobalFilter, columnVisibility, setColumnVisibility, rowSelection, setRowSelection, }; } type DataTableColumnHeaderProps = { column: Column; title: string; }; export const DataTableColumnHeader = ({ column, title, }: DataTableColumnHeaderProps) => { const canSort = column.getCanSort(); const sorted = column.getIsSorted(); if (!canSort) { return ( {title} ); } return ( ); }; type DataTablePaginationProps = { table: TableType; pageSizeOptions?: number[]; }; export const DataTablePagination = ({ table, pageSizeOptions = [10, 20, 30, 40, 50], }: DataTablePaginationProps) => { const currentPage = table.getState().pagination.pageIndex; const pageSize = table.getState().pagination.pageSize; const totalRows = table.getFilteredRowModel().rows.length; const pageCount = table.getPageCount(); const startRow = currentPage * pageSize + 1; const endRow = Math.min((currentPage + 1) * pageSize, totalRows); const relevantPageSizes = pageSizeOptions.filter( (size) => size <= totalRows || size === pageSize, ); return (
{totalRows === 0 ? ( "نتیجه‌ای یافت نشد" ) : ( <> نمایش {formatNumber(startRow)} تا {formatNumber(endRow)} از{" "} {formatNumber(totalRows)} رکورد )}

تعداد در هر صفحه

صفحه {formatNumber(currentPage + 1)} از {formatNumber(pageCount)}
); }; interface DataTableTabbarProps extends React.ComponentProps<"div"> { _table: TableType; search?: React.ReactNode; filter?: React.ReactNode; } export function DataTableTabbar({ _table, children, search, filter, className, ...props }: DataTableTabbarProps) { return (
{children}
{search} {filter}
); } export const schema = z.object({ id: z.string(), purchased: z.string(), order_status: z.string(), customer: z.string(), payment_method: z.string(), payment_status: z.string(), items: z.number(), }); const data = [ { id: "#301456", purchased: "کاپشن چرمی", order_status: "آماده تحویل", customer: "سارا احمدی", payment_method: "****7321", payment_status: "پرداخت‌شده", items: 3, }, { id: "#789123", purchased: "کفش اسپرت آبی", order_status: "تحویل‌شده", customer: "رضا محمدی", payment_method: "****4509", payment_status: "پرداخت‌شده", items: 2, }, { id: "#654321", purchased: "هودی قرمز", order_status: "تحویل‌شده", customer: "نیلوفر حسینی", payment_method: "****9812", payment_status: "در انتظار پیگیری", items: 4, }, { id: "#987654", purchased: "عینک آفتابی مشکی", order_status: "تحویل‌نشده", customer: "داوود کریمی", payment_method: "****6734", payment_status: "در انتظار پیگیری", items: 1, }, { id: "#432187", purchased: "کوله‌پشتی سبز", order_status: "تحویل‌شده", customer: "لیلا رحیمی", payment_method: "****2901", payment_status: "پرداخت‌شده", items: 2, }, { id: "#567890", purchased: "ساعت نقره‌ای", order_status: "آماده تحویل", customer: "امیر نوری", payment_method: "****8456", payment_status: "در انتظار پیگیری", items: 1, }, { id: "#234567", purchased: "شال خاکستری", order_status: "تحویل‌شده", customer: "راحله موسوی", payment_method: "****1234", payment_status: "مسترد شده", items: 3, }, { id: "#876543", purchased: "پیراهن بنفش", order_status: "تحویل‌نشده", customer: "توماس علوی", payment_method: "****5678", payment_status: "مسترد جزئی", items: 2, }, { id: "#345678", purchased: "کلاه سفید", order_status: "آماده تحویل", customer: "آنا رحیمی", payment_method: "****9012", payment_status: "پرداخت‌شده", items: 1, }, { id: "#456789", purchased: "بوت قهوه‌ای", order_status: "تحویل‌شده", customer: "حامد سنگری", payment_method: "****3456", payment_status: "پرداخت‌شده", items: 4, }, { id: "#678901", purchased: "کیف دوشی زرد", order_status: "تحویل‌شده", customer: "سوفیا تهرانی", payment_method: "****7890", payment_status: "در انتظار پیگیری", items: 2, }, { id: "#890123", purchased: "دستکش نارنجی", order_status: "تحویل‌نشده", customer: "هانیه کلارک", payment_method: "****2345", payment_status: "در انتظار پیگیری", items: 1, }, { id: "#123456", purchased: "پلیور صورتی", order_status: "آماده تحویل", customer: "گلناز ایوانس", payment_method: "****6789", payment_status: "پرداخت‌شده", items: 3, }, { id: "#543210", purchased: "کمربند مشکی", order_status: "تحویل‌شده", customer: "دانیال میرزایی", payment_method: "****1122", payment_status: "مسترد شده", items: 1, }, { id: "#765432", purchased: "کاپشن فیروزه‌ای", order_status: "تحویل‌نشده", customer: "الهام سفید", payment_method: "****5566", payment_status: "مسترد جزئی", items: 2, }, { id: "#987012", purchased: "شلوارک سرمه‌ای", order_status: "آماده تحویل", customer: "ایمان خاکپور", payment_method: "****9900", payment_status: "در انتظار پیگیری", items: 5, }, { id: "#210987", purchased: "گردنبند طلا", order_status: "تحویل‌شده", customer: "مریم اسکات", payment_method: "****3344", payment_status: "پرداخت‌شده", items: 1, }, { id: "#432109", purchased: "شلوار زیتونی", order_status: "تحویل‌شده", customer: "یاسین تپل", payment_method: "****7788", payment_status: "پرداخت‌شده", items: 3, }, { id: "#6543210", purchased: "کلاه قرمز", order_status: "تحویل‌نشده", customer: "کلوئه یونگ", payment_method: "****1122", payment_status: "در انتظار پیگیری", items: 1, }, { id: "#8765432", purchased: "جین آبی", order_status: "آماده تحویل", customer: "نوح لوئیس", payment_method: "****5566", payment_status: "پرداخت‌شده", items: 2, }, { id: "#109876", purchased: "پیراهن سبز", order_status: "تحویل‌شده", customer: "آوا شاهی", payment_method: "****9900", payment_status: "مسترد شده", items: 4, }, { id: "#321098", purchased: "دامن بنفش", order_status: "تحویل‌نشده", customer: "لیام آدامز", payment_method: "****3344", payment_status: "مسترد جزئی", items: 1, }, { id: "#5432109", purchased: "کیف پول مشکی", order_status: "آماده تحویل", customer: "ایزابلا بکر", payment_method: "****7788", payment_status: "در انتظار پیگیری", items: 2, }, { id: "#7654321", purchased: "شال زرد", order_status: "تحویل‌شده", customer: "مازیار سبز", payment_method: "****1122", payment_status: "پرداخت‌شده", items: 1, }, { id: "#9876543", purchased: "کفش سفید", order_status: "تحویل‌شده", customer: "الا مارتینز", payment_method: "****5566", payment_status: "پرداخت‌شده", items: 3, }, ]; export type StatusOption = { label: string; value: string; icon?: React.ComponentType<{ className?: string }>; }; export const productTypes = [ { value: "electronics", label: "الکترونیک", icon: Smartphone, }, { value: "accessories", label: "لوازم جانبی", icon: ShoppingBag, }, { value: "clothing", label: "پوشاک", icon: Shirt, }, { value: "footwear", label: "کفش", icon: Footprints, }, { value: "shoes", label: "کفش ورزشی", icon: PackageIcon, }, ]; export const orderStatuses: StatusOption[] = [ { label: "تحویل‌شده", value: "تحویل‌شده", icon: CheckCircle }, { label: "آماده تحویل", value: "آماده تحویل", icon: Package }, { label: "تحویل‌نشده", value: "تحویل‌نشده", icon: Clock }, ]; export const paymentStatuses: StatusOption[] = [ { label: "پرداخت‌شده", value: "پرداخت‌شده", icon: CheckCheckIcon }, { label: "در انتظار پیگیری", value: "در انتظار پیگیری", icon: LoaderIcon }, { label: "مسترد شده", value: "مسترد شده", icon: RefreshCcw }, { label: "مسترد جزئی", value: "مسترد جزئی", icon: PieChart, }, ]; const createStatusLookup = (options: StatusOption[]) => options.reduce>((acc, option) => { acc[option.value] = option; return acc; }, {}); const orderStatusLookup = createStatusLookup(orderStatuses); const paymentStatusLookup = createStatusLookup(paymentStatuses); export const getOrderStatusMeta = (status: string) => orderStatusLookup[status]; export const getPaymentStatusMeta = (status: string) => paymentStatusLookup[status]; export const getStatusVariant = (status: string) => { switch (status) { case "تحویل‌شده": return "default"; case "آماده تحویل": return "secondary"; case "تحویل‌نشده": return "outline"; default: return "outline"; } }; export const getPaymentVariant = (status: string) => { switch (status) { case "پرداخت‌شده": return "default"; case "در انتظار پیگیری": return "secondary"; case "مسترد شده": return "outline"; case "مسترد جزئی": return "outline"; default: return "outline"; } }; type OrderStatusTab = "all" | string; export const validatedData = schema.array().parse(data); export const columns: ColumnDef>[] = [ { accessorKey: "order", header: ({ column }) => ( ), cell: ({ row }) => (
{row.original.id}
), enableHiding: false, meta: { label: "سفارش", }, }, { accessorKey: "purchased", header: ({ column }) => ( ), cell: ({ row }) => { const items = row.original.items; return (
{row.original.purchased}
{items && ( {formatNumber(items)} )}
); }, enableSorting: false, enableHiding: false, meta: { label: "خریداری‌شده", }, }, { accessorKey: "customer", header: ({ column }) => ( ), cell: ({ row }) => (
{row.original.customer}
), meta: { label: "مشتری", }, }, { accessorKey: "order_status", header: ({ column }) => ( ), cell: ({ row }) => { const status = row.original.order_status; const statusMeta = getOrderStatusMeta(status); const StatusIcon = statusMeta?.icon; return (
{StatusIcon && } {statusMeta?.label ?? status}
); }, filterFn: (row, id, value) => { if (!Array.isArray(value) || value.length === 0) { return true; } const rowValue = String(row.getValue(id)); return value.includes(rowValue); }, enableSorting: false, meta: { label: "وضعیت معامله سفارش", }, }, { accessorKey: "payment_status", header: ({ column }) => ( ), cell: ({ row }) => { const status = row.original.payment_status; const statusMeta = getPaymentStatusMeta(status); const StatusIcon = statusMeta?.icon; return (
{StatusIcon && } {statusMeta?.label ?? status}
); }, filterFn: (row, id, value) => { if (!Array.isArray(value) || value.length === 0) { return true; } const rowValue = String(row.getValue(id)); return value.includes(rowValue); }, enableSorting: false, meta: { label: "وضعیت معامله پرداخت", }, }, { accessorKey: "payment_method", header: ({ column }) => ( ), cell: ({ row }) => (
{row.original.payment_method}
), enableSorting: false, meta: { label: "روش پرداخت", }, }, ]; export const DataTable13 = ({ className }: { className?: string }) => { const { table, setColumnFilters } = useDataTable({ data: validatedData, columns, getRowId: (row) => row.id.toString(), }); const orderStatusColumn = table.getColumn("order_status"); const [orderStatusTab, setOrderStatusTab] = React.useState("all"); const orderStatusCounts = React.useMemo( () => orderStatusColumn?.getFacetedUniqueValues(), [orderStatusColumn], ); const allOrdersCount = React.useMemo(() => { if (!orderStatusCounts?.size) { return table.getPreFilteredRowModel().rows.length; } return Array.from(orderStatusCounts.values()).reduce( (acc, count) => acc + Number(count), 0, ); }, [orderStatusCounts, table]); // Single unified handler for status tab changes const handleStatusTabChange = React.useCallback( (value: string) => { setOrderStatusTab(value); setColumnFilters((prev) => { const filtered = prev.filter((f) => f.id !== "order_status"); return value === "all" ? filtered : [...filtered, { id: "order_status", value: [value] }]; }); }, [setColumnFilters], ); const Search = ( table.setGlobalFilter(event.target.value)} className="h-8 w-full lg:w-[224px]" /> ); const Toolbar = ( {orderStatusColumn && ( همه {formatNumber(allOrdersCount)} {orderStatuses.map((status) => { const StatusIcon = status.icon; const count = orderStatusCounts?.get(status.value) ?? 0; return ( {StatusIcon && } {status.label} {formatNumber(count)} ); })} )} ); return (

جدول با فیلترهای تب‌دار

جدول سفارشات با مرتب‌سازی، جستجوی سراسری، فیلتر وضعیت معامله از طریق تب‌ها و کنترل‌های کامل صفحه‌بندی با طراحی واکنش‌گرا.

{Toolbar}
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { return ( {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext(), )} ); })} ))} {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( {flexRender( cell.column.columnDef.cell, cell.getContext(), )} ))} )) ) : ( نتیجه‌ای یافت نشد. )}
); };