import { useEffect, useMemo, useRef, useState } from 'react'; import { useWhatsappSocket } from '../../../shared/hooks/useWhatsappSocket'; import { attendantsByArea, chatContacts, transferAreas, } from '../services/chatMocks'; const API_BASE_URL = 'http://localhost:3001'; function buildInitialMessages() { return chatContacts.reduce((acc, contact) => { acc[contact.id] = contact.messages; return acc; }, {}); } function getSerializedId(value) { if (!value) return ''; if (typeof value === 'string') return value; return value._serialized || `${value.user || ''}@${value.server || 'c.us'}`; } function formatTime(timestamp) { if (!timestamp) return ''; const date = new Date(timestamp * 1000); return date.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }); } function getContactName(chat) { const serializedId = getSerializedId(chat.id); return chat.name || chat.pushname || serializedId.split('@')[0] || 'Contato'; } function getPreviewFromMessage(message) { if (message?.body) return message.body; if (message?.text) return message.text; if (message?.hasMedia || message?.media) return '[Midia]'; return ''; } function normalizeChat(chat) { const id = getSerializedId(chat.id); return { id, name: getContactName(chat), channel: 'WhatsApp', status: 'online', area: chat.assignment?.area_id ? String(chat.assignment.area_id) : 'Suporte', lastSeen: chat.timestamp ? `Visto as ${formatTime(chat.timestamp)}` : 'Online agora', preview: chat.preview || chat.lastMessage?.body || '', time: formatTime(chat.timestamp) || 'Agora', unread: chat.unreadCount || 0, assignment: chat.assignment || null, }; } function normalizeMessage(message) { const id = getSerializedId(message.id) || message.id || `msg-${Date.now()}`; const sender = message.sender || (message.fromMe ? 'agent' : 'customer'); return { id, chatId: message.from || message.to || message.chatId, sender, text: message.body ?? message.text ?? '', timestamp: message.timestamp, hasMedia: Boolean(message.hasMedia || message.media), media: message.media || null, mediaLoading: false, mediaError: null, }; } function fileToBase64(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { const result = String(reader.result || ''); resolve(result.includes(',') ? result.split(',')[1] : result); }; reader.onerror = reject; reader.readAsDataURL(file); }); } function buildFallbackContacts() { return chatContacts.map((contact) => ({ ...contact })); } export function useChat() { const { incomingMessage, clearIncomingMessage } = useWhatsappSocket(); const [contacts, setContacts] = useState(buildFallbackContacts); const [activeContactId, setActiveContactId] = useState(chatContacts[0].id); const [messagesByContact, setMessagesByContact] = useState(buildInitialMessages); const [draft, setDraft] = useState(''); const [attachedFile, setAttachedFile] = useState(null); const [selectedArea, setSelectedArea] = useState(chatContacts[0].area); const [isTransferOpen, setIsTransferOpen] = useState(false); const [transferArea, setTransferArea] = useState('Suporte'); const [transferAttendant, setTransferAttendant] = useState(attendantsByArea.Suporte[0]); const [transferNote, setTransferNote] = useState(''); const [isReplying] = useState(false); const [isLoadingChats, setIsLoadingChats] = useState(false); const [isLoadingMessages, setIsLoadingMessages] = useState(false); const [apiError, setApiError] = useState(null); const activeContactRef = useRef(activeContactId); const activeContact = useMemo( () => contacts.find((contact) => contact.id === activeContactId) || contacts[0], [contacts, activeContactId], ); const messages = messagesByContact[activeContactId] || []; const attendants = attendantsByArea[transferArea] || []; useEffect(() => { setSelectedArea(activeContact.area); }, [activeContact]); useEffect(() => { setTransferAttendant(attendants[0] || ''); }, [transferArea]); useEffect(() => { activeContactRef.current = activeContactId; }, [activeContactId]); useEffect(() => { let isMounted = true; async function loadChats() { setIsLoadingChats(true); try { const response = await fetch(`${API_BASE_URL}/whatsapp/chats`); if (!response.ok) throw new Error('Falha ao carregar chats do WhatsApp.'); const data = await response.json(); if (!isMounted || !Array.isArray(data) || data.length === 0) return; const nextContacts = data.map(normalizeChat); setContacts(nextContacts); setActiveContactId((current) => nextContacts.some((contact) => contact.id === current) ? current : nextContacts[0].id, ); setApiError(null); } catch (error) { if (isMounted) setApiError(error.message); } finally { if (isMounted) setIsLoadingChats(false); } } loadChats(); const intervalId = window.setInterval(loadChats, 30000); return () => { isMounted = false; window.clearInterval(intervalId); }; }, []); useEffect(() => { if (!activeContactId) return; let isMounted = true; async function loadMessages() { if (!activeContactId.includes('@')) return; setIsLoadingMessages(true); try { const response = await fetch(`${API_BASE_URL}/whatsapp/messages/${encodeURIComponent(activeContactId)}`); if (!response.ok) throw new Error('Falha ao carregar mensagens do WhatsApp.'); const data = await response.json(); if (!isMounted || !Array.isArray(data)) return; setMessagesByContact((current) => ({ ...current, [activeContactId]: data.map((message) => ({ ...normalizeMessage(message), chatId: activeContactId, })), })); setApiError(null); } catch (error) { if (isMounted) setApiError(error.message); } finally { if (isMounted) setIsLoadingMessages(false); } } loadMessages(); return () => { isMounted = false; }; }, [activeContactId]); useEffect(() => { if (!incomingMessage) return; const contactId = incomingMessage.from || incomingMessage.to || incomingMessage.chatId; if (!contactId) return; const message = { ...normalizeMessage(incomingMessage), chatId: contactId, }; const preview = getPreviewFromMessage(message); setMessagesByContact((current) => { const currentMessages = current[contactId] || []; if (currentMessages.some((item) => item.id === message.id)) return current; return { ...current, [contactId]: [...currentMessages, message], }; }); setContacts((current) => { const existing = current.find((contact) => contact.id === contactId); const nextContact = { ...(existing || { id: contactId, name: incomingMessage.notifyName || contactId.split('@')[0], channel: 'WhatsApp', status: 'online', area: 'Suporte', lastSeen: 'Online agora', unread: 0, }), preview, time: 'Agora', unread: incomingMessage.fromMe || contactId === activeContactRef.current ? 0 : (existing?.unread || 0) + 1, }; return [nextContact, ...current.filter((contact) => contact.id !== contactId)]; }); clearIncomingMessage(); }, [incomingMessage, clearIncomingMessage]); function updateContactPreview(contactId, preview, media) { setContacts((current) => current.map((contact) => contact.id === contactId ? { ...contact, preview: media ? `[Midia: ${media.filename || 'Arquivo'}]` : preview, time: 'Agora', unread: 0 } : contact, ), ); } async function attachFile(file) { if (!file) return; const data = await fileToBase64(file); setAttachedFile({ name: file.name, type: file.type || 'application/octet-stream', data, }); } function removeAttachedFile() { setAttachedFile(null); } async function hydrateMessageMedia(contactId, messageId) { if (!contactId || !messageId) return; setMessagesByContact((current) => ({ ...current, [contactId]: (current[contactId] || []).map((message) => message.id === messageId ? { ...message, mediaLoading: true, mediaError: null } : message, ), })); try { const response = await fetch( `${API_BASE_URL}/whatsapp/media/${encodeURIComponent(contactId)}/${encodeURIComponent(messageId)}`, ); if (!response.ok) throw new Error('Falha ao carregar midia.'); const media = await response.json(); setMessagesByContact((current) => ({ ...current, [contactId]: (current[contactId] || []).map((message) => message.id === messageId ? { ...message, media, mediaLoading: false } : message, ), })); } catch (error) { setMessagesByContact((current) => ({ ...current, [contactId]: (current[contactId] || []).map((message) => message.id === messageId ? { ...message, mediaLoading: false, mediaError: error.message || 'Erro ao carregar midia.' } : message, ), })); } } async function sendMessage() { const trimmed = draft.trim(); if (!trimmed && !attachedFile) { return; } const media = attachedFile ? { data: attachedFile.data, mimetype: attachedFile.type, filename: attachedFile.name, } : null; const newMessage = { id: `temp-${Date.now()}`, chatId: activeContactId, sender: 'agent', text: trimmed, hasMedia: Boolean(media), media, }; setMessagesByContact((current) => ({ ...current, [activeContactId]: [...(current[activeContactId] || []), newMessage], })); updateContactPreview(activeContactId, trimmed || '[Midia]', media); setDraft(''); setAttachedFile(null); if (!activeContactId.includes('@')) return; try { await fetch(`${API_BASE_URL}/whatsapp/send`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ to: activeContactId, message: trimmed, media, }), }); setApiError(null); } catch (error) { setApiError(error.message); } } function submitTransfer() { const note = transferNote.trim(); const transferMessage = note ? `Transferencia solicitada para ${transferArea} com ${transferAttendant}. Obs: ${note}` : `Transferencia solicitada para ${transferArea} com ${transferAttendant}.`; setMessagesByContact((current) => ({ ...current, [activeContactId]: [ ...(current[activeContactId] || []), { id: Date.now() + 2, sender: 'system', text: transferMessage }, ], })); setContacts((current) => current.map((contact) => contact.id === activeContactId ? { ...contact, area: transferArea } : contact, ), ); setSelectedArea(transferArea); setIsTransferOpen(false); setTransferNote(''); } return { contacts, activeContact, activeContactId, setActiveContactId, messages, draft, setDraft, attachedFile, attachFile, removeAttachedFile, sendMessage, hydrateMessageMedia, isReplying, isLoadingChats, isLoadingMessages, apiError, selectedArea, setSelectedArea, isTransferOpen, setIsTransferOpen, transferArea, setTransferArea, transferAreas, attendants, transferAttendant, setTransferAttendant, transferNote, setTransferNote, submitTransfer, }; }