
- Исправлен missing dependency в useSupplyCart.ts - Исправлен missing dependency в useWildberriesProducts.ts - Добавлен useCallback для getProductTotalWithRecipe для стабильности - Оптимизированы зависимости в useMemo и useCallback хуках - Обновлена система правил для разделенных файлов rules-complete1/2 - Созда��а система проактивного мониторинга контекста - Добавлен детальный план безопасного рефакторинга больших компонентов 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
231 lines
7.4 KiB
TypeScript
231 lines
7.4 KiB
TypeScript
/**
|
||
* ХУКА ДЛЯ ЛОГИКИ КОРЗИНЫ ПОСТАВОК
|
||
*
|
||
* Выделена из 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 = 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 totalGoodsAmount = useMemo(() => {
|
||
return selectedGoods.reduce((sum, item) => {
|
||
return sum + getProductTotalWithRecipe(item.id, item.selectedQuantity)
|
||
}, 0)
|
||
}, [selectedGoods, getProductTotalWithRecipe])
|
||
|
||
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,
|
||
}
|
||
}
|