const db = require('../database'); const { isGerente } = require('../middlewares/roles'); const presence = require('../presence'); /** * Resolve a lista de empresas para a consulta, respeitando a permissão do * usuário. Se vier empresaId e o usuário tiver acesso, usa só ela; senão usa * todas as empresas do usuário. */ function empresasPermitidas(req) { const minhas = (req.user && req.user.empresas) || []; const pedida = parseInt(req.query.empresaId, 10); if (pedida && minhas.includes(pedida)) return [pedida]; return minhas; } class DashboardController { /** * Ping de presença — marca o atendente como online. * POST /api/:alias/dashboard/ping */ static async ping(req, res) { const { alias } = req.params; if (req.user && req.user.id != null) presence.registrar(alias, req.user.id); res.json({ success: true }); } /** * Estatísticas do dashboard (apenas gerente). * GET /api/:alias/dashboard/stats?empresaId= */ static async stats(req, res) { try { if (!(await isGerente(req))) { return res.status(403).json({ success: false, error: 'Apenas gerentes podem visualizar o dashboard.' }); } const { alias } = req.params; const empresas = empresasPermitidas(req); if (empresas.length === 0) { return res.json({ success: true, data: { conversas: { abertas: 0, naoAtendidas: 0, naoAtribuidas: 0, pendentes: 0 }, atendentes: { disponiveis: 0, desconectados: 0, lista: [] }, trafego: [], } }); } const ph = empresas.map(() => '?').join(','); // ===== Conversas ===== // Abertas: têm atendente E equipe const abertas = await db.query(alias, `SELECT COUNT(*) AS CT FROM CHATC2_CONVERSAS WHERE CON_EMPRESA_ID IN (${ph}) AND CON_STATUS IN ('A','E') AND CON_SITUACAO = 'A' AND CON_USUARIO_ID IS NOT NULL AND CON_EQUIPE_ID IS NOT NULL`, empresas); // Não atendidas: têm atendente+equipe e a última mensagem foi do cliente ('C') const naoAtendidas = await db.query(alias, `SELECT COUNT(*) AS CT FROM CHATC2_CONVERSAS c WHERE c.CON_EMPRESA_ID IN (${ph}) AND c.CON_STATUS IN ('A','E') AND c.CON_SITUACAO = 'A' AND c.CON_USUARIO_ID IS NOT NULL AND c.CON_EQUIPE_ID IS NOT NULL AND (SELECT m.CME_REMETENTE FROM CHATC2_CONVERSAS_MENSAGENS m WHERE m.CME_CONVERSA_ID = c.CON_CODIGO_ID ORDER BY m.CME_DT_ENVIO DESC FETCH FIRST 1 ROWS ONLY) = 'C'`, empresas); // Não atribuídas: sem atendente e sem equipe const naoAtribuidas = await db.query(alias, `SELECT COUNT(*) AS CT FROM CHATC2_CONVERSAS WHERE CON_EMPRESA_ID IN (${ph}) AND CON_STATUS IN ('A','E') AND CON_SITUACAO = 'A' AND CON_USUARIO_ID IS NULL AND CON_EQUIPE_ID IS NULL`, empresas); // Pendentes: têm apenas equipe (sem atendente) const pendentes = await db.query(alias, `SELECT COUNT(*) AS CT FROM CHATC2_CONVERSAS WHERE CON_EMPRESA_ID IN (${ph}) AND CON_STATUS IN ('A','E') AND CON_SITUACAO = 'A' AND CON_USUARIO_ID IS NULL AND CON_EQUIPE_ID IS NOT NULL`, empresas); // Agendadas para hoje (status P = Pendente) const agendadas = await db.query(alias, `SELECT COUNT(*) AS CT FROM CHATC2_CONVERSAS_AGENDAMENTOS WHERE CAG_EMPRESA_ID IN (${ph}) AND CAG_DATA = CURRENT_DATE AND CAG_STATUS = 'P'`, empresas); // ===== Atendentes ===== const usuarios = await db.query(alias, `SELECT DISTINCT u.USU_CODIGO_ID, u.USU_NOME FROM USUARIOS u INNER JOIN USUARIOS_EMPRESA ue ON u.USU_CODIGO_ID = ue.USE_USUARIO_ID WHERE ue.USE_EMPRESA_ID IN (${ph}) AND u.USU_STATUS = 'A' AND COALESCE(u.USU_ACESSO_WEB,0) = 1 ORDER BY u.USU_NOME`, empresas); const online = presence.idsOnline(alias); let disponiveis = 0, desconectados = 0; const lista = usuarios.map(function (u) { const on = online.has(Number(u.USU_CODIGO_ID)); if (on) disponiveis++; else desconectados++; return { id: u.USU_CODIGO_ID, nome: (u.USU_NOME || '').trim(), online: on }; }); // ===== Tráfego das conversas (últimos 365 dias, por dia) ===== const trafegoRows = await db.query(alias, `SELECT CAST(COALESCE(CON_DT_INICIO, CON_PRIMEIRA_MSG, CON_DT_ULTIMA_MSG) AS DATE) AS DIA, COUNT(*) AS CT FROM CHATC2_CONVERSAS WHERE CON_EMPRESA_ID IN (${ph}) AND COALESCE(CON_DT_INICIO, CON_PRIMEIRA_MSG, CON_DT_ULTIMA_MSG) >= CURRENT_DATE - 365 GROUP BY CAST(COALESCE(CON_DT_INICIO, CON_PRIMEIRA_MSG, CON_DT_ULTIMA_MSG) AS DATE)`, empresas); const fmtDia = (d) => { if (!d) return null; if (typeof d === 'string') return d.split('T')[0]; if (d instanceof Date) return d.toISOString().split('T')[0]; return String(d); }; const trafego = trafegoRows .map(function (r) { return { dia: fmtDia(r.DIA), total: Number(r.CT) || 0 }; }) .filter(function (r) { return r.dia; }); res.json({ success: true, data: { conversas: { abertas: Number(abertas[0]?.CT) || 0, naoAtendidas: Number(naoAtendidas[0]?.CT) || 0, naoAtribuidas: Number(naoAtribuidas[0]?.CT) || 0, pendentes: Number(pendentes[0]?.CT) || 0, agendadas: Number(agendadas[0]?.CT) || 0, }, atendentes: { disponiveis, desconectados, lista }, trafego, } }); } catch (err) { res.status(500).json({ success: false, error: err.message }); } } } module.exports = DashboardController;