Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
475b02ea2d | |||
3b5defe3d9 | |||
c703fc839a | |||
2a983c956c |
@ -1,4 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { useCart } from "@/contexts/CartContext";
|
||||||
|
import { useFavorites } from "@/contexts/FavoritesContext";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
interface BestPriceItemProps {
|
interface BestPriceItemProps {
|
||||||
image: string;
|
image: string;
|
||||||
@ -7,6 +10,8 @@ interface BestPriceItemProps {
|
|||||||
oldPrice: string;
|
oldPrice: string;
|
||||||
title: string;
|
title: string;
|
||||||
brand: string;
|
brand: string;
|
||||||
|
article?: string;
|
||||||
|
productId?: string;
|
||||||
onAddToCart?: (e: React.MouseEvent) => void;
|
onAddToCart?: (e: React.MouseEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,14 +22,129 @@ const BestPriceItem: React.FC<BestPriceItemProps> = ({
|
|||||||
oldPrice,
|
oldPrice,
|
||||||
title,
|
title,
|
||||||
brand,
|
brand,
|
||||||
|
article,
|
||||||
|
productId,
|
||||||
onAddToCart,
|
onAddToCart,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { addItem } = useCart();
|
||||||
|
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
|
||||||
|
|
||||||
|
// Проверяем, есть ли товар в избранном
|
||||||
|
const isItemFavorite = isFavorite(productId, undefined, article, brand);
|
||||||
|
|
||||||
|
// Функция для парсинга цены из строки
|
||||||
|
const parsePrice = (priceStr: string): number => {
|
||||||
|
const cleanPrice = priceStr.replace(/[^\d.,]/g, '').replace(',', '.');
|
||||||
|
return parseFloat(cleanPrice) || 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Обработчик добавления в корзину
|
||||||
|
const handleAddToCart = async (e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Если передан кастомный обработчик, используем его
|
||||||
|
if (onAddToCart) {
|
||||||
|
onAddToCart(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const numericPrice = parsePrice(price);
|
||||||
|
|
||||||
|
if (numericPrice <= 0) {
|
||||||
|
toast.error('Цена товара не найдена');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем товар в корзину
|
||||||
|
const result = await addItem({
|
||||||
|
productId: productId,
|
||||||
|
name: title,
|
||||||
|
description: `${brand} - ${title}`,
|
||||||
|
brand: brand,
|
||||||
|
article: article,
|
||||||
|
price: numericPrice,
|
||||||
|
currency: 'RUB',
|
||||||
|
quantity: 1,
|
||||||
|
image: image,
|
||||||
|
supplier: 'Protek',
|
||||||
|
deliveryTime: '1 день',
|
||||||
|
isExternal: false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
// Показываем успешный тоастер
|
||||||
|
toast.success(
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold" style={{ color: '#fff' }}>Товар добавлен в корзину!</div>
|
||||||
|
<div className="text-sm" style={{ color: '#fff', opacity: 0.9 }}>{`${brand} - ${title}`}</div>
|
||||||
|
</div>,
|
||||||
|
{
|
||||||
|
duration: 3000,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Показываем ошибку
|
||||||
|
toast.error(result.error || 'Ошибка при добавлении товара в корзину');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка добавления в корзину:', error);
|
||||||
|
toast.error('Ошибка при добавлении товара в корзину');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Обработчик клика по иконке избранного
|
||||||
|
const handleFavoriteClick = (e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (isItemFavorite) {
|
||||||
|
// Находим товар в избранном и удаляем
|
||||||
|
const favoriteItem = favorites.find((fav: any) => {
|
||||||
|
if (productId && fav.productId === productId) return true;
|
||||||
|
if (fav.article === article && fav.brand === brand) return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (favoriteItem) {
|
||||||
|
removeFromFavorites(favoriteItem.id);
|
||||||
|
toast.success('Товар удален из избранного');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Добавляем в избранное
|
||||||
|
const numericPrice = parsePrice(price);
|
||||||
|
addToFavorites({
|
||||||
|
productId,
|
||||||
|
name: title,
|
||||||
|
brand: brand,
|
||||||
|
article: article || '',
|
||||||
|
price: numericPrice,
|
||||||
|
currency: 'RUB',
|
||||||
|
image: image
|
||||||
|
});
|
||||||
|
toast.success('Товар добавлен в избранное');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-layout-vflex bestpriceitem">
|
<div className="w-layout-vflex bestpriceitem">
|
||||||
<div className="favcardcat">
|
<div
|
||||||
|
className="favcardcat"
|
||||||
|
onClick={handleFavoriteClick}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
color: isItemFavorite ? '#ef4444' : 'currentColor'
|
||||||
|
}}
|
||||||
|
title={isItemFavorite ? 'Удалить из избранного' : 'Добавить в избранное'}
|
||||||
|
>
|
||||||
<div className="icon-setting w-embed">
|
<div className="icon-setting w-embed">
|
||||||
<svg width="currenWidth" height="currentHeight" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="currenWidth" height="currentHeight" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
|
<path
|
||||||
|
d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z"
|
||||||
|
fill={isItemFavorite ? 'currentColor' : 'none'}
|
||||||
|
stroke="currentColor"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -46,7 +166,13 @@ const BestPriceItem: React.FC<BestPriceItemProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="w-layout-hflex flex-block-120">
|
<div className="w-layout-hflex flex-block-120">
|
||||||
<div className="nameitembp">{title}</div>
|
<div className="nameitembp">{title}</div>
|
||||||
<a href="#" className="button-icon w-inline-block" onClick={onAddToCart}>
|
<a
|
||||||
|
href="#"
|
||||||
|
className="button-icon w-inline-block"
|
||||||
|
onClick={handleAddToCart}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
aria-label="Добавить в корзину"
|
||||||
|
>
|
||||||
<div className="div-block-26">
|
<div className="div-block-26">
|
||||||
<div className="icon-setting w-embed">
|
<div className="icon-setting w-embed">
|
||||||
<svg width="currentWidht" height="currentHeight" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="currentWidht" height="currentHeight" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
@ -84,7 +84,7 @@ const CartList: React.FC<CartListProps> = ({ isSummaryStep = false }) => {
|
|||||||
}, [state.error, clearError]);
|
}, [state.error, clearError]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-layout-vflex flex-block-48">
|
<div className="w-layout-vflex flex-block-48" style={{ minHeight: '420px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||||
{/* Отображение ошибок корзины */}
|
{/* Отображение ошибок корзины */}
|
||||||
{state.error && (
|
{state.error && (
|
||||||
<div className="alert alert-error mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
|
<div className="alert alert-error mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
|
||||||
@ -145,9 +145,15 @@ const CartList: React.FC<CartListProps> = ({ isSummaryStep = false }) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{displayItems.length === 0 ? (
|
{displayItems.length === 0 ? (
|
||||||
<div className="empty-cart-message" style={{ textAlign: 'center', padding: '2rem', color: '#666' }}>
|
<div className="empty-cart-message" style={{ textAlign: 'center', width: '100%' }}>
|
||||||
<p>Ваша корзина пуста</p>
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '20px', justifyContent: 'center' }}>
|
||||||
<p>Добавьте товары из каталога</p>
|
<span style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: 90, height: 90, borderRadius: '50%', background: '#f3f4f6', marginBottom: 8 }}>
|
||||||
|
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M16 36C14.3431 36 13 37.3431 13 39C13 40.6569 14.3431 42 16 42C17.6569 42 19 40.6569 19 39C19 37.3431 17.6569 36 16 36ZM6 8V12H10.68L16.44 24.016L14.16 28.08C13.7647 28.8001 13.5556 29.6352 13.5556 30.5C13.5556 32.1569 14.8987 33.5 16.5556 33.5H39V30.5H17.1756C17.0891 30.5 17.0178 30.4287 17.0178 30.3422L17.04 30.25L18.88 27H34.8C36.0212 27 37.1042 26.2627 37.6 25.16L43.048 14.352C43.1746 14.0993 43.2382 13.8132 43.2302 13.5242C43.2222 13.2352 43.1428 12.9538 42.9992 12.7087C42.8556 12.4636 42.6532 12.2632 42.4136 12.1302C42.174 11.9972 41.9062 11.9376 41.64 11.96H12.24L10.84 8H6ZM36 36C34.3431 36 33 37.3431 33 39C33 40.6569 34.3431 42 36 42C37.6569 42 39 40.6569 39 39C39 37.3431 37.6569 36 36 36Z" fill="#222"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<div style={{ fontSize: '1.7rem', fontWeight: 700, color: '#222' }}>Ваша корзина пуста</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
displayItems.map((item, idx) => {
|
displayItems.map((item, idx) => {
|
||||||
|
@ -285,7 +285,7 @@ const CartSummary: React.FC<CartSummaryProps> = ({ step, setStep }) => {
|
|||||||
onClick={() => setShowLegalEntityDropdown(!showLegalEntityDropdown)}
|
onClick={() => setShowLegalEntityDropdown(!showLegalEntityDropdown)}
|
||||||
style={{ cursor: 'pointer', justifyContent: 'space-between', alignItems: 'center' }}
|
style={{ cursor: 'pointer', justifyContent: 'space-between', alignItems: 'center' }}
|
||||||
>
|
>
|
||||||
<div className="text-block-31">
|
<div className="text-block-31" style={{ fontSize: '14px', color: '#333' }}>
|
||||||
{isIndividual ? 'Физическое лицо' : selectedLegalEntity || 'Выберите юридическое лицо'}
|
{isIndividual ? 'Физическое лицо' : selectedLegalEntity || 'Выберите юридическое лицо'}
|
||||||
</div>
|
</div>
|
||||||
<div className="code-embed w-embed" style={{ transform: showLegalEntityDropdown ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform 0.2s' }}>
|
<div className="code-embed w-embed" style={{ transform: showLegalEntityDropdown ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform 0.2s' }}>
|
||||||
@ -325,7 +325,7 @@ const CartSummary: React.FC<CartSummaryProps> = ({ step, setStep }) => {
|
|||||||
borderBottom: '1px solid #f0f0f0',
|
borderBottom: '1px solid #f0f0f0',
|
||||||
backgroundColor: isIndividual ? '#f8f9fa' : 'white',
|
backgroundColor: isIndividual ? '#f8f9fa' : 'white',
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
fontWeight: isIndividual ? 500 : 400
|
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
if (!isIndividual) {
|
if (!isIndividual) {
|
||||||
@ -538,7 +538,9 @@ const CartSummary: React.FC<CartSummaryProps> = ({ step, setStep }) => {
|
|||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
borderBottom: '1px solid #f0f0f0',
|
borderBottom: '1px solid #f0f0f0',
|
||||||
backgroundColor: paymentMethod === 'yookassa' ? '#f8f9fa' : 'white',
|
backgroundColor: paymentMethod === 'yookassa' ? '#f8f9fa' : 'white',
|
||||||
fontSize: '14px'
|
fontSize: '14px',
|
||||||
|
fontWeight: paymentMethod === 'yookassa' ? 500 : 400,
|
||||||
|
color: '#222'
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
if (paymentMethod !== 'yookassa') {
|
if (paymentMethod !== 'yookassa') {
|
||||||
|
@ -414,7 +414,7 @@ const Header: React.FC<HeaderProps> = ({ onOpenAuthModal = () => console.log('Au
|
|||||||
onClick={() => setMenuOpen((open) => !open)}
|
onClick={() => setMenuOpen((open) => !open)}
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
<div className="code-embed-5 w-embed"><svg width="30" height="18" viewBox="0 0 30 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<div className="code-embed-5 w-embed"><svg width="currentwidth" height="currenthieght" viewBox="0 0 30 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M0 0H30V3H0V0Z" fill="currentColor"></path>
|
<path d="M0 0H30V3H0V0Z" fill="currentColor"></path>
|
||||||
<path d="M0 7.5H30V10.5H0V7.5Z" fill="currentColor"></path>
|
<path d="M0 7.5H30V10.5H0V7.5Z" fill="currentColor"></path>
|
||||||
<path d="M0 15H30V18H0V15Z" fill="currentColor"></path>
|
<path d="M0 15H30V18H0V15Z" fill="currentColor"></path>
|
||||||
@ -759,7 +759,7 @@ const Header: React.FC<HeaderProps> = ({ onOpenAuthModal = () => console.log('Au
|
|||||||
<div className="w-layout-hflex flex-block-76">
|
<div className="w-layout-hflex flex-block-76">
|
||||||
<Link href="/profile-history" className="button_h w-inline-block">
|
<Link href="/profile-history" className="button_h w-inline-block">
|
||||||
|
|
||||||
<img src="/images/union.svg" alt="История заказов" width={22} height={10} />
|
<img src="/images/union.svg" alt="История заказов" width={20} />
|
||||||
|
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/profile-gar" className="button_h w-inline-block">
|
<Link href="/profile-gar" className="button_h w-inline-block">
|
||||||
|
@ -33,7 +33,7 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main className="pt-[62px] md:pt-[63px]">
|
<main className="pt-[62px] md:pt-[63px]">
|
||||||
<IndexTopMenuNav />
|
<IndexTopMenuNav isIndexPage={router.pathname === '/'} />
|
||||||
{children}</main>
|
{children}</main>
|
||||||
<MobileMenuBottomSection onOpenAuthModal={() => setAuthModalOpen(true)} />
|
<MobileMenuBottomSection onOpenAuthModal={() => setAuthModalOpen(true)} />
|
||||||
</>
|
</>
|
||||||
|
157
src/components/TopSalesItem.tsx
Normal file
157
src/components/TopSalesItem.tsx
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import React from "react";
|
||||||
|
import { useCart } from "@/contexts/CartContext";
|
||||||
|
import { useFavorites } from "@/contexts/FavoritesContext";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
|
interface TopSalesItemProps {
|
||||||
|
image: string;
|
||||||
|
price: string;
|
||||||
|
title: string;
|
||||||
|
brand: string;
|
||||||
|
article?: string;
|
||||||
|
productId?: string;
|
||||||
|
onAddToCart?: (e: React.MouseEvent) => void;
|
||||||
|
discount?: string; // Новый пропс для лейбла/скидки
|
||||||
|
}
|
||||||
|
|
||||||
|
const TopSalesItem: React.FC<TopSalesItemProps> = ({
|
||||||
|
image,
|
||||||
|
price,
|
||||||
|
title,
|
||||||
|
brand,
|
||||||
|
article,
|
||||||
|
productId,
|
||||||
|
onAddToCart,
|
||||||
|
discount = 'Топ продаж', // По умолчанию как раньше
|
||||||
|
}) => {
|
||||||
|
const { addItem } = useCart();
|
||||||
|
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
|
||||||
|
|
||||||
|
const isItemFavorite = isFavorite(productId, undefined, article, brand);
|
||||||
|
|
||||||
|
const parsePrice = (priceStr: string): number => {
|
||||||
|
const cleanPrice = priceStr.replace(/[^\d.,]/g, '').replace(',', '.');
|
||||||
|
return parseFloat(cleanPrice) || 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddToCart = (e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (onAddToCart) {
|
||||||
|
onAddToCart(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!article || !brand) {
|
||||||
|
toast.error('Недостаточно данных для добавления товара в корзину');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const numericPrice = parsePrice(price);
|
||||||
|
addItem({
|
||||||
|
name: title,
|
||||||
|
brand: brand,
|
||||||
|
article: article,
|
||||||
|
description: title,
|
||||||
|
price: numericPrice,
|
||||||
|
quantity: 1,
|
||||||
|
currency: 'RUB',
|
||||||
|
image: image,
|
||||||
|
isExternal: true
|
||||||
|
});
|
||||||
|
toast.success('Товар добавлен в корзину');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка добавления в корзину:', error);
|
||||||
|
toast.error('Ошибка добавления товара в корзину');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFavoriteClick = (e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (isItemFavorite) {
|
||||||
|
const favoriteItem = favorites.find((fav: any) => {
|
||||||
|
if (productId && fav.productId === productId) return true;
|
||||||
|
if (fav.article === article && fav.brand === brand) return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (favoriteItem) {
|
||||||
|
removeFromFavorites(favoriteItem.id);
|
||||||
|
toast.success('Товар удален из избранного');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const numericPrice = parsePrice(price);
|
||||||
|
addToFavorites({
|
||||||
|
productId,
|
||||||
|
name: title,
|
||||||
|
brand: brand,
|
||||||
|
article: article || '',
|
||||||
|
price: numericPrice,
|
||||||
|
currency: 'RUB',
|
||||||
|
image: image
|
||||||
|
});
|
||||||
|
toast.success('Товар добавлен в избранное');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ссылка на карточку товара (если нужно)
|
||||||
|
const cardUrl = article && brand
|
||||||
|
? `/card?article=${encodeURIComponent(article)}&brand=${encodeURIComponent(brand)}`
|
||||||
|
: '/card';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-layout-vflex flex-block-15-copy">
|
||||||
|
<div
|
||||||
|
className={`favcardcat${isItemFavorite ? ' favorite-active' : ''}`}
|
||||||
|
onClick={handleFavoriteClick}
|
||||||
|
style={{ cursor: 'pointer', color: isItemFavorite ? '#ff4444' : '#ccc' }}
|
||||||
|
>
|
||||||
|
<div className="icon-setting w-embed">
|
||||||
|
<svg width="currentwidth" height="currentheight" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" ></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="div-block-4">
|
||||||
|
<img
|
||||||
|
src={image}
|
||||||
|
loading="lazy"
|
||||||
|
width="Auto"
|
||||||
|
height="Auto"
|
||||||
|
alt={title}
|
||||||
|
className="image-5"
|
||||||
|
/>
|
||||||
|
<div className="text-block-7">{discount}</div>
|
||||||
|
</div>
|
||||||
|
<div className="div-block-3">
|
||||||
|
<div className="w-layout-hflex flex-block-16">
|
||||||
|
<div className="text-block-8">{price}</div>
|
||||||
|
{/* <div className="text-block-9">oldPrice</div> */}
|
||||||
|
</div>
|
||||||
|
<div className="w-layout-hflex flex-block-122">
|
||||||
|
<div className="w-layout-vflex">
|
||||||
|
<div className="text-block-10">{title}</div>
|
||||||
|
<div className="text-block-11">{brand}</div>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="button-icon w-inline-block"
|
||||||
|
onClick={handleAddToCart}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
aria-label="Добавить в корзину"
|
||||||
|
>
|
||||||
|
<div className="div-block-26">
|
||||||
|
<div className="icon-setting w-embed">
|
||||||
|
<svg width="currentWidht" height="currentHeight" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.1998 22.2C8.8798 22.2 7.81184 23.28 7.81184 24.6C7.81184 25.92 8.8798 27 10.1998 27C11.5197 27 12.5997 25.92 12.5997 24.6C12.5997 23.28 11.5197 22.2 10.1998 22.2ZM3 3V5.4H5.39992L9.71977 14.508L8.09982 17.448C7.90783 17.784 7.79984 18.18 7.79984 18.6C7.79984 19.92 8.8798 21 10.1998 21H24.5993V18.6H10.7037C10.5357 18.6 10.4037 18.468 10.4037 18.3L10.4397 18.156L11.5197 16.2H20.4594C21.3594 16.2 22.1513 15.708 22.5593 14.964L26.8552 7.176C26.9542 6.99286 27.004 6.78718 26.9997 6.57904C26.9955 6.37089 26.9373 6.16741 26.8309 5.98847C26.7245 5.80952 26.5736 5.66124 26.3927 5.55809C26.2119 5.45495 26.0074 5.40048 25.7992 5.4H8.05183L6.92387 3H3ZM22.1993 22.2C20.8794 22.2 19.8114 23.28 19.8114 24.6C19.8114 25.92 20.8794 27 22.1993 27C23.5193 27 24.5993 25.92 24.5993 24.6C24.5993 23.28 23.5193 22.2 22.1993 22.2Z" fill="currentColor"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TopSalesItem;
|
@ -1,28 +1,108 @@
|
|||||||
import React from "react";
|
import React, { useRef } from "react";
|
||||||
|
import { useQuery } from "@apollo/client";
|
||||||
import BestPriceItem from "../BestPriceItem";
|
import BestPriceItem from "../BestPriceItem";
|
||||||
|
import { GET_BEST_PRICE_PRODUCTS } from "../../lib/graphql";
|
||||||
|
|
||||||
// Моковые данные для лучших цен
|
interface BestPriceProductData {
|
||||||
const bestPriceItems = [
|
id: string;
|
||||||
{
|
productId: string;
|
||||||
image: "images/162615.webp",
|
discount: number;
|
||||||
discount: "-35%",
|
isActive: boolean;
|
||||||
price: "от 17 087 ₽",
|
sortOrder: number;
|
||||||
oldPrice: "22 347 ₽",
|
product: {
|
||||||
title: 'Аккумуляторная батарея TYUMEN BATTERY "STANDARD", 6CT-60L, 60',
|
id: string;
|
||||||
brand: "TYUMEN BATTERY",
|
name: string;
|
||||||
},
|
article?: string;
|
||||||
// ...добавьте еще 7 карточек для примера
|
brand?: string;
|
||||||
...Array(7).fill(0).map((_, i) => ({
|
retailPrice?: number;
|
||||||
image: "images/162615.webp",
|
images: { url: string; alt?: string }[];
|
||||||
discount: "-35%",
|
};
|
||||||
price: `от ${(17087 + i * 1000).toLocaleString('ru-RU')} ₽`,
|
}
|
||||||
oldPrice: `${(22347 + i * 1000).toLocaleString('ru-RU')} ₽`,
|
|
||||||
title: `Товар №${i + 2}`,
|
|
||||||
brand: `Бренд ${i + 2}`,
|
|
||||||
}))
|
|
||||||
];
|
|
||||||
|
|
||||||
const BestPriceSection: React.FC = () => (
|
const SCROLL_AMOUNT = 340; // px, ширина одной карточки + отступ
|
||||||
|
|
||||||
|
const BestPriceSection: React.FC = () => {
|
||||||
|
const { data, loading, error } = useQuery(GET_BEST_PRICE_PRODUCTS);
|
||||||
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const scrollLeft = () => {
|
||||||
|
if (scrollRef.current) {
|
||||||
|
scrollRef.current.scrollBy({ left: -SCROLL_AMOUNT, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const scrollRight = () => {
|
||||||
|
if (scrollRef.current) {
|
||||||
|
scrollRef.current.scrollBy({ left: SCROLL_AMOUNT, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<section className="main">
|
||||||
|
<div className="w-layout-blockcontainer container w-container">
|
||||||
|
<div className="w-layout-hflex flex-block-118">
|
||||||
|
<div className="w-layout-vflex flex-block-119">
|
||||||
|
<h1 className="heading-20">ЛУЧШАЯ ЦЕНА!</h1>
|
||||||
|
<div className="text-block-58">Загрузка...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Ошибка загрузки товаров с лучшей ценой:', error);
|
||||||
|
return (
|
||||||
|
<section className="main">
|
||||||
|
<div className="w-layout-blockcontainer container w-container">
|
||||||
|
<div className="w-layout-hflex flex-block-118">
|
||||||
|
<div className="w-layout-vflex flex-block-119">
|
||||||
|
<h1 className="heading-20">ЛУЧШАЯ ЦЕНА!</h1>
|
||||||
|
<div className="text-block-58">Ошибка загрузки данных</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bestPriceProducts: BestPriceProductData[] = data?.bestPriceProducts || [];
|
||||||
|
|
||||||
|
// Функция для форматирования цены
|
||||||
|
const formatPrice = (price?: number) => {
|
||||||
|
if (!price) return '—';
|
||||||
|
return `от ${price.toLocaleString('ru-RU')} ₽`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция для расчета цены со скидкой
|
||||||
|
const calculateDiscountedPrice = (price?: number, discount?: number) => {
|
||||||
|
if (!price || !discount) return price;
|
||||||
|
return price * (1 - discount / 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Преобразование данных для компонента BestPriceItem
|
||||||
|
const bestPriceItems = bestPriceProducts
|
||||||
|
.filter(item => item.isActive)
|
||||||
|
.sort((a, b) => a.sortOrder - b.sortOrder)
|
||||||
|
.slice(0, 8) // Ограничиваем до 8 товаров
|
||||||
|
.map(item => ({
|
||||||
|
image: item.product.images?.[0]?.url || "images/162615.webp", // Fallback изображение
|
||||||
|
discount: `-${item.discount}%`,
|
||||||
|
price: formatPrice(calculateDiscountedPrice(item.product.retailPrice, item.discount)),
|
||||||
|
oldPrice: formatPrice(item.product.retailPrice),
|
||||||
|
title: item.product.name,
|
||||||
|
brand: item.product.brand || "",
|
||||||
|
article: item.product.article,
|
||||||
|
productId: item.product.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Если нет товаров, не показываем секцию
|
||||||
|
if (bestPriceItems.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<section className="main">
|
<section className="main">
|
||||||
<div className="w-layout-blockcontainer container w-container">
|
<div className="w-layout-blockcontainer container w-container">
|
||||||
<div className="w-layout-hflex flex-block-118">
|
<div className="w-layout-hflex flex-block-118">
|
||||||
@ -31,14 +111,29 @@ const BestPriceSection: React.FC = () => (
|
|||||||
<div className="text-block-58">Подборка лучших предложенийпо цене</div>
|
<div className="text-block-58">Подборка лучших предложенийпо цене</div>
|
||||||
<a href="#" className="button-24 w-button">Показать все</a>
|
<a href="#" className="button-24 w-button">Показать все</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-layout-hflex flex-block-121">
|
<div className="carousel-row">
|
||||||
|
<button className="carousel-arrow carousel-arrow-left" onClick={scrollLeft} aria-label="Прокрутить влево">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
|
||||||
|
<path d="M19.5 24L12.5 16L19.5 8" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div className="w-layout-hflex flex-block-121 carousel-scroll" ref={scrollRef}>
|
||||||
{bestPriceItems.map((item, i) => (
|
{bestPriceItems.map((item, i) => (
|
||||||
<BestPriceItem key={i} {...item} />
|
<BestPriceItem key={i} {...item} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<button className="carousel-arrow carousel-arrow-right" onClick={scrollRight} aria-label="Прокрутить вправо">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
|
||||||
|
<path d="M12.5 8L19.5 16L12.5 24" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default BestPriceSection;
|
export default BestPriceSection;
|
@ -1,8 +1,9 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState, useMemo, useRef } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { GET_LAXIMO_BRANDS } from "@/lib/graphql";
|
import { GET_LAXIMO_BRANDS } from "@/lib/graphql";
|
||||||
import { LaximoBrand } from "@/types/laximo";
|
import { LaximoBrand } from "@/types/laximo";
|
||||||
|
import { Combobox } from '@headlessui/react';
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
"Техническое обслуживание",
|
"Техническое обслуживание",
|
||||||
@ -15,7 +16,8 @@ type Brand = { name: string; code?: string };
|
|||||||
|
|
||||||
const BrandSelectionSection: React.FC = () => {
|
const BrandSelectionSection: React.FC = () => {
|
||||||
const [activeTab, setActiveTab] = useState(0);
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
const [selectedBrand, setSelectedBrand] = useState<string>("");
|
const [selectedBrand, setSelectedBrand] = useState<Brand | null>(null);
|
||||||
|
const [brandQuery, setBrandQuery] = useState('');
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const { data, loading, error } = useQuery<{ laximoBrands: LaximoBrand[] }>(GET_LAXIMO_BRANDS, {
|
const { data, loading, error } = useQuery<{ laximoBrands: LaximoBrand[] }>(GET_LAXIMO_BRANDS, {
|
||||||
@ -42,6 +44,12 @@ const BrandSelectionSection: React.FC = () => {
|
|||||||
console.warn('Laximo API недоступен, используются статические данные:', error.message);
|
console.warn('Laximo API недоступен, используются статические данные:', error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Combobox фильтрация
|
||||||
|
const filteredBrands = useMemo(() => {
|
||||||
|
if (!brandQuery) return brands;
|
||||||
|
return brands.filter(b => b.name.toLowerCase().includes(brandQuery.toLowerCase()));
|
||||||
|
}, [brands, brandQuery]);
|
||||||
|
|
||||||
const handleBrandClick = (brand: Brand) => {
|
const handleBrandClick = (brand: Brand) => {
|
||||||
if (brand.code) {
|
if (brand.code) {
|
||||||
router.push(`/brands?selected=${brand.code}`);
|
router.push(`/brands?selected=${brand.code}`);
|
||||||
@ -53,7 +61,7 @@ const BrandSelectionSection: React.FC = () => {
|
|||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (selectedBrand) {
|
if (selectedBrand) {
|
||||||
const found = brands.find(b => b.code === selectedBrand || b.name === selectedBrand);
|
const found = brands.find(b => b.code === selectedBrand.code || b.name === selectedBrand.name);
|
||||||
if (found && found.code) {
|
if (found && found.code) {
|
||||||
router.push(`/brands?selected=${found.code}`);
|
router.push(`/brands?selected=${found.code}`);
|
||||||
return;
|
return;
|
||||||
@ -123,19 +131,44 @@ const BrandSelectionSection: React.FC = () => {
|
|||||||
<h1 className="heading-21">ПОДБОР АВТОЗАПЧАСТЕЙ ПО МАРКЕ АВТО</h1>
|
<h1 className="heading-21">ПОДБОР АВТОЗАПЧАСТЕЙ ПО МАРКЕ АВТО</h1>
|
||||||
<div className="form-block-4 w-form">
|
<div className="form-block-4 w-form">
|
||||||
<form id="email-form" name="email-form" data-name="Email Form" method="post" data-wf-page-id="685be6dfd87db2e01cbdb7a2" data-wf-element-id="e673036c-0caf-d251-3b66-9ba9cb85064c" onSubmit={handleSubmit}>
|
<form id="email-form" name="email-form" data-name="Email Form" method="post" data-wf-page-id="685be6dfd87db2e01cbdb7a2" data-wf-element-id="e673036c-0caf-d251-3b66-9ba9cb85064c" onSubmit={handleSubmit}>
|
||||||
<select
|
<div style={{ width: 180, marginBottom: 16 }}>
|
||||||
id="field-7"
|
<Combobox value={selectedBrand} onChange={setSelectedBrand} nullable>
|
||||||
name="field-7"
|
<div className="relative">
|
||||||
data-name="Field 7"
|
<Combobox.Input
|
||||||
className="select-copy w-select"
|
className="w-full px-6 py-4 bg-white rounded border border-stone-300 text-sm text-gray-950 placeholder:text-neutral-500 outline-none focus:shadow-none focus:border-stone-300 transition-colors"
|
||||||
value={selectedBrand}
|
displayValue={(brand: Brand | null) => brand?.name || ''}
|
||||||
onChange={e => setSelectedBrand(e.target.value)}
|
onChange={e => setBrandQuery(e.target.value)}
|
||||||
|
placeholder="Марка"
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center px-3 focus:outline-none w-12">
|
||||||
|
<svg className="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 9l6 6 6-6" />
|
||||||
|
</svg>
|
||||||
|
</Combobox.Button>
|
||||||
|
<Combobox.Options
|
||||||
|
className="absolute left-0 top-full z-10 bg-white border-x border-b border-stone-300 rounded-b-lg shadow-lg w-full max-h-60 overflow-auto scrollbar-none"
|
||||||
|
style={{ scrollbarWidth: 'none' }}
|
||||||
|
data-hide-scrollbar
|
||||||
>
|
>
|
||||||
<option value="">Марка</option>
|
{filteredBrands.length === 0 && (
|
||||||
{brands.map((brand, idx) => (
|
<div className="px-6 py-4 text-gray-500">Бренды не найдены</div>
|
||||||
<option value={brand.code || brand.name} key={idx}>{brand.name}</option>
|
)}
|
||||||
|
{filteredBrands.map(brand => (
|
||||||
|
<Combobox.Option
|
||||||
|
key={brand.code || brand.name}
|
||||||
|
value={brand}
|
||||||
|
className={({ active, selected }) =>
|
||||||
|
`px-6 py-4 cursor-pointer hover:!bg-[rgb(236,28,36)] hover:!text-white text-sm transition-colors ${selected ? 'bg-red-50 font-semibold text-gray-950' : 'text-neutral-500'}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{brand.name}
|
||||||
|
</Combobox.Option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</Combobox.Options>
|
||||||
|
</div>
|
||||||
|
</Combobox>
|
||||||
|
</div>
|
||||||
<div className="div-block-10-copy">
|
<div className="div-block-10-copy">
|
||||||
<input type="submit" data-wait="Please wait..." className="button-3-copy w-button" value="Далее" />
|
<input type="submit" data-wait="Please wait..." className="button-3-copy w-button" value="Далее" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
const IndexTopMenuNav = () => (
|
const IndexTopMenuNav = ({ isIndexPage = false }: { isIndexPage?: boolean }) => (
|
||||||
<section className="topmenub">
|
<section className={`topmenub${!isIndexPage ? ' topmenub-white' : ''}`} style={!isIndexPage ? { background: '#fff' } : undefined}>
|
||||||
<div className="w-layout-blockcontainer tb nav w-container">
|
<div className="w-layout-blockcontainer tb nav w-container">
|
||||||
<div className="w-layout-hflex flex-block-107">
|
<div className="w-layout-hflex flex-block-107">
|
||||||
<Link href="/about" className="link-block-8 w-inline-block">
|
<Link href="/about" className="link-block-8 w-inline-block">
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import React, { useRef } from "react";
|
||||||
import ArticleCard from "../ArticleCard";
|
import ArticleCard from "../ArticleCard";
|
||||||
import { PartsAPIArticle } from "@/types/partsapi";
|
import { PartsAPIArticle } from "@/types/partsapi";
|
||||||
|
|
||||||
@ -36,21 +36,52 @@ const newArrivalsArticles: PartsAPIArticle[] = [
|
|||||||
|
|
||||||
const imagePath = "images/162615.webp";
|
const imagePath = "images/162615.webp";
|
||||||
|
|
||||||
const NewArrivalsSection: React.FC = () => (
|
const SCROLL_AMOUNT = 340; // px, ширина одной карточки + отступ
|
||||||
|
|
||||||
|
const NewArrivalsSection: React.FC = () => {
|
||||||
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const scrollLeft = () => {
|
||||||
|
if (scrollRef.current) {
|
||||||
|
scrollRef.current.scrollBy({ left: -SCROLL_AMOUNT, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const scrollRight = () => {
|
||||||
|
if (scrollRef.current) {
|
||||||
|
scrollRef.current.scrollBy({ left: SCROLL_AMOUNT, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
<section className="main">
|
<section className="main">
|
||||||
<div className="w-layout-blockcontainer container w-container">
|
<div className="w-layout-blockcontainer container w-container">
|
||||||
<div className="w-layout-vflex inbt">
|
<div className="w-layout-vflex inbt">
|
||||||
<div className="w-layout-hflex flex-block-31">
|
<div className="w-layout-hflex flex-block-31">
|
||||||
<h2 className="heading-4">Новое поступление</h2>
|
<h2 className="heading-4">Новое поступление</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-layout-hflex core-product-search">
|
<div className="carousel-row">
|
||||||
|
<button className="carousel-arrow carousel-arrow-left" onClick={scrollLeft} aria-label="Прокрутить влево">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
|
||||||
|
<path d="M19.5 24L12.5 16L19.5 8" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div className="w-layout-hflex core-product-search carousel-scroll" ref={scrollRef}>
|
||||||
{newArrivalsArticles.map((article, i) => (
|
{newArrivalsArticles.map((article, i) => (
|
||||||
<ArticleCard key={article.artId || i} article={{ ...article, artId: article.artId }} index={i} image={imagePath} />
|
<ArticleCard key={article.artId || i} article={{ ...article, artId: article.artId }} index={i} image={imagePath} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<button className="carousel-arrow carousel-arrow-right" onClick={scrollRight} aria-label="Прокрутить вправо">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
|
||||||
|
<path d="M12.5 8L19.5 16L12.5 24" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default NewArrivalsSection;
|
export default NewArrivalsSection;
|
@ -1,8 +1,24 @@
|
|||||||
import React from "react";
|
import React, { useRef } from "react";
|
||||||
import NewsCard from "@/components/news/NewsCard";
|
import NewsCard from "@/components/news/NewsCard";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
const NewsAndPromos = () => (
|
const SCROLL_AMOUNT = 340; // px, ширина одной карточки + отступ
|
||||||
|
|
||||||
|
const NewsAndPromos = () => {
|
||||||
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const scrollLeft = () => {
|
||||||
|
if (scrollRef.current) {
|
||||||
|
scrollRef.current.scrollBy({ left: -SCROLL_AMOUNT, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const scrollRight = () => {
|
||||||
|
if (scrollRef.current) {
|
||||||
|
scrollRef.current.scrollBy({ left: SCROLL_AMOUNT, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
<section className="main">
|
<section className="main">
|
||||||
<div className="w-layout-blockcontainer container w-container">
|
<div className="w-layout-blockcontainer container w-container">
|
||||||
<div className="w-layout-vflex news-index-block">
|
<div className="w-layout-vflex news-index-block">
|
||||||
@ -15,7 +31,14 @@ const NewsAndPromos = () => (
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-layout-hflex flex-block-6-copy-copy">
|
<div className="carousel-row">
|
||||||
|
<button className="carousel-arrow carousel-arrow-left" onClick={scrollLeft} aria-label="Прокрутить влево">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
|
||||||
|
<path d="M19.5 24L12.5 16L19.5 8" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div className="w-layout-hflex flex-block-6-copy-copy carousel-scroll" ref={scrollRef}>
|
||||||
<NewsCard
|
<NewsCard
|
||||||
title="Kia Syros будет выделяться необычным стилем"
|
title="Kia Syros будет выделяться необычным стилем"
|
||||||
description="Компания Kia готова представить новый кроссовер Syros"
|
description="Компания Kia готова представить новый кроссовер Syros"
|
||||||
@ -45,9 +68,17 @@ const NewsAndPromos = () => (
|
|||||||
image="/images/news_img.png"
|
image="/images/news_img.png"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<button className="carousel-arrow carousel-arrow-right" onClick={scrollRight} aria-label="Прокрутить вправо">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
|
||||||
|
<path d="M12.5 8L19.5 16L12.5 24" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default NewsAndPromos;
|
export default NewsAndPromos;
|
@ -1,67 +1,350 @@
|
|||||||
import React from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
|
import { useQuery } from '@apollo/client';
|
||||||
|
import { GET_DAILY_PRODUCTS, PARTS_INDEX_SEARCH_BY_ARTICLE } from '@/lib/graphql';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
const ProductOfDaySection: React.FC = () => (
|
interface DailyProduct {
|
||||||
|
id: string;
|
||||||
|
discount?: number;
|
||||||
|
isActive: boolean;
|
||||||
|
sortOrder: number;
|
||||||
|
product: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
article?: string;
|
||||||
|
brand?: string;
|
||||||
|
retailPrice?: number;
|
||||||
|
wholesalePrice?: number;
|
||||||
|
images: Array<{
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
alt?: string;
|
||||||
|
order: number;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductOfDaySection: React.FC = () => {
|
||||||
|
// Получаем текущую дату в формате YYYY-MM-DD
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
|
// Состояние для текущего слайда
|
||||||
|
const [currentSlide, setCurrentSlide] = useState(0);
|
||||||
|
const sliderRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const { data, loading, error } = useQuery<{ dailyProducts: DailyProduct[] }>(
|
||||||
|
GET_DAILY_PRODUCTS,
|
||||||
|
{
|
||||||
|
variables: { displayDate: today },
|
||||||
|
errorPolicy: 'all'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Фильтруем только активные товары и сортируем по sortOrder
|
||||||
|
const activeProducts = React.useMemo(() => {
|
||||||
|
if (!data?.dailyProducts) return [];
|
||||||
|
return data.dailyProducts
|
||||||
|
.filter(item => item.isActive)
|
||||||
|
.sort((a, b) => a.sortOrder - b.sortOrder);
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
// Получаем данные из PartsIndex для текущего товара
|
||||||
|
const currentProduct = activeProducts[currentSlide];
|
||||||
|
const { data: partsIndexData } = useQuery(
|
||||||
|
PARTS_INDEX_SEARCH_BY_ARTICLE,
|
||||||
|
{
|
||||||
|
variables: {
|
||||||
|
articleNumber: currentProduct?.product?.article || '',
|
||||||
|
brandName: currentProduct?.product?.brand || '',
|
||||||
|
lang: 'ru'
|
||||||
|
},
|
||||||
|
skip: !currentProduct?.product?.article || !currentProduct?.product?.brand,
|
||||||
|
errorPolicy: 'ignore'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Функция для расчета цены со скидкой
|
||||||
|
const calculateDiscountedPrice = (price: number, discount?: number) => {
|
||||||
|
if (!discount) return price;
|
||||||
|
return price * (1 - discount / 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция для форматирования цены
|
||||||
|
const formatPrice = (price: number) => {
|
||||||
|
return new Intl.NumberFormat('ru-RU').format(Math.round(price));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция для получения изображения товара
|
||||||
|
const getProductImage = (product: DailyProduct['product']) => {
|
||||||
|
// Сначала пытаемся использовать собственные изображения товара
|
||||||
|
const productImage = product.images
|
||||||
|
?.sort((a, b) => a.order - b.order)
|
||||||
|
?.[0];
|
||||||
|
|
||||||
|
if (productImage) {
|
||||||
|
return {
|
||||||
|
url: productImage.url,
|
||||||
|
alt: productImage.alt || product.name,
|
||||||
|
source: 'internal'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если нет собственных изображений, используем PartsIndex
|
||||||
|
const partsIndexImage = partsIndexData?.partsIndexSearchByArticle?.images?.[0];
|
||||||
|
if (partsIndexImage) {
|
||||||
|
return {
|
||||||
|
url: partsIndexImage,
|
||||||
|
alt: product.name,
|
||||||
|
source: 'partsindex'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Обработчики для слайдера
|
||||||
|
const handlePrevSlide = (e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setCurrentSlide(prev => prev === 0 ? activeProducts.length - 1 : prev - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNextSlide = (e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setCurrentSlide(prev => prev === activeProducts.length - 1 ? 0 : prev + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePrevSlideTouch = (e: React.TouchEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setCurrentSlide(prev => prev === 0 ? activeProducts.length - 1 : prev - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNextSlideTouch = (e: React.TouchEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setCurrentSlide(prev => prev === activeProducts.length - 1 ? 0 : prev + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSlideIndicator = (index: number) => {
|
||||||
|
setCurrentSlide(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Сброс слайда при изменении товаров
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentSlide(0);
|
||||||
|
}, [activeProducts]);
|
||||||
|
|
||||||
|
// Если нет активных товаров дня, не показываем секцию
|
||||||
|
if (loading || error || activeProducts.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const product = currentProduct.product;
|
||||||
|
const productImage = getProductImage(product);
|
||||||
|
|
||||||
|
const originalPrice = product.retailPrice || product.wholesalePrice || 0;
|
||||||
|
const discountedPrice = calculateDiscountedPrice(originalPrice, currentProduct.discount);
|
||||||
|
const hasDiscount = currentProduct.discount && currentProduct.discount > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
<section className="main">
|
<section className="main">
|
||||||
<div className="w-layout-blockcontainer batd w-container">
|
<div className="w-layout-blockcontainer batd w-container">
|
||||||
<div className="w-layout-hflex flex-block-108">
|
<div className="w-layout-hflex flex-block-108">
|
||||||
<div data-delay="4000" data-animation="slide" className="slider w-slider" data-autoplay="false" data-easing="ease" data-hide-arrows="false" data-disable-swipe="false" data-autoplay-limit="0" data-nav-spacing="3" data-duration="500" data-infinite="true">
|
<div
|
||||||
|
ref={sliderRef}
|
||||||
|
className="slider w-slider"
|
||||||
|
>
|
||||||
<div className="mask w-slider-mask">
|
<div className="mask w-slider-mask">
|
||||||
<div className="slide w-slide">
|
{activeProducts.map((_, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`slide w-slide ${index === currentSlide ? 'w--current' : ''}`}
|
||||||
|
style={{
|
||||||
|
display: index === currentSlide ? 'block' : 'none'
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className="div-block-128"></div>
|
<div className="div-block-128"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-slide"></div>
|
))}
|
||||||
<div className="w-slide"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Стрелки слайдера (показываем только если товаров больше 1) */}
|
||||||
|
{activeProducts.length > 1 && (
|
||||||
|
<>
|
||||||
<div className="left-arrow w-slider-arrow-left">
|
<div className="left-arrow w-slider-arrow-left">
|
||||||
<div className="div-block-34">
|
<div className="div-block-34">
|
||||||
<div className="code-embed-14 w-embed"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<div className="code-embed-14 w-embed">
|
||||||
|
<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" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||||
</svg></div>
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="right-arrow w-slider-arrow-right">
|
<div className="right-arrow w-slider-arrow-right">
|
||||||
<div className="div-block-34 right">
|
<div className="div-block-34 right">
|
||||||
<div className="code-embed-14 w-embed"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<div className="code-embed-14 w-embed">
|
||||||
|
<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" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||||
</svg></div>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="slide-nav w-slider-nav w-slider-nav-invert w-round"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Индикаторы слайдов */}
|
||||||
|
{activeProducts.length > 1 && (
|
||||||
|
<div className="slide-nav w-slider-nav w-slider-nav-invert w-round">
|
||||||
|
{activeProducts.map((_, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`w-slider-dot ${index === currentSlide ? 'w--current' : ''}`}
|
||||||
|
onClick={() => handleSlideIndicator(index)}
|
||||||
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
|
style={{ cursor: 'pointer', zIndex: 10 }}
|
||||||
|
></div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="div-block-129">
|
<div className="div-block-129">
|
||||||
<div className="w-layout-hflex flex-block-109">
|
<div className="w-layout-hflex flex-block-109">
|
||||||
<h1 className="heading-18">ТОВАРЫ ДНЯ</h1>
|
<h1 className="heading-18">ТОВАРЫ ДНЯ</h1>
|
||||||
<div className="saletag">-35%</div>
|
{hasDiscount && (
|
||||||
|
<div className="saletag">-{currentProduct.discount}%</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-layout-hflex flex-block-110">
|
<div className="w-layout-hflex flex-block-110">
|
||||||
<div className="w-layout-vflex flex-block-111">
|
<div className="w-layout-vflex flex-block-111">
|
||||||
<div className="w-layout-hflex flex-block-16">
|
<div className="w-layout-hflex flex-block-16">
|
||||||
<div className="text-block-8">от 17 087 ₽</div>
|
<div className="text-block-8">
|
||||||
<div className="text-block-9">22 347 ₽</div>
|
от {formatPrice(discountedPrice)} ₽
|
||||||
</div>
|
</div>
|
||||||
<div className="text-block-10">Аккумуляторная батарея TYUMEN BATTERY "STANDARD", 6CT-60L, 60</div>
|
{hasDiscount && (
|
||||||
</div><img width="Auto" height="Auto" alt="" src="/images/162615.webp" loading="lazy" srcSet="/images/162615-p-500.webp 500w, /images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" className="image-5-copy" />
|
<div className="text-block-9">
|
||||||
|
{formatPrice(originalPrice)} ₽
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="text-block-10" title={product.name}>
|
||||||
|
{product.brand && `${product.brand} `}
|
||||||
|
{product.name}
|
||||||
|
</div>
|
||||||
|
{/* Счетчик товаров если их больше одного */}
|
||||||
|
{/* {activeProducts.length > 1 && (
|
||||||
|
<div className="text-xs text-gray-500 mt-2">
|
||||||
|
{currentSlide + 1} из {activeProducts.length}
|
||||||
|
</div>
|
||||||
|
)} */}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{productImage && (
|
||||||
|
<div className="relative">
|
||||||
|
<img
|
||||||
|
width="Auto"
|
||||||
|
height="Auto"
|
||||||
|
alt={productImage.alt}
|
||||||
|
src={productImage.url}
|
||||||
|
loading="lazy"
|
||||||
|
className="image-5-copy"
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
/>
|
||||||
|
{/* Метка источника изображения */}
|
||||||
|
{productImage.source === 'partsindex' && (
|
||||||
|
<div className="absolute bottom-0 right-0 bg-blue-600 text-white text-xs px-2 py-1 rounded-tl">
|
||||||
|
Parts Index
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="w-layout-hflex flex-block-125">
|
<div className="w-layout-hflex flex-block-125">
|
||||||
<div className="div-block-134">
|
{/* Левая стрелка - предыдущий товар */}
|
||||||
<div className="code-embed-17 w-embed"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
{activeProducts.length > 1 ? (
|
||||||
|
<div
|
||||||
|
className="div-block-134"
|
||||||
|
onClick={handlePrevSlide}
|
||||||
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
|
onTouchStart={handlePrevSlideTouch}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
title="Предыдущий товар"
|
||||||
|
>
|
||||||
|
<div className="code-embed-17 w-embed">
|
||||||
|
<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" stroke="currentcolor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentcolor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||||
</svg></div>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div className="div-block-134-copy">
|
</div>
|
||||||
<div className="code-embed-17 w-embed"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
) : (
|
||||||
|
<div className="div-block-134" style={{ opacity: 0.3 }}>
|
||||||
|
<div className="code-embed-17 w-embed">
|
||||||
|
<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" stroke="currentcolor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentcolor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||||
</svg></div>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Правая стрелка - следующий товар */}
|
||||||
|
{activeProducts.length > 1 ? (
|
||||||
|
<div
|
||||||
|
className="div-block-134-copy"
|
||||||
|
onClick={handleNextSlide}
|
||||||
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
|
onTouchStart={handleNextSlideTouch}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
title="Следующий товар"
|
||||||
|
>
|
||||||
|
<div className="code-embed-17 w-embed">
|
||||||
|
<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" stroke="currentcolor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="div-block-134-copy" style={{ opacity: 0.3 }}>
|
||||||
|
<div className="code-embed-17 w-embed">
|
||||||
|
<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" stroke="currentcolor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Индикаторы точки */}
|
||||||
<div className="w-layout-hflex flex-block-126">
|
<div className="w-layout-hflex flex-block-126">
|
||||||
<div className="div-block-135"></div>
|
{activeProducts.length > 1 ? (
|
||||||
<div className="div-block-135"></div>
|
activeProducts.map((_, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="div-block-135"
|
||||||
|
onClick={() => handleSlideIndicator(index)}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
opacity: index === currentSlide ? 1 : 0.5,
|
||||||
|
backgroundColor: index === currentSlide ? 'currentColor' : 'rgba(128,128,128,0.5)'
|
||||||
|
}}
|
||||||
|
title={`Товар ${index + 1}`}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="div-block-135" style={{ backgroundColor: 'currentColor' }}></div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default ProductOfDaySection;
|
export default ProductOfDaySection;
|
@ -1,54 +1,192 @@
|
|||||||
import React from "react";
|
import React, { useRef } from "react";
|
||||||
import ArticleCard from "../ArticleCard";
|
import { useQuery } from "@apollo/client";
|
||||||
import { PartsAPIArticle } from "@/types/partsapi";
|
import TopSalesItem from "../TopSalesItem";
|
||||||
|
import { GET_TOP_SALES_PRODUCTS } from "../../lib/graphql";
|
||||||
|
|
||||||
// Моковые данные для топ продаж
|
interface TopSalesProductData {
|
||||||
const topSalesArticles: PartsAPIArticle[] = [
|
id: string;
|
||||||
{
|
productId: string;
|
||||||
artId: "1",
|
isActive: boolean;
|
||||||
artArticleNr: "6CT-60L",
|
sortOrder: number;
|
||||||
artSupBrand: "TYUMEN BATTERY",
|
product: {
|
||||||
supBrand: "TYUMEN BATTERY",
|
id: string;
|
||||||
supId: 1,
|
name: string;
|
||||||
productGroup: "Аккумуляторная батарея",
|
article?: string;
|
||||||
ptId: 1,
|
brand?: string;
|
||||||
},
|
retailPrice?: number;
|
||||||
{
|
images: { url: string; alt?: string }[];
|
||||||
artId: "2",
|
};
|
||||||
artArticleNr: "A0001",
|
}
|
||||||
artSupBrand: "Borsehung",
|
|
||||||
supBrand: "Borsehung",
|
|
||||||
supId: 2,
|
|
||||||
productGroup: "Масляный фильтр",
|
|
||||||
ptId: 2,
|
|
||||||
},
|
|
||||||
// ...добавьте еще 6 статей для примера
|
|
||||||
...Array(6).fill(0).map((_, i) => ({
|
|
||||||
artId: `${i+3}`,
|
|
||||||
artArticleNr: `ART${i+3}`,
|
|
||||||
artSupBrand: `Brand${i+3}`,
|
|
||||||
supBrand: `Brand${i+3}`,
|
|
||||||
supId: i+3,
|
|
||||||
productGroup: `Product Group ${i+3}`,
|
|
||||||
ptId: i+3,
|
|
||||||
}))
|
|
||||||
];
|
|
||||||
|
|
||||||
const TopSalesSection: React.FC = () => (
|
const SCROLL_AMOUNT = 340; // px, ширина одной карточки + отступ
|
||||||
|
|
||||||
|
const TopSalesSection: React.FC = () => {
|
||||||
|
const { data, loading, error } = useQuery(GET_TOP_SALES_PRODUCTS);
|
||||||
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const scrollLeft = () => {
|
||||||
|
if (scrollRef.current) {
|
||||||
|
scrollRef.current.scrollBy({ left: -SCROLL_AMOUNT, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const scrollRight = () => {
|
||||||
|
if (scrollRef.current) {
|
||||||
|
scrollRef.current.scrollBy({ left: SCROLL_AMOUNT, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
<section className="main">
|
<section className="main">
|
||||||
<div className="w-layout-blockcontainer container w-container">
|
<div className="w-layout-blockcontainer container w-container">
|
||||||
<div className="w-layout-vflex inbt">
|
<div className="w-layout-vflex inbt">
|
||||||
<div className="w-layout-hflex flex-block-31">
|
<div className="w-layout-hflex flex-block-31">
|
||||||
<h2 className="heading-4">Топ продаж</h2>
|
<h2 className="heading-4">Топ продаж</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-layout-hflex core-product-search">
|
<div className="carousel-row">
|
||||||
{topSalesArticles.map((article, i) => (
|
<button className="carousel-arrow carousel-arrow-left" onClick={scrollLeft} aria-label="Прокрутить влево">
|
||||||
<ArticleCard key={article.artId || i} article={article} index={i} />
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
))}
|
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
|
||||||
|
<path d="M19.5 24L12.5 16L19.5 8" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div className="w-layout-hflex core-product-search carousel-scroll" ref={scrollRef}>
|
||||||
|
<div className="text-block-58">Загрузка...</div>
|
||||||
|
</div>
|
||||||
|
<button className="carousel-arrow carousel-arrow-right" onClick={scrollRight} aria-label="Прокрутить вправо">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
|
||||||
|
<path d="M12.5 8L19.5 16L12.5 24" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Ошибка загрузки топ продаж:', error);
|
||||||
|
return (
|
||||||
|
<section className="main">
|
||||||
|
<div className="w-layout-blockcontainer container w-container">
|
||||||
|
<div className="w-layout-vflex inbt">
|
||||||
|
<div className="w-layout-hflex flex-block-31">
|
||||||
|
<h2 className="heading-4">Топ продаж</h2>
|
||||||
|
</div>
|
||||||
|
<div className="carousel-row">
|
||||||
|
<button className="carousel-arrow carousel-arrow-left" onClick={scrollLeft} aria-label="Прокрутить влево">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
|
||||||
|
<path d="M19.5 24L12.5 16L19.5 8" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div className="w-layout-hflex core-product-search carousel-scroll" ref={scrollRef}>
|
||||||
|
<div className="text-block-58">Ошибка загрузки</div>
|
||||||
|
</div>
|
||||||
|
<button className="carousel-arrow carousel-arrow-right" onClick={scrollRight} aria-label="Прокрутить вправо">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
|
||||||
|
<path d="M12.5 8L19.5 16L12.5 24" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Фильтруем активные товары и сортируем по sortOrder
|
||||||
|
const activeTopSalesProducts = (data?.topSalesProducts || [])
|
||||||
|
.filter((item: TopSalesProductData) => item.isActive)
|
||||||
|
.sort((a: TopSalesProductData, b: TopSalesProductData) => a.sortOrder - b.sortOrder)
|
||||||
|
.slice(0, 8); // Ограничиваем до 8 товаров
|
||||||
|
|
||||||
|
if (activeTopSalesProducts.length === 0) {
|
||||||
|
return (
|
||||||
|
<section className="main">
|
||||||
|
<div className="w-layout-blockcontainer container w-container">
|
||||||
|
<div className="w-layout-vflex inbt">
|
||||||
|
<div className="w-layout-hflex flex-block-31">
|
||||||
|
<h2 className="heading-4">Топ продаж</h2>
|
||||||
|
</div>
|
||||||
|
<div className="carousel-row">
|
||||||
|
<button className="carousel-arrow carousel-arrow-left" onClick={scrollLeft} aria-label="Прокрутить влево">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
|
||||||
|
<path d="M19.5 24L12.5 16L19.5 8" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div className="w-layout-hflex core-product-search carousel-scroll" ref={scrollRef}>
|
||||||
|
<div className="text-block-58">Нет товаров в топ продаж</div>
|
||||||
|
</div>
|
||||||
|
<button className="carousel-arrow carousel-arrow-right" onClick={scrollRight} aria-label="Прокрутить вправо">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
|
||||||
|
<path d="M12.5 8L19.5 16L12.5 24" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="main">
|
||||||
|
<div className="w-layout-blockcontainer container w-container">
|
||||||
|
<div className="w-layout-vflex inbt">
|
||||||
|
<div className="w-layout-hflex flex-block-31">
|
||||||
|
<h2 className="heading-4">Топ продаж</h2>
|
||||||
|
</div>
|
||||||
|
<div className="carousel-row">
|
||||||
|
<button className="carousel-arrow carousel-arrow-left" onClick={scrollLeft} aria-label="Прокрутить влево">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
|
||||||
|
<path d="M19.5 24L12.5 16L19.5 8" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div className="w-layout-hflex core-product-search carousel-scroll" ref={scrollRef}>
|
||||||
|
{activeTopSalesProducts.map((item: TopSalesProductData) => {
|
||||||
|
const product = item.product;
|
||||||
|
const price = product.retailPrice
|
||||||
|
? `от ${product.retailPrice.toLocaleString('ru-RU')} ₽`
|
||||||
|
: 'По запросу';
|
||||||
|
|
||||||
|
const image = product.images && product.images.length > 0
|
||||||
|
? product.images[0].url
|
||||||
|
: '/images/162615.webp'; // Fallback изображение
|
||||||
|
|
||||||
|
const title = product.name;
|
||||||
|
const brand = product.brand || 'Неизвестный бренд';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TopSalesItem
|
||||||
|
key={item.id}
|
||||||
|
image={image}
|
||||||
|
price={price}
|
||||||
|
title={title}
|
||||||
|
brand={brand}
|
||||||
|
article={product.article}
|
||||||
|
productId={product.id}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<button className="carousel-arrow carousel-arrow-right" onClick={scrollRight} aria-label="Прокрутить вправо">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
|
||||||
|
<path d="M12.5 8L19.5 16L12.5 24" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default TopSalesSection;
|
export default TopSalesSection;
|
@ -222,7 +222,7 @@ const KnotIn: React.FC<KnotInProps> = ({ catalogCode, vehicleId, ssd, unitId, un
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="relative inline-block">
|
<div className="relative inline-block p-5" style={{ borderRadius: 8, background: '#fff' }}>
|
||||||
{/* ВРЕМЕННО: выводим количество точек для быстрой проверки */}
|
{/* ВРЕМЕННО: выводим количество точек для быстрой проверки */}
|
||||||
{/* <div style={{ position: 'absolute', top: 4, left: 4, zIndex: 20, background: 'rgba(255,0,0,0.1)', color: '#c00', fontWeight: 700, fontSize: 14, padding: '2px 8px', borderRadius: 6 }}>
|
{/* <div style={{ position: 'absolute', top: 4, left: 4, zIndex: 20, background: 'rgba(255,0,0,0.1)', color: '#c00', fontWeight: 700, fontSize: 14, padding: '2px 8px', borderRadius: 6 }}>
|
||||||
{coordinates.length} точек
|
{coordinates.length} точек
|
||||||
|
@ -1,5 +1,50 @@
|
|||||||
import { gql } from '@apollo/client'
|
import { gql } from '@apollo/client'
|
||||||
|
|
||||||
|
export const GET_BEST_PRICE_PRODUCTS = gql`
|
||||||
|
query GetBestPriceProducts {
|
||||||
|
bestPriceProducts {
|
||||||
|
id
|
||||||
|
productId
|
||||||
|
discount
|
||||||
|
isActive
|
||||||
|
sortOrder
|
||||||
|
product {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
article
|
||||||
|
brand
|
||||||
|
retailPrice
|
||||||
|
images {
|
||||||
|
url
|
||||||
|
alt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const GET_TOP_SALES_PRODUCTS = gql`
|
||||||
|
query GetTopSalesProducts {
|
||||||
|
topSalesProducts {
|
||||||
|
id
|
||||||
|
productId
|
||||||
|
isActive
|
||||||
|
sortOrder
|
||||||
|
product {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
article
|
||||||
|
brand
|
||||||
|
retailPrice
|
||||||
|
images {
|
||||||
|
url
|
||||||
|
alt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
export const CHECK_CLIENT_BY_PHONE = gql`
|
export const CHECK_CLIENT_BY_PHONE = gql`
|
||||||
mutation CheckClientByPhone($phone: String!) {
|
mutation CheckClientByPhone($phone: String!) {
|
||||||
checkClientByPhone(phone: $phone) {
|
checkClientByPhone(phone: $phone) {
|
||||||
@ -1607,3 +1652,30 @@ export const GET_CATEGORY_PRODUCTS_WITH_OFFERS = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// Запрос для получения товаров дня
|
||||||
|
export const GET_DAILY_PRODUCTS = gql`
|
||||||
|
query GetDailyProducts($displayDate: String!) {
|
||||||
|
dailyProducts(displayDate: $displayDate) {
|
||||||
|
id
|
||||||
|
discount
|
||||||
|
isActive
|
||||||
|
sortOrder
|
||||||
|
product {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
article
|
||||||
|
brand
|
||||||
|
retailPrice
|
||||||
|
wholesalePrice
|
||||||
|
images {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
alt
|
||||||
|
order
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
@ -459,7 +459,7 @@ export default function SearchResult() {
|
|||||||
offersCount={result ? result.totalOffers : 0}
|
offersCount={result ? result.totalOffers : 0}
|
||||||
minPrice={minPrice}
|
minPrice={minPrice}
|
||||||
/>
|
/>
|
||||||
<section className="main">
|
<section className="main mobile-only">
|
||||||
<div className="w-layout-blockcontainer container w-container">
|
<div className="w-layout-blockcontainer container w-container">
|
||||||
<div className="w-layout-hflex flex-block-84">
|
<div className="w-layout-hflex flex-block-84">
|
||||||
{/* <CatalogSortDropdown active={sortActive} onChange={setSortActive} /> */}
|
{/* <CatalogSortDropdown active={sortActive} onChange={setSortActive} /> */}
|
||||||
@ -551,7 +551,7 @@ export default function SearchResult() {
|
|||||||
<div className="w-layout-blockcontainer container w-container">
|
<div className="w-layout-blockcontainer container w-container">
|
||||||
<div className="w-layout-hflex flex-block-13-copy">
|
<div className="w-layout-hflex flex-block-13-copy">
|
||||||
{/* Фильтры для десктопа */}
|
{/* Фильтры для десктопа */}
|
||||||
<div style={{ width: '300px', marginRight: '20px' }}>
|
<div style={{ width: '300px', marginRight: '20px', marginBottom: '80px' }}>
|
||||||
<Filters
|
<Filters
|
||||||
filters={searchResultFilters}
|
filters={searchResultFilters}
|
||||||
onFilterChange={handleFilterChange}
|
onFilterChange={handleFilterChange}
|
||||||
@ -578,9 +578,8 @@ export default function SearchResult() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Используем фотографию из Parts Index, если она есть, иначе fallback на mainImageUrl
|
// Используем фотографию только из Parts Index, если она есть
|
||||||
const partsIndexImage = entityInfo?.images?.[0];
|
const partsIndexImage = entityInfo?.images?.[0];
|
||||||
const displayImage = partsIndexImage || mainImageUrl;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -588,7 +587,7 @@ export default function SearchResult() {
|
|||||||
brand={result.brand}
|
brand={result.brand}
|
||||||
article={result.articleNumber}
|
article={result.articleNumber}
|
||||||
name={result.name}
|
name={result.name}
|
||||||
image={displayImage}
|
{...(partsIndexImage ? { image: partsIndexImage } : {})}
|
||||||
offers={mainProductOffers}
|
offers={mainProductOffers}
|
||||||
showMoreText={mainProductOffers.length < filteredOffers.filter(o => !o.isAnalog).length ? "Показать еще" : undefined}
|
showMoreText={mainProductOffers.length < filteredOffers.filter(o => !o.isAnalog).length ? "Показать еще" : undefined}
|
||||||
partsIndexPowered={!!partsIndexImage}
|
partsIndexPowered={!!partsIndexImage}
|
||||||
|
@ -11,11 +11,15 @@ import MetaTags from '@/components/MetaTags';
|
|||||||
import { getMetaByPath } from '@/lib/meta-config';
|
import { getMetaByPath } from '@/lib/meta-config';
|
||||||
|
|
||||||
const InfoBrandSelection = ({
|
const InfoBrandSelection = ({
|
||||||
|
brand,
|
||||||
brandName,
|
brandName,
|
||||||
|
vehicleId,
|
||||||
oemNumber,
|
oemNumber,
|
||||||
detailName
|
detailName
|
||||||
}: {
|
}: {
|
||||||
|
brand: string;
|
||||||
brandName: string;
|
brandName: string;
|
||||||
|
vehicleId: string;
|
||||||
oemNumber: string;
|
oemNumber: string;
|
||||||
detailName?: string;
|
detailName?: string;
|
||||||
}) => (
|
}) => (
|
||||||
@ -27,20 +31,22 @@ const InfoBrandSelection = ({
|
|||||||
<div>Главная</div>
|
<div>Главная</div>
|
||||||
</a>
|
</a>
|
||||||
<div className="text-block-3">→</div>
|
<div className="text-block-3">→</div>
|
||||||
<a href="#" className="link-block-2 w-inline-block">
|
<a href="#" className="link-block w-inline-block">
|
||||||
<div>Каталог</div>
|
<div>Каталог</div>
|
||||||
</a>
|
</a>
|
||||||
<div className="text-block-3">→</div>
|
<div className="text-block-3">→</div>
|
||||||
|
<a href={`/vehicle-search/${brand}/${vehicleId}`} className="link-block w-inline-block">
|
||||||
<div>{brandName}</div>
|
<div>{brandName}</div>
|
||||||
|
</a>
|
||||||
<div className="text-block-3">→</div>
|
<div className="text-block-3">→</div>
|
||||||
|
<a href="#" className="link-block-2 w-inline-block">
|
||||||
<div>Деталь {oemNumber}</div>
|
<div>Деталь {oemNumber}</div>
|
||||||
<div className="text-block-3">→</div>
|
</a>
|
||||||
<div>Выбор производителя</div>
|
|
||||||
</div>
|
|
||||||
<div className="w-layout-hflex flex-block-8">
|
|
||||||
<div className="w-layout-hflex flex-block-10">
|
|
||||||
<h1 className="heading">Выберите производителя для {oemNumber}</h1>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="link-block w-inline-block">
|
||||||
|
|
||||||
|
<div className="heading">Выберите производителя для {oemNumber}</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -123,15 +129,17 @@ const BrandSelectionPage = () => {
|
|||||||
<>
|
<>
|
||||||
<MetaTags {...metaData} />
|
<MetaTags {...metaData} />
|
||||||
<InfoBrandSelection
|
<InfoBrandSelection
|
||||||
|
brand={String(brand)}
|
||||||
brandName={catalogInfo?.name || String(brand)}
|
brandName={catalogInfo?.name || String(brand)}
|
||||||
|
vehicleId={String(vehicleId)}
|
||||||
oemNumber={String(oemNumber)}
|
oemNumber={String(oemNumber)}
|
||||||
detailName={String(detailName || '')}
|
detailName={String(detailName || '')}
|
||||||
/>
|
/>
|
||||||
<div className="page-wrapper bg-[#F5F8FB] min-h-screen">
|
<div className="page-wrapper bg-[#F5F8FB] min-h-screen">
|
||||||
<div className="w-full max-w-[1580px] mx-auto px-8 max-md:px-5 pt-10 pb-16">
|
<div className="mx-auto px-8 max-md:px-5 pt-10 pb-16 ">
|
||||||
|
|
||||||
{/* Кнопка назад */}
|
{/* Кнопка назад */}
|
||||||
<div className="mb-6">
|
{/* <div className="mb-6">
|
||||||
<button
|
<button
|
||||||
onClick={handleBack}
|
onClick={handleBack}
|
||||||
className="flex items-center gap-2 text-gray-600 hover:text-gray-900 transition-colors"
|
className="flex items-center gap-2 text-gray-600 hover:text-gray-900 transition-colors"
|
||||||
@ -141,7 +149,7 @@ const BrandSelectionPage = () => {
|
|||||||
</svg>
|
</svg>
|
||||||
Назад к деталям
|
Назад к деталям
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
{/* Обработка ошибок */}
|
{/* Обработка ошибок */}
|
||||||
{hasError && !loading && (
|
{hasError && !loading && (
|
||||||
@ -187,8 +195,8 @@ const BrandSelectionPage = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : brands.length > 0 && (
|
) : brands.length > 0 && (
|
||||||
<div className="bg-white rounded-2xl shadow p-10">
|
<div className="bg-white rounded-2xl shadow p-10 w-full max-w-[1580px] mx-auto min-h-[500px]">
|
||||||
<div className="border-b border-gray-200 pb-4">
|
{/* <div className="border-b border-gray-200 pb-4">
|
||||||
<h2 className="text-xl font-semibold text-gray-900">
|
<h2 className="text-xl font-semibold text-gray-900">
|
||||||
Выбор производителя для артикула: {oemNumber}
|
Выбор производителя для артикула: {oemNumber}
|
||||||
</h2>
|
</h2>
|
||||||
@ -196,7 +204,7 @@ const BrandSelectionPage = () => {
|
|||||||
{detailName && <span>Деталь: {detailName} • </span>}
|
{detailName && <span>Деталь: {detailName} • </span>}
|
||||||
Найдено производителей: <span className="font-medium">{brands.length}</span>
|
Найдено производителей: <span className="font-medium">{brands.length}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div> */}
|
||||||
<div className="divide-y divide-gray-200">
|
<div className="divide-y divide-gray-200">
|
||||||
{brands.map((brandItem: any, index: number) => (
|
{brands.map((brandItem: any, index: number) => (
|
||||||
<div key={index}>
|
<div key={index}>
|
||||||
|
@ -45,6 +45,14 @@
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.price-in-cart-s1 {
|
||||||
|
|
||||||
|
max-width: 140px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
input.text-block-31 {
|
input.text-block-31 {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
}
|
}
|
||||||
@ -440,6 +448,12 @@ input#VinSearchInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.w-input {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.text-block-56, .dropdown-link-3 {
|
.text-block-56, .dropdown-link-3 {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -600,6 +614,16 @@ body {
|
|||||||
font-family: Onest, sans-serif;
|
font-family: Onest, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.heading{
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
a.link-block.w-inline-block,
|
||||||
|
a.link-block-2.w-inline-block {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.menu-button.w--open {
|
.menu-button.w--open {
|
||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
@ -877,3 +901,96 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.topmenub[style*='#fff'] .link-block-8 {
|
||||||
|
border: 1px solid #E6EDF6 !important;
|
||||||
|
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topmenub-white .link-block-8 {
|
||||||
|
border: 1px solid #E6EDF6 !important;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.info {
|
||||||
|
padding-top: 5px !important;
|
||||||
|
padding-bottom: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-arrow {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
opacity: 0.85;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.carousel-arrow:active {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.carousel-arrow[disabled] {
|
||||||
|
opacity: 0.3;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-arrow-left {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
.carousel-arrow-right {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-scroll {
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: visible;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
-ms-overflow-style: none; /* IE и Edge */
|
||||||
|
}
|
||||||
|
.carousel-scroll::-webkit-scrollbar {
|
||||||
|
display: none; /* Chrome, Safari, Opera */
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
.carousel-scroll {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.carousel-row {
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.carousel-arrow {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.mobile-only {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.mobile-only {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user