Add Configuracoes review section for shared numbers

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.
This commit is contained in:
Gustavo
2026-07-31 16:33:03 -03:00
parent a6dce5c936
commit 18d427e2bd
4 changed files with 105 additions and 1 deletions
+7
View File
@@ -419,6 +419,13 @@
<div id="optout-list" class="lista"></div>
</details>
<details class="settings-section admin-only">
<summary>Números compartilhados entre leads</summary>
<p class="muted">Detectados automaticamente quando o mesmo número aparece em mais de um lead (ex.: contabilidade que registra o próprio telefone para clientes). Envio automático e manual ficam bloqueados até você revisar e liberar.</p>
<p id="shared-numbers-status" class="muted"></p>
<div id="shared-numbers-list" class="lista"></div>
</details>
<details class="settings-section zona-risco admin-only">
<summary>Zona de risco</summary>
<p class="muted">Apaga permanentemente todos os leads, exportações, capturas e extrações. Configurações e nichos não são afetados.</p>
+57 -1
View File
@@ -267,6 +267,62 @@
} 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 () => {
@@ -288,7 +344,7 @@
const generation = nextWhatsAppGeneration();
await comCarregando(button, async () => {
try {
await Promise.all([carregarOutreach(), carregarOptOuts(), refreshWhatsAppStatus(false, generation)]);
await Promise.all([carregarOutreach(), carregarOptOuts(), carregarNumerosCompartilhados(), refreshWhatsAppStatus(false, generation)]);
} catch (error) { toast(`Erro ao carregar configurações: ${error.message}`); }
});
});