Обновлен компонент CreateSupplyPage: удалены неиспользуемые состояния и эффекты, добавлены новые функции для создания поставки и управления состоянием. Внедрен новый компонент DirectSupplyCreation для упрощения процесса создания поставки. Обновлен компонент TabsHeader для поддержки новой логики создания поставки с кнопкой для запуска процесса.
This commit is contained in:
@ -1,24 +1,23 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { useQuery } from '@apollo/client'
|
|
||||||
import { Sidebar } from '@/components/dashboard/sidebar'
|
import { Sidebar } from '@/components/dashboard/sidebar'
|
||||||
import { useSidebar } from '@/hooks/useSidebar'
|
import { useSidebar } from '@/hooks/useSidebar'
|
||||||
import { GET_MY_COUNTERPARTIES, GET_ALL_PRODUCTS } from '@/graphql/queries'
|
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { WBProductCards } from './wb-product-cards'
|
import { DirectSupplyCreation } from './direct-supply-creation'
|
||||||
import { SelectedCard as WBSelectedCard } from '@/types/supplies'
|
import { WholesalerProductsPage } from './wholesaler-products-page'
|
||||||
import { TabsHeader } from './tabs-header'
|
import { TabsHeader } from './tabs-header'
|
||||||
import { WholesalerGrid } from './wholesaler-grid'
|
import { WholesalerGrid } from './wholesaler-grid'
|
||||||
import { CartSummary } from './cart-summary'
|
import { CartSummary } from './cart-summary'
|
||||||
import { FloatingCart } from './floating-cart'
|
import { FloatingCart } from './floating-cart'
|
||||||
import { WholesalerProductsPage } from './wholesaler-products-page'
|
|
||||||
import {
|
import {
|
||||||
WholesalerForCreation,
|
WholesalerForCreation,
|
||||||
WholesalerProduct,
|
WholesalerProduct,
|
||||||
SelectedProduct,
|
SelectedProduct,
|
||||||
CounterpartyWholesaler
|
CounterpartyWholesaler
|
||||||
} from './types'
|
} from './types'
|
||||||
|
import { useQuery } from '@apollo/client'
|
||||||
|
import { GET_MY_COUNTERPARTIES, GET_ALL_PRODUCTS } from '@/graphql/queries'
|
||||||
|
|
||||||
export function CreateSupplyPage() {
|
export function CreateSupplyPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -26,9 +25,10 @@ export function CreateSupplyPage() {
|
|||||||
const [activeTab, setActiveTab] = useState<'cards' | 'wholesaler'>('cards')
|
const [activeTab, setActiveTab] = useState<'cards' | 'wholesaler'>('cards')
|
||||||
const [selectedWholesaler, setSelectedWholesaler] = useState<WholesalerForCreation | null>(null)
|
const [selectedWholesaler, setSelectedWholesaler] = useState<WholesalerForCreation | null>(null)
|
||||||
const [selectedProducts, setSelectedProducts] = useState<SelectedProduct[]>([])
|
const [selectedProducts, setSelectedProducts] = useState<SelectedProduct[]>([])
|
||||||
const [selectedCards, setSelectedCards] = useState<WBSelectedCard[]>([])
|
|
||||||
const [showSummary, setShowSummary] = useState(false)
|
const [showSummary, setShowSummary] = useState(false)
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
|
const [canCreateSupply, setCanCreateSupply] = useState(false)
|
||||||
|
const [isCreatingSupply, setIsCreatingSupply] = useState(false)
|
||||||
|
|
||||||
// Загружаем контрагентов-оптовиков
|
// Загружаем контрагентов-оптовиков
|
||||||
const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(GET_MY_COUNTERPARTIES)
|
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) => {
|
const formatCurrency = (amount: number) => {
|
||||||
return new Intl.NumberFormat('ru-RU', {
|
return new Intl.NumberFormat('ru-RU', {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
@ -106,12 +99,6 @@ export function CreateSupplyPage() {
|
|||||||
return selectedProducts.reduce((sum, product) => sum + product.selectedQuantity, 0)
|
return selectedProducts.reduce((sum, product) => sum + product.selectedQuantity, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCardsComplete = (cards: WBSelectedCard[]) => {
|
|
||||||
setSelectedCards(cards)
|
|
||||||
console.log('Карточки товаров выбраны:', cards)
|
|
||||||
router.push('/supplies')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCreateSupply = () => {
|
const handleCreateSupply = () => {
|
||||||
if (activeTab === 'cards') {
|
if (activeTab === 'cards') {
|
||||||
console.log('Создание поставки с карточками Wildberries')
|
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') {
|
if (selectedWholesaler && activeTab === 'wholesaler') {
|
||||||
return (
|
return (
|
||||||
@ -175,13 +179,7 @@ export function CreateSupplyPage() {
|
|||||||
onTabChange={setActiveTab}
|
onTabChange={setActiveTab}
|
||||||
onBack={() => router.push('/supplies')}
|
onBack={() => router.push('/supplies')}
|
||||||
cartInfo={
|
cartInfo={
|
||||||
activeTab === 'cards' && selectedCards.length > 0
|
activeTab === 'wholesaler' && selectedProducts.length > 0
|
||||||
? {
|
|
||||||
itemCount: selectedCards.reduce((sum, sc) => sum + sc.selectedQuantity, 0),
|
|
||||||
totalAmount: 0,
|
|
||||||
formatCurrency
|
|
||||||
}
|
|
||||||
: activeTab === 'wholesaler' && selectedProducts.length > 0
|
|
||||||
? {
|
? {
|
||||||
itemCount: selectedProducts.length,
|
itemCount: selectedProducts.length,
|
||||||
totalAmount: getTotalAmount(),
|
totalAmount: getTotalAmount(),
|
||||||
@ -190,17 +188,19 @@ export function CreateSupplyPage() {
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
onCartClick={() => setShowSummary(true)}
|
onCartClick={() => setShowSummary(true)}
|
||||||
|
onCreateSupply={handleCreateSupplyClick}
|
||||||
|
canCreateSupply={canCreateSupply}
|
||||||
|
isCreatingSupply={isCreatingSupply}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Контент карточек */}
|
{/* Контент карточек - новый компонент прямого создания поставки */}
|
||||||
{activeTab === 'cards' && (
|
{activeTab === 'cards' && (
|
||||||
<WBProductCards
|
<DirectSupplyCreation
|
||||||
onBack={() => router.push('/supplies')}
|
onComplete={handleSupplyCompleted}
|
||||||
onComplete={handleCardsComplete}
|
onCreateSupply={handleCreateSupplyClick}
|
||||||
showSummary={showSummary}
|
canCreateSupply={canCreateSupply}
|
||||||
setShowSummary={setShowSummary}
|
isCreatingSupply={isCreatingSupply}
|
||||||
selectedCards={selectedCards}
|
onCanCreateSupplyChange={handleCanCreateSupplyChange}
|
||||||
setSelectedCards={setSelectedCards}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
896
src/components/supplies/direct-supply-creation.tsx
Normal file
896
src/components/supplies/direct-supply-creation.tsx
Normal file
@ -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<WildberriesCard[]>([])
|
||||||
|
const [supplyItems, setSupplyItems] = useState<SupplyItem[]>([])
|
||||||
|
|
||||||
|
// Общие настройки
|
||||||
|
const [deliveryDate, setDeliveryDate] = useState<Date | undefined>(undefined)
|
||||||
|
const [selectedFulfillmentOrg, setSelectedFulfillmentOrg] = useState<string>('')
|
||||||
|
const [selectedServices, setSelectedServices] = useState<string[]>([])
|
||||||
|
const [selectedConsumables, setSelectedConsumables] = useState<string[]>([])
|
||||||
|
|
||||||
|
// Поставщики
|
||||||
|
const [suppliers, setSuppliers] = useState<Supplier[]>([])
|
||||||
|
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<string, string>
|
||||||
|
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<string, string>
|
||||||
|
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 (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{/* Основные настройки */}
|
||||||
|
<Card className="bg-gradient-to-r from-purple-500 to-pink-500 p-3">
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-6 gap-3 text-xs">
|
||||||
|
{/* Дата поставки */}
|
||||||
|
<div>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<button className="w-full bg-white/20 rounded px-2 py-1 text-white hover:bg-white/30 transition-colors text-xs">
|
||||||
|
<CalendarIcon className="h-3 w-3 inline mr-1" />
|
||||||
|
{deliveryDate ? format(deliveryDate, "dd.MM") : "Дата"}
|
||||||
|
</button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0">
|
||||||
|
<DatePicker
|
||||||
|
selected={deliveryDate}
|
||||||
|
onChange={(date: Date | null) => setDeliveryDate(date || undefined)}
|
||||||
|
minDate={new Date()}
|
||||||
|
inline
|
||||||
|
locale="ru"
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Фулфилмент */}
|
||||||
|
<div className="md:col-span-2">
|
||||||
|
<Select
|
||||||
|
value={selectedFulfillmentOrg}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setSelectedFulfillmentOrg(value)
|
||||||
|
setSelectedServices([])
|
||||||
|
setSelectedConsumables([])
|
||||||
|
if (value) {
|
||||||
|
loadOrganizationServices(value)
|
||||||
|
loadOrganizationSupplies(value)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="bg-white/20 border-0 text-white h-7 text-xs">
|
||||||
|
<SelectValue placeholder="Фулфилмент" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{fulfillmentOrgs.map((org: Organization) => (
|
||||||
|
<SelectItem key={org.id} value={org.id}>
|
||||||
|
{org.name || org.fullName}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Показатели */}
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-white/80">Товаров</div>
|
||||||
|
<div className="font-bold text-white">{getTotalQuantity()}</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-white/80">Стоимость</div>
|
||||||
|
<div className="font-bold text-white">{formatCurrency(getTotalItemsCost()).replace(' ₽', '₽')}</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-white/80">Услуги ФФ</div>
|
||||||
|
<div className="font-bold text-white">{formatCurrency(getServicesCost() + getConsumablesCost()).replace(' ₽', '₽')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Поиск и карточки */}
|
||||||
|
<Card className="bg-white/10 backdrop-blur border-white/20 p-2">
|
||||||
|
<div className="flex items-center space-x-2 mb-2">
|
||||||
|
<Input
|
||||||
|
placeholder="Поиск товаров..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => 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()}
|
||||||
|
/>
|
||||||
|
<Button onClick={searchCards} disabled={loading} variant="secondary" size="sm" className="h-7 px-2 text-xs">
|
||||||
|
<Search className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex space-x-2 overflow-x-auto pb-1">
|
||||||
|
{loading ? (
|
||||||
|
[...Array(6)].map((_, i) => (
|
||||||
|
<div key={i} className="flex-shrink-0 w-16 h-20 bg-white/5 rounded animate-pulse"></div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
wbCards.map((card) => {
|
||||||
|
const isInSupply = supplyItems.some(item => item.card.nmID === card.nmID)
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={card.nmID}
|
||||||
|
className={`flex-shrink-0 w-16 cursor-pointer transition-all hover:scale-105 relative ${isInSupply ? 'ring-1 ring-purple-400' : ''}`}
|
||||||
|
onClick={() => addToSupply(card)}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={WildberriesService.getCardImage(card, 'c246x328') || '/api/placeholder/64/80'}
|
||||||
|
alt={card.title}
|
||||||
|
className="w-16 h-20 rounded object-cover"
|
||||||
|
/>
|
||||||
|
{isInSupply && (
|
||||||
|
<div className="absolute -top-1 -right-1 bg-purple-500 text-white rounded-full w-3 h-3 flex items-center justify-center text-[8px]">
|
||||||
|
✓
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Услуги и расходники в одной строке */}
|
||||||
|
{selectedFulfillmentOrg && (
|
||||||
|
<Card className="bg-white/10 backdrop-blur border-white/20 p-2">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-xs">
|
||||||
|
<div>
|
||||||
|
<div className="text-white/80 mb-1">Услуги фулфилмента:</div>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{organizationServices[selectedFulfillmentOrg] ? (
|
||||||
|
organizationServices[selectedFulfillmentOrg].map((service) => (
|
||||||
|
<label key={service.id} className="flex items-center space-x-1 cursor-pointer bg-white/5 rounded px-2 py-1 hover:bg-white/10">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedServices.includes(service.id)}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
setSelectedServices(prev => [...prev, service.id])
|
||||||
|
} else {
|
||||||
|
setSelectedServices(prev => prev.filter(id => id !== service.id))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="w-3 h-3"
|
||||||
|
/>
|
||||||
|
<span className="text-white text-xs">{service.name} ({service.price}₽)</span>
|
||||||
|
</label>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<span className="text-white/60">Загрузка...</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="text-white/80 mb-1">Расходные материалы:</div>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{organizationSupplies[selectedFulfillmentOrg] ? (
|
||||||
|
organizationSupplies[selectedFulfillmentOrg].map((supply) => (
|
||||||
|
<label key={supply.id} className="flex items-center space-x-1 cursor-pointer bg-white/5 rounded px-2 py-1 hover:bg-white/10">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedConsumables.includes(supply.id)}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
setSelectedConsumables(prev => [...prev, supply.id])
|
||||||
|
} else {
|
||||||
|
setSelectedConsumables(prev => prev.filter(id => id !== supply.id))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="w-3 h-3"
|
||||||
|
/>
|
||||||
|
<span className="text-white text-xs">{supply.name} ({supply.price}₽)</span>
|
||||||
|
</label>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<span className="text-white/60">Загрузка...</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Компактная таблица товаров */}
|
||||||
|
<Card className="bg-white/10 backdrop-blur border-white/20 p-2">
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<span className="text-white font-medium text-sm">Товары в поставке</span>
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowSupplierModal(true)}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="bg-white/5 border-white/20 text-white hover:bg-white/10 h-6 px-2 text-xs"
|
||||||
|
>
|
||||||
|
<Plus className="h-3 w-3 mr-1" />
|
||||||
|
Поставщик
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{supplyItems.length === 0 ? (
|
||||||
|
<div className="text-center py-4">
|
||||||
|
<Package className="h-6 w-6 text-white/20 mx-auto mb-1" />
|
||||||
|
<p className="text-white/60 text-xs">Добавьте товары из карточек выше</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-1">
|
||||||
|
{supplyItems.map((item) => (
|
||||||
|
<div key={item.card.nmID} className="grid grid-cols-12 gap-2 items-center bg-white/5 rounded p-2 text-xs">
|
||||||
|
{/* Товар */}
|
||||||
|
<div className="col-span-4 flex items-center space-x-2">
|
||||||
|
<img
|
||||||
|
src={WildberriesService.getCardImage(item.card, 'c246x328') || '/api/placeholder/24/24'}
|
||||||
|
alt={item.card.title}
|
||||||
|
className="w-6 h-6 rounded object-cover"
|
||||||
|
/>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<div className="text-white font-medium truncate text-xs">{item.card.title}</div>
|
||||||
|
<div className="text-white/60 text-[10px]">Арт: {item.card.vendorCode}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Количество */}
|
||||||
|
<div className="col-span-2">
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={item.quantity}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Цена */}
|
||||||
|
<div className="col-span-2">
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={item.pricePerUnit || ''}
|
||||||
|
onChange={(e) => 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="Цена"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Поставщик */}
|
||||||
|
<div className="col-span-3">
|
||||||
|
<Select
|
||||||
|
value={item.supplierId}
|
||||||
|
onValueChange={(value) => updateSupplyItem(item.card.nmID, 'supplierId', value)}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="bg-white/5 border-white/20 text-white h-6 text-xs">
|
||||||
|
<SelectValue placeholder="Поставщик" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{suppliers.map((supplier) => (
|
||||||
|
<SelectItem key={supplier.id} value={supplier.id}>
|
||||||
|
{supplier.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Сумма и удаление */}
|
||||||
|
<div className="col-span-1 flex items-center justify-between">
|
||||||
|
<span className="text-white font-bold text-xs">
|
||||||
|
{formatCurrency(item.totalPrice).replace(' ₽', '₽')}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
onClick={() => removeFromSupply(item.card.nmID)}
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
className="h-4 w-4 p-0 text-white/60 hover:text-red-400"
|
||||||
|
>
|
||||||
|
<X className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Модальное окно создания поставщика */}
|
||||||
|
<Dialog open={showSupplierModal} onOpenChange={setShowSupplierModal}>
|
||||||
|
<DialogContent className="glass-card border-white/10 max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="text-white">Создать поставщика</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<div>
|
||||||
|
<Label className="text-white/60 text-xs">Название *</Label>
|
||||||
|
<Input
|
||||||
|
value={newSupplier.name}
|
||||||
|
onChange={(e) => setNewSupplier(prev => ({ ...prev, name: e.target.value }))}
|
||||||
|
className="bg-white/10 border-white/20 text-white h-8 text-xs"
|
||||||
|
placeholder="Название"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label className="text-white/60 text-xs">Имя *</Label>
|
||||||
|
<Input
|
||||||
|
value={newSupplier.contactName}
|
||||||
|
onChange={(e) => setNewSupplier(prev => ({ ...prev, contactName: e.target.value }))}
|
||||||
|
className="bg-white/10 border-white/20 text-white h-8 text-xs"
|
||||||
|
placeholder="Имя"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<div>
|
||||||
|
<Label className="text-white/60 text-xs">Телефон *</Label>
|
||||||
|
<Input
|
||||||
|
value={newSupplier.phone}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label className="text-white/60 text-xs">Рынок</Label>
|
||||||
|
<Select
|
||||||
|
value={newSupplier.market}
|
||||||
|
onValueChange={(value) => setNewSupplier(prev => ({ ...prev, market: value }))}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="bg-white/10 border-white/20 text-white h-8 text-xs">
|
||||||
|
<SelectValue placeholder="Рынок" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{markets.map((market) => (
|
||||||
|
<SelectItem key={market.value} value={market.value}>
|
||||||
|
{market.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<div>
|
||||||
|
<Label className="text-white/60 text-xs">Адрес</Label>
|
||||||
|
<Input
|
||||||
|
value={newSupplier.address}
|
||||||
|
onChange={(e) => setNewSupplier(prev => ({ ...prev, address: e.target.value }))}
|
||||||
|
className="bg-white/10 border-white/20 text-white h-8 text-xs"
|
||||||
|
placeholder="Адрес"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label className="text-white/60 text-xs">Место</Label>
|
||||||
|
<Input
|
||||||
|
value={newSupplier.place}
|
||||||
|
onChange={(e) => setNewSupplier(prev => ({ ...prev, place: e.target.value }))}
|
||||||
|
className="bg-white/10 border-white/20 text-white h-8 text-xs"
|
||||||
|
placeholder="Павильон/место"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label className="text-white/60 text-xs">Телеграм</Label>
|
||||||
|
<Input
|
||||||
|
value={newSupplier.telegram}
|
||||||
|
onChange={(e) => setNewSupplier(prev => ({ ...prev, telegram: e.target.value }))}
|
||||||
|
className="bg-white/10 border-white/20 text-white h-8 text-xs"
|
||||||
|
placeholder="@username"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowSupplierModal(false)}
|
||||||
|
variant="outline"
|
||||||
|
className="flex-1 bg-white/5 border-white/20 text-white hover:bg-white/10 h-8 text-xs"
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleCreateSupplier}
|
||||||
|
className="flex-1 bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 h-8 text-xs"
|
||||||
|
>
|
||||||
|
Создать
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -5,7 +5,8 @@ import { Button } from '@/components/ui/button'
|
|||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ShoppingCart,
|
ShoppingCart,
|
||||||
Users
|
Users,
|
||||||
|
Check
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
|
||||||
interface TabsHeaderProps {
|
interface TabsHeaderProps {
|
||||||
@ -18,6 +19,9 @@ interface TabsHeaderProps {
|
|||||||
formatCurrency: (amount: number) => string
|
formatCurrency: (amount: number) => string
|
||||||
}
|
}
|
||||||
onCartClick?: () => void
|
onCartClick?: () => void
|
||||||
|
onCreateSupply?: () => void
|
||||||
|
canCreateSupply?: boolean
|
||||||
|
isCreatingSupply?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TabsHeader({
|
export function TabsHeader({
|
||||||
@ -25,7 +29,10 @@ export function TabsHeader({
|
|||||||
onTabChange,
|
onTabChange,
|
||||||
onBack,
|
onBack,
|
||||||
cartInfo,
|
cartInfo,
|
||||||
onCartClick
|
onCartClick,
|
||||||
|
onCreateSupply,
|
||||||
|
canCreateSupply = false,
|
||||||
|
isCreatingSupply = false
|
||||||
}: TabsHeaderProps) {
|
}: TabsHeaderProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
@ -52,6 +59,18 @@ export function TabsHeader({
|
|||||||
{activeTab === 'wholesaler' && ` • ${cartInfo.formatCurrency(cartInfo.totalAmount)}`}
|
{activeTab === 'wholesaler' && ` • ${cartInfo.formatCurrency(cartInfo.totalAmount)}`}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Кнопка создания поставки для таба карточек */}
|
||||||
|
{activeTab === 'cards' && onCreateSupply && (
|
||||||
|
<Button
|
||||||
|
onClick={onCreateSupply}
|
||||||
|
disabled={!canCreateSupply || isCreatingSupply}
|
||||||
|
className="bg-white/20 hover:bg-white/30 text-white border-0"
|
||||||
|
>
|
||||||
|
<Check className="h-4 w-4 mr-2" />
|
||||||
|
{isCreatingSupply ? 'Создание...' : 'Создать поставку'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
Reference in New Issue
Block a user