first commit
This commit is contained in:
53
src/hooks/useArticleImage.ts
Normal file
53
src/hooks/useArticleImage.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { GET_PARTSAPI_MAIN_IMAGE } from '@/lib/graphql';
|
||||
import { PartsAPIMainImageData, PartsAPIMainImageVariables } from '@/types/partsapi';
|
||||
|
||||
interface UseArticleImageOptions {
|
||||
enabled?: boolean;
|
||||
fallbackImage?: string;
|
||||
}
|
||||
|
||||
interface UseArticleImageReturn {
|
||||
imageUrl: string;
|
||||
isLoading: boolean;
|
||||
error: boolean;
|
||||
}
|
||||
|
||||
export const useArticleImage = (
|
||||
artId: string | undefined | null,
|
||||
options: UseArticleImageOptions = {}
|
||||
): UseArticleImageReturn => {
|
||||
const { enabled = true, fallbackImage = '' } = options;
|
||||
const [imageUrl, setImageUrl] = useState<string>(fallbackImage);
|
||||
|
||||
// Проверяем что artId валидный
|
||||
const shouldFetch = enabled && artId && artId.trim() !== '';
|
||||
|
||||
const { data, loading, error } = useQuery<PartsAPIMainImageData, PartsAPIMainImageVariables>(
|
||||
GET_PARTSAPI_MAIN_IMAGE,
|
||||
{
|
||||
variables: { artId: artId || '' },
|
||||
skip: !shouldFetch,
|
||||
fetchPolicy: 'cache-first',
|
||||
errorPolicy: 'all',
|
||||
onCompleted: (data) => {
|
||||
const url = data?.partsAPIMainImage;
|
||||
if (url && url !== null) {
|
||||
setImageUrl(url);
|
||||
} else {
|
||||
setImageUrl(fallbackImage);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
setImageUrl(fallbackImage);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
imageUrl,
|
||||
isLoading: loading,
|
||||
error: !!error
|
||||
};
|
||||
};
|
256
src/hooks/useCatalogPrices.ts
Normal file
256
src/hooks/useCatalogPrices.ts
Normal file
@ -0,0 +1,256 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useCart } from '@/contexts/CartContext';
|
||||
|
||||
interface PriceData {
|
||||
minPrice: number | null;
|
||||
cheapestOffer: any | null;
|
||||
isLoading: boolean;
|
||||
hasOffers: boolean;
|
||||
}
|
||||
|
||||
interface UseCatalogPricesReturn {
|
||||
getPriceData: (articleNumber: string, brand: string) => PriceData;
|
||||
addToCart: (articleNumber: string, brand: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useCatalogPrices = (): UseCatalogPricesReturn => {
|
||||
const [priceCache, setPriceCache] = useState<Map<string, PriceData>>(new Map());
|
||||
const loadingRequestsRef = useRef<Set<string>>(new Set());
|
||||
const { addItem } = useCart();
|
||||
|
||||
const getOffersData = useCallback(async (articleNumber: string, brand: string) => {
|
||||
const graphqlUri = process.env.NEXT_PUBLIC_CMS_GRAPHQL_URL || 'http://localhost:3000/api/graphql';
|
||||
|
||||
console.log('🔍 useCatalogPrices: запрос цен для:', { articleNumber, brand, graphqlUri });
|
||||
|
||||
try {
|
||||
const response = await fetch(graphqlUri, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: `
|
||||
query SearchProductOffers($articleNumber: String!, $brand: String!) {
|
||||
searchProductOffers(articleNumber: $articleNumber, brand: $brand) {
|
||||
internalOffers {
|
||||
id
|
||||
productId
|
||||
price
|
||||
quantity
|
||||
warehouse
|
||||
deliveryDays
|
||||
available
|
||||
rating
|
||||
supplier
|
||||
}
|
||||
externalOffers {
|
||||
offerKey
|
||||
brand
|
||||
code
|
||||
name
|
||||
price
|
||||
currency
|
||||
deliveryTime
|
||||
deliveryTimeMax
|
||||
quantity
|
||||
warehouse
|
||||
warehouseName
|
||||
rejects
|
||||
supplier
|
||||
comment
|
||||
weight
|
||||
volume
|
||||
canPurchase
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
articleNumber,
|
||||
brand
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
console.log('📡 useCatalogPrices: HTTP статус ответа:', response.status);
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('❌ useCatalogPrices: HTTP ошибка:', response.status, response.statusText);
|
||||
return { minPrice: null, cheapestOffer: null, hasOffers: false };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('📦 useCatalogPrices: получен ответ:', data);
|
||||
|
||||
// Если есть ошибки GraphQL, логируем их
|
||||
if (data.errors) {
|
||||
console.error('❌ useCatalogPrices: GraphQL ошибки:', data.errors);
|
||||
return { minPrice: null, cheapestOffer: null, hasOffers: false };
|
||||
}
|
||||
|
||||
const offers = data?.data?.searchProductOffers;
|
||||
console.log('🔍 useCatalogPrices: извлеченные предложения:', offers);
|
||||
|
||||
if (!offers) {
|
||||
console.log('⚠️ useCatalogPrices: предложения не найдены');
|
||||
return { minPrice: null, cheapestOffer: null, hasOffers: false };
|
||||
}
|
||||
|
||||
const allOffers: any[] = [];
|
||||
|
||||
// Обрабатываем внутренние предложения
|
||||
if (offers.internalOffers) {
|
||||
console.log('📦 useCatalogPrices: обрабатываем внутренние предложения:', offers.internalOffers.length);
|
||||
offers.internalOffers.forEach((offer: any) => {
|
||||
if (offer.price && offer.price > 0) {
|
||||
allOffers.push({
|
||||
...offer,
|
||||
type: 'internal',
|
||||
id: offer.id,
|
||||
supplierName: offer.supplier,
|
||||
deliveryDays: offer.deliveryDays
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Обрабатываем внешние предложения
|
||||
if (offers.externalOffers) {
|
||||
console.log('🌐 useCatalogPrices: обрабатываем внешние предложения:', offers.externalOffers.length);
|
||||
offers.externalOffers.forEach((offer: any) => {
|
||||
if (offer.price && offer.price > 0) {
|
||||
allOffers.push({
|
||||
...offer,
|
||||
type: 'external',
|
||||
id: offer.offerKey,
|
||||
supplierName: offer.supplier,
|
||||
deliveryDays: offer.deliveryTime || offer.deliveryTimeMax || 0
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('🎯 useCatalogPrices: итого найдено предложений:', allOffers.length);
|
||||
|
||||
// Проверяем, есть ли вообще какие-то предложения (даже без цены)
|
||||
const hasAnyOffers = (offers.internalOffers && offers.internalOffers.length > 0) ||
|
||||
(offers.externalOffers && offers.externalOffers.length > 0);
|
||||
|
||||
if (allOffers.length === 0) {
|
||||
console.log('⚠️ useCatalogPrices: нет валидных предложений с ценой > 0');
|
||||
return { minPrice: null, cheapestOffer: null, hasOffers: hasAnyOffers };
|
||||
}
|
||||
|
||||
// Находим самое дешевое предложение
|
||||
const cheapestOffer = allOffers.reduce((cheapest, current) => {
|
||||
return current.price < cheapest.price ? current : cheapest;
|
||||
});
|
||||
|
||||
console.log('💰 useCatalogPrices: самое дешевое предложение:', {
|
||||
price: cheapestOffer.price,
|
||||
supplier: cheapestOffer.supplierName,
|
||||
type: cheapestOffer.type
|
||||
});
|
||||
|
||||
return {
|
||||
minPrice: cheapestOffer.price,
|
||||
cheapestOffer,
|
||||
hasOffers: true
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('❌ useCatalogPrices: ошибка получения предложений:', error);
|
||||
return { minPrice: null, cheapestOffer: null, hasOffers: false };
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getPriceData = useCallback((articleNumber: string, brand: string): PriceData => {
|
||||
if (!articleNumber || !brand) {
|
||||
return { minPrice: null, cheapestOffer: null, isLoading: false, hasOffers: false };
|
||||
}
|
||||
|
||||
const key = `${brand}-${articleNumber}`;
|
||||
const cached = priceCache.get(key);
|
||||
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Проверяем, не загружается ли уже этот товар
|
||||
if (loadingRequestsRef.current.has(key)) {
|
||||
return { minPrice: null, cheapestOffer: null, isLoading: true, hasOffers: false };
|
||||
}
|
||||
|
||||
// Устанавливаем состояние загрузки
|
||||
const loadingState: PriceData = { minPrice: null, cheapestOffer: null, isLoading: true, hasOffers: false };
|
||||
setPriceCache(prev => new Map(prev).set(key, loadingState));
|
||||
loadingRequestsRef.current.add(key);
|
||||
|
||||
// Загружаем данные асинхронно
|
||||
getOffersData(articleNumber, brand).then(({ minPrice, cheapestOffer, hasOffers }) => {
|
||||
const finalState: PriceData = { minPrice, cheapestOffer, isLoading: false, hasOffers };
|
||||
setPriceCache(prev => new Map(prev).set(key, finalState));
|
||||
loadingRequestsRef.current.delete(key);
|
||||
}).catch((error) => {
|
||||
console.error('❌ useCatalogPrices: ошибка загрузки цены:', error);
|
||||
const errorState: PriceData = { minPrice: null, cheapestOffer: null, isLoading: false, hasOffers: false };
|
||||
setPriceCache(prev => new Map(prev).set(key, errorState));
|
||||
loadingRequestsRef.current.delete(key);
|
||||
});
|
||||
|
||||
return loadingState;
|
||||
}, [priceCache, getOffersData]);
|
||||
|
||||
const addToCart = useCallback(async (articleNumber: string, brand: string) => {
|
||||
const key = `${brand}-${articleNumber}`;
|
||||
const cached = priceCache.get(key);
|
||||
|
||||
let cheapestOffer = cached?.cheapestOffer;
|
||||
|
||||
// Если нет кэшированного предложения, загружаем
|
||||
if (!cheapestOffer) {
|
||||
const { cheapestOffer: offer } = await getOffersData(articleNumber, brand);
|
||||
cheapestOffer = offer;
|
||||
}
|
||||
|
||||
if (!cheapestOffer) {
|
||||
toast.error('Не удалось найти предложения для этого товара');
|
||||
return;
|
||||
}
|
||||
|
||||
// Добавляем в корзину самое дешевое предложение
|
||||
try {
|
||||
const itemToAdd = {
|
||||
productId: cheapestOffer.type === 'internal' ? cheapestOffer.id : undefined,
|
||||
offerKey: cheapestOffer.type === 'external' ? cheapestOffer.id : undefined,
|
||||
name: `${brand} ${articleNumber}`,
|
||||
description: `${brand} ${articleNumber}`,
|
||||
brand: brand,
|
||||
article: articleNumber,
|
||||
price: cheapestOffer.price,
|
||||
currency: cheapestOffer.currency || 'RUB',
|
||||
quantity: 1,
|
||||
deliveryTime: cheapestOffer.deliveryDays?.toString() || '0',
|
||||
warehouse: cheapestOffer.warehouse || 'Склад',
|
||||
supplier: cheapestOffer.supplierName || 'Неизвестный поставщик',
|
||||
isExternal: cheapestOffer.type === 'external',
|
||||
image: '', // Убираем мокап-фотку, изображения будут загружаться отдельно
|
||||
};
|
||||
|
||||
addItem(itemToAdd);
|
||||
|
||||
// Показываем уведомление
|
||||
toast.success(`Товар "${brand} ${articleNumber}" добавлен в корзину за ${cheapestOffer.price} ₽`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка добавления в корзину:', error);
|
||||
toast.error('Ошибка добавления товара в корзину');
|
||||
}
|
||||
}, [priceCache, getOffersData, addItem]);
|
||||
|
||||
return {
|
||||
getPriceData,
|
||||
addToCart
|
||||
};
|
||||
};
|
77
src/hooks/useInfiniteScroll.ts
Normal file
77
src/hooks/useInfiniteScroll.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
|
||||
interface UseInfiniteScrollOptions {
|
||||
threshold?: number;
|
||||
rootMargin?: string;
|
||||
hasMore?: boolean;
|
||||
isLoading?: boolean;
|
||||
debounceMs?: number;
|
||||
}
|
||||
|
||||
interface UseInfiniteScrollReturn {
|
||||
targetRef: React.RefObject<HTMLDivElement | null>;
|
||||
isIntersecting: boolean;
|
||||
}
|
||||
|
||||
// Debounce функция
|
||||
const debounce = (func: Function, wait: number) => {
|
||||
let timeout: NodeJS.Timeout;
|
||||
return function executedFunction(...args: any[]) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
};
|
||||
|
||||
export const useInfiniteScroll = (
|
||||
onLoadMore: () => void,
|
||||
options: UseInfiniteScrollOptions = {}
|
||||
): UseInfiniteScrollReturn => {
|
||||
const {
|
||||
threshold = 0.1,
|
||||
rootMargin = '100px',
|
||||
hasMore = true,
|
||||
isLoading = false,
|
||||
debounceMs = 200
|
||||
} = options;
|
||||
|
||||
const [isIntersecting, setIsIntersecting] = useState(false);
|
||||
const targetRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
// Создаем debounced версию onLoadMore
|
||||
const debouncedOnLoadMore = useCallback(
|
||||
debounce(onLoadMore, debounceMs),
|
||||
[onLoadMore, debounceMs]
|
||||
);
|
||||
|
||||
const handleIntersection = useCallback(
|
||||
(entries: IntersectionObserverEntry[]) => {
|
||||
const [entry] = entries;
|
||||
setIsIntersecting(entry.isIntersecting);
|
||||
|
||||
if (entry.isIntersecting && hasMore && !isLoading) {
|
||||
debouncedOnLoadMore();
|
||||
}
|
||||
},
|
||||
[debouncedOnLoadMore, hasMore, isLoading]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const target = targetRef.current;
|
||||
if (!target) return;
|
||||
|
||||
const observer = new IntersectionObserver(handleIntersection, {
|
||||
threshold,
|
||||
rootMargin,
|
||||
});
|
||||
|
||||
observer.observe(target);
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [handleIntersection, threshold, rootMargin]);
|
||||
|
||||
return { targetRef, isIntersecting };
|
||||
};
|
83
src/hooks/usePartsIndex.ts
Normal file
83
src/hooks/usePartsIndex.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { partsIndexService } from '@/lib/partsindex-service';
|
||||
import { PartsIndexCatalog, PartsIndexGroup, PartsIndexTabData } from '@/types/partsindex';
|
||||
|
||||
export const usePartsIndexCatalogs = () => {
|
||||
const [catalogs, setCatalogs] = useState<PartsIndexCatalog[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCatalogs = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const response = await partsIndexService.getCatalogs('ru');
|
||||
setCatalogs(response.list);
|
||||
} catch (err) {
|
||||
setError(err as Error);
|
||||
console.error('Ошибка загрузки каталогов:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchCatalogs();
|
||||
}, []);
|
||||
|
||||
return { catalogs, loading, error };
|
||||
};
|
||||
|
||||
export const usePartsIndexCatalogGroups = (catalogId: string | null) => {
|
||||
const [group, setGroup] = useState<PartsIndexGroup | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!catalogId) {
|
||||
setGroup(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchGroup = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const groupData = await partsIndexService.getCatalogGroups(catalogId, 'ru');
|
||||
setGroup(groupData);
|
||||
} catch (err) {
|
||||
setError(err as Error);
|
||||
console.error(`Ошибка загрузки группы каталога ${catalogId}:`, err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchGroup();
|
||||
}, [catalogId]);
|
||||
|
||||
return { group, loading, error };
|
||||
};
|
||||
|
||||
// Функция для преобразования данных Parts Index в формат меню
|
||||
export const transformPartsIndexToTabData = (
|
||||
catalogs: PartsIndexCatalog[],
|
||||
catalogGroups: Map<string, PartsIndexGroup>
|
||||
): PartsIndexTabData[] => {
|
||||
return catalogs.map(catalog => {
|
||||
const group = catalogGroups.get(catalog.id);
|
||||
|
||||
// Получаем подкатегории из entityNames или повторяем название категории
|
||||
const links = group?.entityNames && group.entityNames.length > 0
|
||||
? group.entityNames.slice(0, 9).map(entity => entity.name)
|
||||
: [catalog.name]; // Если нет подкатегорий, повторяем название категории
|
||||
|
||||
return {
|
||||
label: catalog.name,
|
||||
heading: catalog.name,
|
||||
links,
|
||||
catalogId: catalog.id,
|
||||
group
|
||||
};
|
||||
});
|
||||
};
|
121
src/hooks/useProductPrices.ts
Normal file
121
src/hooks/useProductPrices.ts
Normal file
@ -0,0 +1,121 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useLazyQuery } from '@apollo/client';
|
||||
import { SEARCH_PRODUCT_OFFERS } from '@/lib/graphql';
|
||||
|
||||
interface ProductOffer {
|
||||
offerKey: string;
|
||||
brand: string;
|
||||
code: string;
|
||||
name: string;
|
||||
price: number;
|
||||
currency: string;
|
||||
deliveryTime: number;
|
||||
deliveryTimeMax: number;
|
||||
quantity: number;
|
||||
warehouse: string;
|
||||
supplier: string;
|
||||
canPurchase: boolean;
|
||||
}
|
||||
|
||||
interface ProductPriceData {
|
||||
searchProductOffers: {
|
||||
articleNumber: string;
|
||||
brand: string;
|
||||
internalOffers: ProductOffer[];
|
||||
externalOffers: ProductOffer[];
|
||||
analogs: number;
|
||||
hasInternalStock: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface ProductPriceVariables {
|
||||
articleNumber: string;
|
||||
brand: string;
|
||||
}
|
||||
|
||||
export const useProductPrices = (products: Array<{ code: string; brand: string; id: string }>) => {
|
||||
const [pricesMap, setPricesMap] = useState<Map<string, ProductOffer | null>>(new Map());
|
||||
const [loadingPrices, setLoadingPrices] = useState<Set<string>>(new Set());
|
||||
|
||||
const [searchOffers] = useLazyQuery<ProductPriceData, ProductPriceVariables>(SEARCH_PRODUCT_OFFERS);
|
||||
|
||||
const loadPrice = async (product: { code: string; brand: string; id: string }) => {
|
||||
const key = `${product.id}_${product.code}_${product.brand}`;
|
||||
|
||||
if (pricesMap.has(key) || loadingPrices.has(key)) {
|
||||
return; // Уже загружено или загружается
|
||||
}
|
||||
|
||||
console.log('💰 Загружаем цену для:', product.code, product.brand);
|
||||
setLoadingPrices(prev => new Set([...prev, key]));
|
||||
|
||||
try {
|
||||
const result = await searchOffers({
|
||||
variables: {
|
||||
articleNumber: product.code,
|
||||
brand: product.brand
|
||||
}
|
||||
});
|
||||
|
||||
if (result.data?.searchProductOffers) {
|
||||
const offers = result.data.searchProductOffers;
|
||||
console.log('📊 Получены предложения для', product.code, ':', {
|
||||
internal: offers.internalOffers?.length || 0,
|
||||
external: offers.externalOffers?.length || 0
|
||||
});
|
||||
|
||||
// Берем первое доступное предложение (внутреннее или внешнее)
|
||||
const bestOffer = offers.internalOffers?.[0] || offers.externalOffers?.[0];
|
||||
|
||||
if (bestOffer) {
|
||||
console.log('✅ Найдена цена для', product.code, ':', bestOffer.price, bestOffer.currency);
|
||||
setPricesMap(prev => new Map([...prev, [key, bestOffer]]));
|
||||
} else {
|
||||
console.log('⚠️ Предложения не найдены для', product.code);
|
||||
setPricesMap(prev => new Map([...prev, [key, null]]));
|
||||
}
|
||||
} else {
|
||||
console.log('❌ Нет данных от API для', product.code);
|
||||
setPricesMap(prev => new Map([...prev, [key, null]]));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка загрузки цены для', product.code, error);
|
||||
setPricesMap(prev => new Map([...prev, [key, null]]));
|
||||
} finally {
|
||||
setLoadingPrices(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(key);
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Загружаем цены для всех товаров с небольшой задержкой между запросами
|
||||
products.forEach((product, index) => {
|
||||
setTimeout(() => {
|
||||
loadPrice(product);
|
||||
}, index * 100); // Задержка 100мс между запросами
|
||||
});
|
||||
}, [products]);
|
||||
|
||||
const getPrice = (product: { code: string; brand: string; id: string }) => {
|
||||
const key = `${product.id}_${product.code}_${product.brand}`;
|
||||
return pricesMap.get(key);
|
||||
};
|
||||
|
||||
const isLoadingPrice = (product: { code: string; brand: string; id: string }) => {
|
||||
const key = `${product.id}_${product.code}_${product.brand}`;
|
||||
return loadingPrices.has(key);
|
||||
};
|
||||
|
||||
const loadPriceOnDemand = (product: { code: string; brand: string; id: string }) => {
|
||||
loadPrice(product);
|
||||
};
|
||||
|
||||
return {
|
||||
getPrice,
|
||||
isLoadingPrice,
|
||||
loadPriceOnDemand
|
||||
};
|
||||
};
|
89
src/hooks/useRecommendedProducts.ts
Normal file
89
src/hooks/useRecommendedProducts.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { GET_CATEGORY_PRODUCTS_WITH_OFFERS } from '@/lib/graphql';
|
||||
|
||||
interface RecommendedProduct {
|
||||
brand: string;
|
||||
articleNumber: string;
|
||||
name?: string;
|
||||
artId?: string;
|
||||
minPrice?: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Хук для получения рекомендуемых товаров из той же категории с AutoEuro предложениями
|
||||
*/
|
||||
export const useRecommendedProducts = (
|
||||
productName: string = '',
|
||||
excludeArticle: string = '',
|
||||
excludeBrand: string = ''
|
||||
) => {
|
||||
// Определяем категорию товара из названия
|
||||
const categoryName = useMemo(() => {
|
||||
const name = productName.toLowerCase()
|
||||
|
||||
// Простое определение категории по ключевым словам в названии
|
||||
if (name.includes('шина') || name.includes('покрышка') || name.includes('резина')) {
|
||||
return 'шины'
|
||||
}
|
||||
if (name.includes('масло') || name.includes('oil')) {
|
||||
return 'масла'
|
||||
}
|
||||
if (name.includes('фильтр')) {
|
||||
return 'фильтры'
|
||||
}
|
||||
if (name.includes('тормоз') || name.includes('колодка')) {
|
||||
return 'тормоза'
|
||||
}
|
||||
if (name.includes('аккумулятор') || name.includes('батарея')) {
|
||||
return 'аккумуляторы'
|
||||
}
|
||||
if (name.includes('свеча')) {
|
||||
return 'свечи'
|
||||
}
|
||||
if (name.includes('стартер')) {
|
||||
return 'стартеры'
|
||||
}
|
||||
if (name.includes('генератор')) {
|
||||
return 'генераторы'
|
||||
}
|
||||
if (name.includes('амортизатор') || name.includes('стойка')) {
|
||||
return 'амортизаторы'
|
||||
}
|
||||
|
||||
// Если категория не определена, используем первое слово из названия
|
||||
const words = productName.split(' ')
|
||||
return words[0] || 'автотовары'
|
||||
}, [productName])
|
||||
|
||||
// Запрос товаров из категории
|
||||
const { data, loading, error } = useQuery(GET_CATEGORY_PRODUCTS_WITH_OFFERS, {
|
||||
variables: {
|
||||
categoryName,
|
||||
excludeArticle,
|
||||
excludeBrand,
|
||||
limit: 5
|
||||
},
|
||||
skip: !categoryName || !excludeArticle, // Пропускаем запрос если нет необходимых данных
|
||||
fetchPolicy: 'cache-first'
|
||||
})
|
||||
|
||||
// Мемоизируем обработку результатов
|
||||
const recommendedProducts = useMemo(() => {
|
||||
if (!data?.getCategoryProductsWithOffers) return [];
|
||||
|
||||
return data.getCategoryProductsWithOffers.map((product: any) => ({
|
||||
brand: product.brand || '',
|
||||
articleNumber: product.articleNumber || '',
|
||||
name: product.name || `${product.brand || ''} ${product.articleNumber || ''}`,
|
||||
artId: product.artId || '',
|
||||
minPrice: product.minPrice
|
||||
})).filter((product: any) => product.brand && product.articleNumber); // Фильтруем только валидные
|
||||
}, [data])
|
||||
|
||||
return {
|
||||
recommendedProducts,
|
||||
isLoading: loading,
|
||||
error: error?.message
|
||||
};
|
||||
};
|
Reference in New Issue
Block a user