320 lines
10 KiB
TypeScript
320 lines
10 KiB
TypeScript
"use client";
|
|
|
|
import type { ColumnDef, SortingState } from "@tanstack/react-table";
|
|
import {
|
|
flexRender,
|
|
getCoreRowModel,
|
|
getSortedRowModel,
|
|
useReactTable,
|
|
} from "@tanstack/react-table";
|
|
import * as React from "react";
|
|
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableFooter,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
const formatPrice = (value: number) =>
|
|
new Intl.NumberFormat("fa-IR", {
|
|
style: "currency",
|
|
currency: "IRR",
|
|
}).format(value);
|
|
|
|
const formatNumber = (value: number) => value.toLocaleString("fa-IR");
|
|
|
|
type LineItem = {
|
|
id: number;
|
|
sku: string;
|
|
name: string;
|
|
description: string;
|
|
quantity: number;
|
|
unitPrice: number;
|
|
};
|
|
|
|
type LineItemColumnMeta = {
|
|
headerClassName?: string;
|
|
cellClassName?: string;
|
|
};
|
|
|
|
const lineItems: LineItem[] = [
|
|
{
|
|
id: 1,
|
|
sku: "RBK-32-MN",
|
|
name: "کولهپشتی ریجوی",
|
|
description: "سرمهای تیره · ظرفیت ۳۲ لیتر",
|
|
quantity: 2,
|
|
unitPrice: 145,
|
|
},
|
|
{
|
|
id: 2,
|
|
sku: "EVB-20-SL",
|
|
name: "بطری روزمره ۲۰ اونسی",
|
|
description: "خاکستری · استیل سهلایه",
|
|
quantity: 3,
|
|
unitPrice: 28,
|
|
},
|
|
{
|
|
id: 3,
|
|
sku: "DBH-FT-NV",
|
|
name: "هودی پشمی سپیدهدم",
|
|
description: "پارچه فرنچ تری · رنگ شبافت",
|
|
quantity: 1,
|
|
unitPrice: 95,
|
|
},
|
|
{
|
|
id: 4,
|
|
sku: "TRV-ORG-SET",
|
|
name: "ست نظمدهنده سفر",
|
|
description: "کیسههای مشبک · بسته ۴ عددی",
|
|
quantity: 1,
|
|
unitPrice: 72,
|
|
},
|
|
{
|
|
id: 5,
|
|
sku: "EXP-90-OLV",
|
|
name: "کیف دوشی اکسپدیشن ۹۰ لیتر",
|
|
description: "ریپاستاپ ضدآب · زیتونی",
|
|
quantity: 1,
|
|
unitPrice: 260,
|
|
},
|
|
];
|
|
|
|
const lineTotal = (item: LineItem) => item.quantity * item.unitPrice;
|
|
|
|
export function DataTable25({ className }: { className?: string }) {
|
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
|
|
const columns = React.useMemo<ColumnDef<LineItem>[]>(
|
|
() => [
|
|
{
|
|
id: "product",
|
|
header: "محصول",
|
|
cell: ({ row }) => {
|
|
const item = row.original;
|
|
return (
|
|
<div className="text-sm">
|
|
<div className="font-medium text-foreground">{item.name}</div>
|
|
<div className="mt-1 truncate text-muted-foreground">
|
|
{item.description}
|
|
</div>
|
|
<div className="mt-1 text-xs tracking-wide text-muted-foreground">
|
|
کد کالا: {item.sku}
|
|
</div>
|
|
</div>
|
|
);
|
|
},
|
|
meta: {
|
|
headerClassName:
|
|
"py-3.5 ps-4 pe-3 text-start text-sm font-semibold text-foreground sm:ps-0 h-auto",
|
|
cellClassName:
|
|
"max-w-0 py-5 ps-4 pe-3 align-top text-sm sm:ps-0 text-foreground",
|
|
},
|
|
},
|
|
{
|
|
accessorKey: "quantity",
|
|
header: "تعداد",
|
|
cell: ({ row }) => formatNumber(row.original.quantity),
|
|
meta: {
|
|
headerClassName:
|
|
"hidden px-3 py-3.5 text-end text-sm font-semibold text-foreground sm:table-cell",
|
|
cellClassName:
|
|
"hidden px-3 py-5 text-end text-sm text-muted-foreground sm:table-cell",
|
|
},
|
|
},
|
|
{
|
|
accessorKey: "unitPrice",
|
|
header: "قیمت واحد",
|
|
cell: ({ row }) => formatPrice(row.original.unitPrice),
|
|
meta: {
|
|
headerClassName:
|
|
"hidden px-3 py-3.5 text-end text-sm font-semibold text-foreground sm:table-cell",
|
|
cellClassName:
|
|
"hidden px-3 py-5 text-end text-sm text-muted-foreground sm:table-cell",
|
|
},
|
|
},
|
|
{
|
|
id: "total",
|
|
header: "جمع",
|
|
cell: ({ row }) => formatPrice(lineTotal(row.original)),
|
|
meta: {
|
|
headerClassName:
|
|
"py-3.5 ps-3 pe-4 text-end text-sm font-semibold text-foreground sm:pe-0 h-auto",
|
|
cellClassName:
|
|
"py-5 ps-3 pe-4 text-end text-sm text-muted-foreground sm:pe-0",
|
|
},
|
|
},
|
|
],
|
|
[],
|
|
);
|
|
|
|
const table = useReactTable({
|
|
data: lineItems,
|
|
columns,
|
|
state: { sorting },
|
|
onSortingChange: setSorting,
|
|
getCoreRowModel: getCoreRowModel(),
|
|
getSortedRowModel: getSortedRowModel(),
|
|
getRowId: (row) => String(row.id),
|
|
});
|
|
|
|
const subtotalAmount = lineItems.reduce(
|
|
(sum, item) => sum + lineTotal(item),
|
|
0,
|
|
);
|
|
const taxAmount = 64.08;
|
|
const totalAmount = subtotalAmount + taxAmount;
|
|
|
|
const subtotal = formatPrice(subtotalAmount);
|
|
const tax = formatPrice(taxAmount);
|
|
const total = formatPrice(totalAmount);
|
|
|
|
return (
|
|
<section dir="rtl" lang="fa" className={cn("py-32", 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>
|
|
</div>
|
|
<div className="overflow-x-auto">
|
|
<Table className="min-w-full">
|
|
<colgroup>
|
|
<col className="w-full sm:w-1/2" />
|
|
<col className="sm:w-1/6" />
|
|
<col className="sm:w-1/6" />
|
|
<col className="sm:w-1/6" />
|
|
</colgroup>
|
|
|
|
<TableHeader className="text-foreground [&_tr]:border-border/60">
|
|
{table.getHeaderGroups().map((headerGroup) => (
|
|
<TableRow
|
|
key={headerGroup.id}
|
|
className="border-0 hover:bg-transparent"
|
|
>
|
|
{headerGroup.headers.map((header) => {
|
|
const meta =
|
|
(header.column.columnDef.meta as LineItemColumnMeta) ??
|
|
{};
|
|
return (
|
|
<TableHead
|
|
key={header.id}
|
|
scope="col"
|
|
className={cn(
|
|
"h-auto bg-transparent text-sm font-semibold",
|
|
meta.headerClassName,
|
|
)}
|
|
>
|
|
{header.isPlaceholder
|
|
? null
|
|
: flexRender(
|
|
header.column.columnDef.header,
|
|
header.getContext(),
|
|
)}
|
|
</TableHead>
|
|
);
|
|
})}
|
|
</TableRow>
|
|
))}
|
|
</TableHeader>
|
|
|
|
<TableBody>
|
|
{table.getRowModel().rows.map((row) => (
|
|
<TableRow
|
|
key={row.id}
|
|
className="border-border/60 hover:bg-transparent"
|
|
>
|
|
{row.getVisibleCells().map((cell) => {
|
|
const meta =
|
|
(cell.column.columnDef.meta as LineItemColumnMeta) ?? {};
|
|
return (
|
|
<TableCell
|
|
key={cell.id}
|
|
className={cn(meta.cellClassName)}
|
|
>
|
|
{flexRender(
|
|
cell.column.columnDef.cell,
|
|
cell.getContext(),
|
|
)}
|
|
</TableCell>
|
|
);
|
|
})}
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
|
|
<TableFooter className="!border-0 !bg-transparent !font-normal">
|
|
<TableRow className="border-0 hover:bg-transparent">
|
|
<TableHead
|
|
scope="row"
|
|
colSpan={3}
|
|
className="hidden h-auto border-0 pt-6 pe-3 ps-4 text-end text-sm font-normal text-muted-foreground sm:table-cell sm:ps-0"
|
|
>
|
|
جمع جزء
|
|
</TableHead>
|
|
<TableHead
|
|
scope="row"
|
|
className="h-auto border-0 pt-6 pe-3 ps-4 text-start text-sm font-normal text-muted-foreground sm:hidden"
|
|
>
|
|
جمع جزء
|
|
</TableHead>
|
|
<TableCell className="border-0 pt-6 pe-4 ps-3 text-end text-sm text-muted-foreground sm:pe-0">
|
|
{subtotal}
|
|
</TableCell>
|
|
</TableRow>
|
|
|
|
<TableRow className="border-0 hover:bg-transparent">
|
|
<TableHead
|
|
scope="row"
|
|
colSpan={3}
|
|
className="hidden h-auto border-0 pt-4 pe-3 ps-4 text-end text-sm font-normal text-muted-foreground sm:table-cell sm:ps-0"
|
|
>
|
|
مالیات
|
|
</TableHead>
|
|
<TableHead
|
|
scope="row"
|
|
className="h-auto border-0 pt-4 pe-3 ps-4 text-start text-sm font-normal text-muted-foreground sm:hidden"
|
|
>
|
|
مالیات
|
|
</TableHead>
|
|
<TableCell className="border-0 pt-4 pe-4 ps-3 text-end text-sm text-muted-foreground sm:pe-0">
|
|
{tax}
|
|
</TableCell>
|
|
</TableRow>
|
|
|
|
<TableRow className="border-0 hover:bg-transparent">
|
|
<TableHead
|
|
scope="row"
|
|
colSpan={3}
|
|
className="hidden h-auto border-0 pt-4 pe-3 ps-4 text-end text-sm font-semibold text-foreground sm:table-cell sm:ps-0"
|
|
>
|
|
جمع کل
|
|
</TableHead>
|
|
<TableHead
|
|
scope="row"
|
|
className="h-auto border-0 pt-4 pe-3 ps-4 text-start text-sm font-semibold text-foreground sm:hidden"
|
|
>
|
|
جمع کل
|
|
</TableHead>
|
|
<TableCell className="border-0 pt-4 pe-4 ps-3 text-end text-sm font-semibold text-foreground sm:pe-0">
|
|
{total}
|
|
</TableCell>
|
|
</TableRow>
|
|
</TableFooter>
|
|
</Table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|