catalog
This commit is contained in:
@ -1,4 +1,3 @@
|
|||||||
import Link from "next/link";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useFavorites } from "@/contexts/FavoritesContext";
|
import { useFavorites } from "@/contexts/FavoritesContext";
|
||||||
|
|
||||||
@ -15,7 +14,7 @@ interface CatalogProductCardProps {
|
|||||||
productId?: string;
|
productId?: string;
|
||||||
offerKey?: string;
|
offerKey?: string;
|
||||||
currency?: string;
|
currency?: string;
|
||||||
priceElement?: React.ReactNode; // Элемент для отображения цены (например, скелетон)
|
priceElement?: React.ReactNode;
|
||||||
onAddToCart?: (e: React.MouseEvent) => void | Promise<void>;
|
onAddToCart?: (e: React.MouseEvent) => void | Promise<void>;
|
||||||
isInCart?: boolean;
|
isInCart?: boolean;
|
||||||
}
|
}
|
||||||
@ -39,40 +38,29 @@ const CatalogProductCard: React.FC<CatalogProductCardProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
|
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
|
||||||
|
|
||||||
// Обрабатываем пустое изображение - используем SVG-заглушку вместо мокап-фотки
|
|
||||||
const displayImage = image || 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjE5MCIgdmlld0JveD0iMCAwIDIxMCAxOTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMTAiIGhlaWdodD0iMTkwIiBmaWxsPSIjRjNGNEY2Ii8+CjxwYXRoIGQ9Ik04NSA5NUw5NSA4NUwxMjUgMTE1TDE0MCA5NUwxNjUgMTIwSDE2NVY5MEg0NVY5MEw4NSA5NVoiIGZpbGw9IiNEMUQ1REIiLz4KPGNpcmNsZSBjeD0iNzUiIGN5PSI3NSIgcj0iMTAiIGZpbGw9IiNEMUQ1REIiLz4KPHRleHQgeD0iMTA1IiB5PSIxNTAiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzlDQTNBRiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+Tm8gaW1hZ2U8L3RleHQ+Cjwvc3ZnPgo=';
|
const displayImage = image || 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjE5MCIgdmlld0JveD0iMCAwIDIxMCAxOTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMTAiIGhlaWdodD0iMTkwIiBmaWxsPSIjRjNGNEY2Ii8+CjxwYXRoIGQ9Ik04NSA5NUw5NSA4NUwxMjUgMTE1TDE0MCA5NUwxNjUgMTIwSDE2NVY5MEg0NVY5MEw4NSA5NVoiIGZpbGw9IiNEMUQ1REIiLz4KPGNpcmNsZSBjeD0iNzUiIGN5PSI3NSIgcj0iMTAiIGZpbGw9IiNEMUQ1REIiLz4KPHRleHQgeD0iMTA1IiB5PSIxNTAiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzlDQTNBRiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+Tm8gaW1hZ2U8L3RleHQ+Cjwvc3ZnPgo=';
|
||||||
|
|
||||||
// Создаем ссылку на card с параметрами товара
|
|
||||||
const cardUrl = articleNumber && brandName
|
const cardUrl = articleNumber && brandName
|
||||||
? `/card?article=${encodeURIComponent(articleNumber)}&brand=${encodeURIComponent(brandName)}${artId ? `&artId=${artId}` : ''}`
|
? `/card?article=${encodeURIComponent(articleNumber)}&brand=${encodeURIComponent(brandName)}${artId ? `&artId=${artId}` : ''}`
|
||||||
: '/card'; // Fallback на card если нет данных
|
: '/card';
|
||||||
|
|
||||||
// Проверяем, есть ли товар в избранном
|
|
||||||
const isItemFavorite = isFavorite(productId, offerKey, articleNumber, brandName || brand);
|
const isItemFavorite = isFavorite(productId, offerKey, articleNumber, brandName || brand);
|
||||||
|
|
||||||
// Обработчик клика по сердечку
|
|
||||||
const handleFavoriteClick = (e: React.MouseEvent) => {
|
const handleFavoriteClick = (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// Извлекаем цену как число
|
|
||||||
const numericPrice = parseFloat(price.replace(/[^\d.,]/g, '').replace(',', '.')) || 0;
|
const numericPrice = parseFloat(price.replace(/[^\d.,]/g, '').replace(',', '.')) || 0;
|
||||||
|
|
||||||
if (isItemFavorite) {
|
if (isItemFavorite) {
|
||||||
// Находим товар в избранном по правильному ID
|
|
||||||
const favoriteItem = favorites.find((fav: any) => {
|
const favoriteItem = favorites.find((fav: any) => {
|
||||||
// Проверяем по разным комбинациям идентификаторов
|
|
||||||
if (productId && fav.productId === productId) return true;
|
if (productId && fav.productId === productId) return true;
|
||||||
if (offerKey && fav.offerKey === offerKey) return true;
|
if (offerKey && fav.offerKey === offerKey) return true;
|
||||||
if (fav.article === articleNumber && fav.brand === (brandName || brand)) return true;
|
if (fav.article === articleNumber && fav.brand === (brandName || brand)) return true;
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (favoriteItem) {
|
if (favoriteItem) {
|
||||||
removeFromFavorites(favoriteItem.id);
|
removeFromFavorites(favoriteItem.id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Добавляем в избранное
|
|
||||||
addToFavorites({
|
addToFavorites({
|
||||||
productId,
|
productId,
|
||||||
offerKey,
|
offerKey,
|
||||||
@ -86,40 +74,30 @@ const CatalogProductCard: React.FC<CatalogProductCardProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Обработчик клика по кнопке "Купить"
|
|
||||||
const handleBuyClick = (e: React.MouseEvent) => {
|
const handleBuyClick = (e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
if (onAddToCart) {
|
if (onAddToCart) {
|
||||||
onAddToCart(e);
|
onAddToCart(e);
|
||||||
} else {
|
} else {
|
||||||
// Fallback - переходим на страницу товара
|
|
||||||
window.location.href = cardUrl;
|
window.location.href = cardUrl;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="w-layout-vflex flex-block-15-copy" data-article-card="visible" itemScope itemType="https://schema.org/Product">
|
||||||
className="w-layout-vflex flex-block-15-copy"
|
|
||||||
data-article-card="visible"
|
|
||||||
itemScope
|
|
||||||
itemType="https://schema.org/Product"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className={`favcardcat ${isItemFavorite ? 'favorite-active' : ''}`}
|
className={`favcardcat${isItemFavorite ? ' favorite-active' : ''}`}
|
||||||
onClick={handleFavoriteClick}
|
onClick={handleFavoriteClick}
|
||||||
style={{
|
style={{ cursor: 'pointer', color: isItemFavorite ? '#ff4444' : '#ccc' }}
|
||||||
cursor: 'pointer',
|
|
||||||
color: isItemFavorite ? '#ff4444' : '#ccc'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className="icon-setting w-embed">
|
<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">
|
<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>
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="div-block-4">
|
||||||
{/* Делаем картинку и контент кликабельными для перехода на card */}
|
|
||||||
<Link href={cardUrl} className="div-block-4" style={{ textDecoration: 'none', color: 'inherit' }}>
|
|
||||||
<img
|
<img
|
||||||
src={displayImage}
|
src={displayImage}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
@ -129,10 +107,18 @@ const CatalogProductCard: React.FC<CatalogProductCardProps> = ({
|
|||||||
className="image-5"
|
className="image-5"
|
||||||
itemProp="image"
|
itemProp="image"
|
||||||
/>
|
/>
|
||||||
<div className="text-block-7">{discount}</div>
|
<div
|
||||||
</Link>
|
className="text-block-7"
|
||||||
|
style={{
|
||||||
<Link href={cardUrl} className="div-block-3" style={{ textDecoration: 'none', color: 'inherit' }}>
|
background: discount ? undefined : 'transparent',
|
||||||
|
color: discount ? undefined : 'transparent',
|
||||||
|
border: discount ? undefined : 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{discount || ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="div-block-3">
|
||||||
<div className="w-layout-hflex flex-block-16">
|
<div className="w-layout-hflex flex-block-16">
|
||||||
{priceElement ? (
|
{priceElement ? (
|
||||||
<div className="text-block-8">{priceElement}</div>
|
<div className="text-block-8">{priceElement}</div>
|
||||||
@ -144,31 +130,31 @@ const CatalogProductCard: React.FC<CatalogProductCardProps> = ({
|
|||||||
)}
|
)}
|
||||||
<div className="text-block-9">{oldPrice}</div>
|
<div className="text-block-9">{oldPrice}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-block-10" itemProp="name">{title}</div>
|
<div className="w-layout-hflex flex-block-122">
|
||||||
<div className="text-block-11" itemProp="brand" itemScope itemType="https://schema.org/Brand">
|
<div className="w-layout-vflex">
|
||||||
<span itemProp="name">{brand}</span>
|
<div className="text-block-10" itemProp="name">{title}</div>
|
||||||
|
<div className="text-block-11" itemProp="brand" itemScope itemType="https://schema.org/Brand">
|
||||||
|
<span itemProp="name">{brand}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="button-icon w-inline-block"
|
||||||
|
onClick={handleBuyClick}
|
||||||
|
style={{ cursor: isInCart ? 'default' : 'pointer', opacity: isInCart ? 0.5 : 1, filter: isInCart ? 'grayscale(1)' : 'none' }}
|
||||||
|
aria-label={isInCart ? 'В корзине' : 'Купить'}
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
<meta itemProp="sku" content={articleNumber || ''} />
|
<meta itemProp="sku" content={articleNumber || ''} />
|
||||||
</Link>
|
|
||||||
|
|
||||||
{/* Обновляем кнопку купить */}
|
|
||||||
<div
|
|
||||||
className="catc w-inline-block"
|
|
||||||
onClick={handleBuyClick}
|
|
||||||
style={{
|
|
||||||
cursor: isInCart ? 'default' : 'pointer',
|
|
||||||
opacity: isInCart ? 0.5 : 1,
|
|
||||||
filter: isInCart ? 'grayscale(1)' : 'none'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="div-block-25">
|
|
||||||
<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>
|
|
||||||
<div className="text-block-6">{isInCart ? 'В корзине' : 'Купить'}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -4,9 +4,10 @@ interface FilterRangeProps {
|
|||||||
title: string;
|
title: string;
|
||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
isMobile?: boolean; // Добавляем флаг для мобильной версии
|
isMobile?: boolean;
|
||||||
value?: [number, number] | null; // Текущее значение диапазона
|
value?: [number, number] | null;
|
||||||
onChange?: (value: [number, number]) => void;
|
onChange?: (value: [number, number]) => void;
|
||||||
|
defaultOpen?: boolean; // Добавляем параметр defaultOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_MIN = 1;
|
const DEFAULT_MIN = 1;
|
||||||
@ -14,14 +15,22 @@ const DEFAULT_MAX = 32000;
|
|||||||
|
|
||||||
const clamp = (v: number, min: number, max: number) => Math.max(min, Math.min(v, max));
|
const clamp = (v: number, min: number, max: number) => Math.max(min, Math.min(v, max));
|
||||||
|
|
||||||
const FilterRange: React.FC<FilterRangeProps> = ({ title, min = DEFAULT_MIN, max = DEFAULT_MAX, isMobile = false, value = null, onChange }) => {
|
const FilterRange: React.FC<FilterRangeProps> = ({
|
||||||
|
title,
|
||||||
|
min = DEFAULT_MIN,
|
||||||
|
max = DEFAULT_MAX,
|
||||||
|
isMobile = false,
|
||||||
|
value = null,
|
||||||
|
onChange,
|
||||||
|
defaultOpen = false // Устанавливаем по умолчанию false
|
||||||
|
}) => {
|
||||||
const [from, setFrom] = useState<string>(value ? String(value[0]) : String(min));
|
const [from, setFrom] = useState<string>(value ? String(value[0]) : String(min));
|
||||||
const [to, setTo] = useState<string>(value ? String(value[1]) : String(max));
|
const [to, setTo] = useState<string>(value ? String(value[1]) : String(max));
|
||||||
const [confirmedFrom, setConfirmedFrom] = useState<number>(value ? value[0] : min);
|
const [confirmedFrom, setConfirmedFrom] = useState<number>(value ? value[0] : min);
|
||||||
const [confirmedTo, setConfirmedTo] = useState<number>(value ? value[1] : max);
|
const [confirmedTo, setConfirmedTo] = useState<number>(value ? value[1] : max);
|
||||||
const [dragging, setDragging] = useState<null | "from" | "to">(null);
|
const [dragging, setDragging] = useState<null | "from" | "to">(null);
|
||||||
const [trackWidth, setTrackWidth] = useState(0);
|
const [trackWidth, setTrackWidth] = useState(0);
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(isMobile || defaultOpen); // Учитываем isMobile и defaultOpen
|
||||||
const trackRef = useRef<HTMLDivElement>(null);
|
const trackRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Обновляем локальное состояние при изменении внешнего значения или границ
|
// Обновляем локальное состояние при изменении внешнего значения или границ
|
||||||
@ -199,7 +208,7 @@ const FilterRange: React.FC<FilterRangeProps> = ({ title, min = DEFAULT_MIN, max
|
|||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 6,
|
top: 6,
|
||||||
left: pxFrom ,
|
left: pxFrom,
|
||||||
zIndex: 3,
|
zIndex: 3,
|
||||||
cursor: 'pointer'
|
cursor: 'pointer'
|
||||||
}}
|
}}
|
||||||
|
@ -38,11 +38,11 @@ const mockData = Array(12).fill({
|
|||||||
brand: "Borsehung",
|
brand: "Borsehung",
|
||||||
});
|
});
|
||||||
|
|
||||||
const ITEMS_PER_PAGE = 50; // Уменьшено для быстрой загрузки и лучшего UX
|
|
||||||
const PARTSINDEX_PAGE_SIZE = 25; // Синхронизировано для оптимальной скорости
|
|
||||||
const MAX_BRANDS_DISPLAY = 10; // Сколько брендов показывать изначально
|
|
||||||
|
|
||||||
export default function Catalog() {
|
export default function Catalog() {
|
||||||
|
const ITEMS_PER_PAGE = 12; // Показывать 12 карточек за раз
|
||||||
|
const PARTSINDEX_PAGE_SIZE = 25; // Синхронизировано для оптимальной скорости
|
||||||
|
const MAX_BRANDS_DISPLAY = 10; // Сколько брендов показывать изначально
|
||||||
|
const [visibleCount, setVisibleCount] = useState(ITEMS_PER_PAGE);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { addItem } = useCart();
|
const { addItem } = useCart();
|
||||||
const {
|
const {
|
||||||
@ -407,7 +407,8 @@ export default function Catalog() {
|
|||||||
type: 'range' as const,
|
type: 'range' as const,
|
||||||
title: param.name,
|
title: param.name,
|
||||||
min,
|
min,
|
||||||
max
|
max,
|
||||||
|
defaultOpen: false,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Для dropdown фильтров
|
// Для dropdown фильтров
|
||||||
@ -418,7 +419,8 @@ export default function Catalog() {
|
|||||||
.filter((value: any) => value.available) // Показываем только доступные
|
.filter((value: any) => value.available) // Показываем только доступные
|
||||||
.map((value: any) => value.title || value.value),
|
.map((value: any) => value.title || value.value),
|
||||||
multi: true,
|
multi: true,
|
||||||
showAll: true
|
showAll: true,
|
||||||
|
defaultOpen: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -564,7 +566,7 @@ export default function Catalog() {
|
|||||||
options: brandsToShow.sort(), // Сортируем по алфавиту для удобства
|
options: brandsToShow.sort(), // Сортируем по алфавиту для удобства
|
||||||
multi: true,
|
multi: true,
|
||||||
showAll: true,
|
showAll: true,
|
||||||
defaultOpen: true,
|
defaultOpen: false,
|
||||||
hasMore: !showAllBrands && sortedBrands.length > MAX_BRANDS_DISPLAY,
|
hasMore: !showAllBrands && sortedBrands.length > MAX_BRANDS_DISPLAY,
|
||||||
onShowMore: () => setShowAllBrands(true)
|
onShowMore: () => setShowAllBrands(true)
|
||||||
});
|
});
|
||||||
@ -577,7 +579,7 @@ export default function Catalog() {
|
|||||||
options: Array.from(productGroups).sort(),
|
options: Array.from(productGroups).sort(),
|
||||||
multi: true,
|
multi: true,
|
||||||
showAll: true,
|
showAll: true,
|
||||||
defaultOpen: true,
|
defaultOpen: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1007,7 +1009,7 @@ export default function Catalog() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isPartsAPIMode ? (
|
{isPartsAPIMode ? (
|
||||||
<div className="filters-desktop">
|
<div className="filters-desktop" style={{ width: '300px', marginRight: '20px', marginBottom: '80px' }}>
|
||||||
<Filters
|
<Filters
|
||||||
filters={dynamicFilters}
|
filters={dynamicFilters}
|
||||||
onFilterChange={handleDesktopFilterChange}
|
onFilterChange={handleDesktopFilterChange}
|
||||||
@ -1018,7 +1020,7 @@ export default function Catalog() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : isPartsIndexMode ? (
|
) : isPartsIndexMode ? (
|
||||||
<div className="filters-desktop">
|
<div className="filters-desktop" style={{ width: '300px', marginRight: '20px', marginBottom: '80px' }}>
|
||||||
<Filters
|
<Filters
|
||||||
filters={catalogFilters}
|
filters={catalogFilters}
|
||||||
onFilterChange={handleDesktopFilterChange}
|
onFilterChange={handleDesktopFilterChange}
|
||||||
@ -1029,7 +1031,7 @@ export default function Catalog() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="filters-desktop">
|
<div className="filters-desktop" style={{ width: '300px', marginRight: '20px', marginBottom: '80px' }}>
|
||||||
<Filters
|
<Filters
|
||||||
filters={catalogFilters}
|
filters={catalogFilters}
|
||||||
onFilterChange={handleDesktopFilterChange}
|
onFilterChange={handleDesktopFilterChange}
|
||||||
@ -1125,142 +1127,112 @@ export default function Catalog() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Отображение товаров PartsIndex */}
|
{/* Отображение товаров PartsIndex */}
|
||||||
{isPartsIndexMode && !isFilterChanging && (() => {
|
{isPartsIndexMode && !isFilterChanging && accumulatedEntities.length > 0 && (
|
||||||
console.log('🎯 Проверяем отображение PartsIndex товаров:', {
|
|
||||||
isPartsIndexMode,
|
|
||||||
visibleEntitiesLength: visibleEntities.length,
|
|
||||||
visibleEntities: visibleEntities.map(e => ({ id: e.id, code: e.code, brand: e.brand.name })),
|
|
||||||
isFilterChanging
|
|
||||||
});
|
|
||||||
return visibleEntities.length > 0;
|
|
||||||
})() && (
|
|
||||||
<>
|
<>
|
||||||
{visibleEntities
|
{accumulatedEntities.slice(0, visibleCount).map((entity, idx) => {
|
||||||
.map((entity, idx) => {
|
const productForPrice = { id: entity.id, code: entity.code, brand: entity.brand.name };
|
||||||
const productForPrice = { id: entity.id, code: entity.code, brand: entity.brand.name };
|
const priceData = getPrice(productForPrice);
|
||||||
const priceData = getPrice(productForPrice);
|
const isLoadingPriceData = isLoadingPrice(productForPrice);
|
||||||
const isLoadingPriceData = isLoadingPrice(productForPrice);
|
|
||||||
|
|
||||||
// Определяем цену для отображения (все товары уже отфильтрованы на сервере)
|
// Определяем цену для отображения (все товары уже отфильтрованы на сервере)
|
||||||
let displayPrice = "";
|
let displayPrice = "";
|
||||||
let displayCurrency = "RUB";
|
let displayCurrency = "RUB";
|
||||||
let priceElement;
|
let priceElement;
|
||||||
|
|
||||||
if (isLoadingPriceData) {
|
if (isLoadingPriceData) {
|
||||||
// Показываем скелетон загрузки вместо текста
|
// Показываем скелетон загрузки вместо текста
|
||||||
priceElement = <PriceSkeleton />;
|
priceElement = <PriceSkeleton />;
|
||||||
} else if (priceData && priceData.price) {
|
} else if (priceData && priceData.price) {
|
||||||
displayPrice = `${priceData.price.toLocaleString('ru-RU')} ₽`;
|
displayPrice = `${priceData.price.toLocaleString('ru-RU')} ₽`;
|
||||||
displayCurrency = priceData.currency || "RUB";
|
displayCurrency = priceData.currency || "RUB";
|
||||||
} else {
|
} else {
|
||||||
// Если нет данных о цене, показываем скелетон (товар должен загрузиться)
|
// Если нет данных о цене, показываем скелетон (товар должен загрузиться)
|
||||||
priceElement = <PriceSkeleton />;
|
priceElement = <PriceSkeleton />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CatalogProductCard
|
<CatalogProductCard
|
||||||
key={`${entity.id}_${idx}`}
|
key={`${entity.id}_${idx}`}
|
||||||
title={entity.originalName || entity.name?.name || 'Товар без названия'}
|
title={entity.originalName || entity.name?.name || 'Товар без названия'}
|
||||||
brand={entity.brand.name}
|
brand={entity.brand.name}
|
||||||
articleNumber={entity.code}
|
articleNumber={entity.code}
|
||||||
brandName={entity.brand.name}
|
brandName={entity.brand.name}
|
||||||
image={entity.images?.[0] || ''}
|
image={entity.images?.[0] || ''}
|
||||||
price={priceElement ? "" : displayPrice}
|
price={priceElement ? "" : displayPrice}
|
||||||
priceElement={priceElement}
|
priceElement={priceElement}
|
||||||
oldPrice=""
|
oldPrice=""
|
||||||
discount=""
|
discount=""
|
||||||
currency={displayCurrency}
|
currency={displayCurrency}
|
||||||
productId={entity.id}
|
productId={entity.id}
|
||||||
artId={entity.id}
|
artId={entity.id}
|
||||||
offerKey={priceData?.offerKey}
|
offerKey={priceData?.offerKey}
|
||||||
isInCart={priceData?.isInCart}
|
isInCart={priceData?.isInCart}
|
||||||
onAddToCart={async () => {
|
onAddToCart={async () => {
|
||||||
// Если цена не загружена, загружаем её и добавляем в корзину
|
// Если цена не загружена, загружаем её и добавляем в корзину
|
||||||
if (!priceData && !isLoadingPriceData) {
|
if (!priceData && !isLoadingPriceData) {
|
||||||
ensurePriceLoaded(productForPrice);
|
ensurePriceLoaded(productForPrice);
|
||||||
console.log('🔄 Загружаем цену для:', entity.code, entity.brand.name);
|
console.log('🔄 Загружаем цену для:', entity.code, entity.brand.name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если цена есть, добавляем в корзину
|
// Если цена есть, добавляем в корзину
|
||||||
if (priceData && priceData.price) {
|
if (priceData && priceData.price) {
|
||||||
const itemToAdd = {
|
const itemToAdd = {
|
||||||
productId: entity.id,
|
productId: entity.id,
|
||||||
offerKey: priceData.offerKey,
|
offerKey: priceData.offerKey,
|
||||||
name: entity.originalName || entity.name?.name || 'Товар без названия',
|
name: entity.originalName || entity.name?.name || 'Товар без названия',
|
||||||
description: `${entity.brand.name} ${entity.code}`,
|
description: `${entity.brand.name} ${entity.code}`,
|
||||||
brand: entity.brand.name,
|
brand: entity.brand.name,
|
||||||
article: entity.code,
|
article: entity.code,
|
||||||
price: priceData.price,
|
price: priceData.price,
|
||||||
currency: priceData.currency || 'RUB',
|
currency: priceData.currency || 'RUB',
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
stock: undefined, // информация о наличии не доступна для PartsIndex
|
stock: undefined, // информация о наличии не доступна для PartsIndex
|
||||||
deliveryTime: '1-3 дня',
|
deliveryTime: '1-3 дня',
|
||||||
warehouse: 'Parts Index',
|
warehouse: 'Parts Index',
|
||||||
supplier: 'Parts Index',
|
supplier: 'Parts Index',
|
||||||
isExternal: true,
|
isExternal: true,
|
||||||
image: entity.images?.[0] || '',
|
image: entity.images?.[0] || '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await addItem(itemToAdd);
|
const result = await addItem(itemToAdd);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
// Показываем уведомление
|
// Показываем уведомление
|
||||||
toast.success(
|
toast.success(
|
||||||
<div>
|
<div>
|
||||||
<div className="font-semibold" style={{ color: '#fff' }}>Товар добавлен в корзину!</div>
|
<div className="font-semibold" style={{ color: '#fff' }}>Товар добавлен в корзину!</div>
|
||||||
<div className="text-sm" style={{ color: '#fff', opacity: 0.9 }}>{`${entity.brand.name} ${entity.code} за ${priceData.price.toLocaleString('ru-RU')} ₽`}</div>
|
<div className="text-sm" style={{ color: '#fff', opacity: 0.9 }}>{`${entity.brand.name} ${entity.code} за ${priceData.price.toLocaleString('ru-RU')} ₽`}</div>
|
||||||
</div>,
|
</div>,
|
||||||
{
|
{
|
||||||
duration: 3000,
|
duration: 3000,
|
||||||
icon: <CartIcon size={20} color="#fff" />,
|
icon: <CartIcon size={20} color="#fff" />,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
toast.error(result.error || 'Ошибка при добавлении товара в корзину');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
toast.error('Цена товара еще загружается. Попробуйте снова через несколько секунд.');
|
toast.error(result.error || 'Ошибка при добавлении товара в корзину');
|
||||||
}
|
}
|
||||||
}}
|
} else {
|
||||||
/>
|
toast.error('Цена товара еще загружается. Попробуйте снова через несколько секунд.');
|
||||||
);
|
}
|
||||||
})}
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
{/* Пагинация для PartsIndex */}
|
{/* Кнопка "Показать еще" */}
|
||||||
<div className="w-layout-hflex pagination">
|
{visibleCount < accumulatedEntities.length && (
|
||||||
<button
|
<div className="w-layout-hflex pagination">
|
||||||
onClick={() => {
|
<button
|
||||||
console.log('🖱️ Клик по кнопке "Назад"');
|
onClick={() => setVisibleCount(c => Math.min(c + ITEMS_PER_PAGE, accumulatedEntities.length))}
|
||||||
handlePrevPage();
|
className="button_strock w-button"
|
||||||
}}
|
>
|
||||||
disabled={currentUserPage <= 1}
|
Показать еще
|
||||||
className="button_strock w-button mr-2"
|
</button>
|
||||||
>
|
</div>
|
||||||
← Назад
|
)}
|
||||||
</button>
|
|
||||||
|
|
||||||
<span className="flex items-center px-4 text-gray-600">
|
{/* Отладочная информация
|
||||||
Страница {currentUserPage} из {Math.ceil(accumulatedEntities.length / ITEMS_PER_PAGE) || 1}
|
|
||||||
{isAutoLoading && ' (загружаем...)'}
|
|
||||||
<span className="ml-2 text-xs text-gray-400">
|
|
||||||
(товаров: {accumulatedEntities.length})
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
console.log('🖱️ Клик по кнопке "Вперед"');
|
|
||||||
handleNextPage();
|
|
||||||
}}
|
|
||||||
disabled={currentUserPage >= Math.ceil(accumulatedEntities.length / ITEMS_PER_PAGE)}
|
|
||||||
className="button_strock w-button ml-2"
|
|
||||||
>
|
|
||||||
Вперед →
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Отладочная информация */}
|
|
||||||
{isPartsIndexMode && (
|
{isPartsIndexMode && (
|
||||||
<div className="text-xs text-gray-500 mt-4 p-2 bg-gray-100 rounded">
|
<div className="text-xs text-gray-500 mt-4 p-2 bg-gray-100 rounded">
|
||||||
<div>🔍 Отладка PartsIndex (исправленная логика):</div>
|
<div>🔍 Отладка PartsIndex (исправленная логика):</div>
|
||||||
@ -1286,7 +1258,7 @@ export default function Catalog() {
|
|||||||
{isAutoLoading ? 'Загружаем...' : 'Загрузить еще'}
|
{isAutoLoading ? 'Загружаем...' : 'Загрузить еще'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -348,6 +348,12 @@ input.input-receiver:focus {
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-block-122 {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
.button-icon.w-inline-block {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
.text-block-10 {
|
.text-block-10 {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
@ -403,6 +409,25 @@ input.input-receiver:focus {
|
|||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-block-7 {
|
||||||
|
border-radius: var(--_round---small-8);
|
||||||
|
background-color: var(--green);
|
||||||
|
color: var(--_fonts---color--white);
|
||||||
|
padding: 5px;
|
||||||
|
font-weight: 600;
|
||||||
|
position: relative;
|
||||||
|
top: -35px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.div-block-3 {
|
||||||
|
grid-column-gap: 5px;
|
||||||
|
grid-row-gap: 5px;
|
||||||
|
flex-flow: column;
|
||||||
|
align-self: auto;
|
||||||
|
margin-top: -30px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
.sort-item.active {
|
.sort-item.active {
|
||||||
color: #111;
|
color: #111;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@ -491,19 +516,20 @@ input#VinSearchInput {
|
|||||||
line-height: 1.4em;
|
line-height: 1.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-block-21-copy {
|
.text-block-21-copy,
|
||||||
|
.heading-9-copy {
|
||||||
width: 250px;
|
width: 250px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.heading-9-copy {
|
/* .heading-9-copy {
|
||||||
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
} */
|
||||||
.pcs-search {
|
.pcs-search {
|
||||||
color: var(--_fonts---color--black);
|
color: var(--_fonts---color--black);
|
||||||
font-size: var(--_fonts---font-size--core);
|
font-size: var(--_fonts---font-size--core);
|
||||||
@ -513,11 +539,11 @@ input#VinSearchInput {
|
|||||||
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
.heading-9-copy {
|
/* .heading-9-copy {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
display: block;
|
display: block;
|
||||||
}
|
} */
|
||||||
|
|
||||||
.w-layout-hflex.flex-block-6 {
|
.w-layout-hflex.flex-block-6 {
|
||||||
flex-direction: column !important;
|
flex-direction: column !important;
|
||||||
@ -944,8 +970,9 @@ a.link-block-2.w-inline-block {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.flex-block-15-copy {
|
.flex-block-15-copy {
|
||||||
width: 232px!important;
|
width: 240px!important;
|
||||||
min-width: 232px!important;
|
height: 315px;
|
||||||
|
min-width: 240px!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nameitembp {
|
.nameitembp {
|
||||||
@ -1268,8 +1295,8 @@ a.link-block-2.w-inline-block {
|
|||||||
max-width: 160px;
|
max-width: 160px;
|
||||||
flex: 0 0 160px;
|
flex: 0 0 160px;
|
||||||
}
|
}
|
||||||
.heading-9-copy {
|
/* .heading-9-copy {
|
||||||
text-align: left !important;
|
text-align: left !important;
|
||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
}
|
} */
|
||||||
}
|
}
|
@ -1539,7 +1539,7 @@ body {
|
|||||||
grid-row-gap: 5px;
|
grid-row-gap: 5px;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
align-self: auto;
|
align-self: auto;
|
||||||
margin-top: -30px;
|
/* margin-top: -30px; */
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user