Обновлен компонент карточек товаров: добавлен сайдбар для улучшения навигации, изменена компоновка для адаптивного отображения, улучшены стили и взаимодействие с пользователем. Оптимизирован код для повышения читаемости и производительности.
This commit is contained in:
@ -8,6 +8,8 @@ import { Badge } from '@/components/ui/badge'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Sidebar } from '@/components/dashboard/sidebar'
|
||||
import { useSidebar } from '@/hooks/useSidebar'
|
||||
import {
|
||||
Search,
|
||||
Plus,
|
||||
@ -79,6 +81,7 @@ interface WBProductCardsProps {
|
||||
|
||||
export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
|
||||
const { user } = useAuth()
|
||||
const { getSidebarMargin } = useSidebar()
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [wbCards, setWbCards] = useState<WildberriesCard[]>([])
|
||||
@ -589,32 +592,35 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
|
||||
|
||||
if (showSummary) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowSummary(false)}
|
||||
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">Сводка заказа</h2>
|
||||
<p className="text-white/60">Проверьте данные перед созданием поставки</p>
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<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={() => setShowSummary(false)}
|
||||
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">Сводка заказа</h2>
|
||||
<p className="text-white/60">Проверьте данные перед созданием поставки</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onBack}
|
||||
className="text-white/60 hover:text-white hover:bg-white/10"
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onBack}
|
||||
className="text-white/60 hover:text-white hover:bg-white/10"
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="lg:col-span-2 space-y-4">
|
||||
@ -720,13 +726,18 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<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
|
||||
@ -780,16 +791,17 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
|
||||
|
||||
{/* Состояние загрузки */}
|
||||
{loading && (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
|
||||
{[...Array(6)].map((_, i) => (
|
||||
<Card key={i} className="bg-white/10 backdrop-blur border-white/20 p-4 animate-pulse">
|
||||
<div className="space-y-4">
|
||||
<div className="bg-white/20 rounded-lg h-48 w-full"></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">
|
||||
{[...Array(12)].map((_, i) => (
|
||||
<Card key={i} className="bg-white/10 backdrop-blur border-white/20 p-3 animate-pulse">
|
||||
<div className="space-y-3">
|
||||
<div className="bg-white/20 rounded-lg aspect-square w-full"></div>
|
||||
<div className="space-y-2">
|
||||
<div className="bg-white/20 rounded h-4 w-3/4"></div>
|
||||
<div className="bg-white/20 rounded h-4 w-1/2"></div>
|
||||
<div className="bg-white/20 rounded h-3 w-3/4"></div>
|
||||
<div className="bg-white/20 rounded h-3 w-1/2"></div>
|
||||
<div className="bg-white/20 rounded h-4 w-2/3"></div>
|
||||
</div>
|
||||
<div className="bg-white/20 rounded h-10 w-full"></div>
|
||||
<div className="bg-white/20 rounded h-7 w-full"></div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
@ -798,7 +810,7 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
|
||||
|
||||
{/* Карточки товаров */}
|
||||
{!loading && wbCards.length > 0 && (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-4">
|
||||
{wbCards.map((card) => {
|
||||
const selectedQuantity = getSelectedQuantity(card)
|
||||
const isSelected = selectedQuantity > 0
|
||||
@ -808,199 +820,117 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
|
||||
const price = mainSize?.discountedPrice || mainSize?.price || 0
|
||||
|
||||
return (
|
||||
<Card key={card.nmID} className={`bg-white/10 backdrop-blur border-white/20 transition-all ${isSelected ? 'ring-2 ring-purple-500/50' : ''}`}>
|
||||
<div className="p-4 space-y-4">
|
||||
<Card key={card.nmID} className={`bg-white/10 backdrop-blur border-white/20 transition-all hover:scale-105 hover:shadow-2xl group ${isSelected ? 'ring-2 ring-purple-500/50 bg-purple-500/10' : ''}`}>
|
||||
<div className="p-3 space-y-3">
|
||||
{/* Изображение и основная информация */}
|
||||
<div className="space-y-3">
|
||||
<div className="relative group">
|
||||
<img
|
||||
src={card.mediaFiles?.[0] || '/api/placeholder/300/200'}
|
||||
alt={card.title}
|
||||
className="w-full h-48 rounded-lg object-cover cursor-pointer"
|
||||
onClick={() => handleCardClick(card)}
|
||||
/>
|
||||
{/* Кнопка "Подробнее" при наведении */}
|
||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center rounded-lg">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
<div className="space-y-2">
|
||||
<div className="relative">
|
||||
<div className="aspect-square relative bg-white/5 overflow-hidden rounded-lg">
|
||||
<img
|
||||
src={card.mediaFiles?.[0] || '/api/placeholder/300/300'}
|
||||
alt={card.title}
|
||||
className="w-full h-full object-cover cursor-pointer group-hover:scale-110 transition-transform duration-500"
|
||||
onClick={() => handleCardClick(card)}
|
||||
className="bg-white/20 backdrop-blur text-white border-white/30 hover:bg-white/30"
|
||||
>
|
||||
<Eye className="h-4 w-4 mr-2" />
|
||||
Подробнее
|
||||
</Button>
|
||||
/>
|
||||
|
||||
{/* Количество в наличии */}
|
||||
<div className="absolute top-2 right-2">
|
||||
<Badge className={`${maxQuantity > 10 ? 'bg-green-500/80' : maxQuantity > 0 ? 'bg-yellow-500/80' : 'bg-red-500/80'} text-white border-0 backdrop-blur text-xs`}>
|
||||
{maxQuantity}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Overlay с кнопкой */}
|
||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={() => handleCardClick(card)}
|
||||
className="bg-white/20 backdrop-blur text-white border-white/30 hover:bg-white/30"
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-white font-medium text-lg mb-1 cursor-pointer hover:text-purple-300 transition-colors" onClick={() => handleCardClick(card)}>{card.title}</h3>
|
||||
<p className="text-white/60 text-sm mb-2">{card.vendorCode}</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-white font-bold text-xl">{formatCurrency(price)}</span>
|
||||
<Badge className={`${maxQuantity > 10 ? 'bg-green-500/20 text-green-300' : maxQuantity > 0 ? 'bg-yellow-500/20 text-yellow-300' : 'bg-red-500/20 text-red-300'}`}>
|
||||
{maxQuantity} шт.
|
||||
</Badge>
|
||||
|
||||
<div className="space-y-2">
|
||||
{/* Заголовок и бренд */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<Badge className="bg-gray-500/20 text-gray-300 border-gray-500/30 text-xs">
|
||||
{card.brand}
|
||||
</Badge>
|
||||
</div>
|
||||
<h3 className="text-white font-semibold text-sm mb-1 line-clamp-2 leading-tight cursor-pointer hover:text-purple-300 transition-colors" onClick={() => handleCardClick(card)}>
|
||||
{card.title}
|
||||
</h3>
|
||||
<p className="text-white/60 text-xs mb-1">{card.vendorCode}</p>
|
||||
</div>
|
||||
|
||||
{/* Цена */}
|
||||
<div className="pt-1 border-t border-white/10">
|
||||
<div className="text-white font-bold text-base">
|
||||
{formatCurrency(price)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Количество */}
|
||||
{/* Управление количеством */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-white text-sm">Количество для заказа</Label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center space-x-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => updateCardSelection(card, 'selectedQuantity', 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"
|
||||
className="h-7 w-7 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="number"
|
||||
min="0"
|
||||
max={maxQuantity}
|
||||
<input
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
pattern="[0-9]*"
|
||||
value={selectedQuantity}
|
||||
onChange={(e) => updateCardSelection(card, 'selectedQuantity', Math.min(maxQuantity, Math.max(0, parseInt(e.target.value) || 0)))}
|
||||
className="bg-white/5 border-white/20 text-white text-center w-20 h-8"
|
||||
onChange={(e) => {
|
||||
const value = e.target.value.replace(/[^0-9]/g, '')
|
||||
const numValue = Math.max(0, Math.min(maxQuantity, parseInt(value) || 0))
|
||||
updateCardSelection(card, 'selectedQuantity', numValue)
|
||||
}}
|
||||
onFocus={(e) => e.target.select()}
|
||||
className="h-7 w-12 text-center bg-white/10 border border-white/20 text-white text-sm rounded focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => updateCardSelection(card, 'selectedQuantity', Math.min(maxQuantity, selectedQuantity + 1))}
|
||||
disabled={selectedQuantity >= maxQuantity}
|
||||
className="h-8 w-8 p-0 text-white/60 hover:text-white hover:bg-white/10"
|
||||
className="h-7 w-7 p-0 text-white/60 hover:text-white hover:bg-white/10 border border-white/20"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Детальные настройки для выбранных товаров */}
|
||||
{isSelected && selectedCard && (
|
||||
<div className="space-y-4 pt-4 border-t border-white/20">
|
||||
{/* Рынок */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-white text-sm flex items-center">
|
||||
<MapPin className="h-3 w-3 mr-1" />
|
||||
Рынок
|
||||
</Label>
|
||||
<Select
|
||||
value={selectedCard.selectedMarket}
|
||||
onValueChange={(value) => updateCardSelection(card, 'selectedMarket', value)}
|
||||
>
|
||||
<SelectTrigger className="bg-white/5 border-white/20 text-white">
|
||||
<SelectValue placeholder="Выберите рынок" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{markets.map((market) => (
|
||||
<SelectItem key={market.value} value={market.value}>
|
||||
{market.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Место на рынке */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-white text-sm">Место на рынке</Label>
|
||||
<Input
|
||||
placeholder="Например: Ряд 5, место 23"
|
||||
value={selectedCard.selectedPlace}
|
||||
onChange={(e) => updateCardSelection(card, 'selectedPlace', e.target.value)}
|
||||
className="bg-white/5 border-white/20 text-white placeholder-white/50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Данные продавца */}
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-white text-sm flex items-center">
|
||||
<User className="h-3 w-3 mr-1" />
|
||||
Имя продавца
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Имя продавца"
|
||||
value={selectedCard.sellerName}
|
||||
onChange={(e) => updateCardSelection(card, 'sellerName', e.target.value)}
|
||||
className="bg-white/5 border-white/20 text-white placeholder-white/50"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-white text-sm flex items-center">
|
||||
<Phone className="h-3 w-3 mr-1" />
|
||||
Телефон продавца
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="+7 (999) 123-45-67"
|
||||
value={selectedCard.sellerPhone}
|
||||
onChange={(e) => updateCardSelection(card, 'sellerPhone', e.target.value)}
|
||||
className="bg-white/5 border-white/20 text-white placeholder-white/50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Дата поставки */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-white text-sm flex items-center">
|
||||
<Calendar className="h-3 w-3 mr-1" />
|
||||
Дата поставки
|
||||
</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={selectedCard.deliveryDate}
|
||||
onChange={(e) => updateCardSelection(card, 'deliveryDate', e.target.value)}
|
||||
className="bg-white/5 border-white/20 text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Услуги фулфилмента */}
|
||||
{fulfillmentServices.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<Label className="text-white text-sm flex items-center">
|
||||
<Wrench className="h-3 w-3 mr-1" />
|
||||
Услуги фулфилмента
|
||||
</Label>
|
||||
<div className="space-y-2 max-h-40 overflow-y-auto">
|
||||
{fulfillmentServices.map((service) => (
|
||||
<label key={service.id} className="flex items-center space-x-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedCard.selectedServices.includes(service.id)}
|
||||
onChange={(e) => {
|
||||
const newServices = e.target.checked
|
||||
? [...selectedCard.selectedServices, service.id]
|
||||
: selectedCard.selectedServices.filter(id => id !== service.id)
|
||||
updateCardSelection(card, 'selectedServices', newServices)
|
||||
}}
|
||||
className="rounded border-white/20 bg-white/5 text-purple-500"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<div className="text-white text-sm">{service.name}</div>
|
||||
<div className="text-white/60 text-xs">
|
||||
{service.organizationName} • {formatCurrency(service.price)}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{selectedCards.length > 1 && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => applyServicesToAll(selectedCard.selectedServices)}
|
||||
className="w-full text-xs border-purple-500/30 text-purple-300 hover:bg-purple-500/10"
|
||||
>
|
||||
Применить ко всем карточкам
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{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-1">
|
||||
<div className="text-green-300 text-xs font-medium text-center">
|
||||
{formatCurrency(price * selectedQuantity)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
@ -1022,13 +952,13 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
|
||||
)}
|
||||
|
||||
{wbCards.length === 0 && !loading && (
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-12">
|
||||
<div className="text-center">
|
||||
<Package className="h-16 w-16 text-white/20 mx-auto mb-4" />
|
||||
<h3 className="text-xl font-semibold text-white mb-2">Карточки товаров Wildberries</h3>
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-8">
|
||||
<div className="text-center max-w-md mx-auto">
|
||||
<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>
|
||||
{user?.organization?.apiKeys?.find(key => key.marketplace === 'WILDBERRIES')?.isActive ? (
|
||||
<>
|
||||
<p className="text-white/60 mb-6">
|
||||
<p className="text-white/60 mb-4 text-sm">
|
||||
Введите запрос в поле поиска, чтобы найти товары в вашем каталоге Wildberries, или загрузите все доступные карточки
|
||||
</p>
|
||||
<Button
|
||||
@ -1036,16 +966,16 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
|
||||
className="bg-gradient-to-r from-blue-500 to-cyan-500 hover:from-blue-600 hover:to-cyan-600 text-white"
|
||||
>
|
||||
<Package className="h-4 w-4 mr-2" />
|
||||
Загрузить карточки из WB API
|
||||
Загрузить из WB API
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-white/60 mb-4">
|
||||
Для работы с реальными карточками товаров необходимо настроить API ключ Wildberries в настройках организации
|
||||
<p className="text-white/60 mb-3 text-sm">
|
||||
Для работы с реальными карточками необходимо настроить API ключ Wildberries
|
||||
</p>
|
||||
<p className="text-white/40 text-sm mb-6">
|
||||
Сейчас показаны демонстрационные товары. Для тестирования используйте поиск или загрузите все.
|
||||
<p className="text-white/40 text-xs mb-4">
|
||||
Показаны демонстрационные товары для тестирования
|
||||
</p>
|
||||
<Button
|
||||
onClick={loadAllCards}
|
||||
@ -1202,6 +1132,8 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user