update carousel
This commit is contained in:
358
src/widgets/categories/ui/CategoryAccordion.tsx
Normal file
358
src/widgets/categories/ui/CategoryAccordion.tsx
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { CategoryResult } from '@/shared/config/api/category/type';
|
||||||
|
import { product_api } from '@/shared/config/api/product/api';
|
||||||
|
import { useRouter } from '@/shared/config/i18n/navigation';
|
||||||
|
import { cn } from '@/shared/lib/utils';
|
||||||
|
import { Button } from '@/shared/ui/button';
|
||||||
|
import {
|
||||||
|
Carousel,
|
||||||
|
CarouselContent,
|
||||||
|
CarouselItem,
|
||||||
|
type CarouselApi,
|
||||||
|
} from '@/shared/ui/carousel';
|
||||||
|
import { ProductCard } from '@/widgets/categories/ui/product-card';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { ChevronDown, ChevronLeft, ChevronRight, Package } from 'lucide-react';
|
||||||
|
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
interface CategoryAccordionProps {
|
||||||
|
category: CategoryResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CategoryAccordion = memo(function CategoryAccordion({
|
||||||
|
category,
|
||||||
|
}: CategoryAccordionProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [carousels, setCarousels] = useState<Map<string, CarouselApi>>(
|
||||||
|
new Map(),
|
||||||
|
);
|
||||||
|
const [scrollStates, setScrollStates] = useState<
|
||||||
|
Map<string, { prev: boolean; next: boolean }>
|
||||||
|
>(new Map());
|
||||||
|
|
||||||
|
// Ref to track if event listeners are already attached
|
||||||
|
const listenersAttached = useRef<Set<string>>(new Set());
|
||||||
|
|
||||||
|
const toggle = useCallback(() => setIsOpen((prev) => !prev), []);
|
||||||
|
|
||||||
|
// Separate effect to handle carousel listeners
|
||||||
|
useEffect(() => {
|
||||||
|
if (carousels.size === 0) return;
|
||||||
|
|
||||||
|
const updateScrollState = (subId: string, api: CarouselApi) => {
|
||||||
|
if (!api) return; // Guard clause
|
||||||
|
|
||||||
|
setScrollStates((prev) => {
|
||||||
|
const newMap = new Map(prev);
|
||||||
|
newMap.set(subId, {
|
||||||
|
prev: api.canScrollPrev(),
|
||||||
|
next: api.canScrollNext(),
|
||||||
|
});
|
||||||
|
return newMap;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanupFunctions: (() => void)[] = [];
|
||||||
|
|
||||||
|
carousels.forEach((api, subId) => {
|
||||||
|
// Skip if api is undefined or listeners already attached
|
||||||
|
if (!api || listenersAttached.current.has(subId)) return;
|
||||||
|
|
||||||
|
const handleUpdate = () => updateScrollState(subId, api);
|
||||||
|
|
||||||
|
// Initial update
|
||||||
|
handleUpdate();
|
||||||
|
|
||||||
|
// Attach listeners
|
||||||
|
api.on('select', handleUpdate);
|
||||||
|
api.on('reInit', handleUpdate);
|
||||||
|
|
||||||
|
// Mark as attached
|
||||||
|
listenersAttached.current.add(subId);
|
||||||
|
|
||||||
|
// Cleanup function
|
||||||
|
cleanupFunctions.push(() => {
|
||||||
|
api.off('select', handleUpdate);
|
||||||
|
api.off('reInit', handleUpdate);
|
||||||
|
listenersAttached.current.delete(subId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cleanupFunctions.forEach((cleanup) => cleanup());
|
||||||
|
};
|
||||||
|
}, [carousels]);
|
||||||
|
|
||||||
|
const handleSetApi = useCallback(
|
||||||
|
(subId: string, api: CarouselApi | undefined) => {
|
||||||
|
if (!api) return;
|
||||||
|
|
||||||
|
setCarousels((prev) => {
|
||||||
|
// Only update if this is a new or different API
|
||||||
|
const existing = prev.get(subId);
|
||||||
|
if (existing === api) return prev;
|
||||||
|
|
||||||
|
const newMap = new Map(prev);
|
||||||
|
newMap.set(subId, api);
|
||||||
|
return newMap;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const scrollPrev = useCallback(
|
||||||
|
(subId: string) => {
|
||||||
|
const api = carousels.get(subId);
|
||||||
|
if (api) {
|
||||||
|
api.scrollPrev();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[carousels],
|
||||||
|
);
|
||||||
|
|
||||||
|
const scrollNext = useCallback(
|
||||||
|
(subId: string) => {
|
||||||
|
const api = carousels.get(subId);
|
||||||
|
if (api) {
|
||||||
|
api.scrollNext();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[carousels],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: productsData, isLoading } = useQuery({
|
||||||
|
queryKey: ['category_products', category.id],
|
||||||
|
queryFn: async () => {
|
||||||
|
const results = await Promise.all(
|
||||||
|
category.product_types.map((sub) =>
|
||||||
|
product_api.list({ page: 1, page_size: 16, product_type_id: sub.id }),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return results.map((r, i) => ({
|
||||||
|
subCategory: category.product_types[i],
|
||||||
|
products: r.data.results.filter((p) => p.state === 'A'),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
enabled: isOpen,
|
||||||
|
staleTime: 5 * 60 * 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="custom-container mt-8 mb-6">
|
||||||
|
{/* Header */}
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'group relative overflow-hidden cursor-pointer',
|
||||||
|
'bg-gradient-to-br from-white via-blue-50 to-indigo-50',
|
||||||
|
'hover:from-blue-50 hover:via-blue-100 hover:to-indigo-100',
|
||||||
|
'border border-slate-200 rounded-2xl transition-all duration-500 ease-in-out',
|
||||||
|
'shadow-sm hover:shadow-lg',
|
||||||
|
isOpen &&
|
||||||
|
'border-blue-400 shadow-xl bg-gradient-to-br from-blue-50 to-indigo-50',
|
||||||
|
)}
|
||||||
|
onClick={toggle}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between items-center p-6 sm:p-8">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'p-3.5 rounded-xl transition-all duration-500 transform',
|
||||||
|
'bg-white shadow-md',
|
||||||
|
'group-hover:bg-gradient-to-br group-hover:from-blue-100 group-hover:to-indigo-100 group-hover:shadow-lg group-hover:scale-110',
|
||||||
|
isOpen &&
|
||||||
|
'bg-gradient-to-br from-blue-200 to-indigo-200 shadow-lg scale-110',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Package
|
||||||
|
className={cn(
|
||||||
|
'size-6 transition-all duration-500',
|
||||||
|
'text-slate-700',
|
||||||
|
'group-hover:text-blue-700',
|
||||||
|
isOpen && 'text-blue-700',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h2
|
||||||
|
className={cn(
|
||||||
|
'text-xl sm:text-3xl font-bold transition-all duration-300',
|
||||||
|
'text-slate-900',
|
||||||
|
'group-hover:text-blue-800',
|
||||||
|
isOpen && 'text-blue-800',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{category.name}
|
||||||
|
</h2>
|
||||||
|
<div className="flex items-center gap-2 mt-1">
|
||||||
|
<span className="inline-flex items-center gap-1 px-3 py-1 rounded-full bg-slate-100 group-hover:bg-blue-100 transition-colors">
|
||||||
|
<span className="text-xs sm:text-sm font-semibold text-slate-700 group-hover:text-blue-700">
|
||||||
|
{category.product_types.length}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs sm:text-sm text-slate-600 group-hover:text-blue-600">
|
||||||
|
kategoriya
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'p-3 rounded-full transition-all duration-500 transform',
|
||||||
|
'bg-white shadow-md',
|
||||||
|
'group-hover:bg-blue-100 group-hover:shadow-lg group-hover:scale-125',
|
||||||
|
isOpen && 'bg-blue-200 shadow-lg scale-125',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ChevronDown
|
||||||
|
className={cn(
|
||||||
|
'size-6 transition-all duration-500',
|
||||||
|
'text-slate-700',
|
||||||
|
'group-hover:text-blue-700',
|
||||||
|
isOpen ? 'rotate-180 text-blue-700' : 'rotate-0',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'h-1.5 bg-gradient-to-r from-blue-600 via-indigo-600 to-purple-600 transition-all duration-500',
|
||||||
|
'scale-x-0 group-hover:scale-x-100',
|
||||||
|
isOpen && 'scale-x-100',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'overflow-hidden transition-all duration-700 ease-in-out',
|
||||||
|
isOpen ? 'max-h-[10000px] opacity-100 mt-6' : 'max-h-0 opacity-0',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="space-y-8">
|
||||||
|
{Array.from({ length: 2 }).map((_, idx) => (
|
||||||
|
<div key={idx} className="space-y-4">
|
||||||
|
<div className="h-7 w-56 bg-gradient-to-r from-slate-200 to-slate-100 rounded-lg animate-pulse" />
|
||||||
|
<div className="flex gap-4 overflow-hidden">
|
||||||
|
{Array.from({ length: 6 }).map((_, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="flex-shrink-0 w-44 h-72 bg-gradient-to-br from-slate-100 to-slate-50 rounded-2xl animate-pulse"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-10 pb-8">
|
||||||
|
{productsData?.map((sub) => {
|
||||||
|
if (!sub.products.length) return null;
|
||||||
|
|
||||||
|
const subId = sub.subCategory.id.toString();
|
||||||
|
const scrollState = scrollStates.get(subId) || {
|
||||||
|
prev: false,
|
||||||
|
next: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={sub.subCategory.id}
|
||||||
|
className="bg-gradient-to-br from-white to-slate-50 rounded-2xl border border-slate-200 p-6 sm:p-8 shadow-md hover:shadow-xl transition-all duration-300"
|
||||||
|
>
|
||||||
|
{/* Subcategory Header */}
|
||||||
|
<div className="flex items-center justify-between mb-6 pb-4 border-b border-slate-200">
|
||||||
|
<div
|
||||||
|
className="flex items-center gap-3 group/title cursor-pointer flex-1"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/category/${category.id}/${sub.subCategory.id}/`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="w-1.5 h-8 bg-gradient-to-b from-blue-600 to-indigo-600 rounded-full"></div>
|
||||||
|
<h3 className="text-xl sm:text-2xl font-bold text-slate-900 group-hover/title:text-blue-700 transition-all duration-300">
|
||||||
|
{sub.subCategory.name}
|
||||||
|
</h3>
|
||||||
|
<div className="p-2 bg-slate-100 rounded-lg group-hover/title:bg-blue-100 transition-all duration-300 ml-auto sm:ml-0">
|
||||||
|
<ChevronRight className="size-5 text-slate-600 group-hover/title:text-blue-600 group-hover/title:translate-x-1 transition-all duration-300" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="inline-flex items-center gap-2 px-4 py-2 bg-blue-50 rounded-full border border-blue-200 ml-4">
|
||||||
|
<span className="text-sm font-bold text-blue-700">
|
||||||
|
{sub.products.length}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-blue-600">mahsulot</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Carousel */}
|
||||||
|
<div className="relative">
|
||||||
|
<Carousel
|
||||||
|
className="w-full"
|
||||||
|
setApi={(api) => handleSetApi(subId, api)}
|
||||||
|
>
|
||||||
|
<CarouselContent className="pr-[12%] sm:pr-0 -ml-3">
|
||||||
|
{sub.products.map((product) => (
|
||||||
|
<CarouselItem
|
||||||
|
key={product.id}
|
||||||
|
className="pl-3 basis-1/2 sm:basis-1/3 md:basis-1/4 lg:basis-1/5 xl:basis-1/6"
|
||||||
|
>
|
||||||
|
<ProductCard product={product} />
|
||||||
|
</CarouselItem>
|
||||||
|
))}
|
||||||
|
</CarouselContent>
|
||||||
|
</Carousel>
|
||||||
|
|
||||||
|
{/* Navigation Buttons */}
|
||||||
|
<Button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
scrollNext(subId);
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
'absolute -top-16 right-4 max-lg:hidden text-white shadow-xl transition-all duration-300 transform hover:scale-110 active:scale-95',
|
||||||
|
scrollState.next
|
||||||
|
? 'bg-gradient-to-br from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700'
|
||||||
|
: 'bg-slate-300 cursor-not-allowed opacity-40',
|
||||||
|
)}
|
||||||
|
disabled={!scrollState.next}
|
||||||
|
size="icon"
|
||||||
|
aria-label="next products"
|
||||||
|
>
|
||||||
|
<ChevronRight className="size-6" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
scrollPrev(subId);
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
'absolute -top-16 right-16 max-lg:hidden text-white shadow-xl transition-all duration-300 transform hover:scale-110 active:scale-95',
|
||||||
|
scrollState.prev
|
||||||
|
? 'bg-gradient-to-br from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700'
|
||||||
|
: 'bg-slate-300 cursor-not-allowed opacity-40',
|
||||||
|
)}
|
||||||
|
disabled={!scrollState.prev}
|
||||||
|
size="icon"
|
||||||
|
aria-label="previous products"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="size-6" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default CategoryAccordion;
|
||||||
@@ -7,16 +7,25 @@ import { cn } from '@/shared/lib/utils';
|
|||||||
import { Button } from '@/shared/ui/button';
|
import { Button } from '@/shared/ui/button';
|
||||||
import {
|
import {
|
||||||
Carousel,
|
Carousel,
|
||||||
CarouselApi,
|
|
||||||
CarouselContent,
|
CarouselContent,
|
||||||
CarouselItem,
|
CarouselItem,
|
||||||
|
type CarouselApi,
|
||||||
} from '@/shared/ui/carousel';
|
} from '@/shared/ui/carousel';
|
||||||
|
import { ProductCard } from '@/widgets/categories/ui/product-card';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { memo, useEffect, useRef, useState } from 'react';
|
||||||
import { ProductCard } from './product-card';
|
|
||||||
|
|
||||||
export function CategoryCarousel({ category }: { category: ProductTypes }) {
|
//////////////////////////
|
||||||
|
/// CategoryCarousel optimized
|
||||||
|
//////////////////////////
|
||||||
|
interface CategoryCarouselProps {
|
||||||
|
category: ProductTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CategoryCarousel = memo(function CategoryCarousel({
|
||||||
|
category,
|
||||||
|
}: CategoryCarouselProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [api, setApi] = useState<CarouselApi>();
|
const [api, setApi] = useState<CarouselApi>();
|
||||||
const [canScrollPrev, setCanScrollPrev] = useState(false);
|
const [canScrollPrev, setCanScrollPrev] = useState(false);
|
||||||
@@ -24,76 +33,59 @@ export function CategoryCarousel({ category }: { category: ProductTypes }) {
|
|||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
const sectionRef = useRef<HTMLElement>(null);
|
const sectionRef = useRef<HTMLElement>(null);
|
||||||
|
|
||||||
// Intersection Observer - faqat ko'ringan kategoriyalar uchun API so'rov yuborish
|
// Intersection Observer
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!sectionRef.current) return;
|
||||||
|
|
||||||
const observer = new IntersectionObserver(
|
const observer = new IntersectionObserver(
|
||||||
(entries) => {
|
(entries) => {
|
||||||
entries.forEach((entry) => {
|
entries.forEach((entry) => {
|
||||||
if (entry.isIntersecting) {
|
if (entry.isIntersecting) {
|
||||||
setIsVisible(true);
|
setIsVisible(true);
|
||||||
observer.disconnect(); // Bir marta ko'ringandan keyin observer ni o'chirish
|
observer.disconnect();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{
|
{ rootMargin: '100px', threshold: 0.1 },
|
||||||
rootMargin: '100px', // Sahifa 100px yaqinlashganda yuklash
|
|
||||||
threshold: 0.1,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (sectionRef.current) {
|
|
||||||
observer.observe(sectionRef.current);
|
observer.observe(sectionRef.current);
|
||||||
}
|
|
||||||
|
|
||||||
return () => observer.disconnect();
|
return () => observer.disconnect();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Carousel buttons
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!api) return;
|
if (!api) return;
|
||||||
|
|
||||||
const updateButtons = () => {
|
const updateButtons = () => {
|
||||||
setCanScrollPrev(api.canScrollPrev());
|
setCanScrollPrev(api.canScrollPrev());
|
||||||
setCanScrollNext(api.canScrollNext());
|
setCanScrollNext(api.canScrollNext());
|
||||||
};
|
};
|
||||||
|
|
||||||
updateButtons();
|
updateButtons();
|
||||||
api.on('select', updateButtons);
|
api.on('select', updateButtons);
|
||||||
api.on('reInit', updateButtons);
|
api.on('reInit', updateButtons);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
api.off('select', updateButtons);
|
api.off('select', updateButtons);
|
||||||
api.off('reInit', updateButtons);
|
api.off('reInit', updateButtons);
|
||||||
};
|
};
|
||||||
}, [api]);
|
}, [api]);
|
||||||
|
|
||||||
const scrollPrev = () => {
|
const scrollPrev = () => api?.scrollPrev();
|
||||||
if (api) {
|
const scrollNext = () => api?.scrollNext();
|
||||||
api?.scrollPrev();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const scrollNext = () => {
|
// React Query
|
||||||
if (api) {
|
|
||||||
api?.scrollNext();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Faqat ko'ringanda API so'rov yuborish
|
|
||||||
const { data: product, isLoading } = useQuery({
|
const { data: product, isLoading } = useQuery({
|
||||||
queryKey: ['product_list', category.id],
|
queryKey: ['product_list', category.id],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
product_api.list({
|
product_api.list({
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 16,
|
page_size: 16,
|
||||||
product_type_id: category.id,
|
category_id: category.id,
|
||||||
}),
|
}),
|
||||||
select(data) {
|
select: (data) => data.data,
|
||||||
return data.data;
|
enabled: isVisible,
|
||||||
},
|
|
||||||
enabled: isVisible, // Faqat ko'ringanda ishga tushadi
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Agar hali ko'rinmagan bo'lsa, bo'sh div qaytarish (scroll uchun joy)
|
// Shartli renderlar
|
||||||
if (!isVisible) {
|
if (!isVisible) {
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
@@ -103,22 +95,12 @@ export function CategoryCarousel({ category }: { category: ProductTypes }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Agar loading bo'lmasa va mahsulotlar bo'lmasa, hech narsa ko'rsatmaymiz
|
if (!isLoading && (!product || product.results.length === 0)) return null;
|
||||||
if (!isLoading && (!product || product.results.length === 0)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Agar loading bo'lmasa va faqat "active" mahsulotlar bo'lmasa ham ko'rsatmaymiz
|
const activeProducts = product?.results.filter((p) => p.state === 'A') ?? [];
|
||||||
if (!isLoading && product) {
|
if (!isLoading && activeProducts.length === 0) return null;
|
||||||
const activeProducts = product.results.filter((p) => p.state === 'A');
|
|
||||||
if (activeProducts.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
@@ -141,11 +123,7 @@ export function CategoryCarousel({ category }: { category: ProductTypes }) {
|
|||||||
|
|
||||||
<Carousel className="w-full mt-2" setApi={setApi}>
|
<Carousel className="w-full mt-2" setApi={setApi}>
|
||||||
<CarouselContent className="pr-[12%] sm:pr-0">
|
<CarouselContent className="pr-[12%] sm:pr-0">
|
||||||
{product &&
|
{activeProducts.map((product) => (
|
||||||
!isLoading &&
|
|
||||||
product.results
|
|
||||||
.filter((product) => product.state === 'A')
|
|
||||||
.map((product) => (
|
|
||||||
<CarouselItem
|
<CarouselItem
|
||||||
key={product.id}
|
key={product.id}
|
||||||
className="basis-1/2 sm:basis-1/3 md:basis-1/4 lg:basis-1/5 xl:basis-1/6 pb-2"
|
className="basis-1/2 sm:basis-1/3 md:basis-1/4 lg:basis-1/5 xl:basis-1/6 pb-2"
|
||||||
@@ -155,6 +133,7 @@ export function CategoryCarousel({ category }: { category: ProductTypes }) {
|
|||||||
))}
|
))}
|
||||||
</CarouselContent>
|
</CarouselContent>
|
||||||
</Carousel>
|
</Carousel>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={scrollNext}
|
onClick={scrollNext}
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -177,7 +156,6 @@ export function CategoryCarousel({ category }: { category: ProductTypes }) {
|
|||||||
? 'bg-green-600 hover:bg-green-600/70'
|
? 'bg-green-600 hover:bg-green-600/70'
|
||||||
: 'bg-green-600/50 cursor-not-allowed',
|
: 'bg-green-600/50 cursor-not-allowed',
|
||||||
)}
|
)}
|
||||||
aria-label="prev images"
|
|
||||||
disabled={!canScrollPrev}
|
disabled={!canScrollPrev}
|
||||||
size="icon"
|
size="icon"
|
||||||
>
|
>
|
||||||
@@ -185,4 +163,6 @@ export function CategoryCarousel({ category }: { category: ProductTypes }) {
|
|||||||
</Button>
|
</Button>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
export default CategoryCarousel;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
type CarouselApi,
|
type CarouselApi,
|
||||||
} from '@/shared/ui/carousel';
|
} from '@/shared/ui/carousel';
|
||||||
import { Skeleton } from '@/shared/ui/skeleton';
|
import { Skeleton } from '@/shared/ui/skeleton';
|
||||||
import { CategoryCarousel } from '@/widgets/categories/ui/category-carousel';
|
import CategoryCarousel from '@/widgets/categories/ui/category-carousel';
|
||||||
import { ProductCard } from '@/widgets/categories/ui/product-card';
|
import { ProductCard } from '@/widgets/categories/ui/product-card';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { AlertCircle, ChevronLeft, ChevronRight } from 'lucide-react';
|
import { AlertCircle, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||||
@@ -241,7 +241,7 @@ const Welcome = () => {
|
|||||||
</CarouselItem>
|
</CarouselItem>
|
||||||
))}
|
))}
|
||||||
</CarouselContent>
|
</CarouselContent>
|
||||||
</Carousel>{' '}
|
</Carousel>
|
||||||
<Button
|
<Button
|
||||||
onClick={scrollNextPro}
|
onClick={scrollNextPro}
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -273,11 +273,7 @@ const Welcome = () => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{category &&
|
{category &&
|
||||||
category.map((e) =>
|
category.map((e) => <CategoryCarousel category={e} key={e.id} />)}
|
||||||
e.product_types.map((c) => (
|
|
||||||
<CategoryCarousel category={c} key={c.id} />
|
|
||||||
)),
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user