From 3b5defe3d963b3e337772071897302ae7d79bdab Mon Sep 17 00:00:00 2001 From: egortriston Date: Thu, 10 Jul 2025 17:21:51 +0300 Subject: [PATCH] fix1007 --- src/components/CartList.tsx | 14 +- src/components/CartSummary.tsx | 8 +- src/components/Header.tsx | 4 +- src/components/TopSalesItem.tsx | 77 +++++----- src/components/index/BestPriceSection.tsx | 38 ++++- .../index/BrandSelectionSection.tsx | 65 ++++++--- src/components/index/NewArrivalsSection.tsx | 61 ++++++-- src/components/index/NewsAndPromos.tsx | 121 ++++++++++------ src/components/index/ProductOfDaySection.tsx | 4 +- src/components/index/TopSalesSection.tsx | 132 ++++++++++++++---- src/components/vin/KnotIn.tsx | 2 +- src/pages/search-result.tsx | 9 +- src/styles/my.css | 91 ++++++++++++ 13 files changed, 455 insertions(+), 171 deletions(-) diff --git a/src/components/CartList.tsx b/src/components/CartList.tsx index a6a083f..bd35112 100644 --- a/src/components/CartList.tsx +++ b/src/components/CartList.tsx @@ -84,7 +84,7 @@ const CartList: React.FC = ({ isSummaryStep = false }) => { }, [state.error, clearError]); return ( -
+
{/* Отображение ошибок корзины */} {state.error && (
@@ -145,9 +145,15 @@ const CartList: React.FC = ({ isSummaryStep = false }) => {
)} {displayItems.length === 0 ? ( -
-

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

-

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

+
+
+ + + + + +
Ваша корзина пуста
+
) : ( displayItems.map((item, idx) => { diff --git a/src/components/CartSummary.tsx b/src/components/CartSummary.tsx index 0aefe19..fcdc38f 100644 --- a/src/components/CartSummary.tsx +++ b/src/components/CartSummary.tsx @@ -285,7 +285,7 @@ const CartSummary: React.FC = ({ step, setStep }) => { onClick={() => setShowLegalEntityDropdown(!showLegalEntityDropdown)} style={{ cursor: 'pointer', justifyContent: 'space-between', alignItems: 'center' }} > -
+
{isIndividual ? 'Физическое лицо' : selectedLegalEntity || 'Выберите юридическое лицо'}
@@ -325,7 +325,7 @@ const CartSummary: React.FC = ({ step, setStep }) => { borderBottom: '1px solid #f0f0f0', backgroundColor: isIndividual ? '#f8f9fa' : 'white', fontSize: '14px', - fontWeight: isIndividual ? 500 : 400 + }} onMouseEnter={(e) => { if (!isIndividual) { @@ -538,7 +538,9 @@ const CartSummary: React.FC = ({ step, setStep }) => { cursor: 'pointer', borderBottom: '1px solid #f0f0f0', backgroundColor: paymentMethod === 'yookassa' ? '#f8f9fa' : 'white', - fontSize: '14px' + fontSize: '14px', + fontWeight: paymentMethod === 'yookassa' ? 500 : 400, + color: '#222' }} onMouseEnter={(e) => { if (paymentMethod !== 'yookassa') { diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 88ce715..0cc7b7c 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -414,7 +414,7 @@ const Header: React.FC = ({ onOpenAuthModal = () => console.log('Au onClick={() => setMenuOpen((open) => !open)} style={{ cursor: "pointer" }} > -
+
@@ -759,7 +759,7 @@ const Header: React.FC = ({ onOpenAuthModal = () => console.log('Au
- История заказов + История заказов diff --git a/src/components/TopSalesItem.tsx b/src/components/TopSalesItem.tsx index cb28a31..b03ce0c 100644 --- a/src/components/TopSalesItem.tsx +++ b/src/components/TopSalesItem.tsx @@ -1,3 +1,4 @@ +import Link from "next/link"; import React from "react"; import { useCart } from "@/contexts/CartContext"; import { useFavorites } from "@/contexts/FavoritesContext"; @@ -11,6 +12,7 @@ interface TopSalesItemProps { article?: string; productId?: string; onAddToCart?: (e: React.MouseEvent) => void; + discount?: string; // Новый пропс для лейбла/скидки } const TopSalesItem: React.FC = ({ @@ -21,37 +23,31 @@ const TopSalesItem: React.FC = ({ article, productId, onAddToCart, + discount = 'Топ продаж', // По умолчанию как раньше }) => { const { addItem } = useCart(); const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites(); - // Проверяем, есть ли товар в избранном const isItemFavorite = isFavorite(productId, undefined, article, brand); - // Функция для парсинга цены из строки const parsePrice = (priceStr: string): number => { const cleanPrice = priceStr.replace(/[^\d.,]/g, '').replace(',', '.'); return parseFloat(cleanPrice) || 0; }; - // Обработчик клика по корзине const handleAddToCart = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - if (onAddToCart) { onAddToCart(e); return; } - try { if (!article || !brand) { toast.error('Недостаточно данных для добавления товара в корзину'); return; } - const numericPrice = parsePrice(price); - addItem({ name: title, brand: brand, @@ -63,7 +59,6 @@ const TopSalesItem: React.FC = ({ image: image, isExternal: true }); - toast.success('Товар добавлен в корзину'); } catch (error) { console.error('Ошибка добавления в корзину:', error); @@ -71,25 +66,20 @@ const TopSalesItem: React.FC = ({ } }; - // Обработчик клика по иконке избранного const handleFavoriteClick = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - if (isItemFavorite) { - // Находим товар в избранном и удаляем const favoriteItem = favorites.find((fav: any) => { if (productId && fav.productId === productId) return true; if (fav.article === article && fav.brand === brand) return true; return false; }); - if (favoriteItem) { removeFromFavorites(favoriteItem.id); toast.success('Товар удален из избранного'); } } else { - // Добавляем в избранное const numericPrice = parsePrice(price); addToFavorites({ productId, @@ -104,23 +94,24 @@ const TopSalesItem: React.FC = ({ } }; + // Ссылка на карточку товара (если нужно) + const cardUrl = article && brand + ? `/card?article=${encodeURIComponent(article)}&brand=${encodeURIComponent(brand)}` + : '/card'; + return ( -
+
- - + +
-
= ({ alt={title} className="image-5" /> -
Топ продаж
+
{discount}
-
{price}
+ {/*
oldPrice
*/}
-
{title}
-
{brand}
-
- - - ); }; diff --git a/src/components/index/BestPriceSection.tsx b/src/components/index/BestPriceSection.tsx index f86814d..200e7a6 100644 --- a/src/components/index/BestPriceSection.tsx +++ b/src/components/index/BestPriceSection.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useRef } from "react"; import { useQuery } from "@apollo/client"; import BestPriceItem from "../BestPriceItem"; import { GET_BEST_PRICE_PRODUCTS } from "../../lib/graphql"; @@ -19,8 +19,22 @@ interface BestPriceProductData { }; } +const SCROLL_AMOUNT = 340; // px, ширина одной карточки + отступ + const BestPriceSection: React.FC = () => { const { data, loading, error } = useQuery(GET_BEST_PRICE_PRODUCTS); + const scrollRef = useRef(null); + + const scrollLeft = () => { + if (scrollRef.current) { + scrollRef.current.scrollBy({ left: -SCROLL_AMOUNT, behavior: 'smooth' }); + } + }; + const scrollRight = () => { + if (scrollRef.current) { + scrollRef.current.scrollBy({ left: SCROLL_AMOUNT, behavior: 'smooth' }); + } + }; if (loading) { return ( @@ -97,10 +111,24 @@ const BestPriceSection: React.FC = () => {
Подборка лучших предложенийпо цене
Показать все
-
- {bestPriceItems.map((item, i) => ( - - ))} +
+ +
+ {bestPriceItems.map((item, i) => ( + + ))} +
+
diff --git a/src/components/index/BrandSelectionSection.tsx b/src/components/index/BrandSelectionSection.tsx index cc3011b..1ebf006 100644 --- a/src/components/index/BrandSelectionSection.tsx +++ b/src/components/index/BrandSelectionSection.tsx @@ -1,8 +1,9 @@ -import React, { useState } from "react"; +import React, { useState, useMemo, useRef } from "react"; import { useRouter } from "next/router"; import { useQuery } from "@apollo/client"; import { GET_LAXIMO_BRANDS } from "@/lib/graphql"; import { LaximoBrand } from "@/types/laximo"; +import { Combobox } from '@headlessui/react'; const tabs = [ "Техническое обслуживание", @@ -15,7 +16,8 @@ type Brand = { name: string; code?: string }; const BrandSelectionSection: React.FC = () => { const [activeTab, setActiveTab] = useState(0); - const [selectedBrand, setSelectedBrand] = useState(""); + const [selectedBrand, setSelectedBrand] = useState(null); + const [brandQuery, setBrandQuery] = useState(''); const router = useRouter(); const { data, loading, error } = useQuery<{ laximoBrands: LaximoBrand[] }>(GET_LAXIMO_BRANDS, { @@ -42,6 +44,12 @@ const BrandSelectionSection: React.FC = () => { console.warn('Laximo API недоступен, используются статические данные:', error.message); } + // Combobox фильтрация + const filteredBrands = useMemo(() => { + if (!brandQuery) return brands; + return brands.filter(b => b.name.toLowerCase().includes(brandQuery.toLowerCase())); + }, [brands, brandQuery]); + const handleBrandClick = (brand: Brand) => { if (brand.code) { router.push(`/brands?selected=${brand.code}`); @@ -53,7 +61,7 @@ const BrandSelectionSection: React.FC = () => { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (selectedBrand) { - const found = brands.find(b => b.code === selectedBrand || b.name === selectedBrand); + const found = brands.find(b => b.code === selectedBrand.code || b.name === selectedBrand.name); if (found && found.code) { router.push(`/brands?selected=${found.code}`); return; @@ -123,19 +131,44 @@ const BrandSelectionSection: React.FC = () => {

ПОДБОР АВТОЗАПЧАСТЕЙ ПО МАРКЕ АВТО

- +
+ +
+ brand?.name || ''} + onChange={e => setBrandQuery(e.target.value)} + placeholder="Марка" + autoComplete="off" + /> + + + + + + + {filteredBrands.length === 0 && ( +
Бренды не найдены
+ )} + {filteredBrands.map(brand => ( + + `px-6 py-4 cursor-pointer hover:!bg-[rgb(236,28,36)] hover:!text-white text-sm transition-colors ${selected ? 'bg-red-50 font-semibold text-gray-950' : 'text-neutral-500'}` + } + > + {brand.name} + + ))} +
+
+
+
diff --git a/src/components/index/NewArrivalsSection.tsx b/src/components/index/NewArrivalsSection.tsx index ec74c45..e1ef8d8 100644 --- a/src/components/index/NewArrivalsSection.tsx +++ b/src/components/index/NewArrivalsSection.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useRef } from "react"; import ArticleCard from "../ArticleCard"; import { PartsAPIArticle } from "@/types/partsapi"; @@ -36,21 +36,52 @@ const newArrivalsArticles: PartsAPIArticle[] = [ const imagePath = "images/162615.webp"; -const NewArrivalsSection: React.FC = () => ( -
-
-
-
-

Новое поступление

-
-
- {newArrivalsArticles.map((article, i) => ( - - ))} +const SCROLL_AMOUNT = 340; // px, ширина одной карточки + отступ + +const NewArrivalsSection: React.FC = () => { + const scrollRef = useRef(null); + + const scrollLeft = () => { + if (scrollRef.current) { + scrollRef.current.scrollBy({ left: -SCROLL_AMOUNT, behavior: 'smooth' }); + } + }; + const scrollRight = () => { + if (scrollRef.current) { + scrollRef.current.scrollBy({ left: SCROLL_AMOUNT, behavior: 'smooth' }); + } + }; + + return ( +
+
+
+
+

Новое поступление

+
+
+ +
+ {newArrivalsArticles.map((article, i) => ( + + ))} +
+ +
-
-
-); + + ); +}; export default NewArrivalsSection; \ No newline at end of file diff --git a/src/components/index/NewsAndPromos.tsx b/src/components/index/NewsAndPromos.tsx index b9a9604..83a1630 100644 --- a/src/components/index/NewsAndPromos.tsx +++ b/src/components/index/NewsAndPromos.tsx @@ -1,53 +1,84 @@ -import React from "react"; +import React, { useRef } from "react"; import NewsCard from "@/components/news/NewsCard"; import Link from "next/link"; -const NewsAndPromos = () => ( -
-
-
-
-

Новости и акции

-
- - Ко всем новостям - - +const SCROLL_AMOUNT = 340; // px, ширина одной карточки + отступ + +const NewsAndPromos = () => { + const scrollRef = useRef(null); + + const scrollLeft = () => { + if (scrollRef.current) { + scrollRef.current.scrollBy({ left: -SCROLL_AMOUNT, behavior: 'smooth' }); + } + }; + const scrollRight = () => { + if (scrollRef.current) { + scrollRef.current.scrollBy({ left: SCROLL_AMOUNT, behavior: 'smooth' }); + } + }; + + return ( +
+
+
+
+

Новости и акции

+
+ + Ко всем новостям + + +
+
+
+ +
+ + + + +
+
-
- - - - -
-
-
-); + + ); +}; export default NewsAndPromos; \ No newline at end of file diff --git a/src/components/index/ProductOfDaySection.tsx b/src/components/index/ProductOfDaySection.tsx index 7bc16f8..611a44f 100644 --- a/src/components/index/ProductOfDaySection.tsx +++ b/src/components/index/ProductOfDaySection.tsx @@ -236,11 +236,11 @@ const ProductOfDaySection: React.FC = () => { {product.name}
{/* Счетчик товаров если их больше одного */} - {activeProducts.length > 1 && ( + {/* {activeProducts.length > 1 && (
{currentSlide + 1} из {activeProducts.length}
- )} + )} */}
{productImage && ( diff --git a/src/components/index/TopSalesSection.tsx b/src/components/index/TopSalesSection.tsx index 471d6ca..2364001 100644 --- a/src/components/index/TopSalesSection.tsx +++ b/src/components/index/TopSalesSection.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useRef } from "react"; import { useQuery } from "@apollo/client"; import TopSalesItem from "../TopSalesItem"; import { GET_TOP_SALES_PRODUCTS } from "../../lib/graphql"; @@ -18,8 +18,22 @@ interface TopSalesProductData { }; } +const SCROLL_AMOUNT = 340; // px, ширина одной карточки + отступ + const TopSalesSection: React.FC = () => { const { data, loading, error } = useQuery(GET_TOP_SALES_PRODUCTS); + const scrollRef = useRef(null); + + const scrollLeft = () => { + if (scrollRef.current) { + scrollRef.current.scrollBy({ left: -SCROLL_AMOUNT, behavior: 'smooth' }); + } + }; + const scrollRight = () => { + if (scrollRef.current) { + scrollRef.current.scrollBy({ left: SCROLL_AMOUNT, behavior: 'smooth' }); + } + }; if (loading) { return ( @@ -29,8 +43,22 @@ const TopSalesSection: React.FC = () => {

Топ продаж

-
-
Загрузка...
+
+ +
+
Загрузка...
+
+
@@ -47,8 +75,22 @@ const TopSalesSection: React.FC = () => {

Топ продаж

-
-
Ошибка загрузки
+
+ +
+
Ошибка загрузки
+
+
@@ -70,8 +112,22 @@ const TopSalesSection: React.FC = () => {

Топ продаж

-
-
Нет товаров в топ продаж
+
+ +
+
Нет товаров в топ продаж
+
+
@@ -86,32 +142,46 @@ const TopSalesSection: React.FC = () => {

Топ продаж

-
- {activeTopSalesProducts.map((item: TopSalesProductData) => { - const product = item.product; - const price = product.retailPrice - ? `от ${product.retailPrice.toLocaleString('ru-RU')} ₽` - : 'По запросу'; - - const image = product.images && product.images.length > 0 - ? product.images[0].url - : '/images/162615.webp'; // Fallback изображение +
+ +
+ {activeTopSalesProducts.map((item: TopSalesProductData) => { + const product = item.product; + const price = product.retailPrice + ? `от ${product.retailPrice.toLocaleString('ru-RU')} ₽` + : 'По запросу'; + + const image = product.images && product.images.length > 0 + ? product.images[0].url + : '/images/162615.webp'; // Fallback изображение - const title = product.name; - const brand = product.brand || 'Неизвестный бренд'; + const title = product.name; + const brand = product.brand || 'Неизвестный бренд'; - return ( - - ); - })} + return ( + + ); + })} +
+
diff --git a/src/components/vin/KnotIn.tsx b/src/components/vin/KnotIn.tsx index 024c819..58533a1 100644 --- a/src/components/vin/KnotIn.tsx +++ b/src/components/vin/KnotIn.tsx @@ -222,7 +222,7 @@ const KnotIn: React.FC = ({ catalogCode, vehicleId, ssd, unitId, un return ( <> -
+
{/* ВРЕМЕННО: выводим количество точек для быстрой проверки */} {/*
{coordinates.length} точек diff --git a/src/pages/search-result.tsx b/src/pages/search-result.tsx index 9d14e01..60d591a 100644 --- a/src/pages/search-result.tsx +++ b/src/pages/search-result.tsx @@ -459,7 +459,7 @@ export default function SearchResult() { offersCount={result ? result.totalOffers : 0} minPrice={minPrice} /> -
+
{/* */} @@ -551,7 +551,7 @@ export default function SearchResult() {
{/* Фильтры для десктопа */} -
+
@@ -588,7 +587,7 @@ export default function SearchResult() { brand={result.brand} article={result.articleNumber} name={result.name} - image={displayImage} + {...(partsIndexImage ? { image: partsIndexImage } : {})} offers={mainProductOffers} showMoreText={mainProductOffers.length < filteredOffers.filter(o => !o.isAnalog).length ? "Показать еще" : undefined} partsIndexPowered={!!partsIndexImage} diff --git a/src/styles/my.css b/src/styles/my.css index aa94fcf..dbcc74d 100644 --- a/src/styles/my.css +++ b/src/styles/my.css @@ -45,6 +45,14 @@ display: none !important; } +.price-in-cart-s1 { + + max-width: 140px; + +} + + + input.text-block-31 { background: none !important; } @@ -440,6 +448,12 @@ input#VinSearchInput { } +.w-input { + border-radius: 8px !important; +} + + + .text-block-56, .dropdown-link-3 { white-space: nowrap; overflow: hidden; @@ -877,3 +891,80 @@ body { } } + +.carousel-row { + display: flex; + align-items: center; + position: relative; + gap: 12px; + width: 100%; +} + +.carousel-arrow { + background: none; + border: none; + padding: 0; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + transition: opacity 0.2s; + opacity: 0.85; + z-index: 2; +} +.carousel-arrow:active { + opacity: 0.6; +} +.carousel-arrow[disabled] { + opacity: 0.3; + pointer-events: none; +} + +.carousel-arrow-left { + margin-right: 4px; +} +.carousel-arrow-right { + margin-left: 4px; +} + +.carousel-scroll { + overflow-x: auto; + overflow-y: visible; + scroll-behavior: smooth; + display: flex; + gap: 24px; + flex: 1 1 auto; + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE и Edge */ +} +.carousel-scroll::-webkit-scrollbar { + display: none; /* Chrome, Safari, Opera */ +} + +@media (max-width: 991px) { + .carousel-scroll { + gap: 12px; + } + .carousel-row { + gap: 4px; + } +} + +@media (max-width: 767px) { + .carousel-arrow { + display: none !important; + } +} + + +.mobile-only { + display: block; +} +@media (min-width: 768px) { + .mobile-only { + display: none !important; + } +} +