Добавлены модели товаров и корзины для оптовиков, реализованы соответствующие мутации и запросы в GraphQL. Обновлен API для загрузки файлов с учетом новых типов данных. Улучшена обработка ошибок и добавлены новые функции для работы с категориями товаров.
This commit is contained in:
109
src/components/cart/cart-dashboard.tsx
Normal file
109
src/components/cart/cart-dashboard.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
"use client"
|
||||
|
||||
import { useQuery } from '@apollo/client'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Sidebar } from '@/components/dashboard/sidebar'
|
||||
import { CartItems } from './cart-items'
|
||||
import { CartSummary } from './cart-summary'
|
||||
import { GET_MY_CART } from '@/graphql/queries'
|
||||
import { ShoppingCart, Package } from 'lucide-react'
|
||||
|
||||
export function CartDashboard() {
|
||||
const { data, loading, error } = useQuery(GET_MY_CART)
|
||||
|
||||
const cart = data?.myCart
|
||||
const hasItems = cart?.items && cart.items.length > 0
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="h-screen bg-gradient-smooth flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className="flex-1 ml-56 px-6 py-4 overflow-hidden">
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-16 w-16 border-4 border-white border-t-transparent mx-auto mb-4"></div>
|
||||
<p className="text-white/70">Загружаем корзину...</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="h-screen bg-gradient-smooth flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className="flex-1 ml-56 px-6 py-4 overflow-hidden">
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<ShoppingCart className="h-16 w-16 text-red-400/40 mx-auto mb-4" />
|
||||
<p className="text-red-400">Ошибка загрузки корзины</p>
|
||||
<p className="text-white/40 text-sm mt-2">{error.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen bg-gradient-smooth flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className="flex-1 ml-56 px-6 py-4 overflow-hidden">
|
||||
<div className="h-full w-full flex flex-col">
|
||||
{/* Заголовок */}
|
||||
<div className="flex items-center space-x-3 mb-6">
|
||||
<ShoppingCart className="h-6 w-6 text-purple-400" />
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">Корзина</h1>
|
||||
<p className="text-white/60">
|
||||
{hasItems
|
||||
? `${cart.totalItems} товаров на сумму ${new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB' }).format(cart.totalPrice)}`
|
||||
: 'Ваша корзина пуста'
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Основной контент */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
{hasItems ? (
|
||||
<div className="h-full grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* Товары в корзине */}
|
||||
<div className="lg:col-span-2">
|
||||
<Card className="glass-card h-full overflow-hidden">
|
||||
<CartItems cart={cart} />
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Сводка заказа */}
|
||||
<div className="lg:col-span-1">
|
||||
<Card className="glass-card h-fit">
|
||||
<CartSummary cart={cart} />
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Card className="glass-card h-full overflow-hidden p-8">
|
||||
<div className="h-full flex flex-col items-center justify-center text-center">
|
||||
<Package className="h-24 w-24 text-white/20 mb-6" />
|
||||
<h2 className="text-xl font-semibold text-white mb-2">Корзина пуста</h2>
|
||||
<p className="text-white/60 mb-6 max-w-md">
|
||||
Добавьте товары из маркета, чтобы оформить заказ
|
||||
</p>
|
||||
<button
|
||||
onClick={() => window.location.href = '/market'}
|
||||
className="bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white px-6 py-3 rounded-lg font-medium transition-all"
|
||||
>
|
||||
Перейти в маркет
|
||||
</button>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
389
src/components/cart/cart-items.tsx
Normal file
389
src/components/cart/cart-items.tsx
Normal file
@ -0,0 +1,389 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useMutation } from '@apollo/client'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import {
|
||||
Trash2,
|
||||
AlertTriangle,
|
||||
Package,
|
||||
Store,
|
||||
Minus,
|
||||
Plus
|
||||
} from 'lucide-react'
|
||||
import { OrganizationAvatar } from '@/components/market/organization-avatar'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import Image from 'next/image'
|
||||
import { UPDATE_CART_ITEM, REMOVE_FROM_CART, CLEAR_CART } from '@/graphql/mutations'
|
||||
import { GET_MY_CART } from '@/graphql/queries'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
interface CartItem {
|
||||
id: string
|
||||
quantity: number
|
||||
totalPrice: number
|
||||
isAvailable: boolean
|
||||
availableQuantity: number
|
||||
product: {
|
||||
id: string
|
||||
name: string
|
||||
article: string
|
||||
price: number
|
||||
quantity: number
|
||||
images: string[]
|
||||
mainImage?: string
|
||||
organization: {
|
||||
id: string
|
||||
name?: string
|
||||
fullName?: string
|
||||
inn: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Cart {
|
||||
id: string
|
||||
items: CartItem[]
|
||||
totalPrice: number
|
||||
totalItems: number
|
||||
}
|
||||
|
||||
interface CartItemsProps {
|
||||
cart: Cart
|
||||
}
|
||||
|
||||
export function CartItems({ cart }: CartItemsProps) {
|
||||
const [loadingItems, setLoadingItems] = useState<Set<string>>(new Set())
|
||||
|
||||
const [updateCartItem] = useMutation(UPDATE_CART_ITEM, {
|
||||
refetchQueries: [{ query: GET_MY_CART }],
|
||||
onCompleted: (data) => {
|
||||
if (data.updateCartItem.success) {
|
||||
toast.success(data.updateCartItem.message)
|
||||
} else {
|
||||
toast.error(data.updateCartItem.message)
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error('Ошибка при обновлении заявки')
|
||||
console.error('Error updating cart item:', error)
|
||||
}
|
||||
})
|
||||
|
||||
const [removeFromCart] = useMutation(REMOVE_FROM_CART, {
|
||||
refetchQueries: [{ query: GET_MY_CART }],
|
||||
onCompleted: (data) => {
|
||||
if (data.removeFromCart.success) {
|
||||
toast.success(data.removeFromCart.message)
|
||||
} else {
|
||||
toast.error(data.removeFromCart.message)
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error('Ошибка при удалении заявки')
|
||||
console.error('Error removing from cart:', error)
|
||||
}
|
||||
})
|
||||
|
||||
const [clearCart] = useMutation(CLEAR_CART, {
|
||||
refetchQueries: [{ query: GET_MY_CART }],
|
||||
onCompleted: () => {
|
||||
toast.success('Заявки очищены')
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error('Ошибка при очистке заявок')
|
||||
console.error('Error clearing cart:', error)
|
||||
}
|
||||
})
|
||||
|
||||
const updateQuantity = async (productId: string, newQuantity: number) => {
|
||||
if (newQuantity <= 0) return
|
||||
|
||||
setLoadingItems(prev => new Set(prev).add(productId))
|
||||
|
||||
try {
|
||||
await updateCartItem({
|
||||
variables: {
|
||||
productId,
|
||||
quantity: newQuantity
|
||||
}
|
||||
})
|
||||
} finally {
|
||||
setLoadingItems(prev => {
|
||||
const newSet = new Set(prev)
|
||||
newSet.delete(productId)
|
||||
return newSet
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const removeItem = async (productId: string) => {
|
||||
setLoadingItems(prev => new Set(prev).add(productId))
|
||||
|
||||
try {
|
||||
await removeFromCart({
|
||||
variables: { productId }
|
||||
})
|
||||
} finally {
|
||||
setLoadingItems(prev => {
|
||||
const newSet = new Set(prev)
|
||||
newSet.delete(productId)
|
||||
return newSet
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleClearCart = async () => {
|
||||
if (confirm('Вы уверены, что хотите очистить все заявки?')) {
|
||||
await clearCart()
|
||||
}
|
||||
}
|
||||
|
||||
const formatPrice = (price: number) => {
|
||||
return new Intl.NumberFormat('ru-RU', {
|
||||
style: 'currency',
|
||||
currency: 'RUB'
|
||||
}).format(price)
|
||||
}
|
||||
|
||||
const unavailableItems = cart.items.filter(item => !item.isAvailable)
|
||||
const availableItems = cart.items.filter(item => item.isAvailable)
|
||||
|
||||
// Группировка товаров по поставщикам
|
||||
const groupedItems = cart.items.reduce((groups, item) => {
|
||||
const orgId = item.product.organization.id
|
||||
if (!groups[orgId]) {
|
||||
groups[orgId] = {
|
||||
organization: item.product.organization,
|
||||
items: [],
|
||||
totalPrice: 0,
|
||||
totalItems: 0
|
||||
}
|
||||
}
|
||||
groups[orgId].items.push(item)
|
||||
groups[orgId].totalPrice += item.totalPrice
|
||||
groups[orgId].totalItems += item.quantity
|
||||
return groups
|
||||
}, {} as Record<string, {
|
||||
organization: CartItem['product']['organization'],
|
||||
items: CartItem[],
|
||||
totalPrice: number,
|
||||
totalItems: number
|
||||
}>)
|
||||
|
||||
const supplierGroups = Object.values(groupedItems)
|
||||
|
||||
return (
|
||||
<div className="p-6 h-full flex flex-col">
|
||||
{/* Заголовок с кнопкой очистки */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-semibold text-white">Заявки на товары</h2>
|
||||
{cart.items.length > 0 && (
|
||||
<Button
|
||||
onClick={handleClearCart}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="border-red-500/30 text-red-400 hover:bg-red-500/10"
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
Очистить заявки
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Предупреждение о недоступных товарах */}
|
||||
{unavailableItems.length > 0 && (
|
||||
<div className="mb-6 p-4 bg-orange-500/10 border border-orange-500/20 rounded-lg">
|
||||
<div className="flex items-center space-x-2 text-orange-400">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<span className="text-sm font-medium">
|
||||
{unavailableItems.length} заявок недоступно для оформления
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Группы поставщиков */}
|
||||
<div className="flex-1 overflow-auto space-y-8">
|
||||
{supplierGroups.map((group) => (
|
||||
<div key={group.organization.id} className="space-y-4">
|
||||
{/* Заголовок поставщика */}
|
||||
<div className="bg-gradient-to-r from-purple-600/20 via-purple-500/10 to-transparent border border-purple-500/20 rounded-xl p-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<OrganizationAvatar organization={group.organization} size="md" />
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-white mb-1">
|
||||
{group.organization.name || group.organization.fullName || `ИНН ${group.organization.inn}`}
|
||||
</h3>
|
||||
<div className="flex items-center space-x-3 text-sm text-white/60">
|
||||
<span className="flex items-center space-x-1">
|
||||
<Package className="h-4 w-4" />
|
||||
<span>{group.totalItems} товаров</span>
|
||||
</span>
|
||||
<span>•</span>
|
||||
<span className="font-medium text-purple-300">
|
||||
{formatPrice(group.totalPrice)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="bg-purple-500/20 text-purple-300 px-3 py-1 text-sm font-medium"
|
||||
>
|
||||
{group.items.length} заявок
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Товары этого поставщика */}
|
||||
<div className="space-y-3">
|
||||
{group.items.map((item) => {
|
||||
const isLoading = loadingItems.has(item.product.id)
|
||||
const mainImage = item.product.images?.[0] || item.product.mainImage
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className={`bg-white/5 backdrop-blur border border-white/10 rounded-xl transition-all hover:bg-white/8 hover:border-white/20 ${
|
||||
!item.isAvailable ? 'opacity-60' : ''
|
||||
}`}
|
||||
>
|
||||
{/* Информация о поставщике в карточке товара */}
|
||||
<div className="px-4 py-2 bg-white/5 border-b border-white/10 rounded-t-xl">
|
||||
<div className="flex items-center space-x-2 text-xs text-white/60">
|
||||
<Store className="h-3 w-3" />
|
||||
<span>Поставщик:</span>
|
||||
<span className="text-white/80 font-medium">
|
||||
{item.product.organization.name || item.product.organization.fullName || `ИНН ${item.product.organization.inn}`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Основное содержимое карточки */}
|
||||
<div className="p-5">
|
||||
<div className="flex space-x-4">
|
||||
{/* Изображение товара */}
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-20 h-20 bg-white/5 rounded-xl overflow-hidden border border-white/10 shadow-lg">
|
||||
{mainImage ? (
|
||||
<Image
|
||||
src={mainImage}
|
||||
alt={item.product.name}
|
||||
width={80}
|
||||
height={80}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<Package className="h-8 w-8 text-white/20" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Информация о товаре */}
|
||||
<div className="flex-1 min-w-0">
|
||||
{/* Название и артикул */}
|
||||
<div className="mb-3">
|
||||
<h4 className="text-base font-semibold text-white mb-1 line-clamp-2">
|
||||
{item.product.name}
|
||||
</h4>
|
||||
<p className="text-sm text-white/50">
|
||||
Артикул: {item.product.article}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Статус доступности */}
|
||||
{!item.isAvailable && (
|
||||
<Badge className="bg-red-500/20 text-red-300 text-xs mb-3 border border-red-500/30">
|
||||
Недоступно
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{/* Нижняя секция: управление количеством и цена */}
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Управление количеством */}
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm text-white/60 font-medium">Количество:</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
onClick={() => updateQuantity(item.product.id, item.quantity - 1)}
|
||||
disabled={isLoading || !item.isAvailable || item.quantity <= 1}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0 border-white/20 text-white/70 hover:bg-white/10"
|
||||
>
|
||||
<Minus className="h-3 w-3" />
|
||||
</Button>
|
||||
|
||||
<Input
|
||||
type="number"
|
||||
min="1"
|
||||
max={item.availableQuantity}
|
||||
value={item.quantity}
|
||||
onChange={(e) => {
|
||||
const value = parseInt(e.target.value) || 1
|
||||
if (value >= 1 && value <= item.availableQuantity && !isLoading && item.isAvailable) {
|
||||
updateQuantity(item.product.id, value)
|
||||
}
|
||||
}}
|
||||
disabled={isLoading || !item.isAvailable}
|
||||
className="w-16 h-8 text-sm text-center bg-white/5 border border-white/20 rounded-lg text-white focus:border-purple-400/50 focus:bg-white/10"
|
||||
/>
|
||||
|
||||
<Button
|
||||
onClick={() => updateQuantity(item.product.id, item.quantity + 1)}
|
||||
disabled={isLoading || !item.isAvailable || item.quantity >= item.availableQuantity}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0 border-white/20 text-white/70 hover:bg-white/10"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className="text-sm text-white/40">
|
||||
из {item.availableQuantity} доступно
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Цена и кнопка удаления */}
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="text-right">
|
||||
<div className="text-lg font-bold text-purple-300 mb-1">
|
||||
{formatPrice(item.totalPrice)}
|
||||
</div>
|
||||
<div className="text-sm text-white/50">
|
||||
{formatPrice(item.product.price)} за шт.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={() => removeItem(item.product.id)}
|
||||
disabled={isLoading}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-red-400 border-red-500/30 hover:bg-red-500/10 hover:text-red-300 h-9 w-9 p-0"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
242
src/components/cart/cart-summary.tsx
Normal file
242
src/components/cart/cart-summary.tsx
Normal file
@ -0,0 +1,242 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import {
|
||||
ShoppingCart,
|
||||
AlertTriangle,
|
||||
CheckCircle,
|
||||
Info
|
||||
} from 'lucide-react'
|
||||
|
||||
interface CartItem {
|
||||
id: string
|
||||
quantity: number
|
||||
totalPrice: number
|
||||
isAvailable: boolean
|
||||
availableQuantity: number
|
||||
product: {
|
||||
id: string
|
||||
name: string
|
||||
article: string
|
||||
price: number
|
||||
quantity: number
|
||||
organization: {
|
||||
id: string
|
||||
name?: string
|
||||
fullName?: string
|
||||
inn: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Cart {
|
||||
id: string
|
||||
items: CartItem[]
|
||||
totalPrice: number
|
||||
totalItems: number
|
||||
}
|
||||
|
||||
interface CartSummaryProps {
|
||||
cart: Cart
|
||||
}
|
||||
|
||||
export function CartSummary({ cart }: CartSummaryProps) {
|
||||
const [isProcessingOrder, setIsProcessingOrder] = useState(false)
|
||||
|
||||
const formatPrice = (price: number) => {
|
||||
return new Intl.NumberFormat('ru-RU', {
|
||||
style: 'currency',
|
||||
currency: 'RUB'
|
||||
}).format(price)
|
||||
}
|
||||
|
||||
// Анализ товаров в корзине
|
||||
const availableItems = cart.items.filter(item => item.isAvailable)
|
||||
const unavailableItems = cart.items.filter(item => !item.isAvailable)
|
||||
|
||||
const availableTotal = availableItems.reduce((sum, item) => sum + item.totalPrice, 0)
|
||||
const availableItemsCount = availableItems.reduce((sum, item) => sum + item.quantity, 0)
|
||||
|
||||
// Группировка по продавцам
|
||||
const sellerGroups = availableItems.reduce((groups, item) => {
|
||||
const sellerId = item.product.organization.id
|
||||
if (!groups[sellerId]) {
|
||||
groups[sellerId] = {
|
||||
organization: item.product.organization,
|
||||
items: [],
|
||||
total: 0
|
||||
}
|
||||
}
|
||||
groups[sellerId].items.push(item)
|
||||
groups[sellerId].total += item.totalPrice
|
||||
return groups
|
||||
}, {} as Record<string, {
|
||||
organization: CartItem['product']['organization']
|
||||
items: CartItem[]
|
||||
total: number
|
||||
}>)
|
||||
|
||||
const sellerCount = Object.keys(sellerGroups).length
|
||||
const canOrder = availableItems.length > 0
|
||||
|
||||
const handleOrder = () => {
|
||||
if (!canOrder) return
|
||||
|
||||
setIsProcessingOrder(true)
|
||||
// Здесь будет логика отправки заявок
|
||||
setTimeout(() => {
|
||||
alert('Функция отправки заявок будет реализована в следующих версиях')
|
||||
setIsProcessingOrder(false)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const getOrganizationName = (org: CartItem['product']['organization']) => {
|
||||
return org.name || org.fullName || 'Неизвестная организация'
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<h2 className="text-lg font-semibold text-white mb-4">Сводка заявок</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Общая информация */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-white/70">Всего заявок:</span>
|
||||
<span className="text-white">{cart.totalItems}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-white/70">Готово к отправке:</span>
|
||||
<span className="text-white">{availableItemsCount}</span>
|
||||
</div>
|
||||
{unavailableItems.length > 0 && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-red-400">Недоступно:</span>
|
||||
<span className="text-red-400">{unavailableItems.length}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Separator className="bg-white/10" />
|
||||
|
||||
{/* Информация о поставщиках */}
|
||||
{sellerCount > 0 && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center space-x-2 text-sm text-white/70">
|
||||
<Info className="h-4 w-4" />
|
||||
<span>Поставщики ({sellerCount}):</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{Object.values(sellerGroups).map((group, index) => (
|
||||
<div
|
||||
key={group.organization.id}
|
||||
className="bg-white/5 p-3 rounded-lg text-xs"
|
||||
>
|
||||
<div className="flex justify-between items-start mb-1">
|
||||
<span className="text-white font-medium line-clamp-1">
|
||||
{getOrganizationName(group.organization)}
|
||||
</span>
|
||||
<span className="text-purple-300 font-medium">
|
||||
{formatPrice(group.total)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-white/50">
|
||||
ИНН: {group.organization.inn}
|
||||
</div>
|
||||
<div className="text-white/50">
|
||||
Заявок: {group.items.length}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Separator className="bg-white/10" />
|
||||
|
||||
{/* Итоговая стоимость */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-white/70">Стоимость заявок:</span>
|
||||
<span className="text-white">{formatPrice(availableTotal)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-lg font-semibold">
|
||||
<span className="text-white">Общая сумма:</span>
|
||||
<span className="text-purple-300">{formatPrice(availableTotal)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator className="bg-white/10" />
|
||||
|
||||
{/* Статус заявок */}
|
||||
<div className="space-y-3">
|
||||
{canOrder ? (
|
||||
<div className="flex items-center space-x-2 text-green-400 text-sm">
|
||||
<CheckCircle className="h-4 w-4" />
|
||||
<span>Готово к отправке</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center space-x-2 text-orange-400 text-sm">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<span>Нет доступных заявок</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{unavailableItems.length > 0 && (
|
||||
<div className="bg-orange-500/10 border border-orange-500/20 rounded-lg p-3 text-xs">
|
||||
<div className="text-orange-400 font-medium mb-1">
|
||||
Внимание!
|
||||
</div>
|
||||
<div className="text-orange-300">
|
||||
{unavailableItems.length} заявок недоступно.
|
||||
Они будут исключены при отправке.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{sellerCount > 1 && (
|
||||
<div className="bg-blue-500/10 border border-blue-500/20 rounded-lg p-3 text-xs">
|
||||
<div className="text-blue-400 font-medium mb-1">
|
||||
Несколько продавцов
|
||||
</div>
|
||||
<div className="text-blue-300">
|
||||
Ваши заявки будут отправлены {sellerCount} разным продавцам
|
||||
для рассмотрения.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Кнопка отправки заявок */}
|
||||
<Button
|
||||
onClick={handleOrder}
|
||||
disabled={!canOrder || isProcessingOrder}
|
||||
className={`w-full h-12 text-sm font-medium ${
|
||||
canOrder
|
||||
? 'bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white'
|
||||
: 'bg-gray-500/20 text-gray-400 cursor-not-allowed'
|
||||
}`}
|
||||
>
|
||||
<ShoppingCart className="h-4 w-4 mr-2" />
|
||||
{isProcessingOrder
|
||||
? 'Отправляем заявки...'
|
||||
: canOrder
|
||||
? `Отправить заявки • ${formatPrice(availableTotal)}`
|
||||
: 'Невозможно отправить заявки'
|
||||
}
|
||||
</Button>
|
||||
|
||||
{/* Дополнительная информация */}
|
||||
<div className="text-xs text-white/50 space-y-1">
|
||||
<p>• Заявки будут отправлены продавцам для рассмотрения</p>
|
||||
<p>• Окончательные условия согласовываются с каждым продавцом</p>
|
||||
<p>• Вы можете изменить количество товаров до отправки заявок</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user