Files
sfera/src/components/supplies/create-supply-page.tsx

1110 lines
51 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import React, { useState, useEffect } from 'react'
import { useQuery } from '@apollo/client'
import { Card } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { Sidebar } from '@/components/dashboard/sidebar'
import { useSidebar } from '@/hooks/useSidebar'
import { GET_MY_COUNTERPARTIES, GET_ALL_PRODUCTS } from '@/graphql/queries'
import {
ArrowLeft,
ShoppingCart,
Users,
Building2,
MapPin,
Phone,
Mail,
Star,
Plus,
Minus,
Info,
Package,
Zap,
Heart,
Eye,
ShoppingBag,
Search
} from 'lucide-react'
import { useRouter } from 'next/navigation'
import Image from 'next/image'
import { WBProductCards } from './wb-product-cards'
interface WholesalerForCreation {
id: string
inn: string
name: string
fullName: string
address: string
phone?: string
email?: string
rating: number
productCount: number
avatar?: string
specialization: string[]
}
interface WholesalerProduct {
id: string
name: string
article: string
description: string
price: number
quantity: number
category: string
brand?: string
color?: string
size?: string
weight?: number
dimensions?: string
material?: string
images: string[]
mainImage?: string
discount?: number
isNew?: boolean
isBestseller?: boolean
}
interface SelectedProduct extends WholesalerProduct {
selectedQuantity: number
wholesalerId: string
wholesalerName: string
}
interface WildberriesCard {
nmID: number
vendorCode: string
title: string
description: string
brand: string
mediaFiles: string[]
sizes: Array<{
chrtID: number
techSize: string
wbSize: string
price: number
discountedPrice: number
quantity: number
}>
}
interface SelectedCard {
card: WildberriesCard
selectedQuantity: number
selectedMarket: string
selectedPlace: string
sellerName: string
sellerPhone: string
deliveryDate: string
selectedServices: string[]
}
// Моковые данные оптовиков
const mockWholesalers: WholesalerForCreation[] = [
{
id: '1',
inn: '7707083893',
name: 'ОПТ-Электроника',
fullName: 'ООО "ОПТ-Электроника"',
address: 'г. Москва, ул. Садовая, д. 15',
phone: '+7 (495) 123-45-67',
email: 'opt@electronics.ru',
rating: 4.8,
productCount: 1250,
specialization: ['Электроника', 'Бытовая техника']
},
{
id: '2',
inn: '7707083894',
name: 'ТекстильМастер',
fullName: 'ООО "ТекстильМастер"',
address: 'г. Иваново, пр. Ленина, д. 42',
phone: '+7 (4932) 55-66-77',
email: 'sales@textilmaster.ru',
rating: 4.6,
productCount: 850,
specialization: ['Текстиль', 'Одежда', 'Домашний текстиль']
},
{
id: '3',
inn: '7707083895',
name: 'МетизКомплект',
fullName: 'ООО "МетизКомплект"',
address: 'г. Тула, ул. Металлургов, д. 8',
phone: '+7 (4872) 33-44-55',
email: 'info@metiz.ru',
rating: 4.9,
productCount: 2100,
specialization: ['Крепеж', 'Метизы', 'Инструменты']
}
]
// Улучшенные моковые данные товаров
const mockProducts: WholesalerProduct[] = [
{
id: '1',
name: 'iPhone 15 Pro Max',
article: 'APL-15PM-256',
description: 'Флагманский смартфон Apple с титановым корпусом, камерой 48 МП и чипом A17 Pro',
price: 124900,
quantity: 45,
category: 'Смартфоны',
brand: 'Apple',
color: 'Натуральный титан',
size: '6.7"',
weight: 221,
dimensions: '159.9 x 76.7 x 8.25 мм',
material: 'Титан, стекло',
images: ['https://store.storeimages.cdn-apple.com/4982/as-images.apple.com/is/iphone-15-pro-max-naturaltitanium-select?wid=470&hei=556&fmt=jpeg&qlt=99&.v=1692845705224'],
mainImage: 'https://store.storeimages.cdn-apple.com/4982/as-images.apple.com/is/iphone-15-pro-max-naturaltitanium-select?wid=470&hei=556&fmt=jpeg&qlt=99&.v=1692845705224',
isNew: true,
isBestseller: true
},
{
id: '2',
name: 'Sony WH-1000XM5',
article: 'SNY-WH1000XM5',
description: 'Беспроводные наушники премиум-класса с лучшим в отрасли шумоподавлением',
price: 34900,
quantity: 128,
category: 'Наушники',
brand: 'Sony',
color: 'Черный',
weight: 250,
material: 'Пластик, эко-кожа',
images: ['https://www.sony.ru/image/5d02da5df552836db894cead8a68f5f3?fmt=pjpeg&wid=330&bgcolor=FFFFFF&bgc=FFFFFF'],
mainImage: 'https://www.sony.ru/image/5d02da5df552836db894cead8a68f5f3?fmt=pjpeg&wid=330&bgcolor=FFFFFF&bgc=FFFFFF',
discount: 15,
isBestseller: true
},
{
id: '3',
name: 'iPad Pro 12.9" M2',
article: 'APL-IPADPRO-M2',
description: 'Самый мощный iPad с чипом M2, дисплеем Liquid Retina XDR и поддержкой Apple Pencil',
price: 109900,
quantity: 32,
category: 'Планшеты',
brand: 'Apple',
color: 'Серый космос',
size: '12.9"',
weight: 682,
dimensions: '280.6 x 214.9 x 6.4 мм',
material: 'Алюминий',
images: ['https://store.storeimages.cdn-apple.com/4982/as-images.apple.com/is/ipad-pro-12-select-wifi-spacegray-202210?wid=470&hei=556&fmt=jpeg&qlt=99&.v=1664411207213'],
mainImage: 'https://store.storeimages.cdn-apple.com/4982/as-images.apple.com/is/ipad-pro-12-select-wifi-spacegray-202210?wid=470&hei=556&fmt=jpeg&qlt=99&.v=1664411207213',
isNew: true
},
{
id: '4',
name: 'MacBook Pro 16" M3 Max',
article: 'APL-MBP16-M3MAX',
description: 'Профессиональный ноутбук с чипом M3 Max, 36 ГБ памяти и дисплеем Liquid Retina XDR',
price: 329900,
quantity: 18,
category: 'Ноутбуки',
brand: 'Apple',
color: 'Серый космос',
size: '16"',
weight: 2160,
dimensions: '355.7 x 248.1 x 16.8 мм',
material: 'Алюминий',
images: ['https://store.storeimages.cdn-apple.com/4982/as-images.apple.com/is/mbp16-spacegray-select-202310?wid=470&hei=556&fmt=jpeg&qlt=99&.v=1697230830200'],
mainImage: 'https://store.storeimages.cdn-apple.com/4982/as-images.apple.com/is/mbp16-spacegray-select-202310?wid=470&hei=556&fmt=jpeg&qlt=99&.v=1697230830200',
isNew: true,
isBestseller: true
},
{
id: '5',
name: 'Apple Watch Ultra 2',
article: 'APL-AWU2-49',
description: 'Самые прочные и функциональные умные часы Apple для экстремальных приключений',
price: 89900,
quantity: 67,
category: 'Умные часы',
brand: 'Apple',
color: 'Натуральный титан',
size: '49 мм',
weight: 61,
dimensions: '49 x 44 x 14.4 мм',
material: 'Титан',
images: ['https://store.storeimages.cdn-apple.com/4982/as-images.apple.com/is/watch-ultra2-select-202309?wid=470&hei=556&fmt=jpeg&qlt=99&.v=1693967875133'],
mainImage: 'https://store.storeimages.cdn-apple.com/4982/as-images.apple.com/is/watch-ultra2-select-202309?wid=470&hei=556&fmt=jpeg&qlt=99&.v=1693967875133',
isNew: true
},
{
id: '6',
name: 'Magic Keyboard для iPad Pro',
article: 'APL-MK-IPADPRO',
description: 'Клавиатура с трекпадом и подсветкой клавиш для iPad Pro 12.9"',
price: 36900,
quantity: 89,
category: 'Аксессуары',
brand: 'Apple',
color: 'Черный',
weight: 710,
dimensions: '280.9 x 214.3 x 25 мм',
material: 'Алюминий, пластик',
images: ['https://store.storeimages.cdn-apple.com/4982/as-images.apple.com/is/MJQJ3?wid=470&hei=556&fmt=jpeg&qlt=99&.v=1639066901000'],
mainImage: 'https://store.storeimages.cdn-apple.com/4982/as-images.apple.com/is/MJQJ3?wid=470&hei=556&fmt=jpeg&qlt=99&.v=1639066901000'
}
]
export function CreateSupplyPage() {
const router = useRouter()
const { getSidebarMargin } = useSidebar()
const [activeTab, setActiveTab] = useState<'cards' | 'wholesaler'>('cards')
const [selectedWholesaler, setSelectedWholesaler] = useState<WholesalerForCreation | null>(null)
const [selectedProducts, setSelectedProducts] = useState<SelectedProduct[]>([])
const [selectedCards, setSelectedCards] = useState<SelectedCard[]>([])
const [showSummary, setShowSummary] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
// Загружаем контрагентов-оптовиков
const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(GET_MY_COUNTERPARTIES)
// Загружаем товары для выбранного оптовика
const { data: productsData, loading: productsLoading } = useQuery(GET_ALL_PRODUCTS, {
skip: !selectedWholesaler,
variables: { search: null, category: null }
})
// Фильтруем только оптовиков
const wholesalers = (counterpartiesData?.myCounterparties || []).filter((org: { type: string }) => org.type === 'WHOLESALE')
// Фильтруем оптовиков по поисковому запросу
const filteredWholesalers = wholesalers.filter((wholesaler: { name?: string; fullName?: string; inn?: string }) =>
wholesaler.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
wholesaler.fullName?.toLowerCase().includes(searchQuery.toLowerCase()) ||
wholesaler.inn?.toLowerCase().includes(searchQuery.toLowerCase())
)
// Фильтруем товары по выбранному оптовику
const wholesalerProducts = selectedWholesaler
? (productsData?.allProducts || []).filter((product: { organization: { id: string } }) =>
product.organization.id === selectedWholesaler.id
)
: []
// Автоматически показываем корзину если в ней есть товары и мы на этапе выбора оптовиков
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',
currency: 'RUB',
minimumFractionDigits: 0
}).format(amount)
}
const renderStars = (rating: number) => {
return Array.from({ length: 5 }, (_, i) => (
<Star
key={i}
className={`h-4 w-4 ${i < Math.floor(rating) ? 'text-yellow-400 fill-current' : 'text-gray-400'}`}
/>
))
}
const updateProductQuantity = (productId: string, quantity: number) => {
const product = wholesalerProducts.find((p: { id: string }) => p.id === productId)
if (!product || !selectedWholesaler) return
setSelectedProducts(prev => {
const existing = prev.find(p => p.id === productId && p.wholesalerId === selectedWholesaler.id)
if (quantity === 0) {
return prev.filter(p => !(p.id === productId && p.wholesalerId === selectedWholesaler.id))
}
if (existing) {
return prev.map(p =>
p.id === productId && p.wholesalerId === selectedWholesaler.id
? { ...p, selectedQuantity: quantity }
: p
)
} else {
return [...prev, {
...product,
selectedQuantity: quantity,
wholesalerId: selectedWholesaler.id,
wholesalerName: selectedWholesaler.name
}]
}
})
}
const getSelectedQuantity = (productId: string): number => {
if (!selectedWholesaler) return 0
const selected = selectedProducts.find(p => p.id === productId && p.wholesalerId === selectedWholesaler.id)
return selected ? selected.selectedQuantity : 0
}
const getTotalAmount = () => {
return selectedProducts.reduce((sum, product) => {
const discountedPrice = product.discount
? product.price * (1 - product.discount / 100)
: product.price
return sum + (discountedPrice * product.selectedQuantity)
}, 0)
}
const getTotalItems = () => {
return selectedProducts.reduce((sum, product) => sum + product.selectedQuantity, 0)
}
const handleCardsComplete = (cards: SelectedCard[]) => {
setSelectedCards(cards)
console.log('Карточки товаров выбраны:', cards)
// TODO: Здесь будет создание поставки с данными карточек
router.push('/supplies')
}
const handleCreateSupply = () => {
if (activeTab === 'cards') {
console.log('Создание поставки с карточками Wildberries')
// TODO: Здесь будет создание поставки с данными карточек
} else {
console.log('Создание поставки с товарами:', selectedProducts)
// TODO: Здесь будет реальное создание поставки
}
router.push('/supplies')
}
const handleGoBack = () => {
if (selectedWholesaler) {
setSelectedWholesaler(null)
// НЕ очищаем корзину! setSelectedProducts([])
setShowSummary(false)
} else {
router.push('/supplies')
}
}
// Рендер товаров оптовика
if (selectedWholesaler && activeTab === 'wholesaler') {
return (
<div className="h-screen flex overflow-hidden">
<Sidebar />
<main className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}>
<div className="p-8">
<div className="flex items-center justify-between mb-8">
<div className="flex items-center space-x-4">
<Button
variant="ghost"
size="sm"
onClick={handleGoBack}
className="text-white/60 hover:text-white hover:bg-white/10"
>
<ArrowLeft className="h-4 w-4 mr-2" />
Назад
</Button>
<div>
<h1 className="text-3xl font-bold text-white mb-2">Товары оптовика</h1>
<p className="text-white/60">{selectedWholesaler.name} {wholesalerProducts.length} товаров</p>
</div>
</div>
<div className="flex items-center space-x-3">
<Button
variant="ghost"
size="sm"
onClick={() => setShowSummary(!showSummary)}
className="text-white/60 hover:text-white hover:bg-white/10"
>
<Info className="h-4 w-4 mr-2" />
Резюме ({selectedProducts.length})
</Button>
</div>
</div>
{showSummary && selectedProducts.length > 0 && (
<Card className="bg-white/5 backdrop-blur border-white/10 mb-8">
<div className="p-6">
<div className="flex items-center justify-between mb-6">
<h3 className="text-white font-semibold text-xl flex items-center">
<ShoppingCart className="h-5 w-5 mr-2" />
Корзина ({selectedProducts.length})
</h3>
<Button
variant="ghost"
size="sm"
onClick={() => setShowSummary(false)}
className="text-white/60 hover:text-white"
>
</Button>
</div>
{/* Текущий оптовик */}
<div className="mb-6">
<div className="flex items-center mb-3 pb-2 border-b border-white/10">
<Building2 className="h-4 w-4 text-blue-400 mr-2" />
<span className="text-white font-medium">{selectedWholesaler?.name}</span>
<Badge className="ml-2 bg-blue-500/20 text-blue-300 border-blue-500/30 text-xs">
{selectedProducts.filter(p => p.wholesalerId === selectedWholesaler?.id).length} товар(ов)
</Badge>
</div>
<div className="space-y-3">
{selectedProducts.filter(p => p.wholesalerId === selectedWholesaler?.id).map((product) => {
const discountedPrice = product.discount
? product.price * (1 - product.discount / 100)
: product.price
const totalPrice = discountedPrice * product.selectedQuantity
return (
<div key={product.id} className="flex items-center space-x-4 bg-white/5 rounded-lg p-4">
<img
src={product.mainImage || '/api/placeholder/60/60'}
alt={product.name}
className="w-16 h-16 rounded-lg object-cover"
/>
<div className="flex-1 min-w-0">
<h4 className="text-white font-medium text-sm mb-1 truncate">{product.name}</h4>
<p className="text-white/60 text-xs mb-2">{product.article}</p>
<div className="flex items-center space-x-3">
<span className="text-white/60 text-sm">× {product.selectedQuantity}</span>
<div className="text-right">
<div className="text-white font-semibold text-sm">{formatCurrency(totalPrice)}</div>
{product.discount && (
<div className="text-white/40 text-xs line-through">
{formatCurrency(product.price * product.selectedQuantity)}
</div>
)}
</div>
</div>
</div>
</div>
)
})}
</div>
</div>
{/* Другие оптовики в корзине */}
{Object.entries(
selectedProducts.filter(p => p.wholesalerId !== selectedWholesaler?.id).reduce((acc, product) => {
if (!acc[product.wholesalerId]) {
acc[product.wholesalerId] = {
wholesaler: product.wholesalerName,
products: []
}
}
acc[product.wholesalerId].products.push(product)
return acc
}, {} as Record<string, { wholesaler: string; products: SelectedProduct[] }>)
).map(([wholesalerId, group]) => (
<div key={wholesalerId} className="mb-6 last:mb-0">
<div className="flex items-center mb-3 pb-2 border-b border-white/10">
<Building2 className="h-4 w-4 text-purple-400 mr-2" />
<span className="text-white font-medium">{group.wholesaler}</span>
<Badge className="ml-2 bg-purple-500/20 text-purple-300 border-purple-500/30 text-xs">
{group.products.length} товар(ов)
</Badge>
</div>
<div className="space-y-3">
{group.products.map((product) => {
const discountedPrice = product.discount
? product.price * (1 - product.discount / 100)
: product.price
const totalPrice = discountedPrice * product.selectedQuantity
return (
<div key={`${product.wholesalerId}-${product.id}`} className="flex items-center space-x-4 bg-white/5 rounded-lg p-4">
<img
src={product.mainImage || '/api/placeholder/60/60'}
alt={product.name}
className="w-16 h-16 rounded-lg object-cover"
/>
<div className="flex-1 min-w-0">
<h4 className="text-white font-medium text-sm mb-1 truncate">{product.name}</h4>
<p className="text-white/60 text-xs mb-2">{product.article}</p>
<div className="flex items-center space-x-3">
<span className="text-white/60 text-sm">× {product.selectedQuantity}</span>
<div className="text-right">
<div className="text-white font-semibold text-sm">{formatCurrency(totalPrice)}</div>
{product.discount && (
<div className="text-white/40 text-xs line-through">
{formatCurrency(product.price * product.selectedQuantity)}
</div>
)}
</div>
</div>
</div>
</div>
)
})}
</div>
</div>
))}
{/* Итого */}
<div className="border-t border-white/20 pt-4 mt-6">
<div className="flex justify-between items-center">
<span className="text-white font-semibold text-lg">
Итого: {getTotalItems()} товаров
</span>
<span className="text-white font-bold text-2xl">
{formatCurrency(getTotalAmount())}
</span>
</div>
<Button
className="w-full mt-4 bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 text-white"
onClick={handleCreateSupply}
>
<ShoppingCart className="h-4 w-4 mr-2" />
Создать поставку
</Button>
</div>
</div>
</Card>
)}
{productsLoading ? (
<div className="flex items-center justify-center p-8 col-span-full">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-4 border-white border-t-transparent mx-auto mb-4"></div>
<p className="text-white/60">Загружаем товары...</p>
</div>
</div>
) : wholesalerProducts.length === 0 ? (
<div className="flex items-center justify-center p-8 col-span-full">
<div className="text-center">
<Package className="h-12 w-12 text-white/20 mx-auto mb-4" />
<p className="text-white/60">У этого оптовика нет товаров</p>
<p className="text-white/40 text-sm mt-2">Выберите другого оптовика</p>
</div>
</div>
) : (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-4">
{wholesalerProducts.map((product: {
id: string;
name: string;
article: string;
description?: string;
price: number;
quantity: number;
category?: { name: string };
brand?: string;
color?: string;
size?: string;
mainImage?: string;
images?: string[]
}) => {
const selectedQuantity = getSelectedQuantity(product.id)
const discountedPrice = product.price // Убираем discount так как его нет в схеме
return (
<Card key={product.id} className="bg-white/10 backdrop-blur border-white/20 overflow-hidden group hover:bg-white/15 hover:border-white/30 transition-all duration-300 hover:scale-105 hover:shadow-2xl">
<div className="aspect-square relative bg-white/5 overflow-hidden">
<img
src={product.mainImage || '/api/placeholder/400/400'}
alt={product.name}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
/>
{/* Количество в наличии */}
<div className="absolute top-2 right-2">
<Badge className={`${product.quantity > 50 ? 'bg-green-500/80' : product.quantity > 10 ? 'bg-yellow-500/80' : 'bg-red-500/80'} text-white border-0 backdrop-blur text-xs`}>
{product.quantity}
</Badge>
</div>
{/* Убираем discount badge так как поля нет в схеме */}
{/* Overlay с кнопками */}
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
<div className="flex space-x-2">
<Button size="sm" variant="secondary" className="bg-white/20 backdrop-blur text-white border-white/30 hover:bg-white/30">
<Eye className="h-4 w-4" />
</Button>
<Button size="sm" variant="secondary" className="bg-white/20 backdrop-blur text-white border-white/30 hover:bg-white/30">
<Heart className="h-4 w-4" />
</Button>
</div>
</div>
</div>
<div className="p-3 space-y-3">
{/* Заголовок и бренд */}
<div>
<div className="flex items-center justify-between mb-1">
{product.brand && (
<Badge className="bg-gray-500/20 text-gray-300 border-gray-500/30 text-xs">
{product.brand}
</Badge>
)}
{/* Убираем isNew и isBestseller так как этих полей нет в схеме */}
</div>
<h3 className="text-white font-semibold text-sm mb-1 line-clamp-2 leading-tight">
{product.name}
</h3>
</div>
{/* Основная характеристика */}
<div className="text-white/60 text-xs">
{product.color && <span className="text-white">{product.color}</span>}
{product.size && <span className="text-white ml-2">{product.size}</span>}
</div>
{/* Цена */}
<div className="pt-2 border-t border-white/10">
<div className="flex items-center space-x-2">
<div className="text-white font-bold text-lg">
{formatCurrency(discountedPrice)}
</div>
{/* Убираем отображение оригинальной цены так как discount нет */}
</div>
</div>
{/* Управление количеством */}
<div className="flex items-center space-x-2">
<Button
variant="ghost"
size="sm"
onClick={() => updateProductQuantity(product.id, Math.max(0, selectedQuantity - 1))}
disabled={selectedQuantity === 0}
className="h-8 w-8 p-0 text-white/60 hover:text-white hover:bg-white/10 border border-white/20"
>
<Minus className="h-3 w-3" />
</Button>
<input
type="text"
inputMode="numeric"
pattern="[0-9]*"
value={selectedQuantity}
onChange={(e) => {
const value = e.target.value.replace(/[^0-9]/g, '')
const numValue = Math.max(0, Math.min(product.quantity, parseInt(value) || 0))
updateProductQuantity(product.id, numValue)
}}
onFocus={(e) => e.target.select()}
className="h-8 w-12 text-center bg-white/10 border border-white/20 text-white text-sm rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
/>
<Button
variant="ghost"
size="sm"
onClick={() => updateProductQuantity(product.id, Math.min(product.quantity, selectedQuantity + 1))}
disabled={selectedQuantity >= product.quantity}
className="h-8 w-8 p-0 text-white/60 hover:text-white hover:bg-white/10 border border-white/20"
>
<Plus className="h-3 w-3" />
</Button>
{selectedQuantity > 0 && (
<Badge className="bg-gradient-to-r from-purple-500 to-pink-500 text-white border-0 text-xs ml-auto">
<ShoppingCart className="h-3 w-3 mr-1" />
{selectedQuantity}
</Badge>
)}
</div>
{/* Сумма для выбранного товара */}
{selectedQuantity > 0 && (
<div className="bg-gradient-to-r from-green-500/20 to-emerald-500/20 border border-green-500/30 rounded p-2">
<div className="text-green-300 text-xs font-medium text-center">
{formatCurrency(discountedPrice * selectedQuantity)}
</div>
</div>
)}
</div>
</Card>
)
})}
</div>
)}
{/* Floating корзина */}
{selectedProducts.length > 0 && (
<div className="fixed bottom-6 right-6 z-50">
<Button
size="lg"
className="bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white shadow-2xl text-lg px-8 py-4"
onClick={() => setShowSummary(!showSummary)}
>
<ShoppingCart className="h-5 w-5 mr-3" />
Корзина ({selectedProducts.length}) {formatCurrency(getTotalAmount())}
</Button>
</div>
)}
</div>
</main>
</div>
)
}
// Главная страница с табами
return (
<div className="h-screen flex overflow-hidden">
<Sidebar />
<main className={`flex-1 ${getSidebarMargin()} px-4 py-3 overflow-hidden transition-all duration-300`}>
<div className="p-4">
{/* Верхняя строка: Назад + Заголовок + Табы */}
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-4">
<Button
variant="ghost"
size="sm"
onClick={() => router.push('/supplies')}
className="text-white/60 hover:text-white hover:bg-white/10"
>
<ArrowLeft className="h-4 w-4 mr-2" />
Назад
</Button>
<h1 className="text-2xl font-bold text-white">Создание поставки</h1>
</div>
<div>
<div className="grid grid-cols-2 bg-white/10 backdrop-blur border border-white/20 rounded-lg p-1">
<button
onClick={() => setActiveTab('cards')}
className={`px-4 py-2 text-sm rounded transition-all ${
activeTab === 'cards'
? 'bg-white/20 text-white'
: 'text-white/60 hover:text-white hover:bg-white/10'
}`}
>
<ShoppingCart className="h-4 w-4 mr-1 inline" />
Карточки
</button>
<button
onClick={() => setActiveTab('wholesaler')}
className={`px-4 py-2 text-sm rounded transition-all ${
activeTab === 'wholesaler'
? 'bg-white/20 text-white'
: 'text-white/60 hover:text-white hover:bg-white/10'
}`}
>
<Users className="h-4 w-4 mr-1 inline" />
Оптовики
</button>
</div>
</div>
</div>
{/* Контент карточек */}
{activeTab === 'cards' && (
<WBProductCards
onBack={() => router.push('/supplies')}
onComplete={handleCardsComplete}
/>
)}
{/* Контент оптовиков */}
{activeTab === 'wholesaler' && (
<div>
{/* Поиск */}
<div className="mb-4">
<div className="relative max-w-md">
<Search className="absolute left-3 top-3 h-4 w-4 text-white/40" />
<Input
placeholder="Поиск оптовиков..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 glass-input text-white placeholder:text-white/40 h-10"
/>
</div>
</div>
{/* Корзина */}
{showSummary && selectedProducts.length > 0 && (
<Card className="bg-gradient-to-br from-purple-500/10 to-pink-500/10 backdrop-blur-xl border border-purple-500/20 mb-6 shadow-2xl">
<div className="p-4">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-3">
<div className="p-2 bg-gradient-to-r from-purple-500 to-pink-500 rounded-lg">
<ShoppingCart className="h-5 w-5 text-white" />
</div>
<div>
<h3 className="text-white font-bold text-lg">Корзина</h3>
<p className="text-purple-200 text-xs">{selectedProducts.length} товаров от {Object.keys(selectedProducts.reduce((acc, p) => ({ ...acc, [p.wholesalerId]: true }), {})).length} поставщиков</p>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => setShowSummary(false)}
className="text-white/60 hover:text-white hover:bg-white/10 rounded-xl"
>
<Eye className="h-4 w-4" />
</Button>
</div>
{/* Группировка по оптовикам */}
{Object.entries(
selectedProducts.reduce((acc, product) => {
if (!acc[product.wholesalerId]) {
acc[product.wholesalerId] = {
wholesaler: product.wholesalerName,
products: []
}
}
acc[product.wholesalerId].products.push(product)
return acc
}, {} as Record<string, { wholesaler: string; products: SelectedProduct[] }>)
).map(([wholesalerId, group]) => (
<div key={wholesalerId} className="mb-4 last:mb-0">
<div className="flex items-center mb-2 pb-1 border-b border-white/10">
<Building2 className="h-4 w-4 text-blue-400 mr-2" />
<span className="text-white font-medium">{group.wholesaler}</span>
<Badge className="ml-2 bg-blue-500/20 text-blue-300 border-blue-500/30 text-xs">
{group.products.length} товар(ов)
</Badge>
</div>
<div className="space-y-2">
{group.products.map((product) => {
const discountedPrice = product.discount
? product.price * (1 - product.discount / 100)
: product.price
const totalPrice = discountedPrice * product.selectedQuantity
return (
<div key={`${product.wholesalerId}-${product.id}`} className="flex items-center space-x-3 bg-white/5 rounded-lg p-3">
<img
src={product.mainImage || '/api/placeholder/50/50'}
alt={product.name}
className="w-12 h-12 rounded-lg object-cover"
/>
<div className="flex-1 min-w-0">
<h4 className="text-white font-medium text-xs mb-1 truncate">{product.name}</h4>
<p className="text-white/60 text-xs mb-1">{product.article}</p>
<div className="flex items-center space-x-2">
<div className="flex items-center space-x-1">
<Button
variant="ghost"
size="sm"
onClick={() => {
const newQuantity = Math.max(0, product.selectedQuantity - 1)
if (newQuantity === 0) {
setSelectedProducts(prev =>
prev.filter(p => !(p.id === product.id && p.wholesalerId === product.wholesalerId))
)
} else {
setSelectedProducts(prev =>
prev.map(p =>
p.id === product.id && p.wholesalerId === product.wholesalerId
? { ...p, selectedQuantity: newQuantity }
: p
)
)
}
}}
className="h-6 w-6 p-0 text-white/60 hover:text-white hover:bg-white/10"
>
<Minus className="h-3 w-3" />
</Button>
<span className="text-white text-xs w-6 text-center">{product.selectedQuantity}</span>
<Button
variant="ghost"
size="sm"
onClick={() => {
setSelectedProducts(prev =>
prev.map(p =>
p.id === product.id && p.wholesalerId === product.wholesalerId
? { ...p, selectedQuantity: Math.min(product.quantity, p.selectedQuantity + 1) }
: p
)
)
}}
disabled={product.selectedQuantity >= product.quantity}
className="h-6 w-6 p-0 text-white/60 hover:text-white hover:bg-white/10"
>
<Plus className="h-3 w-3" />
</Button>
</div>
<div className="text-right">
<div className="text-white font-semibold text-xs">{formatCurrency(totalPrice)}</div>
{product.discount && (
<div className="text-white/40 text-xs line-through">
{formatCurrency(product.price * product.selectedQuantity)}
</div>
)}
</div>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => {
setSelectedProducts(prev =>
prev.filter(p => !(p.id === product.id && p.wholesalerId === product.wholesalerId))
)
}}
className="text-red-400 hover:text-red-300 hover:bg-red-500/10"
>
</Button>
</div>
)
})}
</div>
</div>
))}
{/* Итого */}
<div className="border-t border-white/20 pt-3 mt-4">
<div className="flex justify-between items-center">
<span className="text-white font-semibold text-sm">
Итого: {getTotalItems()} товаров
</span>
<span className="text-white font-bold text-lg">
{formatCurrency(getTotalAmount())}
</span>
</div>
<div className="flex space-x-2 mt-3">
<Button
variant="outline"
className="flex-1 border-purple-300/30 text-white hover:bg-white/10"
onClick={() => setShowSummary(false)}
>
<Plus className="h-4 w-4 mr-2" />
Добавить еще
</Button>
<Button
className="flex-1 bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 text-white"
onClick={handleCreateSupply}
>
<ShoppingCart className="h-4 w-4 mr-2" />
Оформить поставку
</Button>
</div>
</div>
</div>
</Card>
)}
{counterpartiesLoading ? (
<div className="flex items-center justify-center p-8">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-4 border-white border-t-transparent mx-auto mb-4"></div>
<p className="text-white/60">Загружаем оптовиков...</p>
</div>
</div>
) : filteredWholesalers.length === 0 ? (
<div className="text-center p-8">
<Users className="h-12 w-12 text-white/20 mx-auto mb-4" />
<p className="text-white/60">
{searchQuery ? 'Оптовики не найдены' : 'У вас нет контрагентов-оптовиков'}
</p>
<p className="text-white/40 text-sm mt-2">
{searchQuery ? 'Попробуйте изменить условия поиска' : 'Добавьте оптовиков в разделе "Партнеры"'}
</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{filteredWholesalers.map((wholesaler: {
id: string;
name?: string;
fullName?: string;
inn?: string;
address?: string;
phones?: { value: string }[];
emails?: { value: string }[]
}) => (
<Card
key={wholesaler.id}
className="bg-white/10 backdrop-blur border-white/20 p-4 cursor-pointer transition-all hover:bg-white/15 hover:border-white/30 hover:scale-[1.02]"
onClick={() => {
// Адаптируем данные под существующий интерфейс
const adaptedWholesaler = {
id: wholesaler.id,
inn: wholesaler.inn || '',
name: wholesaler.name || 'Неизвестная организация',
fullName: wholesaler.fullName || wholesaler.name || 'Неизвестная организация',
address: wholesaler.address || 'Адрес не указан',
phone: wholesaler.phones?.[0]?.value,
email: wholesaler.emails?.[0]?.value,
rating: 4.5, // Временное значение
productCount: 0, // Временное значение
specialization: ['Оптовая торговля'] // Временное значение
}
setSelectedWholesaler(adaptedWholesaler)
}}
>
<div className="space-y-3">
<div className="flex items-start space-x-2">
<div className="p-2 bg-blue-500/20 rounded-lg">
<Building2 className="h-4 w-4 text-blue-400" />
</div>
<div className="flex-1 min-w-0">
<h3 className="text-white font-semibold text-sm mb-1 truncate">
{wholesaler.name || 'Неизвестная организация'}
</h3>
<p className="text-white/60 text-xs mb-1 truncate">
{wholesaler.fullName || wholesaler.name}
</p>
{wholesaler.inn && (
<p className="text-white/40 text-xs">
ИНН: {wholesaler.inn}
</p>
)}
</div>
</div>
<div className="space-y-1">
{wholesaler.address && (
<div className="flex items-center space-x-1">
<MapPin className="h-3 w-3 text-gray-400" />
<span className="text-white/80 text-xs truncate">{wholesaler.address}</span>
</div>
)}
{wholesaler.phones?.[0]?.value && (
<div className="flex items-center space-x-1">
<Phone className="h-3 w-3 text-gray-400" />
<span className="text-white/80 text-xs">{wholesaler.phones[0].value}</span>
</div>
)}
{wholesaler.emails?.[0]?.value && (
<div className="flex items-center space-x-1">
<Mail className="h-3 w-3 text-gray-400" />
<span className="text-white/80 text-xs truncate">{wholesaler.emails[0].value}</span>
</div>
)}
</div>
<div className="mt-2">
<Badge className="bg-green-500/20 text-green-300 border-green-500/30 text-xs px-2 py-1">
Контрагент
</Badge>
</div>
<div className="pt-2 border-t border-white/10">
<p className="text-white/60 text-xs">ИНН: {wholesaler.inn}</p>
</div>
</div>
</Card>
))}
</div>
)}
{/* Floating корзина */}
{selectedProducts.length > 0 && !showSummary && (
<div className="fixed bottom-6 right-6 z-50">
<Button
size="lg"
className="bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white shadow-2xl"
onClick={() => setShowSummary(true)}
>
<ShoppingCart className="h-5 w-5 mr-2" />
{selectedProducts.length} {formatCurrency(getTotalAmount())}
</Button>
</div>
)}
</div>
)}
</div>
</main>
</div>
)
}