perf(supplies): optimize React components with memo and callbacks

ФАЗА 2: Оптимизация производительности завершена:
- Обернуты все блок-компоненты в React.memo для предотвращения лишних ререндеров
- Добавлены useCallback для всех обработчиков событий в главном компоненте
- Оптимизированы зависимости для минимизации пересоздания функций
- Страница остается полностью функциональной

Компоненты с memo: SuppliersBlock, ProductCardsBlock, DetailedCatalogBlock, CartBlock
Callbacks: handleSupplierSelect, handleProductAdd, handleQuantityChange, handleRecipeChange

🤖 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 20:40:57 +03:00
parent 94ea6c2c77
commit 6d7762b2ee
6 changed files with 66 additions and 39 deletions

View File

@ -41,3 +41,9 @@
POST /api/graphql 200 in 936ms
POST /api/graphql 200 in 638ms
POST /api/graphql 200 in 489ms
POST /api/graphql 200 in 560ms
POST /api/graphql 200 in 473ms
POST /api/graphql 200 in 1273ms
POST /api/graphql 200 in 1323ms
POST /api/graphql 200 in 475ms
POST /api/graphql 200 in 907ms

View File

@ -8,13 +8,14 @@
'use client'
import { ShoppingCart, X } from 'lucide-react'
import React from 'react'
import { Button } from '@/components/ui/button'
import { DatePicker } from '@/components/ui/date-picker'
import type { CartBlockProps } from '../types/supply-creation.types'
export function CartBlock({
export const CartBlock = React.memo(function CartBlock({
selectedGoods,
selectedSupplier,
deliveryDate,
@ -155,4 +156,4 @@ export function CartBlock({
</div>
</div>
)
}
})

View File

@ -9,6 +9,7 @@
import { Package, Settings, Building2 } from 'lucide-react'
import Image from 'next/image'
import React from 'react'
import { Badge } from '@/components/ui/badge'
import { DatePicker } from '@/components/ui/date-picker'
@ -24,7 +25,7 @@ import type {
SellerConsumable,
} from '../types/supply-creation.types'
export function DetailedCatalogBlock({
export const DetailedCatalogBlock = React.memo(function DetailedCatalogBlock({
allSelectedProducts,
productRecipes,
fulfillmentServices,
@ -129,7 +130,7 @@ export function DetailedCatalogBlock({
</div>
</div>
)
}
})
// Компонент детальной карточки товара с рецептурой
interface ProductDetailCardProps {

View File

@ -9,12 +9,17 @@
import { Package, Plus } from 'lucide-react'
import Image from 'next/image'
import React from 'react'
import { Badge } from '@/components/ui/badge'
import type { ProductCardsBlockProps } from '../types/supply-creation.types'
export function ProductCardsBlock({ products, selectedSupplier, onProductAdd }: ProductCardsBlockProps) {
export const ProductCardsBlock = React.memo(function ProductCardsBlock({
products,
selectedSupplier,
onProductAdd,
}: ProductCardsBlockProps) {
if (!selectedSupplier) {
return (
<div className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl p-6">
@ -141,4 +146,4 @@ export function ProductCardsBlock({ products, selectedSupplier, onProductAdd }:
</div>
</div>
)
}
})

View File

@ -8,13 +8,14 @@
'use client'
import { Search } from 'lucide-react'
import React from 'react'
import { OrganizationAvatar } from '@/components/market/organization-avatar'
import { Input } from '@/components/ui/input'
import type { SuppliersBlockProps } from '../types/supply-creation.types'
export function SuppliersBlock({
export const SuppliersBlock = React.memo(function SuppliersBlock({
suppliers,
selectedSupplier,
searchQuery,
@ -132,7 +133,7 @@ export function SuppliersBlock({
)}
</div>
)
}
})
// Утилитарная функция для меток рынков (временно, потом перенести в хук)
function getMarketLabel(market?: string) {

View File

@ -9,6 +9,7 @@
import { ArrowLeft } from 'lucide-react'
import { useRouter } from 'next/navigation'
import React, { useCallback } from 'react'
import { Sidebar } from '@/components/dashboard/sidebar'
import { Button } from '@/components/ui/button'
@ -89,23 +90,30 @@ export function CreateSuppliersSupplyPage() {
} = useRecipeBuilder({ selectedFulfillment })
// Обработчики событий для блоков
const handleSupplierSelect = (supplier: GoodsSupplier) => {
const handleSupplierSelect = useCallback(
(supplier: GoodsSupplier) => {
setSelectedSupplier(supplier)
// Сбрасываем выбранные товары при смене поставщика
setAllSelectedProducts([])
setSelectedGoods([])
}
},
[setSelectedSupplier, setAllSelectedProducts, setSelectedGoods],
)
const handleProductAdd = (product: GoodsProduct) => {
const handleProductAdd = useCallback(
(product: GoodsProduct) => {
const quantity = getProductQuantity(product.id) || 1
addProductToSelected(product, quantity)
initializeProductRecipe(product.id)
// Добавляем в корзину
addToCart(product, quantity)
}
},
[getProductQuantity, addProductToSelected, initializeProductRecipe, addToCart],
)
const handleQuantityChange = (productId: string, quantity: number) => {
const handleQuantityChange = useCallback(
(productId: string, quantity: number) => {
updateSelectedProductQuantity(productId, quantity)
// Синхронизируем с корзиной
@ -116,14 +124,19 @@ export function CreateSuppliersSupplyPage() {
removeFromCart(productId)
removeProductFromSelected(productId)
}
}
},
[updateSelectedProductQuantity, allSelectedProducts, addToCart, removeFromCart, removeProductFromSelected],
)
const handleRecipeChange = (productId: string, recipe: ProductRecipe) => {
const handleRecipeChange = useCallback(
(productId: string, recipe: ProductRecipe) => {
setProductRecipes((prev) => ({
...prev,
[productId]: recipe,
}))
}
},
[setProductRecipes],
)
// Обработчик ошибок
if (suppliersError) {