Merge branch 'main' of https://gittea.biveki.ru/Sfera/sfera
This commit is contained in:
@ -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>
|
||||
|
Reference in New Issue
Block a user