Compare commits
13 Commits
cartandfav
...
6af1ed325c
Author | SHA1 | Date | |
---|---|---|---|
6af1ed325c | |||
1f1ea8baaf | |||
8a953d32ae | |||
69ccc786ea | |||
8cae029d7f | |||
cbf50691c4 | |||
4a3da4d5c5 | |||
215853e8c7 | |||
f894b7e023 | |||
a879e5e5e7 | |||
85f7634158 | |||
d62db55160 | |||
5e454a7367 |
@ -52,7 +52,7 @@ const BrandSelectionModal: React.FC<BrandSelectionModalProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
|
className="fixed inset-0 bg-black/10 bg-opacity-50 flex items-center justify-center z-50 p-4"
|
||||||
onClick={handleBackdropClick}
|
onClick={handleBackdropClick}
|
||||||
>
|
>
|
||||||
<div className="bg-white rounded-lg shadow-xl max-w-md w-full max-h-[80vh] overflow-hidden">
|
<div className="bg-white rounded-lg shadow-xl max-w-md w-full max-h-[80vh] overflow-hidden">
|
||||||
|
@ -16,6 +16,8 @@ interface CartItemProps {
|
|||||||
onComment: (comment: string) => void;
|
onComment: (comment: string) => void;
|
||||||
onCountChange?: (count: number) => void;
|
onCountChange?: (count: number) => void;
|
||||||
onRemove?: () => void;
|
onRemove?: () => void;
|
||||||
|
isSummaryStep?: boolean;
|
||||||
|
itemNumber?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CartItem: React.FC<CartItemProps> = ({
|
const CartItem: React.FC<CartItemProps> = ({
|
||||||
@ -34,9 +36,14 @@ const CartItem: React.FC<CartItemProps> = ({
|
|||||||
onComment,
|
onComment,
|
||||||
onCountChange,
|
onCountChange,
|
||||||
onRemove,
|
onRemove,
|
||||||
|
isSummaryStep = false,
|
||||||
|
itemNumber,
|
||||||
}) => (
|
}) => (
|
||||||
<div className="w-layout-hflex cart-item">
|
<div className="w-layout-hflex cart-item">
|
||||||
<div className="w-layout-hflex info-block-search-copy">
|
<div className="w-layout-hflex info-block-search-copy">
|
||||||
|
{isSummaryStep ? (
|
||||||
|
<div style={{ marginRight: 12, minWidth: 24, textAlign: 'center', fontWeight: 600, fontSize: 14 }}>{itemNumber}</div>
|
||||||
|
) : (
|
||||||
<div
|
<div
|
||||||
className={"div-block-7" + (selected ? " active" : "")}
|
className={"div-block-7" + (selected ? " active" : "")}
|
||||||
onClick={onSelect}
|
onClick={onSelect}
|
||||||
@ -48,9 +55,22 @@ const CartItem: React.FC<CartItemProps> = ({
|
|||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<div className="w-layout-hflex block-name">
|
<div className="w-layout-hflex block-name">
|
||||||
<h4 className="heading-9-copy">{name}</h4>
|
<h4 className="heading-9-copy">{name}</h4>
|
||||||
<div className="text-block-21-copy">{description}</div>
|
<div
|
||||||
|
className={
|
||||||
|
"text-block-21-copy" +
|
||||||
|
(isSummaryStep && itemNumber === 1 ? " border-t-0" : "")
|
||||||
|
}
|
||||||
|
style={
|
||||||
|
isSummaryStep && itemNumber === 1
|
||||||
|
? { borderTop: 'none' }
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-block-copy w-form">
|
<div className="form-block-copy w-form">
|
||||||
<form className="form-copy" onSubmit={e => e.preventDefault()}>
|
<form className="form-copy" onSubmit={e => e.preventDefault()}>
|
||||||
@ -64,6 +84,7 @@ const CartItem: React.FC<CartItemProps> = ({
|
|||||||
id="Search-5"
|
id="Search-5"
|
||||||
value={comment}
|
value={comment}
|
||||||
onChange={e => onComment(e.target.value)}
|
onChange={e => onComment(e.target.value)}
|
||||||
|
disabled={isSummaryStep}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
<div className="success-message w-form-done">
|
<div className="success-message w-form-done">
|
||||||
@ -80,87 +101,95 @@ const CartItem: React.FC<CartItemProps> = ({
|
|||||||
<div className="text-block-21-copy-copy">{deliveryDate}</div>
|
<div className="text-block-21-copy-copy">{deliveryDate}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-layout-hflex pcs-cart-s1">
|
<div className="w-layout-hflex pcs-cart-s1">
|
||||||
<div
|
{isSummaryStep ? (
|
||||||
className="minus-plus"
|
<div className="text-block-26" style={{ fontWeight: 600, fontSize: 14 }}>{count} шт.</div>
|
||||||
onClick={() => onCountChange && onCountChange(count - 1)}
|
) : (
|
||||||
style={{ cursor: 'pointer' }}
|
<>
|
||||||
aria-label="Уменьшить количество"
|
<div
|
||||||
tabIndex={0}
|
className="minus-plus"
|
||||||
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onCountChange && onCountChange(count - 1)}
|
onClick={() => onCountChange && onCountChange(count - 1)}
|
||||||
role="button"
|
style={{ cursor: 'pointer' }}
|
||||||
>
|
aria-label="Уменьшить количество"
|
||||||
<div className="pluspcs w-embed">
|
tabIndex={0}
|
||||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onCountChange && onCountChange(count - 1)}
|
||||||
<path d="M6 10.5V9.5H14V10.5H6Z" fill="currentColor"/>
|
role="button"
|
||||||
</svg>
|
>
|
||||||
</div>
|
<div className="pluspcs w-embed">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 10.5V9.5H14V10.5H6Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="input-pcs">
|
<div className="input-pcs">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min={1}
|
min={1}
|
||||||
value={count}
|
value={count}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
const value = Math.max(1, parseInt(e.target.value, 10) || 1);
|
const value = Math.max(1, parseInt(e.target.value, 10) || 1);
|
||||||
onCountChange && onCountChange(value);
|
onCountChange && onCountChange(value);
|
||||||
}}
|
}}
|
||||||
className="text-block-26 w-full text-center outline-none"
|
className="text-block-26 w-full text-center outline-none"
|
||||||
aria-label="Количество"
|
aria-label="Количество"
|
||||||
style={{ width: 40 }}
|
style={{ width: 40 }}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="minus-plus"
|
||||||
|
onClick={() => onCountChange && onCountChange(count + 1)}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
aria-label="Увеличить количество"
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onCountChange && onCountChange(count + 1)}
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div className="pluspcs w-embed">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 10.5V9.5H14V10.5H6ZM9.5 6H10.5V14H9.5V6Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
className="minus-plus"
|
|
||||||
onClick={() => onCountChange && onCountChange(count + 1)}
|
|
||||||
style={{ cursor: 'pointer' }}
|
|
||||||
aria-label="Увеличить количество"
|
|
||||||
tabIndex={0}
|
|
||||||
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onCountChange && onCountChange(count + 1)}
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
<div className="pluspcs w-embed">
|
|
||||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M6 10.5V9.5H14V10.5H6ZM9.5 6H10.5V14H9.5V6Z" fill="currentColor"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-layout-hflex flex-block-39-copy-copy">
|
<div className="w-layout-hflex flex-block-39-copy-copy">
|
||||||
<h4 className="price-in-cart-s1">{price}</h4>
|
<h4 className="price-in-cart-s1">{price}</h4>
|
||||||
<div className="price-1-pcs-cart-s1">{pricePerItem}</div>
|
<div className="price-1-pcs-cart-s1">{pricePerItem}</div>
|
||||||
</div>
|
</div>
|
||||||
|
{!isSummaryStep && (
|
||||||
<div className="w-layout-hflex control-element">
|
<div className="w-layout-hflex control-element">
|
||||||
<div className="favorite-icon w-embed" onClick={onFavorite} style={{ cursor: 'pointer', color: favorite ? '#e53935' : undefined }}>
|
<div className="favorite-icon w-embed" onClick={onFavorite} style={{ cursor: 'pointer', color: favorite ? '#e53935' : undefined }}>
|
||||||
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M9 16.5L7.84 15.4929C3.72 11.93 1 9.57248 1 6.69619C1 4.33869 2.936 2.5 5.4 2.5C6.792 2.5 8.128 3.11798 9 4.08692C9.872 3.11798 11.208 2.5 12.6 2.5C15.064 2.5 17 4.33869 17 6.69619C17 9.57248 14.28 11.93 10.16 15.4929L9 16.5Z" fill={favorite ? "#e53935" : "currentColor"} />
|
<path d="M9 16.5L7.84 15.4929C3.72 11.93 1 9.57248 1 6.69619C1 4.33869 2.936 2.5 5.4 2.5C6.792 2.5 8.128 3.11798 9 4.08692C9.872 3.11798 11.208 2.5 12.6 2.5C15.064 2.5 17 4.33869 17 6.69619C17 9.57248 14.28 11.93 10.16 15.4929L9 16.5Z" fill={favorite ? "#e53935" : "currentColor"} />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="bdel"
|
className="bdel"
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label="Удалить из корзины"
|
aria-label="Удалить из корзины"
|
||||||
onClick={onRemove}
|
onClick={onRemove}
|
||||||
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onRemove && onRemove()}
|
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onRemove && onRemove()}
|
||||||
style={{ display: 'inline-flex', cursor: 'pointer', transition: 'color 0.2s' }}
|
style={{ display: 'inline-flex', cursor: 'pointer', transition: 'color 0.2s' }}
|
||||||
onMouseEnter={e => {
|
onMouseEnter={e => {
|
||||||
const path = e.currentTarget.querySelector('path');
|
const path = e.currentTarget.querySelector('path');
|
||||||
if (path) path.setAttribute('fill', '#ec1c24');
|
if (path) path.setAttribute('fill', '#ec1c24');
|
||||||
}}
|
}}
|
||||||
onMouseLeave={e => {
|
onMouseLeave={e => {
|
||||||
const path = e.currentTarget.querySelector('path');
|
const path = e.currentTarget.querySelector('path');
|
||||||
if (path) path.setAttribute('fill', '#D0D0D0');
|
if (path) path.setAttribute('fill', '#D0D0D0');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
d="M4.625 17.5C4.14375 17.5 3.73192 17.3261 3.3895 16.9782C3.04708 16.6304 2.87558 16.2117 2.875 15.7222V4.16667H2V2.38889H6.375V1.5H11.625V2.38889H16V4.16667H15.125V15.7222C15.125 16.2111 14.9538 16.6298 14.6114 16.9782C14.269 17.3267 13.8568 17.5006 13.375 17.5H4.625ZM6.375 13.9444H8.125V5.94444H6.375V13.9444ZM9.875 13.9444H11.625V5.94444H9.875V13.9444Z"
|
d="M4.625 17.5C4.14375 17.5 3.73192 17.3261 3.3895 16.9782C3.04708 16.6304 2.87558 16.2117 2.875 15.7222V4.16667H2V2.38889H6.375V1.5H11.625V2.38889H16V4.16667H15.125V15.7222C15.125 16.2111 14.9538 16.6298 14.6114 16.9782C14.269 17.3267 13.8568 17.5006 13.375 17.5H4.625ZM6.375 13.9444H8.125V5.94444H6.375V13.9444ZM9.875 13.9444H11.625V5.94444H9.875V13.9444Z"
|
||||||
fill="#D0D0D0"
|
fill="#D0D0D0"
|
||||||
style={{ transition: 'fill 0.2s' }}
|
style={{ transition: 'fill 0.2s' }}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -3,7 +3,11 @@ import CartItem from "./CartItem";
|
|||||||
import { useCart } from "@/contexts/CartContext";
|
import { useCart } from "@/contexts/CartContext";
|
||||||
import { useFavorites } from "@/contexts/FavoritesContext";
|
import { useFavorites } from "@/contexts/FavoritesContext";
|
||||||
|
|
||||||
const CartList: React.FC = () => {
|
interface CartListProps {
|
||||||
|
isSummaryStep?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CartList: React.FC<CartListProps> = ({ isSummaryStep = false }) => {
|
||||||
const { state, toggleSelect, updateComment, removeItem, selectAll, removeSelected, updateQuantity } = useCart();
|
const { state, toggleSelect, updateComment, removeItem, selectAll, removeSelected, updateQuantity } = useCart();
|
||||||
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
|
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
|
||||||
const { items } = state;
|
const { items } = state;
|
||||||
@ -25,24 +29,18 @@ const CartList: React.FC = () => {
|
|||||||
const handleFavorite = (id: string) => {
|
const handleFavorite = (id: string) => {
|
||||||
const item = items.find(item => item.id === id);
|
const item = items.find(item => item.id === id);
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
const isInFavorites = isFavorite(item.productId, item.offerKey, item.article, item.brand);
|
const isInFavorites = isFavorite(item.productId, item.offerKey, item.article, item.brand);
|
||||||
|
|
||||||
if (isInFavorites) {
|
if (isInFavorites) {
|
||||||
// Находим товар в избранном по правильному ID
|
|
||||||
const favoriteItem = favorites.find((fav: any) => {
|
const favoriteItem = favorites.find((fav: any) => {
|
||||||
// Проверяем по разным комбинациям идентификаторов
|
|
||||||
if (item.productId && fav.productId === item.productId) return true;
|
if (item.productId && fav.productId === item.productId) return true;
|
||||||
if (item.offerKey && fav.offerKey === item.offerKey) return true;
|
if (item.offerKey && fav.offerKey === item.offerKey) return true;
|
||||||
if (fav.article === item.article && fav.brand === item.brand) return true;
|
if (fav.article === item.article && fav.brand === item.brand) return true;
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (favoriteItem) {
|
if (favoriteItem) {
|
||||||
removeFromFavorites(favoriteItem.id);
|
removeFromFavorites(favoriteItem.id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Добавляем в избранное
|
|
||||||
addToFavorites({
|
addToFavorites({
|
||||||
productId: item.productId,
|
productId: item.productId,
|
||||||
offerKey: item.offerKey,
|
offerKey: item.offerKey,
|
||||||
@ -68,59 +66,73 @@ const CartList: React.FC = () => {
|
|||||||
updateQuantity(id, count);
|
updateQuantity(id, count);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Функция для форматирования цены
|
|
||||||
const formatPrice = (price: number, currency: string = 'RUB') => {
|
const formatPrice = (price: number, currency: string = 'RUB') => {
|
||||||
return `${price.toLocaleString('ru-RU')} ${currency === 'RUB' ? '₽' : currency}`;
|
return `${price.toLocaleString('ru-RU')} ${currency === 'RUB' ? '₽' : currency}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// На втором шаге показываем только выбранные товары
|
||||||
|
const displayItems = isSummaryStep ? items.filter(item => item.selected) : items;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-layout-vflex flex-block-48">
|
<div className="w-layout-vflex flex-block-48">
|
||||||
<div className="w-layout-vflex product-list-cart">
|
<div className="w-layout-vflex product-list-cart">
|
||||||
<div className="w-layout-hflex multi-control">
|
{!isSummaryStep && (
|
||||||
<div className="w-layout-hflex select-all-block" onClick={handleSelectAll} style={{ cursor: 'pointer' }}>
|
<div className="w-layout-hflex multi-control">
|
||||||
<div
|
<div className="w-layout-hflex select-all-block" onClick={handleSelectAll} style={{ cursor: 'pointer' }}>
|
||||||
className={"div-block-7" + (allSelected ? " active" : "")}
|
<div
|
||||||
style={{ marginRight: 8, cursor: 'pointer' }}
|
className={"div-block-7" + (allSelected ? " active" : "")}
|
||||||
>
|
style={{ marginRight: 8, cursor: 'pointer' }}
|
||||||
{allSelected && (
|
>
|
||||||
<svg width="14" height="10" viewBox="0 0 14 10" fill="none">
|
{allSelected && (
|
||||||
<path d="M2 5.5L6 9L12 2" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
<svg width="14" height="10" viewBox="0 0 14 10" fill="none">
|
||||||
</svg>
|
<path d="M2 5.5L6 9L12 2" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
)}
|
</svg>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="text-block-30">Выделить всё</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-layout-hflex select-all-block" onClick={handleRemoveSelected} style={{ cursor: 'pointer' }}
|
||||||
|
onMouseEnter={e => {
|
||||||
|
const path = (e.currentTarget.querySelector('path'));
|
||||||
|
if (path) path.setAttribute('fill', '#ec1c24');
|
||||||
|
}}
|
||||||
|
onMouseLeave={e => {
|
||||||
|
const path = (e.currentTarget.querySelector('path'));
|
||||||
|
if (path) path.setAttribute('fill', '#D0D0D0');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="text-block-30">Удалить выбранные</div>
|
||||||
|
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg" className="image-13">
|
||||||
|
<path
|
||||||
|
d="M4.625 17.5C4.14375 17.5 3.73192 17.3261 3.3895 16.9782C3.04708 16.6304 2.87558 16.2117 2.875 15.7222V4.16667H2V2.38889H6.375V1.5H11.625V2.38889H16V4.16667H15.125V15.7222C15.125 16.2111 14.9538 16.6298 14.6114 16.9782C14.269 17.3267 13.8568 17.5006 13.375 17.5H4.625ZM6.375 13.9444H8.125V5.94444H6.375V13.9444ZM9.875 13.9444H11.625V5.94444H9.875V13.9444Z"
|
||||||
|
fill="#D0D0D0"
|
||||||
|
style={{ transition: 'fill 0.2s' }}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-block-30">Выделить всё</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="w-layout-hflex select-all-block" onClick={handleRemoveSelected} style={{ cursor: 'pointer' }}
|
)}
|
||||||
onMouseEnter={e => {
|
{displayItems.length === 0 ? (
|
||||||
const path = (e.currentTarget.querySelector('path'));
|
|
||||||
if (path) path.setAttribute('fill', '#ec1c24');
|
|
||||||
}}
|
|
||||||
onMouseLeave={e => {
|
|
||||||
const path = (e.currentTarget.querySelector('path'));
|
|
||||||
if (path) path.setAttribute('fill', '#D0D0D0');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="text-block-30">Удалить выбранные</div>
|
|
||||||
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg" className="image-13">
|
|
||||||
<path
|
|
||||||
d="M4.625 17.5C4.14375 17.5 3.73192 17.3261 3.3895 16.9782C3.04708 16.6304 2.87558 16.2117 2.875 15.7222V4.16667H2V2.38889H6.375V1.5H11.625V2.38889H16V4.16667H15.125V15.7222C15.125 16.2111 14.9538 16.6298 14.6114 16.9782C14.269 17.3267 13.8568 17.5006 13.375 17.5H4.625ZM6.375 13.9444H8.125V5.94444H6.375V13.9444ZM9.875 13.9444H11.625V5.94444H9.875V13.9444Z"
|
|
||||||
fill="#D0D0D0"
|
|
||||||
style={{ transition: 'fill 0.2s' }}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{items.length === 0 ? (
|
|
||||||
<div className="empty-cart-message" style={{ textAlign: 'center', padding: '2rem', color: '#666' }}>
|
<div className="empty-cart-message" style={{ textAlign: 'center', padding: '2rem', color: '#666' }}>
|
||||||
<p>Ваша корзина пуста</p>
|
<p>Ваша корзина пуста</p>
|
||||||
<p>Добавьте товары из каталога</p>
|
<p>Добавьте товары из каталога</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
items.map((item) => {
|
displayItems.map((item, idx) => {
|
||||||
const isInFavorites = isFavorite(item.productId, item.offerKey, item.article, item.brand);
|
const isInFavorites = isFavorite(item.productId, item.offerKey, item.article, item.brand);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="div-block-21" key={item.id}>
|
<div
|
||||||
|
className={
|
||||||
|
"div-block-21" +
|
||||||
|
(isSummaryStep && idx === 0 ? " border-t-0" : "")
|
||||||
|
}
|
||||||
|
style={
|
||||||
|
isSummaryStep && idx === 0
|
||||||
|
? { borderTop: 'none' }
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
key={item.id}
|
||||||
|
>
|
||||||
<CartItem
|
<CartItem
|
||||||
name={item.name}
|
name={item.name}
|
||||||
description={item.description}
|
description={item.description}
|
||||||
@ -137,6 +149,8 @@ const CartList: React.FC = () => {
|
|||||||
onComment={(comment) => handleComment(item.id, comment)}
|
onComment={(comment) => handleComment(item.id, comment)}
|
||||||
onCountChange={(count) => handleCountChange(item.id, count)}
|
onCountChange={(count) => handleCountChange(item.id, count)}
|
||||||
onRemove={() => handleRemove(item.id)}
|
onRemove={() => handleRemove(item.id)}
|
||||||
|
isSummaryStep={isSummaryStep}
|
||||||
|
itemNumber={idx + 1}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -26,8 +26,17 @@ const CartList2: React.FC = () => {
|
|||||||
<p>Вернитесь на предыдущий шаг и выберите товары</p>
|
<p>Вернитесь на предыдущий шаг и выберите товары</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
selectedItems.map((item) => (
|
selectedItems.map((item, index) => (
|
||||||
<div className="div-block-21-copy" key={item.id}>
|
<div
|
||||||
|
className={
|
||||||
|
"div-block-21-copy" +
|
||||||
|
(index === 0 ? " border-t-0" : "")
|
||||||
|
}
|
||||||
|
style={
|
||||||
|
index === 0 ? { borderTop: 'none' } : undefined
|
||||||
|
}
|
||||||
|
key={item.id}
|
||||||
|
>
|
||||||
<div className="w-layout-hflex cart-item-check">
|
<div className="w-layout-hflex cart-item-check">
|
||||||
<div className="w-layout-hflex info-block-search">
|
<div className="w-layout-hflex info-block-search">
|
||||||
<div className="text-block-35">{item.quantity}</div>
|
<div className="text-block-35">{item.quantity}</div>
|
||||||
|
@ -5,7 +5,12 @@ import { useMutation, useQuery } from "@apollo/client";
|
|||||||
import { CREATE_ORDER, CREATE_PAYMENT, GET_CLIENT_ME, GET_CLIENT_DELIVERY_ADDRESSES } from "@/lib/graphql";
|
import { CREATE_ORDER, CREATE_PAYMENT, GET_CLIENT_ME, GET_CLIENT_DELIVERY_ADDRESSES } from "@/lib/graphql";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
const CartSummary: React.FC = () => {
|
interface CartSummaryProps {
|
||||||
|
step: number;
|
||||||
|
setStep: (step: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CartSummary: React.FC<CartSummaryProps> = ({ step, setStep }) => {
|
||||||
const { state, updateDelivery, updateOrderComment, clearCart } = useCart();
|
const { state, updateDelivery, updateOrderComment, clearCart } = useCart();
|
||||||
const { summary, delivery, items, orderComment } = state;
|
const { summary, delivery, items, orderComment } = state;
|
||||||
const legalEntityDropdownRef = useRef<HTMLDivElement>(null);
|
const legalEntityDropdownRef = useRef<HTMLDivElement>(null);
|
||||||
@ -16,7 +21,6 @@ const CartSummary: React.FC = () => {
|
|||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [isProcessing, setIsProcessing] = useState(false);
|
const [isProcessing, setIsProcessing] = useState(false);
|
||||||
const [showAuthWarning, setShowAuthWarning] = useState(false);
|
const [showAuthWarning, setShowAuthWarning] = useState(false);
|
||||||
const [step, setStep] = useState(1);
|
|
||||||
|
|
||||||
// Новые состояния для первого шага
|
// Новые состояния для первого шага
|
||||||
const [selectedLegalEntity, setSelectedLegalEntity] = useState<string>("");
|
const [selectedLegalEntity, setSelectedLegalEntity] = useState<string>("");
|
||||||
@ -135,25 +139,20 @@ const CartSummary: React.FC = () => {
|
|||||||
toast.error('Пожалуйста, введите имя получателя');
|
toast.error('Пожалуйста, введите имя получателя');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!recipientPhone.trim()) {
|
if (!recipientPhone.trim()) {
|
||||||
toast.error('Пожалуйста, введите телефон получателя');
|
toast.error('Пожалуйста, введите телефон получателя');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedDeliveryAddress.trim()) {
|
if (!selectedDeliveryAddress.trim()) {
|
||||||
toast.error('Пожалуйста, выберите адрес доставки');
|
toast.error('Пожалуйста, выберите адрес доставки');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем данные доставки без стоимости
|
|
||||||
updateDelivery({
|
updateDelivery({
|
||||||
address: selectedDeliveryAddress,
|
address: selectedDeliveryAddress,
|
||||||
cost: 0, // Стоимость включена в товары
|
cost: 0,
|
||||||
date: 'Включена в стоимость товаров',
|
date: 'Включена в стоимость товаров',
|
||||||
time: 'Способ доставки указан в адресе'
|
time: 'Способ доставки указан в адресе'
|
||||||
});
|
});
|
||||||
|
|
||||||
setStep(2);
|
setStep(2);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -894,7 +893,7 @@ const CartSummary: React.FC = () => {
|
|||||||
{error && <div style={{ color: 'red', marginTop: 10 }}>{error}</div>}
|
{error && <div style={{ color: 'red', marginTop: 10 }}>{error}</div>}
|
||||||
|
|
||||||
{/* Кнопка "Назад" */}
|
{/* Кнопка "Назад" */}
|
||||||
{/* <button
|
<button
|
||||||
onClick={handleBackToStep1}
|
onClick={handleBackToStep1}
|
||||||
style={{
|
style={{
|
||||||
background: 'none',
|
background: 'none',
|
||||||
@ -908,7 +907,7 @@ const CartSummary: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
← Назад к настройкам доставки
|
← Назад к настройкам доставки
|
||||||
</button> */}
|
</button>
|
||||||
|
|
||||||
<div className="w-layout-hflex privacy-consent" style={{ cursor: 'pointer' }} onClick={() => setConsent((v) => !v)}>
|
<div className="w-layout-hflex privacy-consent" style={{ cursor: 'pointer' }} onClick={() => setConsent((v) => !v)}>
|
||||||
<div
|
<div
|
||||||
|
@ -208,7 +208,7 @@ const CatalogGroupsSection: React.FC<CatalogGroupsSectionProps> = ({
|
|||||||
vehicleId,
|
vehicleId,
|
||||||
...(ssd && ssd.trim() !== '' && { ssd })
|
...(ssd && ssd.trim() !== '' && { ssd })
|
||||||
},
|
},
|
||||||
skip: !catalogCode || !vehicleId || catalogType !== 'quickGroups',
|
skip: !catalogCode || vehicleId === undefined || vehicleId === null || catalogType !== 'quickGroups',
|
||||||
errorPolicy: 'all'
|
errorPolicy: 'all'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -33,7 +33,7 @@ const CategoriesSection: React.FC<CategoriesSectionProps> = ({
|
|||||||
vehicleId,
|
vehicleId,
|
||||||
...(ssd && ssd.trim() !== '' && { ssd })
|
...(ssd && ssd.trim() !== '' && { ssd })
|
||||||
},
|
},
|
||||||
skip: !catalogCode || !vehicleId,
|
skip: !catalogCode || vehicleId === undefined || vehicleId === null,
|
||||||
errorPolicy: 'all'
|
errorPolicy: 'all'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useLazyQuery } from '@apollo/client';
|
import { useLazyQuery } from '@apollo/client';
|
||||||
import { LaximoFulltextSearchResult, LaximoFulltextDetail, LaximoOEMResult } from '@/types/laximo';
|
import { LaximoFulltextSearchResult, LaximoFulltextDetail, LaximoOEMResult } from '@/types/laximo';
|
||||||
import { SEARCH_LAXIMO_FULLTEXT, SEARCH_LAXIMO_OEM } from '@/lib/graphql';
|
import { GET_LAXIMO_FULLTEXT_SEARCH, SEARCH_LAXIMO_OEM } from '@/lib/graphql/laximo';
|
||||||
import PartDetailCard from './PartDetailCard';
|
import PartDetailCard from './PartDetailCard';
|
||||||
|
|
||||||
interface FulltextSearchSectionProps {
|
interface FulltextSearchSectionProps {
|
||||||
@ -17,7 +17,7 @@ const FulltextSearchSection: React.FC<FulltextSearchSectionProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
|
||||||
const [executeSearch, { data, loading, error }] = useLazyQuery(SEARCH_LAXIMO_FULLTEXT, {
|
const [executeSearch, { data, loading, error }] = useLazyQuery(GET_LAXIMO_FULLTEXT_SEARCH, {
|
||||||
errorPolicy: 'all'
|
errorPolicy: 'all'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -187,13 +187,13 @@ const GroupDetailsSection: React.FC<GroupDetailsSectionProps> = ({
|
|||||||
const { data, loading, error } = useQuery<{ laximoQuickDetail: LaximoQuickDetail }>(
|
const { data, loading, error } = useQuery<{ laximoQuickDetail: LaximoQuickDetail }>(
|
||||||
GET_LAXIMO_QUICK_DETAIL,
|
GET_LAXIMO_QUICK_DETAIL,
|
||||||
{
|
{
|
||||||
variables: {
|
variables: quickGroupId ? {
|
||||||
catalogCode,
|
catalogCode,
|
||||||
vehicleId,
|
vehicleId,
|
||||||
quickGroupId,
|
quickGroupId,
|
||||||
ssd
|
ssd
|
||||||
},
|
} : undefined,
|
||||||
skip: !catalogCode || !vehicleId || !quickGroupId || !ssd,
|
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !quickGroupId || !ssd || ssd.trim() === '',
|
||||||
errorPolicy: 'all'
|
errorPolicy: 'all'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -30,7 +30,7 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
|
|||||||
onSuccess={handleAuthSuccess}
|
onSuccess={handleAuthSuccess}
|
||||||
/>
|
/>
|
||||||
</header>
|
</header>
|
||||||
<main className="pt-[132px]">{children}</main>
|
<main className="pt-[108px] md:pt-[131px]">{children}</main>
|
||||||
<MobileMenuBottomSection onOpenAuthModal={() => setAuthModalOpen(true)} />
|
<MobileMenuBottomSection onOpenAuthModal={() => setAuthModalOpen(true)} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -154,13 +154,13 @@ const QuickDetailSection: React.FC<QuickDetailSectionProps> = ({
|
|||||||
const { data: quickDetailData, loading: quickDetailLoading, error: quickDetailError } = useQuery<{ laximoQuickDetail: LaximoQuickDetail }>(
|
const { data: quickDetailData, loading: quickDetailLoading, error: quickDetailError } = useQuery<{ laximoQuickDetail: LaximoQuickDetail }>(
|
||||||
GET_LAXIMO_QUICK_DETAIL,
|
GET_LAXIMO_QUICK_DETAIL,
|
||||||
{
|
{
|
||||||
variables: {
|
variables: selectedGroup?.quickgroupid ? {
|
||||||
catalogCode,
|
catalogCode,
|
||||||
vehicleId,
|
vehicleId,
|
||||||
quickGroupId: selectedGroup.quickgroupid,
|
quickGroupId: selectedGroup.quickgroupid,
|
||||||
ssd
|
ssd
|
||||||
},
|
} : undefined,
|
||||||
skip: !catalogCode || !vehicleId || !selectedGroup.quickgroupid || !ssd,
|
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !selectedGroup?.quickgroupid || !ssd || ssd.trim() === '',
|
||||||
errorPolicy: 'all',
|
errorPolicy: 'all',
|
||||||
fetchPolicy: 'cache-and-network' // Принудительно запрашиваем данные
|
fetchPolicy: 'cache-and-network' // Принудительно запрашиваем данные
|
||||||
}
|
}
|
||||||
@ -169,11 +169,28 @@ const QuickDetailSection: React.FC<QuickDetailSectionProps> = ({
|
|||||||
const quickDetail = quickDetailData?.laximoQuickDetail;
|
const quickDetail = quickDetailData?.laximoQuickDetail;
|
||||||
|
|
||||||
// Добавляем отладочную информацию
|
// Добавляем отладочную информацию
|
||||||
console.log('🔍 QuickDetailSection Debug:');
|
console.log('🔍 QuickDetailSection Debug:', {
|
||||||
console.log('📊 quickDetailData:', quickDetailData);
|
catalogCode,
|
||||||
console.log('📋 quickDetail:', quickDetail);
|
vehicleId,
|
||||||
console.log('🏗️ quickDetail.units:', quickDetail?.units);
|
vehicleIdType: typeof vehicleId,
|
||||||
console.log('⚙️ Variables:', { catalogCode, vehicleId, quickGroupId: selectedGroup.quickgroupid, ssd });
|
quickGroupId: selectedGroup?.quickgroupid,
|
||||||
|
quickGroupIdType: typeof selectedGroup?.quickgroupid,
|
||||||
|
ssd: ssd ? `${ssd.substring(0, 30)}...` : 'отсутствует',
|
||||||
|
ssdType: typeof ssd,
|
||||||
|
ssdLength: ssd?.length,
|
||||||
|
hasData: !!quickDetailData,
|
||||||
|
hasQuickDetail: !!quickDetail,
|
||||||
|
unitsCount: quickDetail?.units?.length || 0,
|
||||||
|
loading: quickDetailLoading,
|
||||||
|
error: quickDetailError?.message,
|
||||||
|
skipCondition: !catalogCode || vehicleId === undefined || vehicleId === null || !selectedGroup?.quickgroupid || !ssd,
|
||||||
|
skipDetails: {
|
||||||
|
noCatalogCode: !catalogCode,
|
||||||
|
noVehicleId: vehicleId === undefined || vehicleId === null,
|
||||||
|
noQuickGroupId: !selectedGroup?.quickgroupid,
|
||||||
|
noSsd: !ssd
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Если выбран узел для детального просмотра, показываем UnitDetailsSection
|
// Если выбран узел для детального просмотра, показываем UnitDetailsSection
|
||||||
if (selectedUnit) {
|
if (selectedUnit) {
|
||||||
@ -213,6 +230,20 @@ const QuickDetailSection: React.FC<QuickDetailSectionProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (quickDetailError) {
|
if (quickDetailError) {
|
||||||
|
console.error('🚨 QuickDetailSection Error Details:', {
|
||||||
|
message: quickDetailError.message,
|
||||||
|
graphQLErrors: quickDetailError.graphQLErrors,
|
||||||
|
networkError: quickDetailError.networkError,
|
||||||
|
extraInfo: quickDetailError.extraInfo,
|
||||||
|
selectedGroup: selectedGroup,
|
||||||
|
variables: selectedGroup?.quickgroupid ? {
|
||||||
|
catalogCode,
|
||||||
|
vehicleId,
|
||||||
|
quickGroupId: selectedGroup.quickgroupid,
|
||||||
|
ssd
|
||||||
|
} : 'undefined (no variables sent)'
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@ -231,6 +262,33 @@ const QuickDetailSection: React.FC<QuickDetailSectionProps> = ({
|
|||||||
<h3 className="text-lg font-medium text-red-600 mb-2">Ошибка загрузки деталей</h3>
|
<h3 className="text-lg font-medium text-red-600 mb-2">Ошибка загрузки деталей</h3>
|
||||||
<p className="text-red-700">Не удалось загрузить детали для группы "{selectedGroup.name}"</p>
|
<p className="text-red-700">Не удалось загрузить детали для группы "{selectedGroup.name}"</p>
|
||||||
<p className="text-sm text-red-600 mt-2">Ошибка: {quickDetailError.message}</p>
|
<p className="text-sm text-red-600 mt-2">Ошибка: {quickDetailError.message}</p>
|
||||||
|
|
||||||
|
{/* Отладочная информация */}
|
||||||
|
<details className="mt-4">
|
||||||
|
<summary className="text-sm text-red-700 cursor-pointer hover:text-red-800">
|
||||||
|
🔧 Показать отладочную информацию
|
||||||
|
</summary>
|
||||||
|
<div className="mt-2 p-3 bg-red-100 rounded text-xs">
|
||||||
|
<div><strong>Catalog Code:</strong> {catalogCode}</div>
|
||||||
|
<div><strong>Vehicle ID:</strong> {vehicleId} (type: {typeof vehicleId})</div>
|
||||||
|
<div><strong>Quick Group ID:</strong> {selectedGroup?.quickgroupid} (type: {typeof selectedGroup?.quickgroupid})</div>
|
||||||
|
<div><strong>SSD:</strong> {ssd ? `${ssd.substring(0, 100)}...` : 'отсутствует'} (length: {ssd?.length})</div>
|
||||||
|
<div className="mt-2">
|
||||||
|
<strong>GraphQL Errors:</strong>
|
||||||
|
<pre className="mt-1 text-xs overflow-auto">
|
||||||
|
{JSON.stringify(quickDetailError.graphQLErrors, null, 2)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
{quickDetailError.networkError && (
|
||||||
|
<div className="mt-2">
|
||||||
|
<strong>Network Error:</strong>
|
||||||
|
<pre className="mt-1 text-xs overflow-auto">
|
||||||
|
{JSON.stringify(quickDetailError.networkError, null, 2)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -464,7 +522,7 @@ const QuickGroupsSection: React.FC<QuickGroupsSectionProps> = ({
|
|||||||
vehicleId,
|
vehicleId,
|
||||||
...(ssd && ssd.trim() !== '' && { ssd })
|
...(ssd && ssd.trim() !== '' && { ssd })
|
||||||
},
|
},
|
||||||
skip: !catalogCode || !vehicleId,
|
skip: !catalogCode || vehicleId === undefined || vehicleId === null,
|
||||||
errorPolicy: 'all'
|
errorPolicy: 'all'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -39,7 +39,7 @@ const UnitDetailsSection: React.FC<UnitDetailsSectionProps> = ({
|
|||||||
unitId,
|
unitId,
|
||||||
ssd: ssd || ''
|
ssd: ssd || ''
|
||||||
},
|
},
|
||||||
skip: !catalogCode || !vehicleId || !unitId,
|
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId,
|
||||||
errorPolicy: 'all'
|
errorPolicy: 'all'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -54,7 +54,7 @@ const UnitDetailsSection: React.FC<UnitDetailsSectionProps> = ({
|
|||||||
unitId,
|
unitId,
|
||||||
ssd: ssd || ''
|
ssd: ssd || ''
|
||||||
},
|
},
|
||||||
skip: !catalogCode || !vehicleId || !unitId,
|
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId,
|
||||||
errorPolicy: 'all'
|
errorPolicy: 'all'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -69,7 +69,7 @@ const UnitDetailsSection: React.FC<UnitDetailsSectionProps> = ({
|
|||||||
unitId,
|
unitId,
|
||||||
ssd: ssd || ''
|
ssd: ssd || ''
|
||||||
},
|
},
|
||||||
skip: !catalogCode || !vehicleId || !unitId,
|
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId,
|
||||||
errorPolicy: 'all'
|
errorPolicy: 'all'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -59,7 +59,7 @@ const UnitsSection: React.FC<UnitsSectionProps> = ({
|
|||||||
categoryId,
|
categoryId,
|
||||||
...(ssd && ssd.trim() !== '' && { ssd })
|
...(ssd && ssd.trim() !== '' && { ssd })
|
||||||
},
|
},
|
||||||
skip: !catalogCode || !vehicleId || !categoryId,
|
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !categoryId,
|
||||||
errorPolicy: 'all',
|
errorPolicy: 'all',
|
||||||
fetchPolicy: 'no-cache', // Полностью отключаем кэширование для гарантии свежих данных
|
fetchPolicy: 'no-cache', // Полностью отключаем кэширование для гарантии свежих данных
|
||||||
notifyOnNetworkStatusChange: true
|
notifyOnNetworkStatusChange: true
|
||||||
|
@ -110,7 +110,7 @@ const LegalEntityListBlock: React.FC<LegalEntityListBlockProps> = ({ legalEntiti
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
layer-name="link_control_element"
|
layer-name="link_control_element"
|
||||||
className="flex relative gap-1.5 items-center cursor-pointer hover:text-red-600"
|
className="flex relative gap-1.5 items-center cursor-pointer group"
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={() => router.push('/profile-requisites')}
|
onClick={() => router.push('/profile-requisites')}
|
||||||
@ -130,7 +130,7 @@ const LegalEntityListBlock: React.FC<LegalEntityListBlockProps> = ({ legalEntiti
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
layer-name="Редактировать"
|
layer-name="Редактировать"
|
||||||
className="text-sm leading-5 text-gray-600"
|
className="text-sm leading-5 text-gray-600 group-hover:text-red-600"
|
||||||
>
|
>
|
||||||
Реквизиты компании
|
Реквизиты компании
|
||||||
</div>
|
</div>
|
||||||
@ -141,8 +141,9 @@ const LegalEntityListBlock: React.FC<LegalEntityListBlockProps> = ({ legalEntiti
|
|||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className="flex relative gap-1.5 items-center cursor-pointer hover:text-red-600"
|
className="flex relative gap-1.5 items-center cursor-pointer group"
|
||||||
onClick={() => onEdit && onEdit(entity)}
|
onClick={() => onEdit && onEdit(entity)}
|
||||||
|
aria-label="Редактировать юридическое лицо"
|
||||||
>
|
>
|
||||||
<div className="relative h-4 w-[18px]">
|
<div className="relative h-4 w-[18px]">
|
||||||
<Image
|
<Image
|
||||||
@ -153,26 +154,37 @@ const LegalEntityListBlock: React.FC<LegalEntityListBlockProps> = ({ legalEntiti
|
|||||||
className="absolute left-0.5 top-0"
|
className="absolute left-0.5 top-0"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm leading-5 text-gray-600">
|
<div className="text-sm leading-5 text-gray-600 group-hover:text-red-600">
|
||||||
Редактировать
|
Редактировать
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className="flex relative gap-1.5 items-center cursor-pointer hover:text-red-600"
|
className="flex relative gap-1.5 items-center cursor-pointer group"
|
||||||
|
aria-label="Удалить юридическое лицо"
|
||||||
onClick={() => handleDelete(entity.id, entity.shortName)}
|
onClick={() => handleDelete(entity.id, entity.shortName)}
|
||||||
|
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && handleDelete(entity.id, entity.shortName)}
|
||||||
|
style={{ display: 'inline-flex', cursor: 'pointer', transition: 'color 0.2s' }}
|
||||||
|
onMouseEnter={e => {
|
||||||
|
const path = e.currentTarget.querySelector('path');
|
||||||
|
if (path) path.setAttribute('fill', '#ec1c24');
|
||||||
|
}}
|
||||||
|
onMouseLeave={e => {
|
||||||
|
const path = e.currentTarget.querySelector('path');
|
||||||
|
if (path) path.setAttribute('fill', '#D0D0D0');
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="relative h-4 w-[18px]">
|
<div className="relative h-4 w-4">
|
||||||
<Image
|
<svg width="16" height="16" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
src="/images/delete.svg"
|
<path
|
||||||
alt="Удалить"
|
d="M4.625 17.5C4.14375 17.5 3.73192 17.3261 3.3895 16.9782C3.04708 16.6304 2.87558 16.2117 2.875 15.7222V4.16667H2V2.38889H6.375V1.5H11.625V2.38889H16V4.16667H15.125V15.7222C15.125 16.2111 14.9538 16.6298 14.6114 16.9782C14.269 17.3267 13.8568 17.5006 13.375 17.5H4.625ZM6.375 13.9444H8.125V5.94444H6.375V13.9444ZM9.875 13.9444H11.625V5.94444H9.875V13.9444Z"
|
||||||
width={16}
|
fill="#D0D0D0"
|
||||||
height={16}
|
style={{ transition: 'fill 0.2s' }}
|
||||||
className="absolute left-0.5 top-0"
|
/>
|
||||||
/>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm leading-5 text-gray-600">
|
<div className="text-sm leading-5 text-gray-600 group-hover:text-red-600">
|
||||||
Удалить
|
Удалить
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -52,14 +52,50 @@ const ProfileAddressCard: React.FC<ProfileAddressCardProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex justify-between items-start self-stretch">
|
<div className="flex justify-between items-start self-stretch max-sm:flex-row max-sm:gap-4 max-sm:justify-start max-sm:items-center">
|
||||||
<div className="flex gap-1.5 items-center cursor-pointer group" onClick={onEdit}>
|
<div
|
||||||
<img src="/images/edit.svg" alt="edit" width={18} height={18} className="mr-1.5 group-hover:filter-red" />
|
className="flex gap-1.5 items-center cursor-pointer group"
|
||||||
<div className="relative text-sm leading-5 text-gray-600">Редактировать</div>
|
onClick={onEdit}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label="Редактировать адрес"
|
||||||
|
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onEdit && onEdit()}
|
||||||
|
onMouseEnter={e => {
|
||||||
|
const svg = (e.currentTarget as HTMLElement).querySelector('img');
|
||||||
|
if (svg) (svg as HTMLImageElement).style.filter = 'invert(32%) sepia(97%) saturate(7490%) hue-rotate(355deg) brightness(97%) contrast(108%)';
|
||||||
|
}}
|
||||||
|
onMouseLeave={e => {
|
||||||
|
const svg = (e.currentTarget as HTMLElement).querySelector('img');
|
||||||
|
if (svg) (svg as HTMLImageElement).style.filter = '';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img src="/images/edit.svg" alt="edit" width={18} height={18} className="mr-1.5" />
|
||||||
|
<div className="relative text-sm leading-5 text-gray-600 group-hover:text-red-600 max-sm:hidden">Редактировать</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1.5 items-center cursor-pointer group" onClick={onDelete}>
|
<div
|
||||||
<img src="/images/delete.svg" alt="delete" width={18} height={18} className="mr-1.5 group-hover:filter-red" />
|
className="flex gap-1.5 items-center cursor-pointer group"
|
||||||
<div className="relative text-sm leading-5 text-gray-600">Удалить</div>
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label="Удалить адрес"
|
||||||
|
onClick={onDelete}
|
||||||
|
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onDelete && onDelete()}
|
||||||
|
onMouseEnter={e => {
|
||||||
|
const path = e.currentTarget.querySelector('path');
|
||||||
|
if (path) path.setAttribute('fill', '#ec1c24');
|
||||||
|
}}
|
||||||
|
onMouseLeave={e => {
|
||||||
|
const path = e.currentTarget.querySelector('path');
|
||||||
|
if (path) path.setAttribute('fill', '#D0D0D0');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M4.625 17.5C4.14375 17.5 3.73192 17.3261 3.3895 16.9782C3.04708 16.6304 2.87558 16.2117 2.875 15.7222V4.16667H2V2.38889H6.375V1.5H11.625V2.38889H16V4.16667H15.125V15.7222C15.125 16.2111 14.9538 16.6298 14.6114 16.9782C14.269 17.3267 13.8568 17.5006 13.375 17.5H4.625ZM6.375 13.9444H8.125V5.94444H6.375V13.9444ZM9.875 13.9444H11.625V5.94444H9.875V13.9444Z"
|
||||||
|
fill="#D0D0D0"
|
||||||
|
style={{ transition: 'fill 0.2s' }}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div className="relative text-sm leading-5 text-gray-600 group-hover:text-red-600 max-sm:hidden">Удалить</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{onSelectMain && (
|
{onSelectMain && (
|
||||||
|
@ -89,11 +89,11 @@ const ProfileBalanceCard: React.FC<ProfileBalanceCardProps> = ({
|
|||||||
{balance}
|
{balance}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row gap-5 items-end mt-5 w-full max-sm:flex-col">
|
<div className="flex flex-row gap-5 items-end mt-5 w-full max-sm:flex-col max-sm:items-start">
|
||||||
<div className="flex flex-col flex-1 shrink basis-0">
|
<div className="flex flex-col flex-1 shrink basis-0">
|
||||||
<div className="flex flex-col min-w-[160px]">
|
<div className="flex flex-col min-w-[160px]">
|
||||||
<div className="text-sm leading-snug text-gray-600">Лимит отсрочки</div>
|
<div className="text-sm leading-snug text-gray-600">Лимит отсрочки</div>
|
||||||
<div className="flex flex-col self-start mt-2">
|
<div className="flex flex-col mt-2">
|
||||||
<div className="text-lg font-medium leading-none text-gray-950">{limit}</div>
|
<div className="text-lg font-medium leading-none text-gray-950">{limit}</div>
|
||||||
<div className={`text-sm leading-snug ${isOverLimit ? 'text-red-600' : 'text-gray-600'}`}>
|
<div className={`text-sm leading-snug ${isOverLimit ? 'text-red-600' : 'text-gray-600'}`}>
|
||||||
{limitLeft.includes('Не установлен') ? limitLeft : `Осталось ${limitLeft}`}
|
{limitLeft.includes('Не установлен') ? limitLeft : `Осталось ${limitLeft}`}
|
||||||
|
@ -179,7 +179,7 @@ const ProfileGarageMain = () => {
|
|||||||
|
|
||||||
{!vehiclesLoading && filteredVehicles.map((vehicle) => (
|
{!vehiclesLoading && filteredVehicles.map((vehicle) => (
|
||||||
<div key={vehicle.id} className="mt-8">
|
<div key={vehicle.id} className="mt-8">
|
||||||
<div className="flex flex-col justify-center pr-5 py-3 w-full rounded-lg bg-slate-50 max-md:max-w-full">
|
<div className="flex flex-col justify-center px-5 py-3 w-full rounded-lg bg-slate-50 max-md:max-w-full">
|
||||||
<div className="flex flex-wrap gap-8 items-center w-full max-md:max-w-full">
|
<div className="flex flex-wrap gap-8 items-center w-full max-md:max-w-full">
|
||||||
<div className="flex gap-8 items-center self-stretch my-auto min-w-[240px] max-md:flex-col max-md:min-w-0 max-md:gap-2">
|
<div className="flex gap-8 items-center self-stretch my-auto min-w-[240px] max-md:flex-col max-md:min-w-0 max-md:gap-2">
|
||||||
<div className="self-stretch my-auto text-xl font-bold leading-none text-gray-950">
|
<div className="self-stretch my-auto text-xl font-bold leading-none text-gray-950">
|
||||||
@ -203,15 +203,29 @@ const ProfileGarageMain = () => {
|
|||||||
<div className="flex gap-5 items-center self-stretch pr-2.5 my-auto text-sm leading-snug text-gray-600 whitespace-nowrap">
|
<div className="flex gap-5 items-center self-stretch pr-2.5 my-auto text-sm leading-snug text-gray-600 whitespace-nowrap">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex gap-1.5 items-center self-stretch my-auto cursor-pointer text-sm leading-snug text-gray-600 hover:text-red-600 transition-colors"
|
className="flex gap-1.5 items-center self-stretch my-auto text-sm leading-snug text-gray-600 cursor-pointer bg-transparent group"
|
||||||
|
style={{ outline: 'none' }}
|
||||||
|
aria-label="Удалить автомобиль"
|
||||||
|
tabIndex={0}
|
||||||
onClick={() => handleDeleteVehicle(vehicle.id)}
|
onClick={() => handleDeleteVehicle(vehicle.id)}
|
||||||
|
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && handleDeleteVehicle(vehicle.id)}
|
||||||
|
onMouseEnter={e => {
|
||||||
|
const path = e.currentTarget.querySelector('path');
|
||||||
|
if (path) path.setAttribute('fill', '#ec1c24');
|
||||||
|
}}
|
||||||
|
onMouseLeave={e => {
|
||||||
|
const path = e.currentTarget.querySelector('path');
|
||||||
|
if (path) path.setAttribute('fill', '#D0D0D0');
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<svg width="16" height="16" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
loading="lazy"
|
<path
|
||||||
src="/images/delete.svg"
|
d="M4.625 17.5C4.14375 17.5 3.73192 17.3261 3.3895 16.9782C3.04708 16.6304 2.87558 16.2117 2.875 15.7222V4.16667H2V2.38889H6.375V1.5H11.625V2.38889H16V4.16667H15.125V15.7222C15.125 16.2111 14.9538 16.6298 14.6114 16.9782C14.269 17.3267 13.8568 17.5006 13.375 17.5H4.625ZM6.375 13.9444H8.125V5.94444H6.375V13.9444ZM9.875 13.9444H11.625V5.94444H9.875V13.9444Z"
|
||||||
className="object-contain shrink-0 self-stretch my-auto aspect-[1.12] w-[18px]"
|
fill="#D0D0D0"
|
||||||
/>
|
style={{ transition: 'fill 0.2s' }}
|
||||||
<span className="self-stretch my-auto text-gray-600">
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="self-stretch my-auto text-gray-600 group-hover:text-red-600">
|
||||||
Удалить
|
Удалить
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
@ -418,15 +432,29 @@ const ProfileGarageMain = () => {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex gap-1.5 items-center self-stretch my-auto text-sm leading-snug text-gray-600 cursor-pointer bg-transparent hover:text-red-600 transition-colors"
|
className="flex gap-1.5 items-center self-stretch my-auto text-sm leading-snug text-gray-600 cursor-pointer bg-transparent group"
|
||||||
|
style={{ outline: 'none' }}
|
||||||
|
aria-label="Удалить из истории поиска"
|
||||||
|
tabIndex={0}
|
||||||
onClick={() => handleDeleteFromHistory(historyItem.id)}
|
onClick={() => handleDeleteFromHistory(historyItem.id)}
|
||||||
|
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && handleDeleteFromHistory(historyItem.id)}
|
||||||
|
onMouseEnter={e => {
|
||||||
|
const path = e.currentTarget.querySelector('path');
|
||||||
|
if (path) path.setAttribute('fill', '#ec1c24');
|
||||||
|
}}
|
||||||
|
onMouseLeave={e => {
|
||||||
|
const path = e.currentTarget.querySelector('path');
|
||||||
|
if (path) path.setAttribute('fill', '#D0D0D0');
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<svg width="16" height="16" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
loading="lazy"
|
<path
|
||||||
src="/images/delete.svg"
|
d="M4.625 17.5C4.14375 17.5 3.73192 17.3261 3.3895 16.9782C3.04708 16.6304 2.87558 16.2117 2.875 15.7222V4.16667H2V2.38889H6.375V1.5H11.625V2.38889H16V4.16667H15.125V15.7222C15.125 16.2111 14.9538 16.6298 14.6114 16.9782C14.269 17.3267 13.8568 17.5006 13.375 17.5H4.625ZM6.375 13.9444H8.125V5.94444H6.375V13.9444ZM9.875 13.9444H11.625V5.94444H9.875V13.9444Z"
|
||||||
className="object-contain shrink-0 self-stretch my-auto aspect-[1.12] w-[18px]"
|
fill="#D0D0D0"
|
||||||
/>
|
style={{ transition: 'fill 0.2s' }}
|
||||||
<span className="self-stretch my-auto text-gray-600">
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="self-stretch my-auto text-gray-600 group-hover:text-red-600">
|
||||||
Удалить
|
Удалить
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -80,24 +80,25 @@ const ProfileHistoryItem: React.FC<ProfileHistoryItemProps> = ({
|
|||||||
<div className="w-16 text-center max-md:w-full">
|
<div className="w-16 text-center max-md:w-full">
|
||||||
<button
|
<button
|
||||||
onClick={handleDeleteClick}
|
onClick={handleDeleteClick}
|
||||||
className="p-2 text-red-500 hover:text-red-700 hover:bg-red-50 rounded-lg transition-colors group"
|
className="flex items-center p-2 group"
|
||||||
title="Удалить из истории"
|
title="Удалить из истории"
|
||||||
aria-label="Удалить из истории"
|
aria-label="Удалить из истории"
|
||||||
>
|
tabIndex={0}
|
||||||
<svg
|
onMouseEnter={e => {
|
||||||
width="16"
|
const path = e.currentTarget.querySelector('path');
|
||||||
height="16"
|
if (path) path.setAttribute('fill', '#ec1c24');
|
||||||
viewBox="0 0 24 24"
|
}}
|
||||||
fill="none"
|
onMouseLeave={e => {
|
||||||
stroke="currentColor"
|
const path = e.currentTarget.querySelector('path');
|
||||||
strokeWidth="2"
|
if (path) path.setAttribute('fill', '#D0D0D0');
|
||||||
strokeLinecap="round"
|
}}
|
||||||
strokeLinejoin="round"
|
|
||||||
className="transition-colors"
|
|
||||||
>
|
>
|
||||||
<path d="M3 6h18" className="group-hover:stroke-[#ec1c24]" />
|
<svg width="16" height="16" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" className="group-hover:stroke-[#ec1c24]" />
|
<path
|
||||||
<path d="M8 6V4c0-1 1-2 2-2h4c-1 0 2 1 2 2v2" className="group-hover:stroke-[#ec1c24]" />
|
d="M4.625 17.5C4.14375 17.5 3.73192 17.3261 3.3895 16.9782C3.04708 16.6304 2.87558 16.2117 2.875 15.7222V4.16667H2V2.38889H6.375V1.5H11.625V2.38889H16V4.16667H15.125V15.7222C15.125 16.2111 14.9538 16.6298 14.6114 16.9782C14.269 17.3267 13.8568 17.5006 13.375 17.5H4.625ZM6.375 13.9444H8.125V5.94444H6.375V13.9444ZM9.875 13.9444H11.625V5.94444H9.875V13.9444Z"
|
||||||
|
fill="#D0D0D0"
|
||||||
|
style={{ transition: 'fill 0.2s' }}
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -272,7 +272,7 @@ const ProfileHistoryMain = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col min-h-[526px]">
|
<div className="flex flex-col min-h-[526px]">
|
||||||
<div className="flex flex-wrap gap-5 items-center px-8 py-3 w-full leading-snug text-gray-400 whitespace-nowrap bg-white rounded-lg max-md:px-5 max-md:max-w-full max-md:flex-col">
|
<div className="flex gap-5 items-center px-8 py-3 w-full leading-snug text-gray-400 whitespace-nowrap bg-white rounded-lg max-md:px-5 max-md:max-w-full">
|
||||||
<div className="flex-1 shrink self-stretch my-auto text-gray-400 basis-0 text-ellipsis max-md:max-w-full max-md:w-full">
|
<div className="flex-1 shrink self-stretch my-auto text-gray-400 basis-0 text-ellipsis max-md:max-w-full max-md:w-full">
|
||||||
<SearchInput
|
<SearchInput
|
||||||
value={search}
|
value={search}
|
||||||
@ -280,7 +280,7 @@ const ProfileHistoryMain = () => {
|
|||||||
placeholder="Поиск в истории..."
|
placeholder="Поиск в истории..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2 max-sm:hidden">
|
||||||
{(selectedManufacturer !== "Все" || search.trim() || activeTab !== "Все") && (
|
{(selectedManufacturer !== "Все" || search.trim() || activeTab !== "Все") && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -89,7 +89,7 @@ const ProfileHistoryTabs: React.FC<ProfileHistoryTabsProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div
|
<div
|
||||||
className="relative w-[240px] max-w-full"
|
className="relative w-[240px] max-w-full max-sm:w-full"
|
||||||
ref={dropdownRef}
|
ref={dropdownRef}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
|
@ -10,7 +10,7 @@ const ProfileSettingsActionsBlock: React.FC<ProfileSettingsActionsBlockProps> =
|
|||||||
Сохранить изменения
|
Сохранить изменения
|
||||||
</div>
|
</div>
|
||||||
<div className="gap-2.5 self-stretch px-5 py-4 my-auto rounded-xl border border-red-600 min-h-[50px] min-w-[240px] cursor-pointer bg-white text-gray-950" onClick={onAddLegalEntity}>
|
<div className="gap-2.5 self-stretch px-5 py-4 my-auto rounded-xl border border-red-600 min-h-[50px] min-w-[240px] cursor-pointer bg-white text-gray-950" onClick={onAddLegalEntity}>
|
||||||
Добавить юридическое лицо
|
Добавить юр лицо
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,27 @@
|
|||||||
import React from "react";
|
import React, { useRef, useState } from "react";
|
||||||
|
import { useQuery } from '@apollo/client';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { GET_LAXIMO_UNIT_INFO, GET_LAXIMO_UNIT_IMAGE_MAP } from '@/lib/graphql';
|
||||||
|
import BrandSelectionModal from '../BrandSelectionModal';
|
||||||
|
|
||||||
|
interface KnotInProps {
|
||||||
|
catalogCode: string;
|
||||||
|
vehicleId: string;
|
||||||
|
ssd?: string;
|
||||||
|
unitId: string;
|
||||||
|
unitName?: string;
|
||||||
|
parts?: Array<{
|
||||||
|
detailid?: string;
|
||||||
|
codeonimage?: string | number;
|
||||||
|
oem?: string;
|
||||||
|
name?: string;
|
||||||
|
price?: string | number;
|
||||||
|
brand?: string;
|
||||||
|
availability?: string;
|
||||||
|
note?: string;
|
||||||
|
attributes?: Array<{ key: string; name?: string; value: string }>;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
// Функция для корректного формирования URL изображения
|
// Функция для корректного формирования URL изображения
|
||||||
const getImageUrl = (baseUrl: string, size: string) => {
|
const getImageUrl = (baseUrl: string, size: string) => {
|
||||||
@ -11,25 +34,138 @@ const getImageUrl = (baseUrl: string, size: string) => {
|
|||||||
.replace('%size%', size);
|
.replace('%size%', size);
|
||||||
};
|
};
|
||||||
|
|
||||||
const KnotIn = ({ node }: { node: any }) => {
|
const KnotIn: React.FC<KnotInProps> = ({ catalogCode, vehicleId, ssd, unitId, unitName, parts }) => {
|
||||||
if (!node) return null;
|
const imgRef = useRef<HTMLImageElement>(null);
|
||||||
let imageUrl = '';
|
const [imageScale, setImageScale] = useState({ x: 1, y: 1 });
|
||||||
if (node.imageurl) {
|
const selectedImageSize = 'source';
|
||||||
imageUrl = getImageUrl(node.imageurl, '250');
|
const [isBrandModalOpen, setIsBrandModalOpen] = useState(false);
|
||||||
} else if (node.largeimageurl) {
|
const [selectedDetail, setSelectedDetail] = useState<{ oem: string; name: string } | null>(null);
|
||||||
imageUrl = node.largeimageurl;
|
const router = useRouter();
|
||||||
|
|
||||||
|
// Получаем инфо об узле (для картинки)
|
||||||
|
const { data: unitInfoData, loading: unitInfoLoading, error: unitInfoError } = useQuery(
|
||||||
|
GET_LAXIMO_UNIT_INFO,
|
||||||
|
{
|
||||||
|
variables: { catalogCode, vehicleId, unitId, ssd: ssd || '' },
|
||||||
|
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId,
|
||||||
|
errorPolicy: 'all',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// Получаем карту координат
|
||||||
|
const { data: imageMapData, loading: imageMapLoading, error: imageMapError } = useQuery(
|
||||||
|
GET_LAXIMO_UNIT_IMAGE_MAP,
|
||||||
|
{
|
||||||
|
variables: { catalogCode, vehicleId, unitId, ssd: ssd || '' },
|
||||||
|
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId,
|
||||||
|
errorPolicy: 'all',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const unitInfo = unitInfoData?.laximoUnitInfo;
|
||||||
|
const coordinates = imageMapData?.laximoUnitImageMap?.coordinates || [];
|
||||||
|
const imageUrl = unitInfo?.imageurl ? getImageUrl(unitInfo.imageurl, selectedImageSize) : '';
|
||||||
|
|
||||||
|
// Масштабируем точки после загрузки картинки
|
||||||
|
const handleImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {
|
||||||
|
const img = e.currentTarget;
|
||||||
|
if (!img.naturalWidth || !img.naturalHeight) return;
|
||||||
|
setImageScale({
|
||||||
|
x: img.offsetWidth / img.naturalWidth,
|
||||||
|
y: img.offsetHeight / img.naturalHeight,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Клик по точке: найти part по codeonimage/detailid и открыть BrandSelectionModal
|
||||||
|
const handlePointClick = (codeonimage: string | number) => {
|
||||||
|
if (!parts) return;
|
||||||
|
console.log('Клик по точке:', codeonimage, 'Все детали:', parts);
|
||||||
|
const part = parts.find(
|
||||||
|
(p) =>
|
||||||
|
(p.codeonimage && p.codeonimage.toString() === codeonimage.toString()) ||
|
||||||
|
(p.detailid && p.detailid.toString() === codeonimage.toString())
|
||||||
|
);
|
||||||
|
console.log('Найдена деталь для точки:', part);
|
||||||
|
if (part?.oem) {
|
||||||
|
setSelectedDetail({ oem: part.oem, name: part.name || '' });
|
||||||
|
setIsBrandModalOpen(true);
|
||||||
|
} else {
|
||||||
|
console.warn('Нет артикула (oem) для выбранной точки:', codeonimage, part);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Для отладки: вывести детали и координаты
|
||||||
|
React.useEffect(() => {
|
||||||
|
console.log('KnotIn parts:', parts);
|
||||||
|
console.log('KnotIn coordinates:', coordinates);
|
||||||
|
}, [parts, coordinates]);
|
||||||
|
|
||||||
|
if (unitInfoLoading || imageMapLoading) {
|
||||||
|
return <div className="text-center py-8 text-gray-500">Загружаем схему узла...</div>;
|
||||||
}
|
}
|
||||||
|
if (unitInfoError) {
|
||||||
|
return <div className="text-center py-8 text-red-600">Ошибка загрузки схемы: {unitInfoError.message}</div>;
|
||||||
|
}
|
||||||
|
if (!imageUrl) {
|
||||||
|
return <div className="text-center py-8 text-gray-400">Нет изображения для этого узла</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="knotin">
|
<>
|
||||||
{imageUrl ? (
|
<div className="relative inline-block">
|
||||||
<img src={imageUrl} loading="lazy" alt={node.name || "Изображение узла"} className="image-26" />
|
{/* ВРЕМЕННО: выводим количество точек для быстрой проверки */}
|
||||||
) : (
|
<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={{ width: 200, height: 200, background: '#eee', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
{coordinates.length} точек
|
||||||
Нет изображения
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<img
|
||||||
{/* <div style={{ marginTop: 8, fontWeight: 500 }}>{node.name}</div> */}
|
ref={imgRef}
|
||||||
|
src={imageUrl}
|
||||||
|
loading="lazy"
|
||||||
|
alt={unitName || unitInfo?.name || "Изображение узла"}
|
||||||
|
onLoad={handleImageLoad}
|
||||||
|
className="max-w-full h-auto mx-auto rounded"
|
||||||
|
style={{ maxWidth: 400, display: 'block' }}
|
||||||
|
/>
|
||||||
|
{/* Точки/области */}
|
||||||
|
{coordinates.map((coord: any, idx: number) => {
|
||||||
|
const scaledX = coord.x * imageScale.x;
|
||||||
|
const scaledY = coord.y * imageScale.y;
|
||||||
|
const scaledWidth = coord.width * imageScale.x;
|
||||||
|
const scaledHeight = coord.height * imageScale.y;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={`coord-${unitId}-${idx}-${coord.x}-${coord.y}`}
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label={`Деталь ${coord.codeonimage}`}
|
||||||
|
onKeyDown={e => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') handlePointClick(coord.codeonimage);
|
||||||
|
}}
|
||||||
|
className="absolute flex items-center justify-center border-2 border-red-600 bg-white rounded-full cursor-pointer"
|
||||||
|
style={{
|
||||||
|
left: scaledX,
|
||||||
|
top: scaledY,
|
||||||
|
width: scaledWidth,
|
||||||
|
height: scaledHeight,
|
||||||
|
borderRadius: '50%',
|
||||||
|
pointerEvents: 'auto',
|
||||||
|
}}
|
||||||
|
title={coord.codeonimage}
|
||||||
|
onClick={() => handlePointClick(coord.codeonimage)}
|
||||||
|
>
|
||||||
|
<span className="flex items-center justify-center w-full h-full text-black text-sm font-bold select-none pointer-events-none">
|
||||||
|
{coord.codeonimage}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
{/* Модалка выбора бренда */}
|
||||||
|
<BrandSelectionModal
|
||||||
|
isOpen={isBrandModalOpen}
|
||||||
|
onClose={() => setIsBrandModalOpen(false)}
|
||||||
|
articleNumber={selectedDetail?.oem || ''}
|
||||||
|
detailName={selectedDetail?.name || ''}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import BrandSelectionModal from '../BrandSelectionModal';
|
||||||
|
|
||||||
interface KnotPartsProps {
|
interface KnotPartsProps {
|
||||||
parts: Array<{
|
parts: Array<{
|
||||||
@ -13,39 +14,59 @@ interface KnotPartsProps {
|
|||||||
note?: string;
|
note?: string;
|
||||||
attributes?: Array<{ key: string; name?: string; value: string }>;
|
attributes?: Array<{ key: string; name?: string; value: string }>;
|
||||||
}>;
|
}>;
|
||||||
|
selectedCodeOnImage?: string | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const KnotParts: React.FC<KnotPartsProps> = ({ parts }) => {
|
const KnotParts: React.FC<KnotPartsProps> = ({ parts, selectedCodeOnImage }) => {
|
||||||
const router = useRouter();
|
const [isBrandModalOpen, setIsBrandModalOpen] = useState(false);
|
||||||
|
const [selectedDetail, setSelectedDetail] = useState<{ oem: string; name: string } | null>(null);
|
||||||
|
|
||||||
|
const handlePriceClick = (part: any) => {
|
||||||
|
if (part.oem) {
|
||||||
|
setSelectedDetail({ oem: part.oem, name: part.name || '' });
|
||||||
|
setIsBrandModalOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="knot-parts">
|
<>
|
||||||
{parts.map((part, idx) => (
|
<div className="knot-parts">
|
||||||
<div className="w-layout-hflex knotlistitem" key={part.detailid || idx}>
|
{parts.map((part, idx) => {
|
||||||
<div className="w-layout-hflex flex-block-116">
|
const isSelected = part.codeonimage && part.codeonimage === selectedCodeOnImage;
|
||||||
<div className="nuberlist">{part.codeonimage || idx + 1}</div>
|
return (
|
||||||
<div className="oemnuber">{part.oem}</div>
|
<div
|
||||||
</div>
|
className={`w-layout-hflex knotlistitem border rounded transition-colors duration-150 ${isSelected ? 'bg-yellow-100 border-yellow-400' : 'border-transparent'}`}
|
||||||
<div className="partsname">{part.name}</div>
|
key={part.detailid || idx}
|
||||||
<div className="w-layout-hflex flex-block-117">
|
|
||||||
<button
|
|
||||||
className="button-3 w-button"
|
|
||||||
onClick={() => {
|
|
||||||
if (part.oem) {
|
|
||||||
router.push(`/search?q=${encodeURIComponent(part.oem)}&mode=parts`);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Цена
|
<div className="w-layout-hflex flex-block-116">
|
||||||
</button>
|
<div className="nuberlist">{part.codeonimage || idx + 1}</div>
|
||||||
<div className="code-embed-16 w-embed">
|
<div className="oemnuber">{part.oem}</div>
|
||||||
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
</div>
|
||||||
<path d="M8.1 13.5H9.89999V8.1H8.1V13.5ZM8.99999 6.3C9.25499 6.3 9.46889 6.2136 9.64169 6.0408C9.81449 5.868 9.90059 5.6544 9.89999 5.4C9.89939 5.1456 9.81299 4.932 9.64079 4.7592C9.46859 4.5864 9.25499 4.5 8.99999 4.5C8.745 4.5 8.53139 4.5864 8.35919 4.7592C8.187 4.932 8.1006 5.1456 8.1 5.4C8.0994 5.6544 8.1858 5.8683 8.35919 6.0417C8.53259 6.2151 8.74619 6.3012 8.99999 6.3ZM8.99999 18C7.755 18 6.585 17.7636 5.49 17.2908C4.395 16.818 3.4425 16.1769 2.6325 15.3675C1.8225 14.5581 1.1814 13.6056 0.709201 12.51C0.237001 11.4144 0.000601139 10.2444 1.13924e-06 9C-0.00059886 7.7556 0.235801 6.5856 0.709201 5.49C1.1826 4.3944 1.8237 3.4419 2.6325 2.6325C3.4413 1.8231 4.3938 1.182 5.49 0.7092C6.5862 0.2364 7.7562 0 8.99999 0C10.2438 0 11.4138 0.2364 12.51 0.7092C13.6062 1.182 14.5587 1.8231 15.3675 2.6325C16.1763 3.4419 16.8177 4.3944 17.2917 5.49C17.7657 6.5856 18.0018 7.7556 18 9C17.9982 10.2444 17.7618 11.4144 17.2908 12.51C16.8198 13.6056 16.1787 14.5581 15.3675 15.3675C14.5563 16.1769 13.6038 16.8183 12.51 17.2917C11.4162 17.7651 10.2462 18.0012 8.99999 18Z" fill="currentcolor" />
|
<div className="partsname">{part.name}</div>
|
||||||
</svg>
|
<div className="w-layout-hflex flex-block-117">
|
||||||
|
<button
|
||||||
|
className="button-3 w-button"
|
||||||
|
onClick={() => handlePriceClick(part)}
|
||||||
|
>
|
||||||
|
Цена
|
||||||
|
</button>
|
||||||
|
<div className="code-embed-16 w-embed">
|
||||||
|
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8.1 13.5H9.89999V8.1H8.1V13.5ZM8.99999 6.3C9.25499 6.3 9.46889 6.2136 9.64169 6.0408C9.81449 5.868 9.90059 5.6544 9.89999 5.4C9.89939 5.1456 9.81299 4.932 9.64079 4.7592C9.46859 4.5864 9.25499 4.5 8.99999 4.5C8.745 4.5 8.53139 4.5864 8.35919 4.7592C8.187 4.932 8.1006 5.1456 8.1 5.4C8.0994 5.6544 8.1858 5.8683 8.35919 6.0417C8.53259 6.2151 8.74619 6.3012 8.99999 6.3ZM8.99999 18C7.755 18 6.585 17.7636 5.49 17.2908C4.395 16.818 3.4425 16.1769 2.6325 15.3675C1.8225 14.5581 1.1814 13.6056 0.709201 12.51C0.237001 11.4144 0.000601139 10.2444 1.13924e-06 9C-0.00059886 7.7556 0.235801 6.5856 0.709201 5.49C1.1826 4.3944 1.8237 3.4419 2.6325 2.6325C3.4413 1.8231 4.3938 1.182 5.49 0.7092C6.5862 0.2364 7.7562 0 8.99999 0C10.2438 0 11.4138 0.2364 12.51 0.7092C13.6062 1.182 14.5587 1.8231 15.3675 2.6325C16.1763 3.4419 16.8177 4.3944 17.2917 5.49C17.7657 6.5856 18.0018 7.7556 18 9C17.9982 10.2444 17.7618 11.4144 17.2908 12.51C16.8198 13.6056 16.1787 14.5581 15.3675 15.3675C14.5563 16.1769 13.6038 16.8183 12.51 17.2917C11.4162 17.7651 10.2462 18.0012 8.99999 18Z" fill="currentcolor" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
</div>
|
})}
|
||||||
))}
|
</div>
|
||||||
</div>
|
<BrandSelectionModal
|
||||||
|
isOpen={isBrandModalOpen}
|
||||||
|
onClose={() => setIsBrandModalOpen(false)}
|
||||||
|
articleNumber={selectedDetail?.oem || ''}
|
||||||
|
detailName={selectedDetail?.name || ''}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,83 +1,138 @@
|
|||||||
import React, { useState, useRef } from "react";
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { useQuery, useLazyQuery } from "@apollo/client";
|
import { useQuery, useLazyQuery } from '@apollo/client';
|
||||||
import { GET_LAXIMO_CATEGORIES, GET_LAXIMO_UNITS } from "@/lib/graphql/laximo";
|
import { GET_LAXIMO_CATEGORIES, GET_LAXIMO_QUICK_GROUPS, GET_LAXIMO_UNITS } from '@/lib/graphql/laximo';
|
||||||
|
|
||||||
interface VinCategoryProps {
|
interface VinCategoryProps {
|
||||||
catalogCode: string;
|
catalogCode: string;
|
||||||
vehicleId: string;
|
vehicleId: string;
|
||||||
ssd?: string;
|
ssd: string;
|
||||||
onNodeSelect?: (node: any) => void;
|
onNodeSelect?: (node: any) => void;
|
||||||
|
activeTab: 'uzly' | 'manufacturer';
|
||||||
|
onQuickGroupSelect?: (group: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VinCategory: React.FC<VinCategoryProps> = ({ catalogCode, vehicleId, ssd, onNodeSelect }) => {
|
const VinCategory: React.FC<VinCategoryProps> = ({ catalogCode, vehicleId, ssd, onNodeSelect, activeTab, onQuickGroupSelect }) => {
|
||||||
const { data: categoriesData, loading: categoriesLoading, error: categoriesError } = useQuery(GET_LAXIMO_CATEGORIES, {
|
const [selectedCategory, setSelectedCategory] = useState<any>(null);
|
||||||
variables: { catalogCode, vehicleId, ssd },
|
|
||||||
skip: !catalogCode || !vehicleId,
|
|
||||||
errorPolicy: "all",
|
|
||||||
});
|
|
||||||
const categories = categoriesData?.laximoCategories || [];
|
|
||||||
|
|
||||||
const [unitsByCategory, setUnitsByCategory] = useState<{ [key: string]: any[] }>({});
|
const [unitsByCategory, setUnitsByCategory] = useState<{ [key: string]: any[] }>({});
|
||||||
const [getUnits] = useLazyQuery(GET_LAXIMO_UNITS, {
|
|
||||||
onCompleted: (data) => {
|
|
||||||
if (data && data.laximoUnits && lastCategoryIdRef.current) {
|
|
||||||
setUnitsByCategory((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[lastCategoryIdRef.current!]: data.laximoUnits || [],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const [selectedCategory, setSelectedCategory] = useState<any | null>(null);
|
|
||||||
const lastCategoryIdRef = useRef<string | null>(null);
|
const lastCategoryIdRef = useRef<string | null>(null);
|
||||||
|
|
||||||
// Если выбрана категория — показываем подкатегории (children или units)
|
// Сброс выбранной категории при смене вкладки
|
||||||
let subcategories: any[] = [];
|
useEffect(() => {
|
||||||
if (selectedCategory) {
|
setSelectedCategory(null);
|
||||||
if (selectedCategory.children && selectedCategory.children.length > 0) {
|
}, [activeTab]);
|
||||||
subcategories = selectedCategory.children;
|
|
||||||
} else {
|
|
||||||
subcategories = unitsByCategory[selectedCategory.quickgroupid] || [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCategoryClick = (cat: any) => {
|
// Запрос для "Узлы"
|
||||||
if (cat.children && cat.children.length > 0) {
|
const { data: categoriesData, loading: categoriesLoading, error: categoriesError } = useQuery(GET_LAXIMO_CATEGORIES, {
|
||||||
setSelectedCategory(cat);
|
variables: { catalogCode, vehicleId, ssd },
|
||||||
} else {
|
skip: !catalogCode || vehicleId === undefined || vehicleId === null || activeTab !== 'uzly',
|
||||||
// Если нет children, грузим units (подкатегории)
|
errorPolicy: 'all'
|
||||||
if (!unitsByCategory[cat.quickgroupid]) {
|
});
|
||||||
lastCategoryIdRef.current = cat.quickgroupid;
|
|
||||||
getUnits({ variables: { catalogCode, vehicleId, ssd, categoryId: cat.quickgroupid } });
|
// Запрос для получения units (подкатегорий) в режиме "Узлы"
|
||||||
|
const [getUnits] = useLazyQuery(GET_LAXIMO_UNITS, {
|
||||||
|
onCompleted: (data) => {
|
||||||
|
console.log('Units loaded:', data);
|
||||||
|
if (data && data.laximoUnits && lastCategoryIdRef.current) {
|
||||||
|
console.log('Setting units for category:', lastCategoryIdRef.current, data.laximoUnits);
|
||||||
|
setUnitsByCategory(prev => ({
|
||||||
|
...prev,
|
||||||
|
[lastCategoryIdRef.current!]: data.laximoUnits || []
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
setSelectedCategory(cat);
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error('Error loading units:', error);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
|
// Запрос для "От производителя"
|
||||||
|
const { data: quickGroupsData, loading: quickGroupsLoading, error: quickGroupsError } = useQuery(GET_LAXIMO_QUICK_GROUPS, {
|
||||||
|
variables: { catalogCode, vehicleId, ssd },
|
||||||
|
skip: !catalogCode || vehicleId === undefined || vehicleId === null || activeTab !== 'manufacturer',
|
||||||
|
errorPolicy: 'all'
|
||||||
|
});
|
||||||
|
|
||||||
|
const categories = activeTab === 'uzly' ? (categoriesData?.laximoCategories || []) : (quickGroupsData?.laximoQuickGroups || []);
|
||||||
|
const loading = activeTab === 'uzly' ? categoriesLoading : quickGroupsLoading;
|
||||||
|
const error = activeTab === 'uzly' ? categoriesError : quickGroupsError;
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
setSelectedCategory(null);
|
setSelectedCategory(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubcategoryClick = (subcat: any) => {
|
const handleCategoryClick = (category: any) => {
|
||||||
if (onNodeSelect) {
|
if (activeTab === 'manufacturer') {
|
||||||
onNodeSelect({
|
if (category.children && category.children.length > 0) {
|
||||||
...subcat,
|
setSelectedCategory(category);
|
||||||
unitid: subcat.unitid || subcat.quickgroupid || subcat.id,
|
} else if (category.link && onQuickGroupSelect) {
|
||||||
});
|
onQuickGroupSelect(category);
|
||||||
|
} else if (onNodeSelect) {
|
||||||
|
onNodeSelect(category);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Логика для вкладки "Узлы"
|
||||||
|
if (category.children && category.children.length > 0) {
|
||||||
|
setSelectedCategory(category);
|
||||||
|
} else {
|
||||||
|
// Если нет children, грузим units (подкатегории)
|
||||||
|
const categoryId = category.categoryid || category.quickgroupid || category.id;
|
||||||
|
if (!unitsByCategory[categoryId]) {
|
||||||
|
lastCategoryIdRef.current = categoryId;
|
||||||
|
console.log('Loading units for category:', { categoryId, category });
|
||||||
|
getUnits({
|
||||||
|
variables: {
|
||||||
|
catalogCode,
|
||||||
|
vehicleId,
|
||||||
|
ssd,
|
||||||
|
categoryId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setSelectedCategory(category);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (categoriesLoading) return <div>Загрузка категорий...</div>;
|
const handleSubcategoryClick = (subcat: any) => {
|
||||||
if (categoriesError) return <div style={{ color: "red" }}>Ошибка: {categoriesError.message}</div>;
|
if (activeTab === 'uzly' && onNodeSelect) {
|
||||||
|
// Для режима "Узлы" при клике на подкатегорию открываем KnotIn
|
||||||
|
onNodeSelect({
|
||||||
|
...subcat,
|
||||||
|
unitid: subcat.unitid || subcat.categoryid || subcat.quickgroupid || subcat.id
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
handleCategoryClick(subcat);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) return <div>Загрузка категорий...</div>;
|
||||||
|
if (error) return <div style={{ color: "red" }}>Ошибка: {error.message}</div>;
|
||||||
|
|
||||||
|
// Определяем, какие подкатегории показывать
|
||||||
|
let subcategories: any[] = [];
|
||||||
|
if (selectedCategory) {
|
||||||
|
if (activeTab === 'manufacturer') {
|
||||||
|
// Для вкладки "От производителя" используем children
|
||||||
|
subcategories = selectedCategory.children || [];
|
||||||
|
} else {
|
||||||
|
// Для вкладки "Узлы" используем либо children, либо units
|
||||||
|
if (selectedCategory.children && selectedCategory.children.length > 0) {
|
||||||
|
subcategories = selectedCategory.children;
|
||||||
|
} else {
|
||||||
|
const categoryId = selectedCategory.categoryid || selectedCategory.quickgroupid || selectedCategory.id;
|
||||||
|
subcategories = unitsByCategory[categoryId] || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-layout-vflex flex-block-14-copy-copy">
|
<div className="w-layout-vflex flex-block-14-copy-copy">
|
||||||
{!selectedCategory ? (
|
{!selectedCategory ? (
|
||||||
// Список категорий
|
// Список категорий
|
||||||
categories.map((cat: any, idx: number) => (
|
categories.map((cat: any, idx: number) => (
|
||||||
<div
|
<div
|
||||||
className="div-block-131"
|
className="div-block-131"
|
||||||
key={cat.quickgroupid || cat.id || idx}
|
key={cat.quickgroupid || cat.categoryid || cat.id || idx}
|
||||||
onClick={() => handleCategoryClick(cat)}
|
onClick={() => handleCategoryClick(cat)}
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
@ -91,7 +146,7 @@ const VinCategory: React.FC<VinCategoryProps> = ({ catalogCode, vehicleId, ssd,
|
|||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
// Список подкатегорий (children или units)
|
// Список подкатегорий
|
||||||
<>
|
<>
|
||||||
<div className="div-block-131" onClick={handleBack} style={{ cursor: "pointer", fontWeight: 500 }}>
|
<div className="div-block-131" onClick={handleBack} style={{ cursor: "pointer", fontWeight: 500 }}>
|
||||||
<div className="text-block-57">← Назад</div>
|
<div className="text-block-57">← Назад</div>
|
||||||
@ -106,23 +161,23 @@ const VinCategory: React.FC<VinCategoryProps> = ({ catalogCode, vehicleId, ssd,
|
|||||||
{subcategories.map((subcat: any, idx: number) => (
|
{subcategories.map((subcat: any, idx: number) => (
|
||||||
<div
|
<div
|
||||||
className="div-block-131"
|
className="div-block-131"
|
||||||
key={subcat.quickgroupid || subcat.unitid || subcat.id || idx}
|
key={subcat.quickgroupid || subcat.categoryid || subcat.unitid || subcat.id || idx}
|
||||||
onClick={() => handleSubcategoryClick(subcat)}
|
onClick={() => handleSubcategoryClick(subcat)}
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
<div className="text-block-57">{subcat.name}</div>
|
<div className="text-block-57">{subcat.name}</div>
|
||||||
<div className="w-embed">
|
<div className="w-embed">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<rect x="24" width="24" height="24" rx="12" transform="rotate(90 24 0)" fill="currentcolor"></rect>
|
<rect x="24" width="24" height="24" rx="12" transform="rotate(90 24 0)" fill="currentcolor"></rect>
|
||||||
<path fillRule="evenodd" clipRule="evenodd" d="M10.9303 17L10 16.0825L14.1395 12L10 7.91747L10.9303 7L16 12L10.9303 17Z" fill="white"></path>
|
<path fillRule="evenodd" clipRule="evenodd" d="M10.9303 17L10 16.0825L14.1395 12L10 7.91747L10.9303 7L16 12L10.9303 17Z" fill="white"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default VinCategory;
|
export default VinCategory;
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useLazyQuery, useQuery } from '@apollo/client';
|
import { useLazyQuery, useQuery } from '@apollo/client';
|
||||||
import { SEARCH_LAXIMO_FULLTEXT, GET_LAXIMO_CATEGORIES, GET_LAXIMO_UNITS, GET_LAXIMO_QUICK_GROUPS, GET_LAXIMO_QUICK_DETAIL } from '@/lib/graphql/laximo';
|
import { GET_LAXIMO_FULLTEXT_SEARCH, GET_LAXIMO_CATEGORIES, GET_LAXIMO_UNITS, GET_LAXIMO_QUICK_GROUPS, GET_LAXIMO_QUICK_DETAIL } from '@/lib/graphql/laximo';
|
||||||
import VinPartCard from './VinPartCard';
|
import VinPartCard from './VinPartCard';
|
||||||
|
|
||||||
interface VinLeftbarProps {
|
interface VinLeftbarProps {
|
||||||
@ -10,22 +10,37 @@ interface VinLeftbarProps {
|
|||||||
ssd: string;
|
ssd: string;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
onSearchResults?: (results: any[]) => void;
|
onSearchResults?: (data: {
|
||||||
|
results: any[];
|
||||||
|
loading: boolean;
|
||||||
|
error: any;
|
||||||
|
query: string;
|
||||||
|
isSearching?: boolean;
|
||||||
|
}) => void;
|
||||||
onNodeSelect?: (node: any) => void;
|
onNodeSelect?: (node: any) => void;
|
||||||
|
onActiveTabChange?: (tab: 'uzly' | 'manufacturer') => void;
|
||||||
|
onQuickGroupSelect?: (group: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, onNodeSelect }) => {
|
interface QuickGroup {
|
||||||
|
quickgroupid: string;
|
||||||
|
name: string;
|
||||||
|
link?: boolean;
|
||||||
|
children?: QuickGroup[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, onNodeSelect, onActiveTabChange, onQuickGroupSelect }) => {
|
||||||
const catalogCode = vehicleInfo.catalog;
|
const catalogCode = vehicleInfo.catalog;
|
||||||
const vehicleId = vehicleInfo.vehicleid;
|
const vehicleId = vehicleInfo.vehicleid;
|
||||||
const ssd = vehicleInfo.ssd;
|
const ssd = vehicleInfo.ssd;
|
||||||
const [openIndex, setOpenIndex] = useState<number | null>(null);
|
const [openIndex, setOpenIndex] = useState<number | null>(null);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [activeTab, setActiveTab] = useState<'uzly' | 'manufacturer'>('uzly');
|
const [activeTab, setActiveTab] = useState<'uzly' | 'manufacturer'>('uzly');
|
||||||
const [executeSearch, { data, loading, error }] = useLazyQuery(SEARCH_LAXIMO_FULLTEXT, { errorPolicy: 'all' });
|
const [executeSearch, { data, loading, error }] = useLazyQuery(GET_LAXIMO_FULLTEXT_SEARCH, { errorPolicy: 'all' });
|
||||||
|
|
||||||
const { data: categoriesData, loading: categoriesLoading, error: categoriesError } = useQuery(GET_LAXIMO_CATEGORIES, {
|
const { data: categoriesData, loading: categoriesLoading, error: categoriesError } = useQuery(GET_LAXIMO_CATEGORIES, {
|
||||||
variables: { catalogCode, vehicleId, ssd },
|
variables: { catalogCode, vehicleId, ssd },
|
||||||
skip: !catalogCode || !vehicleId,
|
skip: !catalogCode || vehicleId === undefined || vehicleId === null,
|
||||||
errorPolicy: 'all'
|
errorPolicy: 'all'
|
||||||
});
|
});
|
||||||
const categories = categoriesData?.laximoCategories || [];
|
const categories = categoriesData?.laximoCategories || [];
|
||||||
@ -79,16 +94,18 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
|||||||
const searchResults = data?.laximoFulltextSearch;
|
const searchResults = data?.laximoFulltextSearch;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchResults && onSearchResults) {
|
if (onSearchResults) {
|
||||||
onSearchResults(searchResults.details || []);
|
onSearchResults({
|
||||||
|
results: searchResults?.details || [],
|
||||||
|
loading: loading,
|
||||||
|
error: error,
|
||||||
|
query: searchQuery
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (!searchQuery.trim() && onSearchResults) {
|
}, [searchResults, loading, error, searchQuery, onSearchResults]);
|
||||||
onSearchResults([]);
|
|
||||||
}
|
|
||||||
}, [searchResults, searchQuery, onSearchResults]);
|
|
||||||
|
|
||||||
// --- Новый блок: вычисляем доступность поиска ---
|
// --- Новый блок: вычисляем доступность поиска ---
|
||||||
const isSearchAvailable = !!catalogCode && !!vehicleId && !!ssd && ssd.trim() !== '';
|
const isSearchAvailable = !!catalogCode && vehicleId !== undefined && vehicleId !== null && !!ssd && ssd.trim() !== '';
|
||||||
const showWarning = !isSearchAvailable;
|
const showWarning = !isSearchAvailable;
|
||||||
const showError = !!error && isSearchAvailable && searchQuery.trim();
|
const showError = !!error && isSearchAvailable && searchQuery.trim();
|
||||||
const showNotFound = isSearchAvailable && searchQuery.trim() && !loading && data && searchResults && searchResults.details && searchResults.details.length === 0;
|
const showNotFound = isSearchAvailable && searchQuery.trim() && !loading && data && searchResults && searchResults.details && searchResults.details.length === 0;
|
||||||
@ -98,7 +115,7 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
|||||||
const [selectedQuickGroup, setSelectedQuickGroup] = useState<any | null>(null);
|
const [selectedQuickGroup, setSelectedQuickGroup] = useState<any | null>(null);
|
||||||
const { data: quickGroupsData, loading: quickGroupsLoading, error: quickGroupsError } = useQuery(GET_LAXIMO_QUICK_GROUPS, {
|
const { data: quickGroupsData, loading: quickGroupsLoading, error: quickGroupsError } = useQuery(GET_LAXIMO_QUICK_GROUPS, {
|
||||||
variables: { catalogCode, vehicleId, ssd },
|
variables: { catalogCode, vehicleId, ssd },
|
||||||
skip: !catalogCode || !vehicleId || activeTab !== 'manufacturer',
|
skip: !catalogCode || vehicleId === undefined || vehicleId === null || activeTab !== 'manufacturer',
|
||||||
errorPolicy: 'all'
|
errorPolicy: 'all'
|
||||||
});
|
});
|
||||||
const quickGroups = quickGroupsData?.laximoQuickGroups || [];
|
const quickGroups = quickGroupsData?.laximoQuickGroups || [];
|
||||||
@ -136,12 +153,30 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
|||||||
const skipQuickDetail =
|
const skipQuickDetail =
|
||||||
!selectedQuickGroup ||
|
!selectedQuickGroup ||
|
||||||
!catalogCode ||
|
!catalogCode ||
|
||||||
!vehicleId ||
|
vehicleId === undefined ||
|
||||||
!selectedQuickGroup.quickgroupid ||
|
vehicleId === null ||
|
||||||
!ssd;
|
!selectedQuickGroup?.quickgroupid ||
|
||||||
|
!ssd ||
|
||||||
|
ssd.trim() === '';
|
||||||
|
|
||||||
|
console.log('QuickDetail QUERY VARS', {
|
||||||
|
catalogCode,
|
||||||
|
vehicleId,
|
||||||
|
quickGroupId: selectedQuickGroup?.quickgroupid,
|
||||||
|
ssd: ssd ? `${ssd.substring(0, 30)}...` : 'отсутствует'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('QuickDetail SKIP CONDITIONS', {
|
||||||
|
hasSelectedQuickGroup: !!selectedQuickGroup,
|
||||||
|
hasCatalogCode: !!catalogCode,
|
||||||
|
hasVehicleId: vehicleId !== undefined && vehicleId !== null,
|
||||||
|
hasQuickGroupId: !!selectedQuickGroup?.quickgroupid,
|
||||||
|
hasSsd: !!ssd && ssd.trim() !== '',
|
||||||
|
skipQuickDetail
|
||||||
|
});
|
||||||
|
|
||||||
const { data: quickDetailData, loading: quickDetailLoading, error: quickDetailError } = useQuery(GET_LAXIMO_QUICK_DETAIL, {
|
const { data: quickDetailData, loading: quickDetailLoading, error: quickDetailError } = useQuery(GET_LAXIMO_QUICK_DETAIL, {
|
||||||
variables: selectedQuickGroup ? {
|
variables: selectedQuickGroup?.quickgroupid && !skipQuickDetail ? {
|
||||||
catalogCode,
|
catalogCode,
|
||||||
vehicleId,
|
vehicleId,
|
||||||
quickGroupId: selectedQuickGroup.quickgroupid,
|
quickGroupId: selectedQuickGroup.quickgroupid,
|
||||||
@ -152,40 +187,86 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
|||||||
});
|
});
|
||||||
const quickDetail = quickDetailData?.laximoQuickDetail;
|
const quickDetail = quickDetailData?.laximoQuickDetail;
|
||||||
|
|
||||||
const renderQuickGroupTree = (groups: any[], level = 0): React.ReactNode => (
|
// === Полнотекстовый поиск деталей (аналогично FulltextSearchSection) ===
|
||||||
<div>
|
const [fulltextQuery, setFulltextQuery] = useState('');
|
||||||
{groups.map(group => (
|
const [executeFulltextSearch, { data: fulltextData, loading: fulltextLoading, error: fulltextError }] = useLazyQuery(GET_LAXIMO_FULLTEXT_SEARCH, { errorPolicy: 'all' });
|
||||||
<div key={group.quickgroupid} style={{ marginLeft: level * 16, marginBottom: 8 }}>
|
|
||||||
<div
|
const handleFulltextSearch = () => {
|
||||||
className={`flex items-center p-2 rounded cursor-pointer border ${group.link ? 'bg-white hover:bg-red-50 border-gray-200 hover:border-red-300' : 'bg-gray-50 hover:bg-gray-100 border-gray-200'}`}
|
if (!fulltextQuery.trim()) {
|
||||||
onClick={() => handleQuickGroupClick(group)}
|
if (onSearchResults) {
|
||||||
>
|
onSearchResults({
|
||||||
{group.children && group.children.length > 0 && (
|
results: [],
|
||||||
<svg className={`w-4 h-4 text-gray-400 mr-2 transition-transform ${expandedQuickGroups.has(group.quickgroupid) ? 'rotate-90' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
loading: false,
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
error: null,
|
||||||
</svg>
|
query: '',
|
||||||
)}
|
isSearching: false
|
||||||
<span className={`font-medium ${group.link ? 'text-gray-900' : 'text-gray-600'}`}>{group.name}</span>
|
});
|
||||||
{group.link && (
|
}
|
||||||
<span className="ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">Доступен поиск</span>
|
return;
|
||||||
)}
|
}
|
||||||
</div>
|
if (!ssd || ssd.trim() === '') {
|
||||||
{group.children && group.children.length > 0 && expandedQuickGroups.has(group.quickgroupid) && (
|
console.error('SSD обязателен для поиска по названию');
|
||||||
<div className="mt-1">
|
return;
|
||||||
{renderQuickGroupTree(group.children, level + 1)}
|
}
|
||||||
</div>
|
// Отправляем начальное состояние поиска родителю
|
||||||
)}
|
if (onSearchResults) {
|
||||||
</div>
|
onSearchResults({
|
||||||
))}
|
results: [],
|
||||||
</div>
|
loading: true,
|
||||||
);
|
error: null,
|
||||||
|
query: fulltextQuery.trim(),
|
||||||
|
isSearching: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
executeFulltextSearch({
|
||||||
|
variables: {
|
||||||
|
catalogCode,
|
||||||
|
vehicleId,
|
||||||
|
searchQuery: fulltextQuery.trim(),
|
||||||
|
ssd
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const newValue = e.target.value;
|
||||||
|
setFulltextQuery(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (onSearchResults && (fulltextData || fulltextLoading || fulltextError)) {
|
||||||
|
onSearchResults({
|
||||||
|
results: fulltextData?.laximoFulltextSearch?.details || [],
|
||||||
|
loading: fulltextLoading,
|
||||||
|
error: fulltextError,
|
||||||
|
query: fulltextQuery,
|
||||||
|
isSearching: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [fulltextData, fulltextLoading, fulltextError, onSearchResults]);
|
||||||
|
|
||||||
|
const handleFulltextKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
handleFulltextSearch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fulltextResults = fulltextData?.laximoFulltextSearch?.details || [];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (onActiveTabChange) {
|
||||||
|
onActiveTabChange(activeTab);
|
||||||
|
}
|
||||||
|
}, [activeTab, onActiveTabChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-layout-vflex vinleftbar">
|
<div className="w-layout-vflex vinleftbar">
|
||||||
|
{/* === Форма полнотекстового поиска === */}
|
||||||
<div className="div-block-2">
|
<div className="div-block-2">
|
||||||
<div className="form-block w-form">
|
<div className="form-block w-form">
|
||||||
<form id="vin-form-search" name="vin-form-search" data-name="vin-form-search" action="#" method="post" className="form">
|
<form id="vin-form-search" name="vin-form-search" data-name="vin-form-search" action="#" method="post" className="form" onSubmit={e => { e.preventDefault(); handleFulltextSearch(); }}>
|
||||||
<a href="#" className="link-block-3 w-inline-block" onClick={e => { e.preventDefault(); if (!ssd || ssd.trim() === '') { return; } handleSearch(); }}>
|
<a href="#" className="link-block-3 w-inline-block" onClick={e => { e.preventDefault(); handleFulltextSearch(); }}>
|
||||||
<div className="code-embed-6 w-embed">
|
<div className="code-embed-6 w-embed">
|
||||||
{/* SVG */}
|
{/* SVG */}
|
||||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
@ -203,14 +284,29 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
|||||||
type="text"
|
type="text"
|
||||||
id="VinSearchInput"
|
id="VinSearchInput"
|
||||||
required
|
required
|
||||||
value={searchQuery}
|
value={fulltextQuery}
|
||||||
onChange={e => setSearchQuery(e.target.value)}
|
onChange={handleInputChange}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleFulltextKeyDown}
|
||||||
disabled={loading}
|
disabled={fulltextLoading}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
{/* Варианты отображения: предупреждение, ошибка, подсказки, результаты */}
|
{(!ssd || ssd.trim() === '') && (
|
||||||
|
<div className="mt-3 p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||||
|
<div className="flex">
|
||||||
|
<svg className="h-5 w-5 text-yellow-400 flex-shrink-0" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<div className="ml-3">
|
||||||
|
<h3 className="text-sm font-medium text-yellow-800">
|
||||||
|
Полнотекстовый поиск недоступен
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-yellow-700 mt-1">
|
||||||
|
Для поиска по названию деталей необходимо сначала выбрать конкретный автомобиль через поиск по VIN или мастер подбора.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-layout-vflex flex-block-113">
|
<div className="w-layout-vflex flex-block-113">
|
||||||
@ -260,7 +356,6 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
|||||||
<>
|
<>
|
||||||
{categories.map((category: any, idx: number) => {
|
{categories.map((category: any, idx: number) => {
|
||||||
const isOpen = openIndex === idx;
|
const isOpen = openIndex === idx;
|
||||||
// Подкатегории: сначала children, если нет — unitsByCategory
|
|
||||||
const subcategories = category.children && category.children.length > 0
|
const subcategories = category.children && category.children.length > 0
|
||||||
? category.children
|
? category.children
|
||||||
: unitsByCategory[category.quickgroupid] || [];
|
: unitsByCategory[category.quickgroupid] || [];
|
||||||
@ -285,7 +380,7 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
|||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
key={subcat.quickgroupid || subcat.unitid}
|
key={subcat.quickgroupid || subcat.unitid}
|
||||||
className="dropdown-link-3 w-dropdown-link"
|
className="dropdown-link-3 w-dropdown-link pl-0"
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (onNodeSelect) {
|
if (onNodeSelect) {
|
||||||
@ -310,46 +405,158 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
|||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
// Manufacturer tab content (QuickGroups)
|
// Manufacturer tab content (QuickGroups)
|
||||||
<div style={{ padding: '16px' }}>
|
quickGroupsLoading ? (
|
||||||
{quickGroupsLoading ? (
|
<div style={{ padding: 16, textAlign: 'center' }}>Загружаем группы быстрого поиска...</div>
|
||||||
<div style={{ textAlign: 'center' }}>Загружаем группы быстрого поиска...</div>
|
) : quickGroupsError ? (
|
||||||
) : quickGroupsError ? (
|
<div style={{ color: 'red', padding: 16 }}>Ошибка загрузки групп: {quickGroupsError.message}</div>
|
||||||
<div style={{ color: 'red' }}>Ошибка загрузки групп: {quickGroupsError.message}</div>
|
) : (
|
||||||
) : selectedQuickGroup ? (
|
<>
|
||||||
<div>
|
{(quickGroups as QuickGroup[]).map((group: QuickGroup) => {
|
||||||
<button onClick={() => setSelectedQuickGroup(null)} className="mb-4 px-3 py-1 bg-gray-200 rounded">Назад к группам</button>
|
const hasChildren = group.children && group.children.length > 0;
|
||||||
<h3 className="text-lg font-semibold mb-2">{selectedQuickGroup.name}</h3>
|
const isOpen = expandedQuickGroups.has(group.quickgroupid);
|
||||||
{quickDetailLoading ? (
|
|
||||||
<div>Загружаем детали...</div>
|
if (!hasChildren) {
|
||||||
) : quickDetailError ? (
|
return (
|
||||||
<div style={{ color: 'red' }}>Ошибка загрузки деталей: {quickDetailError.message}</div>
|
<a
|
||||||
) : quickDetail && quickDetail.units && quickDetail.units.length > 0 ? (
|
href="#"
|
||||||
<div className="space-y-3">
|
key={group.quickgroupid}
|
||||||
{quickDetail.units.map((unit: any) => (
|
className="dropdown-link-3 w-dropdown-link"
|
||||||
<div key={unit.unitid} className="p-3 bg-gray-50 rounded border border-gray-200">
|
onClick={(e) => {
|
||||||
<div className="font-medium text-gray-900">{unit.name}</div>
|
e.preventDefault();
|
||||||
{unit.details && unit.details.length > 0 && (
|
if (group.link && onQuickGroupSelect) {
|
||||||
<ul className="mt-2 text-sm text-gray-700 list-disc pl-5">
|
onQuickGroupSelect(group);
|
||||||
{unit.details.map((detail: any) => (
|
}
|
||||||
<li key={detail.detailid}>
|
}}
|
||||||
<span className="font-medium">{detail.name}</span> <span className="ml-2 text-xs bg-blue-100 text-blue-800 px-2 py-0.5 rounded">OEM: {detail.oem}</span>
|
>
|
||||||
</li>
|
{group.name}
|
||||||
))}
|
</a>
|
||||||
</ul>
|
);
|
||||||
)}
|
}
|
||||||
</div>
|
|
||||||
))}
|
return (
|
||||||
|
<div
|
||||||
|
key={group.quickgroupid}
|
||||||
|
data-hover="false"
|
||||||
|
data-delay="0"
|
||||||
|
className={`dropdown-4 w-dropdown${isOpen ? " w--open" : ""}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`dropdown-toggle-3 w-dropdown-toggle${isOpen ? " w--open" : ""}`}
|
||||||
|
onClick={() => handleQuickGroupToggle(group.quickgroupid)}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
>
|
||||||
|
<div className="w-icon-dropdown-toggle"></div>
|
||||||
|
<div className="text-block-56">{group.name}</div>
|
||||||
|
</div>
|
||||||
|
<nav className={`dropdown-list-4 w-dropdown-list${isOpen ? " w--open" : ""}`}>
|
||||||
|
{group.children?.map((child: QuickGroup) => {
|
||||||
|
const hasSubChildren = child.children && child.children.length > 0;
|
||||||
|
const isChildOpen = expandedQuickGroups.has(child.quickgroupid);
|
||||||
|
|
||||||
|
if (!hasSubChildren) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
key={child.quickgroupid}
|
||||||
|
className="dropdown-link-3 w-dropdown-link "
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (child.link && onQuickGroupSelect) {
|
||||||
|
onQuickGroupSelect(child);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{child.name}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={child.quickgroupid}
|
||||||
|
data-hover="false"
|
||||||
|
data-delay="0"
|
||||||
|
className={`dropdown-4 w-dropdown pl-0${isChildOpen ? " w--open" : ""}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`dropdown-toggle-card w-dropdown-toggle pl-0${isChildOpen ? " w--open" : ""}`}
|
||||||
|
onClick={() => handleQuickGroupToggle(child.quickgroupid)}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
>
|
||||||
|
<div className="w-icon-dropdown-toggle"></div>
|
||||||
|
<div className="text-block-56">{child.name}</div>
|
||||||
|
</div>
|
||||||
|
<nav className={`dropdown-list-4 w-dropdown-list pl-0${isChildOpen ? " w--open" : ""}`}>
|
||||||
|
{child.children?.map((subChild: QuickGroup) => (
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
key={subChild.quickgroupid}
|
||||||
|
className="dropdown-link-3 w-dropdown-link "
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (subChild.link && onQuickGroupSelect) {
|
||||||
|
onQuickGroupSelect(subChild);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{subChild.name}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
);
|
||||||
<div>Нет деталей для этой группы</div>
|
})}
|
||||||
)}
|
|
||||||
</div>
|
{/* Quick Detail Modal */}
|
||||||
) : quickGroups.length > 0 ? (
|
{selectedQuickGroup && (
|
||||||
renderQuickGroupTree(quickGroups)
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
||||||
) : (
|
<div className="bg-white rounded-lg max-w-2xl w-full max-h-[90vh] overflow-y-auto p-6">
|
||||||
<div>Нет доступных групп быстрого поиска</div>
|
<div className="flex justify-between items-center mb-4">
|
||||||
)}
|
<h3 className="text-lg font-semibold">{selectedQuickGroup.name}</h3>
|
||||||
</div>
|
<button
|
||||||
|
onClick={() => setSelectedQuickGroup(null)}
|
||||||
|
className="text-gray-500 hover:text-gray-700"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{quickDetailLoading ? (
|
||||||
|
<div className="text-center py-4">Загружаем детали...</div>
|
||||||
|
) : quickDetailError ? (
|
||||||
|
<div className="text-red-600 py-4">Ошибка загрузки деталей: {quickDetailError.message}</div>
|
||||||
|
) : quickDetail?.units?.length > 0 ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{quickDetail.units.map((unit: any) => (
|
||||||
|
<div key={unit.unitid} className="border border-gray-200 rounded-lg p-4">
|
||||||
|
<div className="font-medium text-gray-900 mb-2">{unit.name}</div>
|
||||||
|
{unit.details && unit.details.length > 0 && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{unit.details.map((detail: any) => (
|
||||||
|
<div key={detail.detailid} className="flex items-center justify-between bg-gray-50 p-2 rounded">
|
||||||
|
<span className="font-medium text-gray-700">{detail.name}</span>
|
||||||
|
<span className="text-sm bg-blue-100 text-blue-800 px-2 py-1 rounded">
|
||||||
|
OEM: {detail.oem}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-center text-gray-500 py-4">Нет деталей для этой группы</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
{/* Tab content end */}
|
{/* Tab content end */}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
|
import BrandSelectionModal from '../BrandSelectionModal';
|
||||||
|
|
||||||
interface VinPartCardProps {
|
interface VinPartCardProps {
|
||||||
n?: number;
|
n?: number;
|
||||||
@ -10,28 +11,38 @@ interface VinPartCardProps {
|
|||||||
|
|
||||||
const VinPartCard: React.FC<VinPartCardProps> = ({ n, oem, name, onPriceClick }) => {
|
const VinPartCard: React.FC<VinPartCardProps> = ({ n, oem, name, onPriceClick }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [isBrandModalOpen, setIsBrandModalOpen] = useState(false);
|
||||||
|
|
||||||
const handlePriceClick = (e: React.MouseEvent) => {
|
const handlePriceClick = (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (onPriceClick) onPriceClick();
|
if (onPriceClick) onPriceClick();
|
||||||
if (oem) router.push(`/search?q=${encodeURIComponent(oem)}&mode=parts`);
|
setIsBrandModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-layout-hflex knotlistitem">
|
<>
|
||||||
<div className="w-layout-hflex flex-block-116">
|
<div className="w-layout-hflex knotlistitem">
|
||||||
{n !== undefined && <div className="nuberlist">{n}</div>}
|
<div className="w-layout-hflex flex-block-116">
|
||||||
<div className="oemnuber">{oem}</div>
|
{n !== undefined && <div className="nuberlist">{n}</div>}
|
||||||
</div>
|
<div className="oemnuber">{oem}</div>
|
||||||
<div className="partsname">{name}</div>
|
</div>
|
||||||
<div className="w-layout-hflex flex-block-117">
|
<div className="partsname">{name}</div>
|
||||||
<a href="#" className="button-3 w-button" onClick={handlePriceClick}>Цена</a>
|
<div className="w-layout-hflex flex-block-117">
|
||||||
<div className="code-embed-16 w-embed">
|
<a href="#" className="button-3 w-button" onClick={handlePriceClick}>Цена</a>
|
||||||
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<div className="code-embed-16 w-embed">
|
||||||
<path d="M8.1 13.5H9.89999V8.1H8.1V13.5ZM8.99999 6.3C9.25499 6.3 9.46889 6.2136 9.64169 6.0408C9.81449 5.868 9.90059 5.6544 9.89999 5.4C9.89939 5.1456 9.81299 4.932 9.64079 4.7592C9.46859 4.5864 9.25499 4.5 8.99999 4.5C8.745 4.5 8.53139 4.5864 8.35919 4.7592C8.187 4.932 8.1006 5.1456 8.1 5.4C8.0994 5.6544 8.1858 5.8683 8.35919 6.0417C8.53259 6.2151 8.74619 6.3012 8.99999 6.3ZM8.99999 18C7.755 18 6.585 17.7636 5.49 17.2908C4.395 16.818 3.4425 16.1769 2.6325 15.3675C1.8225 14.5581 1.1814 13.6056 0.709201 12.51C0.237001 11.4144 0.000601139 10.2444 1.13924e-06 9C-0.00059886 7.7556 0.235801 6.5856 0.709201 5.49C1.1826 4.3944 1.8237 3.4419 2.6325 2.6325C3.4413 1.8231 4.3938 1.182 5.49 0.7092C6.5862 0.2364 7.7562 0 8.99999 0C10.2438 0 11.4138 0.2364 12.51 0.7092C13.6062 1.182 14.5587 1.8231 15.3675 2.6325C16.1763 3.4419 16.8177 4.3944 17.2917 5.49C17.7657 6.5856 18.0018 7.7556 18 9C17.9982 10.2444 17.7618 11.4144 17.2908 12.51C16.8198 13.6056 16.1787 14.5581 15.3675 15.3675C14.5563 16.1769 13.6038 16.8183 12.51 17.2917C11.4162 17.7651 10.2462 18.0012 8.99999 18Z" fill="currentcolor" />
|
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
</svg>
|
<path d="M8.1 13.5H9.89999V8.1H8.1V13.5ZM8.99999 6.3C9.25499 6.3 9.46889 6.2136 9.64169 6.0408C9.81449 5.868 9.90059 5.6544 9.89999 5.4C9.89939 5.1456 9.81299 4.932 9.64079 4.7592C9.46859 4.5864 9.25499 4.5 8.99999 4.5C8.745 4.5 8.53139 4.5864 8.35919 4.7592C8.187 4.932 8.1006 5.1456 8.1 5.4C8.0994 5.6544 8.1858 5.8683 8.35919 6.0417C8.53259 6.2151 8.74619 6.3012 8.99999 6.3ZM8.99999 18C7.755 18 6.585 17.7636 5.49 17.2908C4.395 16.818 3.4425 16.1769 2.6325 15.3675C1.8225 14.5581 1.1814 13.6056 0.709201 12.51C0.237001 11.4144 0.000601139 10.2444 1.13924e-06 9C-0.00059886 7.7556 0.235801 6.5856 0.709201 5.49C1.1826 4.3944 1.8237 3.4419 2.6325 2.6325C3.4413 1.8231 4.3938 1.182 5.49 0.7092C6.5862 0.2364 7.7562 0 8.99999 0C10.2438 0 11.4138 0.2364 12.51 0.7092C13.6062 1.182 14.5587 1.8231 15.3675 2.6325C16.1763 3.4419 16.8177 4.3944 17.2917 5.49C17.7657 6.5856 18.0018 7.7556 18 9C17.9982 10.2444 17.7618 11.4144 17.2908 12.51C16.8198 13.6056 16.1787 14.5581 15.3675 15.3675C14.5563 16.1769 13.6038 16.8183 12.51 17.2917C11.4162 17.7651 10.2462 18.0012 8.99999 18Z" fill="currentcolor" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<BrandSelectionModal
|
||||||
|
isOpen={isBrandModalOpen}
|
||||||
|
onClose={() => setIsBrandModalOpen(false)}
|
||||||
|
articleNumber={oem}
|
||||||
|
detailName={name}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
101
src/components/vin/VinQuick.tsx
Normal file
101
src/components/vin/VinQuick.tsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useQuery } from '@apollo/client';
|
||||||
|
import { GET_LAXIMO_QUICK_DETAIL } from '@/lib/graphql/laximo';
|
||||||
|
import BrandSelectionModal from '../BrandSelectionModal';
|
||||||
|
|
||||||
|
interface VinQuickProps {
|
||||||
|
quickGroup: any;
|
||||||
|
catalogCode: string;
|
||||||
|
vehicleId: string;
|
||||||
|
ssd: string;
|
||||||
|
onBack: () => void;
|
||||||
|
onNodeSelect: (unit: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VinQuick: React.FC<VinQuickProps> = ({ quickGroup, catalogCode, vehicleId, ssd, onBack, onNodeSelect }) => {
|
||||||
|
const { data, loading, error } = useQuery(GET_LAXIMO_QUICK_DETAIL, {
|
||||||
|
variables: {
|
||||||
|
catalogCode,
|
||||||
|
vehicleId,
|
||||||
|
quickGroupId: quickGroup.quickgroupid,
|
||||||
|
ssd
|
||||||
|
},
|
||||||
|
skip: !quickGroup || !quickGroup.quickgroupid
|
||||||
|
});
|
||||||
|
const quickDetail = data?.laximoQuickDetail;
|
||||||
|
|
||||||
|
const [isBrandModalOpen, setIsBrandModalOpen] = useState(false);
|
||||||
|
const [selectedDetail, setSelectedDetail] = useState<any>(null);
|
||||||
|
|
||||||
|
const handleUnitClick = (unit: any) => {
|
||||||
|
onNodeSelect({
|
||||||
|
...unit,
|
||||||
|
unitid: unit.unitid,
|
||||||
|
name: unit.name,
|
||||||
|
catalogCode,
|
||||||
|
vehicleId,
|
||||||
|
ssd
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleDetailClick = (detail: any) => {
|
||||||
|
setSelectedDetail(detail);
|
||||||
|
setIsBrandModalOpen(true);
|
||||||
|
};
|
||||||
|
const handleCloseBrandModal = () => {
|
||||||
|
setIsBrandModalOpen(false);
|
||||||
|
setSelectedDetail(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full">
|
||||||
|
{/* <button onClick={onBack} className="mb-4 px-4 py-2 bg-gray-200 rounded self-start">Назад</button> */}
|
||||||
|
{loading ? (
|
||||||
|
<div className="text-center py-4">Загружаем детали...</div>
|
||||||
|
) : error ? (
|
||||||
|
<div className="text-red-600 py-4">Ошибка загрузки деталей: {error.message}</div>
|
||||||
|
) : quickDetail && quickDetail.units ? (
|
||||||
|
quickDetail.units.map((unit: any) => (
|
||||||
|
<div key={unit.unitid} className="w-layout-vflex flex-block-14-copy-copy">
|
||||||
|
<div className="knotinfo">
|
||||||
|
{unit.imageurl || unit.largeimageurl ? (
|
||||||
|
<img
|
||||||
|
src={unit.largeimageurl ? unit.largeimageurl.replace('%size%', '250') : unit.imageurl.replace('%size%', '250')}
|
||||||
|
alt={unit.name}
|
||||||
|
className="image-26"
|
||||||
|
onError={e => { (e.currentTarget as HTMLImageElement).src = '/images/image-44.jpg'; }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<img src="/images/image-44.jpg" alt="Нет изображения" className="image-26" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="knot-img">
|
||||||
|
<h1 className="heading-19">{unit.name}</h1>
|
||||||
|
|
||||||
|
{unit.details && unit.details.length > 0 && unit.details.map((detail: any) => (
|
||||||
|
<div className="w-layout-hflex flex-block-115" key={detail.detailid}>
|
||||||
|
<div className="oemnuber">{detail.oem}</div>
|
||||||
|
<div className="partsname">{detail.name}</div>
|
||||||
|
<a href="#" className="button-3 w-button" onClick={e => { e.preventDefault(); handleDetailClick(detail); }}>Показать цены</a>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<a href="#" className="showallparts w-button" onClick={e => { e.preventDefault(); handleUnitClick(unit); }}>Подробнее</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="text-center text-gray-500 py-4">Нет деталей для этой группы</div>
|
||||||
|
)}
|
||||||
|
{isBrandModalOpen && selectedDetail && (
|
||||||
|
<BrandSelectionModal
|
||||||
|
isOpen={isBrandModalOpen}
|
||||||
|
onClose={handleCloseBrandModal}
|
||||||
|
articleNumber={selectedDetail.oem}
|
||||||
|
detailName={selectedDetail.name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VinQuick;
|
@ -180,23 +180,6 @@ export const SEARCH_LAXIMO_OEM = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SEARCH_LAXIMO_FULLTEXT = gql`
|
|
||||||
query SearchLaximoFulltext($catalogCode: String!, $vehicleId: String!, $searchText: String!, $ssd: String!) {
|
|
||||||
laximoFulltextSearch(catalogCode: $catalogCode, vehicleId: $vehicleId, searchText: $searchText, ssd: $ssd) {
|
|
||||||
details {
|
|
||||||
codeonimage
|
|
||||||
code
|
|
||||||
name
|
|
||||||
note
|
|
||||||
filter
|
|
||||||
oem
|
|
||||||
price
|
|
||||||
availability
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GET_LAXIMO_FULLTEXT_SEARCH = gql`
|
export const GET_LAXIMO_FULLTEXT_SEARCH = gql`
|
||||||
query GetLaximoFulltextSearch($catalogCode: String!, $vehicleId: String!, $searchQuery: String!, $ssd: String!) {
|
query GetLaximoFulltextSearch($catalogCode: String!, $vehicleId: String!, $searchQuery: String!, $ssd: String!) {
|
||||||
laximoFulltextSearch(catalogCode: $catalogCode, vehicleId: $vehicleId, searchQuery: $searchQuery, ssd: $ssd) {
|
laximoFulltextSearch(catalogCode: $catalogCode, vehicleId: $vehicleId, searchQuery: $searchQuery, ssd: $ssd) {
|
||||||
@ -208,6 +191,10 @@ export const GET_LAXIMO_FULLTEXT_SEARCH = gql`
|
|||||||
parttype
|
parttype
|
||||||
filter
|
filter
|
||||||
note
|
note
|
||||||
|
codeonimage
|
||||||
|
code
|
||||||
|
price
|
||||||
|
availability
|
||||||
attributes {
|
attributes {
|
||||||
key
|
key
|
||||||
name
|
name
|
||||||
|
@ -7,8 +7,10 @@ import CartSummary from "@/components/CartSummary";
|
|||||||
import CartRecommended from "../components/CartRecommended";
|
import CartRecommended from "../components/CartRecommended";
|
||||||
import CatalogSubscribe from "@/components/CatalogSubscribe";
|
import CatalogSubscribe from "@/components/CatalogSubscribe";
|
||||||
import MobileMenuBottomSection from "@/components/MobileMenuBottomSection";
|
import MobileMenuBottomSection from "@/components/MobileMenuBottomSection";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
export default function CartPage() {
|
export default function CartPage() {
|
||||||
|
const [step, setStep] = useState(1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<><Head>
|
<><Head>
|
||||||
@ -26,8 +28,8 @@ export default function CartPage() {
|
|||||||
<div className="w-layout-blockcontainer container w-container">
|
<div className="w-layout-blockcontainer container w-container">
|
||||||
<div className="w-layout-vflex cart-list">
|
<div className="w-layout-vflex cart-list">
|
||||||
<div className="w-layout-hflex core-product-card">
|
<div className="w-layout-hflex core-product-card">
|
||||||
<CartList />
|
<CartList isSummaryStep={step === 2} />
|
||||||
<CartSummary />
|
<CartSummary step={step} setStep={setStep} />
|
||||||
</div>
|
</div>
|
||||||
<CartRecommended />
|
<CartRecommended />
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,7 +58,7 @@ const ProfileActsPage = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="page-wrapper">
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>ProfileActs</title>
|
<title>ProfileActs</title>
|
||||||
<meta content="ProfileActs" property="og:title" />
|
<meta content="ProfileActs" property="og:title" />
|
||||||
@ -78,7 +78,7 @@ const ProfileActsPage = () => {
|
|||||||
</section>
|
</section>
|
||||||
<MobileMenuBottomSection />
|
<MobileMenuBottomSection />
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -16,6 +16,10 @@ import PartDetailCard from '@/components/PartDetailCard';
|
|||||||
import VinPartCard from '@/components/vin/VinPartCard';
|
import VinPartCard from '@/components/vin/VinPartCard';
|
||||||
import KnotIn from '@/components/vin/KnotIn';
|
import KnotIn from '@/components/vin/KnotIn';
|
||||||
import KnotParts from '@/components/vin/KnotParts';
|
import KnotParts from '@/components/vin/KnotParts';
|
||||||
|
import VinQuick from '@/components/vin/VinQuick';
|
||||||
|
import CatalogSubscribe from '@/components/CatalogSubscribe';
|
||||||
|
import MobileMenuBottomSection from '@/components/MobileMenuBottomSection';
|
||||||
|
|
||||||
|
|
||||||
interface LaximoVehicleInfo {
|
interface LaximoVehicleInfo {
|
||||||
vehicleid: string;
|
vehicleid: string;
|
||||||
@ -53,7 +57,20 @@ const VehicleDetailsPage = () => {
|
|||||||
const [searchType, setSearchType] = useState<'quickgroups' | 'categories' | 'fulltext'>(defaultSearchType);
|
const [searchType, setSearchType] = useState<'quickgroups' | 'categories' | 'fulltext'>(defaultSearchType);
|
||||||
const [showKnot, setShowKnot] = useState(false);
|
const [showKnot, setShowKnot] = useState(false);
|
||||||
const [foundParts, setFoundParts] = useState<any[]>([]);
|
const [foundParts, setFoundParts] = useState<any[]>([]);
|
||||||
|
const [activeTab, setActiveTab] = useState<'uzly' | 'manufacturer'>('uzly');
|
||||||
|
const [searchState, setSearchState] = useState<{
|
||||||
|
loading: boolean;
|
||||||
|
error: any;
|
||||||
|
query: string;
|
||||||
|
isSearching: boolean;
|
||||||
|
}>({
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
query: '',
|
||||||
|
isSearching: false
|
||||||
|
});
|
||||||
const [selectedNode, setSelectedNode] = useState<any | null>(null);
|
const [selectedNode, setSelectedNode] = useState<any | null>(null);
|
||||||
|
const [selectedQuickGroup, setSelectedQuickGroup] = useState<any | null>(null);
|
||||||
const handleCategoryClick = (e?: React.MouseEvent) => {
|
const handleCategoryClick = (e?: React.MouseEvent) => {
|
||||||
if (e) e.preventDefault();
|
if (e) e.preventDefault();
|
||||||
setShowKnot(true);
|
setShowKnot(true);
|
||||||
@ -123,7 +140,7 @@ const VehicleDetailsPage = () => {
|
|||||||
...(finalSsd && { ssd: finalSsd }),
|
...(finalSsd && { ssd: finalSsd }),
|
||||||
localized: true
|
localized: true
|
||||||
},
|
},
|
||||||
skip: !brand || !vehicleId,
|
skip: !brand || vehicleId === undefined || vehicleId === null,
|
||||||
errorPolicy: 'all'
|
errorPolicy: 'all'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -194,8 +211,9 @@ const VehicleDetailsPage = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если vehicleId невалидный (например, '0'), показываем предупреждение и не рендерим поиск
|
// Если vehicleId отсутствует или пустой, показываем предупреждение
|
||||||
if (!vehicleId || vehicleId === '0') {
|
// Важно: vehicleId может быть '0' для некоторых автомобилей, найденных по VIN
|
||||||
|
if (!vehicleId || vehicleId === '') {
|
||||||
return (
|
return (
|
||||||
<main className="min-h-screen bg-yellow-50 flex items-center justify-center">
|
<main className="min-h-screen bg-yellow-50 flex items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
@ -214,7 +232,8 @@ const VehicleDetailsPage = () => {
|
|||||||
|
|
||||||
// Гарантируем, что vehicleId — строка
|
// Гарантируем, что vehicleId — строка
|
||||||
const vehicleIdStr = Array.isArray(vehicleId) ? (vehicleId[0] || '') : (vehicleId || '');
|
const vehicleIdStr = Array.isArray(vehicleId) ? (vehicleId[0] || '') : (vehicleId || '');
|
||||||
const fallbackVehicleId = (vehicleIdStr !== '0' ? vehicleIdStr : '');
|
// Для Laximo API vehicleId может быть '0' для автомобилей, найденных по VIN
|
||||||
|
const fallbackVehicleId = vehicleIdStr;
|
||||||
|
|
||||||
let vehicleInfo = vehicleData?.laximoVehicleInfo || {
|
let vehicleInfo = vehicleData?.laximoVehicleInfo || {
|
||||||
vehicleid: fallbackVehicleId,
|
vehicleid: fallbackVehicleId,
|
||||||
@ -225,8 +244,8 @@ const VehicleDetailsPage = () => {
|
|||||||
attributes: [] as never[]
|
attributes: [] as never[]
|
||||||
};
|
};
|
||||||
|
|
||||||
// Если вдруг с сервера пришёл vehicleid: '0', подменяем на корректный
|
// Убеждаемся, что vehicleid соответствует параметру из URL
|
||||||
if (vehicleInfo.vehicleid === '0' && fallbackVehicleId) {
|
if (vehicleInfo.vehicleid !== fallbackVehicleId && fallbackVehicleId) {
|
||||||
vehicleInfo = { ...vehicleInfo, vehicleid: fallbackVehicleId };
|
vehicleInfo = { ...vehicleInfo, vehicleid: fallbackVehicleId };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,40 +289,101 @@ const VehicleDetailsPage = () => {
|
|||||||
{!selectedNode ? (
|
{!selectedNode ? (
|
||||||
<div className="w-layout-hflex flex-block-13">
|
<div className="w-layout-hflex flex-block-13">
|
||||||
{vehicleInfo && vehicleInfo.catalog && vehicleInfo.vehicleid && vehicleInfo.ssd && (
|
{vehicleInfo && vehicleInfo.catalog && vehicleInfo.vehicleid && vehicleInfo.ssd && (
|
||||||
<VinLeftbar
|
<>
|
||||||
vehicleInfo={vehicleInfo}
|
<VinLeftbar
|
||||||
onSearchResults={setFoundParts}
|
vehicleInfo={vehicleInfo}
|
||||||
onNodeSelect={setSelectedNode}
|
onSearchResults={({ results, loading, error, query, isSearching }) => {
|
||||||
/>
|
setFoundParts(results);
|
||||||
)}
|
setSearchState({ loading, error, query, isSearching: isSearching || false });
|
||||||
{/* Категории или Knot или карточки */}
|
}}
|
||||||
{foundParts.length > 0 ? (
|
onNodeSelect={setSelectedNode}
|
||||||
<div className="knot-parts">
|
onActiveTabChange={(tab) => setActiveTab(tab)}
|
||||||
{foundParts.map((detail, idx) => (
|
onQuickGroupSelect={setSelectedQuickGroup}
|
||||||
<VinPartCard
|
/>
|
||||||
key={detail.oem + idx}
|
{searchState.isSearching ? (
|
||||||
n={idx + 1}
|
<div className="knot-parts">
|
||||||
name={detail.name}
|
{searchState.loading ? (
|
||||||
oem={detail.oem}
|
<div className="text-center py-12">
|
||||||
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-red-600 mx-auto"></div>
|
||||||
|
<p className="mt-4 text-gray-600">Выполняется поиск...</p>
|
||||||
|
</div>
|
||||||
|
) : searchState.error ? (
|
||||||
|
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mt-3">
|
||||||
|
<div className="flex">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="ml-3">
|
||||||
|
<h3 className="text-sm font-medium text-red-800">
|
||||||
|
Ошибка поиска
|
||||||
|
</h3>
|
||||||
|
<div className="mt-2 text-sm text-red-700">
|
||||||
|
<p>{searchState.error.message}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : foundParts.length > 0 ? (
|
||||||
|
foundParts.map((detail, idx) => (
|
||||||
|
<VinPartCard
|
||||||
|
key={detail.oem + idx}
|
||||||
|
n={idx + 1}
|
||||||
|
name={detail.name}
|
||||||
|
oem={detail.oem}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<svg className="w-12 h-12 mx-auto text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.172 16.172a4 4 0 015.656 0M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||||
|
</svg>
|
||||||
|
<p className="text-gray-600">
|
||||||
|
По запросу "{searchState.query}" ничего не найдено
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-500 mt-1">
|
||||||
|
Попробуйте изменить поисковый запрос
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : showKnot ? (
|
||||||
|
<VinKnot />
|
||||||
|
) : selectedQuickGroup ? (
|
||||||
|
<VinQuick
|
||||||
|
quickGroup={selectedQuickGroup}
|
||||||
|
catalogCode={vehicleInfo.catalog}
|
||||||
|
vehicleId={vehicleInfo.vehicleid}
|
||||||
|
ssd={vehicleInfo.ssd}
|
||||||
|
onBack={() => setSelectedQuickGroup(null)}
|
||||||
|
onNodeSelect={setSelectedNode}
|
||||||
/>
|
/>
|
||||||
))}
|
) : (
|
||||||
</div>
|
<VinCategory
|
||||||
) : showKnot ? (
|
catalogCode={vehicleInfo.catalog}
|
||||||
<VinKnot />
|
vehicleId={vehicleInfo.vehicleid}
|
||||||
) : (
|
ssd={vehicleInfo.ssd}
|
||||||
<VinCategory
|
onNodeSelect={setSelectedNode}
|
||||||
catalogCode={vehicleInfo.catalog}
|
activeTab={activeTab}
|
||||||
vehicleId={vehicleInfo.vehicleid}
|
onQuickGroupSelect={setSelectedQuickGroup}
|
||||||
ssd={vehicleInfo.ssd}
|
/>
|
||||||
onNodeSelect={setSelectedNode}
|
)}
|
||||||
/>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-layout-hflex flex-block-13">
|
<div className="w-layout-hflex flex-block-13">
|
||||||
<div className="w-layout-vflex flex-block-14-copy-copy">
|
<div className="w-layout-vflex flex-block-14-copy-copy">
|
||||||
<button onClick={() => setSelectedNode(null)} style={{ marginBottom: 16 }}>Назад</button>
|
<button onClick={() => setSelectedNode(null)} style={{ marginBottom: 16 }}>Назад</button>
|
||||||
<KnotIn node={selectedNode} />
|
<KnotIn
|
||||||
|
catalogCode={vehicleInfo.catalog}
|
||||||
|
vehicleId={vehicleInfo.vehicleid}
|
||||||
|
ssd={vehicleInfo.ssd}
|
||||||
|
unitId={selectedNode.unitid}
|
||||||
|
unitName={selectedNode.name}
|
||||||
|
parts={unitDetails}
|
||||||
|
/>
|
||||||
{unitDetailsLoading ? (
|
{unitDetailsLoading ? (
|
||||||
<div style={{ padding: 24, textAlign: 'center' }}>Загружаем детали узла...</div>
|
<div style={{ padding: 24, textAlign: 'center' }}>Загружаем детали узла...</div>
|
||||||
) : unitDetailsError ? (
|
) : unitDetailsError ? (
|
||||||
@ -317,10 +397,15 @@ const VehicleDetailsPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<section className="section-3">
|
||||||
|
<CatalogSubscribe />
|
||||||
|
</section>
|
||||||
|
<Footer />
|
||||||
|
<MobileMenuBottomSection />
|
||||||
|
|
||||||
{/* ====== ВРЕМЕННЫЙ МАКЕТ ДЛЯ ВЕРСТКИ (конец) ====== */}
|
{/* ====== ВРЕМЕННЫЙ МАКЕТ ДЛЯ ВЕРСТКИ (конец) ====== */}
|
||||||
|
|
||||||
{/* Навигация */}
|
{/* Навигация
|
||||||
<nav className="bg-white border-b">
|
<nav className="bg-white border-b">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div className="flex items-center justify-between h-16">
|
<div className="flex items-center justify-between h-16">
|
||||||
@ -348,7 +433,7 @@ const VehicleDetailsPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Информация об автомобиле */}
|
Информация об автомобиле
|
||||||
<div className="bg-white border-b">
|
<div className="bg-white border-b">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||||
<div className="flex items-center space-x-4 mb-6">
|
<div className="flex items-center space-x-4 mb-6">
|
||||||
@ -368,7 +453,7 @@ const VehicleDetailsPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Предупреждение об ошибке */}
|
Предупреждение об ошибке
|
||||||
{hasError && (
|
{hasError && (
|
||||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
|
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
@ -389,7 +474,7 @@ const VehicleDetailsPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Отладочная информация */}
|
Отладочная информация
|
||||||
<div className="bg-gray-50 border border-gray-200 rounded-lg p-4 mb-6">
|
<div className="bg-gray-50 border border-gray-200 rounded-lg p-4 mb-6">
|
||||||
<h4 className="text-sm font-medium text-gray-900 mb-3">
|
<h4 className="text-sm font-medium text-gray-900 mb-3">
|
||||||
🔧 Отладочная информация
|
🔧 Отладочная информация
|
||||||
@ -420,7 +505,7 @@ const VehicleDetailsPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Характеристики автомобиля */}
|
Характеристики автомобиля
|
||||||
{vehicleInfo.attributes && vehicleInfo.attributes.length > 0 && (
|
{vehicleInfo.attributes && vehicleInfo.attributes.length > 0 && (
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||||
{vehicleInfo.attributes.map((attr, index) => (
|
{vehicleInfo.attributes.map((attr, index) => (
|
||||||
@ -434,7 +519,7 @@ const VehicleDetailsPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Способы поиска запчастей */}
|
Способы поиска запчастей
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<h2 className="text-xl font-bold text-gray-900 mb-2">Поиск запчастей</h2>
|
<h2 className="text-xl font-bold text-gray-900 mb-2">Поиск запчастей</h2>
|
||||||
@ -443,7 +528,7 @@ const VehicleDetailsPage = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Диагностический компонент */}
|
Диагностический компонент
|
||||||
<LaximoDiagnostic
|
<LaximoDiagnostic
|
||||||
catalogCode={vehicleInfo.catalog}
|
catalogCode={vehicleInfo.catalog}
|
||||||
vehicleId={vehicleInfo.vehicleid}
|
vehicleId={vehicleInfo.vehicleid}
|
||||||
@ -456,7 +541,7 @@ const VehicleDetailsPage = () => {
|
|||||||
searchType={searchType}
|
searchType={searchType}
|
||||||
onSearchTypeChange={setSearchType}
|
onSearchTypeChange={setSearchType}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div> */}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -74,12 +74,12 @@ const PartDetailPage = () => {
|
|||||||
oemNumber: oemNumber,
|
oemNumber: oemNumber,
|
||||||
ssd: finalSsd
|
ssd: finalSsd
|
||||||
},
|
},
|
||||||
skip: !brand || !vehicleId || !oemNumber || !finalSsd,
|
skip: !brand || vehicleId === undefined || vehicleId === null || !oemNumber || !finalSsd,
|
||||||
errorPolicy: 'all'
|
errorPolicy: 'all'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!brand || !vehicleId || !oemNumber) {
|
if (!brand || vehicleId === undefined || vehicleId === null || !oemNumber) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
|
@ -386,15 +386,17 @@ input.input-receiver:focus {
|
|||||||
color: var(--_fonts---color--light-blue-grey);
|
color: var(--_fonts---color--light-blue-grey);
|
||||||
}
|
}
|
||||||
|
|
||||||
.knotin {
|
/* .knotin {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
.knotin img {
|
.knotin img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
object-fit: contain; /* или cover */
|
object-fit: contain;
|
||||||
}
|
} */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.tabs-menu.w-tab-menu {
|
.tabs-menu.w-tab-menu {
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
@ -429,3 +431,47 @@ input#VinSearchInput {
|
|||||||
max-height: 2.8em;
|
max-height: 2.8em;
|
||||||
line-height: 1.4em;
|
line-height: 1.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.heading-9-copy,
|
||||||
|
.text-block-21-copy {
|
||||||
|
width: 250px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.w-layout-hflex.flex-block-6 {
|
||||||
|
flex-direction: column !important;
|
||||||
|
}
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.div-block-12,
|
||||||
|
.div-block-12.small,
|
||||||
|
.div-block-12-copy,
|
||||||
|
.div-block-12-copy.small,
|
||||||
|
.div-block-123,
|
||||||
|
.div-block-123.small,
|
||||||
|
.div-block-red,
|
||||||
|
.div-block-red.small {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.flex-block-6 {
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
grid-template-rows: none !important;
|
||||||
|
grid-template-areas: none !important;
|
||||||
|
grid-auto-flow: row !important; /* <--- ВАЖНО! */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.dropdown-toggle-card {
|
||||||
|
padding-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-link-3 {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
@ -787,6 +787,8 @@
|
|||||||
.w-layout-blockcontainer {
|
.w-layout-blockcontainer {
|
||||||
max-width: 728px;
|
max-width: 728px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 767px) {
|
@media screen and (max-width: 767px) {
|
||||||
@ -794,6 +796,8 @@
|
|||||||
max-width: none;
|
max-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.w-commerce-commercelayoutcontainer {
|
.w-commerce-commercelayoutcontainer {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
@ -2844,6 +2848,7 @@ body {
|
|||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-field-copy {
|
.text-field-copy {
|
||||||
@ -3110,6 +3115,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.block-name {
|
.block-name {
|
||||||
|
max-width: 300px;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -3908,6 +3914,7 @@ body {
|
|||||||
font-size: var(--_fonts---font-size--small-font-size);
|
font-size: var(--_fonts---font-size--small-font-size);
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
max-width: 100%;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
@ -4024,6 +4031,7 @@ body {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.button-for-mobile-menu-block:hover {
|
.button-for-mobile-menu-block:hover {
|
||||||
background-color: var(--_button---hover-dark_blue);
|
background-color: var(--_button---hover-dark_blue);
|
||||||
}
|
}
|
||||||
@ -4329,7 +4337,7 @@ body {
|
|||||||
color: var(--_fonts---color--black);
|
color: var(--_fonts---color--black);
|
||||||
font-size: var(--_fonts---font-size--bigger);
|
font-size: var(--_fonts---font-size--bigger);
|
||||||
text-align: right;
|
text-align: right;
|
||||||
max-width: 100px;
|
max-width: 200px;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@ -5426,10 +5434,10 @@ body {
|
|||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
.topmenuh {
|
.bottom_head {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
} */
|
||||||
|
|
||||||
.flex-block-4 {
|
.flex-block-4 {
|
||||||
grid-column-gap: 40px;
|
grid-column-gap: 40px;
|
||||||
@ -5519,7 +5527,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.flex-block-39-copy {
|
.flex-block-39-copy {
|
||||||
width: 200px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-ditail {
|
.cart-ditail {
|
||||||
@ -5579,6 +5587,8 @@ body {
|
|||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.button-for-mobile-menu-block {
|
.button-for-mobile-menu-block {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
@ -5680,9 +5690,9 @@ body {
|
|||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topmenub {
|
/* .bottom_head {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
} */
|
||||||
|
|
||||||
.vinleftbar {
|
.vinleftbar {
|
||||||
width: 320px;
|
width: 320px;
|
||||||
@ -6082,7 +6092,7 @@ body {
|
|||||||
padding-bottom: 40px;
|
padding-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top_head, .topmenuh {
|
.top_head, .bottom_head {
|
||||||
padding-left: 30px;
|
padding-left: 30px;
|
||||||
padding-right: 30px;
|
padding-right: 30px;
|
||||||
}
|
}
|
||||||
@ -6882,7 +6892,7 @@ body {
|
|||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topmenub {
|
.bottom_head {
|
||||||
padding-left: 30px;
|
padding-left: 30px;
|
||||||
padding-right: 30px;
|
padding-right: 30px;
|
||||||
}
|
}
|
||||||
@ -7329,7 +7339,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.flex-block-39-copy {
|
.flex-block-39-copy {
|
||||||
width: 200px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.heading-9-copy-copy {
|
.heading-9-copy-copy {
|
||||||
@ -7543,8 +7553,9 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.flex-block-18-copy-copy {
|
.flex-block-18-copy-copy {
|
||||||
grid-column-gap: 10px;
|
grid-column-gap: 40px;
|
||||||
grid-row-gap: 10px;
|
grid-row-gap: 40px;
|
||||||
|
flex-flow: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-block-4-copy {
|
.link-block-4-copy {
|
||||||
@ -7602,8 +7613,9 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.flex-block-87 {
|
.flex-block-87 {
|
||||||
grid-column-gap: 0px;
|
grid-column-gap: 10px;
|
||||||
grid-row-gap: 0px;
|
grid-row-gap: 10px;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-menu-bottom {
|
.mobile-menu-bottom {
|
||||||
@ -7627,10 +7639,16 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button-for-mobile-menu-block {
|
.button-for-mobile-menu-block {
|
||||||
grid-column-gap: 0px;
|
grid-column-gap: 2px;
|
||||||
grid-row-gap: 0px;
|
grid-row-gap: 2px;
|
||||||
width: 60px;
|
background-color: var(--_fonts---color--white);
|
||||||
padding-bottom: 5px;
|
color: var(--_button---light-blue-grey);
|
||||||
|
flex-flow: column;
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-for-mobile-menu-block:hover {
|
||||||
|
background-color: var(--_button---light-blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-3 {
|
.section-3 {
|
||||||
@ -7643,7 +7661,8 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.flex-block-93 {
|
.flex-block-93 {
|
||||||
margin-left: 0;
|
align-self: auto;
|
||||||
|
min-height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sort-list-card {
|
.sort-list-card {
|
||||||
@ -7882,6 +7901,10 @@ body {
|
|||||||
.container-copy.footer {
|
.container-copy.footer {
|
||||||
padding-bottom: 90px;
|
padding-bottom: 90px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mobile-menu-buttom-section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 479px) {
|
@media screen and (max-width: 479px) {
|
||||||
@ -7951,7 +7974,7 @@ body {
|
|||||||
grid-row-gap: 15px;
|
grid-row-gap: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top_head, .topmenuh {
|
.top_head, .bottom_head {
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
}
|
}
|
||||||
@ -9132,7 +9155,7 @@ body {
|
|||||||
top: 58px;
|
top: 58px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topmenub {
|
.bottom_head {
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
}
|
}
|
||||||
@ -9400,6 +9423,12 @@ body {
|
|||||||
#w-node-_35f55517-cbe0-9ee3-13bb-a3ed00029bba-00029ba8, #w-node-_35f55517-cbe0-9ee3-13bb-a3ed00029bc7-00029ba8 {
|
#w-node-_35f55517-cbe0-9ee3-13bb-a3ed00029bba-00029ba8, #w-node-_35f55517-cbe0-9ee3-13bb-a3ed00029bc7-00029ba8 {
|
||||||
justify-self: stretch;
|
justify-self: stretch;
|
||||||
}
|
}
|
||||||
|
.button-for-mobile-menu-block {
|
||||||
|
grid-column-gap: 0px;
|
||||||
|
grid-row-gap: 0px;
|
||||||
|
width: 60px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-block-113 {
|
.flex-block-113 {
|
||||||
@ -9457,7 +9486,7 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-toggle-3 {
|
.dropdown-toggle-3, .dropdown-toggle-card {
|
||||||
border-top-right-radius: var(--_round---normal);
|
border-top-right-radius: var(--_round---normal);
|
||||||
border-bottom-right-radius: var(--_round---normal);
|
border-bottom-right-radius: var(--_round---normal);
|
||||||
border-left: 2px solid #0000;
|
border-left: 2px solid #0000;
|
||||||
|
Reference in New Issue
Block a user