Оптимизирована производительность 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,14 +1,14 @@
|
||||
"use client"
|
||||
'use client'
|
||||
|
||||
import { useQuery, gql } from '@apollo/client'
|
||||
import { Search, Phone, Building, Calendar, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'
|
||||
import React, { useState, useEffect, useMemo, useCallback } from 'react'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useQuery } from '@apollo/client'
|
||||
import { gql } from '@apollo/client'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Search, Phone, Building, Calendar, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
|
||||
// GraphQL запрос для получения пользователей
|
||||
const ALL_USERS = gql`
|
||||
@ -55,7 +55,7 @@ interface User {
|
||||
}
|
||||
}
|
||||
|
||||
export function UsersSection() {
|
||||
const UsersSection = React.memo(() => {
|
||||
const [search, setSearch] = useState('')
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
@ -65,9 +65,9 @@ export function UsersSection() {
|
||||
variables: {
|
||||
search: searchQuery || undefined,
|
||||
limit,
|
||||
offset: (currentPage - 1) * limit
|
||||
offset: (currentPage - 1) * limit,
|
||||
},
|
||||
fetchPolicy: 'cache-and-network'
|
||||
fetchPolicy: 'cache-and-network',
|
||||
})
|
||||
|
||||
// Обновляем запрос при изменении поиска с дебаунсом
|
||||
@ -80,22 +80,22 @@ export function UsersSection() {
|
||||
return () => clearTimeout(timer)
|
||||
}, [search])
|
||||
|
||||
const users = data?.allUsers?.users || []
|
||||
const total = data?.allUsers?.total || 0
|
||||
const hasMore = data?.allUsers?.hasMore || false
|
||||
const totalPages = Math.ceil(total / limit)
|
||||
const users = useMemo(() => data?.allUsers?.users || [], [data?.allUsers?.users])
|
||||
const total = useMemo(() => data?.allUsers?.total || 0, [data?.allUsers?.total])
|
||||
const _hasMore = useMemo(() => data?.allUsers?.hasMore || false, [data?.allUsers?.hasMore])
|
||||
const totalPages = useMemo(() => Math.ceil(total / limit), [total, limit])
|
||||
|
||||
const getOrganizationTypeBadge = (type: string) => {
|
||||
const getOrganizationTypeBadge = useCallback((type: string) => {
|
||||
const typeMap = {
|
||||
FULFILLMENT: { label: 'Фулфилмент', variant: 'default' as const },
|
||||
SELLER: { label: 'Селлер', variant: 'secondary' as const },
|
||||
LOGIST: { label: 'Логистика', variant: 'outline' as const },
|
||||
WHOLESALE: { label: 'Поставщик', variant: 'destructive' as const }
|
||||
WHOLESALE: { label: 'Поставщик', variant: 'destructive' as const },
|
||||
}
|
||||
return typeMap[type as keyof typeof typeMap] || { label: type, variant: 'outline' as const }
|
||||
}
|
||||
}, [])
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const formatDate = useCallback((dateString: string) => {
|
||||
try {
|
||||
const date = new Date(dateString)
|
||||
if (isNaN(date.getTime())) {
|
||||
@ -106,44 +106,50 @@ export function UsersSection() {
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
minute: '2-digit',
|
||||
})
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return 'Неизвестно'
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const getInitials = (name?: string, phone?: string) => {
|
||||
const getInitials = useCallback((name?: string, phone?: string) => {
|
||||
if (name) {
|
||||
return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2)
|
||||
return name
|
||||
.split(' ')
|
||||
.map((n) => n[0])
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
.slice(0, 2)
|
||||
}
|
||||
if (phone) {
|
||||
return phone.slice(-2)
|
||||
}
|
||||
return 'У'
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handlePrevPage = () => {
|
||||
const handlePrevPage = useCallback(() => {
|
||||
if (currentPage > 1) {
|
||||
setCurrentPage(currentPage - 1)
|
||||
}
|
||||
}
|
||||
}, [currentPage])
|
||||
|
||||
const handleNextPage = () => {
|
||||
const handleNextPage = useCallback(() => {
|
||||
if (currentPage < totalPages) {
|
||||
setCurrentPage(currentPage + 1)
|
||||
}
|
||||
}
|
||||
}, [currentPage, totalPages])
|
||||
|
||||
const handleSearchChange = useCallback((value: string) => {
|
||||
setSearch(value)
|
||||
}, [])
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="p-8">
|
||||
<div className="glass-card p-6 text-center">
|
||||
<p className="text-red-400">Ошибка загрузки пользователей: {error.message}</p>
|
||||
<Button
|
||||
onClick={() => refetch()}
|
||||
className="mt-4 glass-button"
|
||||
>
|
||||
<Button onClick={() => refetch()} className="mt-4 glass-button">
|
||||
Попробовать снова
|
||||
</Button>
|
||||
</div>
|
||||
@ -166,7 +172,7 @@ export function UsersSection() {
|
||||
type="text"
|
||||
placeholder="Поиск по телефону, имени, ИНН организации..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
onChange={(e) => handleSearchChange(e.target.value)}
|
||||
className="pl-10 glass-input text-white placeholder:text-white/50"
|
||||
/>
|
||||
</div>
|
||||
@ -184,9 +190,7 @@ export function UsersSection() {
|
||||
</div>
|
||||
) : users.length === 0 ? (
|
||||
<div className="glass-card p-8 text-center">
|
||||
<p className="text-white/70">
|
||||
{searchQuery ? 'Пользователи не найдены' : 'Пользователи отсутствуют'}
|
||||
</p>
|
||||
<p className="text-white/70">{searchQuery ? 'Пользователи не найдены' : 'Пользователи отсутствуют'}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
@ -206,9 +210,7 @@ export function UsersSection() {
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div>
|
||||
<h3 className="text-white font-medium">
|
||||
{user.managerName || 'Без имени'}
|
||||
</h3>
|
||||
<h3 className="text-white font-medium">{user.managerName || 'Без имени'}</h3>
|
||||
<div className="flex items-center text-white/70 text-sm mt-1">
|
||||
<Phone className="h-3 w-3 mr-1" />
|
||||
{user.phone}
|
||||
@ -236,13 +238,9 @@ export function UsersSection() {
|
||||
{getOrganizationTypeBadge(user.organization.type).label}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-white/70 text-sm">
|
||||
ИНН: {user.organization.inn}
|
||||
</p>
|
||||
<p className="text-white/70 text-sm">ИНН: {user.organization.inn}</p>
|
||||
{user.organization.status && (
|
||||
<p className="text-white/60 text-xs">
|
||||
Статус: {user.organization.status}
|
||||
</p>
|
||||
<p className="text-white/60 text-xs">Статус: {user.organization.status}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-right">
|
||||
@ -291,4 +289,8 @@ export function UsersSection() {
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
UsersSection.displayName = 'UsersSection'
|
||||
|
||||
export { UsersSection }
|
||||
|
Reference in New Issue
Block a user