Обновлен компонент карточек товаров: добавлен сайдбар для улучшения навигации, изменена компоновка для адаптивного отображения, улучшены стили и взаимодействие с пользователем. Оптимизирован код для повышения читаемости и производительности.

This commit is contained in:
Bivekich
2025-07-21 17:13:11 +03:00
parent ec974998c9
commit 155c6c95cd

View File

@ -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,7 +592,10 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
if (showSummary) {
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
@ -722,11 +728,16 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
</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">
<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/200'}
src={card.mediaFiles?.[0] || '/api/placeholder/300/300'}
alt={card.title}
className="w-full h-48 rounded-lg object-cover cursor-pointer"
className="w-full h-full object-cover cursor-pointer group-hover:scale-110 transition-transform duration-500"
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">
{/* Количество в наличии */}
<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="outline"
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 mr-2" />
Подробнее
<Eye className="h-4 w-4" />
</Button>
</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>
</div>
</div>
{/* Количество */}
<div className="space-y-2">
<Label className="text-white text-sm">Количество для заказа</Label>
<div className="flex items-center space-x-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">
<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>
{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}
@ -1203,5 +1133,7 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
</DialogContent>
</Dialog>
</div>
</main>
</div>
)
}