diff --git a/src/components/messenger/messenger-attachments.tsx b/src/components/messenger/messenger-attachments.tsx
index f5e8515..b841ef9 100644
--- a/src/components/messenger/messenger-attachments.tsx
+++ b/src/components/messenger/messenger-attachments.tsx
@@ -1,6 +1,6 @@
"use client"
-import { useState } from 'react'
+import { useState, useEffect } from 'react'
import { useQuery } from '@apollo/client'
import { GET_MESSAGES } from '@/graphql/queries'
import { useAuth } from '@/hooks/useAuth'
@@ -9,6 +9,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Badge } from '@/components/ui/badge'
import { ImageLightbox } from '@/components/ui/image-lightbox'
+import { VoicePlayer } from '@/components/ui/voice-player'
import {
FileText,
Image,
@@ -16,7 +17,8 @@ import {
Video,
Download,
Calendar,
- User
+ User,
+ RefreshCw
} from 'lucide-react'
interface Organization {
@@ -47,24 +49,33 @@ interface Message {
interface MessengerAttachmentsProps {
counterparty: Organization
+ onViewChange?: () => void
}
-export function MessengerAttachments({ counterparty }: MessengerAttachmentsProps) {
+export function MessengerAttachments({ counterparty, onViewChange }: MessengerAttachmentsProps) {
const { user } = useAuth()
const [activeTab, setActiveTab] = useState('all')
const [lightboxImage, setLightboxImage] = useState<{ url: string; fileName: string; fileSize?: number } | null>(null)
// Загружаем все сообщения для получения вложений
- const { data: messagesData, loading } = useQuery(GET_MESSAGES, {
- variables: { counterpartyId: counterparty.id, limit: 1000 }, // Увеличиваем лимит для получения всех файлов
- fetchPolicy: 'cache-first',
+ const { data: messagesData, loading, refetch } = useQuery(GET_MESSAGES, {
+ variables: { counterpartyId: counterparty.id, limit: 1000 },
+ fetchPolicy: 'cache-and-network',
+ pollInterval: 5000, // Обновляем каждые 5 секунд
+ notifyOnNetworkStatusChange: false, // Не показываем loading при обновлениях
})
+ // Обновляем данные при открытии вкладки вложений
+ useEffect(() => {
+ onViewChange?.()
+ }, [onViewChange])
+
const messages: Message[] = messagesData?.messages || []
// Фильтруем только сообщения с вложениями
const attachmentMessages = messages.filter(msg =>
- msg.type && ['VOICE', 'IMAGE', 'FILE'].includes(msg.type) && msg.fileUrl
+ msg.type && ['VOICE', 'IMAGE', 'FILE'].includes(msg.type) &&
+ (msg.fileUrl || (msg.type === 'VOICE' && msg.voiceUrl))
)
// Группируем по типам
@@ -76,10 +87,6 @@ export function MessengerAttachments({ counterparty }: MessengerAttachmentsProps
return org.name || org.fullName || 'Организация'
}
- const getManagerName = (org: Organization) => {
- return org.users?.[0]?.managerName || 'Управляющий'
- }
-
const getInitials = (org: Organization) => {
const name = getOrganizationName(org)
return name.charAt(0).toUpperCase()
@@ -128,12 +135,12 @@ export function MessengerAttachments({ counterparty }: MessengerAttachmentsProps
}
const renderFileIcon = (fileType?: string) => {
- if (!fileType) return
+ if (!fileType) return
- if (fileType.startsWith('image/')) return
- if (fileType.startsWith('audio/')) return
- if (fileType.startsWith('video/')) return
- return
+ if (fileType.startsWith('image/')) return
+ if (fileType.startsWith('audio/')) return
+ if (fileType.startsWith('video/')) return
+ return
}
const renderAttachmentCard = (message: Message) => {
@@ -145,13 +152,13 @@ export function MessengerAttachments({ counterparty }: MessengerAttachmentsProps
{/* Аватар отправителя */}
- {(senderOrg as Organization)?.users?.[0]?.avatar ? (
-
- ) : null}
+ {(senderOrg as Organization)?.users?.[0]?.avatar ? (
+
+ ) : null}
{getInitials(senderOrg as Organization)}
@@ -175,61 +182,65 @@ export function MessengerAttachments({ counterparty }: MessengerAttachmentsProps
{/* Содержимое вложения */}
-
-
- {/* Превью изображения или иконка */}
- {message.type === 'IMAGE' && message.fileUrl ? (
-
handleImageClick(message.fileUrl!, message.fileName, message.fileSize)}
- >
-

-
-
+ {message.type === 'VOICE' && message.voiceUrl ? (
+ /* Голосовой плеер */
+
+
+
+ ) : (
+ /* Обычные файлы и изображения */
+
+
+ {/* Превью изображения или иконка */}
+ {message.type === 'IMAGE' && message.fileUrl ? (
+
handleImageClick(message.fileUrl!, message.fileName, message.fileSize)}
+ >
+

+
+
+
+
+ ) : (
+
+ {renderFileIcon(message.fileType)}
+
+ )}
+
+
+
+ {message.fileName ||
+ (message.type === 'IMAGE' ? 'Изображение' : 'Файл')}
+
+
+ {message.fileSize && (
+ {formatFileSize(message.fileSize)}
+ )}
+ {message.type === 'IMAGE' && (
+ • Нажмите для просмотра
+ )}
- ) : (
-
- {message.type === 'VOICE' ? (
-
- ) : (
- renderFileIcon(message.fileType)
- )}
-
- )}
-
-
-
- {message.fileName ||
- (message.type === 'VOICE' ? 'Голосовое сообщение' :
- message.type === 'IMAGE' ? 'Изображение' : 'Файл')}
-
-
- {message.fileSize && (
- {formatFileSize(message.fileSize)}
- )}
- {message.voiceDuration && (
- {Math.floor(message.voiceDuration / 60)}:{(message.voiceDuration % 60).toString().padStart(2, '0')}
- )}
- {message.type === 'IMAGE' && (
- • Нажмите для просмотра
- )}
-
+
+
-
-
-
+ )}
@@ -244,131 +255,141 @@ export function MessengerAttachments({ counterparty }: MessengerAttachmentsProps
)
}
- return (
-
-
-
-
-
Вложения
-
- {attachmentMessages.length} файлов от {getOrganizationName(counterparty)}
+ if (attachmentMessages.length === 0) {
+ return (
+
+
+
+
Нет вложений
+
+ Файлы, изображения и голосовые сообщения будут отображаться здесь
+ )
+ }
-
-
-
-
-
- Все ({attachmentMessages.length})
+ return (
+
+
+ {/* Кнопка обновления */}
+
+
Вложения
+
+
+
+
+
+ Все ({attachmentMessages.length})
-
-
- Фото ({imageMessages.length})
+
+ Фото ({imageMessages.length})
-
-
- Аудио ({voiceMessages.length})
+
+ Аудио ({voiceMessages.length})
-
-
- Файлы ({fileMessages.length})
+
+ Файлы ({fileMessages.length})
-
-
- {attachmentMessages.length === 0 ? (
-
-
+
+ {attachmentMessages.length === 0 ? (
+
+
- ) : (
-
- {attachmentMessages.map(renderAttachmentCard)}
-
- )}
-
+
+ ) : (
+
+ {attachmentMessages.map(renderAttachmentCard)}
+
+ )}
+
-
- {imageMessages.length === 0 ? (
-
-
+
+ {imageMessages.length === 0 ? (
+
+
- ) : (
-
- {/* Сетка изображений для быстрого просмотра */}
-
- {imageMessages.map((msg) => (
-
handleImageClick(msg.fileUrl!, msg.fileName, msg.fileSize)}
- >
-

-
-
-
-
-
- {formatDate(msg.createdAt)}
-
+
+ ) : (
+
+ {/* Сетка изображений для быстрого просмотра */}
+
+ {imageMessages.map((msg) => (
+
handleImageClick(msg.fileUrl!, msg.fileName, msg.fileSize)}
+ >
+

+
+
+
+
+
+ {formatDate(msg.createdAt)}
- ))}
-
-
- {/* Детальный список карточек */}
-
-
Детальная информация
- {imageMessages.map(renderAttachmentCard)}
-
+
+ ))}
- )}
-
+
+ {/* Детальный список карточек */}
+
+
Детальная информация
+ {imageMessages.map(renderAttachmentCard)}
+
+
+ )}
+
-
- {voiceMessages.length === 0 ? (
-
-
+
+ {voiceMessages.length === 0 ? (
+
+
- ) : (
-
- {voiceMessages.map(renderAttachmentCard)}
-
- )}
-
+
+ ) : (
+
+ {voiceMessages.map(renderAttachmentCard)}
+
+ )}
+
-
- {fileMessages.length === 0 ? (
-
-
+
+ {fileMessages.length === 0 ? (
+
+
- ) : (
-
- {fileMessages.map(renderAttachmentCard)}
-
- )}
-
-
+
+ ) : (
+
+ {fileMessages.map(renderAttachmentCard)}
+
+ )}
+
-
+
{/* Lightbox для просмотра изображений */}
{lightboxImage && (
diff --git a/src/components/messenger/messenger-chat-with-attachments.tsx b/src/components/messenger/messenger-chat-with-attachments.tsx
deleted file mode 100644
index 32c9d46..0000000
--- a/src/components/messenger/messenger-chat-with-attachments.tsx
+++ /dev/null
@@ -1,518 +0,0 @@
-"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 ? (
-
- ) : null}
-
-
- {formatTime(msg.createdAt)}
-
-
-
-
-
- )
- })
- )}
-
-
-
- {/* Поле ввода сообщения */}
-
-
-
-
-
-
-
-
- console.error('Ошибка загрузки файла:', error)}
- accept="image/*"
- icon="image"
- />
- console.error('Ошибка загрузки файла:', error)}
- accept="*"
- icon="file"
- />
-
-
-
-
- >
- ) : (
- /* Вложения */
-
-
-
- )}
-
- )
-}
\ No newline at end of file
diff --git a/src/components/messenger/messenger-chat.tsx b/src/components/messenger/messenger-chat.tsx
index 09fecc8..741165d 100644
--- a/src/components/messenger/messenger-chat.tsx
+++ b/src/components/messenger/messenger-chat.tsx
@@ -5,7 +5,6 @@ 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'
@@ -15,7 +14,7 @@ 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, MoreVertical, FileText, MessageCircle } from 'lucide-react'
+import { Send, FileText, MessageCircle } from 'lucide-react'
import { useAuth } from '@/hooks/useAuth'
interface Organization {
@@ -25,7 +24,10 @@ interface Organization {
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 {
@@ -60,18 +62,17 @@ export function MessengerChat({ counterparty, onMessagesRead }: MessengerChatPro
// Загружаем сообщения с контрагентом
const { data: messagesData, loading, refetch } = useQuery(GET_MESSAGES, {
variables: { counterpartyId: counterparty.id },
- pollInterval: 3000, // Обновляем каждые 3 секунды для получения новых сообщений
- fetchPolicy: 'cache-and-network', // Всегда загружаем свежие данные
- errorPolicy: 'all' // Показываем данные даже при ошибках
+ pollInterval: 3000,
+ fetchPolicy: 'cache-and-network',
+ errorPolicy: 'all'
})
// Мутация для отметки сообщений как прочитанных
const [markMessagesAsReadMutation] = useMutation(MARK_MESSAGES_AS_READ, {
onCompleted: () => {
- // Деликатное обновление с задержкой чтобы было незаметно
setTimeout(() => {
onMessagesRead?.()
- }, 500) // Полсекунды задержки для плавности
+ }, 500)
},
onError: (error) => {
console.error('Ошибка отметки сообщений как прочитанных:', error)
@@ -80,38 +81,35 @@ export function MessengerChat({ counterparty, onMessagesRead }: MessengerChatPro
const [sendMessageMutation] = useMutation(SEND_MESSAGE, {
onCompleted: () => {
- refetch() // Перезагружаем сообщения после отправки
- },
- onError: (error) => {
- console.error('Ошибка отправки сообщения:', error)
+ refetch()
}
})
const [sendVoiceMessageMutation] = useMutation(SEND_VOICE_MESSAGE, {
onCompleted: () => {
- refetch() // Перезагружаем сообщения после отправки
+ refetch()
},
- onError: (error) => {
- console.error('Ошибка отправки голосового сообщения:', error)
- }
+ refetchQueries: [
+ { query: GET_MESSAGES, variables: { counterpartyId: counterparty.id, limit: 1000 } }
+ ]
})
const [sendImageMessageMutation] = useMutation(SEND_IMAGE_MESSAGE, {
onCompleted: () => {
refetch()
},
- onError: (error) => {
- console.error('Ошибка отправки изображения:', error)
- }
+ refetchQueries: [
+ { query: GET_MESSAGES, variables: { counterpartyId: counterparty.id, limit: 1000 } }
+ ]
})
const [sendFileMessageMutation] = useMutation(SEND_FILE_MESSAGE, {
onCompleted: () => {
refetch()
},
- onError: (error) => {
- console.error('Ошибка отправки файла:', error)
- }
+ refetchQueries: [
+ { query: GET_MESSAGES, variables: { counterpartyId: counterparty.id, limit: 1000 } }
+ ]
})
const messages = useMemo(() => messagesData?.messages || [], [messagesData?.messages])
@@ -133,8 +131,6 @@ export function MessengerChat({ counterparty, onMessagesRead }: MessengerChatPro
}
}, [messages, counterparty.id, user?.organization?.id, markMessagesAsReadMutation])
-
-
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
}
@@ -145,30 +141,21 @@ export function MessengerChat({ counterparty, onMessagesRead }: MessengerChatPro
// Автофокус на поле ввода при открытии чата
useEffect(() => {
- const timer = setTimeout(() => {
- messageInputRef.current?.focus()
- }, 100) // Небольшая задержка для корректного фокуса
-
- return () => clearTimeout(timer)
- }, [counterparty.id])
+ 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 getManagerName = (org: Organization) => {
- return org.users?.[0]?.managerName || 'Управляющий'
- }
-
- const getInitials = (org: Organization) => {
- const name = getOrganizationName(org)
- return name.charAt(0).toUpperCase()
- }
-
const getShortCompanyName = (fullName: string) => {
if (!fullName) return 'Полное название не указано'
- // Словарь для замены полных форм на сокращенные
const legalFormReplacements: { [key: string]: string } = {
'Общество с ограниченной ответственностью': 'ООО',
'Открытое акционерное общество': 'ОАО',
@@ -184,7 +171,6 @@ export function MessengerChat({ counterparty, onMessagesRead }: MessengerChatPro
let result = fullName
- // Заменяем полные формы на сокращенные
for (const [fullForm, shortForm] of Object.entries(legalFormReplacements)) {
const regex = new RegExp(`^${fullForm}\\s+`, 'i')
if (regex.test(result)) {
@@ -226,6 +212,11 @@ export function MessengerChat({ counterparty, onMessagesRead }: MessengerChatPro
}
}
+ const getInitials = (org: Organization) => {
+ const name = getOrganizationName(org)
+ return name.charAt(0).toUpperCase()
+ }
+
const handleSendMessage = async () => {
if (!message.trim()) return
@@ -322,8 +313,9 @@ export function MessengerChat({ counterparty, onMessagesRead }: MessengerChatPro
return 'Вчера'
} else {
return date.toLocaleDateString('ru-RU', {
- day: 'numeric',
- month: 'long'
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric'
})
}
}
@@ -345,177 +337,182 @@ export function MessengerChat({ counterparty, onMessagesRead }: MessengerChatPro
{getInitials(counterparty)}
-
-
-
- {getOrganizationName(counterparty)}
-
+
+ {getOrganizationName(counterparty)}
+
+
{getTypeLabel(counterparty.type)}
-
-
- {getShortCompanyName(counterparty.fullName || '')}
-
-
-
-
-
-
-
- {/* Область сообщений */}
-
- {loading ? (
-
-
Загрузка сообщений...
-
- ) : messages.length === 0 ? (
-
-
-
-
-
-
Начните беседу
-
- Отправьте первое сообщение {getOrganizationName(counterparty)}
+
+ {getShortCompanyName(counterparty.fullName || '')}
- ) : (
- 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)}
-
-
+ {/* Основное содержимое */}
+ {currentView === 'chat' ? (
+ <>
+ {/* Сообщения */}
+
+ {loading ? (
+
+
Загрузка сообщений...
+
+ ) : messages.length === 0 ? (
+
+
+
+
- )}
-
-
-
- {/* Имя отправителя */}
- {!isCurrentUser && (
-
-
- {msg.senderOrganization?.users?.[0]?.avatar ? (
-
- ) : null}
-
- {getInitials(msg.senderOrganization)}
-
-
-
- {getManagerName(msg.senderOrganization)}
-
-
- {getTypeLabel(msg.senderOrganization.type)}
-
+
Пока нет сообщений
+
+ Начните беседу с {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 ? (
-
- ) : null}
-
-
+
- {formatTime(msg.createdAt)}
-
+
+ {msg.type === 'VOICE' && msg.voiceUrl ? (
+
+ ) : msg.type === 'IMAGE' && msg.fileUrl ? (
+
+ ) : msg.type === 'FILE' && msg.fileUrl ? (
+
+ ) : msg.content ? (
+
+ ) : null}
+
+
+ {formatTime(msg.createdAt)}
+
+
+
-
-
- )
- })
- )}
-
-
+ )
+ })
+ )}
+
+
- {/* Поле ввода сообщения */}
-
-
-
-
-
-
-
-
-
+ {/* Поле ввода сообщения */}
+
+ >
+ ) : (
+ /* Вложения */
+
+ {
+ // Принудительно обновляем сообщения при переходе на вложения
+ refetch()
+ }}
+ />
-
+ )}
)
}
\ No newline at end of file
diff --git a/src/components/messenger/messenger-dashboard.tsx b/src/components/messenger/messenger-dashboard.tsx
index 64596b0..892296c 100644
--- a/src/components/messenger/messenger-dashboard.tsx
+++ b/src/components/messenger/messenger-dashboard.tsx
@@ -7,7 +7,7 @@ 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 { MessengerChat } from './messenger-chat'
import { MessengerEmptyState } from './messenger-empty-state'
import { GET_CONVERSATIONS, GET_MY_COUNTERPARTIES } from '@/graphql/queries'
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'
@@ -122,7 +122,7 @@ export function MessengerDashboard() {
{selectedCounterparty && selectedCounterpartyData ? (
-