/**
* Central Operacional — Operational feed
* Spec 041 · OF-FE-001 · desk.ligbox.com.br (aba messages → Central Operacional)
*/
(function () {
'use strict';
let host = null;
let stats = null;
let events = [];
let selectedId = null;
let channel = 'all';
let priority = '';
let searchQ = '';
let detailTab = 'conversation';
let replyMode = 'reply';
function esc(s) {
return String(s ?? '').replace(/&/g, '&').replace(//g, '>');
}
function fmtRel(iso) {
if (!iso) return '—';
const diff = (Date.now() - new Date(iso).getTime()) / 60000;
if (diff < 1) return 'agora';
if (diff < 60) return `${Math.round(diff)} min atrás`;
if (diff < 1440) return `${Math.round(diff / 60)} h atrás`;
return new Date(iso).toLocaleDateString('pt-PT');
}
function fmtSla(sec) {
const m = Math.floor(sec / 60);
const s = sec % 60;
return `${m}:${String(s).padStart(2, '0')}`;
}
function channelIcon(ch) {
const map = { whatsapp: '💬', email: '✉', voice: '📞', tickets: '🎫', agents: '🤖', internal: '📋', alerts: '⚠', sms: '📱', telegram: '✈', clients: '👤' };
return map[ch] || '•';
}
async function loadStats() {
const r = await fetchWithTimeout('/api/v1/ops-inbox/stats', { headers: authHeaders() });
if (!r.ok) throw new Error(String(r.status));
stats = await r.json();
}
async function loadEvents() {
const qs = new URLSearchParams({ channel, limit: '128' });
if (priority) qs.set('priority', priority);
if (searchQ) qs.set('q', searchQ);
const r = await fetchWithTimeout(`/api/v1/ops-inbox/events?${qs}`, { headers: authHeaders() });
if (!r.ok) throw new Error(String(r.status));
const data = await r.json();
events = data.events || [];
}
async function loadEvent(id) {
const r = await fetchWithTimeout(`/api/v1/ops-inbox/events/${encodeURIComponent(id)}`, { headers: authHeaders() });
if (!r.ok) throw new Error(String(r.status));
const data = await r.json();
return data.event;
}
function kpisHtml() {
if (!stats) return '';
return `
Eventos hoje${stats.events_today}
Pendentes${stats.pending}
Críticos${stats.critical}
Aguardando você${stats.awaiting_you}
SLA médio${stats.sla_avg_pct}%
`;
}
function sidebarHtml() {
const channels = stats?.channels || [];
return `
`;
}
function eventCard(ev) {
const sel = selectedId === ev.id ? ' selected' : '';
const pri = ev.priority === 'high' || ev.priority === 'critical'
? `${esc(ev.priority)}` : '';
return `
${channelIcon(ev.channel)} ${esc(ev.title)}
${esc(ev.preview)}
${pri}
${(ev.tags || []).map((t) => `${esc(t)}`).join('')}
${fmtRel(ev.created_at)}
${ev.assignee ? `${esc(ev.assignee)}` : ''}
`;
}
function detailHtml(ev) {
if (!ev) {
return ``;
}
const slaPct = Math.min(100, Math.round((ev.sla_remaining_sec / (ev.sla_minutes * 60)) * 100));
let tabContent = '';
if (detailTab === 'conversation') {
const msgs = (ev.messages || []).map((m) => {
const cls = m.author_type === 'user' ? 'user' : m.author_type === 'agent' ? 'agent'
: m.author_type === 'system' ? 'system' : 'operator';
return `${esc(m.author_label)}
${esc(m.body)}
`;
}).join('');
tabContent = `
${msgs || '
Sem mensagens
'}
`;
} else if (detailTab === 'details') {
tabContent = `
- Canal
- ${esc(ev.channel)}
- Tipo
- ${esc(ev.event_type)}
- Estado
- ${esc(ev.status)}
- Responsável
- ${esc(ev.assignee || '—')}
`;
} else {
tabContent = `Histórico simulado — integração futura.
`;
}
return `
${esc(ev.status)}
${esc(ev.contact_name || ev.title)}
${esc(ev.channel)}
${tabContent}
Informações do contacto
${esc(ev.contact_company || '—')}
CNPJ: ${esc(ev.contact_cnpj || '—')}
ID: ${esc(ev.contact_client_id || '—')}
SLA: ${ev.sla_minutes} min · Restante: ${fmtSla(ev.sla_remaining_sec)}
`;
}
let currentEvent = null;
async function renderDetail() {
const detailHost = host?.querySelector('[data-of-detail]');
if (!detailHost || !selectedId) return;
try {
currentEvent = await loadEvent(selectedId);
detailHost.innerHTML = detailHtml(currentEvent);
bindDetailEvents();
} catch (e) {
detailHost.innerHTML = `${esc(e.message)}
`;
}
}
function render() {
if (!host) return;
host.innerHTML = `
${kpisHtml()}
${sidebarHtml()}
${events.length} resultados · Mais recentes
${events.map(eventCard).join('')}
${detailHtml(currentEvent)}
`;
bindEvents();
if (selectedId) renderDetail();
}
function bindDetailEvents() {
host.querySelectorAll('[data-of-tab]').forEach((btn) => {
btn.addEventListener('click', () => { detailTab = btn.dataset.ofTab; renderDetail(); });
});
host.querySelectorAll('[data-of-reply]').forEach((btn) => {
btn.addEventListener('click', () => { replyMode = btn.dataset.ofReply; renderDetail(); });
});
host.querySelector('[data-of-send]')?.addEventListener('click', async () => {
const body = host.querySelector('[data-of-msg]')?.value?.trim();
if (!body || !selectedId) return;
try {
await fetchWithTimeout(`/api/v1/ops-inbox/events/${encodeURIComponent(selectedId)}/messages`, {
method: 'POST',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify({ body, note_type: replyMode }),
});
renderDetail();
} catch (e) {
window.alert(e.message);
}
});
host.querySelector('[data-of-resolve]')?.addEventListener('click', async () => {
await fetchWithTimeout(`/api/v1/ops-inbox/events/${encodeURIComponent(selectedId)}`, {
method: 'PATCH',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify({ status: 'resolved' }),
});
await refresh();
});
host.querySelector('[data-of-assign]')?.addEventListener('click', () => {
const name = window.prompt('Atribuir a:');
if (!name) return;
fetchWithTimeout(`/api/v1/ops-inbox/events/${encodeURIComponent(selectedId)}`, {
method: 'PATCH',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify({ assignee: name }),
}).then(refresh).catch((e) => window.alert(e.message));
});
}
function bindEvents() {
host.querySelectorAll('[data-of-channel]').forEach((btn) => {
btn.addEventListener('click', async () => {
channel = btn.dataset.ofChannel;
await refresh();
});
});
host.querySelectorAll('[data-of-priority]').forEach((btn) => {
btn.addEventListener('click', async () => {
priority = btn.dataset.ofPriority;
await refresh();
});
});
host.querySelector('[data-of-priority-sel]')?.addEventListener('change', async (e) => {
priority = e.target.value;
await refresh();
});
host.querySelector('[data-of-search]')?.addEventListener('input', (e) => {
searchQ = e.target.value;
clearTimeout(host._ofTimer);
host._ofTimer = setTimeout(refresh, 300);
});
host.querySelectorAll('[data-of-event]').forEach((card) => {
card.addEventListener('click', () => {
selectedId = card.dataset.ofEvent;
detailTab = 'conversation';
render();
});
});
}
async function refresh() {
try {
await loadEvents();
if (!selectedId && events.length) selectedId = events[0].id;
render();
} catch (e) {
host.innerHTML = `Erro: ${esc(e.message)}
`;
}
}
async function paint(container) {
host = container;
if (typeof canManageUsers === 'function' && !canManageUsers()) {
host.innerHTML = 'Sem permissão
';
return;
}
host.innerHTML = 'Carregando Central Operacional…
';
try {
await loadStats();
await loadEvents();
if (!selectedId && events.length) selectedId = events[0].id;
render();
} catch (e) {
host.innerHTML = `Erro: ${esc(e.message)}
`;
}
}
window.DeskOperationalFeed = { paint };
})();