feat(refactor): complete modular architecture for direct-supply-creation

 ПЛАН ЭТАПЫ 2-5 ЗАВЕРШЕНЫ:

ПЛАН ЭТАП 2: Создание типов (direct-supply.types.ts)
- 🎯 206 строк типов и интерфейсов
- 📋 Все основные сущности: SupplyItem, Organization, FulfillmentService, etc
- 🔄 Пропсы для всех 5 блоков и 5 хуков
- 📊 Утилиты для расчетов и валидации

ПЛАН ЭТАП 3: Извлечение custom hooks (5 хуков)
- 🎯 useWildberriesProducts.ts (200 строк) - управление товарами WB
- 🔄 useSupplyManagement.ts (125 строк) - логика поставки и расчеты
- ⚙️ useFulfillmentServices.ts (130 строк) - услуги и расходники
- 👥 useSupplierForm.ts (145 строк) - форма поставщиков с валидацией
- 🚀 useSupplyCreation.ts (160 строк) - создание поставки и валидация

ПЛАН ЭТАП 4: Создание блок-компонентов (5 блоков)
- 🔍 ProductSearchBlock.tsx (65 строк) - поиск товаров
- 📦 ProductGridBlock.tsx (120 строк) - сетка товаров WB
- 📋 SupplyItemsBlock.tsx (165 строк) - управление товарами в поставке
- ⚙️ ServicesConfigBlock.tsx (145 строк) - настройка услуг и фулфилмента
- 👤 SupplierModalBlock.tsx (135 строк) - форма создания поставщика

ПЛАН ЭТАП 5: Интеграция в главном компоненте (245 строк)
- 🎯 Композиция всех 5 хуков и 5 блоков
- 🔄 Реактивные связи между модулями
- 📊 Передача callback'ов родительскому компоненту
-  Оптимизация через useCallback и React.memo

📊 АРХИТЕКТУРНЫЕ ДОСТИЖЕНИЯ:
- Модульность: 12 файлов vs 1 монолитный
- Переиспользуемость: блоки можно использовать отдельно
- Типизация: 100% TypeScript coverage
- Тестируемость: каждый хук и блок изолирован
- Производительность: React.memo + useCallback

🔧 ИСПРАВЛЕНЫ ОШИБКИ ESLint:
- Префиксы _ для неиспользуемых переменных
- useCallback для функций в dependencies
- Типизация unknown вместо any
- ESLint disable для img элемента

🎯 Готово к тестированию новой архитектуры\!

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Veronika Smirnova
2025-08-12 21:25:46 +03:00
parent 96cac03ebd
commit 6647299a05
12 changed files with 2423 additions and 0 deletions

View File

@ -0,0 +1,123 @@
/**
* БЛОК СЕТКИ ТОВАРОВ
*
* Выделен из direct-supply-creation.tsx
* Отображает товары WB в виде красивой сетки с возможностью добавления
*/
'use client'
import { Plus, Package } from 'lucide-react'
import React from 'react'
import { WildberriesService } from '@/services/wildberries-service'
import type { ProductGridBlockProps } from '../types/direct-supply.types'
export const ProductGridBlock = React.memo(function ProductGridBlock({
wbCards,
loading,
onAddToSupply,
}: ProductGridBlockProps) {
if (loading) {
return (
<div className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl p-4">
<h3 className="text-white font-semibold text-lg mb-4">Товары (загрузка...)</h3>
{/* Skeleton сетка */}
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-3">
{[...Array(16)].map((_, i) => (
<div key={i} className="group">
<div className="aspect-[3/4] bg-gradient-to-br from-white/10 to-white/5 rounded-xl animate-pulse">
<div className="w-full h-full bg-white/5 rounded-xl"></div>
</div>
<div className="mt-1 px-1">
<div className="h-3 bg-white/10 rounded animate-pulse"></div>
</div>
</div>
))}
</div>
</div>
)
}
if (wbCards.length === 0) {
return (
<div className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl p-4">
<h3 className="text-white font-semibold text-lg mb-4">Товары (0)</h3>
{/* Пустое состояние */}
<div className="flex flex-col items-center justify-center py-12">
<div className="w-16 h-16 bg-gradient-to-r from-purple-500/20 to-blue-500/20 rounded-2xl flex items-center justify-center mb-4">
<Package className="w-8 h-8 text-white/40" />
</div>
<h3 className="text-white/80 font-medium text-base mb-2">Товары не найдены</h3>
<p className="text-white/50 text-sm text-center max-w-md">
Введите поисковый запрос или проверьте настройки API Wildberries
</p>
</div>
</div>
)
}
return (
<div className="relative">
<div className="bg-gradient-to-br from-white/15 via-white/10 to-white/5 backdrop-blur-xl border border-white/20 rounded-2xl p-4 shadow-2xl">
<div className="flex items-center justify-between mb-4">
<h3 className="text-white font-semibold text-lg">Товары ({wbCards.length})</h3>
<p className="text-white/60 text-sm">Нажмите на товар для добавления</p>
</div>
{/* Сетка товаров */}
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-3">
{wbCards.map((card) => (
<div
key={card.nmID}
className="group cursor-pointer transition-all duration-300 hover:scale-105"
onClick={() => onAddToSupply(card, 1, '')}
>
{/* Карточка товара */}
<div className="relative aspect-[3/4] rounded-xl overflow-hidden shadow-lg transition-all duration-300 bg-white/10 hover:bg-white/15 hover:shadow-xl">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={WildberriesService.getCardImage(card, 'c516x688') || '/api/placeholder/200/267'}
alt={card.title}
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
loading="lazy"
/>
{/* Градиентный оверлей */}
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
{/* Информация при наведении */}
<div className="absolute bottom-0 left-0 right-0 p-3 transform translate-y-full group-hover:translate-y-0 transition-transform duration-300">
<h4 className="text-white font-medium text-sm line-clamp-2 mb-1">{card.title}</h4>
<p className="text-white/80 text-xs">WB: {card.nmID}</p>
{card.vendorCode && <p className="text-white/60 text-xs">Арт: {card.vendorCode}</p>}
</div>
{/* Кнопка добавления */}
<div className="absolute top-3 right-3 w-8 h-8 bg-white/20 backdrop-blur rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<Plus className="w-4 h-4 text-white" />
</div>
{/* Эффект при клике */}
<div className="absolute inset-0 bg-white/20 opacity-0 group-active:opacity-100 transition-opacity duration-150" />
</div>
{/* Название под карточкой */}
<div className="mt-2 px-1">
<h4 className="text-white/90 font-medium text-xs line-clamp-2 leading-tight">{card.title}</h4>
{card.brand && <p className="text-white/50 text-[10px] mt-1">{card.brand}</p>}
</div>
</div>
))}
</div>
{/* Декоративные элементы */}
<div className="absolute -top-1 -left-1 w-4 h-4 bg-gradient-to-r from-purple-500 to-blue-500 rounded-full opacity-60 animate-pulse" />
<div className="absolute -bottom-1 -right-1 w-3 h-3 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full opacity-40 animate-pulse delay-700" />
</div>
</div>
)
})