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 936ms
POST /api/graphql 200 in 638ms POST /api/graphql 200 in 638ms
POST /api/graphql 200 in 489ms 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' 'use client'
import { ShoppingCart, X } from 'lucide-react' import { ShoppingCart, X } from 'lucide-react'
import React from 'react'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { DatePicker } from '@/components/ui/date-picker' import { DatePicker } from '@/components/ui/date-picker'
import type { CartBlockProps } from '../types/supply-creation.types' import type { CartBlockProps } from '../types/supply-creation.types'
export function CartBlock({ export const CartBlock = React.memo(function CartBlock({
selectedGoods, selectedGoods,
selectedSupplier, selectedSupplier,
deliveryDate, deliveryDate,
@ -155,4 +156,4 @@ export function CartBlock({
</div> </div>
</div> </div>
) )
} })

View File

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

View File

@ -9,12 +9,17 @@
import { Package, Plus } from 'lucide-react' import { Package, Plus } from 'lucide-react'
import Image from 'next/image' import Image from 'next/image'
import React from 'react'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import type { ProductCardsBlockProps } from '../types/supply-creation.types' 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) { if (!selectedSupplier) {
return ( return (
<div className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl p-6"> <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>
</div> </div>
) )
} })

View File

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

View File

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