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; } export const useCatalogPrices = (): UseCatalogPricesReturn => { const [priceCache, setPriceCache] = useState>(new Map()); const loadingRequestsRef = useRef>(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, stock: cheapestOffer.quantity, // ΠΏΠ΅Ρ€Π΅Π΄Π°Π΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π½Π°Π»ΠΈΡ‡ΠΈΠΈ deliveryTime: cheapestOffer.deliveryDays?.toString() || '0', warehouse: cheapestOffer.warehouse || 'Π‘ΠΊΠ»Π°Π΄', supplier: cheapestOffer.supplierName || 'НСизвСстный поставщик', isExternal: cheapestOffer.type === 'external', image: '', // Π£Π±ΠΈΡ€Π°Π΅ΠΌ ΠΌΠΎΠΊΠ°ΠΏ-Ρ„ΠΎΡ‚ΠΊΡƒ, изобраТСния Π±ΡƒΠ΄ΡƒΡ‚ Π·Π°Π³Ρ€ΡƒΠΆΠ°Ρ‚ΡŒΡΡ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎ }; const result = await addItem(itemToAdd); if (result.success) { // ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅ΠΌ ΡƒΠ²Π΅Π΄ΠΎΠΌΠ»Π΅Π½ΠΈΠ΅ toast.success(`Π’ΠΎΠ²Π°Ρ€ "${brand} ${articleNumber}" Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ Π² ΠΊΠΎΡ€Π·ΠΈΠ½Ρƒ Π·Π° ${cheapestOffer.price} β‚½`); } else { toast.error(result.error || 'Ошибка добавлСния Ρ‚ΠΎΠ²Π°Ρ€Π° Π² ΠΊΠΎΡ€Π·ΠΈΠ½Ρƒ'); } } catch (error) { console.error('Ошибка добавлСния Π² ΠΊΠΎΡ€Π·ΠΈΠ½Ρƒ:', error); toast.error('Ошибка добавлСния Ρ‚ΠΎΠ²Π°Ρ€Π° Π² ΠΊΠΎΡ€Π·ΠΈΠ½Ρƒ'); } }, [priceCache, getOffersData, addItem]); return { getPriceData, addToCart }; };