melhorias na interface, adicionado botão para encaminhar chamado, e ajuste no botão para rechamar o cliente
This commit is contained in:
parent
8e53b7ccbd
commit
8403281a6d
80
index.html
80
index.html
|
|
@ -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">×</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>
|
||||
|
|
@ -107,4 +107,4 @@
|
|||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
44
main.js
44
main.js
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "autoatendcolab",
|
||||
"version": "1.1.6",
|
||||
"version": "1.1.7",
|
||||
"main": "main.js",
|
||||
"isBuildNow": true,
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
60
renderer.js
60
renderer.js
|
|
@ -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();
|
||||
|
|
|
|||
112
style.css
112
style.css
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -822,4 +826,108 @@ 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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue