1150 lines
38 KiB
JavaScript
1150 lines
38 KiB
JavaScript
const { app, BrowserWindow, ipcMain, screen, net, dialog, Menu } = require('electron');
|
|
// const { app: singleInstanceLock } = require('electron-single-instance');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
const { autoUpdater, AppUpdater } = require('electron-updater');
|
|
const pjson = require(path.join(__dirname, '', 'package.json'));
|
|
|
|
let floatingWin;
|
|
let mainWin;
|
|
let loginWin;
|
|
let operatorWin;
|
|
let updateWin;
|
|
|
|
const dataPath = path.join(__dirname, 'data.json'); // Caminho para o JSON (backup local)
|
|
const settingsPath = path.join(app.getPath('userData'), 'settings.json'); // Caminho para as configurações
|
|
|
|
//! api
|
|
const apiUrl = 'https://aapi.linco.work/';
|
|
//! pusher
|
|
const pusherUrl = apiUrl;
|
|
|
|
autoUpdater.autoDownload = false;
|
|
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
|
|
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
|
|
async function readData() {
|
|
try {
|
|
// Verifica se existe token de autenticação
|
|
const token = await getAuthToken();
|
|
const colabId = await floatingWin.webContents.executeJavaScript("localStorage.getItem('idOperator')")
|
|
|
|
if (!token || !colabId) {
|
|
console.log("Usuário não autenticado, retornando lista vazia");
|
|
return [];
|
|
}
|
|
|
|
// Tenta buscar dados da API
|
|
return await fetchDataFromAPI();
|
|
|
|
} catch (error) {
|
|
console.error("Erro ao buscar dados da API:", error);
|
|
}
|
|
}
|
|
|
|
// Função para buscar dados da API
|
|
async function fetchDataFromAPI() {
|
|
//executa uma vez e a cada 30 segundos
|
|
|
|
//!primeira requisição é feita para API
|
|
getFirstData();
|
|
|
|
//! as outras é o websockt que solicita a chamada de requisições em busca de alterações
|
|
const updData = setInterval(() => {
|
|
getDataAndUpdateFloatingBtn();
|
|
}, 30000);
|
|
|
|
const updVersion = setTimeout(() => {
|
|
if (pjson.isBuildNow) {
|
|
autoUpdater.checkForUpdates();
|
|
}
|
|
}, 300000);
|
|
}
|
|
|
|
async function getFirstData() {
|
|
const token = await getAuthToken();
|
|
const colabId = await getSelectedOperatorId();
|
|
const tenantId = await getTenantId();
|
|
const url = apiUrl + 'attendance/next-in-line/' + colabId;
|
|
|
|
if (!token || !colabId) {
|
|
console.warn("Token or colabId not found. Skipping API request.");
|
|
return [];
|
|
}
|
|
|
|
return new Promise((resolve) => {
|
|
const request = net.request({
|
|
method: 'GET',
|
|
url: url,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Bearer ' + token,
|
|
'x-tenant-id': tenantId
|
|
}
|
|
});
|
|
|
|
request.on('response', (response) => {
|
|
let rawData = '';
|
|
response.on('data', (chunk) => { rawData += chunk; });
|
|
response.on('end', async () => {
|
|
try {
|
|
const parsedData = JSON.parse(rawData);
|
|
let proximos = Array.isArray(parsedData) ? parsedData : [];
|
|
|
|
if (response.statusCode === 200) {
|
|
const proximosStr = JSON.stringify(proximos);
|
|
|
|
// Sincroniza o localStorage de todas as janelas importantes
|
|
const updateStorageScript = `localStorage.setItem('proximos', ${JSON.stringify(proximosStr)})`;
|
|
|
|
if (floatingWin && !floatingWin.isDestroyed()) {
|
|
floatingWin.webContents.executeJavaScript(updateStorageScript);
|
|
floatingWin.webContents.send('update-count', proximos.length);
|
|
}
|
|
|
|
// SÓ atualiza a janela principal se NÃO houver atendimento em andamento
|
|
floatingWin.webContents.executeJavaScript("localStorage.getItem('atendimentoAtual')").then(atendimentoAtualId => {
|
|
if (!atendimentoAtualId && mainWin && !mainWin.isDestroyed()) {
|
|
mainWin.webContents.executeJavaScript(updateStorageScript);
|
|
mainWin.webContents.send('load-data', proximos);
|
|
}
|
|
resolve(proximos);
|
|
}).catch(err => {
|
|
console.error("Erro ao verificar atendimento atual:", err);
|
|
resolve(proximos);
|
|
});
|
|
} else {
|
|
console.error(`Erro API: ${response.statusCode}`);
|
|
resolve([]);
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro JSON:", error);
|
|
resolve([]);
|
|
}
|
|
});
|
|
});
|
|
|
|
request.on('error', (error) => {
|
|
console.error("Erro rede:", error);
|
|
resolve([]);
|
|
});
|
|
|
|
request.end();
|
|
});
|
|
}
|
|
|
|
// Atualiza a função de monitoramento para realmente buscar dados a cada 30s (ou o que desejar)
|
|
async function getDataAndUpdateFloatingBtn() {
|
|
await getFirstData();
|
|
}
|
|
|
|
// Função para verificar se o token existe no localStorage
|
|
async function getAuthToken() {
|
|
return new Promise((resolve) => {
|
|
if (loginWin && !loginWin.isDestroyed()) {
|
|
loginWin.webContents.executeJavaScript('localStorage.getItem("authToken");')
|
|
.then(token => resolve(token))
|
|
.catch(() => resolve(null));
|
|
} else if (mainWin && !mainWin.isDestroyed()) {
|
|
mainWin.webContents.executeJavaScript('localStorage.getItem("authToken");')
|
|
.then(token => resolve(token))
|
|
.catch(() => resolve(null));
|
|
} else if (floatingWin && !floatingWin.isDestroyed()) {
|
|
floatingWin.webContents.executeJavaScript('localStorage.getItem("authToken");')
|
|
.then(token => resolve(token))
|
|
.catch(() => resolve(null));
|
|
} else if (operatorWin && !operatorWin.isDestroyed()) {
|
|
operatorWin.webContents.executeJavaScript('localStorage.getItem("authToken");')
|
|
.then(token => resolve(token))
|
|
.catch(() => resolve(null));
|
|
} else {
|
|
resolve(null);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Função para verificar o tenantId no localStorage
|
|
async function getTenantId() {
|
|
const checkWindow = async (win) => {
|
|
if (win && !win.isDestroyed()) {
|
|
try {
|
|
const val = await win.webContents.executeJavaScript('localStorage.getItem("tenantId");');
|
|
return (val && val !== 'null' && val !== 'undefined') ? val : null;
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
// Prioridade para janelas visíveis
|
|
let id = await checkWindow(operatorWin);
|
|
if (!id) id = await checkWindow(mainWin);
|
|
if (!id) id = await checkWindow(floatingWin);
|
|
if (!id) id = await checkWindow(loginWin);
|
|
|
|
return id;
|
|
}
|
|
|
|
// Função para criar a janela de login
|
|
function createLoginWindow() {
|
|
loginWin = new BrowserWindow({
|
|
width: 500,
|
|
height: 500,
|
|
icon: "icon.ico",
|
|
frame: true,
|
|
autoHideMenuBar: true,
|
|
|
|
webPreferences: {
|
|
preload: path.join(__dirname, 'login_preload.js'),
|
|
contextIsolation: true,
|
|
nodeIntegration: true,
|
|
},
|
|
});
|
|
|
|
loginWin.loadFile('login.html');
|
|
|
|
// loginWin.webContents.openDevTools();
|
|
|
|
loginWin.on('closed', () => {
|
|
loginWin = null;
|
|
});
|
|
}
|
|
|
|
// Função para criar a janela de seleção de operador
|
|
function createOperatorWindow() {
|
|
operatorWin = new BrowserWindow({
|
|
width: 1200,//500
|
|
height: 600,
|
|
icon: "icon.ico",
|
|
frame: true,
|
|
autoHideMenuBar: true,
|
|
webPreferences: {
|
|
preload: path.join(__dirname, 'operator_preload.js'),
|
|
contextIsolation: true,
|
|
nodeIntegration: true,
|
|
},
|
|
});
|
|
|
|
// operatorWin.webContents.openDevTools();
|
|
operatorWin.webContents.executeJavaScript('localStorage.setItem("version","' + app.getVersion() + '")');
|
|
|
|
operatorWin.loadFile('operator.html');
|
|
|
|
operatorWin.on('closed', () => {
|
|
operatorWin = null;
|
|
});
|
|
}
|
|
|
|
function createUpdateWindow() {
|
|
updateWin = new BrowserWindow({
|
|
width: 600,
|
|
height: 300,
|
|
icon: "icon.ico",
|
|
show: false, // Inicia oculta
|
|
frame: true,
|
|
autoHideMenuBar: true, // Oculta a barra de menus
|
|
menuBarVisible: false, // Garante que a barra de menus começa
|
|
webPreferences: {
|
|
preload: path.join(__dirname, 'preload.js'),
|
|
contextIsolation: true,
|
|
nodeIntegration: true,
|
|
},
|
|
});
|
|
|
|
updateWin.loadFile('update.html');
|
|
|
|
updateWin.on('closed', () => {
|
|
updateWin = null;
|
|
});
|
|
}
|
|
|
|
|
|
function createFloatingWindow() {
|
|
const primaryDisplay = screen.getPrimaryDisplay();
|
|
const { width: screenWidth, height: screenHeight } = primaryDisplay.workAreaSize;
|
|
const winWidth = 60;
|
|
const winHeight = 70;
|
|
|
|
floatingWin = new BrowserWindow({
|
|
width: winWidth,
|
|
height: winHeight,
|
|
icon: "icon.ico",
|
|
x: screenWidth - winWidth + 0,
|
|
y: screenHeight - winHeight - 180,
|
|
frame: false,
|
|
transparent: true,
|
|
alwaysOnTop: true,
|
|
skipTaskbar: true,
|
|
resizable: false,
|
|
hasShadow: false,
|
|
webPreferences: {
|
|
preload: path.join(__dirname, 'floating_preload.js'),
|
|
contextIsolation: true,
|
|
nodeIntegration: true
|
|
},
|
|
backgroundColor: '#00000000',
|
|
titleBarStyle: 'hidden',
|
|
roundedCorners: false
|
|
});
|
|
|
|
|
|
floatingWin.loadFile('floating.html');
|
|
|
|
// Envia a contagem inicial para a janela flutuante
|
|
const data = readData();
|
|
floatingWin.webContents.on('did-finish-load', () => {
|
|
floatingWin.webContents.send('update-count', data.length);
|
|
});
|
|
|
|
floatingWin.on('closed', () => {
|
|
floatingWin = null;
|
|
});
|
|
}
|
|
|
|
function createMainWindow() {
|
|
mainWin = new BrowserWindow({
|
|
width: 1024,
|
|
height: 600,
|
|
icon: "icon.ico",
|
|
show: false, // Inicia oculta
|
|
frame: true, // Sem bordas, título, etc.
|
|
autoHideMenuBar: true, // Oculta a barra de menus
|
|
menuBarVisible: false, // Garante que a barra de menus começa
|
|
webPreferences: {
|
|
preload: path.join(__dirname, 'preload.js'),
|
|
contextIsolation: true,
|
|
nodeIntegration: true,
|
|
},
|
|
});
|
|
|
|
mainWin.loadFile('index.html');
|
|
|
|
// if(!pjson.isBuildNow) {
|
|
// mainWin.webContents.openDevTools(); // Descomente para depurar
|
|
// }
|
|
|
|
mainWin.webContents.send('current_version', pjson.version);
|
|
|
|
mainWin.on('close', (event) => {
|
|
// Em vez de fechar, apenas oculta a janela principal
|
|
if (!app.isQuitting) {
|
|
event.preventDefault();
|
|
mainWin.hide();
|
|
}
|
|
});
|
|
|
|
mainWin.on('closed', () => {
|
|
mainWin = null;
|
|
});
|
|
}
|
|
|
|
|
|
if (pjson.isBuildNow) {
|
|
autoUpdater.on('update-available', () => {
|
|
let pth = autoUpdater.downloadUpdate();
|
|
updateWin.show(); updateWin.focus();
|
|
updateWin.webContents.send('update_message', `Uma nova versão está dispinível.`);
|
|
updateWin.webContents.send('update_percent', pth);
|
|
})
|
|
autoUpdater.on('download-progress', (obj) => {
|
|
updateWin.webContents.send('update_message', `Estamos baixando uma nova atualização.`);
|
|
});
|
|
autoUpdater.on('update-downloaded', (obj) => {
|
|
updateWin.webContents.send('update_message', `Download concluído. Aguarde, vamos reiniciar para instalar!`);
|
|
setTimeout(() => {
|
|
autoUpdater.quitAndInstall();
|
|
}, 5000);
|
|
});
|
|
autoUpdater.on('error', err => {
|
|
updateWin.webContents.send('update_message', err);
|
|
});
|
|
}
|
|
|
|
|
|
if (!gotTheLock) {
|
|
app.quit();
|
|
} else {
|
|
app.on('second-instance', () => {
|
|
if (mainWin) {
|
|
if (mainWin.isMinimized()) {
|
|
mainWin.restore();
|
|
}
|
|
mainWin.focus();
|
|
}
|
|
});
|
|
|
|
// Inicialização do aplicativo modificada para verificar autenticação
|
|
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
|
|
const token = await getAuthToken();
|
|
|
|
if (!token) {
|
|
// Se não estiver autenticado, mostra a tela de login
|
|
createLoginWindow();
|
|
} else {
|
|
// Se já estiver autenticado, verifica se tem operador selecionado
|
|
// const operator = await getSelectedOperator();
|
|
|
|
if (!operator || operator === 'null' || operator === null || operator === undefined || operator === '') {
|
|
// Se não tiver operador selecionado, mostra a tela de seleção
|
|
createOperatorWindow();
|
|
} else {
|
|
// Se já tiver operador, inicia normalmente
|
|
createFloatingWindow();
|
|
createMainWindow();
|
|
}
|
|
}
|
|
createUpdateWindow();
|
|
|
|
app.on('activate', () => {
|
|
// No macOS é comum recriar uma janela no aplicativo quando o
|
|
// ícone do dock é clicado e não há outras janelas abertas.
|
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
// Poderia recriar a janela principal aqui se necessário,
|
|
// mas como temos a flutuante, talvez não precise.
|
|
if (!floatingWin) createFloatingWindow();
|
|
if (!mainWin) createMainWindow();
|
|
if (!updateWin) createUpdateWindow();
|
|
}
|
|
});
|
|
|
|
if (pjson.isBuildNow) {
|
|
autoUpdater.checkForUpdates();
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Função para verificar se já existe um operador selecionado
|
|
async function getSelectedOperator() {
|
|
return new Promise((resolve) => {
|
|
if (loginWin && !loginWin.isDestroyed()) {
|
|
loginWin.webContents.executeJavaScript('localStorage.getItem("selectedOperator");')
|
|
.then(operator => resolve(operator))
|
|
.catch(() => resolve(null));
|
|
} else if (operatorWin && !operatorWin.isDestroyed()) {
|
|
operatorWin.webContents.executeJavaScript('localStorage.getItem("selectedOperator");')
|
|
.then(operator => resolve(operator))
|
|
.catch(() => resolve(null));
|
|
} else if (mainWin && !mainWin.isDestroyed()) {
|
|
mainWin.webContents.executeJavaScript('localStorage.getItem("authToken");')
|
|
.then(operator => resolve(operator))
|
|
.catch(() => resolve(null));
|
|
} else if (floatingWin && !floatingWin.isDestroyed()) {
|
|
floatingWin.webContents.executeJavaScript('localStorage.getItem("authToken");')
|
|
.then(operator => resolve(operator))
|
|
.catch(() => resolve(null));
|
|
} else {
|
|
resolve(null);
|
|
}
|
|
});
|
|
}
|
|
|
|
async function getSelectedOperatorId() {
|
|
return new Promise((resolve, reject) => {
|
|
if (mainWin && !mainWin.isDestroyed()) {
|
|
mainWin.webContents.executeJavaScript('localStorage.getItem("idOperator");')
|
|
.then(operatorId => resolve(operatorId))
|
|
.catch(() => resolve(null));
|
|
} else if (floatingWin && !floatingWin.isDestroyed()) {
|
|
floatingWin.webContents.executeJavaScript('localStorage.getItem("idOperator");')
|
|
.then(operatorId => resolve(operatorId))
|
|
.catch(() => resolve(null));
|
|
} else {
|
|
reject(new Error('Nenhum componente aberto'));
|
|
}
|
|
});
|
|
}
|
|
|
|
ipcMain.handle('get-pusher-config', async () => {
|
|
// Garante que o host não termine com barra para o Socket.io
|
|
let host = pusherUrl;
|
|
if (host && host.endsWith('/')) {
|
|
host = host.slice(0, -1);
|
|
}
|
|
return { host: host };
|
|
});
|
|
|
|
ipcMain.on('update_version', async (event, arg) => {
|
|
if (updateWin) {
|
|
if (!updateWin.isVisible()) {
|
|
updateWin.show();
|
|
updateWin.focus();
|
|
} else {
|
|
updateWin.focus();
|
|
}
|
|
} else {
|
|
createUpdateWindow();
|
|
updateWin.webContents.on('did-finish-load', () => {
|
|
updateWin.show();
|
|
updateWin.focus();
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
// Ouvir pedido para obter contagem (ex: se o JSON for atualizado)
|
|
ipcMain.handle('get-count', async () => {
|
|
const data = readData();
|
|
return data.length;
|
|
});
|
|
|
|
let isCallingNext = false;
|
|
|
|
// Ouvir pedido para mostrar a janela principal
|
|
ipcMain.on('chamar-fila', async () => {
|
|
// Evita múltiplas chamadas simultâneas
|
|
if (isCallingNext) return;
|
|
isCallingNext = true;
|
|
|
|
// Primeiro, verifica se já existe um atendimento em andamento
|
|
const atendimentoAtualId = await floatingWin.webContents.executeJavaScript("localStorage.getItem('atendimentoAtual')");
|
|
|
|
const showMainWindow = () => {
|
|
if (mainWin) {
|
|
if (!mainWin.isVisible()) {
|
|
mainWin.show();
|
|
mainWin.focus();
|
|
} else {
|
|
mainWin.focus();
|
|
}
|
|
} else {
|
|
createMainWindow(); // Cria se não existir
|
|
mainWin.webContents.on('did-finish-load', () => {
|
|
mainWin.show();
|
|
mainWin.focus();
|
|
});
|
|
}
|
|
}
|
|
|
|
// Se um atendimento já estiver em andamento, apenas mostra a janela principal.
|
|
if (atendimentoAtualId) {
|
|
showMainWindow();
|
|
isCallingNext = false;
|
|
return;
|
|
}
|
|
|
|
const requestData = async () => {
|
|
const colabId = await getSelectedOperatorId();
|
|
const token = await getAuthToken();
|
|
const tenantId = await getTenantId();
|
|
const url = apiUrl + 'attendance/call-next/' + colabId;
|
|
|
|
const request = net.request({
|
|
method: 'POST',
|
|
url: url,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Bearer ' + token,
|
|
'x-tenant-id': tenantId
|
|
}
|
|
});
|
|
|
|
request.on('response', (response) => {
|
|
let rawData = '';
|
|
response.on('data', (chunk) => { rawData += chunk; });
|
|
response.on('end', () => {
|
|
isCallingNext = false;
|
|
try {
|
|
const parsedData = JSON.parse(rawData);
|
|
if (response.statusCode === 201 || response.statusCode === 200) {
|
|
if (parsedData) {
|
|
mainWin.webContents.send('select-atend-id', parsedData);
|
|
if (parsedData.status === 'Fila' || parsedData.status === 'Chamado') {
|
|
showMainWindow();
|
|
} else if (parsedData.status === 'Atendendo') {
|
|
let options2 = {
|
|
'title': 'Precisa finalizar antes de chamar o próximo.',
|
|
'message': 'Em andamento',
|
|
'detail': 'Já possui um atendimento em andamento (Atendendo: ' + parsedData.clientName + '), continue e finalize por favor!',
|
|
'type': 'error',
|
|
'buttons': ['Depois', 'Continuar'],
|
|
};
|
|
dialog.showMessageBox(floatingWin, options2).then(result => {
|
|
if (result.response) {
|
|
mainWin.webContents.send('show-observation');
|
|
showMainWindow();
|
|
} else {
|
|
mainWin.hide();
|
|
};
|
|
});
|
|
}
|
|
} else {
|
|
mainWin.webContents.send('select-atend-id', null);
|
|
}
|
|
} else {
|
|
console.error(`Erro na requisição: Status code ${response.statusCode}`, parsedData);
|
|
mainWin.webContents.send('api-error', {
|
|
message: `Erro ao chamar atendimento: ${parsedData.message || 'Erro desconhecido'}`
|
|
});
|
|
}
|
|
} 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) => {
|
|
isCallingNext = false;
|
|
console.error("Erro na requisição:", error);
|
|
mainWin.webContents.send('api-error', {
|
|
message: `Erro ao chamar atendimento: ${error.message}`
|
|
});
|
|
});
|
|
|
|
request.end();
|
|
};
|
|
|
|
const countFilaValue = async () => {
|
|
const stored = await floatingWin.webContents.executeJavaScript("localStorage.getItem('proximos')");
|
|
const proximos = JSON.parse(stored || '[]');
|
|
return proximos.length;
|
|
}
|
|
|
|
let options = {
|
|
'title': 'Incie o atendimento quando o cliente chegar na sala.',
|
|
'message': 'Chamar um cliente da fila?',
|
|
'type': 'warning',
|
|
'noLink': true,
|
|
'buttons': ['Não', 'Sim'],
|
|
};
|
|
|
|
const count = await countFilaValue();
|
|
if (count > 0) {
|
|
dialog.showMessageBox(floatingWin, options).then(result => {
|
|
if (result.response === 1) { // 1 é 'Sim'
|
|
requestData();
|
|
} else {
|
|
isCallingNext = false;
|
|
}
|
|
});
|
|
} else {
|
|
// Se a fila estiver vazia, apenas abre a janela principal para visualização
|
|
showMainWindow();
|
|
isCallingNext = false;
|
|
}
|
|
});
|
|
|
|
// Ouve um pedido da janela flutuante para forçar a atualização da contagem
|
|
ipcMain.on('refresh-count', async () => {
|
|
if (floatingWin) {
|
|
const stored = await floatingWin.webContents.executeJavaScript("localStorage.getItem('proximos')");
|
|
const proximos = JSON.parse(stored || '[]');
|
|
floatingWin.webContents.send('update-count', proximos.length);
|
|
}
|
|
});
|
|
|
|
ipcMain.on('select-atend-id', (itemId) => {
|
|
selectedItemId = itemId;
|
|
console.log(selectedItemId);
|
|
});
|
|
|
|
// Ouvir clique no botão "Iniciar atendimento"
|
|
ipcMain.on('iniciar-atendimento', async (event, itemId) => {
|
|
|
|
const token = await getAuthToken();
|
|
const tenantId = await getTenantId();
|
|
const url = apiUrl + 'attendance/' + itemId + '/start';
|
|
|
|
// envio de uma solicitação POST com o ID do item
|
|
const request = net.request({
|
|
method: 'PATCH',
|
|
url: url,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Bearer ' + token,
|
|
'x-tenant-id': tenantId
|
|
}
|
|
});
|
|
|
|
request.on('response', (response) => {
|
|
response.on('data', (chunk) => {
|
|
console.log(`BODY: ${chunk}`);
|
|
});
|
|
response.on('end', () => {
|
|
console.log('Solicitação concluída.');
|
|
});
|
|
});
|
|
request.on('error', (error) => {
|
|
console.error(`Erro na solicitação: ${error}`);
|
|
});
|
|
|
|
request.end();
|
|
});
|
|
|
|
// Ouvir clique no botão "Rechamar" - chama novamente o mesmo atendimento
|
|
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;
|
|
|
|
const request = net.request({
|
|
method: 'POST',
|
|
url: url,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Bearer ' + token,
|
|
'x-tenant-id': tenantId
|
|
}
|
|
});
|
|
|
|
request.on('response', (response) => {
|
|
response.on('data', (chunk) => {
|
|
console.log(`Rechamar BODY: ${chunk}`);
|
|
});
|
|
response.on('end', () => {
|
|
console.log('Rechamada concluída.');
|
|
});
|
|
});
|
|
request.on('error', (error) => {
|
|
console.error(`Erro na rechamada: ${error}`);
|
|
});
|
|
|
|
request.end();
|
|
});
|
|
|
|
// 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');
|
|
}
|
|
});
|
|
|
|
// Ouve atualização da fila vinda do renderer (via socket)
|
|
ipcMain.on('update-queue', (event, data) => {
|
|
// Sempre busca dados frescos da API quando o socket notifica mudança,
|
|
// pois o socket pode enviar apenas o objeto da mudança e não a lista completa.
|
|
getFirstData();
|
|
});
|
|
|
|
// Ouvir clique no botão "Salvar"
|
|
ipcMain.on('save-observation', async (event, { itemId, observation }) => {
|
|
|
|
console.log(`Salvando observação para item ${itemId}: ${observation}`);
|
|
|
|
const token = await getAuthToken();
|
|
const tenantId = await getTenantId();
|
|
const url = apiUrl + 'attendance/' + itemId + '/finish';
|
|
|
|
const fmData = JSON.stringify({
|
|
"observation": observation
|
|
});
|
|
|
|
|
|
// Simula o envio de uma solicitação POST com o ID do item
|
|
const request = net.request({
|
|
method: 'POST',
|
|
url: url,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Bearer ' + token,
|
|
'x-tenant-id': tenantId
|
|
}
|
|
});
|
|
|
|
request.on('response', (response) => {
|
|
response.on('data', (chunk) => {
|
|
console.log(`BODY: ${chunk}`);
|
|
});
|
|
response.on('end', () => {
|
|
console.log('Solicitação concluída.');
|
|
});
|
|
});
|
|
request.on('error', (error) => {
|
|
console.error(`Erro na solicitação: ${error}`);
|
|
});
|
|
|
|
// Envia o ID como corpo da requisição (exemplo)
|
|
request.write(fmData);
|
|
request.end();
|
|
|
|
|
|
// Opcional: Fechar ou resetar a janela principal após salvar
|
|
if (mainWin) {
|
|
mainWin.hide();
|
|
}
|
|
});
|
|
|
|
ipcMain.on('logout', () => {
|
|
mainWin.webContents.executeJavaScript(`
|
|
localStorage.removeItem("idOperator");
|
|
localStorage.removeItem("selectedOperator");
|
|
localStorage.removeItem("salaOperator");
|
|
localStorage.removeItem("servicosOperator");
|
|
`).then(() => {
|
|
// Fecha a janela main e abre a janela de operador e inicia o aplicativo
|
|
mainWin.hide();
|
|
floatingWin.hide();
|
|
createOperatorWindow();
|
|
app.isReady();
|
|
});
|
|
});
|
|
|
|
// Handler para login
|
|
ipcMain.on('login-attempt', async (event, credentials) => {
|
|
try {
|
|
const route = apiUrl + 'auth/login';
|
|
|
|
const request = net.request({
|
|
method: 'POST',
|
|
url: route,
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
let responseData = '';
|
|
|
|
request.on('response', (response) => {
|
|
response.on('data', (chunk) => {
|
|
responseData += chunk.toString();
|
|
});
|
|
|
|
response.on('end', () => {
|
|
try {
|
|
const data = JSON.parse(responseData);
|
|
|
|
if (data.access_token) {
|
|
// Tenta encontrar o tenantId em diferentes locais possíveis da resposta
|
|
const tenantId = data.tenantId || (data.user && data.user.tenantId) || (data.user && data.user.tenant_id);
|
|
|
|
console.log(`Login bem-sucedido. Token presente. TenantId encontrado: ${tenantId}`);
|
|
|
|
// Login bem-sucedido
|
|
loginWin.webContents.executeJavaScript(`
|
|
localStorage.setItem("authToken", "${data.access_token}");
|
|
localStorage.setItem("tenantId", "${tenantId || ''}");
|
|
`).then(() => {
|
|
// Fecha a janela de login e abre a de seleção de operador
|
|
event.reply('login-response', { success: true });
|
|
loginWin.close();
|
|
createOperatorWindow();
|
|
});
|
|
|
|
} else {
|
|
// Login falhou
|
|
event.reply('login-response', {
|
|
success: false,
|
|
message: data.message || 'Falha na autenticação'
|
|
});
|
|
}
|
|
} catch (error) {
|
|
event.reply('login-response', {
|
|
success: false,
|
|
message: 'Erro ao processar resposta do servidor'
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
request.on('error', (error) => {
|
|
event.reply('login-response', {
|
|
success: false,
|
|
message: `Erro de conexão: ${error.message}`
|
|
});
|
|
});
|
|
|
|
// Remove máscara do login (CPF/CNPJ) e da senha antes de enviar para a API
|
|
const cleanLogin = credentials.login.replace(/[.\-\/]/g, '');
|
|
const cleanPassword = credentials.password.replace(/[.\-\/]/g, '');
|
|
|
|
// Envia as credenciais
|
|
request.write(JSON.stringify({ login: cleanLogin, password: cleanPassword }));
|
|
request.end();
|
|
} catch (error) {
|
|
event.reply('login-response', {
|
|
success: false,
|
|
message: `Erro: ${error.message}`
|
|
});
|
|
}
|
|
});
|
|
|
|
// Handler para seleção de operador
|
|
ipcMain.on('select-operator', async (event, operator) => {
|
|
try {
|
|
// Salva o operador selecionado
|
|
operatorWin.webContents.executeJavaScript(`
|
|
localStorage.setItem("idOperator", "${operator.id}");
|
|
localStorage.setItem("selectedOperator", "${operator.name}");
|
|
localStorage.setItem("salaOperator", "${operator.sala}");
|
|
localStorage.setItem("servicosOperator", "${operator.servicos}");
|
|
`).then(() => {
|
|
// Fecha a janela de operador e inicia o aplicativo
|
|
operatorWin.close();
|
|
createFloatingWindow();
|
|
createMainWindow();
|
|
createUpdateWindow();
|
|
});
|
|
} catch (error) {
|
|
event.reply('operator-response', {
|
|
success: false,
|
|
message: `Erro: ${error.message}`
|
|
});
|
|
}
|
|
});
|
|
|
|
// Handler para buscar lista de operadores
|
|
ipcMain.handle('get-operators', async () => {
|
|
try {
|
|
// Verifica se existe token de autenticação
|
|
const token = await getAuthToken();
|
|
const tenantId = await getTenantId();
|
|
|
|
console.log(`Buscando operadores - TenantId: ${tenantId}, Token presente: ${!!token}`);
|
|
|
|
if (!token) {
|
|
return {
|
|
success: false,
|
|
message: 'Não autenticado',
|
|
operators: []
|
|
};
|
|
}
|
|
|
|
const route = apiUrl + 'collaborators';
|
|
|
|
return new Promise((resolve) => {
|
|
const headers = {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${token}`
|
|
};
|
|
|
|
// Garante que o tenantId é uma string válida e não "null" ou "undefined"
|
|
if (tenantId && tenantId !== 'null' && tenantId !== 'undefined') {
|
|
headers['x-tenant-id'] = tenantId;
|
|
}
|
|
|
|
const request = net.request({
|
|
method: 'GET',
|
|
url: route,
|
|
headers: headers
|
|
});
|
|
|
|
let responseData = '';
|
|
|
|
request.on('response', (response) => {
|
|
let body = '';
|
|
response.on('data', (chunk) => {
|
|
body += chunk.toString();
|
|
});
|
|
|
|
response.on('end', () => {
|
|
if (response.statusCode !== 200) {
|
|
console.error(`Erro na API (${response.statusCode}):`, body);
|
|
resolve({
|
|
success: false,
|
|
message: `Erro ${response.statusCode}: ${body}`,
|
|
operators: []
|
|
});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const data = JSON.parse(body);
|
|
|
|
if (Array.isArray(data)) {
|
|
const operators = data.map(colab => ({
|
|
id: colab._id,
|
|
name: colab.name.toUpperCase(),
|
|
sala: colab.roomId?.name || 'Sala',
|
|
servicos: colab.serviceIds?.map(s => s.name).join(',') || ''
|
|
}));
|
|
|
|
resolve({
|
|
success: true,
|
|
operators: operators
|
|
});
|
|
} else {
|
|
resolve({
|
|
success: false,
|
|
message: 'Resposta da API não é uma lista válida',
|
|
operators: []
|
|
});
|
|
}
|
|
} catch (error) {
|
|
resolve({
|
|
success: false,
|
|
message: 'Erro ao processar resposta do servidor',
|
|
operators: []
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
request.on('error', (error) => {
|
|
resolve({
|
|
success: false,
|
|
message: `Erro de conexão: ${error.message}`,
|
|
operators: []
|
|
});
|
|
});
|
|
|
|
request.end();
|
|
});
|
|
} catch (error) {
|
|
console.error('Erro ao obter operadores:', error);
|
|
return {
|
|
success: false,
|
|
message: `Erro: ${error.message}`,
|
|
operators: []
|
|
};
|
|
}
|
|
});
|
|
|
|
// Handler para verificar se o token já existe
|
|
ipcMain.on('token-exists', async () => {
|
|
// Checa se o operador já foi selecionado
|
|
const operator = await getSelectedOperator();
|
|
|
|
// Fecha a janela de login, independentemente do operador já ter sido selecionado
|
|
if (loginWin && !loginWin.isDestroyed()) {
|
|
loginWin.close();
|
|
loginWin = null; // Garante que a referência seja limpa
|
|
}
|
|
|
|
if (!operator || operator === 'null' || operator === null || operator === undefined || operator === '') {
|
|
// Se não tiver operador selecionado, mostra a tela de seleção
|
|
createOperatorWindow();
|
|
} else {
|
|
// Se já tiver operador, inicia normalmente
|
|
createFloatingWindow();
|
|
createMainWindow();
|
|
}
|
|
//TODO sempre que já existir um token ele checa por novas atualizações
|
|
createUpdateWindow();
|
|
});
|
|
|
|
|
|
ipcMain.on('sair', () => {
|
|
let exec = `
|
|
localStorage.removeItem("idOperator");
|
|
localStorage.removeItem("selectedOperator");
|
|
localStorage.removeItem("salaOperator");
|
|
localStorage.removeItem("servicosOperator");
|
|
`;
|
|
|
|
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 });
|
|
});
|