diff --git a/src/components/dashboard/sidebar.tsx b/src/components/dashboard/sidebar.tsx
index 60ed55f..69001b9 100644
--- a/src/components/dashboard/sidebar.tsx
+++ b/src/components/dashboard/sidebar.tsx
@@ -30,9 +30,10 @@ export function Sidebar() {
// Загружаем список чатов для подсчета непрочитанных сообщений
const { data: conversationsData } = useQuery(GET_CONVERSATIONS, {
- pollInterval: 10000, // Обновляем каждые 10 секунд
- fetchPolicy: 'cache-and-network',
+ pollInterval: 60000, // Обновляем каждую минуту в сайдбаре - этого достаточно
+ fetchPolicy: 'cache-first',
errorPolicy: 'ignore', // Игнорируем ошибки чтобы не ломать сайдбар
+ notifyOnNetworkStatusChange: false, // Плавные обновления без мерцания
})
const conversations = conversationsData?.conversations || []
diff --git a/src/components/messenger/messenger-attachments.tsx b/src/components/messenger/messenger-attachments.tsx
new file mode 100644
index 0000000..6f81efb
--- /dev/null
+++ b/src/components/messenger/messenger-attachments.tsx
@@ -0,0 +1,315 @@
+"use client"
+
+import { useState } from 'react'
+import { useQuery } from '@apollo/client'
+import { GET_MESSAGES } from '@/graphql/queries'
+import { useAuth } from '@/hooks/useAuth'
+import { Card } from '@/components/ui/card'
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
+import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
+import { Badge } from '@/components/ui/badge'
+import {
+ FileText,
+ Image,
+ Music,
+ Video,
+ Download,
+ Calendar,
+ User
+} from 'lucide-react'
+
+interface Organization {
+ id: string
+ inn: string
+ name?: string
+ fullName?: string
+ type: 'FULFILLMENT' | 'SELLER' | 'LOGIST' | 'WHOLESALE'
+ users?: Array<{ id: string, avatar?: string, managerName?: string }>
+}
+
+interface Message {
+ id: string
+ content?: string
+ type?: 'TEXT' | 'VOICE' | 'IMAGE' | 'FILE'
+ voiceUrl?: string
+ voiceDuration?: number
+ fileUrl?: string
+ fileName?: string
+ fileSize?: number
+ fileType?: string
+ senderId: string
+ senderOrganization: Organization
+ receiverOrganization: Organization
+ createdAt: string
+ isRead: boolean
+}
+
+interface MessengerAttachmentsProps {
+ counterparty: Organization
+}
+
+export function MessengerAttachments({ counterparty }: MessengerAttachmentsProps) {
+ const { user } = useAuth()
+ const [activeTab, setActiveTab] = useState('all')
+
+ // Загружаем все сообщения для получения вложений
+ const { data: messagesData, loading } = useQuery(GET_MESSAGES, {
+ variables: { counterpartyId: counterparty.id, limit: 1000 }, // Увеличиваем лимит для получения всех файлов
+ fetchPolicy: 'cache-first',
+ })
+
+ const messages: Message[] = messagesData?.messages || []
+
+ // Фильтруем только сообщения с вложениями
+ const attachmentMessages = messages.filter(msg =>
+ msg.type && ['VOICE', 'IMAGE', 'FILE'].includes(msg.type) && msg.fileUrl
+ )
+
+ // Группируем по типам
+ const imageMessages = attachmentMessages.filter(msg => msg.type === 'IMAGE')
+ const voiceMessages = attachmentMessages.filter(msg => msg.type === 'VOICE')
+ const fileMessages = attachmentMessages.filter(msg => msg.type === 'FILE')
+
+ 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 formatFileSize = (bytes?: number) => {
+ if (!bytes) return '0 B'
+ const k = 1024
+ const sizes = ['B', 'KB', 'MB', 'GB']
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+ }
+
+ const formatDate = (dateString: string) => {
+ const date = new Date(dateString)
+ return date.toLocaleDateString('ru-RU', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric'
+ })
+ }
+
+ const formatTime = (dateString: string) => {
+ const date = new Date(dateString)
+ return date.toLocaleTimeString('ru-RU', {
+ hour: '2-digit',
+ minute: '2-digit'
+ })
+ }
+
+ const handleDownload = (fileUrl: string, fileName: string) => {
+ const link = document.createElement('a')
+ link.href = fileUrl
+ link.download = fileName
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+ }
+
+ const renderFileIcon = (fileType?: string) => {
+ if (!fileType) return
+
+ if (fileType.startsWith('image/')) return
+ if (fileType.startsWith('audio/')) return
+ if (fileType.startsWith('video/')) return
+ return
+ }
+
+ const renderAttachmentCard = (message: Message) => {
+ const isCurrentUser = message.senderOrganization?.id === user?.organization?.id
+ const senderOrg = isCurrentUser ? user?.organization : message.senderOrganization
+
+ return (
+
+
+ {/* Аватар отправителя */}
+
+ {(senderOrg as Organization)?.users?.[0]?.avatar ? (
+
+ ) : null}
+
+ {getInitials(senderOrg as Organization)}
+
+
+
+
+
+
+
+ {isCurrentUser ? 'Вы' : getOrganizationName(senderOrg as Organization)}
+
+
+ {isCurrentUser ? 'Исходящий' : 'Входящий'}
+
+
+
+
+ {formatDate(message.createdAt)}
+ {formatTime(message.createdAt)}
+
+
+
+ {/* Содержимое вложения */}
+
+
+
+ {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')}
+ )}
+
+
+
+
+
+
+
+
+
+ )
+ }
+
+ if (loading) {
+ return (
+
+ )
+ }
+
+ return (
+
+
+
+
+
Вложения
+
+ {attachmentMessages.length} файлов от {getOrganizationName(counterparty)}
+
+
+
+
+
+
+
+
+
+ Все ({attachmentMessages.length})
+
+
+
+ Фото ({imageMessages.length})
+
+
+
+ Аудио ({voiceMessages.length})
+
+
+
+ Файлы ({fileMessages.length})
+
+
+
+
+
+ {attachmentMessages.length === 0 ? (
+
+ ) : (
+
+ {attachmentMessages.map(renderAttachmentCard)}
+
+ )}
+
+
+
+ {imageMessages.length === 0 ? (
+
+ ) : (
+
+ {imageMessages.map(renderAttachmentCard)}
+
+ )}
+
+
+
+ {voiceMessages.length === 0 ? (
+
+ ) : (
+
+ {voiceMessages.map(renderAttachmentCard)}
+
+ )}
+
+
+
+ {fileMessages.length === 0 ? (
+
+ ) : (
+
+ {fileMessages.map(renderAttachmentCard)}
+
+ )}
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/messenger/messenger-chat-with-attachments.tsx b/src/components/messenger/messenger-chat-with-attachments.tsx
new file mode 100644
index 0000000..32c9d46
--- /dev/null
+++ b/src/components/messenger/messenger-chat-with-attachments.tsx
@@ -0,0 +1,518 @@
+"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 0c2460e..09fecc8 100644
--- a/src/components/messenger/messenger-chat.tsx
+++ b/src/components/messenger/messenger-chat.tsx
@@ -2,7 +2,7 @@
import { useState, useRef, useEffect, useMemo } from 'react'
import { useMutation, useQuery } from '@apollo/client'
-import { GET_MESSAGES } from '@/graphql/queries'
+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'
@@ -14,7 +14,8 @@ 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 { Send, MoreVertical } from 'lucide-react'
+import { MessengerAttachments } from './messenger-attachments'
+import { Send, MoreVertical, FileText, MessageCircle } from 'lucide-react'
import { useAuth } from '@/hooks/useAuth'
interface Organization {
@@ -46,12 +47,15 @@ interface Message {
interface MessengerChatProps {
counterparty: Organization
+ onMessagesRead?: () => void
}
-export function MessengerChat({ counterparty }: MessengerChatProps) {
+export function MessengerChat({ 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, {
@@ -63,6 +67,12 @@ export function MessengerChat({ counterparty }: MessengerChatProps) {
// Мутация для отметки сообщений как прочитанных
const [markMessagesAsReadMutation] = useMutation(MARK_MESSAGES_AS_READ, {
+ onCompleted: () => {
+ // Деликатное обновление с задержкой чтобы было незаметно
+ setTimeout(() => {
+ onMessagesRead?.()
+ }, 500) // Полсекунды задержки для плавности
+ },
onError: (error) => {
console.error('Ошибка отметки сообщений как прочитанных:', error)
}
@@ -106,22 +116,12 @@ export function MessengerChat({ counterparty }: MessengerChatProps) {
const messages = useMemo(() => messagesData?.messages || [], [messagesData?.messages])
- // Автоматически отмечаем сообщения как прочитанные при открытии чата
- useEffect(() => {
- if (user?.organization?.id && counterparty.id) {
- const conversationId = `${user.organization.id}-${counterparty.id}`
- markMessagesAsReadMutation({
- variables: { conversationId },
- })
- }
- }, [counterparty.id, user?.organization?.id, markMessagesAsReadMutation])
-
- // Отмечаем сообщения как прочитанные при получении новых сообщений
+ // Отмечаем сообщения как прочитанные только если есть непрочитанные
useEffect(() => {
if (messages.length > 0 && user?.organization?.id && counterparty.id) {
const hasUnreadMessages = messages.some((msg: Message) =>
!msg.isRead &&
- msg.senderOrganization?.id !== user.organization?.id
+ msg.senderOrganization?.id === counterparty.id
)
if (hasUnreadMessages) {
@@ -143,6 +143,15 @@ export function MessengerChat({ counterparty }: MessengerChatProps) {
scrollToBottom()
}, [messages])
+ // Автофокус на поле ввода при открытии чата
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ messageInputRef.current?.focus()
+ }, 100) // Небольшая задержка для корректного фокуса
+
+ return () => clearTimeout(timer)
+ }, [counterparty.id])
+
const getOrganizationName = (org: Organization) => {
return org.name || org.fullName || 'Организация'
}
@@ -472,6 +481,7 @@ export function MessengerChat({ counterparty }: MessengerChatProps) {