Compare commits
10 Commits
numbers
...
8a953d32ae
Author | SHA1 | Date | |
---|---|---|---|
8a953d32ae | |||
69ccc786ea | |||
8cae029d7f | |||
cbf50691c4 | |||
4a3da4d5c5 | |||
215853e8c7 | |||
f894b7e023 | |||
a879e5e5e7 | |||
85f7634158 | |||
5e454a7367 |
@ -16,6 +16,8 @@ interface CartItemProps {
|
||||
onComment: (comment: string) => void;
|
||||
onCountChange?: (count: number) => void;
|
||||
onRemove?: () => void;
|
||||
isSummaryStep?: boolean;
|
||||
itemNumber?: number;
|
||||
}
|
||||
|
||||
const CartItem: React.FC<CartItemProps> = ({
|
||||
@ -34,9 +36,14 @@ const CartItem: React.FC<CartItemProps> = ({
|
||||
onComment,
|
||||
onCountChange,
|
||||
onRemove,
|
||||
isSummaryStep = false,
|
||||
itemNumber,
|
||||
}) => (
|
||||
<div className="w-layout-hflex cart-item">
|
||||
<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
|
||||
className={"div-block-7" + (selected ? " active" : "")}
|
||||
onClick={onSelect}
|
||||
@ -48,9 +55,22 @@ const CartItem: React.FC<CartItemProps> = ({
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="w-layout-hflex block-name">
|
||||
<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 className="form-block-copy w-form">
|
||||
<form className="form-copy" onSubmit={e => e.preventDefault()}>
|
||||
@ -64,6 +84,7 @@ const CartItem: React.FC<CartItemProps> = ({
|
||||
id="Search-5"
|
||||
value={comment}
|
||||
onChange={e => onComment(e.target.value)}
|
||||
disabled={isSummaryStep}
|
||||
/>
|
||||
</form>
|
||||
<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>
|
||||
<div className="w-layout-hflex pcs-cart-s1">
|
||||
<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.5H6Z" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
{isSummaryStep ? (
|
||||
<div className="text-block-26" style={{ fontWeight: 600, fontSize: 14 }}>{count} шт.</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.5H6Z" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div className="input-pcs">
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={count}
|
||||
onChange={e => {
|
||||
const value = Math.max(1, parseInt(e.target.value, 10) || 1);
|
||||
onCountChange && onCountChange(value);
|
||||
}}
|
||||
className="text-block-26 w-full text-center outline-none"
|
||||
aria-label="Количество"
|
||||
style={{ width: 40 }}
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={count}
|
||||
onChange={e => {
|
||||
const value = Math.max(1, parseInt(e.target.value, 10) || 1);
|
||||
onCountChange && onCountChange(value);
|
||||
}}
|
||||
className="text-block-26 w-full text-center outline-none"
|
||||
aria-label="Количество"
|
||||
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
|
||||
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 className="w-layout-hflex flex-block-39-copy-copy">
|
||||
<h4 className="price-in-cart-s1">{price}</h4>
|
||||
<div className="price-1-pcs-cart-s1">{pricePerItem}</div>
|
||||
</div>
|
||||
{!isSummaryStep && (
|
||||
<div className="w-layout-hflex control-element">
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
<div
|
||||
className="bdel"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label="Удалить из корзины"
|
||||
onClick={onRemove}
|
||||
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onRemove && onRemove()}
|
||||
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');
|
||||
}}
|
||||
>
|
||||
<svg width="18" height="19" 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>
|
||||
<div
|
||||
className="bdel"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label="Удалить из корзины"
|
||||
onClick={onRemove}
|
||||
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onRemove && onRemove()}
|
||||
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');
|
||||
}}
|
||||
>
|
||||
<svg width="18" height="19" 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>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -3,7 +3,11 @@ import CartItem from "./CartItem";
|
||||
import { useCart } from "@/contexts/CartContext";
|
||||
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 { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
|
||||
const { items } = state;
|
||||
@ -25,24 +29,18 @@ const CartList: React.FC = () => {
|
||||
const handleFavorite = (id: string) => {
|
||||
const item = items.find(item => item.id === id);
|
||||
if (!item) return;
|
||||
|
||||
const isInFavorites = isFavorite(item.productId, item.offerKey, item.article, item.brand);
|
||||
|
||||
if (isInFavorites) {
|
||||
// Находим товар в избранном по правильному ID
|
||||
const favoriteItem = favorites.find((fav: any) => {
|
||||
// Проверяем по разным комбинациям идентификаторов
|
||||
if (item.productId && fav.productId === item.productId) return true;
|
||||
if (item.offerKey && fav.offerKey === item.offerKey) return true;
|
||||
if (fav.article === item.article && fav.brand === item.brand) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
if (favoriteItem) {
|
||||
removeFromFavorites(favoriteItem.id);
|
||||
}
|
||||
} else {
|
||||
// Добавляем в избранное
|
||||
addToFavorites({
|
||||
productId: item.productId,
|
||||
offerKey: item.offerKey,
|
||||
@ -68,59 +66,73 @@ const CartList: React.FC = () => {
|
||||
updateQuantity(id, count);
|
||||
};
|
||||
|
||||
// Функция для форматирования цены
|
||||
const formatPrice = (price: number, currency: string = 'RUB') => {
|
||||
return `${price.toLocaleString('ru-RU')} ${currency === 'RUB' ? '₽' : currency}`;
|
||||
};
|
||||
|
||||
// На втором шаге показываем только выбранные товары
|
||||
const displayItems = isSummaryStep ? items.filter(item => item.selected) : items;
|
||||
|
||||
return (
|
||||
<div className="w-layout-vflex flex-block-48">
|
||||
<div className="w-layout-vflex product-list-cart">
|
||||
<div className="w-layout-hflex multi-control">
|
||||
<div className="w-layout-hflex select-all-block" onClick={handleSelectAll} style={{ cursor: 'pointer' }}>
|
||||
<div
|
||||
className={"div-block-7" + (allSelected ? " active" : "")}
|
||||
style={{ marginRight: 8, cursor: 'pointer' }}
|
||||
>
|
||||
{allSelected && (
|
||||
<svg width="14" height="10" viewBox="0 0 14 10" fill="none">
|
||||
<path d="M2 5.5L6 9L12 2" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
)}
|
||||
{!isSummaryStep && (
|
||||
<div className="w-layout-hflex multi-control">
|
||||
<div className="w-layout-hflex select-all-block" onClick={handleSelectAll} style={{ cursor: 'pointer' }}>
|
||||
<div
|
||||
className={"div-block-7" + (allSelected ? " active" : "")}
|
||||
style={{ marginRight: 8, cursor: 'pointer' }}
|
||||
>
|
||||
{allSelected && (
|
||||
<svg width="14" height="10" viewBox="0 0 14 10" fill="none">
|
||||
<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 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>
|
||||
{items.length === 0 ? (
|
||||
)}
|
||||
{displayItems.length === 0 ? (
|
||||
<div className="empty-cart-message" style={{ textAlign: 'center', padding: '2rem', color: '#666' }}>
|
||||
<p>Ваша корзина пуста</p>
|
||||
<p>Добавьте товары из каталога</p>
|
||||
</div>
|
||||
) : (
|
||||
items.map((item) => {
|
||||
displayItems.map((item, idx) => {
|
||||
const isInFavorites = isFavorite(item.productId, item.offerKey, item.article, item.brand);
|
||||
|
||||
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
|
||||
name={item.name}
|
||||
description={item.description}
|
||||
@ -137,6 +149,8 @@ const CartList: React.FC = () => {
|
||||
onComment={(comment) => handleComment(item.id, comment)}
|
||||
onCountChange={(count) => handleCountChange(item.id, count)}
|
||||
onRemove={() => handleRemove(item.id)}
|
||||
isSummaryStep={isSummaryStep}
|
||||
itemNumber={idx + 1}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -26,8 +26,17 @@ const CartList2: React.FC = () => {
|
||||
<p>Вернитесь на предыдущий шаг и выберите товары</p>
|
||||
</div>
|
||||
) : (
|
||||
selectedItems.map((item) => (
|
||||
<div className="div-block-21-copy" key={item.id}>
|
||||
selectedItems.map((item, index) => (
|
||||
<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 info-block-search">
|
||||
<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 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 { summary, delivery, items, orderComment } = state;
|
||||
const legalEntityDropdownRef = useRef<HTMLDivElement>(null);
|
||||
@ -16,7 +21,6 @@ const CartSummary: React.FC = () => {
|
||||
const [error, setError] = useState("");
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [showAuthWarning, setShowAuthWarning] = useState(false);
|
||||
const [step, setStep] = useState(1);
|
||||
|
||||
// Новые состояния для первого шага
|
||||
const [selectedLegalEntity, setSelectedLegalEntity] = useState<string>("");
|
||||
@ -135,25 +139,20 @@ const CartSummary: React.FC = () => {
|
||||
toast.error('Пожалуйста, введите имя получателя');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!recipientPhone.trim()) {
|
||||
toast.error('Пожалуйста, введите телефон получателя');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedDeliveryAddress.trim()) {
|
||||
toast.error('Пожалуйста, выберите адрес доставки');
|
||||
return;
|
||||
}
|
||||
|
||||
// Обновляем данные доставки без стоимости
|
||||
updateDelivery({
|
||||
address: selectedDeliveryAddress,
|
||||
cost: 0, // Стоимость включена в товары
|
||||
cost: 0,
|
||||
date: 'Включена в стоимость товаров',
|
||||
time: 'Способ доставки указан в адресе'
|
||||
});
|
||||
|
||||
setStep(2);
|
||||
};
|
||||
|
||||
@ -894,7 +893,7 @@ const CartSummary: React.FC = () => {
|
||||
{error && <div style={{ color: 'red', marginTop: 10 }}>{error}</div>}
|
||||
|
||||
{/* Кнопка "Назад" */}
|
||||
{/* <button
|
||||
<button
|
||||
onClick={handleBackToStep1}
|
||||
style={{
|
||||
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
|
||||
|
@ -208,7 +208,7 @@ const CatalogGroupsSection: React.FC<CatalogGroupsSectionProps> = ({
|
||||
vehicleId,
|
||||
...(ssd && ssd.trim() !== '' && { ssd })
|
||||
},
|
||||
skip: !catalogCode || !vehicleId || catalogType !== 'quickGroups',
|
||||
skip: !catalogCode || vehicleId === undefined || vehicleId === null || catalogType !== 'quickGroups',
|
||||
errorPolicy: 'all'
|
||||
}
|
||||
);
|
||||
|
@ -33,7 +33,7 @@ const CategoriesSection: React.FC<CategoriesSectionProps> = ({
|
||||
vehicleId,
|
||||
...(ssd && ssd.trim() !== '' && { ssd })
|
||||
},
|
||||
skip: !catalogCode || !vehicleId,
|
||||
skip: !catalogCode || vehicleId === undefined || vehicleId === null,
|
||||
errorPolicy: 'all'
|
||||
}
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useLazyQuery } from '@apollo/client';
|
||||
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';
|
||||
|
||||
interface FulltextSearchSectionProps {
|
||||
@ -17,7 +17,7 @@ const FulltextSearchSection: React.FC<FulltextSearchSectionProps> = ({
|
||||
}) => {
|
||||
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'
|
||||
});
|
||||
|
||||
|
@ -187,13 +187,13 @@ const GroupDetailsSection: React.FC<GroupDetailsSectionProps> = ({
|
||||
const { data, loading, error } = useQuery<{ laximoQuickDetail: LaximoQuickDetail }>(
|
||||
GET_LAXIMO_QUICK_DETAIL,
|
||||
{
|
||||
variables: {
|
||||
variables: quickGroupId ? {
|
||||
catalogCode,
|
||||
vehicleId,
|
||||
quickGroupId,
|
||||
ssd
|
||||
},
|
||||
skip: !catalogCode || !vehicleId || !quickGroupId || !ssd,
|
||||
} : undefined,
|
||||
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !quickGroupId || !ssd || ssd.trim() === '',
|
||||
errorPolicy: 'all'
|
||||
}
|
||||
);
|
||||
|
@ -30,7 +30,7 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||
onSuccess={handleAuthSuccess}
|
||||
/>
|
||||
</header>
|
||||
<main className="pt-[132px]">{children}</main>
|
||||
<main className="pt-[108px] md:pt-[131px]">{children}</main>
|
||||
<MobileMenuBottomSection onOpenAuthModal={() => setAuthModalOpen(true)} />
|
||||
</>
|
||||
);
|
||||
|
@ -154,13 +154,13 @@ const QuickDetailSection: React.FC<QuickDetailSectionProps> = ({
|
||||
const { data: quickDetailData, loading: quickDetailLoading, error: quickDetailError } = useQuery<{ laximoQuickDetail: LaximoQuickDetail }>(
|
||||
GET_LAXIMO_QUICK_DETAIL,
|
||||
{
|
||||
variables: {
|
||||
variables: selectedGroup?.quickgroupid ? {
|
||||
catalogCode,
|
||||
vehicleId,
|
||||
quickGroupId: selectedGroup.quickgroupid,
|
||||
ssd
|
||||
},
|
||||
skip: !catalogCode || !vehicleId || !selectedGroup.quickgroupid || !ssd,
|
||||
} : undefined,
|
||||
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !selectedGroup?.quickgroupid || !ssd || ssd.trim() === '',
|
||||
errorPolicy: 'all',
|
||||
fetchPolicy: 'cache-and-network' // Принудительно запрашиваем данные
|
||||
}
|
||||
@ -169,11 +169,28 @@ const QuickDetailSection: React.FC<QuickDetailSectionProps> = ({
|
||||
const quickDetail = quickDetailData?.laximoQuickDetail;
|
||||
|
||||
// Добавляем отладочную информацию
|
||||
console.log('🔍 QuickDetailSection Debug:');
|
||||
console.log('📊 quickDetailData:', quickDetailData);
|
||||
console.log('📋 quickDetail:', quickDetail);
|
||||
console.log('🏗️ quickDetail.units:', quickDetail?.units);
|
||||
console.log('⚙️ Variables:', { catalogCode, vehicleId, quickGroupId: selectedGroup.quickgroupid, ssd });
|
||||
console.log('🔍 QuickDetailSection Debug:', {
|
||||
catalogCode,
|
||||
vehicleId,
|
||||
vehicleIdType: typeof vehicleId,
|
||||
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
|
||||
if (selectedUnit) {
|
||||
@ -213,6 +230,20 @@ const QuickDetailSection: React.FC<QuickDetailSectionProps> = ({
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="space-y-4">
|
||||
<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>
|
||||
<p className="text-red-700">Не удалось загрузить детали для группы "{selectedGroup.name}"</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>
|
||||
);
|
||||
@ -464,7 +522,7 @@ const QuickGroupsSection: React.FC<QuickGroupsSectionProps> = ({
|
||||
vehicleId,
|
||||
...(ssd && ssd.trim() !== '' && { ssd })
|
||||
},
|
||||
skip: !catalogCode || !vehicleId,
|
||||
skip: !catalogCode || vehicleId === undefined || vehicleId === null,
|
||||
errorPolicy: 'all'
|
||||
}
|
||||
);
|
||||
|
@ -39,7 +39,7 @@ const UnitDetailsSection: React.FC<UnitDetailsSectionProps> = ({
|
||||
unitId,
|
||||
ssd: ssd || ''
|
||||
},
|
||||
skip: !catalogCode || !vehicleId || !unitId,
|
||||
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId,
|
||||
errorPolicy: 'all'
|
||||
}
|
||||
);
|
||||
@ -54,7 +54,7 @@ const UnitDetailsSection: React.FC<UnitDetailsSectionProps> = ({
|
||||
unitId,
|
||||
ssd: ssd || ''
|
||||
},
|
||||
skip: !catalogCode || !vehicleId || !unitId,
|
||||
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId,
|
||||
errorPolicy: 'all'
|
||||
}
|
||||
);
|
||||
@ -69,7 +69,7 @@ const UnitDetailsSection: React.FC<UnitDetailsSectionProps> = ({
|
||||
unitId,
|
||||
ssd: ssd || ''
|
||||
},
|
||||
skip: !catalogCode || !vehicleId || !unitId,
|
||||
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId,
|
||||
errorPolicy: 'all'
|
||||
}
|
||||
);
|
||||
|
@ -59,7 +59,7 @@ const UnitsSection: React.FC<UnitsSectionProps> = ({
|
||||
categoryId,
|
||||
...(ssd && ssd.trim() !== '' && { ssd })
|
||||
},
|
||||
skip: !catalogCode || !vehicleId || !categoryId,
|
||||
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !categoryId,
|
||||
errorPolicy: 'all',
|
||||
fetchPolicy: 'no-cache', // Полностью отключаем кэширование для гарантии свежих данных
|
||||
notifyOnNetworkStatusChange: true
|
||||
|
@ -110,7 +110,7 @@ const LegalEntityListBlock: React.FC<LegalEntityListBlockProps> = ({ legalEntiti
|
||||
</div>
|
||||
<div
|
||||
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"
|
||||
tabIndex={0}
|
||||
onClick={() => router.push('/profile-requisites')}
|
||||
@ -130,7 +130,7 @@ const LegalEntityListBlock: React.FC<LegalEntityListBlockProps> = ({ legalEntiti
|
||||
</div>
|
||||
<div
|
||||
layer-name="Редактировать"
|
||||
className="text-sm leading-5 text-gray-600"
|
||||
className="text-sm leading-5 text-gray-600 group-hover:text-red-600"
|
||||
>
|
||||
Реквизиты компании
|
||||
</div>
|
||||
@ -141,8 +141,9 @@ const LegalEntityListBlock: React.FC<LegalEntityListBlockProps> = ({ legalEntiti
|
||||
<div
|
||||
role="button"
|
||||
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)}
|
||||
aria-label="Редактировать юридическое лицо"
|
||||
>
|
||||
<div className="relative h-4 w-[18px]">
|
||||
<Image
|
||||
@ -153,26 +154,37 @@ const LegalEntityListBlock: React.FC<LegalEntityListBlockProps> = ({ legalEntiti
|
||||
className="absolute left-0.5 top-0"
|
||||
/>
|
||||
</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
|
||||
role="button"
|
||||
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)}
|
||||
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]">
|
||||
<Image
|
||||
src="/images/delete.svg"
|
||||
alt="Удалить"
|
||||
width={16}
|
||||
height={16}
|
||||
className="absolute left-0.5 top-0"
|
||||
/>
|
||||
<div className="relative h-4 w-4">
|
||||
<svg width="16" height="16" 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>
|
||||
<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>
|
||||
|
@ -52,14 +52,50 @@ const ProfileAddressCard: React.FC<ProfileAddressCardProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between items-start self-stretch">
|
||||
<div className="flex gap-1.5 items-center cursor-pointer group" onClick={onEdit}>
|
||||
<img src="/images/edit.svg" alt="edit" width={18} height={18} className="mr-1.5 group-hover:filter-red" />
|
||||
<div className="relative text-sm leading-5 text-gray-600">Редактировать</div>
|
||||
<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}
|
||||
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 className="flex gap-1.5 items-center cursor-pointer group" onClick={onDelete}>
|
||||
<img src="/images/delete.svg" alt="delete" width={18} height={18} className="mr-1.5 group-hover:filter-red" />
|
||||
<div className="relative text-sm leading-5 text-gray-600">Удалить</div>
|
||||
<div
|
||||
className="flex gap-1.5 items-center cursor-pointer group"
|
||||
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>
|
||||
{onSelectMain && (
|
||||
|
@ -89,11 +89,11 @@ const ProfileBalanceCard: React.FC<ProfileBalanceCardProps> = ({
|
||||
{balance}
|
||||
</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 min-w-[160px]">
|
||||
<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-sm leading-snug ${isOverLimit ? 'text-red-600' : 'text-gray-600'}`}>
|
||||
{limitLeft.includes('Не установлен') ? limitLeft : `Осталось ${limitLeft}`}
|
||||
|
@ -179,7 +179,7 @@ const ProfileGarageMain = () => {
|
||||
|
||||
{!vehiclesLoading && filteredVehicles.map((vehicle) => (
|
||||
<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 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">
|
||||
@ -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">
|
||||
<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)}
|
||||
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
|
||||
loading="lazy"
|
||||
src="/images/delete.svg"
|
||||
className="object-contain shrink-0 self-stretch my-auto aspect-[1.12] w-[18px]"
|
||||
/>
|
||||
<span className="self-stretch my-auto text-gray-600">
|
||||
<svg width="16" height="16" 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>
|
||||
<span className="self-stretch my-auto text-gray-600 group-hover:text-red-600">
|
||||
Удалить
|
||||
</span>
|
||||
</button>
|
||||
@ -418,15 +432,29 @@ const ProfileGarageMain = () => {
|
||||
</div>
|
||||
<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)}
|
||||
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
|
||||
loading="lazy"
|
||||
src="/images/delete.svg"
|
||||
className="object-contain shrink-0 self-stretch my-auto aspect-[1.12] w-[18px]"
|
||||
/>
|
||||
<span className="self-stretch my-auto text-gray-600">
|
||||
<svg width="16" height="16" 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>
|
||||
<span className="self-stretch my-auto text-gray-600 group-hover:text-red-600">
|
||||
Удалить
|
||||
</span>
|
||||
</button>
|
||||
|
@ -80,24 +80,25 @@ const ProfileHistoryItem: React.FC<ProfileHistoryItemProps> = ({
|
||||
<div className="w-16 text-center max-md:w-full">
|
||||
<button
|
||||
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="Удалить из истории"
|
||||
aria-label="Удалить из истории"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="transition-colors"
|
||||
tabIndex={0}
|
||||
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');
|
||||
}}
|
||||
>
|
||||
<path d="M3 6h18" className="group-hover:stroke-[#ec1c24]" />
|
||||
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" className="group-hover:stroke-[#ec1c24]" />
|
||||
<path d="M8 6V4c0-1 1-2 2-2h4c-1 0 2 1 2 2v2" 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="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>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -272,7 +272,7 @@ const ProfileHistoryMain = () => {
|
||||
|
||||
return (
|
||||
<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">
|
||||
<SearchInput
|
||||
value={search}
|
||||
@ -280,7 +280,7 @@ const ProfileHistoryMain = () => {
|
||||
placeholder="Поиск в истории..."
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2 max-sm:hidden">
|
||||
{(selectedManufacturer !== "Все" || search.trim() || activeTab !== "Все") && (
|
||||
<button
|
||||
onClick={() => {
|
||||
|
@ -89,7 +89,7 @@ const ProfileHistoryTabs: React.FC<ProfileHistoryTabsProps> = ({
|
||||
</div>
|
||||
))}
|
||||
<div
|
||||
className="relative w-[240px] max-w-full"
|
||||
className="relative w-[240px] max-w-full max-sm:w-full"
|
||||
ref={dropdownRef}
|
||||
tabIndex={0}
|
||||
>
|
||||
|
@ -10,7 +10,7 @@ const ProfileSettingsActionsBlock: React.FC<ProfileSettingsActionsBlockProps> =
|
||||
Сохранить изменения
|
||||
</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>
|
||||
</div>
|
||||
);
|
||||
|
@ -47,7 +47,7 @@ const KnotIn: React.FC<KnotInProps> = ({ catalogCode, vehicleId, ssd, unitId, un
|
||||
GET_LAXIMO_UNIT_INFO,
|
||||
{
|
||||
variables: { catalogCode, vehicleId, unitId, ssd: ssd || '' },
|
||||
skip: !catalogCode || !vehicleId || !unitId,
|
||||
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId,
|
||||
errorPolicy: 'all',
|
||||
}
|
||||
);
|
||||
@ -56,7 +56,7 @@ const KnotIn: React.FC<KnotInProps> = ({ catalogCode, vehicleId, ssd, unitId, un
|
||||
GET_LAXIMO_UNIT_IMAGE_MAP,
|
||||
{
|
||||
variables: { catalogCode, vehicleId, unitId, ssd: ssd || '' },
|
||||
skip: !catalogCode || !vehicleId || !unitId,
|
||||
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId,
|
||||
errorPolicy: 'all',
|
||||
}
|
||||
);
|
||||
|
@ -12,7 +12,7 @@ interface VinCategoryProps {
|
||||
const VinCategory: React.FC<VinCategoryProps> = ({ catalogCode, vehicleId, ssd, onNodeSelect }) => {
|
||||
const { data: categoriesData, loading: categoriesLoading, error: categoriesError } = useQuery(GET_LAXIMO_CATEGORIES, {
|
||||
variables: { catalogCode, vehicleId, ssd },
|
||||
skip: !catalogCode || !vehicleId,
|
||||
skip: !catalogCode || vehicleId === undefined || vehicleId === null,
|
||||
errorPolicy: "all",
|
||||
});
|
||||
const categories = categoriesData?.laximoCategories || [];
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
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';
|
||||
|
||||
interface VinLeftbarProps {
|
||||
@ -10,10 +10,23 @@ interface VinLeftbarProps {
|
||||
ssd: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
onSearchResults?: (results: any[]) => void;
|
||||
onSearchResults?: (data: {
|
||||
results: any[];
|
||||
loading: boolean;
|
||||
error: any;
|
||||
query: string;
|
||||
isSearching?: boolean;
|
||||
}) => void;
|
||||
onNodeSelect?: (node: any) => void;
|
||||
}
|
||||
|
||||
interface QuickGroup {
|
||||
quickgroupid: string;
|
||||
name: string;
|
||||
link?: boolean;
|
||||
children?: QuickGroup[];
|
||||
}
|
||||
|
||||
const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, onNodeSelect }) => {
|
||||
const catalogCode = vehicleInfo.catalog;
|
||||
const vehicleId = vehicleInfo.vehicleid;
|
||||
@ -21,11 +34,11 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
const [openIndex, setOpenIndex] = useState<number | null>(null);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
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, {
|
||||
variables: { catalogCode, vehicleId, ssd },
|
||||
skip: !catalogCode || !vehicleId,
|
||||
skip: !catalogCode || vehicleId === undefined || vehicleId === null,
|
||||
errorPolicy: 'all'
|
||||
});
|
||||
const categories = categoriesData?.laximoCategories || [];
|
||||
@ -79,16 +92,18 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
const searchResults = data?.laximoFulltextSearch;
|
||||
|
||||
useEffect(() => {
|
||||
if (searchResults && onSearchResults) {
|
||||
onSearchResults(searchResults.details || []);
|
||||
if (onSearchResults) {
|
||||
onSearchResults({
|
||||
results: searchResults?.details || [],
|
||||
loading: loading,
|
||||
error: error,
|
||||
query: searchQuery
|
||||
});
|
||||
}
|
||||
if (!searchQuery.trim() && onSearchResults) {
|
||||
onSearchResults([]);
|
||||
}
|
||||
}, [searchResults, searchQuery, onSearchResults]);
|
||||
}, [searchResults, loading, error, searchQuery, onSearchResults]);
|
||||
|
||||
// --- Новый блок: вычисляем доступность поиска ---
|
||||
const isSearchAvailable = !!catalogCode && !!vehicleId && !!ssd && ssd.trim() !== '';
|
||||
const isSearchAvailable = !!catalogCode && vehicleId !== undefined && vehicleId !== null && !!ssd && ssd.trim() !== '';
|
||||
const showWarning = !isSearchAvailable;
|
||||
const showError = !!error && isSearchAvailable && searchQuery.trim();
|
||||
const showNotFound = isSearchAvailable && searchQuery.trim() && !loading && data && searchResults && searchResults.details && searchResults.details.length === 0;
|
||||
@ -98,7 +113,7 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
const [selectedQuickGroup, setSelectedQuickGroup] = useState<any | null>(null);
|
||||
const { data: quickGroupsData, loading: quickGroupsLoading, error: quickGroupsError } = useQuery(GET_LAXIMO_QUICK_GROUPS, {
|
||||
variables: { catalogCode, vehicleId, ssd },
|
||||
skip: !catalogCode || !vehicleId || activeTab !== 'manufacturer',
|
||||
skip: !catalogCode || vehicleId === undefined || vehicleId === null || activeTab !== 'manufacturer',
|
||||
errorPolicy: 'all'
|
||||
});
|
||||
const quickGroups = quickGroupsData?.laximoQuickGroups || [];
|
||||
@ -136,12 +151,30 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
const skipQuickDetail =
|
||||
!selectedQuickGroup ||
|
||||
!catalogCode ||
|
||||
!vehicleId ||
|
||||
!selectedQuickGroup.quickgroupid ||
|
||||
!ssd;
|
||||
vehicleId === undefined ||
|
||||
vehicleId === null ||
|
||||
!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, {
|
||||
variables: selectedQuickGroup ? {
|
||||
variables: selectedQuickGroup?.quickgroupid && !skipQuickDetail ? {
|
||||
catalogCode,
|
||||
vehicleId,
|
||||
quickGroupId: selectedQuickGroup.quickgroupid,
|
||||
@ -152,40 +185,80 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
});
|
||||
const quickDetail = quickDetailData?.laximoQuickDetail;
|
||||
|
||||
const renderQuickGroupTree = (groups: any[], level = 0): React.ReactNode => (
|
||||
<div>
|
||||
{groups.map(group => (
|
||||
<div key={group.quickgroupid} style={{ marginLeft: level * 16, marginBottom: 8 }}>
|
||||
<div
|
||||
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'}`}
|
||||
onClick={() => handleQuickGroupClick(group)}
|
||||
>
|
||||
{group.children && group.children.length > 0 && (
|
||||
<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">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
)}
|
||||
<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>
|
||||
)}
|
||||
</div>
|
||||
{group.children && group.children.length > 0 && expandedQuickGroups.has(group.quickgroupid) && (
|
||||
<div className="mt-1">
|
||||
{renderQuickGroupTree(group.children, level + 1)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
// === Полнотекстовый поиск деталей (аналогично FulltextSearchSection) ===
|
||||
const [fulltextQuery, setFulltextQuery] = useState('');
|
||||
const [executeFulltextSearch, { data: fulltextData, loading: fulltextLoading, error: fulltextError }] = useLazyQuery(GET_LAXIMO_FULLTEXT_SEARCH, { errorPolicy: 'all' });
|
||||
|
||||
const handleFulltextSearch = () => {
|
||||
if (!fulltextQuery.trim()) {
|
||||
if (onSearchResults) {
|
||||
onSearchResults({
|
||||
results: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
query: '',
|
||||
isSearching: false
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!ssd || ssd.trim() === '') {
|
||||
console.error('SSD обязателен для поиска по названию');
|
||||
return;
|
||||
}
|
||||
// Отправляем начальное состояние поиска родителю
|
||||
if (onSearchResults) {
|
||||
onSearchResults({
|
||||
results: [],
|
||||
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 || [];
|
||||
|
||||
return (
|
||||
<div className="w-layout-vflex vinleftbar">
|
||||
{/* === Форма полнотекстового поиска === */}
|
||||
<div className="div-block-2">
|
||||
<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">
|
||||
<a href="#" className="link-block-3 w-inline-block" onClick={e => { e.preventDefault(); if (!ssd || ssd.trim() === '') { return; } handleSearch(); }}>
|
||||
<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(); handleFulltextSearch(); }}>
|
||||
<div className="code-embed-6 w-embed">
|
||||
{/* SVG */}
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
@ -203,14 +276,29 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
type="text"
|
||||
id="VinSearchInput"
|
||||
required
|
||||
value={searchQuery}
|
||||
onChange={e => setSearchQuery(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
disabled={loading}
|
||||
value={fulltextQuery}
|
||||
onChange={handleInputChange}
|
||||
onKeyDown={handleFulltextKeyDown}
|
||||
disabled={fulltextLoading}
|
||||
/>
|
||||
</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 className="w-layout-vflex flex-block-113">
|
||||
@ -260,7 +348,6 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
<>
|
||||
{categories.map((category: any, idx: number) => {
|
||||
const isOpen = openIndex === idx;
|
||||
// Подкатегории: сначала children, если нет — unitsByCategory
|
||||
const subcategories = category.children && category.children.length > 0
|
||||
? category.children
|
||||
: unitsByCategory[category.quickgroupid] || [];
|
||||
@ -271,11 +358,11 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
data-delay="0"
|
||||
className={`dropdown-4 w-dropdown${isOpen ? " w--open" : ""}`}
|
||||
>
|
||||
<div
|
||||
className={`dropdown-toggle-3 w-dropdown-toggle${isOpen ? " w--open" : ""}`}
|
||||
onClick={() => handleToggle(idx, category.quickgroupid)}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<div
|
||||
className={`dropdown-toggle-card w-dropdown-toggle${isOpen ? " w--open" : ""}`}
|
||||
onClick={() => handleToggle(idx, category.quickgroupid)}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<div className="w-icon-dropdown-toggle"></div>
|
||||
<div className="text-block-56">{category.name}</div>
|
||||
</div>
|
||||
@ -285,7 +372,7 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
<a
|
||||
href="#"
|
||||
key={subcat.quickgroupid || subcat.unitid}
|
||||
className="dropdown-link-3 w-dropdown-link"
|
||||
className="dropdown-link-3 w-dropdown-link pl-0"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
if (onNodeSelect) {
|
||||
@ -310,46 +397,158 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
)
|
||||
) : (
|
||||
// Manufacturer tab content (QuickGroups)
|
||||
<div style={{ padding: '16px' }}>
|
||||
{quickGroupsLoading ? (
|
||||
<div style={{ textAlign: 'center' }}>Загружаем группы быстрого поиска...</div>
|
||||
) : quickGroupsError ? (
|
||||
<div style={{ color: 'red' }}>Ошибка загрузки групп: {quickGroupsError.message}</div>
|
||||
) : selectedQuickGroup ? (
|
||||
<div>
|
||||
<button onClick={() => setSelectedQuickGroup(null)} className="mb-4 px-3 py-1 bg-gray-200 rounded">Назад к группам</button>
|
||||
<h3 className="text-lg font-semibold mb-2">{selectedQuickGroup.name}</h3>
|
||||
{quickDetailLoading ? (
|
||||
<div>Загружаем детали...</div>
|
||||
) : quickDetailError ? (
|
||||
<div style={{ color: 'red' }}>Ошибка загрузки деталей: {quickDetailError.message}</div>
|
||||
) : quickDetail && quickDetail.units && quickDetail.units.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{quickDetail.units.map((unit: any) => (
|
||||
<div key={unit.unitid} className="p-3 bg-gray-50 rounded border border-gray-200">
|
||||
<div className="font-medium text-gray-900">{unit.name}</div>
|
||||
{unit.details && unit.details.length > 0 && (
|
||||
<ul className="mt-2 text-sm text-gray-700 list-disc pl-5">
|
||||
{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>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
quickGroupsLoading ? (
|
||||
<div style={{ padding: 16, textAlign: 'center' }}>Загружаем группы быстрого поиска...</div>
|
||||
) : quickGroupsError ? (
|
||||
<div style={{ color: 'red', padding: 16 }}>Ошибка загрузки групп: {quickGroupsError.message}</div>
|
||||
) : (
|
||||
<>
|
||||
{(quickGroups as QuickGroup[]).map((group: QuickGroup) => {
|
||||
const hasChildren = group.children && group.children.length > 0;
|
||||
const isOpen = expandedQuickGroups.has(group.quickgroupid);
|
||||
|
||||
if (!hasChildren) {
|
||||
return (
|
||||
<a
|
||||
href="#"
|
||||
key={group.quickgroupid}
|
||||
className="dropdown-link-3 w-dropdown-link"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
if (group.link) {
|
||||
handleQuickGroupClick(group);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{group.name}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
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) {
|
||||
handleQuickGroupClick(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) {
|
||||
handleQuickGroupClick(subChild);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{subChild.name}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
) : (
|
||||
<div>Нет деталей для этой группы</div>
|
||||
)}
|
||||
</div>
|
||||
) : quickGroups.length > 0 ? (
|
||||
renderQuickGroupTree(quickGroups)
|
||||
) : (
|
||||
<div>Нет доступных групп быстрого поиска</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Quick Detail Modal */}
|
||||
{selectedQuickGroup && (
|
||||
<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 className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-semibold">{selectedQuickGroup.name}</h3>
|
||||
<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 */}
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { useRouter } from 'next/router';
|
||||
import BrandSelectionModal from '../BrandSelectionModal';
|
||||
|
||||
interface VinPartCardProps {
|
||||
n?: number;
|
||||
@ -10,28 +11,38 @@ interface VinPartCardProps {
|
||||
|
||||
const VinPartCard: React.FC<VinPartCardProps> = ({ n, oem, name, onPriceClick }) => {
|
||||
const router = useRouter();
|
||||
const [isBrandModalOpen, setIsBrandModalOpen] = useState(false);
|
||||
|
||||
const handlePriceClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
if (onPriceClick) onPriceClick();
|
||||
if (oem) router.push(`/search?q=${encodeURIComponent(oem)}&mode=parts`);
|
||||
setIsBrandModalOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-layout-hflex knotlistitem">
|
||||
<div className="w-layout-hflex flex-block-116">
|
||||
{n !== undefined && <div className="nuberlist">{n}</div>}
|
||||
<div className="oemnuber">{oem}</div>
|
||||
</div>
|
||||
<div className="partsname">{name}</div>
|
||||
<div className="w-layout-hflex flex-block-117">
|
||||
<a href="#" className="button-3 w-button" onClick={handlePriceClick}>Цена</a>
|
||||
<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 className="w-layout-hflex knotlistitem">
|
||||
<div className="w-layout-hflex flex-block-116">
|
||||
{n !== undefined && <div className="nuberlist">{n}</div>}
|
||||
<div className="oemnuber">{oem}</div>
|
||||
</div>
|
||||
<div className="partsname">{name}</div>
|
||||
<div className="w-layout-hflex flex-block-117">
|
||||
<a href="#" className="button-3 w-button" onClick={handlePriceClick}>Цена</a>
|
||||
<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>
|
||||
<BrandSelectionModal
|
||||
isOpen={isBrandModalOpen}
|
||||
onClose={() => setIsBrandModalOpen(false)}
|
||||
articleNumber={oem}
|
||||
detailName={name}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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`
|
||||
query GetLaximoFulltextSearch($catalogCode: String!, $vehicleId: String!, $searchQuery: String!, $ssd: String!) {
|
||||
laximoFulltextSearch(catalogCode: $catalogCode, vehicleId: $vehicleId, searchQuery: $searchQuery, ssd: $ssd) {
|
||||
@ -208,6 +191,10 @@ export const GET_LAXIMO_FULLTEXT_SEARCH = gql`
|
||||
parttype
|
||||
filter
|
||||
note
|
||||
codeonimage
|
||||
code
|
||||
price
|
||||
availability
|
||||
attributes {
|
||||
key
|
||||
name
|
||||
|
@ -7,8 +7,10 @@ import CartSummary from "@/components/CartSummary";
|
||||
import CartRecommended from "../components/CartRecommended";
|
||||
import CatalogSubscribe from "@/components/CatalogSubscribe";
|
||||
import MobileMenuBottomSection from "@/components/MobileMenuBottomSection";
|
||||
import React, { useState } from "react";
|
||||
|
||||
export default function CartPage() {
|
||||
const [step, setStep] = useState(1);
|
||||
|
||||
return (
|
||||
<><Head>
|
||||
@ -26,8 +28,8 @@ export default function CartPage() {
|
||||
<div className="w-layout-blockcontainer container w-container">
|
||||
<div className="w-layout-vflex cart-list">
|
||||
<div className="w-layout-hflex core-product-card">
|
||||
<CartList />
|
||||
<CartSummary />
|
||||
<CartList isSummaryStep={step === 2} />
|
||||
<CartSummary step={step} setStep={setStep} />
|
||||
</div>
|
||||
<CartRecommended />
|
||||
</div>
|
||||
|
@ -58,7 +58,7 @@ const ProfileActsPage = () => {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="page-wrapper">
|
||||
<>
|
||||
<Head>
|
||||
<title>ProfileActs</title>
|
||||
<meta content="ProfileActs" property="og:title" />
|
||||
@ -78,7 +78,7 @@ const ProfileActsPage = () => {
|
||||
</section>
|
||||
<MobileMenuBottomSection />
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -53,6 +53,17 @@ const VehicleDetailsPage = () => {
|
||||
const [searchType, setSearchType] = useState<'quickgroups' | 'categories' | 'fulltext'>(defaultSearchType);
|
||||
const [showKnot, setShowKnot] = useState(false);
|
||||
const [foundParts, setFoundParts] = useState<any[]>([]);
|
||||
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 handleCategoryClick = (e?: React.MouseEvent) => {
|
||||
if (e) e.preventDefault();
|
||||
@ -123,7 +134,7 @@ const VehicleDetailsPage = () => {
|
||||
...(finalSsd && { ssd: finalSsd }),
|
||||
localized: true
|
||||
},
|
||||
skip: !brand || !vehicleId,
|
||||
skip: !brand || vehicleId === undefined || vehicleId === null,
|
||||
errorPolicy: 'all'
|
||||
}
|
||||
);
|
||||
@ -194,8 +205,9 @@ const VehicleDetailsPage = () => {
|
||||
);
|
||||
}
|
||||
|
||||
// Если vehicleId невалидный (например, '0'), показываем предупреждение и не рендерим поиск
|
||||
if (!vehicleId || vehicleId === '0') {
|
||||
// Если vehicleId отсутствует или пустой, показываем предупреждение
|
||||
// Важно: vehicleId может быть '0' для некоторых автомобилей, найденных по VIN
|
||||
if (!vehicleId || vehicleId === '') {
|
||||
return (
|
||||
<main className="min-h-screen bg-yellow-50 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
@ -214,7 +226,8 @@ const VehicleDetailsPage = () => {
|
||||
|
||||
// Гарантируем, что 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 || {
|
||||
vehicleid: fallbackVehicleId,
|
||||
@ -225,8 +238,8 @@ const VehicleDetailsPage = () => {
|
||||
attributes: [] as never[]
|
||||
};
|
||||
|
||||
// Если вдруг с сервера пришёл vehicleid: '0', подменяем на корректный
|
||||
if (vehicleInfo.vehicleid === '0' && fallbackVehicleId) {
|
||||
// Убеждаемся, что vehicleid соответствует параметру из URL
|
||||
if (vehicleInfo.vehicleid !== fallbackVehicleId && fallbackVehicleId) {
|
||||
vehicleInfo = { ...vehicleInfo, vehicleid: fallbackVehicleId };
|
||||
}
|
||||
|
||||
@ -270,33 +283,74 @@ const VehicleDetailsPage = () => {
|
||||
{!selectedNode ? (
|
||||
<div className="w-layout-hflex flex-block-13">
|
||||
{vehicleInfo && vehicleInfo.catalog && vehicleInfo.vehicleid && vehicleInfo.ssd && (
|
||||
<VinLeftbar
|
||||
vehicleInfo={vehicleInfo}
|
||||
onSearchResults={setFoundParts}
|
||||
onNodeSelect={setSelectedNode}
|
||||
/>
|
||||
)}
|
||||
{/* Категории или Knot или карточки */}
|
||||
{foundParts.length > 0 ? (
|
||||
<div className="knot-parts">
|
||||
{foundParts.map((detail, idx) => (
|
||||
<VinPartCard
|
||||
key={detail.oem + idx}
|
||||
n={idx + 1}
|
||||
name={detail.name}
|
||||
oem={detail.oem}
|
||||
<>
|
||||
<VinLeftbar
|
||||
vehicleInfo={vehicleInfo}
|
||||
onSearchResults={({ results, loading, error, query, isSearching }) => {
|
||||
setFoundParts(results);
|
||||
setSearchState({ loading, error, query, isSearching: isSearching || false });
|
||||
}}
|
||||
onNodeSelect={setSelectedNode}
|
||||
/>
|
||||
{searchState.isSearching ? (
|
||||
<div className="knot-parts">
|
||||
{searchState.loading ? (
|
||||
<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 />
|
||||
) : (
|
||||
<VinCategory
|
||||
catalogCode={vehicleInfo.catalog}
|
||||
vehicleId={vehicleInfo.vehicleid}
|
||||
ssd={vehicleInfo.ssd}
|
||||
onNodeSelect={setSelectedNode}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : showKnot ? (
|
||||
<VinKnot />
|
||||
) : (
|
||||
<VinCategory
|
||||
catalogCode={vehicleInfo.catalog}
|
||||
vehicleId={vehicleInfo.vehicleid}
|
||||
ssd={vehicleInfo.ssd}
|
||||
onNodeSelect={setSelectedNode}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
|
@ -74,12 +74,12 @@ const PartDetailPage = () => {
|
||||
oemNumber: oemNumber,
|
||||
ssd: finalSsd
|
||||
},
|
||||
skip: !brand || !vehicleId || !oemNumber || !finalSsd,
|
||||
skip: !brand || vehicleId === undefined || vehicleId === null || !oemNumber || !finalSsd,
|
||||
errorPolicy: 'all'
|
||||
}
|
||||
);
|
||||
|
||||
if (!brand || !vehicleId || !oemNumber) {
|
||||
if (!brand || vehicleId === undefined || vehicleId === null || !oemNumber) {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
|
@ -430,4 +430,48 @@ input#VinSearchInput {
|
||||
text-overflow: ellipsis;
|
||||
max-height: 2.8em;
|
||||
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 {
|
||||
max-width: 728px;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
@ -794,6 +796,8 @@
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.w-commerce-commercelayoutcontainer {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
@ -2844,6 +2848,7 @@ body {
|
||||
flex-flow: column;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
||||
}
|
||||
|
||||
.text-field-copy {
|
||||
@ -3110,6 +3115,7 @@ body {
|
||||
}
|
||||
|
||||
.block-name {
|
||||
max-width: 300px;
|
||||
flex-flow: column;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
@ -3908,6 +3914,7 @@ body {
|
||||
font-size: var(--_fonts---font-size--small-font-size);
|
||||
height: 20px;
|
||||
margin-top: 0;
|
||||
max-width: 100%;
|
||||
margin-bottom: 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
@ -4024,6 +4031,7 @@ body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
.button-for-mobile-menu-block:hover {
|
||||
background-color: var(--_button---hover-dark_blue);
|
||||
}
|
||||
@ -4329,7 +4337,7 @@ body {
|
||||
color: var(--_fonts---color--black);
|
||||
font-size: var(--_fonts---font-size--bigger);
|
||||
text-align: right;
|
||||
max-width: 100px;
|
||||
max-width: 200px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
font-weight: 700;
|
||||
@ -5426,10 +5434,10 @@ body {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.topmenuh {
|
||||
/*
|
||||
.bottom_head {
|
||||
margin-top: 0;
|
||||
}
|
||||
} */
|
||||
|
||||
.flex-block-4 {
|
||||
grid-column-gap: 40px;
|
||||
@ -5519,7 +5527,7 @@ body {
|
||||
}
|
||||
|
||||
.flex-block-39-copy {
|
||||
width: 200px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.cart-ditail {
|
||||
@ -5579,6 +5587,8 @@ body {
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.button-for-mobile-menu-block {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
@ -5680,9 +5690,9 @@ body {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.topmenub {
|
||||
/* .bottom_head {
|
||||
margin-top: 0;
|
||||
}
|
||||
} */
|
||||
|
||||
.vinleftbar {
|
||||
width: 320px;
|
||||
@ -6082,7 +6092,7 @@ body {
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.top_head, .topmenuh {
|
||||
.top_head, .bottom_head {
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
@ -6882,7 +6892,7 @@ body {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.topmenub {
|
||||
.bottom_head {
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
@ -7329,7 +7339,7 @@ body {
|
||||
}
|
||||
|
||||
.flex-block-39-copy {
|
||||
width: 200px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.heading-9-copy-copy {
|
||||
@ -7543,8 +7553,9 @@ body {
|
||||
}
|
||||
|
||||
.flex-block-18-copy-copy {
|
||||
grid-column-gap: 10px;
|
||||
grid-row-gap: 10px;
|
||||
grid-column-gap: 40px;
|
||||
grid-row-gap: 40px;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
.link-block-4-copy {
|
||||
@ -7602,8 +7613,9 @@ body {
|
||||
}
|
||||
|
||||
.flex-block-87 {
|
||||
grid-column-gap: 0px;
|
||||
grid-row-gap: 0px;
|
||||
grid-column-gap: 10px;
|
||||
grid-row-gap: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mobile-menu-bottom {
|
||||
@ -7627,10 +7639,16 @@ body {
|
||||
}
|
||||
|
||||
.button-for-mobile-menu-block {
|
||||
grid-column-gap: 0px;
|
||||
grid-row-gap: 0px;
|
||||
width: 60px;
|
||||
padding-bottom: 5px;
|
||||
grid-column-gap: 2px;
|
||||
grid-row-gap: 2px;
|
||||
background-color: var(--_fonts---color--white);
|
||||
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 {
|
||||
@ -7643,7 +7661,8 @@ body {
|
||||
}
|
||||
|
||||
.flex-block-93 {
|
||||
margin-left: 0;
|
||||
align-self: auto;
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
.sort-list-card {
|
||||
@ -7882,6 +7901,10 @@ body {
|
||||
.container-copy.footer {
|
||||
padding-bottom: 90px;
|
||||
}
|
||||
|
||||
.mobile-menu-buttom-section {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 479px) {
|
||||
@ -7951,7 +7974,7 @@ body {
|
||||
grid-row-gap: 15px;
|
||||
}
|
||||
|
||||
.top_head, .topmenuh {
|
||||
.top_head, .bottom_head {
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
@ -9132,7 +9155,7 @@ body {
|
||||
top: 58px;
|
||||
}
|
||||
|
||||
.topmenub {
|
||||
.bottom_head {
|
||||
padding-left: 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 {
|
||||
justify-self: stretch;
|
||||
}
|
||||
.button-for-mobile-menu-block {
|
||||
grid-column-gap: 0px;
|
||||
grid-row-gap: 0px;
|
||||
width: 60px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.flex-block-113 {
|
||||
@ -9457,7 +9486,7 @@ body {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.dropdown-toggle-3 {
|
||||
.dropdown-toggle-3, .dropdown-toggle-card {
|
||||
border-top-right-radius: var(--_round---normal);
|
||||
border-bottom-right-radius: var(--_round---normal);
|
||||
border-left: 2px solid #0000;
|
||||
|
Reference in New Issue
Block a user