"use client"; import type { ColumnFiltersState, RowSelectionState, VisibilityState, } from "@tanstack/react-table"; import { type Column, type ColumnDef, flexRender, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, type SortingState, type Table as TableType, useReactTable, } from "@tanstack/react-table"; import { ArrowDown, ArrowUp, Check, CheckCheckIcon, CheckCircle, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, ChevronsUpDown, Clock, Footprints, LoaderIcon, Package, PackageIcon, PieChart, PlusCircle, RefreshCcw, Settings2, 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 { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from "@/components/ui/command"; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; 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 DataTableViewOptionsProps { table: TableType; } export function DataTableViewOptions({ table, }: DataTableViewOptionsProps) { const columns = table.getAllColumns().filter((column) => column.getCanHide()); const hiddenCount = columns.filter((col) => !col.getIsVisible()).length; const hasHiddenColumns = hiddenCount > 0; const handleResetColumns = () => { table.resetColumnVisibility(); }; return ( نمایش ستون‌ها {hasHiddenColumns && ( )} {columns.length === 0 ? (
ستونی در دسترس نیست
) : ( columns.map((column) => { const label = (column.columnDef.meta as { label?: string })?.label || column.id; return ( column.toggleVisibility(!!value)} > {label} ); }) )}
); } interface DataTableFacetedFilterProps { column?: Column; title?: string; options: { label: string; value: string; icon?: React.ComponentType<{ className?: string }>; }[]; } export function DataTableFacetedFilter({ column, title, options, }: DataTableFacetedFilterProps) { const facets = column?.getFacetedUniqueValues(); const selectedValues = new Set(column?.getFilterValue() as string[]); return ( نتیجه‌ای یافت نشد. {options.map((option) => { const isSelected = selectedValues.has(option.value); return ( { if (isSelected) { selectedValues.delete(option.value); } else { selectedValues.add(option.value); } const filterValues = Array.from(selectedValues); column?.setFilterValue( filterValues.length ? filterValues : undefined, ); }} >
{option.icon && ( )} {option.label} {facets?.get(option.value) && ( {formatNumber(facets.get(option.value)!)} )}
); })}
{selectedValues.size > 0 && ( <> column?.setFilterValue(undefined)} className="justify-center text-center" > پاک کردن فیلترها )}
); } interface DataTableToolbarProps extends React.ComponentProps<"div"> { table: TableType; showViewOptions?: boolean; } export const DataTableToolbar = ({ table, children, className, showViewOptions = true, ...props }: DataTableToolbarProps) => { return (
{children}
{showViewOptions && }
); }; 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"; } }; 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 DataTable12 = ({ className }: { className?: string }) => { const { table } = useDataTable({ data: validatedData, columns, getRowId: (row) => row.id.toString(), }); const Toolbar = ( table.setGlobalFilter(event.target.value)} className="h-8 w-full lg:w-[250px]" /> {table.getColumn("order_status") && ( )} {table.getColumn("payment_status") && ( )} ); 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(), )} ))} )) ) : ( نتیجه‌ای یافت نشد. )}
); };