Оптимизирована производительность 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,12 +1,13 @@
"use client"
'use client'
import { useState } from 'react'
import { useQuery } from '@apollo/client'
import { Card } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { Wrench, Plus, Calendar, TrendingUp, AlertCircle, Search, Filter } from 'lucide-react'
import { useState } from 'react'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { GET_MY_SUPPLIES } from '@/graphql/queries'
interface MaterialSupply {
@ -35,14 +36,14 @@ export function MaterialsSuppliesTab() {
// Загружаем расходники из GraphQL
const { data, loading, error, refetch } = useQuery(GET_MY_SUPPLIES, {
fetchPolicy: 'cache-and-network', // Всегда проверяем сервер
errorPolicy: 'all' // Показываем ошибки
errorPolicy: 'all', // Показываем ошибки
})
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: 'RUB',
minimumFractionDigits: 0
minimumFractionDigits: 0,
}).format(amount)
}
@ -50,7 +51,7 @@ export function MaterialsSuppliesTab() {
return new Date(dateString).toLocaleDateString('ru-RU', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
year: 'numeric',
})
}
@ -59,11 +60,11 @@ export function MaterialsSuppliesTab() {
planned: { variant: 'outline' as const, color: 'text-blue-300 border-blue-400/30', label: 'Запланировано' },
'in-transit': { variant: 'outline' as const, color: 'text-yellow-300 border-yellow-400/30', label: 'В пути' },
delivered: { variant: 'outline' as const, color: 'text-green-300 border-green-400/30', label: 'Доставлено' },
'in-stock': { variant: 'outline' as const, color: 'text-purple-300 border-purple-400/30', label: 'На складе' }
'in-stock': { variant: 'outline' as const, color: 'text-purple-300 border-purple-400/30', label: 'На складе' },
}
const config = statusConfig[status as keyof typeof statusConfig] || statusConfig.planned
return (
<Badge variant={config.variant} className={`glass-secondary ${config.color}`}>
{config.label}
@ -96,18 +97,19 @@ export function MaterialsSuppliesTab() {
const supplies: MaterialSupply[] = data?.mySupplies || []
const filteredSupplies = supplies.filter((supply: MaterialSupply) => {
const matchesSearch = supply.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
(supply.category || '').toLowerCase().includes(searchTerm.toLowerCase()) ||
(supply.supplier || '').toLowerCase().includes(searchTerm.toLowerCase())
const matchesSearch =
supply.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
(supply.category || '').toLowerCase().includes(searchTerm.toLowerCase()) ||
(supply.supplier || '').toLowerCase().includes(searchTerm.toLowerCase())
const matchesCategory = categoryFilter === 'all' || supply.category === categoryFilter
const matchesStatus = statusFilter === 'all' || supply.status === statusFilter
return matchesSearch && matchesCategory && matchesStatus
})
const getTotalAmount = () => {
return filteredSupplies.reduce((sum: number, supply: MaterialSupply) => sum + (supply.price * supply.quantity), 0)
return filteredSupplies.reduce((sum: number, supply: MaterialSupply) => sum + supply.price * supply.quantity, 0)
}
const getTotalQuantity = () => {
@ -140,10 +142,7 @@ export function MaterialsSuppliesTab() {
<AlertCircle className="h-12 w-12 text-red-400 mx-auto mb-4" />
<p className="text-white/60">Ошибка загрузки данных</p>
<p className="text-white/40 text-sm mt-2">{error.message}</p>
<Button
onClick={() => refetch()}
className="mt-4 bg-blue-500 hover:bg-blue-600"
>
<Button onClick={() => refetch()} className="mt-4 bg-blue-500 hover:bg-blue-600">
Попробовать снова
</Button>
</div>
@ -156,41 +155,41 @@ export function MaterialsSuppliesTab() {
{/* Статистика с кнопкой заказа */}
<div className="flex items-center justify-between gap-4">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3 flex-1">
<Card className="glass-card p-3 h-[60px]">
<div className="flex items-center space-x-2 h-full">
<div className="p-1.5 bg-purple-500/20 rounded">
<Wrench className="h-3 w-3 text-purple-400" />
<Card className="glass-card p-3 h-[60px]">
<div className="flex items-center space-x-2 h-full">
<div className="p-1.5 bg-purple-500/20 rounded">
<Wrench className="h-3 w-3 text-purple-400" />
</div>
<div>
<p className="text-white/60 text-xs">Поставок</p>
<p className="text-lg font-bold text-white">{filteredSupplies.length}</p>
</div>
</div>
<div>
<p className="text-white/60 text-xs">Поставок</p>
<p className="text-lg font-bold text-white">{filteredSupplies.length}</p>
</div>
</div>
</Card>
</Card>
<Card className="glass-card p-3 h-[60px]">
<div className="flex items-center space-x-2 h-full">
<div className="p-1.5 bg-green-500/20 rounded">
<TrendingUp className="h-3 w-3 text-green-400" />
<Card className="glass-card p-3 h-[60px]">
<div className="flex items-center space-x-2 h-full">
<div className="p-1.5 bg-green-500/20 rounded">
<TrendingUp className="h-3 w-3 text-green-400" />
</div>
<div>
<p className="text-white/60 text-xs">Сумма</p>
<p className="text-lg font-bold text-white">{formatCurrency(getTotalAmount())}</p>
</div>
</div>
<div>
<p className="text-white/60 text-xs">Сумма</p>
<p className="text-lg font-bold text-white">{formatCurrency(getTotalAmount())}</p>
</div>
</div>
</Card>
</Card>
<Card className="glass-card p-3 h-[60px]">
<div className="flex items-center space-x-2 h-full">
<div className="p-1.5 bg-blue-500/20 rounded">
<AlertCircle className="h-3 w-3 text-blue-400" />
<Card className="glass-card p-3 h-[60px]">
<div className="flex items-center space-x-2 h-full">
<div className="p-1.5 bg-blue-500/20 rounded">
<AlertCircle className="h-3 w-3 text-blue-400" />
</div>
<div>
<p className="text-white/60 text-xs">Единиц</p>
<p className="text-lg font-bold text-white">{getTotalQuantity()}</p>
</div>
</div>
<div>
<p className="text-white/60 text-xs">Единиц</p>
<p className="text-lg font-bold text-white">{getTotalQuantity()}</p>
</div>
</div>
</Card>
</Card>
<Card className="glass-card p-3 h-[60px]">
<div className="flex items-center space-x-2 h-full">
@ -204,11 +203,11 @@ export function MaterialsSuppliesTab() {
</div>
</Card>
</div>
{/* Кнопка заказа */}
<Button
<Button
size="sm"
onClick={() => window.location.href = '/fulfillment-supplies/materials/order'}
onClick={() => (window.location.href = '/fulfillment-supplies/materials/order')}
className="bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white text-sm px-6 h-[60px] whitespace-nowrap"
>
<Plus className="h-4 w-4 mr-2" />
@ -227,7 +226,7 @@ export function MaterialsSuppliesTab() {
className="pl-7 h-8 text-sm glass-input text-white placeholder:text-white/40"
/>
</div>
<div className="flex gap-1">
<select
value={categoryFilter}
@ -235,11 +234,13 @@ export function MaterialsSuppliesTab() {
className="px-2 py-1 h-8 bg-white/5 border border-white/20 rounded text-white text-xs focus:outline-none focus:ring-1 focus:ring-purple-500"
>
<option value="all">Все категории</option>
{categories.map(category => (
<option key={category} value={category}>{category}</option>
{categories.map((category) => (
<option key={category} value={category}>
{category}
</option>
))}
</select>
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
@ -277,20 +278,22 @@ export function MaterialsSuppliesTab() {
<td className="p-2">
<div>
<span className="text-white font-medium text-sm">{supply.name}</span>
{supply.description && (
<p className="text-white/60 text-xs mt-0.5">{supply.description}</p>
)}
{supply.description && <p className="text-white/60 text-xs mt-0.5">{supply.description}</p>}
</div>
</td>
<td className="p-2">
<span className="text-white/80 text-sm">{supply.category || 'Не указано'}</span>
</td>
<td className="p-2">
<span className="text-white font-semibold text-sm">{supply.quantity} {supply.unit || 'шт'}</span>
<span className="text-white font-semibold text-sm">
{supply.quantity} {supply.unit || 'шт'}
</span>
</td>
<td className="p-2">
<div className="flex flex-col gap-0.5">
<span className="text-white font-semibold text-sm">{supply.currentStock || 0} {supply.unit || 'шт'}</span>
<span className="text-white font-semibold text-sm">
{supply.currentStock || 0} {supply.unit || 'шт'}
</span>
{getStockStatusBadge(supply.currentStock || 0, supply.minStock || 0)}
</div>
</td>
@ -298,14 +301,16 @@ export function MaterialsSuppliesTab() {
<span className="text-white/80 text-sm">{supply.supplier || 'Не указан'}</span>
</td>
<td className="p-2">
<span className="text-white/80 text-sm">{supply.date ? formatDate(supply.date) : 'Не указано'}</span>
<span className="text-white/80 text-sm">
{supply.date ? formatDate(supply.date) : 'Не указано'}
</span>
</td>
<td className="p-2">
<span className="text-white font-semibold text-sm">{formatCurrency(supply.price * supply.quantity)}</span>
</td>
<td className="p-2">
{getStatusBadge(supply.status || 'planned')}
<span className="text-white font-semibold text-sm">
{formatCurrency(supply.price * supply.quantity)}
</span>
</td>
<td className="p-2">{getStatusBadge(supply.status || 'planned')}</td>
</tr>
))}
</tbody>
@ -327,4 +332,4 @@ export function MaterialsSuppliesTab() {
</Card>
</div>
)
}
}