favourite list update page

This commit is contained in:
Samandar Turgunboyev
2026-03-16 16:10:25 +05:00
parent 26c933d196
commit 3d1d9d4860
3 changed files with 71 additions and 45 deletions

View File

@@ -3,25 +3,54 @@
import { product_api } from '@/shared/config/api/product/api'; import { product_api } from '@/shared/config/api/product/api';
import { useRouter } from '@/shared/config/i18n/navigation'; import { useRouter } from '@/shared/config/i18n/navigation';
import { Button } from '@/shared/ui/button'; import { Button } from '@/shared/ui/button';
import { Card } from '@/shared/ui/card';
import { Skeleton } from '@/shared/ui/skeleton'; import { Skeleton } from '@/shared/ui/skeleton';
import { ProductCard } from '@/widgets/categories/ui/product-card'; import { ProductCard } from '@/widgets/categories/ui/product-card';
import { useQuery } from '@tanstack/react-query'; import { useInfiniteQuery } from '@tanstack/react-query';
import { Heart } from 'lucide-react'; import { Heart, Loader2 } from 'lucide-react';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { useEffect, useRef } from 'react';
export default function Favourite() { export default function Favourite() {
const router = useRouter(); const router = useRouter();
const t = useTranslations(); const t = useTranslations();
const { data: favourite, isLoading } = useQuery({ const loadMoreRef = useRef<HTMLDivElement>(null);
queryKey: ['favourite_product'],
queryFn: () => product_api.favouuriteProduct(),
select(data) {
return data.data;
},
});
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 ( return (
<div className="min-h-screen py-12"> <div className="min-h-screen py-12">
<div className="container mx-auto px-4"> <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-8 w-64 mb-2" />
<Skeleton className="h-4 w-32" /> <Skeleton className="h-4 w-32" />
</div> </div>
{/* Grid */}
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4"> <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) => ( {Array.from({ length: 10 }).map((_, i) => (
<div key={i} className="rounded-xl border p-3 space-y-3"> <div key={i} className="rounded-xl border p-3 space-y-3">
@@ -71,37 +98,32 @@ export default function Favourite() {
return ( return (
<div className="custom-container"> <div className="custom-container">
<> <div className="mb-8">
<div className="mb-8"> <h1 className="text-3xl font-bold text-slate-800 mb-2">
<div> {t('Sevimli mahsulotlar')}
<h1 className="text-3xl font-bold text-slate-800 mb-2"> </h1>
{t('Sevimli mahsulotlar')} <p className="text-slate-500">
</h1> {favourite.length} {t('ta mahsulot')}
<p className="text-slate-500"> </p>
{favourite && favourite.total} {t('ta mahsulot')} </div>
</p>
</div>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4 mb-30"> <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 xl:grid-cols-6 gap-4 mb-8">
{isLoading && {favourite
Array.from({ length: 6 }).map((_, index) => ( .filter((product) => product.state === 'A')
<Card className="p-3 space-y-3 rounded-xl" key={index}> .map((product) => (
<Skeleton className="h-40 sm:h-48 md:h-56 w-full rounded-lg" /> <ProductCard key={product.id} product={product} />
<Skeleton className="h-4 w-3/4" /> ))}
<Skeleton className="h-4 w-1/2" /> </div>
<Skeleton className="h-10 w-full rounded-lg" />
</Card> {/* Sentinel element — screen pastiga yetganda trigger bo'ladi */}
))} <div ref={loadMoreRef} className="h-10" />
{favourite &&
!isLoading && {/* Yuklash indikatori */}
favourite?.results {isFetchingNextPage && (
.filter((product) => product.state === 'A') <div className="flex justify-center py-6">
.map((product) => ( <Loader2 className="w-6 h-6 animate-spin text-blue-600" />
<ProductCard key={product.id} product={product} />
))}
</div> </div>
</> )}
</div> </div>
); );
} }

View File

@@ -55,8 +55,11 @@ export const product_api = {
return res; return res;
}, },
async favouuriteProduct(): Promise<AxiosResponse<FavouriteProduct>> { async favouuriteProduct(params: {
const res = await httpClient.get(API_URLS.FavouriteProduct); page: number;
page_size: number;
}): Promise<AxiosResponse<FavouriteProduct>> {
const res = await httpClient.get(API_URLS.FavouriteProduct, { params });
return res; return res;
}, },
}; };

View File

@@ -173,7 +173,8 @@ export function ProductCard({
mutationFn: (productId: string) => product_api.favourite(productId), mutationFn: (productId: string) => product_api.favourite(productId),
onSuccess: () => { onSuccess: () => {
queryClient.refetchQueries({ queryKey: ['product_list'] }); queryClient.refetchQueries({ queryKey: ['list'] });
queryClient.refetchQueries({ queryKey: ['all_products'] });
queryClient.refetchQueries({ queryKey: ['favourite_product'] }); queryClient.refetchQueries({ queryKey: ['favourite_product'] });
}, },