Added table with row actions and pagination. next up: filtering
This commit is contained in:
96
frontend/app/all-customers/columns.tsx
Normal file
96
frontend/app/all-customers/columns.tsx
Normal 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>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
122
frontend/app/all-customers/customer-data.ts
Normal file
122
frontend/app/all-customers/customer-data.ts
Normal 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,
|
||||
},
|
||||
];
|
||||
187
frontend/app/all-customers/data-table.tsx
Normal file
187
frontend/app/all-customers/data-table.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user