From 297abbdb030ecdb94434ac1663d68c43b57a1236 Mon Sep 17 00:00:00 2001 From: Bivekich Date: Tue, 22 Jul 2025 16:46:25 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=D0=B4=D0=B3=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=BD=D1=8B=D0=BC=D0=B8=20=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=80=D0=B0=D0=BC=D0=B8=20=D0=B2=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=B5=20WBProductCar?= =?UTF-8?q?ds.=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=20=D0=B4=D0=B0=D1=82?= =?UTF-8?q?=D1=8B=20=D0=BF=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=D1=81=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20=D0=BA=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B4=D0=B0=D1=80=D1=8F=20=D0=B8=20=D0=BF=D0=BE=D0=BF=D0=BE?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=B0.=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=B0=20=D0=B8=20=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=80=D0=BE=D0=B2,=20=D0=B4=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=84=D1=83=D0=BD=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=BE=D0=B4?= =?UTF-8?q?=D1=81=D1=87=D0=B5=D1=82=D0=B0=20=D0=BE=D0=B1=D1=89=D0=B5=D0=B9?= =?UTF-8?q?=20=D1=81=D1=82=D0=BE=D0=B8=D0=BC=D0=BE=D1=81=D1=82=D0=B8=20?= =?UTF-8?q?=D0=B8=20=D0=BA=D0=BE=D0=BB=D0=B8=D1=87=D0=B5=D1=81=D1=82=D0=B2?= =?UTF-8?q?=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BD=D1=8B=D1=85=20=D1=82=D0=BE=D0=B2=D0=B0=D1=80?= =?UTF-8?q?=D0=BE=D0=B2.=20=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20=D1=81=D1=82=D0=B8=D0=BB=D0=B8=20=D0=B8=20=D0=B2=D0=B7?= =?UTF-8?q?=D0=B0=D0=B8=D0=BC=D0=BE=D0=B4=D0=B5=D0=B9=D1=81=D1=82=D0=B2?= =?UTF-8?q?=D0=B8=D0=B5=20=D1=81=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D0=B5=D0=BC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/supplies/wb-product-cards.tsx | 484 +++++++++++-------- 1 file changed, 293 insertions(+), 191 deletions(-) diff --git a/src/components/supplies/wb-product-cards.tsx b/src/components/supplies/wb-product-cards.tsx index 77d3c21..aa6916f 100644 --- a/src/components/supplies/wb-product-cards.tsx +++ b/src/components/supplies/wb-product-cards.tsx @@ -8,6 +8,8 @@ import { Badge } from '@/components/ui/badge' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' +import { Calendar as CalendarComponent } from '@/components/ui/calendar' +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { Sidebar } from '@/components/dashboard/sidebar' import { useSidebar } from '@/hooks/useSidebar' import { @@ -34,6 +36,8 @@ 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' +import { format } from 'date-fns' +import { ru } from 'date-fns/locale' import { SelectedCard, FulfillmentService, ConsumableService, WildberriesCard } from '@/types/supplies' @@ -56,8 +60,10 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) { const [searchTerm, setSearchTerm] = useState('') const [loading, setLoading] = useState(false) const [wbCards, setWbCards] = useState([]) - const [selectedCards, setSelectedCards] = useState([]) + const [selectedCards, setSelectedCards] = useState([]) // Товары в корзине + const [preparingCards, setPreparingCards] = useState([]) // Товары, готовящиеся к добавлению const [showSummary, setShowSummary] = useState(false) + const [globalDeliveryDate, setGlobalDeliveryDate] = useState(undefined) const [fulfillmentServices, setFulfillmentServices] = useState([]) 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}>}>({}) @@ -436,7 +442,7 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) { const filteredCards = mockCards.filter(card => card.title.toLowerCase().includes(searchTerm.toLowerCase()) || card.brand.toLowerCase().includes(searchTerm.toLowerCase()) || - card.vendorCode.toLowerCase().includes(searchTerm.toLowerCase()) || + card.nmID.toString().includes(searchTerm.toLowerCase()) || card.object?.toLowerCase().includes(searchTerm.toLowerCase()) ) @@ -449,7 +455,7 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) { const filteredCards = mockCards.filter(card => card.title.toLowerCase().includes(searchTerm.toLowerCase()) || card.brand.toLowerCase().includes(searchTerm.toLowerCase()) || - card.vendorCode.toLowerCase().includes(searchTerm.toLowerCase()) || + card.nmID.toString().includes(searchTerm.toLowerCase()) || card.object?.toLowerCase().includes(searchTerm.toLowerCase()) ) setWbCards(filteredCards) @@ -460,7 +466,7 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) { } const updateCardSelection = (card: WildberriesCard, field: keyof SelectedCard, value: string | number | string[]) => { - setSelectedCards(prev => { + setPreparingCards(prev => { const existing = prev.find(sc => sc.card.nmID === card.nmID) if (field === 'selectedQuantity' && typeof value === 'number' && value === 0) { @@ -468,10 +474,15 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) { } if (existing) { + const updatedCard = { ...existing, [field]: value } + + // При изменении количества сбрасываем цену, чтобы пользователь ввел новую + if (field === 'selectedQuantity' && typeof value === 'number' && existing.customPrice > 0) { + updatedCard.customPrice = 0 + } + return prev.map(sc => - sc.card.nmID === card.nmID - ? { ...sc, [field]: value } - : sc + sc.card.nmID === card.nmID ? updatedCard : sc ) } else if (field === 'selectedQuantity' && typeof value === 'number' && value > 0) { const newSelectedCard: SelectedCard = { @@ -496,11 +507,73 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) { }) } + // Функция для получения цены за единицу товара + const getSelectedUnitPrice = (card: WildberriesCard): number => { + const selected = preparingCards.find(sc => sc.card.nmID === card.nmID) + if (!selected || selected.selectedQuantity === 0) return 0 + return selected.customPrice / selected.selectedQuantity + } + + // Функция для получения общей стоимости товара + const getSelectedTotalPrice = (card: WildberriesCard): number => { + const selected = preparingCards.find(sc => sc.card.nmID === card.nmID) + return selected ? selected.customPrice : 0 + } + const getSelectedQuantity = (card: WildberriesCard): number => { - const selected = selectedCards.find(sc => sc.card.nmID === card.nmID) + const selected = preparingCards.find(sc => sc.card.nmID === card.nmID) return selected ? selected.selectedQuantity : 0 } + // Функция для добавления подготовленных товаров в корзину + const addToCart = () => { + const validCards = preparingCards.filter(card => + card.selectedQuantity > 0 && card.customPrice > 0 + ) + + if (validCards.length === 0) { + toast.error('Выберите товары и укажите цены') + return + } + + if (!globalDeliveryDate) { + toast.error('Выберите дату поставки') + return + } + + setSelectedCards(prev => { + const newCards = [...prev] + validCards.forEach(prepCard => { + const cardWithDate = { + ...prepCard, + deliveryDate: globalDeliveryDate.toISOString().split('T')[0] + } + const existingIndex = newCards.findIndex(sc => sc.card.nmID === prepCard.card.nmID) + if (existingIndex >= 0) { + // Обновляем существующий товар + newCards[existingIndex] = cardWithDate + } else { + // Добавляем новый товар + newCards.push(cardWithDate) + } + }) + return newCards + }) + + // Очищаем подготовленные товары + setPreparingCards([]) + toast.success(`Добавлено ${validCards.length} товар(ов) в корзину`) + } + + // Функции подсчета для подготовленных товаров + const getPreparingTotalItems = () => { + return preparingCards.reduce((sum, card) => sum + card.selectedQuantity, 0) + } + + const getPreparingTotalAmount = () => { + return preparingCards.reduce((sum, card) => sum + card.customPrice, 0) + } + const formatCurrency = (amount: number) => { return new Intl.NumberFormat('ru-RU', { style: 'currency', @@ -665,7 +738,7 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {

{sc.card.title}

-

{sc.card.vendorCode}

+

WB: {sc.card.nmID}

@@ -734,15 +807,6 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) { return null })()}
-
- - updateCardSelection(sc.card, 'deliveryDate', e.target.value)} - className="bg-white/5 border-white/20 text-white mt-1" - /> -
{/* Услуги */} @@ -966,24 +1030,92 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) { {/* Поиск */} - -
+ {/* Поиск товаров и выбор даты поставки */} + +
+ {/* Поиск */}
setSearchTerm(e.target.value)} - className="bg-white/5 border-white/20 text-white placeholder-white/50" + className="bg-white/5 border-white/20 text-white placeholder-white/50 h-9" onKeyPress={(e) => e.key === 'Enter' && searchCards()} />
+ + {/* Выбор даты поставки */} +
+ + + + + +
+

Дата поставки

+

Выберите дату для всех товаров

+
+ date < new Date()} + initialFocus + locale={ru} + className="glass-card border-0 p-3" + classNames={{ + months: "flex flex-col space-y-3", + month: "space-y-3", + caption: "flex justify-center pt-2 relative items-center", + caption_label: "text-sm font-medium text-white", + nav: "space-x-1 flex items-center", + nav_button: "h-7 w-7 glass-secondary text-white hover:bg-white/20 hover:text-white rounded-md border-white/20", + nav_button_previous: "absolute left-1", + nav_button_next: "absolute right-1", + table: "w-full border-collapse space-y-1", + head_row: "flex", + head_cell: "text-white/70 rounded-md w-9 font-normal text-xs", + row: "flex w-full mt-2", + cell: "h-9 w-9 text-center text-xs p-0 relative focus-within:relative focus-within:z-20", + day: "h-9 w-9 p-0 font-normal text-white hover:bg-white/15 hover:text-white rounded-md transition-colors", + day_selected: "glass-button text-white hover:bg-gradient-to-r hover:from-purple-500 hover:to-pink-500", + day_today: "bg-white/15 text-white font-semibold border border-white/30", + day_outside: "text-white/30 opacity-50", + day_disabled: "text-white/20 opacity-30", + }} + /> + {globalDeliveryDate && ( +
+ + + {format(globalDeliveryDate, "dd MMMM yyyy", { locale: ru })} + +
+ )} +
+
+
+ + {/* Кнопка поиска */}
@@ -1017,7 +1149,7 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) { return ( -
+
{/* Изображение и основная информация */}
@@ -1039,9 +1171,9 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) { {/* Индикатор выбранного товара */} {isSelected && (
- - - В корзине + + + Подготовлен
)} @@ -1073,7 +1205,6 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {

handleCardClick(card)}> {card.title}

-

Артикул: {card.vendorCode}

{/* Информация о товаре */} @@ -1085,56 +1216,89 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
- {/* Управление количеством */} + {/* Компактное управление */}
-
- Добавить в поставку: -
-
+ {/* Количество - компактно */} +
-
- { - 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" - /> -
+ { + 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="flex-1 h-7 text-center bg-white/10 border border-white/20 text-white text-xs rounded focus:outline-none focus:ring-1 focus:ring-purple-500 focus:border-transparent [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" + placeholder="Кол-во" + />
- {/* Указание что настройки в корзине */} + {/* Цена - компактно, показывается только если есть количество */} {selectedQuantity > 0 && ( -
-
- В корзине: {selectedQuantity} шт -

Настройте цену и услуги в корзине

+ { + const value = e.target.value.replace(/[^0-9.,]/g, '').replace(',', '.') + const totalPrice = parseFloat(value) || 0 + updateCardSelection(card, 'customPrice', totalPrice) + }} + onFocus={(e) => e.target.select()} + className="w-full h-7 text-center bg-white/10 border border-white/20 text-white text-xs rounded focus:outline-none focus:ring-1 focus:ring-green-500 focus:border-transparent" + placeholder={`Общая цена за ${selectedQuantity} шт`} + /> + )} + + {/* Результат - очень компактно */} + {selectedQuantity > 0 && getSelectedTotalPrice(card) > 0 && ( +
+
+
+ {formatCurrency(getSelectedTotalPrice(card))} +
+
+ ~{formatCurrency(getSelectedUnitPrice(card))}/шт +
)} + + {/* Индикатор подготовки к добавлению */} + {selectedQuantity > 0 && ( +
+ + + Подготовлен + +
+ )}
@@ -1143,15 +1307,15 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) {
)} - {/* Плавающая корзина */} - {selectedCards.length > 0 && !showSummary && ( + {/* Плавающая кнопка "В корзину" для подготовленных товаров */} + {preparingCards.length > 0 && getPreparingTotalItems() > 0 && (
)} @@ -1197,149 +1361,87 @@ export function WBProductCards({ onBack, onComplete }: WBProductCardsProps) { {/* Модальное окно с детальной информацией о товаре */} !open && closeDetailsModal()}> - - - Детальная информация о товаре + + + Информация о товаре {selectedCardForDetails && ( -
- {/* Изображения */} -
-
- {selectedCardForDetails.title} - - {/* Навигация по изображениям */} - {WildberriesService.getCardImages(selectedCardForDetails).length > 1 && ( - <> - - - -
- {currentImageIndex + 1} из {WildberriesService.getCardImages(selectedCardForDetails).length} -
- - )} -
+
+ {/* Изображение */} +
+ {selectedCardForDetails.title} - {/* Миниатюры изображений */} + {/* Навигация по изображениям */} {WildberriesService.getCardImages(selectedCardForDetails).length > 1 && ( -
-
- {WildberriesService.getCardImages(selectedCardForDetails).map((image, index) => ( - {`${selectedCardForDetails.title} setCurrentImageIndex(index)} - /> - ))} + <> + + + +
+ {currentImageIndex + 1} из {WildberriesService.getCardImages(selectedCardForDetails).length}
-
+ )}
- {/* Информация о товаре */} -
+ {/* Основная информация */} +
-

{selectedCardForDetails.title}

-

Артикул: {selectedCardForDetails.vendorCode}

- -
-
- Бренд: - {selectedCardForDetails.brand} -
-
- Категория: - {selectedCardForDetails.object} -
-
- Родительская: - {selectedCardForDetails.parent} -
-
- Страна: - {selectedCardForDetails.countryProduction} -
+

{selectedCardForDetails.title}

+

Артикул WB: {selectedCardForDetails.nmID}

+
+ +
+
+ Бренд: + {selectedCardForDetails.brand} +
+
+ Категория: + {selectedCardForDetails.object}
{selectedCardForDetails.description && (
-

Описание

-
-

- {selectedCardForDetails.description} -

-
+

Описание

+

+ {selectedCardForDetails.description} +

)} - - {/* Размеры и цены */} -
-

Доступные варианты

-
- {selectedCardForDetails.sizes.map((size) => ( -
-
- {size.wbSize} - 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} шт. - -
-
- Размер: {size.techSize} -
-
{formatCurrency(size.discountedPrice || size.price)}
- {size.discountedPrice && size.discountedPrice < size.price && ( -
{formatCurrency(size.price)}
- )} -
-
-
- ))} -
-
- - {/* Кнопки действий в модальном окне */} -
- - -
+ + {/* Миниатюры изображений */} + {WildberriesService.getCardImages(selectedCardForDetails).length > 1 && ( +
+ {WildberriesService.getCardImages(selectedCardForDetails).map((image, index) => ( + {`${selectedCardForDetails.title} setCurrentImageIndex(index)} + /> + ))} +
+ )}
)}