diff --git a/src/components/market/market-counterparties.tsx b/src/components/market/market-counterparties.tsx deleted file mode 100644 index 1e508c6..0000000 --- a/src/components/market/market-counterparties.tsx +++ /dev/null @@ -1,835 +0,0 @@ -'use client' - -import { useQuery, useMutation } from '@apollo/client' -import { - Users, - ArrowUpCircle, - ArrowDownCircle, - Search, - Filter, - SortAsc, - SortDesc, - Calendar, - Building, - Phone, - Mail, - MapPin, - X, - Copy, - Gift, - TrendingUp, -} from 'lucide-react' -import React, { useState, useMemo } from 'react' -import { toast } from 'sonner' - -import { Badge } from '@/components/ui/badge' -import { Button } from '@/components/ui/button' -import { Card } from '@/components/ui/card' -import { GlassInput } from '@/components/ui/input' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { RESPOND_TO_COUNTERPARTY_REQUEST, CANCEL_COUNTERPARTY_REQUEST, REMOVE_COUNTERPARTY } from '@/graphql/mutations' -import { - GET_MY_COUNTERPARTIES, - GET_INCOMING_REQUESTS, - GET_OUTGOING_REQUESTS, - SEARCH_ORGANIZATIONS, -} from '@/graphql/queries' -import { GET_MY_PARTNER_LINK } from '@/graphql/referral-queries' - -import { OrganizationAvatar } from './organization-avatar' - -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 }> -} - -interface CounterpartyRequest { - id: string - message?: string - status: 'PENDING' | 'ACCEPTED' | 'REJECTED' | 'CANCELLED' - createdAt: string - sender: Organization - receiver: Organization -} - -type SortField = 'name' | 'date' | 'inn' | 'type' -type SortOrder = 'asc' | 'desc' - -export function MarketCounterparties() { - const [searchQuery, setSearchQuery] = useState('') - const [typeFilter, setTypeFilter] = useState('all') - const [sortField, setSortField] = useState('name') - const [sortOrder, setSortOrder] = useState('asc') - - const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(GET_MY_COUNTERPARTIES) - const { data: incomingData, loading: incomingLoading } = useQuery(GET_INCOMING_REQUESTS) - const { data: outgoingData, loading: outgoingLoading } = useQuery(GET_OUTGOING_REQUESTS) - const { data: partnerLinkData } = useQuery(GET_MY_PARTNER_LINK) - - const [respondToRequest] = useMutation(RESPOND_TO_COUNTERPARTY_REQUEST, { - refetchQueries: [ - { query: GET_INCOMING_REQUESTS }, - { query: GET_MY_COUNTERPARTIES }, - { query: SEARCH_ORGANIZATIONS, variables: { type: 'SELLER' } }, - { query: SEARCH_ORGANIZATIONS, variables: { type: 'FULFILLMENT' } }, - { query: SEARCH_ORGANIZATIONS, variables: { type: 'LOGIST' } }, - { query: SEARCH_ORGANIZATIONS, variables: { type: 'WHOLESALE' } }, - ], - awaitRefetchQueries: true, - }) - - const [cancelRequest] = useMutation(CANCEL_COUNTERPARTY_REQUEST, { - refetchQueries: [ - { query: GET_OUTGOING_REQUESTS }, - { query: SEARCH_ORGANIZATIONS, variables: { type: 'SELLER' } }, - { query: SEARCH_ORGANIZATIONS, variables: { type: 'FULFILLMENT' } }, - { query: SEARCH_ORGANIZATIONS, variables: { type: 'LOGIST' } }, - { query: SEARCH_ORGANIZATIONS, variables: { type: 'WHOLESALE' } }, - ], - awaitRefetchQueries: true, - }) - - const [removeCounterparty] = useMutation(REMOVE_COUNTERPARTY, { - refetchQueries: [ - { query: GET_MY_COUNTERPARTIES }, - { query: SEARCH_ORGANIZATIONS, variables: { type: 'SELLER' } }, - { query: SEARCH_ORGANIZATIONS, variables: { type: 'FULFILLMENT' } }, - { query: SEARCH_ORGANIZATIONS, variables: { type: 'LOGIST' } }, - { query: SEARCH_ORGANIZATIONS, variables: { type: 'WHOLESALE' } }, - ], - awaitRefetchQueries: true, - }) - - // Функция копирования партнерской ссылки - const copyPartnerLink = async () => { - try { - const partnerLink = partnerLinkData?.myPartnerLink - if (!partnerLink) { - toast.error('Партнерская ссылка недоступна') - return - } - await navigator.clipboard.writeText(partnerLink) - toast.success('Партнерская ссылка скопирована!', { - description: 'Поделитесь ей для прямого делового сотрудничества', - }) - } catch { - toast.error('Не удалось скопировать ссылку') - } - } - - // Фильтрация и сортировка контрагентов - const filteredAndSortedCounterparties = useMemo(() => { - const filtered = (counterpartiesData?.myCounterparties || []).filter((org: Organization) => { - const matchesSearch = - !searchQuery || - org.name?.toLowerCase().includes(searchQuery.toLowerCase()) || - org.fullName?.toLowerCase().includes(searchQuery.toLowerCase()) || - org.inn.includes(searchQuery) || - org.address?.toLowerCase().includes(searchQuery.toLowerCase()) - - const matchesType = typeFilter === 'all' || org.type === typeFilter - - return matchesSearch && matchesType - }) - - // Сортировка - filtered.sort((a: Organization, b: Organization) => { - let aValue: string | number - let bValue: string | number - - switch (sortField) { - case 'name': - aValue = (a.name || a.fullName || '').toLowerCase() - bValue = (b.name || b.fullName || '').toLowerCase() - break - case 'date': - aValue = new Date(a.createdAt).getTime() - bValue = new Date(b.createdAt).getTime() - break - case 'inn': - aValue = a.inn - bValue = b.inn - break - case 'type': - aValue = a.type - bValue = b.type - break - default: - return 0 - } - - if (sortOrder === 'asc') { - return aValue < bValue ? -1 : aValue > bValue ? 1 : 0 - } else { - return aValue > bValue ? -1 : aValue < bValue ? 1 : 0 - } - }) - - return filtered - }, [counterpartiesData?.myCounterparties, searchQuery, typeFilter, sortField, sortOrder]) - - const handleAcceptRequest = async (requestId: string) => { - try { - await respondToRequest({ - variables: { - input: { - requestId, - action: 'APPROVE', - }, - }, - }) - } catch (error) { - console.error('Ошибка при принятии заявки:', error) - } - } - - const handleRejectRequest = async (requestId: string) => { - try { - await respondToRequest({ - variables: { - input: { - requestId, - action: 'REJECT', - }, - }, - }) - } catch (error) { - console.error('Ошибка при отклонении заявки:', error) - } - } - - const handleCancelRequest = async (requestId: string) => { - try { - await cancelRequest({ - variables: { requestId }, - }) - } catch (error) { - console.error('Ошибка при отмене заявки:', error) - } - } - - const handleRemoveCounterparty = async (organizationId: string) => { - try { - await removeCounterparty({ - variables: { organizationId }, - }) - } catch (error) { - console.error('Ошибка при удалении контрагента:', error) - } - } - - const formatDate = (dateString: string) => { - if (!dateString) return '' - try { - let date: Date - - // Проверяем, является ли строка числом (Unix timestamp) - if (/^\d+$/.test(dateString)) { - // Если это Unix timestamp в миллисекундах - const timestamp = parseInt(dateString, 10) - date = new Date(timestamp) - } else { - // Обычная строка даты - date = new Date(dateString) - } - - if (isNaN(date.getTime())) { - return 'Неверная дата' - } - - return date.toLocaleDateString('ru-RU', { - year: 'numeric', - month: 'long', - day: 'numeric', - }) - } catch { - return 'Ошибка даты' - } - } - - const clearFilters = () => { - setSearchQuery('') - setTypeFilter('all') - setSortField('name') - setSortOrder('asc') - } - - const hasActiveFilters = searchQuery || typeFilter !== 'all' || sortField !== 'name' || sortOrder !== 'asc' - - const counterparties = counterpartiesData?.myCounterparties || [] - const incomingRequests = incomingData?.incomingRequests || [] - const outgoingRequests = outgoingData?.outgoingRequests || [] - - const getTypeLabel = (type: string) => { - switch (type) { - case 'FULFILLMENT': - return 'Фулфилмент' - case 'SELLER': - return 'Селлер' - case 'LOGIST': - return 'Логистика' - case 'WHOLESALE': - return 'Поставщик' - default: - return type - } - } - - const getTypeBadgeStyles = (type: string) => { - switch (type) { - case 'FULFILLMENT': - return 'bg-blue-500/20 text-blue-300 border-blue-500/30' - case 'SELLER': - return 'bg-green-500/20 text-green-300 border-green-500/30' - case 'LOGIST': - return 'bg-orange-500/20 text-orange-300 border-orange-500/30' - case 'WHOLESALE': - return 'bg-purple-500/20 text-purple-300 border-purple-500/30' - default: - return 'bg-gray-500/20 text-gray-300 border-gray-500/30' - } - } - - return ( -
-
- - - - - Контрагенты ({counterparties.length}) - - 0 ? 'ring-2 ring-green-400/50 animate-pulse' : ''}`} - > - - Входящие ({incomingRequests.length}) - {incomingRequests.length > 0 && ( -
- )} -
- - - Исходящие ({outgoingRequests.length}) - -
- - -
- {/* Компактный блок с партнерской ссылкой */} - -
-
-
- -
-

Партнерская ссылка

-
-
- Прямое деловое сотрудничество с автоматическим добавлением -
-
- -
-
- {partnerLinkData?.myPartnerLink || 'http://localhost:3000/register?partner=LOADING'} -
- -
-
- - {/* Компактная статистика */} -
- -
-
- -
-
-

Партнеров

-

- {counterpartiesLoading ? ( - - ) : ( - counterparties.length - )} -

-
-
-
- - -
-
- -
-
-

Заявок

-

- {incomingLoading ? ( - - ) : ( - incomingRequests.length - )} -

-
-
-
- - -
-
- -
-
-

За месяц

-

- {counterpartiesLoading ? ( - - ) : ( - counterparties.filter((org) => { - const monthAgo = new Date() - monthAgo.setMonth(monthAgo.getMonth() - 1) - return new Date(org.createdAt) > monthAgo - }).length - )} -

-
-
-
- - -
-
- -
-
-

Исходящих

-

- {outgoingLoading ? ( - - ) : ( - outgoingRequests.length - )} -

-
-
-
-
- - {/* Компактные фильтры */} - -
- {/* Поиск */} -
-
- - setSearchQuery(e.target.value)} - className="pl-10 h-9" - /> -
-
- - {/* Фильтры и сортировка */} -
- - - - - - - {hasActiveFilters && ( - - )} -
-
- - {/* Статистика и быстрые фильтры */} -
-
- {filteredAndSortedCounterparties.length} из {counterparties.length} -
- -
- {['FULFILLMENT', 'SELLER', 'LOGIST', 'WHOLESALE'].map((type) => { - const count = counterparties.filter((org: Organization) => org.type === type).length - if (count === 0) return null - - return ( - - ) - })} -
-
-
- - {/* Таблица контрагентов */} - -
-
- {/* Заголовок таблицы */} -
-
-
- - Дата добавления -
-
- - Организация -
-
- Тип -
-
- - Контакты -
-
- - Адрес -
-
- Действия -
-
-
- - {/* Строки таблицы */} - {counterpartiesLoading ? ( -
-
Загрузка...
-
- ) : filteredAndSortedCounterparties.length === 0 ? ( -
- {counterparties.length === 0 ? ( - <> - -

У вас пока нет контрагентов

-

- Перейдите на другие вкладки, чтобы найти партнеров -

- - ) : ( - <> - -

Ничего не найдено

-

- Попробуйте изменить параметры поиска или фильтрации -

- - )} -
- ) : ( - filteredAndSortedCounterparties.map((organization: Organization) => ( -
-
-
-
- - {formatDate(organization.createdAt)} -
-
-
-
- -
-

- {organization.name || organization.fullName} -

-

- - {organization.inn} -

-
-
-
-
- - {getTypeLabel(organization.type)} - -
-
-
- {organization.phones && organization.phones.length > 0 && ( -
- - {organization.phones[0].value} -
- )} - {organization.emails && organization.emails.length > 0 && ( -
- - {organization.emails[0].value} -
- )} - {!organization.phones?.length && !organization.emails?.length && ( - Нет контактов - )} -
-
-
- {organization.address ? ( -

{organization.address}

- ) : ( - Не указан - )} -
-
- -
-
-
- )) - )} -
-
-
-
-
- - - {incomingLoading ? ( -
-
Загрузка...
-
- ) : incomingRequests.length === 0 ? ( -
-
- -

Нет входящих заявок

-
-
- ) : ( -
- {incomingRequests.map((request: CounterpartyRequest) => ( -
-
-
- -
-
-

- {request.sender.name || request.sender.fullName} -

-
- - {getTypeLabel(request.sender.type)} - -
-
- -
-

ИНН: {request.sender.inn}

- {request.sender.address && ( -
- {request.sender.address} -
- )} - {request.message && ( -
-

"{request.message}"

-
- )} -
- Заявка от {formatDate(request.createdAt)} -
-
-
-
- -
- - -
-
-
- ))} -
- )} -
- - - {outgoingLoading ? ( -
-
Загрузка...
-
- ) : outgoingRequests.length === 0 ? ( -
-
- -

Нет исходящих заявок

-
-
- ) : ( -
- {outgoingRequests.map((request: CounterpartyRequest) => ( -
-
-
- -
-
-

- {request.receiver.name || request.receiver.fullName} -

-
- - {getTypeLabel(request.receiver.type)} - - - {request.status === 'PENDING' - ? 'Ожидает ответа' - : request.status === 'REJECTED' - ? 'Отклонено' - : request.status} - -
-
- -
-

ИНН: {request.receiver.inn}

- {request.receiver.address && ( -
- {request.receiver.address} -
- )} - {request.message && ( -
-

"{request.message}"

-
- )} -
- Отправлено {formatDate(request.createdAt)} -
-
-
-
- - {request.status === 'PENDING' && ( - - )} -
-
- ))} -
- )} -
-
-
-
- ) -} diff --git a/src/components/market/market-counterparties/blocks/CounterpartiesListBlock.tsx b/src/components/market/market-counterparties/blocks/CounterpartiesListBlock.tsx index e799136..00c7f43 100644 --- a/src/components/market/market-counterparties/blocks/CounterpartiesListBlock.tsx +++ b/src/components/market/market-counterparties/blocks/CounterpartiesListBlock.tsx @@ -5,7 +5,7 @@ 'use client' -import { Users, ArrowDownCircle, TrendingUp, ArrowUpCircle, Building, Phone, Mail, MapPin, X, Calendar, Gift, Copy, Search, Filter, SortAsc, SortDesc, Send } from 'lucide-react' +import { Users, ArrowDownCircle, TrendingUp, ArrowUpCircle, Building, Phone, Mail, MapPin, X, Calendar, Gift, Copy, Search, SortAsc, SortDesc, Send } from 'lucide-react' import React from 'react' import { Badge } from '@/components/ui/badge' @@ -13,7 +13,6 @@ import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' import { GlassInput } from '@/components/ui/input' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -import { Textarea } from '@/components/ui/textarea' import { OrganizationAvatar } from '../../organization-avatar' import { ORGANIZATION_TYPES, type CounterpartiesListBlockProps } from '../types' diff --git a/src/components/market/market-counterparties/blocks/PartnerLinksBlock.tsx b/src/components/market/market-counterparties/blocks/PartnerLinksBlock.tsx index 6c4b6f5..2e18054 100644 --- a/src/components/market/market-counterparties/blocks/PartnerLinksBlock.tsx +++ b/src/components/market/market-counterparties/blocks/PartnerLinksBlock.tsx @@ -5,13 +5,11 @@ 'use client' -import { Copy, Gift, TrendingUp, ExternalLink } from 'lucide-react' +import { Copy, Gift, ExternalLink } from 'lucide-react' import React from 'react' -import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' -import { GlassInput } from '@/components/ui/input' import type { PartnerLinksBlockProps } from '../types' diff --git a/src/components/market/market-counterparties/hooks/useCounterpartyData.ts b/src/components/market/market-counterparties/hooks/useCounterpartyData.ts index ebae5dd..4f1328a 100644 --- a/src/components/market/market-counterparties/hooks/useCounterpartyData.ts +++ b/src/components/market/market-counterparties/hooks/useCounterpartyData.ts @@ -18,7 +18,6 @@ import type { UseCounterpartyDataReturn, Organization, CounterpartyRequest, - PartnerLink, OrganizationType, } from '../types' diff --git a/src/components/market/market-counterparties/index.tsx b/src/components/market/market-counterparties/index.tsx index aa276af..cc8bd06 100644 --- a/src/components/market/market-counterparties/index.tsx +++ b/src/components/market/market-counterparties/index.tsx @@ -20,7 +20,6 @@ import { useCounterpartyFilters } from './hooks/useCounterpartyFilters' import { CounterpartiesListBlock } from './blocks/CounterpartiesListBlock' import { IncomingRequestsBlock } from './blocks/IncomingRequestsBlock' import { OutgoingRequestsBlock } from './blocks/OutgoingRequestsBlock' - // Types import type { Organization } from './types' @@ -37,16 +36,16 @@ export default function MarketCounterparties({ className }: MarketCounterparties counterparties, incomingRequests, outgoingRequests, - searchResults, + _searchResults, partnerLink, counterpartiesLoading, incomingLoading, outgoingLoading, - searchLoading, - partnerLinkLoading, + _searchLoading, + _partnerLinkLoading, _error, refetchAll, - searchOrganizations, + _searchOrganizations, } = useCounterpartyData() // Action Hooks @@ -62,12 +61,12 @@ export default function MarketCounterparties({ className }: MarketCounterparties // Filter Hooks const { _searchQuery, - typeFilter, - debouncedSearch, - handleSearchChange, - handleTypeFilterChange, + _typeFilter, + _debouncedSearch, + _handleSearchChange, + _handleTypeFilterChange, } = useCounterpartyFilters({ - onSearch: searchOrganizations, + onSearch: _searchOrganizations, }) // Unified loading states for blocks @@ -75,7 +74,7 @@ export default function MarketCounterparties({ className }: MarketCounterparties counterparties: counterpartiesLoading, incoming: incomingLoading, outgoing: outgoingLoading, - search: searchLoading, + search: _searchLoading, } // Обработчики действий с callback для обновления данных @@ -84,7 +83,7 @@ export default function MarketCounterparties({ className }: MarketCounterparties await removeCounterparty(id) await refetchAll() toast.success('Контрагент удален') - } catch (_error) { + } catch { toast.error('Ошибка удаления контрагента') } } @@ -94,7 +93,7 @@ export default function MarketCounterparties({ className }: MarketCounterparties await acceptRequest(id) await refetchAll() toast.success('Заявка принята') - } catch (_error) { + } catch { toast.error('Ошибка принятия заявки') } } @@ -104,7 +103,7 @@ export default function MarketCounterparties({ className }: MarketCounterparties await rejectRequest(id) await refetchAll() toast.success('Заявка отклонена') - } catch (_error) { + } catch { toast.error('Ошибка отклонения заявки') } } @@ -114,17 +113,17 @@ export default function MarketCounterparties({ className }: MarketCounterparties await cancelRequest(id) await refetchAll() toast.success('Заявка отменена') - } catch (_error) { + } catch { toast.error('Ошибка отмены заявки') } } - const handleSendRequest = async (organizationId: string, message?: string) => { + const _handleSendRequest = async (organizationId: string, message?: string) => { try { await sendRequest(organizationId, message) await refetchAll() toast.success('Заявка отправлена') - } catch (_error) { + } catch { toast.error('Ошибка отправки заявки') } } @@ -134,12 +133,12 @@ export default function MarketCounterparties({ className }: MarketCounterparties toast.success('Ссылка скопирована в буфер обмена') } - const handleGenerateLink = async () => { + const _handleGenerateLink = async () => { try { // TODO: Реализовать создание партнерской ссылки await refetchAll() toast.success('Партнерская ссылка создана') - } catch (_error) { + } catch { toast.error('Ошибка создания ссылки') } }