alterações de melhorias

This commit is contained in:
2026-06-24 14:13:09 +00:00
parent d942694ec0
commit 29190cd71c
34 changed files with 2570 additions and 950 deletions
+44 -4
View File
@@ -1101,7 +1101,7 @@ async function carregarConversas() {
});
}
renderConversas(data.data, busca);
} catch(e) {}
} catch(e) { if (Chatc2Toast) Chatc2Toast.error('Erro ao carregar conversas'); }
}
var AVATAR_COLORS = ['#667eea','#764ba2','#f093fb','#4facfe','#43e97b','#fa709a','#fee140','#30cfd0','#a18cd1','#fda085'];
@@ -1151,6 +1151,23 @@ function renderConversas(convs, busca) {
return '<span class="conv-badge" style="background:' + esc(l.cor || '#667eea') + '">' + esc(l.nome || '') + '</span>';
}).join('');
const ultimaMsg = (c.ultimaMsg || '').substring(0, 60);
// Preview com ícone por tipo de mensagem
var previewHtml;
if (!c.ultimaMsg) {
previewHtml = '<em>Sem mensagens</em>';
} else if (c.ultimaTipo === 'audio' || c.ultimaTipo === 'ptt') {
previewHtml = '🎤 Mensagem de voz';
} else if (c.ultimaTipo === 'image') {
previewHtml = '🖼️ Imagem';
} else if (c.ultimaTipo === 'video') {
previewHtml = '🎥 Vídeo';
} else if (c.ultimaTipo === 'document') {
previewHtml = '📎 Documento';
} else if (c.ultimaTipo === 'triagem') {
previewHtml = '🤖 Menu de triagem';
} else {
previewHtml = esc(ultimaMsg);
}
const tempo = formatTime(c.dtUltimaMsg);
return '<div class="conv-item ' + isActive + '" onclick="abrirConversa(' + c.id + ')" data-id="' + c.id + '">' +
'<div class="conv-avatar" style="background:' + cor + '">' + iniciais + '</div>' +
@@ -1159,7 +1176,7 @@ function renderConversas(convs, busca) {
'<div class="conv-nome">' + esc(nome) + '</div>' +
'<div style="font-size:10px;color:rgba(255,255,255,0.35);flex-shrink:0">' + tempo + '</div>' +
'</div>' +
'<div class="conv-ultima">' + (ultimaMsg ? esc(ultimaMsg) : '<em>Sem mensagens</em>') + '</div>' +
'<div class="conv-ultima">' + previewHtml + '</div>' +
'<div class="conv-status">' + statusDot + ' ' + (c.status === 'E' ? 'Em espera' : c.status === 'A' ? 'Aberto' : 'Finalizado') + '</div>' +
(labelsHtml ? '<div class="conv-badges">' + labelsHtml + '</div>' : '') +
'</div>' +
@@ -1491,8 +1508,20 @@ async function carregarMensagens() {
headers: { 'Authorization': 'Bearer ' + token }
});
var data = await res.json();
if (data.success) renderMensagens(data.data);
} catch(e) {}
if (data.success) {
// Notificação desktop: detecta novas mensagens de cliente
var novasDoCliente = data.data.filter(function(m) {
return m.remetente === 'C' && m.id > ultimaMsgIdGlobal && m.privada !== 'S';
});
if (novasDoCliente.length > 0 && Chatc2Notify) {
var ultima = novasDoCliente[novasDoCliente.length - 1];
Chatc2Notify.show('Nova mensagem', (ultima.mensagem || 'Mídia recebida').substring(0, 100));
}
// Atualiza ID máximo global
data.data.forEach(function(m) { if (m.id > ultimaMsgIdGlobal) ultimaMsgIdGlobal = m.id; });
renderMensagens(data.data);
}
} catch(e) { if (Chatc2Toast) Chatc2Toast.error('Erro ao carregar mensagens'); }
carregandoMsgs = false;
}
@@ -2110,11 +2139,20 @@ window.logout = function() {
};
// Auto-refresh a cada 5 segundos
let ultimaMsgIdGlobal = 0;
setInterval(function() {
carregarConversas();
if (conversaAtiva) carregarMensagens();
}, 5000);
// Notificação desktop: pede permissão na primeira interação
document.addEventListener('click', function pedirPermissao() {
document.removeEventListener('click', pedirPermissao);
if (Chatc2Notify && Chatc2Notify.isSupported()) {
Chatc2Notify.requestPermission();
}
}, { once: true });
// Presença: marca o atendente como "online" para o dashboard
function pingPresenca() {
fetch('/api/' + alias + '/dashboard/ping', {
@@ -2133,6 +2171,8 @@ document.getElementById('msgInput').addEventListener('input', function() {
})();
</script>
<script src="/js/dark-mode.js"></script>
<script src="/js/notifications.js"></script>
<script src="/js/toast.js"></script>
<!-- impeccable-live-start -->
<script src="http://localhost:8400/live.js"></script>
<!-- impeccable-live-end -->
+30 -8
View File
@@ -1,14 +1,29 @@
// Dark Mode Controller — Unified for all Chatc2 pages
(function() {
// Detecta preferência do sistema
var osPrefersDark = window.matchMedia('(prefers-color-scheme: dark)');
var manualOverride = localStorage.getItem('chatc2_dark_mode_manual');
// Decide tema inicial: manual > preferência OS > claro
function getInitialTheme() {
if (manualOverride !== null) return manualOverride === 'true';
return osPrefersDark.matches;
}
// Apply on load immediately (FOUC prevention)
if (localStorage.getItem('chatc2_dark_mode') === 'true') {
if (getInitialTheme()) {
document.documentElement.classList.add('dark-mode-pending');
}
function applyTheme(enable) {
function applyTheme(enable, isManual) {
document.documentElement.classList.remove('dark-mode-pending');
document.body && document.body.classList.toggle('dark-mode', enable);
if (document.body) {
document.body.classList.toggle('dark-mode', enable);
}
localStorage.setItem('chatc2_dark_mode', enable ? 'true' : 'false');
if (isManual) {
localStorage.setItem('chatc2_dark_mode_manual', enable ? 'true' : 'false');
}
document.querySelectorAll('.dark-mode-toggle').forEach(function(btn) {
btn.innerHTML = enable ? '☀️ Claro' : '🌙 Escuro';
});
@@ -16,21 +31,28 @@
window.darkModeToggle = function() {
var isDark = localStorage.getItem('chatc2_dark_mode') === 'true';
applyTheme(!isDark);
applyTheme(!isDark, true); // toggle manual
};
window.darkModeApply = function(enable) { applyTheme(enable); };
window.darkModeApply = function(enable) { applyTheme(enable, false); };
window.darkModeIsDark = function() {
return localStorage.getItem('chatc2_dark_mode') === 'true';
};
// Apply stored preference after DOM ready
// Reage a mudanças no tema do sistema (só quando não há override manual)
osPrefersDark.addEventListener('change', function(e) {
if (localStorage.getItem('chatc2_dark_mode_manual') === null) {
applyTheme(e.matches, false);
}
});
// Apply after DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
applyTheme(localStorage.getItem('chatc2_dark_mode') === 'true');
applyTheme(getInitialTheme(), false);
});
} else {
applyTheme(localStorage.getItem('chatc2_dark_mode') === 'true');
applyTheme(getInitialTheme(), false);
}
})();
+94
View File
@@ -0,0 +1,94 @@
/**
* Notificações Desktop — Chatc2
*
* Incluir em páginas que precisam de notificações (chat, dashboard):
* <script src="/js/notifications.js"></script>
*
* Uso:
* Chatc2Notify.requestPermission(); // pede permissão ao usuário
* Chatc2Notify.show('Nova mensagem', 'Cliente enviou uma mensagem');
*
* Respeita: permissão do navegador, foco da janela (não notifica se já está focada).
*/
(function() {
var permitido = false;
var inicializado = false;
function init() {
if (inicializado) return;
inicializado = true;
// Verifica suporte
if (!('Notification' in window)) return;
// Verifica permissão atual
if (Notification.permission === 'granted') {
permitido = true;
} else if (Notification.permission === 'denied') {
permitido = false;
}
}
window.Chatc2Notify = {
/** Pede permissão ao usuário (deve ser chamado após interação do usuário). */
requestPermission: function() {
init();
if (!('Notification' in window)) return;
if (Notification.permission === 'granted') {
permitido = true;
return;
}
if (Notification.permission === 'denied') return;
Notification.requestPermission().then(function(permission) {
permitido = permission === 'granted';
});
},
/**
* Exibe uma notificação.
* Só dispara se: permissão concedida E janela não está focada.
*/
show: function(title, body, icon) {
init();
if (!permitido) return;
if (document.hasFocus && document.hasFocus()) return; // já está olhando
try {
var n = new Notification(title, {
body: body || '',
icon: icon || '/favicon.ico',
tag: 'chatc2',
});
// Clica na notificação → foca a janela
n.onclick = function() {
window.focus();
n.close();
};
// Auto-fecha após 5s
setTimeout(function() { n.close(); }, 5000);
} catch(e) {
// Ignora erros (ex: Service Worker não registrado)
}
},
/** Retorna true se notificações estão disponíveis. */
isSupported: function() {
init();
return 'Notification' in window;
},
/** Retorna true se permissão está concedida. */
isPermitted: function() {
init();
return permitido;
},
};
// Inicializa ao carregar
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
+70
View File
@@ -0,0 +1,70 @@
/**
* Toast notifications — Chatc2
*
* Incluir em páginas que precisam de feedback visual:
* <script src="/js/toast.js"></script>
*
* Uso:
* Chatc2Toast.success('Mensagem enviada!');
* Chatc2Toast.error('Erro ao carregar conversas');
* Chatc2Toast.warn('Conexão lenta...');
*/
(function() {
var container = null;
function ensureContainer() {
if (container) return container;
container = document.createElement('div');
container.id = 'chatc2-toast-container';
container.style.cssText = 'position:fixed;top:20px;right:20px;z-index:99999;display:flex;flex-direction:column;gap:8px;pointer-events:none;';
document.body.appendChild(container);
return container;
}
function show(message, type) {
var c = ensureContainer();
var toast = document.createElement('div');
var colors = {
success: { bg: '#d1fae5', color: '#065f46', border: '#6ee7b7' },
error: { bg: '#fef2f2', color: '#991b1b', border: '#fca5a5' },
warn: { bg: '#fef3c7', color: '#92400e', border: '#fcd34d' },
info: { bg: '#ede9fe', color: '#5b21b6', border: '#c4b5fd' },
};
var col = colors[type] || colors.info;
toast.style.cssText = 'pointer-events:auto;padding:10px 16px;border-radius:8px;font-size:13px;font-weight:500;' +
'max-width:380px;box-shadow:0 4px 16px rgba(0,0,0,0.12);animation:chatc2ToastIn 0.25s ease;' +
'background:' + col.bg + ';color:' + col.color + ';border-left:4px solid ' + col.border + ';';
toast.textContent = message;
c.appendChild(toast);
// Auto-remove
var duration = type === 'error' ? 6000 : 3500;
setTimeout(function() {
toast.style.opacity = '0';
toast.style.transform = 'translateX(20px)';
toast.style.transition = 'all 0.2s ease';
setTimeout(function() {
if (toast.parentNode) toast.parentNode.removeChild(toast);
}, 200);
}, duration);
}
// Inject keyframe
if (!document.getElementById('chatc2-toast-style')) {
var style = document.createElement('style');
style.id = 'chatc2-toast-style';
style.textContent = '@keyframes chatc2ToastIn{from{opacity:0;transform:translateX(20px)}to{opacity:1;transform:translateX(0)}}';
document.head.appendChild(style);
}
window.Chatc2Toast = {
success: function(msg) { show(msg, 'success'); },
error: function(msg) { show(msg, 'error'); },
warn: function(msg) { show(msg, 'warn'); },
info: function(msg) { show(msg, 'info'); },
};
})();