"use client" import React, { useState, useEffect } from 'react' import { Card } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Badge } from '@/components/ui/badge' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { Sidebar } from '@/components/dashboard/sidebar' import { useSidebar } from '@/hooks/useSidebar' import { Search, Plus, Minus, ShoppingCart, Calendar, Phone, User, MapPin, Package, Wrench, ArrowLeft, Check, Eye, ChevronLeft, ChevronRight } from 'lucide-react' import { WildberriesService } from '@/services/wildberries-service' import { useAuth } from '@/hooks/useAuth' import { useQuery, useMutation } from '@apollo/client' import { GET_MY_COUNTERPARTIES } from '@/graphql/queries' import { CREATE_WILDBERRIES_SUPPLY } from '@/graphql/mutations' import { toast } from 'sonner' interface WildberriesCard { nmID: number vendorCode: string sizes: Array<{ chrtID: number techSize: string wbSize: string price: number discountedPrice: number quantity: number }> mediaFiles: string[] object: string parent: string countryProduction: string supplierVendorCode: string brand: string title: string description: string } interface SelectedCard { card: WildberriesCard selectedQuantity: number selectedMarket: string selectedPlace: string sellerName: string sellerPhone: string deliveryDate: string selectedServices: string[] } interface FulfillmentService { id: string name: string description?: string price: number organizationName: string } interface WBProductCardsProps { onBack: () => void onComplete: (selectedCards: SelectedCard[]) => void } export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) { const { user } = useAuth() const { getSidebarMargin } = useSidebar() const [searchTerm, setSearchTerm] = useState('') const [loading, setLoading] = useState(false) const [wbCards, setWbCards] = useState([]) const [selectedCards, setSelectedCards] = useState([]) const [showSummary, setShowSummary] = useState(false) const [fulfillmentServices, setFulfillmentServices] = useState([]) const [selectedCardForDetails, setSelectedCardForDetails] = useState(null) const [currentImageIndex, setCurrentImageIndex] = useState(0) // Моковые товары для демонстрации const getMockCards = (): WildberriesCard[] => [ { nmID: 123456789, vendorCode: 'SKU001', title: 'Смартфон Samsung Galaxy A54', description: 'Современный смартфон с отличной камерой и долгим временем автономной работы', brand: 'Samsung', object: 'Смартфоны', parent: 'Электроника', countryProduction: 'Корея', supplierVendorCode: 'SUPPLIER-001', mediaFiles: ['/api/placeholder/400/400', '/api/placeholder/400/401', '/api/placeholder/400/402'], sizes: [ { chrtID: 123456, techSize: '128GB', wbSize: '128GB Черный', price: 25990, discountedPrice: 22990, quantity: 15 } ] }, { nmID: 987654321, vendorCode: 'SKU002', title: 'Наушники Apple AirPods Pro', description: 'Беспроводные наушники с активным шумоподавлением и пространственным звуком', brand: 'Apple', object: 'Наушники', parent: 'Электроника', countryProduction: 'Китай', supplierVendorCode: 'SUPPLIER-002', mediaFiles: ['/api/placeholder/400/403', '/api/placeholder/400/404'], sizes: [ { chrtID: 987654, techSize: 'Standard', wbSize: 'Белый', price: 24990, discountedPrice: 19990, quantity: 8 } ] }, { nmID: 555666777, vendorCode: 'SKU003', title: 'Кроссовки Nike Air Max 270', description: 'Спортивные кроссовки с современным дизайном и комфортной посадкой', brand: 'Nike', object: 'Кроссовки', parent: 'Обувь', countryProduction: 'Вьетнам', supplierVendorCode: 'SUPPLIER-003', mediaFiles: ['/api/placeholder/400/405', '/api/placeholder/400/406', '/api/placeholder/400/407'], sizes: [ { chrtID: 555666, techSize: '42', wbSize: '42 EU', price: 12990, discountedPrice: 9990, quantity: 25 }, { chrtID: 555667, techSize: '43', wbSize: '43 EU', price: 12990, discountedPrice: 9990, quantity: 20 } ] }, { nmID: 444333222, vendorCode: 'SKU004', title: 'Футболка Adidas Originals', description: 'Классическая футболка из органического хлопка с логотипом бренда', brand: 'Adidas', object: 'Футболки', parent: 'Одежда', countryProduction: 'Бангладеш', supplierVendorCode: 'SUPPLIER-004', mediaFiles: ['/api/placeholder/400/408', '/api/placeholder/400/409'], sizes: [ { chrtID: 444333, techSize: 'M', wbSize: 'M', price: 2990, discountedPrice: 2490, quantity: 50 }, { chrtID: 444334, techSize: 'L', wbSize: 'L', price: 2990, discountedPrice: 2490, quantity: 45 }, { chrtID: 444335, techSize: 'XL', wbSize: 'XL', price: 2990, discountedPrice: 2490, quantity: 30 } ] }, { nmID: 111222333, vendorCode: 'SKU005', title: 'Рюкзак для ноутбука Xiaomi', description: 'Стильный и функциональный рюкзак для ноутбука до 15.6 дюймов', brand: 'Xiaomi', object: 'Рюкзаки', parent: 'Аксессуары', countryProduction: 'Китай', supplierVendorCode: 'SUPPLIER-005', mediaFiles: ['/api/placeholder/400/410'], sizes: [ { chrtID: 111222, techSize: '15.6"', wbSize: 'Черный', price: 4990, discountedPrice: 3990, quantity: 35 } ] }, { nmID: 777888999, vendorCode: 'SKU006', title: 'Умные часы Apple Watch Series 9', description: 'Новейшие умные часы с передовыми функциями здоровья и фитнеса', brand: 'Apple', object: 'Умные часы', parent: 'Электроника', countryProduction: 'Китай', supplierVendorCode: 'SUPPLIER-006', mediaFiles: ['/api/placeholder/400/411', '/api/placeholder/400/412', '/api/placeholder/400/413'], sizes: [ { chrtID: 777888, techSize: '41mm', wbSize: '41mm GPS', price: 39990, discountedPrice: 35990, quantity: 12 }, { chrtID: 777889, techSize: '45mm', wbSize: '45mm GPS', price: 42990, discountedPrice: 38990, quantity: 8 } ] } ] // Загружаем контрагентов-фулфилментов const { data: counterpartiesData } = useQuery(GET_MY_COUNTERPARTIES) // Мутация для создания поставки const [createSupply, { loading: creatingSupply }] = useMutation(CREATE_WILDBERRIES_SUPPLY, { onCompleted: (data) => { if (data.createWildberriesSupply.success) { toast.success(data.createWildberriesSupply.message) onComplete(selectedCards) } else { toast.error(data.createWildberriesSupply.message) } }, onError: (error) => { toast.error('Ошибка при создании поставки') console.error('Error creating supply:', error) } }) // Моковые данные рынков const markets = [ { value: 'sadovod', label: 'Садовод' }, { value: 'luzhniki', label: 'Лужники' }, { value: 'tishinka', label: 'Тишинка' }, { value: 'food-city', label: 'Фуд Сити' } ] useEffect(() => { // Загружаем услуги фулфилмента из контрагентов if (counterpartiesData?.myCounterparties) { interface Organization { id: string name?: string fullName?: string type: string } const fulfillmentOrganizations = counterpartiesData.myCounterparties.filter( (org: Organization) => org.type === 'FULFILLMENT' ) // В реальном приложении здесь был бы запрос услуг для каждой организации const mockServices: FulfillmentService[] = fulfillmentOrganizations.flatMap((org: Organization) => [ { id: `${org.id}-packaging`, name: 'Упаковка товаров', description: 'Профессиональная упаковка товаров', price: 50, organizationName: org.name || org.fullName }, { id: `${org.id}-labeling`, name: 'Маркировка товаров', description: 'Нанесение этикеток и штрих-кодов', price: 30, organizationName: org.name || org.fullName }, { id: `${org.id}-quality-check`, name: 'Контроль качества', description: 'Проверка качества товаров', price: 100, organizationName: org.name || org.fullName } ]) setFulfillmentServices(mockServices) } }, [counterpartiesData]) // Автоматически загружаем товары при открытии компонента useEffect(() => { const loadCards = async () => { setLoading(true) try { const wbApiKey = user?.organization?.apiKeys?.find(key => key.marketplace === 'WILDBERRIES') console.log('WB API Key found:', !!wbApiKey) console.log('WB API Key active:', wbApiKey?.isActive) console.log('WB API Key validationData:', wbApiKey?.validationData) if (wbApiKey?.isActive) { // Попытка загрузить реальные данные из API Wildberries const validationData = wbApiKey.validationData as Record // API ключ может храниться в разных местах const apiToken = validationData?.token || validationData?.apiKey || validationData?.key || (wbApiKey as { apiKey?: string }).apiKey // Прямое поле apiKey из базы console.log('API Token extracted:', !!apiToken) console.log('API Token length:', apiToken?.length) if (apiToken) { console.log('Загружаем карточки из WB API...') const cards = await WildberriesService.getAllCards(apiToken, 50) setWbCards(cards) console.log('Загружено карточек из WB API:', cards.length) return } } // Если API ключ не настроен, оставляем пустое состояние console.log('API ключ WB не настроен, показываем пустое состояние') setWbCards([]) } catch (error) { console.error('Ошибка загрузки карточек WB:', error) // При ошибке API показываем пустое состояние setWbCards([]) } finally { setLoading(false) } } loadCards() }, [user]) const loadAllCards = async () => { setLoading(true) try { const wbApiKey = user?.organization?.apiKeys?.find(key => key.marketplace === 'WILDBERRIES') if (wbApiKey?.isActive) { // Попытка загрузить реальные данные из API Wildberries const validationData = wbApiKey.validationData as Record const apiToken = validationData?.token || validationData?.apiKey || validationData?.key || (wbApiKey as { apiKey?: string }).apiKey if (apiToken) { console.log('Загружаем все карточки из WB API...') const cards = await WildberriesService.getAllCards(apiToken, 100) setWbCards(cards) console.log('Загружено карточек из WB API:', cards.length) return } } // Если API ключ не настроен, загружаем моковые данные console.log('API ключ WB не настроен, загружаем моковые данные') const allCards = getMockCards() setWbCards(allCards) console.log('Загружены моковые товары:', allCards.length) } catch (error) { console.error('Ошибка загрузки всех карточек WB:', error) // При ошибке загружаем моковые данные const allCards = getMockCards() setWbCards(allCards) console.log('Загружены моковые товары (fallback):', allCards.length) } finally { setLoading(false) } } const searchCards = async () => { if (!searchTerm.trim()) { loadAllCards() return } setLoading(true) try { const wbApiKey = user?.organization?.apiKeys?.find(key => key.marketplace === 'WILDBERRIES') if (wbApiKey?.isActive) { // Попытка поиска в реальном API Wildberries const validationData = wbApiKey.validationData as Record const apiToken = validationData?.token || validationData?.apiKey || validationData?.key || (wbApiKey as { apiKey?: string }).apiKey if (apiToken) { console.log('Поиск в WB API:', searchTerm) const cards = await WildberriesService.searchCards(apiToken, searchTerm, 50) setWbCards(cards) console.log('Найдено карточек в WB API:', cards.length) return } } // Если API ключ не настроен, ищем в моковых данных console.log('API ключ WB не настроен, поиск в моковых данных:', searchTerm) const mockCards = getMockCards() // Фильтруем товары по поисковому запросу const filteredCards = mockCards.filter(card => card.title.toLowerCase().includes(searchTerm.toLowerCase()) || card.brand.toLowerCase().includes(searchTerm.toLowerCase()) || card.vendorCode.toLowerCase().includes(searchTerm.toLowerCase()) || card.object.toLowerCase().includes(searchTerm.toLowerCase()) ) setWbCards(filteredCards) console.log('Найдено моковых товаров:', filteredCards.length) } catch (error) { console.error('Ошибка поиска карточек WB:', error) // При ошибке ищем в моковых данных const mockCards = getMockCards() const filteredCards = mockCards.filter(card => card.title.toLowerCase().includes(searchTerm.toLowerCase()) || card.brand.toLowerCase().includes(searchTerm.toLowerCase()) || card.vendorCode.toLowerCase().includes(searchTerm.toLowerCase()) || card.object.toLowerCase().includes(searchTerm.toLowerCase()) ) setWbCards(filteredCards) console.log('Найдено моковых товаров (fallback):', filteredCards.length) } finally { setLoading(false) } } const updateCardSelection = (card: WildberriesCard, field: keyof SelectedCard, value: string | number | string[]) => { setSelectedCards(prev => { const existing = prev.find(sc => sc.card.nmID === card.nmID) if (field === 'selectedQuantity' && typeof value === 'number' && value === 0) { return prev.filter(sc => sc.card.nmID !== card.nmID) } if (existing) { return prev.map(sc => sc.card.nmID === card.nmID ? { ...sc, [field]: value } : sc ) } else if (field === 'selectedQuantity' && typeof value === 'number' && value > 0) { const newSelectedCard: SelectedCard = { card, selectedQuantity: value as number, selectedMarket: '', selectedPlace: '', sellerName: '', sellerPhone: '', deliveryDate: '', selectedServices: [] } return [...prev, newSelectedCard] } return prev }) } const getSelectedQuantity = (card: WildberriesCard): number => { const selected = selectedCards.find(sc => sc.card.nmID === card.nmID) return selected ? selected.selectedQuantity : 0 } const formatCurrency = (amount: number) => { return new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB', minimumFractionDigits: 0 }).format(amount) } const getTotalAmount = () => { return selectedCards.reduce((sum, sc) => { const cardPrice = sc.card.sizes[0]?.discountedPrice || sc.card.sizes[0]?.price || 0 const servicesPrice = sc.selectedServices.reduce((serviceSum, serviceId) => { const service = fulfillmentServices.find(s => s.id === serviceId) return serviceSum + (service?.price || 0) }, 0) return sum + (cardPrice + servicesPrice) * sc.selectedQuantity }, 0) } const getTotalItems = () => { return selectedCards.reduce((sum, sc) => sum + sc.selectedQuantity, 0) } const applyServicesToAll = (serviceIds: string[]) => { setSelectedCards(prev => prev.map(sc => ({ ...sc, selectedServices: serviceIds })) ) } const handleCardClick = (card: WildberriesCard) => { setSelectedCardForDetails(card) setCurrentImageIndex(0) } const closeDetailsModal = () => { setSelectedCardForDetails(null) setCurrentImageIndex(0) } const nextImage = () => { if (selectedCardForDetails && selectedCardForDetails.mediaFiles?.length > 1) { setCurrentImageIndex((prev) => (prev + 1) % selectedCardForDetails.mediaFiles.length) } } const prevImage = () => { if (selectedCardForDetails && selectedCardForDetails.mediaFiles?.length > 1) { setCurrentImageIndex((prev) => (prev - 1 + selectedCardForDetails.mediaFiles.length) % selectedCardForDetails.mediaFiles.length) } } const handleCreateSupply = async () => { try { const supplyInput = { deliveryDate: selectedCards[0]?.deliveryDate || null, cards: selectedCards.map(sc => ({ nmId: sc.card.nmID.toString(), vendorCode: sc.card.vendorCode, title: sc.card.title, brand: sc.card.brand, price: sc.card.sizes[0]?.price || 0, discountedPrice: sc.card.sizes[0]?.discountedPrice || null, quantity: sc.card.sizes[0]?.quantity || 0, selectedQuantity: sc.selectedQuantity, selectedMarket: sc.selectedMarket, selectedPlace: sc.selectedPlace, sellerName: sc.sellerName, sellerPhone: sc.sellerPhone, deliveryDate: sc.deliveryDate || null, mediaFiles: sc.card.mediaFiles, selectedServices: sc.selectedServices })) } await createSupply({ variables: { input: supplyInput } }) } catch (error) { console.error('Error creating supply:', error) } } if (showSummary) { return (

Сводка заказа

Проверьте данные перед созданием поставки

{selectedCards.map((sc) => { const cardPrice = sc.card.sizes[0]?.discountedPrice || sc.card.sizes[0]?.price || 0 const servicesPrice = sc.selectedServices.reduce((sum, serviceId) => { const service = fulfillmentServices.find(s => s.id === serviceId) return sum + (service?.price || 0) }, 0) const totalPrice = (cardPrice + servicesPrice) * sc.selectedQuantity return (
{sc.card.title}

{sc.card.title}

{sc.card.vendorCode}

Количество: {sc.selectedQuantity}
Рынок: {markets.find(m => m.value === sc.selectedMarket)?.label || 'Не выбран'}
Место: {sc.selectedPlace || 'Не указано'}
Продавец: {sc.sellerName || 'Не указан'}
Телефон: {sc.sellerPhone || 'Не указан'}
Дата поставки: {sc.deliveryDate || 'Не выбрана'}
{sc.selectedServices.length > 0 && (

Услуги:

{sc.selectedServices.map(serviceId => { const service = fulfillmentServices.find(s => s.id === serviceId) return service ? ( {service.name} ({formatCurrency(service.price)}) ) : null })}
)}
{formatCurrency(totalPrice)}
) })}

Итого

Товаров: {getTotalItems()}
Карточек: {selectedCards.length}
Общая сумма: {formatCurrency(getTotalAmount())}
) } return (

Карточки товаров Wildberries

Найдите и выберите товары для поставки

{selectedCards.length > 0 && ( )}
{/* Поиск */}
setSearchTerm(e.target.value)} className="bg-white/5 border-white/20 text-white placeholder-white/50" onKeyPress={(e) => e.key === 'Enter' && searchCards()} />
{/* Состояние загрузки */} {loading && (
{[...Array(12)].map((_, i) => (
))}
)} {/* Карточки товаров */} {!loading && wbCards.length > 0 && (
{wbCards.map((card) => { const selectedQuantity = getSelectedQuantity(card) const isSelected = selectedQuantity > 0 const selectedCard = selectedCards.find(sc => sc.card.nmID === card.nmID) const mainSize = card.sizes[0] const maxQuantity = mainSize?.quantity || 0 const price = mainSize?.discountedPrice || mainSize?.price || 0 return (
{/* Изображение и основная информация */}
{card.title} handleCardClick(card)} /> {/* Количество в наличии */}
10 ? 'bg-green-500/80' : maxQuantity > 0 ? 'bg-yellow-500/80' : 'bg-red-500/80'} text-white border-0 backdrop-blur text-xs`}> {maxQuantity}
{/* Overlay с кнопкой */}
{/* Заголовок и бренд */}
{card.brand}

handleCardClick(card)}> {card.title}

{card.vendorCode}

{/* Цена */}
{formatCurrency(price)}
{/* Управление количеством */}
{ const value = e.target.value.replace(/[^0-9]/g, '') const numValue = Math.max(0, Math.min(maxQuantity, parseInt(value) || 0)) updateCardSelection(card, 'selectedQuantity', numValue) }} onFocus={(e) => e.target.select()} className="h-7 w-12 text-center bg-white/10 border border-white/20 text-white text-sm rounded focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> {selectedQuantity > 0 && ( {selectedQuantity} )}
{/* Сумма для выбранного товара */} {selectedQuantity > 0 && (
{formatCurrency(price * selectedQuantity)}
)}
) })}
)} {/* Плавающая корзина */} {selectedCards.length > 0 && !showSummary && (
)} {wbCards.length === 0 && !loading && (

Карточки товаров Wildberries

{user?.organization?.apiKeys?.find(key => key.marketplace === 'WILDBERRIES')?.isActive ? ( <>

Введите запрос в поле поиска, чтобы найти товары в вашем каталоге Wildberries, или загрузите все доступные карточки

) : ( <>

Для работы с реальными карточками необходимо настроить API ключ Wildberries

Показаны демонстрационные товары для тестирования

)}
)} {/* Модальное окно с детальной информацией о товаре */} !open && closeDetailsModal()}> Детальная информация о товаре {selectedCardForDetails && (
{/* Изображения */}
{selectedCardForDetails.title} {/* Навигация по изображениям */} {selectedCardForDetails.mediaFiles?.length > 1 && ( <>
{currentImageIndex + 1} из {selectedCardForDetails.mediaFiles?.length || 0}
)}
{/* Миниатюры изображений */} {selectedCardForDetails.mediaFiles?.length > 1 && (
{selectedCardForDetails.mediaFiles?.map((image, index) => ( {`${selectedCardForDetails.title} setCurrentImageIndex(index)} /> ))}
)}
{/* Информация о товаре */}

{selectedCardForDetails.title}

Артикул: {selectedCardForDetails.vendorCode}

Бренд: {selectedCardForDetails.brand}
Категория: {selectedCardForDetails.object}
Родительская категория: {selectedCardForDetails.parent}
Страна производства: {selectedCardForDetails.countryProduction}
{selectedCardForDetails.description && (

Описание

{selectedCardForDetails.description}

)} {/* Размеры и цены */}

Доступные варианты

{selectedCardForDetails.sizes.map((size) => (
{size.wbSize} 10 ? 'bg-green-500/20 text-green-300' : size.quantity > 0 ? 'bg-yellow-500/20 text-yellow-300' : 'bg-red-500/20 text-red-300'}`}> {size.quantity} шт.
Размер: {size.techSize}
{formatCurrency(size.discountedPrice || size.price)}
{size.discountedPrice && size.discountedPrice < size.price && (
{formatCurrency(size.price)}
)}
))}
{/* Кнопки действий в модальном окне */}
)}
) }