Compare commits

..

4 Commits

11 changed files with 359 additions and 177 deletions

View File

@ -7,7 +7,7 @@
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 wss://aa.linco.work:443; connect-src 'self' ws://localhost:3000 http://localhost:3000;
"> ">
<title>Tela de Atendimento</title> <title>Tela de Atendimento</title>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
@ -45,7 +45,7 @@
<button id="save-button">Salvar</button> <button id="save-button">Salvar</button>
</div> </div>
<script src="res/js/pusher.min.js"></script> <script src="res/js/socket.io.min.js"></script>
<script src="renderer.js"></script> <script src="renderer.js"></script>
</body> </body>

233
main.js
View File

@ -13,19 +13,63 @@ let operatorWin;
let updateWin; let updateWin;
const dataPath = path.join(__dirname, 'data.json'); // Caminho para o JSON (backup local) const dataPath = path.join(__dirname, 'data.json'); // Caminho para o JSON (backup local)
const apiUrl = 'https://autoatend.linco.work/api/v1/'; const settingsPath = path.join(app.getPath('userData'), 'settings.json'); // Caminho para as configurações
// const apiUrl = 'http://_lara10-autoatend.devel/api/v1/';
const pusherUrl = 'aa.linco.work';
// const pusherUrl = 'localhost';
//! API
const apiUrl = 'http://localhost:3000/api/'; // Adaptado para NestJS
//! pusher
const pusherUrl = 'http://localhost:3000'; // Agora aponta para o Socket.io (NestJS)
autoUpdater.autoDownload = false; autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = true; autoUpdater.autoInstallOnAppQuit = true;
//? Função para ler as configurações
function getSettings() {
try {
if (fs.existsSync(settingsPath)) {
const settingsData = fs.readFileSync(settingsPath, 'utf-8');
return JSON.parse(settingsData);
}
} catch (error) {
console.error('Erro ao ler arquivo de configurações:', error);
}
return {}; // Retorna objeto vazio se o arquivo não existir ou houver erro
}
//? Função para salvar as configurações
function saveSettings(settings) {
try {
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
} catch (error) {
console.error('Erro ao salvar arquivo de configurações:', error);
}
}
ipcMain.handle('get-setting', (event, key) => {
const settings = getSettings();
return settings[key];
});
ipcMain.on('set-setting', (event, key, value) => {
const settings = getSettings();
settings[key] = value;
saveSettings(settings);
app.relaunch();
app.exit();
});
//! final de configurações
//impede que o app seja executado mais de uma vez //impede que o app seja executado mais de uma vez
const gotTheLock = app.requestSingleInstanceLock(); const gotTheLock = app.requestSingleInstanceLock();
// Função para configurar inicialização automática
function setAutoLaunch(start) {
app.setLoginItemSettings({
openAtLogin: start,
path: app.getPath('exe'), // Aponta para o executável do seu app
});
}
// Função modificada para buscar dados da API // Função modificada para buscar dados da API
async function readData() { async function readData() {
try { try {
@ -69,7 +113,8 @@ async function getFirstData() {
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')")
const url = apiUrl + 'get-proximos/' + colabId; const tenantId = await floatingWin.webContents.executeJavaScript("localStorage.getItem('tenantId')")
const url = apiUrl + 'attendance/next-in-line/' + colabId;
//! checa se o token e o colabId existem //! 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; } if (!token && !colabId) { console.warn("Token or colabId not found in localStorage. API requests will not be made."); return; }
@ -80,7 +125,8 @@ async function getFirstData() {
url: url, url: url,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token 'Authorization': 'Bearer ' + token,
'x-tenant-id': tenantId
} }
}); });
@ -91,7 +137,7 @@ async function getFirstData() {
response.on('end', () => { response.on('end', () => {
try { try {
const parsedData = JSON.parse(rawData); const parsedData = JSON.parse(rawData);
let proximos = parsedData; let proximos = Array.isArray(parsedData) ? parsedData : [];
if (response.statusCode === 200) { if (response.statusCode === 200) {
floatingWin.webContents.executeJavaScript("localStorage.setItem('proximos','" + JSON.stringify(proximos) + "')"); floatingWin.webContents.executeJavaScript("localStorage.setItem('proximos','" + JSON.stringify(proximos) + "')");
let count = proximos.length; let count = proximos.length;
@ -119,7 +165,8 @@ async function getFirstData() {
// 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 stored = await floatingWin.webContents.executeJavaScript("localStorage.getItem('proximos')");
const proximos = JSON.parse(stored || '[]');
let count = proximos.length; let count = proximos.length;
//lista a contagem no botão flutuante //lista a contagem no botão flutuante
@ -193,6 +240,7 @@ function createOperatorWindow() {
}); });
// operatorWin.webContents.openDevTools(); // operatorWin.webContents.openDevTools();
operatorWin.webContents.executeJavaScript('localStorage.setItem("version","' + app.getVersion() + '")');
operatorWin.loadFile('operator.html'); operatorWin.loadFile('operator.html');
@ -256,8 +304,6 @@ function createFloatingWindow() {
floatingWin.loadFile('floating.html'); floatingWin.loadFile('floating.html');
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();
floatingWin.webContents.on('did-finish-load', () => { floatingWin.webContents.on('did-finish-load', () => {
@ -344,6 +390,11 @@ if (!gotTheLock) {
// 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 () => {
//define a inicialização automatica do aplicativo ao entrar no windows
const settings = getSettings();
const enableAutoStart = settings.autostart === undefined ? true : settings.autostart;
setAutoLaunch(enableAutoStart);
// Verifica se o usuário já está autenticado // Verifica se o usuário já está autenticado
const token = await getAuthToken(); const token = await getAuthToken();
@ -399,11 +450,11 @@ async function getSelectedOperator() {
.then(operator => resolve(operator)) .then(operator => resolve(operator))
.catch(() => resolve(null)); .catch(() => resolve(null));
} else if (mainWin && !mainWin.isDestroyed()) { } else if (mainWin && !mainWin.isDestroyed()) {
mainWin.webContents.executeJavaScript('localStorage.getItem("selectedOperator");') mainWin.webContents.executeJavaScript('localStorage.getItem("authToken");')
.then(operator => resolve(operator)) .then(operator => resolve(operator))
.catch(() => resolve(null)); .catch(() => resolve(null));
} else if (floatingWin && !floatingWin.isDestroyed()) { } else if (floatingWin && !floatingWin.isDestroyed()) {
floatingWin.webContents.executeJavaScript('localStorage.getItem("selectedOperator");') floatingWin.webContents.executeJavaScript('localStorage.getItem("authToken");')
.then(operator => resolve(operator)) .then(operator => resolve(operator))
.catch(() => resolve(null)); .catch(() => resolve(null));
} else { } else {
@ -430,9 +481,8 @@ async function getSelectedOperatorId() {
ipcMain.handle('get-pusher-config', async () => { ipcMain.handle('get-pusher-config', async () => {
// Obtenha sua chave e host de forma segura aqui (ambiente, .env, etc.) // Obtenha sua chave e host de forma segura aqui (ambiente, .env, etc.)
const PUSHER_APP_KEY = process.env.PUSHER_APP_KEY || '1feb970af7708cb';
const PUSHER_HOST = process.env.PUSHER_HOST || pusherUrl; const PUSHER_HOST = process.env.PUSHER_HOST || pusherUrl;
return { appKey: PUSHER_APP_KEY, host: PUSHER_HOST }; return { host: PUSHER_HOST };
}); });
ipcMain.on('update_version', async (event, arg) => { ipcMain.on('update_version', async (event, arg) => {
@ -491,25 +541,25 @@ ipcMain.on('chamar-fila', async () => {
// Se não houver atendimento em andamento, continua com a lógica original. // Se não houver atendimento em andamento, continua com a lógica original.
const countFila = async () => { const countFila = async () => {
const proximos = JSON.parse(await floatingWin.webContents.executeJavaScript("localStorage.getItem('proximos')")) ?? []; const stored = await floatingWin.webContents.executeJavaScript("localStorage.getItem('proximos')");
const proximos = JSON.parse(stored || '[]');
return proximos.length; return proximos.length;
} }
const requestData = async () => { const requestData = async () => {
const colabId = await getSelectedOperatorId(); const colabId = await getSelectedOperatorId();
const token = await getAuthToken('token'); const token = await getAuthToken();
const url = apiUrl + 'chama-fila-app-colab/' + colabId; // URL de exemplo para enviar a solicitação const tenantId = await floatingWin.webContents.executeJavaScript("localStorage.getItem('tenantId')")
const url = apiUrl + 'attendance/call-next/' + colabId;
const request = net.request({ const request = net.request({
method: 'GET', method: 'POST',
url: url, url: url,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token 'Authorization': 'Bearer ' + token,
'x-tenant-id': tenantId
} }
}); });
@ -523,31 +573,33 @@ ipcMain.on('chamar-fila', async () => {
response.on('end', () => { response.on('end', () => {
try { try {
const parsedData = JSON.parse(rawData); const parsedData = JSON.parse(rawData);
if (response.statusCode === 200) { if (response.statusCode === 201 || response.statusCode === 200) {
mainWin.webContents.send('select-atend-id', parsedData.data); if (parsedData) {
if (parsedData.data && (parsedData.data.Status === 'Fila' || parsedData.data.Status === 'Chamado')) { showMainWindow(); } else mainWin.webContents.send('select-atend-id', parsedData);
if (parsedData.data && parsedData.data.Status === 'Atendendo') { if (parsedData.status === 'Fila' || parsedData.status === 'Chamado') { showMainWindow(); } else
let options2 = { if (parsedData.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.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');
showMainWindow();
} else {
mainWin.hide();
}; };
}); dialog.showMessageBox(floatingWin, options2).then(result => {
} if (result.response) {
// console.log(parsedData); mainWin.webContents.send('show-observation');
showMainWindow();
} else {
mainWin.hide();
};
});
}
} else {
mainWin.webContents.send('select-atend-id', null);
}
} else { } else {
console.error(`Erro na requisição: Status code ${response.statusCode}`, parsedData); console.error(`Erro na requisição: Status code ${response.statusCode}`, parsedData);
// Lidar com o erro adequadamente, talvez enviando uma mensagem para a janela principal
mainWin.webContents.send('api-error', { mainWin.webContents.send('api-error', {
message: `Erro ao chamar atendimento: ${parsedData.message || 'Erro desconhecido'}` message: `Erro ao chamar atendimento: ${parsedData.message || 'Erro desconhecido'}`
}); });
@ -597,7 +649,8 @@ ipcMain.on('chamar-fila', async () => {
// Ouve um pedido da janela flutuante para forçar a atualização da contagem // Ouve um pedido da janela flutuante para forçar a atualização da contagem
ipcMain.on('refresh-count', async () => { ipcMain.on('refresh-count', async () => {
if (floatingWin) { if (floatingWin) {
const proximos = JSON.parse(await floatingWin.webContents.executeJavaScript("localStorage.getItem('proximos')")) ?? []; const stored = await floatingWin.webContents.executeJavaScript("localStorage.getItem('proximos')");
const proximos = JSON.parse(stored || '[]');
floatingWin.webContents.send('update-count', proximos.length); floatingWin.webContents.send('update-count', proximos.length);
} }
}); });
@ -611,44 +664,33 @@ ipcMain.on('select-atend-id', (itemId) => {
ipcMain.on('iniciar-atendimento', async (event, itemId) => { ipcMain.on('iniciar-atendimento', async (event, itemId) => {
const token = await getAuthToken(); const token = await getAuthToken();
const colabId = await floatingWin.webContents.executeJavaScript("localStorage.getItem('idOperator')") const tenantId = await floatingWin.webContents.executeJavaScript("localStorage.getItem('tenantId')")
//TODO inicia o atendimento o id do atendimento deve ser requisitado do backend const url = apiUrl + 'attendance/' + itemId + '/start';
const url = apiUrl + 'iniciar-atendimento/' + itemId; // URL para enviar a solicitaçã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: 'PATCH',
url: url, url: url,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token 'Authorization': 'Bearer ' + token,
'x-tenant-id': tenantId
} }
}); });
request.on('response', (response) => { request.on('response', (response) => {
// console.log(`STATUS: ${response.statusCode}`);
// console.log(`HEADERS: ${JSON.stringify(response.headers)}`);
response.on('data', (chunk) => { response.on('data', (chunk) => {
console.log(`BODY: ${chunk}`); console.log(`BODY: ${chunk}`);
}); });
response.on('end', () => { response.on('end', () => {
console.log('Solicitação concluída.'); console.log('Solicitação concluída.');
// Avisa a janela principal que a solicitação foi feita (opcional)
// mainWin.webContents.send('request-done', itemId);
}); });
}); });
request.on('error', (error) => { request.on('error', (error) => {
console.error(`Erro na solicitação: ${error}`); console.error(`Erro na solicitação: ${error}`);
// Poderia notificar a UI sobre o erro
}); });
// Envia o ID como corpo da requisição (exemplo)
request.write(JSON.stringify({ id: itemId }));
request.end(); request.end();
// Não precisamos esperar a resposta para mudar a UI na janela principal
// A janela principal já mudou a UI ao enviar o evento 'next-step'
}); });
// Ouve quando um atendimento é iniciado e notifica a janela flutuante // Ouve quando um atendimento é iniciado e notifica a janela flutuante
@ -668,19 +710,14 @@ ipcMain.on('atendimento-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 }) => {
//TODO salva a observação e finaliza o atendimento
console.log(`Salvando observação para item ${itemId}: ${observation}`); console.log(`Salvando observação para item ${itemId}: ${observation}`);
const token = await getAuthToken(); const token = await getAuthToken();
const colabId = await floatingWin.webContents.executeJavaScript("localStorage.getItem('idOperator')") const tenantId = await floatingWin.webContents.executeJavaScript("localStorage.getItem('tenantId')")
//TODO inicia o atendimento o id do atendimento deve ser requisitado do backend const url = apiUrl + 'attendance/' + itemId + '/finish';
const url = apiUrl + 'finalizar-atendimento/' + itemId; // URL de exemplo para enviar a solicitação
const fmData = JSON.stringify({ const fmData = JSON.stringify({
"colabId": colabId, "observation": observation
"obsAtendimento": observation
}); });
@ -690,7 +727,8 @@ ipcMain.on('save-observation', async (event, { itemId, observation }) => {
url: url, url: url,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token 'Authorization': 'Bearer ' + token,
'x-tenant-id': tenantId
} }
}); });
@ -700,13 +738,10 @@ ipcMain.on('save-observation', async (event, { itemId, observation }) => {
}); });
response.on('end', () => { response.on('end', () => {
console.log('Solicitação concluída.'); console.log('Solicitação concluída.');
// Avisa a janela principal que a solicitação foi feita (opcional)
// mainWin.webContents.send('request-done', itemId);
}); });
}); });
request.on('error', (error) => { request.on('error', (error) => {
console.error(`Erro na solicitação: ${error}`); console.error(`Erro na solicitação: ${error}`);
// Poderia notificar a UI sobre o erro
}); });
// Envia o ID como corpo da requisição (exemplo) // Envia o ID como corpo da requisição (exemplo)
@ -716,28 +751,17 @@ ipcMain.on('save-observation', async (event, { itemId, observation }) => {
// Opcional: Fechar ou resetar a janela principal após salvar // Opcional: Fechar ou resetar a janela principal após salvar
if (mainWin) { if (mainWin) {
mainWin.hide(); // Ou mainWin.webContents.send('reset-view'); mainWin.hide();
} }
}); });
// Permite que a janela flutuante seja arrastada
// REMOVA ou comente este listener inteiro:
/*
ipcMain.on('drag-float-window', (event, { offsetX, offsetY }) => {
if (floatingWin) {
const { x, y } = screen.getCursorScreenPoint();
floatingWin.setPosition(x - offsetX, y - 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");
localStorage.removeItem("salaOperator"); localStorage.removeItem("salaOperator");
localStorage.removeItem("servicosOperator"); localStorage.removeItem("servicosOperator");
localStorage.removeItem("tenantId");
`).then(() => { `).then(() => {
// Fecha a janela main e abre a janela de operador e inicia o aplicativo // Fecha a janela main e abre a janela de operador e inicia o aplicativo
mainWin.hide(); mainWin.hide();
@ -750,8 +774,7 @@ ipcMain.on('logout', () => {
// Handler para login // Handler para login
ipcMain.on('login-attempt', async (event, credentials) => { ipcMain.on('login-attempt', async (event, credentials) => {
try { try {
// Substitua pela URL real da sua API de autenticação const route = apiUrl + 'auth/login';
const route = apiUrl + 'login';
const request = net.request({ const request = net.request({
method: 'POST', method: 'POST',
@ -766,19 +789,17 @@ ipcMain.on('login-attempt', async (event, credentials) => {
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
}); });
response.on('end', () => { response.on('end', () => {
try { try {
const data = JSON.parse(responseData); const data = JSON.parse(responseData);
if (data.access_token) {
if (data.status === 'Authorized' && data.api_key) {
// Login bem-sucedido // Login bem-sucedido
loginWin.webContents.executeJavaScript(` loginWin.webContents.executeJavaScript(`
localStorage.setItem("authToken", "${data.api_key}"); localStorage.setItem("authToken", "${data.access_token}");
localStorage.setItem("channel", "${data.channel}"); localStorage.setItem("tenantId", "${data.user.tenantId}");
`).then(() => { `).then(() => {
// Fecha a janela de login e abre a de seleção de operador // Fecha a janela de login e abre a de seleção de operador
event.reply('login-response', { success: true }); event.reply('login-response', { success: true });
@ -794,7 +815,6 @@ ipcMain.on('login-attempt', async (event, credentials) => {
}); });
} }
} catch (error) { } catch (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'
@ -811,7 +831,7 @@ ipcMain.on('login-attempt', async (event, credentials) => {
}); });
// Envia as credenciais // Envia as credenciais
request.write(JSON.stringify(credentials)); request.write(JSON.stringify({ login: credentials.login, password: credentials.password }));
request.end(); request.end();
} catch (error) { } catch (error) {
event.reply('login-response', { event.reply('login-response', {
@ -859,10 +879,7 @@ ipcMain.handle('get-operators', async () => {
}; };
} }
// Aqui você pode fazer uma chamada à API para obter os operadores const route = apiUrl + 'collaborators';
// Por enquanto, vamos retornar alguns operadores de exemplo
// Substitua pela URL real da sua API de autenticação
const route = apiUrl + 'colabs/list';
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const request = net.request({ const request = net.request({
@ -879,19 +896,18 @@ ipcMain.handle('get-operators', async () => {
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
}); });
response.on('end', () => { response.on('end', () => {
try { try {
const data = JSON.parse(responseData); const data = JSON.parse(responseData);
if (data.colabs) { if (Array.isArray(data)) {
const operators = data.colabs.map(colab => ({ const operators = data.map(colab => ({
id: colab.id, id: colab._id,
name: colab.nome.toUpperCase(), name: colab.name.toUpperCase(),
sala: colab.sala, sala: colab.roomId?.name || 'Sala',
servicos: colab.servicos servicos: colab.serviceIds?.map(s => s.name).join(',') || ''
})); }));
resolve({ resolve({
@ -901,12 +917,11 @@ ipcMain.handle('get-operators', async () => {
} else { } else {
reject({ reject({
success: false, success: false,
message: data.message || 'Erro ao obter a lista de colaboradores', message: 'Erro ao obter a lista de colaboradores',
operators: [] operators: []
}); });
} }
} catch (error) { } catch (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',

View File

@ -19,6 +19,14 @@
<option value="">Selecione um operador</option> <option value="">Selecione um operador</option>
<!-- Operadores serão carregados aqui --> <!-- Operadores serão carregados aqui -->
</select> </select>
&nbsp;
<p>
Iniciar automaticamente com o sistema
</p>
<label class="switch">
<input type="checkbox" id="active-switch" checked>
<span class="slider round"></span>
</label>
</div> </div>
<button id="select-button" disabled>Continuar</button> <button id="select-button" disabled>Continuar</button>
@ -29,11 +37,11 @@
<script> <script>
$(function(){ $(function(){
let opse = localStorage.getItem('selectedOperator'); let opse = localStorage.getItem('selectedOperator');
let vs = localStorage.getItem('version');
setTimeout(()=>{ setTimeout(()=>{
let vs = localStorage.getItem('version');
$('span.op').text(opse); $('span.op').text(opse);
$('#version').text(vs); $('#version').text(vs);
},2000); },1000);
}); });
</script> </script>
</body> </body>

View File

@ -3,9 +3,17 @@ const selectButton = document.getElementById('select-button');
const errorMessage = document.getElementById('error-message'); const errorMessage = document.getElementById('error-message');
const quitButton = document.getElementById('sair-button'); const quitButton = document.getElementById('sair-button');
const verionSpan = document.getElementById('version'); const verionSpan = document.getElementById('version');
const autoStartSwitch = document.getElementById('active-switch');
// Carrega a lista de operadores ao iniciar // Carrega a lista de operadores ao iniciar
window.addEventListener('DOMContentLoaded', async () => { window.addEventListener('DOMContentLoaded', async () => {
//... (código existente)
// Carrega o estado do autostart
const autostart = await window.electronAPI.getSetting('autostart');
autoStartSwitch.checked = autostart === undefined ? true : autostart;
try { try {
const response = await window.electronAPI.getOperators(); const response = await window.electronAPI.getOperators();
@ -88,7 +96,10 @@ window.electronAPI.onOperatorResponse((response) => {
// Se for bem-sucedido, o processo principal fechará esta janela // Se for bem-sucedido, o processo principal fechará esta janela
}); });
autoStartSwitch.addEventListener('change', () => {
const isEnabled = autoStartSwitch.checked;
window.electronAPI.setSetting('autostart', isEnabled);
});
quitButton.addEventListener('click',()=>{ quitButton.addEventListener('click',()=>{
window.electronAPI.quitApp(); window.electronAPI.quitApp();

View File

@ -6,4 +6,6 @@ contextBridge.exposeInMainWorld('electronAPI', {
showVersion: (version) => ipcRenderer.on('show-version', version), showVersion: (version) => ipcRenderer.on('show-version', version),
onOperatorResponse: (callback) => ipcRenderer.on('operator-response', (_event, response) => callback(response)), onOperatorResponse: (callback) => ipcRenderer.on('operator-response', (_event, response) => callback(response)),
quitApp : () => ipcRenderer.send('sair'), quitApp : () => ipcRenderer.send('sair'),
getSetting: (key) => ipcRenderer.invoke('get-setting', key),
setSetting: (key, value) => ipcRenderer.send('set-setting', key, value),
}); });

99
package-lock.json generated
View File

@ -1,18 +1,19 @@
{ {
"name": "autoatendcolab", "name": "autoatendcolab",
"version": "1.0.7", "version": "1.1.5",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "autoatendcolab", "name": "autoatendcolab",
"version": "1.0.7", "version": "1.1.5",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@electron/remote": "^2.1.2", "@electron/remote": "^2.1.2",
"electron-single-instance": "^0.0.2", "electron-single-instance": "^0.0.2",
"electron-updater": "^6.6.2", "electron-updater": "^6.6.2",
"jquery": "^3.7.1" "jquery": "^3.7.1",
"socket.io-client": "^4.8.3"
}, },
"devDependencies": { "devDependencies": {
"electron": "^35.2.1", "electron": "^35.2.1",
@ -952,6 +953,12 @@
"url": "https://github.com/sindresorhus/is?sponsor=1" "url": "https://github.com/sindresorhus/is?sponsor=1"
} }
}, },
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"license": "MIT"
},
"node_modules/@szmarczak/http-timer": { "node_modules/@szmarczak/http-timer": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
@ -2041,9 +2048,10 @@
} }
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.0", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": { "dependencies": {
"ms": "^2.1.3" "ms": "^2.1.3"
}, },
@ -2743,6 +2751,28 @@
"once": "^1.4.0" "once": "^1.4.0"
} }
}, },
"node_modules/engine.io-client": {
"version": "6.6.4",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz",
"integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.18.3",
"xmlhttprequest-ssl": "~2.1.1"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/env-paths": { "node_modules/env-paths": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
@ -5029,6 +5059,34 @@
"npm": ">= 3.0.0" "npm": ">= 3.0.0"
} }
}, },
"node_modules/socket.io-client": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz",
"integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1",
"engine.io-client": "~6.6.1",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz",
"integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socks": { "node_modules/socks": {
"version": "2.8.4", "version": "2.8.4",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz",
@ -5734,6 +5792,27 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
}, },
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xmlbuilder": { "node_modules/xmlbuilder": {
"version": "15.1.1", "version": "15.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
@ -5744,6 +5823,14 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/xmlhttprequest-ssl": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/y18n": { "node_modules/y18n": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

View File

@ -1,6 +1,6 @@
{ {
"name": "autoatendcolab", "name": "autoatendcolab",
"version": "1.1.3", "version": "1.1.5",
"main": "main.js", "main": "main.js",
"isBuildNow": true, "isBuildNow": true,
"scripts": { "scripts": {
@ -40,7 +40,8 @@
"@electron/remote": "^2.1.2", "@electron/remote": "^2.1.2",
"electron-single-instance": "^0.0.2", "electron-single-instance": "^0.0.2",
"electron-updater": "^6.6.2", "electron-updater": "^6.6.2",
"jquery": "^3.7.1" "jquery": "^3.7.1",
"socket.io-client": "^4.8.3"
}, },
"devDependencies": { "devDependencies": {
"electron": "^35.2.1", "electron": "^35.2.1",

View File

@ -25,61 +25,47 @@ window.electronAPI.onLoadData((data) => {
populateList(data[0]); populateList(data[0]);
}); });
async function initializePusher() { async function initializeSocket() {
if (typeof window.electronAPI === 'undefined' || !window.electronAPI.getPusherConfig) { if (typeof window.electronAPI === 'undefined' || !window.electronAPI.getPusherConfig) {
console.error('electronAPI not available or getPusherConfig method missing.'); console.error('electronAPI not available or getPusherConfig method missing.');
return; return;
} }
const pusherConfig = await window.electronAPI.getPusherConfig(); const config = await window.electronAPI.getPusherConfig();
const PUSHER_APP_KEY = pusherConfig.appKey; let host = config.host;
let host = pusherConfig.host;
const channelLocal = localStorage.getItem('channel'); const tenantId = localStorage.getItem('tenantId');
const colabId = localStorage.getItem('idOperator'); const token = localStorage.getItem('authToken');
//! checa se ja tem o colabId //! checa se ja tem o colabId e o tenantId
if (channelLocal && colabId && PUSHER_APP_KEY) { if (tenantId && token) {
Pusher.logToConsole = true; // Conexão Socket.io adaptada do Pusher
const socket = io(host, {
var pusher = new Pusher(PUSHER_APP_KEY, { auth: { token },
wsHost: host, extraHeaders: { 'x-tenant-id': tenantId }
wsPort: 80,
wssPort: 443,
forceTLS: true,
enableStats: false,
enabledTransports: ['wss', 'ws'],
cluster: 'mt1'
}); });
let channel; socket.on('connect', () => {
if (pusher.connection.state === 'connected') { console.log('Socket.io conectado ao host: ', host);
pusher.unsubscribe('chat.' + channelLocal + '_' + colabId); });
}
channel = pusher.subscribe('chat.' + channelLocal + '_' + colabId); // Evento que substitui o bind do Pusher
socket.on('queueUpdate', (data) => {
channel.bind('message-sent', function (r) { console.log('Atualização de fila recebida:', data);
let data = r.data.fila.original;
let count = data.length;
console.log(data);
localStorage.setItem('proximos', JSON.stringify(data)); localStorage.setItem('proximos', JSON.stringify(data));
populateList(data);
populateList(r.data.currentData.original);
}); });
pusher.connection.bind('error', function (err) { socket.on('error', (err) => {
console.error('Pusher connection error: ', err); console.error('Socket.io connection error: ', err);
}); });
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 tenantId not available. WebSocket not connected.');
} }
} }
initializePusher(); initializeSocket();
//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) => {
@ -93,12 +79,12 @@ window.electronAPI.selectAtendID((data) => {
showListView(); showListView();
// Garante que o item selecionado (ID e Nome) seja o que veio da chamada, sobrescrevendo o da lista. // 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 ?? ''; 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>` : '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.ticketNumber} ]</i>` : 'Ninguem aguardando atendimento';
}); });
window.electronAPI.showObservation(() => { window.electronAPI.showObservation(() => {
@ -107,7 +93,7 @@ window.electronAPI.showObservation(() => {
}); });
// Função para popular a lista de itens // Função para popular a lista de itens
function populateList(currentData) { function populateList(proximos) {
const atendimentoEmAndamentoId = localStorage.getItem('atendimentoAtual'); const atendimentoEmAndamentoId = localStorage.getItem('atendimentoAtual');
const atendimentoEmAndamentoNome = localStorage.getItem('atendimentoAtualNome'); const atendimentoEmAndamentoNome = localStorage.getItem('atendimentoAtualNome');
@ -118,25 +104,27 @@ function populateList(currentData) {
return; return;
} }
let datastorage = localStorage.getItem('proximos'); // Se não vier dados por parâmetro, tenta pegar do localStorage (fallback)
if (!Array.isArray(proximos)) {
let datastorage = localStorage.getItem('proximos');
proximos = JSON.parse(datastorage || '[]');
}
// Adiciona os outros itens apenas para visualização (opcional)
const proximos = JSON.parse(datastorage);
itemList.innerHTML = ''; itemList.innerHTML = '';
setTimeout(() => { setTimeout(() => {
nextButton.disabled = !currentData; nextButton.disabled = !proximos || proximos.length === 0;
}, 5000); }, 1000);
// 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
const itemToProcess = proximos[0]; // Pega o primeiro item const itemToProcess = proximos[0]; // Pega o primeiro item
if (itemToProcess) { if (itemToProcess) {
selectedItemId = itemToProcess.id; selectedItemId = itemToProcess._id;
selectedItemName = itemToProcess.clientName; selectedItemName = itemToProcess.clientName;
const li = document.createElement('li'); const li = document.createElement('li');
li.innerHTML = /*${itemToProcess.senhaGen}: */ `<u>${itemToProcess.clientName.toUpperCase()}</u> - ${itemToProcess.attendanceType.toUpperCase()} - ${itemToProcess.descricaoServico.toUpperCase()}`; li.innerHTML = /*${itemToProcess.ticketNumber}: */ `<u>${itemToProcess.clientName.toUpperCase()}</u> - ${itemToProcess.ticketNumber}`;
li.dataset.id = itemToProcess.id; // Armazena o ID no elemento li.dataset.id = itemToProcess._id; // Armazena o ID no elemento
li.classList.add('selected'); // Marca como selecionado visualmente (precisa de CSS) li.classList.add('selected'); // Marca como selecionado visualmente (precisa de CSS)
itemList.appendChild(li); itemList.appendChild(li);
} else { } else {
@ -149,8 +137,7 @@ function populateList(currentData) {
// Adiciona os outros itens apenas para visualização (opcional) // Adiciona os outros itens apenas para visualização (opcional)
proximos.slice(1).forEach(item => { proximos.slice(1).forEach(item => {
const li = document.createElement('li'); const li = document.createElement('li');
//${item.senhaGen}: li.textContent = `${item.clientName.toUpperCase()} - ${item.ticketNumber}`;
li.textContent = `${item.clientName.toUpperCase()} - ${item.attendanceType.toUpperCase()} - ${item.descricaoServico.toUpperCase()}`;
itemList.appendChild(li); itemList.appendChild(li);
}); });
} }
@ -223,5 +210,3 @@ saveButton.addEventListener('click', () => {
window.location.reload(); window.location.reload();
} }
}); });
// Inicialmente, mostra a view da lista (estará vazia até receber dados)

7
res/js/socket.io.min.js vendored Normal file

File diff suppressed because one or more lines are too long

3
settings.json Normal file
View File

@ -0,0 +1,3 @@
{
"autostart": false
}

View File

@ -301,3 +301,66 @@ button{
#save-button:hover{ #save-button:hover{
background-color: var(--warning-color); background-color: var(--warning-color);
} }
/* Estilo do Switch */
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
z-index: 1; /* Garante que o switch esteja acima de outros elementos */
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: var(--success-color);
}
input:focus + .slider {
box-shadow: 0 0 1px var(--success-color);
}
input:checked + .slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
/* width: 60px; */
}
.slider.round:before {
border-radius: 50%;
}