"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 { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import DatePicker from "react-datepicker" import "react-datepicker/dist/react-datepicker.css" import { Search, Plus, Calendar as CalendarIcon, Package, Check, X, User, Phone, MapPin } from 'lucide-react' import { WildberriesService } from '@/services/wildberries-service' import { useAuth } from '@/hooks/useAuth' import { useQuery, useMutation } from '@apollo/client' import { apolloClient } from '@/lib/apollo-client' import { GET_MY_COUNTERPARTIES, GET_COUNTERPARTY_SERVICES, GET_COUNTERPARTY_SUPPLIES } from '@/graphql/queries' import { CREATE_WILDBERRIES_SUPPLY } from '@/graphql/mutations' import { toast } from 'sonner' import { format } from 'date-fns' import { ru } from 'date-fns/locale' import { WildberriesCard } from '@/types/supplies' interface SupplyItem { card: WildberriesCard quantity: number pricePerUnit: number totalPrice: number supplierId: string } interface Organization { id: string name?: string fullName?: string type: string } interface FulfillmentService { id: string name: string description?: string price: number } interface Supplier { id: string name: string contactName: string phone: string market: string address: string place: string telegram: string } interface DirectSupplyCreationProps { onComplete: () => void onCreateSupply: () => void canCreateSupply: boolean isCreatingSupply: boolean onCanCreateSupplyChange?: (canCreate: boolean) => void } export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupply, isCreatingSupply, onCanCreateSupplyChange }: DirectSupplyCreationProps) { const { user } = useAuth() // Состояние для товаров const [searchTerm, setSearchTerm] = useState('') const [loading, setLoading] = useState(false) const [wbCards, setWbCards] = useState([]) const [supplyItems, setSupplyItems] = useState([]) // Общие настройки const [deliveryDate, setDeliveryDate] = useState(undefined) const [selectedFulfillmentOrg, setSelectedFulfillmentOrg] = useState('') const [selectedServices, setSelectedServices] = useState([]) const [selectedConsumables, setSelectedConsumables] = useState([]) // Поставщики const [suppliers, setSuppliers] = useState([]) const [showSupplierModal, setShowSupplierModal] = useState(false) const [newSupplier, setNewSupplier] = useState({ name: '', contactName: '', phone: '', market: '', address: '', place: '', telegram: '' }) // Данные для фулфилмента const [organizationServices, setOrganizationServices] = useState<{[orgId: string]: FulfillmentService[]}>({}) const [organizationSupplies, setOrganizationSupplies] = useState<{[orgId: string]: FulfillmentService[]}>({}) // Загружаем контрагентов-фулфилментов 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() } else { toast.error(data.createWildberriesSupply.message) } }, onError: (error) => { toast.error('Ошибка при создании поставки') console.error('Error creating supply:', error) } }) // Моковые данные товаров для демонстрации const getMockCards = (): WildberriesCard[] => [ { nmID: 123456789, vendorCode: 'SKU001', title: 'Платье летнее розовое', description: 'Легкое летнее платье из натурального хлопка', brand: 'Fashion', object: 'Платья', parent: 'Одежда', countryProduction: 'Россия', supplierVendorCode: 'SUPPLIER-001', mediaFiles: ['/api/placeholder/400/400'], sizes: [{ chrtID: 123456, techSize: 'M', wbSize: 'M Розовый', price: 2500, discountedPrice: 2000, quantity: 50 }] }, { nmID: 987654321, vendorCode: 'SKU002', title: 'Платье черное вечернее', description: 'Элегантное вечернее платье для особых случаев', brand: 'Fashion', object: 'Платья', parent: 'Одежда', countryProduction: 'Россия', supplierVendorCode: 'SUPPLIER-002', mediaFiles: ['/api/placeholder/400/403'], sizes: [{ chrtID: 987654, techSize: 'M', wbSize: 'M Черный', price: 3500, discountedPrice: 3000, quantity: 30 }] }, { nmID: 555666777, vendorCode: 'SKU003', title: 'Блузка белая офисная', description: 'Классическая белая блузка для офиса', brand: 'Office', object: 'Блузки', parent: 'Одежда', countryProduction: 'Турция', supplierVendorCode: 'SUPPLIER-003', mediaFiles: ['/api/placeholder/400/405'], sizes: [{ chrtID: 555666, techSize: 'L', wbSize: 'L Белый', price: 1800, discountedPrice: 1500, quantity: 40 }] }, { nmID: 444333222, vendorCode: 'SKU004', title: 'Джинсы женские синие', description: 'Классические женские джинсы прямого кроя', brand: 'Denim', object: 'Джинсы', parent: 'Одежда', countryProduction: 'Бангладеш', supplierVendorCode: 'SUPPLIER-004', mediaFiles: ['/api/placeholder/400/408'], sizes: [{ chrtID: 444333, techSize: '30', wbSize: '30 Синий', price: 2800, discountedPrice: 2300, quantity: 25 }] }, { nmID: 111222333, vendorCode: 'SKU005', title: 'Кроссовки женские белые', description: 'Удобные женские кроссовки для повседневной носки', brand: 'Sport', object: 'Кроссовки', parent: 'Обувь', countryProduction: 'Вьетнам', supplierVendorCode: 'SUPPLIER-005', mediaFiles: ['/api/placeholder/400/410'], sizes: [{ chrtID: 111222, techSize: '37', wbSize: '37 Белый', price: 3200, discountedPrice: 2800, quantity: 35 }] }, { nmID: 777888999, vendorCode: 'SKU006', title: 'Сумка женская черная', description: 'Стильная женская сумка из экокожи', brand: 'Accessories', object: 'Сумки', parent: 'Аксессуары', countryProduction: 'Китай', supplierVendorCode: 'SUPPLIER-006', mediaFiles: ['/api/placeholder/400/411'], sizes: [{ chrtID: 777888, techSize: 'Универсальный', wbSize: 'Черный', price: 1500, discountedPrice: 1200, quantity: 60 }] } ] // Загружаем товары при инициализации useEffect(() => { loadCards() }, [user]) const loadCards = async () => { setLoading(true) try { const wbApiKey = user?.organization?.apiKeys?.find(key => key.marketplace === 'WILDBERRIES') if (wbApiKey?.isActive) { 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, 20) setWbCards(cards) console.log('Загружено карточек из WB API:', cards.length) return } } // Если API ключ не настроен, показываем моковые данные console.log('API ключ WB не настроен, показываем моковые данные') setWbCards(getMockCards()) } catch (error) { console.error('Ошибка загрузки карточек WB:', error) // При ошибке API показываем моковые данные setWbCards(getMockCards()) } finally { setLoading(false) } } const searchCards = async () => { if (!searchTerm.trim()) { loadCards() return } setLoading(true) try { const wbApiKey = user?.organization?.apiKeys?.find(key => key.marketplace === 'WILDBERRIES') if (wbApiKey?.isActive) { 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, 20) 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.nmID.toString().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.nmID.toString().includes(searchTerm.toLowerCase()) || card.object?.toLowerCase().includes(searchTerm.toLowerCase()) ) setWbCards(filteredCards) console.log('Найдено моковых товаров (fallback):', filteredCards.length) } finally { setLoading(false) } } // Функции для работы с услугами и расходниками const loadOrganizationServices = async (organizationId: string) => { if (organizationServices[organizationId]) return try { const response = await apolloClient.query({ query: GET_COUNTERPARTY_SERVICES, variables: { organizationId } }) if (response.data?.counterpartyServices) { setOrganizationServices(prev => ({ ...prev, [organizationId]: response.data.counterpartyServices })) } } catch (error) { console.error('Ошибка загрузки услуг организации:', error) } } const loadOrganizationSupplies = async (organizationId: string) => { if (organizationSupplies[organizationId]) return try { const response = await apolloClient.query({ query: GET_COUNTERPARTY_SUPPLIES, variables: { organizationId } }) if (response.data?.counterpartySupplies) { setOrganizationSupplies(prev => ({ ...prev, [organizationId]: response.data.counterpartySupplies })) } } catch (error) { console.error('Ошибка загрузки расходников организации:', error) } } // Работа с товарами поставки const addToSupply = (card: WildberriesCard) => { const existingItem = supplyItems.find(item => item.card.nmID === card.nmID) if (existingItem) { toast.info('Товар уже добавлен в поставку') return } const newItem: SupplyItem = { card, quantity: 1200, pricePerUnit: 0, totalPrice: 0, supplierId: '' } setSupplyItems(prev => [...prev, newItem]) toast.success('Товар добавлен в поставку') } const removeFromSupply = (nmID: number) => { setSupplyItems(prev => prev.filter(item => item.card.nmID !== nmID)) } const updateSupplyItem = (nmID: number, field: keyof SupplyItem, value: string | number) => { setSupplyItems(prev => prev.map(item => { if (item.card.nmID === nmID) { const updatedItem = { ...item, [field]: value } if (field === 'quantity' || field === 'pricePerUnit') { updatedItem.totalPrice = updatedItem.quantity * updatedItem.pricePerUnit } return updatedItem } return item })) } // Работа с поставщиками const handleCreateSupplier = () => { if (!newSupplier.name || !newSupplier.contactName || !newSupplier.phone) { toast.error('Заполните обязательные поля') return } const supplier: Supplier = { id: Date.now().toString(), ...newSupplier } setSuppliers(prev => [...prev, supplier]) setNewSupplier({ name: '', contactName: '', phone: '', market: '', address: '', place: '', telegram: '' }) setShowSupplierModal(false) toast.success('Поставщик создан') } // Расчеты const getTotalQuantity = () => { return supplyItems.reduce((sum, item) => sum + item.quantity, 0) } const getTotalItemsCost = () => { return supplyItems.reduce((sum, item) => sum + item.totalPrice, 0) } const getServicesCost = () => { if (!selectedFulfillmentOrg || selectedServices.length === 0) return 0 const services = organizationServices[selectedFulfillmentOrg] || [] return selectedServices.reduce((sum, serviceId) => { const service = services.find(s => s.id === serviceId) return sum + (service ? service.price : 0) }, 0) * getTotalQuantity() } const getConsumablesCost = () => { if (!selectedFulfillmentOrg || selectedConsumables.length === 0) return 0 const supplies = organizationSupplies[selectedFulfillmentOrg] || [] return selectedConsumables.reduce((sum, supplyId) => { const supply = supplies.find(s => s.id === supplyId) return sum + (supply ? supply.price : 0) }, 0) * getTotalQuantity() } const formatCurrency = (amount: number) => { return new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB', minimumFractionDigits: 0 }).format(amount) } // Создание поставки const handleCreateSupplyInternal = async () => { if (supplyItems.length === 0) { toast.error('Добавьте товары в поставку') return } if (!deliveryDate) { toast.error('Выберите дату поставки') return } if (supplyItems.some(item => item.quantity <= 0 || item.pricePerUnit <= 0)) { toast.error('Укажите количество и цену для всех товаров') return } try { const supplyInput = { deliveryDate: deliveryDate.toISOString().split('T')[0], cards: supplyItems.map(item => ({ nmId: item.card.nmID.toString(), vendorCode: item.card.vendorCode, title: item.card.title, brand: item.card.brand, selectedQuantity: item.quantity, customPrice: item.totalPrice, selectedFulfillmentOrg: selectedFulfillmentOrg, selectedFulfillmentServices: selectedServices, selectedConsumableOrg: selectedFulfillmentOrg, selectedConsumableServices: selectedConsumables, deliveryDate: deliveryDate.toISOString().split('T')[0], mediaFiles: item.card.mediaFiles })) } await createSupply({ variables: { input: supplyInput } }) toast.success('Поставка успешно создана!') onComplete() } catch (error) { console.error('Error creating supply:', error) toast.error('Ошибка при создании поставки') } } // Обработка внешнего вызова создания поставки React.useEffect(() => { if (isCreatingSupply) { handleCreateSupplyInternal() } }, [isCreatingSupply]) // Обновление статуса возможности создания поставки React.useEffect(() => { const canCreate = supplyItems.length > 0 && deliveryDate !== null && supplyItems.every(item => item.quantity > 0 && item.pricePerUnit > 0) if (onCanCreateSupplyChange) { onCanCreateSupplyChange(canCreate) } }, [supplyItems, deliveryDate, onCanCreateSupplyChange]) const fulfillmentOrgs = (counterpartiesData?.myCounterparties || []).filter((org: Organization) => org.type === 'FULFILLMENT') const markets = [ { value: 'sadovod', label: 'Садовод' }, { value: 'tyak-moscow', label: 'ТЯК Москва' } ] return (
{/* Основные настройки */}
{/* Дата поставки */}
setDeliveryDate(date || undefined)} minDate={new Date()} inline locale="ru" />
{/* Фулфилмент */}
{/* Показатели */}
Товаров
{getTotalQuantity()}
Стоимость
{formatCurrency(getTotalItemsCost()).replace(' ₽', '₽')}
Услуги ФФ
{formatCurrency(getServicesCost() + getConsumablesCost()).replace(' ₽', '₽')}
{/* Поиск и карточки */}
setSearchTerm(e.target.value)} className="bg-white/5 border-white/20 text-white placeholder-white/50 h-7 text-xs flex-1" onKeyPress={(e) => e.key === 'Enter' && searchCards()} />
{loading ? ( [...Array(6)].map((_, i) => (
)) ) : ( wbCards.map((card) => { const isInSupply = supplyItems.some(item => item.card.nmID === card.nmID) return (
addToSupply(card)} > {card.title} {isInSupply && (
)}
) }) )}
{/* Услуги и расходники в одной строке */} {selectedFulfillmentOrg && (
Услуги фулфилмента:
{organizationServices[selectedFulfillmentOrg] ? ( organizationServices[selectedFulfillmentOrg].map((service) => ( )) ) : ( Загрузка... )}
Расходные материалы:
{organizationSupplies[selectedFulfillmentOrg] ? ( organizationSupplies[selectedFulfillmentOrg].map((supply) => ( )) ) : ( Загрузка... )}
)} {/* Компактная таблица товаров */}
Товары в поставке
{supplyItems.length === 0 ? (

Добавьте товары из карточек выше

) : (
{supplyItems.map((item) => (
{/* Товар */}
{item.card.title}
{item.card.title}
Арт: {item.card.vendorCode}
{/* Количество */}
updateSupplyItem(item.card.nmID, 'quantity', parseInt(e.target.value) || 0)} className="bg-purple-500 border-0 text-white text-center h-6 text-xs font-bold" min="1" />
{/* Цена */}
updateSupplyItem(item.card.nmID, 'pricePerUnit', parseFloat(e.target.value) || 0)} className="bg-white/10 border-white/20 text-white text-center h-6 text-xs" placeholder="Цена" />
{/* Поставщик */}
{/* Сумма и удаление */}
{formatCurrency(item.totalPrice).replace(' ₽', '₽')}
))}
)}
{/* Модальное окно создания поставщика */} Создать поставщика
setNewSupplier(prev => ({ ...prev, name: e.target.value }))} className="bg-white/10 border-white/20 text-white h-8 text-xs" placeholder="Название" />
setNewSupplier(prev => ({ ...prev, contactName: e.target.value }))} className="bg-white/10 border-white/20 text-white h-8 text-xs" placeholder="Имя" />
setNewSupplier(prev => ({ ...prev, phone: e.target.value }))} className="bg-white/10 border-white/20 text-white h-8 text-xs" placeholder="+7 999 123-45-67" />
setNewSupplier(prev => ({ ...prev, address: e.target.value }))} className="bg-white/10 border-white/20 text-white h-8 text-xs" placeholder="Адрес" />
setNewSupplier(prev => ({ ...prev, place: e.target.value }))} className="bg-white/10 border-white/20 text-white h-8 text-xs" placeholder="Павильон/место" />
setNewSupplier(prev => ({ ...prev, telegram: e.target.value }))} className="bg-white/10 border-white/20 text-white h-8 text-xs" placeholder="@username" />
) }