Files
sfera-new/src/components/market/market-counterparties.tsx

326 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState } from 'react'
import { useQuery, useMutation } from '@apollo/client'
import { Card } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import {
Users,
Clock,
Send,
CheckCircle,
XCircle,
ArrowUpCircle,
ArrowDownCircle
} from 'lucide-react'
import { OrganizationCard } from './organization-card'
import { GET_MY_COUNTERPARTIES, GET_INCOMING_REQUESTS, GET_OUTGOING_REQUESTS } from '@/graphql/queries'
import { RESPOND_TO_COUNTERPARTY_REQUEST, CANCEL_COUNTERPARTY_REQUEST, REMOVE_COUNTERPARTY } from '@/graphql/mutations'
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
}
export function MarketCounterparties() {
const { data: counterpartiesData, loading: counterpartiesLoading, refetch: refetchCounterparties } = useQuery(GET_MY_COUNTERPARTIES)
const { data: incomingData, loading: incomingLoading, refetch: refetchIncoming } = useQuery(GET_INCOMING_REQUESTS)
const { data: outgoingData, loading: outgoingLoading, refetch: refetchOutgoing } = useQuery(GET_OUTGOING_REQUESTS)
const [respondToRequest] = useMutation(RESPOND_TO_COUNTERPARTY_REQUEST, {
onCompleted: () => {
refetchIncoming()
refetchCounterparties()
}
})
const [cancelRequest] = useMutation(CANCEL_COUNTERPARTY_REQUEST, {
onCompleted: () => {
refetchOutgoing()
}
})
const [removeCounterparty] = useMutation(REMOVE_COUNTERPARTY, {
onCompleted: () => {
refetchCounterparties()
}
})
const handleAcceptRequest = async (requestId: string) => {
try {
await respondToRequest({
variables: { requestId, response: 'ACCEPTED' }
})
} catch (error) {
console.error('Ошибка при принятии заявки:', error)
}
}
const handleRejectRequest = async (requestId: string) => {
try {
await respondToRequest({
variables: { requestId, response: 'REJECTED' }
})
} 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 (error) {
return 'Ошибка даты'
}
}
const counterparties = counterpartiesData?.myCounterparties || []
const incomingRequests = incomingData?.incomingRequests || []
const outgoingRequests = outgoingData?.outgoingRequests || []
return (
<div className="h-full flex flex-col">
<div className="flex items-center space-x-3 mb-6">
<Users className="h-6 w-6 text-blue-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-hidden">
<Tabs defaultValue="counterparties" className="h-full flex flex-col">
<TabsList className="grid w-full grid-cols-3 bg-white/5 border-white/10">
<TabsTrigger value="counterparties" className="data-[state=active]:bg-blue-500/20 data-[state=active]:text-blue-300">
<Users className="h-4 w-4 mr-2" />
Контрагенты ({counterparties.length})
</TabsTrigger>
<TabsTrigger value="incoming" className="data-[state=active]:bg-green-500/20 data-[state=active]:text-green-300">
<ArrowDownCircle className="h-4 w-4 mr-2" />
Входящие ({incomingRequests.length})
</TabsTrigger>
<TabsTrigger value="outgoing" className="data-[state=active]:bg-orange-500/20 data-[state=active]:text-orange-300">
<ArrowUpCircle className="h-4 w-4 mr-2" />
Исходящие ({outgoingRequests.length})
</TabsTrigger>
</TabsList>
<TabsContent value="counterparties" className="flex-1 overflow-auto mt-4">
{counterpartiesLoading ? (
<div className="flex items-center justify-center p-8">
<div className="text-white/60">Загрузка...</div>
</div>
) : counterparties.length === 0 ? (
<div className="glass-card p-8">
<div className="text-center">
<Users className="h-12 w-12 text-white/20 mx-auto mb-4" />
<p className="text-white/60">У вас пока нет контрагентов</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">
{counterparties.map((organization: Organization) => (
<OrganizationCard
key={organization.id}
organization={organization}
onRemove={handleRemoveCounterparty}
showRemoveButton={true}
/>
))}
</div>
)}
</TabsContent>
<TabsContent value="incoming" className="flex-1 overflow-auto mt-4">
{incomingLoading ? (
<div className="flex items-center justify-center p-8">
<div className="text-white/60">Загрузка...</div>
</div>
) : incomingRequests.length === 0 ? (
<div className="glass-card p-8">
<div className="text-center">
<ArrowDownCircle className="h-12 w-12 text-white/20 mx-auto mb-4" />
<p className="text-white/60">Нет входящих заявок</p>
</div>
</div>
) : (
<div className="space-y-4">
{incomingRequests.map((request: CounterpartyRequest) => (
<Card key={request.id} className="glass-card p-4">
<div className="flex items-start justify-between">
<div className="flex items-start space-x-3 flex-1">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-white font-semibold">
{(request.sender.name || request.sender.fullName || 'O').charAt(0).toUpperCase()}
</div>
</div>
<div className="flex-1 min-w-0">
<h4 className="text-white font-medium">
{request.sender.name || request.sender.fullName}
</h4>
<p className="text-white/60 text-sm">ИНН: {request.sender.inn}</p>
{request.message && (
<p className="text-white/80 text-sm mt-2 italic">&quot;{request.message}&quot;</p>
)}
<div className="flex items-center space-x-2 mt-2">
<Clock className="h-3 w-3 text-white/40" />
<span className="text-white/40 text-xs">{formatDate(request.createdAt)}</span>
</div>
</div>
</div>
<div className="flex space-x-2 ml-4">
<Button
size="sm"
onClick={() => handleAcceptRequest(request.id)}
className="bg-green-500/20 hover:bg-green-500/30 text-green-300 border-green-500/30 cursor-pointer"
>
<CheckCircle className="h-4 w-4" />
</Button>
<Button
size="sm"
variant="outline"
onClick={() => handleRejectRequest(request.id)}
className="bg-red-500/20 hover:bg-red-500/30 text-red-300 border-red-500/30 cursor-pointer"
>
<XCircle className="h-4 w-4" />
</Button>
</div>
</div>
</Card>
))}
</div>
)}
</TabsContent>
<TabsContent value="outgoing" className="flex-1 overflow-auto mt-4">
{outgoingLoading ? (
<div className="flex items-center justify-center p-8">
<div className="text-white/60">Загрузка...</div>
</div>
) : outgoingRequests.length === 0 ? (
<div className="glass-card p-8">
<div className="text-center">
<ArrowUpCircle className="h-12 w-12 text-white/20 mx-auto mb-4" />
<p className="text-white/60">Нет исходящих заявок</p>
</div>
</div>
) : (
<div className="space-y-4">
{outgoingRequests.map((request: CounterpartyRequest) => (
<Card key={request.id} className="glass-card p-4">
<div className="flex items-start justify-between">
<div className="flex items-start space-x-3 flex-1">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-white font-semibold">
{(request.receiver.name || request.receiver.fullName || 'O').charAt(0).toUpperCase()}
</div>
</div>
<div className="flex-1 min-w-0">
<h4 className="text-white font-medium">
{request.receiver.name || request.receiver.fullName}
</h4>
<p className="text-white/60 text-sm">ИНН: {request.receiver.inn}</p>
{request.message && (
<p className="text-white/80 text-sm mt-2 italic">&quot;{request.message}&quot;</p>
)}
<div className="flex items-center space-x-4 mt-2">
<div className="flex items-center space-x-2">
<Clock className="h-3 w-3 text-white/40" />
<span className="text-white/40 text-xs">{formatDate(request.createdAt)}</span>
</div>
<Badge className={
request.status === 'PENDING' ? 'bg-yellow-500/20 text-yellow-300 border-yellow-500/30' :
request.status === 'REJECTED' ? 'bg-red-500/20 text-red-300 border-red-500/30' :
'bg-gray-500/20 text-gray-300 border-gray-500/30'
}>
{request.status === 'PENDING' ? 'Ожидает' : request.status === 'REJECTED' ? 'Отклонено' : request.status}
</Badge>
</div>
</div>
</div>
{request.status === 'PENDING' && (
<Button
size="sm"
variant="outline"
onClick={() => handleCancelRequest(request.id)}
className="bg-red-500/20 hover:bg-red-500/30 text-red-300 border-red-500/30 cursor-pointer ml-4"
>
<XCircle className="h-4 w-4" />
</Button>
)}
</div>
</Card>
))}
</div>
)}
</TabsContent>
</Tabs>
</div>
</div>
)
}