From f8ff05357d71e6a604a8e0d7008894a2296a2394 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Tue, 23 Sep 2025 09:07:32 +0330 Subject: [PATCH] product list --- package-lock.json | 1 + package.json | 1 + src/components/PaginationUi.tsx | 89 ++++++++++++ src/components/ui/button.tsx | 58 ++++++++ src/components/ui/pagination.tsx | 127 ++++++++++++++++++ src/pages/products/List.tsx | 134 ++++++++++++++++++- src/pages/products/enum/ProductEnum.ts | 28 ++++ src/pages/products/hooks/useProductData.ts | 12 +- src/pages/products/service/ProductService.ts | 13 +- src/pages/products/types/Types.ts | 93 +++++++++++++ 10 files changed, 547 insertions(+), 9 deletions(-) create mode 100644 src/components/PaginationUi.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/pagination.tsx create mode 100644 src/pages/products/enum/ProductEnum.ts diff --git a/package-lock.json b/package-lock.json index 1d40d6d..cf2afb3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@headlessui/react": "^2.2.7", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-slot": "^1.2.3", "@tailwindcss/vite": "^4.1.12", "@tanstack/react-query": "^5.85.5", "axios": "^1.11.0", diff --git a/package.json b/package.json index f75b320..7dd4730 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@headlessui/react": "^2.2.7", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-slot": "^1.2.3", "@tailwindcss/vite": "^4.1.12", "@tanstack/react-query": "^5.85.5", "axios": "^1.11.0", diff --git a/src/components/PaginationUi.tsx b/src/components/PaginationUi.tsx new file mode 100644 index 0000000..71b7655 --- /dev/null +++ b/src/components/PaginationUi.tsx @@ -0,0 +1,89 @@ +import { type FC } from 'react'; +import { + Pagination, + PaginationContent, + PaginationItem, + PaginationLink, + PaginationEllipsis, +} from './ui/pagination'; +import { type PagerType } from '../pages/products/types/Types'; + +interface PaginationUiProps { + pager: PagerType | null | undefined; + onPageChange: (page: number) => void; +} + +const PaginationUi: FC = ({ pager, onPageChange }) => { + const handlePageChange = (page: number) => { + onPageChange(page); + }; + + const getPageNumbers = () => { + if (!pager) return []; + + const pageNumbers: (number | 'ellipsis')[] = []; + const maxVisiblePages = 5; + + if (pager.totalPages <= maxVisiblePages) { + for (let i = 1; i <= pager.totalPages; i++) { + pageNumbers.push(i); + } + } else { + if (pager.page > 3) { + pageNumbers.push(1); + if (pager.page > 4) { + pageNumbers.push('ellipsis'); + } + } + + const start = Math.max(2, pager.page - 1); + const end = Math.min(pager.totalPages - 1, pager.page + 1); + + for (let i = start; i <= end; i++) { + pageNumbers.push(i); + } + + if (pager.page < pager.totalPages - 2) { + if (pager.page < pager.totalPages - 3) { + pageNumbers.push('ellipsis'); + } + pageNumbers.push(pager.totalPages); + } + } + + return pageNumbers; + }; + + if (!pager || pager.totalPages <= 1) { + return null; + } + + return ( +
+ + + {getPageNumbers().map((page, index) => ( + + {page === 'ellipsis' ? ( + + ) : ( + { + e.preventDefault(); + handlePageChange(page); + }} + > + {page} + + )} + + ))} + + +
+ ); +}; + +export default PaginationUi; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..d96719c --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,58 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +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", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Button({ + className, + variant, + size, + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot : "button" + + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx new file mode 100644 index 0000000..0d18541 --- /dev/null +++ b/src/components/ui/pagination.tsx @@ -0,0 +1,127 @@ +import * as React from "react" +import { + ChevronLeftIcon, + ChevronRightIcon, + MoreHorizontalIcon, +} from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button, buttonVariants } from "@/components/ui/button" + +function Pagination({ className, ...props }: React.ComponentProps<"nav">) { + return ( +