import Head from "next/head"; import { useRouter } from "next/router"; import { useEffect, useState, useMemo } from "react"; import { useQuery, useLazyQuery } from "@apollo/client"; import Header from "@/components/Header"; import Footer from "@/components/Footer"; import CatalogSubscribe from "@/components/CatalogSubscribe"; import Filters, { FilterConfig } from "@/components/Filters"; import BestPriceCard from "@/components/BestPriceCard"; import CoreProductCard from "@/components/CoreProductCard"; import AnalogueBlock from "@/components/AnalogueBlock"; import InfoSearch from "@/components/InfoSearch"; import FiltersPanelMobile from "@/components/FiltersPanelMobile"; import CatalogSortDropdown from "@/components/CatalogSortDropdown"; import MobileMenuBottomSection from '../components/MobileMenuBottomSection'; import { SEARCH_PRODUCT_OFFERS, GET_ANALOG_OFFERS } from "@/lib/graphql"; import { useArticleImage } from "@/hooks/useArticleImage"; import { usePartsIndexEntityInfo } from "@/hooks/usePartsIndex"; import { useCart } from "@/contexts/CartContext"; import MetaTags from "@/components/MetaTags"; import { createProductMeta } from "@/lib/meta-config"; const ANALOGS_CHUNK_SIZE = 5; // Функция для расчета даты доставки const calculateDeliveryDate = (deliveryDays: number): string => { const today = new Date(); const deliveryDate = new Date(today); deliveryDate.setDate(today.getDate() + deliveryDays); const months = [ 'января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря' ]; const day = deliveryDate.getDate(); const month = months[deliveryDate.getMonth()]; const year = deliveryDate.getFullYear(); return `${day} ${month} ${year}`; }; // Функция для создания динамических фильтров const createFilters = (result: any, loadedAnalogs: any): FilterConfig[] => { const filters: FilterConfig[] = []; if (result) { // Фильтр по производителю const brands = new Set(); brands.add(result.brand); result.externalOffers?.forEach((offer: any) => { if (offer.brand) brands.add(offer.brand); }); result.analogs?.forEach((analog: any) => { if (analog.brand) brands.add(analog.brand); }); if (brands.size > 1) { filters.push({ type: "dropdown", title: "Производитель", options: Array.from(brands).sort(), multi: true, showAll: true, }); } // Получаем все доступные предложения для расчета диапазонов const allAvailableOffers: any[] = []; // Добавляем основные предложения result.internalOffers?.forEach((offer: any) => { allAvailableOffers.push(offer); }); result.externalOffers?.forEach((offer: any) => { allAvailableOffers.push(offer); }); // Добавляем предложения аналогов Object.values(loadedAnalogs).forEach((analog: any) => { analog.internalOffers?.forEach((offer: any) => { allAvailableOffers.push({ ...offer, deliveryDuration: offer.deliveryDays }); }); analog.externalOffers?.forEach((offer: any) => { allAvailableOffers.push({ ...offer, deliveryDuration: offer.deliveryTime }); }); }); // Фильтр по цене - только если есть предложения с разными ценами const prices: number[] = []; allAvailableOffers.forEach((offer: any) => { if (offer.price > 0) prices.push(offer.price); }); if (prices.length > 1) { const minPrice = Math.min(...prices); const maxPrice = Math.max(...prices); if (maxPrice > minPrice) { filters.push({ type: "range", title: "Цена (₽)", min: Math.floor(minPrice), max: Math.ceil(maxPrice), }); } } // Фильтр по сроку доставки - только если есть предложения с разными сроками const deliveryDays: number[] = []; allAvailableOffers.forEach((offer: any) => { const days = offer.deliveryDays || offer.deliveryTime || offer.deliveryDuration; if (days && days > 0) deliveryDays.push(days); }); if (deliveryDays.length > 1) { const minDays = Math.min(...deliveryDays); const maxDays = Math.max(...deliveryDays); if (maxDays > minDays) { filters.push({ type: "range", title: "Срок доставки (дни)", min: minDays, max: maxDays, }); } } // Фильтр по количеству наличия - только если есть предложения с разными количествами const quantities: number[] = []; allAvailableOffers.forEach((offer: any) => { if (offer.quantity && offer.quantity > 0) quantities.push(offer.quantity); }); if (quantities.length > 1) { const minQuantity = Math.min(...quantities); const maxQuantity = Math.max(...quantities); if (maxQuantity > minQuantity) { filters.push({ type: "range", title: "Количество (шт.)", min: minQuantity, max: maxQuantity, }); } } } return filters; }; // Функция для получения лучших предложений по заданным критериям const getBestOffers = (offers: any[]) => { const validOffers = offers.filter(offer => offer.price > 0 && typeof offer.deliveryDuration !== 'undefined'); if (validOffers.length === 0) return []; const result: { offer: any; type: string }[] = []; // 1. Самая низкая цена (среди всех предложений) const lowestPriceOffer = [...validOffers].sort((a, b) => a.price - b.price)[0]; if (lowestPriceOffer) { result.push({ offer: lowestPriceOffer, type: 'Самая низкая цена' }); } // 2. Самый дешевый аналог (только среди аналогов) - всегда показываем если есть аналоги const analogOffers = validOffers.filter(offer => offer.isAnalog); if (analogOffers.length > 0) { const cheapestAnalogOffer = [...analogOffers].sort((a, b) => a.price - b.price)[0]; result.push({ offer: cheapestAnalogOffer, type: 'Самый дешевый аналог' }); } // 3. Самая быстрая доставка (среди всех предложений) const fastestDeliveryOffer = [...validOffers].sort((a, b) => a.deliveryDuration - b.deliveryDuration)[0]; if (fastestDeliveryOffer) { result.push({ offer: fastestDeliveryOffer, type: 'Самая быстрая доставка' }); } return result; }; // Убрано: функция сортировки теперь в CoreProductCard // Функция для проверки наличия товара на складе const checkProductStock = (result: any): boolean => { if (!result) return false; // Используем новые данные stockCalculation если доступны if (result.stockCalculation) { return result.stockCalculation.hasAnyStock; } // Fallback к старой логике для обратной совместимости const hasInternalStock = result.internalOffers?.some((offer: any) => offer.quantity > 0 && offer.available ); const hasExternalStock = result.externalOffers?.some((offer: any) => offer.quantity > 0 ); return hasInternalStock || hasExternalStock; }; const transformOffersForCard = (offers: any[], hasStock: boolean = true) => { return offers.map(offer => { const isExternal = offer.type === 'external'; const deliveryDays = isExternal ? offer.deliveryTime : offer.deliveryDays; return { id: offer.id, productId: offer.productId, offerKey: offer.offerKey, pcs: `${offer.quantity} шт.`, days: deliveryDays ? calculateDeliveryDate(deliveryDays) : 'Уточняйте', recommended: !isExternal && offer.available, price: `${offer.price.toLocaleString('ru-RU')} ₽`, count: "1", isExternal, currency: offer.currency || "RUB", warehouse: offer.warehouse, supplier: offer.supplier, deliveryTime: deliveryDays, hasStock, // Добавляем информацию о наличии }; }); }; export default function SearchResult() { const router = useRouter(); const { article, brand, q, artId } = router.query; const { state: cartState } = useCart(); // Убрано: глобальная сортировка теперь не используется const [showFiltersMobile, setShowFiltersMobile] = useState(false); const [showSortMobile, setShowSortMobile] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const [brandQuery, setBrandQuery] = useState(""); const [loadedAnalogs, setLoadedAnalogs] = useState<{ [key: string]: any }>({}); const [visibleAnalogsCount, setVisibleAnalogsCount] = useState(ANALOGS_CHUNK_SIZE); // Состояния для фильтров const [selectedBrands, setSelectedBrands] = useState([]); const [priceRange, setPriceRange] = useState<[number, number] | null>(null); const [deliveryRange, setDeliveryRange] = useState<[number, number] | null>(null); const [quantityRange, setQuantityRange] = useState<[number, number] | null>(null); const [filterSearchTerm, setFilterSearchTerm] = useState(''); useEffect(() => { if (article && typeof article === 'string') { setSearchQuery(article.trim().toUpperCase()); } if (brand && typeof brand === 'string') { setBrandQuery(brand.trim()); } setLoadedAnalogs({}); setVisibleAnalogsCount(ANALOGS_CHUNK_SIZE); }, [article, brand]); // Подготавливаем данные корзины для отправки на backend const cartItems = cartState.items.map(item => ({ productId: item.productId, offerKey: item.offerKey, article: item.article || '', brand: item.brand || '', quantity: item.quantity })); const { data, loading, error } = useQuery(SEARCH_PRODUCT_OFFERS, { variables: { articleNumber: searchQuery, brand: brandQuery || '', // Используем пустую строку если бренд не указан cartItems: cartItems }, skip: !searchQuery, errorPolicy: 'all' }); const { imageUrl: mainImageUrl } = useArticleImage(artId as string, { enabled: !!artId }); // Получаем информацию о детали из Parts Index const { entityInfo, loading: partsIndexLoading } = usePartsIndexEntityInfo( searchQuery || null, brandQuery || null ); const [ getAnalogOffers, { loading: analogsLoading, data: analogsData } ] = useLazyQuery(GET_ANALOG_OFFERS, { onCompleted: (data) => { if (data && data.getAnalogOffers) { const newAnalogs = data.getAnalogOffers.reduce((acc: any, analog: any) => { const key = `${analog.brand}-${analog.articleNumber}`; // Сразу трансформируем, но пока не используем // const offers = transformOffersForCard(analog.internalOffers, analog.externalOffers); acc[key] = { ...analog }; // offers убрали, т.к. allOffers - единый источник return acc; }, {}); setLoadedAnalogs(prev => ({ ...prev, ...newAnalogs })); } } }); // Эффект для автоматической загрузки предложений видимых аналогов useEffect(() => { if (data?.searchProductOffers?.analogs) { const analogsToLoad = data.searchProductOffers.analogs .slice(0, visibleAnalogsCount) .filter((a: any) => !loadedAnalogs[`${a.brand}-${a.articleNumber}`]) .map((a: any) => ({ brand: a.brand, articleNumber: a.articleNumber })); if (analogsToLoad.length > 0) { getAnalogOffers({ variables: { analogs: analogsToLoad } }); } } }, [visibleAnalogsCount, data, getAnalogOffers, loadedAnalogs]); const result = data?.searchProductOffers; const allOffers = useMemo(() => { if (!result) return []; const offers: any[] = []; // Основной товар result.internalOffers.forEach((o: any) => offers.push({ ...o, deliveryDuration: o.deliveryDays, type: 'internal', brand: result.brand, articleNumber: result.articleNumber, name: result.name })); result.externalOffers.forEach((o: any) => offers.push({ ...o, deliveryDuration: o.deliveryTime, type: 'external', articleNumber: o.code, name: o.name })); // Аналоги Object.values(loadedAnalogs).forEach((analog: any) => { analog.internalOffers.forEach((o: any) => offers.push({ ...o, deliveryDuration: o.deliveryDays, type: 'internal', brand: analog.brand, articleNumber: analog.articleNumber, name: analog.name, isAnalog: true })); analog.externalOffers.forEach((o: any) => offers.push({ ...o, deliveryDuration: o.deliveryTime, type: 'external', brand: o.brand || analog.brand, articleNumber: o.code || analog.articleNumber, name: o.name, isAnalog: true })); }); return offers; }, [result, loadedAnalogs]); const filteredOffers = useMemo(() => { return allOffers.filter(offer => { // Фильтр по бренду if (selectedBrands.length > 0 && !selectedBrands.includes(offer.brand)) { return false; } // Фильтр по цене if (priceRange && (offer.price < priceRange[0] || offer.price > priceRange[1])) { return false; } // Фильтр по сроку доставки if (deliveryRange) { const deliveryDays = offer.deliveryDuration; if (deliveryDays < deliveryRange[0] || deliveryDays > deliveryRange[1]) { return false; } } // Фильтр по количеству наличия if (quantityRange) { const quantity = offer.quantity; if (quantity < quantityRange[0] || quantity > quantityRange[1]) { return false; } } // Фильтр по поисковой строке if (filterSearchTerm) { const searchTerm = filterSearchTerm.toLowerCase(); const brandMatch = offer.brand.toLowerCase().includes(searchTerm); const articleMatch = offer.articleNumber.toLowerCase().includes(searchTerm); const nameMatch = offer.name.toLowerCase().includes(searchTerm); if (!brandMatch && !articleMatch && !nameMatch) { return false; } } return true; }); }, [allOffers, selectedBrands, priceRange, deliveryRange, quantityRange, filterSearchTerm]); const handleFilterChange = (type: string, value: any) => { if (type === 'Производитель') { setSelectedBrands(value); } else if (type === 'Цена (₽)') { setPriceRange(value); } else if (type === 'Срок доставки (дни)') { setDeliveryRange(value); } else if (type === 'Количество (шт.)') { setQuantityRange(value); } else if (type === 'search') { setFilterSearchTerm(value); } }; const initialOffersExist = allOffers.length > 0; const filtersAreActive = selectedBrands.length > 0 || priceRange !== null || deliveryRange !== null || quantityRange !== null || filterSearchTerm !== ''; const hasOffers = result && (result.internalOffers.length > 0 || result.externalOffers.length > 0); const hasAnalogs = result && result.analogs.length > 0; // Создаем динамические фильтры на основе доступных данных с учетом активных фильтров const searchResultFilters = useMemo(() => { const baseFilters = createFilters(result, loadedAnalogs); // Если нет активных фильтров, возвращаем базовые фильтры if (!filtersAreActive) { return baseFilters; } // Создаем динамические фильтры с учетом других активных фильтров return baseFilters.map(filter => { if (filter.type !== 'range') { return filter; } // Для каждого диапазонного фильтра пересчитываем границы на основе // предложений, отфильтрованных другими фильтрами (исключая текущий) let relevantOffers = allOffers; // Применяем все фильтры кроме текущего relevantOffers = allOffers.filter(offer => { // Фильтр по бренду (если это не фильтр производителя) if (filter.title !== 'Производитель' && selectedBrands.length > 0 && !selectedBrands.includes(offer.brand)) { return false; } // Фильтр по цене (если это не фильтр цены) if (filter.title !== 'Цена (₽)' && priceRange && (offer.price < priceRange[0] || offer.price > priceRange[1])) { return false; } // Фильтр по сроку доставки (если это не фильтр доставки) if (filter.title !== 'Срок доставки (дни)' && deliveryRange) { const deliveryDays = offer.deliveryDuration; if (deliveryDays < deliveryRange[0] || deliveryDays > deliveryRange[1]) { return false; } } // Фильтр по количеству (если это не фильтр количества) if (filter.title !== 'Количество (шт.)' && quantityRange) { const quantity = offer.quantity; if (quantity < quantityRange[0] || quantity > quantityRange[1]) { return false; } } // Фильтр по поисковой строке if (filterSearchTerm) { const searchTerm = filterSearchTerm.toLowerCase(); const brandMatch = offer.brand.toLowerCase().includes(searchTerm); const articleMatch = offer.articleNumber.toLowerCase().includes(searchTerm); const nameMatch = offer.name.toLowerCase().includes(searchTerm); if (!brandMatch && !articleMatch && !nameMatch) { return false; } } return true; }); // Пересчитываем диапазон на основе отфильтрованных предложений if (filter.title === 'Цена (₽)') { const prices = relevantOffers.filter(o => o.price > 0).map(o => o.price); if (prices.length > 0) { return { ...filter, min: Math.floor(Math.min(...prices)), max: Math.ceil(Math.max(...prices)) }; } } else if (filter.title === 'Срок доставки (дни)') { const deliveryDays = relevantOffers .map(o => o.deliveryDuration) .filter(d => d && d > 0); if (deliveryDays.length > 0) { return { ...filter, min: Math.min(...deliveryDays), max: Math.max(...deliveryDays) }; } } else if (filter.title === 'Количество (шт.)') { const quantities = relevantOffers .map(o => o.quantity) .filter(q => q && q > 0); if (quantities.length > 0) { return { ...filter, min: Math.min(...quantities), max: Math.max(...quantities) }; } } return filter; }); }, [result, loadedAnalogs, filtersAreActive, allOffers, selectedBrands, priceRange, deliveryRange, quantityRange, filterSearchTerm]); const bestOffersData = getBestOffers(filteredOffers); // Если это поиск по параметру q (из UnitDetailsSection), используем q как article useEffect(() => { if (q && typeof q === 'string' && !article) { setSearchQuery(q.trim().toUpperCase()); // Определяем бренд из каталога или используем дефолтный const catalogFromUrl = router.query.catalog as string; const vehicleFromUrl = router.query.vehicle as string; if (catalogFromUrl) { // Маппинг каталогов к брендам const catalogToBrandMap: { [key: string]: string } = { 'AU1587': 'AUDI', 'VW1587': 'VOLKSWAGEN', 'BMW1587': 'BMW', 'MB1587': 'MERCEDES-BENZ', // Добавьте другие маппинги по необходимости }; setBrandQuery(catalogToBrandMap[catalogFromUrl] || ''); } else { setBrandQuery(''); } } }, [q, article, router.query]); // Удаляем старую заглушку - теперь обрабатываем все типы поиска const minPrice = useMemo(() => { if (result && result.minPrice) return result.minPrice; if (allOffers.length > 0) { const prices = allOffers.filter(o => o.price > 0).map(o => o.price); if (prices.length > 0) { return `${Math.min(...prices).toLocaleString('ru-RU')} ₽`; } } return "-"; }, [result, allOffers]); if (loading) { return ( <> Поиск предложений {searchQuery} - Protek

Поиск предложений...