refactor(supplies): extract custom hooks for supply creation logic
ЭТАП 1.2: Безопасное выделение бизнес-логики в хуки - Create useSupplierSelection.ts: управление выбором поставщиков и поиском - Create useProductCatalog.ts: загрузка и управление каталогом товаров - Create useSupplyCart.ts: логика корзины и создания поставки - Create useRecipeBuilder.ts: построение рецептур товаров (услуги + расходники) Каждый хук инкапсулирует отдельную область ответственности: - Состояние и действия изолированы - GraphQL запросы сгруппированы по функциональности - Бизнес-логика отделена от UI компонентов - Полная типизация с TypeScript No functional changes - pure logic extraction for better maintainability. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
227
src/components/supplies/create-suppliers/hooks/useSupplyCart.ts
Normal file
227
src/components/supplies/create-suppliers/hooks/useSupplyCart.ts
Normal file
@ -0,0 +1,227 @@
|
||||
/**
|
||||
* ХУКА ДЛЯ ЛОГИКИ КОРЗИНЫ ПОСТАВОК
|
||||
*
|
||||
* Выделена из create-suppliers-supply-page.tsx
|
||||
* Управляет корзиной товаров и настройками поставки
|
||||
*/
|
||||
|
||||
import { useMutation } from '@apollo/client'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useState, useMemo } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { CREATE_SUPPLY_ORDER } from '@/graphql/mutations'
|
||||
|
||||
import type {
|
||||
SelectedGoodsItem,
|
||||
GoodsSupplier,
|
||||
GoodsProduct,
|
||||
ProductRecipe,
|
||||
SupplyCreationFormData,
|
||||
} from '../types/supply-creation.types'
|
||||
|
||||
interface UseSupplyCartProps {
|
||||
selectedSupplier: GoodsSupplier | null
|
||||
allCounterparties: GoodsSupplier[]
|
||||
productRecipes: Record<string, ProductRecipe>
|
||||
}
|
||||
|
||||
export function useSupplyCart({ selectedSupplier, allCounterparties, productRecipes }: UseSupplyCartProps) {
|
||||
const router = useRouter()
|
||||
|
||||
// Состояния корзины и настроек
|
||||
const [selectedGoods, setSelectedGoods] = useState<SelectedGoodsItem[]>([])
|
||||
const [deliveryDate, setDeliveryDate] = useState('')
|
||||
const [selectedLogistics, setSelectedLogistics] = useState<string>('auto')
|
||||
const [selectedFulfillment, setSelectedFulfillment] = useState<string>('')
|
||||
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?: {
|
||||
completeness?: string
|
||||
recipe?: string
|
||||
specialRequirements?: string
|
||||
parameters?: Array<{ name: string; value: string }>
|
||||
},
|
||||
) => {
|
||||
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,
|
||||
unit: product.unit,
|
||||
category: product.category?.name,
|
||||
supplierId: selectedSupplier?.id || '',
|
||||
supplierName: selectedSupplier?.name || selectedSupplier?.fullName || '',
|
||||
completeness: additionalData?.completeness,
|
||||
recipe: additionalData?.recipe,
|
||||
specialRequirements: additionalData?.specialRequirements,
|
||||
parameters: additionalData?.parameters,
|
||||
}
|
||||
|
||||
setSelectedGoods((prev) => [...prev, newItem])
|
||||
toast.success('Товар добавлен в корзину')
|
||||
}
|
||||
}
|
||||
|
||||
// Удаление товара из корзины
|
||||
const removeFromCart = (itemId: string) => {
|
||||
setSelectedGoods((prev) => prev.filter((item) => item.id !== itemId))
|
||||
toast.success('Товар удален из корзины')
|
||||
}
|
||||
|
||||
// Функция расчета полной стоимости товара с рецептурой
|
||||
const getProductTotalWithRecipe = (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
|
||||
}
|
||||
|
||||
// Расчеты для корзины
|
||||
const totalGoodsAmount = useMemo(() => {
|
||||
return selectedGoods.reduce((sum, item) => {
|
||||
return sum + getProductTotalWithRecipe(item.id, item.selectedQuantity)
|
||||
}, 0)
|
||||
}, [selectedGoods, productRecipes])
|
||||
|
||||
const totalQuantity = useMemo(() => {
|
||||
return selectedGoods.reduce((sum, item) => sum + item.selectedQuantity, 0)
|
||||
}, [selectedGoods])
|
||||
|
||||
// Валидация формы
|
||||
const hasRequiredServices = useMemo(() => {
|
||||
return selectedGoods.every((item) => productRecipes[item.id]?.selectedServices?.length > 0)
|
||||
}, [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
|
||||
}
|
||||
|
||||
if (!selectedSupplier) {
|
||||
toast.error('Поставщик не выбран')
|
||||
return
|
||||
}
|
||||
|
||||
setIsCreatingSupply(true)
|
||||
|
||||
try {
|
||||
await createSupplyOrder({
|
||||
variables: {
|
||||
supplierId: selectedSupplier?.id || '',
|
||||
fulfillmentCenterId: selectedFulfillment,
|
||||
items: selectedGoods.map((item) => ({
|
||||
productId: item.id,
|
||||
quantity: item.selectedQuantity,
|
||||
recipe: productRecipes[item.id] || {
|
||||
productId: item.id,
|
||||
selectedServices: [],
|
||||
selectedFFConsumables: [],
|
||||
selectedSellerConsumables: [],
|
||||
},
|
||||
})),
|
||||
deliveryDate: deliveryDate,
|
||||
logistics: selectedLogistics,
|
||||
specialRequirements: selectedGoods
|
||||
.map((item) => item.specialRequirements)
|
||||
.filter(Boolean)
|
||||
.join('; '),
|
||||
} satisfies SupplyCreationFormData,
|
||||
})
|
||||
|
||||
toast.success('Поставка успешно создана!')
|
||||
router.push('/supplies')
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка создания поставки:', error)
|
||||
toast.error('Ошибка при создании поставки')
|
||||
} finally {
|
||||
setIsCreatingSupply(false)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// Состояние корзины
|
||||
selectedGoods,
|
||||
setSelectedGoods,
|
||||
deliveryDate,
|
||||
setDeliveryDate,
|
||||
selectedLogistics,
|
||||
setSelectedLogistics,
|
||||
selectedFulfillment,
|
||||
setSelectedFulfillment,
|
||||
isCreatingSupply,
|
||||
|
||||
// Данные
|
||||
logisticsCompanies,
|
||||
|
||||
// Расчеты
|
||||
totalGoodsAmount,
|
||||
totalQuantity,
|
||||
|
||||
// Валидация
|
||||
hasRequiredServices,
|
||||
isFormValid,
|
||||
|
||||
// Функции управления корзиной
|
||||
addToCart,
|
||||
removeFromCart,
|
||||
getProductTotalWithRecipe,
|
||||
handleCreateSupply,
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user