feat: add DataTable component and integrate with portfolio page; update dependencies

This commit is contained in:
THIS ONE IS A LITTLE BIT TRICKY KRUB 2024-11-11 16:17:35 +07:00
parent 86d9a2376a
commit 0bef51750c
5 changed files with 251 additions and 43 deletions

34
package-lock.json generated
View File

@ -34,6 +34,7 @@
"@supabase/supabase-js": "^2.46.1",
"@tanstack/react-query": "^5.59.0",
"@tanstack/react-query-devtools": "^5.59.0",
"@tanstack/react-table": "^8.20.5",
"b2d-ventures": "file:",
"chart.js": "^4.4.6",
"class-variance-authority": "^0.7.0",
@ -3263,6 +3264,39 @@
"react": "^18 || ^19"
}
},
"node_modules/@tanstack/react-table": {
"version": "8.20.5",
"resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.5.tgz",
"integrity": "sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==",
"license": "MIT",
"dependencies": {
"@tanstack/table-core": "8.20.5"
},
"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.20.5",
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz",
"integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.3.0.tgz",

View File

@ -36,6 +36,7 @@
"@supabase/supabase-js": "^2.46.1",
"@tanstack/react-query": "^5.59.0",
"@tanstack/react-query-devtools": "^5.59.0",
"@tanstack/react-table": "^8.20.5",
"b2d-ventures": "file:",
"chart.js": "^4.4.6",
"class-variance-authority": "^0.7.0",

View File

@ -1,42 +0,0 @@
"use client";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import Image from "next/image";
import { StaticImport } from "next/dist/shared/lib/get-img-props";
interface ItemProps {
src: string | StaticImport;
alt: string;
width: number;
height: number;
className?: string;
}
const ImageModal = ({ src, alt, width, height, className }: ItemProps) => {
return (
<Dialog>
<DialogTrigger asChild>
<Image src={src} alt={alt} width={width} height={height} className={className} />
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Image Preview</DialogTitle>
<DialogDescription>Click outside to close the image preview.</DialogDescription>
</DialogHeader>
<Image src={src} alt={alt} width={700} height={400} />
<DialogFooter />
</DialogContent>
</Dialog>
);
};
export function DisplayFullImage({ src, alt, width, height, className }: ItemProps) {
return <ImageModal src={src} alt={alt} width={width} height={height} className={className} />;
}

View File

@ -24,6 +24,8 @@ import { NoDataAlert } from "@/components/alert/noData/alert";
import { error } from "console";
import { UnAuthorizedAlert } from "@/components/alert/unauthorized/alert";
import Link from "next/link";
import { DataTable } from "@/components/dataTable";
import { Button } from "@/components/ui/button";
export default async function Portfolio({ params }: { params: { uid: string } }) {
const supabase = createSupabaseClient();
@ -230,8 +232,12 @@ export default async function Portfolio({ params }: { params: { uid: string } })
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-md font-bold">Recent investment</CardTitle>
</CardHeader>
<CardContent className="mt-5">
<CardContent className="mt-5 grid grid-flow-row-dense">
<RecentFunds data={latestDeals} />
<div className="mt-5 flex justify-center">
{deals && deals.length > 5 ? <Button>View More</Button> : ""}
{/* <DataTable data={latestDeals} /> */}
</div>
</CardContent>
</Card>
</div>

View File

@ -0,0 +1,209 @@
"use client";
import * as React from "react";
import {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import { ArrowUpDown, ChevronDown } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
export type Payment = {
data?: {
name?: string;
amount?: number;
avatar?: string;
date?: Date;
logo_url?: string;
status?: string;
profile_url?: string;
}[];
};
export const columns: ColumnDef<Payment>[] = [
{
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: "email",
header: ({ column }) => {
return (
<Button variant="ghost" onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}>
Email
<ArrowUpDown />
</Button>
);
},
cell: ({ row }) => <div className="lowercase">{row.getValue("email")}</div>,
},
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => <div className="capitalize">{row.getValue("status")}</div>,
},
{
accessorKey: "amount",
header: () => <div className="text-right">Amount</div>,
cell: ({ row }) => {
const amount = parseFloat(row.getValue("amount"));
// Format the amount as a dollar amount
const formatted = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(amount);
return <div className="text-right font-medium">{formatted}</div>;
},
},
];
export function DataTable({ data }: { data: Payment[] }) {
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="flex items-center py-4">
<Input
placeholder="Filter emails..."
value={(table.getColumn("email")?.getFilterValue() as string) ?? ""}
onChange={(event) => table.getColumn("email")?.setFilterValue(event.target.value)}
className="max-w-sm"
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto">
Columns <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) => column.toggleVisibility(!!value)}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<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 items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of {table.getFilteredRowModel().rows.length} row(s)
selected.
</div>
<div className="space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button variant="outline" size="sm" onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}>
Next
</Button>
</div>
</div>
</div>
);
}