Adds an admin-only "Numeros compartilhados entre leads" section to the settings modal, wired to the Task 4 shared-numbers routes: lists detected groups, lets an admin liberar (with an inline motivo input) or bloquear de novo. Updates the two frontend VM-based test files' default API mocks to answer GET /api/outreach/shared-numbers so existing settings-modal tests keep passing, and adds a new interaction test covering the liberar round trip.
487 lines
21 KiB
JavaScript
487 lines
21 KiB
JavaScript
/* Shell da Máquina Comercial Pessoal: sessão, abas, WhatsApp e configurações. */
|
|
(async function init() {
|
|
let usuarioAtual;
|
|
try {
|
|
({ user: usuarioAtual } = await API.get('/api/auth/me'));
|
|
} catch {
|
|
window.location.href = '/login.html';
|
|
return;
|
|
}
|
|
|
|
const isAdmin = usuarioAtual.role === 'admin';
|
|
let manualLeadContext = null;
|
|
let whatsappPollTimer = null;
|
|
let whatsappGeneration = 0;
|
|
let whatsappPollBusyGeneration = null;
|
|
let lastQrExpiry = null;
|
|
const pollingStates = new Set(['STARTING', 'QR_REQUIRED', 'AUTHENTICATING', 'RECONNECTING']);
|
|
const stateLabels = {
|
|
STOPPED: 'Parado', STARTING: 'Iniciando', QR_REQUIRED: 'QR necessário',
|
|
AUTHENTICATING: 'Autenticando', READY: 'Conectado', RECONNECTING: 'Reconectando',
|
|
DISCONNECTED: 'Desconectado', ERROR: 'Erro'
|
|
};
|
|
|
|
el('usuario-logado').textContent = `${usuarioAtual.username} (${isAdmin ? 'admin' : 'comercial'})`;
|
|
el('btn-usuarios').classList.toggle('hidden', !isAdmin);
|
|
for (const control of document.querySelectorAll('.admin-only')) control.classList.toggle('hidden', !isAdmin);
|
|
|
|
el('btn-sair').addEventListener('click', async () => {
|
|
const button = el('btn-sair');
|
|
await comCarregando(button, async () => {
|
|
await API.post('/api/auth/logout');
|
|
window.location.href = '/login.html';
|
|
});
|
|
});
|
|
|
|
async function carregarListaUsuarios() {
|
|
const { users } = await API.get('/api/users');
|
|
el('lista-usuarios').replaceChildren(...users.map((user) => h('div', { class: 'row space-between' },
|
|
h('span', {}, user.username),
|
|
h('span', { class: 'muted' }, user.role === 'admin' ? 'Administrador' : 'Comercial'))));
|
|
}
|
|
|
|
el('btn-usuarios').addEventListener('click', async () => {
|
|
const button = el('btn-usuarios');
|
|
el('modal-usuarios').showModal();
|
|
await comCarregando(button, async () => {
|
|
try { await carregarListaUsuarios(); } catch (error) { toast(`Erro: ${error.message}`); }
|
|
});
|
|
});
|
|
el('btn-fechar-usuarios').addEventListener('click', () => el('modal-usuarios').close());
|
|
el('form-novo-usuario').addEventListener('submit', async (event) => {
|
|
event.preventDefault();
|
|
const form = el('form-novo-usuario');
|
|
const button = form.querySelector('button[type=submit]');
|
|
await comCarregando(button, async () => {
|
|
try {
|
|
await API.post('/api/users', {
|
|
username: el('novo-usuario-username').value,
|
|
password: el('novo-usuario-password').value,
|
|
role: el('novo-usuario-role').value
|
|
});
|
|
el('novo-usuario-username').value = '';
|
|
el('novo-usuario-password').value = '';
|
|
await carregarListaUsuarios();
|
|
toast('Usuário criado');
|
|
} catch (error) { toast(`Erro: ${error.message}`); }
|
|
});
|
|
});
|
|
|
|
for (const tab of document.querySelectorAll('.tab')) {
|
|
tab.addEventListener('click', () => {
|
|
for (const item of document.querySelectorAll('.tab')) {
|
|
item.classList.toggle('active', item === tab);
|
|
item.setAttribute('aria-selected', item === tab ? 'true' : 'false');
|
|
}
|
|
for (const panel of document.querySelectorAll('.tab-panel')) {
|
|
panel.classList.toggle('active', panel.id === `tab-${tab.dataset.tab}`);
|
|
}
|
|
if (tab.dataset.tab === 'tratamento') Tratamento.ativar();
|
|
if (tab.dataset.tab === 'campanhas') Campanhas.ativar();
|
|
});
|
|
}
|
|
|
|
function stopWhatsAppPolling() {
|
|
clearTimeout(whatsappPollTimer);
|
|
whatsappPollTimer = null;
|
|
}
|
|
|
|
function ownsWhatsAppGeneration(generation) {
|
|
return generation === whatsappGeneration && el('modal-settings').open;
|
|
}
|
|
|
|
function nextWhatsAppGeneration() {
|
|
stopWhatsAppPolling();
|
|
whatsappGeneration += 1;
|
|
return whatsappGeneration;
|
|
}
|
|
|
|
function scheduleWhatsAppPolling(state, generation) {
|
|
stopWhatsAppPolling();
|
|
if (!ownsWhatsAppGeneration(generation)) return;
|
|
const delay = pollingStates.has(state) ? 1000 : 5000;
|
|
whatsappPollTimer = setTimeout(() => refreshWhatsAppStatus(true, generation), delay);
|
|
}
|
|
|
|
function renderWhatsAppStatus(status, generation) {
|
|
if (!ownsWhatsAppGeneration(generation)) return false;
|
|
const state = status?.state || 'STOPPED';
|
|
const stateEl = el('whatsapp-state');
|
|
stateEl.textContent = stateLabels[state] || state;
|
|
stateEl.className = `chip connection-state state-${state.toLowerCase()}`;
|
|
const connectedNumber = status?.number || status?.numero || status?.phone || status?.connectedNumber;
|
|
el('whatsapp-number').textContent = connectedNumber ? `Número conectado: ${connectedNumber}` : '';
|
|
el('whatsapp-number').classList.toggle('hidden', !connectedNumber);
|
|
el('whatsapp-error').textContent = status?.error || status?.erro || '';
|
|
el('whatsapp-error').classList.toggle('hidden', !(status?.error || status?.erro));
|
|
if (state !== 'QR_REQUIRED') hideQr();
|
|
return true;
|
|
}
|
|
|
|
function hideQr() {
|
|
el('whatsapp-qr').removeAttribute('src');
|
|
el('whatsapp-qr').classList.add('hidden');
|
|
el('whatsapp-qr-placeholder').classList.remove('hidden');
|
|
el('whatsapp-qr-expiry').textContent = '';
|
|
lastQrExpiry = null;
|
|
}
|
|
|
|
async function loadQr(generation) {
|
|
if (!isAdmin || !ownsWhatsAppGeneration(generation)) return;
|
|
try {
|
|
const { qr, expiresAt } = await API.get('/api/whatsapp/qr');
|
|
if (!ownsWhatsAppGeneration(generation)) return;
|
|
if (expiresAt !== lastQrExpiry || !el('whatsapp-qr').getAttribute('src')) {
|
|
el('whatsapp-qr').src = qr;
|
|
lastQrExpiry = expiresAt;
|
|
}
|
|
el('whatsapp-qr').classList.remove('hidden');
|
|
el('whatsapp-qr-placeholder').classList.add('hidden');
|
|
el('whatsapp-qr-expiry').textContent = expiresAt ? `Expira em ${dataBr(expiresAt)}` : '';
|
|
} catch (error) {
|
|
if (!ownsWhatsAppGeneration(generation)) return;
|
|
el('whatsapp-qr-expiry').textContent = error.message;
|
|
}
|
|
}
|
|
|
|
async function refreshWhatsAppStatus(fromPoll = false, generation = whatsappGeneration) {
|
|
if (!ownsWhatsAppGeneration(generation) || whatsappPollBusyGeneration === generation) return;
|
|
whatsappPollBusyGeneration = generation;
|
|
try {
|
|
const status = await API.get('/api/whatsapp/status');
|
|
if (!renderWhatsAppStatus(status, generation)) return;
|
|
if (status.state === 'QR_REQUIRED') await loadQr(generation);
|
|
scheduleWhatsAppPolling(status.state || 'STOPPED', generation);
|
|
} catch (error) {
|
|
if (!renderWhatsAppStatus({ state: 'ERROR', error: error.message }, generation)) return;
|
|
if (!fromPoll) toast(`WhatsApp: ${error.message}`);
|
|
scheduleWhatsAppPolling('ERROR', generation);
|
|
} finally {
|
|
if (whatsappPollBusyGeneration === generation) whatsappPollBusyGeneration = null;
|
|
}
|
|
}
|
|
|
|
async function whatsappAction(path) {
|
|
const generation = nextWhatsAppGeneration();
|
|
try {
|
|
const result = await API.post(path);
|
|
if (!ownsWhatsAppGeneration(generation)) return;
|
|
const status = result.status || await API.get('/api/whatsapp/status');
|
|
if (!renderWhatsAppStatus(status, generation)) return;
|
|
if (status.state === 'QR_REQUIRED') await loadQr(generation);
|
|
scheduleWhatsAppPolling(status.state || 'STOPPED', generation);
|
|
} catch (error) {
|
|
if (!ownsWhatsAppGeneration(generation)) return;
|
|
toast(`WhatsApp: ${error.message}`);
|
|
await refreshWhatsAppStatus(false, generation);
|
|
}
|
|
}
|
|
|
|
el('btn-whatsapp-connect').addEventListener('click', () => comCarregando(el('btn-whatsapp-connect'), () => whatsappAction('/api/whatsapp/connect')));
|
|
el('btn-whatsapp-refresh-qr').addEventListener('click', () => comCarregando(el('btn-whatsapp-refresh-qr'), () => whatsappAction('/api/whatsapp/refresh-qr')));
|
|
el('btn-whatsapp-disconnect').addEventListener('click', () => comCarregando(el('btn-whatsapp-disconnect'), () => whatsappAction('/api/whatsapp/disconnect')));
|
|
el('btn-whatsapp-logout').addEventListener('click', async () => {
|
|
if (!window.confirm('Sair do WhatsApp e apagar a sessão vinculada neste computador?')) return;
|
|
await comCarregando(el('btn-whatsapp-logout'), () => whatsappAction('/api/whatsapp/logout'));
|
|
});
|
|
|
|
function renderLeadContext() {
|
|
if (!manualLeadContext) {
|
|
el('manual-lead-context').textContent = 'Envio avulso, sem lead selecionado.';
|
|
return;
|
|
}
|
|
const lead = manualLeadContext;
|
|
el('manual-lead-context').textContent = [
|
|
`Lead: ${lead.empresa || lead.legal_name || 'Empresa não informada'}`,
|
|
lead.cidade ? `${lead.cidade}${lead.uf ? `/${lead.uf}` : ''}` : '',
|
|
lead.segmento_label || lead.segmento || '',
|
|
lead.cnpj || ''
|
|
].filter(Boolean).join(' · ');
|
|
}
|
|
|
|
window.MaquinaSelecionarLeadManual = (lead) => {
|
|
manualLeadContext = lead || null;
|
|
renderLeadContext();
|
|
};
|
|
|
|
window.MaquinaLimparLeadManual = () => {
|
|
manualLeadContext = null;
|
|
renderLeadContext();
|
|
};
|
|
el('btn-limpar-lead-manual').addEventListener('click', window.MaquinaLimparLeadManual);
|
|
|
|
async function carregarOutreach() {
|
|
const [{ settings }, { templates }, status] = await Promise.all([
|
|
API.get('/api/settings'), API.get('/api/outreach/templates'), API.get('/api/outreach/status')
|
|
]);
|
|
el('outreach-ativo').checked = settings['outreach.ativo'] === 'true';
|
|
el('outreach-limite').value = settings['outreach.mensagensPorDia'] || 10;
|
|
el('outreach-status').textContent = `Consumidos do limite hoje: ${status.enviadosHoje}/${status.mensagensPorDia}`;
|
|
|
|
if (!templates.length) {
|
|
el('outreach-templates').replaceChildren(h('p', { class: 'muted' },
|
|
'Nenhum template disponível. Eles aparecerão após configurar seus serviços.'));
|
|
} else {
|
|
el('outreach-templates').replaceChildren(...templates.map((template) => h('div', {
|
|
class: 'item outreach-template', 'data-produto': template.produto
|
|
},
|
|
h('div', { class: 'row space-between' },
|
|
h('strong', {}, template.produto),
|
|
h('label', { class: 'radio' },
|
|
h('input', { type: 'checkbox', class: 'outreach-template-ativo', checked: template.ativo ? '' : null }), ' Ativo')),
|
|
h('textarea', { class: 'outreach-template-mensagem', rows: 3 }, template.mensagem || ''))));
|
|
}
|
|
|
|
const sendStatusLabels = { pendente: 'Preparando', aceito: 'Aguardando confirmação', enviado: 'Enviado ao servidor', entregue: 'Entregue', lido: 'Lido', desconhecido: 'Resultado desconhecido', erro: 'Falhou' };
|
|
el('outreach-ultimos').replaceChildren(...status.ultimos.map((send) => h('div', { class: 'item' },
|
|
h('div', {}, `${sendStatusLabels[send.status] || send.status} · ${send.numero || send.empresa || 'número não informado'}`),
|
|
h('div', { class: 'sub' }, `${dataBr(send.sent_at)}${send.erro ? ` · ${send.erro}` : ''}`))));
|
|
if (!status.ultimos.length) el('outreach-ultimos').replaceChildren(h('p', { class: 'muted' }, 'Nenhum envio ainda.'));
|
|
}
|
|
|
|
async function carregarOptOuts() {
|
|
const { optOuts } = await API.get('/api/outreach/opt-outs');
|
|
const list = el('optout-list');
|
|
if (!optOuts.length) {
|
|
list.replaceChildren(h('p', { class: 'muted' }, 'Nenhum número bloqueado.'));
|
|
return;
|
|
}
|
|
list.replaceChildren(...optOuts.map((item) => {
|
|
let rowButton;
|
|
rowButton = h('button', {
|
|
class: 'btn-danger btn-sm', type: 'button',
|
|
onclick: () => comCarregando(rowButton, () => removerOptOut(item.numero))
|
|
}, 'Remover');
|
|
return h('div', { class: 'item opt-out-item' },
|
|
h('div', {}, h('strong', {}, item.numero),
|
|
h('div', { class: 'sub' }, `${item.motivo || 'Sem motivo'} · ${dataBr(item.created_at)}`)),
|
|
rowButton);
|
|
}));
|
|
}
|
|
|
|
async function removerOptOut(numero) {
|
|
try {
|
|
await API.delete(`/api/outreach/opt-outs/${encodeURIComponent(numero)}`);
|
|
el('optout-status').textContent = 'Número removido da lista.';
|
|
await carregarOptOuts();
|
|
} catch (error) { el('optout-status').textContent = `Erro: ${error.message}`; }
|
|
}
|
|
|
|
async function carregarNumerosCompartilhados() {
|
|
const { groups } = await API.get('/api/outreach/shared-numbers');
|
|
const list = el('shared-numbers-list');
|
|
if (!groups.length) {
|
|
list.replaceChildren(h('p', { class: 'muted' }, 'Nenhum número compartilhado detectado.'));
|
|
return;
|
|
}
|
|
list.replaceChildren(...groups.map((group) => {
|
|
const empresas = group.leads.map((lead) => lead.empresa).join(' · ');
|
|
if (group.liberado) {
|
|
let blockButton;
|
|
blockButton = h('button', {
|
|
class: 'btn-danger btn-sm', type: 'button',
|
|
onclick: () => comCarregando(blockButton, () => bloquearNumeroCompartilhado(group.numero))
|
|
}, 'Bloquear de novo');
|
|
return h('div', { class: 'item opt-out-item' },
|
|
h('div', {},
|
|
h('strong', {}, group.numero),
|
|
h('div', { class: 'sub' }, `${group.leads.length} leads: ${empresas}`),
|
|
h('div', { class: 'sub' }, `Liberado: ${group.motivo || 'sem motivo'}`)),
|
|
blockButton);
|
|
}
|
|
const motivoInput = h('input', { type: 'text', placeholder: 'Motivo da liberação' });
|
|
let liberarButton;
|
|
liberarButton = h('button', {
|
|
class: 'btn btn-sm', type: 'button',
|
|
onclick: () => comCarregando(liberarButton, () => liberarNumeroCompartilhado(group.numero, motivoInput.value.trim()))
|
|
}, 'Liberar');
|
|
return h('div', { class: 'item opt-out-item' },
|
|
h('div', {},
|
|
h('strong', {}, group.numero),
|
|
h('div', { class: 'sub' }, `${group.leads.length} leads: ${empresas}`)),
|
|
motivoInput, liberarButton);
|
|
}));
|
|
}
|
|
|
|
async function liberarNumeroCompartilhado(numero, motivo) {
|
|
if (!motivo) {
|
|
el('shared-numbers-status').textContent = 'Informe um motivo para liberar.';
|
|
return;
|
|
}
|
|
try {
|
|
await API.post(`/api/outreach/shared-numbers/${encodeURIComponent(numero)}/liberar`, { motivo });
|
|
el('shared-numbers-status').textContent = 'Número liberado.';
|
|
await carregarNumerosCompartilhados();
|
|
} catch (error) { el('shared-numbers-status').textContent = `Erro: ${error.message}`; }
|
|
}
|
|
|
|
async function bloquearNumeroCompartilhado(numero) {
|
|
try {
|
|
await API.delete(`/api/outreach/shared-numbers/${encodeURIComponent(numero)}/liberar`);
|
|
el('shared-numbers-status').textContent = 'Número bloqueado novamente.';
|
|
await carregarNumerosCompartilhados();
|
|
} catch (error) { el('shared-numbers-status').textContent = `Erro: ${error.message}`; }
|
|
}
|
|
|
|
el('btn-optout-add').addEventListener('click', async () => {
|
|
const button = el('btn-optout-add');
|
|
await comCarregando(button, async () => {
|
|
try {
|
|
await API.post('/api/outreach/opt-outs', {
|
|
numero: el('optout-numero').value.trim(), motivo: el('optout-motivo').value.trim()
|
|
});
|
|
el('optout-numero').value = '';
|
|
el('optout-motivo').value = '';
|
|
el('optout-status').textContent = 'Número adicionado à lista.';
|
|
await carregarOptOuts();
|
|
} catch (error) { el('optout-status').textContent = `Erro: ${error.message}`; }
|
|
});
|
|
});
|
|
|
|
el('btn-settings').addEventListener('click', async () => {
|
|
const button = el('btn-settings');
|
|
el('modal-settings').showModal();
|
|
const generation = nextWhatsAppGeneration();
|
|
await comCarregando(button, async () => {
|
|
try {
|
|
await Promise.all([carregarOutreach(), carregarOptOuts(), carregarNumerosCompartilhados(), refreshWhatsAppStatus(false, generation)]);
|
|
} catch (error) { toast(`Erro ao carregar configurações: ${error.message}`); }
|
|
});
|
|
});
|
|
|
|
el('btn-enviar-manual').addEventListener('click', async () => {
|
|
const button = el('btn-enviar-manual');
|
|
if (button.disabled) return;
|
|
button.disabled = true;
|
|
const statusEl = el('manual-status');
|
|
statusEl.classList.remove('status-ok', 'status-error');
|
|
statusEl.textContent = 'Enviando...';
|
|
try {
|
|
const result = await API.post('/api/outreach/enviar-manual', {
|
|
numero: el('manual-numero').value.trim(),
|
|
mensagem: el('manual-mensagem').value,
|
|
leadId: manualLeadContext?.id || null,
|
|
lead: manualLeadContext
|
|
});
|
|
const uncertain = result.status === 'resultado_desconhecido' || result.status === 'persistencia_incerta';
|
|
const warning = Boolean(result.aviso);
|
|
statusEl.textContent = uncertain || warning
|
|
? result.aviso
|
|
: result.ok
|
|
? `Aceito pelo WhatsApp; aguardando confirmação${result.messageId ? ` · ID ${result.messageId}` : ''}`
|
|
: result.erro;
|
|
statusEl.classList.toggle('status-ok', Boolean(result.ok) && !warning);
|
|
statusEl.classList.toggle('status-error', !result.ok || warning);
|
|
await carregarOutreach();
|
|
} catch (error) {
|
|
statusEl.textContent = `Erro: ${error.message}`;
|
|
statusEl.classList.add('status-error');
|
|
} finally {
|
|
button.disabled = false;
|
|
}
|
|
});
|
|
|
|
el('btn-toggle-outreach-log').addEventListener('click', () => {
|
|
const hidden = el('outreach-ultimos').classList.toggle('hidden');
|
|
el('btn-toggle-outreach-log').textContent = hidden ? 'Ver log de envios' : 'Ocultar log de envios';
|
|
});
|
|
|
|
el('btn-salvar-outreach').addEventListener('click', async () => {
|
|
const button = el('btn-salvar-outreach');
|
|
await comCarregando(button, async () => {
|
|
try {
|
|
for (const item of el('outreach-templates').querySelectorAll('.outreach-template')) {
|
|
await API.put(`/api/outreach/templates/${encodeURIComponent(item.dataset.produto)}`, {
|
|
mensagem: item.querySelector('.outreach-template-mensagem').value,
|
|
ativo: item.querySelector('.outreach-template-ativo').checked
|
|
});
|
|
}
|
|
await API.put('/api/settings', {
|
|
'outreach.ativo': el('outreach-ativo').checked ? 'true' : 'false',
|
|
'outreach.mensagensPorDia': el('outreach-limite').value
|
|
});
|
|
await carregarOutreach();
|
|
toast('Templates salvos');
|
|
} catch (error) { toast(`Erro: ${error.message}`); }
|
|
});
|
|
});
|
|
|
|
el('btn-alterar-senha').addEventListener('click', async () => {
|
|
const button = el('btn-alterar-senha');
|
|
const status = el('senha-status');
|
|
const novaSenha = el('senha-nova').value;
|
|
if (novaSenha !== el('senha-confirmacao').value) {
|
|
status.textContent = 'A confirmação não corresponde à nova senha.';
|
|
return;
|
|
}
|
|
await comCarregando(button, async () => {
|
|
try {
|
|
const result = await API.put('/api/auth/password', { senhaAtual: el('senha-atual').value, novaSenha });
|
|
el('senha-atual').value = '';
|
|
el('senha-nova').value = '';
|
|
el('senha-confirmacao').value = '';
|
|
status.textContent = 'Senha alterada. Entre novamente.';
|
|
if (result.relogin) window.location.href = '/login.html';
|
|
} catch (error) {
|
|
status.textContent = `Erro: ${error.message}`;
|
|
}
|
|
});
|
|
});
|
|
|
|
el('btn-salvar-settings').addEventListener('click', async (event) => {
|
|
event.preventDefault();
|
|
const button = el('btn-salvar-settings');
|
|
await comCarregando(button, async () => {
|
|
try {
|
|
await API.put('/api/settings', {
|
|
'outreach.ativo': el('outreach-ativo').checked ? 'true' : 'false',
|
|
'outreach.mensagensPorDia': el('outreach-limite').value
|
|
});
|
|
stopWhatsAppPolling();
|
|
el('modal-settings').close();
|
|
toast('Configurações salvas');
|
|
} catch (error) { toast(`Erro: ${error.message}`); }
|
|
});
|
|
});
|
|
|
|
el('modal-settings').addEventListener('close', nextWhatsAppGeneration);
|
|
el('btn-limpar-base').addEventListener('click', () => {
|
|
el('reset-base-senha').value = '';
|
|
el('reset-base-erro').classList.add('hidden');
|
|
stopWhatsAppPolling();
|
|
el('modal-settings').close();
|
|
el('modal-reset-base').showModal();
|
|
});
|
|
el('btn-cancelar-reset-base').addEventListener('click', () => el('modal-reset-base').close());
|
|
el('btn-confirmar-reset-base').addEventListener('click', async () => {
|
|
const button = el('btn-confirmar-reset-base');
|
|
const erroEl = el('reset-base-erro');
|
|
const senha = el('reset-base-senha').value;
|
|
if (!senha) {
|
|
erroEl.textContent = 'Informe a senha de segurança';
|
|
erroEl.classList.remove('hidden');
|
|
return;
|
|
}
|
|
await comCarregando(button, async () => {
|
|
try {
|
|
await API.post('/api/admin/reset', { senha });
|
|
el('modal-reset-base').close();
|
|
toast('Base de leads limpa com sucesso. Recarregando...');
|
|
setTimeout(() => window.location.reload(), 1200);
|
|
} catch (error) {
|
|
erroEl.textContent = `Erro: ${error.message}`;
|
|
erroEl.classList.remove('hidden');
|
|
}
|
|
});
|
|
});
|
|
|
|
for (const dialog of document.querySelectorAll('dialog')) {
|
|
dialog.addEventListener('click', (event) => { if (event.target === dialog) dialog.close(); });
|
|
}
|
|
|
|
renderLeadContext();
|
|
Tratamento.bindEvents();
|
|
Campanhas.bindEvents();
|
|
try { await Captura.init(); } catch (error) { toast(`Erro ao carregar: ${error.message}`, 8000); }
|
|
})();
|