From d4e4389fd3e0a30456a6e3ed74af96e0c26ae9ab Mon Sep 17 00:00:00 2001 From: Emeka Akashili <94780632+Dev-Akashili@users.noreply.github.com> Date: Mon, 8 Apr 2024 10:08:34 +0100 Subject: [PATCH] Add Nextjs Client App (#665) * Add next-client-app * Add scan reports data table --- app/next-client-app/.gitignore | 38 + app/next-client-app/README.md | 36 + app/next-client-app/api/data.ts | 309 + app/next-client-app/api/error.ts | 7 + app/next-client-app/api/request.ts | 37 + app/next-client-app/api/scan-reports.ts | 16 + app/next-client-app/app/globals.css | 3 + app/next-client-app/app/layout.tsx | 22 + app/next-client-app/app/page.tsx | 3 + .../app/scan-reports/columns.tsx | 63 + app/next-client-app/app/scan-reports/page.tsx | 53 + app/next-client-app/components.json | 17 + app/next-client-app/components/Navbar.tsx | 95 + .../data-table/DataTableColumnHeader.tsx | 71 + .../data-table/DataTablePagination.tsx | 97 + .../data-table/DataTableViewOptions.tsx | 59 + .../components/data-table/index.tsx | 175 + .../components/ui/breadcrumb.tsx | 118 + app/next-client-app/components/ui/button.tsx | 58 + .../components/ui/dropdown-menu.tsx | 200 + app/next-client-app/components/ui/input.tsx | 25 + app/next-client-app/components/ui/select.tsx | 160 + app/next-client-app/components/ui/table.tsx | 117 + app/next-client-app/constants/index.ts | 1 + app/next-client-app/lib/utils.ts | 6 + app/next-client-app/next-env.d.ts | 5 + app/next-client-app/next.config.mjs | 4 + app/next-client-app/package-lock.json | 5754 +++++++++++++++++ app/next-client-app/package.json | 38 + app/next-client-app/postcss.config.js | 6 + app/next-client-app/public/coconnect-logo.png | Bin 0 -> 4711 bytes app/next-client-app/tailwind.config.ts | 40 + app/next-client-app/tsconfig.json | 26 + app/next-client-app/types/index.ts | 19 + 34 files changed, 7678 insertions(+) create mode 100644 app/next-client-app/.gitignore create mode 100644 app/next-client-app/README.md create mode 100644 app/next-client-app/api/data.ts create mode 100644 app/next-client-app/api/error.ts create mode 100644 app/next-client-app/api/request.ts create mode 100644 app/next-client-app/api/scan-reports.ts create mode 100644 app/next-client-app/app/globals.css create mode 100644 app/next-client-app/app/layout.tsx create mode 100644 app/next-client-app/app/page.tsx create mode 100644 app/next-client-app/app/scan-reports/columns.tsx create mode 100644 app/next-client-app/app/scan-reports/page.tsx create mode 100644 app/next-client-app/components.json create mode 100644 app/next-client-app/components/Navbar.tsx create mode 100644 app/next-client-app/components/data-table/DataTableColumnHeader.tsx create mode 100644 app/next-client-app/components/data-table/DataTablePagination.tsx create mode 100644 app/next-client-app/components/data-table/DataTableViewOptions.tsx create mode 100644 app/next-client-app/components/data-table/index.tsx create mode 100644 app/next-client-app/components/ui/breadcrumb.tsx create mode 100644 app/next-client-app/components/ui/button.tsx create mode 100644 app/next-client-app/components/ui/dropdown-menu.tsx create mode 100644 app/next-client-app/components/ui/input.tsx create mode 100644 app/next-client-app/components/ui/select.tsx create mode 100644 app/next-client-app/components/ui/table.tsx create mode 100644 app/next-client-app/constants/index.ts create mode 100644 app/next-client-app/lib/utils.ts create mode 100644 app/next-client-app/next-env.d.ts create mode 100644 app/next-client-app/next.config.mjs create mode 100644 app/next-client-app/package-lock.json create mode 100644 app/next-client-app/package.json create mode 100644 app/next-client-app/postcss.config.js create mode 100644 app/next-client-app/public/coconnect-logo.png create mode 100644 app/next-client-app/tailwind.config.ts create mode 100644 app/next-client-app/tsconfig.json create mode 100644 app/next-client-app/types/index.ts diff --git a/app/next-client-app/.gitignore b/app/next-client-app/.gitignore new file mode 100644 index 000000000..be772e3ce --- /dev/null +++ b/app/next-client-app/.gitignore @@ -0,0 +1,38 @@ +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history +.next diff --git a/app/next-client-app/README.md b/app/next-client-app/README.md new file mode 100644 index 000000000..c4033664f --- /dev/null +++ b/app/next-client-app/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/app/next-client-app/api/data.ts b/app/next-client-app/api/data.ts new file mode 100644 index 000000000..a7fd1c98a --- /dev/null +++ b/app/next-client-app/api/data.ts @@ -0,0 +1,309 @@ +const user = { id: 1, username: "admin" }; + +const dataset = { + id: 1, + created_at: "2024-02-19T13:21:25.741370Z", + updated_at: "2024-03-04T12:27:01.460913Z", + name: "Test Dataset", + visibility: "PUBLIC", + hidden: false, + data_partner: 1, + viewers: [1], + admins: [1], + editors: [1], +}; + +const datapartner = { + id: 1, + created_at: "2024-02-19T13:16:39.480024Z", + updated_at: "2024-02-19T13:16:39.480070Z", + name: "Test", +}; + +export const scanReports: ScanReport[] = [ + { + id: 11, + created_at: "2024-02-26T12:55:00.442208Z", + updated_at: "2024-02-26T13:21:14.063955Z", + name: "ScanReport 1_20240226-125500_gdk4duld.xlsx", + dataset: "Test 3", + hidden: false, + file: null, + status: "UPCOMPL", + visibility: "PUBLIC", + author: 1, + data_dictionary: 11, + parent_dataset: 1, + viewers: [], + editors: [], + data_partner: datapartner.name, + dataset_name: dataset.name, + author_name: user.username, + }, + { + id: 9, + created_at: "2024-02-19T15:11:22.494235Z", + updated_at: "2024-02-26T12:53:57.446183Z", + name: "ScanReport 1_20240219-151122_9pk3w726.xlsx", + dataset: "ok", + hidden: false, + file: null, + status: "UPCOMPL", + visibility: "PUBLIC", + author: 1, + data_dictionary: 9, + parent_dataset: 1, + viewers: [], + editors: [], + data_partner: datapartner.name, + dataset_name: dataset.name, + author_name: user.username, + }, + { + id: 8, + created_at: "2024-02-19T15:10:41.625933Z", + updated_at: "2024-02-26T12:53:57.570027Z", + name: "ScanReport 1_20240219-151041_882paigl.xlsx", + dataset: "rgh", + hidden: false, + file: null, + status: "UPCOMPL", + visibility: "PUBLIC", + author: 1, + data_dictionary: 8, + parent_dataset: 1, + viewers: [], + editors: [1], + data_partner: datapartner.name, + dataset_name: dataset.name, + author_name: user.username, + }, + { + id: 15, + created_at: "2024-03-04T10:26:28.276062Z", + updated_at: "2024-03-04T10:26:31.689316Z", + name: "ScanReport 1_20240304-102628_gt7cgx5g.xlsx", + dataset: "New SR", + hidden: false, + file: null, + status: "UPCOMPL", + visibility: "PUBLIC", + author: 1, + data_dictionary: 15, + parent_dataset: 1, + viewers: [], + editors: [], + data_partner: datapartner.name, + dataset_name: dataset.name, + author_name: user.username, + }, + { + id: 10, + created_at: "2024-02-19T15:14:02.292426Z", + updated_at: "2024-02-26T12:53:57.358177Z", + name: "ScanReport 1_20240219-151402_sr2inrua.xlsx", + dataset: "tewetg", + hidden: false, + file: null, + status: "UPCOMPL", + visibility: "PUBLIC", + author: 1, + data_dictionary: 10, + parent_dataset: 1, + viewers: [], + editors: [], + data_partner: datapartner.name, + dataset_name: dataset.name, + author_name: user.username, + }, + { + id: 4, + created_at: "2024-02-19T14:56:55.645450Z", + updated_at: "2024-02-26T13:21:17.770341Z", + name: "ScanReport 1_20240219-145655_gcbikqfx.xlsx", + dataset: "Test 4", + hidden: false, + file: null, + status: "UPCOMPL", + visibility: "PUBLIC", + author: 1, + data_dictionary: 4, + parent_dataset: 1, + viewers: [], + editors: [1], + data_partner: datapartner.name, + dataset_name: dataset.name, + author_name: user.username, + }, + { + id: 14, + created_at: "2024-02-26T13:17:58.019263Z", + updated_at: "2024-02-26T13:17:59.292093Z", + name: "ScanReport 1_20240226-131758_nj9t5x4x.xlsx", + dataset: "Test 7", + hidden: false, + file: null, + status: "UPCOMPL", + visibility: "PUBLIC", + author: 1, + data_dictionary: 14, + parent_dataset: 1, + viewers: [], + editors: [], + data_partner: datapartner.name, + dataset_name: dataset.name, + author_name: user.username, + }, + { + id: 7, + created_at: "2024-02-19T15:05:56.907512Z", + updated_at: "2024-02-26T12:53:57.452807Z", + name: "ScanReport 1_20240219-150556_2b3dz3yk.xlsx", + dataset: "twest§", + hidden: false, + file: null, + status: "UPCOMPL", + visibility: "PUBLIC", + author: 1, + data_dictionary: 7, + parent_dataset: 1, + viewers: [], + editors: [], + data_partner: datapartner.name, + dataset_name: dataset.name, + author_name: user.username, + }, + { + id: 1, + created_at: "2024-02-19T13:23:58.887308Z", + updated_at: "2024-02-26T13:21:24.845321Z", + name: "ScanReport 1_20240219-132358_pzvqq404.xlsx", + dataset: "Test Scan Report", + hidden: false, + file: null, + status: "UPCOMPL", + visibility: "PUBLIC", + author: 1, + data_dictionary: 1, + parent_dataset: 1, + viewers: [], + editors: [], + data_partner: datapartner.name, + dataset_name: dataset.name, + author_name: user.username, + }, + { + id: 5, + created_at: "2024-02-19T14:57:48.553286Z", + updated_at: "2024-02-26T12:53:57.607800Z", + name: "ScanReport 1_20240219-145748_84otxrwb.xlsx", + dataset: "5", + hidden: false, + file: null, + status: "UPCOMPL", + visibility: "PUBLIC", + author: 1, + data_dictionary: 5, + parent_dataset: 1, + viewers: [], + editors: [1], + data_partner: datapartner.name, + dataset_name: dataset.name, + author_name: user.username, + }, + { + id: 2, + created_at: "2024-02-19T14:51:17.936852Z", + updated_at: "2024-02-26T13:21:21.722470Z", + name: "ScanReport 1_20240219-145117_is51ehx2.xlsx", + dataset: "Test 2", + hidden: false, + file: null, + status: "UPCOMPL", + visibility: "PUBLIC", + author: 1, + data_dictionary: 2, + parent_dataset: 1, + viewers: [], + editors: [], + data_partner: datapartner.name, + dataset_name: dataset.name, + author_name: user.username, + }, + { + id: 6, + created_at: "2024-02-19T15:05:13.820308Z", + updated_at: "2024-02-26T12:53:57.590286Z", + name: "ScanReport 1_20240219-150513_fmsf8do5.xlsx", + dataset: "test5", + hidden: false, + file: null, + status: "UPCOMPL", + visibility: "PUBLIC", + author: 1, + data_dictionary: 6, + parent_dataset: 1, + viewers: [], + editors: [], + data_partner: datapartner.name, + dataset_name: dataset.name, + author_name: user.username, + }, + { + id: 12, + created_at: "2024-02-26T13:02:01.854665Z", + updated_at: "2024-02-26T13:21:10.828365Z", + name: "ScanReport 1_20240226-130201_u6jczpjy.xlsx", + dataset: "Test 3", + hidden: false, + file: null, + status: "UPCOMPL", + visibility: "PUBLIC", + author: 1, + data_dictionary: 12, + parent_dataset: 1, + viewers: [], + editors: [], + data_partner: datapartner.name, + dataset_name: dataset.name, + author_name: user.username, + }, + { + id: 13, + created_at: "2024-02-26T13:16:47.367767Z", + updated_at: "2024-02-26T13:16:49.402517Z", + name: "ScanReport 1_20240226-131647_frl6t11y.xlsx", + dataset: "Test 6", + hidden: false, + file: null, + status: "UPCOMPL", + visibility: "PUBLIC", + author: 1, + data_dictionary: 13, + parent_dataset: 1, + viewers: [], + editors: [], + data_partner: datapartner.name, + dataset_name: dataset.name, + author_name: user.username, + }, + { + id: 3, + created_at: "2024-02-19T14:54:29.485808Z", + updated_at: "2024-02-26T13:21:20.063523Z", + name: "ScanReport 1_20240219-145429_yrq1vfl6.xlsx", + dataset: "Test 3", + hidden: false, + file: null, + status: "UPCOMPL", + visibility: "PUBLIC", + author: 1, + data_dictionary: 3, + parent_dataset: 1, + viewers: [], + editors: [], + data_partner: datapartner.name, + dataset_name: dataset.name, + author_name: user.username, + }, +]; diff --git a/app/next-client-app/api/error.ts b/app/next-client-app/api/error.ts new file mode 100644 index 000000000..567a97c23 --- /dev/null +++ b/app/next-client-app/api/error.ts @@ -0,0 +1,7 @@ +export class ApiError extends Error { + status: number; + constructor(message: string, status: number) { + super(message); + this.status = status; + } +} diff --git a/app/next-client-app/api/request.ts b/app/next-client-app/api/request.ts new file mode 100644 index 000000000..b00ac8061 --- /dev/null +++ b/app/next-client-app/api/request.ts @@ -0,0 +1,37 @@ +import { apiUrl as apiUrl } from "@/constants"; +import { ApiError } from "./error"; + +interface RequestOptions { + method?: string; + headers?: HeadersInit; + body?: BodyInit; + download?: boolean; + cache?: RequestCache; + next?: { revalidate: number }; +} + +const request = async (url: string, options: RequestOptions = {}) => { + const response = await fetch(`${apiUrl}/api/${url}`, { + method: options.method || "GET", + body: options.body, + cache: options.cache, + next: options.next, + }); + + if (!response.ok) { + const errorMessage = await response.text(); + throw new ApiError(errorMessage, response.status); + } + + if (options.download) { + return response.blob() as unknown as T; + } + + if (response.status === 204) { + return {} as T; + } + + return response.json(); +}; + +export default request; diff --git a/app/next-client-app/api/scan-reports.ts b/app/next-client-app/api/scan-reports.ts new file mode 100644 index 000000000..27f8a957d --- /dev/null +++ b/app/next-client-app/api/scan-reports.ts @@ -0,0 +1,16 @@ +import { scanReports } from "./data"; +import request from "./request"; + +const fetchKeys = { + list: "scanreports", +}; + +export async function getScanReports(): Promise { + try { + // return await request(fetchKeys.list); + return scanReports; + } catch (error) { + console.warn("Failed to fetch data."); + return []; + } +} diff --git a/app/next-client-app/app/globals.css b/app/next-client-app/app/globals.css new file mode 100644 index 000000000..b5c61c956 --- /dev/null +++ b/app/next-client-app/app/globals.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/app/next-client-app/app/layout.tsx b/app/next-client-app/app/layout.tsx new file mode 100644 index 000000000..7cb273e2d --- /dev/null +++ b/app/next-client-app/app/layout.tsx @@ -0,0 +1,22 @@ +import type { Metadata } from "next"; +import { Navbar } from "@/components/Navbar"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "CaRROT-Mapper", + description: "CaRROT-Mapper", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/app/next-client-app/app/page.tsx b/app/next-client-app/app/page.tsx new file mode 100644 index 000000000..aa5883279 --- /dev/null +++ b/app/next-client-app/app/page.tsx @@ -0,0 +1,3 @@ +export default function Home() { + return
Home
; +} diff --git a/app/next-client-app/app/scan-reports/columns.tsx b/app/next-client-app/app/scan-reports/columns.tsx new file mode 100644 index 000000000..99446bf0c --- /dev/null +++ b/app/next-client-app/app/scan-reports/columns.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { DataTableColumnHeader } from "@/components/data-table/DataTableColumnHeader"; +import { ColumnDef } from "@tanstack/react-table"; + +export const columns: ColumnDef[] = [ + { + accessorKey: "id", + header: ({ column }) => ( + + ), + enableHiding: false, + }, + { + accessorKey: "data_partner", + header: ({ column }) => ( + + ), + enableHiding: false, + }, + { + accessorKey: "dataset_name", + header: ({ column }) => ( + + ), + enableHiding: false, + }, + { + accessorKey: "dataset", + header: ({ column }) => ( + + ), + enableHiding: false, + }, + { + accessorKey: "author_name", + header: ({ column }) => ( + + ), + enableHiding: false, + }, + { + accessorKey: "created_at", + header: ({ column }) => ( + + ), + enableHiding: false, + enableSorting: true, + cell: ({ row }) => { + const date = new Date(row.original.created_at); + const options: any = { + month: "short", + day: "2-digit", + year: "numeric", + hour: "numeric", + minute: "numeric", + hour12: true, + }; + const formattedDate = date.toLocaleDateString("en-US", options); + return formattedDate; + }, + }, +]; diff --git a/app/next-client-app/app/scan-reports/page.tsx b/app/next-client-app/app/scan-reports/page.tsx new file mode 100644 index 000000000..c766b6525 --- /dev/null +++ b/app/next-client-app/app/scan-reports/page.tsx @@ -0,0 +1,53 @@ +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbSeparator, +} from "@/components/ui/breadcrumb"; +import { Button } from "@/components/ui/button"; +import { getScanReports } from "@/api/scan-reports"; +import { DataTable } from "@/components/data-table"; +import { columns } from "./columns"; + +export default async function ScanReports() { + const result = await getScanReports(); + + return ( +
+
+ + + + Home + + / + + Scan Reports + + + +
+
+

Scan Reports Active

+
+ + +
+
+ +
+ +
+
+ ); +} diff --git a/app/next-client-app/components.json b/app/next-client-app/components.json new file mode 100644 index 000000000..7a1c1770e --- /dev/null +++ b/app/next-client-app/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "slate", + "cssVariables": false, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} diff --git a/app/next-client-app/components/Navbar.tsx b/app/next-client-app/components/Navbar.tsx new file mode 100644 index 000000000..30416eaf8 --- /dev/null +++ b/app/next-client-app/components/Navbar.tsx @@ -0,0 +1,95 @@ +/* eslint-disable @next/next/no-img-element */ + +import React, { ReactNode } from "react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "./ui/dropdown-menu"; +import { ChevronDownIcon } from "@heroicons/react/24/solid"; + +const navItems = [ + { name: "Home", link: "/" }, + { name: "Datasets", link: "" }, + { + name: "Scan Reports", + options: [ + { name: "Scan Reports", link: "/scan-reports" }, + { name: "New Scan Report Upload", link: "" }, + ], + }, + { name: "Documentation", link: "" }, + { name: "Change Password", link: "" }, + { name: "Logout", link: "" }, +]; + +interface NavMenuProps { + options: { name: string; link: string }[]; + children: ReactNode; +} + +function NavMenu({ options, children }: NavMenuProps) { + return ( + + {children} + + {options.map((option, index) => ( + <> + + + {option.name} + + + {!(index >= options.length - 1) && } + + ))} + + + ); +} + +interface NavItemProps { + name: string; + link?: string; +} + +function NavItem({ name, link }: NavItemProps) { + return ( + + {name} + {name === "Scan Reports" && } + + ); +} + +export function Navbar() { + return ( +
+
+ + CaRROT + + {navItems.map((item, index) => ( + + {item.options ? ( + + + + ) : ( + + )} + + ))} +
+
+ ); +} diff --git a/app/next-client-app/components/data-table/DataTableColumnHeader.tsx b/app/next-client-app/components/data-table/DataTableColumnHeader.tsx new file mode 100644 index 000000000..e2a913768 --- /dev/null +++ b/app/next-client-app/components/data-table/DataTableColumnHeader.tsx @@ -0,0 +1,71 @@ +import { + ArrowDownIcon, + ArrowUpIcon, + CaretSortIcon, + EyeNoneIcon, +} from "@radix-ui/react-icons"; +import { Column } from "@tanstack/react-table"; + +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +interface DataTableColumnHeaderProps + extends React.HTMLAttributes { + column: Column; + title: string; +} + +export function DataTableColumnHeader({ + column, + title, + className, +}: DataTableColumnHeaderProps) { + if (!column.getCanSort()) { + return
{title}
; + } + + return ( +
+ + + + + + column.toggleSorting(false)}> + + Asc + + column.toggleSorting(true)}> + + Desc + + + column.toggleVisibility(false)}> + + Hide + + + +
+ ); +} diff --git a/app/next-client-app/components/data-table/DataTablePagination.tsx b/app/next-client-app/components/data-table/DataTablePagination.tsx new file mode 100644 index 000000000..d79d0fed7 --- /dev/null +++ b/app/next-client-app/components/data-table/DataTablePagination.tsx @@ -0,0 +1,97 @@ +import { + ChevronLeftIcon, + ChevronRightIcon, + DoubleArrowLeftIcon, + DoubleArrowRightIcon, +} from "@radix-ui/react-icons"; +import { Table } from "@tanstack/react-table"; + +import { Button } from "@/components/ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +interface DataTablePaginationProps { + table: Table; +} + +export function DataTablePagination({ + table, +}: DataTablePaginationProps) { + return ( +
+
+ {table.getFilteredSelectedRowModel().rows.length} of{" "} + {table.getFilteredRowModel().rows.length} row(s) selected. +
+
+
+

Rows per page

+ +
+
+ Page {table.getState().pagination.pageIndex + 1} of{" "} + {table.getPageCount()} +
+
+ + + + +
+
+
+ ); +} diff --git a/app/next-client-app/components/data-table/DataTableViewOptions.tsx b/app/next-client-app/components/data-table/DataTableViewOptions.tsx new file mode 100644 index 000000000..9134db38d --- /dev/null +++ b/app/next-client-app/components/data-table/DataTableViewOptions.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"; +import { MixerHorizontalIcon } from "@radix-ui/react-icons"; +import { Table } from "@tanstack/react-table"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuSeparator, +} from "@/components/ui/dropdown-menu"; + +interface DataTableViewOptionsProps { + table: Table; +} + +export function DataTableViewOptions({ + table, +}: DataTableViewOptionsProps) { + return ( + + + + + + Toggle columns + + {table + .getAllColumns() + .filter( + (column) => + typeof column.accessorFn !== "undefined" && column.getCanHide(), + ) + .map((column) => { + return ( + column.toggleVisibility(!!value)} + > + {column.id} + + ); + })} + + + ); +} diff --git a/app/next-client-app/components/data-table/index.tsx b/app/next-client-app/components/data-table/index.tsx new file mode 100644 index 000000000..af6bff5da --- /dev/null +++ b/app/next-client-app/components/data-table/index.tsx @@ -0,0 +1,175 @@ +"use client"; + +import { + ColumnDef, + ColumnFiltersState, + SortingState, + VisibilityState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; + +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import React from "react"; +import { Input } from "../ui/input"; +import { Button } from "../ui/button"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; +} + +export function DataTable({ + columns, + data, +}: DataTableProps) { + const [sorting, setSorting] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState( + [], + ); + const [columnVisibility, setColumnVisibility] = + React.useState({}); + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + onColumnFiltersChange: setColumnFilters, + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onSortingChange: setSorting, + getSortedRowModel: getSortedRowModel(), + onColumnVisibilityChange: setColumnVisibility, + state: { + sorting, + columnFilters, + columnVisibility, + }, + }); + + return ( +
+
+ + table.getColumn("email")?.setFilterValue(event.target.value) + } + className="max-w-sm" + /> + + + + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + + column.toggleVisibility(!!value) + } + > + {column.id} + + ); + })} + + +
+
+ + + {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/app/next-client-app/components/ui/breadcrumb.tsx b/app/next-client-app/components/ui/breadcrumb.tsx new file mode 100644 index 000000000..414f47452 --- /dev/null +++ b/app/next-client-app/components/ui/breadcrumb.tsx @@ -0,0 +1,118 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { ChevronRight, MoreHorizontal } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode; + } +>(({ ...props }, ref) =>