/** * InboxPanel — extracted from AIChatPanel.renderInboxPanel() * Displays inbox conversations with search, agent filter, and date range filtering. */ import React from 'react'; import { X, Search, Loader2, User, Users, ChevronRight, Inbox, Filter, Link2, Pencil, } from 'lucide-react'; import { cn } from '@/shared/utils/cn'; import { useAuthStore } from '@/features/auth/store/authStore'; import type { AIAgent } from '../types'; type PanelTab = 'none' | 'contacts' | 'ai-agents' | 'tasks' | 'settings' | 'inbox'; interface InboxConversation { id: number; title: string | null; type: string; agent_id?: number; agent_name?: string | null; agent_icon?: string | null; unread_count: number; updated_at: string; participants: Array<{ user_id: number; name: string; email?: string; avatar_url?: string; }>; sub_agents?: Array<{ row_id: number; name: string; icon?: string | null; response_mode?: string }>; bound_table_id?: number | null; bound_row_id?: number | null; bound_row_title?: string | null; bound_table_name?: string | null; bound_table_icon?: string | null; } interface ChatPartner { type: string; id: number; name: string; avatarUrl?: string; email?: string; icon?: string | null; participants?: Array<{ id: number; name: string; type: 'user' | 'agent' }>; } interface ChatParticipant { id: number; name: string; type: 'user' | 'agent'; email?: string; avatar?: string; } interface BoundRow { table_id: number; row_id: number; table_name?: string; table_icon?: string; row_title?: string; } export interface InboxPanelProps { inboxConversations: InboxConversation[]; safeAgents: AIAgent[]; totalUnreadCount: number; showInboxFilters: boolean; setShowInboxFilters: React.Dispatch>; hasActiveInboxFilters: boolean; inboxSearch: string; setInboxSearch: (value: string) => void; inboxAgentFilter: string; setInboxAgentFilter: (value: string) => void; inboxAgentOptions: Array<{ id: number; name: string; icon?: string | null }>; inboxDateFrom: string; setInboxDateFrom: (value: string) => void; inboxDateTo: string; setInboxDateTo: (value: string) => void; isLoadingInbox: boolean; inboxRenamingId: number | null; setInboxRenamingId: (id: number | null) => void; inboxRenamingTitle: string; setInboxRenamingTitle: (title: string) => void; inboxRenameInputRef: React.RefObject; renameConversation: (id: number, title: string) => void; userConversationId: number | null; currentConversationId: number | null; chatPartner: ChatPartner | null; setChatPartner: (partner: ChatPartner | null) => void; setChatParticipants: (participants: ChatParticipant[]) => void; setChatMode: (mode: 'ai' | 'people') => void; selectAgent: (agent: AIAgent) => void; selectConversation: (id: number) => void; setUserConversationId: (id: number | null) => void; setBoundRows: (rows: BoundRow[]) => void; setShowBoundRowsBar: (show: boolean) => void; markAsReadMutation: { mutate: (id: number) => void }; setActivePanel: (panel: PanelTab) => void; } export function InboxPanel({ inboxConversations, safeAgents, totalUnreadCount, showInboxFilters, setShowInboxFilters, hasActiveInboxFilters, inboxSearch, setInboxSearch, inboxAgentFilter, setInboxAgentFilter, inboxAgentOptions, inboxDateFrom, setInboxDateFrom, inboxDateTo, setInboxDateTo, isLoadingInbox, inboxRenamingId, setInboxRenamingId, inboxRenamingTitle, setInboxRenamingTitle, inboxRenameInputRef, renameConversation, userConversationId, currentConversationId, chatPartner, setChatPartner, setChatParticipants, setChatMode, selectAgent, selectConversation, setUserConversationId, setBoundRows, setShowBoundRowsBar, markAsReadMutation, setActivePanel, }: InboxPanelProps) { return (
{/* Header */}
Все чаты
{totalUnreadCount > 0 && ( {totalUnreadCount} )}
{/* Search bar — always visible */}
setInboxSearch(e.target.value)} placeholder="Поиск чатов..." className="w-full pl-8 pr-3 py-1.5 text-xs rounded-lg bg-[var(--bg-tertiary)] border border-[var(--border-primary)] text-[var(--text-primary)] placeholder:text-[var(--text-tertiary)] focus:outline-none focus:ring-1 focus:ring-[var(--color-primary-500)]/30" /> {inboxSearch && ( )}
{/* Ticket #81444: Filter controls — collapsible */} {showInboxFilters && (
{/* Agent filter */}
{/* Date range */}
setInboxDateFrom(e.target.value)} className="w-full px-2 py-1 text-xs rounded-lg bg-[var(--bg-tertiary)] border border-[var(--border-primary)] text-[var(--text-primary)] focus:outline-none focus:ring-1 focus:ring-[var(--color-primary-500)]/30" />
setInboxDateTo(e.target.value)} className="w-full px-2 py-1 text-xs rounded-lg bg-[var(--bg-tertiary)] border border-[var(--border-primary)] text-[var(--text-primary)] focus:outline-none focus:ring-1 focus:ring-[var(--color-primary-500)]/30" />
{/* Clear filters */} {hasActiveInboxFilters && ( )}
)} {/* Conversation list */}
{isLoadingInbox ? (
) : inboxConversations.length === 0 ? (
{hasActiveInboxFilters ? 'Ничего не найдено' : 'Нет активных бесед'}
) : ( inboxConversations.map(conv => { const currentUserId = useAuthStore.getState().user?.id; const otherParticipants = conv.participants?.filter(p => currentUserId && p.user_id !== Number(currentUserId)) || []; // For agent conversations with no other participants, show agent name from response or local cache const agentForConv = conv.agent_id ? safeAgents.find(a => a.id === conv.agent_id) : null; const agentName = conv.agent_name || agentForConv?.name || null; const agentIcon = conv.agent_icon || agentForConv?.icon || null; const displayName = conv.title || otherParticipants.map(p => p.name).join(', ') || agentName || 'Беседа'; return (
)}
{agentName ? `🤖 ${agentName}` : conv.type === 'direct' ? 'Личный чат' : `${conv.participants?.length || 0} участников`} {' • '} {new Date(conv.updated_at).toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' })}
{/* Ticket #81438: Show enriched bound row label */} {conv.bound_row_id && (
{conv.bound_table_icon || ''}{' '} {conv.bound_table_name ? `${conv.bound_table_name}: ` : ''} {conv.bound_row_title || `#${conv.bound_row_id}`}
)}
{conv.unread_count > 0 && ( )} ); }) )} ); }