Merge pull request 'all cart' (#8) from frontcart into main
Reviewed-on: #8
This commit is contained in:
@ -16,6 +16,8 @@ interface CartItemProps {
|
|||||||
onComment: (comment: string) => void;
|
onComment: (comment: string) => void;
|
||||||
onCountChange?: (count: number) => void;
|
onCountChange?: (count: number) => void;
|
||||||
onRemove?: () => void;
|
onRemove?: () => void;
|
||||||
|
isSummaryStep?: boolean;
|
||||||
|
itemNumber?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CartItem: React.FC<CartItemProps> = ({
|
const CartItem: React.FC<CartItemProps> = ({
|
||||||
@ -34,9 +36,14 @@ const CartItem: React.FC<CartItemProps> = ({
|
|||||||
onComment,
|
onComment,
|
||||||
onCountChange,
|
onCountChange,
|
||||||
onRemove,
|
onRemove,
|
||||||
|
isSummaryStep = false,
|
||||||
|
itemNumber,
|
||||||
}) => (
|
}) => (
|
||||||
<div className="w-layout-hflex cart-item">
|
<div className="w-layout-hflex cart-item">
|
||||||
<div className="w-layout-hflex info-block-search-copy">
|
<div className="w-layout-hflex info-block-search-copy">
|
||||||
|
{isSummaryStep ? (
|
||||||
|
<div style={{ marginRight: 12, minWidth: 24, textAlign: 'center', fontWeight: 600, fontSize: 14 }}>{itemNumber}</div>
|
||||||
|
) : (
|
||||||
<div
|
<div
|
||||||
className={"div-block-7" + (selected ? " active" : "")}
|
className={"div-block-7" + (selected ? " active" : "")}
|
||||||
onClick={onSelect}
|
onClick={onSelect}
|
||||||
@ -48,9 +55,22 @@ const CartItem: React.FC<CartItemProps> = ({
|
|||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<div className="w-layout-hflex block-name">
|
<div className="w-layout-hflex block-name">
|
||||||
<h4 className="heading-9-copy">{name}</h4>
|
<h4 className="heading-9-copy">{name}</h4>
|
||||||
<div className="text-block-21-copy">{description}</div>
|
<div
|
||||||
|
className={
|
||||||
|
"text-block-21-copy" +
|
||||||
|
(isSummaryStep && itemNumber === 1 ? " border-t-0" : "")
|
||||||
|
}
|
||||||
|
style={
|
||||||
|
isSummaryStep && itemNumber === 1
|
||||||
|
? { borderTop: 'none' }
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-block-copy w-form">
|
<div className="form-block-copy w-form">
|
||||||
<form className="form-copy" onSubmit={e => e.preventDefault()}>
|
<form className="form-copy" onSubmit={e => e.preventDefault()}>
|
||||||
@ -64,6 +84,7 @@ const CartItem: React.FC<CartItemProps> = ({
|
|||||||
id="Search-5"
|
id="Search-5"
|
||||||
value={comment}
|
value={comment}
|
||||||
onChange={e => onComment(e.target.value)}
|
onChange={e => onComment(e.target.value)}
|
||||||
|
disabled={isSummaryStep}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
<div className="success-message w-form-done">
|
<div className="success-message w-form-done">
|
||||||
@ -80,87 +101,95 @@ const CartItem: React.FC<CartItemProps> = ({
|
|||||||
<div className="text-block-21-copy-copy">{deliveryDate}</div>
|
<div className="text-block-21-copy-copy">{deliveryDate}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-layout-hflex pcs-cart-s1">
|
<div className="w-layout-hflex pcs-cart-s1">
|
||||||
<div
|
{isSummaryStep ? (
|
||||||
className="minus-plus"
|
<div className="text-block-26" style={{ fontWeight: 600, fontSize: 14 }}>{count} шт.</div>
|
||||||
onClick={() => onCountChange && onCountChange(count - 1)}
|
) : (
|
||||||
style={{ cursor: 'pointer' }}
|
<>
|
||||||
aria-label="Уменьшить количество"
|
<div
|
||||||
tabIndex={0}
|
className="minus-plus"
|
||||||
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onCountChange && onCountChange(count - 1)}
|
onClick={() => onCountChange && onCountChange(count - 1)}
|
||||||
role="button"
|
style={{ cursor: 'pointer' }}
|
||||||
>
|
aria-label="Уменьшить количество"
|
||||||
<div className="pluspcs w-embed">
|
tabIndex={0}
|
||||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onCountChange && onCountChange(count - 1)}
|
||||||
<path d="M6 10.5V9.5H14V10.5H6Z" fill="currentColor"/>
|
role="button"
|
||||||
</svg>
|
>
|
||||||
</div>
|
<div className="pluspcs w-embed">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 10.5V9.5H14V10.5H6Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="input-pcs">
|
<div className="input-pcs">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min={1}
|
min={1}
|
||||||
value={count}
|
value={count}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
const value = Math.max(1, parseInt(e.target.value, 10) || 1);
|
const value = Math.max(1, parseInt(e.target.value, 10) || 1);
|
||||||
onCountChange && onCountChange(value);
|
onCountChange && onCountChange(value);
|
||||||
}}
|
}}
|
||||||
className="text-block-26 w-full text-center outline-none"
|
className="text-block-26 w-full text-center outline-none"
|
||||||
aria-label="Количество"
|
aria-label="Количество"
|
||||||
style={{ width: 40 }}
|
style={{ width: 40 }}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="minus-plus"
|
||||||
|
onClick={() => onCountChange && onCountChange(count + 1)}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
aria-label="Увеличить количество"
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onCountChange && onCountChange(count + 1)}
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div className="pluspcs w-embed">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 10.5V9.5H14V10.5H6ZM9.5 6H10.5V14H9.5V6Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
className="minus-plus"
|
|
||||||
onClick={() => onCountChange && onCountChange(count + 1)}
|
|
||||||
style={{ cursor: 'pointer' }}
|
|
||||||
aria-label="Увеличить количество"
|
|
||||||
tabIndex={0}
|
|
||||||
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onCountChange && onCountChange(count + 1)}
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
<div className="pluspcs w-embed">
|
|
||||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M6 10.5V9.5H14V10.5H6ZM9.5 6H10.5V14H9.5V6Z" fill="currentColor"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-layout-hflex flex-block-39-copy-copy">
|
<div className="w-layout-hflex flex-block-39-copy-copy">
|
||||||
<h4 className="price-in-cart-s1">{price}</h4>
|
<h4 className="price-in-cart-s1">{price}</h4>
|
||||||
<div className="price-1-pcs-cart-s1">{pricePerItem}</div>
|
<div className="price-1-pcs-cart-s1">{pricePerItem}</div>
|
||||||
</div>
|
</div>
|
||||||
|
{!isSummaryStep && (
|
||||||
<div className="w-layout-hflex control-element">
|
<div className="w-layout-hflex control-element">
|
||||||
<div className="favorite-icon w-embed" onClick={onFavorite} style={{ cursor: 'pointer', color: favorite ? '#e53935' : undefined }}>
|
<div className="favorite-icon w-embed" onClick={onFavorite} style={{ cursor: 'pointer', color: favorite ? '#e53935' : undefined }}>
|
||||||
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M9 16.5L7.84 15.4929C3.72 11.93 1 9.57248 1 6.69619C1 4.33869 2.936 2.5 5.4 2.5C6.792 2.5 8.128 3.11798 9 4.08692C9.872 3.11798 11.208 2.5 12.6 2.5C15.064 2.5 17 4.33869 17 6.69619C17 9.57248 14.28 11.93 10.16 15.4929L9 16.5Z" fill={favorite ? "#e53935" : "currentColor"} />
|
<path d="M9 16.5L7.84 15.4929C3.72 11.93 1 9.57248 1 6.69619C1 4.33869 2.936 2.5 5.4 2.5C6.792 2.5 8.128 3.11798 9 4.08692C9.872 3.11798 11.208 2.5 12.6 2.5C15.064 2.5 17 4.33869 17 6.69619C17 9.57248 14.28 11.93 10.16 15.4929L9 16.5Z" fill={favorite ? "#e53935" : "currentColor"} />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="bdel"
|
className="bdel"
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label="Удалить из корзины"
|
aria-label="Удалить из корзины"
|
||||||
onClick={onRemove}
|
onClick={onRemove}
|
||||||
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onRemove && onRemove()}
|
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onRemove && onRemove()}
|
||||||
style={{ display: 'inline-flex', cursor: 'pointer', transition: 'color 0.2s' }}
|
style={{ display: 'inline-flex', cursor: 'pointer', transition: 'color 0.2s' }}
|
||||||
onMouseEnter={e => {
|
onMouseEnter={e => {
|
||||||
const path = e.currentTarget.querySelector('path');
|
const path = e.currentTarget.querySelector('path');
|
||||||
if (path) path.setAttribute('fill', '#ec1c24');
|
if (path) path.setAttribute('fill', '#ec1c24');
|
||||||
}}
|
}}
|
||||||
onMouseLeave={e => {
|
onMouseLeave={e => {
|
||||||
const path = e.currentTarget.querySelector('path');
|
const path = e.currentTarget.querySelector('path');
|
||||||
if (path) path.setAttribute('fill', '#D0D0D0');
|
if (path) path.setAttribute('fill', '#D0D0D0');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
d="M4.625 17.5C4.14375 17.5 3.73192 17.3261 3.3895 16.9782C3.04708 16.6304 2.87558 16.2117 2.875 15.7222V4.16667H2V2.38889H6.375V1.5H11.625V2.38889H16V4.16667H15.125V15.7222C15.125 16.2111 14.9538 16.6298 14.6114 16.9782C14.269 17.3267 13.8568 17.5006 13.375 17.5H4.625ZM6.375 13.9444H8.125V5.94444H6.375V13.9444ZM9.875 13.9444H11.625V5.94444H9.875V13.9444Z"
|
d="M4.625 17.5C4.14375 17.5 3.73192 17.3261 3.3895 16.9782C3.04708 16.6304 2.87558 16.2117 2.875 15.7222V4.16667H2V2.38889H6.375V1.5H11.625V2.38889H16V4.16667H15.125V15.7222C15.125 16.2111 14.9538 16.6298 14.6114 16.9782C14.269 17.3267 13.8568 17.5006 13.375 17.5H4.625ZM6.375 13.9444H8.125V5.94444H6.375V13.9444ZM9.875 13.9444H11.625V5.94444H9.875V13.9444Z"
|
||||||
fill="#D0D0D0"
|
fill="#D0D0D0"
|
||||||
style={{ transition: 'fill 0.2s' }}
|
style={{ transition: 'fill 0.2s' }}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -3,7 +3,11 @@ import CartItem from "./CartItem";
|
|||||||
import { useCart } from "@/contexts/CartContext";
|
import { useCart } from "@/contexts/CartContext";
|
||||||
import { useFavorites } from "@/contexts/FavoritesContext";
|
import { useFavorites } from "@/contexts/FavoritesContext";
|
||||||
|
|
||||||
const CartList: React.FC = () => {
|
interface CartListProps {
|
||||||
|
isSummaryStep?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CartList: React.FC<CartListProps> = ({ isSummaryStep = false }) => {
|
||||||
const { state, toggleSelect, updateComment, removeItem, selectAll, removeSelected, updateQuantity } = useCart();
|
const { state, toggleSelect, updateComment, removeItem, selectAll, removeSelected, updateQuantity } = useCart();
|
||||||
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
|
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
|
||||||
const { items } = state;
|
const { items } = state;
|
||||||
@ -25,24 +29,18 @@ const CartList: React.FC = () => {
|
|||||||
const handleFavorite = (id: string) => {
|
const handleFavorite = (id: string) => {
|
||||||
const item = items.find(item => item.id === id);
|
const item = items.find(item => item.id === id);
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
const isInFavorites = isFavorite(item.productId, item.offerKey, item.article, item.brand);
|
const isInFavorites = isFavorite(item.productId, item.offerKey, item.article, item.brand);
|
||||||
|
|
||||||
if (isInFavorites) {
|
if (isInFavorites) {
|
||||||
// Находим товар в избранном по правильному ID
|
|
||||||
const favoriteItem = favorites.find((fav: any) => {
|
const favoriteItem = favorites.find((fav: any) => {
|
||||||
// Проверяем по разным комбинациям идентификаторов
|
|
||||||
if (item.productId && fav.productId === item.productId) return true;
|
if (item.productId && fav.productId === item.productId) return true;
|
||||||
if (item.offerKey && fav.offerKey === item.offerKey) return true;
|
if (item.offerKey && fav.offerKey === item.offerKey) return true;
|
||||||
if (fav.article === item.article && fav.brand === item.brand) return true;
|
if (fav.article === item.article && fav.brand === item.brand) return true;
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (favoriteItem) {
|
if (favoriteItem) {
|
||||||
removeFromFavorites(favoriteItem.id);
|
removeFromFavorites(favoriteItem.id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Добавляем в избранное
|
|
||||||
addToFavorites({
|
addToFavorites({
|
||||||
productId: item.productId,
|
productId: item.productId,
|
||||||
offerKey: item.offerKey,
|
offerKey: item.offerKey,
|
||||||
@ -68,59 +66,73 @@ const CartList: React.FC = () => {
|
|||||||
updateQuantity(id, count);
|
updateQuantity(id, count);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Функция для форматирования цены
|
|
||||||
const formatPrice = (price: number, currency: string = 'RUB') => {
|
const formatPrice = (price: number, currency: string = 'RUB') => {
|
||||||
return `${price.toLocaleString('ru-RU')} ${currency === 'RUB' ? '₽' : currency}`;
|
return `${price.toLocaleString('ru-RU')} ${currency === 'RUB' ? '₽' : currency}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// На втором шаге показываем только выбранные товары
|
||||||
|
const displayItems = isSummaryStep ? items.filter(item => item.selected) : items;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-layout-vflex flex-block-48">
|
<div className="w-layout-vflex flex-block-48">
|
||||||
<div className="w-layout-vflex product-list-cart">
|
<div className="w-layout-vflex product-list-cart">
|
||||||
<div className="w-layout-hflex multi-control">
|
{!isSummaryStep && (
|
||||||
<div className="w-layout-hflex select-all-block" onClick={handleSelectAll} style={{ cursor: 'pointer' }}>
|
<div className="w-layout-hflex multi-control">
|
||||||
<div
|
<div className="w-layout-hflex select-all-block" onClick={handleSelectAll} style={{ cursor: 'pointer' }}>
|
||||||
className={"div-block-7" + (allSelected ? " active" : "")}
|
<div
|
||||||
style={{ marginRight: 8, cursor: 'pointer' }}
|
className={"div-block-7" + (allSelected ? " active" : "")}
|
||||||
>
|
style={{ marginRight: 8, cursor: 'pointer' }}
|
||||||
{allSelected && (
|
>
|
||||||
<svg width="14" height="10" viewBox="0 0 14 10" fill="none">
|
{allSelected && (
|
||||||
<path d="M2 5.5L6 9L12 2" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
<svg width="14" height="10" viewBox="0 0 14 10" fill="none">
|
||||||
</svg>
|
<path d="M2 5.5L6 9L12 2" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
)}
|
</svg>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="text-block-30">Выделить всё</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-layout-hflex select-all-block" onClick={handleRemoveSelected} style={{ cursor: 'pointer' }}
|
||||||
|
onMouseEnter={e => {
|
||||||
|
const path = (e.currentTarget.querySelector('path'));
|
||||||
|
if (path) path.setAttribute('fill', '#ec1c24');
|
||||||
|
}}
|
||||||
|
onMouseLeave={e => {
|
||||||
|
const path = (e.currentTarget.querySelector('path'));
|
||||||
|
if (path) path.setAttribute('fill', '#D0D0D0');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="text-block-30">Удалить выбранные</div>
|
||||||
|
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg" className="image-13">
|
||||||
|
<path
|
||||||
|
d="M4.625 17.5C4.14375 17.5 3.73192 17.3261 3.3895 16.9782C3.04708 16.6304 2.87558 16.2117 2.875 15.7222V4.16667H2V2.38889H6.375V1.5H11.625V2.38889H16V4.16667H15.125V15.7222C15.125 16.2111 14.9538 16.6298 14.6114 16.9782C14.269 17.3267 13.8568 17.5006 13.375 17.5H4.625ZM6.375 13.9444H8.125V5.94444H6.375V13.9444ZM9.875 13.9444H11.625V5.94444H9.875V13.9444Z"
|
||||||
|
fill="#D0D0D0"
|
||||||
|
style={{ transition: 'fill 0.2s' }}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-block-30">Выделить всё</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="w-layout-hflex select-all-block" onClick={handleRemoveSelected} style={{ cursor: 'pointer' }}
|
)}
|
||||||
onMouseEnter={e => {
|
{displayItems.length === 0 ? (
|
||||||
const path = (e.currentTarget.querySelector('path'));
|
|
||||||
if (path) path.setAttribute('fill', '#ec1c24');
|
|
||||||
}}
|
|
||||||
onMouseLeave={e => {
|
|
||||||
const path = (e.currentTarget.querySelector('path'));
|
|
||||||
if (path) path.setAttribute('fill', '#D0D0D0');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="text-block-30">Удалить выбранные</div>
|
|
||||||
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg" className="image-13">
|
|
||||||
<path
|
|
||||||
d="M4.625 17.5C4.14375 17.5 3.73192 17.3261 3.3895 16.9782C3.04708 16.6304 2.87558 16.2117 2.875 15.7222V4.16667H2V2.38889H6.375V1.5H11.625V2.38889H16V4.16667H15.125V15.7222C15.125 16.2111 14.9538 16.6298 14.6114 16.9782C14.269 17.3267 13.8568 17.5006 13.375 17.5H4.625ZM6.375 13.9444H8.125V5.94444H6.375V13.9444ZM9.875 13.9444H11.625V5.94444H9.875V13.9444Z"
|
|
||||||
fill="#D0D0D0"
|
|
||||||
style={{ transition: 'fill 0.2s' }}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{items.length === 0 ? (
|
|
||||||
<div className="empty-cart-message" style={{ textAlign: 'center', padding: '2rem', color: '#666' }}>
|
<div className="empty-cart-message" style={{ textAlign: 'center', padding: '2rem', color: '#666' }}>
|
||||||
<p>Ваша корзина пуста</p>
|
<p>Ваша корзина пуста</p>
|
||||||
<p>Добавьте товары из каталога</p>
|
<p>Добавьте товары из каталога</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
items.map((item) => {
|
displayItems.map((item, idx) => {
|
||||||
const isInFavorites = isFavorite(item.productId, item.offerKey, item.article, item.brand);
|
const isInFavorites = isFavorite(item.productId, item.offerKey, item.article, item.brand);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="div-block-21" key={item.id}>
|
<div
|
||||||
|
className={
|
||||||
|
"div-block-21" +
|
||||||
|
(isSummaryStep && idx === 0 ? " border-t-0" : "")
|
||||||
|
}
|
||||||
|
style={
|
||||||
|
isSummaryStep && idx === 0
|
||||||
|
? { borderTop: 'none' }
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
key={item.id}
|
||||||
|
>
|
||||||
<CartItem
|
<CartItem
|
||||||
name={item.name}
|
name={item.name}
|
||||||
description={item.description}
|
description={item.description}
|
||||||
@ -137,6 +149,8 @@ const CartList: React.FC = () => {
|
|||||||
onComment={(comment) => handleComment(item.id, comment)}
|
onComment={(comment) => handleComment(item.id, comment)}
|
||||||
onCountChange={(count) => handleCountChange(item.id, count)}
|
onCountChange={(count) => handleCountChange(item.id, count)}
|
||||||
onRemove={() => handleRemove(item.id)}
|
onRemove={() => handleRemove(item.id)}
|
||||||
|
isSummaryStep={isSummaryStep}
|
||||||
|
itemNumber={idx + 1}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -26,8 +26,17 @@ const CartList2: React.FC = () => {
|
|||||||
<p>Вернитесь на предыдущий шаг и выберите товары</p>
|
<p>Вернитесь на предыдущий шаг и выберите товары</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
selectedItems.map((item) => (
|
selectedItems.map((item, index) => (
|
||||||
<div className="div-block-21-copy" key={item.id}>
|
<div
|
||||||
|
className={
|
||||||
|
"div-block-21-copy" +
|
||||||
|
(index === 0 ? " border-t-0" : "")
|
||||||
|
}
|
||||||
|
style={
|
||||||
|
index === 0 ? { borderTop: 'none' } : undefined
|
||||||
|
}
|
||||||
|
key={item.id}
|
||||||
|
>
|
||||||
<div className="w-layout-hflex cart-item-check">
|
<div className="w-layout-hflex cart-item-check">
|
||||||
<div className="w-layout-hflex info-block-search">
|
<div className="w-layout-hflex info-block-search">
|
||||||
<div className="text-block-35">{item.quantity}</div>
|
<div className="text-block-35">{item.quantity}</div>
|
||||||
|
@ -5,7 +5,12 @@ import { useMutation, useQuery } from "@apollo/client";
|
|||||||
import { CREATE_ORDER, CREATE_PAYMENT, GET_CLIENT_ME, GET_CLIENT_DELIVERY_ADDRESSES } from "@/lib/graphql";
|
import { CREATE_ORDER, CREATE_PAYMENT, GET_CLIENT_ME, GET_CLIENT_DELIVERY_ADDRESSES } from "@/lib/graphql";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
const CartSummary: React.FC = () => {
|
interface CartSummaryProps {
|
||||||
|
step: number;
|
||||||
|
setStep: (step: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CartSummary: React.FC<CartSummaryProps> = ({ step, setStep }) => {
|
||||||
const { state, updateDelivery, updateOrderComment, clearCart } = useCart();
|
const { state, updateDelivery, updateOrderComment, clearCart } = useCart();
|
||||||
const { summary, delivery, items, orderComment } = state;
|
const { summary, delivery, items, orderComment } = state;
|
||||||
const legalEntityDropdownRef = useRef<HTMLDivElement>(null);
|
const legalEntityDropdownRef = useRef<HTMLDivElement>(null);
|
||||||
@ -16,7 +21,6 @@ const CartSummary: React.FC = () => {
|
|||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [isProcessing, setIsProcessing] = useState(false);
|
const [isProcessing, setIsProcessing] = useState(false);
|
||||||
const [showAuthWarning, setShowAuthWarning] = useState(false);
|
const [showAuthWarning, setShowAuthWarning] = useState(false);
|
||||||
const [step, setStep] = useState(1);
|
|
||||||
|
|
||||||
// Новые состояния для первого шага
|
// Новые состояния для первого шага
|
||||||
const [selectedLegalEntity, setSelectedLegalEntity] = useState<string>("");
|
const [selectedLegalEntity, setSelectedLegalEntity] = useState<string>("");
|
||||||
@ -135,25 +139,20 @@ const CartSummary: React.FC = () => {
|
|||||||
toast.error('Пожалуйста, введите имя получателя');
|
toast.error('Пожалуйста, введите имя получателя');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!recipientPhone.trim()) {
|
if (!recipientPhone.trim()) {
|
||||||
toast.error('Пожалуйста, введите телефон получателя');
|
toast.error('Пожалуйста, введите телефон получателя');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedDeliveryAddress.trim()) {
|
if (!selectedDeliveryAddress.trim()) {
|
||||||
toast.error('Пожалуйста, выберите адрес доставки');
|
toast.error('Пожалуйста, выберите адрес доставки');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем данные доставки без стоимости
|
|
||||||
updateDelivery({
|
updateDelivery({
|
||||||
address: selectedDeliveryAddress,
|
address: selectedDeliveryAddress,
|
||||||
cost: 0, // Стоимость включена в товары
|
cost: 0,
|
||||||
date: 'Включена в стоимость товаров',
|
date: 'Включена в стоимость товаров',
|
||||||
time: 'Способ доставки указан в адресе'
|
time: 'Способ доставки указан в адресе'
|
||||||
});
|
});
|
||||||
|
|
||||||
setStep(2);
|
setStep(2);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -894,7 +893,7 @@ const CartSummary: React.FC = () => {
|
|||||||
{error && <div style={{ color: 'red', marginTop: 10 }}>{error}</div>}
|
{error && <div style={{ color: 'red', marginTop: 10 }}>{error}</div>}
|
||||||
|
|
||||||
{/* Кнопка "Назад" */}
|
{/* Кнопка "Назад" */}
|
||||||
{/* <button
|
<button
|
||||||
onClick={handleBackToStep1}
|
onClick={handleBackToStep1}
|
||||||
style={{
|
style={{
|
||||||
background: 'none',
|
background: 'none',
|
||||||
@ -908,7 +907,7 @@ const CartSummary: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
← Назад к настройкам доставки
|
← Назад к настройкам доставки
|
||||||
</button> */}
|
</button>
|
||||||
|
|
||||||
<div className="w-layout-hflex privacy-consent" style={{ cursor: 'pointer' }} onClick={() => setConsent((v) => !v)}>
|
<div className="w-layout-hflex privacy-consent" style={{ cursor: 'pointer' }} onClick={() => setConsent((v) => !v)}>
|
||||||
<div
|
<div
|
||||||
|
@ -92,7 +92,7 @@ const ProfileHistoryItem: React.FC<ProfileHistoryItemProps> = ({
|
|||||||
const path = e.currentTarget.querySelector('path');
|
const path = e.currentTarget.querySelector('path');
|
||||||
if (path) path.setAttribute('fill', '#D0D0D0');
|
if (path) path.setAttribute('fill', '#D0D0D0');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg width="16" height="16" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
d="M4.625 17.5C4.14375 17.5 3.73192 17.3261 3.3895 16.9782C3.04708 16.6304 2.87558 16.2117 2.875 15.7222V4.16667H2V2.38889H6.375V1.5H11.625V2.38889H16V4.16667H15.125V15.7222C15.125 16.2111 14.9538 16.6298 14.6114 16.9782C14.269 17.3267 13.8568 17.5006 13.375 17.5H4.625ZM6.375 13.9444H8.125V5.94444H6.375V13.9444ZM9.875 13.9444H11.625V5.94444H9.875V13.9444Z"
|
d="M4.625 17.5C4.14375 17.5 3.73192 17.3261 3.3895 16.9782C3.04708 16.6304 2.87558 16.2117 2.875 15.7222V4.16667H2V2.38889H6.375V1.5H11.625V2.38889H16V4.16667H15.125V15.7222C15.125 16.2111 14.9538 16.6298 14.6114 16.9782C14.269 17.3267 13.8568 17.5006 13.375 17.5H4.625ZM6.375 13.9444H8.125V5.94444H6.375V13.9444ZM9.875 13.9444H11.625V5.94444H9.875V13.9444Z"
|
||||||
|
@ -198,12 +198,42 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
|||||||
</div>
|
</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 (
|
return (
|
||||||
<div className="w-layout-vflex vinleftbar">
|
<div className="w-layout-vflex vinleftbar">
|
||||||
|
{/* === Форма полнотекстового поиска === */}
|
||||||
<div className="div-block-2">
|
<div className="div-block-2">
|
||||||
<div className="form-block w-form">
|
<div className="form-block w-form">
|
||||||
<form id="vin-form-search" name="vin-form-search" data-name="vin-form-search" action="#" method="post" className="form">
|
<form id="vin-form-search" name="vin-form-search" data-name="vin-form-search" action="#" method="post" className="form" onSubmit={e => { e.preventDefault(); handleFulltextSearch(); }}>
|
||||||
<a href="#" className="link-block-3 w-inline-block" onClick={e => { e.preventDefault(); if (!ssd || ssd.trim() === '') { return; } handleSearch(); }}>
|
<a href="#" className="link-block-3 w-inline-block" onClick={e => { e.preventDefault(); if (!ssd || ssd.trim() === '') { return; } handleFulltextSearch(); }}>
|
||||||
<div className="code-embed-6 w-embed">
|
<div className="code-embed-6 w-embed">
|
||||||
{/* SVG */}
|
{/* SVG */}
|
||||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
@ -221,14 +251,95 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
|
|||||||
type="text"
|
type="text"
|
||||||
id="VinSearchInput"
|
id="VinSearchInput"
|
||||||
required
|
required
|
||||||
value={searchQuery}
|
value={fulltextQuery}
|
||||||
onChange={e => setSearchQuery(e.target.value)}
|
onChange={e => setFulltextQuery(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleFulltextKeyDown}
|
||||||
disabled={loading}
|
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>
|
</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>
|
</div>
|
||||||
<div className="w-layout-vflex flex-block-113">
|
<div className="w-layout-vflex flex-block-113">
|
||||||
|
@ -7,8 +7,10 @@ import CartSummary from "@/components/CartSummary";
|
|||||||
import CartRecommended from "../components/CartRecommended";
|
import CartRecommended from "../components/CartRecommended";
|
||||||
import CatalogSubscribe from "@/components/CatalogSubscribe";
|
import CatalogSubscribe from "@/components/CatalogSubscribe";
|
||||||
import MobileMenuBottomSection from "@/components/MobileMenuBottomSection";
|
import MobileMenuBottomSection from "@/components/MobileMenuBottomSection";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
export default function CartPage() {
|
export default function CartPage() {
|
||||||
|
const [step, setStep] = useState(1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<><Head>
|
<><Head>
|
||||||
@ -26,8 +28,8 @@ export default function CartPage() {
|
|||||||
<div className="w-layout-blockcontainer container w-container">
|
<div className="w-layout-blockcontainer container w-container">
|
||||||
<div className="w-layout-vflex cart-list">
|
<div className="w-layout-vflex cart-list">
|
||||||
<div className="w-layout-hflex core-product-card">
|
<div className="w-layout-hflex core-product-card">
|
||||||
<CartList />
|
<CartList isSummaryStep={step === 2} />
|
||||||
<CartSummary />
|
<CartSummary step={step} setStep={setStep} />
|
||||||
</div>
|
</div>
|
||||||
<CartRecommended />
|
<CartRecommended />
|
||||||
</div>
|
</div>
|
||||||
|
@ -432,6 +432,13 @@ input#VinSearchInput {
|
|||||||
line-height: 1.4em;
|
line-height: 1.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.heading-9-copy,
|
||||||
|
.text-block-21-copy {
|
||||||
|
width: 250px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
.w-layout-hflex.flex-block-6 {
|
.w-layout-hflex.flex-block-6 {
|
||||||
|
@ -2848,6 +2848,7 @@ body {
|
|||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-field-copy {
|
.text-field-copy {
|
||||||
@ -3114,6 +3115,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.block-name {
|
.block-name {
|
||||||
|
max-width: 300px;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -3912,6 +3914,7 @@ body {
|
|||||||
font-size: var(--_fonts---font-size--small-font-size);
|
font-size: var(--_fonts---font-size--small-font-size);
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
max-width: 100%;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
@ -4334,7 +4337,7 @@ body {
|
|||||||
color: var(--_fonts---color--black);
|
color: var(--_fonts---color--black);
|
||||||
font-size: var(--_fonts---font-size--bigger);
|
font-size: var(--_fonts---font-size--bigger);
|
||||||
text-align: right;
|
text-align: right;
|
||||||
max-width: 100px;
|
max-width: 200px;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@ -5524,7 +5527,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.flex-block-39-copy {
|
.flex-block-39-copy {
|
||||||
width: 200px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-ditail {
|
.cart-ditail {
|
||||||
@ -7336,7 +7339,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.flex-block-39-copy {
|
.flex-block-39-copy {
|
||||||
width: 200px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.heading-9-copy-copy {
|
.heading-9-copy-copy {
|
||||||
|
Reference in New Issue
Block a user