all cart #8
@ -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
|
||||
|
@ -92,7 +92,7 @@ const ProfileHistoryItem: React.FC<ProfileHistoryItemProps> = ({
|
||||
const path = e.currentTarget.querySelector('path');
|
||||
if (path) path.setAttribute('fill', '#D0D0D0');
|
||||
}}
|
||||
>
|
||||
>
|
||||
<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"
|
||||
|
@ -180,12 +180,42 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
||||
</div>
|
||||
);
|
||||
|
||||
// === Полнотекстовый поиск деталей (аналогично FulltextSearchSection) ===
|
||||
const [fulltextQuery, setFulltextQuery] = useState('');
|
||||
const [executeFulltextSearch, { data: fulltextData, loading: fulltextLoading, error: fulltextError }] = useLazyQuery(SEARCH_LAXIMO_FULLTEXT, { errorPolicy: 'all' });
|
||||
|
||||
const handleFulltextSearch = () => {
|
||||
if (!fulltextQuery.trim()) return;
|
||||
if (!ssd || ssd.trim() === '') {
|
||||
console.error('SSD обязателен для поиска по названию');
|
||||
return;
|
||||
}
|
||||
executeFulltextSearch({
|
||||
variables: {
|
||||
catalogCode,
|
||||
vehicleId,
|
||||
searchText: fulltextQuery.trim(),
|
||||
ssd
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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(); if (!ssd || ssd.trim() === '') { return; } 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 +233,95 @@ 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={e => setFulltextQuery(e.target.value)}
|
||||
onKeyDown={handleFulltextKeyDown}
|
||||
disabled={fulltextLoading}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleFulltextSearch}
|
||||
disabled={!fulltextQuery.trim() || fulltextLoading || !ssd || ssd.trim() === ''}
|
||||
className="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors ml-2"
|
||||
>
|
||||
{fulltextLoading ? 'Поиск...' : 'Найти'}
|
||||
</button>
|
||||
</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>
|
||||
)}
|
||||
{fulltextError && (
|
||||
<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>{fulltextError.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{fulltextResults && (
|
||||
<div className="bg-white border border-gray-200 rounded-lg overflow-hidden mt-3">
|
||||
<div className="px-6 py-4 bg-gray-50 border-b border-gray-200">
|
||||
<h3 className="text-lg font-medium text-gray-900">
|
||||
Результаты поиска: "{fulltextQuery}"
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Найдено {fulltextResults.length} деталей
|
||||
</p>
|
||||
</div>
|
||||
{fulltextResults.length > 0 ? (
|
||||
<div className="space-y-4 p-6">
|
||||
<div className="text-sm text-blue-700 bg-blue-50 border border-blue-200 rounded-lg p-3">
|
||||
💡 Нажмите на карточку детали для поиска предложений и цен. Используйте кнопку "Показать применимость" для просмотра применения в автомобиле.
|
||||
</div>
|
||||
{fulltextResults.map((detail: any, index: number) => (
|
||||
<VinPartCard
|
||||
key={`${detail.oem}-${index}`}
|
||||
n={index + 1}
|
||||
name={detail.name}
|
||||
oem={detail.oem}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-6 py-8 text-center">
|
||||
<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">
|
||||
По запросу "{fulltextQuery}" ничего не найдено
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
Попробуйте изменить поисковый запрос
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-layout-vflex flex-block-113">
|
||||
|
@ -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>
|
||||
|
@ -432,6 +432,13 @@ input#VinSearchInput {
|
||||
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 {
|
||||
|
@ -2848,6 +2848,7 @@ body {
|
||||
flex-flow: column;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
||||
}
|
||||
|
||||
.text-field-copy {
|
||||
@ -3114,6 +3115,7 @@ body {
|
||||
}
|
||||
|
||||
.block-name {
|
||||
max-width: 300px;
|
||||
flex-flow: column;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
@ -3912,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;
|
||||
}
|
||||
@ -4334,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;
|
||||
@ -5524,7 +5527,7 @@ body {
|
||||
}
|
||||
|
||||
.flex-block-39-copy {
|
||||
width: 200px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.cart-ditail {
|
||||
@ -7336,7 +7339,7 @@ body {
|
||||
}
|
||||
|
||||
.flex-block-39-copy {
|
||||
width: 200px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.heading-9-copy-copy {
|
||||
|
Reference in New Issue
Block a user