/* Aba Tratamento: dashboard, navegador de leads com filtros e exportações. */ const Tratamento = (() => { let page = 1; let totalPages = 1; let carregado = false; let refreshTimer = null; const CLASSIFICACAO_LABELS = { excelente: 'Excelente', bom: 'Bom', medio: 'Médio', baixo: 'Baixo', muito_baixo: 'Muito Baixo' }; const PRIORIDADE_LABELS = { 1: 'P1 — atacar imediatamente', 2: 'P2 — trabalhar durante a semana', 3: 'P3 — quando não houver prioritários', 4: 'P4 — só em campanhas específicas' }; function estrelas(n) { const cheias = Math.max(0, Math.min(5, Number(n) || 0)); return '★'.repeat(cheias) + '☆'.repeat(5 - cheias); } // ---------- filtros ---------- function filtroAtual() { const filtro = {}; for (const input of document.querySelectorAll('#filtros [data-filtro]')) { const value = input.value.trim(); if (value) filtro[input.dataset.filtro] = value; } return filtro; } function filtroQuery(extra = {}) { return new URLSearchParams({ ...filtroAtual(), ...extra }).toString(); } // Clique nos cards/barras do dashboard: aplica o filtro somando aos ja // ativos; clicar de novo no mesmo valor remove so aquele filtro. function toggleFiltro(inputId, valor) { const input = el(inputId); const novo = String(valor ?? ''); input.value = input.value === novo ? '' : novo; atualizar(); } function descreverFiltro() { const f = filtroAtual(); const partes = []; if (f.uf) partes.push(`UF ${f.uf}`); if (f.cidade) partes.push(f.cidade); if (f.segmento) partes.push(el('f-segmento').selectedOptions[0]?.textContent || f.segmento); if (f.classificacao) partes.push(CLASSIFICACAO_LABELS[f.classificacao] || f.classificacao); if (f.prioridade) partes.push(`P${f.prioridade}`); if (f.produto) partes.push(`produto: ${f.produto}`); if (f.interesse) partes.push(f.interesse); if (f.telefone_valido) partes.push(f.telefone_valido === 'sim' ? 'com WhatsApp' : 'sem WhatsApp'); if (f.website) partes.push(f.website === 'sim' ? 'com website' : 'sem website'); if (f.email) partes.push(f.email === 'sim' ? 'com e-mail' : 'sem e-mail'); if (f.exportado) partes.push(f.exportado === 'nao' ? 'ainda não exportados' : 'já exportados'); if (f.q) partes.push(`busca "${f.q}"`); return partes.length ? partes.join(' · ') : 'todos os leads'; } // ---------- carregamento ---------- async function atualizar() { page = 1; const [summary, evolutionRes, oportunidadeRes] = await Promise.all([ API.get(`/api/dashboard/summary?${filtroQuery()}`), API.get(`/api/dashboard/evolution?dias=14&${filtroQuery()}`), API.get(`/api/dashboard/oportunidade?${filtroQuery()}`) ]); await Promise.all([carregarTiles(summary), carregarRankings(summary), carregarFunil(summary), carregarTabela()]); carregarEvolucaoDiaria(evolutionRes.evolution); carregarMaiorOportunidade(oportunidadeRes.oportunidade); } function invalidar() { carregado = false; clearTimeout(refreshTimer); refreshTimer = setTimeout(() => { if (document.querySelector('#tab-tratamento.active')) ativar(); }, 800); } async function ativar() { if (carregado) return; carregado = true; await Promise.all([carregarFacets(), atualizar(), carregarExports()]); } async function carregarFacets() { const facets = await API.get('/api/leads/facets'); const fill = (select, valores, prefixo, formatar = (v) => v) => { const atual = select.value; const primeira = select.options[0]; select.replaceChildren(primeira, ...valores.map((item) => h('option', { value: item.value }, `${formatar(item.value)} (${fmt.format(item.total)})`) )); select.value = atual; }; fill(el('f-uf'), facets.ufs); fill(el('f-interesse'), facets.interesses); fill(el('f-produto'), facets.produtos); const segSelect = el('f-segmento'); const atual = segSelect.value; segSelect.replaceChildren(segSelect.options[0], ...facets.segmentos.map((s) => h('option', { value: s.value }, `${s.value.replace(/_/g, ' ')} (${fmt.format(s.total)})`) )); segSelect.value = atual; el('dl-cidades').replaceChildren(...facets.cidades.map((c) => h('option', { value: c.value }))); } async function salvarMetaDiaria(valor) { const meta = Math.max(1, Number(valor) || 100); await API.put('/api/settings', { meta_diaria: meta }); const summary = await API.get(`/api/dashboard/summary?${filtroQuery()}`); carregarTiles(summary); } async function carregarTiles(s) { const pct = (n) => s.total ? ` (${((n / s.total) * 100).toFixed(1)}%)` : ''; el('stat-tiles').replaceChildren( tile(s.total, 'Leads no filtro', ''), tile(s.com_whatsapp, 'Com WhatsApp', pct(s.com_whatsapp), 't-good'), tile(s.p1, '🎯 Potencial Comercial Imediato', `${s.potencial_pct}% da base — atacar agora`, 't-good'), tile(s.p4, '📦 P4 — campanhas específicas', pct(s.p4)) ); el('stat-tiles').append( cardCobertura(s), cardPrioridades(s), cardMeta(s) ); function tile(valor, rotulo, detalhe, cls = '') { return h('div', { class: `tile ${cls}` }, h('div', { class: 'valor' }, fmt.format(valor)), h('div', { class: 'rotulo' }, rotulo), h('div', { class: 'detalhe' }, detalhe) ); } function cardCobertura(s) { const cidadeAtiva = filtroAtual().cidade; const titulo = cidadeAtiva ? `Cobertura em ${cidadeAtiva}` : 'Base Comercial — cobertura'; return h('div', { class: 'tile tile-wide' }, h('div', { class: 'rotulo' }, titulo), h('div', { class: 'progress-bar' }, h('div', { class: 'progress-fill', style: `width:${s.cobertura_pct}%` })), h('div', { class: 'detalhe' }, `${s.cobertura_pct}% trabalhada · ${fmt.format(s.exportados)} exportadas · ${fmt.format(s.disponiveis)} disponíveis`) ); } function cardPrioridades(s) { const ativa = filtroAtual().prioridade; const linhas = [ ['P1', s.p1, s.p1_pct], ['P2', s.p2, s.p2_pct], ['P3', s.p3, s.p3_pct], ['P4', s.p4, s.p4_pct] ]; return h('div', { class: 'tile tile-wide' }, h('div', { class: 'rotulo' }, 'Distribuição por prioridade'), h('div', { class: 'ranking', style: 'margin-top:6px' }, ...linhas.map(([label, total, p], i) => h('div', { class: `rank-row${ativa === String(i + 1) ? ' ativo' : ''}`, title: `${label}: ${fmt.format(total)} leads — clique para filtrar`, onclick: () => toggleFiltro('f-prioridade', i + 1) }, h('div', { class: 'rank-label' }, label), h('div', { class: 'rank-track' }, h('div', { class: 'rank-fill', style: `width:${Math.max(1.5, p)}%` })), h('div', { class: 'rank-value' }, `${p}%`) ))) ); } function cardMeta(s) { return h('div', { class: 'tile tile-wide' }, h('div', { class: 'row space-between' }, h('div', { class: 'rotulo' }, '🎯 Meta diária'), h('input', { type: 'number', min: '1', value: String(s.meta_diaria), class: 'meta-input', onchange: (e) => salvarMetaDiaria(e.target.value) }) ), h('div', { class: 'progress-bar' }, h('div', { class: 'progress-fill', style: `width:${Math.min(100, s.meta_pct)}%` })), h('div', { class: 'detalhe' }, `${fmt.format(s.hoje_exportados)} de ${fmt.format(s.meta_diaria)} hoje (${s.meta_pct}%) · ~${fmt.format(s.dias_estimados)} dias úteis restantes no ritmo atual`) ); } } async function carregarRankings(summary) { const cidadeAtiva = filtroAtual().cidade; el('rank-produtos-titulo').textContent = cidadeAtiva ? `Produtos recomendados em ${cidadeAtiva}` : 'Top produto recomendado'; const [cidades, segmentos, produtos] = await Promise.all([ API.get(`/api/dashboard/rankings?by=cidade&limit=10&${filtroQuery()}`), API.get(`/api/dashboard/rankings?by=segmento&limit=10&${filtroQuery()}`), API.get(`/api/dashboard/rankings?by=produto&limit=10&${filtroQuery()}`) ]); const f = filtroAtual(); const segmentoValuePorLabel = (label) => { const opts = [...el('f-segmento').options]; const porValor = opts.find((o) => o.value && o.value.replace(/_/g, ' ').toLowerCase() === label.toLowerCase()); const porTexto = opts.find((o) => o.textContent.toLowerCase().startsWith(label.toLowerCase())); return (porValor || porTexto)?.value || ''; }; renderRanking(el('rank-cidades'), cidades.ranking, (label) => toggleFiltro('f-cidade', label), null, (label) => f.cidade === label); renderRanking(el('rank-segmentos'), segmentos.ranking, (label) => toggleFiltro('f-segmento', segmentoValuePorLabel(label)), null, (label) => !!f.segmento && f.segmento === segmentoValuePorLabel(label)); renderRanking(el('rank-produtos'), produtos.ranking, (label) => toggleFiltro('f-produto', label), summary.total, (label) => f.produto === label); carregarOportunidadesDoDia(produtos.ranking, summary.total); } function carregarOportunidadesDoDia(produtosRanking, totalGeral) { const top3 = produtosRanking.slice(0, 3); if (!top3.length) { el('card-oportunidades-dia').replaceChildren( h('h2', {}, '🔥 Oportunidades do Dia'), h('div', { class: 'muted' }, 'Sem dados para este filtro.') ); return; } const produtoAtivo = filtroAtual().produto; el('card-oportunidades-dia').replaceChildren( h('h2', {}, '🔥 Oportunidades do Dia'), h('div', { class: 'ranking', style: 'margin-top:6px' }, ...top3.map((r) => { const p = totalGeral ? Math.round((r.total / totalGeral) * 100) : 0; return h('div', { class: `rank-row${produtoAtivo === r.label ? ' ativo' : ''}`, title: `${r.label}: ${fmt.format(r.total)} leads — clique para filtrar`, onclick: () => toggleFiltro('f-produto', r.label) }, h('div', { class: 'rank-label' }, r.label), h('div', { class: 'rank-track' }, h('div', { class: 'rank-fill', style: `width:${Math.max(1.5, p)}%` })), h('div', { class: 'rank-value' }, `${p}%`) ); })) ); } function dataBrCurta(diaIso) { const [, mes, dia] = diaIso.split('-'); return `${dia}/${mes}`; } function carregarEvolucaoDiaria(evolution) { if (!evolution.length) { el('card-evolucao-diaria').replaceChildren( h('h2', {}, '📈 Evolução Diária'), h('div', { class: 'muted' }, 'Nenhuma exportação registrada ainda.') ); return; } const max = Math.max(...evolution.map((d) => d.total)); el('card-evolucao-diaria').replaceChildren( h('h2', {}, '📈 Evolução Diária'), h('div', { class: 'muted', style: 'margin:2px 0 8px' }, 'Exportações por dia — única atividade com data registrada hoje.'), h('div', { class: 'ranking' }, ...evolution.map((d) => h('div', { class: 'rank-row nao-clicavel' }, h('div', { class: 'rank-label' }, dataBrCurta(d.dia)), h('div', { class: 'rank-track' }, h('div', { class: 'rank-fill', style: `width:${Math.max(1.5, (d.total / max) * 100)}%` })), h('div', { class: 'rank-value' }, fmt.format(d.total)) ))) ); } function carregarMaiorOportunidade(oportunidade) { if (!oportunidade) { el('card-maior-oportunidade').replaceChildren( h('h2', {}, '🏆 Maior Oportunidade'), h('div', { class: 'muted' }, 'Sem dados suficientes para este filtro.') ); return; } const f = filtroAtual(); const jaAplicado = f.cidade === oportunidade.cidade && f.produto === oportunidade.produto; el('card-maior-oportunidade').replaceChildren( h('h2', {}, '🏆 Maior Oportunidade'), h('div', { class: `row space-between oportunidade-clicavel${jaAplicado ? ' ativo' : ''}`, style: 'margin-top:6px', title: jaAplicado ? 'Clique para remover o filtro cidade + produto' : 'Clique para filtrar por esta cidade + produto', onclick: () => { el('f-cidade').value = jaAplicado ? '' : oportunidade.cidade; el('f-produto').value = jaAplicado ? '' : oportunidade.produto; atualizar(); } }, h('div', {}, h('div', { class: 'rotulo' }, 'Cidade'), h('div', { class: 'valor', style: 'font-size:20px' }, oportunidade.cidade)), h('div', {}, h('div', { class: 'rotulo' }, 'Produto'), h('div', { class: 'valor', style: 'font-size:20px' }, oportunidade.produto)), h('div', {}, h('div', { class: 'rotulo' }, 'Empresas'), h('div', { class: 'valor', style: 'font-size:20px' }, fmt.format(oportunidade.total))) ) ); } function renderRanking(box, ranking, onClick, totalGeral, isAtivo = () => false) { if (!ranking.length) { box.replaceChildren(h('div', { class: 'muted' }, 'Sem dados para este filtro.')); return; } const max = ranking[0].total; box.replaceChildren(...ranking.map((r) => { const pctGeral = totalGeral ? ` · ${Math.round((r.total / totalGeral) * 100)}%` : ''; const ativo = isAtivo(r.label); return h('div', { class: `rank-row${ativo ? ' ativo' : ''}`, title: `${r.label}: ${fmt.format(r.total)} leads (${fmt.format(r.p1)} prioridade 1) — ${ativo ? 'clique para remover o filtro' : 'clique para filtrar'}`, onclick: () => onClick(r.label) }, h('div', { class: 'rank-label' }, r.label), h('div', { class: 'rank-track' }, h('div', { class: 'rank-fill', style: `width:${Math.max(1.5, (r.total / max) * 100)}%` })), h('div', { class: 'rank-value' }, `${fmt.format(r.total)}${pctGeral}`) ); })); } const FUNIL_ETAPAS = [ { key: 'total', label: 'Leads capturados' }, { key: 'disponiveis', label: 'Leads disponíveis' }, { key: 'exportados', label: 'Leads exportados' }, { key: null, label: 'Empresas contatadas' }, { key: null, label: 'Empresas interessadas' }, { key: null, label: 'Propostas enviadas' }, { key: null, label: 'Clientes convertidos' } ]; function carregarFunil(s) { const base = s.total || 1; el('card-funil').replaceChildren( h('h2', {}, 'Evolução Comercial'), h('div', { class: 'funil' }, ...FUNIL_ETAPAS.map((etapa) => { const pendente = etapa.key === null; const valor = pendente ? null : s[etapa.key]; const largura = pendente ? 0 : Math.max(1.5, (valor / base) * 100); return h('div', { class: `funil-etapa${pendente ? ' pendente' : ''}`, title: pendente ? 'Aguardando integração com CRM' : '' }, h('div', { class: 'funil-label' }, etapa.label), h('div', { class: 'funil-barra' }, h('div', { class: 'funil-fill', style: `width:${largura}%` })), h('div', { class: 'funil-valor' }, pendente ? '—' : fmt.format(valor)) ); })) ); } async function carregarTabela() { const data = await API.get(`/api/leads?${filtroQuery({ page, pageSize: 50 })}`); totalPages = Math.max(1, Math.ceil(data.total / data.pageSize)); el('leads-total').textContent = `— ${fmt.format(data.total)} encontrados`; el('pg-info').textContent = `página ${data.page} de ${fmt.format(totalPages)}`; el('pg-prev').disabled = page <= 1; el('pg-next').disabled = page >= totalPages; const tbody = document.querySelector('#tabela-leads tbody'); if (!data.rows.length) { tbody.replaceChildren(h('tr', {}, h('td', { colspan: '10', class: 'muted' }, 'Nenhum lead com este filtro.'))); return; } const abrirWhatsAppLead = async (event, lead) => { event.stopPropagation(); if (!lead.whatsapp) { toast('Este lead não tem WhatsApp cadastrado'); return; } let leadCompleto = lead; try { leadCompleto = await API.get(`/api/leads/${lead.id}`); } catch { leadCompleto = lead; } const settingsButton = el('btn-settings'); settingsButton.click(); setTimeout(() => { window.GTISelecionarLeadManual?.(leadCompleto); el('manual-numero').value = leadCompleto.whatsapp || lead.whatsapp; el('manual-canal-naooficial').checked = true; el('manual-canal-naooficial').dispatchEvent(new Event('change')); el('manual-mensagem').value = `Olá ${leadCompleto.empresa || lead.empresa}! Somos do Grupo GTI. Podemos conversar?`; el('manual-status').textContent = ''; }, 250); }; tbody.replaceChildren(...data.rows.map((lead) => h('tr', { onclick: () => abrirLead(lead.id) }, h('td', { class: 'acao-col' }, h('button', { class: 'whatsapp-action', title: 'Enviar WhatsApp para este lead', onclick: (event) => abrirWhatsAppLead(event, lead) }, '☎')), h('td', { class: 'empresa-cell', title: lead.legal_name || lead.empresa }, lead.empresa), h('td', {}, lead.whatsapp || '—'), h('td', {}, lead.cidade), h('td', {}, lead.segmento_label || lead.segmento), h('td', {}, h('span', { class: `badge prioridade-${lead.prioridade}` }, `P${lead.prioridade}`)), h('td', { class: 'num' }, String(lead.score)), h('td', {}, h('span', { class: `badge ${lead.classificacao}` }, CLASSIFICACAO_LABELS[lead.classificacao] || lead.classificacao)), h('td', { class: 'produto-cell' }, produtoCell(lead.produto_principal, lead.afinidade)), h('td', {}, lead.exported_at ? '📤 sim' : '—') ))); } const AFINIDADE_TABELA_LABELS = { alta: 'Alta aderência', media: 'Média aderência', baixa: 'Baixa aderência' }; function produtoCell(produto, afinidade) { if (!produto) return '—'; const label = AFINIDADE_TABELA_LABELS[afinidade]; return label ? `${produto} · ${label}` : produto; } const OBSERVACAO_POR_PRIORIDADE = { 1: 'Empresa apta para abordagem imediata.', 2: 'Aguardar a janela de trabalho desta semana.', 3: 'Abordar quando não houver leads de prioridade mais alta.', 4: 'Reservar para campanhas específicas — baixa prioridade comercial.' }; const AFINIDADE_LABELS = { alta: 'nicho de alta aderência', media: 'nicho de média aderência', baixa: 'nicho de baixa aderência' }; function montarResumoComercial(lead) { const potencial = Math.max(1, Math.min(5, Math.round((lead.score_comercial || 0) / 20))); const pontosFortes = []; if (lead.telefone_valido) pontosFortes.push('WhatsApp válido'); if (lead.cnpj_valido) pontosFortes.push('CNPJ válido'); if (lead.website) pontosFortes.push('Website'); if (lead.email) pontosFortes.push('E-mail cadastrado'); if (lead.afinidade) pontosFortes.push(AFINIDADE_LABELS[lead.afinidade] || 'nicho identificado'); return h('div', { class: 'resumo-comercial' }, h('h3', {}, 'Resumo comercial'), h('div', { class: 'row space-between' }, h('span', { class: 'estrelas', title: `Potencial comercial: ${potencial}/5` }, estrelas(potencial)), h('span', { class: `badge prioridade-${lead.prioridade}` }, PRIORIDADE_LABELS[lead.prioridade] || `P${lead.prioridade}`) ), lead.produtos && lead.produtos.length ? h('div', {}, h('div', { class: 'muted', style: 'margin:8px 0 4px' }, 'Produtos recomendados'), ...lead.produtos.map((p) => h('div', {}, h('span', { class: 'estrelas' }, estrelas(p.estrelas)), ` ${p.nome}`))) : h('div', { class: 'muted', style: 'margin-top:8px' }, 'Nenhum produto sugerido para este nicho.'), pontosFortes.length ? h('ul', { class: 'pontos-fortes', style: 'margin-top:10px' }, ...pontosFortes.map((p) => h('li', {}, `✓ ${p}`))) : null, h('p', { class: 'muted', style: 'margin:10px 0 0' }, OBSERVACAO_POR_PRIORIDADE[lead.prioridade] || '') ); } function montarScoreBreakdown(lead) { const nichoPontos = lead.afinidade === 'alta' ? '+40' : lead.afinidade === 'baixa' ? '+20' : '+30'; const qualidade = [ [lead.telefone_valido, 'WhatsApp válido', '+30'], [lead.cnpj_valido, 'CNPJ válido', '+20'], [lead.empresa_valida, 'Empresa válida', '+20'], [!!lead.cidade, 'Cidade identificada', '+5'], [!!lead.segmento, 'Nicho identificado', '+10'], [!!lead.cnae, 'CNAE identificado', '+10'], [!!lead.website, 'Website informado', '+5'] ]; const comercial = [ [true, `Nicho ${AFINIDADE_TABELA_LABELS[lead.afinidade] || 'de aderência não identificada'}`, nichoPontos], [!!lead.website, 'Website informado', '+15'], [!!lead.email, 'E-mail informado', '+10'], [lead.telefone_valido, 'WhatsApp válido', '+15'], [lead.cnpj_valido, 'CNPJ válido', '+20'] ]; const bloco = (itens, titulo, valorTotal) => h('div', {}, h('div', { class: 'row space-between', style: 'margin-top:10px' }, h('strong', {}, titulo), h('span', {}, String(valorTotal))), h('ul', { class: 'score-breakdown' }, ...itens.map(([ok, label, pontos]) => h('li', { class: ok ? 'ok' : 'nok' }, `${ok ? '✓' : '✗'} ${label} (${pontos})`))) ); return h('div', { class: 'score-breakdown-wrap' }, bloco(qualidade, 'Score Qualidade', lead.score_qualidade), bloco(comercial, 'Score Comercial', lead.score_comercial) ); } async function abrirLead(id) { const lead = await API.get(`/api/leads/${id}`); const linha = (dt, dd) => [h('dt', {}, dt), h('dd', {}, dd || '—')]; el('lead-detalhe').replaceChildren( h('h2', {}, lead.empresa), montarResumoComercial(lead), montarScoreBreakdown(lead), h('dl', { class: 'detalhe-grid', style: 'margin-top:14px' }, linha('Razão social', lead.legal_name), linha('WhatsApp', lead.whatsapp), linha('Cidade/UF', `${lead.cidade}/${lead.uf}`), linha('Endereço', lead.endereco), linha('Nicho', lead.segmento_label || lead.segmento), linha('Interesse', lead.interesse), linha('CNPJ', lead.cnpj), linha('CNAE', lead.cnae), linha('Score geral', `${lead.score} (${CLASSIFICACAO_LABELS[lead.classificacao] || lead.classificacao})`), linha('Dores', (lead.pains || []).join(', ')), linha('Origem', lead.origem), linha('Capturado em', dataBr(lead.captured_at || lead.created_at)), linha('Exportado em', lead.exported_at ? dataBr(lead.exported_at) : 'ainda não'), linha('Exportações', lead.exports.length ? lead.exports.map((e) => `#${e.id} ${e.tipo}`).join(', ') : 'nenhuma') ), h('div', { class: 'row space-between', style: 'margin-top:14px' }, h('button', { class: 'btn-ghost', onclick: () => el('modal-lead').close() }, 'Fechar') ) ); el('modal-lead').showModal(); } // ---------- exportação ---------- const EXPORT_ATALHOS = [ { label: 'Só P1', set: () => { el('f-prioridade').value = '1'; } }, { label: 'Só ERP', set: () => { el('f-produto').value = 'ERP'; } }, { label: 'Só G-Chat', set: () => { el('f-produto').value = 'G-Chat'; } }, { label: 'Só Sistema de Ponto', set: () => { el('f-produto').value = 'Sistema de Ponto'; } }, { label: 'Não exportados', set: () => { el('f-exportado').value = 'nao'; } } ]; async function atualizarRecapExport() { const { total } = await API.get(`/api/dashboard/summary?${filtroQuery()}`); el('export-recap').textContent = `${fmt.format(total)} leads · ${descreverFiltro()}`; return total; } function renderAtalhosExport() { el('export-atalhos').replaceChildren(...EXPORT_ATALHOS.map((atalho) => h('button', { type: 'button', class: 'chip', onclick: async () => { atalho.set(); await atualizar(); await atualizarRecapExport(); } }, atalho.label))); } async function abrirModalExport() { const total = await atualizarRecapExport(); if (!total) { toast('Nenhum lead com este filtro — ajuste os filtros antes de exportar'); return; } renderAtalhosExport(); const { settings } = await API.get('/api/settings'); el('zp-pipeline').value = settings['zpro.pipelineId'] || 1; el('zp-stage').value = settings['zpro.stageId'] || 1; el('zp-responsible').value = settings['zpro.responsibleId'] || 15; el('modal-export').showModal(); } async function confirmarExport(event) { event.preventDefault(); const tipo = document.querySelector('input[name="export-tipo"]:checked').value; const body = { tipo, filtro: filtroAtual(), params: { marcarExportado: el('export-marcar').checked, pipelineId: Number(el('zp-pipeline').value) || undefined, stageId: Number(el('zp-stage').value) || undefined, responsibleId: Number(el('zp-responsible').value) || undefined, mensagem: tipo === 'zpro-bulk' ? el('export-mensagem').value : undefined } }; try { const { jobId, totalEstimado } = await API.post('/api/exports', body); el('modal-export').close(); toast(`Gerando arquivos para ${fmt.format(totalEstimado)} leads...`); API.watchJob(jobId, { done() { toast('✅ Exportação pronta! Baixe os arquivos na lista abaixo.'); carregarExports(); atualizar(); }, error(e) { toast(`❌ Erro na exportação: ${e.message}`); carregarExports(); } }); } catch (error) { toast(`Erro: ${error.message}`); } } async function carregarExports() { const { exports } = await API.get('/api/exports?limit=12'); const box = el('historico-exports'); if (!exports.length) { box.replaceChildren(h('div', { class: 'muted' }, 'Nenhuma exportação gerada ainda.')); return; } const tipoLabel = { excel: '📊 Planilha', 'zpro-oportunidades': '🗂️ Z-PRO Oportunidades', 'zpro-bulk': '📨 Z-PRO Disparo em massa', legado: '📦 Importação antiga (Z-PRO)' }; box.replaceChildren(...exports.map((e) => h('div', { class: 'item' }, h('div', {}, h('div', {}, h('strong', {}, `#${e.id} · ${tipoLabel[e.tipo] || e.tipo}`), ` · ${fmt.format(e.total_leads)} leads`), h('div', { class: 'sub' }, `${dataBr(e.created_at)} · ${e.status}${e.error ? ` · ${e.error.slice(0, 60)}` : ''}`) ), h('div', { class: 'row' }, ...(e.status === 'done' && e.tipo !== 'legado' ? e.arquivos.map((nome) => h('a', { class: 'btn-ghost', href: `/api/exports/${e.id}/download?arquivo=${encodeURIComponent(nome)}` }, `⬇ ${nome.split('.').pop().toUpperCase()}`)) : [])) ))); } // ---------- eventos ---------- function bindEvents() { let debounce = null; for (const input of document.querySelectorAll('#filtros [data-filtro]')) { input.addEventListener(input.tagName === 'SELECT' ? 'change' : 'input', () => { clearTimeout(debounce); debounce = setTimeout(atualizar, input.tagName === 'SELECT' ? 0 : 350); }); } el('btn-limpar-filtros').addEventListener('click', () => { for (const input of document.querySelectorAll('#filtros [data-filtro]')) input.value = ''; atualizar(); }); el('pg-prev').addEventListener('click', () => { page -= 1; carregarTabela(); }); el('pg-next').addEventListener('click', () => { page += 1; carregarTabela(); }); el('btn-exportar').addEventListener('click', abrirModalExport); el('form-export').addEventListener('submit', confirmarExport); for (const radio of document.querySelectorAll('input[name="export-tipo"]')) { radio.addEventListener('change', () => { el('export-mensagem-wrap').classList.toggle('hidden', radio.value !== 'zpro-bulk'); el('export-zpro-params').classList.toggle('hidden', radio.value === 'excel'); }); } el('export-zpro-params').classList.add('hidden'); } return { ativar, invalidar, bindEvents }; })(); window.Tratamento = Tratamento;