Изменен механизм выбора вкладок в компоненте CreateSupplyPage: заменено состояние selectedVariant на activeTab для улучшения читаемости кода. Обновлены условия отображения корзины и логика обработки товаров. Оптимизирован рендеринг карточек и оптовиков. В компоненте WBProductCards изменен заголовок на "Нет товаров" при отсутствии выбранных карточек.
This commit is contained in:
@ -255,7 +255,7 @@ const mockProducts: WholesalerProduct[] = [
|
|||||||
export function CreateSupplyPage() {
|
export function CreateSupplyPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { getSidebarMargin } = useSidebar()
|
const { getSidebarMargin } = useSidebar()
|
||||||
const [selectedVariant, setSelectedVariant] = useState<'cards' | 'wholesaler' | null>(null)
|
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<SelectedCard[]>([])
|
const [selectedCards, setSelectedCards] = useState<SelectedCard[]>([])
|
||||||
@ -290,10 +290,10 @@ export function CreateSupplyPage() {
|
|||||||
|
|
||||||
// Автоматически показываем корзину если в ней есть товары и мы на этапе выбора оптовиков
|
// Автоматически показываем корзину если в ней есть товары и мы на этапе выбора оптовиков
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedVariant === 'wholesaler' && !selectedWholesaler && selectedProducts.length > 0) {
|
if (activeTab === 'wholesaler' && !selectedWholesaler && selectedProducts.length > 0) {
|
||||||
setShowSummary(true)
|
setShowSummary(true)
|
||||||
}
|
}
|
||||||
}, [selectedVariant, selectedWholesaler, selectedProducts.length])
|
}, [activeTab, selectedWholesaler, selectedProducts.length])
|
||||||
|
|
||||||
const formatCurrency = (amount: number) => {
|
const formatCurrency = (amount: number) => {
|
||||||
return new Intl.NumberFormat('ru-RU', {
|
return new Intl.NumberFormat('ru-RU', {
|
||||||
@ -367,7 +367,7 @@ export function CreateSupplyPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleCreateSupply = () => {
|
const handleCreateSupply = () => {
|
||||||
if (selectedVariant === 'cards') {
|
if (activeTab === 'cards') {
|
||||||
console.log('Создание поставки с карточками Wildberries')
|
console.log('Создание поставки с карточками Wildberries')
|
||||||
// TODO: Здесь будет создание поставки с данными карточек
|
// TODO: Здесь будет создание поставки с данными карточек
|
||||||
} else {
|
} else {
|
||||||
@ -382,17 +382,13 @@ export function CreateSupplyPage() {
|
|||||||
setSelectedWholesaler(null)
|
setSelectedWholesaler(null)
|
||||||
// НЕ очищаем корзину! setSelectedProducts([])
|
// НЕ очищаем корзину! setSelectedProducts([])
|
||||||
setShowSummary(false)
|
setShowSummary(false)
|
||||||
} else if (selectedVariant) {
|
|
||||||
setSelectedVariant(null)
|
|
||||||
setSelectedProducts([]) // Очищаем корзину только при полном выходе
|
|
||||||
setShowSummary(false)
|
|
||||||
} else {
|
} else {
|
||||||
router.push('/supplies')
|
router.push('/supplies')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Рендер товаров оптовика
|
// Рендер товаров оптовика
|
||||||
if (selectedWholesaler && selectedVariant === 'wholesaler') {
|
if (selectedWholesaler && activeTab === 'wholesaler') {
|
||||||
return (
|
return (
|
||||||
<div className="h-screen flex overflow-hidden">
|
<div className="h-screen flex overflow-hidden">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
@ -743,370 +739,16 @@ export function CreateSupplyPage() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Рендер карточек Wildberries
|
|
||||||
if (selectedVariant === 'cards') {
|
|
||||||
return (
|
|
||||||
<WBProductCards
|
|
||||||
onBack={() => setSelectedVariant(null)}
|
|
||||||
onComplete={handleCardsComplete}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Рендер выбора оптовиков
|
|
||||||
if (selectedVariant === '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">
|
|
||||||
{selectedProducts.length > 0
|
|
||||||
? `Выберите еще оптовика или перейдите к оформлению (${selectedProducts.length} товаров в корзине)`
|
|
||||||
: "Выберите оптовика для создания поставки"
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{selectedProducts.length > 0 && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setShowSummary(!showSummary)}
|
|
||||||
className="text-white/60 hover:text-white hover:bg-white/10"
|
|
||||||
>
|
|
||||||
<ShoppingCart className="h-4 w-4 mr-2" />
|
|
||||||
Корзина ({selectedProducts.length})
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</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-8 shadow-2xl">
|
|
||||||
<div className="p-6">
|
|
||||||
<div className="flex items-center justify-between mb-6">
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<div className="p-3 bg-gradient-to-r from-purple-500 to-pink-500 rounded-xl">
|
|
||||||
<ShoppingCart className="h-6 w-6 text-white" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="text-white font-bold text-xl">Корзина</h3>
|
|
||||||
<p className="text-purple-200 text-sm">{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-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-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-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">
|
|
||||||
<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-7 w-7 p-0 text-white/60 hover:text-white hover:bg-white/10"
|
|
||||||
>
|
|
||||||
<Minus className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
<span className="text-white text-sm w-8 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-7 w-7 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-sm">{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-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>
|
|
||||||
<div className="flex space-x-3 mt-4">
|
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Поиск */}
|
|
||||||
<div className="mb-6">
|
|
||||||
<div className="relative">
|
|
||||||
<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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{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 gap-6">
|
|
||||||
{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-6 cursor-pointer transition-all hover:bg-white/15 hover:border-white/30 hover:scale-105"
|
|
||||||
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-4">
|
|
||||||
<div className="flex items-start space-x-3">
|
|
||||||
<div className="p-3 bg-blue-500/20 rounded-lg">
|
|
||||||
<Building2 className="h-6 w-6 text-blue-400" />
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<h3 className="text-white font-semibold text-lg mb-1 truncate">
|
|
||||||
{wholesaler.name || 'Неизвестная организация'}
|
|
||||||
</h3>
|
|
||||||
<p className="text-white/60 text-xs mb-2 truncate">
|
|
||||||
{wholesaler.fullName || wholesaler.name}
|
|
||||||
</p>
|
|
||||||
{wholesaler.inn && (
|
|
||||||
<p className="text-white/40 text-xs">
|
|
||||||
ИНН: {wholesaler.inn}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
{wholesaler.address && (
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<MapPin className="h-4 w-4 text-gray-400" />
|
|
||||||
<span className="text-white/80 text-sm truncate">{wholesaler.address}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{wholesaler.phones?.[0]?.value && (
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Phone className="h-4 w-4 text-gray-400" />
|
|
||||||
<span className="text-white/80 text-sm">{wholesaler.phones[0].value}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{wholesaler.emails?.[0]?.value && (
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Mail className="h-4 w-4 text-gray-400" />
|
|
||||||
<span className="text-white/80 text-sm truncate">{wholesaler.emails[0].value}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-4">
|
|
||||||
<Badge className="bg-green-500/20 text-green-300 border-green-500/30">
|
|
||||||
Контрагент
|
|
||||||
</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>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Главная страница выбора варианта
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen flex overflow-hidden">
|
<div className="h-screen flex overflow-hidden">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<main className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}>
|
<main className={`flex-1 ${getSidebarMargin()} px-4 py-3 overflow-hidden transition-all duration-300`}>
|
||||||
<div className="p-8">
|
<div className="p-4">
|
||||||
<div className="flex items-center justify-between mb-8">
|
{/* Верхняя строка: Назад + Заголовок + Табы */}
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@ -1115,56 +757,352 @@ export function CreateSupplyPage() {
|
|||||||
className="text-white/60 hover:text-white hover:bg-white/10"
|
className="text-white/60 hover:text-white hover:bg-white/10"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||||
Назад к поставкам
|
Назад
|
||||||
</Button>
|
</Button>
|
||||||
<div>
|
<h1 className="text-2xl font-bold text-white">Создание поставки</h1>
|
||||||
<h1 className="text-3xl font-bold text-white mb-2">Создание поставки</h1>
|
</div>
|
||||||
<p className="text-white/60">Выберите способ создания поставки</p>
|
|
||||||
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
{/* Контент карточек */}
|
||||||
<Card
|
{activeTab === 'cards' && (
|
||||||
className="bg-white/10 backdrop-blur border-white/20 p-8 cursor-pointer transition-all hover:bg-white/15 hover:border-white/30 hover:scale-105"
|
<WBProductCards
|
||||||
onClick={() => setSelectedVariant('cards')}
|
onBack={() => router.push('/supplies')}
|
||||||
>
|
onComplete={handleCardsComplete}
|
||||||
<div className="text-center space-y-6">
|
/>
|
||||||
<div className="p-6 bg-blue-500/20 rounded-2xl w-fit mx-auto">
|
)}
|
||||||
<ShoppingCart className="h-12 w-12 text-blue-400" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="text-2xl font-semibold text-white mb-3">Карточки</h3>
|
|
||||||
<p className="text-white/60">
|
|
||||||
Создание поставки через выбор товаров по карточкам Wildberries
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Badge className="bg-green-500/20 text-green-300 border-green-500/30 text-lg px-4 py-2">
|
|
||||||
Доступно
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card
|
{/* Контент оптовиков */}
|
||||||
className="bg-white/10 backdrop-blur border-white/20 p-8 cursor-pointer transition-all hover:bg-white/15 hover:border-white/30 hover:scale-105"
|
{activeTab === 'wholesaler' && (
|
||||||
onClick={() => setSelectedVariant('wholesaler')}
|
<div>
|
||||||
>
|
{/* Поиск */}
|
||||||
<div className="text-center space-y-6">
|
<div className="mb-4">
|
||||||
<div className="p-6 bg-green-500/20 rounded-2xl w-fit mx-auto">
|
<div className="relative max-w-md">
|
||||||
<Users className="h-12 w-12 text-green-400" />
|
<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>
|
||||||
<div>
|
</div>
|
||||||
<h3 className="text-2xl font-semibold text-white mb-3">Оптовик</h3>
|
|
||||||
|
{/* Корзина */}
|
||||||
|
{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">
|
<p className="text-white/60">
|
||||||
Создание поставки через выбор товаров у оптовиков
|
{searchQuery ? 'Оптовики не найдены' : 'У вас нет контрагентов-оптовиков'}
|
||||||
|
</p>
|
||||||
|
<p className="text-white/40 text-sm mt-2">
|
||||||
|
{searchQuery ? 'Попробуйте изменить условия поиска' : 'Добавьте оптовиков в разделе "Партнеры"'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Badge className="bg-green-500/20 text-green-300 border-green-500/30 text-lg px-4 py-2">
|
) : (
|
||||||
Доступно
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||||
</Badge>
|
{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>
|
</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>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
@ -997,26 +997,8 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen flex overflow-hidden">
|
<div className="space-y-4">
|
||||||
<Sidebar />
|
<div className="flex items-center justify-end">
|
||||||
<main className={`flex-1 ${getSidebarMargin()} overflow-auto transition-all duration-300`}>
|
|
||||||
<div className="p-6 space-y-6">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={onBack}
|
|
||||||
className="text-white/60 hover:text-white hover:bg-white/10"
|
|
||||||
>
|
|
||||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
|
||||||
Назад
|
|
||||||
</Button>
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-bold text-white mb-1">Карточки товаров Wildberries</h2>
|
|
||||||
<p className="text-white/60">Найдите и выберите товары для поставки</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedCards.length > 0 && (
|
{selectedCards.length > 0 && (
|
||||||
<Button
|
<Button
|
||||||
@ -1324,7 +1306,7 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
|
|||||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-8">
|
<Card className="bg-white/10 backdrop-blur border-white/20 p-8">
|
||||||
<div className="text-center max-w-md mx-auto">
|
<div className="text-center max-w-md mx-auto">
|
||||||
<Package className="h-12 w-12 text-white/20 mx-auto mb-4" />
|
<Package className="h-12 w-12 text-white/20 mx-auto mb-4" />
|
||||||
<h3 className="text-lg font-semibold text-white mb-2">Карточки товаров Wildberries</h3>
|
<h3 className="text-lg font-semibold text-white mb-2">Нет товаров</h3>
|
||||||
{user?.organization?.apiKeys?.find(key => key.marketplace === 'WILDBERRIES')?.isActive ? (
|
{user?.organization?.apiKeys?.find(key => key.marketplace === 'WILDBERRIES')?.isActive ? (
|
||||||
<>
|
<>
|
||||||
<p className="text-white/60 mb-4 text-sm">
|
<p className="text-white/60 mb-4 text-sm">
|
||||||
@ -1446,8 +1428,6 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
|
|||||||
)}
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
)
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
Reference in New Issue
Block a user