Initial import of 1407 block sources under blocks/
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,955 @@
|
||||
"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<TData> = {
|
||||
data: Array<TData>;
|
||||
columns: Array<ColumnDef<TData, unknown>>;
|
||||
getRowId?: (row: TData) => string;
|
||||
initialSorting?: SortingState;
|
||||
initialGlobalFilter?: string;
|
||||
initialFilters?: ColumnFiltersState;
|
||||
initialVisibility?: VisibilityState;
|
||||
initialSelection?: RowSelectionState;
|
||||
enableRowSelection?: boolean;
|
||||
};
|
||||
export function useDataTable<TData>(options: UseDataTableOptions<TData>) {
|
||||
const {
|
||||
data,
|
||||
columns,
|
||||
getRowId,
|
||||
initialSorting = [],
|
||||
initialFilters = [],
|
||||
initialGlobalFilter = "",
|
||||
initialVisibility = {},
|
||||
initialSelection = {},
|
||||
enableRowSelection = true,
|
||||
} = options;
|
||||
|
||||
const [sorting, setSorting] = React.useState<SortingState>(initialSorting);
|
||||
const [columnFilters, setColumnFilters] =
|
||||
React.useState<ColumnFiltersState>(initialFilters);
|
||||
const [globalFilter, setGlobalFilter] =
|
||||
React.useState<string>(initialGlobalFilter);
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>(initialVisibility);
|
||||
const [rowSelection, setRowSelection] =
|
||||
React.useState<RowSelectionState>(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<TData, TValue> = {
|
||||
column: Column<TData, TValue>;
|
||||
title: string;
|
||||
};
|
||||
|
||||
export const DataTableColumnHeader = <TData, TValue>({
|
||||
column,
|
||||
title,
|
||||
}: DataTableColumnHeaderProps<TData, TValue>) => {
|
||||
const canSort = column.getCanSort();
|
||||
const sorted = column.getIsSorted();
|
||||
|
||||
if (!canSort) {
|
||||
return (
|
||||
<span className="flex h-8 items-center text-sm font-medium text-foreground">
|
||||
{title}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="flex h-8 items-center gap-2 px-0 text-sm font-medium text-foreground"
|
||||
onClick={() => column.toggleSorting(sorted === "asc")}
|
||||
>
|
||||
<span>{title}</span>
|
||||
{sorted === "desc" ? (
|
||||
<ArrowDown className="h-4 w-4" />
|
||||
) : sorted === "asc" ? (
|
||||
<ArrowUp className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronsUpDown className="h-4 w-4 opacity-50" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
type DataTablePaginationProps<TData> = {
|
||||
table: TableType<TData>;
|
||||
pageSizeOptions?: number[];
|
||||
};
|
||||
|
||||
export const DataTablePagination = <TData,>({
|
||||
table,
|
||||
pageSizeOptions = [10, 20, 30, 40, 50],
|
||||
}: DataTablePaginationProps<TData>) => {
|
||||
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 (
|
||||
<div className="flex flex-col gap-4 px-2 py-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex-1 text-center text-sm text-muted-foreground sm:text-start">
|
||||
<span className="font-medium">
|
||||
{totalRows === 0 ? (
|
||||
"نتیجهای یافت نشد"
|
||||
) : (
|
||||
<>
|
||||
نمایش {formatNumber(startRow)} تا {formatNumber(endRow)} از{" "}
|
||||
{formatNumber(totalRows)} ردیف
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center gap-4 sm:flex-row sm:gap-6 lg:gap-8">
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-sm font-medium whitespace-nowrap">تعداد در هر صفحه</p>
|
||||
<Select
|
||||
value={`${pageSize}`}
|
||||
onValueChange={(value) => {
|
||||
table.setPageSize(Number(value));
|
||||
}}
|
||||
disabled={totalRows === 0}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-[70px]">
|
||||
<SelectValue placeholder={pageSize} />
|
||||
</SelectTrigger>
|
||||
<SelectContent side="top">
|
||||
{relevantPageSizes.map((size) => (
|
||||
<SelectItem key={size} value={`${size}`}>
|
||||
{formatNumber(size)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-sm font-medium whitespace-nowrap">
|
||||
صفحه {formatNumber(currentPage + 1)} از {formatNumber(pageCount)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="hidden size-8 lg:flex"
|
||||
onClick={() => table.setPageIndex(0)}
|
||||
disabled={currentPage === 0 || totalRows === 0}
|
||||
aria-label="رفتن به صفحه اول"
|
||||
>
|
||||
<ChevronsLeft className="h-4 w-4 rtl:rotate-180" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="size-8"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
aria-label="رفتن به صفحه قبلی"
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4 rtl:rotate-180" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="size-8"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
aria-label="رفتن به صفحه بعدی"
|
||||
>
|
||||
<ChevronRight className="h-4 w-4 rtl:rotate-180" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="hidden size-8 lg:flex"
|
||||
onClick={() => table.setPageIndex(pageCount - 1)}
|
||||
disabled={currentPage === pageCount - 1 || totalRows === 0}
|
||||
aria-label="رفتن به صفحه آخر"
|
||||
>
|
||||
<ChevronsRight className="h-4 w-4 rtl:rotate-180" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface DataTableTabbarProps<TData> extends React.ComponentProps<"div"> {
|
||||
_table: TableType<TData>;
|
||||
search?: React.ReactNode;
|
||||
filter?: React.ReactNode;
|
||||
}
|
||||
export function DataTableTabbar<TData>({
|
||||
_table,
|
||||
children,
|
||||
search,
|
||||
filter,
|
||||
className,
|
||||
...props
|
||||
}: DataTableTabbarProps<TData>) {
|
||||
return (
|
||||
<div
|
||||
className={cn("flex items-center justify-between py-4", className)}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex flex-1 items-center gap-2">{children}</div>
|
||||
<div className="flex w-full items-center gap-2 sm:w-auto">
|
||||
{search}
|
||||
{filter}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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<Record<string, StatusOption>>((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<z.infer<typeof schema>>[] = [
|
||||
{
|
||||
accessorKey: "order",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="سفارش" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<div className="w-[100px] font-mono">{row.original.id}</div>
|
||||
),
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
label: "سفارش",
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "purchased",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="خریداریشده" />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const items = row.original.items;
|
||||
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<div className="max-w-[500px] truncate font-medium">
|
||||
{row.original.purchased}
|
||||
</div>
|
||||
{items && (
|
||||
<Badge variant="outline">{formatNumber(items)}</Badge>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
label: "خریداریشده",
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "customer",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="مشتری" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<div className="max-w-[150px] truncate">{row.original.customer}</div>
|
||||
),
|
||||
meta: {
|
||||
label: "مشتری",
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "order_status",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="وضعیت سفارش" />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const status = row.original.order_status;
|
||||
const statusMeta = getOrderStatusMeta(status);
|
||||
const StatusIcon = statusMeta?.icon;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge
|
||||
variant={getStatusVariant(status)}
|
||||
className="gap-1 whitespace-nowrap"
|
||||
>
|
||||
{StatusIcon && <StatusIcon className="size-3" />}
|
||||
<span>{statusMeta?.label ?? status}</span>
|
||||
</Badge>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
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 }) => (
|
||||
<DataTableColumnHeader column={column} title="وضعیت پرداخت" />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const status = row.original.payment_status;
|
||||
const statusMeta = getPaymentStatusMeta(status);
|
||||
const StatusIcon = statusMeta?.icon;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge
|
||||
variant={getPaymentVariant(status)}
|
||||
className="gap-1 whitespace-nowrap"
|
||||
>
|
||||
{StatusIcon && <StatusIcon className="size-3" />}
|
||||
<span>{statusMeta?.label ?? status}</span>
|
||||
</Badge>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
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 }) => (
|
||||
<DataTableColumnHeader column={column} title="روش پرداخت" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<div className="font-mono text-sm">{row.original.payment_method}</div>
|
||||
),
|
||||
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<OrderStatusTab>("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 = (
|
||||
<Input
|
||||
placeholder="جستجو در همه ستونها..."
|
||||
value={(table.getState().globalFilter as string) ?? ""}
|
||||
onChange={(event) => table.setGlobalFilter(event.target.value)}
|
||||
className="h-8 w-full lg:w-[224px]"
|
||||
/>
|
||||
);
|
||||
|
||||
const Toolbar = (
|
||||
<DataTableTabbar
|
||||
_table={table}
|
||||
search={Search}
|
||||
className="flex-wrap gap-y-2"
|
||||
>
|
||||
{orderStatusColumn && (
|
||||
<Tabs
|
||||
value={orderStatusTab}
|
||||
onValueChange={handleStatusTabChange}
|
||||
className="w-full min-w-[260px] sm:w-auto"
|
||||
>
|
||||
<TabsList
|
||||
className="flex w-full flex-wrap justify-start gap-1 group-data-horizontal/tabs:h-auto"
|
||||
role="tablist"
|
||||
aria-label="فیلتر سفارشها بر اساس وضعیت"
|
||||
>
|
||||
<TabsTrigger value="all" className="w-fit flex-none gap-1.5">
|
||||
<span>همه</span>
|
||||
<Badge variant="secondary" className="rounded-sm px-1.5">
|
||||
{formatNumber(allOrdersCount)}
|
||||
</Badge>
|
||||
</TabsTrigger>
|
||||
{orderStatuses.map((status) => {
|
||||
const StatusIcon = status.icon;
|
||||
const count = orderStatusCounts?.get(status.value) ?? 0;
|
||||
return (
|
||||
<TabsTrigger
|
||||
key={status.value}
|
||||
value={status.value}
|
||||
className="w-fit flex-none gap-1.5"
|
||||
>
|
||||
{StatusIcon && <StatusIcon className="size-3" />}
|
||||
<span className="hidden text-xs md:inline">
|
||||
{status.label}
|
||||
</span>
|
||||
<Badge variant="secondary" className="rounded-sm px-1.5">
|
||||
{formatNumber(count)}
|
||||
</Badge>
|
||||
</TabsTrigger>
|
||||
);
|
||||
})}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
)}
|
||||
</DataTableTabbar>
|
||||
);
|
||||
|
||||
return (
|
||||
<section dir="rtl" lang="fa" className={cn("w-full py-32 lg:w-auto", className)}>
|
||||
<div className="container">
|
||||
<div className="w-full">
|
||||
<div className="mb-8 text-start">
|
||||
<h2 className="text-xl font-bold tracking-tight">
|
||||
جدول با فیلترهای تبدار
|
||||
</h2>
|
||||
<p className="mt-2 text-muted-foreground">
|
||||
جدول سفارشات با مرتبسازی، جستجوی سراسری، فیلتر وضعیت از طریق
|
||||
تبها و کنترلهای کامل صفحهبندی با طراحی واکنشگرا.
|
||||
</p>
|
||||
</div>
|
||||
{Toolbar}
|
||||
<div className="overflow-hidden rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext(),
|
||||
)}
|
||||
</TableHead>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
نتیجهای یافت نشد.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<DataTablePagination table={table} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user