Обновлены компоненты интерфейса для работы с карточками товаров Wildberries: добавлены новые функции для загрузки и поиска карточек, улучшен интерфейс отображения товаров и их деталей. Исправлены проблемы с отображением текста и добавлены новые поля в GraphQL для работы с API ключами. Реализована логика обработки ошибок при взаимодействии с API.

This commit is contained in:
Bivekich
2025-07-21 13:51:12 +03:00
parent d964b9b6d4
commit d3fb590c6e
10 changed files with 836 additions and 254 deletions

View File

@ -1447,7 +1447,7 @@ export function NavigationDemo() {
{/* Load More Pattern */}
<div>
<h4 className="text-white/90 text-sm font-medium mb-3">Паттерн "Загрузить еще"</h4>
<h4 className="text-white/90 text-sm font-medium mb-3">Паттерн &quot;Загрузить еще&quot;</h4>
<div className="glass-card p-4 rounded-xl border border-white/10 text-center">
<div className="space-y-3">
<div className="text-white/70 text-sm">

View File

@ -526,7 +526,7 @@ export function MaterialsOrderForm() {
: "Партнеры не найдены"}
</p>
<p className="text-white/40 text-sm mt-2">
Добавьте партнеров в разделе "Партнеры"
Добавьте партнеров в разделе &quot;Партнеры&quot;
</p>
</div>
</div>

View File

@ -30,6 +30,7 @@ import {
} from 'lucide-react'
import { useRouter } from 'next/navigation'
import Image from 'next/image'
import { WBProductCards } from './wb-product-cards'
interface WholesalerForCreation {
id: string
@ -72,6 +73,34 @@ interface SelectedProduct extends WholesalerProduct {
wholesalerName: string
}
interface WildberriesCard {
nmID: number
vendorCode: string
title: string
description: string
brand: string
mediaFiles: string[]
sizes: Array<{
chrtID: number
techSize: string
wbSize: string
price: number
discountedPrice: number
quantity: number
}>
}
interface SelectedCard {
card: WildberriesCard
selectedQuantity: number
selectedMarket: string
selectedPlace: string
sellerName: string
sellerPhone: string
deliveryDate: string
selectedServices: string[]
}
// Моковые данные оптовиков
const mockWholesalers: WholesalerForCreation[] = [
{
@ -229,6 +258,7 @@ export function CreateSupplyPage() {
const [selectedVariant, setSelectedVariant] = useState<'cards' | 'wholesaler' | null>(null)
const [selectedWholesaler, setSelectedWholesaler] = useState<WholesalerForCreation | null>(null)
const [selectedProducts, setSelectedProducts] = useState<SelectedProduct[]>([])
const [selectedCards, setSelectedCards] = useState<SelectedCard[]>([])
const [showSummary, setShowSummary] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
@ -329,6 +359,13 @@ export function CreateSupplyPage() {
return selectedProducts.reduce((sum, product) => sum + product.selectedQuantity, 0)
}
const handleCardsComplete = (cards: SelectedCard[]) => {
setSelectedCards(cards)
console.log('Карточки товаров выбраны:', cards)
// TODO: Здесь будет создание поставки с данными карточек
router.push('/supplies')
}
const handleCreateSupply = () => {
if (selectedVariant === 'cards') {
console.log('Создание поставки с карточками Wildberries')
@ -706,6 +743,16 @@ export function CreateSupplyPage() {
)
}
// Рендер карточек Wildberries
if (selectedVariant === 'cards') {
return (
<WBProductCards
onBack={() => setSelectedVariant(null)}
onComplete={handleCardsComplete}
/>
)
}
// Рендер выбора оптовиков
if (selectedVariant === 'wholesaler') {
return (

View File

@ -7,6 +7,7 @@ import { Input } from '@/components/ui/input'
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 {
Search,
Plus,
@ -20,7 +21,9 @@ import {
Wrench,
ArrowLeft,
Check,
X
Eye,
ChevronLeft,
ChevronRight
} from 'lucide-react'
import { WildberriesService } from '@/services/wildberries-service'
import { useAuth } from '@/hooks/useAuth'
@ -82,6 +85,176 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
const [selectedCards, setSelectedCards] = useState<SelectedCard[]>([])
const [showSummary, setShowSummary] = useState(false)
const [fulfillmentServices, setFulfillmentServices] = useState<FulfillmentService[]>([])
const [selectedCardForDetails, setSelectedCardForDetails] = useState<WildberriesCard | null>(null)
const [currentImageIndex, setCurrentImageIndex] = useState(0)
// Моковые товары для демонстрации
const getMockCards = (): WildberriesCard[] => [
{
nmID: 123456789,
vendorCode: 'SKU001',
title: 'Смартфон Samsung Galaxy A54',
description: 'Современный смартфон с отличной камерой и долгим временем автономной работы',
brand: 'Samsung',
object: 'Смартфоны',
parent: 'Электроника',
countryProduction: 'Корея',
supplierVendorCode: 'SUPPLIER-001',
mediaFiles: ['/api/placeholder/400/400', '/api/placeholder/400/401', '/api/placeholder/400/402'],
sizes: [
{
chrtID: 123456,
techSize: '128GB',
wbSize: '128GB Черный',
price: 25990,
discountedPrice: 22990,
quantity: 15
}
]
},
{
nmID: 987654321,
vendorCode: 'SKU002',
title: 'Наушники Apple AirPods Pro',
description: 'Беспроводные наушники с активным шумоподавлением и пространственным звуком',
brand: 'Apple',
object: 'Наушники',
parent: 'Электроника',
countryProduction: 'Китай',
supplierVendorCode: 'SUPPLIER-002',
mediaFiles: ['/api/placeholder/400/403', '/api/placeholder/400/404'],
sizes: [
{
chrtID: 987654,
techSize: 'Standard',
wbSize: 'Белый',
price: 24990,
discountedPrice: 19990,
quantity: 8
}
]
},
{
nmID: 555666777,
vendorCode: 'SKU003',
title: 'Кроссовки Nike Air Max 270',
description: 'Спортивные кроссовки с современным дизайном и комфортной посадкой',
brand: 'Nike',
object: 'Кроссовки',
parent: 'Обувь',
countryProduction: 'Вьетнам',
supplierVendorCode: 'SUPPLIER-003',
mediaFiles: ['/api/placeholder/400/405', '/api/placeholder/400/406', '/api/placeholder/400/407'],
sizes: [
{
chrtID: 555666,
techSize: '42',
wbSize: '42 EU',
price: 12990,
discountedPrice: 9990,
quantity: 25
},
{
chrtID: 555667,
techSize: '43',
wbSize: '43 EU',
price: 12990,
discountedPrice: 9990,
quantity: 20
}
]
},
{
nmID: 444333222,
vendorCode: 'SKU004',
title: 'Футболка Adidas Originals',
description: 'Классическая футболка из органического хлопка с логотипом бренда',
brand: 'Adidas',
object: 'Футболки',
parent: 'Одежда',
countryProduction: 'Бангладеш',
supplierVendorCode: 'SUPPLIER-004',
mediaFiles: ['/api/placeholder/400/408', '/api/placeholder/400/409'],
sizes: [
{
chrtID: 444333,
techSize: 'M',
wbSize: 'M',
price: 2990,
discountedPrice: 2490,
quantity: 50
},
{
chrtID: 444334,
techSize: 'L',
wbSize: 'L',
price: 2990,
discountedPrice: 2490,
quantity: 45
},
{
chrtID: 444335,
techSize: 'XL',
wbSize: 'XL',
price: 2990,
discountedPrice: 2490,
quantity: 30
}
]
},
{
nmID: 111222333,
vendorCode: 'SKU005',
title: 'Рюкзак для ноутбука Xiaomi',
description: 'Стильный и функциональный рюкзак для ноутбука до 15.6 дюймов',
brand: 'Xiaomi',
object: 'Рюкзаки',
parent: 'Аксессуары',
countryProduction: 'Китай',
supplierVendorCode: 'SUPPLIER-005',
mediaFiles: ['/api/placeholder/400/410'],
sizes: [
{
chrtID: 111222,
techSize: '15.6"',
wbSize: 'Черный',
price: 4990,
discountedPrice: 3990,
quantity: 35
}
]
},
{
nmID: 777888999,
vendorCode: 'SKU006',
title: 'Умные часы Apple Watch Series 9',
description: 'Новейшие умные часы с передовыми функциями здоровья и фитнеса',
brand: 'Apple',
object: 'Умные часы',
parent: 'Электроника',
countryProduction: 'Китай',
supplierVendorCode: 'SUPPLIER-006',
mediaFiles: ['/api/placeholder/400/411', '/api/placeholder/400/412', '/api/placeholder/400/413'],
sizes: [
{
chrtID: 777888,
techSize: '41mm',
wbSize: '41mm GPS',
price: 39990,
discountedPrice: 35990,
quantity: 12
},
{
chrtID: 777889,
techSize: '45mm',
wbSize: '45mm GPS',
price: 42990,
discountedPrice: 38990,
quantity: 8
}
]
}
]
// Загружаем контрагентов-фулфилментов
const { data: counterpartiesData } = useQuery(GET_MY_COUNTERPARTIES)
@ -113,12 +286,19 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
useEffect(() => {
// Загружаем услуги фулфилмента из контрагентов
if (counterpartiesData?.myCounterparties) {
interface Organization {
id: string
name?: string
fullName?: string
type: string
}
const fulfillmentOrganizations = counterpartiesData.myCounterparties.filter(
(org: any) => org.type === 'FULFILLMENT'
(org: Organization) => org.type === 'FULFILLMENT'
)
// В реальном приложении здесь был бы запрос услуг для каждой организации
const mockServices: FulfillmentService[] = fulfillmentOrganizations.flatMap((org: any) => [
const mockServices: FulfillmentService[] = fulfillmentOrganizations.flatMap((org: Organization) => [
{
id: `${org.id}-packaging`,
name: 'Упаковка товаров',
@ -146,83 +326,155 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
}
}, [counterpartiesData])
const searchCards = async () => {
if (!searchTerm.trim()) return
// Автоматически загружаем товары при открытии компонента
useEffect(() => {
const loadCards = async () => {
setLoading(true)
try {
const wbApiKey = user?.organization?.apiKeys?.find(key => key.marketplace === 'WILDBERRIES')
console.log('WB API Key found:', !!wbApiKey)
console.log('WB API Key active:', wbApiKey?.isActive)
console.log('WB API Key validationData:', wbApiKey?.validationData)
if (wbApiKey?.isActive) {
// Попытка загрузить реальные данные из API Wildberries
const validationData = wbApiKey.validationData as Record<string, string>
// API ключ может храниться в разных местах
const apiToken = validationData?.token ||
validationData?.apiKey ||
validationData?.key ||
(wbApiKey as { apiKey?: string }).apiKey // Прямое поле apiKey из базы
console.log('API Token extracted:', !!apiToken)
console.log('API Token length:', apiToken?.length)
if (apiToken) {
console.log('Загружаем карточки из WB API...')
const cards = await WildberriesService.getAllCards(apiToken, 50)
setWbCards(cards)
console.log('Загружено карточек из WB API:', cards.length)
return
}
}
// Если API ключ не настроен, оставляем пустое состояние
console.log('API ключ WB не настроен, показываем пустое состояние')
setWbCards([])
} catch (error) {
console.error('Ошибка загрузки карточек WB:', error)
// При ошибке API показываем пустое состояние
setWbCards([])
} finally {
setLoading(false)
}
}
loadCards()
}, [user])
const loadAllCards = async () => {
setLoading(true)
try {
const wbApiKey = user?.organization?.apiKeys?.find(key => key.marketplace === 'WILDBERRIES')
if (!wbApiKey?.isActive) {
throw new Error('API ключ Wildberries не настроен')
}
const validationData = wbApiKey.validationData as Record<string, string>
const apiToken = validationData?.token || validationData?.apiKey
if (!apiToken) {
throw new Error('API токен не найден')
}
const cards = await WildberriesService.searchCards(apiToken, searchTerm)
setWbCards(cards)
} catch (error) {
console.error('Ошибка поиска карточек:', error)
// Для демо загрузим моковые данные
setWbCards([
{
nmID: 123456789,
vendorCode: 'SKU001',
title: 'Смартфон Samsung Galaxy A54',
description: 'Современный смартфон с отличной камерой',
brand: 'Samsung',
object: 'Смартфоны',
parent: 'Электроника',
countryProduction: 'Корея',
supplierVendorCode: 'SUPPLIER-001',
mediaFiles: ['/api/placeholder/300/300'],
sizes: [
{
chrtID: 123456,
techSize: '128GB',
wbSize: '128GB Черный',
price: 25990,
discountedPrice: 22990,
quantity: 10
}
]
},
{
nmID: 987654321,
vendorCode: 'SKU002',
title: 'Наушники Apple AirPods Pro',
description: 'Беспроводные наушники с шумоподавлением',
brand: 'Apple',
object: 'Наушники',
parent: 'Электроника',
countryProduction: 'Китай',
supplierVendorCode: 'SUPPLIER-002',
mediaFiles: ['/api/placeholder/300/300'],
sizes: [
{
chrtID: 987654,
techSize: 'Standart',
wbSize: 'Белый',
price: 24990,
discountedPrice: 19990,
quantity: 5
}
]
if (wbApiKey?.isActive) {
// Попытка загрузить реальные данные из API Wildberries
const validationData = wbApiKey.validationData as Record<string, string>
const apiToken = validationData?.token ||
validationData?.apiKey ||
validationData?.key ||
(wbApiKey as { apiKey?: string }).apiKey
if (apiToken) {
console.log('Загружаем все карточки из WB API...')
const cards = await WildberriesService.getAllCards(apiToken, 100)
setWbCards(cards)
console.log('Загружено карточек из WB API:', cards.length)
return
}
])
}
// Если API ключ не настроен, загружаем моковые данные
console.log('API ключ WB не настроен, загружаем моковые данные')
const allCards = getMockCards()
setWbCards(allCards)
console.log('Загружены моковые товары:', allCards.length)
} catch (error) {
console.error('Ошибка загрузки всех карточек WB:', error)
// При ошибке загружаем моковые данные
const allCards = getMockCards()
setWbCards(allCards)
console.log('Загружены моковые товары (fallback):', allCards.length)
} finally {
setLoading(false)
}
}
const updateCardSelection = (card: WildberriesCard, field: keyof SelectedCard, value: any) => {
const searchCards = async () => {
if (!searchTerm.trim()) {
loadAllCards()
return
}
setLoading(true)
try {
const wbApiKey = user?.organization?.apiKeys?.find(key => key.marketplace === 'WILDBERRIES')
if (wbApiKey?.isActive) {
// Попытка поиска в реальном API Wildberries
const validationData = wbApiKey.validationData as Record<string, string>
const apiToken = validationData?.token ||
validationData?.apiKey ||
validationData?.key ||
(wbApiKey as { apiKey?: string }).apiKey
if (apiToken) {
console.log('Поиск в WB API:', searchTerm)
const cards = await WildberriesService.searchCards(apiToken, searchTerm, 50)
setWbCards(cards)
console.log('Найдено карточек в WB API:', cards.length)
return
}
}
// Если API ключ не настроен, ищем в моковых данных
console.log('API ключ WB не настроен, поиск в моковых данных:', searchTerm)
const mockCards = getMockCards()
// Фильтруем товары по поисковому запросу
const filteredCards = mockCards.filter(card =>
card.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
card.brand.toLowerCase().includes(searchTerm.toLowerCase()) ||
card.vendorCode.toLowerCase().includes(searchTerm.toLowerCase()) ||
card.object.toLowerCase().includes(searchTerm.toLowerCase())
)
setWbCards(filteredCards)
console.log('Найдено моковых товаров:', filteredCards.length)
} catch (error) {
console.error('Ошибка поиска карточек WB:', error)
// При ошибке ищем в моковых данных
const mockCards = getMockCards()
const filteredCards = mockCards.filter(card =>
card.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
card.brand.toLowerCase().includes(searchTerm.toLowerCase()) ||
card.vendorCode.toLowerCase().includes(searchTerm.toLowerCase()) ||
card.object.toLowerCase().includes(searchTerm.toLowerCase())
)
setWbCards(filteredCards)
console.log('Найдено моковых товаров (fallback):', filteredCards.length)
} finally {
setLoading(false)
}
}
const updateCardSelection = (card: WildberriesCard, field: keyof SelectedCard, value: string | number | string[]) => {
setSelectedCards(prev => {
const existing = prev.find(sc => sc.card.nmID === card.nmID)
if (field === 'selectedQuantity' && value === 0) {
if (field === 'selectedQuantity' && typeof value === 'number' && value === 0) {
return prev.filter(sc => sc.card.nmID !== card.nmID)
}
@ -232,10 +484,10 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
? { ...sc, [field]: value }
: sc
)
} else if (field === 'selectedQuantity' && value > 0) {
} else if (field === 'selectedQuantity' && typeof value === 'number' && value > 0) {
const newSelectedCard: SelectedCard = {
card,
selectedQuantity: value,
selectedQuantity: value as number,
selectedMarket: '',
selectedPlace: '',
sellerName: '',
@ -284,6 +536,28 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
)
}
const handleCardClick = (card: WildberriesCard) => {
setSelectedCardForDetails(card)
setCurrentImageIndex(0)
}
const closeDetailsModal = () => {
setSelectedCardForDetails(null)
setCurrentImageIndex(0)
}
const nextImage = () => {
if (selectedCardForDetails && selectedCardForDetails.mediaFiles?.length > 1) {
setCurrentImageIndex((prev) => (prev + 1) % selectedCardForDetails.mediaFiles.length)
}
}
const prevImage = () => {
if (selectedCardForDetails && selectedCardForDetails.mediaFiles?.length > 1) {
setCurrentImageIndex((prev) => (prev - 1 + selectedCardForDetails.mediaFiles.length) % selectedCardForDetails.mediaFiles.length)
}
}
const handleCreateSupply = async () => {
try {
const supplyInput = {
@ -356,7 +630,7 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
<Card key={sc.card.nmID} className="bg-white/10 backdrop-blur border-white/20 p-4">
<div className="flex space-x-4">
<img
src={sc.card.mediaFiles[0] || '/api/placeholder/120/120'}
src={sc.card.mediaFiles?.[0] || '/api/placeholder/120/120'}
alt={sc.card.title}
className="w-20 h-20 rounded-lg object-cover"
/>
@ -504,8 +778,26 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
</div>
</Card>
{/* Состояние загрузки */}
{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="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>
<div className="bg-white/20 rounded h-10 w-full"></div>
</div>
</Card>
))}
</div>
)}
{/* Карточки товаров */}
{wbCards.length > 0 && (
{!loading && wbCards.length > 0 && (
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
{wbCards.map((card) => {
const selectedQuantity = getSelectedQuantity(card)
@ -520,13 +812,28 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
<div className="p-4 space-y-4">
{/* Изображение и основная информация */}
<div className="space-y-3">
<img
src={card.mediaFiles[0] || '/api/placeholder/300/200'}
alt={card.title}
className="w-full h-48 rounded-lg object-cover"
/>
<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"
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>
</div>
<div>
<h3 className="text-white font-medium text-lg mb-1">{card.title}</h3>
<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>
@ -718,13 +1025,183 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
<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">Поиск товаров</h3>
<p className="text-white/60 mb-4">
Введите запрос в поле поиска, чтобы найти товары в вашем каталоге Wildberries
</p>
<h3 className="text-xl font-semibold text-white mb-2">Карточки товаров Wildberries</h3>
{user?.organization?.apiKeys?.find(key => key.marketplace === 'WILDBERRIES')?.isActive ? (
<>
<p className="text-white/60 mb-6">
Введите запрос в поле поиска, чтобы найти товары в вашем каталоге Wildberries, или загрузите все доступные карточки
</p>
<Button
onClick={loadAllCards}
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
</Button>
</>
) : (
<>
<p className="text-white/60 mb-4">
Для работы с реальными карточками товаров необходимо настроить API ключ Wildberries в настройках организации
</p>
<p className="text-white/40 text-sm mb-6">
Сейчас показаны демонстрационные товары. Для тестирования используйте поиск или загрузите все.
</p>
<Button
onClick={loadAllCards}
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" />
Показать демо товары
</Button>
</>
)}
</div>
</Card>
)}
{/* Модальное окно с детальной информацией о товаре */}
<Dialog open={!!selectedCardForDetails} onOpenChange={(open) => !open && closeDetailsModal()}>
<DialogContent className="max-w-4xl max-h-[90vh] bg-black/90 backdrop-blur-xl border border-white/20 p-0">
<DialogHeader className="sr-only">
<DialogTitle>Детальная информация о товаре</DialogTitle>
</DialogHeader>
{selectedCardForDetails && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 p-6">
{/* Изображения */}
<div className="space-y-4">
<div className="relative">
<img
src={selectedCardForDetails.mediaFiles?.[currentImageIndex] || '/api/placeholder/400/400'}
alt={selectedCardForDetails.title}
className="w-full aspect-square rounded-lg object-cover"
/>
{/* Навигация по изображениям */}
{selectedCardForDetails.mediaFiles?.length > 1 && (
<>
<button
onClick={prevImage}
className="absolute left-4 top-1/2 -translate-y-1/2 bg-black/50 hover:bg-black/70 text-white p-3 rounded-full"
>
<ChevronLeft className="h-6 w-6" />
</button>
<button
onClick={nextImage}
className="absolute right-4 top-1/2 -translate-y-1/2 bg-black/50 hover:bg-black/70 text-white p-3 rounded-full"
>
<ChevronRight className="h-6 w-6" />
</button>
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 bg-black/50 px-3 py-1 rounded-full text-white text-sm">
{currentImageIndex + 1} из {selectedCardForDetails.mediaFiles?.length || 0}
</div>
</>
)}
</div>
{/* Миниатюры изображений */}
{selectedCardForDetails.mediaFiles?.length > 1 && (
<div className="flex space-x-2 overflow-x-auto">
{selectedCardForDetails.mediaFiles?.map((image, index) => (
<img
key={index}
src={image}
alt={`${selectedCardForDetails.title} ${index + 1}`}
className={`w-16 h-16 rounded-lg object-cover cursor-pointer flex-shrink-0 ${
index === currentImageIndex ? 'ring-2 ring-purple-500' : 'opacity-60 hover:opacity-100'
}`}
onClick={() => setCurrentImageIndex(index)}
/>
))}
</div>
)}
</div>
{/* Информация о товаре */}
<div className="space-y-6">
<div>
<h2 className="text-2xl font-bold text-white mb-2">{selectedCardForDetails.title}</h2>
<p className="text-white/60 mb-4">Артикул: {selectedCardForDetails.vendorCode}</p>
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<span className="text-white/60">Бренд:</span>
<span className="text-white ml-2">{selectedCardForDetails.brand}</span>
</div>
<div>
<span className="text-white/60">Категория:</span>
<span className="text-white ml-2">{selectedCardForDetails.object}</span>
</div>
<div>
<span className="text-white/60">Родительская категория:</span>
<span className="text-white ml-2">{selectedCardForDetails.parent}</span>
</div>
<div>
<span className="text-white/60">Страна производства:</span>
<span className="text-white ml-2">{selectedCardForDetails.countryProduction}</span>
</div>
</div>
</div>
{selectedCardForDetails.description && (
<div>
<h3 className="text-white font-semibold mb-2">Описание</h3>
<p className="text-white/80 text-sm leading-relaxed">{selectedCardForDetails.description}</p>
</div>
)}
{/* Размеры и цены */}
<div>
<h3 className="text-white font-semibold mb-3">Доступные варианты</h3>
<div className="space-y-2">
{selectedCardForDetails.sizes.map((size) => (
<div key={size.chrtID} className="bg-white/5 rounded-lg p-3">
<div className="flex justify-between items-center mb-2">
<span className="text-white font-medium">{size.wbSize}</span>
<Badge className={`${size.quantity > 10 ? 'bg-green-500/20 text-green-300' : size.quantity > 0 ? 'bg-yellow-500/20 text-yellow-300' : 'bg-red-500/20 text-red-300'}`}>
{size.quantity} шт.
</Badge>
</div>
<div className="flex justify-between items-center text-sm">
<span className="text-white/60">Размер: {size.techSize}</span>
<div className="text-right">
<div className="text-white font-bold">{formatCurrency(size.discountedPrice || size.price)}</div>
{size.discountedPrice && size.discountedPrice < size.price && (
<div className="text-white/40 line-through text-xs">{formatCurrency(size.price)}</div>
)}
</div>
</div>
</div>
))}
</div>
</div>
{/* Кнопки действий в модальном окне */}
<div className="flex gap-3 pt-4 border-t border-white/20">
<Button
className="flex-1 bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white"
onClick={() => {
const currentQuantity = getSelectedQuantity(selectedCardForDetails)
updateCardSelection(selectedCardForDetails, 'selectedQuantity', currentQuantity + 1)
}}
>
<Plus className="h-4 w-4 mr-2" />
Добавить в корзину
</Button>
<Button
variant="outline"
className="border-white/20 text-white hover:bg-white/10"
onClick={closeDetailsModal}
>
Закрыть
</Button>
</div>
</div>
</div>
)}
</DialogContent>
</Dialog>
</div>
)
}