"use client";
import { useState } from "react";
import {
type ColumnDef,
type SortingState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import {
ChevronLeft,
ChevronRight,
Eye,
Pencil,
Trash2,
MoreHorizontal,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
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 { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
type Status = "completed" | "pending" | "processing" | "cancelled";
interface Item {
id: string;
name: string;
date: string;
status: Status;
amount: string;
}
const statusConfig: Record<Status, { label: string; className: string }> = {
completed: {
label: "Completed",
className:
"bg-emerald-500/15 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400",
},
pending: {
label: "Pending",
className:
"bg-amber-500/15 text-amber-700 dark:bg-amber-500/10 dark:text-amber-400",
},
processing: {
label: "Processing",
className:
"bg-blue-500/15 text-blue-700 dark:bg-blue-500/10 dark:text-blue-400",
},
cancelled: {
label: "Cancelled",
className:
"bg-rose-500/15 text-rose-700 dark:bg-rose-500/10 dark:text-rose-400",
},
};
function StatusBadge({ status }: { status: Status }) {
const config = statusConfig[status];
return (
<Badge variant="outline" className={cn("border-0", config.className)}>
{config.label}
</Badge>
);
}
const columns: ColumnDef<Item>[] = [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "name",
header: "Name",
cell: ({ row }) => (
<span className="font-medium">{row.getValue("name")}</span>
),
},
{
accessorKey: "date",
header: "Date",
},
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => <StatusBadge status={row.getValue("status")} />,
},
{
accessorKey: "amount",
header: () => <div className="text-right">Amount</div>,
cell: ({ row }) => (
<div className="text-right font-medium">{row.getValue("amount")}</div>
),
},
{
id: "actions",
cell: () => (
<div className="text-right">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8">
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">Open menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<Eye className="mr-2 h-4 w-4" />
View details
</DropdownMenuItem>
<DropdownMenuItem>
<Pencil className="mr-2 h-4 w-4" />
Edit
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-destructive">
<Trash2 className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
),
},
];
const data: Item[] = [
{
id: "1",
name: "Project Alpha",
date: "Jan 15, 2024",
status: "completed",
amount: "$2,500",
},
{
id: "2",
name: "Website Redesign",
date: "Feb 3, 2024",
status: "processing",
amount: "$4,200",
},
{
id: "3",
name: "Mobile App MVP",
date: "Feb 18, 2024",
status: "pending",
amount: "$8,750",
},
{
id: "4",
name: "Brand Identity",
date: "Mar 5, 2024",
status: "completed",
amount: "$1,800",
},
{
id: "5",
name: "Marketing Campaign",
date: "Mar 22, 2024",
status: "cancelled",
amount: "$3,400",
},
{
id: "6",
name: "Analytics Dashboard",
date: "Apr 8, 2024",
status: "processing",
amount: "$5,600",
},
{
id: "7",
name: "E-commerce Platform",
date: "Apr 25, 2024",
status: "pending",
amount: "$12,000",
},
{
id: "8",
name: "API Integration",
date: "May 10, 2024",
status: "completed",
amount: "$3,200",
},
];
export default function Table05() {
const [sorting, setSorting] = useState<SortingState>([]);
const [rowSelection, setRowSelection] = useState({});
const [globalFilter, setGlobalFilter] = useState("");
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onSortingChange: setSorting,
onRowSelectionChange: setRowSelection,
onGlobalFilterChange: setGlobalFilter,
globalFilterFn: "includesString",
state: {
sorting,
rowSelection,
globalFilter,
},
initialState: {
pagination: { pageSize: 5 },
},
});
const pageCount = table.getPageCount();
const currentPage = table.getState().pagination.pageIndex + 1;
return (
<div className="max-w-3xl w-full space-y-4">
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground">Show</span>
<Select
value={String(table.getState().pagination.pageSize)}
onValueChange={(value) => table.setPageSize(Number(value))}
>
<SelectTrigger className="h-8 w-16">
<SelectValue />
</SelectTrigger>
<SelectContent>
{[5, 10, 20].map((size) => (
<SelectItem key={size} value={String(size)}>
{size}
</SelectItem>
))}
</SelectContent>
</Select>
<span className="text-sm text-muted-foreground">entries</span>
</div>
<Input
placeholder="Search..."
value={globalFilter}
onChange={(e) => setGlobalFilter(e.target.value)}
className="h-8 w-full sm:w-64"
/>
</div>
<div className="rounded-lg border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<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"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<p className="text-sm text-muted-foreground">
Showing{" "}
{table.getState().pagination.pageIndex *
table.getState().pagination.pageSize +
1}{" "}
to{" "}
{Math.min(
(table.getState().pagination.pageIndex + 1) *
table.getState().pagination.pageSize,
table.getFilteredRowModel().rows.length
)}{" "}
of {table.getFilteredRowModel().rows.length} entries
</p>
<div className="flex items-center gap-1">
<Button
variant="outline"
size="icon"
className="h-8 w-8"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<ChevronLeft className="h-4 w-4" />
<span className="sr-only">Previous page</span>
</Button>
{Array.from({ length: pageCount }, (_, i) => i + 1).map((page) => (
<Button
key={page}
variant={currentPage === page ? "default" : "outline"}
size="icon"
className="h-8 w-8"
onClick={() => table.setPageIndex(page - 1)}
>
{page}
</Button>
))}
<Button
variant="outline"
size="icon"
className="h-8 w-8"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<ChevronRight className="h-4 w-4" />
<span className="sr-only">Next page</span>
</Button>
</div>
</div>
</div>
);
}