/** * 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 `

Seleccione um evento

`; } 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 = `

Central Operacional

Operational feed — mensagens, solicitações e eventos de todos os canais

${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 }; })();