From 215853e8c7a4dc8e06e8c91403109b896a9b4095 Mon Sep 17 00:00:00 2001 From: egortriston Date: Mon, 30 Jun 2025 00:38:29 +0300 Subject: [PATCH] all cart --- src/components/CartItem.tsx | 161 +++++++++++------- src/components/CartList.tsx | 102 ++++++----- src/components/CartList2.tsx | 13 +- src/components/CartSummary.tsx | 19 +-- src/components/profile/ProfileHistoryItem.tsx | 2 +- src/components/vin/VinLeftbar.tsx | 127 +++++++++++++- src/pages/cart.tsx | 6 +- src/styles/my.css | 7 + src/styles/protekproject.webflow.css | 9 +- 9 files changed, 310 insertions(+), 136 deletions(-) diff --git a/src/components/CartItem.tsx b/src/components/CartItem.tsx index dacaebb..d5751e0 100644 --- a/src/components/CartItem.tsx +++ b/src/components/CartItem.tsx @@ -16,6 +16,8 @@ interface CartItemProps { onComment: (comment: string) => void; onCountChange?: (count: number) => void; onRemove?: () => void; + isSummaryStep?: boolean; + itemNumber?: number; } const CartItem: React.FC = ({ @@ -34,9 +36,14 @@ const CartItem: React.FC = ({ onComment, onCountChange, onRemove, + isSummaryStep = false, + itemNumber, }) => (
+ {isSummaryStep ? ( +
{itemNumber}
+ ) : (
= ({ )}
+ )}

{name}

-
{description}
+
+ {description} +
e.preventDefault()}> @@ -64,6 +84,7 @@ const CartItem: React.FC = ({ id="Search-5" value={comment} onChange={e => onComment(e.target.value)} + disabled={isSummaryStep} />
@@ -80,87 +101,95 @@ const CartItem: React.FC = ({
{deliveryDate}
-
onCountChange && onCountChange(count - 1)} - style={{ cursor: 'pointer' }} - aria-label="Уменьшить количество" - tabIndex={0} - onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onCountChange && onCountChange(count - 1)} - role="button" - > -
- - - -
+ {isSummaryStep ? ( +
{count} шт.
+ ) : ( + <> +
onCountChange && onCountChange(count - 1)} + style={{ cursor: 'pointer' }} + aria-label="Уменьшить количество" + tabIndex={0} + onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onCountChange && onCountChange(count - 1)} + role="button" + > +
+ + + +
- { - 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 }} - /> + { + 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 }} + /> +
+
onCountChange && onCountChange(count + 1)} + style={{ cursor: 'pointer' }} + aria-label="Увеличить количество" + tabIndex={0} + onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onCountChange && onCountChange(count + 1)} + role="button" + > +
+ + +
-
onCountChange && onCountChange(count + 1)} - style={{ cursor: 'pointer' }} - aria-label="Увеличить количество" - tabIndex={0} - onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onCountChange && onCountChange(count + 1)} - role="button" - > -
- - - -
+ + )}

{price}

{pricePerItem}
+ {!isSummaryStep && (
-
(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'); - }} - > - - - -
+
(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'); + }} + > + + + +
+ )}
); diff --git a/src/components/CartList.tsx b/src/components/CartList.tsx index 4627a36..7ab981f 100644 --- a/src/components/CartList.tsx +++ b/src/components/CartList.tsx @@ -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 = ({ 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 (
-
-
-
- {allSelected && ( - - - - )} + {!isSummaryStep && ( +
+
+
+ {allSelected && ( + + + + )} +
+
Выделить всё
+
+
{ + 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'); + }} + > +
Удалить выбранные
+ + +
-
Выделить всё
-
{ - 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'); - }} - > -
Удалить выбранные
- - - -
-
- {items.length === 0 ? ( + )} + {displayItems.length === 0 ? (

Ваша корзина пуста

Добавьте товары из каталога

) : ( - items.map((item) => { + displayItems.map((item, idx) => { const isInFavorites = isFavorite(item.productId, item.offerKey, item.article, item.brand); - return ( -
+
{ onComment={(comment) => handleComment(item.id, comment)} onCountChange={(count) => handleCountChange(item.id, count)} onRemove={() => handleRemove(item.id)} + isSummaryStep={isSummaryStep} + itemNumber={idx + 1} />
); diff --git a/src/components/CartList2.tsx b/src/components/CartList2.tsx index da8889a..dc58ee9 100644 --- a/src/components/CartList2.tsx +++ b/src/components/CartList2.tsx @@ -26,8 +26,17 @@ const CartList2: React.FC = () => {

Вернитесь на предыдущий шаг и выберите товары

) : ( - selectedItems.map((item) => ( -
+ selectedItems.map((item, index) => ( +
{item.quantity}
diff --git a/src/components/CartSummary.tsx b/src/components/CartSummary.tsx index fc74eca..a4c98eb 100644 --- a/src/components/CartSummary.tsx +++ b/src/components/CartSummary.tsx @@ -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 = ({ step, setStep }) => { const { state, updateDelivery, updateOrderComment, clearCart } = useCart(); const { summary, delivery, items, orderComment } = state; const legalEntityDropdownRef = useRef(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(""); @@ -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 &&
{error}
} {/* Кнопка "Назад" */} - {/* */} +
setConsent((v) => !v)}>
= ({ const path = e.currentTarget.querySelector('path'); if (path) path.setAttribute('fill', '#D0D0D0'); }} - > + > = ({ vehicleInfo, onSearchResults, o
); + // === Полнотекстовый поиск деталей (аналогично 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) => { + if (e.key === 'Enter') { + e.preventDefault(); + handleFulltextSearch(); + } + }; + + const fulltextResults = fulltextData?.laximoFulltextSearch?.details || []; + return (
+ {/* === Форма полнотекстового поиска === */}
-
diff --git a/src/pages/cart.tsx b/src/pages/cart.tsx index ab54d43..ab0dc0c 100644 --- a/src/pages/cart.tsx +++ b/src/pages/cart.tsx @@ -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 ( <> @@ -26,8 +28,8 @@ export default function CartPage() {
- - + +
diff --git a/src/styles/my.css b/src/styles/my.css index 3948d63..7492bcc 100644 --- a/src/styles/my.css +++ b/src/styles/my.css @@ -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 { diff --git a/src/styles/protekproject.webflow.css b/src/styles/protekproject.webflow.css index db68418..ade85d5 100644 --- a/src/styles/protekproject.webflow.css +++ b/src/styles/protekproject.webflow.css @@ -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 {