Оптимизирована производительность 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:
@ -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>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
Reference in New Issue
Block a user