
КРИТИЧНЫЕ КОМПОНЕНТЫ ОПТИМИЗИРОВАНЫ: • 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>
135 lines
4.8 KiB
TypeScript
135 lines
4.8 KiB
TypeScript
'use client'
|
||
|
||
import { useQuery, useMutation } from '@apollo/client'
|
||
import { Search, ShoppingCart } from 'lucide-react'
|
||
import { useState } from 'react'
|
||
|
||
import { Button } from '@/components/ui/button'
|
||
import { Input } from '@/components/ui/input'
|
||
import { SEND_COUNTERPARTY_REQUEST } from '@/graphql/mutations'
|
||
import { SEARCH_ORGANIZATIONS, GET_INCOMING_REQUESTS, GET_OUTGOING_REQUESTS } from '@/graphql/queries'
|
||
|
||
import { OrganizationCard } from './organization-card'
|
||
|
||
interface Organization {
|
||
id: string
|
||
inn: string
|
||
name?: string
|
||
fullName?: string
|
||
type: 'FULFILLMENT' | 'SELLER' | 'LOGIST' | 'WHOLESALE'
|
||
address?: string
|
||
phones?: Array<{ value: string }>
|
||
emails?: Array<{ value: string }>
|
||
createdAt: string
|
||
users?: Array<{ id: string; avatar?: string }>
|
||
isCounterparty?: boolean
|
||
isCurrentUser?: boolean
|
||
hasOutgoingRequest?: boolean
|
||
hasIncomingRequest?: boolean
|
||
}
|
||
|
||
export function MarketSellers() {
|
||
const [searchTerm, setSearchTerm] = useState('')
|
||
|
||
const { data, loading, refetch } = useQuery(SEARCH_ORGANIZATIONS, {
|
||
variables: { type: 'SELLER', search: searchTerm || null },
|
||
})
|
||
|
||
const [sendRequest, { loading: sendingRequest }] = useMutation(SEND_COUNTERPARTY_REQUEST, {
|
||
refetchQueries: [
|
||
{ query: SEARCH_ORGANIZATIONS, variables: { type: 'SELLER' } },
|
||
{ query: SEARCH_ORGANIZATIONS, variables: { type: 'FULFILLMENT' } },
|
||
{ query: SEARCH_ORGANIZATIONS, variables: { type: 'LOGIST' } },
|
||
{ query: SEARCH_ORGANIZATIONS, variables: { type: 'WHOLESALE' } },
|
||
{ query: GET_OUTGOING_REQUESTS },
|
||
{ query: GET_INCOMING_REQUESTS },
|
||
],
|
||
awaitRefetchQueries: true,
|
||
})
|
||
|
||
const handleSearch = () => {
|
||
refetch({ type: 'SELLER', search: searchTerm || null })
|
||
}
|
||
|
||
const handleSendRequest = async (organizationId: string, message: string) => {
|
||
try {
|
||
await sendRequest({
|
||
variables: {
|
||
organizationId: organizationId,
|
||
message: message || 'Заявка на добавление в контрагенты',
|
||
},
|
||
})
|
||
} catch (error) {
|
||
console.error('Ошибка отправки заявки:', error)
|
||
}
|
||
}
|
||
|
||
const organizations = data?.searchOrganizations || []
|
||
|
||
return (
|
||
<div className="h-full flex flex-col space-y-4 overflow-hidden">
|
||
{/* Поиск */}
|
||
<div className="flex space-x-4 flex-shrink-0">
|
||
<div className="relative flex-1">
|
||
<Search className="absolute left-3 top-3 h-4 w-4 text-white/40" />
|
||
<Input
|
||
placeholder="Поиск селлеров по названию или ИНН..."
|
||
value={searchTerm}
|
||
onChange={(e) => setSearchTerm(e.target.value)}
|
||
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
||
className="pl-10 glass-input text-white placeholder:text-white/40 h-10"
|
||
/>
|
||
</div>
|
||
<Button
|
||
onClick={handleSearch}
|
||
className="bg-green-500/20 hover:bg-green-500/30 text-green-300 border-green-500/30 cursor-pointer"
|
||
>
|
||
<Search className="h-4 w-4 mr-2" />
|
||
Найти
|
||
</Button>
|
||
</div>
|
||
|
||
{/* Заголовок с иконкой */}
|
||
<div className="flex items-center space-x-3 flex-shrink-0 mb-4">
|
||
<ShoppingCart className="h-6 w-6 text-green-400" />
|
||
<div>
|
||
<h3 className="text-lg font-semibold text-white">Селлеры</h3>
|
||
<p className="text-white/60 text-sm">Найдите и добавьте селлеров в контрагенты</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Результаты поиска */}
|
||
<div className="flex-1 overflow-auto">
|
||
{loading ? (
|
||
<div className="flex items-center justify-center p-8">
|
||
<div className="text-white/60">Поиск...</div>
|
||
</div>
|
||
) : organizations.length === 0 ? (
|
||
<div className="glass-card p-8">
|
||
<div className="text-center">
|
||
<ShoppingCart className="h-12 w-12 text-white/20 mx-auto mb-4" />
|
||
<p className="text-white/60">
|
||
{searchTerm ? 'Селлеры не найдены' : 'Введите запрос для поиска селлеров'}
|
||
</p>
|
||
<p className="text-white/40 text-sm mt-2">Попробуйте изменить условия поиска</p>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
{organizations.map((organization: Organization) => (
|
||
<OrganizationCard
|
||
key={organization.id}
|
||
organization={organization}
|
||
onSendRequest={handleSendRequest}
|
||
actionButtonText="Добавить"
|
||
actionButtonColor="orange"
|
||
requestSending={sendingRequest}
|
||
/>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|