'use client' import React, { useState, useEffect } from 'react' import DatePicker from 'react-datepicker' import { toast } from 'sonner' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { PhoneInput } from '@/components/ui/phone-input' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { formatPhoneInput, isValidPhone, formatNameInput } from '@/lib/input-masks' import 'react-datepicker/dist/react-datepicker.css' import { Search, Plus, Calendar as CalendarIcon, Package, Check, X, User, Phone, MapPin, Building, Truck, } 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, GET_SUPPLY_SUPPLIERS, } from '@/graphql/queries' import { CREATE_WILDBERRIES_SUPPLY, CREATE_SUPPLY_SUPPLIER } from '@/graphql/mutations' import { format } from 'date-fns' import { ru } from 'date-fns/locale' import { WildberriesCard } from '@/types/supplies' // Добавляем CSS стили для line-clamp const lineClampStyles = ` .line-clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } ` interface SupplyItem { card: WildberriesCard quantity: number pricePerUnit: number totalPrice: number supplierId: string priceType: 'perUnit' | 'total' // за штуку или за общее количество } 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 selectedFulfillmentId?: string onServicesCostChange?: (cost: number) => void onItemsPriceChange?: (totalPrice: number) => void onItemsCountChange?: (hasItems: boolean) => void onConsumablesCostChange?: (cost: number) => void onVolumeChange?: (totalVolume: number) => void onSuppliersChange?: (suppliers: unknown[]) => void } export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupply, isCreatingSupply, onCanCreateSupplyChange, selectedFulfillmentId, onServicesCostChange, onItemsPriceChange, onItemsCountChange, onConsumablesCostChange, onVolumeChange, onSuppliersChange, }: DirectSupplyCreationProps) { const { user } = useAuth() // Новые состояния для блока создания поставки const [deliveryDate, setDeliveryDate] = useState('') const [selectedFulfillment, setSelectedFulfillment] = useState('') const [goodsQuantity, setGoodsQuantity] = useState(1200) const [goodsVolume, setGoodsVolume] = useState(0) const [cargoPlaces, setCargoPlaces] = useState(0) const [goodsPrice, setGoodsPrice] = useState(0) const [fulfillmentServicesPrice, setFulfillmentServicesPrice] = useState(0) const [logisticsPrice, setLogisticsPrice] = useState(0) // Оригинальные состояния для товаров const [searchTerm, setSearchTerm] = useState('') const [loading, setLoading] = useState(false) const [wbCards, setWbCards] = useState([]) const [supplyItems, setSupplyItems] = useState([]) // Общие настройки (оригинальные) const [deliveryDateOriginal, setDeliveryDateOriginal] = 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 [supplierErrors, setSupplierErrors] = useState({ name: '', contactName: '', phone: '', telegram: '', }) // Данные для фулфилмента const [organizationServices, setOrganizationServices] = useState<{ [orgId: string]: FulfillmentService[] }>({}) const [organizationSupplies, setOrganizationSupplies] = useState<{ [orgId: string]: FulfillmentService[] }>({}) // Загружаем контрагентов-фулфилментов const { data: counterpartiesData } = useQuery(GET_MY_COUNTERPARTIES) const { data: suppliersData, refetch: refetchSuppliers } = useQuery(GET_SUPPLY_SUPPLIERS) // Мутации 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 [createSupplierMutation, { loading: creatingSupplier }] = useMutation(CREATE_SUPPLY_SUPPLIER, { onCompleted: (data) => { if (data.createSupplySupplier.success) { toast.success('Поставщик добавлен успешно!') // Обновляем список поставщиков из БД refetchSuppliers() // Очищаем форму setNewSupplier({ name: '', contactName: '', phone: '', market: '', address: '', place: '', telegram: '', }) setSupplierErrors({ name: '', contactName: '', phone: '', telegram: '', }) setShowSupplierModal(false) } else { toast.error(data.createSupplySupplier.message || 'Ошибка при добавлении поставщика') } }, onError: (error) => { toast.error('Ошибка при создании поставщика') console.error('Error creating supplier:', error) }, }) // Моковые данные товаров для демонстрации const getMockCards = (): WildberriesCard[] => [ { nmID: 123456789, vendorCode: 'SKU001', title: 'Платье летнее розовое', description: 'Легкое летнее платье из натурального хлопка', brand: 'Fashion', object: 'Платья', parent: 'Одежда', countryProduction: 'Россия', supplierVendorCode: 'SUPPLIER-001', mediaFiles: ['/api/placeholder/400/400'], dimensions: { length: 30, // 30 см width: 25, // 25 см height: 5, // 5 см weightBrutto: 0.3, // 300г isValid: true, }, 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'], dimensions: { length: 35, // 35 см width: 28, // 28 см height: 6, // 6 см weightBrutto: 0.4, // 400г isValid: true, }, 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]) // Загружаем услуги и расходники при выборе фулфилмента useEffect(() => { if (selectedFulfillmentId) { console.warn('Загружаем услуги и расходники для фулфилмента:', selectedFulfillmentId) loadOrganizationServices(selectedFulfillmentId) loadOrganizationSupplies(selectedFulfillmentId) } }, [selectedFulfillmentId]) // Уведомляем об изменении стоимости услуг useEffect(() => { if (onServicesCostChange) { const servicesCost = getServicesCost() onServicesCostChange(servicesCost) } }, [selectedServices, selectedFulfillmentId, onServicesCostChange]) // Уведомляем об изменении общей стоимости товаров useEffect(() => { if (onItemsPriceChange) { const totalItemsPrice = getTotalItemsCost() onItemsPriceChange(totalItemsPrice) } }, [supplyItems, onItemsPriceChange]) // Уведомляем об изменении количества товаров useEffect(() => { if (onItemsCountChange) { onItemsCountChange(supplyItems.length > 0) } }, [supplyItems.length, onItemsCountChange]) // Уведомляем об изменении стоимости расходников useEffect(() => { if (onConsumablesCostChange) { const consumablesCost = getConsumablesCost() onConsumablesCostChange(consumablesCost) } }, [selectedConsumables, selectedFulfillmentId, supplyItems.length, onConsumablesCostChange]) 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.warn('Загружаем карточки из WB API...') const cards = await WildberriesService.getAllCards(apiToken, 500) // Логируем информацию о размерах товаров cards.forEach((card) => { if (card.dimensions) { const volume = (card.dimensions.length / 100) * (card.dimensions.width / 100) * (card.dimensions.height / 100) console.warn( `WB API: Карточка ${card.nmID} - размеры: ${card.dimensions.length}x${card.dimensions.width}x${ card.dimensions.height } см, объем: ${volume.toFixed(6)} м³`, ) } else { console.warn(`WB API: Карточка ${card.nmID} - размеры отсутствуют`) } }) setWbCards(cards) console.warn('Загружено карточек из WB API:', cards.length) console.warn('Карточки с размерами:', cards.filter((card) => card.dimensions).length) return } } // Если API ключ не настроен, показываем моковые данные console.warn('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.warn('Поиск в WB API:', searchTerm) const cards = await WildberriesService.searchCards(apiToken, searchTerm, 100) // Логируем информацию о размерах найденных товаров cards.forEach((card) => { if (card.dimensions) { const volume = (card.dimensions.length / 100) * (card.dimensions.width / 100) * (card.dimensions.height / 100) console.warn( `WB API: Найденная карточка ${card.nmID} - размеры: ${ card.dimensions.length }x${card.dimensions.width}x${card.dimensions.height} см, объем: ${volume.toFixed(6)} м³`, ) } else { console.warn(`WB API: Найденная карточка ${card.nmID} - размеры отсутствуют`) } }) setWbCards(cards) console.warn('Найдено карточек в WB API:', cards.length) console.warn('Найденные карточки с размерами:', cards.filter((card) => card.dimensions).length) return } } // Если API ключ не настроен, ищем в моковых данных console.warn('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.warn('Найдено моковых товаров:', 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.warn('Найдено моковых товаров (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: 0, pricePerUnit: 0, totalPrice: 0, supplierId: '', priceType: 'perUnit', } 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) => { const newItems = prev.map((item) => { if (item.card.nmID === nmID) { const updatedItem = { ...item, [field]: value } // Пересчитываем totalPrice в зависимости от типа цены if (field === 'quantity' || field === 'pricePerUnit' || field === 'priceType') { if (updatedItem.priceType === 'perUnit') { // Цена за штуку - умножаем на количество updatedItem.totalPrice = updatedItem.quantity * updatedItem.pricePerUnit } else { // Цена за общее количество - pricePerUnit становится общей ценой updatedItem.totalPrice = updatedItem.pricePerUnit } } return updatedItem } return item }) // Если изменился поставщик, уведомляем родительский компонент асинхронно if (field === 'supplierId' && onSuppliersChange) { // Создаем список поставщиков с информацией о выборе const suppliersInfo = suppliers.map((supplier) => ({ ...supplier, selected: newItems.some((item) => item.supplierId === supplier.id), })) console.warn('Обновление поставщиков из updateSupplyItem:', suppliersInfo) // Вызываем асинхронно чтобы не обновлять состояние во время рендера setTimeout(() => { onSuppliersChange(suppliersInfo) }, 0) } return newItems }) } // Валидация полей поставщика const validateSupplierField = (field: string, value: string) => { let error = '' switch (field) { case 'name': if (!value.trim()) error = 'Название обязательно' else if (value.length < 2) error = 'Минимум 2 символа' break case 'contactName': if (!value.trim()) error = 'Имя обязательно' else if (value.length < 2) error = 'Минимум 2 символа' break case 'phone': if (!value.trim()) error = 'Телефон обязателен' else if (!isValidPhone(value)) error = 'Неверный формат телефона' break case 'telegram': if (value && !value.match(/^@[a-zA-Z0-9_]{5,32}$/)) { error = 'Формат: @username (5-32 символа)' } break } setSupplierErrors((prev) => ({ ...prev, [field]: error })) return error === '' } const validateAllSupplierFields = () => { const nameValid = validateSupplierField('name', newSupplier.name) const contactNameValid = validateSupplierField('contactName', newSupplier.contactName) const phoneValid = validateSupplierField('phone', newSupplier.phone) const telegramValid = validateSupplierField('telegram', newSupplier.telegram) return nameValid && contactNameValid && phoneValid && telegramValid } // Работа с поставщиками const handleCreateSupplier = async () => { if (!validateAllSupplierFields()) { toast.error('Исправьте ошибки в форме') return } try { await createSupplierMutation({ variables: { input: { name: newSupplier.name, contactName: newSupplier.contactName, phone: newSupplier.phone, market: newSupplier.market || null, address: newSupplier.address || null, place: newSupplier.place || null, telegram: newSupplier.telegram || null, }, }, }) } catch (error) { // Ошибка обрабатывается в onError мутации } } // Расчеты для нового блока const getTotalSum = () => { return goodsPrice + fulfillmentServicesPrice + logisticsPrice } // Оригинальные расчеты const getTotalQuantity = () => { return supplyItems.reduce((sum, item) => sum + item.quantity, 0) } // Функция для расчета объема одного товара в м³ const calculateItemVolume = (card: WildberriesCard): number => { if (!card.dimensions) return 0 const { length, width, height } = card.dimensions // Проверяем что все размеры указаны и больше 0 if (!length || !width || !height || length <= 0 || width <= 0 || height <= 0) { return 0 } // Переводим из сантиметров в метры и рассчитываем объем const volumeInM3 = (length / 100) * (width / 100) * (height / 100) return volumeInM3 } // Функция для расчета общего объема всех товаров в поставке const getTotalVolume = () => { return supplyItems.reduce((totalVolume, item) => { const itemVolume = calculateItemVolume(item.card) return totalVolume + itemVolume * item.quantity }, 0) } const getTotalItemsCost = () => { return supplyItems.reduce((sum, item) => sum + item.totalPrice, 0) } const getServicesCost = () => { if (!selectedFulfillmentId || selectedServices.length === 0) return 0 const services = organizationServices[selectedFulfillmentId] || [] return ( selectedServices.reduce((sum, serviceId) => { const service = services.find((s) => s.id === serviceId) return sum + (service ? service.price : 0) }, 0) * getTotalQuantity() ) } const getConsumablesCost = () => { if (!selectedFulfillmentId || selectedConsumables.length === 0) return 0 const supplies = organizationSupplies[selectedFulfillmentId] || [] 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 (!deliveryDateOriginal) { toast.error('Выберите дату поставки') return } if (supplyItems.some((item) => item.quantity <= 0 || item.pricePerUnit <= 0)) { toast.error('Укажите количество и цену для всех товаров') return } try { const supplyInput = { deliveryDate: deliveryDateOriginal.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: deliveryDateOriginal.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 totalVolume = getTotalVolume() if (onVolumeChange) { onVolumeChange(totalVolume) } }, [supplyItems, onVolumeChange]) // Загрузка поставщиков из правильного источника React.useEffect(() => { if (suppliersData?.supplySuppliers) { console.warn('Загружаем поставщиков из БД:', suppliersData.supplySuppliers) setSuppliers(suppliersData.supplySuppliers) // Проверяем есть ли уже выбранные поставщики и уведомляем родителя if (onSuppliersChange && supplyItems.length > 0) { const suppliersInfo = suppliersData.supplySuppliers.map((supplier: { id: string; selected?: boolean }) => ({ ...supplier, selected: supplyItems.some((item) => item.supplierId === supplier.id), })) if (suppliersInfo.some((s: { selected?: boolean }) => s.selected)) { console.warn('Найдены выбранные поставщики при загрузке:', suppliersInfo) // Вызываем асинхронно чтобы не обновлять состояние во время рендера setTimeout(() => { onSuppliersChange(suppliersInfo) }, 0) } } } }, [suppliersData]) // Обновление статуса возможности создания поставки React.useEffect(() => { const canCreate = supplyItems.length > 0 && deliveryDateOriginal !== null && supplyItems.every((item) => item.quantity > 0 && item.pricePerUnit > 0) if (onCanCreateSupplyChange) { onCanCreateSupplyChange(canCreate) } }, [supplyItems, deliveryDateOriginal, onCanCreateSupplyChange]) const fulfillmentOrgs = (counterpartiesData?.myCounterparties || []).filter( (org: Organization) => org.type === 'FULFILLMENT', ) const markets = [ { value: 'sadovod', label: 'Садовод' }, { value: 'tyak-moscow', label: 'ТЯК Москва' }, ] return ( <>
{/* Элегантный блок поиска и товаров */}
{/* Главная карточка с градиентом */}
{/* Компактный заголовок с поиском */}

Каталог товаров

Найдено: {wbCards.length}

{/* Поиск в заголовке */}
setSearchTerm(e.target.value)} className="pl-3 pr-16 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:bg-white/15 focus:border-white/40 text-sm h-8" onKeyPress={(e) => e.key === 'Enter' && searchCards()} />
{/* Статистика в поставке */} {supplyItems.length > 0 && (
В поставке: {supplyItems.length}
)}
{/* Сетка товаров */}
{loading ? ( // Красивые skeleton-карточки [...Array(16)].map((_, i) => (
)) ) : wbCards.length > 0 ? ( // Красивые карточки товаров wbCards.map((card) => { const isInSupply = supplyItems.some((item) => item.card.nmID === card.nmID) return (
addToSupply(card)} > {/* Карточка товара */}
{card.title} {/* Градиентный оверлей */}
{/* Информация при наведении */}

{card.title}

WB: {card.nmID}

{/* Индикаторы */} {isInSupply ? (
) : (
)} {/* Эффект при клике */}
{/* Название под карточкой */}

{card.title}

) }) ) : ( // Пустое состояние

{searchTerm ? 'Товары не найдены' : 'Начните поиск товаров'}

{searchTerm ? 'Попробуйте изменить поисковый запрос' : 'Введите название товара в поле поиска'}

)}
{/* Декоративные элементы */}
{/* Услуги и расходники в одной строке */} {selectedFulfillmentOrg && (
Услуги фулфилмента:
{organizationServices[selectedFulfillmentOrg] ? ( organizationServices[selectedFulfillmentOrg].map((service) => ( )) ) : ( Загрузка... )}
Расходные материалы:
{organizationSupplies[selectedFulfillmentOrg] ? ( organizationSupplies[selectedFulfillmentOrg].map((supply) => ( )) ) : ( Загрузка... )}
)} {/* Модуль товаров в поставке - растягивается до низа */}
Товары в поставке {supplyItems.length > 0 && ( ∑ {getTotalVolume().toFixed(4)} м³ )}
{supplyItems.length === 0 ? (

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

) : (
{supplyItems.map((item) => ( {/* Компактный заголовок товара */}
{item.card.title}
WB: {item.card.nmID} {calculateItemVolume(item.card) > 0 ? ( | {(calculateItemVolume(item.card) * item.quantity).toFixed(4)} м³ ) : ( | размеры не указаны )}
{/* Компактные названия блоков */}
Товар
Параметры
Заказать
Цена
Услуги фулфилмента
Поставщик
Расходники фулфилмента
Расходники
{/* Компактная сетка блоков */}
{/* Блок 1: Картинка товара */}
{item.card.title}
{/* Блок 2: Параметры */}
{/* Создаем массив валидных параметров */} {(() => { const params = [] // Бренд if (item.card.brand && item.card.brand.trim() && item.card.brand !== '0') { params.push({ value: item.card.brand, color: 'bg-blue-500/80', key: 'brand', }) } // Категория (объект) if (item.card.object && item.card.object.trim() && item.card.object !== '0') { params.push({ value: item.card.object, color: 'bg-green-500/80', key: 'object', }) } // Страна (только если не пустая и не 0) if ( item.card.countryProduction && item.card.countryProduction.trim() && item.card.countryProduction !== '0' ) { params.push({ value: item.card.countryProduction, color: 'bg-purple-500/80', key: 'country', }) } // Цена WB if (item.card.sizes?.[0]?.price && item.card.sizes[0].price > 0) { params.push({ value: formatCurrency(item.card.sizes[0].price), color: 'bg-yellow-500/80', key: 'price', }) } // Внутренний артикул if (item.card.vendorCode && item.card.vendorCode.trim() && item.card.vendorCode !== '0') { params.push({ value: item.card.vendorCode, color: 'bg-gray-500/80', key: 'vendor', }) } // НАМЕРЕННО НЕ ВКЛЮЧАЕМ techSize и wbSize так как они равны '0' return params.map((param) => ( {param.value} )) })()}
{/* Блок 3: Заказать */}
Количество
updateSupplyItem(item.card.nmID, 'quantity', parseInt(e.target.value) || 0)} className="bg-purple-500/20 border-purple-400/30 text-white text-center h-8 text-sm font-bold" min="1" />
{/* Блок 4: Цена */}
{/* Переключатель типа цены */}
updateSupplyItem(item.card.nmID, 'pricePerUnit', parseFloat(e.target.value) || 0) } className="bg-white/20 border-white/20 text-white text-center h-7 text-xs" placeholder="₽" />
Итого: {formatCurrency(item.totalPrice).replace(' ₽', '₽')}
{/* Блок 5: Услуги фулфилмента */}
{/* DEBUG */} {console.warn('DEBUG SERVICES:', { selectedFulfillmentId, hasServices: !!organizationServices[selectedFulfillmentId], servicesCount: organizationServices[selectedFulfillmentId]?.length || 0, allOrganizationServices: Object.keys(organizationServices), })} {selectedFulfillmentId && organizationServices[selectedFulfillmentId] ? ( organizationServices[selectedFulfillmentId].slice(0, 3).map((service) => ( )) ) : ( {selectedFulfillmentId ? 'Нет услуг' : 'Выберите фулфилмент'} )}
{/* Блок 6: Поставщик */}
{/* Компактная информация о выбранном поставщике */} {item.supplierId && suppliers.find((s) => s.id === item.supplierId) ? (
{suppliers.find((s) => s.id === item.supplierId)?.contactName}
{suppliers.find((s) => s.id === item.supplierId)?.phone}
) : ( )}
{/* Блок 7: Расходники фулфилмента */}
{/* DEBUG для расходников */} {console.warn('DEBUG CONSUMABLES:', { selectedFulfillmentId, hasConsumables: !!organizationSupplies[selectedFulfillmentId], consumablesCount: organizationSupplies[selectedFulfillmentId]?.length || 0, allOrganizationSupplies: Object.keys(organizationSupplies), })} {selectedFulfillmentId && organizationSupplies[selectedFulfillmentId] ? ( organizationSupplies[selectedFulfillmentId].slice(0, 3).map((supply) => ( )) ) : ( {selectedFulfillmentId ? 'Нет расходников' : 'Выберите фулфилмент'} )}
{/* Блок 8: Расходники селлера */}
))}
)}
{/* Модальное окно создания поставщика */} Добавить поставщика

Контактная информация поставщика для этой поставки

{ const value = formatNameInput(e.target.value) setNewSupplier((prev) => ({ ...prev, name: value, })) validateSupplierField('name', value) }} className={`bg-white/10 border-white/20 text-white h-8 text-xs ${ supplierErrors.name ? 'border-red-400 focus:border-red-400' : '' }`} placeholder="Название" /> {supplierErrors.name &&

{supplierErrors.name}

}
{ const value = formatNameInput(e.target.value) setNewSupplier((prev) => ({ ...prev, contactName: value, })) validateSupplierField('contactName', value) }} className={`bg-white/10 border-white/20 text-white h-8 text-xs ${ supplierErrors.contactName ? 'border-red-400 focus:border-red-400' : '' }`} placeholder="Имя" /> {supplierErrors.contactName && (

{supplierErrors.contactName}

)}
{ setNewSupplier((prev) => ({ ...prev, phone: value, })) validateSupplierField('phone', value) }} className={`bg-white/10 border-white/20 text-white h-8 text-xs ${ supplierErrors.phone ? 'border-red-400 focus:border-red-400' : '' }`} placeholder="+7 (999) 123-45-67" /> {supplierErrors.phone &&

{supplierErrors.phone}

}
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="Павильон/место" />
{ const value = e.target.value setNewSupplier((prev) => ({ ...prev, telegram: value, })) validateSupplierField('telegram', value) }} className={`bg-white/10 border-white/20 text-white h-8 text-xs ${ supplierErrors.telegram ? 'border-red-400 focus:border-red-400' : '' }`} placeholder="@username" /> {supplierErrors.telegram &&

{supplierErrors.telegram}

}
) }