/* CRM — caixa de entrada operacional. */ const { crmInboxAppointmentDate, crmInboxPhone, crmInboxContactName, crmInboxInitials, crmInboxStatusLabels, crmInboxRoleLabels, crmInboxArrangeMessages, crmInboxReplyPreview, CrmConversationItem, CrmKanbanCard, CrmMessageBubble, CrmNewConversationModal, } = window.CrmInboxParts; function crmInboxFriendlyError(error, fallback) { const status = Number(error && error.status); const raw = String((error && error.message) || ''); if (status === 403) { return 'Você não tem permissão para realizar esta ação.'; } if (/24 horas|modelo aprovado/i.test(raw)) { return 'Para enviar uma mensagem livre, o cliente precisa ter iniciado a conversa nas últimas 24 horas. Fora desse período, use uma mensagem previamente aprovada.'; } if (/Aguarde um minuto/i.test(raw)) { return 'Aguarde um minuto antes de enviar novas mensagens.'; } if (/telefone|número/i.test(raw) && /DDD|país|válid|inválid/i.test(raw)) { return 'Confira o número do WhatsApp, incluindo país e DDD.'; } if (/Áudios não aceitam legenda|nome do atendente|Digite uma mensagem/i.test(raw)) { return raw; } if (/Meta|modelo|arquivo|mídia|cabeçalho|destinatário|autorização do canal/i.test(raw)) { return raw; } return fallback; } function CrmInboxScreen({ user }) { const [payload, setPayload] = React.useState(null); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(''); const [notice, setNotice] = React.useState(''); const [channelBusy, setChannelBusy] = React.useState(''); const [settingsOpen, setSettingsOpen] = React.useState(false); const [accessOpen, setAccessOpen] = React.useState(false); const [settingsView, setSettingsView] = React.useState(false); const [busyUserId, setBusyUserId] = React.useState(0); const [savedUserId, setSavedUserId] = React.useState(0); const [conversations, setConversations] = React.useState([]); const [counts, setCounts] = React.useState({ all: 0, open: 0, pending: 0, resolved: 0, unread: 0, awaiting: 0 }); const [conversationLoading, setConversationLoading] = React.useState(false); const [filter, setFilter] = React.useState('all'); const [assignedFilter, setAssignedFilter] = React.useState('all'); const [search, setSearch] = React.useState(''); const [selectedId, setSelectedId] = React.useState(0); const [selectedConversation, setSelectedConversation] = React.useState(null); const [messages, setMessages] = React.useState([]); const [threadLoading, setThreadLoading] = React.useState(false); const [conversationBusy, setConversationBusy] = React.useState(false); const [messageBody, setMessageBody] = React.useState(''); const [messageBusy, setMessageBusy] = React.useState(false); const [attachment, setAttachment] = React.useState(null); const [replyingTo, setReplyingTo] = React.useState(null); const [senderEditorOpen, setSenderEditorOpen] = React.useState(false); const [senderNameDraft, setSenderNameDraft] = React.useState(''); const [senderIdentityBusy, setSenderIdentityBusy] = React.useState(false); const [contextOpen, setContextOpen] = React.useState(false); const [inboxView, setInboxView] = React.useState(() => window.localStorage.getItem('crmInboxView') === 'board' ? 'board' : 'list'); const [boardBusyId, setBoardBusyId] = React.useState(0); const [boardDragId, setBoardDragId] = React.useState(0); const [boardDropStatus, setBoardDropStatus] = React.useState(''); const messageViewportRef = React.useRef(null); const attachmentInputRef = React.useRef(null); const messageSendingRef = React.useRef(false); const [newConversationOpen, setNewConversationOpen] = React.useState(false); const [contactQuery, setContactQuery] = React.useState(''); const [contacts, setContacts] = React.useState([]); const [contactsLoading, setContactsLoading] = React.useState(false); const [newPhone, setNewPhone] = React.useState(''); const [newBusy, setNewBusy] = React.useState(false); const [newConversationError, setNewConversationError] = React.useState(''); const [templates, setTemplates] = React.useState([]); const [templatesLoading, setTemplatesLoading] = React.useState(false); const [templateKey, setTemplateKey] = React.useState(''); const [templateParameters, setTemplateParameters] = React.useState({}); const [templateMedia, setTemplateMedia] = React.useState(null); React.useEffect(() => { const navigate = (event) => { const target = String((event.detail && event.detail.target) || 'inbox'); if (target === 'settings' || target === 'team' || target === 'channel') { setSettingsView(true); setSettingsOpen(target === 'settings' || target === 'channel'); setAccessOpen(target === 'settings' || target === 'team'); const selector = target === 'team' ? '.crm-access-collapsible' : '.crm-channel-settings'; window.setTimeout(() => document.querySelector(selector)?.scrollIntoView({ behavior: 'smooth', block: 'start' }), 0); return; } setSettingsView(false); setSettingsOpen(false); if (target === 'contacts') { setContactQuery(''); setNewPhone(''); setNewConversationOpen(true); return; } window.setTimeout(() => document.querySelector('.crm-inbox-shell')?.scrollIntoView({ behavior: 'smooth', block: 'start' }), 0); }; window.addEventListener('crm:navigate', navigate); return () => window.removeEventListener('crm:navigate', navigate); }, []); const crm = (payload && payload.crm) || {}; const senderIdentity = crm.senderIdentity || {}; const senderDisplayName = senderIdentity.displayName || user.crmDisplayName || user.name || user.login || 'Atendimento'; const messageMaxLength = Math.max(1, (attachment ? 1024 : 4096) - String(senderDisplayName).length - 4); const access = crm.access || user.crmAccess || {}; const users = (payload && payload.users) || []; const assignees = (payload && payload.assignees) || []; const whatsapp = payload && payload.channels && payload.channels.whatsapp; const whatsappConnected = !!(whatsapp && whatsapp.status === 'connected'); const whatsappCoexistence = whatsappConnected && whatsapp.keepsMobileNumber === true; const channelSetupReady = !!crm.channelsReady; const connectionId = Number(whatsapp ? whatsapp.id : 0); const enabledUsersCount = users.filter((item) => item.crmEnabled).length; const arrangedMessages = React.useMemo(() => crmInboxArrangeMessages(messages), [messages]); const messagesByProviderId = React.useMemo(() => { const index = new Map(); messages.forEach((message) => { if (message.providerMessageId) index.set(message.providerMessageId, message); }); return index; }, [messages]); const boardColumns = React.useMemo(() => { const columns = { awaiting: [], open: [], pending: [], resolved: [] }; conversations.forEach((conversation) => { if (conversation.lastMessageDirection === 'inbound') columns.awaiting.push(conversation); else if (conversation.status === 'resolved') columns.resolved.push(conversation); else if (conversation.status === 'pending') columns.pending.push(conversation); else columns.open.push(conversation); }); return columns; }, [conversations]); const loadStatus = React.useCallback(async () => { setLoading(true); setError(''); try { setPayload(await window.API.crm.status()); } catch (err) { setError(crmInboxFriendlyError(err, 'Não foi possível carregar o CRM.')); } finally { setLoading(false); } }, []); React.useEffect(() => { loadStatus(); }, [loadStatus]); const loadConversations = React.useCallback(async (silent = false) => { if (!connectionId || !whatsappConnected) { setConversations([]); setCounts({ all: 0, open: 0, pending: 0, resolved: 0, unread: 0, awaiting: 0 }); return; } if (!silent) setConversationLoading(true); try { const result = await window.API.crm.conversations({ status: inboxView === 'board' || filter === 'unread' ? 'all' : filter, q: search.trim(), unread: inboxView !== 'board' && filter === 'unread', assigned: assignedFilter, limit: 150, }); setConversations(result.conversations || []); setCounts(result.counts || { all: 0, open: 0, pending: 0, resolved: 0, unread: 0, awaiting: 0 }); } catch (err) { if (!silent) setError(crmInboxFriendlyError(err, 'Não foi possível carregar as conversas.')); } finally { if (!silent) setConversationLoading(false); } }, [connectionId, whatsappConnected, filter, search, assignedFilter, inboxView]); const loadConversation = React.useCallback(async (conversationId, markRead = false, silent = false) => { if (!conversationId) return; if (!silent) setThreadLoading(true); try { const result = await window.API.crm.conversation(conversationId, 250); let conversation = result.conversation || null; setMessages(result.messages || []); if (markRead && conversation && conversation.unreadCount > 0) { const updated = await window.API.crm.updateConversation(conversationId, { markRead: true }); conversation = updated.conversation || conversation; setConversations((current) => current.map((item) => item.id === conversationId ? { ...item, unreadCount: 0 } : item)); setCounts((current) => ({ ...current, unread: Math.max(0, current.unread - 1) })); } setSelectedConversation(conversation); } catch (err) { if (Number(err && err.status) === 404) { setSelectedId((current) => current === Number(conversationId) ? 0 : current); setSelectedConversation(null); setMessages([]); setConversations((current) => current.filter((item) => Number(item.id) !== Number(conversationId))); return; } if (!silent) setError(crmInboxFriendlyError(err, 'Não foi possível abrir a conversa.')); } finally { if (!silent) setThreadLoading(false); } }, []); React.useEffect(() => { if (!whatsappConnected) return undefined; const timer = window.setTimeout(() => loadConversations(), search.trim() ? 260 : 0); return () => window.clearTimeout(timer); }, [whatsappConnected, loadConversations]); React.useEffect(() => { if (inboxView === 'board' || selectedId || conversations.length === 0) return; if (!window.matchMedia || window.matchMedia('(min-width: 721px)').matches) { const firstId = Number(conversations[0].id); setSelectedId(firstId); loadConversation(firstId, true); } }, [conversations, selectedId, loadConversation, inboxView]); React.useEffect(() => { if (!whatsappConnected) return undefined; const timer = window.setInterval(() => { loadConversations(true); if (selectedId) loadConversation(selectedId, true, true); }, 5000); return () => window.clearInterval(timer); }, [whatsappConnected, selectedId, loadConversations, loadConversation]); React.useEffect(() => { const viewport = messageViewportRef.current; if (viewport) { viewport.scrollTop = viewport.scrollHeight; } }, [messages.length, selectedId]); React.useEffect(() => { if (!newConversationOpen) return undefined; const timer = window.setTimeout(async () => { setContactsLoading(true); try { const result = await window.API.crm.contacts(contactQuery, 30); setContacts(result.contacts || []); } catch (err) { setError(crmInboxFriendlyError(err, 'Não foi possível buscar os clientes.')); } finally { setContactsLoading(false); } }, contactQuery.trim() ? 240 : 0); return () => window.clearTimeout(timer); }, [newConversationOpen, contactQuery]); React.useEffect(() => { if (!newConversationOpen) return undefined; let active = true; setTemplatesLoading(true); window.API.crm.messageTemplates() .then((result) => { if (active) setTemplates(result.templates || []); }) .catch((err) => { if (active) setError(crmInboxFriendlyError(err, 'Não foi possível carregar os modelos aprovados da Meta.')); }) .finally(() => { if (active) setTemplatesLoading(false); }); return () => { active = false; }; }, [newConversationOpen]); const chooseConversation = (conversationId) => { setReplyingTo(null); setSelectedId(conversationId); setSelectedConversation(null); setMessages([]); loadConversation(conversationId, true); }; const changeInboxView = (view) => { const nextView = view === 'board' ? 'board' : 'list'; setInboxView(nextView); window.localStorage.setItem('crmInboxView', nextView); if (nextView === 'board') { setReplyingTo(null); setSelectedId(0); setSelectedConversation(null); setMessages([]); setContextOpen(false); } }; const openBoardConversation = (conversationId) => { changeInboxView('list'); chooseConversation(conversationId); }; const moveBoardConversation = async (conversationId, status) => { if (!['open', 'pending', 'resolved'].includes(status) || boardBusyId) return; const previous = conversations; setBoardBusyId(conversationId); setError(''); setConversations((current) => current.map((item) => Number(item.id) === Number(conversationId) ? { ...item, status, unreadCount: status === 'open' ? 0 : item.unreadCount } : item)); try { await window.API.crm.updateConversation(conversationId, { status, markRead: status === 'open', }); setNotice(status === 'resolved' ? 'Conversa concluída.' : status === 'pending' ? 'Conversa movida para pendentes.' : 'Conversa em atendimento.'); await loadConversations(true); } catch (err) { setConversations(previous); setError(crmInboxFriendlyError(err, 'Não foi possível mover a conversa.')); } finally { setBoardBusyId(0); setBoardDragId(0); setBoardDropStatus(''); } }; const updateConversation = async (data, successMessage = '') => { if (!selectedId) return; setConversationBusy(true); setError(''); try { const result = await window.API.crm.updateConversation(selectedId, data); const updated = result.conversation; setSelectedConversation(updated); setConversations((current) => current.map((item) => item.id === selectedId ? { ...item, ...updated } : item)); if (successMessage) setNotice(successMessage); loadConversations(true); } catch (err) { setError(crmInboxFriendlyError(err, 'Não foi possível atualizar a conversa.')); } finally { setConversationBusy(false); } }; const sendMessage = async (event) => { event.preventDefault(); if (messageSendingRef.current || !selectedId || (!messageBody.trim() && !attachment)) return; const submittedBody = messageBody; const submittedAttachment = attachment; const submittedReply = replyingTo; messageSendingRef.current = true; setMessageBusy(true); setMessageBody(''); setAttachment(null); setReplyingTo(null); if (attachmentInputRef.current) attachmentInputRef.current.value = ''; setError(''); setNotice(''); try { const result = submittedAttachment ? await window.API.crm.sendMedia( selectedId, submittedAttachment, submittedBody, submittedReply ? submittedReply.providerMessageId : '' ) : await window.API.crm.sendMessage( '', submittedBody, selectedId, submittedReply ? submittedReply.providerMessageId : '' ); if (result.message) { setMessages((current) => [ ...current.filter((item) => item.id !== result.message.id), result.message, ]); } if (result.conversation) setSelectedConversation(result.conversation); loadConversations(true); window.setTimeout(() => loadConversation(selectedId, true, true), 1200); } catch (err) { const rejectedBeforeSend = Number(err && err.status) >= 400 && Number(err && err.status) < 500; if (rejectedBeforeSend) { setMessageBody((current) => current || submittedBody); setAttachment((current) => current || submittedAttachment); setReplyingTo((current) => current || submittedReply); setError(crmInboxFriendlyError(err, 'Não foi possível enviar a mensagem.')); } else { // Em timeout/erro 5xx a Meta pode já ter aceitado a mensagem. O campo // permanece vazio para impedir um segundo envio acidental. setMessageBody(''); setAttachment(null); setReplyingTo(null); setError('Não foi possível confirmar a atualização da tela. Verifique o histórico antes de tentar novamente.'); window.setTimeout(() => loadConversation(selectedId, true, true), 900); } } finally { messageSendingRef.current = false; setMessageBusy(false); } }; const saveSenderIdentity = async () => { if (senderIdentityBusy) return; setSenderIdentityBusy(true); setError(''); try { const result = await window.API.crm.setSenderIdentity(senderNameDraft); setPayload((current) => ({ ...(current || {}), crm: { ...((current && current.crm) || {}), senderIdentity: result.senderIdentity, }, })); setSenderEditorOpen(false); setNotice(`As mensagens serão identificadas como ${result.senderIdentity.displayName}.`); } catch (err) { setError(crmInboxFriendlyError(err, 'Não foi possível alterar o nome do atendimento.')); } finally { setSenderIdentityBusy(false); } }; const sendNewConversation = async () => { if (!newPhone.trim() || !templateKey) return; setNewBusy(true); setError(''); setNewConversationError(''); try { const separator = templateKey.lastIndexOf('|'); const result = await window.API.crm.sendTemplate( newPhone, templateKey.slice(0, separator), templateKey.slice(separator + 1), templateParameters, templateMedia ); const conversationId = Number( (result.conversation && result.conversation.id) || (result.message && result.message.conversationId) || 0 ); setNewConversationOpen(false); setContactQuery(''); setContacts([]); setNewPhone(''); setTemplateKey(''); setTemplateParameters({}); setTemplateMedia(null); await loadConversations(true); if (conversationId) { setSelectedId(conversationId); await loadConversation(conversationId, true); } } catch (err) { const friendlyError = crmInboxFriendlyError(err, 'Não foi possível iniciar a conversa.'); setNewConversationError(friendlyError); } finally { setNewBusy(false); } }; const connectWhatsApp = async () => { let signupWaiter = null; setChannelBusy('connect'); setError(''); setNotice(''); try { const start = await window.API.crm.startOnboarding('whatsapp'); const publicMeta = start.meta || {}; const onboarding = start.onboarding || {}; const featureType = String(publicMeta.embeddedSignupFeatureType || ''); if ( publicMeta.keepsMobileNumber !== true || onboarding.keepsMobileNumber !== true || onboarding.featureType !== featureType || featureType !== 'whatsapp_business_app_onboarding' ) { throw new Error('Não foi possível iniciar a conexão.'); } const FB = await window.loadCrmMetaSdk(publicMeta.appId, publicMeta.graphVersion); signupWaiter = window.createEmbeddedSignupWaiter(); const code = await new Promise((resolve, reject) => { FB.login((response) => { const authCode = response && response.authResponse && response.authResponse.code; if (authCode) resolve(String(authCode)); else reject(new Error('Não foi possível autorizar a conexão.')); }, { config_id: String(publicMeta.configId), response_type: 'code', override_default_response_type: true, extras: { setup: {}, featureType, sessionInfoVersion: '3', }, }); }); const session = await signupWaiter.promise; if (!session) throw new Error('A conexão foi interrompida.'); const result = await window.API.crm.completeOnboarding({ state: onboarding.state || '', code, ...session, }); setPayload((current) => ({ ...(current || {}), channels: { ...((current && current.channels) || {}), whatsapp: result.connection }, })); setNotice('WhatsApp conectado. O número continua disponível no celular e também pode ser atendido pelo CRM.'); setSettingsOpen(false); } catch (err) { if (signupWaiter) signupWaiter.cancel(); setError(crmInboxFriendlyError(err, 'Não foi possível conectar o WhatsApp. Tente novamente ou entre em contato com o suporte.')); } finally { setChannelBusy(''); } }; const verifyWhatsApp = async () => { setChannelBusy('verify'); setError(''); setNotice(''); try { const result = await window.API.crm.verifyChannel(); setPayload((current) => ({ ...(current || {}), channels: { ...((current && current.channels) || {}), whatsapp: result.connection }, })); setNotice('Conexão verificada com sucesso.'); } catch (err) { setError(crmInboxFriendlyError(err, 'Não foi possível verificar a conexão. Tente novamente.')); } finally { setChannelBusy(''); } }; const disconnectWhatsApp = async () => { const question = whatsappCoexistence ? 'Desconectar este número do CRM? Ele continuará disponível no WhatsApp Business do celular.' : 'Desconectar este número do CRM?'; if (!window.confirm(question)) return; setChannelBusy('disconnect'); setError(''); try { const result = await window.API.crm.disconnectChannel(); setPayload((current) => ({ ...(current || {}), channels: { ...((current && current.channels) || {}), whatsapp: result.connection }, })); setSelectedId(0); setSelectedConversation(null); setMessages([]); setNotice('Canal desconectado do CRM.'); } catch (err) { setError(crmInboxFriendlyError(err, 'Não foi possível desconectar o WhatsApp. Tente novamente.')); } finally { setChannelBusy(''); } }; const updateAccess = async (targetUserId, role) => { setBusyUserId(targetUserId); setSavedUserId(0); setError(''); try { const result = await window.API.crm.setUserAccess(targetUserId, role); const nextUsers = result.users || []; setPayload((current) => ({ ...(current || {}), users: nextUsers, assignees: nextUsers .filter((item) => item.crmEnabled && item.status === 'ativo') .map((item) => ({ id: item.id, name: item.name || item.login, role: item.crmRole, })), })); setSavedUserId(targetUserId); window.setTimeout(() => setSavedUserId((current) => current === targetUserId ? 0 : current), 1800); } catch (err) { setError(crmInboxFriendlyError(err, 'Não foi possível alterar o acesso.')); } finally { setBusyUserId(0); } }; if (loading) { return (
Carregando caixa de entrada…
); } if (error && !payload) { return (
Tentar novamente
); } const currentConversation = selectedConversation || conversations.find((item) => item.id === selectedId) || null; const currentClient = currentConversation && currentConversation.client; const currentContactName = crmInboxContactName(currentConversation); const roleLabel = crmInboxRoleLabels[access.role] || 'Atendente'; const moduleStatusLabel = { trialing: 'Período de teste', active: 'Ativo', grace: 'Período de carência', }[crm.status] || 'Ativo'; const filterItems = [ ['all', 'Todas', counts.all], ['awaiting', 'Aguardando resposta', counts.awaiting], ['open', 'Em atendimento', counts.open], ['unread', 'Não lidas', counts.unread], ['pending', 'Pendentes', counts.pending], ['resolved', 'Resolvidas', counts.resolved], ]; return (
{error &&
{error}
} {notice &&
{notice}
} {settingsView && (
Administração

Configurações do CRM

Gerencie o canal oficial e quem pode atender conversas.

{ setSettingsView(false); setSettingsOpen(false); window.dispatchEvent(new CustomEvent('crm:section-changed', { detail: { target: 'inbox' } })); }}>Voltar à caixa de entrada
)} {!settingsView &&
{whatsappConnected ? 'WhatsApp oficial conectado' : 'WhatsApp oficial não conectado'} {whatsappConnected ? `${whatsapp.displayName || 'Conta verificada'} · ${whatsapp.displayPhoneNumber || 'Número conectado'}` : 'Conecte um número para receber e responder conversas no CRM.'}
{whatsappCoexistence && Celular + CRM} {moduleStatusLabel} · {roleLabel}
{whatsappConnected && ( { loadConversations(); if (selectedId) loadConversation(selectedId, true); }} disabled={conversationLoading || threadLoading} > Atualizar )}
} {settingsView && settingsOpen && access.manageChannels && (

Canal do WhatsApp

Gerencie o número usado para receber e responder conversas.

{whatsappConnected ? 'Conectado' : 'Não conectado'}
WhatsApp Business {whatsappConnected ? `${whatsapp.displayName || 'Conta verificada'} · ${whatsapp.displayPhoneNumber || 'Número conectado'}` : 'Use o mesmo número no aplicativo e no CRM.'}
{whatsappConnected ? ( <> {channelBusy === 'verify' ? 'Verificando…' : 'Verificar'} {channelBusy === 'disconnect' ? 'Desconectando…' : 'Desconectar'} ) : ( {channelBusy === 'connect' ? 'Abrindo conexão…' : 'Conectar WhatsApp'} )}
{!channelSetupReady && (
A conexão do WhatsApp está temporariamente indisponível. Entre em contato com o suporte.
)} {whatsapp && whatsapp.status === 'error' && (
Não foi possível manter a conexão. Verifique novamente ou entre em contato com o suporte.
)}
)} {!settingsView && (whatsappConnected ? (
Atendimento

Caixa de entrada

setNewConversationOpen(true)}> Nova conversa
{filterItems.map(([value, label, count]) => ( ))}
{inboxView === 'board' ? (
Quadro de atendimentosArraste os cartões para atualizar a situação
setSearch(event.target.value)} aria-label="Buscar por nome, telefone ou e-mail" />
{access.viewAll && ( setAssignedFilter(event.target.value)} > )}
{[ ['awaiting', 'Aguardando resposta', 'O cliente enviou a última mensagem', 'bell', ''], ['open', 'Em atendimento', 'Conversas sendo atendidas agora', 'whatsapp', 'open'], ['pending', 'Pendentes', 'Aguardando retorno ou ação', 'clock', 'pending'], ['resolved', 'Concluídas', 'Atendimentos finalizados', 'check', 'resolved'], ].map(([key, label, description, icon, dropStatus]) => (
{ if (!dropStatus) return; event.preventDefault(); event.dataTransfer.dropEffect = 'move'; setBoardDropStatus(dropStatus); }} onDragLeave={(event) => { if (!event.currentTarget.contains(event.relatedTarget)) setBoardDropStatus(''); }} onDrop={(event) => { if (!dropStatus) return; event.preventDefault(); const conversationId = Number(event.dataTransfer.getData('text/plain') || boardDragId); if (conversationId) moveBoardConversation(conversationId, dropStatus); }} >
{label}{description} {boardColumns[key].length}
{conversationLoading && conversations.length === 0 ? (
Carregando atendimentos…
) : boardColumns[key].length === 0 ? (
Nenhum atendimento nesta etapa
) : boardColumns[key].map((conversation) => ( { setBoardDragId(Number(item.id)); event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setData('text/plain', String(item.id)); }} onDragEnd={() => { setBoardDragId(0); setBoardDropStatus(''); }} /> ))}
))}
) : (
{!currentConversation ? (
Selecione uma conversa Abra um atendimento para ver o histórico e responder.
) : ( <>
{currentConversation.status !== 'pending' && ( )} {currentConversation.status !== 'resolved' && ( )} {currentConversation.status === 'resolved' && ( )}
{threadLoading && messages.length === 0 ? (
Carregando histórico…
) : messages.length === 0 ? (
Nenhuma mensagem nesta conversa.
) : arrangedMessages.map((item) => ( { setReplyingTo(message); window.setTimeout(() => document.querySelector('.crm-inbox-composer .textarea')?.focus(), 0); }} key={item.message.id} /> ))}
{!currentConversation.canSendFreeform && (
A janela de 24 horas está fechada. Use Nova conversa para enviar um modelo aprovado e aguarde a resposta do cliente.
)}
{user.name || user.login} conectado · cliente verá {senderDisplayName} em negrito
{senderEditorOpen && (
setSenderNameDraft(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') { event.preventDefault(); saveSenderIdentity(); } }} /> {senderIdentityBusy ? 'Salvando…' : 'Salvar nome'}
)}
{['Olá! Como posso ajudar?', 'Vou verificar para você.', 'Posso ajudar com um agendamento?'].map((reply) => ( ))}
{attachment && (
{attachment.name}{(attachment.size / 1024 / 1024).toFixed(1)} MB
)} {replyingTo && (
Respondendo à mensagem {crmInboxReplyPreview(replyingTo)}
)} setMessageBody(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); if (!messageBusy && messageBody.trim()) event.currentTarget.form.requestSubmit(); } }} />
Enter envia · Shift + Enter quebra a linha
setAttachment(event.target.files && event.target.files[0] ? event.target.files[0] : null)} /> {messageBody.length}/{messageMaxLength} {messageBusy ? 'Enviando…' : 'Enviar'}
)}
)}
) : (
{access.manageChannels && (
{channelBusy === 'connect' ? 'Abrindo conexão…' : 'Conectar WhatsApp'}
)}
))} {settingsView && access.manageUsers && (
{accessOpen && (
{users.map((item) => (
{item.name || item.login} {item.login} · {item.agendaRole === 'admin' ? 'Administrador da Agenda' : 'Usuário da Agenda'}
updateAccess(item.id, event.target.value)} > {item.isPrimaryAdmin && Acesso obrigatório} {busyUserId === item.id && Salvando…} {savedUserId === item.id && Salvo}
))}
)}
)} { setTemplateKey(value); setTemplateParameters({}); setTemplateMedia(null); }} templateParameters={templateParameters} setTemplateParameters={setTemplateParameters} templateMedia={templateMedia} setTemplateMedia={setTemplateMedia} error={newConversationError} busy={newBusy} onClose={() => { if (newBusy) return; setNewConversationOpen(false); setContactQuery(''); setContacts([]); setNewPhone(''); setTemplateKey(''); setTemplateParameters({}); setTemplateMedia(null); setNewConversationError(''); }} onSubmit={sendNewConversation} />
); } // A empresa de demonstração mantém a tela usada na análise da Meta. // Os demais tenants recebem a caixa de entrada operacional atual. function CrmInboxRouteScreen(props) { return props.user && props.user.hasCrm ? : ; } if (window.TENANT_SLUG !== 'empresa-demonstracao') { window.CrmScreen = CrmInboxRouteScreen; }