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:
@ -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>
|
||||
)
|
||||
})
|
Reference in New Issue
Block a user