"use client" import { useState, useRef, useEffect, useMemo } from 'react' import { useMutation, useQuery } from '@apollo/client' import { GET_MESSAGES, GET_CONVERSATIONS } from '@/graphql/queries' import { SEND_MESSAGE, SEND_VOICE_MESSAGE, SEND_IMAGE_MESSAGE, SEND_FILE_MESSAGE, MARK_MESSAGES_AS_READ } from '@/graphql/mutations' import { Button } from '@/components/ui/button' import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { Badge } from '@/components/ui/badge' import { EmojiPickerComponent } from '@/components/ui/emoji-picker' import { VoiceRecorder } from '@/components/ui/voice-recorder' import { VoicePlayer } from '@/components/ui/voice-player' import { FileUploader } from '@/components/ui/file-uploader' import { ImageMessage } from '@/components/ui/image-message' import { FileMessage } from '@/components/ui/file-message' import { MessengerAttachments } from './messenger-attachments' import { Send, FileText, MessageCircle } from 'lucide-react' import { useAuth } from '@/hooks/useAuth' 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, managerName?: string }> createdAt: string } interface Message { id: string content?: string type?: 'TEXT' | 'VOICE' | 'IMAGE' | 'FILE' | null voiceUrl?: string voiceDuration?: number fileUrl?: string fileName?: string fileSize?: number fileType?: string senderId: string senderOrganization: Organization receiverOrganization: Organization createdAt: string isRead: boolean } interface MessengerChatProps { counterparty: Organization onMessagesRead?: () => void } export function MessengerChatWithAttachments({ counterparty, onMessagesRead }: MessengerChatProps) { const { user } = useAuth() const [message, setMessage] = useState('') const [currentView, setCurrentView] = useState<'chat' | 'attachments'>('chat') const messagesEndRef = useRef(null) const messageInputRef = useRef(null) // Загружаем сообщения с контрагентом const { data: messagesData, loading, refetch } = useQuery(GET_MESSAGES, { variables: { counterpartyId: counterparty.id }, pollInterval: 3000, fetchPolicy: 'cache-and-network', errorPolicy: 'all' }) // Мутация для отметки сообщений как прочитанных const [markMessagesAsReadMutation] = useMutation(MARK_MESSAGES_AS_READ, { onCompleted: () => { setTimeout(() => { onMessagesRead?.() }, 500) }, onError: (error) => { console.error('Ошибка отметки сообщений как прочитанных:', error) } }) const [sendMessageMutation] = useMutation(SEND_MESSAGE, { onCompleted: () => { refetch() } }) const [sendVoiceMessageMutation] = useMutation(SEND_VOICE_MESSAGE, { onCompleted: () => { refetch() } }) const [sendImageMessageMutation] = useMutation(SEND_IMAGE_MESSAGE, { onCompleted: () => { refetch() } }) const [sendFileMessageMutation] = useMutation(SEND_FILE_MESSAGE, { onCompleted: () => { refetch() } }) const messages = useMemo(() => messagesData?.messages || [], [messagesData?.messages]) // Отмечаем сообщения как прочитанные только если есть непрочитанные useEffect(() => { if (messages.length > 0 && user?.organization?.id && counterparty.id) { const hasUnreadMessages = messages.some((msg: Message) => !msg.isRead && msg.senderOrganization?.id === counterparty.id ) if (hasUnreadMessages) { const conversationId = `${user.organization.id}-${counterparty.id}` markMessagesAsReadMutation({ variables: { conversationId }, }) } } }, [messages, counterparty.id, user?.organization?.id, markMessagesAsReadMutation]) const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }) } useEffect(() => { scrollToBottom() }, [messages]) // Автофокус на поле ввода при открытии чата useEffect(() => { if (currentView === 'chat') { const timer = setTimeout(() => { messageInputRef.current?.focus() }, 100) return () => clearTimeout(timer) } }, [counterparty.id, currentView]) const getOrganizationName = (org: Organization) => { return org.name || org.fullName || 'Организация' } const getShortCompanyName = (fullName: string) => { if (!fullName) return 'Полное название не указано' const legalFormReplacements: { [key: string]: string } = { 'Общество с ограниченной ответственностью': 'ООО', 'Открытое акционерное общество': 'ОАО', 'Закрытое акционерное общество': 'ЗАО', 'Публичное акционерное общество': 'ПАО', 'Непубличное акционерное общество': 'НАО', 'Акционерное общество': 'АО', 'Индивидуальный предприниматель': 'ИП', 'Товарищество с ограниченной ответственностью': 'ТОО', 'Частное предприятие': 'ЧП', 'Субъект предпринимательской деятельности': 'СПД' } let result = fullName for (const [fullForm, shortForm] of Object.entries(legalFormReplacements)) { const regex = new RegExp(`^${fullForm}\\s+`, 'i') if (regex.test(result)) { result = result.replace(regex, `${shortForm} `) break } } return result } const getTypeLabel = (type: string) => { switch (type) { case 'FULFILLMENT': return 'Фулфилмент' case 'SELLER': return 'Селлер' case 'LOGIST': return 'Логистика' case 'WHOLESALE': return 'Оптовик' default: return type } } const getTypeColor = (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' } } const getInitials = (org: Organization) => { const name = getOrganizationName(org) return name.charAt(0).toUpperCase() } const handleSendMessage = async () => { if (!message.trim()) return try { await sendMessageMutation({ variables: { receiverOrganizationId: counterparty.id, content: message.trim() } }) setMessage('') } catch (error) { console.error('Ошибка отправки сообщения:', error) } } const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() handleSendMessage() } } const handleEmojiSelect = (emoji: string) => { setMessage(prev => prev + emoji) } const handleSendVoice = async (audioUrl: string, duration: number) => { try { await sendVoiceMessageMutation({ variables: { receiverOrganizationId: counterparty.id, voiceUrl: audioUrl, voiceDuration: duration } }) } catch (error) { console.error('Ошибка отправки голосового сообщения:', error) } } const handleSendImage = async (fileUrl: string, fileName: string, fileSize: number, fileType: string) => { try { await sendImageMessageMutation({ variables: { receiverOrganizationId: counterparty.id, fileUrl, fileName, fileSize, fileType } }) } catch (error) { console.error('Ошибка отправки изображения:', error) } } const handleSendFile = async (fileUrl: string, fileName: string, fileSize: number, fileType: string) => { try { await sendFileMessageMutation({ variables: { receiverOrganizationId: counterparty.id, fileUrl, fileName, fileSize, fileType } }) } catch (error) { console.error('Ошибка отправки файла:', error) } } const formatTime = (dateString: string) => { const date = new Date(dateString) if (isNaN(date.getTime())) { return '00:00' } return date.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' }) } const formatDate = (dateString: string) => { const date = new Date(dateString) if (isNaN(date.getTime())) { return 'Неизвестная дата' } const today = new Date() const yesterday = new Date(today) yesterday.setDate(yesterday.getDate() - 1) if (date.toDateString() === today.toDateString()) { return 'Сегодня' } else if (date.toDateString() === yesterday.toDateString()) { return 'Вчера' } else { return date.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' }) } } return (
{/* Заголовок чата */}
{counterparty.users?.[0]?.avatar ? ( ) : null} {getInitials(counterparty)}

{getOrganizationName(counterparty)}

{getTypeLabel(counterparty.type)}

{getShortCompanyName(counterparty.fullName || '')}

{/* Переключатель между чатом и вложениями */}
{/* Основное содержимое */} {currentView === 'chat' ? ( <> {/* Сообщения */}
{loading ? (
Загрузка сообщений...
) : messages.length === 0 ? (

Пока нет сообщений

Начните беседу с {getOrganizationName(counterparty)}

) : ( messages.map((msg: Message, index: number) => { const isCurrentUser = msg.senderOrganization?.id === user?.organization?.id const showDate = index === 0 || formatDate(messages[index - 1].createdAt) !== formatDate(msg.createdAt) return (
{showDate && (
{formatDate(msg.createdAt)}
)}
{msg.type === 'VOICE' && msg.voiceUrl ? ( ) : msg.type === 'IMAGE' && msg.fileUrl ? ( ) : msg.type === 'FILE' && msg.fileUrl ? ( ) : msg.content ? (

{msg.content}

) : null}

{formatTime(msg.createdAt)}

) }) )}
{/* Поле ввода сообщения */}