fix1607
This commit is contained in:
@ -3,138 +3,69 @@ import { useQuery } from '@apollo/client';
|
||||
import { GET_PARTSINDEX_CATEGORIES } from '@/lib/graphql';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
interface PartsIndexCatalog {
|
||||
interface CategoryNavItem {
|
||||
id: string;
|
||||
name: string;
|
||||
image?: string;
|
||||
groups?: PartsIndexGroup[];
|
||||
}
|
||||
|
||||
interface PartsIndexGroup {
|
||||
id: string;
|
||||
name: string;
|
||||
image?: string;
|
||||
subgroups?: PartsIndexSubgroup[];
|
||||
entityNames?: { id: string; name: string }[];
|
||||
}
|
||||
|
||||
interface PartsIndexSubgroup {
|
||||
id: string;
|
||||
name: string;
|
||||
image?: string;
|
||||
entityNames?: { id: string; name: string }[];
|
||||
}
|
||||
const FALLBACK_CATEGORIES: CategoryNavItem[] = [
|
||||
{ id: '1', name: 'Детали для ТО', image: '/images/catalog_item.png' },
|
||||
{ id: '2', name: 'Шины', image: '/images/catalog_item2.png' },
|
||||
{ id: '3', name: 'Диски', image: '/images/catalog_item3.png' },
|
||||
{ id: '4', name: 'Масла и жидкости', image: '/images/catalog_item4.png' },
|
||||
{ id: '5', name: 'Инструменты', image: '/images/catalog_item5.png' },
|
||||
{ id: '6', name: 'Автохимия', image: '/images/catalog_item6.png' },
|
||||
{ id: '7', name: 'Аксессуары', image: '/images/catalog_item7.png' },
|
||||
{ id: '8', name: 'Электрика', image: '/images/catalog_item8.png' },
|
||||
{ id: '9', name: 'АКБ', image: '/images/catalog_item9.png' },
|
||||
];
|
||||
|
||||
const CategoryNavSection: React.FC = () => {
|
||||
const router = useRouter();
|
||||
|
||||
const { data, loading, error } = useQuery<{ partsIndexCategoriesWithGroups: PartsIndexCatalog[] }>(
|
||||
|
||||
const { data } = useQuery<{ partsIndexCategoriesWithGroups: CategoryNavItem[] }>(
|
||||
GET_PARTSINDEX_CATEGORIES,
|
||||
{
|
||||
variables: {
|
||||
lang: 'ru'
|
||||
},
|
||||
variables: { lang: 'ru' },
|
||||
errorPolicy: 'all',
|
||||
fetchPolicy: 'cache-first'
|
||||
fetchPolicy: 'cache-first',
|
||||
}
|
||||
);
|
||||
|
||||
// Обработчик клика по категории для перехода в каталог с товарами
|
||||
const handleCategoryClick = (catalog: PartsIndexCatalog) => {
|
||||
console.log('🔍 Клик по категории:', { catalogId: catalog.id, categoryName: catalog.name });
|
||||
|
||||
// Получаем первую группу для groupId (это правильный ID для partsIndexCategory)
|
||||
const firstGroup = catalog.groups?.[0];
|
||||
const groupId = firstGroup?.id;
|
||||
|
||||
console.log('🔍 Найденная группа:', { groupId, groupName: firstGroup?.name });
|
||||
|
||||
// Переходим на страницу каталога с параметрами PartsIndex
|
||||
const categories = (data?.partsIndexCategoriesWithGroups && data.partsIndexCategoriesWithGroups.length > 0)
|
||||
? data.partsIndexCategoriesWithGroups.slice(0, 9)
|
||||
: FALLBACK_CATEGORIES;
|
||||
|
||||
const handleCategoryClick = (category: CategoryNavItem) => {
|
||||
router.push({
|
||||
pathname: '/catalog',
|
||||
query: {
|
||||
partsIndexCatalog: catalog.id,
|
||||
categoryName: encodeURIComponent(catalog.name),
|
||||
...(groupId && { partsIndexCategory: groupId })
|
||||
categoryId: category.id,
|
||||
categoryName: encodeURIComponent(category.name)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Fallback данные на случай ошибки
|
||||
const fallbackCategories = [
|
||||
{ id: '1', name: 'Двигатель', image: '/images/catalog_item.png' },
|
||||
{ id: '2', name: 'Трансмиссия', image: '/images/catalog_item2.png' },
|
||||
{ id: '3', name: 'Подвеска', image: '/images/catalog_item3.png' },
|
||||
{ id: '4', name: 'Тормоза', image: '/images/catalog_item4.png' },
|
||||
{ id: '5', name: 'Электрика', image: '/images/catalog_item5.png' },
|
||||
{ id: '6', name: 'Кузов', image: '/images/catalog_item6.png' },
|
||||
{ id: '7', name: 'Салон', image: '/images/catalog_item7.png' },
|
||||
{ id: '8', name: 'Климат', image: '/images/catalog_item8.png' },
|
||||
{ id: '9', name: 'Расходники', image: '/images/catalog_item9.png' }
|
||||
];
|
||||
|
||||
// Используем данные из API или fallback
|
||||
const categories = data?.partsIndexCategoriesWithGroups || [];
|
||||
const displayCategories = categories.length > 0 ? categories : fallbackCategories;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="w-layout-blockcontainer container-2 w-container">
|
||||
<div className="w-layout-hflex flex-block-6">
|
||||
{Array.from({ length: 9 }).map((_, index) => (
|
||||
<div key={index} className="w-layout-vflex flex-block-7">
|
||||
<div className="animate-pulse bg-gray-200 h-6 w-20 rounded mb-2"></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="w-layout-hflex flex-block-5">
|
||||
{Array.from({ length: 9 }).map((_, index) => (
|
||||
<div key={index} className="w-layout-vflex flex-block-8">
|
||||
<div className="animate-pulse bg-gray-200 h-32 w-full rounded"></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-layout-blockcontainer container-2 w-container">
|
||||
{/* Навигационная панель с названиями категорий */}
|
||||
<div className="w-layout-hflex flex-block-6">
|
||||
{displayCategories.slice(0, 9).map((catalog) => (
|
||||
<div key={catalog.id} className="w-layout-vflex flex-block-7">
|
||||
<div
|
||||
className="text-block-10"
|
||||
onClick={() => handleCategoryClick(catalog)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
<section className="catnav">
|
||||
<div className="w-layout-blockcontainer batd w-container">
|
||||
<div className="w-layout-hflex flex-block-108-copy">
|
||||
{categories.map((category, idx) => (
|
||||
<div
|
||||
key={category.id}
|
||||
className={`ci${idx + 1}`}
|
||||
style={category.image ? { cursor: 'pointer', backgroundImage: `url('${category.image}')`, backgroundSize: 'cover', backgroundPosition: 'center' } : { cursor: 'pointer' }}
|
||||
onClick={() => handleCategoryClick(category)}
|
||||
>
|
||||
{catalog.name}
|
||||
<div className={idx === 0 ? 'text-block-54-copy' : 'text-block-54'} style={{ textAlign: 'center' }}>
|
||||
{category.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Блок с изображениями категорий */}
|
||||
<div className="w-layout-hflex flex-block-5">
|
||||
{displayCategories.slice(0, 9).map((catalog) => (
|
||||
<div key={catalog.id} className="w-layout-vflex flex-block-8">
|
||||
<img
|
||||
src={catalog.image || '/images/catalog_item.png'}
|
||||
loading="lazy"
|
||||
alt={catalog.name}
|
||||
className="image-5"
|
||||
onClick={() => handleCategoryClick(catalog)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.src = '/images/catalog_item.png';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { GET_HERO_BANNERS } from '@/lib/graphql';
|
||||
import Link from 'next/link';
|
||||
@ -13,20 +13,94 @@ interface HeroBanner {
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
// Добавим CSS для стрелок
|
||||
const arrowStyles = `
|
||||
.pod-slider-arrow {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.pod-slider-arrow-left { left: 12px; }
|
||||
.pod-slider-arrow-right { right: 12px; }
|
||||
.pod-slider-arrow .arrow-circle {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.85);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.pod-slider-arrow:hover .arrow-circle,
|
||||
.pod-slider-arrow:focus .arrow-circle {
|
||||
background: #ec1c24;
|
||||
}
|
||||
.pod-slider-arrow .arrow-svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: block;
|
||||
transition: stroke 0.2s;
|
||||
stroke: #222;
|
||||
}
|
||||
.pod-slider-arrow:hover .arrow-svg,
|
||||
.pod-slider-arrow:focus .arrow-svg {
|
||||
stroke: #fff;
|
||||
}
|
||||
`;
|
||||
|
||||
const slideStyles = `
|
||||
.pod-slider-slide {
|
||||
position: absolute;
|
||||
top: 0; left: 0;
|
||||
opacity: 0;
|
||||
transform: translateX(40px) scale(0.98);
|
||||
transition: opacity 0.5s cubic-bezier(.4,0,.2,1), transform 0.5s cubic-bezier(.4,0,.2,1);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
.pod-slider-slide.active {
|
||||
opacity: 1;
|
||||
transform: translateX(0) scale(1);
|
||||
pointer-events: auto;
|
||||
z-index: 2;
|
||||
}
|
||||
.pod-slider-slide.prev {
|
||||
opacity: 0;
|
||||
transform: translateX(-40px) scale(0.98);
|
||||
z-index: 1;
|
||||
}
|
||||
.pod-slider-slide.next {
|
||||
opacity: 0;
|
||||
transform: translateX(40px) scale(0.98);
|
||||
z-index: 1;
|
||||
}
|
||||
.mask.w-slider-mask { position: relative; }
|
||||
`;
|
||||
|
||||
const ProductOfDayBanner: React.FC = () => {
|
||||
const [currentSlide, setCurrentSlide] = useState(0);
|
||||
|
||||
const { data, loading, error } = useQuery(GET_HERO_BANNERS, {
|
||||
errorPolicy: 'all'
|
||||
});
|
||||
const [showArrows, setShowArrows] = useState(false);
|
||||
const sliderRef = useRef<HTMLDivElement>(null);
|
||||
const { data } = useQuery(GET_HERO_BANNERS, { errorPolicy: 'all' });
|
||||
|
||||
// Фильтруем только активные баннеры и сортируем их
|
||||
const banners: HeroBanner[] = data?.heroBanners
|
||||
?.filter((banner: HeroBanner) => banner.isActive)
|
||||
?.slice()
|
||||
?.sort((a: HeroBanner, b: HeroBanner) => a.sortOrder - b.sortOrder) || [];
|
||||
|
||||
// Если нет баннеров из админки, показываем дефолтный
|
||||
const allBanners = banners.length > 0 ? banners : [{
|
||||
id: 'default',
|
||||
title: 'ДОСТАВИМ БЫСТРО!',
|
||||
@ -37,18 +111,15 @@ const ProductOfDayBanner: React.FC = () => {
|
||||
sortOrder: 0
|
||||
}];
|
||||
|
||||
// Автопрокрутка слайдов
|
||||
useEffect(() => {
|
||||
if (allBanners.length > 1) {
|
||||
const interval = setInterval(() => {
|
||||
setCurrentSlide(prev => (prev + 1) % allBanners.length);
|
||||
}, 5000); // Меняем слайд каждые 5 секунд
|
||||
|
||||
}, 5000);
|
||||
return () => clearInterval(interval);
|
||||
}
|
||||
}, [allBanners.length]);
|
||||
|
||||
// Сброс текущего слайда если он вне диапазона
|
||||
useEffect(() => {
|
||||
if (currentSlide >= allBanners.length) {
|
||||
setCurrentSlide(0);
|
||||
@ -67,141 +138,109 @@ const ProductOfDayBanner: React.FC = () => {
|
||||
setCurrentSlide(index);
|
||||
};
|
||||
|
||||
const currentBanner = allBanners[currentSlide];
|
||||
|
||||
const renderBannerContent = (banner: HeroBanner) => {
|
||||
return (
|
||||
<div className="div-block-128" style={{
|
||||
backgroundImage: `url(${banner.imageUrl})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: '200px',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: '20px',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{(banner.title || banner.subtitle) && (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: '20px',
|
||||
left: '20px',
|
||||
right: '20px',
|
||||
color: 'white',
|
||||
textShadow: '0 2px 4px rgba(0,0,0,0.5)'
|
||||
}}>
|
||||
{banner.title && (
|
||||
<h3 style={{ margin: 0, fontSize: '18px', fontWeight: 'bold' }}>
|
||||
{banner.title}
|
||||
</h3>
|
||||
)}
|
||||
{banner.subtitle && (
|
||||
<p style={{ margin: '5px 0 0 0', fontSize: '14px' }}>
|
||||
{banner.subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const bannerContent = renderBannerContent(currentBanner);
|
||||
|
||||
const finalContent = currentBanner.linkUrl ? (
|
||||
<Link href={currentBanner.linkUrl} style={{ cursor: 'pointer', display: 'block', width: '100%', height: '100%' }}>
|
||||
{bannerContent}
|
||||
</Link>
|
||||
) : bannerContent;
|
||||
// Показывать стрелки при наведении на слайдер или стрелки
|
||||
const handleMouseEnter = () => setShowArrows(true);
|
||||
const handleMouseLeave = () => setShowArrows(false);
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
|
||||
{finalContent}
|
||||
|
||||
{/* Навигация стрелками (показываем только если баннеров больше 1) */}
|
||||
{allBanners.length > 1 && (
|
||||
<>
|
||||
<div
|
||||
onClick={handlePrevSlide}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '10px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
background: 'rgba(0,0,0,0.5)',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '50%',
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: 'pointer',
|
||||
zIndex: 10,
|
||||
fontSize: '18px'
|
||||
}}
|
||||
>
|
||||
‹
|
||||
</div>
|
||||
<div
|
||||
onClick={handleNextSlide}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: '10px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
background: 'rgba(0,0,0,0.5)',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '50%',
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: 'pointer',
|
||||
zIndex: 10,
|
||||
fontSize: '18px'
|
||||
}}
|
||||
>
|
||||
›
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Индикаторы слайдов (показываем только если баннеров больше 1) */}
|
||||
{allBanners.length > 1 && (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: '10px',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
display: 'flex',
|
||||
gap: '8px',
|
||||
zIndex: 10
|
||||
}}>
|
||||
{allBanners.map((_, index) => (
|
||||
<div
|
||||
className="slider w-slider"
|
||||
ref={sliderRef}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
tabIndex={0}
|
||||
style={{ position: 'relative' }}
|
||||
>
|
||||
{/* Вставляем стили для стрелок */}
|
||||
<style>{arrowStyles}{slideStyles}</style>
|
||||
<div className="mask w-slider-mask">
|
||||
{allBanners.map((banner, idx) => {
|
||||
let slideClass = 'pod-slider-slide';
|
||||
if (idx === currentSlide) slideClass += ' active';
|
||||
else if (idx === (currentSlide === 0 ? allBanners.length - 1 : currentSlide - 1)) slideClass += ' prev';
|
||||
else if (idx === (currentSlide + 1) % allBanners.length) slideClass += ' next';
|
||||
const slideContent = (
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => handleSlideIndicator(index)}
|
||||
className="div-block-128"
|
||||
style={{
|
||||
width: '10px',
|
||||
height: '10px',
|
||||
borderRadius: '50%',
|
||||
background: index === currentSlide ? 'white' : 'rgba(255,255,255,0.5)',
|
||||
cursor: 'pointer',
|
||||
transition: 'background 0.3s'
|
||||
backgroundImage: `url(${banner.imageUrl})`,
|
||||
// backgroundSize: 'cover',
|
||||
// backgroundPosition: 'center',
|
||||
// backgroundRepeat: 'no-repeat',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{/* Можно добавить текст поверх баннера, если нужно */}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={slideClass + ' slide w-slide'}
|
||||
key={banner.id}
|
||||
// style={{ display: idx === currentSlide ? 'block' : 'none', position: 'relative' }}
|
||||
style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }}
|
||||
>
|
||||
{banner.linkUrl ? (
|
||||
<Link href={banner.linkUrl} style={{ display: 'block', width: '100%', height: '100%' }}>{slideContent}</Link>
|
||||
) : slideContent}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* SVG-стрелки как в Webflow, поверх баннера, с hover-эффектом */}
|
||||
<button
|
||||
className="pod-slider-arrow pod-slider-arrow-left"
|
||||
onClick={handlePrevSlide}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
style={{
|
||||
opacity: showArrows ? 1 : 0,
|
||||
pointerEvents: showArrows ? 'auto' : 'none',
|
||||
}}
|
||||
tabIndex={-1}
|
||||
aria-label="Предыдущий баннер"
|
||||
>
|
||||
<span className="arrow-circle">
|
||||
<svg className="arrow-svg" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="pod-slider-arrow pod-slider-arrow-right"
|
||||
onClick={handleNextSlide}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
style={{
|
||||
opacity: showArrows ? 1 : 0,
|
||||
pointerEvents: showArrows ? 'auto' : 'none',
|
||||
}}
|
||||
tabIndex={-1}
|
||||
aria-label="Следующий баннер"
|
||||
>
|
||||
<span className="arrow-circle">
|
||||
<svg className="arrow-svg" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.33398 10H16.6673M16.6673 10L11.6673 5M16.6673 10L11.6673 15" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
<div className="slide-nav w-slider-nav w-slider-nav-invert w-round">
|
||||
{allBanners.map((_, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="w-slider-dot"
|
||||
style={{
|
||||
background: idx === currentSlide ? 'white' : 'rgba(255,255,255,0.5)',
|
||||
borderRadius: '50%',
|
||||
width: 10,
|
||||
height: 10,
|
||||
margin: 4,
|
||||
display: 'inline-block',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={() => handleSlideIndicator(idx)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user