Merge branch 'new_features' into _dialogflow_omnihit_hit_new_features

adriano 2023-08-21 14:39:11 -03:00
commit 763d707ca5
57 changed files with 5522 additions and 3359 deletions

5
.gitignore vendored
View File

@ -23,6 +23,11 @@ WWebJS
*/WWebJS/* */WWebJS/*
**WWebJS **WWebJS
.wwebjs_auth
*/wwebjs_auth/*
.wwebjs_cache
*/wwebjs_cache/*
# testing # testing
/coverage /coverage

View File

View File

@ -1,3 +1,8 @@
PORT=8019 PORT=8019
PORT_START=8020 PORT_START=8020
BASE_URL=http://localhost BASE_URL=http://localhost
PASS="strongpassword, strongpassword32"
DB_MONGO_URL=mongodb://localhost:27017
DB_MONGO_NAME=session_out_omnihit_db
DB_MONGO_NAME_CAMPAIGN=broker_omnihit

View File

@ -1,30 +1,33 @@
const express = require('express'); const express = require('express')
const bodyparser = require('body-parser'); const bodyparser = require('body-parser')
const dotenv = require('dotenv'); const dotenv = require('dotenv')
dotenv.config({ path: '.env' }); dotenv.config({ path: '.env' })
const copyFolder = require('./helpers/copyFolder') const copyFolder = require('./helpers/copyFolder')
const createDir = require('./helpers/createDir') const createDir = require('./helpers/createDir')
const createFile = require('./helpers/createFile') const createFile = require('./helpers/createFile')
const path = require('path'); const path = require('path')
const db_info = require('./db_conn') const db_info = require('./db_conn')
const fs = require('fs'); const fs = require('fs')
let mysql_conn = require('./helpers/mysql_conn.js'); let mysql_conn = require('./helpers/mysql_conn.js')
const { exec, execSync, spawn } = require("child_process"); const { exec, execSync, spawn } = require('child_process')
const startPm2Process = require('./helpers/startPm2Process'); const startPm2Process = require('./helpers/startPm2Process')
const removeDir = require('./helpers/remove_dir'); const removeDir = require('./helpers/remove_dir')
const setSessionName = require('./helpers/setSessionNumber') const setSessionName = require('./helpers/setSessionNumber')
const getNumberFromName = require('./helpers/getNumberSequence') const getNumberFromName = require('./helpers/getNumberSequence')
const pm2 = require('pm2'); const pm2 = require('pm2')
const bcrypt = require('bcrypt')
const OmnihitDBConn = require('./model/db_conn')
const app = express(); const app = express()
app.use(bodyparser.json()); app.use(bodyparser.json())
app.get('/', function (req, res) { return res.send('Express + TypeScript Server'); }); app.get('/', function (req, res) {
return res.send('Express + TypeScript Server')
})
app.post('/api/session', async function (req, res) { app.post('/api/session', async function (req, res) {
let { app_name, whatsappId, client_url, number } = req.body let { app_name, whatsappId, client_url, number } = req.body
if (app_name) { if (app_name) {
@ -33,13 +36,21 @@ app.post('/api/session', async function (req, res) {
console.log('__dirname: ', path.join(__dirname, '..', app_name)) console.log('__dirname: ', path.join(__dirname, '..', app_name))
console.log('app_name: ', app_name, ' | whatsappId: ', whatsappId, ' | client_url: ',client_url) console.log(
'app_name: ',
app_name,
' | whatsappId: ',
whatsappId,
' | client_url: ',
client_url
)
const sessionsPath = path.join(__dirname, '..', 'sessions') const sessionsPath = path.join(__dirname, '..', 'sessions')
const directoriesInDIrectory = fs.readdirSync(sessionsPath, { withFileTypes: true }) const directoriesInDIrectory = fs
.readdirSync(sessionsPath, { withFileTypes: true })
.filter((item) => item.isDirectory()) .filter((item) => item.isDirectory())
.map((item) => item.name); .map((item) => item.name)
console.log('directoriesInDIrectory: ', directoriesInDIrectory) console.log('directoriesInDIrectory: ', directoriesInDIrectory)
@ -48,146 +59,154 @@ app.post('/api/session', async function (req, res) {
let dirSessionsApp = path.join(sessionsPath, app_name) let dirSessionsApp = path.join(sessionsPath, app_name)
if (dirExist.length == 0) { if (dirExist.length == 0) {
let create = createDir(dirSessionsApp) let create = createDir(dirSessionsApp)
if (!create) { if (!create) {
res.status(500).json({ message: 'Cannot create the directory path!' }) res.status(500).json({ message: 'Cannot create the directory path!' })
return return
} }
} }
let appPort = [] let appPort = []
let existSubDir = false let existSubDir = false
for (let i = 0; i < directoriesInDIrectory.length; i++) { for (let i = 0; i < directoriesInDIrectory.length; i++) {
console.log('directoriesInDIrectory[i]', directoriesInDIrectory[i]) console.log('directoriesInDIrectory[i]', directoriesInDIrectory[i])
const subDir = fs.readdirSync(path.join(sessionsPath, directoriesInDIrectory[i]), { withFileTypes: true }) const subDir = fs
.readdirSync(path.join(sessionsPath, directoriesInDIrectory[i]), {
withFileTypes: true,
})
.filter((item) => item.isDirectory()) .filter((item) => item.isDirectory())
.map((item) => item.name); .map((item) => item.name)
for (let x = 0; x < subDir.length; x++) { for (let x = 0; x < subDir.length; x++) {
console.log('subdir: ', subDir[x]) console.log('subdir: ', subDir[x])
let whatsId = subDir[x].split('_')[0] let whatsId = subDir[x].split('_')[0]
if (whatsId == whatsappId && app_name == directoriesInDIrectory[i]) { if (whatsId == whatsappId && app_name == directoriesInDIrectory[i]) {
let currPath = path.join(
sessionsPath,
directoriesInDIrectory[i],
subDir[x]
)
let currPath = path.join(sessionsPath, directoriesInDIrectory[i], subDir[x]) console.log(
'PATH: ',
console.log('PATH: ', path.join(sessionsPath, directoriesInDIrectory[i], subDir[x])) path.join(sessionsPath, directoriesInDIrectory[i], subDir[x])
)
let oldNumber = subDir[x].split('_')[1] let oldNumber = subDir[x].split('_')[1]
// let sessionNum = subDir[x].split('_')[2]
// let sessionPort = subDir[x].split('_')[3]
// let newSessionAppName = `${whatsId}_${number}_${sessionNum}_${sessionPort}`
// let newPath = path.join(sessionsPath, directoriesInDIrectory[i], newSessionAppName)
// console.log(`number: ${number}\noldNumber: ${oldNumber}\nsessionNum: ${sessionNum}\nsessionPort: ${sessionPort}\nnewSessionAppName:${newSessionAppName}`)
if (oldNumber != number) { if (oldNumber != number) {
deletePm2Process(subDir[x], currPath) deletePm2Process(subDir[x], currPath)
removeDir(currPath) removeDir(currPath)
} } else {
else {
res.send('ok') res.send('ok')
return return
} }
// try {
// //
// fs.renameSync(currPath, newPath)
// console.log("Successfully renamed the directory.")
// const data = fs.readFileSync(path.join(`${newPath}`, '.env'), 'utf-8');
// // console.log('Data: ', data)
// const newValue = data.replace(`MOBILEUID=${oldNumber}`, `MOBILEUID=${number}`)
// fs.writeFileSync(path.join(`${newPath}`, '.env'), newValue, 'utf-8');
// if (oldNumber != number) {
// removeDir(path.join(newPath, '.wwebjs_auth'))
// }
// startPm2Process(newSessionAppName, 'app.js', newPath, sessionPort)
// } catch (err) {
// console.log(err)
// }
// res.send('ok')
// return
} }
appPort.push(+subDir[x].split('_')[3]) let auxPort = subDir[x].split('_')[3]
console.log('---------> appPort: '+appPort) console.log('---------> auxPort: ' + auxPort)
if (auxPort) {
auxPort = +auxPort.trim()
if (!isNaN(auxPort)) {
appPort.push(auxPort)
}
}
existSubDir = true existSubDir = true
} }
} }
appPort = existSubDir ? Math.max(...appPort) + 1 : process.env.PORT_START appPort = existSubDir ? Math.max(...appPort) + 1 : process.env.PORT_START
console.log('new port: ', appPort) console.log('new port: ', appPort)
let dirSessionAppName let dirSessionAppName
let numberSession = 1 let numberSession = 1
// const dirSessionsNumberAppDirectories = fs.readdirSync(dirSessionsApp, { withFileTypes: true }) let lstPass = process.env.PASS
// .filter((item) => item.isDirectory() && item.name.includes(`${number}`))
// .map((item) => item.name);
// console.log('dirSessionsNumberAppDirectories', dirSessionsNumberAppDirectories, ' | dirSessionsApp: ', dirSessionsApp) if (!lstPass) {
console.log('PASS VARIABLE NOT FOUND INTO .ENV!')
console.log('client_url: ', client_url) return res.send('OK')
let db = db_info.filter((e) => e.client_url == client_url)
if (db && db.length > 0) {
db = db[0].db_conf
} }
// if (dirSessionsNumberAppDirectories.length > 0) { let db_credentials
try {
db_credentials = await OmnihitDBConn.findOne({ client_url })
if (db && Object.keys(db).length > 0) { if (!db_credentials) {
db_credentials = new OmnihitDBConn({
client_url: client_url,
db_conf: {
DB: '',
DB_HOST: '',
DB_USER: '',
DB_PASS: '',
DB_PORT: '',
},
})
await db_credentials.save()
return res.send('ok')
}
} catch (error) {
console.log(error)
}
if (db_credentials && db_credentials.db_conf.DB.trim().length > 0) {
lstPass = lstPass.split(',')
let password = null
// password = await lstPass.find(
// async (pass) =>
// await bcrypt.compare(pass.trim(), db_credentials.db_conf.DB_PASS)
// )
for (let i = 0; i < lstPass.length; i++) {
const hasPass = await bcrypt.compare(
lstPass[i].trim(),
db_credentials.db_conf.DB_PASS
)
if (hasPass) {
password = lstPass[i].trim()
break
}
}
if (password) {
db_credentials.db_conf.DB_PASS = password
} else {
return res.send('ok')
}
}
if (
db_credentials.db_conf &&
Object.keys(db_credentials.db_conf).length > 0
) {
const whatsapp_numbers = await new Promise((resolve, reject) => { const whatsapp_numbers = await new Promise((resolve, reject) => {
mysql_conn(db).query('SELECT name FROM Whatsapps WHERE name LIKE ?', [`%${number}%`], (err, result) => { mysql_conn(db_credentials.db_conf).query(
'SELECT name FROM Whatsapps WHERE name LIKE ?',
[`%${number}%`],
(err, result) => {
if (err) { if (err) {
reject(err) reject(err)
} } else {
else {
resolve(result) resolve(result)
} }
}); }
)
}) })
console.log('whatsapp_numbers: ', whatsapp_numbers) console.log('whatsapp_numbers: ', whatsapp_numbers)
@ -195,11 +214,12 @@ app.post('/api/session', async function (req, res) {
let session_num = [] let session_num = []
if (whatsapp_numbers && whatsapp_numbers.length > 0) { if (whatsapp_numbers && whatsapp_numbers.length > 0) {
console.log('whatsapp_numbers.length: ', whatsapp_numbers.length) console.log('whatsapp_numbers.length: ', whatsapp_numbers.length)
if (whatsapp_numbers.length == 5) { if (whatsapp_numbers.length == 5) {
res.status(400).json({ message: 'Cannot create more than 4 sessions from the same number' }) res.status(400).json({
message: 'Cannot create more than 4 sessions from the same number',
})
return return
} }
@ -209,16 +229,21 @@ app.post('/api/session', async function (req, res) {
console.log('numbered_sessions: ', numbered_sessions) console.log('numbered_sessions: ', numbered_sessions)
session_num = numbered_sessions.map((e) => parseInt((e.name.split('->')[e.name.split('->').length - 1]).trim().match(/\d+/)[0])) session_num = numbered_sessions.map((e) =>
parseInt(
e.name
.split('->')
[e.name.split('->').length - 1].trim()
.match(/\d+/)[0]
)
)
console.log('session_num', session_num) console.log('session_num', session_num)
} }
let index = 1; let index = 1
while (index <= 4) { while (index <= 4) {
if (!session_num.includes(index)) { if (!session_num.includes(index)) {
console.log(index) console.log(index)
numberSession = index numberSession = index
@ -226,11 +251,8 @@ app.post('/api/session', async function (req, res) {
} }
index++ index++
} }
} }
// numberSession = Math.max(...session_number) + 1 // numberSession = Math.max(...session_number) + 1
console.log('Number session: ', numberSession) console.log('Number session: ', numberSession)
@ -239,61 +261,63 @@ app.post('/api/session', async function (req, res) {
dirSessionAppName = `${whatsappId}_${number}_${numberSession}_${appPort}` dirSessionAppName = `${whatsappId}_${number}_${numberSession}_${appPort}`
destDir = path.join(dirSessionsApp, dirSessionAppName) destDir = path.join(dirSessionsApp, dirSessionAppName)
originDir = path.join(__dirname, '..', 'whats') originDir = path.join(__dirname, '..', 'whats')
copyFolder(originDir, destDir) copyFolder(originDir, destDir)
if (
if (db && Object.keys(db).length > 0) { db_credentials.db_conf &&
Object.keys(db_credentials.db_conf).length > 0
console.log('kkkkkkkkkkkkkkk') ) {
console.log('***SUCCESS SEED DIRECTORY CREATED***')
let whatsName let whatsName
const whatsapp = await new Promise((resolve, reject) => { const whatsapp = await new Promise((resolve, reject) => {
mysql_conn(db_credentials.db_conf).query(
mysql_conn(db).query("SELECT name from Whatsapps where id = ?", [whatsappId], (err, result) => { 'SELECT name from Whatsapps where id = ?',
[whatsappId],
(err, result) => {
if (err) { if (err) {
reject(err) reject(err)
} } else {
else {
resolve(result) resolve(result)
} }
}); }
)
}) })
if (whatsapp[0]["name"].split('->').length > 0) { if (whatsapp[0]['name'].split('->').length > 0) {
whatsName = `${whatsapp[0]["name"].split('->')[0]} -> S${numberSession}` whatsName = `${whatsapp[0]['name'].split('->')[0]} -> S${numberSession}`
} } else {
else { whatsName = `${whatsapp[0]['name']} -> S${numberSession}`
whatsName = `${whatsapp[0]["name"]} -> S${numberSession}`
} }
console.log('whatsName: ', whatsName) console.log('whatsName: ', whatsName)
console.log(`url: ${process.env.BASE_URL}:${appPort}\n whatsname: ${whatsName}\n whatsappId: ${whatsappId}`) console.log(
`url: ${process.env.BASE_URL}:${appPort}\n whatsname: ${whatsName}\n whatsappId: ${whatsappId}`
)
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
mysql_conn(db).query("UPDATE Whatsapps SET url = ?, name = ? where id = ?", [`${process.env.BASE_URL}:${appPort}`, `${whatsName}`, whatsappId], mysql_conn(db_credentials.db_conf).query(
'UPDATE Whatsapps SET url = ?, name = ? where id = ?',
[`${process.env.BASE_URL}:${appPort}`, `${whatsName}`, whatsappId],
function (err, result) { function (err, result) {
if (err) { if (err) {
reject(err) reject(err)
console.log("===> ERROR: " + err); console.log('===> ERROR: ' + err)
} } else {
else {
resolve(result) resolve(result)
// console.log('RESULT: ', result) // console.log('RESULT: ', result)
} }
// else // else
// console.log('myslq result: ', result); // console.log('myslq result: ', result);
}); }
)
}) })
let whatsappName = `${number} - s${numberSession}` let whatsappName = `${number} - s${numberSession}`
@ -301,80 +325,86 @@ app.post('/api/session', async function (req, res) {
console.log('-------------- numberSession', numberSession) console.log('-------------- numberSession', numberSession)
if (whatsapp.length > 0) { if (whatsapp.length > 0) {
if (whatsapp[0]['name'].split(' ').length > 0) { if (whatsapp[0]['name'].split(' ').length > 0) {
whatsappName = `${whatsapp[0]['name'].split(' ')[0]
whatsappName = `${whatsapp[0]['name'].split(' ')[0]} - S${numberSession}` } - S${numberSession}`
} }
} }
console.log('whatsapp: ', whatsapp) console.log('whatsapp: ', whatsapp)
console.log("whatsapp[0]['name']: ", whatsapp[0]['name']) console.log("whatsapp[0]['name']: ", whatsapp[0]['name'])
const keys = Object.keys(db); const keys = Object.keys(db_credentials.db_conf)
var stream = fs.createWriteStream(path.join(destDir, '.env')); var stream = fs.createWriteStream(path.join(destDir, '.env'))
stream.once('open', function (fd) { stream.once('open', function (fd) {
stream.write("# NUMBER AND NAME THAT WILL BE DISPLAYED ON CONSOLE\n"); stream.write('# NUMBER AND NAME THAT WILL BE DISPLAYED ON CONSOLE\n')
stream.write(`MOBILEUID=${number}\n`); stream.write(`MOBILEUID=${number}\n`)
stream.write(`MOBILENAME=${whatsappName}\n`); stream.write(`MOBILENAME=${whatsappName}\n`)
stream.write("\n"); stream.write('\n')
stream.write("# PORT NUMBER FOR THIS API\n"); stream.write('# PORT NUMBER FOR THIS API\n')
stream.write(`PORT=${appPort}\n`); stream.write(`PORT=${appPort}\n`)
stream.write("\n"); stream.write('\n')
stream.write("# URL FROM THE OMNIHIT BACKEND API\n"); stream.write('# URL FROM THE OMNIHIT BACKEND API\n')
stream.write(`CLIENT_URL=${client_url}\n`); stream.write(`CLIENT_URL=${client_url}\n`)
stream.write("\n"); stream.write('\n')
stream.write("# OMNIHIT DATABASE\n"); stream.write('# OMNIHIT DATABASE\n')
keys.forEach((key, index) => { keys.forEach((key, index) => {
stream.write(`${key}=${db[key]}\n`); stream.write(`${key}=${db_credentials.db_conf[key]}\n`)
}); })
stream.write("\n"); stream.write('\n')
stream.write(`# WHATSAPP ID OF THE TABLE Whatsapps FROM THE OMNIHIT DATABASE\n`); stream.write(
stream.write(`WHATSAPP_ID=${whatsappId}`); `# WHATSAPP ID OF THE TABLE Whatsapps FROM THE OMNIHIT DATABASE\n`
)
stream.write(`WHATSAPP_ID=${whatsappId}\n`)
stream.write('\n')
stream.end(); stream.write('# MONGO CONNECTION\n')
}); stream.write(`DB_MONGO_URL=${process.env.DB_MONGO_URL}\n`)
stream.write('\n')
stream.write('# MONGO COLLECTION\n')
stream.write(`DB_MONGO_NAME=${process.env.DB_MONGO_NAME_CAMPAIGN}\n`)
stream.write('\n')
stream.end()
})
console.log('----------------destDir: ', destDir) console.log('----------------destDir: ', destDir)
execSync(`npm install`, { cwd: destDir }, (error, stdout, stderr) => { execSync(`npm install`, { cwd: destDir }, (error, stdout, stderr) => {
if (error) { if (error) {
console.log(`error: ${error.message}`); console.log(`error: ${error.message}`)
return; return
} }
if (stderr) { if (stderr) {
console.log(`stderr: ${stderr}`); console.log(`stderr: ${stderr}`)
return; return
} }
console.log(`stdout: ${stdout}`); console.log(`stdout: ${stdout}`)
}); })
startPm2Process(dirSessionAppName, 'app.js', destDir, appPort)
const env = {
PORT: appPort,
DB_MONGO_URL: process.env.DB_MONGO_URL,
DB_MONGO_NAME: process.env.DB_MONGO_NAME_CAMPAIGN
} }
res.send("OK"); startPm2Process(dirSessionAppName, 'app.js', destDir, env)
}
}); res.send('OK')
})
app.post('/api/session/edit', async function (req, res) { app.post('/api/session/edit', async function (req, res) {
const { app_name, whatsappId, client_url, number } = req.body const { app_name, whatsappId, client_url, number } = req.body
}) })
app.post('/api/session/del', async function (req, res) { app.post('/api/session/del', async function (req, res) {
let { whatsappId, app_name } = req.body let { whatsappId, app_name } = req.body
if (app_name) { if (app_name) {
@ -383,9 +413,10 @@ app.post('/api/session/del', async function (req, res) {
const sessionsPath = path.join(__dirname, '..', 'sessions') const sessionsPath = path.join(__dirname, '..', 'sessions')
const directoriesInDIrectory = fs.readdirSync(sessionsPath, { withFileTypes: true }) const directoriesInDIrectory = fs
.readdirSync(sessionsPath, { withFileTypes: true })
.filter((item) => item.isDirectory()) .filter((item) => item.isDirectory())
.map((item) => item.name); .map((item) => item.name)
console.log('directoriesInDIrectory: ', directoriesInDIrectory) console.log('directoriesInDIrectory: ', directoriesInDIrectory)
@ -393,94 +424,95 @@ app.post('/api/session/del', async function (req, res) {
console.log('dirExist: ', dirExist) console.log('dirExist: ', dirExist)
if (dirExist.length == 0) if (dirExist.length == 0) res.send('ok')
res.send('ok')
for (let i = 0; i < directoriesInDIrectory.length; i++) { for (let i = 0; i < directoriesInDIrectory.length; i++) {
console.log('directoriesInDIrectory[i]', directoriesInDIrectory[i]) console.log('directoriesInDIrectory[i]', directoriesInDIrectory[i])
const subDir = fs.readdirSync(path.join(sessionsPath, directoriesInDIrectory[i]), { withFileTypes: true }) const subDir = fs
.readdirSync(path.join(sessionsPath, directoriesInDIrectory[i]), {
withFileTypes: true,
})
.filter((item) => item.isDirectory()) .filter((item) => item.isDirectory())
.map((item) => item.name); .map((item) => item.name)
for (let x = 0; x < subDir.length; x++) { for (let x = 0; x < subDir.length; x++) {
console.log('subdir: ', subDir[x]) console.log('subdir: ', subDir[x])
let whatsId = subDir[x].split('_')[0] let whatsId = subDir[x].split('_')[0]
if (whatsId == whatsappId && app_name == directoriesInDIrectory[i]) { if (whatsId == whatsappId && app_name == directoriesInDIrectory[i]) {
let currPath = path.join(
sessionsPath,
directoriesInDIrectory[i],
subDir[x]
)
let currPath = path.join(sessionsPath, directoriesInDIrectory[i], subDir[x]) deletePm2Process(subDir[x], currPath)
deletePm2Process(subDir[x], currPath);
console.log('currPath: ', currPath) console.log('currPath: ', currPath)
removeDir(currPath) removeDir(currPath)
return res.send('ok') return res.send('ok')
} }
} }
} }
res.send('ok') res.send('ok')
}) })
app.listen(process.env.PORT || 8003, function () { app.listen(process.env.PORT || 8003, function () {
console.log("\u26A1[server]: Server is running at Port ::: " + process.env.PORT || 8003); console.log(
}); '\u26A1[server]: Server is running at Port ::: ' + process.env.PORT || 8003
)
})
process.on('uncaughtException', function (err) { process.on('uncaughtException', function (err) {
console.error(' '); console.error(' ')
console.error('----- ' + (new Date).toUTCString() + ' ----------------------------------') console.error(
'----- ' + new Date().toUTCString() + ' ----------------------------------'
)
console.error('Erro uncaughtException: ', err.message) console.error('Erro uncaughtException: ', err.message)
console.error(err.stack) console.error(err.stack)
console.error(' '); console.error(' ')
return return
}); })
function deletePm2Process(process_name, currPath) { function deletePm2Process(process_name, currPath) {
pm2.connect(function (err) { pm2.connect(function (err) {
if (err) { if (err) {
console.error(err); console.error(err)
} }
pm2.list(function (err, processes) { pm2.list(function (err, processes) {
if (err) { if (err) {
console.error(err); console.error(err)
} }
processes.forEach(function (process) { processes.forEach(function (process) {
console.log('.........process.name: ', process.name)
console.log(".........process.name: ", process.name);
if (process.name === process_name) { if (process.name === process_name) {
execSync(`pm2 delete ${process_name} && pm2 save --force`, { cwd: currPath }, (error, stdout, stderr) => { execSync(
`pm2 delete ${process_name} && pm2 save --force`,
{ cwd: currPath },
(error, stdout, stderr) => {
if (error) { if (error) {
console.log(`error: ${error.message}`); console.log(`error: ${error.message}`)
return; return
} }
if (stderr) { if (stderr) {
console.log(`stderr: ${stderr}`); console.log(`stderr: ${stderr}`)
return; return
} }
console.log(`stdout: ${stdout}`); console.log(`stdout: ${stdout}`)
});
} }
)
});
pm2.disconnect();
});
});
} }
})
pm2.disconnect()
})
})
}

View File

@ -0,0 +1,11 @@
const mongoose = require('mongoose')
require('dotenv').config({ path: `${process.cwd()}/.env` })
async function main(){
await mongoose.connect(process.env.DB_MONGO_URL, { dbName: process.env.DB_MONGO_NAME })
console.log('Conectou ao Mongoose!')
}
main().catch((err)=>console.log(err))
module.exports = mongoose

View File

@ -0,0 +1,20 @@
const bcrypt = require('bcrypt')
// const pass = async () => {
// // create a password
// const salt = await bcrypt.genSalt(12)
// const passwordHash = await bcrypt.hash('7901228899', salt)
// console.log(passwordHash)
// }
// pass()
const passDec = async () => {
const _pass = await bcrypt.compare(
'strongpassword',
'$2b$12$PZ8N1jU77nnNUCCGyKTMNOi2QI7X/SgPsISVQfr.cQ/jgdx5Z7AqC'
)
console.log('_pass: ', _pass)
}
passDec()
console.log('process.cwd(): ', process.cwd())

View File

@ -1,44 +1,49 @@
const pm2 = require('pm2'); const pm2 = require('pm2')
const { execSync } = require("child_process"); const { execSync } = require("child_process")
function startPm2Process(process_name, file, path, port) { function startPm2Process(process_name, file, path, env) {
pm2.connect(function (err) { pm2.connect(function (err) {
if (err) { if (err) {
console.error(err); console.error(err)
// process.exit(2); // process.exit(2);
} }
console.log('ENV PM2: ', env)
pm2.start({ pm2.start({
name: process_name, name: process_name,
script: file, script: file,
cwd: path, cwd: path,
env: { env
PORT: port // env: {
} // NODE_ENV: 'production',
// PORT: port,
// }
// additional options here if needed // additional options here if needed
}, function (err, apps) { }, function (err, apps) {
if (err) { if (err) {
console.error(err); console.error(err)
// process.exit(2); // process.exit(2);
} }
else { else {
execSync(`pm2 save --force`, { cwd: path }, (error, stdout, stderr) => { execSync(`pm2 save --force`, { cwd: path }, (error, stdout, stderr) => {
if (error) { if (error) {
console.log(`error: ${error.message}`); console.log(`error: ${error.message}`)
return; return
} }
if (stderr) { if (stderr) {
console.log(`stderr: ${stderr}`); console.log(`stderr: ${stderr}`)
return; return
} }
console.log(`stdout: ${stdout}`); console.log(`stdout: ${stdout}`)
}); })
} }
pm2.disconnect(); pm2.disconnect()
}); })
}); })
} }

View File

@ -0,0 +1,33 @@
const mongoose = require('../db/connMongo')
const { Schema } = mongoose
const OmnihitDBConn = mongoose.model(
'Omnihit_db_conn',
new Schema(
{
client_url: {
type: String,
},
db_conf: {
DB: {
type: String,
},
DB_HOST: {
type: String,
},
DB_USER: {
type: String,
},
DB_PASS: {
type: String,
},
DB_PORT: {
type: String,
},
},
},
{ timestamps: true }
)
)
module.exports = OmnihitDBConn

File diff suppressed because it is too large Load Diff

View File

@ -11,10 +11,12 @@
"author": "Adriano <adriano08andrade@hotmail.com>", "author": "Adriano <adriano08andrade@hotmail.com>",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bcrypt": "^5.1.0",
"body-parser": "^1.20.1", "body-parser": "^1.20.1",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"express": "^4.18.2", "express": "^4.18.2",
"fs-extra": "^11.1.0", "fs-extra": "^11.1.0",
"mongoose": "^7.4.0",
"mysql": "^2.18.1", "mysql": "^2.18.1",
"nodemon": "^2.0.20", "nodemon": "^2.0.20",
"socket.io": "^4.5.4" "socket.io": "^4.5.4"

View File

@ -1,9 +1,9 @@
# NUMBER AND NAME THAT WILL BE DISPLAYED ON CONSOLE # NUMBER AND NAME THAT WILL BE DISPLAYED ON CONSOLE
MOBILEUID=5517988310949 MOBILEUID=5517988310949
MOBILENAME=Numero de teste MOBILENAME=test - S1
# PORT NUMBER FOR THIS API # PORT NUMBER FOR THIS API
PORT=8020 PORT=8029
# URL FROM THE OMNIHIT BACKEND API # URL FROM THE OMNIHIT BACKEND API
CLIENT_URL=http://localhost:8080 CLIENT_URL=http://localhost:8080
@ -16,5 +16,10 @@ DB_PASS=strongpassword
DB_PORT=3306 DB_PORT=3306
# WHATSAPP ID OF THE TABLE Whatsapps FROM THE OMNIHIT DATABASE # WHATSAPP ID OF THE TABLE Whatsapps FROM THE OMNIHIT DATABASE
WHATSAPP_ID=46 WHATSAPP_ID=223
# MONGO CONNECTION
DB_MONGO_URL=mongodb://localhost:27017
# MONGO COLLECTION
DB_MONGO_NAME=broker_omnihit

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
const { MongoClient } = require('mongodb')
const uri = process.env.DB_MONGO_URL
const mongo = new MongoClient(uri)
async function run() {
try {
await mongo.connect()
console.log('Connectado ao mongo db')
} catch (err) {
console.log(err)
}
}
run()
module.exports = mongo

View File

@ -0,0 +1,41 @@
const http = require('http')
const checkInternetConnection = async () => {
const options = {
hostname: 'www.google.com',
port: 80,
method: 'HEAD'
}
return new Promise((resolve, reject) => {
const req = http.request(options, (res) => {
if (res.statusCode === 200) {
resolve(true)
} else {
resolve(false)
}
req.abort()
})
req.on('error', (err) => {
resolve(false)
})
req.end()
})
};
// (async () => {
// try {
// const isConnected = await checkInternetConnection()
// if (isConnected) {
// console.log('Internet connection is available.')
// } else {
// console.log('Internet connection is not available.')
// }
// } catch (error) {
// console.error('Error checking internet connection:', error)
// }
// })()
module.exports = checkInternetConnection

File diff suppressed because it is too large Load Diff

View File

@ -15,17 +15,18 @@
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"express": "^4.17.1", "express": "^4.17.1",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"fs-extra": "^11.1.0",
"logger": "^0.0.1", "logger": "^0.0.1",
"mime": "^2.4.5", "mime": "^2.4.5",
"mongodb": "^4.1.1", "mongodb": "^4.1.1",
"mongoose": "^7.4.3",
"multer": "^1.4.4", "multer": "^1.4.4",
"mysql": "^2.18.1", "mysql": "^2.18.1",
"node-os-utils": "^1.3.5", "node-os-utils": "^1.3.5",
"qr-encode": "^0.3.0", "qr-encode": "^0.3.0",
"qrcode-terminal": "^0.12.0", "qrcode-terminal": "^0.12.0",
"socket.io": "^4.5.4", "socket.io": "^4.5.4",
"socket.io-client": "^4.5.4", "socket.io-client": "^4.5.4"
"fs-extra": "^11.1.0"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^2.0.20" "nodemon": "^2.0.20"

View File

@ -23,8 +23,8 @@
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"cookie-parser": "^1.4.5", "cookie-parser": "^1.4.5",
"cors": "^2.8.5", "cors": "^2.8.5",
"date-fns": "^2.16.1", "date-fns": "^2.30.0",
"date-fns-tz": "^1.3.4", "date-fns-tz": "^1.3.8",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"express": "^4.17.1", "express": "^4.17.1",
"express-async-errors": "^3.1.1", "express-async-errors": "^3.1.1",

View File

@ -8,7 +8,7 @@ import ShowContactService from "../services/ContactServices/ShowContactService";
import UpdateContactService from "../services/ContactServices/UpdateContactService"; import UpdateContactService from "../services/ContactServices/UpdateContactService";
import DeleteContactService from "../services/ContactServices/DeleteContactService"; import DeleteContactService from "../services/ContactServices/DeleteContactService";
import CheckContactNumber from "../services/WbotServices/CheckNumber" import CheckContactNumber from "../services/WbotServices/CheckNumber";
import CheckIsValidContact from "../services/WbotServices/CheckIsValidContact"; import CheckIsValidContact from "../services/WbotServices/CheckIsValidContact";
import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl"; import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl";
import AppError from "../errors/AppError"; import AppError from "../errors/AppError";
@ -47,42 +47,46 @@ type IndexGetContactQuery = {
export const index = async (req: Request, res: Response): Promise<Response> => { export const index = async (req: Request, res: Response): Promise<Response> => {
let { searchParam, pageNumber } = req.query as IndexQuery; let { searchParam, pageNumber } = req.query as IndexQuery;
console.log('PAGE NUMBER CONTACT: ', pageNumber) console.log("PAGE NUMBER CONTACT: ", pageNumber);
if (pageNumber === undefined || pageNumber.trim().length == 0) { if (pageNumber === undefined || pageNumber.trim().length == 0) {
pageNumber = '1' pageNumber = "1";
} }
// TEST DEL // TEST DEL
if (searchParam && searchParam.trim().length > 0 && process.env.CACHE) { if (searchParam && searchParam.trim().length > 0 && process.env.CACHE) {
try { try {
const offset = 20 * (+pageNumber - 1); const offset = 20 * (+pageNumber - 1);
searchParam = searchParam.replace(/\s+/g, ' ').trim().toLowerCase(); searchParam = searchParam.replace(/\s+/g, " ").trim().toLowerCase();
const data = await searchContactCache(searchParam, offset, 20) const data = await searchContactCache(searchParam, offset, 20);
if (data) { if (data) {
console.log("QUERY CONTACTS FROM CACHE SEARCH PARAM: ", searchParam);
console.log('QUERY CONTACTS FROM CACHE SEARCH PARAM: ', searchParam) console.log("QUERY CONTACTS FROM CACHE QUERY LENGTH: ", data.length);
console.log('QUERY CONTACTS FROM CACHE QUERY LENGTH: ', data.length) return res.json({
contacts: data,
return res.json({ contacts: data, count: data.length, hasMore: data.length > 0 ? true : false }); count: data.length,
hasMore: data.length > 0 ? true : false
});
} }
} catch (error) { } catch (error) {
console.log('There was an error on search ContactController.ts search cache: ', error) console.log(
"There was an error on search ContactController.ts search cache: ",
error
);
}
} }
console.log("QUERY CONTACTS FROM DATABASE SEARCH PARAM: ", searchParam);
} const { contacts, count, hasMore } = await ListContactsService({
searchParam,
console.log('QUERY CONTACTS FROM DATABASE SEARCH PARAM: ', searchParam) pageNumber
});
const { contacts, count, hasMore } = await ListContactsService({ searchParam, pageNumber });
return res.json({ contacts, count, hasMore }); return res.json({ contacts, count, hasMore });
}; };
@ -126,8 +130,7 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
const profilePicUrl = await GetProfilePicUrl(validNumber); const profilePicUrl = await GetProfilePicUrl(validNumber);
console.log('xxxxxxxxxxx profilePicUrl: ',profilePicUrl) console.log("xxxxxxxxxxx profilePicUrl: ", profilePicUrl);
// console.log(`newContact.name: ${newContact.name}\n // console.log(`newContact.name: ${newContact.name}\n
// newContact.number: ${newContact.number}\n // newContact.number: ${newContact.number}\n
@ -146,7 +149,7 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
email, email,
useDialogflow, useDialogflow,
profilePicUrl: profilePicUrl, profilePicUrl: profilePicUrl,
extraInfo, extraInfo
}); });
const io = getIO(); const io = getIO();
@ -257,7 +260,14 @@ export const contacsBulkInsertOnQueue = async (req: Request, res: Response): Pro
// console.log('THE BODY: ', req.body) // console.log('THE BODY: ', req.body)
const { adminId, identifier, queueStatus, file, contacts_inserted } = req.body const {
adminId,
identifier,
queueStatus,
file,
contacts_inserted,
campaign
} = req.body;
const io = getIO(); const io = getIO();
io.emit("contactsBulkInsertOnQueueStatus", { io.emit("contactsBulkInsertOnQueueStatus", {
@ -266,17 +276,14 @@ export const contacsBulkInsertOnQueue = async (req: Request, res: Response): Pro
adminId: adminId, adminId: adminId,
identifier: identifier, identifier: identifier,
queueStatus: queueStatus, queueStatus: queueStatus,
file: file file: file,
campaign
} }
}); });
if (process.env.CACHE && contacts_inserted) { if (process.env.CACHE && contacts_inserted) {
await insertContactsCache(contacts_inserted);
await insertContactsCache(contacts_inserted)
} }
return res.status(200).json({ message: 'ok' }) return res.status(200).json({ message: "ok" });
}; };

View File

@ -5,6 +5,9 @@ import AppError from "../errors/AppError";
import UpdateSettingService from "../services/SettingServices/UpdateSettingService"; import UpdateSettingService from "../services/SettingServices/UpdateSettingService";
import ListSettingsService from "../services/SettingServices/ListSettingsService"; import ListSettingsService from "../services/SettingServices/ListSettingsService";
import loadSettings from "../helpers/LoadSettings";
import updateSettingTicket from "../services/SettingServices/UpdateSettingTicket";
import SettingTicket from "../models/SettingTicket";
export const index = async (req: Request, res: Response): Promise<Response> => { export const index = async (req: Request, res: Response): Promise<Response> => {
// if (req.user.profile !== "master") { // if (req.user.profile !== "master") {
@ -13,7 +16,76 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
const settings = await ListSettingsService(); const settings = await ListSettingsService();
return res.status(200).json(settings); const config = await SettingTicket.findAll();
return res.status(200).json({ settings, config });
};
export const updateTicketSettings = async (
req: Request,
res: Response
): Promise<Response> => {
const {
outBusinessHours,
ticketExpiration,
weekend,
saturday,
sunday,
holiday
} = req.body;
if (outBusinessHours && Object.keys(outBusinessHours).length > 0) {
await updateSettingTicket({
...outBusinessHours,
key: "outBusinessHours"
});
}
if (ticketExpiration && Object.keys(ticketExpiration).length > 0) {
await updateSettingTicket({
...ticketExpiration,
key: "ticketExpiration"
});
}
if (weekend && Object.keys(weekend).length > 0) {
await updateSettingTicket({
...weekend,
key: "weekend"
});
}
if (saturday && Object.keys(saturday).length > 0) {
await updateSettingTicket({
...saturday,
key: "saturday"
});
}
if (sunday && Object.keys(sunday).length > 0) {
await updateSettingTicket({
...sunday,
key: "sunday"
});
}
if (holiday && Object.keys(holiday).length > 0) {
await updateSettingTicket({
...holiday,
key: "holiday"
});
}
return res
.status(200)
.json({
outBusinessHours,
ticketExpiration,
weekend,
saturday,
sunday,
holiday
});
}; };
export const update = async ( export const update = async (
@ -31,6 +103,8 @@ export const update = async (
value value
}); });
loadSettings();
const io = getIO(); const io = getIO();
io.emit("settings", { io.emit("settings", {
action: "update", action: "update",

View File

@ -8,24 +8,22 @@ import ShowTicketService from "../services/TicketServices/ShowTicketService";
import UpdateTicketService from "../services/TicketServices/UpdateTicketService"; import UpdateTicketService from "../services/TicketServices/UpdateTicketService";
import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage"; import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage";
import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService"; import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService";
import ShowStatusChatEndService from '../services/StatusChatEndService/ShowStatusChatEndService' import ShowStatusChatEndService from "../services/StatusChatEndService/ShowStatusChatEndService";
import CreateSchedulingNotifyService from "../services/SchedulingNotifyServices/CreateSchedulingNotifyService"; import CreateSchedulingNotifyService from "../services/SchedulingNotifyServices/CreateSchedulingNotifyService";
import ListSchedulingNotifyContactService from "../services/SchedulingNotifyServices/ListSchedulingNotifyContactService"; import ListSchedulingNotifyContactService from "../services/SchedulingNotifyServices/ListSchedulingNotifyContactService";
import { isScheduling } from "../helpers/CheckSchedulingReminderNotify" import { isScheduling } from "../helpers/CheckSchedulingReminderNotify";
import ptBR from 'date-fns/locale/pt-BR'; import ptBR from "date-fns/locale/pt-BR";
import { splitDateTime } from "../helpers/SplitDateTime"; import { splitDateTime } from "../helpers/SplitDateTime";
import format from 'date-fns/format'; import format from "date-fns/format";
import ListTicketsServiceCache from "../services/TicketServices/ListTicketServiceCache"; import ListTicketsServiceCache from "../services/TicketServices/ListTicketServiceCache";
import { searchTicketCache, loadTicketsCache, } from '../helpers/TicketCache' import { searchTicketCache, loadTicketsCache } from "../helpers/TicketCache";
import { Op } from "sequelize"; import { Op } from "sequelize";
type IndexQuery = { type IndexQuery = {
searchParam: string; searchParam: string;
pageNumber: string; pageNumber: string;
@ -35,7 +33,7 @@ type IndexQuery = {
withUnreadMessages: string; withUnreadMessages: string;
queueIds: string; queueIds: string;
unlimited?: string; unlimited?: string;
searchParamContent?: string searchParamContent?: string;
}; };
interface TicketData { interface TicketData {
@ -43,13 +41,12 @@ interface TicketData {
status: string; status: string;
queueId: number; queueId: number;
userId: number; userId: number;
whatsappId?: string | number whatsappId?: string | number;
msg?: string, msg?: string;
transfer?: boolean | undefined, transfer?: boolean | undefined;
fromMe?: boolean fromMe?: boolean;
} }
import ListStatusChatEndService from "../services/StatusChatEndService/ListStatusChatEndService"; import ListStatusChatEndService from "../services/StatusChatEndService/ListStatusChatEndService";
import Ticket from "../models/Ticket"; import Ticket from "../models/Ticket";
import ShowUserServiceReport from "../services/UserServices/ShowUserServiceReport"; import ShowUserServiceReport from "../services/UserServices/ShowUserServiceReport";
@ -60,16 +57,15 @@ import ShowUserService from "../services/UserServices/ShowUserService";
import axios from "axios"; import axios from "axios";
import User from "../models/User"; import User from "../models/User";
import CheckContactOpenTickets from "../helpers/CheckContactOpenTickets"; import CheckContactOpenTickets from "../helpers/CheckContactOpenTickets";
import QueuesByUser from "../services/UserServices/ShowQueuesByUser";
import GetDefaultWhatsApp from "../helpers/GetDefaultWhatsApp"; import GetDefaultWhatsApp from "../helpers/GetDefaultWhatsApp";
import { getWbot } from "../libs/wbot"; import { getWbot } from "../libs/wbot";
import endPointQuery from "../helpers/old_EndPointQuery"; import endPointQuery from "../helpers/old_EndPointQuery";
import Contact from "../models/Contact"; import Contact from "../models/Contact";
import BotIsOnQueue from "../helpers/BotIsOnQueue"; import BotIsOnQueue from "../helpers/BotIsOnQueue";
import { setMessageAsRead } from "../helpers/SetMessageAsRead"; import { setMessageAsRead } from "../helpers/SetMessageAsRead";
import { getSettingValue } from "../helpers/WhaticketSettings";
export const index = async (req: Request, res: Response): Promise<Response> => { export const index = async (req: Request, res: Response): Promise<Response> => {
const { const {
pageNumber, pageNumber,
status, status,
@ -82,7 +78,6 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
searchParamContent searchParamContent
} = req.query as IndexQuery; } = req.query as IndexQuery;
const userId = req.user.id; const userId = req.user.id;
let queueIds: number[] = []; let queueIds: number[] = [];
@ -110,22 +105,23 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
export const store = async (req: Request, res: Response): Promise<Response> => { export const store = async (req: Request, res: Response): Promise<Response> => {
const { contactId, status, userId, msg, queueId }: TicketData = req.body; const { contactId, status, userId, msg, queueId }: TicketData = req.body;
const botInfo = await BotIsOnQueue('botqueue') // const botInfo = await BotIsOnQueue("botqueue");
let ticket = await Ticket.findOne({ let ticket = await Ticket.findOne({
where: { where: {
[Op.or]: [ [Op.or]: [
{ contactId, status: 'queueChoice' }, { contactId, status: "queueChoice" }
{ contactId, status: 'open', userId: botInfo.userIdBot } // { contactId, status: "open", userId: botInfo.userIdBot }
] ]
} }
}); });
if (ticket) { if (ticket) {
await UpdateTicketService({ ticketData: { status: 'open', userId: userId, queueId }, ticketId: ticket.id }); await UpdateTicketService({
ticketData: { status: "open", userId: userId, queueId },
} ticketId: ticket.id
else { });
} else {
ticket = await CreateTicketService({ contactId, status, userId, queueId }); ticket = await CreateTicketService({ contactId, status, userId, queueId });
} }
@ -136,7 +132,6 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
}); });
// //
// const ticket = await CreateTicketService({ contactId, status, userId }); // const ticket = await CreateTicketService({ contactId, status, userId });
// const io = getIO(); // const io = getIO();
@ -148,58 +143,65 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
return res.status(200).json(ticket); return res.status(200).json(ticket);
}; };
export const show = async (req: Request, res: Response): Promise<Response> => { export const show = async (req: Request, res: Response): Promise<Response> => {
const { ticketId } = req.params; const { ticketId } = req.params;
const contact = await ShowTicketService(ticketId); const contact = await ShowTicketService(ticketId);
const { statusChatEnd, count, hasMore } = await ListStatusChatEndService({ searchParam: "", pageNumber: "1" }); const { statusChatEnd, count, hasMore } = await ListStatusChatEndService({
searchParam: "",
pageNumber: "1"
});
////////////////// //////////////////
const schedulesContact = await ListSchedulingNotifyContactService(contact.contact.number); const schedulesContact = await ListSchedulingNotifyContactService(
contact.contact.number
);
///////////////// /////////////////
return res.status(200).json({ contact, statusChatEnd, schedulesContact }); return res.status(200).json({ contact, statusChatEnd, schedulesContact });
}; };
export const count = async (req: Request, res: Response): Promise<Response> => { export const count = async (req: Request, res: Response): Promise<Response> => {
// type indexQ = { status: string; date?: string; }; // type indexQ = { status: string; date?: string; };
const { status, date } = req.query as IndexQuery const { status, date } = req.query as IndexQuery;
const ticketCount = await CountTicketService(status, date); const ticketCount = await CountTicketService(status, date);
return res.status(200).json(ticketCount); return res.status(200).json(ticketCount);
}; };
export const update = async (
export const update = async (req: Request, res: Response): Promise<Response> => { req: Request,
res: Response
console.log('ENTROU NO UPDATE TICKET CONTROLLER') ): Promise<Response> => {
console.log("ENTROU NO UPDATE TICKET CONTROLLER");
const { ticketId } = req.params; const { ticketId } = req.params;
const userOldInfo = await Ticket.findByPk(ticketId) const userOldInfo = await Ticket.findByPk(ticketId);
let ticket2 = {} let ticket2 = {};
if (req.body['status'] === "closed") {
if (req.body["status"] === "closed") {
const { status, userId, schedulingNotifyData } = req.body; const { status, userId, schedulingNotifyData } = req.body;
// lembrete // lembrete
const scheduleData = JSON.parse(schedulingNotifyData) const scheduleData = JSON.parse(schedulingNotifyData);
const statusChatEndName = await ShowStatusChatEndService(scheduleData.statusChatEndId) const statusChatEndName = await ShowStatusChatEndService(
scheduleData.statusChatEndId
);
const { ticket } = await UpdateTicketService({ const { ticket } = await UpdateTicketService({
ticketData: { 'status': status, 'userId': userId, 'statusChatEnd': statusChatEndName.name }, ticketData: {
status: status,
userId: userId,
statusChatEnd: statusChatEndName.name
},
ticketId ticketId
}); });
if (scheduleData.farewellMessage) { if (scheduleData.farewellMessage) {
const whatsapp = await ShowWhatsAppService(ticket.whatsappId); const whatsapp = await ShowWhatsAppService(ticket.whatsappId);
@ -210,115 +212,105 @@ export const update = async (req: Request, res: Response): Promise<Response> =>
} }
} }
// lembrete // agendamento // lembrete // agendamento
if (scheduleData.statusChatEndId === '2' || scheduleData.statusChatEndId === '3') { if (
scheduleData.statusChatEndId === "2" ||
scheduleData.statusChatEndId === "3"
if (isScheduling(scheduleData.schedulingDate, scheduleData.schedulingTime)) { ) {
console.log('*** É AGENDAMENTO!') if (
} isScheduling(scheduleData.schedulingDate, scheduleData.schedulingTime)
else { ) {
console.log('*** É LEMBRETE!') console.log("*** É AGENDAMENTO!");
} else {
console.log("*** É LEMBRETE!");
} }
const schedulingNotifyCreate = await CreateSchedulingNotifyService( const schedulingNotifyCreate = await CreateSchedulingNotifyService({
{
ticketId: scheduleData.ticketId, ticketId: scheduleData.ticketId,
statusChatEndId: scheduleData.statusChatEndId, statusChatEndId: scheduleData.statusChatEndId,
schedulingDate: scheduleData.schedulingDate, schedulingDate: scheduleData.schedulingDate,
schedulingTime: scheduleData.schedulingTime, schedulingTime: scheduleData.schedulingTime,
message: scheduleData.message message: scheduleData.message
} });
)
} }
ticket2 = ticket ticket2 = ticket;
} else {
}
else {
// Para aparecer pendente para todos usuarios que estao na fila // Para aparecer pendente para todos usuarios que estao na fila
if (req.body.transfer) { if (req.body.transfer) {
req.body.userId = null req.body.userId = null;
} }
let ticketData: TicketData = req.body; let ticketData: TicketData = req.body;
// console.log('ticketData: ', ticketData) if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") {
// console.log('ticketData.transfer', ticketData.transfer) if (ticketData.transfer) {
const defaultWhatsapp: any = await GetDefaultWhatsApp(
ticketData.userId
);
// return res.send() const _ticket: any = await Ticket.findByPk(ticketId);
if (defaultWhatsapp && ticketData.status != "open") {
await CheckContactOpenTickets(
_ticket.dataValues.contactId,
defaultWhatsapp.dataValues.id
);
}
// if (ticketData.transfer) { ticketData.whatsappId = defaultWhatsapp.dataValues.id;
}
// const defaultWhatsapp: any = await GetDefaultWhatsApp(ticketData.userId); }
// const _ticket: any = await Ticket.findByPk(ticketId)
// if (defaultWhatsapp && ticketData.status != 'open') {
// await CheckContactOpenTickets(_ticket.dataValues.contactId, defaultWhatsapp.dataValues.id)
// }
// ticketData.whatsappId = defaultWhatsapp.dataValues.id
// }
console.log('--------> ticketData.status: ', ticketData.status, ' | ticketData.fromMe: ', ticketData.fromMe)
console.log(
"--------> ticketData.status: ",
ticketData.status,
" | ticketData.fromMe: ",
ticketData.fromMe
);
const { ticket } = await UpdateTicketService({ const { ticket } = await UpdateTicketService({
ticketData, ticketData,
ticketId, ticketId
}); });
if (ticketData.status == "open" && !ticketData.fromMe) {
if (ticketData.status == 'open' && !ticketData.fromMe) {
await setMessageAsRead(ticket); await setMessageAsRead(ticket);
} }
console.log('ticket.unreadMessages: ', ticket.unreadMessages) console.log("ticket.unreadMessages: ", ticket.unreadMessages);
if (ticketData.userId) { if (ticketData.userId) {
const dateToday = splitDateTime(
const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }))) new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR }))
TicketEmiterSumOpenClosedByUser(ticketData.userId.toString(), dateToday.fullDate, dateToday.fullDate) );
TicketEmiterSumOpenClosedByUser(
ticketData.userId.toString(),
dateToday.fullDate,
dateToday.fullDate
);
} }
ticket2 = ticket ticket2 = ticket;
} }
if (userOldInfo) { if (userOldInfo) {
const dateToday = splitDateTime(
const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }))) new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR }))
);
if (userOldInfo.userId) { if (userOldInfo.userId) {
TicketEmiterSumOpenClosedByUser(
TicketEmiterSumOpenClosedByUser(userOldInfo.userId.toString(), dateToday.fullDate, dateToday.fullDate) userOldInfo.userId.toString(),
dateToday.fullDate,
dateToday.fullDate
);
} }
} }
return res.status(200).json(ticket2); return res.status(200).json(ticket2);
}; };
// export const update = async ( // export const update = async (
// req: Request, // req: Request,
// res: Response // res: Response
@ -341,7 +333,6 @@ export const update = async (req: Request, res: Response): Promise<Response> =>
// } // }
// } // }
// return res.status(200).json(ticket); // return res.status(200).json(ticket);
// }; // };
@ -354,10 +345,7 @@ export const remove = async (
const ticket = await DeleteTicketService(ticketId); const ticket = await DeleteTicketService(ticketId);
const io = getIO(); const io = getIO();
io.to(ticket.status) io.to(ticket.status).to(ticketId).to("notification").emit("ticket", {
.to(ticketId)
.to("notification")
.emit("ticket", {
action: "delete", action: "delete",
ticketId: +ticketId ticketId: +ticketId
}); });
@ -371,4 +359,3 @@ export const remove = async (
// await endPointQuery(`${wbot_url}/api/sendSeen`, { number: `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us` }); // await endPointQuery(`${wbot_url}/api/sendSeen`, { number: `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us` });
// } // }

View File

@ -17,6 +17,7 @@ import UserOnlineTime from "../models/UserOnlineTime";
import Dialogflow from "../models/Dialogflow"; import Dialogflow from "../models/Dialogflow";
import QueryItem from "../models/QueryItem"; import QueryItem from "../models/QueryItem";
import SettingTicket from "../models/SettingTicket";
// eslint-disable-next-line // eslint-disable-next-line
const dbConfig = require("../config/database"); const dbConfig = require("../config/database");
// import dbConfig from "../config/database"; // import dbConfig from "../config/database";
@ -40,7 +41,8 @@ const models = [
StatusChatEnd, StatusChatEnd,
UserOnlineTime, UserOnlineTime,
Dialogflow, Dialogflow,
QueryItem QueryItem,
SettingTicket
]; ];
sequelize.addModels(models); sequelize.addModels(models);

View File

@ -0,0 +1,46 @@
import { QueryInterface, DataTypes } from "sequelize"
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.createTable("SettingTickets", {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false
},
message: {
type: DataTypes.STRING,
allowNull: true
},
startTime: {
type: DataTypes.DATE,
allowNull: true
},
endTime: {
type: DataTypes.DATE,
allowNull: true
},
value: {
type: DataTypes.STRING,
allowNull: false
},
key: {
type: DataTypes.STRING,
allowNull: false
},
createdAt: {
type: DataTypes.DATE,
allowNull: false
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false
}
});
},
down: (queryInterface: QueryInterface) => {
return queryInterface.dropTable("SettingTickets");
}
}

View File

@ -0,0 +1,22 @@
import { QueryInterface } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.bulkInsert(
"Settings",
[
{
key: "oneContactChatWithManyWhats",
value: "enabled",
createdAt: new Date(),
updatedAt: new Date()
}
],
{}
);
},
down: (queryInterface: QueryInterface) => {
return queryInterface.bulkDelete("Settings", {});
}
};

View File

@ -0,0 +1,34 @@
import { QueryInterface } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.bulkInsert(
"SettingTickets",
[
{
message: "",
startTime: new Date(),
endTime: new Date(),
value: "disabled",
key: "outBusinessHours",
createdAt: new Date(),
updatedAt: new Date()
},
{
message: "",
startTime: new Date(),
endTime: new Date(),
value: "disabled",
key: "ticketExpiration",
createdAt: new Date(),
updatedAt: new Date()
}
],
{}
);
},
down: (queryInterface: QueryInterface) => {
return queryInterface.bulkDelete("SettingTickets", {});
}
};

View File

@ -0,0 +1,34 @@
import { QueryInterface } from "sequelize"
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.bulkInsert(
"SettingTickets",
[
{
message: "",
startTime: new Date(),
endTime: new Date(),
value: "disabled",
key: "saturday",
createdAt: new Date(),
updatedAt: new Date()
},
{
message: "",
startTime: new Date(),
endTime: new Date(),
value: "disabled",
key: "sunday",
createdAt: new Date(),
updatedAt: new Date()
}
],
{}
);
},
down: (queryInterface: QueryInterface) => {
return queryInterface.bulkDelete("SettingTickets", {})
}
}

View File

@ -0,0 +1,25 @@
import { QueryInterface } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.bulkInsert(
"SettingTickets",
[
{
message: "",
startTime: new Date(),
endTime: new Date(),
value: "disabled",
key: "holiday",
createdAt: new Date(),
updatedAt: new Date()
},
],
{}
);
},
down: (queryInterface: QueryInterface) => {
return queryInterface.bulkDelete("SettingTickets", {});
}
};

View File

@ -0,0 +1,25 @@
import { QueryInterface } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.bulkInsert(
"SettingTickets",
[
{
message: "",
startTime: new Date(),
endTime: new Date(),
value: "disabled",
key: "weekend",
createdAt: new Date(),
updatedAt: new Date()
}
],
{}
);
},
down: (queryInterface: QueryInterface) => {
return queryInterface.bulkDelete("SettingTickets", {});
}
};

View File

@ -0,0 +1,84 @@
import SettingTicket from "../models/SettingTicket";
import ListTicketTimeLife from "../services/TicketServices/ListTicketTimeLife";
import UpdateTicketService from "../services/TicketServices/UpdateTicketService";
import BotIsOnQueue from "./BotIsOnQueue";
import {
format as _format,
isWithinInterval,
parse,
subMinutes
} from "date-fns";
import ptBR from "date-fns/locale/pt-BR";
import { splitDateTime } from "./SplitDateTime";
const fsPromises = require("fs/promises");
const fs = require("fs");
let timer: any;
const AutoCloseTickets = async () => {
try {
// const botInfo = await BotIsOnQueue('botqueue')
// if (!botInfo.userIdBot) return
const ticketExpiration = await SettingTicket.findOne({
where: { key: "ticketExpiration" }
});
if (ticketExpiration && ticketExpiration.value == "enabled") {
const startTime = splitDateTime(
new Date(
_format(new Date(ticketExpiration.startTime), "yyyy-MM-dd HH:mm:ss", {
locale: ptBR
})
)
);
const seconds = timeStringToSeconds(startTime.fullTime);
// console.log("Ticket seconds: ", seconds);
let tickets: any = await ListTicketTimeLife({
timeseconds: seconds,
status: "open"
});
// console.log("tickets: ", tickets);
for (let i = 0; i < tickets.length; i++) {
await UpdateTicketService({
ticketData: { status: "closed", statusChatEnd: "FINALIZADO" },
ticketId: tickets[i].ticket_id,
msg: ticketExpiration.message
});
}
}
} catch (error) {
console.log("There was an error on try close the bot tickets: ", error);
}
};
function timeStringToSeconds(timeString: any) {
const [hours, minutes, seconds] = timeString.split(":").map(Number);
return hours * 3600 + minutes * 60 + seconds;
}
const schedule = async () => {
try {
clearInterval(timer);
await AutoCloseTickets();
} catch (error) {
console.log("error on schedule: ", error);
} finally {
timer = setInterval(schedule, 60000);
}
};
timer = setInterval(schedule, 60000);
export default schedule;

View File

@ -1,11 +1,32 @@
import { Op } from "sequelize"; import { Op } from "sequelize";
import AppError from "../errors/AppError"; import AppError from "../errors/AppError";
import Ticket from "../models/Ticket"; import Ticket from "../models/Ticket";
import ListWhatsAppsNumber from "../services/WhatsappService/ListWhatsAppsNumber";
import { getSettingValue } from "./WhaticketSettings";
const CheckContactOpenTickets = async (contactId: number): Promise<void> => { const CheckContactOpenTickets = async (
const ticket = await Ticket.findOne({ contactId: number,
whatsappId: number | string
): Promise<void> => {
let ticket;
if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") {
let whats = await ListWhatsAppsNumber(whatsappId);
ticket = await Ticket.findOne({
where: {
[Op.and]: [
{ contactId: contactId },
{ whatsappId: { [Op.in]: whats.whatsapps.map((w: any) => w.id) } },
{ status: { [Op.or]: ["open", "pending"] } }
]
}
});
} else {
ticket = await Ticket.findOne({
where: { contactId, status: { [Op.or]: ["open", "pending"] } } where: { contactId, status: { [Op.or]: ["open", "pending"] } }
}); });
}
if (ticket) { if (ticket) {
throw new AppError("ERR_OTHER_OPEN_TICKET"); throw new AppError("ERR_OTHER_OPEN_TICKET");

View File

@ -0,0 +1,27 @@
import Setting from "../models/Setting";
import fs from "fs";
import dir from "path";
import os from "os";
async function loadSettings() {
const setting = await Setting.findAll({});
if (setting) {
let sett = setting.map((s: any) => {
return { key: s.key, value: s.value };
});
try {
const keyFilename = dir.join(os.tmpdir(), `whaticket_settings.json`);
const jsonSettings = JSON.stringify(sett, null, 2);
fs.writeFileSync(keyFilename, jsonSettings);
console.log(`Settings saved to ${keyFilename}`);
} catch (err) {
console.error("Error saving Settings to file:", err);
}
}
}
export default loadSettings;

View File

@ -0,0 +1,182 @@
import SettingTicket from "../models/SettingTicket";
import { splitDateTime } from "./SplitDateTime";
import { utcToZonedTime, zonedTimeToUtc } from "date-fns-tz";
import {
format as _format,
isWithinInterval,
parse,
subMinutes,
isSaturday,
isSunday,
parseISO
} from "date-fns";
import ptBR from "date-fns/locale/pt-BR";
const isHoliday = async () => {
let obj = { set: false, msg: "" };
const holiday = await SettingTicket.findOne({
where: { key: "holiday" }
});
if (
holiday &&
holiday.value == "enabled" &&
holiday.message?.trim()?.length > 0
) {
const startTime = splitDateTime(
new Date(
_format(new Date(holiday.startTime), "yyyy-MM-dd HH:mm:ss", {
locale: ptBR
})
)
);
const currentDate = splitDateTime(
new Date(
_format(new Date(), "yyyy-MM-dd HH:mm:ss", {
locale: ptBR
})
)
);
if (currentDate.fullDate == startTime.fullDate) {
obj.set = true;
obj.msg = holiday.message.trim();
}
}
return obj;
};
const isWeekend = async () => {
let obj = { set: false, msg: "" };
const weekend = await SettingTicket.findOne({
where: { key: "weekend" }
});
if (
weekend &&
weekend.value == "enabled" &&
weekend.message?.trim()?.length > 0
) {
// Specify your desired timezone
const brazilTimeZone = "America/Sao_Paulo";
const currentDateUtc = new Date();
// Convert UTC date to Brazil time zone
const currentDate = utcToZonedTime(currentDateUtc, brazilTimeZone);
// Format the date using the desired format
const formattedDate = _format(currentDate, "yyyy-MM-dd HH:mm:ssXXX");
const parsedDate = parseISO(formattedDate);
// Convert parsed date to Brazil time zone
const localDate = utcToZonedTime(parsedDate, brazilTimeZone);
// Check if it's Saturday or Sunday
if (isSaturday(localDate)) {
const saturday = await SettingTicket.findOne({
where: { key: "saturday" }
});
if (saturday && saturday.value == "enabled") {
// botSendMessage(ticket, weekend.message);
obj.set = true;
obj.msg = weekend.message;
}
} else if (isSunday(localDate)) {
const sunday = await SettingTicket.findOne({
where: { key: "sunday" }
});
if (sunday && sunday.value == "enabled") {
// botSendMessage(ticket, weekend.message);
obj.set = true;
obj.msg = weekend.message;
}
}
else{
// obj.set = true;
// obj.msg = weekend.message;
}
return obj;
}
};
async function isOutBusinessTime() {
let obj = { set: false, msg: "" };
const outBusinessHours = await SettingTicket.findOne({
where: { key: "outBusinessHours" }
});
let isWithinRange = false;
if (
outBusinessHours &&
outBusinessHours.value == "enabled" &&
outBusinessHours?.message?.trim()?.length > 0
) {
const ticketDateTimeUpdate = splitDateTime(
new Date(
_format(new Date(), "yyyy-MM-dd HH:mm:ss", {
locale: ptBR
})
)
);
const startTime = splitDateTime(
new Date(
_format(new Date(outBusinessHours.startTime), "yyyy-MM-dd HH:mm:ss", {
locale: ptBR
})
)
);
const endTime = splitDateTime(
new Date(
_format(new Date(outBusinessHours.endTime), "yyyy-MM-dd HH:mm:ss", {
locale: ptBR
})
)
);
const format = "HH:mm:ss";
const parsedStartTime = parse(
ticketDateTimeUpdate.fullTime,
format,
new Date()
);
const parsedEndTime = parse(startTime.fullTime, format, new Date());
const parsedTimeToCheck = parse(endTime.fullTime, format, new Date());
const timeInterval = { start: parsedStartTime, end: parsedEndTime };
// If the time range spans across different days, handle the date part
if (parsedEndTime < parsedStartTime) {
const nextDay = new Date(parsedStartTime);
nextDay.setDate(nextDay.getDate() + 1);
timeInterval.end = nextDay;
}
isWithinRange = isWithinInterval(parsedTimeToCheck, timeInterval);
if (!isWithinRange) {
obj.set = true;
obj.msg = outBusinessHours.message;
}
}
return obj;
}
export {
isWeekend,
isHoliday,
isOutBusinessTime
};

View File

@ -0,0 +1,18 @@
import fs from "fs";
import dir from "path";
import os from "os";
export function getSettingValue(key: any) {
let value: any = {};
try {
const keyFilename = dir.join(os.tmpdir(), `whaticket_settings.json`);
const jsonData = fs.readFileSync(keyFilename, "utf8");
const settings = JSON.parse(jsonData);
value = settings.find((s: any) => s.key === key);
} catch (err) {
console.error("Error reading or parsing data from file:", err);
}
return value;
}

View File

@ -3,37 +3,44 @@ import { Server } from "http";
import AppError from "../errors/AppError"; import AppError from "../errors/AppError";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from "uuid";
import ListUserParamiterService from "../services/UserServices/ListUserParamiterService"; import ListUserParamiterService from "../services/UserServices/ListUserParamiterService";
import { addHours, addMinutes, addSeconds, intervalToDuration, add } from "date-fns"; import {
addHours,
addMinutes,
addSeconds,
intervalToDuration,
add
} from "date-fns";
let io: SocketIO; let io: SocketIO;
//test del //test del
import createOrUpdateOnlineUserService from "../services/UserServices/CreateOrUpdateOnlineUserService"; import createOrUpdateOnlineUserService from "../services/UserServices/CreateOrUpdateOnlineUserService";
import { splitDateTime } from "../helpers/SplitDateTime"; import { splitDateTime } from "../helpers/SplitDateTime";
import format from 'date-fns/format'; import format from "date-fns/format";
import ptBR from 'date-fns/locale/pt-BR'; import ptBR from "date-fns/locale/pt-BR";
import ListUserOnlineOffline from "../services/UserServices/ListUsersOnlineOfflineService"; import ListUserOnlineOffline from "../services/UserServices/ListUsersOnlineOfflineService";
import { handleMessage, handleMsgAck } from "../services/WbotServices/wbotMessageListener"; import {
handleMessage,
handleMsgAck
} from "../services/WbotServices/wbotMessageListener";
import { join } from "path"; import { join } from "path";
import Whatsapp from "../models/Whatsapp"; import Whatsapp from "../models/Whatsapp";
let count: number = 0 let count: number = 0;
let listOnline: any[] = [] let listOnline: any[] = [];
let listOnlineAux: any[] = [] let listOnlineAux: any[] = [];
let countOnline: number = 0 let countOnline: number = 0;
let obj: any = { listOnline: [], uuid: null, listOnlineAux: [] } let obj: any = { listOnline: [], uuid: null, listOnlineAux: [] };
let lstOnline: any[] = [];
let lstOnlineAux: any[] = [];
let lstTry: any[] = [];
let dateTime = splitDateTime(
new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR }))
let lstOnline: any[] = [] );
let lstOnlineAux: any[] = []
let lstTry: any[] = []
let dateTime = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR })))
export const initIO = (httpServer: Server): SocketIO => { export const initIO = (httpServer: Server): SocketIO => {
io = new SocketIO(httpServer, { io = new SocketIO(httpServer, {
@ -43,7 +50,6 @@ export const initIO = (httpServer: Server): SocketIO => {
maxHttpBufferSize: 1e8 maxHttpBufferSize: 1e8
}); });
io.on("connection", socket => { io.on("connection", socket => {
logger.info("Client Connected"); logger.info("Client Connected");
@ -53,9 +59,8 @@ export const initIO = (httpServer: Server): SocketIO => {
}); });
socket.on("message_from_client", () => { socket.on("message_from_client", () => {
socket.emit("message_from_server", "Sent an event from the server!");
socket.emit('message_from_server', 'Sent an event from the server!'); });
})
socket.on("message_create", async (data: any) => { socket.on("message_create", async (data: any) => {
@ -65,154 +70,141 @@ export const initIO = (httpServer: Server): SocketIO => {
}); });
socket.on("media_uploaded", async (data: any) => { socket.on("media_uploaded", async (data: any) => {
handleMessage(data.msg, data); handleMessage(data.msg, data);
}); });
socket.on("message_ack", async (data: any) => { socket.on("message_ack", async (data: any) => {
handleMsgAck(data.id, data.ack);
handleMsgAck(data.id, data.ack)
}); });
socket.on("campaign_message_sent", async (data: any) => {
// console.log("campaign_message_sent: ", data);
const io = getIO();
let campaign = {};
if (data?.read) {
campaign = {
id: data.id,
read: data.read
};
} else if (data?.sent) {
campaign = {
id: data.id,
sent: data.sent
};
} else if (data?.status) {
campaign = {
id: data.id,
status: data.status
};
}
io.emit("campaign", {
action: "update",
campaign
});
});
socket.on("online", (userId: any) => { socket.on("online", (userId: any) => {
// console.log('userId: ', userId) // console.log('userId: ', userId)
obj.uuid = uuidv4() obj.uuid = uuidv4();
if (userId.logoutUserId) { if (userId.logoutUserId) {
let index = lstOnline.findIndex(
let index = lstOnline.findIndex((x: any) => x.id == userId.logoutUserId) (x: any) => x.id == userId.logoutUserId
);
if (index != -1) { if (index != -1) {
lstOnline.splice(index, 1);
lstOnline.splice(index, 1)
} }
index = lstOnlineAux.findIndex((x: any) => x.id == userId.logoutUserId);
index = lstOnlineAux.findIndex((x: any) => x.id == userId.logoutUserId)
if (index != -1) { if (index != -1) {
lstOnlineAux.splice(index, 1);
lstOnlineAux.splice(index, 1)
} }
return return;
} }
if (lstOnline.length == 0) { if (lstOnline.length == 0) {
const index = listOnlineAux.findIndex((e: any) => e.id == userId);
const index = listOnlineAux.findIndex((e: any) => e.id == userId)
if (index == -1) { if (index == -1) {
listOnlineAux.push({ 'id': userId }) listOnlineAux.push({ id: userId });
} } else {
else { return;
return
} }
lstOnline.push({ 'id': userId, 'status': 'online', 'try': 0 }) lstOnline.push({ id: userId, status: "online", try: 0 });
lstOnlineAux.push({ 'id': userId }) lstOnlineAux.push({ id: userId });
console.log(' 1 PUSHED NEW USER ID 1: ', userId) console.log(" 1 PUSHED NEW USER ID 1: ", userId);
obj.listOnline = lstOnline obj.listOnline = lstOnline;
} else {
} const indexAux = lstOnlineAux.findIndex((e: any) => e.id == userId);
else {
const indexAux = lstOnlineAux.findIndex((e: any) => e.id == userId)
if (indexAux == -1) { if (indexAux == -1) {
lstOnlineAux.push({ 'id': userId }) lstOnlineAux.push({ id: userId });
} }
const index = lstOnline.findIndex((e: any) => e.id == userId) const index = lstOnline.findIndex((e: any) => e.id == userId);
if (index == -1) { if (index == -1) {
lstOnline.push({ id: userId, status: "online", try: 0 });
console.log(" 2 PUSHED NEW USER ID: ", userId);
lstOnline.push({ 'id': userId, 'status': 'online', 'try': 0 }) obj.listOnline = lstOnline;
} else {
if (countOnline > lstOnline.length - 1) {
console.log(' 2 PUSHED NEW USER ID: ', userId)
obj.listOnline = lstOnline
}
else {
if (countOnline > (lstOnline.length - 1)) {
lstOnline.forEach((x: any) => { lstOnline.forEach((x: any) => {
if (lstOnlineAux.map((e: any) => e.id).includes(x.id)) { if (lstOnlineAux.map((e: any) => e.id).includes(x.id)) {
x.try = 0 x.try = 0;
x.status = 'online' x.status = "online";
} }
}) });
var difference = lstOnline.filter((x: any) => !lstOnlineAux.map((e: any) => e.id).includes(x.id));
var difference = lstOnline.filter(
(x: any) => !lstOnlineAux.map((e: any) => e.id).includes(x.id)
);
if (difference.length > 0) { if (difference.length > 0) {
difference.forEach(e => {
difference.forEach((e) => { e.try += 1;
e.try += 1
if (e.try > 1) { if (e.try > 1) {
e.status = 'waiting...' e.status = "waiting...";
} }
if (e.try > 3) { if (e.try > 3) {
const index = lstOnline.findIndex((x: any) => x.id == e.id);
const index = lstOnline.findIndex((x: any) => x.id == e.id)
if (index != -1) { if (index != -1) {
lstOnline.splice(index, 1);
}
}
});
}
obj.listOnline = lstOnline;
obj.listOnlineAux = lstOnlineAux;
lstOnline.splice(index, 1) lstOnlineAux = [];
listOnlineAux = [];
countOnline = -1;
}
countOnline++;
} }
} }
}) exports.ob = obj;
}
obj.listOnline = lstOnline
obj.listOnlineAux = lstOnlineAux
lstOnlineAux = []
listOnlineAux = []
countOnline = -1
}
countOnline++
}
}
exports.ob = obj
}); });
socket.on("joinChatBox", (ticketId: string) => { socket.on("joinChatBox", (ticketId: string) => {
@ -235,49 +227,51 @@ export const initIO = (httpServer: Server): SocketIO => {
}); });
socket.on("disconnecting", async () => { socket.on("disconnecting", async () => {
console.log('socket.rooms: ', socket.rooms); // the Set contains at least the socket ID console.log("socket.rooms: ", socket.rooms); // the Set contains at least the socket ID
let rooms = socket.rooms let rooms = socket.rooms;
console.log('rooms: ', rooms, ' | rooms.size: ', rooms.size) console.log("rooms: ", rooms, " | rooms.size: ", rooms.size);
if(rooms && rooms.size==1) return if (rooms && rooms.size == 1) return;
if(rooms && rooms.size==2 && !([...rooms][1].startsWith('session_'))) return if (rooms && rooms.size == 2 && ![...rooms][1].startsWith("session_"))
return;
let whatsappIds: any = await Whatsapp.findAll({ attributes: ['id'], raw: true }) let whatsappIds: any = await Whatsapp.findAll({
attributes: ["id"],
raw: true
});
if (whatsappIds && whatsappIds.length > 0) { if (whatsappIds && whatsappIds.length > 0) {
whatsappIds = whatsappIds.map((e: any) => `${e.id}`);
whatsappIds = whatsappIds.map((e: any) => `${e.id}`) console.log(
"whatsappIds whatsappIds whatsappIds whatsappIds whatsappIds: ",
whatsappIds
);
console.log('whatsappIds whatsappIds whatsappIds whatsappIds whatsappIds: ',whatsappIds) if (
rooms &&
rooms.size == 2 &&
[...rooms][1].startsWith("session_") &&
whatsappIds.includes([...rooms][1].replace("session_", ""))
) {
console.log([...rooms][1]);
if (rooms && rooms.size == 2 && let whatsappId = [...rooms][1].replace("session_", "");
[...rooms][1].startsWith('session_') &&
whatsappIds.includes([...rooms][1].replace('session_', ''))) {
console.log([...rooms][1]) const whatsapp = await Whatsapp.findByPk(whatsappId, {});
let whatsappId = [...rooms][1].replace('session_', '')
const whatsapp = await Whatsapp.findByPk(whatsappId, {})
if (whatsapp) { if (whatsapp) {
await whatsapp.update({ status: "OPENING" });
await whatsapp.update({ status: 'OPENING' });
io.emit("whatsappSession", { io.emit("whatsappSession", {
action: "update", action: "update",
session: whatsapp session: whatsapp
}); });
} }
} }
} }
}); });
}); });
return io; return io;
@ -290,12 +284,9 @@ export const getIO = (): SocketIO => {
return io; return io;
}; };
function writeFileAsync(arg0: any, data: any, arg2: string) { function writeFileAsync(arg0: any, data: any, arg2: string) {
throw new Error("Function not implemented."); throw new Error("Function not implemented.");
} }
// exports.listOnlineUsers = listUserId // exports.listOnlineUsers = listUserId
// exports.listUserId // exports.listUserId

View File

@ -0,0 +1,40 @@
import {
Table,
Column,
CreatedAt,
UpdatedAt,
Model,
PrimaryKey,
AutoIncrement
} from "sequelize-typescript";
@Table
class SettingTicket extends Model<SettingTicket> {
@PrimaryKey
@AutoIncrement
@Column
id: number;
@Column
message: string;
@Column
startTime: Date;
@Column
endTime: Date;
@Column
value: string;
@Column
key: string;
@CreatedAt
createdAt: Date;
@UpdatedAt
updatedAt: Date;
}
export default SettingTicket;

View File

@ -9,7 +9,15 @@ settingRoutes.get("/settings", SettingController.index);
// routes.get("/settings/:settingKey", isAuth, SettingsController.show); // routes.get("/settings/:settingKey", isAuth, SettingsController.show);
settingRoutes.put(
"/settings/ticket",
isAuth,
SettingController.updateTicketSettings
);
// change setting key to key in future // change setting key to key in future
settingRoutes.put("/settings/:settingKey", isAuth, SettingController.update); settingRoutes.put("/settings/:settingKey", isAuth, SettingController.update);
export default settingRoutes; export default settingRoutes;

View File

@ -12,16 +12,22 @@ import { loadContactsCache } from "./helpers/ContactsCache";
import "./helpers/CloseBotTickets"; import "./helpers/CloseBotTickets";
import { loadSchedulesCache } from "./helpers/SchedulingNotifyCache"; import { loadSchedulesCache } from "./helpers/SchedulingNotifyCache";
import { delRestoreControllFile } from "./helpers/RestoreControll"; import { delRestoreControllFile } from "./helpers/RestoreControll";
import "./helpers/AutoCloseTickets";
import "./helpers/SchedulingNotifySendMessage" import "./helpers/SchedulingNotifySendMessage";
import axios from "axios"; import axios from "axios";
import os from 'os'; import os from "os";
import Setting from "./models/Setting";
import fs from "fs";
import dir from "path";
import { getSettingValue } from "./helpers/WhaticketSettings";
import loadSettings from "./helpers/LoadSettings";
const server = app.listen(process.env.PORT, () => { const server = app.listen(process.env.PORT, () => {
logger.info(`Server started on port: ${process.env.PORT}`); logger.info(`Server started on port: ${process.env.PORT}`);
}); });
// if (global.gc) { // if (global.gc) {
// console.log(">> Starting Garbage Collector..."); // console.log(">> Starting Garbage Collector...");
// global.gc(); // global.gc();
@ -34,99 +40,84 @@ initIO(server);
// StartAllWhatsAppsSessions(); // StartAllWhatsAppsSessions();
gracefulShutdown(server); gracefulShutdown(server);
(async () => { (async () => {
console.log("os.tmpdir(): ", os.tmpdir());
console.log('os.tmpdir(): ', os.tmpdir()) loadSettings();
let whatsapps: any = await Whatsapp.findAll({ attributes: ['id', 'url'] }) let whatsapps: any = await Whatsapp.findAll({ attributes: ["id", "url"] });
// console.log('whatsapps: ', whatsapps) // console.log('whatsapps: ', whatsapps)
if (whatsapps && whatsapps.length > 0) { if (whatsapps && whatsapps.length > 0) {
for (let i = 0; i < whatsapps.length; i++) { for (let i = 0; i < whatsapps.length; i++) {
try { try {
console.log(
`API URL: ${whatsapps[i].dataValues.url}/api/connection/status`
);
console.log(`API URL: ${whatsapps[i].dataValues.url}/api/connection/status`) const response = await axios.get(
`${whatsapps[i].dataValues.url}/api/connection/status`,
{}
);
const response = await axios.get(`${whatsapps[i].dataValues.url}/api/connection/status`, {}); console.log(`-------> Response: ${response.data.data}`);
console.log(`-------> Response: ${response.data.data}`)
if (!response) { if (!response) {
throw new Error('Response null'); throw new Error("Response null");
} }
if (response.data.data && response.data.data == 'CONNECTED') { if (response.data.data && response.data.data == "CONNECTED") {
await whatsapps[i].update({ status: 'CONNECTED' }); await whatsapps[i].update({ status: "CONNECTED" });
} }
} catch (error: any) { } catch (error: any) {
await whatsapps[i].update({ status: "OPENING" });
await whatsapps[i].update({ status: 'OPENING' }); console.log(
`There was an error on try acess the api sessions ${whatsapps[i].dataValues.url}`
console.log(`There was an error on try acess the api sessions ${whatsapps[i].dataValues.url}`) );
} }
await new Promise(f => setTimeout(f, 100)); await new Promise(f => setTimeout(f, 100));
} }
} }
if (process.env.CACHE) { if (process.env.CACHE) {
const cacheLength = await cacheSize();
const cacheLength = await cacheSize() console.log("cacheSize: ", cacheLength);
console.log('cacheSize: ', cacheLength)
if (cacheLength == 0) { if (cacheLength == 0) {
console.log('Loading from cache...') console.log("Loading from cache...");
await flushCache() await flushCache();
await loadContactsCache() await loadContactsCache();
await loadTicketsCache() await loadTicketsCache();
} }
await loadSchedulesCache() await loadSchedulesCache();
// await loadWhatsappCache() // await loadWhatsappCache()
} }
delRestoreControllFile() delRestoreControllFile();
})() })();
setTimeout(async () => { setTimeout(async () => {
const io = getIO(); const io = getIO();
console.log('Triggered socket!') console.log("Triggered socket!");
let users = await User.findAll({ raw: true, attributes: ["id"], }) let users = await User.findAll({ raw: true, attributes: ["id"] });
if (users && users.length > 0) { if (users && users.length > 0) {
for (let i = 0; i < users.length; i++) { for (let i = 0; i < users.length; i++) {
io.emit("reload_page", { io.emit("reload_page", {
action: "update", action: "update",
userId: users[i].id userId: users[i].id
}); });
console.log('USER ID: ', users[i].id) console.log("USER ID: ", users[i].id);
await new Promise(f => setTimeout(f, 100)); await new Promise(f => setTimeout(f, 100));
} }
} }
}, 5000);
}, 5000)

View File

@ -0,0 +1,35 @@
import AppError from "../../errors/AppError";
import SettingTicket from "../../models/SettingTicket";
interface Request {
key: string;
startTime: string;
endTime: string;
value: string;
message: string;
}
const updateSettingTicket = async ({
key,
startTime,
endTime,
value,
message
}: Request): Promise<SettingTicket | undefined> => {
try {
const businessHours = await SettingTicket.findOne({ where: { key } });
if (!businessHours) {
throw new AppError("ERR_NO_SETTING_FOUND", 404);
}
await businessHours.update({ startTime, endTime, message, value });
return businessHours;
} catch (error: any) {
console.error("===> Error on UpdateSettingService.ts file: \n", error);
throw new AppError(error.message);
}
};
export default updateSettingTicket;

View File

@ -35,8 +35,9 @@ const CreateTicketService = async ({
try { try {
const defaultWhatsapp = await GetDefaultWhatsApp(userId); const defaultWhatsapp = await GetDefaultWhatsApp(userId);
if (!queueId) {
const user = await User.findByPk(userId, { raw: true }); const user = await User.findByPk(userId, { raw: true });
if (!queueId) {
const matchingQueue = await whatsappQueueMatchingUserQueue( const matchingQueue = await whatsappQueueMatchingUserQueue(
userId, userId,
defaultWhatsapp, defaultWhatsapp,
@ -45,7 +46,7 @@ const CreateTicketService = async ({
queueId = matchingQueue ? matchingQueue.queueId : undefined; queueId = matchingQueue ? matchingQueue.queueId : undefined;
} }
await CheckContactOpenTickets(contactId); await CheckContactOpenTickets(contactId, defaultWhatsapp.id);
const { isGroup } = await ShowContactService(contactId); const { isGroup } = await ShowContactService(contactId);

View File

@ -6,7 +6,8 @@ import Ticket from "../../models/Ticket";
import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService";
import ShowTicketService from "./ShowTicketService"; import ShowTicketService from "./ShowTicketService";
import AppError from "../../errors/AppError"; import AppError from "../../errors/AppError";
import ListWhatsAppsNumber from "../WhatsappService/ListWhatsAppsNumber";
import { getSettingValue } from "../../helpers/WhaticketSettings";
const FindOrCreateTicketService = async ( const FindOrCreateTicketService = async (
contact: Contact, contact: Contact,
@ -14,10 +15,23 @@ const FindOrCreateTicketService = async (
unreadMessages: number, unreadMessages: number,
groupContact?: Contact groupContact?: Contact
): Promise<Ticket> => { ): Promise<Ticket> => {
try { try {
let ticket;
let ticket = await Ticket.findOne({ if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") {
let whats = await ListWhatsAppsNumber(whatsappId);
ticket = await Ticket.findOne({
where: {
status: {
[Op.or]: ["open", "pending", "queueChoice"]
},
contactId: groupContact ? groupContact.id : contact.id,
whatsappId: { [Op.in]: whats.whatsapps.map((w: any) => w.id) }
}
});
} else {
ticket = await Ticket.findOne({
where: { where: {
status: { status: {
[Op.or]: ["open", "pending", "queueChoice"] [Op.or]: ["open", "pending", "queueChoice"]
@ -25,10 +39,10 @@ const FindOrCreateTicketService = async (
contactId: groupContact ? groupContact.id : contact.id contactId: groupContact ? groupContact.id : contact.id
} }
}); });
}
const { queues, greetingMessage } = await ShowWhatsAppService(whatsappId); const { queues, greetingMessage } = await ShowWhatsAppService(whatsappId);
//Habilitar esse caso queira usar o bot //Habilitar esse caso queira usar o bot
const botInfo = await BotIsOnQueue('botqueue') const botInfo = await BotIsOnQueue('botqueue')
// const botInfo = { isOnQueue: false } // const botInfo = { isOnQueue: false }
@ -46,10 +60,7 @@ const FindOrCreateTicketService = async (
order: [["updatedAt", "DESC"]] order: [["updatedAt", "DESC"]]
}); });
if (ticket) { if (ticket) {
await ticket.update({ await ticket.update({
status: "pending", status: "pending",
userId: null, userId: null,
@ -59,7 +70,6 @@ const FindOrCreateTicketService = async (
} }
if (!ticket && !groupContact) { if (!ticket && !groupContact) {
ticket = await Ticket.findOne({ ticket = await Ticket.findOne({
where: { where: {
updatedAt: { updatedAt: {
@ -77,7 +87,6 @@ const FindOrCreateTicketService = async (
}); });
if (ticket) { if (ticket) {
await ticket.update({ await ticket.update({
status: "pending", status: "pending",
userId: null, userId: null,
@ -87,11 +96,10 @@ const FindOrCreateTicketService = async (
} }
if (!ticket) { if (!ticket) {
let status = "pending";
let status = "pending"
if (queues.length > 1 && !botInfo.isOnQueue) { if (queues.length > 1 && !botInfo.isOnQueue) {
status = "queueChoice" status = "queueChoice";
} }
ticket = await Ticket.create({ ticket = await Ticket.create({
@ -113,9 +121,8 @@ const FindOrCreateTicketService = async (
ticket = await ShowTicketService(ticket.id); ticket = await ShowTicketService(ticket.id);
return ticket; return ticket;
} catch (error: any) { } catch (error: any) {
console.error('===> Error on FindOrCreateTicketService.ts file: \n', error) console.error("===> Error on FindOrCreateTicketService.ts file: \n", error);
throw new AppError(error.message); throw new AppError(error.message);
} }
}; };

View File

@ -6,12 +6,10 @@ import SendWhatsAppMessage from "../WbotServices/SendWhatsAppMessage";
import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService";
import ShowTicketService from "./ShowTicketService"; import ShowTicketService from "./ShowTicketService";
import { createOrUpdateTicketCache } from '../../helpers/TicketCache' import { createOrUpdateTicketCache } from "../../helpers/TicketCache";
import AppError from "../../errors/AppError"; import AppError from "../../errors/AppError";
import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket"; import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket";
var flatten = require('flat') var flatten = require("flat");
interface TicketData { interface TicketData {
status?: string; status?: string;
@ -19,12 +17,13 @@ interface TicketData {
queueId?: number; queueId?: number;
statusChatEnd?: string; statusChatEnd?: string;
unreadMessages?: number; unreadMessages?: number;
whatsappId?: string | number;
} }
interface Request { interface Request {
ticketData: TicketData; ticketData: TicketData;
ticketId: string | number; ticketId: string | number;
msg?: string msg?: string;
} }
interface Response { interface Response {
@ -36,12 +35,17 @@ interface Response {
const UpdateTicketService = async ({ const UpdateTicketService = async ({
ticketData, ticketData,
ticketId, ticketId,
msg='' msg = ""
}: Request): Promise<Response> => { }: Request): Promise<Response> => {
try { try {
const {
const { status, userId, queueId, statusChatEnd, unreadMessages } = ticketData; status,
userId,
queueId,
statusChatEnd,
unreadMessages,
whatsappId
} = ticketData;
const ticket = await ShowTicketService(ticketId); const ticket = await ShowTicketService(ticketId);
// await SetTicketMessagesAsRead(ticket); // await SetTicketMessagesAsRead(ticket);
@ -50,7 +54,7 @@ const UpdateTicketService = async ({
const oldUserId = ticket.user?.id; const oldUserId = ticket.user?.id;
if (oldStatus === "closed") { if (oldStatus === "closed") {
await CheckContactOpenTickets(ticket.contact.id); await CheckContactOpenTickets(ticket.contact.id, ticket.whatsappId);
} }
await ticket.update({ await ticket.update({
@ -58,36 +62,36 @@ const UpdateTicketService = async ({
queueId, queueId,
userId, userId,
unreadMessages, unreadMessages,
statusChatEnd statusChatEnd,
whatsappId
}); });
await ticket.reload(); await ticket.reload();
if (msg.length > 0) { if (msg?.trim().length > 0) {
setTimeout(async () => { setTimeout(async () => {
sendWhatsAppMessageSocket(ticket, msg);
sendWhatsAppMessageSocket(ticket, msg) }, 2000);
}, 2000)
} }
// TEST DEL // TEST DEL
try { try {
// const { name, number } = await ShowContactService(ticket.contactId) // const { name, number } = await ShowContactService(ticket.contactId)
let jsonString = JSON.stringify(ticket); //convert to string to remove the sequelize specific meta data let jsonString = JSON.stringify(ticket); //convert to string to remove the sequelize specific meta data
let ticket_obj = JSON.parse(jsonString); //to make plain json let ticket_obj = JSON.parse(jsonString); //to make plain json
delete ticket_obj['contact']['extraInfo'] delete ticket_obj["contact"]["extraInfo"];
delete ticket_obj['user'] delete ticket_obj["user"];
ticket_obj = flatten(ticket_obj) ticket_obj = flatten(ticket_obj);
await createOrUpdateTicketCache(`ticket:${ticket.id}`, ticket_obj)
await createOrUpdateTicketCache(`ticket:${ticket.id}`, ticket_obj);
} catch (error) { } catch (error) {
console.log('There was an error on UpdateTicketService.ts on createTicketCache: ', error) console.log(
"There was an error on UpdateTicketService.ts on createTicketCache: ",
error
);
} }
// //
@ -100,7 +104,6 @@ const UpdateTicketService = async ({
}); });
} }
io.to(ticket.status) io.to(ticket.status)
.to("notification") .to("notification")
.to(ticketId.toString()) .to(ticketId.toString())
@ -109,20 +112,16 @@ const UpdateTicketService = async ({
ticket ticket
}); });
io.emit("ticketStatus", { io.emit("ticketStatus", {
action: "update", action: "update",
ticketStatus: { ticketId: ticket.id, status: ticket.status } ticketStatus: { ticketId: ticket.id, status: ticket.status }
}); });
return { ticket, oldStatus, oldUserId }; return { ticket, oldStatus, oldUserId };
} catch (error: any) { } catch (error: any) {
console.error('===> Error on UpdateTicketService.ts file: \n', error) console.error("===> Error on UpdateTicketService.ts file: \n", error);
throw new AppError(error.message); throw new AppError(error.message);
} }
}; };
export default UpdateTicketService; export default UpdateTicketService;

View File

@ -1,40 +1,62 @@
import { Sequelize } from "sequelize";
import { Sequelize, } from "sequelize";
const dbConfig = require("../../config/database"); const dbConfig = require("../../config/database");
const sequelize = new Sequelize(dbConfig); const sequelize = new Sequelize(dbConfig);
const { QueryTypes } = require('sequelize'); const { QueryTypes } = require("sequelize");
interface Request { interface Request {
profile: string; profile?: string;
userId?: string | number; userId?: string | number;
} }
const QueuesByUser = async ({ profile, userId }: Request): Promise<any[]> => { const QueuesByUser = async ({ profile, userId }: Request): Promise<any[]> => {
let queueByUsersInfo: any = [];
let queueByUsersInfo = [] if (userId && profile) {
if (userId) {
// CONSULTANDO FILAS PELO ID DO USUARIO // CONSULTANDO FILAS PELO ID DO USUARIO
queueByUsersInfo = await sequelize.query(`select UserQueues.userId, UserQueues.queueId, Users.name, queueByUsersInfo = await sequelize.query(
`select UserQueues.userId, UserQueues.queueId, Users.name,
Queues.name, Queues.color from UserQueues inner join Users inner join Queues on Queues.name, Queues.color from UserQueues inner join Users inner join Queues on
UserQueues.queueId = Queues.id and UserQueues.userId = Users.id and Users.id = '${userId}' and Users.profile = '${profile}' order by userId, queueId;`, { type: QueryTypes.SELECT }); UserQueues.queueId = Queues.id and UserQueues.userId = Users.id and Users.id = '${userId}' and Users.profile = '${profile}' order by userId, queueId;`,
{ type: QueryTypes.SELECT }
} else { );
} else if (profile) {
// CONSULTANDO FILAS PELO USUARIO // CONSULTANDO FILAS PELO USUARIO
queueByUsersInfo = await sequelize.query(`select UserQueues.userId, UserQueues.queueId, Users.name, queueByUsersInfo = await sequelize.query(
`select UserQueues.userId, UserQueues.queueId, Users.name,
Queues.name, Queues.color from UserQueues inner join Users inner join Queues on Queues.name, Queues.color from UserQueues inner join Users inner join Queues on
UserQueues.queueId = Queues.id and UserQueues.userId = Users.id and Users.profile = '${profile}' order by userId, queueId;`, { type: QueryTypes.SELECT }); UserQueues.queueId = Queues.id and UserQueues.userId = Users.id and Users.profile = '${profile}' order by userId, queueId;`,
{ type: QueryTypes.SELECT }
);
} else if (userId) {
queueByUsersInfo = await sequelize.query(
`select UserQueues.userId, UserQueues.queueId, Users.name,
Queues.name, Queues.color from UserQueues inner join Users inner join Queues on
UserQueues.queueId = Queues.id and UserQueues.userId = Users.id and Users.id = '${userId}' order by userId, queueId;`,
{ type: QueryTypes.SELECT }
);
} }
// if (userId) {
// // CONSULTANDO FILAS PELO ID DO USUARIO
// queueByUsersInfo = await sequelize.query(
// `select UserQueues.userId, UserQueues.queueId, Users.name,
// Queues.name, Queues.color from UserQueues inner join Users inner join Queues on
// UserQueues.queueId = Queues.id and UserQueues.userId = Users.id and Users.id = '${userId}' and Users.profile = '${profile}' order by userId, queueId;`,
// { type: QueryTypes.SELECT }
// );
// } else {
// // CONSULTANDO FILAS PELO USUARIO
// queueByUsersInfo = await sequelize.query(
// `select UserQueues.userId, UserQueues.queueId, Users.name,
// Queues.name, Queues.color from UserQueues inner join Users inner join Queues on
// UserQueues.queueId = Queues.id and UserQueues.userId = Users.id and Users.profile = '${profile}' order by userId, queueId;`,
// { type: QueryTypes.SELECT }
// );
// }
return queueByUsersInfo; return queueByUsersInfo;
}; };
export default QueuesByUser; export default QueuesByUser;

View File

@ -7,7 +7,21 @@ import { copyFolder } from "../../helpers/CopyFolder";
import { removeDir } from "../../helpers/DeleteDirectory"; import { removeDir } from "../../helpers/DeleteDirectory";
import path from "path"; import path from "path";
import { format } from "date-fns"; import {
isHoliday,
isOutBusinessTime,
isWeekend
} from "../../helpers/TicketConfig";
import {
format as _format,
isWithinInterval,
parse,
subMinutes,
isSaturday,
isSunday,
parseISO
} from "date-fns";
import ptBR from "date-fns/locale/pt-BR"; import ptBR from "date-fns/locale/pt-BR";
import { import {
@ -37,6 +51,8 @@ import ShowQueueService from "../QueueService/ShowQueueService";
import ShowTicketMessage from "../TicketServices/ShowTicketMessage"; import ShowTicketMessage from "../TicketServices/ShowTicketMessage";
import BotIsOnQueue from "../../helpers/BotIsOnQueue"; import BotIsOnQueue from "../../helpers/BotIsOnQueue";
import Queue from "../../models/Queue"; import Queue from "../../models/Queue";
import SettingTicket from "../../models/SettingTicket";
import fs from "fs"; import fs from "fs";
@ -914,6 +930,19 @@ const handleMessage = async (msg: any, wbot: any): Promise<void> => {
// let groupContact: Contact | undefined; // let groupContact: Contact | undefined;
if (msg.fromMe) { if (msg.fromMe) {
const ticketExpiration = await SettingTicket.findOne({
where: { key: "ticketExpiration" }
});
if (
ticketExpiration &&
ticketExpiration.value == "enabled" &&
ticketExpiration?.message.trim() == msg.body.trim()
) {
console.log("*********** TICKET EXPIRATION");
return;
}
// console.log('FROM ME: ', msg.fromMe, ' | /\u200e/.test(msg.body[0]: ', (/\u200e/.test(msg.body[0]))) // console.log('FROM ME: ', msg.fromMe, ' | /\u200e/.test(msg.body[0]: ', (/\u200e/.test(msg.body[0])))
// messages sent automatically by wbot have a special character in front of it // messages sent automatically by wbot have a special character in front of it
@ -1206,6 +1235,54 @@ const handleMessage = async (msg: any, wbot: any): Promise<void> => {
if (msg && !msg.fromMe && ticket.status == "pending") { if (msg && !msg.fromMe && ticket.status == "pending") {
await setMessageAsRead(ticket); await setMessageAsRead(ticket);
} }
let ticketHasQueue = false;
if (ticket?.queueId) {
ticketHasQueue = true;
}
if (ticketHasQueue) {
// MESSAGE TO HOLIDAY
const holiday: any = await isHoliday();
if (holiday && holiday.set) {
if (msg.fromMe && holiday.msg == msg.body) {
console.log("HOLIDAY MESSAGE IGNORED");
return;
}
botSendMessage(ticket, holiday.msg);
return;
}
// MESSAGES TO SATURDAY OR SUNDAY
const weekend: any = await isWeekend();
if (weekend && weekend.set) {
if (msg.fromMe && weekend.msg == msg.body) {
console.log("WEEKEND MESSAGE IGNORED");
return;
}
botSendMessage(ticket, weekend.msg);
return;
}
// MESSAGE TO BUSINESS TIME
const businessTime = await isOutBusinessTime();
if (businessTime && businessTime.set) {
if (msg.fromMe && businessTime.msg == msg.body) {
console.log("BUSINESS TIME MESSAGE IGNORED");
return;
}
botSendMessage(ticket, businessTime.msg);
return;
}
}
} catch (err) { } catch (err) {
Sentry.captureException(err); Sentry.captureException(err);
console.log("xxxxxxxxxxxxx err: ", err); console.log("xxxxxxxxxxxxx err: ", err);

View File

@ -1,30 +1,32 @@
import Whatsapp from "../../models/Whatsapp"; import Whatsapp from "../../models/Whatsapp";
const ListWhatsAppsNumber = async (whatsappId: string | number, status: string): Promise<Whatsapp[] | any> => { const ListWhatsAppsNumber = async (
whatsappId: string | number,
status?: string
): Promise<Whatsapp[] | any> => {
const whatsapp = await Whatsapp.findByPk(whatsappId, { raw: true });
// const whatsapp = await Whatsapp.findOne({ let whatsapps: any = [];
// raw: true,
// where: { id: whatsappId }
// })
const whatsapp = await Whatsapp.findByPk(whatsappId, { raw: true })
if (whatsapp) { if (whatsapp) {
if (status) {
const whatsapps = await Whatsapp.findAll({ whatsapps = await Whatsapp.findAll({
raw: true, raw: true,
where: { number: whatsapp.number, status: status }, where: { number: whatsapp.number, status: status },
attributes: ['id', 'number', 'status', 'isDefault', 'url'] attributes: ["id", "number", "status", "isDefault", "url"]
});
} else {
whatsapps = await Whatsapp.findAll({
raw: true,
where: { number: whatsapp.number },
attributes: ["id", "number", "status", "isDefault", "url"]
}); });
return { whatsapps, whatsapp };
} }
return { whatsapps: [], whatsapp: null } return { whatsapps, whatsapp };
}
return { whatsapps: [], whatsapp: null };
}; };
export default ListWhatsAppsNumber; export default ListWhatsAppsNumber;

View File

@ -4,13 +4,13 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@date-io/date-fns": "^1.3.13", "@date-io/date-fns": "^1.3.13",
"@emotion/react": "^11.7.1", "@emotion/react": "^11.11.1",
"@emotion/styled": "^11.6.0", "@emotion/styled": "^11.11.0",
"@material-ui/core": "^4.12.1", "@material-ui/core": "^4.12.1",
"@material-ui/icons": "^4.9.1", "@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.56", "@material-ui/lab": "^4.0.0-alpha.56",
"@material-ui/pickers": "^3.3.10", "@material-ui/pickers": "^3.3.10",
"@mui/material": "^5.3.0", "@mui/material": "^5.14.4",
"@mui/x-data-grid": "^5.3.0", "@mui/x-data-grid": "^5.3.0",
"@testing-library/jest-dom": "^5.11.4", "@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.0.4", "@testing-library/react": "^11.0.4",
@ -20,6 +20,7 @@
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"emoji-mart": "^3.0.1", "emoji-mart": "^3.0.1",
"formik": "^2.2.0", "formik": "^2.2.0",
"formik-material-ui-pickers": "^1.0.0-alpha.1",
"i18next": "^19.8.2", "i18next": "^19.8.2",
"i18next-browser-languagedetector": "^6.0.1", "i18next-browser-languagedetector": "^6.0.1",
"js-file-download": "^0.4.12", "js-file-download": "^0.4.12",
@ -30,6 +31,7 @@
"react": "^17.0.2", "react": "^17.0.2",
"react-color": "^2.19.3", "react-color": "^2.19.3",
"react-csv": "^2.2.2", "react-csv": "^2.2.2",
"react-datepicker": "^4.16.0",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-modal-image": "^2.5.0", "react-modal-image": "^2.5.0",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",

View File

@ -0,0 +1,379 @@
import React, { useState, useEffect, useContext, } from 'react'
import * as Yup from 'yup'
import { Formik, Form, Field, } from 'formik'
import { toast } from 'react-toastify'
import { makeStyles } from '@material-ui/core/styles'
import { green } from '@material-ui/core/colors'
import { AuthContext } from '../../context/Auth/AuthContext'
import apiBroker from '../../services/apiBroker'
import Select from "@material-ui/core/Select"
import MenuItem from "@material-ui/core/MenuItem"
import { WhatsAppsContext } from "../../context/WhatsApp/WhatsAppsContext"
import {
Dialog,
DialogContent,
DialogTitle,
Button,
DialogActions,
CircularProgress,
TextField,
} from '@material-ui/core'
import { i18n } from '../../translate/i18n'
import toastError from '../../errors/toastError'
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
flexWrap: 'wrap',
},
multFieldLine: {
display: 'flex',
'& > *:not(:last-child)': {
marginRight: theme.spacing(1),
},
},
btnWrapper: {
position: 'relative',
},
buttonProgress: {
color: green[500],
position: 'absolute',
top: '50%',
left: '50%',
marginTop: -12,
marginLeft: -12,
},
}))
const SessionSchema = Yup.object().shape({
name: Yup.string()
.min(2, 'Too Short!')
.max(100, 'Too Long!')
.required('Required'),
secondStart: Yup.number()
.required('Min time is required')
.min(3, 'Min time must be 3')
.max(3600, 'Min must be less than 3600'),
secondEnd: Yup.number()
.required('Max time is required')
.min(3, 'Min time must be 3')
.max(3600, 'Max must be less than 3600')
.test('higher-than-lower', 'Tempo final deve ser maior que tempo inicio!', function (
secondEnd
) {
const secondStart = this.resolve(Yup.ref('secondStart'))
return secondStart === undefined || secondEnd === undefined || secondEnd > secondStart
}),
})
const CampaignModal = ({ open, onClose, campaignId, dispatch }) => {
const classes = useStyles()
const initialState = {
name: '',
message: '',
status: 'pending',
whatsapp_sender: '',
csv_original_file_name: '',
secondStart: '3',
secondEnd: '15',
textToSeconds: true,
}
const { user } = useContext(AuthContext)
const { whatsApps } = useContext(WhatsAppsContext)
const [campaign, setCampaign] = useState(initialState)
// const [selectedQueueIds, setSelectedQueueIds] = useState([])
const [file, setFile] = useState()
const [selectedNumber, setSelectedNumber] = useState('')
useEffect(() => {
const fetchSession = async () => {
if (!campaignId) return
try {
const { data } = await apiBroker.get('/campaign', {
params: {
adminId: user.id,
baseURL: process.env.REACT_APP_BACKEND_URL_PRIVATE,
identifier: 'campaign',
id: campaignId
}
})
setCampaign(data.campaign)
setSelectedNumber(data.campaign.whatsapp_sender)
} catch (err) {
toastError(err)
}
}
fetchSession()
}, [campaignId, user.id])
const handleSaveCampaign = async (values) => {
let response = null
const formData = new FormData()
formData.append('adminId', user.id)
formData.append('baseURL', process.env.REACT_APP_BACKEND_URL_PRIVATE)
formData.append('frontURL', process.env.REACT_APP_FRONTEND_URL)
formData.append('identifier', 'campaign')
formData.append('file', file)
formData.append('name', values.name)
formData.append('whatsapp_sender', selectedNumber)
formData.append('secondStart', values.secondStart)
formData.append('secondEnd', values.secondEnd)
formData.append('textToSeconds', values.textToSeconds)
formData.append('message', values.message)
formData.append('csv_original_file_name', file?.name)
const config = {
headers: {
'content-type': 'multipart/form-data',
},
}
try {
if (campaignId) {
response = await apiBroker.patch(`/campaign/${campaignId}`,
formData,
config
)
toast.success('Campanha atualizada com sucesso!')
} else {
response = await apiBroker.post('/campaign',
formData,
config
)
toast.success('Campanha criada com sucesso!')
}
dispatch({ type: "UPDATE_CAMPAIGNS", payload: response.data.campaign })
handleClose()
} catch (err) {
toastError(err)
}
}
const handleClose = () => {
onClose()
setCampaign(initialState)
setSelectedNumber('')
setFile(null)
}
async function handleChange(event) {
try {
if (event.target.files[0].size > 1024 * 1024 * 4) {
alert('Arquivo não pode ser maior que 4 MB!')
return
}
setFile(event.target.files[0])
} catch (err) {
toastError(err)
}
}
return (
<div className={classes.root}>
<Dialog
open={open}
onClose={handleClose}
maxWidth="sm"
fullWidth
scroll="paper"
>
<DialogTitle>
{campaignId ? 'Editar campanha' : 'Adicionar campanha'}
</DialogTitle>
<Formik
initialValues={campaign}
enableReinitialize={true}
validationSchema={SessionSchema}
onSubmit={(values, actions) => {
setTimeout(() => {
handleSaveCampaign(values)
actions.setSubmitting(false)
}, 400)
}}
>
{({ values, touched, errors, isSubmitting }) => (
<Form>
<DialogContent dividers>
<div>
<div className={classes.multFieldLine}>
<Field
as={TextField}
label={i18n.t('whatsappModal.form.name')}
autoFocus
name="name"
error={touched.name && Boolean(errors.name)}
helperText={touched.name && errors.name}
variant="outlined"
margin="dense"
className={classes.textField}
/>
<Select
value={selectedNumber}
onChange={(e) => setSelectedNumber(e.target.value)}
label={i18n.t("transferTicketModal.fieldQueuePlaceholder")}
required
>
<MenuItem style={{ background: "white", }} value={''}>&nbsp;</MenuItem>
{whatsApps.map((whatsapp) => (
<MenuItem
key={whatsapp.id}
value={whatsapp.number}
>{whatsapp.number}
</MenuItem>
))}
</Select>
</div>
<div className={classes.multFieldLine}>
</div>
</div>
<div>
<Field
as={TextField}
label="Mensagem"
type="message"
multiline
rows={5}
fullWidth
name="message"
error={touched.message && Boolean(errors.message)}
helperText={touched.message && errors.message}
variant="outlined"
margin="dense"
/>
</div>
<div
style={{
display: 'flex',
gap: '10px',
}}
>
<input
type="file"
accept=".csv"
style={{ display: 'none' }}
onChange={handleChange}
id="contained-button-file"
/>
<label htmlFor="contained-button-file">
<Button
variant="contained"
color="primary"
component="span"
>
CSV UPLOAD
</Button>
</label>
<h3>{file?.name || campaign?.csv_original_file_name}</h3>
</div>
<div className={classes.multFieldLine} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Field
as={TextField}
label='Inicio em segundos'
autoFocus
name="secondStart"
error={touched.secondStart && Boolean(errors.secondStart)}
helperText={touched.secondStart && errors.secondStart}
variant="outlined"
margin="dense"
className={classes.textField}
/>
<Field
as={TextField}
label='Fim em segundos'
autoFocus
name="secondEnd"
error={touched.secondEnd && Boolean(errors.secondEnd)}
helperText={touched.secondEnd && errors.secondEnd}
variant="outlined"
margin="dense"
className={classes.textField}
/>
<label>
<Field type="checkbox" name="textToSeconds"/>
Tamanho do texto para segundos
</label>
</div>
</DialogContent>
<DialogActions>
<Button
onClick={handleClose}
color="secondary"
disabled={isSubmitting}
variant="outlined"
>
{i18n.t('whatsappModal.buttons.cancel')}
</Button>
<Button
type="submit"
color="primary"
disabled={isSubmitting}
variant="contained"
className={classes.btnWrapper}
>
{campaignId
? i18n.t('whatsappModal.buttons.okEdit')
: i18n.t('whatsappModal.buttons.okAdd')}
{isSubmitting && (
<CircularProgress
size={24}
className={classes.buttonProgress}
/>
)}
</Button>
</DialogActions>
</Form>
)}
</Formik>
</Dialog>
</div>
)
}
export default React.memo(CampaignModal)

View File

@ -0,0 +1,449 @@
import React, { useState, useEffect, } from 'react'
// import * as Yup from 'yup'
import { Formik, Form, Field, } from 'formik'
import { toast } from 'react-toastify'
import { makeStyles } from '@material-ui/core/styles'
import { green } from '@material-ui/core/colors'
import { TimePicker, DatePicker } from 'formik-material-ui-pickers'
import DateFnsUtils from '@date-io/date-fns'
import ptBrLocale from "date-fns/locale/pt-BR"
import {
MuiPickersUtilsProvider,
} from '@material-ui/pickers'
import {
Dialog,
DialogContent,
DialogTitle,
Button,
DialogActions,
CircularProgress,
TextField,
Switch,
FormControlLabel,
} from '@material-ui/core'
import api from '../../services/api'
import { i18n } from '../../translate/i18n'
import toastError from '../../errors/toastError'
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
flexWrap: 'wrap',
},
multFieldLine: {
display: 'flex',
'& > *:not(:last-child)': {
marginRight: theme.spacing(1),
},
},
btnWrapper: {
position: 'relative',
},
buttonProgress: {
color: green[500],
position: 'absolute',
top: '50%',
left: '50%',
marginTop: -12,
marginLeft: -12,
},
}))
// const SessionSchema = Yup.object().shape({
// name: Yup.string()
// .min(2, 'Too Short!')
// .max(100, 'Too Long!')
// .required('Required'),
// })
const ConfigModal = ({ open, onClose, change }) => {
const classes = useStyles()
const initialState = {
startTimeBus: new Date(),
endTimeBus: new Date(),
messageBus: '',
businessTimeEnable: false,
ticketTimeExpiration: new Date(),
ticketExpirationMsg: '',
ticketExpirationEnable: false,
holidayDate: new Date(),
holidayDateEnable: false,
holidayDateMessage: '',
checkboxSundayValue: false,
checkboxSaturdayValue: false,
weekendMessage: '',
enableWeekendMessage: false
}
const [config, setConfig] = useState(initialState)
useEffect(() => {
const fetchSession = async () => {
try {
const { data } = await api.get('/settings')
const outBusinessHours = data.config.find((c) => c.key === "outBusinessHours")
const ticketExpiration = data.config.find((c) => c.key === "ticketExpiration")
const saturday = data.config.find((c) => c.key === "saturday")
const sunday = data.config.find((c) => c.key === "sunday")
const weekend = data.config.find((c) => c.key === "weekend")
const holiday = data.config.find((c) => c.key === "holiday")
setConfig({
startTimeBus: outBusinessHours.startTime,
endTimeBus: outBusinessHours.endTime,
messageBus: outBusinessHours.message,
businessTimeEnable: outBusinessHours.value === 'enabled' ? true : false,
ticketTimeExpiration: ticketExpiration.startTime,
ticketExpirationMsg: ticketExpiration.message,
ticketExpirationEnable: ticketExpiration.value === 'enabled' ? true : false,
checkboxSaturdayValue: saturday.value === 'enabled' ? true : false,
checkboxSundayValue: sunday.value === 'enabled' ? true : false,
weekendMessage: weekend.message,
enableWeekendMessage: weekend.value === 'enabled' ? true : false,
holidayDate: holiday.startTime,
holidayDateMessage: holiday.message,
holidayDateEnable: holiday.value === 'enabled' ? true : false,
})
} catch (err) {
toastError(err)
}
}
fetchSession()
}, [change])
const handleSaveConfig = async (values) => {
values = {
outBusinessHours: {
startTime: values.startTimeBus,
endTime: values.endTimeBus,
message: values.messageBus,
value: values.businessTimeEnable ? 'enabled' : 'disabled'
},
ticketExpiration: {
startTime: values.ticketTimeExpiration,
message: values.ticketExpirationMsg,
value: values.ticketExpirationEnable ? 'enabled' : 'disabled'
},
weekend: {
message: values.weekendMessage,
value: values.enableWeekendMessage ? 'enabled' : 'disabled'
},
saturday:{
value: values.checkboxSaturdayValue ? 'enabled' : 'disabled'
},
sunday: {
value: values.checkboxSundayValue ? 'enabled' : 'disabled'
},
holiday: {
startTime: values.holidayDate,
message: values.holidayDateMessage,
value: values.holidayDateEnable ? 'enabled' : 'disabled'
}
}
try {
await api.put(`/settings/ticket`, values)
toast.success('Atualização realizada com sucesso!')
handleClose()
} catch (err) {
toastError(err)
}
}
const handleClose = () => {
onClose()
// setConfig(initialState)
}
return (
<div className={classes.root}>
<Dialog
open={open}
onClose={handleClose}
maxWidth="sm"
fullWidth
scroll="paper"
>
<DialogTitle>
Configurações
</DialogTitle>
<Formik
initialValues={config}
enableReinitialize={true}
// validationSchema={SessionSchema}
onSubmit={(values, actions) => {
setTimeout(() => {
handleSaveConfig(values)
actions.setSubmitting(false)
}, 100)
}}
>
{({ values, touched, errors, isSubmitting }) => (
<MuiPickersUtilsProvider utils={DateFnsUtils} locale={ptBrLocale}>
<Form>
<DialogContent dividers>
<div className={classes.multFieldLine}>
<Field
component={TimePicker}
name="startTimeBus"
label="Inicio atendimento"
ampm={false}
openTo="hours"
views={['hours', 'minutes',]}
format="HH:mm"
/>
{' '}
<Field
component={TimePicker}
name="endTimeBus"
label="Fim atendimento"
ampm={false}
openTo="hours"
views={['hours', 'minutes',]}
format="HH:mm"
/>
<FormControlLabel
control={
<Field
as={Switch}
color="primary"
name="businessTimeEnable"
checked={values.businessTimeEnable}
/>
}
label={'Ativar/Desativar'} />
</div>
<div>
<Field
as={TextField}
label={'Mensagem fora do horário de atendimento'}
type="messageBus"
multiline
rows={5}
fullWidth
name="messageBus"
error={
touched.messageBus && Boolean(errors.messageBus)
}
helperText={
touched.messageBus && errors.messageBus
}
variant="outlined"
margin="dense"
/>
</div>
<br />
{/* Saturday and Sunday date */}
<div className={classes.multFieldLine} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<label style={{ marginRight: '10px' }}>
<Field type="checkbox" name="checkboxSundayValue" />
Sábado
</label>
<label>
<Field type="checkbox" name="checkboxSaturdayValue" />
Domingo
</label>
</div>
<FormControlLabel
control={
<Field
as={Switch}
color="primary"
name="enableWeekendMessage"
checked={values.enableWeekendMessage}
/>
}
label={'Ativar/Desativar'}
/>
</div>
<div>
<Field
as={TextField}
label={'Mensagem para final de semana'}
type="weekendMessage"
multiline
rows={5}
fullWidth
name="weekendMessage"
error={
touched.weekendMessage && Boolean(errors.weekendMessage)
}
helperText={
touched.weekendMessage && errors.weekendMessage
}
variant="outlined"
margin="dense"
/>
</div>
<br />
{/* Holiday date */}
<div className={classes.multFieldLine} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Field
component={DatePicker}
name="holidayDate"
label="Data do feriado"
format="dd/MM/yyyy"
fullWidth
/>
<FormControlLabel
control={
<Field
as={Switch}
color="primary"
name="holidayDateEnable"
checked={values.holidayDateEnable}
/>
}
label={'Ativar/Desativar'}
/>
</div>
<div>
<Field
as={TextField}
label={'Mensagem para feriado'}
type="holidayDateMessage"
multiline
rows={5}
fullWidth
name="holidayDateMessage"
error={
touched.holidayDateMessage && Boolean(errors.holidayDateMessage)
}
helperText={
touched.holidayDateMessage && errors.holidayDateMessage
}
variant="outlined"
margin="dense"
/>
</div>
<br />
<div className={classes.multFieldLine} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Field
component={TimePicker}
name="ticketTimeExpiration"
label="Ticket expira em hh:mm"
ampm={false}
openTo="hours"
views={['hours', 'minutes',]}
format="HH:mm"
/>
<FormControlLabel
control={
<Field
as={Switch}
color="primary"
name="ticketExpirationEnable"
checked={values.ticketExpirationEnable}
/>
}
label={'Ativar/Desativar'}
/>
</div>
<div>
<Field
as={TextField}
label={'Mensagem por falta de atividade no atendimento'}
type="ticketExpirationMsg"
multiline
rows={5}
fullWidth
name="ticketExpirationMsg"
error={
touched.ticketExpirationMsg && Boolean(errors.ticketExpirationMsg)
}
helperText={
touched.ticketExpirationMsg && errors.ticketExpirationMsg
}
variant="outlined"
margin="dense"
/>
</div>
</DialogContent>
<DialogActions>
<Button
onClick={handleClose}
color="secondary"
disabled={isSubmitting}
variant="outlined"
>
{i18n.t('whatsappModal.buttons.cancel')}
</Button>
<Button
type="submit"
color="primary"
disabled={isSubmitting}
variant="contained"
className={classes.btnWrapper}
>
{isSubmitting ? (
<CircularProgress
size={24}
className={classes.buttonProgress}
/>
) : 'Salvar'}
</Button>
</DialogActions>
</Form>
</MuiPickersUtilsProvider>
)}
</Formik>
</Dialog>
</div>
)
}
export default React.memo(ConfigModal)

View File

@ -2,7 +2,7 @@ import { toast } from "react-toastify";
import { i18n } from "../translate/i18n"; import { i18n } from "../translate/i18n";
const toastError = err => { const toastError = err => {
const errorMsg = err.response?.data?.message || err.response.data.error; const errorMsg = err.response?.data?.message || err?.response?.data?.error || `${err?.message}`;
if (errorMsg) { if (errorMsg) {
if (i18n.exists(`backendErrors.${errorMsg}`)) { if (i18n.exists(`backendErrors.${errorMsg}`)) {
toast.error(i18n.t(`backendErrors.${errorMsg}`), { toast.error(i18n.t(`backendErrors.${errorMsg}`), {

View File

@ -76,7 +76,7 @@ const useAuth = () => {
const fetchSession = async () => { const fetchSession = async () => {
try { try {
const { data } = await api.get('/settings') const { data } = await api.get('/settings')
setSetting(data) setSetting(data.settings)
} catch (err) { } catch (err) {
toastError(err) toastError(err)
} }

View File

@ -1,41 +1,46 @@
import React, { useContext, useEffect, useState } from "react"; import React, { useContext, useEffect, useState } from 'react'
import { Link as RouterLink } from "react-router-dom"; import { Link as RouterLink } from 'react-router-dom'
import DeviceHubOutlined from "@material-ui/icons/DeviceHubOutlined" import DeviceHubOutlined from "@material-ui/icons/DeviceHubOutlined"
import ListItem from "@material-ui/core/ListItem"; import ListItemText from "@material-ui/core/ListItemText"
import ListItemIcon from "@material-ui/core/ListItemIcon"; import ListSubheader from "@material-ui/core/ListSubheader"
import ListItemText from "@material-ui/core/ListItemText"; import Divider from "@material-ui/core/Divider"
import ListSubheader from "@material-ui/core/ListSubheader"; import { Badge } from "@material-ui/core"
import Divider from "@material-ui/core/Divider"; import DashboardOutlinedIcon from "@material-ui/icons/DashboardOutlined"
import { Badge } from "@material-ui/core"; import ListItem from '@material-ui/core/ListItem'
import DashboardOutlinedIcon from "@material-ui/icons/DashboardOutlined"; import ListItemIcon from '@material-ui/core/ListItemIcon'
import ReportOutlinedIcon from "@material-ui/icons/ReportOutlined"; import ReportOutlinedIcon from '@material-ui/icons/ReportOutlined'
import SendOutlined from "@material-ui/icons/SendOutlined"; import CampaignIcon from '@material-ui/icons/Send'
import SendOutlined from '@material-ui/icons/SendOutlined'
//import ReportOutlined from "@bit/mui-org.material-ui-icons.report-outlined"; //import ReportOutlined from "@bit/mui-org.material-ui-icons.report-outlined";
import WhatsAppIcon from '@material-ui/icons/WhatsApp'
import SyncAltIcon from '@material-ui/icons/SyncAlt'
import SettingsOutlinedIcon from '@material-ui/icons/SettingsOutlined'
import PeopleAltOutlinedIcon from '@material-ui/icons/PeopleAltOutlined'
import ContactPhoneOutlinedIcon from '@material-ui/icons/ContactPhoneOutlined'
import AccountTreeOutlinedIcon from '@material-ui/icons/AccountTreeOutlined'
import QuestionAnswerOutlinedIcon from '@material-ui/icons/QuestionAnswerOutlined'
import WhatsAppIcon from "@material-ui/icons/WhatsApp"; import { i18n } from '../translate/i18n'
import SyncAltIcon from "@material-ui/icons/SyncAlt"; import { WhatsAppsContext } from '../context/WhatsApp/WhatsAppsContext'
import SettingsOutlinedIcon from "@material-ui/icons/SettingsOutlined"; import { AuthContext } from '../context/Auth/AuthContext'
import PeopleAltOutlinedIcon from "@material-ui/icons/PeopleAltOutlined"; import { Can } from '../components/Can'
import ContactPhoneOutlinedIcon from "@material-ui/icons/ContactPhoneOutlined";
import AccountTreeOutlinedIcon from "@material-ui/icons/AccountTreeOutlined";
import QuestionAnswerOutlinedIcon from "@material-ui/icons/QuestionAnswerOutlined";
import { i18n } from "../translate/i18n";
import { WhatsAppsContext } from "../context/WhatsApp/WhatsAppsContext";
import { AuthContext } from "../context/Auth/AuthContext";
import { Can } from "../components/Can";
function ListItemLink(props) { function ListItemLink(props) {
const { icon, primary, to, className } = props; const { icon, primary, to, className } = props
const renderLink = React.useMemo( const renderLink = React.useMemo(
() => React.forwardRef((itemProps, ref) => <RouterLink to={to} ref={ref} {...itemProps} />), () =>
React.forwardRef((itemProps, ref) => (
<RouterLink to={to} ref={ref} {...itemProps} />
)),
[to] [to]
); )
return ( return (
<li> <li>
@ -44,60 +49,59 @@ function ListItemLink(props) {
<ListItemText primary={primary} /> <ListItemText primary={primary} />
</ListItem> </ListItem>
</li> </li>
); )
} }
const MainListItems = (props) => { const MainListItems = (props) => {
const { setDrawerOpen } = props; const { setDrawerOpen } = props
const { whatsApps } = useContext(WhatsAppsContext); const { whatsApps } = useContext(WhatsAppsContext)
const { user } = useContext(AuthContext); const { user } = useContext(AuthContext)
const [connectionWarning, setConnectionWarning] = useState(false); const [connectionWarning, setConnectionWarning] = useState(false)
useEffect(() => { useEffect(() => {
const delayDebounceFn = setTimeout(() => { const delayDebounceFn = setTimeout(() => {
if (whatsApps.length > 0) { if (whatsApps.length > 0) {
const offlineWhats = whatsApps.filter((whats) => { const offlineWhats = whatsApps.filter((whats) => {
return ( return (
whats.status === "qrcode" || whats.status === 'qrcode' ||
whats.status === "PAIRING" || whats.status === 'PAIRING' ||
whats.status === "DISCONNECTED" || whats.status === 'DISCONNECTED' ||
whats.status === "TIMEOUT" || whats.status === 'TIMEOUT' ||
whats.status === "OPENING" whats.status === 'OPENING'
); )
}); })
if (offlineWhats.length > 0) { if (offlineWhats.length > 0) {
setConnectionWarning(true); setConnectionWarning(true)
} else { } else {
setConnectionWarning(false); setConnectionWarning(false)
} }
} }
}, 2000); }, 2000)
return () => clearTimeout(delayDebounceFn); return () => clearTimeout(delayDebounceFn)
}, [whatsApps]); }, [whatsApps])
return ( return (
//Solicitado pelo Adriano: Click no LinkItem e fechar o menu! //Solicitado pelo Adriano: Click no LinkItem e fechar o menu!
<div onClick={() => setDrawerOpen(false)}> <div onClick={() => setDrawerOpen(false)}>
<ListItemLink <ListItemLink
to="/tickets" to="/tickets"
primary={i18n.t("mainDrawer.listItems.tickets")} primary={i18n.t('mainDrawer.listItems.tickets')}
icon={<WhatsAppIcon />} icon={<WhatsAppIcon />}
/> />
<ListItemLink <ListItemLink
to="/contacts" to="/contacts"
primary={i18n.t("mainDrawer.listItems.contacts")} primary={i18n.t('mainDrawer.listItems.contacts')}
icon={<ContactPhoneOutlinedIcon />} icon={<ContactPhoneOutlinedIcon />}
/> />
<ListItemLink
<ListItemLink to="/schedulesReminder" to="/schedulesReminder"
primary={i18n.t("mainDrawer.listItems.reminders")} primary={i18n.t('mainDrawer.listItems.schedules')}
icon={<SendOutlined />} icon={<SendOutlined />}
/> />
<ListItemLink <ListItemLink
to="/quickAnswers" to="/quickAnswers"
primary={i18n.t("mainDrawer.listItems.quickAnswers")} primary={i18n.t('mainDrawer.listItems.quickAnswers')}
icon={<QuestionAnswerOutlinedIcon />} icon={<QuestionAnswerOutlinedIcon />}
/> />
<Can <Can
@ -106,31 +110,47 @@ const MainListItems = (props) => {
yes={() => ( yes={() => (
<> <>
<Divider /> <Divider />
<ListSubheader inset>{i18n.t("mainDrawer.listItems.administration")}</ListSubheader> <ListSubheader inset>
{i18n.t('mainDrawer.listItems.administration')}
</ListSubheader>
<ListItemLink <ListItemLink
to="/users" to="/users"
primary={i18n.t("mainDrawer.listItems.users")} primary={i18n.t('mainDrawer.listItems.users')}
icon={<PeopleAltOutlinedIcon />} icon={<PeopleAltOutlinedIcon />}
/> />
<ListItemLink <ListItemLink
to="/queues" to="/queues"
primary={i18n.t("mainDrawer.listItems.queues")} primary={i18n.t('mainDrawer.listItems.queues')}
icon={<AccountTreeOutlinedIcon />} icon={<AccountTreeOutlinedIcon />}
/> />
<ListItemLink <ListItemLink
to="/connections" to="/connections"
primary={i18n.t("mainDrawer.listItems.connections")} primary={i18n.t('mainDrawer.listItems.connections')}
icon={ icon={
<Badge badgeContent={connectionWarning ? "!" : 0} color="error"> <Badge badgeContent={connectionWarning ? '!' : 0} color="error">
<SyncAltIcon /> <SyncAltIcon />
</Badge> </Badge>
} }
/> />
<ListItemLink to="/" primary="Dashboard" icon={<DashboardOutlinedIcon />} /> <ListItemLink
to="/"
primary="Dashboard"
icon={<DashboardOutlinedIcon />}
/>
<ListItemLink to="/report" primary="Relatório" icon={<ReportOutlinedIcon />} /> <ListItemLink
to="/report"
primary="Relatório"
icon={<ReportOutlinedIcon />}
/>
<ListItemLink
to="/campaign"
primary="Campanha"
icon={<CampaignIcon />}
/>
<Can <Can
role={user.profile} role={user.profile}
@ -148,13 +168,14 @@ const MainListItems = (props) => {
icon={<DeviceHubOutlined />} icon={<DeviceHubOutlined />}
/> />
</> </>
)} )}
/> />
</> </>
)} )}
/> />
</div> </div>
); )
}; }
export default MainListItems; export default MainListItems

View File

@ -0,0 +1,490 @@
import React, { useState, useCallback, useEffect, useReducer, useContext } from 'react'
import { toast } from 'react-toastify'
import openSocket from 'socket.io-client'
import { makeStyles } from '@material-ui/core/styles'
import { green } from '@material-ui/core/colors'
import {
Button,
TableBody,
TableRow,
TableCell,
IconButton,
Table,
TableHead,
Paper,
} from '@material-ui/core'
import {
Edit,
DeleteOutline,
// Restore
} from '@material-ui/icons'
import MainContainer from '../../components/MainContainer'
import MainHeader from '../../components/MainHeader'
import MainHeaderButtonsWrapper from '../../components/MainHeaderButtonsWrapper'
import Title from '../../components/Title'
import TableRowSkeleton from '../../components/TableRowSkeleton'
import CampaignModal from '../../components/CampaignModal'
import ConfirmationModal from '../../components/ConfirmationModal'
import QrcodeModal from '../../components/QrcodeModal'
import { i18n } from '../../translate/i18n'
// import { WhatsAppsContext } from '../../context/WhatsApp/WhatsAppsContext'
import toastError from '../../errors/toastError'
//--------
import { AuthContext } from '../../context/Auth/AuthContext'
import { Can } from '../../components/Can'
import apiBroker from '../../services/apiBroker'
const reducer = (state, action) => {
if (action.type === "LOAD_CAMPAIGNS") {
const campaigns = action.payload
return [...state, ...campaigns]
}
if (action.type === "UPDATE_CAMPAIGNS") {
const campaign = action.payload
const campaignIndex = state.findIndex((c) => c.id === campaign.id)
if (campaignIndex !== -1) {
state[campaignIndex] = { ...state[campaignIndex], ...campaign }
return [...state]
// state[campaignIndex] = campaign
// return [...state]
} else {
return [campaign, ...state]
}
}
if (action.type === "DELETE_CAMPAIGN") {
const campaignId = action.payload
const campaignIndex = state.findIndex((c) => c.id === campaignId)
if (campaignIndex !== -1) {
state.splice(campaignIndex, 1)
}
return [...state]
}
if (action.type === "RESET") {
return []
}
}
const useStyles = makeStyles((theme) => ({
mainPaper: {
flex: 1,
padding: theme.spacing(1),
overflowY: 'scroll',
...theme.scrollbarStyles,
},
customTableCell: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
tooltip: {
backgroundColor: '#f5f5f9',
color: 'rgba(0, 0, 0, 0.87)',
fontSize: theme.typography.pxToRem(14),
border: '1px solid #dadde9',
maxWidth: 450,
},
tooltipPopper: {
textAlign: 'center',
},
buttonProgress: {
color: green[500],
},
}))
const Campaign = () => {
//--------
const { user } = useContext(AuthContext)
const classes = useStyles()
// const { whatsApps } = useContext(WhatsAppsContext)
// const { campaigns, loading } = useContext(WhatsAppsContext)
const [campaignModalOpen, setCampaignModalOpen] = useState(false)
const [qrModalOpen, setQrModalOpen] = useState(false)
const [selectedCampaign, setSelectedCampaign] = useState(null)
const [confirmModalOpen, setConfirmModalOpen] = useState(false)
const [campaigns, dispatch] = useReducer(reducer, [])
const [loading, setLoading] = useState(true)
const confirmationModalInitialState = {
action: '',
title: '',
message: '',
campaignId: '',
csv_original_file_name: '',
open: false,
}
const [confirmModalInfo, setConfirmModalInfo] = useState(
confirmationModalInitialState
)
useEffect(() => {
dispatch({ type: "RESET" })
}, [])
useEffect(() => {
setLoading(true)
const delayDebounceFn = setTimeout(() => {
const fetchContacts = async () => {
try {
const { data } = await apiBroker.get('/campaign', {
params: {
adminId: user.id,
baseURL: process.env.REACT_APP_BACKEND_URL_PRIVATE,
identifier: 'campaign'
}
})
dispatch({ type: "LOAD_CAMPAIGNS", payload: data.campaign })
setLoading(false)
} catch (err) {
toastError(err)
}
}
fetchContacts()
}, 500)
return () => clearTimeout(delayDebounceFn)
}, [user.id])
const handleOpenCampaignModal = () => {
setSelectedCampaign(null)
setCampaignModalOpen(true)
}
const handleCloseCampaignModal = useCallback(() => {
setCampaignModalOpen(false)
setSelectedCampaign(null)
}, [setSelectedCampaign, setCampaignModalOpen])
const handleStart = async (campaign) => {
try {
const { data } = await apiBroker.post(`/campaign/start/${campaign.id}`)
dispatch({ type: "UPDATE_CAMPAIGNS", payload: data.campaign })
toast.success('Campanha iniciada com sucesso')
} catch (err) {
toastError(err)
}
}
const handleStop = async (campaign) => {
try {
const { data } = await apiBroker.post(`/campaign/stop/${campaign.id}`)
dispatch({ type: "UPDATE_CAMPAIGNS", payload: data.campaign })
toast.success('Campanha parada com sucesso')
} catch (err) {
toastError(err)
}
}
const handleCloseQrModal = useCallback(() => {
setSelectedCampaign(null)
setQrModalOpen(false)
}, [setQrModalOpen, setSelectedCampaign])
const handleEditCampaign = (campaign) => {
setSelectedCampaign(campaign)
setCampaignModalOpen(true)
}
const handleOpenConfirmationModal = (action, campaignId) => {
if (action === 'disconnect') {
setConfirmModalInfo({
action: action,
title: i18n.t('connections.confirmationModal.disconnectTitle'),
message: i18n.t('connections.confirmationModal.disconnectMessage'),
campaignId: campaignId,
})
}
if (action === 'delete') {
setConfirmModalInfo({
action: action,
title: i18n.t('connections.confirmationModal.deleteTitle'),
message: i18n.t('connections.confirmationModal.deleteMessage'),
campaignId: campaignId,
})
}
setConfirmModalOpen(true)
}
const handleSubmitConfirmationModal = async () => {
if (confirmModalInfo.action === 'delete') {
try {
await apiBroker.delete(`/campaign/${confirmModalInfo.campaignId}`, {
params: {
adminId: user.id,
baseURL: process.env.REACT_APP_BACKEND_URL_PRIVATE,
identifier: 'campaign',
},
})
dispatch({ type: "DELETE_CAMPAIGN", payload: confirmModalInfo.campaignId })
toast.success('Campanha deletada com sucesso')
} catch (err) {
toastError(err)
}
}
setConfirmModalInfo(confirmationModalInitialState)
}
const renderActionButtons = (campaign) => {
return (
<>
{campaign.status === 'stopped' && (
<Button
size="small"
variant="contained"
color="primary"
onClick={() => handleStart(campaign)}
>
Start
</Button>
)}
{campaign.status === 'running' && (
<Button
size="small"
variant="contained"
color="primary"
onClick={() => handleStop(campaign)}
>
Stop
</Button>
)}
</>
)
}
useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL)
socket.on("contactsBulkInsertOnQueueStatus", (data) => {
if (data.action === 'update') {
if (String(data.insertOnQueue.adminId) === String(user.id)) {
if (data?.insertOnQueue?.campaign?.status === "stopped") {
dispatch({ type: "UPDATE_CAMPAIGNS", payload: data.insertOnQueue.campaign })
}
}
}
})
socket.on('campaign', (data) => {
if (data.action === 'update') {
dispatch({ type: "UPDATE_CAMPAIGNS", payload: data.campaign })
}
})
return () => {
socket.disconnect()
}
}, [user.id])
return (
<Can
role={user.profile}
perform="connections-view:show"
yes={() => (
<MainContainer>
<ConfirmationModal
title={confirmModalInfo.title}
open={confirmModalOpen}
onClose={setConfirmModalOpen}
onConfirm={handleSubmitConfirmationModal}
>
{confirmModalInfo.message}
</ConfirmationModal>
<QrcodeModal
open={qrModalOpen}
onClose={handleCloseQrModal}
campaignId={!campaignModalOpen && selectedCampaign?.id}
/>
<CampaignModal
open={campaignModalOpen}
onClose={handleCloseCampaignModal}
campaignId={selectedCampaign?.id}
dispatch={dispatch}
/>
<MainHeader>
<Title>Campanhas</Title>
<MainHeaderButtonsWrapper>
<Button
variant="contained"
color="primary"
onClick={handleOpenCampaignModal}
>
criar campanha
</Button>
</MainHeaderButtonsWrapper>
</MainHeader>
<Paper className={classes.mainPaper} variant="outlined">
<>
<Table size="small">
<TableHead>
<TableRow>
<TableCell align="center">
Name
</TableCell>
<TableCell align="center">
Status
</TableCell>
<TableCell align="center">
Sent
</TableCell>
<TableCell align="center">
Read
</TableCell>
<TableCell align="center">
Start/stop
</TableCell>
<TableCell align="center">
Sender
</TableCell>
<TableCell align="center">
Message
</TableCell>
<TableCell align="center">
{i18n.t('connections.table.actions')}
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{loading ? (
<TableRowSkeleton />
) : (
<>
{campaigns?.length > 0 &&
campaigns.map((campaign) => (
<TableRow key={campaign.id}>
<TableCell align="center">
{campaign.name}
</TableCell>
<TableCell align="center">
{campaign.status}
</TableCell>
<TableCell align="center">
{campaign.status === 'stopped' || campaign.status === 'running' || campaign.status === 'success' ? `${campaign.sent}/${campaign.all}` : '0/0'}
</TableCell>
<TableCell align="center">
{campaign.status === 'stopped' || campaign.status === 'running' || campaign.status === 'success' ? `${campaign.read}` : '0'}
</TableCell>
<TableCell align="center">
{renderActionButtons(campaign)}
</TableCell>
<TableCell align="center">
{campaign.whatsapp_sender}
</TableCell>
<TableCell align="center">
{campaign.message}
</TableCell>
{campaign.status !== 'running' ? <TableCell align="center">
{campaign.status !== 'success' && (
<IconButton
size="small"
onClick={() =>
handleEditCampaign(campaign)
}
>
<Edit />
</IconButton>)}
<IconButton
size="small"
onClick={(e) => {
handleOpenConfirmationModal(
'delete',
campaign.id
)
}}
>
<DeleteOutline />
</IconButton>
</TableCell> : <TableCell align="center"></TableCell>}
</TableRow>
))}
</>
)}
</TableBody>
</Table>
</>
</Paper>
</MainContainer>
)}
/>
)
}
export default Campaign

View File

@ -6,6 +6,9 @@ import openSocket from 'socket.io-client'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { green } from '@material-ui/core/colors' import { green } from '@material-ui/core/colors'
import Settings from "@material-ui/icons/Settings";
import { import {
Button, Button,
TableBody, TableBody,
@ -47,6 +50,7 @@ import toastError from '../../errors/toastError'
//-------- //--------
import { AuthContext } from '../../context/Auth/AuthContext' import { AuthContext } from '../../context/Auth/AuthContext'
import { Can } from '../../components/Can' import { Can } from '../../components/Can'
import ConfigModal from '../../components/ConfigModal'
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
mainPaper: { mainPaper: {
@ -107,6 +111,7 @@ const Connections = () => {
const { whatsApps, loading } = useContext(WhatsAppsContext) const { whatsApps, loading } = useContext(WhatsAppsContext)
const [whatsAppModalOpen, setWhatsAppModalOpen] = useState(false) const [whatsAppModalOpen, setWhatsAppModalOpen] = useState(false)
const [configModalOpen, setConfigModalOpen] = useState(false)
const [qrModalOpen, setQrModalOpen] = useState(false) const [qrModalOpen, setQrModalOpen] = useState(false)
const [selectedWhatsApp, setSelectedWhatsApp] = useState(null) const [selectedWhatsApp, setSelectedWhatsApp] = useState(null)
const [confirmModalOpen, setConfirmModalOpen] = useState(false) const [confirmModalOpen, setConfirmModalOpen] = useState(false)
@ -134,7 +139,7 @@ const Connections = () => {
const fetchSession = async () => { const fetchSession = async () => {
try { try {
const { data } = await api.get('/settings') const { data } = await api.get('/settings')
setSettings(data) setSettings(data.settings)
} catch (err) { } catch (err) {
toastError(err) toastError(err)
} }
@ -205,6 +210,13 @@ const Connections = () => {
setWhatsAppModalOpen(true) setWhatsAppModalOpen(true)
} }
const handleOpenConfigModal = () => {
setConfigModalOpen(true)
}
const handleCloseConfigModal = () => {
setConfigModalOpen(false)
}
const handleCloseWhatsAppModal = useCallback(() => { const handleCloseWhatsAppModal = useCallback(() => {
setWhatsAppModalOpen(false) setWhatsAppModalOpen(false)
setSelectedWhatsApp(null) setSelectedWhatsApp(null)
@ -454,10 +466,24 @@ const Connections = () => {
whatsAppId={!qrModalOpen && selectedWhatsApp?.id} whatsAppId={!qrModalOpen && selectedWhatsApp?.id}
/> />
<ConfigModal
open={configModalOpen}
onClose={handleCloseConfigModal}
change={configModalOpen}
/>
<MainHeader> <MainHeader>
<Title>{i18n.t('connections.title')}</Title> <Title>{i18n.t('connections.title')}</Title>
<MainHeaderButtonsWrapper> <MainHeaderButtonsWrapper>
<Button
variant="contained"
color="primary"
onClick={handleOpenConfigModal}
>
<Settings/>
</Button>
<Can <Can
role={user.profile} role={user.profile}
perform="btn-add-whatsapp" perform="btn-add-whatsapp"

View File

@ -1,43 +1,43 @@
import React, { useState, useEffect, useReducer, useContext } from "react"; import React, { useState, useEffect, useReducer, useContext } from "react"
import openSocket from "socket.io-client"; import openSocket from "socket.io-client"
import { toast } from "react-toastify"; import { toast } from "react-toastify"
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom"
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles"
import Table from "@material-ui/core/Table"; import Table from "@material-ui/core/Table"
import TableBody from "@material-ui/core/TableBody"; import TableBody from "@material-ui/core/TableBody"
import TableCell from "@material-ui/core/TableCell"; import TableCell from "@material-ui/core/TableCell"
import TableHead from "@material-ui/core/TableHead"; import TableHead from "@material-ui/core/TableHead"
import TableRow from "@material-ui/core/TableRow"; import TableRow from "@material-ui/core/TableRow"
import Paper from "@material-ui/core/Paper"; import Paper from "@material-ui/core/Paper"
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button"
import Avatar from "@material-ui/core/Avatar"; import Avatar from "@material-ui/core/Avatar"
import WhatsAppIcon from "@material-ui/icons/WhatsApp"; import WhatsAppIcon from "@material-ui/icons/WhatsApp"
import SearchIcon from "@material-ui/icons/Search"; import SearchIcon from "@material-ui/icons/Search"
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField"
import InputAdornment from "@material-ui/core/InputAdornment"; import InputAdornment from "@material-ui/core/InputAdornment"
import IconButton from "@material-ui/core/IconButton"; import IconButton from "@material-ui/core/IconButton"
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline"; import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline"
import EditIcon from "@material-ui/icons/Edit"; import EditIcon from "@material-ui/icons/Edit"
import api from "../../services/api"; import api from "../../services/api"
import TableRowSkeleton from "../../components/TableRowSkeleton"; import TableRowSkeleton from "../../components/TableRowSkeleton"
import ContactModal from "../../components/ContactModal"; import ContactModal from "../../components/ContactModal"
import ConfirmationModal from "../../components/ConfirmationModal/"; import ConfirmationModal from "../../components/ConfirmationModal/"
import { i18n } from "../../translate/i18n"; import { i18n } from "../../translate/i18n"
import MainHeader from "../../components/MainHeader"; import MainHeader from "../../components/MainHeader"
import Title from "../../components/Title"; import Title from "../../components/Title"
import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper"; import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper"
import MainContainer from "../../components/MainContainer"; import MainContainer from "../../components/MainContainer"
import toastError from "../../errors/toastError"; import toastError from "../../errors/toastError"
import { AuthContext } from "../../context/Auth/AuthContext"; import { AuthContext } from "../../context/Auth/AuthContext"
import { Can } from "../../components/Can"; import { Can } from "../../components/Can"
import apiBroker from "../../services/apiBroker"; import apiBroker from "../../services/apiBroker"
import fileDownload from 'js-file-download' import fileDownload from 'js-file-download'
import ContactCreateTicketModal from "../../components/ContactCreateTicketModal"; import ContactCreateTicketModal from "../../components/ContactCreateTicketModal"
@ -46,50 +46,50 @@ const reducer = (state, action) => {
if (action.type === "LOAD_CONTACTS") { if (action.type === "LOAD_CONTACTS") {
const contacts = action.payload; const contacts = action.payload
const newContacts = []; const newContacts = []
contacts.forEach((contact) => { contacts.forEach((contact) => {
const contactIndex = state.findIndex((c) => +c.id === +contact.id); const contactIndex = state.findIndex((c) => +c.id === +contact.id)
if (contactIndex !== -1) { if (contactIndex !== -1) {
state[contactIndex] = contact; state[contactIndex] = contact
} else { } else {
newContacts.push(contact); newContacts.push(contact)
} }
}); })
return [...state, ...newContacts]; return [...state, ...newContacts]
} }
if (action.type === "UPDATE_CONTACTS") { if (action.type === "UPDATE_CONTACTS") {
const contact = action.payload; const contact = action.payload
const contactIndex = state.findIndex((c) => +c.id === +contact.id); const contactIndex = state.findIndex((c) => +c.id === +contact.id)
if (contactIndex !== -1) { if (contactIndex !== -1) {
state[contactIndex] = contact; state[contactIndex] = contact
return [...state]; return [...state]
} else { } else {
return [contact, ...state]; return [contact, ...state]
} }
} }
if (action.type === "DELETE_CONTACT") { if (action.type === "DELETE_CONTACT") {
const contactId = action.payload; const contactId = action.payload
const contactIndex = state.findIndex((c) => +c.id === +contactId); const contactIndex = state.findIndex((c) => +c.id === +contactId)
if (contactIndex !== -1) { if (contactIndex !== -1) {
state.splice(contactIndex, 1); state.splice(contactIndex, 1)
} }
return [...state]; return [...state]
} }
if (action.type === "RESET") { if (action.type === "RESET") {
return []; return []
}
} }
};
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
mainPaper: { mainPaper: {
@ -98,24 +98,24 @@ const useStyles = makeStyles((theme) => ({
overflowY: "scroll", overflowY: "scroll",
...theme.scrollbarStyles, ...theme.scrollbarStyles,
}, },
})); }))
const Contacts = () => { const Contacts = () => {
const classes = useStyles(); const classes = useStyles()
const history = useHistory(); const history = useHistory()
const { user } = useContext(AuthContext); const { user } = useContext(AuthContext)
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false)
const [pageNumber, setPageNumber] = useState(1); const [pageNumber, setPageNumber] = useState(1)
const [searchParam, setSearchParam] = useState(""); const [searchParam, setSearchParam] = useState("")
const [contacts, dispatch] = useReducer(reducer, []); const [contacts, dispatch] = useReducer(reducer, [])
const [selectedContactId, setSelectedContactId] = useState(null); const [selectedContactId, setSelectedContactId] = useState(null)
const [contactModalOpen, setContactModalOpen] = useState(false); const [contactModalOpen, setContactModalOpen] = useState(false)
const [isCreateTicketModalOpen, setIsCreateTicketModalOpen] = useState(false) const [isCreateTicketModalOpen, setIsCreateTicketModalOpen] = useState(false)
const [deletingContact, setDeletingContact] = useState(null); const [deletingContact, setDeletingContact] = useState(null)
const [confirmOpen, setConfirmOpen] = useState(false); const [confirmOpen, setConfirmOpen] = useState(false)
const [hasMore, setHasMore] = useState(false); const [hasMore, setHasMore] = useState(false)
const [onQueueStatus, setOnQueueProcessStatus] = useState(undefined) const [onQueueStatus, setOnQueueProcessStatus] = useState(undefined)
@ -132,20 +132,20 @@ const Contacts = () => {
return return
} }
const formData = new FormData(); const formData = new FormData()
formData.append("adminId", user.id); formData.append("adminId", user.id)
formData.append("baseURL", process.env.REACT_APP_BACKEND_URL_PRIVATE,); formData.append("baseURL", process.env.REACT_APP_BACKEND_URL_PRIVATE,)
formData.append("frontURL", process.env.REACT_APP_FRONTEND_URL); formData.append("frontURL", process.env.REACT_APP_FRONTEND_URL)
formData.append("identifier", 'contacts_insert_csv'); formData.append("identifier", 'contacts_insert_csv')
formData.append("file", event.target.files[0]); formData.append("file", event.target.files[0])
const config = { const config = {
headers: { headers: {
'content-type': 'multipart/form-data', 'content-type': 'multipart/form-data',
}, },
}; }
const bulk_contact_insert = await apiBroker.post("/contacts/bulk/insert", formData, config); const bulk_contact_insert = await apiBroker.post("/contacts/bulk/insert", formData, config)
console.log(bulk_contact_insert.data) console.log(bulk_contact_insert.data)
@ -156,7 +156,7 @@ const Contacts = () => {
// history.go(0); // history.go(0);
} catch (err) { } catch (err) {
toastError(err); toastError(err)
} }
} }
@ -177,13 +177,10 @@ const Contacts = () => {
baseURL: process.env.REACT_APP_BACKEND_URL_PRIVATE, baseURL: process.env.REACT_APP_BACKEND_URL_PRIVATE,
identifier: 'contacts_insert_csv' identifier: 'contacts_insert_csv'
} }
}); })
if (insertOnQueue && insertOnQueue.data) { if (insertOnQueue && insertOnQueue.data) {
console.log('insertOnQueue: ', insertOnQueue.data)
console.log('data.app.file: ', insertOnQueue.data.app.file)
setZipFile(insertOnQueue.data.app.file) setZipFile(insertOnQueue.data.app.file)
setOnQueueProcessStatus(insertOnQueue.data.app.status) setOnQueueProcessStatus(insertOnQueue.data.app.status)
} }
@ -193,23 +190,23 @@ const Contacts = () => {
} catch (err) { } catch (err) {
console.log(err); console.log(err)
}
} }
};
fetchReportOnQueue(); fetchReportOnQueue()
}, 500); }, 500)
return () => clearTimeout(delayDebounceFn); return () => clearTimeout(delayDebounceFn)
}, [user]) }, [user])
useEffect(() => { useEffect(() => {
dispatch({ type: "RESET" }); dispatch({ type: "RESET" })
setPageNumber(1); setPageNumber(1)
}, [searchParam]); }, [searchParam])
useEffect(() => { useEffect(() => {
@ -220,7 +217,7 @@ const Contacts = () => {
setLoading(true); setLoading(true)
const delayDebounceFn = setTimeout(() => { const delayDebounceFn = setTimeout(() => {
const fetchContacts = async () => { const fetchContacts = async () => {
@ -230,25 +227,25 @@ const Contacts = () => {
return return
} }
const { data } = await api.get("/contacts/", { params: { searchParam, pageNumber }, }); const { data } = await api.get("/contacts/", { params: { searchParam, pageNumber }, })
dispatch({ type: "LOAD_CONTACTS", payload: data.contacts }); dispatch({ type: "LOAD_CONTACTS", payload: data.contacts })
setHasMore(data.hasMore); setHasMore(data.hasMore)
setLoading(false); setLoading(false)
} catch (err) { } catch (err) {
toastError(err); toastError(err)
} }
}; }
fetchContacts(); fetchContacts()
}, 500); }, 500)
return () => clearTimeout(delayDebounceFn); return () => clearTimeout(delayDebounceFn)
}, [searchParam, pageNumber]); }, [searchParam, pageNumber])
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket(process.env.REACT_APP_BACKEND_URL)
socket.on("contactsBulkInsertOnQueueStatus", (data) => { socket.on("contactsBulkInsertOnQueueStatus", (data) => {
if (data.action === 'update') { if (data.action === 'update') {
@ -261,7 +258,7 @@ const Contacts = () => {
setOnQueueProcessStatus(data.insertOnQueue.queueStatus) setOnQueueProcessStatus(data.insertOnQueue.queueStatus)
if (data.insertOnQueue.queueStatus === "success") { if (data.insertOnQueue.queueStatus === "success") {
history.go(0); history.go(0)
} }
} }
@ -270,18 +267,18 @@ const Contacts = () => {
socket.on("contact", (data) => { socket.on("contact", (data) => {
if (data.action === "update" || data.action === "create") { if (data.action === "update" || data.action === "create") {
dispatch({ type: "UPDATE_CONTACTS", payload: data.contact }); dispatch({ type: "UPDATE_CONTACTS", payload: data.contact })
} }
if (data.action === "delete") { if (data.action === "delete") {
dispatch({ type: "DELETE_CONTACT", payload: +data.contactId }); dispatch({ type: "DELETE_CONTACT", payload: +data.contactId })
} }
}); })
return () => { return () => {
socket.disconnect(); socket.disconnect()
}; }
}, [user, history]); }, [user, history])
const removeExtraSpace = (str) => { const removeExtraSpace = (str) => {
@ -291,18 +288,18 @@ const Contacts = () => {
} }
const handleSearch = (event) => { const handleSearch = (event) => {
setSearchParam(removeExtraSpace(event.target.value.toLowerCase())); setSearchParam(removeExtraSpace(event.target.value.toLowerCase()))
}; }
const handleOpenContactModal = () => { const handleOpenContactModal = () => {
setSelectedContactId(null); setSelectedContactId(null)
setContactModalOpen(true); setContactModalOpen(true)
}; }
const handleCloseContactModal = () => { const handleCloseContactModal = () => {
setSelectedContactId(null); setSelectedContactId(null)
setContactModalOpen(false); setContactModalOpen(false)
}; }
const handleOpenCreateTicketModal = (contactId) => { const handleOpenCreateTicketModal = (contactId) => {
setSelectedContactId(contactId) setSelectedContactId(contactId)
@ -330,46 +327,46 @@ const Contacts = () => {
// }; // };
const hadleEditContact = (contactId) => { const hadleEditContact = (contactId) => {
setSelectedContactId(contactId); setSelectedContactId(contactId)
setContactModalOpen(true); setContactModalOpen(true)
}; }
const handleDeleteContact = async (contactId) => { const handleDeleteContact = async (contactId) => {
try { try {
await api.delete(`/contacts/${contactId}`); await api.delete(`/contacts/${contactId}`)
toast.success(i18n.t("contacts.toasts.deleted")); toast.success(i18n.t("contacts.toasts.deleted"))
} catch (err) { } catch (err) {
toastError(err); toastError(err)
}
setDeletingContact(null)
setSearchParam("")
setPageNumber(1)
} }
setDeletingContact(null);
setSearchParam("");
setPageNumber(1);
};
const handleimportContact = async () => { const handleimportContact = async () => {
try { try {
await api.post("/contacts/import"); await api.post("/contacts/import")
history.go(0); history.go(0)
} catch (err) { } catch (err) {
toastError(err); toastError(err)
}
} }
};
const loadMore = () => { const loadMore = () => {
setPageNumber((prevState) => prevState + 1); setPageNumber((prevState) => prevState + 1)
}; }
const handleScroll = (e) => { const handleScroll = (e) => {
if (!hasMore || loading) return; if (!hasMore || loading) return
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget; const { scrollTop, scrollHeight, clientHeight } = e.currentTarget
if (scrollHeight - (scrollTop + 100) < clientHeight) { if (scrollHeight - (scrollTop + 100) < clientHeight) {
loadMore(); loadMore()
}
} }
};
const handleDownload = async () => { const handleDownload = async () => {
@ -378,16 +375,16 @@ const Contacts = () => {
try { try {
let res = await apiBroker.get(`/contacts/download/${zipfile}`, { responseType: 'blob' }); let res = await apiBroker.get(`/contacts/download/${zipfile}`, { responseType: 'blob' })
if (res) { if (res) {
fileDownload(res.data, `${zipfile}`); fileDownload(res.data, `${zipfile}`)
setOnQueueProcessStatus('empty') setOnQueueProcessStatus('empty')
} }
} catch (err) { } catch (err) {
console.log(err); console.log(err)
} }
@ -406,7 +403,7 @@ const Contacts = () => {
baseURL: process.env.REACT_APP_BACKEND_URL_PRIVATE, baseURL: process.env.REACT_APP_BACKEND_URL_PRIVATE,
identifier: 'contacts_insert_csv' identifier: 'contacts_insert_csv'
} }
}); })
if (res) { if (res) {
setOnQueueProcessStatus('empty') setOnQueueProcessStatus('empty')
@ -414,7 +411,7 @@ const Contacts = () => {
} catch (err) { } catch (err) {
console.log(err); console.log(err)
} }
@ -452,13 +449,13 @@ const Contacts = () => {
> >
{"CSV ALL"} {"CSV ALL"}
</Button> */} </Button> */}
</>); </>)
case 'pending' || 'processing': case 'pending' || 'processing':
return ( return (
<> <>
<span>PROCESSING...</span> <span>PROCESSING...</span>
</>); </>)
case 'success': case 'success':
return ( return (
@ -472,7 +469,7 @@ const Contacts = () => {
> >
{'DOWNLOAD'} {'DOWNLOAD'}
</Button> </Button>
</>); </>)
case 'error': case 'error':
return ( return (
<> <>
@ -485,16 +482,16 @@ const Contacts = () => {
> >
{'ERROR'} {'ERROR'}
</Button> </Button>
</>); </>)
case 'downloading': case 'downloading':
return ( return (
<> <>
<span>DOWNLOADING...</span> <span>DOWNLOADING...</span>
</>); </>)
default: default:
return (<><span>WAITING...</span></>); return (<><span>WAITING...</span></>)
} }
} }
@ -642,8 +639,8 @@ const Contacts = () => {
<IconButton <IconButton
size="small" size="small"
onClick={(e) => { onClick={(e) => {
setConfirmOpen(true); setConfirmOpen(true)
setDeletingContact(contact); setDeletingContact(contact)
}} }}
> >
<DeleteOutlineIcon /> <DeleteOutlineIcon />
@ -659,7 +656,7 @@ const Contacts = () => {
</Table> </Table>
</Paper> </Paper>
</MainContainer> </MainContainer>
); )
}; }
export default Contacts; export default Contacts

View File

@ -121,7 +121,7 @@ const Queues = () => {
const fetchSession = async () => { const fetchSession = async () => {
try { try {
const { data } = await api.get('/settings') const { data } = await api.get('/settings')
setSettings(data) setSettings(data.settings)
} catch (err) { } catch (err) {
toastError(err) toastError(err)
} }

View File

@ -52,7 +52,7 @@ const Settings = () => {
const fetchSession = async () => { const fetchSession = async () => {
try { try {
const { data } = await api.get('/settings') const { data } = await api.get('/settings')
setSettings(data) setSettings(data.settings)
} catch (err) { } catch (err) {
toastError(err) toastError(err)
} }
@ -198,6 +198,34 @@ const Settings = () => {
</Paper> </Paper>
</Container> </Container>
</div> </div>
<div className={classes.root}>
<Container className={classes.container} maxWidth="sm">
<Paper className={classes.paper}>
<Typography variant="body1">
Contato conversar com whatsapps distintos no omnihit
</Typography>
<Select
margin="dense"
variant="outlined"
native
id="oneContactChatWithManyWhats-setting"
name="oneContactChatWithManyWhats"
value={
settings &&
settings.length > 0 &&
getSettingValue('oneContactChatWithManyWhats')
}
className={classes.settingOption}
onChange={handleChangeSetting}
>
<option value="enabled">Ativado</option>
<option value="disabled">Desativado</option>
</Select>
</Paper>
</Container>
</div>
</div> </div>
)} )}
/> />

View File

@ -1,27 +1,27 @@
import React from "react"; import React from 'react'
import { BrowserRouter, Switch } from "react-router-dom"; import { BrowserRouter, Switch } from 'react-router-dom'
import { ToastContainer } from "react-toastify"; import { ToastContainer } from 'react-toastify'
import LoggedInLayout from "../layout"; import LoggedInLayout from '../layout'
import Dashboard from "../pages/Dashboard/"; import Dashboard from '../pages/Dashboard/'
import Report from "../pages/Report/";
import SchedulesReminder from "../pages/SchedulesReminder/";
import Tickets from "../pages/Tickets/";
import Signup from "../pages/Signup/";
import Login from "../pages/Login/";
import Connections from "../pages/Connections/";
import Settings from "../pages/Settings/";
import Users from "../pages/Users";
import Contacts from "../pages/Contacts/";
import QuickAnswers from "../pages/QuickAnswers/";
import Queues from "../pages/Queues/";
import { AuthProvider } from "../context/Auth/AuthContext";
import { WhatsAppsProvider } from "../context/WhatsApp/WhatsAppsContext";
import Route from "./Route";
import Dialogflows from "../pages/Dialogflow/"; import Dialogflows from "../pages/Dialogflow/";
import Report from '../pages/Report/'
import SchedulesReminder from '../pages/SchedulesReminder/'
import Tickets from '../pages/Tickets/'
import Signup from '../pages/Signup/'
import Login from '../pages/Login/'
import Connections from '../pages/Connections/'
import Campaign from '../pages/Campaign'
import Settings from '../pages/Settings/'
import Users from '../pages/Users'
import Contacts from '../pages/Contacts/'
import QuickAnswers from '../pages/QuickAnswers/'
import Queues from '../pages/Queues/'
import { AuthProvider } from '../context/Auth/AuthContext'
import { WhatsAppsProvider } from '../context/WhatsApp/WhatsAppsContext'
import Route from './Route'
const Routes = () => { const Routes = () => {
return ( return (
@ -33,28 +33,49 @@ const Routes = () => {
<WhatsAppsProvider> <WhatsAppsProvider>
<LoggedInLayout> <LoggedInLayout>
<Route exact path="/" component={Dashboard} isPrivate /> <Route exact path="/" component={Dashboard} isPrivate />
<Route exact path="/tickets/:ticketId?" component={Tickets} isPrivate /> <Route
exact
path="/tickets/:ticketId?"
component={Tickets}
isPrivate
/>
<Route exact path="/connections" component={Connections} isPrivate /> <Route
exact
path="/connections"
component={Connections}
isPrivate
/>
<Route exact path="/report" component={Report} isPrivate /> <Route exact path="/report" component={Report} isPrivate />
<Route exact path="/contacts" component={Contacts} isPrivate /> <Route exact path="/contacts" component={Contacts} isPrivate />
<Route exact path="/schedulesReminder" component={SchedulesReminder} isPrivate /> <Route
exact
path="/schedulesReminder"
component={SchedulesReminder}
isPrivate
/>
<Route exact path="/users" component={Users} isPrivate /> <Route exact path="/users" component={Users} isPrivate />
<Route exact path="/quickAnswers" component={QuickAnswers} isPrivate /> <Route
exact
path="/quickAnswers"
component={QuickAnswers}
isPrivate
/>
<Route exact path="/Settings" component={Settings} isPrivate /> <Route exact path="/Settings" component={Settings} isPrivate />
<Route exact path="/Queues" component={Queues} isPrivate /> <Route exact path="/Queues" component={Queues} isPrivate />
<Route exact path="/Dialogflows" component={Dialogflows} isPrivate /> <Route exact path="/Dialogflows" component={Dialogflows} isPrivate />
<Route exact path="/campaign" component={Campaign} isPrivate />
</LoggedInLayout> </LoggedInLayout>
</WhatsAppsProvider> </WhatsAppsProvider>
</Switch> </Switch>
<ToastContainer autoClose={3000} /> <ToastContainer autoClose={3000} />
</AuthProvider> </AuthProvider>
</BrowserRouter> </BrowserRouter>
); )
}; }
export default Routes; export default Routes