# АРХИТЕКТУРА REACT КОМПОНЕНТОВ СИСТЕМЫ SFERA ## 🎯 ОБЩИЕ ПРИНЦИПЫ АРХИТЕКТУРЫ ### 1. МОДУЛЬНАЯ АРХИТЕКТУРА (Официальный стандарт) **Применяется для компонентов >500 строк (обязательно) и >800 строк (рефакторинг)** ```typescript // ✅ Модульная структура (пример: create-suppliers) src/components/supplies/create-suppliers/ ├── index.tsx // Главный компонент-оркестратор ├── blocks/ // UI блок-компоненты │ ├── SuppliersBlock.tsx // Выбор поставщиков │ ├── ProductCardsBlock.tsx // Каталог товаров │ ├── DetailedCatalogBlock.tsx // Детальный каталог │ └── CartBlock.tsx // Корзина заказа ├── hooks/ // Бизнес-логика │ ├── useSupplierSelection.ts // Логика выбора поставщиков │ ├── useProductCatalog.ts // Логика каталога │ ├── useRecipeBuilder.ts // Построение рецептур │ └── useSupplyCart.ts // Управление корзиной └── types/ └── supply-creation.types.ts // TypeScript интерфейсы ``` ### 2. ПРИНЦИП КОМПОЗИЦИИ КОМПОНЕНТОВ ```tsx // ✅ Главный компонент как оркестратор export function CreateSuppliersSupplyPage() { // Подключение hooks для бизнес-логики const supplierLogic = useSupplierSelection() const catalogLogic = useProductCatalog() const cartLogic = useSupplyCart() return (
{/* Композиция из блок-компонентов */}
) } ``` ### 3. РАЗДЕЛЕНИЕ ОТВЕТСТВЕННОСТИ ```typescript // ✅ Четкое разделение ролей interface ComponentRoles { index.tsx: "Композиция блоков + координация между ними" blocks/: "UI представление + локальная логика взаимодействия" hooks/: "Бизнес-логика + API взаимодействие + состояние" types/: "TypeScript контракты + интерфейсы данных" } ``` ## 🧩 ТИПЫ КОМПОНЕНТОВ В СИСТЕМЕ ### 1. DASHBOARD КОМПОНЕНТЫ **Назначение**: Главные экраны кабинетов организаций ```typescript // Паттерн: [OrganizationType]-[Feature]-dashboard src/components/dashboard/ ├── dashboard.tsx // Общий роутер по типам ├── fulfillment-dashboard.tsx // Специализированный дашборд ├── seller-dashboard.tsx ├── wholesale-dashboard.tsx └── logist-dashboard.tsx // Структура dashboard компонента: export function FulfillmentDashboard() { const { organization } = useAuth() // Условная маршрутизация по функциям if (activeTab === 'supplies') return if (activeTab === 'warehouse') return if (activeTab === 'orders') return } ``` ### 2. CREATION/FORM КОМПОНЕНТЫ **Назначение**: Создание сущностей, сложные формы ```typescript // Паттерн: create-[entity]-[action]-page src/components/supplies/ ├── create-suppliers/ // Модульная архитектура ├── create-supply-page.tsx // Простая форма ├── create-consumables-supply-page.tsx └── direct-supply-creation/ // Модульная архитектура // Принципы создания форм: const CreateEntityForm = () => { // 1. Валидация данных const { register, handleSubmit, errors } = useForm() // 2. API интеграция const { createEntity, loading } = useEntityCreation() // 3. Навигация после создания const router = useRouter() const onSubmit = async (data) => { await createEntity(data) router.push('/success-page') } } ``` ### 3. LISTING/TABLE КОМПОНЕНТЫ **Назначение**: Списки, таблицы, отображение данных ```typescript // Паттерн: [entity]-[action]-tab или [entity]-list src/components/supplies/ ├── goods-supplies-table.tsx // Таблица товаров ├── multilevel-supplies-table.tsx // Многоуровневая таблица ├── fulfillment-supplies/ │ ├── all-supplies-tab.tsx // Табы для группировки │ ├── fulfillment-supplies-tab.tsx │ └── seller-supply-orders-tab.tsx // Структура listing компонента: const EntityListingTab = () => { // 1. Загрузка данных const { entities, loading, error } = useEntityList() // 2. Фильтрация и поиск const { filteredEntities, searchQuery, setSearchQuery } = useEntityFilters(entities) // 3. Пагинация const { currentPage, pageSize, paginatedEntities } = usePagination(filteredEntities) return (
) } ``` ### 4. MODAL/DIALOG КОМПОНЕНТЫ **Назначение**: Всплывающие окна, детальные формы ```typescript // Паттерн: [entity]-[action]-modal src/components/market/ ├── organization-details-modal.tsx // Детали организации └── product-details-modal.tsx // Детали товара src/components/supplies/ └── add-goods-modal.tsx // Добавление товаров // Структура modal компонента: interface ModalProps { isOpen: boolean onClose: () => void entityId?: string // Для редактирования существующей сущности } const EntityDetailsModal = ({ isOpen, onClose, entityId }: ModalProps) => { // Загрузка данных при открытии const { entity, loading } = useEntityDetails(entityId, isOpen) return ( {loading ? : } ) } ``` ## 🔗 ПРАВИЛА ИНТЕГРАЦИИ С HOOKS ### 1. BUSINESS LOGIC HOOKS ```typescript // ✅ Правильная структура business hook const useEntityManagement = (params?: EntityParams) => { // Состояние const [entities, setEntities] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) // API интеграция const { mutate: createEntity } = useMutation(CREATE_ENTITY_MUTATION) const { data, loading: queryLoading } = useQuery(GET_ENTITIES_QUERY) // Публичный интерфейс return { // Данные entities, loading: loading || queryLoading, error, // Действия createEntity: async (data: EntityInput) => { ... }, updateEntity: async (id: string, data: Partial) => { ... }, deleteEntity: async (id: string) => { ... }, // Вычисляемые значения totalCount: entities.length, hasEntities: entities.length > 0, } } ``` ### 2. UI STATE HOOKS ```typescript // ✅ Правильная структура UI state hook const useEntityFilters = (entities: Entity[]) => { const [searchQuery, setSearchQuery] = useState('') const [selectedCategory, setSelectedCategory] = useState(null) const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc') // Мемоизация вычислений const filteredEntities = useMemo(() => { return entities .filter( (entity) => entity.name.toLowerCase().includes(searchQuery.toLowerCase()) && (selectedCategory ? entity.category === selectedCategory : true), ) .sort((a, b) => (sortOrder === 'asc' ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name))) }, [entities, searchQuery, selectedCategory, sortOrder]) return { // Состояние фильтров searchQuery, setSearchQuery, selectedCategory, setSelectedCategory, sortOrder, setSortOrder, // Результат фильтрации filteredEntities, totalCount: filteredEntities.length, // Утилиты clearFilters: () => { setSearchQuery('') setSelectedCategory(null) setSortOrder('asc') }, } } ``` ### 3. ФОРМАТИРОВАНИЕ ДАННЫХ ```typescript // ✅ Правильная структура data formatting hook const useEntityFormatting = () => { // Форматирование для UI const formatEntityForDisplay = useCallback( (entity: Entity) => ({ id: entity.id, displayName: `${entity.name} (${entity.code})`, statusBadge: { text: entity.status, variant: entity.status === 'active' ? 'success' : 'warning', }, formattedPrice: new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB', }).format(entity.price), formattedDate: format(entity.createdAt, 'dd.MM.yyyy HH:mm'), }), [], ) return { formatEntityForDisplay, formatPrice: (price: number) => `${price} ₽`, formatDate: (date: Date) => format(date, 'dd.MM.yyyy'), } } ``` ## 🎨 UI КОМПОНЕНТЫ И PATTERNS ### 1. SHADCN/UI ИНТЕГРАЦИЯ ```typescript // ✅ Правильное использование базовых UI компонентов import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' // Создание составных компонентов на основе базовых const EntityCard = ({ entity, onEdit }: EntityCardProps) => ( {entity.name}
{entity.code}
) ``` ### 2. LAYOUT PATTERNS ```typescript // ✅ Стандартная структура page layout const PageLayout = ({ children }: { children: React.ReactNode }) => { const { getSidebarMargin } = useSidebar() return (
{children}
) } // ✅ Стандартная структура content area const ContentArea = ({ title, actions, children }: ContentAreaProps) => (
{/* Header */}

{title}

{actions}
{/* Content */}
{children}
) ``` ### 3. СОСТОЯНИЯ ЗАГРУЗКИ И ОШИБОК ```typescript // ✅ Стандартные компоненты для состояний const LoadingState = () => (
Загрузка...
) const ErrorState = ({ error, onRetry }: { error: string, onRetry?: () => void }) => (

{error}

{onRetry && ( )}
) const EmptyState = ({ message, action }: { message: string, action?: React.ReactNode }) => (

{message}

{action}
) ``` ## 🔄 ПРАВИЛА РЕФАКТОРИНГА ### 1. КРИТЕРИИ ДЛЯ РЕФАКТОРИНГА ```typescript // ❌ Кандидаты для рефакторинга: const ProblematicComponent = () => { // 1. Много useState (>8-10) const [state1, setState1] = useState() const [state2, setState2] = useState() // ... еще 8 useState // 2. Длинные useEffect (>20 строк) useEffect(() => { // 50+ строк логики }, []) // 3. Встроенная бизнес-логика в JSX return (
{data.map(item => { // 20+ строк трансформации данных прямо в JSX const processedItem = complexProcessing(item) return })}
) } ``` ### 2. СТРАТЕГИЯ РЕФАКТОРИНГА ```typescript // ✅ После рефакторинга: // 1. Выделение business logic в hooks const useItemsLogic = () => { const { items, loading } = useQuery(GET_ITEMS) const processedItems = useMemo(() => items.map(complexProcessing), [items] ) return { processedItems, loading } } // 2. Выделение UI блоков const ItemsList = ({ items }: { items: ProcessedItem[] }) => (
{items.map(item => ( ))}
) // 3. Чистый главный компонент const RefactoredComponent = () => { const { processedItems, loading } = useItemsLogic() if (loading) return return ( ) } ``` ### 3. MIGRATION CHECKLIST ```typescript // ✅ Чек-лист рефакторинга: const RefactoringChecklist = { before: [ '✅ Проанализировать размер компонента (>500 строк)', '✅ Выделить логические блоки UI', '✅ Найти повторяющиеся паттерны useState', '✅ Определить бизнес-логику для вынесения в hooks', ], during: [ '✅ Создать папочную структуру модуля', '✅ Определить TypeScript интерфейсы в types/', '✅ Вынести бизнес-логику в hooks/', '✅ Создать блок-компоненты в blocks/', '✅ Собрать главный компонент как оркестратор', ], after: [ '✅ Проверить TypeScript ошибки', '✅ Запустить линтер', '✅ Протестировать функциональность', '✅ Обновить импорты в других файлах', ], } ``` ## 🚀 ПРОИЗВОДИТЕЛЬНОСТЬ И ОПТИМИЗАЦИЯ ### 1. МЕМОИЗАЦИЯ КОМПОНЕНТОВ ```typescript // ✅ Правильная мемоизация const ExpensiveComponent = React.memo(({ data, onAction }: Props) => { // Мемоизация вычислений const processedData = useMemo(() => expensiveDataProcessing(data), [data] ) // Мемоизация коллбэков const handleAction = useCallback((id: string) => { onAction(id) }, [onAction]) return
...
}) // ✅ Оптимизация списков const OptimizedList = ({ items }: { items: Item[] }) => { return (
{items.map(item => ( ))}
) } const MemoizedItemCard = React.memo(({ item }: { item: Item }) => ( {item.name} )) ``` ### 2. LAZY LOADING КОМПОНЕНТОВ ```typescript // ✅ Ленивая загрузка тяжелых компонентов const HeavyDashboard = lazy(() => import('./heavy-dashboard')) const ComplexChart = lazy(() => import('./complex-chart')) const ComponentWithLazyLoading = () => { const [showHeavyContent, setShowHeavyContent] = useState(false) return (
{showHeavyContent && ( }> )}
) } ``` ## 📋 КОНКРЕТНЫЕ ПРИМЕРЫ ИЗ РЕАЛЬНОЙ СИСТЕМЫ ### 1. МОДУЛЬНЫЙ КОМПОНЕНТ СОЗДАНИЯ ПОСТАВОК **Файл**: `src/components/supplies/create-suppliers/` #### Структура модульного компонента: ```typescript src/components/supplies/create-suppliers/ ├── index.tsx // 🎯 Главный оркестратор - 287 строк ├── blocks/ // 🧱 UI блок-компоненты │ ├── SuppliersBlock.tsx // Выбор поставщиков │ ├── ProductCardsBlock.tsx // Превью товаров │ ├── DetailedCatalogBlock.tsx// Детальный каталог с рецептурой │ └── CartBlock.tsx // Корзина с расчетами - 336 строк ├── hooks/ // ⚙️ Бизнес-логика (custom hooks) │ ├── useSupplierSelection.ts // Управление поставщиками │ ├── useProductCatalog.ts // Каталог товаров │ ├── useRecipeBuilder.ts // Построение рецептур │ └── useSupplyCart.ts // Логика корзины - 284 строки └── types/ // 📝 TypeScript определения └── supply-creation.types.ts // Интерфейсы - 384 строки ``` #### Архитектурные принципы реализации: **1. ОРКЕСТРАТОР (index.tsx) - 287 строк:** ```typescript /** * СОЗДАНИЕ ПОСТАВОК ПОСТАВЩИКОВ - НОВАЯ МОДУЛЬНАЯ АРХИТЕКТУРА * Композиция из блок-компонентов с использованием custom hooks */ export function CreateSuppliersSupplyPage() { const router = useRouter() const { getSidebarMargin } = useSidebar() // 1. ХУКА ВЫБОРА ПОСТАВЩИКОВ const { selectedSupplier, setSelectedSupplier, searchQuery, setSearchQuery, suppliers, allCounterparties, loading: suppliersLoading, error: suppliersError, } = useSupplierSelection() // 2. ХУКА КАТАЛОГА ТОВАРОВ const { products, allSelectedProducts, setAllSelectedProducts, getProductQuantity, addProductToSelected, updateSelectedProductQuantity, removeProductFromSelected, } = useProductCatalog({ selectedSupplier }) // 3. ХУКА ПОСТРОЕНИЯ РЕЦЕПТУР const { productRecipes, setProductRecipes, fulfillmentServices, fulfillmentConsumables, sellerConsumables, initializeProductRecipe, } = useRecipeBuilder({ selectedFulfillment }) // 4. ХУКА КОРЗИНЫ ПОСТАВОК const { selectedGoods, deliveryDate, selectedFulfillment, totalGoodsAmount, isFormValid, addToCart, handleCreateSupply, } = useSupplyCart({ selectedSupplier, allCounterparties, productRecipes: productRecipes, }) // 🎨 КОМПОЗИЦИЯ БЛОКОВ return (
{/* ЛЕВАЯ ЧАСТЬ - ЗАГОЛОВОК И БЛОКИ 1-3 */}
{/* ЗАГОЛОВОК И НАВИГАЦИЯ */}

Создание поставки от поставщика

{/* БЛОКИ 1-3 */}
{/* БЛОК 1: ВЫБОР ПОСТАВЩИКОВ */}
{/* БЛОК 2: КАРТОЧКИ ТОВАРОВ */}
{/* БЛОК 3: ДЕТАЛЬНЫЙ КАТАЛОГ С РЕЦЕПТУРОЙ */}
{/* ПРАВАЯ КОЛОНКА - БЛОК 4: КОРЗИНА */}
) } ``` **2. СЛОЖНЫЙ HOOK - ЛОГИКА КОРЗИНЫ (useSupplyCart.ts) - 284 строки:** ```typescript /** * ХУКА ДЛЯ ЛОГИКИ КОРЗИНЫ ПОСТАВОК * Управляет корзиной товаров и настройками поставки */ export function useSupplyCart({ selectedSupplier, allCounterparties, productRecipes }: UseSupplyCartProps) { const router = useRouter() // Состояния корзины и настроек const [selectedGoods, setSelectedGoods] = useState([]) const [deliveryDate, setDeliveryDate] = useState(() => { const tomorrow = new Date() tomorrow.setDate(tomorrow.getDate() + 1) return tomorrow.toISOString().split('T')[0] // Формат YYYY-MM-DD }) const [selectedLogistics, setSelectedLogistics] = useState('auto') const [selectedFulfillment, setSelectedFulfillment] = useState('') const [isCreatingSupply, setIsCreatingSupply] = useState(false) // Мутация создания поставки const [createSupplyOrder] = useMutation(CREATE_SUPPLY_ORDER) // Получаем логистические компании const logisticsCompanies = useMemo(() => { return allCounterparties?.filter((partner) => partner.type === 'LOGIST') || [] }, [allCounterparties]) // Добавление товара в корзину const addToCart = (product: GoodsProduct, quantity: number, additionalData?: object) => { if (!selectedSupplier) { toast.error('Сначала выберите поставщика') return } if (quantity <= 0) { toast.error('Укажите количество товара') return } const existingItemIndex = selectedGoods.findIndex((item) => item.id === product.id) if (existingItemIndex >= 0) { // Обновляем существующий товар setSelectedGoods((prev) => { const updated = [...prev] updated[existingItemIndex] = { ...updated[existingItemIndex], selectedQuantity: quantity, ...additionalData, } return updated }) toast.success('Количество товара обновлено') } else { // Добавляем новый товар const newItem: SelectedGoodsItem = { id: product.id, name: product.name, sku: product.article, price: product.price, selectedQuantity: quantity, supplierId: selectedSupplier?.id || '', supplierName: selectedSupplier?.name || selectedSupplier?.fullName || '', ...additionalData, } setSelectedGoods((prev) => [...prev, newItem]) toast.success('Товар добавлен в корзину') } } // Функция расчета полной стоимости товара с рецептурой const getProductTotalWithRecipe = useCallback( (productId: string, quantity: number) => { const product = selectedGoods.find((p) => p.id === productId) if (!product) return 0 const baseTotal = product.price * quantity const recipe = productRecipes[productId] if (!recipe) return baseTotal // Здесь будет логика расчета стоимости услуг и расходников // Пока возвращаем базовую стоимость return baseTotal }, [selectedGoods, productRecipes], ) // БИЗНЕС-ВАЛИДАЦИЯ (реактивная) const hasRequiredServices = useMemo(() => { return selectedGoods.every((item) => { const hasServices = productRecipes[item.id]?.selectedServices?.length > 0 return hasServices }) }, [selectedGoods, productRecipes]) const isFormValid = useMemo(() => { return selectedSupplier && selectedGoods.length > 0 && deliveryDate && selectedFulfillment && hasRequiredServices }, [selectedSupplier, selectedGoods.length, deliveryDate, selectedFulfillment, hasRequiredServices]) // Создание поставки const handleCreateSupply = async () => { if (!isFormValid) { if (!hasRequiredServices) { toast.error('Каждый товар должен иметь минимум 1 услугу фулфилмента') } else { toast.error('Заполните все обязательные поля') } return } setIsCreatingSupply(true) try { const inputData = { partnerId: selectedSupplier?.id || '', fulfillmentCenterId: selectedFulfillment, deliveryDate: new Date(deliveryDate).toISOString(), // Конвертируем в ISO string logisticsPartnerId: selectedLogistics === 'auto' ? null : selectedLogistics, items: selectedGoods.map((item) => { const recipe = productRecipes[item.id] || { selectedServices: [], selectedFFConsumables: [], selectedSellerConsumables: [], } return { productId: item.id, quantity: item.selectedQuantity, recipe: { services: recipe.selectedServices || [], fulfillmentConsumables: recipe.selectedFFConsumables || [], sellerConsumables: recipe.selectedSellerConsumables || [], marketplaceCardId: recipe.selectedWBCard || null, }, } }), } await createSupplyOrder({ variables: { input: inputData } }) toast.success('Поставка успешно создана!') router.push('/supplies') } catch (error) { console.error('❌ Ошибка создания поставки:', error) toast.error('Ошибка при создании поставки') } finally { setIsCreatingSupply(false) } } return { // Состояние корзины selectedGoods, deliveryDate, selectedFulfillment, isCreatingSupply, // Расчеты totalGoodsAmount, // Валидация hasRequiredServices, isFormValid, // Функции управления корзиной addToCart, removeFromCart, getProductTotalWithRecipe, handleCreateSupply, } } ``` **3. БЛОК С БИЗНЕС-ЛОГИКОЙ (CartBlock.tsx) - 336 строк:** ```typescript /** * БЛОК КОРЗИНЫ И НАСТРОЕК ПОСТАВКИ * КЛЮЧЕВЫЕ ФУНКЦИИ: * 1. Отображение товаров в корзине с детализацией рецептуры * 2. Расчет полной стоимости с учетом услуг и расходников ФФ/селлера * 3. Настройки поставки (дата, фулфилмент, логистика) * 4. Валидация и создание поставки * * БИЗНЕС-ЛОГИКА РАСЧЕТА ЦЕН: * - Базовая цена товара × количество * - + Услуги фулфилмента × количество * - + Расходники фулфилмента × количество * - + Расходники селлера × количество * = Итоговая стоимость за товар */ export const CartBlock = React.memo(function CartBlock({ selectedGoods, productRecipes, fulfillmentServices, fulfillmentConsumables, sellerConsumables, onCreateSupply, }: CartBlockProps) { return (

Корзина

{selectedGoods.length} шт
{selectedGoods.length === 0 ? (

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

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

) : ( <> {/* Список товаров в корзине - компактная область */}
{selectedGoods.map((item) => { /** * АЛГОРИТМ РАСЧЕТА ПОЛНОЙ СТОИМОСТИ ТОВАРА * * 1. Базовая стоимость = цена товара × количество * 2. Услуги ФФ = сумма всех выбранных услуг × количество товара * 3. Расходники ФФ = сумма всех выбранных расходников × количество * 4. Расходники селлера = сумма расходников селлера × количество * 5. Итого = базовая + услуги + расходники ФФ + расходники селлера */ const recipe = productRecipes[item.id] const baseCost = item.price * item.selectedQuantity // РАСЧЕТ УСЛУГ ФУЛФИЛМЕНТА // Каждая услуга применяется к каждой единице товара const servicesCost = (recipe?.selectedServices || []).reduce((sum, serviceId) => { const service = fulfillmentServices.find(s => s.id === serviceId) return sum + (service ? service.price * item.selectedQuantity : 0) }, 0) // РАСЧЕТ РАСХОДНИКОВ ФУЛФИЛМЕНТА // Расходники ФФ тоже масштабируются по количеству товара const ffConsumablesCost = (recipe?.selectedFFConsumables || []).reduce((sum, consumableId) => { const consumable = fulfillmentConsumables.find(c => c.id === consumableId) return sum + (consumable ? consumable.price * item.selectedQuantity : 0) }, 0) // РАСЧЕТ РАСХОДНИКОВ СЕЛЛЕРА // Используется pricePerUnit как цена за единицу расходника const sellerConsumablesCost = (recipe?.selectedSellerConsumables || []).reduce((sum, consumableId) => { const consumable = sellerConsumables.find(c => c.id === consumableId) return sum + (consumable ? (consumable.pricePerUnit || 0) * item.selectedQuantity : 0) }, 0) const totalItemCost = baseCost + servicesCost + ffConsumablesCost + sellerConsumablesCost const hasRecipe = servicesCost > 0 || ffConsumablesCost > 0 || sellerConsumablesCost > 0 return (
{/* Основная информация о товаре */}

{item.name}

{item.price.toLocaleString('ru-RU')} ₽ × {item.selectedQuantity} = {baseCost.toLocaleString('ru-RU')} ₽
{/* Детализация рецептуры */} {hasRecipe && (
{servicesCost > 0 && (
+ Услуги ФФ: {servicesCost.toLocaleString('ru-RU')} ₽
)} {ffConsumablesCost > 0 && (
+ Расходники ФФ: {ffConsumablesCost.toLocaleString('ru-RU')} ₽
)} {sellerConsumablesCost > 0 && (
+ Расходники сел.: {sellerConsumablesCost.toLocaleString('ru-RU')} ₽
)}
Итого за товар: {totalItemCost.toLocaleString('ru-RU')} ₽
)}
) })}
)}
) }) ``` **4. СТРОГАЯ ТИПИЗАЦИЯ (supply-creation.types.ts) - 384 строки:** ```typescript /** * ТИПЫ ДЛЯ СОЗДАНИЯ ПОСТАВОК ПОСТАВЩИКОВ * Согласно модульной архитектуре */ // Основные сущности export interface GoodsSupplier { id: string inn: string name?: string fullName?: string type: 'FULFILLMENT' | 'SELLER' | 'LOGIST' | 'WHOLESALE' address?: string phones?: Array<{ value: string }> emails?: Array<{ value: string }> users?: Array<{ id: string; avatar?: string; managerName?: string }> createdAt: string rating?: number market?: string // Принадлежность к рынку } export interface GoodsProduct { id: string name: string description?: string price: number category?: { name: string } images: string[] mainImage?: string article: string // Артикул поставщика organization: { id: string name: string } quantity?: number unit?: string weight?: number dimensions?: { length: number width: number height: number } } export interface SelectedGoodsItem { id: string name: string sku: string price: number selectedQuantity: number unit?: string category?: string supplierId: string supplierName: string completeness?: string // Комплектность recipe?: string // Рецептура/состав specialRequirements?: string // Особые требования parameters?: Array<{ name: string; value: string }> // Параметры товара } // Компоненты рецептуры export interface FulfillmentService { id: string name: string description?: string price: number category?: string } export interface FulfillmentConsumable { id: string name: string price: number quantity: number unit?: string } export interface SellerConsumable { id: string name: string pricePerUnit: number warehouseStock: number unit?: string } export interface ProductRecipe { productId: string selectedServices: string[] selectedFFConsumables: string[] selectedSellerConsumables: string[] selectedWBCard?: string } // Пропсы для блок-компонентов export interface CartBlockProps { selectedGoods: SelectedGoodsItem[] selectedSupplier: GoodsSupplier | null deliveryDate: string selectedFulfillment: string selectedLogistics: string allCounterparties: GoodsSupplier[] totalAmount: number isFormValid: boolean isCreatingSupply: boolean // Новые поля для расчета с рецептурой allSelectedProducts: Array productRecipes: Record fulfillmentServices: FulfillmentService[] fulfillmentConsumables: FulfillmentConsumable[] sellerConsumables: SellerConsumable[] onLogisticsChange: (logistics: string) => void onCreateSupply: () => void onItemRemove: (itemId: string) => void } // === РАСШИРЕННАЯ СИСТЕМА ДЛЯ МНОГОУРОВНЕВЫХ ПОСТАВОК === // Расширенный интерфейс поставки для многоуровневой таблицы export interface MultiLevelSupplyOrder { id: string organizationId: string partnerId: string partner: GoodsSupplier deliveryDate: string status: SupplyOrderStatus totalAmount: number totalItems: number fulfillmentCenterId?: string fulfillmentCenter?: GoodsSupplier logisticsPartnerId?: string logisticsPartner?: GoodsSupplier // Новые поля packagesCount?: number // Количество грузовых мест volume?: number // Объём товара в м³ responsibleEmployee?: string // ID ответственного сотрудника ФФ employee?: Employee // Ответственный сотрудник notes?: string // Заметки routes: SupplyRoute[] // Маршруты поставки items: MultiLevelSupplyOrderItem[] createdAt: string updatedAt: string organization: GoodsSupplier } // Развернутая рецептура с детализацией export interface ExpandedProductRecipe { services: FulfillmentService[] fulfillmentConsumables: FulfillmentConsumable[] sellerConsumables: SellerConsumable[] marketplaceCardId?: string totalServicesCost: number totalConsumablesCost: number totalRecipeCost: number } // Статусы поставок export type SupplyOrderStatus = | 'PENDING' // Ожидает одобрения поставщика | 'SUPPLIER_APPROVED' // Поставщик одобрил | 'LOGISTICS_CONFIRMED' // Логистика подтвердила | 'SHIPPED' // Отправлено поставщиком | 'IN_TRANSIT' // В пути | 'DELIVERED' // Доставлено | 'CANCELLED' // Отменено ``` --- _Создано на основе анализа: модульная архитектура, паттерны компонентов, hooks система_ _Дата: 2025-08-21_ _Основано на коде: src/components/, MODULAR_ARCHITECTURE_PATTERN.md_