add: transaction history & its datatable

This commit is contained in:
Mahyar Khanbolooki
2025-07-22 22:42:58 +03:30
parent 2454107da0
commit b9d14478a1
8 changed files with 1116 additions and 0 deletions
+501
View File
@@ -0,0 +1,501 @@
"use client"
import * as React from "react"
import {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table"
import { Button } from "@/components/ui/button"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import clsx from "clsx"
import { EyeToggleIcon } from "../icons/EyeToggleIcon"
import { ArrowLeft2, ArrowRight2 } from "iconsax-react"
const data: Payment[] = [
{
id: "m5gr84i9",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "3u1reuv4",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "derv1ws0",
type: 'تایپ',
status: false,
date: 14040401,
},
{
id: "5kma53ae",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "bhqecj4p",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "m5gr84i9",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "3u1reuv4",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "derv1ws0",
type: 'تایپ',
status: false,
date: 14040401,
},
{
id: "5kma53ae",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "bhqecj4p",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "m5gr84i9",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "3u1reuv4",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "derv1ws0",
type: 'تایپ',
status: false,
date: 14040401,
},
{
id: "5kma53ae",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "bhqecj4p",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "m5gr84i9",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "3u1reuv4",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "derv1ws0",
type: 'تایپ',
status: false,
date: 14040401,
},
{
id: "5kma53ae",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "bhqecj4p",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "m5gr84i9",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "3u1reuv4",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "derv1ws0",
type: 'تایپ',
status: false,
date: 14040401,
},
{
id: "5kma53ae",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "bhqecj4p",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "m5gr84i9",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "3u1reuv4",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "derv1ws0",
type: 'تایپ',
status: false,
date: 14040401,
},
{
id: "5kma53ae",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "bhqecj4p",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "m5gr84i9",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "3u1reuv4",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "derv1ws0",
type: 'تایپ',
status: false,
date: 14040401,
},
{
id: "5kma53ae",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "bhqecj4p",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "m5gr84i9",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "3u1reuv4",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "derv1ws0",
type: 'تایپ',
status: false,
date: 14040401,
},
{
id: "5kma53ae",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "bhqecj4p",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "m5gr84i9",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "3u1reuv4",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "derv1ws0",
type: 'تایپ',
status: false,
date: 14040401,
},
{
id: "5kma53ae",
type: 'تایپ',
status: true,
date: 14040401,
},
{
id: "bhqecj4p",
type: 'تایپ',
status: true,
date: 14040401,
},
]
export type Payment = {
id: string
type: string
// status: "pending" | "processing" | "success" | "failed"
status: boolean
date: number
}
export const columns: ColumnDef<Payment>[] = [
{
accessorKey: "id",
header: "شماره",
cell: ({ row }) => (
<div className="capitalize">{row.getValue("id")}</div>
),
},
{
accessorKey: "type",
header: () => "نوع تراکنش",
cell: ({ row }) => <div className="capitalize">{row.getValue("type")}</div>,
},
{
accessorKey: "date",
header: () => <div className="text-right">تاریخ</div>,
cell: ({ row }) => {
const date = parseFloat(row.getValue("date"))
// Format the amount as a dollar amount
// const formatted = new Intl.NumberFormat("en-US", {
// style: "currency",
// currency: "USD",
// }).format(amount)
// TODO: convert to jalali
return <div className="text-right">{date}</div>
},
},
{
accessorKey: "status",
header: () => <div className="text-right">وضعیت</div>,
cell: ({ row }) => {
const status = Boolean(row.getValue("status"))
const str = status ? 'پاسخ داده شده' : 'پاسخ داده نشده';
return (
<div className='text-right flex items-center justify-center gap-x-2'>
<span className={clsx('size-2 rounded-full', status ? 'bg-[#00BA4B]' : 'bg-transparent border border-primary')}></span>
{str}
</div>
)
},
},
{
id: "actions",
enableHiding: false,
cell: ({ row }) => {
const status = Boolean(row.getValue("status"))
return (
<>
<EyeToggleIcon slash={status} />
</>
)
},
},
]
export function DataTableDemo() {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({})
const [rowSelection, setRowSelection] = React.useState({})
const table = useReactTable({
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
})
return (
<div className="w-full">
<div className="rounded-3xl w-full">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id} className="font-light text-xs text-disabled-text text-center">
{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"}
className="text-center w-full text-xs odd:bg-[#E9EBF433] even:bg-container border-0 border-b h-14 border-r-4 border-r-transparent active:border-r-primary"
>
{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"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4 px-7">
<div className="flex-1 text-sm text-disabled-text">
نمایش: <span className="text-primary">{table.getRowModel().rows.length}</span> از {table.getFilteredRowModel().rows.length}
</div>
<div className="space-x-2 justify-center flex">
<Button
variant="ghost"
size="sm"
onClick={() => table.previousPage()}
hidden={!table.getCanPreviousPage()}
>
<ArrowRight2 size={12} className="stroke-[#A8ABBF]" />
</Button>
<Button
variant='secondary'
size="sm"
onClick={() => table.previousPage()}
hidden={!table.getCanPreviousPage()}
>
{table.getState().pagination.pageIndex}
</Button>
<Button
variant='default'
size="sm"
hidden={!table.getCanPreviousPage() && !table.getCanNextPage()}
>
{table.getState().pagination.pageIndex + 1}
</Button>
<Button
variant='secondary'
size="sm"
onClick={() => table.nextPage()}
hidden={!table.getCanNextPage()}
>
{table.getState().pagination.pageIndex + 2}
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => table.nextPage()}
hidden={!table.getCanNextPage()}
>
<ArrowLeft2 size={12} className="stroke-[#A8ABBF]" />
</Button>
</div>
</div>
</div>
)
}
+201
View File
@@ -0,0 +1,201 @@
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}
+22
View File
@@ -0,0 +1,22 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }
+48
View File
@@ -0,0 +1,48 @@
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }
+120
View File
@@ -0,0 +1,120 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}