Compare commits

...

5 Commits
1.0.9 ... main

8 changed files with 400 additions and 158 deletions

View File

@ -3,6 +3,11 @@ const countSpan = document.getElementById('count');
// Atualiza a contagem e a cor do botão quando recebe do main process // Atualiza a contagem e a cor do botão quando recebe do main process
window.electronAPI.onUpdateCount((value) => { window.electronAPI.onUpdateCount((value) => {
// Se estiver em atendimento, ignora qualquer atualização de contagem
if (floatButton.classList.contains('em-atendimento')) {
return;
}
countSpan.innerHTML = value ?? 0; countSpan.innerHTML = value ?? 0;
// Verifica a contagem para mudar a cor // Verifica a contagem para mudar a cor
if (value > 0) { if (value > 0) {
@ -12,10 +17,50 @@ window.electronAPI.onUpdateCount((value) => {
} }
}); });
// Altera a cor do botão com base no status do atendimento
window.electronAPI.onAtendimentoStatusChanged((status) => {
if (status === 'iniciado') {
const nomeAtendimento = localStorage.getItem('atendimentoAtualNome');
floatButton.classList.remove('has-items');
floatButton.classList.add('em-atendimento');
countSpan.innerHTML = '-'; // Mostra o hífen
if (nomeAtendimento) {
floatButton.setAttribute('title', `Atendendo: ${nomeAtendimento}`);
}
} else { // 'finalizado'
floatButton.classList.remove('em-atendimento');
floatButton.setAttribute('title', 'Chamar próximo da fila');
// Solicita uma atualização imediata da contagem para refletir o estado atual da fila
window.electronAPI.refreshCount();
}
});
// Ao carregar, verifica se já existe um atendimento em andamento
document.addEventListener('DOMContentLoaded', () => {
const atendimentoAtual = localStorage.getItem('atendimentoAtual');
if (atendimentoAtual) {
const nomeAtendimento = localStorage.getItem('atendimentoAtualNome');
floatButton.classList.remove('has-items');
floatButton.classList.add('em-atendimento');
countSpan.innerHTML = '-'; // Mostra o hífen
if (nomeAtendimento) {
floatButton.setAttribute('title', `Atendendo: ${nomeAtendimento}`);
}
} else {
floatButton.setAttribute('title', 'Chamar próximo da fila');
}
});
// Mostra a janela principal ao clicar // Mostra a janela principal ao clicar
floatButton.addEventListener('click', () => { floatButton.addEventListener('click', () => {
window.electronAPI.showMainWindow(); window.electronAPI.showMainWindow();
}); });
// Adiciona um listener para o evento de clique com o botão direito
floatButton.addEventListener('contextmenu', (e) => {
e.preventDefault(); // Impede o menu padrão do navegador
window.electronAPI.showMenu(); // Chama a função para mostrar o menu no processo principal
});
// Ajuste inicial do cursor // Ajuste inicial do cursor
floatButton.style.cursor = 'pointer'; floatButton.style.cursor = 'pointer';

View File

@ -2,7 +2,8 @@ const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', { contextBridge.exposeInMainWorld('electronAPI', {
onUpdateCount: (callback) => ipcRenderer.on('update-count', (_event, value) => callback(value)), onUpdateCount: (callback) => ipcRenderer.on('update-count', (_event, value) => callback(value)),
showMainWindow: () => ipcRenderer.send('chamar-fila') onAtendimentoStatusChanged: (callback) => ipcRenderer.on('atendimento-status-changed', (_event, status) => callback(status)),
// Remova a linha abaixo: refreshCount: () => ipcRenderer.send('refresh-count'),
// startDrag: (offset) => ipcRenderer.send('drag-float-window', offset) showMainWindow: () => ipcRenderer.send('chamar-fila'),
showMenu: () => ipcRenderer.send('show-context-menu')
}); });

View File

@ -1,17 +1,19 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content=" <meta http-equiv="Content-Security-Policy" content="
default-src 'self'; default-src 'self';
script-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';
connect-src 'self' ws://autoatend.linco.work:6001 ws://localhost:6001; connect-src 'self' ws://autoatend.linco.work:6001 ws://localhost:6001 wss://aa.linco.work:443;
"> ">
<title>Tela de Atendimento</title> <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> <script src="res/js/jquery/jquery.js"></script>
</head> </head>
<body> <body>
<div id="list-view"> <div id="list-view">
<h1>Fila</h1> <h1>Fila</h1>
@ -20,7 +22,7 @@
<!-- Itens serão carregados aqui --> <!-- Itens serão carregados aqui -->
</ul> </ul>
<button id="next-button" disabled> <button id="next-button" disabled>
<span id="counter-start"></span> <span id="counter-start"></span>
Iniciar atendimento Iniciar atendimento
</button> </button>
<!-- <button id="sendto-button" disabled>Encaminhar</button> --> <!-- <button id="sendto-button" disabled>Encaminhar</button> -->
@ -47,4 +49,5 @@
<script src="renderer.js"></script> <script src="renderer.js"></script>
</body> </body>
</html> </html>

389
main.js
View File

@ -1,10 +1,10 @@
const { app, BrowserWindow, ipcMain, screen, net, dialog } = require('electron'); const { app, BrowserWindow, ipcMain, screen, net, dialog, Menu } = require('electron');
// const { app: singleInstanceLock } = require('electron-single-instance'); // const { app: singleInstanceLock } = require('electron-single-instance');
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const { autoUpdater, AppUpdater } = require('electron-updater'); const { autoUpdater, AppUpdater } = require('electron-updater');
const pjson = require(path.join(__dirname,'','package.json')); const pjson = require(path.join(__dirname, '', 'package.json'));
let floatingWin; let floatingWin;
let mainWin; let mainWin;
@ -16,7 +16,7 @@ const dataPath = path.join(__dirname, 'data.json'); // Caminho para o JSON (back
const apiUrl = 'https://autoatend.linco.work/api/v1/'; const apiUrl = 'https://autoatend.linco.work/api/v1/';
// const apiUrl = 'http://_lara10-autoatend.devel/api/v1/'; // const apiUrl = 'http://_lara10-autoatend.devel/api/v1/';
const pusherUrl = 'autoatend.linco.work'; const pusherUrl = 'aa.linco.work';
// const pusherUrl = 'localhost'; // const pusherUrl = 'localhost';
@ -32,14 +32,14 @@ async function readData() {
// Verifica se existe token de autenticação // Verifica se existe token de autenticação
const token = await getAuthToken(); const token = await getAuthToken();
const colabId = await floatingWin.webContents.executeJavaScript("localStorage.getItem('idOperator')") const colabId = await floatingWin.webContents.executeJavaScript("localStorage.getItem('idOperator')")
if (!token || !colabId) { if (!token || !colabId) {
console.log("Usuário não autenticado, retornando lista vazia"); console.log("Usuário não autenticado, retornando lista vazia");
return []; return [];
} }
// Tenta buscar dados da API // Tenta buscar dados da API
return await fetchDataFromAPI(); return await fetchDataFromAPI();
} catch (error) { } catch (error) {
console.error("Erro ao buscar dados da API:", error); console.error("Erro ao buscar dados da API:", error);
@ -50,30 +50,81 @@ async function readData() {
async function fetchDataFromAPI() { async function fetchDataFromAPI() {
//executa uma vez e a cada 30 segundos //executa uma vez e a cada 30 segundos
//TODO propicio para fazer um websockt nessas funções que repetem a chamada de requisições em busca de alterações //!primeira requisição é feita para API
getFirstData();
getDataAndUpdateFloatingBtn(); //! as outras é o websockt que solicita a chamada de requisições em busca de alterações
const updData = setInterval(() => {
const updData = setInterval(()=>{
getDataAndUpdateFloatingBtn(); getDataAndUpdateFloatingBtn();
},3000); }, 3000);
const updVersion = setTimeout(()=>{ const updVersion = setTimeout(() => {
if(pjson.isBuildNow){ if (pjson.isBuildNow) {
autoUpdater.checkForUpdates(); autoUpdater.checkForUpdates();
} }
},300000); }, 300000);
}
async function getFirstData() {
const token = await getAuthToken();
const colabId = await floatingWin.webContents.executeJavaScript("localStorage.getItem('idOperator')")
const url = apiUrl + 'get-proximos/' + colabId;
//! checa se o token e o colabId existem
if (!token && !colabId) { console.warn("Token or colabId not found in localStorage. API requests will not be made."); return; }
//! faz o request
const request = net.request({
method: 'GET',
url: url,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
}
});
//! busca pela resposta
request.on('response', (response) => {
let rawData = '';
response.on('data', (chunk) => { rawData += chunk; });
response.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
let proximos = parsedData;
if (response.statusCode === 200) {
floatingWin.webContents.executeJavaScript("localStorage.setItem('proximos','" + JSON.stringify(proximos) + "')");
let count = proximos.length;
floatingWin.webContents.send('update-count', count);
} else {
console.error(`Erro na requisição: Status code ${response.statusCode}`, parsedData);
}
} catch (error) {
console.error("Erro ao analisar a resposta JSON:", error);
mainWin.webContents.send('api-error', {
message: `Erro ao processar resposta do servidor.`
});
}
});
});
request.on('error', (error) => {
console.error("Erro na requisição:", error);
});
request.end();
return JSON.parse(await floatingWin.webContents.executeJavaScript("localStorage.getItem('proximos')"));
} }
// Função para coletar a lista de atendimentos do servidor, vai ser chamada uma vez e a cada 30s // Função para coletar a lista de atendimentos do servidor, vai ser chamada uma vez e a cada 30s
async function getDataAndUpdateFloatingBtn (){ async function getDataAndUpdateFloatingBtn() {
const proximos = JSON.parse(await floatingWin.webContents.executeJavaScript("localStorage.getItem('proximos')")) ?? []; const proximos = JSON.parse(await floatingWin.webContents.executeJavaScript("localStorage.getItem('proximos')")) ?? [];
let count = proximos.length; let count = proximos.length;
//lista a contagem no botão flutuante //lista a contagem no botão flutuante
floatingWin.webContents.send('update-count', count); floatingWin.webContents.send('update-count', count);
} }
// Função para verificar se o token existe no localStorage // Função para verificar se o token existe no localStorage
@ -109,7 +160,7 @@ function createLoginWindow() {
icon: "icon.ico", icon: "icon.ico",
frame: true, frame: true,
autoHideMenuBar: true, autoHideMenuBar: true,
webPreferences: { webPreferences: {
preload: path.join(__dirname, 'login_preload.js'), preload: path.join(__dirname, 'login_preload.js'),
contextIsolation: true, contextIsolation: true,
@ -144,7 +195,7 @@ function createOperatorWindow() {
// operatorWin.webContents.openDevTools(); // operatorWin.webContents.openDevTools();
operatorWin.loadFile('operator.html'); operatorWin.loadFile('operator.html');
operatorWin.on('closed', () => { operatorWin.on('closed', () => {
operatorWin = null; operatorWin = null;
}); });
@ -167,7 +218,7 @@ function createUpdateWindow() {
}); });
updateWin.loadFile('update.html'); updateWin.loadFile('update.html');
updateWin.on('closed', () => { updateWin.on('closed', () => {
updateWin = null; updateWin = null;
}); });
@ -205,7 +256,7 @@ function createFloatingWindow() {
floatingWin.loadFile('floating.html'); floatingWin.loadFile('floating.html');
floatingWin.webContents.executeJavaScript('localStorage.setItem("version","'+app.getVersion()+'")'); floatingWin.webContents.executeJavaScript('localStorage.setItem("version","' + app.getVersion() + '")');
// Envia a contagem inicial para a janela flutuante // Envia a contagem inicial para a janela flutuante
const data = readData(); const data = readData();
@ -256,46 +307,46 @@ function createMainWindow() {
} }
if(pjson.isBuildNow){ if (pjson.isBuildNow) {
autoUpdater.on('update-available', () => { autoUpdater.on('update-available', () => {
let pth = autoUpdater.downloadUpdate(); let pth = autoUpdater.downloadUpdate();
updateWin.show(); updateWin.focus(); updateWin.show(); updateWin.focus();
updateWin.webContents.send('update_message',`Uma nova versão está dispinível.`); updateWin.webContents.send('update_message', `Uma nova versão está dispinível.`);
updateWin.webContents.send('update_percent',pth); updateWin.webContents.send('update_percent', pth);
}) })
autoUpdater.on('download-progress',(obj) => { autoUpdater.on('download-progress', (obj) => {
updateWin.webContents.send('update_message',`Estamos baixando uma nova atualização.`); updateWin.webContents.send('update_message', `Estamos baixando uma nova atualização.`);
}); });
autoUpdater.on('update-downloaded',(obj) => { autoUpdater.on('update-downloaded', (obj) => {
updateWin.webContents.send('update_message',`Download concluído. Aguarde, vamos reiniciar para instalar!`); updateWin.webContents.send('update_message', `Download concluído. Aguarde, vamos reiniciar para instalar!`);
setTimeout(()=>{ setTimeout(() => {
autoUpdater.quitAndInstall(); autoUpdater.quitAndInstall();
},5000); }, 5000);
}); });
autoUpdater.on('error',err => { autoUpdater.on('error', err => {
updateWin.webContents.send('update_message',err); updateWin.webContents.send('update_message', err);
}); });
} }
if (!gotTheLock) { if (!gotTheLock) {
app.quit(); app.quit();
} else { } else {
app.on('second-instance', () => { app.on('second-instance', () => {
if (mainWin) { if (mainWin) {
if (mainWin.isMinimized()) { if (mainWin.isMinimized()) {
mainWin.restore(); mainWin.restore();
} }
mainWin.focus(); mainWin.focus();
} }
}); });
// Inicialização do aplicativo modificada para verificar autenticação // Inicialização do aplicativo modificada para verificar autenticação
app.whenReady().then(async () => { app.whenReady().then(async () => {
// Verifica se o usuário já está autenticado // Verifica se o usuário já está autenticado
const token = await getAuthToken(); const token = await getAuthToken();
if (!token) { if (!token) {
// Se não estiver autenticado, mostra a tela de login // Se não estiver autenticado, mostra a tela de login
createLoginWindow(); createLoginWindow();
@ -326,7 +377,7 @@ if (!gotTheLock) {
} }
}); });
if(pjson.isBuildNow){ if (pjson.isBuildNow) {
autoUpdater.checkForUpdates(); autoUpdater.checkForUpdates();
} }
@ -385,18 +436,18 @@ ipcMain.handle('get-pusher-config', async () => {
}); });
ipcMain.on('update_version', async (event, arg) => { ipcMain.on('update_version', async (event, arg) => {
if(updateWin){ if (updateWin) {
if(!updateWin.isVisible()){ if (!updateWin.isVisible()) {
updateWin.show(); updateWin.show();
updateWin.focus(); updateWin.focus();
}else{ } else {
updateWin.focus(); updateWin.focus();
} }
} else { } else {
createUpdateWindow(); createUpdateWindow();
updateWin.webContents.on('did-finish-load', () => { updateWin.webContents.on('did-finish-load', () => {
updateWin.show(); updateWin.show();
updateWin.focus(); updateWin.focus();
}); });
} }
}); });
@ -411,13 +462,10 @@ ipcMain.handle('get-count', async () => {
// Ouvir pedido para mostrar a janela principal // Ouvir pedido para mostrar a janela principal
ipcMain.on('chamar-fila', async () => { ipcMain.on('chamar-fila', async () => {
const countFila = async () => { // Primeiro, verifica se já existe um atendimento em andamento
const proximos = JSON.parse(await floatingWin.webContents.executeJavaScript("localStorage.getItem('proximos')")) ?? []; const atendimentoAtualId = await floatingWin.webContents.executeJavaScript("localStorage.getItem('atendimentoAtual')");
return proximos.length;
}
const requestData = async () =>{
const showMainWindow = () => {
if (mainWin) { if (mainWin) {
if (!mainWin.isVisible()) { if (!mainWin.isVisible()) {
mainWin.show(); mainWin.show();
@ -432,11 +480,29 @@ ipcMain.on('chamar-fila', async () => {
mainWin.focus(); mainWin.focus();
}); });
} }
}
// Se um atendimento já estiver em andamento, apenas mostra a janela principal.
// A lógica em renderer.js cuidará de exibir a tela de observação.
if (atendimentoAtualId) {
showMainWindow();
return; // Interrompe a execução aqui
}
// Se não houver atendimento em andamento, continua com a lógica original.
const countFila = async () => {
const proximos = JSON.parse(await floatingWin.webContents.executeJavaScript("localStorage.getItem('proximos')")) ?? [];
return proximos.length;
}
const requestData = async () => {
const colabId = await getSelectedOperatorId(); const colabId = await getSelectedOperatorId();
const token = await getAuthToken('token'); const token = await getAuthToken('token');
const url = apiUrl + 'chama-fila-app-colab/'+colabId; // URL de exemplo para enviar a solicitação const url = apiUrl + 'chama-fila-app-colab/' + colabId; // URL de exemplo para enviar a solicitação
const request = net.request({ const request = net.request({
method: 'GET', method: 'GET',
@ -457,27 +523,27 @@ ipcMain.on('chamar-fila', async () => {
response.on('end', () => { response.on('end', () => {
try { try {
const parsedData = JSON.parse(rawData); const parsedData = JSON.parse(rawData);
console.log(parsedData);
if (response.statusCode === 200) { if (response.statusCode === 200) {
mainWin.webContents.send('select-atend-id', parsedData.data); mainWin.webContents.send('select-atend-id', parsedData.data);
if(parsedData.data && parsedData.data.Status === 'Atendendo'){ if (parsedData.data && (parsedData.data.Status === 'Fila' || parsedData.data.Status === 'Chamado')) { showMainWindow(); } else
let options2 = { if (parsedData.data && parsedData.data.Status === 'Atendendo') {
'title': 'Precisa finalizar antes de chamar o próximo.', let options2 = {
'message': 'Em andamento', 'title': 'Precisa finalizar antes de chamar o próximo.',
'detail': 'Já possui um atendimento em andamento (Atendendo: '+ parsedData.data.clientName +'), continue e finalize por favor!', 'message': 'Em andamento',
'type': 'error', 'detail': 'Já possui um atendimento em andamento (Atendendo: ' + parsedData.data.clientName + '), continue e finalize por favor!',
'noLink': true, 'type': 'error',
'buttons': ['Depois','Continuar'], 'noLink': true,
}; 'buttons': ['Depois', 'Continuar'],
dialog.showMessageBox(floatingWin, options2).then(result => {
if(result.response){
mainWin.webContents.send('show-observation');
} else {
mainWin.hide();
}; };
}); dialog.showMessageBox(floatingWin, options2).then(result => {
} if (result.response) {
mainWin.webContents.send('show-observation');
showMainWindow();
} else {
mainWin.hide();
};
});
}
// console.log(parsedData); // console.log(parsedData);
} else { } else {
console.error(`Erro na requisição: Status code ${response.statusCode}`, parsedData); console.error(`Erro na requisição: Status code ${response.statusCode}`, parsedData);
@ -506,19 +572,19 @@ ipcMain.on('chamar-fila', async () => {
}; };
let options = { let options = {
'title': 'Incie o atendimento quando o cliente chegar na sala.', 'title': 'Incie o atendimento quando o cliente chegar na sala.',
'message': 'Chamar um cliente da fila?', 'message': 'Chamar um cliente da fila?',
'type': 'warning', 'type': 'warning',
'noLink': true, 'noLink': true,
'buttons': ['Não','Sim'], 'buttons': ['Não', 'Sim'],
}; };
if(await countFila()){
if (await countFila()) {
dialog.showMessageBox(floatingWin, options).then(result => { dialog.showMessageBox(floatingWin, options).then(result => {
if(result.response){ if (result.response) {
requestData(); requestData();
}; };
}); });
@ -528,7 +594,15 @@ ipcMain.on('chamar-fila', async () => {
}); });
ipcMain.on('select-atend-id',(itemId)=>{ // Ouve um pedido da janela flutuante para forçar a atualização da contagem
ipcMain.on('refresh-count', async () => {
if (floatingWin) {
const proximos = JSON.parse(await floatingWin.webContents.executeJavaScript("localStorage.getItem('proximos')")) ?? [];
floatingWin.webContents.send('update-count', proximos.length);
}
});
ipcMain.on('select-atend-id', (itemId) => {
selectedItemId = itemId; selectedItemId = itemId;
console.log(selectedItemId); console.log(selectedItemId);
}); });
@ -540,9 +614,9 @@ ipcMain.on('iniciar-atendimento', async (event, itemId) => {
const colabId = await floatingWin.webContents.executeJavaScript("localStorage.getItem('idOperator')") const colabId = await floatingWin.webContents.executeJavaScript("localStorage.getItem('idOperator')")
//TODO inicia o atendimento o id do atendimento deve ser requisitado do backend //TODO inicia o atendimento o id do atendimento deve ser requisitado do backend
const url = apiUrl + 'iniciar-atendimento/'+itemId; // URL de exemplo para enviar a solicitação const url = apiUrl + 'iniciar-atendimento/' + itemId; // URL para enviar a solicitação
// Simula o envio de uma solicitação POST com o ID do item // envio de uma solicitação POST com o ID do item
const request = net.request({ const request = net.request({
method: 'GET', method: 'GET',
url: url, url: url,
@ -577,6 +651,20 @@ ipcMain.on('iniciar-atendimento', async (event, itemId) => {
// A janela principal já mudou a UI ao enviar o evento 'next-step' // A janela principal já mudou a UI ao enviar o evento 'next-step'
}); });
// Ouve quando um atendimento é iniciado e notifica a janela flutuante
ipcMain.on('atendimento-iniciado', (event, itemId) => {
if (floatingWin) {
floatingWin.webContents.send('atendimento-status-changed', 'iniciado');
}
});
// Ouve quando um atendimento é finalizado e notifica a janela flutuante
ipcMain.on('atendimento-finalizado', () => {
if (floatingWin) {
floatingWin.webContents.send('atendimento-status-changed', 'finalizado');
}
});
// Ouvir clique no botão "Salvar" // Ouvir clique no botão "Salvar"
ipcMain.on('save-observation', async (event, { itemId, observation }) => { ipcMain.on('save-observation', async (event, { itemId, observation }) => {
@ -588,7 +676,7 @@ ipcMain.on('save-observation', async (event, { itemId, observation }) => {
const colabId = await floatingWin.webContents.executeJavaScript("localStorage.getItem('idOperator')") const colabId = await floatingWin.webContents.executeJavaScript("localStorage.getItem('idOperator')")
//TODO inicia o atendimento o id do atendimento deve ser requisitado do backend //TODO inicia o atendimento o id do atendimento deve ser requisitado do backend
const url = apiUrl + 'finalizar-atendimento/'+itemId; // URL de exemplo para enviar a solicitação const url = apiUrl + 'finalizar-atendimento/' + itemId; // URL de exemplo para enviar a solicitação
const fmData = JSON.stringify({ const fmData = JSON.stringify({
"colabId": colabId, "colabId": colabId,
@ -644,7 +732,7 @@ ipcMain.on('drag-float-window', (event, { offsetX, offsetY }) => {
*/ */
ipcMain.on('logout', ()=>{ ipcMain.on('logout', () => {
mainWin.webContents.executeJavaScript(` mainWin.webContents.executeJavaScript(`
localStorage.removeItem("idOperator"); localStorage.removeItem("idOperator");
localStorage.removeItem("selectedOperator"); localStorage.removeItem("selectedOperator");
@ -664,7 +752,7 @@ ipcMain.on('login-attempt', async (event, credentials) => {
try { try {
// Substitua pela URL real da sua API de autenticação // Substitua pela URL real da sua API de autenticação
const route = apiUrl + 'login'; const route = apiUrl + 'login';
const request = net.request({ const request = net.request({
method: 'POST', method: 'POST',
url: route, url: route,
@ -672,20 +760,20 @@ ipcMain.on('login-attempt', async (event, credentials) => {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
}); });
let responseData = ''; let responseData = '';
request.on('response', (response) => { request.on('response', (response) => {
response.on('data', (chunk) => { response.on('data', (chunk) => {
responseData += chunk.toString(); responseData += chunk.toString();
console.log("Resposta da API:", responseData); // Adiciona este log para ver a resposta crua console.log("Resposta da API:", responseData); // Adiciona este log para ver a resposta crua
}); });
response.on('end', () => { response.on('end', () => {
try { try {
const data = JSON.parse(responseData); const data = JSON.parse(responseData);
if (data.status === 'Authorized' && data.api_key) { if (data.status === 'Authorized' && data.api_key) {
// Login bem-sucedido // Login bem-sucedido
loginWin.webContents.executeJavaScript(` loginWin.webContents.executeJavaScript(`
@ -700,35 +788,35 @@ ipcMain.on('login-attempt', async (event, credentials) => {
} else { } else {
// Login falhou // Login falhou
event.reply('login-response', { event.reply('login-response', {
success: false, success: false,
message: data.message || 'Falha na autenticação' message: data.message || 'Falha na autenticação'
}); });
} }
} catch (error) { } catch (error) {
console.log("Resposta da API:", error); console.log("Resposta da API:", error);
event.reply('login-response', { event.reply('login-response', {
success: false, success: false,
message: 'Erro ao processar resposta do servidor' message: 'Erro ao processar resposta do servidor'
}); });
} }
}); });
}); });
request.on('error', (error) => { request.on('error', (error) => {
event.reply('login-response', { event.reply('login-response', {
success: false, success: false,
message: `Erro de conexão: ${error.message}` message: `Erro de conexão: ${error.message}`
}); });
}); });
// Envia as credenciais // Envia as credenciais
request.write(JSON.stringify(credentials)); request.write(JSON.stringify(credentials));
request.end(); request.end();
} catch (error) { } catch (error) {
event.reply('login-response', { event.reply('login-response', {
success: false, success: false,
message: `Erro: ${error.message}` message: `Erro: ${error.message}`
}); });
} }
}); });
@ -750,9 +838,9 @@ ipcMain.on('select-operator', async (event, operator) => {
createUpdateWindow(); createUpdateWindow();
}); });
} catch (error) { } catch (error) {
event.reply('operator-response', { event.reply('operator-response', {
success: false, success: false,
message: `Erro: ${error.message}` message: `Erro: ${error.message}`
}); });
} }
}); });
@ -762,15 +850,15 @@ ipcMain.handle('get-operators', async () => {
try { try {
// Verifica se existe token de autenticação // Verifica se existe token de autenticação
const token = await getAuthToken(); const token = await getAuthToken();
if (!token) { if (!token) {
return { return {
success: false, success: false,
message: 'Não autenticado', message: 'Não autenticado',
operators: [] operators: []
}; };
} }
// Aqui você pode fazer uma chamada à API para obter os operadores // Aqui você pode fazer uma chamada à API para obter os operadores
// Por enquanto, vamos retornar alguns operadores de exemplo // Por enquanto, vamos retornar alguns operadores de exemplo
// Substitua pela URL real da sua API de autenticação // Substitua pela URL real da sua API de autenticação
@ -811,7 +899,7 @@ ipcMain.handle('get-operators', async () => {
operators: operators operators: operators
}); });
} else { } else {
reject({ reject({
success: false, success: false,
message: data.message || 'Erro ao obter a lista de colaboradores', message: data.message || 'Erro ao obter a lista de colaboradores',
operators: [] operators: []
@ -819,7 +907,7 @@ ipcMain.handle('get-operators', async () => {
} }
} catch (error) { } catch (error) {
console.log("Erro ao processar resposta da API:", error); console.log("Erro ao processar resposta da API:", error);
reject({ reject({
success: false, success: false,
message: 'Erro ao processar resposta do servidor', message: 'Erro ao processar resposta do servidor',
operators: [] operators: []
@ -840,10 +928,10 @@ ipcMain.handle('get-operators', async () => {
}); });
} catch (error) { } catch (error) {
console.error('Erro ao obter operadores:', error); console.error('Erro ao obter operadores:', error);
return { return {
success: false, success: false,
message: `Erro: ${error.message}`, message: `Erro: ${error.message}`,
operators: [] operators: []
}; };
} }
}); });
@ -872,13 +960,66 @@ ipcMain.on('token-exists', async () => {
}); });
ipcMain.on('sair', ()=>{ ipcMain.on('sair', () => {
operatorWin.webContents.executeJavaScript(` let exec = `
localStorage.removeItem("idOperator"); localStorage.removeItem("idOperator");
localStorage.removeItem("selectedOperator"); localStorage.removeItem("selectedOperator");
localStorage.removeItem("salaOperator"); localStorage.removeItem("salaOperator");
localStorage.removeItem("servicosOperator"); localStorage.removeItem("servicosOperator");
`).then(() => { `;
app.exit();
let options = {
'title': 'Deseja realmente sair?',
'message': 'Tem certeza que deseja fechar o Auto Atendimento?',
'type': 'question',
'buttons': ['Não', 'Sim'],
'defaultId': 0,
'cancelId': 0,
};
dialog.showMessageBox(floatingWin, options).then(result => {
if (result.response === 1) { // Se o usuário clicou em "Sim" (índice 1)
if (floatingWin) {
floatingWin.webContents.executeJavaScript(exec).then(() => { app.exit(); });
} else if (operatorWin) {
operatorWin.webContents.executeJavaScript(exec).then(() => { app.exit(); });
}
}
}); });
});
ipcMain.on('show-context-menu', (event) => {
const template = [
{
label: 'Chamar Fila',
click: () => {
// Lógica para chamar atendimento
if (floatingWin) {
ipcMain.emit('chamar-fila');
}
}
},
{
label: 'Trocar Colab',
click: () => {
// Lógica para chamar atendimento
if (floatingWin) {
ipcMain.emit('logout');
}
}
},
{
label: 'Sair',
click: () => {
// Lógica para fechar o aplicativo
if (floatingWin) {
ipcMain.emit('sair');
}
}
}
];
const menu = Menu.buildFromTemplate(template);
menu.popup({ window: floatingWin });
}); });

View File

@ -1,6 +1,6 @@
{ {
"name": "autoatendcolab", "name": "autoatendcolab",
"version": "1.0.9", "version": "1.1.3",
"main": "main.js", "main": "main.js",
"isBuildNow": true, "isBuildNow": true,
"scripts": { "scripts": {
@ -48,4 +48,4 @@
"electron-reload": "^2.0.0-alpha.1", "electron-reload": "^2.0.0-alpha.1",
"nodemon": "^3.1.10" "nodemon": "^3.1.10"
} }
} }

View File

@ -14,6 +14,10 @@ contextBridge.exposeInMainWorld('electronAPI', {
//inicia o atendimento atual //inicia o atendimento atual
iniciaAtendimento: (itemId) => ipcRenderer.send('iniciar-atendimento', itemId), iniciaAtendimento: (itemId) => ipcRenderer.send('iniciar-atendimento', itemId),
//notifica sobre o status do atendimento
atendimentoIniciado: (itemId) => ipcRenderer.send('atendimento-iniciado', itemId),
atendimentoFinalizado: () => ipcRenderer.send('atendimento-finalizado'),
showObservation: (callback) => ipcRenderer.on('show-observation', (_event) => callback() ), showObservation: (callback) => ipcRenderer.on('show-observation', (_event) => callback() ),
//salva a observação do atendimento //salva a observação do atendimento
saveObservation: (data) => ipcRenderer.send('save-observation', data), saveObservation: (data) => ipcRenderer.send('save-observation', data),

View File

@ -18,11 +18,11 @@ let selectedItemName = '';
window.electronAPI.onLoadData((data) => { window.electronAPI.onLoadData((data) => {
nextButton.disabled = true; nextButton.disabled = true;
if(!data){ if (!data) {
return; return;
} }
// Reseta a view para a lista sempre que os dados são carregados // Reseta a view para a lista sempre que os dados são carregados
populateList(data[0]); populateList(data[0]);
}); });
async function initializePusher() { async function initializePusher() {
@ -38,34 +38,42 @@ async function initializePusher() {
const channelLocal = localStorage.getItem('channel'); const channelLocal = localStorage.getItem('channel');
const colabId = localStorage.getItem('idOperator'); const colabId = localStorage.getItem('idOperator');
if (channelLocal && PUSHER_APP_KEY) { //! checa se ja tem o colabId
if (channelLocal && colabId && PUSHER_APP_KEY) {
Pusher.logToConsole = true; Pusher.logToConsole = true;
var pusher = new Pusher(PUSHER_APP_KEY, { var pusher = new Pusher(PUSHER_APP_KEY, {
wsHost: host, wsHost: host,
wsPort: 6001, wsPort: 80,
wssPort: 6001, wssPort: 443,
forceTLS: false, forceTLS: true,
enableStats: false, enableStats: false,
enabledTransports: ['ws','wss'], enabledTransports: ['wss', 'ws'],
cluster: 'mt1' cluster: 'mt1'
}); });
var channel = pusher.subscribe('chat.' + channelLocal + '_' + colabId); let channel;
channel.bind('message-sent', function(r) { if (pusher.connection.state === 'connected') {
let count = r.data.fila.original.length; pusher.unsubscribe('chat.' + channelLocal + '_' + colabId);
console.log(r.data.fila.original); }
localStorage.setItem('proximos',JSON.stringify(r.data.fila.original));
channel = pusher.subscribe('chat.' + channelLocal + '_' + colabId);
channel.bind('message-sent', function (r) {
let data = r.data.fila.original;
let count = data.length;
console.log(data);
localStorage.setItem('proximos', JSON.stringify(data));
populateList(r.data.currentData.original); populateList(r.data.currentData.original);
}); });
pusher.connection.bind('error', function(err) { pusher.connection.bind('error', function (err) {
console.error('Pusher connection error:', err); console.error('Pusher connection error: ', err);
}); });
console.log('Host de conexão:', host); console.log('Host de conexão: ', host);
} else { } else {
console.warn('User not authenticated or Pusher APP_KEY not available. Private channel not subscribed.'); console.warn('User not authenticated or Pusher APP_KEY not available. Private channel not subscribed.');
} }
@ -73,43 +81,52 @@ async function initializePusher() {
initializePusher(); initializePusher();
//chama o proximo da fila ao abrir a janela de atendimentos //chama o proximo da fila ao abrir a janela de atendimentos
window.electronAPI.selectAtendID((data)=>{ window.electronAPI.selectAtendID((data) => {
nextButton.disabled = true; nextButton.disabled = true;
if(!data){ if (!data) {
queueNumber.innerHTML = 'Ninguem aguardando atendimento, fechando a janela em alguns segundos...'; queueNumber.innerHTML = 'Ninguem aguardando atendimento, fechando a janela em alguns segundos...';
setTimeout(() => {
localStorage.removeItem('proximos');
window.close();
},5000);
return; return;
} }
// Reseta a view para a lista sempre que os dados são carregados ao clicar no botão para abrir a janela // Reseta a view para a lista sempre que os dados são carregados ao clicar no botão para abrir a janela
populateList(data); populateList(data);
showListView(); showListView();
// Garante que o item selecionado (ID e Nome) seja o que veio da chamada, sobrescrevendo o da lista.
selectedItemId = data.id ?? null; selectedItemId = data.id ?? null;
selectedItemName = data.clientName ?? '';
//data.senhaGen //data.senhaGen
queueNumber.innerHTML = data ? `NA VEZ: <u>${data.clientName.toUpperCase()}</u> - ${data.descricaoServico.toUpperCase()}` : 'Ninguem aguardando atendimento'; queueNumber.innerHTML = data ? `NA VEZ: <u>${data.clientName.toUpperCase()}</u> - ${data.descricaoServico.toUpperCase()}` : 'Ninguem aguardando atendimento';
selectedItemNameSpan.innerHTML = data ? `<u> ${data.clientName.toUpperCase()} </u> <i style="float:right;">[ ${data.senhaGen} ]</i>` : 'Ninguem aguardando atendimento'; selectedItemNameSpan.innerHTML = data ? `<u> ${data.clientName.toUpperCase()} </u> <i style="float:right;">[ ${data.senhaGen} ]</i>` : 'Ninguem aguardando atendimento';
}); });
window.electronAPI.showObservation(()=>{ window.electronAPI.showObservation(() => {
window.electronAPI.iniciaAtendimento(selectedItemId); window.electronAPI.iniciaAtendimento(selectedItemId);
showObservationView(); // Muda para a tela de observação showObservationView(); // Muda para a tela de observação
}); });
// Função para popular a lista de itens // Função para popular a lista de itens
function populateList(currentData) { function populateList(currentData) {
const atendimentoEmAndamentoId = localStorage.getItem('atendimentoAtual');
const atendimentoEmAndamentoNome = localStorage.getItem('atendimentoAtualNome');
if (atendimentoEmAndamentoId) {
itemList.innerHTML = `<li>Atendimento com <strong>${(atendimentoEmAndamentoNome || '').toUpperCase()}</strong> em andamento.</li>`;
queueNumber.innerHTML = `EM ATENDIMENTO: <u>${(atendimentoEmAndamentoNome || '').toUpperCase()}</u>`;
nextButton.disabled = true;
return;
}
let datastorage = localStorage.getItem('proximos'); let datastorage = localStorage.getItem('proximos');
// Adiciona os outros itens apenas para visualização (opcional) // Adiciona os outros itens apenas para visualização (opcional)
const proximos = JSON.parse(datastorage); const proximos = JSON.parse(datastorage);
itemList.innerHTML = ''; itemList.innerHTML = '';
setTimeout(()=>{ setTimeout(() => {
nextButton.disabled = !currentData; nextButton.disabled = !currentData;
},5000); }, 5000);
// Seleciona o primeiro item por padrão (ou o próximo disponível) // Seleciona o primeiro item por padrão (ou o próximo disponível)
// Aqui, vamos apenas pegar o primeiro da lista atual // Aqui, vamos apenas pegar o primeiro da lista atual
@ -139,6 +156,19 @@ function populateList(currentData) {
} }
// Verifica o estado ao carregar a janela
document.addEventListener('DOMContentLoaded', () => {
const atendimentoEmAndamentoId = localStorage.getItem('atendimentoAtual');
if (atendimentoEmAndamentoId) {
// Se um atendimento está em andamento, a tela de observação deve ser mostrada
selectedItemId = atendimentoEmAndamentoId;
selectedItemName = localStorage.getItem('atendimentoAtualNome');
selectedItemNameSpan.innerHTML = `<u> ${(selectedItemName || '').toUpperCase()} </u>`;
showObservationView();
}
});
//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';
@ -160,6 +190,12 @@ function showObservationView() {
// // Evento do botão "Iniciar atendimento" // // Evento do botão "Iniciar atendimento"
nextButton.addEventListener('click', () => { nextButton.addEventListener('click', () => {
if (selectedItemId !== null) { if (selectedItemId !== null) {
// Salva o estado de atendimento no localStorage
localStorage.setItem('atendimentoAtual', selectedItemId);
localStorage.setItem('atendimentoAtualNome', selectedItemName);
// Notifica o main process e muda a view
window.electronAPI.atendimentoIniciado(selectedItemId);
window.electronAPI.iniciaAtendimento(selectedItemId); window.electronAPI.iniciaAtendimento(selectedItemId);
showObservationView(); // Muda para a tela de observação showObservationView(); // Muda para a tela de observação
} else { } else {
@ -169,7 +205,7 @@ nextButton.addEventListener('click', () => {
logoutButton.addEventListener('click',()=>{ logoutButton.addEventListener('click', () => {
window.electronAPI.logoutApp(); window.electronAPI.logoutApp();
}); });
@ -178,6 +214,11 @@ logoutButton.addEventListener('click',()=>{
saveButton.addEventListener('click', () => { saveButton.addEventListener('click', () => {
const observation = observationText.value; const observation = observationText.value;
if (selectedItemId !== null) { if (selectedItemId !== null) {
// Limpa o estado de atendimento
localStorage.removeItem('atendimentoAtual');
localStorage.removeItem('atendimentoAtualNome');
window.electronAPI.atendimentoFinalizado();
window.electronAPI.saveObservation({ itemId: selectedItemId, observation: observation }); window.electronAPI.saveObservation({ itemId: selectedItemId, observation: observation });
window.location.reload(); window.location.reload();
} }

View File

@ -78,6 +78,13 @@ body#floating{
animation: pulse-border .5s infinite; animation: pulse-border .5s infinite;
} }
/* Nova classe para quando estiver em atendimento */
#float-button.em-atendimento {
background-color: var(--danger-color); /* Vermelho */
border: 4px solid var(--dark-danger-color);
animation: pulse-border .2s infinite; /* Pulso mais rápido */
}
@keyframes pulse-border { @keyframes pulse-border {
0% { 0% {
border-color: var(--medium-gold); border-color: var(--medium-gold);