150 lines
6.7 KiB
TypeScript
150 lines
6.7 KiB
TypeScript
"use client"
|
||
|
||
import React, { useState } from 'react'
|
||
import { useQuery } from '@apollo/client'
|
||
import { Card } from '@/components/ui/card'
|
||
import { Button } from '@/components/ui/button'
|
||
import { Sidebar } from '@/components/dashboard/sidebar'
|
||
import { useSidebar } from '@/hooks/useSidebar'
|
||
import { MessengerConversations } from './messenger-conversations'
|
||
import { MessengerChatWithAttachments } from './messenger-chat-with-attachments'
|
||
import { MessengerEmptyState } from './messenger-empty-state'
|
||
import { GET_CONVERSATIONS, GET_MY_COUNTERPARTIES } from '@/graphql/queries'
|
||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'
|
||
import { MessageCircle } from 'lucide-react'
|
||
|
||
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 }>
|
||
users?: Array<{ id: string, avatar?: string }>
|
||
createdAt: string
|
||
}
|
||
|
||
interface Conversation {
|
||
id: string
|
||
counterparty: Organization
|
||
lastMessage?: {
|
||
id: string
|
||
content?: string
|
||
type?: string
|
||
senderId: string
|
||
isRead: boolean
|
||
createdAt: string
|
||
}
|
||
unreadCount: number
|
||
updatedAt: string
|
||
}
|
||
|
||
export function MessengerDashboard() {
|
||
const { getSidebarMargin } = useSidebar()
|
||
const [selectedCounterparty, setSelectedCounterparty] = useState<string | null>(null)
|
||
|
||
// Загружаем список чатов (conversations) для отображения непрочитанных сообщений
|
||
const { data: conversationsData, loading: conversationsLoading, refetch: refetchConversations } = useQuery(GET_CONVERSATIONS, {
|
||
pollInterval: 30000, // Обновляем каждые 30 секунд - реже, но достаточно
|
||
fetchPolicy: 'cache-first', // Приоритет кэшу для стабильности
|
||
notifyOnNetworkStatusChange: false, // Не показываем загрузку при фоновых обновлениях
|
||
})
|
||
|
||
// Также загружаем полный список контрагентов на случай, если с кем-то еще не общались
|
||
const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(GET_MY_COUNTERPARTIES)
|
||
|
||
const conversations: Conversation[] = conversationsData?.conversations || []
|
||
const counterparties = counterpartiesData?.myCounterparties || []
|
||
|
||
const handleSelectCounterparty = (counterpartyId: string) => {
|
||
setSelectedCounterparty(counterpartyId)
|
||
}
|
||
|
||
// Найти данные выбранного контрагента (сначала в чатах, потом в общем списке)
|
||
const selectedCounterpartyData = conversations.find((conv: Conversation) => conv.counterparty.id === selectedCounterparty)?.counterparty ||
|
||
counterparties.find((cp: Organization) => cp.id === selectedCounterparty)
|
||
|
||
// Если нет контрагентов, показываем заглушку
|
||
if (!counterpartiesLoading && !conversationsLoading && counterparties.length === 0) {
|
||
return (
|
||
<div className="h-screen flex overflow-hidden">
|
||
<Sidebar />
|
||
<main className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}>
|
||
<div className="h-full w-full flex flex-col">
|
||
<div className="flex-1 overflow-hidden">
|
||
<Card className="glass-card h-full overflow-hidden p-6">
|
||
<MessengerEmptyState />
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<div className="h-screen flex overflow-hidden">
|
||
<Sidebar />
|
||
<main className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}>
|
||
<div className="h-full w-full flex flex-col">
|
||
{/* Основной контент */}
|
||
<div className="flex-1 overflow-hidden">
|
||
<PanelGroup direction="horizontal" className="h-full">
|
||
{/* Левая панель - список контрагентов */}
|
||
<Panel
|
||
defaultSize={30}
|
||
minSize={15}
|
||
maxSize={50}
|
||
className="pr-2"
|
||
>
|
||
<Card className="glass-card h-full overflow-hidden p-4">
|
||
<MessengerConversations
|
||
conversations={conversations}
|
||
counterparties={counterparties}
|
||
loading={counterpartiesLoading || conversationsLoading}
|
||
selectedCounterparty={selectedCounterparty}
|
||
onSelectCounterparty={handleSelectCounterparty}
|
||
onRefresh={refetchConversations}
|
||
compact={false}
|
||
/>
|
||
</Card>
|
||
</Panel>
|
||
|
||
{/* Разделитель для изменения размера */}
|
||
<PanelResizeHandle className="w-2 hover:bg-white/10 transition-colors relative group cursor-col-resize">
|
||
<div className="absolute inset-y-0 left-1/2 transform -translate-x-1/2 w-1 bg-white/10 group-hover:bg-white/20 transition-colors" />
|
||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-1 h-8 bg-white/30 rounded-full opacity-0 group-hover:opacity-100 transition-opacity" />
|
||
</PanelResizeHandle>
|
||
|
||
{/* Правая панель - чат */}
|
||
<Panel defaultSize={70} className="pl-2">
|
||
<Card className="glass-card h-full overflow-hidden">
|
||
{selectedCounterparty && selectedCounterpartyData ? (
|
||
<MessengerChatWithAttachments
|
||
counterparty={selectedCounterpartyData}
|
||
onMessagesRead={refetchConversations}
|
||
/>
|
||
) : (
|
||
<div className="flex items-center justify-center h-full">
|
||
<div className="text-center">
|
||
<div className="w-16 h-16 bg-white/10 rounded-full flex items-center justify-center mx-auto mb-4">
|
||
<MessageCircle className="h-8 w-8 text-white/40" />
|
||
</div>
|
||
<p className="text-white/60 text-lg mb-2">Выберите контрагента</p>
|
||
<p className="text-white/40 text-sm">
|
||
Начните беседу с одним из ваших контрагентов
|
||
</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</Card>
|
||
</Panel>
|
||
</PanelGroup>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
)
|
||
}
|