infiniti scroll product list

This commit is contained in:
hamid zarghami
2025-09-06 15:09:58 +03:30
parent 0f77a3208d
commit 5ec2b621bc
5 changed files with 122 additions and 19 deletions
+46 -19
View File
@@ -12,12 +12,12 @@ import {
} from '@/components/ui/breadcrumb'
import Filters from '../components/Filters'
import Sorts from '../components/Sorts'
import Pagination from '../components/Pagination'
import GridWrapper from '@/components/GridWrapper'
import ProductCard from '@/components/ProductCard'
import { Setting5 } from 'iconsax-react'
import { useState, useEffect } from 'react'
import { useCategoryProductsData } from '../hooks/useProductsData'
import { useState, useEffect, useMemo } from 'react'
import InfiniteScroll from 'react-infinite-scroll-component'
import { useInfiniteCategoryProductsData } from '../hooks/useInfiniteProductsData'
import { IProduct } from '@/types/landing.types'
import { Product } from '@/types/products.types'
import { useSearchParams, useParams } from 'next/navigation'
@@ -55,8 +55,20 @@ const Products: NextPage = () => {
setFilterParams(params)
}, [searchParams])
// دریافت داده‌های محصولات از API بر اساس categoryUrl
const { data, isLoading, error } = useCategoryProductsData(categoryUrl, filterParams)
// دریافت داده‌های محصولات از API بر اساس categoryUrl با infinite scroll
const {
data,
isLoading,
error,
fetchNextPage,
hasNextPage
} = useInfiniteCategoryProductsData(categoryUrl, filterParams)
// جمع‌آوری تمام محصولات از صفحات مختلف
const allProducts = useMemo(() => {
if (!data?.pages) return [];
return data.pages.flatMap(page => page.results.products);
}, [data?.pages]);
// تبدیل Product به IProduct
const convertToIProduct = (product: Product): IProduct => {
@@ -91,6 +103,7 @@ const Products: NextPage = () => {
status: defaultVariant?.market_status || 'active',
imagesUrl: product.imagesUrl,
url: product.url,
isWishlist: false,
default_variant: {
_id: defaultVariant?._id || product._id.toString(),
market_status: defaultVariant?.market_status || 'active',
@@ -117,11 +130,11 @@ const Products: NextPage = () => {
<BreadcrumbItem>
<BreadcrumbLink href="/">فروشگاه آناهیتا</BreadcrumbLink>
</BreadcrumbItem>
{data?.results.breadcrumb?.map((item, index) => (
{data?.pages?.[0]?.results.breadcrumb?.map((item, index) => (
<div key={item._id} className="flex items-center">
<BreadcrumbSeparator className="text-muted-foreground select-none">/</BreadcrumbSeparator>
<BreadcrumbItem>
{index === data.results.breadcrumb.length - 1 ? (
{index === (data?.pages?.[0]?.results.breadcrumb?.length || 0) - 1 ? (
<BreadcrumbPage className="text-foreground">
{item.title_fa}
</BreadcrumbPage>
@@ -160,7 +173,7 @@ const Products: NextPage = () => {
/>
<div className='flex-1'>
<Sorts data={data} />
<Sorts data={data?.pages?.[0]} />
<div className='mt-6 sm:mt-8 md:mt-10'>
{isLoading ? (
@@ -171,15 +184,31 @@ const Products: NextPage = () => {
<div className="text-center py-8">
<div className="text-red-500">خطا در بارگذاری محصولات</div>
</div>
) : data?.results.products ? (
<GridWrapper desktop={4} mobile={2} gapMobile={3} gapDesktop={6}>
{data.results.products.map((product) => (
<ProductCard
key={product._id}
item={convertToIProduct(product)}
/>
))}
</GridWrapper>
) : allProducts.length > 0 ? (
<InfiniteScroll
dataLength={allProducts.length}
next={fetchNextPage}
hasMore={!!hasNextPage}
loader={
<div className="text-center py-4">
<div className="text-gray-500">در حال بارگذاری محصولات بیشتر...</div>
</div>
}
endMessage={
<div className="text-center py-4">
{/* <div className="text-gray-500">تمام محصولات بارگذاری شد</div> */}
</div>
}
>
<GridWrapper desktop={4} mobile={2} gapMobile={3} gapDesktop={6}>
{allProducts.map((product) => (
<ProductCard
key={product._id}
item={convertToIProduct(product)}
/>
))}
</GridWrapper>
</InfiniteScroll>
) : (
<div className="text-center py-8">
<div className="text-gray-500">محصولی یافت نشد</div>
@@ -187,8 +216,6 @@ const Products: NextPage = () => {
)}
</div>
{/* Pagination */}
<Pagination pager={data?.results?.pager} />
</div>
</div>
</div>
@@ -0,0 +1,50 @@
import { useInfiniteQuery } from "@tanstack/react-query";
import * as api from "../service/Service";
import { ProductsResponse } from "@/types/products.types";
interface UseInfiniteProductsDataParams {
page?: number;
limit?: number;
category?: string;
brand?: string;
minPrice?: number;
maxPrice?: number;
attributes?: string[];
sizes?: number[];
meterages?: number[];
sort?: string;
wholeSale?: string;
stock?: string;
rating?: number;
}
export const useInfiniteProductsData = (
params?: UseInfiniteProductsDataParams
) => {
return useInfiniteQuery<ProductsResponse>({
queryKey: ["infinite-products", params],
queryFn: ({ pageParam = 1 }) =>
api.getProducts({ ...params, page: pageParam }),
getNextPageParam: (lastPage) => {
const pager = lastPage.results.pager;
return pager.nextPage ? pager.page + 1 : undefined;
},
initialPageParam: 1,
});
};
export const useInfiniteCategoryProductsData = (
categoryUrl: string,
params?: UseInfiniteProductsDataParams
) => {
return useInfiniteQuery<ProductsResponse>({
queryKey: ["infinite-category-products", categoryUrl, params],
queryFn: ({ pageParam = 1 }) =>
api.getCategoryProducts(categoryUrl, { ...params, page: pageParam }),
getNextPageParam: (lastPage) => {
const pager = lastPage.results.pager;
return pager.nextPage ? pager.page + 1 : undefined;
},
initialPageParam: 1,
});
};
+3
View File
@@ -4,6 +4,7 @@ import { Heart } from 'iconsax-react'
import Image from 'next/image'
import { FC, useState } from 'react'
import { useAddWishlist, useRemoveWishlist } from '@/app/products/hooks/useProductsData'
import { useSharedStore } from '@/share/store/sharedStore'
type Props = {
item?: IProduct
@@ -12,6 +13,7 @@ type Props = {
const ProductCard: FC<Props> = ({ item }) => {
const { isLogin } = useSharedStore()
const [isWished, setIsWished] = useState(item?.isWishlist)
const { mutate: addWishlist } = useAddWishlist()
@@ -19,6 +21,7 @@ const ProductCard: FC<Props> = ({ item }) => {
const handleWish = () => {
if (!isLogin) return
if (!item?._id || !item?.variants[0]?._id) return
if (isWished) {