melhorias na interface, adicionado botão para encaminhar chamado, e ajuste no botão para rechamar o cliente

This commit is contained in:
Eder Moraes 2026-05-04 16:44:37 -03:00
parent 8e53b7ccbd
commit 8403281a6d
6 changed files with 255 additions and 47 deletions

View File

@ -1,56 +1,53 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="pt-br">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content=" <meta name="viewport" content="width=device-width, initial-scale=1.0">
default-src 'self'; <title>Painel do Operador</title>
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
connect-src 'self' ws://localhost:3000 http://localhost:3000 ws://autoatends.linco.work wss://autoatends.linco.work;
">
<title>Tela de Atendimento</title>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<script src="res/js/jquery/jquery.js"></script>
</head> </head>
<body> <body>
<!-- TELA PRINCIPAL: Lista de fila + Card do chamado atual -->
<div id="list-view"> <div id="list-view">
<div class="attendance-layout"> <div class="main-container">
<!-- Lado esquerdo: tabela da fila --> <!-- Sidebar/Lista de Espera -->
<div class="queue-panel"> <div class="queue-sidebar">
<div class="panel-header"> <div class="sidebar-header">
<h2>Fila de Espera</h2> <h2>Fila de Espera</h2>
<span id="queue-count" class="queue-badge">0</span> <span id="queue-count" class="badge">0</span>
</div> </div>
<div class="queue-table-wrapper"> <div class="queue-list-container">
<table class="queue-table"> <table class="queue-table">
<thead> <thead>
<tr> <tr>
<th>SENHA</th> <th>Senha</th>
<th>CLIENTE</th> <th>Cliente</th>
<th>SERVIÇO</th>
<th>TIPO</th>
</tr> </tr>
</thead> </thead>
<tbody id="queue-table-body"> <tbody id="queue-table-body">
<!-- Linhas da fila --> <!-- Filas serão inseridas aqui -->
</tbody> </tbody>
</table> </table>
<div id="queue-empty" class="queue-empty" style="display:none;"> <div id="queue-empty" class="empty-state">
<span class="empty-icon">📋</span> Ninguém na fila
<p>Nenhum cliente aguardando</p>
</div> </div>
</div> </div>
</div> </div>
<!-- Lado direito: card do atendimento atual --> <!-- Área Principal / Chamada -->
<div class="current-card-panel"> <div class="call-area">
<div id="no-current" class="no-current-card">
<div class="pulse-icon">🔔</div>
<h2>Aguardando Chamada</h2>
<p>Clique no botão abaixo para chamar o próximo cliente</p>
<button id="call-next-btn" class="btn-primary">CHAMAR PRÓXIMO</button>
</div>
<div id="current-card" class="current-card" style="display: none;"> <div id="current-card" class="current-card" style="display: none;">
<div class="card-header"> <div class="card-header">
<span id="card-ticket" class="card-ticket">---</span> <span id="card-status-badge" class="status-badge">CHAMADO</span>
<span id="card-status-badge" class="card-badge badge-chamado">Chamado</span> <h1 id="card-ticket">---</h1>
</div> </div>
<div class="card-body"> <div class="card-body">
<p id="card-client-name" class="card-client-name">---</p> <p id="card-client-name" class="card-client-name">---</p>
@ -59,17 +56,13 @@
</div> </div>
<div class="card-actions"> <div class="card-actions">
<button id="recall-button" class="btn-recall" disabled>RECHAMAR</button> <button id="recall-button" class="btn-recall" disabled>RECHAMAR</button>
<button id="forward-button" class="btn-forward" disabled>ENCAMINHAR</button>
<button id="next-button" class="btn-start" disabled> <button id="next-button" class="btn-start" disabled>
<span id="counter-start"></span> <span id="counter-start"></span>
INICIAR INICIAR
</button> </button>
</div> </div>
</div> </div>
<div id="no-current" class="no-current-card">
<span class="empty-icon"></span>
<p>Nenhum atendimento chamado</p>
<small>Clique no botão flutuante para chamar o próximo</small>
</div>
</div> </div>
</div> </div>
@ -95,11 +88,18 @@
</div> </div>
</div> </div>
<div id="encaminhar-view" style="display: none;"> <!-- MODAL DE ENCAMINHAMENTO -->
<h1>Observações</h1> <div id="forward-view" class="modal-view" style="display: none;">
<p>Atendendo: <span id="enc-selected-item-name"></span></p> <div class="modal-content">
<textarea id="enc-observation-text" rows="10" cols="50" placeholder="Digite suas observações..."></textarea> <div class="modal-header">
<button id="enc-save-button">Salvar</button> <h2>Encaminhar</h2>
<button id="close-forward" class="btn-close">&times;</button>
</div>
<p style="color: #aaa; font-size: 14px; margin-bottom: 10px;">Selecione para quem encaminhar <span id="forward-ticket" class="highlight"></span>:</p>
<div id="operators-list" class="operators-grid">
<!-- Operadores serão carregados aqui -->
</div>
</div>
</div> </div>
<script src="res/js/socket.io.min.js"></script> <script src="res/js/socket.io.min.js"></script>

44
main.js
View File

@ -739,10 +739,11 @@ ipcMain.on('rechamar-atendimento', async (event, itemId) => {
const token = await getAuthToken(); const token = await getAuthToken();
const tenantId = await getTenantId(); const tenantId = await getTenantId();
const colabId = await getSelectedOperatorId(); const colabId = await getSelectedOperatorId();
const url = apiUrl + 'attendance/call-next/' + colabId; // Rota correta para atualizar o status do item específico e disparar o chamado na TV
const url = apiUrl + 'attendance/' + itemId + '/status';
const request = net.request({ const request = net.request({
method: 'POST', method: 'PATCH',
url: url, url: url,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -751,6 +752,12 @@ ipcMain.on('rechamar-atendimento', async (event, itemId) => {
} }
}); });
// Enviar o corpo com o status "Chamado"
request.write(JSON.stringify({
status: 'Chamado',
collaboratorId: colabId
}));
request.on('response', (response) => { request.on('response', (response) => {
response.on('data', (chunk) => { response.on('data', (chunk) => {
console.log(`Rechamar BODY: ${chunk}`); console.log(`Rechamar BODY: ${chunk}`);
@ -766,6 +773,39 @@ ipcMain.on('rechamar-atendimento', async (event, itemId) => {
request.end(); request.end();
}); });
// Encaminhar atendimento para outro colaborador
ipcMain.on('encaminhar-atendimento', async (event, { itemId, collaboratorId }) => {
const token = await getAuthToken();
const tenantId = await getTenantId();
const url = apiUrl + 'attendance/' + itemId + '/forward';
const request = net.request({
method: 'PATCH',
url: url,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token,
'x-tenant-id': tenantId
}
});
request.write(JSON.stringify({ collaboratorId }));
request.on('response', (response) => {
response.on('data', (chunk) => {
console.log(`Encaminhar BODY: ${chunk}`);
});
response.on('end', () => {
console.log('Encaminhamento concluído.');
});
});
request.on('error', (error) => {
console.error(`Erro no encaminhamento: ${error}`);
});
request.end();
});
// Ouve quando um atendimento é iniciado e notifica a janela flutuante // Ouve quando um atendimento é iniciado e notifica a janela flutuante
ipcMain.on('atendimento-iniciado', (event, itemId) => { ipcMain.on('atendimento-iniciado', (event, itemId) => {
if (floatingWin) { if (floatingWin) {

View File

@ -1,6 +1,6 @@
{ {
"name": "autoatendcolab", "name": "autoatendcolab",
"version": "1.1.6", "version": "1.1.7",
"main": "main.js", "main": "main.js",
"isBuildNow": true, "isBuildNow": true,
"scripts": { "scripts": {

View File

@ -17,6 +17,10 @@ contextBridge.exposeInMainWorld('electronAPI', {
//rechama o atendimento atual //rechama o atendimento atual
rechamarAtendimento: (itemId) => ipcRenderer.send('rechamar-atendimento', itemId), rechamarAtendimento: (itemId) => ipcRenderer.send('rechamar-atendimento', itemId),
//encaminha o atendimento atual
getOperators: () => ipcRenderer.invoke('get-operators'),
encaminharAtendimento: (data) => ipcRenderer.send('encaminhar-atendimento', data),
//notifica sobre o status do atendimento //notifica sobre o status do atendimento
atendimentoIniciado: (itemId) => ipcRenderer.send('atendimento-iniciado', itemId), atendimentoIniciado: (itemId) => ipcRenderer.send('atendimento-iniciado', itemId),
atendimentoFinalizado: () => ipcRenderer.send('atendimento-finalizado'), atendimentoFinalizado: () => ipcRenderer.send('atendimento-finalizado'),

View File

@ -1,6 +1,5 @@
const listView = document.getElementById('list-view'); const listView = document.getElementById('list-view');
const observationView = document.getElementById('obs-view'); const observationView = document.getElementById('obs-view');
const encaminharView = document.getElementById('encaminhar-view');
// Novos elementos do layout redesenhado // Novos elementos do layout redesenhado
const queueTableBody = document.getElementById('queue-table-body'); const queueTableBody = document.getElementById('queue-table-body');
@ -15,6 +14,7 @@ const cardService = document.getElementById('card-service');
const cardType = document.getElementById('card-type'); const cardType = document.getElementById('card-type');
const nextButton = document.getElementById('next-button'); const nextButton = document.getElementById('next-button');
const recallButton = document.getElementById('recall-button'); const recallButton = document.getElementById('recall-button');
const forwardButton = document.getElementById('forward-button');
const logoutButton = document.getElementById('logout-button'); const logoutButton = document.getElementById('logout-button');
const observationText = document.getElementById('observation-text'); const observationText = document.getElementById('observation-text');
const saveButton = document.getElementById('save-button'); const saveButton = document.getElementById('save-button');
@ -22,6 +22,12 @@ const selectedItemNameSpan = document.getElementById('selected-item-name');
const idAtend = document.getElementById('idAtend'); const idAtend = document.getElementById('idAtend');
const counterStart = document.getElementById('counter-start'); const counterStart = document.getElementById('counter-start');
// Elementos do Modal de Encaminhamento
const forwardView = document.getElementById('forward-view');
const operatorsList = document.getElementById('operators-list');
const closeForward = document.getElementById('close-forward');
const forwardTicketSpan = document.getElementById('forward-ticket');
let currentData = []; let currentData = [];
let selectedItemId = null; let selectedItemId = null;
let selectedItemName = ''; let selectedItemName = '';
@ -137,6 +143,7 @@ function populateQueueTable(proximos) {
cardType.textContent = ''; cardType.textContent = '';
nextButton.disabled = true; nextButton.disabled = true;
recallButton.disabled = true; recallButton.disabled = true;
forwardButton.disabled = true;
return; return;
} }
@ -214,6 +221,7 @@ function showCurrentCard(data) {
setTimeout(() => { setTimeout(() => {
nextButton.disabled = false; nextButton.disabled = false;
recallButton.disabled = false; recallButton.disabled = false;
forwardButton.disabled = false;
}, 500); }, 500);
} }
@ -240,7 +248,7 @@ document.addEventListener('DOMContentLoaded', () => {
//mostra a tela de listagem e permite iniciar o atendimento //mostra a tela de listagem e permite iniciar o atendimento
function showListView() { function showListView() {
listView.style.display = 'block'; listView.style.display = 'block';
encaminharView.style.display = 'none'; forwardView.style.display = 'none';
observationView.style.display = 'none'; observationView.style.display = 'none';
observationText.value = ''; // Limpa a textarea observationText.value = ''; // Limpa a textarea
} }
@ -301,6 +309,54 @@ recallButton.addEventListener('click', () => {
} }
}); });
// Evento do botão ENCAMINHAR
forwardButton.addEventListener('click', async () => {
if (calledAtendimentoData) {
forwardTicketSpan.textContent = calledAtendimentoData.ticketNumber;
forwardView.style.display = 'flex';
// Carrega operadores
operatorsList.innerHTML = '<p style="color: #aaa; padding: 20px; text-align: center;">Carregando atendentes...</p>';
const response = await window.electronAPI.getOperators();
if (response.success && response.operators) {
operatorsList.innerHTML = '';
response.operators.forEach(op => {
const item = document.createElement('div');
item.className = 'operator-item';
item.textContent = op.name;
item.onclick = () => {
if (confirm(`Encaminhar para ${op.name}?`)) {
window.electronAPI.encaminharAtendimento({
itemId: calledAtendimentoData._id,
collaboratorId: op._id
});
forwardView.style.display = 'none';
// Limpa o atendimento atual pois foi encaminhado
localStorage.removeItem('atendimentoAtual');
localStorage.removeItem('atendimentoAtualNome');
calledAtendimentoData = null;
selectedItemId = null;
showListView();
}
};
operatorsList.appendChild(item);
});
if (response.operators.length === 0) {
operatorsList.innerHTML = '<p style="color: #aaa; padding: 20px; text-align: center;">Nenhum outro atendente disponível.</p>';
}
} else {
operatorsList.innerHTML = `<p style="color: #e74c3c; padding: 20px; text-align: center;">${response.message || 'Erro ao carregar atendentes.'}</p>`;
}
}
});
closeForward.addEventListener('click', () => {
forwardView.style.display = 'none';
});
logoutButton.addEventListener('click', () => { logoutButton.addEventListener('click', () => {
window.electronAPI.logoutApp(); window.electronAPI.logoutApp();

110
style.css
View File

@ -499,8 +499,8 @@ body#floating{
} }
.btn-logout:hover { .btn-logout:hover {
color: var(--white);
background: var(--danger-color); background: var(--danger-color);
color: #fff;
} }
@ -703,10 +703,12 @@ button{
} }
#logout-button{ #logout-button{
color: var(--white);
background-color: var(--danger-color); background-color: var(--danger-color);
} }
#logout-button:hover{ #logout-button:hover{
color: var(--white);
background-color: var(--dark-danger-color); background-color: var(--dark-danger-color);
} }
@ -726,10 +728,12 @@ button{
#sair-button{ #sair-button{
color: var(--white);
background-color: #eb574d; background-color: #eb574d;
} }
#sair-button:hover{ #sair-button:hover{
color: var(--white);
background-color: #e93f2c; background-color: #e93f2c;
} }
@ -823,3 +827,107 @@ input:checked + .slider:before {
.queue-table-wrapper::-webkit-scrollbar-thumb:hover { .queue-table-wrapper::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.25); background: rgba(255, 255, 255, 0.25);
} }
.btn-forward {
flex: 1;
padding: 12px 16px;
border: 2px solid #8e44ad;
background: transparent;
color: #8e44ad;
border-radius: 8px;
font-weight: 700;
font-size: 13px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-forward:hover:not(:disabled) {
background: #8e44ad;
color: #fff;
}
.btn-forward:disabled {
opacity: 0.4;
cursor: not-allowed;
}
/* Modal Forward styles */
.modal-view {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
backdrop-filter: blur(5px);
}
.modal-content {
background: var(--primary-color);
width: 90%;
max-width: 500px;
border-radius: 16px;
border: 1px solid var(--secondary-color);
padding: 24px;
box-shadow: 0 20px 40px rgba(0,0,0,0.5);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.modal-header h2 {
color: var(--white);
margin: 0;
font-size: 1.5rem;
}
.btn-close {
background: transparent;
border: none;
color: var(--dark-gray);
font-size: 24px;
cursor: pointer;
}
.operators-grid {
display: grid;
grid-template-columns: 1fr;
gap: 12px;
max-height: 300px;
overflow-y: auto;
margin-top: 15px;
padding-right: 5px;
}
.operator-item {
background: var(--secondary-color);
border: 1px solid var(--tertiary-color);
padding: 15px;
border-radius: 10px;
color: var(--white);
cursor: pointer;
transition: all 0.2s;
text-align: left;
font-weight: 500;
}
.operator-item:hover {
background: var(--medium-gold);
border-color: var(--accent-gold);
}
.highlight {
color: var(--accent-gold);
font-weight: bold;
}