From ca3d076607eaa9291eb7c801a946ef9a4587dca2 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 7 Aug 2024 13:56:36 +0200 Subject: [PATCH 1/4] feat: add Pagination component and DataTableColumnHeader component --- components/providers/index.ts | 2 + .../providers/table/DataTableColumnHeader.tsx | 49 ++++ .../providers/table/DataTablePagination.tsx | 66 +++++ .../providers/table/DataTableProvider.tsx | 31 ++- components/ui/index.ts | 1 + components/ui/select/Select.tsx | 164 +++++++++++++ package-lock.json | 226 ++++++++++++++++++ package.json | 1 + 8 files changed, 523 insertions(+), 17 deletions(-) create mode 100644 components/providers/table/DataTableColumnHeader.tsx create mode 100644 components/providers/table/DataTablePagination.tsx create mode 100644 components/ui/select/Select.tsx diff --git a/components/providers/index.ts b/components/providers/index.ts index 00bd2634c5..0b4074236c 100644 --- a/components/providers/index.ts +++ b/components/providers/index.ts @@ -10,5 +10,7 @@ export * from "./DeleteProvider"; export * from "./ProviderInfo"; export * from "./ScanStatus"; export * from "./table/ColumnsProvider"; +export * from "./table/DataTableColumnHeader"; +export * from "./table/DataTablePagination"; export * from "./table/DataTableProvider"; export * from "./table/SkeletonTableProvider"; diff --git a/components/providers/table/DataTableColumnHeader.tsx b/components/providers/table/DataTableColumnHeader.tsx new file mode 100644 index 0000000000..7fe6027527 --- /dev/null +++ b/components/providers/table/DataTableColumnHeader.tsx @@ -0,0 +1,49 @@ +import { Button } from "@nextui-org/react"; +import { Column } from "@tanstack/react-table"; +import { + ArrowDownIcon, + ArrowUpIcon, + ChevronsLeftRightIcon, +} from "lucide-react"; +import { HTMLAttributes } from "react"; + +interface DataTableColumnHeaderProps + extends HTMLAttributes { + column: Column; + title: string; +} + +export const DataTableColumnHeader = ({ + column, + title, + className, +}: DataTableColumnHeaderProps) => { + const renderSortIcon = () => { + const sort = column.getIsSorted(); + if (!sort) { + return ; + } + return sort === "desc" ? ( + + ) : ( + + ); + }; + + if (!column.getCanSort()) { + return
{title}
; + } + return ( +
+ +
+ ); +}; diff --git a/components/providers/table/DataTablePagination.tsx b/components/providers/table/DataTablePagination.tsx new file mode 100644 index 0000000000..6f31f826e6 --- /dev/null +++ b/components/providers/table/DataTablePagination.tsx @@ -0,0 +1,66 @@ +import { Button } from "@nextui-org/react"; +import { + ChevronLeftIcon, + ChevronRightIcon, + DoubleArrowLeftIcon, + DoubleArrowRightIcon, +} from "@radix-ui/react-icons"; +import { type Table } from "@tanstack/react-table"; + +interface DataTablePaginationProps { + table: Table; + pageSizeOptions?: number[]; +} + +export function DataTablePagination({ + table, +}: DataTablePaginationProps) { + return ( +
+
+
+ Page {table.getState().pagination.pageIndex + 1} of{" "} + {table.getPageCount()} +
+
+ + + + +
+
+
+ ); +} diff --git a/components/providers/table/DataTableProvider.tsx b/components/providers/table/DataTableProvider.tsx index a24202685e..a6ddca5d65 100644 --- a/components/providers/table/DataTableProvider.tsx +++ b/components/providers/table/DataTableProvider.tsx @@ -1,13 +1,15 @@ "use client"; -import { Button } from "@nextui-org/react"; import { ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, + getSortedRowModel, + SortingState, useReactTable, } from "@tanstack/react-table"; +import { useState } from "react"; import { Table, @@ -18,6 +20,8 @@ import { TableRow, } from "@/components/ui/table/Table"; +import { DataTablePagination } from "./DataTablePagination"; + interface DataTableProps { columns: ColumnDef[]; data: TData[]; @@ -27,11 +31,19 @@ export function DataTableProvider({ columns, data, }: DataTableProps) { + const [sorting, setSorting] = useState([]); + const table = useReactTable({ data, columns, + enableSorting: true, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), + onSortingChange: setSorting, + getSortedRowModel: getSortedRowModel(), + state: { + sorting, + }, }); return ( @@ -87,22 +99,7 @@ export function DataTableProvider({
- - +
); diff --git a/components/ui/index.ts b/components/ui/index.ts index 96064508d6..f202f8ffe5 100644 --- a/components/ui/index.ts +++ b/components/ui/index.ts @@ -1,6 +1,7 @@ export * from "./alert/Alert"; export * from "./dialog/Dialog"; export * from "./header/Header"; +export * from "./select/Select"; export * from "./sidebar"; export * from "./table/StatusBadge"; export * from "./table/Table"; diff --git a/components/ui/select/Select.tsx b/components/ui/select/Select.tsx new file mode 100644 index 0000000000..64b4d471c5 --- /dev/null +++ b/components/ui/select/Select.tsx @@ -0,0 +1,164 @@ +"use client"; + +import { + CaretSortIcon, + CheckIcon, + ChevronDownIcon, + ChevronUpIcon, +} from "@radix-ui/react-icons"; +import * as SelectPrimitive from "@radix-ui/react-select"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Select = SelectPrimitive.Root; + +const SelectGroup = SelectPrimitive.Group; + +const SelectValue = SelectPrimitive.Value; + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1 dark:border-slate-800 dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus:ring-slate-300", + className, + )} + {...props} + > + {children} + + + + +)); +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName; + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)); +SelectContent.displayName = SelectPrimitive.Content.displayName; + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectLabel.displayName = SelectPrimitive.Label.displayName; + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +SelectItem.displayName = SelectPrimitive.Item.displayName; + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectSeparator.displayName = SelectPrimitive.Separator.displayName; + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +}; diff --git a/package-lock.json b/package-lock.json index 7b8757dbd2..3ef31ce458 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@nextui-org/theme": "2.2.5", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-toast": "^1.2.1", "@react-aria/ssr": "3.9.4", "@react-aria/visually-hidden": "3.8.12", @@ -649,6 +650,40 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.6.tgz", + "integrity": "sha512-Vkvsw6EcpMHjvZZdMkSY+djMGFbt7CRssW99Ne8tar2WLnZ/l3dbxeTShbLQj+/s35h+Qb4cmnob+EzwtjrXGQ==", + "dependencies": { + "@floating-ui/utils": "^0.2.6" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.9.tgz", + "integrity": "sha512-zB1PcI350t4tkm3rvUhSRKa9sT7vH5CrAbQxW+VaPYJXKAO0gsg4CTueL+6Ajp7XzAQC8CW4Jj1Wgqc0sB6oUQ==", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.6" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz", + "integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.6.tgz", + "integrity": "sha512-0KI3zGxIUs1KDR/pjQPdJH4Z8nGBm0yJ5WRoRfdw1Kzeh45jkIfA0rmD0kBF6fKHH+xaH7g8y4jIXyAV5MGK3g==" + }, "node_modules/@formatjs/ecma402-abstract": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz", @@ -2592,11 +2627,38 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", + "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", + "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", @@ -2709,6 +2771,20 @@ } } }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-dismissable-layer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz", @@ -2798,6 +2874,37 @@ } } }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", + "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-portal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", @@ -2866,6 +2973,72 @@ } } }, + "node_modules/@radix-ui/react-select": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.1.tgz", + "integrity": "sha512-8iRDfyLtzxlprOo9IicnzvpsO1wNCkuwzzCM+Z5Rb5tNOpCdMvcc2AkzX0Fz+Tz9v6NJ5B/7EEgyZveo4FBRfQ==", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/react-remove-scroll": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", + "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.4", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", @@ -2978,6 +3151,54 @@ } } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", + "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", + "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", + "dependencies": { + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", + "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-visually-hidden": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz", @@ -3000,6 +3221,11 @@ } } }, + "node_modules/@radix-ui/rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", + "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==" + }, "node_modules/@react-aria/breadcrumbs": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.13.tgz", diff --git a/package.json b/package.json index 7795424b1a..2c9b02c073 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "@nextui-org/theme": "2.2.5", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-toast": "^1.2.1", "@react-aria/ssr": "3.9.4", "@react-aria/visually-hidden": "3.8.12", From bed2b1e7f789285fe2a05b5d68e3ee2b544151be Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 8 Aug 2024 20:08:11 +0200 Subject: [PATCH 2/4] feat: add Pagination - WIP --- actions/providers.ts | 29 ++++-- app/(prowler)/providers/page.tsx | 46 +++++++-- components/providers/AddProviderModal.tsx | 4 +- .../providers/table/DataTablePagination.tsx | 1 + .../providers/table/DataTableProvider.tsx | 1 - components/ui/index.ts | 1 + components/ui/pagination/Pagination.tsx | 95 +++++++++++++++++++ 7 files changed, 161 insertions(+), 16 deletions(-) create mode 100644 components/ui/pagination/Pagination.tsx diff --git a/actions/providers.ts b/actions/providers.ts index 30ac54ff5b..34471c0b1f 100644 --- a/actions/providers.ts +++ b/actions/providers.ts @@ -4,19 +4,36 @@ import { revalidatePath } from "next/cache"; import { parseStringify } from "@/lib"; -export const getProvider = async () => { +interface PaginationOptions { + page?: number; +} + +export const getProvider = async ({ page = 1 }: PaginationOptions) => { + if (isNaN(Number(page))) page = 1; + if (page < 1) page = 1; + const keyServer = process.env.LOCAL_SERVER_URL; try { - const providers = await fetch(`${keyServer}/providers`, { - headers: { - "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, + const providers = await fetch( + `${keyServer}/providers?page%5Bnumber%5D=${page}`, + { + headers: { + "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, + }, }, - }); + ); const data = await providers.json(); + const parsedData = parseStringify(data); revalidatePath("/providers"); - return parseStringify(data); + return { + providerDetails: parsedData?.data, + currentPage: parsedData?.meta?.pagination?.page, + totalPages: parsedData?.meta?.pagination?.pages, + totalItems: parsedData?.meta?.pagination?.count, + }; } catch (error) { + console.error("Error fetching providers:", error); return undefined; } }; diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index c58d57db4a..f429bb2acc 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -1,4 +1,5 @@ import { Spacer } from "@nextui-org/react"; +import { redirect } from "next/navigation"; import { Suspense } from "react"; import { getProvider } from "@/actions"; @@ -8,9 +9,16 @@ import { DataTableProvider, SkeletonTableProvider, } from "@/components/providers"; -import { Header } from "@/components/ui"; +import { Header, Pagination } from "@/components/ui"; -export default async function Providers() { +export default async function Providers({ + searchParams, +}: { + searchParams: { + page: number; + }; +}) { + console.log({ searchParams }, "los searchParamsss!"); return ( <>
@@ -20,18 +28,40 @@ export default async function Providers() { - }> - + }> + ); } -const SSRDataTable = async () => { - const providersData = await getProvider(); - const [providers] = await Promise.all([providersData]); +const SSRDataTable = async ({ + searchParams, +}: { + searchParams: { + page: number; + }; +}) => { + // const perPage = searchParams['per_page'] ?? '5' + // const page = searchParams.page ? parseInt(searchParams.page) : 1; + const providersData = await getProvider({}); + // const [providers] = await Promise.all([providersData]); + // const { providerDetails, currentPage, totalPages, totalItems } = providersData; + // console.log(currentPage, totalPages, 'hehe') + // if (providers.meta.pagination.count === 0) { + // redirect('/'); + // } + // console.log(providers); + // const currentPage = providers.meta.pagination.page; + // const pages = providers.meta.pagination.pages; + // const count = providers.meta.pagination.count; + + // console.log(`Pages: ${pages}, Count: ${count}`); return ( - + <> + {/* */} + {/* */} + ); }; diff --git a/components/providers/AddProviderModal.tsx b/components/providers/AddProviderModal.tsx index a04c19c7bb..b9facbe627 100644 --- a/components/providers/AddProviderModal.tsx +++ b/components/providers/AddProviderModal.tsx @@ -48,7 +48,9 @@ export const AddProviderModal = () => { return ( - + diff --git a/components/providers/table/DataTablePagination.tsx b/components/providers/table/DataTablePagination.tsx index 6f31f826e6..4fe18c76dd 100644 --- a/components/providers/table/DataTablePagination.tsx +++ b/components/providers/table/DataTablePagination.tsx @@ -15,6 +15,7 @@ interface DataTablePaginationProps { export function DataTablePagination({ table, }: DataTablePaginationProps) { + // console.log(table); return (
diff --git a/components/providers/table/DataTableProvider.tsx b/components/providers/table/DataTableProvider.tsx index a6ddca5d65..522539d9e6 100644 --- a/components/providers/table/DataTableProvider.tsx +++ b/components/providers/table/DataTableProvider.tsx @@ -45,7 +45,6 @@ export function DataTableProvider({ sorting, }, }); - return ( <>
diff --git a/components/ui/index.ts b/components/ui/index.ts index f202f8ffe5..d2267337a5 100644 --- a/components/ui/index.ts +++ b/components/ui/index.ts @@ -1,6 +1,7 @@ export * from "./alert/Alert"; export * from "./dialog/Dialog"; export * from "./header/Header"; +export * from "./pagination/Pagination"; export * from "./select/Select"; export * from "./sidebar"; export * from "./table/StatusBadge"; diff --git a/components/ui/pagination/Pagination.tsx b/components/ui/pagination/Pagination.tsx new file mode 100644 index 0000000000..e490942398 --- /dev/null +++ b/components/ui/pagination/Pagination.tsx @@ -0,0 +1,95 @@ +"use client"; + +import Link from "next/link"; +import { usePathname, useSearchParams } from "next/navigation"; +import React from "react"; + +interface Props { + totalPages: number; + currentPage: number; + nextPage?: string; +} + +export const Pagination = ({ totalPages, currentPage }: Props) => { + const pathname = usePathname(); + const searchParams = useSearchParams(); + // const currentPage = searchParams["page"] ?? "1"; + const createPageUrl = (pageNumber: number | string) => { + const params = new URLSearchParams(searchParams); + + if (pageNumber === "...") return `${pathname}?${params.toString()}`; + + if (+pageNumber <= 0) { + return `${pathname}`; + } + if (+pageNumber > totalPages) { + return `${pathname}?${params.toString()}`; + } + params.set("page", pageNumber.toString()); + return `${pathname}?${params.toString()}`; + }; + + console.log(pathname, searchParams, currentPage); + + return ( +
+ +
+ ); +}; From 5bb3c012c9deb5d7e69ce7ed6ce8d66dea61263e Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 9 Aug 2024 11:54:58 +0200 Subject: [PATCH 3/4] feat: add functionality to the Pagination component --- actions/providers.ts | 17 +--- app/(prowler)/providers/page.tsx | 48 +++------- .../providers/table/DataTablePagination.tsx | 86 ++++++++++------- .../providers/table/DataTableProvider.tsx | 8 +- components/ui/index.ts | 1 - components/ui/pagination/Pagination.tsx | 95 ------------------- lib/utils.ts | 10 ++ types/components.ts | 15 +++ 8 files changed, 100 insertions(+), 180 deletions(-) delete mode 100644 components/ui/pagination/Pagination.tsx diff --git a/actions/providers.ts b/actions/providers.ts index 34471c0b1f..6c6f3adee7 100644 --- a/actions/providers.ts +++ b/actions/providers.ts @@ -1,16 +1,12 @@ "use server"; import { revalidatePath } from "next/cache"; +import { redirect } from "next/navigation"; import { parseStringify } from "@/lib"; -interface PaginationOptions { - page?: number; -} - -export const getProvider = async ({ page = 1 }: PaginationOptions) => { - if (isNaN(Number(page))) page = 1; - if (page < 1) page = 1; +export const getProvider = async ({ page = 1 }) => { + if (isNaN(Number(page)) || page < 1) redirect("/providers"); const keyServer = process.env.LOCAL_SERVER_URL; @@ -26,12 +22,7 @@ export const getProvider = async ({ page = 1 }: PaginationOptions) => { const data = await providers.json(); const parsedData = parseStringify(data); revalidatePath("/providers"); - return { - providerDetails: parsedData?.data, - currentPage: parsedData?.meta?.pagination?.page, - totalPages: parsedData?.meta?.pagination?.pages, - totalItems: parsedData?.meta?.pagination?.count, - }; + return parsedData; } catch (error) { console.error("Error fetching providers:", error); return undefined; diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index f429bb2acc..2c79e94c79 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -9,16 +9,10 @@ import { DataTableProvider, SkeletonTableProvider, } from "@/components/providers"; -import { Header, Pagination } from "@/components/ui"; +import { Header } from "@/components/ui"; +import { searchParamsProps } from "@/types"; -export default async function Providers({ - searchParams, -}: { - searchParams: { - page: number; - }; -}) { - console.log({ searchParams }, "los searchParamsss!"); +export default async function Providers({ searchParams }: searchParamsProps) { return ( <>
@@ -36,32 +30,18 @@ export default async function Providers({ ); } -const SSRDataTable = async ({ - searchParams, -}: { - searchParams: { - page: number; - }; -}) => { - // const perPage = searchParams['per_page'] ?? '5' - // const page = searchParams.page ? parseInt(searchParams.page) : 1; - const providersData = await getProvider({}); - // const [providers] = await Promise.all([providersData]); - // const { providerDetails, currentPage, totalPages, totalItems } = providersData; - // console.log(currentPage, totalPages, 'hehe') - // if (providers.meta.pagination.count === 0) { - // redirect('/'); - // } - // console.log(providers); - // const currentPage = providers.meta.pagination.page; - // const pages = providers.meta.pagination.pages; - // const count = providers.meta.pagination.count; +const SSRDataTable = async ({ searchParams }: searchParamsProps) => { + const page = searchParams.page ? parseInt(searchParams.page) : 1; + const providersData = await getProvider({ page }); + const [providers] = await Promise.all([providersData]); + + if (providers?.errors) redirect("/providers"); - // console.log(`Pages: ${pages}, Count: ${count}`); return ( - <> - {/* */} - {/* */} - + ); }; diff --git a/components/providers/table/DataTablePagination.tsx b/components/providers/table/DataTablePagination.tsx index 4fe18c76dd..83fdd43be3 100644 --- a/components/providers/table/DataTablePagination.tsx +++ b/components/providers/table/DataTablePagination.tsx @@ -1,65 +1,83 @@ -import { Button } from "@nextui-org/react"; +"use client"; + import { ChevronLeftIcon, ChevronRightIcon, DoubleArrowLeftIcon, DoubleArrowRightIcon, } from "@radix-ui/react-icons"; -import { type Table } from "@tanstack/react-table"; +import Link from "next/link"; +import { redirect, usePathname, useSearchParams } from "next/navigation"; -interface DataTablePaginationProps { - table: Table; +import { extractPaginationInfo } from "@/lib"; +import { MetaDataProps } from "@/types"; + +interface DataTablePaginationProps { pageSizeOptions?: number[]; + metadata?: MetaDataProps; } -export function DataTablePagination({ - table, -}: DataTablePaginationProps) { - // console.log(table); +export function DataTablePagination({ metadata }: DataTablePaginationProps) { + if (!metadata) return null; + const pathname = usePathname(); + const searchParams = useSearchParams(); + + const { currentPage, totalPages, totalEntries } = + extractPaginationInfo(metadata); + + const createPageUrl = (pageNumber: number | string) => { + const params = new URLSearchParams(searchParams); + + if (pageNumber === "...") return `${pathname}?${params.toString()}`; + + if (+pageNumber > totalPages) { + return `${pathname}?${params.toString()}`; + } + + params.set("page", pageNumber.toString()); + return `${pathname}?${params.toString()}`; + }; + return (
+
+ {totalEntries} entries in Total. +
- Page {table.getState().pagination.pageIndex + 1} of{" "} - {table.getPageCount()} + Page {currentPage} of {totalPages}
- - - - +
diff --git a/components/providers/table/DataTableProvider.tsx b/components/providers/table/DataTableProvider.tsx index 522539d9e6..cfbb095e57 100644 --- a/components/providers/table/DataTableProvider.tsx +++ b/components/providers/table/DataTableProvider.tsx @@ -19,20 +19,22 @@ import { TableHeader, TableRow, } from "@/components/ui/table/Table"; +import { MetaDataProps } from "@/types"; import { DataTablePagination } from "./DataTablePagination"; interface DataTableProps { columns: ColumnDef[]; data: TData[]; + metadata?: MetaDataProps; } export function DataTableProvider({ columns, data, + metadata, }: DataTableProps) { const [sorting, setSorting] = useState([]); - const table = useReactTable({ data, columns, @@ -97,8 +99,8 @@ export function DataTableProvider({
-
- +
+
); diff --git a/components/ui/index.ts b/components/ui/index.ts index d2267337a5..f202f8ffe5 100644 --- a/components/ui/index.ts +++ b/components/ui/index.ts @@ -1,7 +1,6 @@ export * from "./alert/Alert"; export * from "./dialog/Dialog"; export * from "./header/Header"; -export * from "./pagination/Pagination"; export * from "./select/Select"; export * from "./sidebar"; export * from "./table/StatusBadge"; diff --git a/components/ui/pagination/Pagination.tsx b/components/ui/pagination/Pagination.tsx deleted file mode 100644 index e490942398..0000000000 --- a/components/ui/pagination/Pagination.tsx +++ /dev/null @@ -1,95 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { usePathname, useSearchParams } from "next/navigation"; -import React from "react"; - -interface Props { - totalPages: number; - currentPage: number; - nextPage?: string; -} - -export const Pagination = ({ totalPages, currentPage }: Props) => { - const pathname = usePathname(); - const searchParams = useSearchParams(); - // const currentPage = searchParams["page"] ?? "1"; - const createPageUrl = (pageNumber: number | string) => { - const params = new URLSearchParams(searchParams); - - if (pageNumber === "...") return `${pathname}?${params.toString()}`; - - if (+pageNumber <= 0) { - return `${pathname}`; - } - if (+pageNumber > totalPages) { - return `${pathname}?${params.toString()}`; - } - params.set("page", pageNumber.toString()); - return `${pathname}?${params.toString()}`; - }; - - console.log(pathname, searchParams, currentPage); - - return ( -
- -
- ); -}; diff --git a/lib/utils.ts b/lib/utils.ts index c3e96dd8df..84a5fe24e3 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,6 +1,8 @@ import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; +import { MetaDataProps } from "@/types"; + export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } @@ -9,6 +11,14 @@ export const parseStringify = (value: any) => JSON.parse(JSON.stringify(value)); export const convertFileToUrl = (file: File) => URL.createObjectURL(file); +export const extractPaginationInfo = (metadata: MetaDataProps) => { + const currentPage = metadata?.pagination.page ?? "1"; + const totalPages = metadata?.pagination.pages; + const totalEntries = metadata?.pagination.count; + + return { currentPage, totalPages, totalEntries }; +}; + // FORMAT DATE TIME export const formatDateTime = ( dateString: Date | string, diff --git a/types/components.ts b/types/components.ts index b7b53724e5..1d23c712a9 100644 --- a/types/components.ts +++ b/types/components.ts @@ -9,6 +9,12 @@ export type IconProps = { style?: React.CSSProperties; }; +export interface searchParamsProps { + searchParams: { + page?: string; + }; +} + export interface ProviderProps { id: string; type: "providers"; @@ -47,3 +53,12 @@ export interface FindingsProps { account: string; }; } + +export interface MetaDataProps { + pagination: { + page: number; + pages: number; + count: number; + }; + version: string; +} From 10fc131e13f51c14bd351bdbae0ee652f3803dd2 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 9 Aug 2024 13:06:09 +0200 Subject: [PATCH 4/4] feat: remove dependency --- components/providers/table/DataTablePagination.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/providers/table/DataTablePagination.tsx b/components/providers/table/DataTablePagination.tsx index 83fdd43be3..0e195f0507 100644 --- a/components/providers/table/DataTablePagination.tsx +++ b/components/providers/table/DataTablePagination.tsx @@ -7,7 +7,7 @@ import { DoubleArrowRightIcon, } from "@radix-ui/react-icons"; import Link from "next/link"; -import { redirect, usePathname, useSearchParams } from "next/navigation"; +import { usePathname, useSearchParams } from "next/navigation"; import { extractPaginationInfo } from "@/lib"; import { MetaDataProps } from "@/types";