favourite list update page
This commit is contained in:
@@ -3,25 +3,54 @@
|
||||
import { product_api } from '@/shared/config/api/product/api';
|
||||
import { useRouter } from '@/shared/config/i18n/navigation';
|
||||
import { Button } from '@/shared/ui/button';
|
||||
import { Card } from '@/shared/ui/card';
|
||||
import { Skeleton } from '@/shared/ui/skeleton';
|
||||
import { ProductCard } from '@/widgets/categories/ui/product-card';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Heart } from 'lucide-react';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { Heart, Loader2 } from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
export default function Favourite() {
|
||||
const router = useRouter();
|
||||
const t = useTranslations();
|
||||
const { data: favourite, isLoading } = useQuery({
|
||||
queryKey: ['favourite_product'],
|
||||
queryFn: () => product_api.favouuriteProduct(),
|
||||
select(data) {
|
||||
return data.data;
|
||||
},
|
||||
});
|
||||
const loadMoreRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
if (favourite && favourite.results.length === 0) {
|
||||
const { data, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ['favourite_product'],
|
||||
initialPageParam: 1,
|
||||
queryFn: ({ pageParam }) =>
|
||||
product_api.favouuriteProduct({ page: pageParam, page_size: 30 }),
|
||||
getNextPageParam: (lastPage) => {
|
||||
if (lastPage.data.has_next) return lastPage.data.page + 1;
|
||||
return undefined;
|
||||
},
|
||||
select(data) {
|
||||
return data.pages.flatMap((page) => page.data.results);
|
||||
},
|
||||
});
|
||||
|
||||
// Intersection Observer — loadMoreRef ko'ringanda keyingi sahifani yuklaydi
|
||||
useEffect(() => {
|
||||
const el = loadMoreRef.current;
|
||||
if (!el) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
},
|
||||
{ threshold: 0.1 },
|
||||
);
|
||||
|
||||
observer.observe(el);
|
||||
return () => observer.disconnect();
|
||||
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
||||
|
||||
const favourite = data ?? [];
|
||||
|
||||
if (!isLoading && favourite.length === 0) {
|
||||
return (
|
||||
<div className="min-h-screen py-12">
|
||||
<div className="container mx-auto px-4">
|
||||
@@ -54,8 +83,6 @@ export default function Favourite() {
|
||||
<Skeleton className="h-8 w-64 mb-2" />
|
||||
<Skeleton className="h-4 w-32" />
|
||||
</div>
|
||||
|
||||
{/* Grid */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
|
||||
{Array.from({ length: 10 }).map((_, i) => (
|
||||
<div key={i} className="rounded-xl border p-3 space-y-3">
|
||||
@@ -71,37 +98,32 @@ export default function Favourite() {
|
||||
|
||||
return (
|
||||
<div className="custom-container">
|
||||
<>
|
||||
<div className="mb-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-slate-800 mb-2">
|
||||
{t('Sevimli mahsulotlar')}
|
||||
</h1>
|
||||
<p className="text-slate-500">
|
||||
{favourite && favourite.total} {t('ta mahsulot')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-slate-800 mb-2">
|
||||
{t('Sevimli mahsulotlar')}
|
||||
</h1>
|
||||
<p className="text-slate-500">
|
||||
{favourite.length} {t('ta mahsulot')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4 mb-30">
|
||||
{isLoading &&
|
||||
Array.from({ length: 6 }).map((_, index) => (
|
||||
<Card className="p-3 space-y-3 rounded-xl" key={index}>
|
||||
<Skeleton className="h-40 sm:h-48 md:h-56 w-full rounded-lg" />
|
||||
<Skeleton className="h-4 w-3/4" />
|
||||
<Skeleton className="h-4 w-1/2" />
|
||||
<Skeleton className="h-10 w-full rounded-lg" />
|
||||
</Card>
|
||||
))}
|
||||
{favourite &&
|
||||
!isLoading &&
|
||||
favourite?.results
|
||||
.filter((product) => product.state === 'A')
|
||||
.map((product) => (
|
||||
<ProductCard key={product.id} product={product} />
|
||||
))}
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 xl:grid-cols-6 gap-4 mb-8">
|
||||
{favourite
|
||||
.filter((product) => product.state === 'A')
|
||||
.map((product) => (
|
||||
<ProductCard key={product.id} product={product} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Sentinel element — screen pastiga yetganda trigger bo'ladi */}
|
||||
<div ref={loadMoreRef} className="h-10" />
|
||||
|
||||
{/* Yuklash indikatori */}
|
||||
{isFetchingNextPage && (
|
||||
<div className="flex justify-center py-6">
|
||||
<Loader2 className="w-6 h-6 animate-spin text-blue-600" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,8 +55,11 @@ export const product_api = {
|
||||
return res;
|
||||
},
|
||||
|
||||
async favouuriteProduct(): Promise<AxiosResponse<FavouriteProduct>> {
|
||||
const res = await httpClient.get(API_URLS.FavouriteProduct);
|
||||
async favouuriteProduct(params: {
|
||||
page: number;
|
||||
page_size: number;
|
||||
}): Promise<AxiosResponse<FavouriteProduct>> {
|
||||
const res = await httpClient.get(API_URLS.FavouriteProduct, { params });
|
||||
return res;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -173,7 +173,8 @@ export function ProductCard({
|
||||
mutationFn: (productId: string) => product_api.favourite(productId),
|
||||
|
||||
onSuccess: () => {
|
||||
queryClient.refetchQueries({ queryKey: ['product_list'] });
|
||||
queryClient.refetchQueries({ queryKey: ['list'] });
|
||||
queryClient.refetchQueries({ queryKey: ['all_products'] });
|
||||
queryClient.refetchQueries({ queryKey: ['favourite_product'] });
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user