diff --git a/frontend/app/all-customers/columns.tsx b/frontend/app/all-customers/columns.tsx new file mode 100644 index 0000000..5a3ebe6 --- /dev/null +++ b/frontend/app/all-customers/columns.tsx @@ -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[] = [ + { + 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: () =>
Balance
, + 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
{formatted}
; + }, + }, + { + id: "actions", + header: () =>
More Actions
, + cell: ({ row }) => { + const customer = row.original; + + return ( +
+ + + + + + + Actions + navigator.clipboard.writeText(customer.id)} + > + Copy Customer ID + + View Transactions + Edit Customer + + + Delete Customer + + + +
+ ); + }, + }, +]; diff --git a/frontend/app/all-customers/customer-data.ts b/frontend/app/all-customers/customer-data.ts new file mode 100644 index 0000000..5873bd5 --- /dev/null +++ b/frontend/app/all-customers/customer-data.ts @@ -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, + }, +]; diff --git a/frontend/app/all-customers/data-table.tsx b/frontend/app/all-customers/data-table.tsx new file mode 100644 index 0000000..4105c31 --- /dev/null +++ b/frontend/app/all-customers/data-table.tsx @@ -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 { + columns: ColumnDef[]; + data: TData[]; +} + +export function DataTable({ + columns, + data, +}: DataTableProps) { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + }); + + return ( +
+ {/* Pagination Control */} + +
+ + + {/* Chevron Left (Previous Page) */} + + + {/* Page info display */} + + Page {table.getState().pagination.pageIndex + 1} of{" "} + {table.getPageCount()} + + + {/* Chevron Right (Next Page) */} + + + {/* Double Chevron Right (Go to Last Page) */} + +
+ + {/*
+ + + + Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()} + + + +
*/} + + {/* Table rendering */} +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+
+ ); +} diff --git a/frontend/app/all-customers/page.tsx b/frontend/app/all-customers/page.tsx index 3d75b0e..15164c2 100644 --- a/frontend/app/all-customers/page.tsx +++ b/frontend/app/all-customers/page.tsx @@ -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 = () => { -
A table of customers
+
+

A table of customers

+ +
+ +
+
); }; diff --git a/frontend/components/ui/button.tsx b/frontend/components/ui/button.tsx index b5ea4ab..0a254f1 100644 --- a/frontend/components/ui/button.tsx +++ b/frontend/components/ui/button.tsx @@ -1,11 +1,11 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" -import { Slot } from "radix-ui" +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; +import { Slot } from "radix-ui"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { @@ -35,8 +35,8 @@ const buttonVariants = cva( variant: "default", size: "default", }, - } -) + }, +); function Button({ className, @@ -46,9 +46,9 @@ function Button({ ...props }: React.ComponentProps<"button"> & VariantProps & { - asChild?: boolean + asChild?: boolean; }) { - const Comp = asChild ? Slot.Root : "button" + const Comp = asChild ? Slot.Root : "button"; return ( - ) + ); } -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/frontend/components/ui/dropdown-menu.tsx b/frontend/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..6c675dd --- /dev/null +++ b/frontend/components/ui/dropdown-menu.tsx @@ -0,0 +1,257 @@ +"use client"; + +import * as React from "react"; +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"; +import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"; + +import { cn } from "@/lib/utils"; + +function DropdownMenu({ + ...props +}: React.ComponentProps) { + return ; +} + +function DropdownMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuContent({ + className, + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ); +} + +function DropdownMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean; + variant?: "default" | "destructive"; +}) { + return ( + + ); +} + +function DropdownMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ); +} + +function DropdownMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ); +} + +function DropdownMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean; +}) { + return ( + + ); +} + +function DropdownMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ); +} + +function DropdownMenuSub({ + ...props +}: React.ComponentProps) { + return ; +} + +function DropdownMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean; +}) { + return ( + + {children} + + + ); +} + +function DropdownMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { + DropdownMenu, + DropdownMenuPortal, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuSubContent, +}; diff --git a/frontend/components/ui/select.tsx b/frontend/components/ui/select.tsx new file mode 100644 index 0000000..fd01b74 --- /dev/null +++ b/frontend/components/ui/select.tsx @@ -0,0 +1,190 @@ +"use client" + +import * as React from "react" +import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react" +import { Select as SelectPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +function Select({ + ...props +}: React.ComponentProps) { + return +} + +function SelectGroup({ + ...props +}: React.ComponentProps) { + return +} + +function SelectValue({ + ...props +}: React.ComponentProps) { + return +} + +function SelectTrigger({ + className, + size = "default", + children, + ...props +}: React.ComponentProps & { + size?: "sm" | "default" +}) { + return ( + + {children} + + + + + ) +} + +function SelectContent({ + className, + children, + position = "item-aligned", + align = "center", + ...props +}: React.ComponentProps) { + return ( + + + + + {children} + + + + + ) +} + +function SelectLabel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SelectItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function SelectSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SelectScrollUpButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function SelectScrollDownButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +} diff --git a/frontend/components/ui/table.tsx b/frontend/components/ui/table.tsx new file mode 100644 index 0000000..51b74dd --- /dev/null +++ b/frontend/components/ui/table.tsx @@ -0,0 +1,116 @@ +"use client" + +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Table({ className, ...props }: React.ComponentProps<"table">) { + return ( +
+ + + ) +} + +function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { + return ( + + ) +} + +function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { + return ( + + ) +} + +function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { + return ( + tr]:last:border-b-0", + className + )} + {...props} + /> + ) +} + +function TableRow({ className, ...props }: React.ComponentProps<"tr">) { + return ( + + ) +} + +function TableHead({ className, ...props }: React.ComponentProps<"th">) { + return ( +
[role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> + ) +} + +function TableCell({ className, ...props }: React.ComponentProps<"td">) { + return ( + [role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> + ) +} + +function TableCaption({ + className, + ...props +}: React.ComponentProps<"caption">) { + return ( +
+ ) +} + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9c9f7dc..c75f2d7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "frontend", "version": "0.1.0", "dependencies": { + "@tanstack/react-table": "^8.21.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.574.0", @@ -3764,6 +3765,39 @@ "tailwindcss": "4.1.18" } }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@ts-morph/common": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 8af2872..164f96a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,7 @@ "lint": "eslint" }, "dependencies": { + "@tanstack/react-table": "^8.21.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.574.0",