From f478754befb2f969f985da187f79ecce323feb2b Mon Sep 17 00:00:00 2001 From: Bivekich Date: Wed, 23 Jul 2025 16:05:44 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=20CreateSupplyPage:=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20=D0=BD=D0=B5=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D1=83=D0=B5=D0=BC=D1=8B=D0=B5=20=D1=81=D0=BE=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D1=8F=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D1=8D=D1=84=D1=84=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D1=8B,=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20=D1=84?= =?UTF-8?q?=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=B8=20=D1=83=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8=D0=B5=D0=BC.=20=D0=92?= =?UTF-8?q?=D0=BD=D0=B5=D0=B4=D1=80=D0=B5=D0=BD=20=D0=BD=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D0=B9=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=20DirectSupplyCreation=20=D0=B4=D0=BB=D1=8F=20=D1=83=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=86=D0=B5=D1=81=D1=81=D0=B0=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D0=BA?= =?UTF-8?q?=D0=B8.=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=20TabsHe?= =?UTF-8?q?ader=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D0=B6=D0=BA=D0=B8=20=D0=BD=D0=BE=D0=B2=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B8=20=D1=81=D0=BE=D0=B7=D0=B4?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=D1=81=D1=82=D0=B0=D0=B2?= =?UTF-8?q?=D0=BA=D0=B8=20=D1=81=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=BE?= =?UTF-8?q?=D0=B9=20=D0=B4=D0=BB=D1=8F=20=D0=B7=D0=B0=D0=BF=D1=83=D1=81?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BF=D1=80=D0=BE=D1=86=D0=B5=D1=81=D1=81=D0=B0?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../supplies/create-supply-page.tsx | 72 +- .../supplies/direct-supply-creation.tsx | 896 ++++++++++++++++++ src/components/supplies/tabs-header.tsx | 23 +- 3 files changed, 953 insertions(+), 38 deletions(-) create mode 100644 src/components/supplies/direct-supply-creation.tsx diff --git a/src/components/supplies/create-supply-page.tsx b/src/components/supplies/create-supply-page.tsx index 3ec2d8a..50001ce 100644 --- a/src/components/supplies/create-supply-page.tsx +++ b/src/components/supplies/create-supply-page.tsx @@ -1,24 +1,23 @@ "use client" -import React, { useState, useEffect } from 'react' -import { useQuery } from '@apollo/client' +import React, { useState } from 'react' import { Sidebar } from '@/components/dashboard/sidebar' import { useSidebar } from '@/hooks/useSidebar' -import { GET_MY_COUNTERPARTIES, GET_ALL_PRODUCTS } from '@/graphql/queries' import { useRouter } from 'next/navigation' -import { WBProductCards } from './wb-product-cards' -import { SelectedCard as WBSelectedCard } from '@/types/supplies' +import { DirectSupplyCreation } from './direct-supply-creation' +import { WholesalerProductsPage } from './wholesaler-products-page' import { TabsHeader } from './tabs-header' import { WholesalerGrid } from './wholesaler-grid' import { CartSummary } from './cart-summary' import { FloatingCart } from './floating-cart' -import { WholesalerProductsPage } from './wholesaler-products-page' import { WholesalerForCreation, WholesalerProduct, SelectedProduct, CounterpartyWholesaler } from './types' +import { useQuery } from '@apollo/client' +import { GET_MY_COUNTERPARTIES, GET_ALL_PRODUCTS } from '@/graphql/queries' export function CreateSupplyPage() { const router = useRouter() @@ -26,9 +25,10 @@ export function CreateSupplyPage() { const [activeTab, setActiveTab] = useState<'cards' | 'wholesaler'>('cards') const [selectedWholesaler, setSelectedWholesaler] = useState(null) const [selectedProducts, setSelectedProducts] = useState([]) - const [selectedCards, setSelectedCards] = useState([]) const [showSummary, setShowSummary] = useState(false) const [searchQuery, setSearchQuery] = useState('') + const [canCreateSupply, setCanCreateSupply] = useState(false) + const [isCreatingSupply, setIsCreatingSupply] = useState(false) // Загружаем контрагентов-оптовиков const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(GET_MY_COUNTERPARTIES) @@ -50,13 +50,6 @@ export function CreateSupplyPage() { ) : [] - // Автоматически показываем корзину если в ней есть товары и мы на этапе выбора оптовиков - useEffect(() => { - if (activeTab === 'wholesaler' && !selectedWholesaler && selectedProducts.length > 0) { - setShowSummary(true) - } - }, [activeTab, selectedWholesaler, selectedProducts.length]) - const formatCurrency = (amount: number) => { return new Intl.NumberFormat('ru-RU', { style: 'currency', @@ -106,12 +99,6 @@ export function CreateSupplyPage() { return selectedProducts.reduce((sum, product) => sum + product.selectedQuantity, 0) } - const handleCardsComplete = (cards: WBSelectedCard[]) => { - setSelectedCards(cards) - console.log('Карточки товаров выбраны:', cards) - router.push('/supplies') - } - const handleCreateSupply = () => { if (activeTab === 'cards') { console.log('Создание поставки с карточками Wildberries') @@ -146,6 +133,23 @@ export function CreateSupplyPage() { ) } + const handleSupplyComplete = () => { + router.push('/supplies') + } + + const handleCreateSupplyClick = () => { + setIsCreatingSupply(true) + } + + const handleCanCreateSupplyChange = (canCreate: boolean) => { + setCanCreateSupply(canCreate) + } + + const handleSupplyCompleted = () => { + setIsCreatingSupply(false) + handleSupplyComplete() + } + // Рендер страницы товаров оптовика if (selectedWholesaler && activeTab === 'wholesaler') { return ( @@ -175,13 +179,7 @@ export function CreateSupplyPage() { onTabChange={setActiveTab} onBack={() => router.push('/supplies')} cartInfo={ - activeTab === 'cards' && selectedCards.length > 0 - ? { - itemCount: selectedCards.reduce((sum, sc) => sum + sc.selectedQuantity, 0), - totalAmount: 0, - formatCurrency - } - : activeTab === 'wholesaler' && selectedProducts.length > 0 + activeTab === 'wholesaler' && selectedProducts.length > 0 ? { itemCount: selectedProducts.length, totalAmount: getTotalAmount(), @@ -190,17 +188,19 @@ export function CreateSupplyPage() { : undefined } onCartClick={() => setShowSummary(true)} + onCreateSupply={handleCreateSupplyClick} + canCreateSupply={canCreateSupply} + isCreatingSupply={isCreatingSupply} /> - {/* Контент карточек */} + {/* Контент карточек - новый компонент прямого создания поставки */} {activeTab === 'cards' && ( - router.push('/supplies')} - onComplete={handleCardsComplete} - showSummary={showSummary} - setShowSummary={setShowSummary} - selectedCards={selectedCards} - setSelectedCards={setSelectedCards} + )} @@ -229,7 +229,7 @@ export function CreateSupplyPage() { itemCount={selectedProducts.length} totalAmount={getTotalAmount()} formatCurrency={formatCurrency} - onClick={() => setShowSummary(true)} + onClick={() => setShowSummary(true)} visible={selectedProducts.length > 0 && !showSummary} /> diff --git a/src/components/supplies/direct-supply-creation.tsx b/src/components/supplies/direct-supply-creation.tsx new file mode 100644 index 0000000..3a4e927 --- /dev/null +++ b/src/components/supplies/direct-supply-creation.tsx @@ -0,0 +1,896 @@ +"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" + /> +
+ +
+ + +
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/src/components/supplies/tabs-header.tsx b/src/components/supplies/tabs-header.tsx index eaad07d..c3d7fc5 100644 --- a/src/components/supplies/tabs-header.tsx +++ b/src/components/supplies/tabs-header.tsx @@ -5,7 +5,8 @@ import { Button } from '@/components/ui/button' import { ArrowLeft, ShoppingCart, - Users + Users, + Check } from 'lucide-react' interface TabsHeaderProps { @@ -18,6 +19,9 @@ interface TabsHeaderProps { formatCurrency: (amount: number) => string } onCartClick?: () => void + onCreateSupply?: () => void + canCreateSupply?: boolean + isCreatingSupply?: boolean } export function TabsHeader({ @@ -25,7 +29,10 @@ export function TabsHeader({ onTabChange, onBack, cartInfo, - onCartClick + onCartClick, + onCreateSupply, + canCreateSupply = false, + isCreatingSupply = false }: TabsHeaderProps) { return (
@@ -52,6 +59,18 @@ export function TabsHeader({ {activeTab === 'wholesaler' && ` • ${cartInfo.formatCurrency(cartInfo.totalAmount)}`} )} + + {/* Кнопка создания поставки для таба карточек */} + {activeTab === 'cards' && onCreateSupply && ( + + )}