Оптимизирована производительность React компонентов с помощью мемоизации

КРИТИЧНЫЕ КОМПОНЕНТЫ ОПТИМИЗИРОВАНЫ:
• AdminDashboard (346 kB) - добавлены React.memo, useCallback, useMemo
• SellerStatisticsDashboard (329 kB) - мемоизация кэша и callback функций
• CreateSupplyPage (276 kB) - оптимизированы вычисления и обработчики
• EmployeesDashboard (268 kB) - мемоизация списков и функций
• SalesTab + AdvertisingTab - React.memo обертка

ТЕХНИЧЕСКИЕ УЛУЧШЕНИЯ:
 React.memo() для предотвращения лишних рендеров
 useMemo() для тяжелых вычислений
 useCallback() для стабильных ссылок на функции
 Мемоизация фильтрации и сортировки списков
 Оптимизация пропсов в компонентах-контейнерах

РЕЗУЛЬТАТЫ:
• Все компоненты успешно компилируются
• Линтер проходит без критических ошибок
• Сохранена вся функциональность
• Улучшена производительность рендеринга
• Снижена нагрузка на React дерево

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Veronika Smirnova
2025-08-06 13:18:45 +03:00
parent ef5de31ce7
commit bf27f3ba29
317 changed files with 26722 additions and 38332 deletions

View File

@ -1,96 +1,90 @@
"use client";
'use client'
import React, { useState } from "react";
import { useQuery } from "@apollo/client";
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Sidebar } from "@/components/dashboard/sidebar";
import { useSidebar } from "@/hooks/useSidebar";
import { ProductForm } from "./product-form";
import { ProductCard } from "./product-card";
import { WarehouseStatistics } from "./warehouse-statistics";
import { GET_MY_PRODUCTS } from "@/graphql/queries";
import { Plus, Package, Grid3X3, List, Edit3, Trash2 } from "lucide-react";
import { Input } from "@/components/ui/input";
import { useQuery } from '@apollo/client'
import { Plus, Package, Grid3X3, List, Edit3, Trash2 } from 'lucide-react'
import React, { useState } from 'react'
import { Sidebar } from '@/components/dashboard/sidebar'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import { GET_MY_PRODUCTS } from '@/graphql/queries'
import { useSidebar } from '@/hooks/useSidebar'
import { ProductCard } from './product-card'
import { ProductForm } from './product-form'
import { WarehouseStatistics } from './warehouse-statistics'
interface Product {
id: string;
name: string;
article: string;
description: string;
price: number;
pricePerSet?: number;
quantity: number;
setQuantity?: number;
ordered?: number;
inTransit?: number;
stock?: number;
sold?: number;
type: "PRODUCT" | "CONSUMABLE";
category: { id: string; name: string } | null;
brand: string;
color: string;
size: string;
weight: number;
dimensions: string;
material: string;
images: string[];
mainImage: string;
isActive: boolean;
createdAt: string;
updatedAt: string;
id: string
name: string
article: string
description: string
price: number
pricePerSet?: number
quantity: number
setQuantity?: number
ordered?: number
inTransit?: number
stock?: number
sold?: number
type: 'PRODUCT' | 'CONSUMABLE'
category: { id: string; name: string } | null
brand: string
color: string
size: string
weight: number
dimensions: string
material: string
images: string[]
mainImage: string
isActive: boolean
createdAt: string
updatedAt: string
}
export function WarehouseDashboard() {
const { getSidebarMargin } = useSidebar();
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [editingProduct, setEditingProduct] = useState<Product | null>(null);
const [searchQuery, setSearchQuery] = useState("");
const [viewMode, setViewMode] = useState<'cards' | 'table'>('cards');
const { getSidebarMargin } = useSidebar()
const [isDialogOpen, setIsDialogOpen] = useState(false)
const [editingProduct, setEditingProduct] = useState<Product | null>(null)
const [searchQuery, setSearchQuery] = useState('')
const [viewMode, setViewMode] = useState<'cards' | 'table'>('cards')
const { data, loading, error, refetch } = useQuery(GET_MY_PRODUCTS, {
errorPolicy: "all",
});
errorPolicy: 'all',
})
const products: Product[] = data?.myProducts || [];
const products: Product[] = data?.myProducts || []
// Фильтрация товаров по поисковому запросу
const filteredProducts = products.filter(
(product) =>
product.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
product.article.toLowerCase().includes(searchQuery.toLowerCase()) ||
product.category?.name
?.toLowerCase()
.includes(searchQuery.toLowerCase()) ||
product.brand?.toLowerCase().includes(searchQuery.toLowerCase())
);
product.category?.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
product.brand?.toLowerCase().includes(searchQuery.toLowerCase()),
)
const handleCreateProduct = () => {
setEditingProduct(null);
setIsDialogOpen(true);
};
setEditingProduct(null)
setIsDialogOpen(true)
}
const handleEditProduct = (product: Product) => {
setEditingProduct(product);
setIsDialogOpen(true);
};
setEditingProduct(product)
setIsDialogOpen(true)
}
const handleProductSaved = () => {
setIsDialogOpen(false);
setEditingProduct(null);
refetch();
};
setIsDialogOpen(false)
setEditingProduct(null)
refetch()
}
const handleProductDeleted = () => {
refetch();
};
refetch()
}
if (error) {
return (
@ -102,16 +96,9 @@ export function WarehouseDashboard() {
<div className="flex items-center justify-center h-full">
<div className="text-center">
<Package className="h-16 w-16 text-white/40 mx-auto mb-4" />
<h3 className="text-lg font-medium text-white mb-2">
Ошибка загрузки
</h3>
<p className="text-white/60 text-sm mb-4">
{error.message || "Не удалось загрузить товары"}
</p>
<Button
onClick={() => refetch()}
className="bg-purple-600 hover:bg-purple-700 text-white"
>
<h3 className="text-lg font-medium text-white mb-2">Ошибка загрузки</h3>
<p className="text-white/60 text-sm mb-4">{error.message || 'Не удалось загрузить товары'}</p>
<Button onClick={() => refetch()} className="bg-purple-600 hover:bg-purple-700 text-white">
Попробовать снова
</Button>
</div>
@ -120,15 +107,13 @@ export function WarehouseDashboard() {
</div>
</main>
</div>
);
)
}
return (
<div className="h-screen flex overflow-hidden">
<Sidebar />
<main
className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}
>
<main className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}>
<div className="h-full w-full flex flex-col">
{/* Поиск и управление */}
<div className="flex items-center justify-between mb-4 flex-shrink-0">
@ -171,7 +156,7 @@ export function WarehouseDashboard() {
</Button>
</div>
</div>
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<Button
@ -182,12 +167,13 @@ export function WarehouseDashboard() {
Добавить товар/расходник
</Button>
</DialogTrigger>
<DialogContent className="glass-card !w-[90vw] !max-w-[90vw] max-h-[95vh]" style={{ width: '90vw', maxWidth: '90vw' }}>
<DialogContent
className="glass-card !w-[90vw] !max-w-[90vw] max-h-[95vh]"
style={{ width: '90vw', maxWidth: '90vw' }}
>
<DialogHeader>
<DialogTitle className="text-white">
{editingProduct
? "Редактировать товар/расходник"
: "Добавить товар/расходник"}
{editingProduct ? 'Редактировать товар/расходник' : 'Добавить товар/расходник'}
</DialogTitle>
</DialogHeader>
<ProductForm
@ -206,7 +192,7 @@ export function WarehouseDashboard() {
{/* Основной контент */}
<Card className="flex-1 bg-white/5 backdrop-blur border-white/10 p-6 overflow-y-auto">
{loading ? (
{loading ? (
<div className="flex items-center justify-center h-full">
<div className="text-center">
<div className="animate-spin rounded-full h-16 w-16 border-4 border-white border-t-transparent mx-auto mb-4"></div>
@ -218,18 +204,13 @@ export function WarehouseDashboard() {
<div className="text-center">
<Package className="h-16 w-16 text-white/40 mx-auto mb-4" />
<h3 className="text-lg font-medium text-white mb-2">
{searchQuery ? "Товары не найдены" : "Склад пуст"}
{searchQuery ? 'Товары не найдены' : 'Склад пуст'}
</h3>
<p className="text-white/60 text-sm mb-4">
{searchQuery
? "Попробуйте изменить критерии поиска"
: "Добавьте ваш первый товар на склад"}
{searchQuery ? 'Попробуйте изменить критерии поиска' : 'Добавьте ваш первый товар на склад'}
</p>
{!searchQuery && (
<Button
onClick={handleCreateProduct}
className="bg-purple-600 hover:bg-purple-700 text-white"
>
<Button onClick={handleCreateProduct} className="bg-purple-600 hover:bg-purple-700 text-white">
<Plus className="w-4 h-4 mr-2" />
Добавить товар
</Button>
@ -265,7 +246,10 @@ export function WarehouseDashboard() {
<div className="col-span-1">Действия</div>
</div>
{filteredProducts.map((product) => (
<div key={product.id} className="grid grid-cols-12 gap-4 p-4 hover:bg-white/5 rounded-lg transition-colors">
<div
key={product.id}
className="grid grid-cols-12 gap-4 p-4 hover:bg-white/5 rounded-lg transition-colors"
>
<div className="col-span-1">
{product.mainImage || product.images[0] ? (
<img
@ -285,29 +269,34 @@ export function WarehouseDashboard() {
</div>
<div className="col-span-1 text-white/70 text-sm font-mono">{product.article}</div>
<div className="col-span-1">
<span className={`inline-block px-2 py-1 rounded text-xs ${
product.type === 'PRODUCT'
? 'bg-blue-500/20 text-blue-300 border border-blue-400/30'
: 'bg-orange-500/20 text-orange-300 border border-orange-400/30'
}`}>
<span
className={`inline-block px-2 py-1 rounded text-xs ${
product.type === 'PRODUCT'
? 'bg-blue-500/20 text-blue-300 border border-blue-400/30'
: 'bg-orange-500/20 text-orange-300 border border-orange-400/30'
}`}
>
{product.type === 'PRODUCT' ? 'Товар' : 'Расходник'}
</span>
</div>
<div className="col-span-1 text-white/70 text-sm">
{product.category?.name || 'Нет'}
</div>
<div className="col-span-1 text-white/70 text-sm">{product.category?.name || 'Нет'}</div>
<div className="col-span-1 text-white text-sm font-medium">
{new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: 'RUB',
minimumFractionDigits: 0
minimumFractionDigits: 0,
}).format(product.price)}
</div>
<div className="col-span-1 text-white text-sm">
<span className={`${
(product.stock || product.quantity) === 0 ? 'text-red-400' :
(product.stock || product.quantity) < 10 ? 'text-yellow-400' : 'text-green-400'
}`}>
<span
className={`${
(product.stock || product.quantity) === 0
? 'text-red-400'
: (product.stock || product.quantity) < 10
? 'text-yellow-400'
: 'text-green-400'
}`}
>
{product.stock || product.quantity || 0}
</span>
</div>
@ -343,5 +332,5 @@ export function WarehouseDashboard() {
</div>
</main>
</div>
);
)
}