diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/ConsumablesBlock.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/ConsumablesBlock.tsx new file mode 100644 index 0000000..a3c8e58 --- /dev/null +++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/ConsumablesBlock.tsx @@ -0,0 +1,307 @@ +// ============================================================================= +// 📦 БЛОК РАСХОДНИКОВ +// ============================================================================= +// ВНИМАНИЕ: Визуал остается ТОЧНО таким же как в монолитной версии! +// Все gradients, glassmorphism, анимации, индикаторы остатков сохранены + +import { Search, Wrench, Package, Plus, Minus } from 'lucide-react' +import Image from 'next/image' + +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Card } from '@/components/ui/card' +import { Input } from '@/components/ui/input' + +import type { FulfillmentConsumableProduct } from '../types' + +import type { ConsumablesBlockProps } from './types' + +export function ConsumablesBlock({ + selectedSupplier, + products, + productsLoading, + productSearchQuery, + getSelectedQuantity, + onProductSearchChange, + onUpdateQuantity, + formatCurrency, +}: ConsumablesBlockProps) { + return ( + +
+
+

+ + Расходники для фулфилмента + {selectedSupplier && ( + + - {selectedSupplier.name || selectedSupplier.fullName} + + )} +

+
+ {selectedSupplier && ( +
+ + onProductSearchChange(e.target.value)} + className="bg-white/10 border-white/20 text-white placeholder-white/40 pl-7 h-8 text-sm" + /> +
+ )} +
+ +
+ {!selectedSupplier ? ( +
+ +

Выберите поставщика для просмотра расходников

+
+ ) : productsLoading ? ( +
+
+

Загрузка...

+
+ ) : products.length === 0 ? ( +
+ +

Нет доступных расходников

+
+ ) : ( +
+ {products.map((product: FulfillmentConsumableProduct, index: number) => { + const selectedQuantity = getSelectedQuantity(product.id) + return ( + 0 + ? 'ring-2 ring-green-400/50 bg-gradient-to-br from-green-500/20 via-green-400/10 to-green-500/20' + : 'hover:from-white/20 hover:via-white/10 hover:to-white/20 hover:border-white/40' + }`} + style={{ + animationDelay: `${index * 50}ms`, + minHeight: '200px', + width: '100%', + }} + > +
+ {/* Изображение товара */} +
+ {/* 🚫 ОВЕРЛЕЙ НЕДОСТУПНОСТИ */} + {(() => { + const totalStock = product.stock || (product as any).quantity || 0 + const orderedStock = (product as any).ordered || 0 + const availableStock = totalStock - orderedStock + + if (availableStock <= 0) { + return ( +
+
+
НЕТ В НАЛИЧИИ
+
+
+ ) + } + return null + })()} + {product.images && product.images.length > 0 && product.images[0] ? ( + {product.name} + ) : product.mainImage ? ( + {product.name} + ) : ( +
+ +
+ )} + {selectedQuantity > 0 && ( +
+ + {selectedQuantity > 999 ? '999+' : selectedQuantity} + +
+ )} +
+ + {/* Информация о товаре */} +
+

+ {product.name} +

+
+ {product.category && ( + + {product.category.name.slice(0, 10)} + + )} + {/* 🚨 ИНДИКАТОР НИЗКИХ ОСТАТКОВ согласно правилам (раздел 6.3) */} + {(() => { + const totalStock = product.stock || product.quantity || 0 + const orderedStock = product.ordered || 0 + const availableStock = totalStock - orderedStock + + if (availableStock <= 0) { + return ( + + Нет в наличии + + ) + } else if (availableStock <= 10) { + return ( + + Мало остатков + + ) + } + return null + })()} +
+
+ + {formatCurrency(product.price)} + + {/* 📊 АКТУАЛЬНЫЙ ОСТАТОК согласно правилам (раздел 6.4.2) */} +
+ {(() => { + const totalStock = product.stock || product.quantity || 0 + const orderedStock = product.ordered || 0 + const availableStock = totalStock - orderedStock + + return ( +
+ + Доступно: {availableStock} + + {orderedStock > 0 && ( + Заказано: {orderedStock} + )} +
+ ) + })()} +
+
+
+ + {/* Управление количеством */} +
+ {(() => { + const totalStock = product.stock || (product as any).quantity || 0 + const orderedStock = (product as any).ordered || 0 + const availableStock = totalStock - orderedStock + + return ( +
+ + { + let inputValue = e.target.value + + // Удаляем все нецифровые символы + inputValue = inputValue.replace(/[^0-9]/g, '') + + // Удаляем ведущие нули + inputValue = inputValue.replace(/^0+/, '') + + // Если строка пустая после удаления нулей, устанавливаем 0 + const numericValue = inputValue === '' ? 0 : parseInt(inputValue) + + // Ограничиваем значение максимумом доступного остатка + const clampedValue = Math.min(numericValue, availableStock, 99999) + + onUpdateQuantity(product.id, clampedValue) + }} + onBlur={(e) => { + // При потере фокуса, если поле пустое, устанавливаем 0 + if (e.target.value === '') { + onUpdateQuantity(product.id, 0) + } + }} + className="w-16 h-7 text-center text-sm bg-white/10 border-white/20 text-white rounded px-1 focus:ring-2 focus:ring-purple-400/50 focus:border-purple-400/50" + placeholder="0" + /> + +
+ ) + })()} + + {selectedQuantity > 0 && ( +
+ + {formatCurrency(product.price * selectedQuantity)} + +
+ )} +
+
+ + {/* Hover эффект */} +
+
+ ) + })} +
+ )} +
+
+ ) +} \ No newline at end of file diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/PageHeader.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/PageHeader.tsx new file mode 100644 index 0000000..dc3c502 --- /dev/null +++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/PageHeader.tsx @@ -0,0 +1,32 @@ +// ============================================================================= +// 📄 БЛОК ЗАГОЛОВКА СТРАНИЦЫ +// ============================================================================= +// ВНИМАНИЕ: Визуал остается ТОЧНО таким же как в монолитной версии! + +import { ArrowLeft } from 'lucide-react' + +import { Button } from '@/components/ui/button' + +import type { PageHeaderProps } from './types' + +export function PageHeader({ onBack }: PageHeaderProps) { + return ( +
+
+

Создание поставки расходников фулфилмента

+

+ Выберите поставщика и добавьте расходники в заказ для вашего фулфилмент-центра +

+
+ +
+ ) +} \ No newline at end of file diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/ShoppingCartBlock.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/ShoppingCartBlock.tsx new file mode 100644 index 0000000..863b359 --- /dev/null +++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/ShoppingCartBlock.tsx @@ -0,0 +1,143 @@ +// ============================================================================= +// 🛒 БЛОК КОРЗИНЫ +// ============================================================================= +// ВНИМАНИЕ: Визуал остается ТОЧНО таким же как в монолитной версии! +// Все gradients, glassmorphism, анимации сохранены + +import { ShoppingCart } from 'lucide-react' + +import { Button } from '@/components/ui/button' +import { Card } from '@/components/ui/card' +import { Input } from '@/components/ui/input' + +import type { ShoppingCartBlockProps } from './types' + +export function ShoppingCartBlock({ + selectedConsumables, + deliveryDate, + notes, + selectedLogistics, + logisticsPartners, + isCreatingSupply, + getTotalAmount, + getTotalItems, + formatCurrency, + onUpdateQuantity, + onSetDeliveryDate, + onSetNotes, + onSetLogistics, + onCreateSupply, +}: ShoppingCartBlockProps) { + return ( + +

+ + Корзина ({getTotalItems()} шт) +

+ + {selectedConsumables.length === 0 ? ( +
+
+ +
+

Корзина пуста

+

Добавьте расходники для создания поставки

+
+ ) : ( +
+ {selectedConsumables.map((consumable) => ( +
+
+

{consumable.name}

+

+ {formatCurrency(consumable.price)} × {consumable.selectedQuantity} +

+
+
+ + {formatCurrency(consumable.price * consumable.selectedQuantity)} + + +
+
+ ))} +
+ )} + +
+
+ + onSetDeliveryDate(e.target.value)} + className="bg-white/10 border-white/20 text-white h-8 text-sm" + min={new Date().toISOString().split('T')[0]} + required + /> +
+ + {/* Выбор логистики */} +
+ +
+ +
+ + + +
+
+
+ + {/* Заметки */} +
+ +