favourite list update page
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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'] });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user