Added table with row actions and pagination. next up: filtering

This commit is contained in:
2026-02-19 23:49:30 +05:30
parent 13ca4a144b
commit ab785c610f
10 changed files with 1024 additions and 12 deletions

View File

@@ -0,0 +1,96 @@
"use client";
import { ColumnDef } from "@tanstack/react-table";
import { Ghost, MoreHorizontal } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export type Customer = {
id: string;
name: string;
occupation: string;
phone: string;
address: string;
balance: number;
};
export const columns: ColumnDef<Customer>[] = [
{
accessorKey: "id",
header: "ID",
size: 70,
},
{
accessorKey: "name",
header: "Name",
},
{
accessorKey: "occupation",
header: "Occupation",
},
{
accessorKey: "phone",
header: "Phone",
},
{
accessorKey: "address",
header: "Address",
size: 150,
},
{
accessorKey: "balance",
header: () => <div className="text-right">Balance</div>,
cell: ({ row }) => {
const amount = parseFloat(row.getValue("balance"));
const formatted = new Intl.NumberFormat("en-IN", {
style: "currency",
currency: "INR",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(amount);
return <div className="text-right">{formatted}</div>;
},
},
{
id: "actions",
header: () => <div className="text-right">More Actions</div>,
cell: ({ row }) => {
const customer = row.original;
return (
<div className="text-right">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost">
<span className="sr-only">Open Menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(customer.id)}
>
Copy Customer ID
</DropdownMenuItem>
<DropdownMenuItem>View Transactions</DropdownMenuItem>
<DropdownMenuItem>Edit Customer</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
Delete Customer
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
},
},
];

View File

@@ -0,0 +1,122 @@
export const customerData = [
{
id: "1",
name: "Imran Khan",
occupation: "Electrician",
phone: "9912246789",
address: "House No. 15, Paharilal Road, Haibargaon, Assam",
balance: 225500,
},
{
id: "2",
name: "Ramesh Sharma",
occupation: "Farmer",
phone: "9876543210",
address: "Plot No. 8, Sudenga, Kachari, Assam",
balance: 470000,
},
{
id: "3",
name: "Jameela Begum",
occupation: "Tailor",
phone: "9123456789",
address: "House No. 24, Chalchali Bazar, Assam",
balance: 290000,
},
{
id: "4",
name: "Nekibur Rahman",
occupation: "Plumber",
phone: "9118844122",
address: "House No. 42, Baruah Path, Beltola, Guwahati",
balance: 534400,
},
{
id: "5",
name: "Sunita Devi",
occupation: "Housewife",
phone: "9865723456",
address: "Deka Bagan, Kawaimari, Assam",
balance: 115600,
},
{
id: "6",
name: "Anwar Hussain",
occupation: "Carpenter",
phone: "9798754631",
address: "Village Road, Charigaon, Assam",
balance: 360000,
},
{
id: "7",
name: "Manoj Kumar",
occupation: "Shopkeeper",
phone: "9223344556",
address: "House No. 18, Koilamari, Assam",
balance: 144500,
},
{
id: "8",
name: "Sharmin Akhtar",
occupation: "Caterer",
phone: "9403126789",
address: "Block No. 3, Samaguri, Assam",
balance: 290400,
},
{
id: "9",
name: "Gopal Choudhury",
occupation: "Mechanic",
phone: "9115678901",
address: "Near Post Office, Sialmari, Assam",
balance: 198300,
},
{
id: "10",
name: "Zahida Khatun",
occupation: "School Teacher",
phone: "9536879412",
address: "House No. 35, Puthimari, Assam",
balance: 410500,
},
{
id: "11",
name: "Jitendra Prasad",
occupation: "Painter",
phone: "9732857410",
address: "Near Bypass Road, Haibargaon, Assam",
balance: 220700,
},
{
id: "12",
name: "Shubham Singh",
occupation: "Driver",
phone: "9102468832",
address: "Khalihamari, Near Chalchali, Assam",
balance: 182000,
},
{
id: "13",
name: "Rehana Bibi",
occupation: "Domestic Worker",
phone: "9120784531",
address: "House No. 50, Madheli, Assam",
balance: 250000,
},
{
id: "14",
name: "Prakash Chandra",
occupation: "Construction Worker",
phone: "9324576801",
address: "Near Temple, Sarpara, Assam",
balance: 315400,
},
{
id: "15",
name: "Shahrukh Ali",
occupation: "Cook",
phone: "9401815612",
address: "House No. 88, Borkhat, Assam",
balance: 379000,
},
];

View File

@@ -0,0 +1,187 @@
"use client";
import {
ColumnDef,
flexRender,
getCoreRowModel,
getPaginationRowModel,
useReactTable,
} from "@tanstack/react-table";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import {
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
} from "lucide-react";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
}
export function DataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
return (
<div>
{/* Pagination Control */}
<div className="flex items-center justify-start space-x-2 py-4">
<Button
variant="outline"
size="icon"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<ChevronsLeft />
</Button>
{/* Chevron Left (Previous Page) */}
<Button
variant="outline"
size="icon"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<ChevronLeft />
</Button>
{/* Page info display */}
<span className="text-sm font-medium">
Page {table.getState().pagination.pageIndex + 1} of{" "}
{table.getPageCount()}
</span>
{/* Chevron Right (Next Page) */}
<Button
variant="outline"
size="icon"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<ChevronRight />
</Button>
{/* Double Chevron Right (Go to Last Page) */}
<Button
variant="outline"
size="icon"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<ChevronsRight />
</Button>
</div>
{/* <div className="flex items-center justify-start space-x-2 py-4">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<span className="text-sm font-medium">
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
</span>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div> */}
{/* Table rendering */}
<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}
style={{
minWidth: header.column.columnDef.size,
maxWidth: header.column.columnDef.size,
}}
>
{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}
className="whitespace-normal"
style={{
minWidth: cell.column.columnDef.size,
maxWidth: cell.column.columnDef.size,
}}
>
{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>
);
}

View File

@@ -1,4 +1,7 @@
import { Button } from "@/components/ui/button";
import { DataTable } from "./data-table";
import { columns } from "./columns";
import { customerData } from "./customer-data";
const page = () => {
return (
@@ -9,7 +12,13 @@ const page = () => {
<Button>Add Customer</Button>
</div>
<div>A table of customers</div>
<div>
<p>A table of customers</p>
<div className="w-full">
<DataTable columns={columns} data={customerData} />
</div>
</div>
</div>
);
};