This commit is contained in:
Veronika Smirnova
2025-07-21 17:48:26 +03:00
5 changed files with 675 additions and 240 deletions

View File

@ -30,7 +30,8 @@ import {
import { WildberriesService } from '@/services/wildberries-service'
import { useAuth } from '@/hooks/useAuth'
import { useQuery, useMutation } from '@apollo/client'
import { GET_MY_COUNTERPARTIES } from '@/graphql/queries'
import { apolloClient } from '@/lib/apollo-client'
import { GET_MY_COUNTERPARTIES, GET_COUNTERPARTY_SERVICES, GET_COUNTERPARTY_SUPPLIES } from '@/graphql/queries'
import { CREATE_WILDBERRIES_SUPPLY } from '@/graphql/mutations'
import { toast } from 'sonner'
@ -58,12 +59,12 @@ interface WildberriesCard {
interface SelectedCard {
card: WildberriesCard
selectedQuantity: number
selectedMarket: string
selectedPlace: string
sellerName: string
sellerPhone: string
customPrice: number // Пользовательская цена за все количество
selectedFulfillmentOrg: string // ID выбранной FF организации
selectedFulfillmentServices: string[] // ID выбранных услуг FF (множественный выбор)
selectedConsumableOrg: string // ID выбранной организации расходников
selectedConsumableServices: string[] // ID выбранных расходников (множественный выбор)
deliveryDate: string
selectedServices: string[]
}
interface FulfillmentService {
@ -74,6 +75,13 @@ interface FulfillmentService {
organizationName: string
}
interface Organization {
id: string
name?: string
fullName?: string
type: string
}
interface WBProductCardsProps {
onBack: () => void
onComplete: (selectedCards: SelectedCard[]) => void
@ -88,6 +96,8 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
const [selectedCards, setSelectedCards] = useState<SelectedCard[]>([])
const [showSummary, setShowSummary] = useState(false)
const [fulfillmentServices, setFulfillmentServices] = useState<FulfillmentService[]>([])
const [organizationServices, setOrganizationServices] = useState<{[orgId: string]: Array<{id: string, name: string, description?: string, price: number}>}>({})
const [organizationSupplies, setOrganizationSupplies] = useState<{[orgId: string]: Array<{id: string, name: string, description?: string, price: number}>}>({})
const [selectedCardForDetails, setSelectedCardForDetails] = useState<WildberriesCard | null>(null)
const [currentImageIndex, setCurrentImageIndex] = useState(0)
@ -261,6 +271,60 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
// Загружаем контрагентов-фулфилментов
const { data: counterpartiesData } = useQuery(GET_MY_COUNTERPARTIES)
// Автоматически загружаем услуги и расходники для уже выбранных организаций
useEffect(() => {
selectedCards.forEach(sc => {
if (sc.selectedFulfillmentOrg && !organizationServices[sc.selectedFulfillmentOrg]) {
loadOrganizationServices(sc.selectedFulfillmentOrg)
}
if (sc.selectedConsumableOrg && !organizationSupplies[sc.selectedConsumableOrg]) {
loadOrganizationSupplies(sc.selectedConsumableOrg)
}
})
}, [selectedCards])
// Функция для загрузки услуг организации
const loadOrganizationServices = async (organizationId: string) => {
if (organizationServices[organizationId]) return // Уже загружены
try {
const response = await apolloClient.query({
query: GET_COUNTERPARTY_SERVICES,
variables: { organizationId }
})
if (response.data?.counterpartyServices) {
setOrganizationServices(prev => ({
...prev,
[organizationId]: response.data.counterpartyServices
}))
}
} catch (error) {
console.error('Ошибка загрузки услуг организации:', error)
}
}
// Функция для загрузки расходников организации
const loadOrganizationSupplies = async (organizationId: string) => {
if (organizationSupplies[organizationId]) return // Уже загружены
try {
const response = await apolloClient.query({
query: GET_COUNTERPARTY_SUPPLIES,
variables: { organizationId }
})
if (response.data?.counterpartySupplies) {
setOrganizationSupplies(prev => ({
...prev,
[organizationId]: response.data.counterpartySupplies
}))
}
} catch (error) {
console.error('Ошибка загрузки расходников организации:', error)
}
}
// Мутация для создания поставки
const [createSupply, { loading: creatingSupply }] = useMutation(CREATE_WILDBERRIES_SUPPLY, {
@ -286,48 +350,7 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
{ value: 'food-city', label: 'Фуд Сити' }
]
useEffect(() => {
// Загружаем услуги фулфилмента из контрагентов
if (counterpartiesData?.myCounterparties) {
interface Organization {
id: string
name?: string
fullName?: string
type: string
}
const fulfillmentOrganizations = counterpartiesData.myCounterparties.filter(
(org: Organization) => org.type === 'FULFILLMENT'
)
// В реальном приложении здесь был бы запрос услуг для каждой организации
const mockServices: FulfillmentService[] = fulfillmentOrganizations.flatMap((org: Organization) => [
{
id: `${org.id}-packaging`,
name: 'Упаковка товаров',
description: 'Профессиональная упаковка товаров',
price: 50,
organizationName: org.name || org.fullName
},
{
id: `${org.id}-labeling`,
name: 'Маркировка товаров',
description: 'Нанесение этикеток и штрих-кодов',
price: 30,
organizationName: org.name || org.fullName
},
{
id: `${org.id}-quality-check`,
name: 'Контроль качества',
description: 'Проверка качества товаров',
price: 100,
organizationName: org.name || org.fullName
}
])
setFulfillmentServices(mockServices)
}
}, [counterpartiesData])
// Автоматически загружаем товары при открытии компонента
useEffect(() => {
@ -491,12 +514,12 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
const newSelectedCard: SelectedCard = {
card,
selectedQuantity: value as number,
selectedMarket: '',
selectedPlace: '',
sellerName: '',
sellerPhone: '',
deliveryDate: '',
selectedServices: []
customPrice: 0,
selectedFulfillmentOrg: '',
selectedFulfillmentServices: [],
selectedConsumableOrg: '',
selectedConsumableServices: [],
deliveryDate: ''
}
return [...prev, newSelectedCard]
}
@ -518,14 +541,50 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
}).format(amount)
}
// Функция для получения цены услуги по ID
const getServicePrice = (orgId: string, serviceId: string): number => {
const services = organizationServices[orgId]
if (!services) return 0
const service = services.find(s => s.id === serviceId)
return service ? service.price : 0
}
// Функция для получения цены расходника по ID
const getSupplyPrice = (orgId: string, supplyId: string): number => {
const supplies = organizationSupplies[orgId]
if (!supplies) return 0
const supply = supplies.find(s => s.id === supplyId)
return supply ? supply.price : 0
}
// Функция для расчета стоимости услуг и расходников за 1 штуку
const calculateAdditionalCostPerUnit = (sc: SelectedCard): number => {
let servicesCost = 0
let suppliesCost = 0
// Стоимость услуг фулфилмента
if (sc.selectedFulfillmentOrg && sc.selectedFulfillmentServices.length > 0) {
servicesCost = sc.selectedFulfillmentServices.reduce((sum, serviceId) => {
return sum + getServicePrice(sc.selectedFulfillmentOrg, serviceId)
}, 0)
}
// Стоимость расходных материалов
if (sc.selectedConsumableOrg && sc.selectedConsumableServices.length > 0) {
suppliesCost = sc.selectedConsumableServices.reduce((sum, supplyId) => {
return sum + getSupplyPrice(sc.selectedConsumableOrg, supplyId)
}, 0)
}
return servicesCost + suppliesCost
}
const getTotalAmount = () => {
return selectedCards.reduce((sum, sc) => {
const cardPrice = sc.card.sizes[0]?.discountedPrice || sc.card.sizes[0]?.price || 0
const servicesPrice = sc.selectedServices.reduce((serviceSum, serviceId) => {
const service = fulfillmentServices.find(s => s.id === serviceId)
return serviceSum + (service?.price || 0)
}, 0)
return sum + (cardPrice + servicesPrice) * sc.selectedQuantity
const additionalCostPerUnit = calculateAdditionalCostPerUnit(sc)
const totalCostPerUnit = (sc.customPrice / sc.selectedQuantity) + additionalCostPerUnit
const totalCostForAllItems = totalCostPerUnit * sc.selectedQuantity
return sum + totalCostForAllItems
}, 0)
}
@ -533,11 +592,7 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
return selectedCards.reduce((sum, sc) => sum + sc.selectedQuantity, 0)
}
const applyServicesToAll = (serviceIds: string[]) => {
setSelectedCards(prev =>
prev.map(sc => ({ ...sc, selectedServices: serviceIds }))
)
}
// Функция больше не нужна, так как услуги выбираются индивидуально
const handleCardClick = (card: WildberriesCard) => {
setSelectedCardForDetails(card)
@ -570,17 +625,14 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
vendorCode: sc.card.vendorCode,
title: sc.card.title,
brand: sc.card.brand,
price: sc.card.sizes[0]?.price || 0,
discountedPrice: sc.card.sizes[0]?.discountedPrice || null,
quantity: sc.card.sizes[0]?.quantity || 0,
selectedQuantity: sc.selectedQuantity,
selectedMarket: sc.selectedMarket,
selectedPlace: sc.selectedPlace,
sellerName: sc.sellerName,
sellerPhone: sc.sellerPhone,
deliveryDate: sc.deliveryDate || null,
mediaFiles: sc.card.mediaFiles,
selectedServices: sc.selectedServices
customPrice: sc.customPrice,
selectedFulfillmentOrg: sc.selectedFulfillmentOrg,
selectedFulfillmentServices: sc.selectedFulfillmentServices,
selectedConsumableOrg: sc.selectedConsumableOrg,
selectedConsumableServices: sc.selectedConsumableServices,
deliveryDate: sc.deliveryDate || null,
mediaFiles: sc.card.mediaFiles
}))
}
@ -625,72 +677,245 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-4">
{selectedCards.map((sc) => {
const cardPrice = sc.card.sizes[0]?.discountedPrice || sc.card.sizes[0]?.price || 0
const servicesPrice = sc.selectedServices.reduce((sum, serviceId) => {
const service = fulfillmentServices.find(s => s.id === serviceId)
return sum + (service?.price || 0)
}, 0)
const totalPrice = (cardPrice + servicesPrice) * sc.selectedQuantity
const fulfillmentOrgs = (counterpartiesData?.myCounterparties || []).filter((org: Organization) => org.type === 'FULFILLMENT')
const consumableOrgs = (counterpartiesData?.myCounterparties || []).filter((org: Organization) => org.type === 'FULFILLMENT')
return (
<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"
/>
<div className="flex-1 space-y-3">
<div className="flex-1 space-y-4">
<div>
<h3 className="text-white font-medium">{sc.card.title}</h3>
<p className="text-white/60 text-sm">{sc.card.vendorCode}</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 text-sm">
<div>
<span className="text-white/60">Количество:</span>
<span className="text-white ml-2">{sc.selectedQuantity}</span>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Количество и цена */}
<div className="space-y-3">
<div>
<label className="text-white/60 text-sm">Количество:</label>
<Input
type="number"
value={sc.selectedQuantity}
onChange={(e) => updateCardSelection(sc.card, 'selectedQuantity', parseInt(e.target.value) || 0)}
className="bg-white/5 border-white/20 text-white mt-1"
min="1"
/>
</div>
<div>
<label className="text-white/60 text-sm">Цена за единицу:</label>
<Input
type="number"
value={sc.customPrice === 0 ? '' : (sc.customPrice / sc.selectedQuantity).toFixed(2)}
onChange={(e) => {
const pricePerUnit = e.target.value === '' ? 0 : parseFloat(e.target.value) || 0
const totalPrice = pricePerUnit * sc.selectedQuantity
updateCardSelection(sc.card, 'customPrice', totalPrice)
}}
className="bg-white/5 border-white/20 text-white mt-1"
placeholder="Введите цену за 1 штуку"
/>
{/* Показываем расчет дополнительных расходов */}
{(() => {
const additionalCost = calculateAdditionalCostPerUnit(sc)
if (additionalCost > 0) {
return (
<div className="mt-2 p-2 bg-blue-500/20 border border-blue-500/30 rounded text-xs">
<div className="text-blue-300 font-medium">Дополнительные расходы за 1 шт:</div>
{sc.selectedFulfillmentServices.length > 0 && (
<div className="text-blue-200">
Услуги: {sc.selectedFulfillmentServices.map(serviceId => {
const price = getServicePrice(sc.selectedFulfillmentOrg, serviceId)
const services = organizationServices[sc.selectedFulfillmentOrg]
const service = services?.find(s => s.id === serviceId)
return service ? `${service.name} (${price}₽)` : ''
}).join(', ')}
</div>
)}
{sc.selectedConsumableServices.length > 0 && (
<div className="text-blue-200">
Расходники: {sc.selectedConsumableServices.map(supplyId => {
const price = getSupplyPrice(sc.selectedConsumableOrg, supplyId)
const supplies = organizationSupplies[sc.selectedConsumableOrg]
const supply = supplies?.find(s => s.id === supplyId)
return supply ? `${supply.name} (${price}₽)` : ''
}).join(', ')}
</div>
)}
<div className="text-blue-300 font-medium mt-1">
Итого доп. расходы: {formatCurrency(additionalCost)}
</div>
<div className="text-green-300 font-medium">
Полная стоимость за 1 шт: {formatCurrency((sc.customPrice / sc.selectedQuantity) + additionalCost)}
</div>
</div>
)
}
return null
})()}
</div>
<div>
<label className="text-white/60 text-sm">Дата поставки:</label>
<Input
type="date"
value={sc.deliveryDate}
onChange={(e) => updateCardSelection(sc.card, 'deliveryDate', e.target.value)}
className="bg-white/5 border-white/20 text-white mt-1"
/>
</div>
</div>
<div>
<span className="text-white/60">Рынок:</span>
<span className="text-white ml-2">{markets.find(m => m.value === sc.selectedMarket)?.label || 'Не выбран'}</span>
</div>
<div>
<span className="text-white/60">Место:</span>
<span className="text-white ml-2">{sc.selectedPlace || 'Не указано'}</span>
</div>
<div>
<span className="text-white/60">Продавец:</span>
<span className="text-white ml-2">{sc.sellerName || 'Не указан'}</span>
</div>
<div>
<span className="text-white/60">Телефон:</span>
<span className="text-white ml-2">{sc.sellerPhone || 'Не указан'}</span>
</div>
<div>
<span className="text-white/60">Дата поставки:</span>
<span className="text-white ml-2">{sc.deliveryDate || 'Не выбрана'}</span>
{/* Услуги */}
<div className="space-y-3">
<div>
<label className="text-white/60 text-sm">Фулфилмент организация:</label>
<Select
value={sc.selectedFulfillmentOrg}
onValueChange={(value) => {
updateCardSelection(sc.card, 'selectedFulfillmentOrg', value)
updateCardSelection(sc.card, 'selectedFulfillmentServices', []) // Сбрасываем услуги
if (value) {
loadOrganizationServices(value) // Автоматически загружаем услуги
}
}}
>
<SelectTrigger className="bg-white/5 border-white/20 text-white mt-1">
<SelectValue placeholder="Выберите фулфилмент" />
</SelectTrigger>
<SelectContent>
{fulfillmentOrgs.map((org: Organization) => (
<SelectItem key={org.id} value={org.id}>
{org.name || org.fullName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{sc.selectedFulfillmentOrg && (
<div>
<label className="text-white/60 text-sm">Услуги фулфилмента:</label>
<div className="mt-2 space-y-2 max-h-32 overflow-y-auto bg-white/5 border border-white/20 rounded p-2">
{organizationServices[sc.selectedFulfillmentOrg] ? (
organizationServices[sc.selectedFulfillmentOrg].length > 0 ? (
organizationServices[sc.selectedFulfillmentOrg].map((service) => {
const isSelected = sc.selectedFulfillmentServices.includes(service.id)
return (
<label key={service.id} className="flex items-center space-x-2 cursor-pointer hover:bg-white/5 p-1 rounded">
<input
type="checkbox"
checked={isSelected}
onChange={(e) => {
const newServices = e.target.checked
? [...sc.selectedFulfillmentServices, service.id]
: sc.selectedFulfillmentServices.filter(id => id !== service.id)
updateCardSelection(sc.card, 'selectedFulfillmentServices', newServices)
}}
className="rounded border-white/20 bg-white/10 text-purple-500 focus:ring-purple-500"
/>
<span className="text-white text-sm">
{service.name} - {service.price}
</span>
</label>
)
})
) : (
<div className="text-white/60 text-sm text-center py-2">
У данной организации нет услуг
</div>
)
) : (
<div className="text-white/60 text-sm text-center py-2">
Загрузка услуг...
</div>
)}
</div>
</div>
)}
<div>
<label className="text-white/60 text-sm">Поставщик расходников:</label>
<Select
value={sc.selectedConsumableOrg}
onValueChange={(value) => {
updateCardSelection(sc.card, 'selectedConsumableOrg', value)
updateCardSelection(sc.card, 'selectedConsumableServices', []) // Сбрасываем услуги
if (value) {
loadOrganizationSupplies(value) // Автоматически загружаем расходники
}
}}
>
<SelectTrigger className="bg-white/5 border-white/20 text-white mt-1">
<SelectValue placeholder="Выберите поставщика" />
</SelectTrigger>
<SelectContent>
{consumableOrgs.map((org: Organization) => (
<SelectItem key={org.id} value={org.id}>
{org.name || org.fullName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{sc.selectedConsumableOrg && (
<div>
<label className="text-white/60 text-sm">Расходные материалы:</label>
<div className="mt-2 space-y-2 max-h-32 overflow-y-auto bg-white/5 border border-white/20 rounded p-2">
{organizationSupplies[sc.selectedConsumableOrg] ? (
organizationSupplies[sc.selectedConsumableOrg].length > 0 ? (
organizationSupplies[sc.selectedConsumableOrg].map((supply) => {
const isSelected = sc.selectedConsumableServices.includes(supply.id)
return (
<label key={supply.id} className="flex items-center space-x-2 cursor-pointer hover:bg-white/5 p-1 rounded">
<input
type="checkbox"
checked={isSelected}
onChange={(e) => {
const newSupplies = e.target.checked
? [...sc.selectedConsumableServices, supply.id]
: sc.selectedConsumableServices.filter(id => id !== supply.id)
updateCardSelection(sc.card, 'selectedConsumableServices', newSupplies)
}}
className="rounded border-white/20 bg-white/10 text-green-500 focus:ring-green-500"
/>
<span className="text-white text-sm">
{supply.name} - {supply.price}
</span>
</label>
)
})
) : (
<div className="text-white/60 text-sm text-center py-2">
У данной организации нет расходников
</div>
)
) : (
<div className="text-white/60 text-sm text-center py-2">
Загрузка расходников...
</div>
)}
</div>
</div>
)}
</div>
</div>
{sc.selectedServices.length > 0 && (
<div>
<p className="text-white/60 text-sm mb-2">Услуги:</p>
<div className="flex flex-wrap gap-1">
{sc.selectedServices.map(serviceId => {
const service = fulfillmentServices.find(s => s.id === serviceId)
return service ? (
<Badge key={serviceId} className="bg-purple-500/20 text-purple-300 border-purple-500/30 text-xs">
{service.name} ({formatCurrency(service.price)})
</Badge>
) : null
})}
</div>
</div>
)}
<div className="text-right">
<span className="text-white font-bold text-lg">{formatCurrency(totalPrice)}</span>
<span className="text-white font-bold text-lg">
{formatCurrency(sc.customPrice)}
</span>
{sc.selectedQuantity > 0 && sc.customPrice > 0 && (
<p className="text-white/60 text-sm">
~{formatCurrency(sc.customPrice / sc.selectedQuantity)} за шт.
</p>
)}
</div>
</div>
</div>
@ -815,12 +1040,9 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
const selectedQuantity = getSelectedQuantity(card)
const isSelected = selectedQuantity > 0
const selectedCard = selectedCards.find(sc => sc.card.nmID === card.nmID)
const mainSize = card.sizes[0]
const maxQuantity = mainSize?.quantity || 0
const price = mainSize?.discountedPrice || mainSize?.price || 0
return (
<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' : ''}`}>
<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' : ''} relative overflow-hidden`}>
<div className="p-3 space-y-3">
{/* Изображение и основная информация */}
<div className="space-y-2">
@ -833,13 +1055,23 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
onClick={() => handleCardClick(card)}
/>
{/* Количество в наличии */}
{/* Индикатор товара WB */}
<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 className="bg-blue-500/90 text-white border-0 backdrop-blur text-xs font-medium px-2 py-1">
WB
</Badge>
</div>
{/* Индикатор выбранного товара */}
{isSelected && (
<div className="absolute top-2 left-2">
<Badge className="bg-gradient-to-r from-purple-500 to-pink-500 text-white border-0 text-xs font-medium">
<ShoppingCart className="h-3 w-3 mr-1" />
В корзине
</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
@ -848,7 +1080,8 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
onClick={() => handleCardClick(card)}
className="bg-white/20 backdrop-blur text-white border-white/30 hover:bg-white/30"
>
<Eye className="h-4 w-4" />
<Eye className="h-4 w-4 mr-2" />
Подробнее
</Button>
</div>
</div>
@ -857,80 +1090,78 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
<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">
<div className="flex items-center justify-between mb-2">
<Badge className="bg-blue-500/20 text-blue-300 border-blue-500/30 text-xs font-medium">
{card.brand}
</Badge>
<span className="text-white/40 text-xs">{card.nmID}</span>
</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>
<p className="text-white/60 text-xs mb-2">Артикул: {card.vendorCode}</p>
</div>
{/* Цена */}
<div className="pt-1 border-t border-white/10">
<div className="text-white font-bold text-base">
{formatCurrency(price)}
{/* Информация о товаре */}
<div className="pt-2 border-t border-white/10">
<div className="text-center">
<span className="text-white/60 text-xs">Добавьте в поставку для настройки</span>
</div>
</div>
</div>
</div>
{/* Управление количеством */}
<div className="space-y-2">
<div className="flex items-center space-x-1">
<div className="space-y-2 bg-white/5 rounded-lg p-2">
<div className="flex items-center justify-center mb-1">
<span className="text-white/60 text-xs font-medium">Добавить в поставку:</span>
</div>
<div className="flex items-center space-x-2">
<Button
variant="ghost"
size="sm"
onClick={() => updateCardSelection(card, 'selectedQuantity', Math.max(0, selectedQuantity - 1))}
disabled={selectedQuantity <= 0}
className="h-7 w-7 p-0 text-white/60 hover:text-white hover:bg-white/10 border border-white/20"
className="h-8 w-8 p-0 text-white/60 hover:text-white hover:bg-white/10 border border-white/20 disabled:opacity-50"
>
<Minus className="h-3 w-3" />
</Button>
<input
type="text"
inputMode="numeric"
pattern="[0-9]*"
value={selectedQuantity}
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"
/>
<div className="flex-1">
<input
type="text"
inputMode="numeric"
pattern="[0-9]*"
value={selectedQuantity}
onChange={(e) => {
const value = e.target.value.replace(/[^0-9]/g, '')
const numValue = Math.max(0, parseInt(value) || 0)
updateCardSelection(card, 'selectedQuantity', numValue)
}}
onFocus={(e) => e.target.select()}
className="h-8 w-full 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"
placeholder="0"
/>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => updateCardSelection(card, 'selectedQuantity', Math.min(maxQuantity, selectedQuantity + 1))}
disabled={selectedQuantity >= maxQuantity}
className="h-7 w-7 p-0 text-white/60 hover:text-white hover:bg-white/10 border border-white/20"
onClick={() => updateCardSelection(card, 'selectedQuantity', selectedQuantity + 1)}
className="h-8 w-8 p-0 text-white/60 hover:text-white hover:bg-white/10 border border-white/20"
>
<Plus className="h-3 w-3" />
</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 className="bg-gradient-to-r from-blue-500/20 to-purple-500/20 border border-blue-500/30 rounded p-2 mt-2">
<div className="text-center">
<span className="text-blue-300 text-xs">В корзине: {selectedQuantity} шт</span>
<p className="text-blue-200 text-xs mt-1">Настройте цену и услуги в корзине</p>
</div>
</div>
)}
</div>
</div>
</Card>
)
@ -992,14 +1223,14 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
{/* Модальное окно с детальной информацией о товаре */}
<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">
<DialogContent className="max-w-6xl w-[95vw] max-h-[95vh] bg-black/95 backdrop-blur-xl border border-white/20 p-0 overflow-hidden">
<DialogHeader className="sr-only">
<DialogTitle>Детальная информация о товаре</DialogTitle>
</DialogHeader>
{selectedCardForDetails && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 p-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-0 h-full max-w-full">
{/* Изображения */}
<div className="space-y-4">
<div className="p-4 lg:p-6 space-y-4 overflow-hidden">
<div className="relative">
<img
src={selectedCardForDetails.mediaFiles?.[currentImageIndex] || '/api/placeholder/400/400'}
@ -1012,18 +1243,18 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
<>
<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"
className="absolute left-3 top-1/2 -translate-y-1/2 bg-black/70 hover:bg-black/90 text-white p-2 rounded-full transition-all"
>
<ChevronLeft className="h-6 w-6" />
<ChevronLeft className="h-5 w-5" />
</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"
className="absolute right-3 top-1/2 -translate-y-1/2 bg-black/70 hover:bg-black/90 text-white p-2 rounded-full transition-all"
>
<ChevronRight className="h-6 w-6" />
<ChevronRight className="h-5 w-5" />
</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">
<div className="absolute bottom-3 left-1/2 -translate-x-1/2 bg-black/70 px-3 py-1 rounded-full text-white text-sm">
{currentImageIndex + 1} из {selectedCardForDetails.mediaFiles?.length || 0}
</div>
</>
@ -1031,107 +1262,114 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
</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)}
/>
))}
{selectedCardForDetails.mediaFiles?.length > 1 && (
<div className="w-full overflow-hidden">
<div className="flex space-x-2 overflow-x-auto pb-2 scrollbar-thin scrollbar-thumb-white/20 scrollbar-track-transparent">
{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 transition-all ${
index === currentImageIndex ? 'ring-2 ring-purple-500' : 'opacity-60 hover:opacity-100'
}`}
onClick={() => setCurrentImageIndex(index)}
/>
))}
</div>
</div>
)}
</div>
{/* Информация о товаре */}
<div className="space-y-6">
<div className="p-4 lg:p-6 overflow-y-auto max-h-[95vh] space-y-4 max-w-full">
<div>
<h2 className="text-2xl font-bold text-white mb-2">{selectedCardForDetails.title}</h2>
<p className="text-white/60 mb-4">Артикул: {selectedCardForDetails.vendorCode}</p>
<h2 className="text-xl font-bold text-white mb-2 leading-tight">{selectedCardForDetails.title}</h2>
<p className="text-white/60 mb-3 text-sm">Артикул: {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 className="grid grid-cols-1 gap-3 text-sm bg-white/5 rounded-lg p-4">
<div className="flex justify-between items-start gap-2">
<span className="text-white/60 flex-shrink-0">Бренд:</span>
<span className="text-white font-medium text-right break-words">{selectedCardForDetails.brand}</span>
</div>
<div>
<span className="text-white/60">Категория:</span>
<span className="text-white ml-2">{selectedCardForDetails.object}</span>
<div className="flex justify-between items-start gap-2">
<span className="text-white/60 flex-shrink-0">Категория:</span>
<span className="text-white font-medium text-right break-words">{selectedCardForDetails.object}</span>
</div>
<div>
<span className="text-white/60">Родительская категория:</span>
<span className="text-white ml-2">{selectedCardForDetails.parent}</span>
<div className="flex justify-between items-start gap-2">
<span className="text-white/60 flex-shrink-0">Родительская:</span>
<span className="text-white font-medium text-right break-words">{selectedCardForDetails.parent}</span>
</div>
<div>
<span className="text-white/60">Страна производства:</span>
<span className="text-white ml-2">{selectedCardForDetails.countryProduction}</span>
<div className="flex justify-between items-start gap-2">
<span className="text-white/60 flex-shrink-0">Страна:</span>
<span className="text-white font-medium text-right break-words">{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>
<h3 className="text-white font-semibold mb-2 text-lg">Описание</h3>
<div className="bg-white/5 rounded-lg p-4 max-h-40 overflow-y-auto scrollbar-thin scrollbar-thumb-white/20 scrollbar-track-transparent">
<p className="text-white/80 text-sm leading-relaxed whitespace-pre-wrap break-words">
{selectedCardForDetails.description}
</p>
</div>
</div>
)}
{/* Размеры и цены */}
<div>
<h3 className="text-white font-semibold mb-3">Доступные варианты</h3>
<h3 className="text-white font-semibold mb-3 text-lg">Доступные варианты</h3>
<div className="space-y-2">
{selectedCardForDetails.sizes.map((size) => (
<div key={size.chrtID} className="bg-white/5 rounded-lg p-3">
<div key={size.chrtID} className="bg-white/5 border border-white/10 rounded-lg p-4">
<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'}`}>
<Badge className={`${size.quantity > 10 ? 'bg-green-500/30 text-green-200' : size.quantity > 0 ? 'bg-yellow-500/30 text-yellow-200' : 'bg-red-500/30 text-red-200'} font-medium`}>
{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>
<div className="text-white font-bold text-base">{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>
</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 className="flex gap-3 pt-4 border-t border-white/20 sticky bottom-0 bg-black/50 backdrop-blur">
<Button
className="flex-1 bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white font-medium"
onClick={() => {
const currentQuantity = getSelectedQuantity(selectedCardForDetails)
updateCardSelection(selectedCardForDetails, 'selectedQuantity', currentQuantity + 1)
closeDetailsModal()
}}
>
<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>
</main>
</div>