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>
<html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
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>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Painel do Operador</title>
<link rel="stylesheet" href="style.css">
<script src="res/js/jquery/jquery.js"></script>
</head>
<body>
<!-- TELA PRINCIPAL: Lista de fila + Card do chamado atual -->
<div id="list-view">
<div class="attendance-layout">
<!-- Lado esquerdo: tabela da fila -->
<div class="queue-panel">
<div class="panel-header">
<div class="main-container">
<!-- Sidebar/Lista de Espera -->
<div class="queue-sidebar">
<div class="sidebar-header">
<h2>Fila de Espera</h2>
<span id="queue-count" class="queue-badge">0</span>
<span id="queue-count" class="badge">0</span>
</div>
<div class="queue-table-wrapper">
<div class="queue-list-container">
<table class="queue-table">
<thead>
<tr>
<th>SENHA</th>
<th>CLIENTE</th>
<th>SERVIÇO</th>
<th>TIPO</th>
<th>Senha</th>
<th>Cliente</th>
</tr>
</thead>
<tbody id="queue-table-body">
<!-- Linhas da fila -->
<!-- Filas serão inseridas aqui -->
</tbody>
</table>
<div id="queue-empty" class="queue-empty" style="display:none;">
<span class="empty-icon">📋</span>
<p>Nenhum cliente aguardando</p>
<div id="queue-empty" class="empty-state">
Ninguém na fila
</div>
</div>
</div>
<!-- Lado direito: card do atendimento atual -->
<div class="current-card-panel">
<div id="current-card" class="current-card" style="display:none;">
<!-- Área Principal / Chamada -->
<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 class="card-header">
<span id="card-ticket" class="card-ticket">---</span>
<span id="card-status-badge" class="card-badge badge-chamado">Chamado</span>
<span id="card-status-badge" class="status-badge">CHAMADO</span>
<h1 id="card-ticket">---</h1>
</div>
<div class="card-body">
<p id="card-client-name" class="card-client-name">---</p>
@ -59,17 +56,13 @@
</div>
<div class="card-actions">
<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>
<span id="counter-start"></span>
INICIAR
</button>
</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>
@ -95,11 +88,18 @@
</div>
</div>
<div id="encaminhar-view" style="display: none;">
<h1>Observações</h1>
<p>Atendendo: <span id="enc-selected-item-name"></span></p>
<textarea id="enc-observation-text" rows="10" cols="50" placeholder="Digite suas observações..."></textarea>
<button id="enc-save-button">Salvar</button>
<!-- MODAL DE ENCAMINHAMENTO -->
<div id="forward-view" class="modal-view" style="display: none;">
<div class="modal-content">
<div class="modal-header">
<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>
<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 tenantId = await getTenantId();
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({
method: 'POST',
method: 'PATCH',
url: url,
headers: {
'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) => {
response.on('data', (chunk) => {
console.log(`Rechamar BODY: ${chunk}`);
@ -766,6 +773,39 @@ ipcMain.on('rechamar-atendimento', async (event, itemId) => {
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
ipcMain.on('atendimento-iniciado', (event, itemId) => {
if (floatingWin) {

View File

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

View File

@ -17,6 +17,10 @@ contextBridge.exposeInMainWorld('electronAPI', {
//rechama o atendimento atual
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
atendimentoIniciado: (itemId) => ipcRenderer.send('atendimento-iniciado', itemId),
atendimentoFinalizado: () => ipcRenderer.send('atendimento-finalizado'),

View File

@ -1,6 +1,5 @@
const listView = document.getElementById('list-view');
const observationView = document.getElementById('obs-view');
const encaminharView = document.getElementById('encaminhar-view');
// Novos elementos do layout redesenhado
const queueTableBody = document.getElementById('queue-table-body');
@ -15,6 +14,7 @@ const cardService = document.getElementById('card-service');
const cardType = document.getElementById('card-type');
const nextButton = document.getElementById('next-button');
const recallButton = document.getElementById('recall-button');
const forwardButton = document.getElementById('forward-button');
const logoutButton = document.getElementById('logout-button');
const observationText = document.getElementById('observation-text');
const saveButton = document.getElementById('save-button');
@ -22,6 +22,12 @@ const selectedItemNameSpan = document.getElementById('selected-item-name');
const idAtend = document.getElementById('idAtend');
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 selectedItemId = null;
let selectedItemName = '';
@ -137,6 +143,7 @@ function populateQueueTable(proximos) {
cardType.textContent = '';
nextButton.disabled = true;
recallButton.disabled = true;
forwardButton.disabled = true;
return;
}
@ -214,6 +221,7 @@ function showCurrentCard(data) {
setTimeout(() => {
nextButton.disabled = false;
recallButton.disabled = false;
forwardButton.disabled = false;
}, 500);
}
@ -240,7 +248,7 @@ document.addEventListener('DOMContentLoaded', () => {
//mostra a tela de listagem e permite iniciar o atendimento
function showListView() {
listView.style.display = 'block';
encaminharView.style.display = 'none';
forwardView.style.display = 'none';
observationView.style.display = 'none';
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', () => {
window.electronAPI.logoutApp();

110
style.css
View File

@ -499,8 +499,8 @@ body#floating{
}
.btn-logout:hover {
color: var(--white);
background: var(--danger-color);
color: #fff;
}
@ -703,10 +703,12 @@ button{
}
#logout-button{
color: var(--white);
background-color: var(--danger-color);
}
#logout-button:hover{
color: var(--white);
background-color: var(--dark-danger-color);
}
@ -726,10 +728,12 @@ button{
#sair-button{
color: var(--white);
background-color: #eb574d;
}
#sair-button:hover{
color: var(--white);
background-color: #e93f2c;
}
@ -823,3 +827,107 @@ input:checked + .slider:before {
.queue-table-wrapper::-webkit-scrollbar-thumb:hover {
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;
}