9cf5f00243
- delete: src/resolucaoSetup.js (migration paralela, consolidado em #24) - shrink: chatController.sendEvolutionMessage (140 -> 6 linhas, delega ao evolutionService) - shrink: triageController.sendEvolution (55 -> 10 linhas, delega ao evolutionService) - delete: compressVideo em chatController (codigo morto, chamado so pelo old sendEvolutionMessage) - add: migration #24 consolida DDL que estava espalhada em runtime - fix: configController.remove garantirEstrutura calls (arquivo removido) - net: -270 linhas, -1 arquivo
2485 lines
98 KiB
HTML
2485 lines
98 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="pt-BR">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Chat - Chatc2</title>
|
||
<link rel="stylesheet" href="/css/main.css">
|
||
<style>
|
||
/* ===== CHAT PAGE - Estilos específicos ===== */
|
||
body { background:var(--surface-3); display:flex; height:100vh; overflow:hidden; }
|
||
|
||
/* ===== SIDEBAR ESQUERDA ===== */
|
||
.sidebar-left {
|
||
width: 300px;
|
||
background: linear-gradient(180deg, #1e1b4b 0%, #312e81 100%);
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex-shrink: 0;
|
||
color: #fff;
|
||
}
|
||
.admin-only-nav { display: none; }
|
||
|
||
.sidebar-left .header {
|
||
padding: 18px 14px 14px;
|
||
border-bottom: 1px solid rgba(255,255,255,0.08);
|
||
}
|
||
.sidebar-left .header h2 {
|
||
font-size: 17px;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
letter-spacing: -0.2px;
|
||
}
|
||
.sidebar-left .header-nav {
|
||
display: flex;
|
||
gap: 3px;
|
||
margin-top: 6px;
|
||
}
|
||
.sidebar-left .header-nav a {
|
||
padding: 6px 9px;
|
||
border-radius: 6px;
|
||
color: rgba(255,255,255,0.55);
|
||
text-decoration: none;
|
||
font-size: 17px;
|
||
transition: all .15s;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.sidebar-left .header-nav a:hover {
|
||
background: rgba(255,255,255,0.14);
|
||
color: #fff;
|
||
}
|
||
|
||
/* Tabs de navegação */
|
||
.sidebar-left .nav-tabs {
|
||
display: flex;
|
||
border-bottom: 1px solid rgba(255,255,255,0.08);
|
||
padding: 0 6px;
|
||
gap: 2px;
|
||
}
|
||
.sidebar-left .nav-tabs a {
|
||
flex: 1;
|
||
text-align: center;
|
||
padding: 9px 4px;
|
||
font-size: 11px;
|
||
font-weight: 500;
|
||
color: rgba(255,255,255,0.45);
|
||
text-decoration: none;
|
||
border-bottom: 2px solid transparent;
|
||
transition: all .15s;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
.sidebar-left .nav-tabs a:hover { color: rgba(255,255,255,0.8); }
|
||
.sidebar-left .nav-tabs a.active {
|
||
color: #fff;
|
||
border-bottom-color: #a78bfa;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* Search box */
|
||
.sidebar-left .search-box {
|
||
padding: 10px 12px;
|
||
display: flex;
|
||
gap: 6px;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
border-bottom: 1px solid rgba(255,255,255,0.06);
|
||
}
|
||
.sidebar-left .search-box input[type="text"] {
|
||
flex: 1;
|
||
min-width: 0;
|
||
padding: 8px 12px;
|
||
border: 1px solid rgba(255,255,255,0.18);
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
outline: none;
|
||
background: rgba(255,255,255,0.09);
|
||
color: #fff;
|
||
transition: all .15s;
|
||
width: auto;
|
||
box-shadow: none;
|
||
}
|
||
.sidebar-left .search-box input[type="text"]::placeholder { color: rgba(255,255,255,0.35); }
|
||
.sidebar-left .search-box input[type="text"]:focus {
|
||
border-color: rgba(255,255,255,0.38);
|
||
background: rgba(255,255,255,0.14);
|
||
box-shadow: none;
|
||
}
|
||
.sidebar-left .search-box label {
|
||
font-size: 11px;
|
||
color: rgba(255,255,255,0.45);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
white-space: nowrap;
|
||
cursor: pointer;
|
||
}
|
||
.sidebar-left .search-box input[type="checkbox"] {
|
||
width: 13px;
|
||
height: 13px;
|
||
flex: none;
|
||
padding: 0;
|
||
cursor: pointer;
|
||
accent-color: #a78bfa;
|
||
box-shadow: none;
|
||
border: none;
|
||
background: transparent;
|
||
}
|
||
|
||
/* Lista de conversas */
|
||
.sidebar-left .conversations-list { flex: 1; overflow-y: auto; }
|
||
.sidebar-left .conversations-list::-webkit-scrollbar { width: 4px; }
|
||
.sidebar-left .conversations-list::-webkit-scrollbar-track { background: transparent; }
|
||
.sidebar-left .conversations-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.18); border-radius: 4px; }
|
||
|
||
.conv-item {
|
||
padding: 11px 14px;
|
||
border-bottom: 1px solid rgba(255,255,255,0.05);
|
||
cursor: pointer;
|
||
transition: background .15s;
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
}
|
||
.conv-item:hover { background: rgba(255,255,255,0.08); }
|
||
.conv-item.active { background: rgba(255,255,255,0.16); }
|
||
|
||
.conv-avatar {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 15px;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
flex-shrink: 0;
|
||
letter-spacing: -0.5px;
|
||
}
|
||
.conv-body { flex: 1; min-width: 0; }
|
||
.conv-item .conv-nome {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: rgba(255,255,255,0.95);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
.conv-item .conv-ultima {
|
||
font-size: 12px;
|
||
color: rgba(255,255,255,0.45);
|
||
margin-top: 1px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.conv-item .conv-status {
|
||
font-size: 10px;
|
||
color: rgba(255,255,255,0.35);
|
||
margin-top: 2px;
|
||
}
|
||
.conv-item .conv-badges { display: flex; gap: 3px; margin-top: 3px; flex-wrap: wrap; }
|
||
.conv-item .conv-badge {
|
||
font-size: 10px;
|
||
padding: 1px 6px;
|
||
border-radius: 4px;
|
||
color: #fff;
|
||
font-weight: 500;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
/* Sidebar footer */
|
||
.sidebar-footer {
|
||
padding: 10px 12px;
|
||
border-top: 1px solid rgba(255,255,255,0.08);
|
||
flex-shrink: 0;
|
||
}
|
||
.sidebar-footer button.dark-mode-toggle {
|
||
width: 100%;
|
||
margin-bottom: 4px;
|
||
padding: 8px 12px;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
background: rgba(255,255,255,0.07);
|
||
border: 1px solid rgba(255,255,255,0.12);
|
||
font-size: 13px;
|
||
color: rgba(255,255,255,0.75);
|
||
transition: all .15s;
|
||
text-align: left;
|
||
display: block;
|
||
}
|
||
.sidebar-footer button.dark-mode-toggle:hover {
|
||
background: rgba(255,255,255,0.14);
|
||
color: #fff;
|
||
}
|
||
.sidebar-footer a {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 12px;
|
||
border-radius: 8px;
|
||
color: rgba(255,255,255,0.52);
|
||
text-decoration: none;
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
transition: all .15s;
|
||
}
|
||
.sidebar-footer a:hover {
|
||
background: rgba(255,255,255,0.10);
|
||
color: #fca5a5;
|
||
}
|
||
|
||
/* ===== CHAT CENTER ===== */
|
||
.chat-center { flex: 1; display: flex; flex-direction: column; background: #fff; min-width: 0; }
|
||
|
||
.chat-header {
|
||
padding: 11px 18px;
|
||
border-bottom: 1px solid var(--border);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-shadow: 0 1px 4px rgba(0,0,0,0.05);
|
||
flex-shrink: 0;
|
||
}
|
||
.chat-header .info h3 { font-size: 15px; font-weight: 600; color: var(--text-primary); }
|
||
.chat-header .info span { font-size: 12px; color: var(--text-muted); }
|
||
.chat-header .actions { display: flex; gap: 7px; }
|
||
.chat-header .actions button {
|
||
padding: 6px 14px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
color: var(--text-secondary);
|
||
transition: all .15s;
|
||
}
|
||
.chat-header .actions button:hover { background: var(--surface-3); border-color: #d1d5db; }
|
||
.chat-header .actions .btn-finalizar {
|
||
background: var(--danger);
|
||
color: #fff;
|
||
border-color: var(--danger);
|
||
font-weight: 600;
|
||
}
|
||
.chat-header .actions .btn-finalizar:hover { background: var(--danger-text); border-color: var(--danger-text); }
|
||
|
||
/* Área de mensagens - estilo chat */
|
||
.messages-area {
|
||
flex: 1;
|
||
padding: 16px 20px;
|
||
overflow-y: auto;
|
||
background-color: #e5ddd5;
|
||
background-image: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23bfb6ab' fill-opacity='0.18'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
.messages-area::-webkit-scrollbar { width: 5px; }
|
||
.messages-area::-webkit-scrollbar-track { background: transparent; }
|
||
.messages-area::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.15); border-radius: 4px; }
|
||
|
||
/* Bolhas de mensagem */
|
||
.msg {
|
||
max-width: 70%;
|
||
padding: 8px 12px 5px;
|
||
border-radius: 12px;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
position: relative;
|
||
word-break: break-word;
|
||
}
|
||
.msg.cliente {
|
||
align-self: flex-start;
|
||
background: #fff;
|
||
border-bottom-left-radius: 4px;
|
||
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
|
||
color: var(--text-primary);
|
||
}
|
||
.msg.usuario {
|
||
align-self: flex-end;
|
||
background: #dcf8c6;
|
||
color: var(--text-primary);
|
||
border-bottom-right-radius: 4px;
|
||
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
|
||
}
|
||
.msg.sistema {
|
||
align-self: center;
|
||
background: rgba(255,255,255,0.75);
|
||
color: var(--text-muted);
|
||
font-size: 12px;
|
||
padding: 4px 14px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 1px 2px rgba(0,0,0,0.06);
|
||
max-width: 60%;
|
||
text-align: center;
|
||
}
|
||
.msg.privada {
|
||
background: #fef9c3;
|
||
color: #854d0e;
|
||
align-self: flex-end;
|
||
border: 1px solid #fde047;
|
||
border-bottom-right-radius: 4px;
|
||
}
|
||
.msg .hora {
|
||
font-size: 10px;
|
||
opacity: 0.55;
|
||
margin-top: 3px;
|
||
text-align: right;
|
||
display: block;
|
||
}
|
||
.msg img {
|
||
max-width: 260px;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
display: block;
|
||
}
|
||
|
||
/* Estado: enviando */
|
||
.msg.enviando {
|
||
opacity: 0.55;
|
||
}
|
||
.msg.enviando .hora::after {
|
||
content: ' ⌛';
|
||
font-size: 10px;
|
||
}
|
||
|
||
/* Estado: erro ao enviar */
|
||
.msg.erro {
|
||
border: 1px solid var(--danger);
|
||
background: var(--danger-bg);
|
||
}
|
||
.msg.erro .msg-erro-acoes {
|
||
display: flex;
|
||
gap: 6px;
|
||
margin-top: 6px;
|
||
align-items: center;
|
||
font-size: 12px;
|
||
color: var(--danger-text);
|
||
}
|
||
.msg.erro .btn-reenviar {
|
||
padding: 3px 10px;
|
||
border: 1px solid var(--danger-text);
|
||
border-radius: 6px;
|
||
background: #fff;
|
||
color: var(--danger-text);
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all .15s;
|
||
}
|
||
.msg.erro .btn-reenviar:hover {
|
||
background: var(--danger-text);
|
||
color: #fff;
|
||
}
|
||
|
||
/* Indicador de conexão */
|
||
.connection-status {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
font-size: 11px;
|
||
font-weight: 500;
|
||
padding: 2px 10px;
|
||
border-radius: 9999px;
|
||
margin-left: 8px;
|
||
}
|
||
.connection-status.online {
|
||
background: var(--success-bg);
|
||
color: var(--success-text);
|
||
}
|
||
.connection-status.offline {
|
||
background: var(--danger-bg);
|
||
color: var(--danger-text);
|
||
}
|
||
.connection-status .conn-dot {
|
||
width: 7px;
|
||
height: 7px;
|
||
border-radius: 50%;
|
||
flex-shrink: 0;
|
||
}
|
||
.connection-status.online .conn-dot { background: var(--success); }
|
||
.connection-status.offline .conn-dot { background: var(--danger); animation: pulse 1.5s infinite; }
|
||
|
||
/* Input de chat */
|
||
.chat-input {
|
||
padding: 10px 14px;
|
||
border-top: 1px solid var(--border);
|
||
display: flex;
|
||
gap: 6px;
|
||
align-items: flex-end;
|
||
background: #f0f2f5;
|
||
flex-shrink: 0;
|
||
}
|
||
.chat-input textarea {
|
||
flex: 1;
|
||
padding: 10px 16px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 9999px;
|
||
font-size: 14px;
|
||
resize: none;
|
||
outline: none;
|
||
max-height: 120px;
|
||
font-family: inherit;
|
||
background: #fff;
|
||
color: var(--text-primary);
|
||
line-height: 1.4;
|
||
transition: border-color .15s;
|
||
}
|
||
.chat-input textarea:focus { border-color: var(--primary); }
|
||
.chat-input textarea::placeholder { color: var(--text-faint); }
|
||
|
||
.chat-input .btn-enviar {
|
||
width: 40px;
|
||
height: 40px;
|
||
background: #25d366;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 50%;
|
||
font-size: 18px;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all .15s;
|
||
flex-shrink: 0;
|
||
padding: 0;
|
||
}
|
||
.chat-input .btn-enviar:hover { background: #22c55e; transform: scale(1.06); }
|
||
.chat-input .btn-enviar:disabled {
|
||
opacity: 0.35;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
}
|
||
.chat-input .btn-enviar:disabled:hover {
|
||
background: #25d366;
|
||
transform: none;
|
||
}
|
||
|
||
/* Botões de ação agrupados no menu "+" */
|
||
.chat-input .btn-action {
|
||
width: 36px;
|
||
height: 36px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border: none;
|
||
border-radius: 50%;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
font-size: 18px;
|
||
color: var(--text-muted);
|
||
transition: all .15s;
|
||
padding: 0;
|
||
}
|
||
.chat-input .btn-action:hover { background: var(--border); color: var(--text-secondary); }
|
||
|
||
@media (max-width: 768px) {
|
||
.chat-input .btn-action {
|
||
width: 44px;
|
||
height: 44px;
|
||
font-size: 20px;
|
||
}
|
||
.chat-input .btn-enviar {
|
||
width: 44px;
|
||
height: 44px;
|
||
font-size: 20px;
|
||
}
|
||
}
|
||
|
||
/* Menu "+" dropdown */
|
||
.chat-input .action-menu-wrap {
|
||
position: relative;
|
||
flex-shrink: 0;
|
||
}
|
||
.chat-input .action-menu {
|
||
display: none;
|
||
position: absolute;
|
||
bottom: 100%;
|
||
left: 0;
|
||
margin-bottom: 6px;
|
||
background: #fff;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 16px rgba(0,0,0,0.10);
|
||
padding: 6px;
|
||
z-index: 50;
|
||
min-width: 160px;
|
||
}
|
||
.chat-input .action-menu.show { display: block; }
|
||
.chat-input .action-menu button {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
padding: 8px 12px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
background: transparent;
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
color: var(--text-secondary);
|
||
white-space: nowrap;
|
||
text-align: left;
|
||
transition: background .15s;
|
||
}
|
||
.chat-input .action-menu button:hover { background: var(--surface-3); }
|
||
|
||
/* Toggle de mensagem privada com texto */
|
||
.chat-input .privada-toggle {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding: 6px 12px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 9999px;
|
||
background: #fff;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: var(--text-secondary);
|
||
transition: all .15s;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
}
|
||
.chat-input .privada-toggle:hover { background: var(--surface-3); }
|
||
.chat-input .privada-toggle.ativa { background: var(--warning-bg); color: #d97706; border-color: #fde047; }
|
||
.chat-input .privada-toggle .priv-icon { font-size: 14px; }
|
||
|
||
/* Gravação */
|
||
.chat-input .btn-audio.recording {
|
||
background: var(--danger-bg);
|
||
color: var(--danger);
|
||
animation: pulse 1s infinite;
|
||
}
|
||
@keyframes pulse { 0%{transform:scale(1)} 50%{transform:scale(1.15)} 100%{transform:scale(1)} }
|
||
|
||
/* ===== SIDEBAR DIREITA ===== */
|
||
.sidebar-right {
|
||
width: 280px;
|
||
background: #fff;
|
||
border-left: 1px solid var(--border);
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex-shrink: 0;
|
||
overflow-y: auto;
|
||
}
|
||
.sidebar-right .header {
|
||
padding: 14px 16px;
|
||
border-bottom: 1px solid var(--border);
|
||
background: var(--surface-2);
|
||
flex-shrink: 0;
|
||
}
|
||
.sidebar-right .header h3 {
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
color: var(--text-muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.07em;
|
||
}
|
||
|
||
/* Accordions no painel direito */
|
||
.sidebar-right .accordion-toggle {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
width: 100%;
|
||
padding: 10px 16px;
|
||
border: none;
|
||
border-bottom: 1px solid var(--surface-3);
|
||
background: none;
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
color: var(--text-muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.07em;
|
||
cursor: pointer;
|
||
transition: background .15s;
|
||
}
|
||
.sidebar-right .accordion-toggle:hover { background: var(--surface-2); }
|
||
.sidebar-right .accordion-toggle .arrow {
|
||
font-size: 10px;
|
||
transition: transform .15s;
|
||
}
|
||
.sidebar-right .accordion-toggle.open .arrow { transform: rotate(90deg); }
|
||
.sidebar-right .accordion-body {
|
||
display: none;
|
||
}
|
||
.sidebar-right .accordion-body.open { display: block; }
|
||
.sidebar-right .cliente-foto {
|
||
width: 70px;
|
||
height: 70px;
|
||
border-radius: 50%;
|
||
margin: 18px auto 10px;
|
||
background: linear-gradient(135deg, var(--primary), #764ba2);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 24px;
|
||
color: #fff;
|
||
font-weight: 700;
|
||
overflow: hidden;
|
||
box-shadow: 0 4px 14px rgba(102,126,234,0.3);
|
||
flex-shrink: 0;
|
||
}
|
||
.sidebar-right .cliente-foto img { width: 100%; height: 100%; object-fit: cover; }
|
||
.sidebar-right .info-section {
|
||
padding: 10px 16px;
|
||
border-bottom: 1px solid var(--surface-3);
|
||
}
|
||
.sidebar-right .info-section .label {
|
||
font-size: 10px;
|
||
text-transform: uppercase;
|
||
color: var(--text-faint);
|
||
letter-spacing: 0.07em;
|
||
font-weight: 600;
|
||
margin-bottom: 4px;
|
||
}
|
||
.sidebar-right .info-section .value { font-size: 13px; color: var(--text-secondary); }
|
||
.sidebar-right .info-section select {
|
||
width: 100%;
|
||
padding: 6px 10px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
outline: none;
|
||
color: var(--text-secondary);
|
||
background: #fff;
|
||
cursor: pointer;
|
||
transition: border-color .15s;
|
||
}
|
||
.sidebar-right .info-section select:focus { border-color: var(--primary); }
|
||
.sidebar-right .info-section .inadimplente { color: var(--danger); font-weight: 600; }
|
||
.sidebar-right .info-section .adimplente { color: var(--success); font-weight: 600; }
|
||
.sidebar-right .acoes { padding: 10px 12px; display: flex; flex-direction: column; gap: 6px; }
|
||
.sidebar-right .acoes button {
|
||
padding: 9px 12px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
text-align: left;
|
||
color: var(--text-secondary);
|
||
transition: all .15s;
|
||
}
|
||
.sidebar-right .acoes button:hover { background: var(--surface-3); border-color: #d1d5db; }
|
||
.sidebar-right .acoes button .icon { margin-right: 6px; }
|
||
.sidebar-right .labels-section { padding: 10px 16px; }
|
||
.sidebar-right .labels-section .label-tag {
|
||
display: inline-block;
|
||
padding: 3px 11px;
|
||
border-radius: 12px;
|
||
font-size: 12px;
|
||
color: #fff;
|
||
margin: 2px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: opacity .15s;
|
||
}
|
||
.sidebar-right .labels-section .label-tag:hover { opacity: 0.82; }
|
||
|
||
/* ===== MODAL ===== */
|
||
.modal-overlay {
|
||
position: fixed;
|
||
top: 0; left: 0;
|
||
width: 100%; height: 100%;
|
||
background: rgba(0,0,0,0.45);
|
||
backdrop-filter: blur(4px);
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
}
|
||
.modal-overlay.show { display: flex; }
|
||
.modal {
|
||
background: #fff;
|
||
border-radius: 16px;
|
||
padding: 26px;
|
||
width: 90%;
|
||
max-width: 420px;
|
||
box-shadow: 0 24px 64px rgba(0,0,0,0.2);
|
||
animation: modalIn .18s ease;
|
||
}
|
||
@keyframes modalIn {
|
||
from { opacity: 0; transform: scale(0.96) translateY(-8px); }
|
||
to { opacity: 1; transform: scale(1) translateY(0); }
|
||
}
|
||
.modal h3 { margin-bottom: 14px; font-size: 16px; font-weight: 700; color: var(--text-primary); }
|
||
.modal input, .modal select {
|
||
width: 100%;
|
||
padding: 9px 13px;
|
||
border: 2px solid var(--border);
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
margin-bottom: 12px;
|
||
outline: none;
|
||
transition: border-color .15s;
|
||
background: var(--surface-2);
|
||
color: var(--text-primary);
|
||
}
|
||
.modal input:focus { border-color: var(--primary); background: #fff; box-shadow: 0 0 0 3px rgba(102,126,234,0.1); }
|
||
.modal .btn-group { display: flex; gap: 8px; justify-content: flex-end; margin-top: 4px; }
|
||
.modal .btn-group button {
|
||
padding: 9px 18px;
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
border: 1px solid var(--border);
|
||
background: #fff;
|
||
color: var(--text-secondary);
|
||
transition: all .15s;
|
||
}
|
||
.modal .btn-group button:hover { background: var(--surface-3); }
|
||
.modal .btn-group .btn-primary { background: var(--primary); color: #fff; border-color: var(--primary); }
|
||
.modal .btn-group .btn-primary:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
|
||
|
||
/* ===== DRAG OVERLAY ===== */
|
||
.drag-overlay {
|
||
display: none;
|
||
position: fixed;
|
||
top: 0; left: 0;
|
||
width: 100%; height: 100%;
|
||
background: rgba(102,126,234,0.12);
|
||
border: 3px dashed var(--primary);
|
||
z-index: 999;
|
||
pointer-events: none;
|
||
font-size: 22px;
|
||
font-weight: 600;
|
||
color: var(--primary);
|
||
}
|
||
.drag-overlay.show { display: flex; align-items: center; justify-content: center; }
|
||
|
||
/* ===== EMPTY STATE ===== */
|
||
.empty-state {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-direction: column;
|
||
color: var(--text-faint);
|
||
gap: 6px;
|
||
user-select: none;
|
||
}
|
||
.empty-state .icon { font-size: 52px; opacity: 0.45; }
|
||
.empty-state p { font-size: 15px; color: var(--text-muted); }
|
||
|
||
/* Botões só do mobile (voltar / info) */
|
||
.mobile-only { display: none; }
|
||
|
||
/* ===== RESPONSIVO: navegação de painel único (lista → conversa → info) ===== */
|
||
@media (max-width: 768px) {
|
||
.sidebar-left { width: 100%; }
|
||
.chat-center { display: none; }
|
||
|
||
/* Quando uma conversa está aberta, mostra o chat e esconde a lista */
|
||
body.chat-aberto .sidebar-left { display: none; }
|
||
body.chat-aberto .chat-center { display: flex; }
|
||
|
||
/* Painel de informações vira overlay deslizante (oculto por padrão) */
|
||
.sidebar-right {
|
||
position: fixed;
|
||
top: 0; right: 0; bottom: 0;
|
||
width: 88%; max-width: 340px;
|
||
z-index: 1200;
|
||
box-shadow: -4px 0 24px rgba(0,0,0,0.28);
|
||
display: none !important;
|
||
}
|
||
body.info-aberto .sidebar-right { display: flex !important; }
|
||
|
||
.mobile-only { display: inline-flex !important; align-items: center; justify-content: center; }
|
||
|
||
.chat-header { gap: 8px; }
|
||
.chat-header .info h3 { font-size: 14px; }
|
||
}
|
||
|
||
/* ===== DARK MODE: Novos componentes ===== */
|
||
body.dark-mode .connection-status.online { background: #0a2e0a; color: #4ade80; }
|
||
body.dark-mode .connection-status.offline { background: #2e0a0a; color: #ff6b6b; }
|
||
body.dark-mode .action-menu { background: #16213e; border-color: #0f3460; }
|
||
body.dark-mode .action-menu button { color: #e0e0e0; }
|
||
body.dark-mode .action-menu button:hover { background: #1a2744; }
|
||
body.dark-mode .privada-toggle { background: #1a1a2e; border-color: #0f3460; color: #888; }
|
||
body.dark-mode .privada-toggle:hover { background: #16213e; }
|
||
body.dark-mode .privada-toggle.ativa { background: #3a2e0a; color: #ffd700; border-color: #5a4a0a; }
|
||
body.dark-mode .accordion-toggle { color: #888; border-bottom-color: #0f3460; }
|
||
body.dark-mode .accordion-toggle:hover { background: #1a2744; }
|
||
body.dark-mode .msg.erro { background: #2e0a0a; border-color: #8b0000; }
|
||
body.dark-mode .msg.erro .msg-erro-acoes { color: #ff6b6b; }
|
||
body.dark-mode .msg.erro .btn-reenviar { background: #1a1a2e; border-color: #8b0000; color: #ff6b6b; }
|
||
body.dark-mode .msg.erro .btn-reenviar:hover { background: #8b0000; color: #fff; }
|
||
body.dark-mode .msg.enviando { opacity: 0.4; }
|
||
</style>
|
||
<link rel="stylesheet" href="/css/dark-mode.css">
|
||
<script>(function(){var m=localStorage.getItem("chatc2_dark_mode_manual");var d=m!==null?m==="true":window.matchMedia("(prefers-color-scheme: dark)").matches;if(d)document.documentElement.className="dark"})()</script>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- Drag overlay -->
|
||
<div class="drag-overlay" id="dragOverlay">📎 Solte o arquivo aqui</div>
|
||
|
||
<!-- Modal confirmar finalização -->
|
||
<div class="modal-overlay" id="modalConfirmarFinalizar">
|
||
<div class="modal" style="max-width:420px">
|
||
<h3>⚠️ Finalizar conversa</h3>
|
||
<p style="font-size:13px;color:#6b7280;margin-bottom:12px">Tem certeza que deseja finalizar este atendimento?</p>
|
||
|
||
<!-- Agendamento -->
|
||
<div id="agendamentoBox" style="margin-bottom:14px;padding:14px;background:var(--surface-2);border-radius:8px;border:1px solid var(--border)">
|
||
<label style="display:flex;align-items:center;gap:8px;font-size:13px;font-weight:600;color:var(--text-secondary);cursor:pointer;margin-bottom:0">
|
||
<input type="checkbox" id="chkAgendar" onchange="toggleAgendamento()" style="accent-color:var(--primary);width:16px;height:16px"> 📅 Agendar retorno
|
||
</label>
|
||
<div id="agendamentoCampos" style="display:none;margin-top:12px">
|
||
<div style="display:flex;gap:8px;margin-bottom:8px">
|
||
<div style="flex:1">
|
||
<label style="font-size:11px;font-weight:600;color:var(--text-muted);display:block;margin-bottom:4px;text-transform:uppercase;letter-spacing:0.04em">Data</label>
|
||
<input type="date" id="agData" style="width:100%;padding:8px 10px;border:2px solid var(--border);border-radius:8px;font-size:13px;outline:none;background:var(--surface);color:var(--text-primary)">
|
||
</div>
|
||
<div style="width:90px">
|
||
<label style="font-size:11px;font-weight:600;color:var(--text-muted);display:block;margin-bottom:4px;text-transform:uppercase;letter-spacing:0.04em">Hora</label>
|
||
<input type="time" id="agHora" style="width:100%;padding:8px 10px;border:2px solid var(--border);border-radius:8px;font-size:13px;outline:none;background:var(--surface);color:var(--text-primary)">
|
||
</div>
|
||
</div>
|
||
<label style="font-size:11px;font-weight:600;color:var(--text-muted);display:block;margin-bottom:4px;text-transform:uppercase;letter-spacing:0.04em">Anotação</label>
|
||
<textarea id="agNota" rows="2" placeholder="Ex: Confirmar pagamento do boleto..." style="width:100%;padding:8px 10px;border:2px solid var(--border);border-radius:8px;font-size:13px;font-family:inherit;resize:vertical;outline:none;background:var(--surface);color:var(--text-primary)"></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="btn-group">
|
||
<button onclick="fecharModal('modalConfirmarFinalizar')">Cancelar</button>
|
||
<button class="btn-primary" onclick="fecharModal('modalConfirmarFinalizar');finalizarConversa()">Finalizar</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal retorno agendado -->
|
||
<div class="modal-overlay" id="modalRetorno">
|
||
<div class="modal" style="max-width:460px">
|
||
<h3>📅 Retorno agendado</h3>
|
||
<p style="font-size:13px;color:var(--text-muted);margin-bottom:14px">Conversa criada. Revise o texto antes de enviar:</p>
|
||
<input type="hidden" id="retornoConversaId">
|
||
<div class="form-group">
|
||
<textarea id="retornoTextoSugerido" rows="4" style="width:100%;padding:10px 14px;border:2px solid var(--border);border-radius:8px;font-size:14px;font-family:inherit;resize:vertical;outline:none;background:var(--surface-2);color:var(--text-primary)"></textarea>
|
||
</div>
|
||
<p style="font-size:11px;color:var(--text-faint);margin-bottom:16px">Use [CLIENTE], [CONTATO], [EMPRESAF] para dados automáticos.</p>
|
||
<div class="btn-group">
|
||
<button onclick="document.getElementById('retornoConversaId').value='';fecharModal('modalRetorno')">Cancelar</button>
|
||
<button class="btn-primary" onclick="confirmarRetorno()">✅ Abrir conversa</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal associar cliente -->
|
||
<div class="modal-overlay" id="modalAssociar">
|
||
<div class="modal">
|
||
<h3>🔗 Associar</h3>
|
||
<div style="display:flex;gap:12px;margin-bottom:12px">
|
||
<label style="cursor:pointer;font-size:13px"><input type="radio" name="tipoAssoc" value="cliente" checked onchange="mudarTipoAssoc()"> Cliente</label>
|
||
<label style="cursor:pointer;font-size:13px"><input type="radio" name="tipoAssoc" value="dependente" onchange="mudarTipoAssoc()"> Dependente</label>
|
||
</div>
|
||
<p style="font-size:13px;color:#6b7280;margin-bottom:12px" id="assocLabel">Digite o nome, CPF ou matrícula do cliente:</p>
|
||
<input type="text" id="associarBusca" placeholder="Buscar..." onkeyup="buscarClienteAssociar(event)">
|
||
<div id="associarResultados" style="max-height:200px;overflow-y:auto"></div>
|
||
<div class="btn-group">
|
||
<button onclick="fecharModal('modalAssociar')">Cancelar</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal enviar boleto -->
|
||
<div class="modal-overlay" id="modalBoleto">
|
||
<div class="modal">
|
||
<h3>💳 Enviar boleto</h3>
|
||
<p style="font-size:13px;color:#6b7280;margin-bottom:12px">Selecione o título a enviar para o cliente:</p>
|
||
<div id="boletoLista" style="max-height:320px;overflow-y:auto"></div>
|
||
<div class="btn-group">
|
||
<button onclick="fecharModalBoleto()">Fechar</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sidebar-left">
|
||
<div class="header">
|
||
<h2>💬 Chatc2</h2>
|
||
<div class="header-nav">
|
||
<a id="navDashChat" href="#" title="Dashboard">📊</a>
|
||
<a id="navClientsChat" href="#" title="Clientes">👥</a>
|
||
<a id="navConfigChat" class="admin-only-nav" href="#" title="Configurações">⚙️</a>
|
||
</div>
|
||
</div>
|
||
<div id="empresaSwitcherWrap" style="display:none;padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.08)">
|
||
<select id="empresaSwitcher" onchange="trocarEmpresa(this.value)" style="width:100%;padding:6px 8px;border:1px solid rgba(255,255,255,0.15);border-radius:6px;font-size:12px;background:rgba(255,255,255,0.06);color:#fff;outline:none"></select>
|
||
</div>
|
||
<div class="nav-tabs">
|
||
<a href="#" class="active" onclick="mudarFiltro('mine',this)">Minhas (0)</a>
|
||
<a href="#" onclick="mudarFiltro('unassigned',this)">Sem atend. (0)</a>
|
||
<a href="#" id="tabTodasConvs" onclick="mudarFiltro('all',this)" class="admin-only-nav">Todas</a>
|
||
<a href="#" onclick="mudarFiltro('equipe',this)" id="tabEquipeConvs">Equipe (0)</a>
|
||
<a href="#" onclick="mudarFiltro('scheduled',this)" id="tabAgendadas">📅 Agendadas (0)</a>
|
||
</div>
|
||
<div class="search-box">
|
||
<input type="text" id="searchConv" placeholder="🔍 Buscar conversas..." onkeyup="carregarConversas()">
|
||
<button id="btnNovaConversa" onclick="mostrarModalNovaConversa()" title="Nova conversa" style="background:rgba(255,255,255,0.12);border:1px solid rgba(255,255,255,0.2);color:#fff;border-radius:8px;width:32px;height:32px;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:all .15s" onmouseover="this.style.background='rgba(255,255,255,0.22)'" onmouseout="this.style.background='rgba(255,255,255,0.12)'">+</button>
|
||
<label>
|
||
<input type="checkbox" id="chkFinalizadas" onchange="carregarConversas()"> Finalizadas
|
||
</label>
|
||
</div>
|
||
<div class="conversations-list" id="conversationsList"></div>
|
||
<div class="sidebar-footer">
|
||
<button class="dark-mode-toggle" onclick="darkModeToggle()">🌙 Escuro</button>
|
||
<a onclick="logout()">🚪 Sair</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="chat-center">
|
||
<div class="chat-header" id="chatHeader" style="display:none">
|
||
<button class="mobile-only" onclick="voltarLista()" title="Voltar" style="background:none;border:none;font-size:20px;cursor:pointer;padding:4px 6px;color:#374151" aria-label="Voltar para lista de conversas">←</button>
|
||
<button class="menu-hamburger" onclick="toggleSidebar()" aria-label="Menu" style="display:none;margin-right:4px">☰</button>
|
||
<div class="info">
|
||
<h3 id="chatNome"></h3>
|
||
<span id="chatStatus"></span>
|
||
<span class="connection-status online" id="connectionStatus" style="display:none">
|
||
<span class="conn-dot"></span>
|
||
<span id="connectionLabel">Conectado</span>
|
||
</span>
|
||
<h3 id="chatNome"></h3>
|
||
<span id="chatStatus"></span>
|
||
</div>
|
||
<div class="actions">
|
||
<button class="mobile-only" onclick="toggleInfo()" title="Informações" aria-label="Informações do cliente">ℹ️</button>
|
||
<button class="btn-finalizar" onclick="confirmarFinalizar()" aria-label="Finalizar conversa">✅ Finalizar</button>
|
||
</div>
|
||
</div>
|
||
<div class="messages-area" id="messagesArea">
|
||
<div class="empty-state">
|
||
<div class="icon">💬</div>
|
||
<p>Selecione uma conversa</p>
|
||
</div>
|
||
</div>
|
||
<div class="chat-input" id="chatInput" style="display:none">
|
||
<button class="privada-toggle" id="privadaToggle" onclick="togglePrivada()" title="Alternar mensagem privada" aria-label="Mensagem privada">
|
||
<span class="priv-icon">🔒</span> <span id="privadaLabel">Normal</span>
|
||
</button>
|
||
<div class="action-menu-wrap">
|
||
<button class="btn-action" id="btnPlus" onclick="toggleActionMenu()" title="Mais ações" aria-label="Mais ações">+</button>
|
||
<div class="action-menu" id="actionMenu">
|
||
<button onclick="document.getElementById('fileInput').click();toggleActionMenu()" aria-label="Anexar arquivo">📎 Anexar</button>
|
||
<button id="btnAudio" class="btn-audio" onclick="toggleGravacao();toggleActionMenu()" aria-label="Gravar áudio">🎤 Gravar</button>
|
||
</div>
|
||
</div>
|
||
<input type="file" id="fileInput" style="display:none" accept="image/*,audio/*" onchange="enviarMidia(this)" aria-label="Selecionar arquivo">
|
||
<textarea id="msgInput" placeholder="Digite uma mensagem..." rows="1" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();enviarMensagem()}" oninput="toggleBtnEnviar()" aria-label="Mensagem de texto"></textarea>
|
||
<button class="btn-enviar" id="btnEnviar" onclick="enviarMensagem()" title="Enviar mensagem" aria-label="Enviar mensagem" disabled>➤</button>
|
||
<div id="audioPreview" style="display:none;align-items:center;gap:8px;flex:1">
|
||
<audio controls style="height:38px;flex:1" id="audioPreviewPlayer" aria-label="Pré-visualização do áudio"></audio>
|
||
<button class="btn-enviar" onclick="enviarAudioGravado()" title="Enviar áudio" aria-label="Enviar áudio gravado">➤</button>
|
||
<button class="btn-action" onclick="cancelarAudioGravado()" title="Cancelar" aria-label="Cancelar gravação" style="background:#fef2f2;color:#ef4444;width:36px;height:36px">✕</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sidebar-right" id="sidebarRight" style="display:none">
|
||
<div class="header"><h3>📋 Informações</h3></div>
|
||
<div class="cliente-foto" id="clienteFoto">?</div>
|
||
|
||
<button class="accordion-toggle open" onclick="toggleAccordion(this,'accordionCliente')">
|
||
DADOS DO CLIENTE <span class="arrow">▸</span>
|
||
</button>
|
||
<div class="accordion-body open" id="accordionCliente">
|
||
<div class="info-section" id="clienteInfoContainer"></div>
|
||
</div>
|
||
|
||
<!-- Fluxo de Resolução -->
|
||
<button class="accordion-toggle" id="accResolucaoToggle" style="display:none" onclick="toggleAccordion(this,'accordionResolucao')">
|
||
RESOLUÇÃO <span class="arrow">▸</span>
|
||
</button>
|
||
<div class="accordion-body" id="accordionResolucao">
|
||
<div class="info-section" id="resolucaoPanel" style="display:none">
|
||
<div class="label" style="font-size:10px;text-transform:uppercase;color:#9ca3af;margin-bottom:6px">RESOLUÇÃO DO ATENDIMENTO</div>
|
||
<div id="motivoWrap" style="display:none;margin-bottom:8px">
|
||
<select id="selectMotivo" style="width:100%;padding:6px 8px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;outline:none">
|
||
<option value="">Selecione o motivo...</option>
|
||
</select>
|
||
</div>
|
||
<div id="resolucaoWrap" style="display:none">
|
||
<textarea id="campoResolucao" rows="3" placeholder="Descreva a resolução do atendimento..." style="width:100%;padding:8px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;outline:none;resize:vertical;font-family:inherit"></textarea>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button class="accordion-toggle open" onclick="toggleAccordion(this,'accordionClassificacao')">
|
||
CLASSIFICAÇÃO <span class="arrow">▸</span>
|
||
</button>
|
||
<div class="accordion-body open" id="accordionClassificacao">
|
||
<div class="info-section">
|
||
<div class="label" style="font-size:10px;text-transform:uppercase;color:#9ca3af;margin-bottom:4px">ATENDENTE</div>
|
||
<select id="selectAtendente" style="width:100%;padding:6px 8px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;outline:none" onchange="mudarAtendente(this)">
|
||
<option value="">Selecione...</option>
|
||
</select>
|
||
</div>
|
||
<div class="info-section">
|
||
<div class="label" style="font-size:10px;text-transform:uppercase;color:#9ca3af;margin-bottom:4px">EQUIPE</div>
|
||
<select id="selectEquipe" style="width:100%;padding:6px 8px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;outline:none" onchange="mudarEquipe(this)">
|
||
<option value="">Sem equipe</option>
|
||
</select>
|
||
</div>
|
||
<div class="labels-section" id="labelsContainer">
|
||
<div class="label" style="font-size:10px;text-transform:uppercase;color:#9ca3af;margin-bottom:4px">ETIQUETAS</div>
|
||
<div id="etiquetasAtivas" style="margin-bottom:6px"></div>
|
||
<select id="selectEtiqueta" style="width:100%;padding:6px 8px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;outline:none" onchange="adicionarEtiqueta(this)">
|
||
<option value="">Adicionar etiqueta...</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="acoes" id="acoesContainer">
|
||
<button onclick="mostrarModalAssociar()"><span class="icon">🔗</span> Associar</button>
|
||
<button onclick="irPaginaCliente()"><span class="icon">📋</span> Dados do Titular</button>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(function(){
|
||
'use strict';
|
||
|
||
const token = localStorage.getItem('chatc2_token');
|
||
const pathParts = window.location.pathname.split('/');
|
||
const alias = pathParts[2];
|
||
let empresaId = pathParts[4];
|
||
const conversaId = pathParts[6];
|
||
const usuarioLogado = JSON.parse(localStorage.getItem('chatc2_user') || '{}');
|
||
// Navegacao rapida no header do chat
|
||
var tc = usuarioLogado.tipoChat || "A";
|
||
if (tc === "G") {
|
||
var els = document.querySelectorAll(".admin-only-nav");
|
||
for (var i = 0; i < els.length; i++) els[i].style.display = "";
|
||
}
|
||
document.getElementById("navDashChat").onclick = function(e) { e.preventDefault(); window.location.href = "/app/" + alias + "/dashboard"; };
|
||
document.getElementById("navClientsChat").onclick = function(e) { e.preventDefault(); window.location.href = "/app/" + alias + "/clients"; };
|
||
var cfgChat = document.getElementById("navConfigChat");
|
||
if (cfgChat) cfgChat.onclick = function(e) { e.preventDefault(); window.location.href = "/app/" + alias + "/settings"; };
|
||
|
||
if (!token) { window.location.href = '/app/' + alias + '/login'; return; }
|
||
|
||
let conversaAtiva = conversaId || null;
|
||
let mensagens = [];
|
||
let carregandoMsgs = false;
|
||
|
||
// Escapa HTML para evitar XSS armazenado (conteúdo vem do WhatsApp/cliente)
|
||
function esc(s){ return String(s == null ? '' : s)
|
||
.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
|
||
.replace(/"/g,'"').replace(/'/g,'''); }
|
||
|
||
// ===== FLUXO DE RESOLUÇÃO =====
|
||
var cfgResolucao = { motivoVisualizar:'N', motivoObrigatorio:'N', resolucaoVisualizar:'N', resolucaoObrigatorio:'N' };
|
||
var motivosList = [];
|
||
async function carregarConfigResolucao() {
|
||
try {
|
||
var cd = await (await fetch('/api/' + alias + '/company/config?empresaId=' + empresaId, { headers: { 'Authorization': 'Bearer ' + token } })).json();
|
||
if (cd.success && cd.data) cfgResolucao = cd.data;
|
||
var md = await (await fetch('/api/' + alias + '/motivos?empresaId=' + empresaId, { headers: { 'Authorization': 'Bearer ' + token } })).json();
|
||
if (md.success) motivosList = md.data || [];
|
||
} catch(e) {}
|
||
}
|
||
function aplicarPainelResolucao(conv) {
|
||
var panel = document.getElementById('resolucaoPanel');
|
||
var accToggle = document.getElementById('accResolucaoToggle');
|
||
var accBody = document.getElementById('accordionResolucao');
|
||
if (!panel) return;
|
||
var verMot = cfgResolucao.motivoVisualizar === 'S';
|
||
var verRes = cfgResolucao.resolucaoVisualizar === 'S';
|
||
var mostrar = verMot || verRes;
|
||
panel.style.display = mostrar ? 'block' : 'none';
|
||
if (accToggle) accToggle.style.display = mostrar ? 'flex' : 'none';
|
||
if (mostrar && accBody) {
|
||
accToggle.classList.add('open');
|
||
accBody.classList.add('open');
|
||
}
|
||
document.getElementById('motivoWrap').style.display = verMot ? 'block' : 'none';
|
||
document.getElementById('resolucaoWrap').style.display = verRes ? 'block' : 'none';
|
||
if (verMot) {
|
||
var sel = document.getElementById('selectMotivo');
|
||
sel.innerHTML = '<option value="">Selecione o motivo...</option>' +
|
||
motivosList.map(function(m){ return '<option value="' + m.id + '">' + esc(m.descricao) + '</option>'; }).join('');
|
||
sel.value = (conv && conv.motivoId) ? String(conv.motivoId) : '';
|
||
}
|
||
if (verRes) {
|
||
document.getElementById('campoResolucao').value = (conv && conv.resolucao) ? conv.resolucao : '';
|
||
}
|
||
}
|
||
|
||
// ===== NAVEGAÇÃO =====
|
||
document.querySelectorAll('.sidebar-left .nav-tabs a').forEach(function(a) {
|
||
a.addEventListener('click', function(e) { e.preventDefault(); });
|
||
});
|
||
|
||
// ===== CARREGAR CONVERSAS =====
|
||
async function carregarConversas() {
|
||
// Aba Agendadas: carrega da API de agendamentos
|
||
if (filtroAtual === 'scheduled') {
|
||
return carregarAgendadas();
|
||
}
|
||
|
||
const busca = document.getElementById('searchConv').value;
|
||
var statusFilter = document.getElementById('chkFinalizadas').checked ? 'A,E,F' : 'A,E';
|
||
try {
|
||
const res = await fetch('/api/' + alias + '/conversations?empresaId=' + empresaId + '&status=' + statusFilter + '&filter=' + filtroAtual, {
|
||
headers: { 'Authorization': 'Bearer ' + token }
|
||
});
|
||
const data = await res.json();
|
||
if (!data.success) return;
|
||
// Atualiza contagens nos filtros
|
||
if (data.contagens) {
|
||
var filtros = document.querySelectorAll('.nav-tabs a');
|
||
filtros.forEach(function(f) {
|
||
var txt = f.textContent.replace(/\s*\(\d+\)/, '').trim();
|
||
if (f.getAttribute('onclick') && f.getAttribute('onclick').includes('mine')) {
|
||
f.innerHTML = txt + ' (' + (data.contagens.mine || 0) + ')';
|
||
} else if (f.getAttribute('onclick') && f.getAttribute('onclick').includes('unassigned')) {
|
||
f.innerHTML = txt + ' (' + (data.contagens.unassigned || 0) + ')';
|
||
} else if (f.getAttribute('onclick') && f.getAttribute('onclick').includes('equipe')) {
|
||
f.innerHTML = txt + ' (' + (data.contagens.equipe || 0) + ')';
|
||
}
|
||
});
|
||
}
|
||
renderConversas(data.data, busca);
|
||
} catch(e) { if (Chatc2Toast) Chatc2Toast.error('Erro ao carregar conversas'); }
|
||
}
|
||
|
||
var AVATAR_COLORS = ['#667eea','#764ba2','#f093fb','#4facfe','#43e97b','#fa709a','#fee140','#30cfd0','#a18cd1','#fda085'];
|
||
function avatarColor(str) {
|
||
var h = 0;
|
||
for (var i = 0; i < (str||'').length; i++) h = (h * 31 + str.charCodeAt(i)) & 0xffff;
|
||
return AVATAR_COLORS[h % AVATAR_COLORS.length];
|
||
}
|
||
function avatarInitials(name) {
|
||
if (!name) return '?';
|
||
var parts = name.trim().split(' ');
|
||
if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();
|
||
return name.substring(0, 2).toUpperCase();
|
||
}
|
||
function formatTime(dt) {
|
||
if (!dt) return '';
|
||
var d = new Date(dt);
|
||
var now = new Date();
|
||
var diff = now - d;
|
||
if (diff < 60000) return 'agora';
|
||
if (diff < 3600000) return Math.floor(diff/60000) + 'min';
|
||
if (d.toDateString() === now.toDateString()) return d.toLocaleTimeString('pt-BR', {hour:'2-digit',minute:'2-digit'});
|
||
return d.toLocaleDateString('pt-BR', {day:'2-digit',month:'2-digit'});
|
||
}
|
||
|
||
function renderConversas(convs, busca) {
|
||
const list = document.getElementById('conversationsList');
|
||
let filtered = convs;
|
||
if (busca) {
|
||
const q = busca.toLowerCase();
|
||
filtered = convs.filter(function(c) {
|
||
return (c.nomeContato && c.nomeContato.toLowerCase().includes(q)) ||
|
||
(c.numero && c.numero.includes(q));
|
||
});
|
||
}
|
||
if (filtered.length === 0) {
|
||
list.innerHTML = '<div style="padding:24px;text-align:center;color:rgba(255,255,255,0.4);font-size:13px">Nenhuma conversa</div>';
|
||
return;
|
||
}
|
||
list.innerHTML = filtered.map(function(c) {
|
||
const isActive = c.id == conversaAtiva ? 'active' : '';
|
||
const nome = c.nomeContato || c.numero || 'Desconhecido';
|
||
const cor = avatarColor(nome);
|
||
const iniciais = avatarInitials(nome);
|
||
const statusDot = c.status === 'E' ? '🟡' : c.status === 'A' ? '🟢' : '⚫';
|
||
const labelsHtml = (c.labels || []).map(function(l) {
|
||
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>' +
|
||
'<div class="conv-body">' +
|
||
'<div style="display:flex;justify-content:space-between;align-items:baseline;gap:4px">' +
|
||
'<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">' + 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>' +
|
||
'</div>';
|
||
}).join('');
|
||
}
|
||
|
||
// ===== ABRIR CONVERSA =====
|
||
window.abrirConversa = async function(id, skipFotoRefresh) {
|
||
conversaAtiva = id;
|
||
// Mobile: alterna para a visão da conversa (e fecha o painel de info)
|
||
document.body.classList.add('chat-aberto');
|
||
document.body.classList.remove('info-aberto');
|
||
document.querySelectorAll('.conv-item').forEach(function(el) { el.classList.remove('active'); });
|
||
var item = document.querySelector('.conv-item[data-id="' + id + '"]');
|
||
if (item) item.classList.add('active');
|
||
|
||
// Atualiza a URL sem recarregar a página
|
||
var novaUrl = '/app/' + alias + '/company/' + empresaId + '/conversation/' + id;
|
||
window.history.pushState({ conversaId: id }, '', novaUrl);
|
||
|
||
document.getElementById('chatHeader').style.display = 'flex';
|
||
document.getElementById('chatInput').style.display = 'flex';
|
||
document.getElementById('sidebarRight').style.display = 'flex';
|
||
|
||
try {
|
||
var res = await fetch('/api/' + alias + '/conversations/' + id, {
|
||
headers: { 'Authorization': 'Bearer ' + token }
|
||
});
|
||
var data = await res.json();
|
||
if (!data.success) return;
|
||
renderInfoConversa(data.data);
|
||
// Foto automática: se o cliente está vinculado mas sem foto, busca via Evolution
|
||
// em segundo plano e recarrega o painel quando a foto chegar.
|
||
if (!skipFotoRefresh && data.data && data.data.cliente && !data.data.cliente.foto) {
|
||
autoAtualizarFoto(id);
|
||
}
|
||
} catch(e) {}
|
||
|
||
carregarMensagens();
|
||
};
|
||
|
||
// Busca a foto do cliente via Evolution (fetchProfile) em segundo plano, sem bloquear.
|
||
function autoAtualizarFoto(id) {
|
||
fetch('/api/' + alias + '/evolution/refresh-photo/' + id, {
|
||
method: 'POST', headers: { 'Authorization': 'Bearer ' + token }
|
||
}).then(function(r){ return r.json(); }).then(function(r){
|
||
// Recarrega apenas se a foto foi atualizada e ainda estamos na mesma conversa
|
||
if (r && r.success && r.atualizada && conversaAtiva === id) abrirConversa(id, true);
|
||
}).catch(function(){});
|
||
}
|
||
|
||
function renderInfoConversa(conv) {
|
||
document.getElementById('chatNome').textContent = conv.nomeContato || conv.numero || 'Desconhecido';
|
||
document.getElementById('chatStatus').textContent = conv.status === 'E' ? '🟡 Em espera' : conv.status === 'A' ? '🟢 Em atendimento' : '⚫ Finalizado';
|
||
|
||
if (conv.status === 'F') {
|
||
document.querySelector('.btn-finalizar').textContent = '✅ Finalizado';
|
||
document.querySelector('.btn-finalizar').disabled = true;
|
||
document.querySelector('.btn-finalizar').style.opacity = '0.6';
|
||
} else {
|
||
document.querySelector('.btn-finalizar').textContent = '✅ Finalizar';
|
||
document.querySelector('.btn-finalizar').disabled = false;
|
||
document.querySelector('.btn-finalizar').style.opacity = '1';
|
||
}
|
||
|
||
window._convAtual = conv;
|
||
aplicarPainelResolucao(conv);
|
||
|
||
// Info cliente
|
||
var infoContainer = document.getElementById('clienteInfoContainer');
|
||
var fotoContainer = document.getElementById('clienteFoto');
|
||
|
||
if (conv.cliente) {
|
||
var c = conv.cliente;
|
||
if (c.foto) {
|
||
var srcFoto = c.foto;
|
||
if (srcFoto.indexOf('data:') !== 0 && srcFoto.length > 50) {
|
||
srcFoto = 'data:image/jpeg;base64,' + srcFoto;
|
||
}
|
||
fotoContainer.innerHTML = '<img src="' + esc(srcFoto) + '">';
|
||
} else {
|
||
fotoContainer.innerHTML = c.nome ? esc(c.nome.charAt(0)) : '?';
|
||
}
|
||
var inadClass = c.inadimplente ? 'inadimplente' : 'adimplente';
|
||
var inadLabel = c.inadimplente ? '🔴 Inadimplente' : '🟢 Adimplente';
|
||
infoContainer.innerHTML =
|
||
'<div class="value" style="font-size:16px;font-weight:600;margin-bottom:8px">' + esc(c.nome) + '</div>' +
|
||
(c.cidade || c.bairro ? '<div class="field"><div class="label">Cidade/Bairro</div><div class="value">' + esc(c.cidade || '') + (c.bairro ? ' - ' + esc(c.bairro) : '') + '</div></div>' : '') +
|
||
(c.matricula ? '<div class="field"><div class="label">Matrícula</div><div class="value">' + esc(c.matricula) + '</div></div>' : '') +
|
||
(c.plano ? '<div class="field"><div class="label">Plano</div><div class="value">' + esc(c.plano) + '</div></div>' : '') +
|
||
'<div class="field"><div class="label">Contato</div><div class="value">' + esc(c.celular || c.telefone || conv.numero || '-') + '</div></div>' +
|
||
'<div class="field"><div class="label ' + inadClass + '">' + inadLabel + '</div></div>';
|
||
} else if (conv.dependente) {
|
||
var dep = conv.dependente;
|
||
fotoContainer.textContent = (dep.nome || '?').charAt(0).toUpperCase();
|
||
var inads = dep.titularInadimplente ? '<span style="color:#ef4444;font-weight:600">🔴 Inadimplente</span>' : '<span style="color:#059669;font-weight:600">🟢 Adimplente</span>';
|
||
infoContainer.innerHTML =
|
||
'<div style="font-size:16px;font-weight:600;color:#111827">' + esc(dep.nome) + '</div>' +
|
||
'<div style="font-size:12px;color:#667eea;font-weight:500;margin:2px 0 8px">📋 Dependente</div>' +
|
||
(dep.titularCidade || dep.titularBairro ? '<div class="field"><div class="label">Cidade/Bairro</div><div class="value">' + esc(dep.titularCidade || '') + (dep.titularBairro ? ' - ' + esc(dep.titularBairro) : '') + '</div></div>' : '') +
|
||
(dep.titularMatricula ? '<div class="field"><div class="label">Matrícula (Titular)</div><div class="value">' + esc(dep.titularMatricula) + '</div></div>' : '') +
|
||
(dep.titularPlano ? '<div class="field"><div class="label">Plano</div><div class="value">' + esc(dep.titularPlano) + '</div></div>' : '') +
|
||
'<div class="field"><div class="label">Contato</div><div class="value">' + esc(dep.telefone || conv.numero || '-') + '</div></div>' +
|
||
'<div class="field"><div class="label">' + inads + '</div></div>';
|
||
} else {
|
||
// Sem cliente vinculado: mostra o número e nome do contato WhatsApp
|
||
var numeroExibir = (conv.numero || '').replace(/^(\d{2})(\d{2})(\d{5})(\d{4})$/, '($1) $2 $3-$4').replace(/^(\d{2})(\d{5})(\d{4})$/, '($1) $2-$3');
|
||
if (!numeroExibir) numeroExibir = conv.numero || '-';
|
||
var nomeContato = (conv.nomeContato || '').trim();
|
||
// Se o nome do contato for igual ao número (formato puro), mostra só o número formatado
|
||
var nomeLimpo = nomeContato.replace(/\D/g, '');
|
||
var numLimpo = (conv.numero || '').replace(/\D/g, '');
|
||
if (nomeLimpo === numLimpo) nomeContato = '';
|
||
|
||
fotoContainer.innerHTML = '<span style="font-weight:600">?</span>';
|
||
infoContainer.innerHTML =
|
||
'<div style="font-size:13px;font-weight:600;color:#d97706;margin-bottom:6px">⚠️ Número não identificado</div>' +
|
||
'<div style="font-size:14px;font-weight:600;color:var(--text-primary);margin-bottom:4px">' + esc(nomeContato || numeroExibir) + '</div>' +
|
||
(nomeContato ? '<div class="field"><div class="label">WhatsApp</div><div class="value">' + esc(numeroExibir) + '</div></div>' : '') +
|
||
'<div class="field"><div class="label">Status</div><div class="value" style="color:var(--text-muted)">Sem cadastro no sistema</div></div>';
|
||
}
|
||
|
||
// Botão "Enviar boleto" (aparece se a empresa habilitar e houver titular)
|
||
var boletoClienteId = conv.cliente ? conv.cliente.id : (conv.dependente ? conv.dependente.titularId : null);
|
||
if (cfgResolucao.enviarBoleto === 'S' && boletoClienteId && conv.status !== 'F') {
|
||
infoContainer.innerHTML +=
|
||
'<button id="btnEnviarBoleto" onclick="abrirModalBoleto(' + boletoClienteId + ')" style="margin-top:10px;width:100%;padding:8px 10px;border:1px solid #c7d2fe;border-radius:6px;background:#eef2ff;cursor:pointer;font-size:13px;color:#4338ca;font-weight:600">💳 Enviar boleto</button>';
|
||
}
|
||
|
||
// Verifica convalescentes pendentes (situação E = entrega)
|
||
var clienteId = conv.cliente ? conv.cliente.id : (conv.dependente ? conv.dependente.titularId : null);
|
||
if (clienteId) {
|
||
(async function() {
|
||
try {
|
||
var r = await fetch('/api/' + alias + '/clients/' + clienteId + '/convalescentes', {
|
||
headers: { 'Authorization': 'Bearer ' + token }
|
||
});
|
||
var d = await r.json();
|
||
if (d.success && d.data && d.data.length > 0) {
|
||
// Apenas situação 'E' = Entrega pendente
|
||
var pendentes = d.data.filter(function(c) {
|
||
return (c.situacao || '').toUpperCase() === 'E';
|
||
});
|
||
if (pendentes.length > 0) {
|
||
infoContainer.innerHTML += '<div class="field" style="margin-top:8px;padding-top:8px;border-top:1px solid #e5e7eb"><div class="label" style="color:#d97706;font-weight:600">🛏️ <strong>' + pendentes.length + ' convalescente(s) para entregar</strong></div></div>';
|
||
}
|
||
}
|
||
} catch(e) { /* ignora */ }
|
||
})();
|
||
}
|
||
|
||
// Carrega dropdowns de atendente, equipe e etiquetas
|
||
carregarAtendentes(conv.usuarioId);
|
||
carregarEquipes(conv.equipeId);
|
||
carregarEtiquetasDisponiveis(conv.labels);
|
||
|
||
// Etiquetas ativas
|
||
renderEtiquetasAtivas(conv.labels || []);
|
||
window.conversaAtual = conv;
|
||
}
|
||
|
||
// ===== CARREGAR ATENDENTES (FILTRADO POR EQUIPE) =====
|
||
var _todosUsuarios = null;
|
||
function carregarAtendentes(selectedId) {
|
||
var sel = document.getElementById('selectAtendente');
|
||
if (_todosUsuarios) {
|
||
filtrarAtendentesPorEquipe(sel, _todosUsuarios, selectedId);
|
||
return;
|
||
}
|
||
fetch('/api/' + alias + '/company/users?empresaId=' + empresaId, { headers: { 'Authorization': 'Bearer ' + token } })
|
||
.then(function(r) { return r.json(); })
|
||
.then(function(d) {
|
||
if (d.success) {
|
||
_todosUsuarios = d.data;
|
||
filtrarAtendentesPorEquipe(sel, d.data, selectedId);
|
||
}
|
||
}).catch(function() {});
|
||
}
|
||
|
||
function getMembrosDaEquipe(equipeId) {
|
||
if (!equipeId || !_equipesCache) return null;
|
||
var equipe = _equipesCache.find(function(e) { return e.id === equipeId; });
|
||
return equipe ? (equipe.membros || []).map(function(m) { return m.id; }) : null;
|
||
}
|
||
|
||
function filtrarAtendentesPorEquipe(sel, users, selectedId) {
|
||
var equipeId = parseInt(document.getElementById('selectEquipe').value);
|
||
var membrosIds = getMembrosDaEquipe(equipeId);
|
||
|
||
var filtrados = users;
|
||
if (membrosIds) {
|
||
filtrados = users.filter(function(u) { return membrosIds.includes(u.id); });
|
||
}
|
||
|
||
sel.innerHTML = '<option value="">' + (membrosIds ? 'Nenhum da equipe' : 'Nenhum') + '</option>';
|
||
filtrados.forEach(function(u) {
|
||
var opt = document.createElement('option');
|
||
opt.value = u.id;
|
||
opt.textContent = u.nome + ' (' + u.login + ')';
|
||
if (u.id === selectedId) opt.selected = true;
|
||
sel.appendChild(opt);
|
||
});
|
||
|
||
if (membrosIds && filtrados.length === 0) {
|
||
sel.innerHTML = '<option value="">Nenhum usuário nesta equipe</option>';
|
||
}
|
||
}
|
||
|
||
window.mudarAtendente = function(sel) {
|
||
var val = sel.value;
|
||
fetch('/api/' + alias + '/conversations/' + conversaAtiva + '/assign', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||
body: JSON.stringify({ usuarioId: val ? parseInt(val) : null })
|
||
}).catch(function() {});
|
||
};
|
||
|
||
// ===== CARREGAR EQUIPES =====
|
||
var _equipesCache = null;
|
||
function carregarEquipes(selectedId) {
|
||
var sel = document.getElementById('selectEquipe');
|
||
if (_equipesCache) {
|
||
preencherEquipes(sel, _equipesCache, selectedId);
|
||
return;
|
||
}
|
||
fetch('/api/' + alias + '/teams?empresaId=' + empresaId, { headers: { 'Authorization': 'Bearer ' + token } })
|
||
.then(function(r) { return r.json(); })
|
||
.then(function(d) {
|
||
if (d.success) {
|
||
_equipesCache = d.data;
|
||
preencherEquipes(sel, d.data, selectedId);
|
||
}
|
||
}).catch(function() {});
|
||
}
|
||
|
||
function preencherEquipes(sel, teams, selectedId) {
|
||
sel.innerHTML = '<option value="">Sem equipe</option>';
|
||
teams.forEach(function(t) {
|
||
var opt = document.createElement('option');
|
||
opt.value = t.id;
|
||
opt.textContent = t.nome;
|
||
if (t.id === selectedId) opt.selected = true;
|
||
sel.appendChild(opt);
|
||
});
|
||
}
|
||
|
||
window.mudarEquipe = function(sel) {
|
||
var val = sel.value;
|
||
fetch('/api/' + alias + '/conversations/' + conversaAtiva + '/assign-team', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||
body: JSON.stringify({ equipeId: val ? parseInt(val) : null })
|
||
}).catch(function() {});
|
||
// Recarrega atendentes filtrando pela nova equipe
|
||
if (_todosUsuarios) {
|
||
var selAtend = document.getElementById('selectAtendente');
|
||
var currentAttendant = window._convAtual ? window._convAtual.usuarioId : null;
|
||
filtrarAtendentesPorEquipe(selAtend, _todosUsuarios, currentAttendant);
|
||
}
|
||
};
|
||
|
||
// ===== ETIQUETAS =====
|
||
var _etiquetasCache = null;
|
||
function carregarEtiquetasDisponiveis(ativas) {
|
||
var sel = document.getElementById('selectEtiqueta');
|
||
if (_etiquetasCache) {
|
||
preencherEtiquetas(sel, _etiquetasCache, ativas);
|
||
return;
|
||
}
|
||
fetch('/api/' + alias + '/labels?empresaId=' + empresaId, { headers: { 'Authorization': 'Bearer ' + token } })
|
||
.then(function(r) { return r.json(); })
|
||
.then(function(d) {
|
||
if (d.success) {
|
||
_etiquetasCache = d.data;
|
||
preencherEtiquetas(sel, d.data, ativas);
|
||
}
|
||
}).catch(function() {});
|
||
}
|
||
|
||
function preencherEtiquetas(sel, labels, ativas) {
|
||
var ativasIds = (ativas || []).map(function(l) { return l.id; });
|
||
sel.innerHTML = '<option value="">Adicionar etiqueta...</option>';
|
||
labels.forEach(function(l) {
|
||
if (!ativasIds.includes(l.id)) {
|
||
var opt = document.createElement('option');
|
||
opt.value = l.id;
|
||
opt.textContent = l.nome;
|
||
sel.appendChild(opt);
|
||
}
|
||
});
|
||
}
|
||
|
||
window.adicionarEtiqueta = function(sel) {
|
||
var val = sel.value;
|
||
if (!val) return;
|
||
fetch('/api/' + alias + '/conversations/' + conversaAtiva + '/labels', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||
body: JSON.stringify({ etiquetaId: parseInt(val), ativar: true })
|
||
})
|
||
.then(function(r) { return r.json(); })
|
||
.then(function(d) {
|
||
if (d.success) {
|
||
// Recarrega a conversa para mostrar a nova etiqueta
|
||
abrirConversa(conversaAtiva);
|
||
}
|
||
}).catch(function() {});
|
||
sel.value = '';
|
||
};
|
||
|
||
function renderEtiquetasAtivas(labels) {
|
||
var div = document.getElementById('etiquetasAtivas');
|
||
if (!labels || labels.length === 0) {
|
||
div.innerHTML = '<span style="font-size:12px;color:#9ca3af">Nenhuma</span>';
|
||
return;
|
||
}
|
||
div.innerHTML = labels.map(function(l) {
|
||
return '<span class="label-tag" style="background:' + (l.cor || '#667eea') + ';cursor:pointer;position:relative" onclick="removerEtiqueta(' + l.id + ')" title="Clique para remover">' + (l.nome || '') + ' ✕</span>';
|
||
}).join(' ');
|
||
}
|
||
|
||
window.removerEtiqueta = function(etiquetaId) {
|
||
fetch('/api/' + alias + '/conversations/' + conversaAtiva + '/labels', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||
body: JSON.stringify({ etiquetaId: etiquetaId, ativar: false })
|
||
})
|
||
.then(function(r) { return r.json(); })
|
||
.then(function(d) {
|
||
if (d.success) abrirConversa(conversaAtiva);
|
||
}).catch(function() {});
|
||
};
|
||
|
||
// ===== MENSAGENS =====
|
||
async function carregarMensagens() {
|
||
if (!conversaAtiva || carregandoMsgs) return;
|
||
carregandoMsgs = true;
|
||
|
||
try {
|
||
var res = await fetch('/api/' + alias + '/conversations/' + conversaAtiva + '/messages?page=1&limit=100', {
|
||
headers: { 'Authorization': 'Bearer ' + token }
|
||
});
|
||
var data = await res.json();
|
||
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;
|
||
}
|
||
|
||
function renderMensagens(msgs) {
|
||
mensagens = msgs;
|
||
var area = document.getElementById('messagesArea');
|
||
if (msgs.length === 0) {
|
||
area.innerHTML = '<div class="empty-state"><div class="icon">💬</div><p>Nenhuma mensagem ainda</p></div>';
|
||
return;
|
||
}
|
||
area.innerHTML = msgs.map(function(m) {
|
||
var classe = m.remetente === 'U' ? 'usuario' : m.remetente === 'S' ? 'sistema' : 'cliente';
|
||
if (m.privada === 'S') classe += ' privada';
|
||
var conteudo = esc(m.mensagem || '');
|
||
var mediaTok = '&token=' + encodeURIComponent(token);
|
||
|
||
// IMAGEM
|
||
if ((m.tipo === 'image' || m.tipo === 'imageMessage') && m.midiaId) {
|
||
conteudo = '<img src="/api/' + alias + '/media/' + m.midiaId + '?_=' + Date.now() + mediaTok + '" onclick="window.open(this.src)" style="max-width:250px;border-radius:8px;cursor:pointer">';
|
||
}
|
||
// AUDIO
|
||
else if ((m.tipo === 'audio' || m.tipo === 'ptt' || m.tipo === 'audioMessage') && m.midiaId) {
|
||
conteudo = '<audio controls style="max-width:250px;height:44px" src="/api/' + alias + '/media/' + m.midiaId + '?_=' + Date.now() + mediaTok + '" preload="auto" onerror="this.nextElementSibling.style.display=\'block\';this.controls=false;this.style.opacity=0.5"></audio><span style="display:none;font-size:11px;color:#ef4444;margin-top:2px">⚠️ Áudio indisponível</span>';
|
||
// Transcricao
|
||
if (m.transcricao && m.transcricao.length > 0) {
|
||
conteudo += '<div style="font-size:11px;color:#6b7280;margin-top:4px;padding:4px 8px;background:#f9fafb;border-radius:4px;max-width:250px">📝 ' + esc(m.transcricao) + '</div>';
|
||
}
|
||
} else if (m.tipo === 'audio' && !m.midiaId && m.mensagem) {
|
||
var audioMsg = m.mensagem;
|
||
if (audioMsg.startsWith('data:') || audioMsg.length > 100) {
|
||
conteudo = '<audio controls style="max-width:250px;height:44px" src="' + esc(audioMsg) + '" preload="auto"></audio>';
|
||
}
|
||
}
|
||
// VIDEO
|
||
else if ((m.tipo === 'video' || m.tipo === 'videoMessage') && m.midiaId) {
|
||
conteudo = '<video controls style="max-width:250px;border-radius:8px" src="/api/' + alias + '/media/' + m.midiaId + '?_=' + Date.now() + mediaTok + '"></video>';
|
||
}
|
||
// DOCUMENTO
|
||
else if ((m.tipo === 'document' || m.tipo === 'documentMessage') && m.midiaId) {
|
||
conteudo = '<a href="/api/' + alias + '/media/' + m.midiaId + '?token=' + encodeURIComponent(token) + '" target="_blank" style="display:inline-flex;align-items:center;gap:6px;padding:8px 12px;background:#f3f4f6;border-radius:8px;text-decoration:none;color:#374151;font-size:13px">📎 <span>' + esc(m.mensagem || 'Documento') + '</span></a>';
|
||
}
|
||
|
||
if (m.remetente === 'U' && m.privada === 'S') {
|
||
conteudo = '🔒 ' + conteudo;
|
||
}
|
||
var hora = m.dtEnvio ? new Date(m.dtEnvio).toLocaleTimeString('pt-BR', {hour:'2-digit',minute:'2-digit'}) : '';
|
||
return '<div class="msg ' + classe + '">' + conteudo + '<div class="hora">' + hora + '</div></div>';
|
||
}).join('');
|
||
area.scrollTop = area.scrollHeight;
|
||
}
|
||
|
||
// ===== ENVIAR MENSAGEM =====
|
||
window.enviarMensagem = async function() {
|
||
var input = document.getElementById('msgInput');
|
||
var texto = input.value.trim();
|
||
if (!texto || !conversaAtiva) return;
|
||
|
||
var privada = document.getElementById('privadaToggle').classList.contains('ativa') ? 'S' : 'N';
|
||
|
||
// Mostrar mensagem temporária "enviando"
|
||
var tempId = 'temp-' + Date.now();
|
||
var tempMsg = criarMsgTemp(texto, privada === 'S', tempId);
|
||
document.getElementById('messagesArea').appendChild(tempMsg);
|
||
scrollToBottom();
|
||
|
||
input.value = '';
|
||
input.style.height = 'auto';
|
||
|
||
try {
|
||
var res = await fetch('/api/' + alias + '/conversations/' + conversaAtiva + '/messages', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||
body: JSON.stringify({ mensagem: texto, tipo: 'text', privada: privada })
|
||
});
|
||
var data = await res.json();
|
||
if (data.success) {
|
||
// Remove temporária e recarrega mensagens
|
||
var el = document.getElementById(tempId);
|
||
if (el) el.remove();
|
||
carregarMensagens();
|
||
carregarConversas();
|
||
} else {
|
||
// Mostrar estado de erro na mensagem temporária
|
||
var el = document.getElementById(tempId);
|
||
if (el) {
|
||
el.classList.remove('enviando');
|
||
el.classList.add('erro');
|
||
el.querySelector('.msg-erro-acoes').style.display = 'flex';
|
||
}
|
||
}
|
||
} catch(e) {
|
||
var el = document.getElementById(tempId);
|
||
if (el) {
|
||
el.classList.remove('enviando');
|
||
el.classList.add('erro');
|
||
el.querySelector('.msg-erro-acoes').style.display = 'flex';
|
||
}
|
||
}
|
||
};
|
||
|
||
// ===== CRIAR MENSAGEM TEMPORÁRIA =====
|
||
window.criarMsgTemp = function(texto, privada, id) {
|
||
var div = document.createElement('div');
|
||
div.id = id;
|
||
div.className = 'msg usuario enviando';
|
||
if (privada) div.className += ' privada';
|
||
div.innerHTML = esc(texto).replace(/\n/g, '<br>') +
|
||
'<span class="hora">agora ⌛</span>' +
|
||
'<div class="msg-erro-acoes" style="display:none">⚠️ Falha ao enviar. <button class="btn-reenviar" onclick="reenviarMensagem(\'' + id + '\',\'' + esc(texto).replace(/'/g,"\\'") + '\',' + (privada ? 'true' : 'false') + ')">Reenviar</button></div>';
|
||
return div;
|
||
};
|
||
|
||
window.reenviarMensagem = function(oldId, texto, privada) {
|
||
var el = document.getElementById(oldId);
|
||
if (el) el.remove();
|
||
var input = document.getElementById('msgInput');
|
||
input.value = texto;
|
||
if (privada && !document.getElementById('privadaToggle').classList.contains('ativa')) {
|
||
togglePrivada();
|
||
} else if (!privada && document.getElementById('privadaToggle').classList.contains('ativa')) {
|
||
togglePrivada();
|
||
}
|
||
enviarMensagem();
|
||
};
|
||
|
||
window.scrollToBottom = function() {
|
||
var area = document.getElementById('messagesArea');
|
||
if (area) area.scrollTop = area.scrollHeight;
|
||
};
|
||
|
||
// ===== HABILITAR/DESABILITAR BOTÃO ENVIAR =====
|
||
window.toggleBtnEnviar = function() {
|
||
var input = document.getElementById('msgInput');
|
||
var btn = document.getElementById('btnEnviar');
|
||
if (input && btn) {
|
||
btn.disabled = !input.value.trim();
|
||
}
|
||
};
|
||
|
||
// ===== ENVIAR MÍDIA =====
|
||
window.enviarMidia = async function(input) {
|
||
if (!input.files || !input.files[0] || !conversaAtiva) return;
|
||
var file = input.files[0];
|
||
|
||
var reader = new FileReader();
|
||
reader.onload = async function(e) {
|
||
var base64 = e.target.result.split(',')[1];
|
||
var tipo = file.type.startsWith('image') ? 'image' : file.type.startsWith('audio') ? 'audio' : 'document';
|
||
|
||
try {
|
||
await fetch('/api/' + alias + '/conversations/' + conversaAtiva + '/messages', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||
body: JSON.stringify({ mensagem: '', tipo: tipo, midiaBase64: base64, nomeArquivo: file.name })
|
||
});
|
||
carregarMensagens();
|
||
} catch(e) {}
|
||
};
|
||
reader.readAsDataURL(file);
|
||
input.value = '';
|
||
};
|
||
|
||
// ===== GRAVACAO DE AUDIO =====
|
||
var mediaRecorder = null;
|
||
var audioChunks = [];
|
||
var gravando = false;
|
||
var audioGravadoBlob = null;
|
||
var audioGravadoB64 = null;
|
||
var audioGravadoMime = 'audio/webm';
|
||
|
||
window.toggleGravacao = async function() {
|
||
var btn = document.getElementById("btnAudio");
|
||
if (gravando) {
|
||
if (mediaRecorder && mediaRecorder.state !== "inactive") mediaRecorder.stop();
|
||
btn.classList.remove("recording");
|
||
btn.innerHTML = "🎤 Gravar";
|
||
gravando = false;
|
||
return;
|
||
}
|
||
|
||
// Se ja tem audio pendente, cancela
|
||
cancelarAudioGravado();
|
||
|
||
try {
|
||
var stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||
// Tenta formato Ogg (compatível com WhatsApp), fallback para webm
|
||
var mimeType = "audio/webm";
|
||
if (MediaRecorder.isTypeSupported("audio/ogg;codecs=opus")) {
|
||
mimeType = "audio/ogg;codecs=opus";
|
||
} else if (MediaRecorder.isTypeSupported("audio/webm;codecs=opus")) {
|
||
mimeType = "audio/webm;codecs=opus";
|
||
}
|
||
mediaRecorder = new MediaRecorder(stream, { mimeType: mimeType });
|
||
audioChunks = [];
|
||
|
||
mediaRecorder.ondataavailable = function(e) {
|
||
if (e.data.size > 0) audioChunks.push(e.data);
|
||
};
|
||
|
||
mediaRecorder.onstop = function() {
|
||
stream.getTracks().forEach(function(t) { t.stop(); });
|
||
if (audioChunks.length === 0 || !conversaAtiva) return;
|
||
|
||
audioGravadoBlob = new Blob(audioChunks, { type: mimeType });
|
||
audioGravadoMime = mimeType; // guarda o MIME real para usar no envio
|
||
|
||
// Mostra preview para ouvir antes de enviar
|
||
var url = URL.createObjectURL(audioGravadoBlob);
|
||
document.getElementById("audioPreviewPlayer").src = url;
|
||
document.getElementById("audioPreview").style.display = "flex";
|
||
document.getElementById("msgInput").style.display = "none";
|
||
document.querySelector(".btn-enviar").style.display = "none";
|
||
document.getElementById("btnAudio").style.display = "none";
|
||
document.getElementById("btnPlus").style.display = "none";
|
||
document.getElementById("privadaToggle").style.display = "none";
|
||
|
||
// Converte para base64
|
||
var fr = new FileReader();
|
||
fr.onload = function(ev) {
|
||
audioGravadoB64 = ev.target.result.split(",")[1];
|
||
};
|
||
fr.readAsDataURL(audioGravadoBlob);
|
||
};
|
||
|
||
mediaRecorder.start();
|
||
btn.classList.add("recording");
|
||
btn.innerHTML = "⏹️ Parar";
|
||
gravando = true;
|
||
} catch(err) {
|
||
alert("Erro ao acessar microfone: " + err.message);
|
||
}
|
||
};
|
||
|
||
window.enviarAudioGravado = async function() {
|
||
if (!audioGravadoB64 || !conversaAtiva) return;
|
||
try {
|
||
var ext = (audioGravadoMime || 'audio/webm').includes('ogg') ? 'ogg' : 'webm';
|
||
var nomeAudio = 'audio.' + ext;
|
||
var resp = await fetch("/api/" + alias + "/conversations/" + conversaAtiva + "/messages", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json", "Authorization": "Bearer " + token },
|
||
body: JSON.stringify({ mensagem: "", tipo: "audio", midiaBase64: audioGravadoB64, nomeArquivo: nomeAudio })
|
||
});
|
||
var data = await resp.json();
|
||
if (!data.success) {
|
||
alert("Erro ao enviar áudio: " + (data.error || "desconhecido"));
|
||
return;
|
||
}
|
||
cancelarAudioGravado();
|
||
carregarMensagens();
|
||
} catch(e) {
|
||
alert("Erro ao enviar áudio: " + e.message);
|
||
}
|
||
};
|
||
|
||
window.cancelarAudioGravado = function() {
|
||
audioGravadoBlob = null;
|
||
audioGravadoB64 = null;
|
||
document.getElementById("audioPreview").style.display = "none";
|
||
document.getElementById("audioPreviewPlayer").src = "";
|
||
document.getElementById("msgInput").style.display = "";
|
||
document.querySelector(".btn-enviar").style.display = "";
|
||
document.getElementById("btnAudio").style.display = "";
|
||
document.getElementById("btnPlus").style.display = "";
|
||
document.getElementById("privadaToggle").style.display = "";
|
||
};
|
||
|
||
// ===== DRAG AND DROP =====
|
||
var dropArea = document.body;
|
||
dropArea.addEventListener('dragover', function(e) { e.preventDefault(); document.getElementById('dragOverlay').classList.add('show'); });
|
||
dropArea.addEventListener('dragleave', function(e) { document.getElementById('dragOverlay').classList.remove('show'); });
|
||
dropArea.addEventListener('drop', function(e) {
|
||
e.preventDefault();
|
||
document.getElementById('dragOverlay').classList.remove('show');
|
||
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
|
||
document.getElementById('fileInput').files = e.dataTransfer.files;
|
||
enviarMidia(document.getElementById('fileInput'));
|
||
}
|
||
});
|
||
|
||
// ===== FINALIZAR =====
|
||
window.toggleAgendamento = function() {
|
||
var show = document.getElementById('chkAgendar').checked;
|
||
document.getElementById('agendamentoCampos').style.display = show ? 'block' : 'none';
|
||
if (show) {
|
||
if (!document.getElementById('agData').value) {
|
||
document.getElementById('agData').value = new Date().toISOString().split('T')[0];
|
||
}
|
||
// Preenche anotação com o texto da resolução (se existir)
|
||
var txtResolucao = document.getElementById('campoResolucao');
|
||
var nota = document.getElementById('agNota');
|
||
if (txtResolucao && nota && !nota.value) {
|
||
nota.value = (txtResolucao.value || '').trim();
|
||
}
|
||
}
|
||
};
|
||
|
||
window.finalizarConversa = async function() {
|
||
if (!conversaAtiva) return;
|
||
|
||
// Coleta motivo/resolução (se visíveis) e valida obrigatoriedade
|
||
var motivoId = null, resolucao = '';
|
||
if (cfgResolucao.motivoVisualizar === 'S') {
|
||
var sel = document.getElementById('selectMotivo');
|
||
motivoId = sel && sel.value ? parseInt(sel.value, 10) : null;
|
||
if (cfgResolucao.motivoObrigatorio === 'S' && !motivoId) {
|
||
alert('Selecione o motivo do atendimento para finalizar.'); return;
|
||
}
|
||
}
|
||
if (cfgResolucao.resolucaoVisualizar === 'S') {
|
||
var txt = document.getElementById('campoResolucao');
|
||
resolucao = txt ? (txt.value || '').trim() : '';
|
||
if (cfgResolucao.resolucaoObrigatorio === 'S' && !resolucao) {
|
||
alert('Preencha a resolução do atendimento para finalizar.'); return;
|
||
}
|
||
}
|
||
|
||
// Confirmação já foi feita pelo modal — segue direto
|
||
try {
|
||
var res = await fetch('/api/' + alias + '/conversations/' + conversaAtiva + '/finalize', {
|
||
method: 'POST',
|
||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ motivoId: motivoId, resolucao: resolucao, skipCsat: document.getElementById('chkAgendar') && document.getElementById('chkAgendar').checked })
|
||
});
|
||
var data = await res.json();
|
||
if (data.success) {
|
||
// Agenda retorno se checkbox marcado
|
||
if (document.getElementById('chkAgendar') && document.getElementById('chkAgendar').checked) {
|
||
var agData = document.getElementById('agData').value;
|
||
var agHora = document.getElementById('agHora').value || null;
|
||
var agNota = document.getElementById('agNota').value.trim() || null;
|
||
if (agData) {
|
||
fetch('/api/' + alias + '/conversations/' + conversaAtiva + '/schedule', {
|
||
method: 'POST',
|
||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ data: agData, hora: agHora, nota: agNota })
|
||
}).catch(function(){});
|
||
}
|
||
}
|
||
document.querySelector('.btn-finalizar').textContent = '✅ Finalizado';
|
||
document.querySelector('.btn-finalizar').disabled = true;
|
||
document.querySelector('.btn-finalizar').style.opacity = '0.6';
|
||
carregarConversas();
|
||
// Fecha a conversa da tela (volta ao estado vazio)
|
||
conversaAtiva = null;
|
||
document.getElementById('chatHeader').style.display = 'none';
|
||
document.getElementById('chatInput').style.display = 'none';
|
||
document.getElementById('messagesArea').innerHTML = '<div class="empty-state"><div class="icon">💬</div><p>Selecione uma conversa</p></div>';
|
||
document.getElementById('clienteInfoContainer').innerHTML = '';
|
||
document.getElementById('clienteFoto').innerHTML = '?';
|
||
} else {
|
||
alert(data.error || 'Não foi possível finalizar a conversa.');
|
||
}
|
||
} catch(e) {}
|
||
};
|
||
|
||
// ===== PRIVADA TOGGLE =====
|
||
// ===== NAVEGAÇÃO MOBILE (painel único) =====
|
||
window.voltarLista = function() {
|
||
document.body.classList.remove('chat-aberto', 'info-aberto');
|
||
};
|
||
window.toggleInfo = function() {
|
||
document.body.classList.toggle('info-aberto');
|
||
};
|
||
|
||
// ===== ENVIAR BOLETO =====
|
||
var boletoClienteAtual = null;
|
||
window.abrirModalBoleto = async function(clienteId) {
|
||
if (!conversaAtiva || !clienteId) return;
|
||
boletoClienteAtual = clienteId;
|
||
var modal = document.getElementById('modalBoleto');
|
||
var lista = document.getElementById('boletoLista');
|
||
modal.classList.add('show');
|
||
lista.innerHTML = '<div style="text-align:center;color:#9ca3af;padding:20px">⏳ Carregando títulos...</div>';
|
||
try {
|
||
var r = await (await fetch('/api/' + alias + '/conversations/' + conversaAtiva + '/boletos?clienteId=' + clienteId, {
|
||
headers: { 'Authorization': 'Bearer ' + token }
|
||
})).json();
|
||
if (!r.success) { lista.innerHTML = '<div style="color:#ef4444;padding:16px">' + esc(r.error || 'Erro ao carregar títulos.') + '</div>'; return; }
|
||
var carnes = (r.data || []).filter(function(c){ return c.temLinhaDigitavel || c.temPix; });
|
||
if (carnes.length === 0) {
|
||
lista.innerHTML = '<div style="color:#9ca3af;padding:16px;text-align:center">Nenhum título em aberto com boleto/PIX disponível.</div>';
|
||
return;
|
||
}
|
||
lista.innerHTML = carnes.map(function(c){
|
||
var valor = (c.valor != null) ? ('R$ ' + Number(c.valor).toFixed(2).replace('.', ',')) : '-';
|
||
var venc = c.vencimento ? c.vencimento.split('-').reverse().join('/') : '-';
|
||
var parc = c.parcela ? (' • Parcela ' + c.parcela + (c.totalParcelas ? '/' + c.totalParcelas : '')) : '';
|
||
var tags = (c.temPix ? '<span style="font-size:10px;background:#dcfce7;color:#166534;padding:1px 6px;border-radius:8px;margin-left:6px">PIX</span>' : '') +
|
||
(c.temLinhaDigitavel ? '<span style="font-size:10px;background:#dbeafe;color:#1e40af;padding:1px 6px;border-radius:8px;margin-left:6px">Boleto</span>' : '');
|
||
return '<div style="display:flex;justify-content:space-between;align-items:center;gap:8px;padding:10px;border:1px solid #e5e7eb;border-radius:8px;margin-bottom:8px">' +
|
||
'<div><div style="font-weight:600;color:#111827">' + valor + tags + '</div>' +
|
||
'<div style="font-size:12px;color:#6b7280">Vencimento: ' + venc + parc + '</div></div>' +
|
||
'<button onclick="enviarBoleto(' + c.id + ', this)" style="padding:6px 12px;background:#4f46e5;color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600;white-space:nowrap">Enviar</button>' +
|
||
'</div>';
|
||
}).join('');
|
||
} catch(e) {
|
||
lista.innerHTML = '<div style="color:#ef4444;padding:16px">Erro de conexão.</div>';
|
||
}
|
||
};
|
||
window.fecharModalBoleto = function() {
|
||
document.getElementById('modalBoleto').classList.remove('show');
|
||
};
|
||
window.enviarBoleto = async function(carneId, btn) {
|
||
if (!conversaAtiva || !boletoClienteAtual) return;
|
||
if (btn) { btn.disabled = true; btn.textContent = 'Enviando...'; }
|
||
try {
|
||
var r = await (await fetch('/api/' + alias + '/conversations/' + conversaAtiva + '/send-boleto', {
|
||
method: 'POST',
|
||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ clienteId: boletoClienteAtual, carneId: carneId })
|
||
})).json();
|
||
if (r.success) {
|
||
fecharModalBoleto();
|
||
carregarMensagens();
|
||
} else if (btn) {
|
||
btn.disabled = false; btn.textContent = 'Enviar';
|
||
alert(r.error || 'Não foi possível enviar o boleto.');
|
||
}
|
||
} catch(e) {
|
||
if (btn) { btn.disabled = false; btn.textContent = 'Enviar'; }
|
||
}
|
||
};
|
||
|
||
window.togglePrivada = function() {
|
||
var btn = document.getElementById('privadaToggle');
|
||
btn.classList.toggle('ativa');
|
||
var isPriv = btn.classList.contains('ativa');
|
||
document.getElementById('privadaLabel').textContent = isPriv ? 'Privada' : 'Normal';
|
||
btn.querySelector('.priv-icon').textContent = isPriv ? '🔓' : '🔒';
|
||
};
|
||
|
||
// ===== FINALIZAR COM CONFIRMAÇÃO =====
|
||
window.confirmarFinalizar = function() {
|
||
document.getElementById('modalConfirmarFinalizar').classList.add('show');
|
||
};
|
||
|
||
// ===== MENU DE AÇÕES =====
|
||
window.toggleActionMenu = function() {
|
||
document.getElementById('actionMenu').classList.toggle('show');
|
||
};
|
||
|
||
// ===== ACCORDION DO PAINEL DIREITO =====
|
||
window.toggleAccordion = function(btn, bodyId) {
|
||
btn.classList.toggle('open');
|
||
document.getElementById(bodyId).classList.toggle('open');
|
||
};
|
||
|
||
// ===== ATALHOS DE TECLADO =====
|
||
document.addEventListener('keydown', function(e) {
|
||
// Ctrl+J / Ctrl+K: navegar entre conversas
|
||
if ((e.ctrlKey || e.metaKey) && (e.key === 'j' || e.key === 'k')) {
|
||
e.preventDefault();
|
||
var items = document.querySelectorAll('.conv-item');
|
||
if (!items.length) return;
|
||
var idx = -1;
|
||
for (var i = 0; i < items.length; i++) {
|
||
if (items[i].classList.contains('active')) { idx = i; break; }
|
||
}
|
||
if (e.key === 'j') idx = Math.min(idx + 1, items.length - 1);
|
||
else idx = Math.max(idx - 1, 0);
|
||
if (idx >= 0 && items[idx]) items[idx].click();
|
||
}
|
||
// Ctrl+F: focar busca de conversas
|
||
if ((e.ctrlKey || e.metaKey) && e.key === 'f' && document.activeElement !== document.getElementById('msgInput')) {
|
||
e.preventDefault();
|
||
var s = document.getElementById('searchConv');
|
||
if (s) { s.focus(); s.select(); }
|
||
}
|
||
// Esc: fechar modais e menus
|
||
if (e.key === 'Escape') {
|
||
document.getElementById('actionMenu').classList.remove('show');
|
||
document.querySelectorAll('.modal-overlay.show').forEach(function(m) {
|
||
m.classList.remove('show');
|
||
});
|
||
}
|
||
});
|
||
|
||
// Fechar menu de ações ao clicar fora
|
||
document.addEventListener('click', function(e) {
|
||
var menu = document.getElementById('actionMenu');
|
||
var btn = document.getElementById('btnPlus');
|
||
if (menu && btn && !menu.contains(e.target) && !btn.contains(e.target)) {
|
||
menu.classList.remove('show');
|
||
}
|
||
});
|
||
|
||
// ===== ASSOCIAR CLIENTE =====
|
||
window.mostrarModalAssociar = function() {
|
||
document.getElementById('modalAssociar').classList.add('show');
|
||
document.getElementById('associarBusca').value = '';
|
||
document.getElementById('associarResultados').innerHTML = '';
|
||
};
|
||
|
||
window.fecharModal = function(id) { document.getElementById(id).classList.remove('show'); };
|
||
|
||
window.mudarTipoAssoc = function() {
|
||
var tipo = document.querySelector('input[name="tipoAssoc"]:checked').value;
|
||
document.getElementById('assocLabel').textContent = tipo === 'cliente' ? 'Digite o nome, CPF ou matrícula do cliente:' : 'Digite o nome do dependente ou matrícula do titular:';
|
||
document.getElementById('associarBusca').placeholder = tipo === 'cliente' ? 'Buscar cliente...' : 'Buscar dependente...';
|
||
document.getElementById('associarResultados').innerHTML = '';
|
||
};
|
||
|
||
window.buscarClienteAssociar = async function(event) {
|
||
if (event.key !== 'Enter') return;
|
||
var q = document.getElementById('associarBusca').value.trim();
|
||
var tipo = document.querySelector('input[name="tipoAssoc"]:checked').value;
|
||
if (!q) return;
|
||
var div = document.getElementById('associarResultados');
|
||
div.innerHTML = '<div style="padding:12px;color:#9ca3af;font-size:13px">Buscando...</div>';
|
||
try {
|
||
if (tipo === 'dependente') {
|
||
var res = await fetch('/api/' + alias + '/dependents/search?q=' + encodeURIComponent(q) + '&empresaId=' + empresaId, {
|
||
headers: { 'Authorization': 'Bearer ' + token }
|
||
});
|
||
var data = await res.json();
|
||
if (!data.success || !data.data || data.data.length === 0) {
|
||
div.innerHTML = '<div style="padding:12px;color:#9ca3af;font-size:13px">Nenhum dependente encontrado</div>'; return;
|
||
}
|
||
div.innerHTML = data.data.map(function(d) {
|
||
var info = d.titularMatricula ? 'Matrícula: ' + d.titularMatricula : '';
|
||
info += d.titularNome ? (info ? ' - ' : '') + d.titularNome : '';
|
||
return '<div style="padding:8px 12px;border-bottom:1px solid #f3f4f6;cursor:pointer" onclick="associarDependente(' + d.id + ',\'' + d.nome.replace(/'/g,"\\'") + '\',\'' + (d.telefone||'').replace(/'/g,"\\'") + '\')">' +
|
||
'<strong>' + d.nome + '</strong><br><span style="font-size:11px;color:#6b7280">' + info + '</span></div>';
|
||
}).join('');
|
||
} else {
|
||
var res = await fetch('/api/' + alias + '/clients/search?q=' + encodeURIComponent(q) + '&empresaId=' + empresaId, {
|
||
headers: { 'Authorization': 'Bearer ' + token }
|
||
});
|
||
var data = await res.json();
|
||
if (!data.success) { div.innerHTML = '<div style="padding:12px;color:#9ca3af;font-size:13px">Nenhum resultado</div>'; return; }
|
||
div.innerHTML = data.data.map(function(c) {
|
||
return '<div style="padding:8px 12px;border-bottom:1px solid #f3f4f6;cursor:pointer" onclick="associarCliente(' + c.id + ',\'' + c.nome.replace(/'/g,"\\'") + '\')">' +
|
||
'<strong>' + c.nome + '</strong> - ' + (c.cpf || c.matricula || '') + '</div>';
|
||
}).join('') || '<div style="padding:12px;color:#9ca3af;font-size:13px">Nenhum resultado</div>';
|
||
}
|
||
} catch(e) { div.innerHTML = '<div style="padding:12px;color:#ef4444;font-size:13px">Erro: ' + e.message + '</div>'; }
|
||
};
|
||
|
||
window.associarCliente = async function(clienteId, nome) {
|
||
try {
|
||
var res = await fetch('/api/' + alias + '/conversations/' + conversaAtiva + '/link-client', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||
body: JSON.stringify({ clienteId: clienteId })
|
||
});
|
||
var data = await res.json();
|
||
if (data.success) {
|
||
alert('Cliente ' + nome + ' associado com sucesso!');
|
||
fecharModal('modalAssociar');
|
||
// Recarrega a conversa para mostrar os dados atualizados
|
||
conversaAtiva = parseInt(conversaAtiva);
|
||
window.abrirConversa(conversaAtiva);
|
||
} else {
|
||
alert('Erro: ' + (data.error || 'desconhecido'));
|
||
}
|
||
} catch(e) {
|
||
alert('Erro de conexão: ' + e.message);
|
||
}
|
||
};
|
||
|
||
// ===== ASSOCIAR DEPENDENTE =====
|
||
window.associarDependente = async function(depId, nome, telefone) {
|
||
if (!confirm('Associar dependente "' + nome + '" a esta conversa?')) return;
|
||
try {
|
||
// Busca o cliente titular pelo ID do dependente
|
||
var res = await fetch('/api/' + alias + '/dependents/search?q=' + encodeURIComponent(nome.split(' ')[0]) + '&empresaId=' + empresaId, {
|
||
headers: { 'Authorization': 'Bearer ' + token }
|
||
});
|
||
var data = await res.json();
|
||
if (!data.success || !data.data || data.data.length === 0) {
|
||
alert('Erro ao buscar dados do dependente'); return;
|
||
}
|
||
var dep = data.data.find(function(d) { return d.id === depId; }) || data.data[0];
|
||
if (!dep.clienteId) { alert('Dependente sem cliente titular vinculado'); return; }
|
||
var res2 = await fetch('/api/' + alias + '/conversations/' + conversaAtiva + '/link-client', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||
body: JSON.stringify({ clienteId: dep.clienteId })
|
||
});
|
||
var data2 = await res2.json();
|
||
if (data2.success) {
|
||
alert('Dependente ' + nome + ' associado!');
|
||
fecharModal('modalAssociar');
|
||
conversaAtiva = parseInt(conversaAtiva);
|
||
window.abrirConversa(conversaAtiva);
|
||
} else {
|
||
alert('Erro: ' + (data2.error || 'desconhecido'));
|
||
}
|
||
} catch(e) { alert('Erro: ' + e.message); }
|
||
};
|
||
|
||
// ===== IR PÁGINA CLIENTE =====
|
||
window.irPaginaCliente = function() {
|
||
var conv = window.conversaAtual;
|
||
if (!conv) return;
|
||
// Se for dependente, abre página do titular
|
||
if (conv.dependente && conv.dependente.titularId) {
|
||
window.open('/app/' + alias + '/company/' + empresaId + '/client/' + conv.dependente.titularId, '_blank');
|
||
return;
|
||
}
|
||
// Se for cliente, abre página do cliente
|
||
if (conv.cliente) {
|
||
window.open('/app/' + alias + '/company/' + empresaId + '/client/' + conv.cliente.id, '_blank');
|
||
}
|
||
};
|
||
|
||
// ===== SWITCH TAB =====
|
||
var filtroAtual = 'mine';
|
||
|
||
window.mudarFiltro = function(filtro, el) {
|
||
document.querySelectorAll('.nav-tabs a').forEach(function(a) { a.classList.remove('active'); });
|
||
el.classList.add('active');
|
||
filtroAtual = filtro;
|
||
// Mostra/esconde busca e checkbox de finalizadas (não fazem sentido para agendadas)
|
||
var searchBox = document.querySelector('.search-box');
|
||
if (filtro === 'scheduled') {
|
||
if (searchBox) searchBox.style.display = 'none';
|
||
} else {
|
||
if (searchBox) searchBox.style.display = '';
|
||
}
|
||
carregarConversas();
|
||
};
|
||
|
||
// ===== AGENDADAS =====
|
||
async function carregarAgendadas() {
|
||
var dataSel = document.getElementById('agendadasData');
|
||
var data = dataSel ? dataSel.value : new Date().toISOString().split('T')[0];
|
||
var list = document.getElementById('conversationsList');
|
||
list.innerHTML = '<div style="padding:24px;text-align:center;color:rgba(255,255,255,0.4);font-size:13px">Carregando agendamentos...</div>';
|
||
|
||
try {
|
||
var res = await fetch('/api/' + alias + '/schedules?empresaId=' + empresaId + '&data=' + data, {
|
||
headers: { 'Authorization': 'Bearer ' + token }
|
||
});
|
||
var d = await res.json();
|
||
if (!d.success) return;
|
||
|
||
var dataFmt = data.split('-').reverse().join('/');
|
||
var html = '<div style="padding:10px 12px;border-bottom:1px solid rgba(255,255,255,0.06);display:flex;align-items:center;gap:8px">' +
|
||
'<span style="font-size:13px;color:rgba(255,255,255,0.7)">📅</span>' +
|
||
'<input type="date" id="agendadasData" value="' + data + '" onchange="carregarAgendadas()" style="flex:1;padding:6px 8px;border:1px solid rgba(255,255,255,0.15);border-radius:6px;font-size:12px;background:rgba(255,255,255,0.06);color:#fff;outline:none">' +
|
||
'<span style="font-size:12px;color:rgba(255,255,255,0.5)">' + (d.count || 0) + ' agend.</span></div>';
|
||
|
||
if (!d.data || d.data.length === 0) {
|
||
html += '<div style="padding:24px;text-align:center;color:rgba(255,255,255,0.35);font-size:13px">Nenhum agendamento para ' + dataFmt + '</div>';
|
||
} else {
|
||
d.data.forEach(function(a) {
|
||
var nome = a.nomeContato || a.numero || '-';
|
||
var cor = avatarColor(nome);
|
||
var ini = avatarInitials(nome);
|
||
var hora = a.hora ? a.hora.substring(0,5) : '';
|
||
html += '<div class="conv-item" style="cursor:pointer" onclick="reabrirAgendamento(' + a.id + ',' + a.conversaId + ')">' +
|
||
'<div class="conv-avatar" style="background:' + cor + '">' + ini + '</div>' +
|
||
'<div class="conv-body">' +
|
||
'<div class="conv-nome">' + nome + (hora ? ' <span style="font-size:10px;opacity:0.6">🕐 ' + hora + '</span>' : '') + '</div>' +
|
||
'<div class="conv-ultima" style="font-size:11px">' + (a.nota || 'Sem anotação') + '</div>' +
|
||
'</div>' +
|
||
'</div>';
|
||
});
|
||
}
|
||
list.innerHTML = html;
|
||
} catch(e) {}
|
||
}
|
||
|
||
window.reabrirAgendamento = async function(agId, convId) {
|
||
try {
|
||
var res = await fetch('/api/' + alias + '/schedules/' + agId + '/reopen', {
|
||
method: 'POST', headers: { 'Authorization': 'Bearer ' + token }
|
||
});
|
||
var d = await res.json();
|
||
if (d.success) {
|
||
// Mostra modal com o texto sugerido
|
||
document.getElementById('retornoTextoSugerido').value = d.textoSugerido || '';
|
||
document.getElementById('retornoConversaId').value = d.conversaId;
|
||
document.getElementById('modalRetorno').classList.add('show');
|
||
}
|
||
} catch(e) {}
|
||
};
|
||
|
||
window.confirmarRetorno = function() {
|
||
var convId = document.getElementById('retornoConversaId').value;
|
||
var texto = document.getElementById('retornoTextoSugerido').value.trim();
|
||
fecharModal('modalRetorno');
|
||
if (!convId) return;
|
||
// Abre a conversa e preenche o input
|
||
mudarFiltro('mine', document.querySelector('.nav-tabs a'));
|
||
carregarConversas();
|
||
setTimeout(function() {
|
||
abrirConversa(parseInt(convId));
|
||
// Aguarda o chat carregar e preenche o input
|
||
setTimeout(function() {
|
||
var input = document.getElementById('msgInput');
|
||
if (input && texto) {
|
||
input.value = texto;
|
||
input.style.height = 'auto';
|
||
input.style.height = input.scrollHeight + 'px';
|
||
input.focus();
|
||
}
|
||
}, 500);
|
||
}, 300);
|
||
};
|
||
|
||
// Mostra
|
||
|
||
// ===== SELETOR DE EMPRESA (usuário com acesso a mais de uma) =====
|
||
async function carregarEmpresas() {
|
||
try {
|
||
var r = await (await fetch('/api/' + alias + '/empresas', { headers: { 'Authorization': 'Bearer ' + token } })).json();
|
||
if (!r.success || !r.data || r.data.length <= 1) return; // só mostra se tiver mais de uma
|
||
var sel = document.getElementById('empresaSwitcher');
|
||
sel.innerHTML = r.data.map(function(e) {
|
||
var nome = e.nomeFantasia || e.nome || ('Empresa ' + e.id);
|
||
return '<option value="' + e.id + '"' + (String(e.id) === String(empresaId) ? ' selected' : '') + '>' + esc(nome) + '</option>';
|
||
}).join('');
|
||
document.getElementById('empresaSwitcherWrap').style.display = 'block';
|
||
} catch(e) {}
|
||
}
|
||
window.trocarEmpresa = function(novaEmpresaId) {
|
||
if (!novaEmpresaId || String(novaEmpresaId) === String(empresaId)) return;
|
||
// Recarrega a tela na empresa escolhida (estado limpo: conversas, config, etc.)
|
||
window.location.href = '/app/' + alias + '/company/' + novaEmpresaId + '/conversation/0';
|
||
};
|
||
|
||
// ===== INICIAR =====
|
||
carregarEmpresas();
|
||
carregarConversas();
|
||
carregarConfigResolucao().then(function() {
|
||
if (conversaId) abrirConversa(parseInt(conversaId));
|
||
});
|
||
|
||
// ===== NOVA CONVERSA (número não cadastrado) =====
|
||
window.mostrarModalNovaConversa = function() {
|
||
document.getElementById('modalNovaConversa').style.display = 'flex';
|
||
document.getElementById('novoNumero').value = '';
|
||
document.getElementById('novoNome').value = '';
|
||
document.getElementById('novoMensagem').value = '';
|
||
document.getElementById('novoNumero').focus();
|
||
}
|
||
|
||
window.fecharModalNovaConversa = function() {
|
||
document.getElementById('modalNovaConversa').style.display = 'none';
|
||
}
|
||
|
||
window.criarNovaConversa = async function() {
|
||
const numero = document.getElementById('novoNumero').value.replace(/\D/g, '');
|
||
const nome = document.getElementById('novoNome').value.trim() || numero;
|
||
const mensagem = document.getElementById('novoMensagem').value.trim() || '👋 Olá! Em que posso ajudar?';
|
||
|
||
if (!numero || numero.length < 10) {
|
||
if (Chatc2Toast) Chatc2Toast.warn('Informe um número de WhatsApp válido.');
|
||
return;
|
||
}
|
||
|
||
const btn = document.getElementById('btnCriarConversa');
|
||
btn.disabled = true;
|
||
btn.textContent = 'Criando...';
|
||
|
||
try {
|
||
const res = await fetch('/api/' + alias + '/conversations/create', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||
body: JSON.stringify({
|
||
empresaId: empresaId,
|
||
numero: numero,
|
||
nomeContato: nome,
|
||
mensagem: mensagem,
|
||
instanciaId: null
|
||
})
|
||
});
|
||
const data = await res.json();
|
||
if (data.success) {
|
||
fecharModalNovaConversa();
|
||
await carregarConversas();
|
||
abrirConversa(data.data.id);
|
||
if (Chatc2Toast) Chatc2Toast.success('Conversa iniciada com ' + nome);
|
||
} else {
|
||
if (Chatc2Toast) Chatc2Toast.error(data.error || 'Erro ao criar conversa');
|
||
}
|
||
} catch(e) {
|
||
if (Chatc2Toast) Chatc2Toast.error('Erro de conexão ao criar conversa');
|
||
}
|
||
btn.disabled = false;
|
||
btn.textContent = 'Iniciar conversa';
|
||
}
|
||
|
||
// ===== LOGOUT =====
|
||
window.logout = function() {
|
||
['chatc2_token','chatc2_alias','chatc2_user'].forEach(function(k) { localStorage.removeItem(k); });
|
||
window.location.href = '/app/' + alias + '/login';
|
||
};
|
||
|
||
// 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', {
|
||
method: 'POST', headers: { 'Authorization': 'Bearer ' + token }
|
||
}).catch(function(){});
|
||
}
|
||
pingPresenca();
|
||
setInterval(pingPresenca, 45000);
|
||
|
||
// Auto-resize textarea
|
||
document.getElementById('msgInput').addEventListener('input', function() {
|
||
this.style.height = 'auto';
|
||
this.style.height = (this.scrollHeight) + 'px';
|
||
});
|
||
|
||
})();
|
||
</script>
|
||
<style>
|
||
/* Dark mode para o modal Nova Conversa */
|
||
body.dark-mode #modalNovaConversa > div {
|
||
background: #1e1e2e !important;
|
||
box-shadow: 0 20px 60px rgba(0,0,0,0.5) !important;
|
||
}
|
||
body.dark-mode #modalNovaConversa h3 {
|
||
color: #e0e0e0 !important;
|
||
}
|
||
body.dark-mode #modalNovaConversa p {
|
||
color: #9ca3af !important;
|
||
}
|
||
body.dark-mode #modalNovaConversa label {
|
||
color: #d1d5db !important;
|
||
}
|
||
body.dark-mode #modalNovaConversa input,
|
||
body.dark-mode #modalNovaConversa textarea {
|
||
background: #2a2a3e !important;
|
||
border-color: #3f3f5a !important;
|
||
color: #e0e0e0 !important;
|
||
}
|
||
body.dark-mode #modalNovaConversa input:focus,
|
||
body.dark-mode #modalNovaConversa textarea:focus {
|
||
border-color: #818cf8 !important;
|
||
background: #2a2a3e !important;
|
||
box-shadow: 0 0 0 3px rgba(129,140,248,0.15) !important;
|
||
}
|
||
body.dark-mode #modalNovaConversa input::placeholder,
|
||
body.dark-mode #modalNovaConversa textarea::placeholder {
|
||
color: #6b7280 !important;
|
||
}
|
||
body.dark-mode #modalNovaConversa button {
|
||
color: #d1d5db !important;
|
||
}
|
||
/* Botão Cancelar no dark mode */
|
||
body.dark-mode #btnCancelarConversa {
|
||
background: #2a2a3e !important;
|
||
border-color: #3f3f5a !important;
|
||
color: #d1d5db !important;
|
||
}
|
||
</style>
|
||
<!-- Modal: Nova Conversa -->
|
||
<div id="modalNovaConversa" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:10000;align-items:center;justify-content:center" onclick="if(event.target===this) fecharModalNovaConversa()">
|
||
<div style="background:#fff;border-radius:16px;padding:32px 28px 24px;width:100%;max-width:420px;box-shadow:0 20px 60px rgba(0,0,0,0.18)" onclick="event.stopPropagation()">
|
||
<h3 style="margin:0 0 6px;font-size:18px;font-weight:700;color:#111827">Nova conversa</h3>
|
||
<p style="margin:0 0 18px;font-size:13px;color:#6b7280">Inicie um atendimento para um número que ainda não está no sistema.</p>
|
||
<div class="form-group" style="margin-bottom:14px">
|
||
<label style="display:block;font-size:13px;font-weight:600;color:#374151;margin-bottom:5px">Número (WhatsApp) *</label>
|
||
<input type="tel" id="novoNumero" placeholder="(11) 99999-9999" style="width:100%;padding:10px 14px;border:2px solid #e5e7eb;border-radius:8px;font-size:14px;outline:none;background:#f9fafb;transition:all .15s" onfocus="this.style.borderColor='#667eea';this.style.background='#fff'" onblur="this.style.borderColor='#e5e7eb';this.style.background='#f9fafb'">
|
||
</div>
|
||
<div class="form-group" style="margin-bottom:14px">
|
||
<label style="display:block;font-size:13px;font-weight:600;color:#374151;margin-bottom:5px">Nome do contato</label>
|
||
<input type="text" id="novoNome" placeholder="Nome (opcional)" style="width:100%;padding:10px 14px;border:2px solid #e5e7eb;border-radius:8px;font-size:14px;outline:none;background:#f9fafb;transition:all .15s" onfocus="this.style.borderColor='#667eea';this.style.background='#fff'" onblur="this.style.borderColor='#e5e7eb';this.style.background='#f9fafb'">
|
||
</div>
|
||
<div class="form-group" style="margin-bottom:14px">
|
||
<label style="display:block;font-size:13px;font-weight:600;color:#374151;margin-bottom:5px">Mensagem inicial</label>
|
||
<textarea id="novoMensagem" rows="3" placeholder="👋 Olá! Em que posso ajudar?" style="width:100%;padding:10px 14px;border:2px solid #e5e7eb;border-radius:8px;font-size:14px;outline:none;background:#f9fafb;resize:vertical;font-family:inherit;transition:all .15s" onfocus="this.style.borderColor='#667eea';this.style.background='#fff'" onblur="this.style.borderColor='#e5e7eb';this.style.background='#f9fafb'"></textarea>
|
||
</div>
|
||
<div style="display:flex;gap:10px;justify-content:flex-end;margin-top:20px">
|
||
<button id="btnCancelarConversa" onclick="fecharModalNovaConversa()" style="padding:10px 20px;border:1px solid #e5e7eb;border-radius:8px;background:#fff;color:#374151;font-size:14px;font-weight:600;cursor:pointer">Cancelar</button>
|
||
<button id="btnCriarConversa" onclick="criarNovaConversa()" style="padding:10px 20px;border:none;border-radius:8px;background:#667eea;color:#fff;font-size:14px;font-weight:600;cursor:pointer;transition:all .15s" onmouseover="this.style.background='#5a67d8'" onmouseout="this.style.background='#667eea'">Iniciar conversa</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<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 -->
|
||
|
||
<!-- Bottom Tab Bar (mobile) -->
|
||
<script src="/js/tabbar.js"></script>
|
||
</body>
|
||
</html>
|