import { useState } from 'react'; import { Card, List, Avatar, Typography, Input, Button, Tag, Empty, Spin, Space, Badge, Dropdown, Select, Tooltip, Modal, message, } from 'antd'; import { SendOutlined, UserOutlined, MoreOutlined, SwapOutlined, RobotOutlined, CheckCircleOutlined, MessageOutlined, FileTextOutlined, } from '@ant-design/icons'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { apiClient } from '../api/client'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; import 'dayjs/locale/es'; dayjs.extend(relativeTime); dayjs.locale('es'); const { Text } = Typography; interface Contact { id: string; phone_number: string; name: string | null; } interface Message { id: string; direction: 'inbound' | 'outbound'; type: string; content: string | null; media_url: string | null; created_at: string; is_internal_note: boolean; sent_by: string | null; } interface Conversation { id: string; contact: Contact; status: string; priority: string; assigned_to: string | null; queue_id: string | null; last_message_at: string | null; messages?: Message[]; } interface Queue { id: string; name: string; } interface Agent { id: string; name: string; status: string; } interface QuickReply { shortcut: string; content: string; } const STATUS_COLORS: Record = { bot: 'blue', waiting: 'orange', active: 'green', resolved: 'default', }; const PRIORITY_COLORS: Record = { low: 'default', normal: 'blue', high: 'orange', urgent: 'red', }; function getMessageBackground(msg: Message): string { if (msg.is_internal_note) { return '#fff7e6'; } if (msg.direction === 'outbound') { return '#25D366'; } return '#f0f0f0'; } function getMessageColor(msg: Message): string { if (msg.is_internal_note) { return '#d46b08'; } if (msg.direction === 'outbound') { return 'white'; } return 'inherit'; } function getMessageBorder(msg: Message): string { if (msg.is_internal_note) { return '1px dashed #ffc069'; } return 'none'; } export default function Inbox(): JSX.Element { const [selectedId, setSelectedId] = useState(null); const [messageText, setMessageText] = useState(''); const [statusFilter, setStatusFilter] = useState(null); const [isNoteMode, setIsNoteMode] = useState(false); const [transferModalOpen, setTransferModalOpen] = useState(false); const [transferType, setTransferType] = useState<'queue' | 'agent'>('queue'); const queryClient = useQueryClient(); const { data: conversations, isLoading } = useQuery({ queryKey: ['conversations', statusFilter], queryFn: async () => { const url = statusFilter ? `/api/whatsapp/conversations?status=${statusFilter}` : '/api/whatsapp/conversations'; return apiClient.get(url); }, refetchInterval: 3000, }); const { data: selectedConversation } = useQuery({ queryKey: ['conversation', selectedId], queryFn: async () => { if (!selectedId) return null; return apiClient.get(`/api/whatsapp/conversations/${selectedId}`); }, enabled: !!selectedId, refetchInterval: 2000, }); const { data: queues } = useQuery({ queryKey: ['queues'], queryFn: () => apiClient.get('/api/queues'), }); const { data: agents } = useQuery({ queryKey: ['agents'], queryFn: () => apiClient.get('/api/auth/agents'), }); const { data: quickReplies } = useQuery({ queryKey: ['quick-replies'], queryFn: () => apiClient.get('/api/queues/quick-replies'), }); const sendMutation = useMutation({ mutationFn: async (data: { conversationId: string; content: string }) => { await apiClient.post(`/api/whatsapp/conversations/${data.conversationId}/messages`, { type: 'text', content: data.content, }); }, onSuccess: () => { setMessageText(''); queryClient.invalidateQueries({ queryKey: ['conversation', selectedId] }); queryClient.invalidateQueries({ queryKey: ['conversations'] }); }, }); const noteMutation = useMutation({ mutationFn: async (data: { conversationId: string; content: string }) => { await apiClient.post(`/api/whatsapp/conversations/${data.conversationId}/notes`, { content: data.content, }); }, onSuccess: () => { setMessageText(''); setIsNoteMode(false); message.success('Nota agregada'); queryClient.invalidateQueries({ queryKey: ['conversation', selectedId] }); }, }); const transferQueueMutation = useMutation({ mutationFn: async (data: { conversationId: string; queueId: string }) => { await apiClient.post(`/api/whatsapp/conversations/${data.conversationId}/transfer-to-queue`, { queue_id: data.queueId, }); }, onSuccess: () => { message.success('Transferido a cola'); setTransferModalOpen(false); queryClient.invalidateQueries({ queryKey: ['conversations'] }); }, }); const transferAgentMutation = useMutation({ mutationFn: async (data: { conversationId: string; agentId: string }) => { await apiClient.post(`/api/whatsapp/conversations/${data.conversationId}/transfer-to-agent`, { agent_id: data.agentId, }); }, onSuccess: () => { message.success('Transferido a agente'); setTransferModalOpen(false); queryClient.invalidateQueries({ queryKey: ['conversations'] }); }, }); const transferBotMutation = useMutation({ mutationFn: async (conversationId: string) => { await apiClient.post(`/api/whatsapp/conversations/${conversationId}/transfer-to-bot`, {}); }, onSuccess: () => { message.success('Transferido a bot'); queryClient.invalidateQueries({ queryKey: ['conversations'] }); }, }); const resolveMutation = useMutation({ mutationFn: async (conversationId: string) => { await apiClient.post(`/api/whatsapp/conversations/${conversationId}/resolve`, {}); }, onSuccess: () => { message.success('Conversacion resuelta'); queryClient.invalidateQueries({ queryKey: ['conversations'] }); }, }); function handleSend(): void { if (!messageText.trim() || !selectedId) return; if (isNoteMode) { noteMutation.mutate({ conversationId: selectedId, content: messageText }); } else { sendMutation.mutate({ conversationId: selectedId, content: messageText }); } } function handleTransferQueue(): void { setTransferType('queue'); setTransferModalOpen(true); } function handleTransferAgent(): void { setTransferType('agent'); setTransferModalOpen(true); } function handleTransferBot(): void { if (selectedId) { transferBotMutation.mutate(selectedId); } } function handleResolve(): void { if (selectedId) { resolveMutation.mutate(selectedId); } } function handleQueueSelect(value: string): void { if (selectedId) { transferQueueMutation.mutate({ conversationId: selectedId, queueId: value }); } } function handleAgentSelect(value: string): void { if (selectedId) { transferAgentMutation.mutate({ conversationId: selectedId, agentId: value }); } } const actionMenuItems = [ { key: 'transfer-queue', icon: , label: 'Transferir a cola', onClick: handleTransferQueue, }, { key: 'transfer-agent', icon: , label: 'Transferir a agente', onClick: handleTransferAgent, }, { key: 'transfer-bot', icon: , label: 'Transferir a bot', onClick: handleTransferBot, }, { type: 'divider' as const }, { key: 'resolve', icon: , label: 'Resolver conversacion', onClick: handleResolve, }, ]; function renderConversationList(): JSX.Element { if (isLoading) { return (
); } if (!conversations || conversations.length === 0) { return ; } return ( ( setSelectedId(conv.id)} style={{ padding: '12px 16px', cursor: 'pointer', background: selectedId === conv.id ? '#f5f5f5' : 'transparent', }} > } /> } title={ {conv.contact.name || conv.contact.phone_number} {conv.status} {conv.priority !== 'normal' && ( {conv.priority} )} } description={ {conv.last_message_at ? dayjs(conv.last_message_at).fromNow() : 'Sin mensajes'} } /> )} /> ); } function renderMessageContent(msg: Message): JSX.Element { // Render media based on type if (msg.media_url) { const mediaType = msg.type?.toUpperCase(); if (mediaType === 'IMAGE') { return ( <> Imagen window.open(msg.media_url!, '_blank')} /> {msg.content && msg.content !== '[Image]' && ( {msg.content} )} ); } if (mediaType === 'AUDIO') { return ( ); } if (mediaType === 'VIDEO') { return ( ); } if (mediaType === 'DOCUMENT') { return ( 📄 {msg.content || 'Documento'} ); } } // Default text content return {msg.content}; } function renderMessage(msg: Message): JSX.Element { return (
{msg.is_internal_note && ( Nota interna )} {renderMessageContent(msg)}
{dayjs(msg.created_at).format('HH:mm')}
); } function renderQuickReplies(): JSX.Element | null { if (!quickReplies || quickReplies.length === 0) { return null; } return (
{quickReplies.slice(0, 5).map((qr) => ( setMessageText(qr.content)}> {qr.shortcut} ))}
); } function renderMessageInput(): JSX.Element { const inputStyle = isNoteMode ? { borderColor: '#ffc069' } : {}; const buttonStyle = isNoteMode ? { background: '#d46b08', borderColor: '#d46b08' } : { background: '#25D366', borderColor: '#25D366' }; return (
setMessageText(e.target.value)} onPressEnter={handleSend} style={inputStyle} />
); } function renderChatContent(): JSX.Element { if (!selectedConversation) { return (
); } return ( <>
{selectedConversation.contact.name || selectedConversation.contact.phone_number}
{selectedConversation.contact.phone_number}
{selectedConversation.status}
{selectedConversation.messages?.map(renderMessage)}
{renderQuickReplies()} {renderMessageInput()} ); } function renderTransferModal(): JSX.Element { const title = transferType === 'queue' ? 'Transferir a Cola' : 'Transferir a Agente'; return ( setTransferModalOpen(false)} footer={null} > {transferType === 'queue' ? ( ) : ( )} ); } return (
Conversaciones
} > {renderConversationList()} {renderChatContent()} {renderTransferModal()} ); }