// Tela: Jornadas do Cliente — kanban de fluxo const { useState: useJornState, useRef: useJornRef, useEffect: useJornEffect } = React; const CHAT_MSGS = [ { id: 1, from: 'them', text: 'Olá! Queria saber mais sobre os serviços de vocês.', time: '09:14' }, { id: 2, from: 'me', ai: true, text: 'Oi! Que ótimo falar com você 😊 Me conta — qual é o principal desafio que você quer resolver agora?', time: '09:15' }, { id: 3, from: 'them', text: 'Preciso melhorar meu processo comercial, tá bem desorganizado.', time: '09:17' }, { id: 4, from: 'me', ai: true, text: 'Entendo! Você já usa alguma ferramenta hoje, ou está tudo em planilha?', time: '09:18' }, { id: 5, from: 'them', text: 'Só planilha mesmo, já não aguento mais 😅', time: '09:19' }, { id: 6, from: 'me', ai: true, text: 'Haha, a planilha sempre chega no limite! 📊 Tenho uma solução que encaixa certinho. Posso agendar uma demo rápida com nosso especialista esta semana?', time: '09:21' }, { id: 7, from: 'them', text: 'Pode ser! Prefiro quinta ou sexta pela manhã.', time: '09:23' }, ]; const AI_REPLIES = [ 'Perfeito! Vou confirmar a disponibilidade do nosso especialista e já te retorno com o link de agendamento. 📅', 'Ótimo! Enquanto isso, posso te enviar um material rápido sobre como empresas do seu segmento usam nossa solução?', 'Entendido! Para personalizar a demo, qual é o tamanho da sua equipe comercial hoje?', 'Que legal! Com esse perfil você vai ver resultado muito rápido. Animada para a demo? 🚀', ]; // Atendentes do fluxo com contagem de casos ativos const ATENDENTES_FLUXO = [ { nome: 'Ana Beatriz', casos: 4, avatarIdx: 0 }, { nome: 'Bruno Souza', casos: 2, avatarIdx: 1 }, { nome: 'Camila Lima', casos: 3, avatarIdx: 2 }, { nome: 'Diego Rocha', casos: 1, avatarIdx: 3 }, { nome: 'Eva Pinto', casos: 4, avatarIdx: 4 }, { nome: 'Felipe Couto', casos: 2, avatarIdx: 5 }, { nome: 'Gabriela Sá', casos: 3, avatarIdx: 6 }, ]; function ConversaDrawer({ contato, onClose }) { const [msg, setMsg] = useJornState(''); const [msgs, setMsgs] = useJornState(CHAT_MSGS); const [isAi, setIsAi] = useJornState(true); const [aiTyping, setAiTyping] = useJornState(false); const [sending, setSending] = useJornState(false); const [analise, setAnalise] = useJornState(null); const [analisando, setAnalisando] = useJornState(false); const [showAnalise, setShowAnalise] = useJornState(false); const endRef = useJornRef(null); const fileRef = useJornRef(null); const aiReplyIdx = useJornRef(0); const toast = useToast(); const analisarConversa = async () => { setAnalisando(true); setShowAnalise(true); try { const historico = msgs.filter(m => m.text).map(m => ({ from: m.from, text: m.text })); const res = await fetch('/api/ai/analise-conversa', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contato, historico }), }); const data = await res.json(); if (res.ok && data.ok) setAnalise(data); else toast('Erro na análise: ' + (data.error || 'tente novamente')); } catch { toast('⚠️ Proxy offline — análise indisponível'); } finally { setAnalisando(false); } }; const handleFile = (e) => { const file = e.target.files?.[0]; if (!file) return; const kb = (file.size / 1024).toFixed(0); const mb = file.size > 1024 * 1024 ? (file.size / (1024 * 1024)).toFixed(1) + ' MB' : kb + ' KB'; const isImg = file.type.startsWith('image/'); if (isImg) { const url = URL.createObjectURL(file); setMsgs(prev => [...prev, { id: Date.now(), from: 'me', ai: false, type: 'img', url, name: file.name, size: mb, time: now() }]); } else { setMsgs(prev => [...prev, { id: Date.now(), from: 'me', ai: false, type: 'file', name: file.name, size: mb, time: now() }]); } toast('📎 Arquivo anexado — envio de mídia via API em breve'); e.target.value = ''; }; const now = () => new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }); // Converte "(21) 98821-4490" ou "+55 21 9 8821-4490" → "5521988214490" const toE164 = (raw = '') => { const digits = (raw || '').replace(/\D/g, ''); return digits.startsWith('55') ? digits : '55' + digits; }; const triggerAiReply = async (currentMsgs) => { setAiTyping(true); try { const historico = (currentMsgs || msgs).filter(m => m.text).map(m => ({ from: m.from, text: m.text })); const res = await fetch('/api/ai/sdr-reply', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contato, historico }), }); const data = await res.json(); const text = (res.ok && data.reply) ? data.reply : AI_REPLIES[aiReplyIdx.current % AI_REPLIES.length]; aiReplyIdx.current += 1; setAiTyping(false); setMsgs(prev => [...prev, { id: Date.now(), from: 'me', ai: true, text, time: now() }]); } catch { // fallback para resposta estática se proxy offline const text = AI_REPLIES[aiReplyIdx.current % AI_REPLIES.length]; aiReplyIdx.current += 1; setAiTyping(false); setMsgs(prev => [...prev, { id: Date.now(), from: 'me', ai: true, text, time: now() }]); } }; const send = async () => { if (!msg.trim() || sending) return; const text = msg.trim(); // Adiciona na UI imediatamente (optimistic) setMsgs(prev => [...prev, { id: Date.now(), from: 'me', ai: false, text, time: now() }]); setMsg(''); // Tenta enviar via WhatsApp real const phone = toE164(contato?.whatsapp || contato?.fone || ''); if (phone.length >= 12) { setSending(true); try { const res = await fetch('/api/whatsapp/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ to: phone, message: text }), }); const data = await res.json(); if (res.ok && data.ok) { toast('✅ Mensagem enviada via WhatsApp'); } else { toast('⚠️ Mensagem salva localmente — ' + (data.error || 'erro no envio')); } } catch { toast('⚠️ Proxy offline — mensagem salva localmente'); } finally { setSending(false); } } }; const simulateLead = () => { const leadMsgs = ['Tudo bem! Quando seria a demo?', 'Quantos usuários posso ter no plano inicial?', 'Tem integração com WhatsApp?', 'Qual é o valor mensal?']; const text = leadMsgs[Math.floor(Math.random() * leadMsgs.length)]; const newMsg = { id: Date.now(), from: 'them', text, time: now() }; setMsgs(prev => { const updated = [...prev, newMsg]; if (isAi) triggerAiReply(updated); return updated; }); }; const onKey = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }; useJornEffect(() => { endRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [msgs, aiTyping]); const WaIcon = () => ( ); const BoltIcon = () => ( ); return ( <>
{/* ── cabeçalho ── */}
{contato.nome}
online agora
WhatsApp
{/* ── painel análise Gemini ── */} {showAnalise && (
✦ Análise Gemini
{analisando ? (
Analisando conversa…
) : analise ? (
{[ { label:'Sentimento', value: analise.sentimento, color: analise.sentimento==='positivo'?'oklch(0.45 0.18 155)':analise.sentimento==='negativo'?'oklch(0.50 0.20 25)':'oklch(0.45 0.12 255)' }, { label:'Urgência', value: analise.urgencia, color: analise.urgencia==='alta'?'oklch(0.50 0.20 25)':analise.urgencia==='media'?'oklch(0.55 0.18 75)':'oklch(0.45 0.12 255)' }, { label:'Interesse', value: analise.interesse+'%', color: analise.interesse>=70?'oklch(0.45 0.18 155)':analise.interesse>=40?'oklch(0.55 0.18 75)':'oklch(0.50 0.20 25)' }, ].map(({label,value,color}) => (
{label}
{value}
))}
Situação: {analise.resumo_curto}
💡 Ação: {analise.sugestao}
) : null}
)} {/* ── banner IA ── */}
{isAi ? 'IA SDR respondendo automaticamente' : 'Modo manual — você está no controle'}
{isAi ? : }
{/* ── mensagens ── */}
Hoje
{msgs.map(m => (
{m.from === 'me' && m.ai && (
IA SDR
)}
{m.type === 'img' ? (
{m.name} {m.name} · {m.size}
) : m.type === 'file' ? (
📄
{m.name} {m.size}
) : m.text}
{m.time}{m.from === 'me' && ' ✓✓'}
))} {aiTyping && (
IA SDR
)}
{/* ── botão demo ── */}
{/* ── input ── */}
{isAi ? (
IA está respondendo
) : ( <>