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_auth
*/wwebjs_auth/*
.wwebjs_cache
*/wwebjs_cache/*
# testing
/coverage

View File

View File

@ -1,3 +1,8 @@
PORT=8019
PORT_START=8020
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,45 +1,56 @@
const express = require('express');
const bodyparser = require('body-parser');
const dotenv = require('dotenv');
dotenv.config({ path: '.env' });
const express = require('express')
const bodyparser = require('body-parser')
const dotenv = require('dotenv')
dotenv.config({ path: '.env' })
const copyFolder = require('./helpers/copyFolder')
const createDir = require('./helpers/createDir')
const createFile = require('./helpers/createFile')
const path = require('path');
const path = require('path')
const db_info = require('./db_conn')
const fs = require('fs');
let mysql_conn = require('./helpers/mysql_conn.js');
const { exec, execSync, spawn } = require("child_process");
const fs = require('fs')
let mysql_conn = require('./helpers/mysql_conn.js')
const { exec, execSync, spawn } = require('child_process')
const startPm2Process = require('./helpers/startPm2Process');
const removeDir = require('./helpers/remove_dir');
const startPm2Process = require('./helpers/startPm2Process')
const removeDir = require('./helpers/remove_dir')
const setSessionName = require('./helpers/setSessionNumber')
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) {
let { app_name, whatsappId, client_url, number } = req.body
if(app_name){
if (app_name) {
app_name = app_name.trim()
}
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 directoriesInDIrectory = fs.readdirSync(sessionsPath, { withFileTypes: true })
const directoriesInDIrectory = fs
.readdirSync(sessionsPath, { withFileTypes: true })
.filter((item) => item.isDirectory())
.map((item) => item.name);
.map((item) => item.name)
console.log('directoriesInDIrectory: ', directoriesInDIrectory)
@ -48,146 +59,154 @@ app.post('/api/session', async function (req, res) {
let dirSessionsApp = path.join(sessionsPath, app_name)
if (dirExist.length == 0) {
let create = createDir(dirSessionsApp)
if (!create) {
res.status(500).json({ message: 'Cannot create the directory path!' })
return
}
}
let appPort = []
let existSubDir = false
for (let i = 0; i < directoriesInDIrectory.length; 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())
.map((item) => item.name);
.map((item) => item.name)
for (let x = 0; x < subDir.length; x++) {
console.log('subdir: ', subDir[x])
let whatsId = subDir[x].split('_')[0]
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: ', path.join(sessionsPath, directoriesInDIrectory[i], subDir[x]))
console.log(
'PATH: ',
path.join(sessionsPath, directoriesInDIrectory[i], subDir[x])
)
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) {
deletePm2Process(subDir[x], currPath)
removeDir(currPath)
}
else {
} else {
res.send('ok')
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
}
}
appPort = existSubDir ? Math.max(...appPort) + 1 : process.env.PORT_START
console.log('new port: ', appPort)
let dirSessionAppName
let numberSession = 1
// const dirSessionsNumberAppDirectories = fs.readdirSync(dirSessionsApp, { withFileTypes: true })
// .filter((item) => item.isDirectory() && item.name.includes(`${number}`))
// .map((item) => item.name);
let lstPass = process.env.PASS
// console.log('dirSessionsNumberAppDirectories', dirSessionsNumberAppDirectories, ' | dirSessionsApp: ', dirSessionsApp)
console.log('client_url: ', client_url)
let db = db_info.filter((e) => e.client_url == client_url)
if (db && db.length > 0) {
db = db[0].db_conf
if (!lstPass) {
console.log('PASS VARIABLE NOT FOUND INTO .ENV!')
return res.send('OK')
}
// 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) => {
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) {
reject(err)
}
else {
} else {
resolve(result)
}
});
}
)
})
console.log('whatsapp_numbers: ', whatsapp_numbers)
@ -195,11 +214,12 @@ app.post('/api/session', async function (req, res) {
let session_num = []
if (whatsapp_numbers && whatsapp_numbers.length > 0) {
console.log('whatsapp_numbers.length: ', whatsapp_numbers.length)
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
}
@ -209,16 +229,21 @@ app.post('/api/session', async function (req, res) {
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)
}
let index = 1;
let index = 1
while (index <= 4) {
if (!session_num.includes(index)) {
console.log(index)
numberSession = index
@ -226,11 +251,8 @@ app.post('/api/session', async function (req, res) {
}
index++
}
}
// numberSession = Math.max(...session_number) + 1
console.log('Number session: ', numberSession)
@ -239,61 +261,63 @@ app.post('/api/session', async function (req, res) {
dirSessionAppName = `${whatsappId}_${number}_${numberSession}_${appPort}`
destDir = path.join(dirSessionsApp, dirSessionAppName)
originDir = path.join(__dirname, '..', 'whats')
copyFolder(originDir, destDir)
if (db && Object.keys(db).length > 0) {
console.log('kkkkkkkkkkkkkkk')
if (
db_credentials.db_conf &&
Object.keys(db_credentials.db_conf).length > 0
) {
console.log('***SUCCESS SEED DIRECTORY CREATED***')
let whatsName
const whatsapp = await new Promise((resolve, reject) => {
mysql_conn(db).query("SELECT name from Whatsapps where id = ?", [whatsappId], (err, result) => {
mysql_conn(db_credentials.db_conf).query(
'SELECT name from Whatsapps where id = ?',
[whatsappId],
(err, result) => {
if (err) {
reject(err)
}
else {
} else {
resolve(result)
}
});
}
)
})
if (whatsapp[0]["name"].split('->').length > 0) {
whatsName = `${whatsapp[0]["name"].split('->')[0]} -> S${numberSession}`
}
else {
whatsName = `${whatsapp[0]["name"]} -> S${numberSession}`
if (whatsapp[0]['name'].split('->').length > 0) {
whatsName = `${whatsapp[0]['name'].split('->')[0]} -> S${numberSession}`
} else {
whatsName = `${whatsapp[0]['name']} -> S${numberSession}`
}
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) => {
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) {
if (err) {
reject(err)
console.log("===> ERROR: " + err);
}
else {
console.log('===> ERROR: ' + err)
} else {
resolve(result)
// console.log('RESULT: ', result)
}
// else
// console.log('myslq result: ', result);
});
}
)
})
let whatsappName = `${number} - s${numberSession}`
@ -301,91 +325,98 @@ app.post('/api/session', async function (req, res) {
console.log('-------------- numberSession', numberSession)
if (whatsapp.length > 0) {
if (whatsapp[0]['name'].split(' ').length > 0) {
whatsappName = `${whatsapp[0]['name'].split(' ')[0]} - S${numberSession}`
whatsappName = `${whatsapp[0]['name'].split(' ')[0]
} - S${numberSession}`
}
}
console.log('whatsapp: ', whatsapp)
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.write("# NUMBER AND NAME THAT WILL BE DISPLAYED ON CONSOLE\n");
stream.write(`MOBILEUID=${number}\n`);
stream.write(`MOBILENAME=${whatsappName}\n`);
stream.write("\n");
stream.write('# NUMBER AND NAME THAT WILL BE DISPLAYED ON CONSOLE\n')
stream.write(`MOBILEUID=${number}\n`)
stream.write(`MOBILENAME=${whatsappName}\n`)
stream.write('\n')
stream.write("# PORT NUMBER FOR THIS API\n");
stream.write(`PORT=${appPort}\n`);
stream.write("\n");
stream.write('# PORT NUMBER FOR THIS API\n')
stream.write(`PORT=${appPort}\n`)
stream.write('\n')
stream.write("# URL FROM THE OMNIHIT BACKEND API\n");
stream.write(`CLIENT_URL=${client_url}\n`);
stream.write("\n");
stream.write('# URL FROM THE OMNIHIT BACKEND API\n')
stream.write(`CLIENT_URL=${client_url}\n`)
stream.write('\n')
stream.write("# OMNIHIT DATABASE\n");
stream.write('# OMNIHIT DATABASE\n')
keys.forEach((key, index) => {
stream.write(`${key}=${db[key]}\n`);
});
stream.write("\n");
stream.write(`${key}=${db_credentials.db_conf[key]}\n`)
})
stream.write('\n')
stream.write(`# WHATSAPP ID OF THE TABLE Whatsapps FROM THE OMNIHIT DATABASE\n`);
stream.write(`WHATSAPP_ID=${whatsappId}`);
stream.write(
`# 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)
execSync(`npm install`, { cwd: destDir }, (error, stdout, stderr) => {
if (error) {
console.log(`error: ${error.message}`);
return;
console.log(`error: ${error.message}`)
return
}
if (stderr) {
console.log(`stderr: ${stderr}`);
return;
console.log(`stderr: ${stderr}`)
return
}
console.log(`stdout: ${stdout}`);
});
startPm2Process(dirSessionAppName, 'app.js', destDir, appPort)
console.log(`stdout: ${stdout}`)
})
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) {
const { app_name, whatsappId, client_url, number } = req.body
})
app.post('/api/session/del', async function (req, res) {
let { whatsappId, app_name } = req.body
if(app_name){
if (app_name) {
app_name = app_name.trim()
}
const sessionsPath = path.join(__dirname, '..', 'sessions')
const directoriesInDIrectory = fs.readdirSync(sessionsPath, { withFileTypes: true })
const directoriesInDIrectory = fs
.readdirSync(sessionsPath, { withFileTypes: true })
.filter((item) => item.isDirectory())
.map((item) => item.name);
.map((item) => item.name)
console.log('directoriesInDIrectory: ', directoriesInDIrectory)
@ -393,94 +424,95 @@ app.post('/api/session/del', async function (req, res) {
console.log('dirExist: ', dirExist)
if (dirExist.length == 0)
res.send('ok')
if (dirExist.length == 0) res.send('ok')
for (let i = 0; i < directoriesInDIrectory.length; 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())
.map((item) => item.name);
.map((item) => item.name)
for (let x = 0; x < subDir.length; x++) {
console.log('subdir: ', subDir[x])
let whatsId = subDir[x].split('_')[0]
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)
removeDir(currPath)
return res.send('ok')
}
}
}
res.send('ok')
})
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) {
console.error(' ');
console.error('----- ' + (new Date).toUTCString() + ' ----------------------------------')
console.error(' ')
console.error(
'----- ' + new Date().toUTCString() + ' ----------------------------------'
)
console.error('Erro uncaughtException: ', err.message)
console.error(err.stack)
console.error(' ');
console.error(' ')
return
});
})
function deletePm2Process(process_name, currPath) {
pm2.connect(function (err) {
if (err) {
console.error(err);
console.error(err)
}
pm2.list(function (err, processes) {
if (err) {
console.error(err);
console.error(err)
}
processes.forEach(function (process) {
console.log(".........process.name: ", process.name);
console.log('.........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) {
console.log(`error: ${error.message}`);
return;
console.log(`error: ${error.message}`)
return
}
if (stderr) {
console.log(`stderr: ${stderr}`);
return;
console.log(`stderr: ${stderr}`)
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 { execSync } = require("child_process");
const pm2 = require('pm2')
const { execSync } = require("child_process")
function startPm2Process(process_name, file, path, port) {
function startPm2Process(process_name, file, path, env) {
pm2.connect(function (err) {
if (err) {
console.error(err);
console.error(err)
// process.exit(2);
}
console.log('ENV PM2: ', env)
pm2.start({
name: process_name,
script: file,
cwd: path,
env: {
PORT: port
}
env
// env: {
// NODE_ENV: 'production',
// PORT: port,
// }
// additional options here if needed
}, function (err, apps) {
if (err) {
console.error(err);
console.error(err)
// process.exit(2);
}
else {
execSync(`pm2 save --force`, { cwd: path }, (error, stdout, stderr) => {
if (error) {
console.log(`error: ${error.message}`);
return;
console.log(`error: ${error.message}`)
return
}
if (stderr) {
console.log(`stderr: ${stderr}`);
return;
console.log(`stderr: ${stderr}`)
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>",
"license": "MIT",
"dependencies": {
"bcrypt": "^5.1.0",
"body-parser": "^1.20.1",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"fs-extra": "^11.1.0",
"mongoose": "^7.4.0",
"mysql": "^2.18.1",
"nodemon": "^2.0.20",
"socket.io": "^4.5.4"

View File

@ -1,9 +1,9 @@
# NUMBER AND NAME THAT WILL BE DISPLAYED ON CONSOLE
MOBILEUID=5517988310949
MOBILENAME=Numero de teste
MOBILENAME=test - S1
# PORT NUMBER FOR THIS API
PORT=8020
PORT=8029
# URL FROM THE OMNIHIT BACKEND API
CLIENT_URL=http://localhost:8080
@ -16,5 +16,10 @@ DB_PASS=strongpassword
DB_PORT=3306
# 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",
"express": "^4.17.1",
"form-data": "^4.0.0",
"fs-extra": "^11.1.0",
"logger": "^0.0.1",
"mime": "^2.4.5",
"mongodb": "^4.1.1",
"mongoose": "^7.4.3",
"multer": "^1.4.4",
"mysql": "^2.18.1",
"node-os-utils": "^1.3.5",
"qr-encode": "^0.3.0",
"qrcode-terminal": "^0.12.0",
"socket.io": "^4.5.4",
"socket.io-client": "^4.5.4",
"fs-extra": "^11.1.0"
"socket.io-client": "^4.5.4"
},
"devDependencies": {
"nodemon": "^2.0.20"

View File

@ -23,8 +23,8 @@
"bcryptjs": "^2.4.3",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
"date-fns": "^2.16.1",
"date-fns-tz": "^1.3.4",
"date-fns": "^2.30.0",
"date-fns-tz": "^1.3.8",
"dotenv": "^8.2.0",
"express": "^4.17.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 DeleteContactService from "../services/ContactServices/DeleteContactService";
import CheckContactNumber from "../services/WbotServices/CheckNumber"
import CheckContactNumber from "../services/WbotServices/CheckNumber";
import CheckIsValidContact from "../services/WbotServices/CheckIsValidContact";
import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl";
import AppError from "../errors/AppError";
@ -47,42 +47,46 @@ type IndexGetContactQuery = {
export const index = async (req: Request, res: Response): Promise<Response> => {
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) {
pageNumber = '1'
pageNumber = "1";
}
// TEST DEL
if (searchParam && searchParam.trim().length > 0 && process.env.CACHE) {
try {
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) {
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, count: data.length, hasMore: data.length > 0 ? true : false });
return res.json({
contacts: data,
count: data.length,
hasMore: data.length > 0 ? true : false
});
}
} 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);
}
console.log('QUERY CONTACTS FROM DATABASE SEARCH PARAM: ', searchParam)
const { contacts, count, hasMore } = await ListContactsService({ searchParam, pageNumber });
const { contacts, count, hasMore } = await ListContactsService({
searchParam,
pageNumber
});
return res.json({ contacts, count, hasMore });
};
@ -120,14 +124,13 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
// const validNumber: any = await CheckContactNumber(newContact.number)
if(!validNumber){
if (!validNumber) {
throw new AppError("ERR_WAPP_CHECK_CONTACT");
}
const profilePicUrl = await GetProfilePicUrl(validNumber);
console.log('xxxxxxxxxxx profilePicUrl: ',profilePicUrl)
console.log("xxxxxxxxxxx profilePicUrl: ", profilePicUrl);
// console.log(`newContact.name: ${newContact.name}\n
// newContact.number: ${newContact.number}\n
@ -146,7 +149,7 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
email,
useDialogflow,
profilePicUrl: profilePicUrl,
extraInfo,
extraInfo
});
const io = getIO();
@ -175,7 +178,7 @@ export const update = async (
const schema = Yup.object().shape({
name: Yup.string(),
number: Yup.string()
.matches(/^\d+$/,"Invalid number format. Only numbers is allowed.")
.matches(/^\d+$/, "Invalid number format. Only numbers is allowed.")
.matches(/^55\d+$/, "The number must start with 55.")
});
@ -257,7 +260,14 @@ export const contacsBulkInsertOnQueue = async (req: Request, res: Response): Pro
// 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();
io.emit("contactsBulkInsertOnQueueStatus", {
@ -266,17 +276,14 @@ export const contacsBulkInsertOnQueue = async (req: Request, res: Response): Pro
adminId: adminId,
identifier: identifier,
queueStatus: queueStatus,
file: file
file: file,
campaign
}
});
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 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> => {
// if (req.user.profile !== "master") {
@ -13,7 +16,76 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
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 (
@ -31,6 +103,8 @@ export const update = async (
value
});
loadSettings();
const io = getIO();
io.emit("settings", {
action: "update",

View File

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

View File

@ -17,6 +17,7 @@ import UserOnlineTime from "../models/UserOnlineTime";
import Dialogflow from "../models/Dialogflow";
import QueryItem from "../models/QueryItem";
import SettingTicket from "../models/SettingTicket";
// eslint-disable-next-line
const dbConfig = require("../config/database");
// import dbConfig from "../config/database";
@ -40,7 +41,8 @@ const models = [
StatusChatEnd,
UserOnlineTime,
Dialogflow,
QueryItem
QueryItem,
SettingTicket
];
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 AppError from "../errors/AppError";
import Ticket from "../models/Ticket";
import ListWhatsAppsNumber from "../services/WhatsappService/ListWhatsAppsNumber";
import { getSettingValue } from "./WhaticketSettings";
const CheckContactOpenTickets = async (contactId: number): Promise<void> => {
const ticket = await Ticket.findOne({
const CheckContactOpenTickets = async (
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"] } }
});
}
if (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 { logger } from "../utils/logger";
import { v4 as uuidv4 } from 'uuid';
import { v4 as uuidv4 } from "uuid";
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;
//test del
import createOrUpdateOnlineUserService from "../services/UserServices/CreateOrUpdateOnlineUserService";
import { splitDateTime } from "../helpers/SplitDateTime";
import format from 'date-fns/format';
import ptBR from 'date-fns/locale/pt-BR';
import format from "date-fns/format";
import ptBR from "date-fns/locale/pt-BR";
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 Whatsapp from "../models/Whatsapp";
let count: number = 0
let listOnline: any[] = []
let listOnlineAux: any[] = []
let countOnline: number = 0
let obj: any = { listOnline: [], uuid: null, listOnlineAux: [] }
let count: number = 0;
let listOnline: any[] = [];
let listOnlineAux: any[] = [];
let countOnline: number = 0;
let obj: any = { listOnline: [], uuid: null, listOnlineAux: [] };
let lstOnline: any[] = [];
let lstOnlineAux: any[] = [];
let lstTry: any[] = [];
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 dateTime = splitDateTime(
new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR }))
);
export const initIO = (httpServer: Server): SocketIO => {
io = new SocketIO(httpServer, {
@ -43,7 +50,6 @@ export const initIO = (httpServer: Server): SocketIO => {
maxHttpBufferSize: 1e8
});
io.on("connection", socket => {
logger.info("Client Connected");
@ -53,9 +59,8 @@ export const initIO = (httpServer: Server): SocketIO => {
});
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) => {
@ -65,154 +70,141 @@ export const initIO = (httpServer: Server): SocketIO => {
});
socket.on("media_uploaded", async (data: any) => {
handleMessage(data.msg, data);
});
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) => {
// console.log('userId: ', userId)
obj.uuid = uuidv4()
obj.uuid = uuidv4();
if (userId.logoutUserId) {
let index = lstOnline.findIndex((x: any) => x.id == userId.logoutUserId)
let index = lstOnline.findIndex(
(x: any) => x.id == userId.logoutUserId
);
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) {
lstOnlineAux.splice(index, 1)
lstOnlineAux.splice(index, 1);
}
return
return;
}
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) {
listOnlineAux.push({ 'id': userId })
}
else {
return
listOnlineAux.push({ id: userId });
} else {
return;
}
lstOnline.push({ 'id': userId, 'status': 'online', 'try': 0 })
lstOnline.push({ id: userId, status: "online", try: 0 });
lstOnlineAux.push({ 'id': userId })
console.log(' 1 PUSHED NEW USER ID 1: ', userId)
lstOnlineAux.push({ id: userId });
console.log(" 1 PUSHED NEW USER ID 1: ", userId);
obj.listOnline = lstOnline
}
else {
const indexAux = lstOnlineAux.findIndex((e: any) => e.id == userId)
obj.listOnline = lstOnline;
} else {
const indexAux = lstOnlineAux.findIndex((e: any) => e.id == userId);
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) {
lstOnline.push({ id: userId, status: "online", try: 0 });
console.log(" 2 PUSHED NEW USER ID: ", userId);
lstOnline.push({ 'id': userId, 'status': 'online', 'try': 0 })
console.log(' 2 PUSHED NEW USER ID: ', userId)
obj.listOnline = lstOnline
}
else {
if (countOnline > (lstOnline.length - 1)) {
obj.listOnline = lstOnline;
} else {
if (countOnline > lstOnline.length - 1) {
lstOnline.forEach((x: any) => {
if (lstOnlineAux.map((e: any) => e.id).includes(x.id)) {
x.try = 0
x.status = 'online'
x.try = 0;
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) {
difference.forEach((e) => {
e.try += 1
difference.forEach(e => {
e.try += 1;
if (e.try > 1) {
e.status = 'waiting...'
e.status = "waiting...";
}
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) {
lstOnline.splice(index, 1);
}
}
});
}
obj.listOnline = lstOnline;
obj.listOnlineAux = lstOnlineAux;
lstOnline.splice(index, 1)
lstOnlineAux = [];
listOnlineAux = [];
countOnline = -1;
}
countOnline++;
}
}
})
}
obj.listOnline = lstOnline
obj.listOnlineAux = lstOnlineAux
lstOnlineAux = []
listOnlineAux = []
countOnline = -1
}
countOnline++
}
}
exports.ob = obj
exports.ob = obj;
});
socket.on("joinChatBox", (ticketId: string) => {
@ -235,49 +227,51 @@ export const initIO = (httpServer: Server): SocketIO => {
});
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==2 && !([...rooms][1].startsWith('session_'))) return
if (rooms && rooms.size == 1) 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) {
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 &&
[...rooms][1].startsWith('session_') &&
whatsappIds.includes([...rooms][1].replace('session_', ''))) {
let whatsappId = [...rooms][1].replace("session_", "");
console.log([...rooms][1])
let whatsappId = [...rooms][1].replace('session_', '')
const whatsapp = await Whatsapp.findByPk(whatsappId, {})
const whatsapp = await Whatsapp.findByPk(whatsappId, {});
if (whatsapp) {
await whatsapp.update({ status: 'OPENING' });
await whatsapp.update({ status: "OPENING" });
io.emit("whatsappSession", {
action: "update",
session: whatsapp
});
}
}
}
});
});
return io;
@ -290,12 +284,9 @@ export const getIO = (): SocketIO => {
return io;
};
function writeFileAsync(arg0: any, data: any, arg2: string) {
throw new Error("Function not implemented.");
}
// exports.listOnlineUsers = 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);
settingRoutes.put(
"/settings/ticket",
isAuth,
SettingController.updateTicketSettings
);
// change setting key to key in future
settingRoutes.put("/settings/:settingKey", isAuth, SettingController.update);
export default settingRoutes;

View File

@ -12,16 +12,22 @@ import { loadContactsCache } from "./helpers/ContactsCache";
import "./helpers/CloseBotTickets";
import { loadSchedulesCache } from "./helpers/SchedulingNotifyCache";
import { delRestoreControllFile } from "./helpers/RestoreControll";
import "./helpers/AutoCloseTickets";
import "./helpers/SchedulingNotifySendMessage"
import "./helpers/SchedulingNotifySendMessage";
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, () => {
logger.info(`Server started on port: ${process.env.PORT}`);
});
// if (global.gc) {
// console.log(">> Starting Garbage Collector...");
// global.gc();
@ -34,99 +40,84 @@ initIO(server);
// StartAllWhatsAppsSessions();
gracefulShutdown(server);
(async () => {
console.log("os.tmpdir(): ", os.tmpdir());
loadSettings();
(async()=>{
console.log('os.tmpdir(): ', os.tmpdir())
let whatsapps: any = await Whatsapp.findAll({ attributes: ['id', 'url'] })
let whatsapps: any = await Whatsapp.findAll({ attributes: ["id", "url"] });
// console.log('whatsapps: ', whatsapps)
if (whatsapps && whatsapps.length > 0) {
for (let i = 0; i < whatsapps.length; i++) {
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) {
throw new Error('Response null');
throw new Error("Response null");
}
if (response.data.data && response.data.data == 'CONNECTED') {
await whatsapps[i].update({ status: 'CONNECTED' });
if (response.data.data && response.data.data == "CONNECTED") {
await whatsapps[i].update({ status: "CONNECTED" });
}
} 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));
}
}
if (process.env.CACHE) {
const cacheLength = await cacheSize();
const cacheLength = await cacheSize()
console.log('cacheSize: ', cacheLength)
console.log("cacheSize: ", cacheLength);
if (cacheLength == 0) {
console.log('Loading from cache...')
await flushCache()
await loadContactsCache()
await loadTicketsCache()
console.log("Loading from cache...");
await flushCache();
await loadContactsCache();
await loadTicketsCache();
}
await loadSchedulesCache()
await loadSchedulesCache();
// await loadWhatsappCache()
}
delRestoreControllFile()
})()
delRestoreControllFile();
})();
setTimeout(async () => {
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) {
for (let i = 0; i < users.length; i++) {
io.emit("reload_page", {
action: "update",
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));
}
}
}, 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 {
const defaultWhatsapp = await GetDefaultWhatsApp(userId);
if (!queueId) {
const user = await User.findByPk(userId, { raw: true });
if (!queueId) {
const matchingQueue = await whatsappQueueMatchingUserQueue(
userId,
defaultWhatsapp,
@ -45,7 +46,7 @@ const CreateTicketService = async ({
queueId = matchingQueue ? matchingQueue.queueId : undefined;
}
await CheckContactOpenTickets(contactId);
await CheckContactOpenTickets(contactId, defaultWhatsapp.id);
const { isGroup } = await ShowContactService(contactId);

View File

@ -6,7 +6,8 @@ import Ticket from "../../models/Ticket";
import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService";
import ShowTicketService from "./ShowTicketService";
import AppError from "../../errors/AppError";
import ListWhatsAppsNumber from "../WhatsappService/ListWhatsAppsNumber";
import { getSettingValue } from "../../helpers/WhaticketSettings";
const FindOrCreateTicketService = async (
contact: Contact,
@ -14,10 +15,23 @@ const FindOrCreateTicketService = async (
unreadMessages: number,
groupContact?: Contact
): Promise<Ticket> => {
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: {
status: {
[Op.or]: ["open", "pending", "queueChoice"]
@ -25,10 +39,10 @@ const FindOrCreateTicketService = async (
contactId: groupContact ? groupContact.id : contact.id
}
});
}
const { queues, greetingMessage } = await ShowWhatsAppService(whatsappId);
//Habilitar esse caso queira usar o bot
const botInfo = await BotIsOnQueue('botqueue')
// const botInfo = { isOnQueue: false }
@ -46,10 +60,7 @@ const FindOrCreateTicketService = async (
order: [["updatedAt", "DESC"]]
});
if (ticket) {
await ticket.update({
status: "pending",
userId: null,
@ -59,7 +70,6 @@ const FindOrCreateTicketService = async (
}
if (!ticket && !groupContact) {
ticket = await Ticket.findOne({
where: {
updatedAt: {
@ -77,7 +87,6 @@ const FindOrCreateTicketService = async (
});
if (ticket) {
await ticket.update({
status: "pending",
userId: null,
@ -87,11 +96,10 @@ const FindOrCreateTicketService = async (
}
if (!ticket) {
let status = "pending"
let status = "pending";
if (queues.length > 1 && !botInfo.isOnQueue) {
status = "queueChoice"
status = "queueChoice";
}
ticket = await Ticket.create({
@ -113,9 +121,8 @@ const FindOrCreateTicketService = async (
ticket = await ShowTicketService(ticket.id);
return ticket;
} 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);
}
};

View File

@ -6,12 +6,10 @@ import SendWhatsAppMessage from "../WbotServices/SendWhatsAppMessage";
import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService";
import ShowTicketService from "./ShowTicketService";
import { createOrUpdateTicketCache } from '../../helpers/TicketCache'
import { createOrUpdateTicketCache } from "../../helpers/TicketCache";
import AppError from "../../errors/AppError";
import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket";
var flatten = require('flat')
var flatten = require("flat");
interface TicketData {
status?: string;
@ -19,12 +17,13 @@ interface TicketData {
queueId?: number;
statusChatEnd?: string;
unreadMessages?: number;
whatsappId?: string | number;
}
interface Request {
ticketData: TicketData;
ticketId: string | number;
msg?: string
msg?: string;
}
interface Response {
@ -36,12 +35,17 @@ interface Response {
const UpdateTicketService = async ({
ticketData,
ticketId,
msg=''
msg = ""
}: Request): Promise<Response> => {
try {
const { status, userId, queueId, statusChatEnd, unreadMessages } = ticketData;
const {
status,
userId,
queueId,
statusChatEnd,
unreadMessages,
whatsappId
} = ticketData;
const ticket = await ShowTicketService(ticketId);
// await SetTicketMessagesAsRead(ticket);
@ -50,7 +54,7 @@ const UpdateTicketService = async ({
const oldUserId = ticket.user?.id;
if (oldStatus === "closed") {
await CheckContactOpenTickets(ticket.contact.id);
await CheckContactOpenTickets(ticket.contact.id, ticket.whatsappId);
}
await ticket.update({
@ -58,36 +62,36 @@ const UpdateTicketService = async ({
queueId,
userId,
unreadMessages,
statusChatEnd
statusChatEnd,
whatsappId
});
await ticket.reload();
if (msg.length > 0) {
if (msg?.trim().length > 0) {
setTimeout(async () => {
sendWhatsAppMessageSocket(ticket, msg)
}, 2000)
sendWhatsAppMessageSocket(ticket, msg);
}, 2000);
}
// TEST DEL
try {
// const { name, number } = await ShowContactService(ticket.contactId)
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
delete ticket_obj['contact']['extraInfo']
delete ticket_obj['user']
delete ticket_obj["contact"]["extraInfo"];
delete ticket_obj["user"];
ticket_obj = flatten(ticket_obj)
await createOrUpdateTicketCache(`ticket:${ticket.id}`, ticket_obj)
ticket_obj = flatten(ticket_obj);
await createOrUpdateTicketCache(`ticket:${ticket.id}`, ticket_obj);
} 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)
.to("notification")
.to(ticketId.toString())
@ -109,20 +112,16 @@ const UpdateTicketService = async ({
ticket
});
io.emit("ticketStatus", {
action: "update",
ticketStatus: { ticketId: ticket.id, status: ticket.status }
});
return { ticket, oldStatus, oldUserId };
} 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);
}
};
export default UpdateTicketService;

View File

@ -1,40 +1,62 @@
import { Sequelize, } from "sequelize";
import { Sequelize } from "sequelize";
const dbConfig = require("../../config/database");
const sequelize = new Sequelize(dbConfig);
const { QueryTypes } = require('sequelize');
const { QueryTypes } = require("sequelize");
interface Request {
profile: string;
profile?: string;
userId?: string | number;
}
const QueuesByUser = async ({ profile, userId }: Request): Promise<any[]> => {
let queueByUsersInfo: any = [];
let queueByUsersInfo = []
if (userId) {
if (userId && profile) {
// 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
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 {
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 if (profile) {
// 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
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;
};
export default QueuesByUser;

View File

@ -7,7 +7,21 @@ import { copyFolder } from "../../helpers/CopyFolder";
import { removeDir } from "../../helpers/DeleteDirectory";
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 {
@ -37,6 +51,8 @@ import ShowQueueService from "../QueueService/ShowQueueService";
import ShowTicketMessage from "../TicketServices/ShowTicketMessage";
import BotIsOnQueue from "../../helpers/BotIsOnQueue";
import Queue from "../../models/Queue";
import SettingTicket from "../../models/SettingTicket";
import fs from "fs";
@ -914,6 +930,19 @@ const handleMessage = async (msg: any, wbot: any): Promise<void> => {
// let groupContact: Contact | undefined;
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])))
// 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") {
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) {
Sentry.captureException(err);
console.log("xxxxxxxxxxxxx err: ", err);

View File

@ -1,30 +1,32 @@
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({
// raw: true,
// where: { id: whatsappId }
// })
const whatsapp = await Whatsapp.findByPk(whatsappId, { raw: true })
let whatsapps: any = [];
if (whatsapp) {
const whatsapps = await Whatsapp.findAll({
if (status) {
whatsapps = await Whatsapp.findAll({
raw: true,
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;

View File

@ -4,13 +4,13 @@
"private": true,
"dependencies": {
"@date-io/date-fns": "^1.3.13",
"@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@material-ui/core": "^4.12.1",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.56",
"@material-ui/pickers": "^3.3.10",
"@mui/material": "^5.3.0",
"@mui/material": "^5.14.4",
"@mui/x-data-grid": "^5.3.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.0.4",
@ -20,6 +20,7 @@
"dotenv": "^16.0.1",
"emoji-mart": "^3.0.1",
"formik": "^2.2.0",
"formik-material-ui-pickers": "^1.0.0-alpha.1",
"i18next": "^19.8.2",
"i18next-browser-languagedetector": "^6.0.1",
"js-file-download": "^0.4.12",
@ -30,6 +31,7 @@
"react": "^17.0.2",
"react-color": "^2.19.3",
"react-csv": "^2.2.2",
"react-datepicker": "^4.16.0",
"react-dom": "^17.0.2",
"react-modal-image": "^2.5.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";
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 (i18n.exists(`backendErrors.${errorMsg}`)) {
toast.error(i18n.t(`backendErrors.${errorMsg}`), {

View File

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

View File

@ -1,41 +1,46 @@
import React, { useContext, useEffect, useState } from "react";
import { Link as RouterLink } from "react-router-dom";
import React, { useContext, useEffect, useState } from 'react'
import { Link as RouterLink } from 'react-router-dom'
import DeviceHubOutlined from "@material-ui/icons/DeviceHubOutlined"
import ListItem from "@material-ui/core/ListItem";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import ListSubheader from "@material-ui/core/ListSubheader";
import Divider from "@material-ui/core/Divider";
import { Badge } from "@material-ui/core";
import DashboardOutlinedIcon from "@material-ui/icons/DashboardOutlined";
import ListItemText from "@material-ui/core/ListItemText"
import ListSubheader from "@material-ui/core/ListSubheader"
import Divider from "@material-ui/core/Divider"
import { Badge } from "@material-ui/core"
import DashboardOutlinedIcon from "@material-ui/icons/DashboardOutlined"
import ListItem from '@material-ui/core/ListItem'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import ReportOutlinedIcon from "@material-ui/icons/ReportOutlined";
import SendOutlined from "@material-ui/icons/SendOutlined";
import ReportOutlinedIcon from '@material-ui/icons/ReportOutlined'
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 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 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 { i18n } from "../translate/i18n";
import { WhatsAppsContext } from "../context/WhatsApp/WhatsAppsContext";
import { AuthContext } from "../context/Auth/AuthContext";
import { Can } from "../components/Can";
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) {
const { icon, primary, to, className } = props;
const { icon, primary, to, className } = props
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]
);
)
return (
<li>
@ -44,60 +49,59 @@ function ListItemLink(props) {
<ListItemText primary={primary} />
</ListItem>
</li>
);
)
}
const MainListItems = (props) => {
const { setDrawerOpen } = props;
const { whatsApps } = useContext(WhatsAppsContext);
const { user } = useContext(AuthContext);
const [connectionWarning, setConnectionWarning] = useState(false);
const { setDrawerOpen } = props
const { whatsApps } = useContext(WhatsAppsContext)
const { user } = useContext(AuthContext)
const [connectionWarning, setConnectionWarning] = useState(false)
useEffect(() => {
const delayDebounceFn = setTimeout(() => {
if (whatsApps.length > 0) {
const offlineWhats = whatsApps.filter((whats) => {
return (
whats.status === "qrcode" ||
whats.status === "PAIRING" ||
whats.status === "DISCONNECTED" ||
whats.status === "TIMEOUT" ||
whats.status === "OPENING"
);
});
whats.status === 'qrcode' ||
whats.status === 'PAIRING' ||
whats.status === 'DISCONNECTED' ||
whats.status === 'TIMEOUT' ||
whats.status === 'OPENING'
)
})
if (offlineWhats.length > 0) {
setConnectionWarning(true);
setConnectionWarning(true)
} else {
setConnectionWarning(false);
setConnectionWarning(false)
}
}
}, 2000);
return () => clearTimeout(delayDebounceFn);
}, [whatsApps]);
}, 2000)
return () => clearTimeout(delayDebounceFn)
}, [whatsApps])
return (
//Solicitado pelo Adriano: Click no LinkItem e fechar o menu!
<div onClick={() => setDrawerOpen(false)}>
<ListItemLink
to="/tickets"
primary={i18n.t("mainDrawer.listItems.tickets")}
primary={i18n.t('mainDrawer.listItems.tickets')}
icon={<WhatsAppIcon />}
/>
<ListItemLink
to="/contacts"
primary={i18n.t("mainDrawer.listItems.contacts")}
primary={i18n.t('mainDrawer.listItems.contacts')}
icon={<ContactPhoneOutlinedIcon />}
/>
<ListItemLink to="/schedulesReminder"
primary={i18n.t("mainDrawer.listItems.reminders")}
<ListItemLink
to="/schedulesReminder"
primary={i18n.t('mainDrawer.listItems.schedules')}
icon={<SendOutlined />}
/>
<ListItemLink
to="/quickAnswers"
primary={i18n.t("mainDrawer.listItems.quickAnswers")}
primary={i18n.t('mainDrawer.listItems.quickAnswers')}
icon={<QuestionAnswerOutlinedIcon />}
/>
<Can
@ -106,31 +110,47 @@ const MainListItems = (props) => {
yes={() => (
<>
<Divider />
<ListSubheader inset>{i18n.t("mainDrawer.listItems.administration")}</ListSubheader>
<ListSubheader inset>
{i18n.t('mainDrawer.listItems.administration')}
</ListSubheader>
<ListItemLink
to="/users"
primary={i18n.t("mainDrawer.listItems.users")}
primary={i18n.t('mainDrawer.listItems.users')}
icon={<PeopleAltOutlinedIcon />}
/>
<ListItemLink
to="/queues"
primary={i18n.t("mainDrawer.listItems.queues")}
primary={i18n.t('mainDrawer.listItems.queues')}
icon={<AccountTreeOutlinedIcon />}
/>
<ListItemLink
to="/connections"
primary={i18n.t("mainDrawer.listItems.connections")}
primary={i18n.t('mainDrawer.listItems.connections')}
icon={
<Badge badgeContent={connectionWarning ? "!" : 0} color="error">
<Badge badgeContent={connectionWarning ? '!' : 0} color="error">
<SyncAltIcon />
</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
role={user.profile}
@ -148,13 +168,14 @@ const MainListItems = (props) => {
icon={<DeviceHubOutlined />}
/>
</>
)}
/>
</>
)}
/>
</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 { green } from '@material-ui/core/colors'
import Settings from "@material-ui/icons/Settings";
import {
Button,
TableBody,
@ -47,6 +50,7 @@ import toastError from '../../errors/toastError'
//--------
import { AuthContext } from '../../context/Auth/AuthContext'
import { Can } from '../../components/Can'
import ConfigModal from '../../components/ConfigModal'
const useStyles = makeStyles((theme) => ({
mainPaper: {
@ -107,6 +111,7 @@ const Connections = () => {
const { whatsApps, loading } = useContext(WhatsAppsContext)
const [whatsAppModalOpen, setWhatsAppModalOpen] = useState(false)
const [configModalOpen, setConfigModalOpen] = useState(false)
const [qrModalOpen, setQrModalOpen] = useState(false)
const [selectedWhatsApp, setSelectedWhatsApp] = useState(null)
const [confirmModalOpen, setConfirmModalOpen] = useState(false)
@ -134,7 +139,7 @@ const Connections = () => {
const fetchSession = async () => {
try {
const { data } = await api.get('/settings')
setSettings(data)
setSettings(data.settings)
} catch (err) {
toastError(err)
}
@ -205,6 +210,13 @@ const Connections = () => {
setWhatsAppModalOpen(true)
}
const handleOpenConfigModal = () => {
setConfigModalOpen(true)
}
const handleCloseConfigModal = () => {
setConfigModalOpen(false)
}
const handleCloseWhatsAppModal = useCallback(() => {
setWhatsAppModalOpen(false)
setSelectedWhatsApp(null)
@ -454,10 +466,24 @@ const Connections = () => {
whatsAppId={!qrModalOpen && selectedWhatsApp?.id}
/>
<ConfigModal
open={configModalOpen}
onClose={handleCloseConfigModal}
change={configModalOpen}
/>
<MainHeader>
<Title>{i18n.t('connections.title')}</Title>
<MainHeaderButtonsWrapper>
<Button
variant="contained"
color="primary"
onClick={handleOpenConfigModal}
>
<Settings/>
</Button>
<Can
role={user.profile}
perform="btn-add-whatsapp"

View File

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

View File

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

View File

@ -52,7 +52,7 @@ const Settings = () => {
const fetchSession = async () => {
try {
const { data } = await api.get('/settings')
setSettings(data)
setSettings(data.settings)
} catch (err) {
toastError(err)
}
@ -198,6 +198,34 @@ const Settings = () => {
</Paper>
</Container>
</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>
)}
/>

View File

@ -1,27 +1,27 @@
import React from "react";
import { BrowserRouter, Switch } from "react-router-dom";
import { ToastContainer } from "react-toastify";
import React from 'react'
import { BrowserRouter, Switch } from 'react-router-dom'
import { ToastContainer } from 'react-toastify'
import LoggedInLayout from "../layout";
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 LoggedInLayout from '../layout'
import Dashboard from '../pages/Dashboard/'
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 = () => {
return (
@ -33,28 +33,49 @@ const Routes = () => {
<WhatsAppsProvider>
<LoggedInLayout>
<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="/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="/quickAnswers" component={QuickAnswers} isPrivate />
<Route
exact
path="/quickAnswers"
component={QuickAnswers}
isPrivate
/>
<Route exact path="/Settings" component={Settings} isPrivate />
<Route exact path="/Queues" component={Queues} isPrivate />
<Route exact path="/Dialogflows" component={Dialogflows} isPrivate />
<Route exact path="/campaign" component={Campaign} isPrivate />
</LoggedInLayout>
</WhatsAppsProvider>
</Switch>
<ToastContainer autoClose={3000} />
</AuthProvider>
</BrowserRouter>
);
};
)
}
export default Routes;
export default Routes