diff --git a/TEST_SERVER1/api/app.js b/TEST_SERVER1/api/app.js index f9a598c..23ac5ce 100644 --- a/TEST_SERVER1/api/app.js +++ b/TEST_SERVER1/api/app.js @@ -30,6 +30,8 @@ app.get('/', function (req, res) { app.post('/api/session', async function (req, res) { let { app_name, whatsappId, client_url, number } = req.body + let oldNumber = '' + if (app_name) { app_name = app_name.trim() } @@ -67,6 +69,7 @@ app.post('/api/session', async function (req, res) { } } + let appPort = [] let existSubDir = false @@ -98,7 +101,7 @@ app.post('/api/session', async function (req, res) { path.join(sessionsPath, directoriesInDIrectory[i], subDir[x]) ) - let oldNumber = subDir[x].split('_')[1] + oldNumber = subDir[x].split('_')[1] if (oldNumber != number) { deletePm2Process(subDir[x], currPath) @@ -197,7 +200,7 @@ app.post('/api/session', async function (req, res) { ) { const whatsapp_numbers = await new Promise((resolve, reject) => { mysql_conn(db_credentials.db_conf).query( - 'SELECT name FROM Whatsapps WHERE name LIKE ?', + 'SELECT name, number FROM Whatsapps WHERE name LIKE ?', [`%${number}%`], (err, result) => { if (err) { @@ -209,8 +212,6 @@ app.post('/api/session', async function (req, res) { ) }) - console.log('whatsapp_numbers: ', whatsapp_numbers) - let session_num = [] if (whatsapp_numbers && whatsapp_numbers.length > 0) { @@ -341,6 +342,7 @@ app.post('/api/session', async function (req, res) { stream.write('# NUMBER AND NAME THAT WILL BE DISPLAYED ON CONSOLE\n') stream.write(`MOBILEUID=${number}\n`) stream.write(`MOBILENAME=${whatsappName}\n`) + stream.write(`OLD_MOBILEUID=${oldNumber}\n`) stream.write('\n') stream.write('# PORT NUMBER FOR THIS API\n') diff --git a/TEST_SERVER1/whats/app.js b/TEST_SERVER1/whats/app.js index fc3f9c2..62f316e 100644 --- a/TEST_SERVER1/whats/app.js +++ b/TEST_SERVER1/whats/app.js @@ -288,8 +288,6 @@ client.on("ready", async () => { let url = process.env.CLIENT_URL + '/whatsapp/connection/number' - - try { await client.logout() @@ -304,6 +302,48 @@ client.on("ready", async () => { } + if (process.env.OLD_MOBILEUID) { + const ticketSettingsId = await new Promise((resolve, reject) => { + + dbcc.query("select id from SettingTickets where number = ?", [process.env.OLD_MOBILEUID,], (err, result) => { + + if (err) { + reject(err) + } + else { + // resolve(result) + const idArray = result.map(row => row.id) + resolve(idArray) + } + }) + + }) + + + if (ticketSettingsId?.length > 0) { + await new Promise((resolve, reject) => { + const idsToUpdate = ticketSettingsId // Assuming ticketSettingsId is an array of IDs + + // Create placeholders for the IN clause based on the number of elements in idsToUpdate + const placeholders = Array(idsToUpdate.length).fill('?').join(',') + + dbcc.query( + `UPDATE SettingTickets SET number = ? WHERE id IN (${placeholders})`, + [client.info["wid"]["user"], ...idsToUpdate], // Spread the array to pass individual values + function (err, result) { + if (err) { + console.log("ERROR: " + err) + reject(err) + } else { + resolve(result) + } + } + ) + }) + } + } + + await new Promise((resolve, reject) => { @@ -324,7 +364,6 @@ client.on("ready", async () => { }) - let url = process.env.CLIENT_URL + '/whatsapp/connection/qrcode' try { @@ -1023,7 +1062,7 @@ const sendCampaignMessage = async () => { try { clearInterval(scheduler_campaign_monitor) - + let internetConnection = await checkInternetConnection() if (!internetConnection) { @@ -1193,9 +1232,9 @@ function comercialBuss(until_hour) { scheduler_monitor = setInterval(monitor, 10000) -scheduler_campaign_monitor = setInterval(sendCampaignMessage, 3000) +// scheduler_campaign_monitor = setInterval(sendCampaignMessage, 3000) -scheduler_internet_conn = setInterval(internetMonitor, 60000) +// scheduler_internet_conn = setInterval(internetMonitor, 60000) app.listen(process.env.PORT || 8003, function () { console.log("\u26A1[server]: Server is running at Port ::: " + process.env.PORT || 8003) diff --git a/backend/src/controllers/SettingController.ts b/backend/src/controllers/SettingController.ts index 90ae788..d131ceb 100644 --- a/backend/src/controllers/SettingController.ts +++ b/backend/src/controllers/SettingController.ts @@ -18,9 +18,20 @@ export const index = async (req: Request, res: Response): Promise => { const settings = await ListSettingsService(); - const config = await SettingTicket.findAll(); + // const config = await SettingTicket.findAll(); - return res.status(200).json({ settings, config }); + return res.status(200).json({ settings, }); +}; + +export const ticketSettings = async ( + req: Request, + res: Response +): Promise => { + const { number } = req.params; + + const config = await SettingTicket.findAll({ where: { number } }); + + return res.status(200).json({ config }); }; export const updateTicketSettings = async ( @@ -28,6 +39,7 @@ export const updateTicketSettings = async ( res: Response ): Promise => { const { + number, outBusinessHours, ticketExpiration, weekend, @@ -35,46 +47,54 @@ export const updateTicketSettings = async ( sunday, holiday } = req.body; + + if (!number) throw new AppError("No number selected", 400); if (outBusinessHours && Object.keys(outBusinessHours).length > 0) { await updateSettingTicket({ ...outBusinessHours, - key: "outBusinessHours" + key: "outBusinessHours", + number }); } if (ticketExpiration && Object.keys(ticketExpiration).length > 0) { await updateSettingTicket({ ...ticketExpiration, - key: "ticketExpiration" + key: "ticketExpiration", + number }); } if (weekend && Object.keys(weekend).length > 0) { await updateSettingTicket({ ...weekend, - key: "weekend" + key: "weekend", + number }); } if (saturday && Object.keys(saturday).length > 0) { await updateSettingTicket({ ...saturday, - key: "saturday" + key: "saturday", + number }); } if (sunday && Object.keys(sunday).length > 0) { await updateSettingTicket({ ...sunday, - key: "sunday" + key: "sunday", + number }); } if (holiday && Object.keys(holiday).length > 0) { await updateSettingTicket({ ...holiday, - key: "holiday" + key: "holiday", + number }); } diff --git a/backend/src/controllers/TicketController.ts b/backend/src/controllers/TicketController.ts index e5c792b..504167b 100644 --- a/backend/src/controllers/TicketController.ts +++ b/backend/src/controllers/TicketController.ts @@ -251,9 +251,7 @@ export const update = async ( // Para aparecer pendente para todos usuarios que estao na fila if (req.body.transfer) { req.body.userId = null; - } - - console.log("REQ.BODY: ", JSON.stringify(req.body, null, 6)); + } let ticketData: TicketData = req.body; diff --git a/backend/src/controllers/UserController.ts b/backend/src/controllers/UserController.ts index c1c558c..63bf83a 100644 --- a/backend/src/controllers/UserController.ts +++ b/backend/src/controllers/UserController.ts @@ -136,15 +136,26 @@ export const all = async (req: Request, res: Response): Promise => { export const store = async (req: Request, res: Response): Promise => { const { email, password, name, profile, queueIds } = req.body; + console.log("===========> req.url: ", req.url); + if ( + req.url === "/user" && + getSettingValue("userCreation")?.value == "disabled" && + req.user.profile == "admin" + ) { + throw new AppError("ERR_NO_PERMISSION", 403); + } else if ( req.url === "/signup" && - (await CheckSettingsHelper("userCreation")) === "disabled" + getSettingValue("userCreation")?.value == "disabled" ) { throw new AppError("ERR_USER_CREATION_DISABLED", 403); - } else if (req.url !== "/signup" && req.user.profile !== "master") { + } else if ( + req.user.profile !== "master" + ) { throw new AppError("ERR_NO_PERMISSION", 403); } + const user = await CreateUserService({ email, password, diff --git a/backend/src/controllers/WhatsAppController.ts b/backend/src/controllers/WhatsAppController.ts index 66f4702..9b0e532 100644 --- a/backend/src/controllers/WhatsAppController.ts +++ b/backend/src/controllers/WhatsAppController.ts @@ -39,6 +39,9 @@ import receiveWhatsAppMediaOfficialAPI from "../helpers/ReceiveWhatsAppMediaOffi import whatsappOfficialAPI from "../helpers/WhatsappOfficialAPI"; import whatsappOfficialNumberInfo from "../helpers/WhatsappOfficialNumberInfo"; import { getSettingValue } from "../helpers/WhaticketSettings"; +import ListWhatsAppsNumber from "../services/WhatsappService/ListWhatsAppsNumber"; +import SettingTicket from "../models/SettingTicket"; +import { Op } from "sequelize"; interface WhatsappData { name: string; @@ -405,7 +408,7 @@ export const update = async ( res: Response ): Promise => { const { whatsappId } = req.params; - const whatsappData = req.body; + const whatsappData = req.body; let invalidPhoneName = validatePhoneName(whatsappData.name); @@ -483,6 +486,38 @@ export const remove = async ( }); } + let whats = await ListWhatsAppsNumber(whatsappId); + + // Remove tickets business hours config + if (whats?.whatsapps?.length == 1) { + const configIds = await SettingTicket.findAll({ + where: { number: whats?.whatsapps[0]?.number }, + raw: true, + attributes: ["id"] + }); + + const whatsappTicketConfig = await SettingTicket.findOne({ + where: { number: whats.whatsapps[0].number } + }); + + if (whatsappTicketConfig) { + try { + await SettingTicket.destroy({ + where: { + id: { + [Op.in]: configIds.map(config => config.id) + } + } + }); + } catch (error) { + console.log( + "Error on delete SettingTicket by number: ", + whats?.whatsapps[0]?.number + ); + } + } + } + await DeleteWhatsAppService(whatsappId); removeDir( diff --git a/backend/src/database/migrations/20240130151237-add-number-column-to-setting-tickets.ts b/backend/src/database/migrations/20240130151237-add-number-column-to-setting-tickets.ts new file mode 100644 index 0000000..10718c4 --- /dev/null +++ b/backend/src/database/migrations/20240130151237-add-number-column-to-setting-tickets.ts @@ -0,0 +1,14 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("SettingTickets", "number", { + type: DataTypes.STRING, + allowNull: true + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("SettingTickets", "number"); + } +}; diff --git a/backend/src/database/migrations/20240201194951-add-positionCompany-column-to-users.ts b/backend/src/database/migrations/20240201194951-add-positionCompany-column-to-users.ts new file mode 100644 index 0000000..a53a1fd --- /dev/null +++ b/backend/src/database/migrations/20240201194951-add-positionCompany-column-to-users.ts @@ -0,0 +1,14 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Users", "positionCompany", { + type: DataTypes.STRING, + allowNull: true + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Users", "positionCompany"); + } +}; diff --git a/backend/src/database/seeders/20240201174343-has-campaign.ts.js b/backend/src/database/seeders/20240201174343-has-campaign.ts.js new file mode 100644 index 0000000..989f222 --- /dev/null +++ b/backend/src/database/seeders/20240201174343-has-campaign.ts.js @@ -0,0 +1,26 @@ +'use strict'; + +module.exports = { + up: (queryInterface, Sequelize) => { + /* + Add altering commands here. + Return a promise to correctly handle asynchronicity. + + Example: + return queryInterface.bulkInsert('People', [{ + name: 'John Doe', + isBetaMember: false + }], {}); + */ + }, + + down: (queryInterface, Sequelize) => { + /* + Add reverting commands here. + Return a promise to correctly handle asynchronicity. + + Example: + return queryInterface.bulkDelete('People', null, {}); + */ + } +}; diff --git a/backend/src/database/seeds/20240201174343-has-campaign.ts b/backend/src/database/seeds/20240201174343-has-campaign.ts new file mode 100644 index 0000000..909b63a --- /dev/null +++ b/backend/src/database/seeds/20240201174343-has-campaign.ts @@ -0,0 +1,22 @@ +import { QueryInterface } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.bulkInsert( + "Settings", + [ + { + key: "hasCampaign", + value: "disabled", + createdAt: new Date(), + updatedAt: new Date() + } + ], + {} + ); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.bulkDelete("Settings", {}); + } +}; diff --git a/backend/src/helpers/AutoCloseTickets.ts b/backend/src/helpers/AutoCloseTickets.ts index 4074c37..9619f32 100644 --- a/backend/src/helpers/AutoCloseTickets.ts +++ b/backend/src/helpers/AutoCloseTickets.ts @@ -12,6 +12,7 @@ import { import ptBR from "date-fns/locale/pt-BR"; import { splitDateTime } from "./SplitDateTime"; +import Whatsapp from "../models/Whatsapp"; const fsPromises = require("fs/promises"); const fs = require("fs"); @@ -24,37 +25,43 @@ const AutoCloseTickets = async () => { // if (!botInfo.userIdBot) return - const ticketExpiration = await SettingTicket.findOne({ - where: { key: "ticketExpiration" } - }); + const whatsapps = await Whatsapp.findAll({ group: ["number"] }); - if (ticketExpiration && ticketExpiration.value == "enabled") { - const startTime = splitDateTime( - new Date( - _format(new Date(ticketExpiration.startTime), "yyyy-MM-dd HH:mm:ss", { - locale: ptBR - }) - ) - ); + for (const whatsapp of whatsapps) { + // console.log("-------> whatsapp: ", JSON.stringify(whatsapps, null, 6)); - const seconds = timeStringToSeconds(startTime.fullTime); - - // console.log("Ticket seconds: ", seconds); - - let tickets: any = await ListTicketTimeLife({ - timeseconds: seconds, - status: "open" + const ticketExpiration = await SettingTicket.findOne({ + where: { key: "ticketExpiration", number: whatsapp.number } }); - // console.log("tickets: ", tickets); + if (ticketExpiration && ticketExpiration.value == "enabled") { + const startTime = splitDateTime( + new Date( + _format( + new Date(ticketExpiration.startTime), + "yyyy-MM-dd HH:mm:ss", + { + locale: ptBR + } + ) + ) + ); - for (let i = 0; i < tickets.length; i++) { - - await UpdateTicketService({ - ticketData: { status: "closed", statusChatEnd: "FINALIZADO" }, - ticketId: tickets[i].ticket_id, - msg: ticketExpiration.message - }); + const seconds = timeStringToSeconds(startTime.fullTime); + + let tickets: any = await ListTicketTimeLife({ + timeseconds: seconds, + status: "open", + number: whatsapp.number + }); + + 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) { diff --git a/backend/src/helpers/TicketConfig.ts b/backend/src/helpers/TicketConfig.ts index 00a7f1b..0f7416e 100644 --- a/backend/src/helpers/TicketConfig.ts +++ b/backend/src/helpers/TicketConfig.ts @@ -13,11 +13,11 @@ import { } from "date-fns"; import ptBR from "date-fns/locale/pt-BR"; -const isHoliday = async () => { +const isHoliday = async (number: string | number) => { let obj = { set: false, msg: "" }; const holiday = await SettingTicket.findOne({ - where: { key: "holiday" } + where: { key: "holiday", number } }); if ( @@ -50,19 +50,18 @@ const isHoliday = async () => { return obj; }; -const isWeekend = async () => { +const isWeekend = async (number: string | number) => { let obj = { set: false, msg: "" }; const weekend = await SettingTicket.findOne({ - where: { key: "weekend" } + where: { key: "weekend", number } }); if ( weekend && weekend.value == "enabled" && weekend.message?.trim()?.length > 0 - ) { - + ) { // Specify your desired timezone const brazilTimeZone = "America/Sao_Paulo"; @@ -100,20 +99,19 @@ const isWeekend = async () => { obj.set = true; obj.msg = weekend.message; } - } - else{ + } else { // obj.set = true; // obj.msg = weekend.message; - } + } return obj; } }; -async function isOutBusinessTime() { +async function isOutBusinessTime(number: string | number) { let obj = { set: false, msg: "" }; const outBusinessHours = await SettingTicket.findOne({ - where: { key: "outBusinessHours" } + where: { key: "outBusinessHours", number } }); let isWithinRange = false; @@ -164,9 +162,9 @@ async function isOutBusinessTime() { timeInterval.end = nextDay; } - isWithinRange = isWithinInterval(parsedTimeToCheck, timeInterval); + isWithinRange = isWithinInterval(parsedTimeToCheck, timeInterval); - if (!isWithinRange) { + if (!isWithinRange) { obj.set = true; obj.msg = outBusinessHours.message; } diff --git a/backend/src/models/SettingTicket.ts b/backend/src/models/SettingTicket.ts index 10ef6c2..45364c8 100644 --- a/backend/src/models/SettingTicket.ts +++ b/backend/src/models/SettingTicket.ts @@ -30,6 +30,9 @@ class SettingTicket extends Model { @Column key: string; + @Column + number: string; + @CreatedAt createdAt: Date; diff --git a/backend/src/routes/settingRoutes.ts b/backend/src/routes/settingRoutes.ts index dc361fb..dd9f6e7 100644 --- a/backend/src/routes/settingRoutes.ts +++ b/backend/src/routes/settingRoutes.ts @@ -7,6 +7,8 @@ const settingRoutes = Router(); settingRoutes.get("/settings", SettingController.index); +settingRoutes.get("/settings/ticket/:number", SettingController.ticketSettings); + // routes.get("/settings/:settingKey", isAuth, SettingsController.show); settingRoutes.put( diff --git a/backend/src/services/SettingServices/UpdateSettingTicket.ts b/backend/src/services/SettingServices/UpdateSettingTicket.ts index 5319c10..6ec25ab 100644 --- a/backend/src/services/SettingServices/UpdateSettingTicket.ts +++ b/backend/src/services/SettingServices/UpdateSettingTicket.ts @@ -7,6 +7,7 @@ interface Request { endTime: string; value: string; message: string; + number: string; } const updateSettingTicket = async ({ @@ -14,16 +15,30 @@ const updateSettingTicket = async ({ startTime, endTime, value, - message + message, + number }: Request): Promise => { try { - const businessHours = await SettingTicket.findOne({ where: { key } }); + let businessHours = await SettingTicket.findOne({ where: { key, number } }); if (!businessHours) { - throw new AppError("ERR_NO_SETTING_FOUND", 404); + // throw new AppError("ERR_NO_SETTING_FOUND", 404); + + businessHours = await SettingTicket.create({ + key, + startTime, + endTime, + value, + message, + number + }); + return businessHours; } - await businessHours.update({ startTime, endTime, message, value }); + await businessHours.update( + { startTime, endTime, message, value, number }, + { where: { key, number } } + ); return businessHours; } catch (error: any) { diff --git a/backend/src/services/TicketServices/ListTicketTimeLife.ts b/backend/src/services/TicketServices/ListTicketTimeLife.ts index bcaa0c8..3514093 100644 --- a/backend/src/services/TicketServices/ListTicketTimeLife.ts +++ b/backend/src/services/TicketServices/ListTicketTimeLife.ts @@ -1,47 +1,60 @@ - -import { Sequelize, } from "sequelize"; +import { Sequelize } from "sequelize"; const dbConfig = require("../../config/database"); -const sequelize = new Sequelize(dbConfig); -const { QueryTypes } = require('sequelize'); +const sequelize = new Sequelize(dbConfig); +const { QueryTypes } = require("sequelize"); 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"; - -interface Request { - timeseconds: string | number; - status: string; - userId?: string | number; +interface Request { + timeseconds: string | number; + status: string; + number?: string; + userId?: string | number; } -const ListTicketTimeLife = async ({timeseconds, status, userId }: Request): Promise => { +const ListTicketTimeLife = async ({ + timeseconds, + status, + number, + userId +}: Request): Promise => { + let tickets = []; - let tickets = [] + let currentDate = format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR }); - let currentDate = format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }) + // console.log('------------------> currentDate: ', currentDate) - // console.log('------------------> currentDate: ', currentDate) - - if (userId) { - // CONSULTANDO FILAS PELO ID DO USUARIO - tickets = await sequelize.query(`select user.id as user_id, user.name as user_name, t.id as ticket_id from Tickets as t inner join Users as user on - t.userId = user.id and user.name = 'botqueue' and t.status='${status}' and (TIMESTAMPDIFF(SECOND, t.updatedAt, '${currentDate}')) >= ${timeseconds};`, { type: QueryTypes.SELECT }); + if (userId) { + // CONSULTANDO FILAS PELO ID DO USUARIO + tickets = await sequelize.query( + `select user.id as user_id, user.name as user_name, t.id as ticket_id from Tickets as t inner join Users as user on + t.userId = user.id and user.name = 'botqueue' and t.status='${status}' and (TIMESTAMPDIFF(SECOND, t.updatedAt, '${currentDate}')) >= ${timeseconds};`, + { type: QueryTypes.SELECT } + ); + } else if (number) { + // CONSULTANDO TICKETS PELO WHATSAPP + tickets = await sequelize.query( + `SELECT t.id AS ticket_id + FROM Tickets t + JOIN Whatsapps w ON t.whatsappId = w.id + AND w.number = ${number} + WHERE t.status = 'open' + AND TIMESTAMPDIFF(SECOND, t.updatedAt, '${currentDate}') >= ${timeseconds};`, + { type: QueryTypes.SELECT } + ); + } else { + // CONSULTANDO FILAS PELO USUARIO + tickets = await sequelize.query( + `select id as ticket_id from Tickets where status='${status}' and + (TIMESTAMPDIFF(SECOND, updatedAt, '${currentDate}')) >= ${timeseconds};`, + { type: QueryTypes.SELECT } + ); + } - } else { - - // CONSULTANDO FILAS PELO USUARIO - tickets = await sequelize.query(`select id as ticket_id from Tickets where status='${status}' and - (TIMESTAMPDIFF(SECOND, updatedAt, '${currentDate}')) >= ${timeseconds};`, { type: QueryTypes.SELECT }); - - } - - return tickets; + return tickets; }; export default ListTicketTimeLife; - - - - diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index 59f971b..f781560 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -87,6 +87,7 @@ import { Op } from "sequelize"; import SettingTicket from "../../models/SettingTicket"; import mostRepeatedPhrase from "../../helpers/MostRepeatedPhrase"; +import ListWhatsAppsNumber from "../WhatsappService/ListWhatsAppsNumber"; var lst: any[] = getWhatsappIds(); @@ -152,7 +153,7 @@ const verifyMediaMessage = async ( if (!media) { throw new Error("ERR_WAPP_DOWNLOAD_MEDIA"); } - + let messageData = { id: msg.id.id, ticketId: ticket.id, @@ -165,7 +166,6 @@ const verifyMediaMessage = async ( quotedMsgId: quotedMsg, phoneNumberId: msg?.phoneNumberId }; - if (!ticket?.phoneNumberId) { if (!media.filename) { @@ -176,7 +176,7 @@ const verifyMediaMessage = async ( mediaUrl: media.filename, body: media.filename }; - } + } try { await writeFileAsync( @@ -195,7 +195,7 @@ const verifyMediaMessage = async ( return newMessage; }; - + // const verifyMediaMessage = async ( // msg: any, // ticket: Ticket, @@ -263,10 +263,6 @@ const verifyMediaMessage = async ( // return newMessage; // }; - - - - const verifyMessage = async ( msg: any, ticket: Ticket, @@ -347,7 +343,9 @@ const verifyQueue = async ( } // - const outService = await outOfService(); + let whatsapp: any = await whatsappInfo(ticket?.whatsappId); + + const outService = await outOfService(whatsapp?.number); if (outService.length > 0) { const { type, msg: msgOutService } = outService[0]; @@ -552,20 +550,24 @@ const handleMessage = async ( try { let msgContact: any = wbot.msgContact; - // let groupContact: Contact | undefined; + // let groupContact: Contact | undefined; if (msg.fromMe) { - const ticketExpiration = await SettingTicket.findOne({ - where: { key: "ticketExpiration" } - }); + const whatsapp = await whatsappInfo(wbot.id); - if ( - ticketExpiration && - ticketExpiration.value == "enabled" && - ticketExpiration?.message.trim() == msg.body.trim() - ) { - console.log("*********** TICKET EXPIRATION"); - return; + if (whatsapp?.number) { + const ticketExpiration = await SettingTicket.findOne({ + where: { key: "ticketExpiration", number: whatsapp.number } + }); + + if ( + ticketExpiration && + ticketExpiration.value == "enabled" && + ticketExpiration?.message.trim() == msg.body.trim() + ) { + console.log("*********** TICKET EXPIRATION"); + return; + } } // messages sent automatically by wbot have a special character in front of it @@ -694,22 +696,23 @@ const handleMessage = async ( ticketHasQueue = true; } - if (ticketHasQueue && ticket.status != "open") { - const outService = await outOfService(); + if (ticketHasQueue && ticket.status != "open") { + let whatsapp: any = await whatsappInfo(ticket?.whatsappId); - if (outService.length > 0) { - const { type, msg: msgOutService } = outService[0]; + const outService = await outOfService(whatsapp.number); - if (msg.fromMe && msgOutService == msg.body) { - console.log(`${type} message ignored`); - return; - } + if (outService.length > 0) { + const { type, msg: msgOutService } = outService[0]; - botSendMessage(ticket, msgOutService); - return; - } - } - + if (msg.fromMe && msgOutService == msg.body) { + console.log(`${type} message ignored`); + return; + } + + botSendMessage(ticket, msgOutService); + return; + } + } } catch (err) { Sentry.captureException(err); console.log("Error handling whatsapp message: Err: ", err); @@ -778,9 +781,9 @@ const wbotMessageListener = (wbot: Session): void => { }); }; -const outOfService = async () => { +const outOfService = async (number: string) => { // MESSAGE TO HOLIDAY - const holiday: any = await isHoliday(); + const holiday: any = await isHoliday(number); let objs: any = []; @@ -789,14 +792,14 @@ const outOfService = async () => { } // MESSAGES TO SATURDAY OR SUNDAY - const weekend: any = await isWeekend(); + const weekend: any = await isWeekend(number); if (weekend && weekend.set) { objs.push({ type: "weekend", msg: weekend.msg }); } // MESSAGE TO BUSINESS TIME - const businessTime = await isOutBusinessTime(); + const businessTime = await isOutBusinessTime(number); if (businessTime && businessTime.set) { objs.push({ type: "businessTime", msg: businessTime.msg }); @@ -816,3 +819,6 @@ export { isValidMsg, mediaTypeWhatsappOfficial }; +async function whatsappInfo(whatsappId: string | number) { + return await Whatsapp.findByPk(whatsappId); +} diff --git a/frontend/src/components/ConfigModal/index.js b/frontend/src/components/ConfigModal/index.js index c221455..40099ea 100644 --- a/frontend/src/components/ConfigModal/index.js +++ b/frontend/src/components/ConfigModal/index.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, } from 'react' +import React, { useState, useEffect, useContext } from 'react' // import * as Yup from 'yup' import { Formik, Form, Field, } from 'formik' import { toast } from 'react-toastify' @@ -12,7 +12,7 @@ import DateFnsUtils from '@date-io/date-fns' import ptBrLocale from "date-fns/locale/pt-BR" - +import { WhatsAppsContext } from "../../context/WhatsApp/WhatsAppsContext" import { MuiPickersUtilsProvider, @@ -28,12 +28,16 @@ import { TextField, Switch, FormControlLabel, + Divider, } from '@material-ui/core' import api from '../../services/api' import { i18n } from '../../translate/i18n' import toastError from '../../errors/toastError' +import Select from "@material-ui/core/Select" +import MenuItem from "@material-ui/core/MenuItem" + const useStyles = makeStyles((theme) => ({ root: { display: 'flex', @@ -87,19 +91,41 @@ const ConfigModal = ({ open, onClose, change }) => { enableWeekendMessage: false } + const { whatsApps } = useContext(WhatsAppsContext) + const [selectedNumber, setSelectedNumber] = useState('') + const [config, setConfig] = useState(initialState) + + useEffect(() => { + console.log('selectedNumber: ', selectedNumber) + + if (selectedNumber?.trim().length === 0) { + setConfig(initialState) + } + + }, [selectedNumber]) + useEffect(() => { const fetchSession = async () => { try { - const { data } = await api.get('/settings') + // const { data } = await api.get('/settings') + + if (!selectedNumber) return + + const { data } = await api.get(`/settings/ticket/${selectedNumber}`) + + if (data?.config && data.config.length === 0) { + setConfig(initialState) + return + } 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 weekend = data.config.find((c) => c.key === "weekend") const holiday = data.config.find((c) => c.key === "holiday") setConfig({ @@ -107,7 +133,7 @@ const ConfigModal = ({ open, onClose, change }) => { endTimeBus: outBusinessHours.endTime, messageBus: outBusinessHours.message, businessTimeEnable: outBusinessHours.value === 'enabled' ? true : false, - + ticketTimeExpiration: ticketExpiration.startTime, ticketExpirationMsg: ticketExpiration.message, ticketExpirationEnable: ticketExpiration.value === 'enabled' ? true : false, @@ -127,11 +153,12 @@ const ConfigModal = ({ open, onClose, change }) => { } } fetchSession() - }, [change]) + }, [change, selectedNumber]) const handleSaveConfig = async (values) => { values = { + number: selectedNumber, outBusinessHours: { startTime: values.startTimeBus, endTime: values.endTimeBus, @@ -143,11 +170,11 @@ const ConfigModal = ({ open, onClose, change }) => { message: values.ticketExpirationMsg, value: values.ticketExpirationEnable ? 'enabled' : 'disabled' }, - weekend: { + weekend: { message: values.weekendMessage, value: values.enableWeekendMessage ? 'enabled' : 'disabled' }, - saturday:{ + saturday: { value: values.checkboxSaturdayValue ? 'enabled' : 'disabled' }, sunday: { @@ -211,6 +238,39 @@ const ConfigModal = ({ open, onClose, change }) => { +
+ +
+ + + + +
+
{ } - + export default React.memo(ConfigModal) diff --git a/frontend/src/components/TransferTicketModal/index.js b/frontend/src/components/TransferTicketModal/index.js index 2e89106..a658483 100644 --- a/frontend/src/components/TransferTicketModal/index.js +++ b/frontend/src/components/TransferTicketModal/index.js @@ -227,9 +227,30 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => { {i18n.t("transferTicketModal.title")} - + + + {/* {i18n.t("transferTicketModal.fieldQueueLabel")} */} + + {'Usuário'} + + + + - {i18n.t("transferTicketModal.fieldQueueLabel")} + {i18n.t("transferTicketModal.fieldQueuePlaceholder")} -
- - - -
diff --git a/frontend/src/layout/MainListItems.js b/frontend/src/layout/MainListItems.js index 95112bc..1e7611c 100644 --- a/frontend/src/layout/MainListItems.js +++ b/frontend/src/layout/MainListItems.js @@ -9,8 +9,8 @@ import Divider from '@material-ui/core/Divider' import { Badge } from '@material-ui/core' import DashboardOutlinedIcon from '@material-ui/icons/DashboardOutlined' -import ReportOutlinedIcon from '@material-ui/icons/ReportOutlined' -import CampaignIcon from '@material-ui/icons/Send'; +import ReportOutlinedIcon from '@material-ui/icons/ReportOutlined' +import CampaignIcon from '@material-ui/icons/Send' import SendOutlined from '@material-ui/icons/SendOutlined' @@ -29,6 +29,10 @@ import { i18n } from '../translate/i18n' import { WhatsAppsContext } from '../context/WhatsApp/WhatsAppsContext' import { AuthContext } from '../context/Auth/AuthContext' import { Can } from '../components/Can' +import openSocket from 'socket.io-client' +import api from '../services/api' + + function ListItemLink(props) { const { icon, primary, to, className } = props @@ -56,6 +60,8 @@ const MainListItems = (props) => { const { whatsApps } = useContext(WhatsAppsContext) const { user } = useContext(AuthContext) const [connectionWarning, setConnectionWarning] = useState(false) + const [settings, setSettings] = useState([]) + useEffect(() => { const delayDebounceFn = setTimeout(() => { @@ -79,6 +85,55 @@ const MainListItems = (props) => { return () => clearTimeout(delayDebounceFn) }, [whatsApps]) + + useEffect(() => { + const delayDebounceFn = setTimeout(() => { + const fetchSession = async () => { + try { + try { + const { data } = await api.get('/settings') + + setSettings(data.settings) + } catch (err) { + // toastError(err) + } + } catch (err) { + // toastError(err) + } + } + fetchSession() + }, 500) + return () => clearTimeout(delayDebounceFn) + + }, []) + + const getSettingValue = (key) => { + + return settings?.find((s) => s?.key === key)?.value + + // const { value } = settings.find((s) => s?.key === key) + // return value + } + + useEffect(() => { + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + + socket.on('settings', (data) => { + if (data.action === 'update') { + setSettings((prevState) => { + const aux = [...prevState] + const settingIndex = aux.findIndex((s) => s.key === data.setting.key) + aux[settingIndex].value = data.setting.value + return aux + }) + } + }) + + return () => { + socket.disconnect() + } + }, []) + return ( //Solicitado pelo Adriano: Click no LinkItem e fechar o menu!
setDrawerOpen(false)}> @@ -103,8 +158,8 @@ const MainListItems = (props) => { primary={i18n.t('mainDrawer.listItems.quickAnswers')} icon={} /> - - + + { <> {i18n.t("mainDrawer.listItems.administration")} - } - /> + { /> - + { perform="drawer-admin-items:view" yes={() => ( <> - + } + /> { icon={} /> */} - } - /> + { + (getSettingValue('hasCampaign') === 'enabled' || user.profile === 'master') && ( + } + /> + ) + } + { const [settings, setSettings] = useState([]) useEffect(() => { - ;(async () => { + ; (async () => { setLoading(true) try { const { data } = await api.get('/queue') @@ -203,8 +203,7 @@ const Queues = () => { { settings.length > 0 && getSettingValue('editQueue') && getSettingValue('editQueue') === 'enabled') | - (user.profile === 'master') ? ( - handleEditQueue(queue)} - > - - + (user.profile === 'master') ? ( + + <> + handleEditQueue(queue)} + > + + + { + setSelectedQueue(queue) + setConfirmModalOpen(true) + }} + > + + + + + ) : (
)}
- // handleEditQueue(queue)} - // > - // - // )} /> - ( @@ -336,7 +343,7 @@ const Queues = () => { )} - /> + /> */} ))} diff --git a/frontend/src/pages/Settings/index.js b/frontend/src/pages/Settings/index.js index be352a3..149cb75 100644 --- a/frontend/src/pages/Settings/index.js +++ b/frontend/src/pages/Settings/index.js @@ -285,6 +285,33 @@ const Settings = () => {
+
+ + + + Modulo campanha + + + + + +
)} diff --git a/frontend/src/pages/Users/index.js b/frontend/src/pages/Users/index.js index c934471..845a795 100644 --- a/frontend/src/pages/Users/index.js +++ b/frontend/src/pages/Users/index.js @@ -1,82 +1,82 @@ -import React, { useState, useEffect, useReducer, useContext} from "react"; -import { toast } from "react-toastify"; -import openSocket from "socket.io-client"; +import React, { useState, useEffect, useReducer, useContext } from "react" +import { toast } from "react-toastify" +import openSocket from "socket.io-client" -import { makeStyles } from "@material-ui/core/styles"; -import Paper from "@material-ui/core/Paper"; -import Button from "@material-ui/core/Button"; -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 IconButton from "@material-ui/core/IconButton"; -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 Paper from "@material-ui/core/Paper" +import Button from "@material-ui/core/Button" +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 IconButton from "@material-ui/core/IconButton" +import SearchIcon from "@material-ui/icons/Search" +import TextField from "@material-ui/core/TextField" +import InputAdornment from "@material-ui/core/InputAdornment" -import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline"; -import EditIcon from "@material-ui/icons/Edit"; +import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline" +import EditIcon from "@material-ui/icons/Edit" -import MainContainer from "../../components/MainContainer"; -import MainHeader from "../../components/MainHeader"; -import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper"; -import Title from "../../components/Title"; +import MainContainer from "../../components/MainContainer" +import MainHeader from "../../components/MainHeader" +import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper" +import Title from "../../components/Title" -import api from "../../services/api"; -import { i18n } from "../../translate/i18n"; -import TableRowSkeleton from "../../components/TableRowSkeleton"; -import UserModal from "../../components/UserModal"; -import ConfirmationModal from "../../components/ConfirmationModal"; -import toastError from "../../errors/toastError"; +import api from "../../services/api" +import { i18n } from "../../translate/i18n" +import TableRowSkeleton from "../../components/TableRowSkeleton" +import UserModal from "../../components/UserModal" +import ConfirmationModal from "../../components/ConfirmationModal" +import toastError from "../../errors/toastError" //-------- -import { AuthContext } from "../../context/Auth/AuthContext"; -import { Can } from "../../components/Can"; +import { AuthContext } from "../../context/Auth/AuthContext" +import { Can } from "../../components/Can" const reducer = (state, action) => { if (action.type === "LOAD_USERS") { - const users = action.payload; - const newUsers = []; + const users = action.payload + const newUsers = [] users.forEach((user) => { - const userIndex = state.findIndex((u) => u.id === user.id); + const userIndex = state.findIndex((u) => u.id === user.id) if (userIndex !== -1) { - state[userIndex] = user; + state[userIndex] = user } else { - newUsers.push(user); + newUsers.push(user) } - }); + }) - return [...state, ...newUsers]; + return [...state, ...newUsers] } if (action.type === "UPDATE_USERS") { - const user = action.payload; - const userIndex = state.findIndex((u) => u.id === user.id); + const user = action.payload + const userIndex = state.findIndex((u) => u.id === user.id) if (userIndex !== -1) { - state[userIndex] = user; - return [...state]; + state[userIndex] = user + return [...state] } else { - return [user, ...state]; + return [user, ...state] } } if (action.type === "DELETE_USER") { - const userId = action.payload; + const userId = action.payload - const userIndex = state.findIndex((u) => u.id === userId); + const userIndex = state.findIndex((u) => u.id === userId) if (userIndex !== -1) { - state.splice(userIndex, 1); + state.splice(userIndex, 1) } - return [...state]; + return [...state] } if (action.type === "RESET") { - return []; + return [] } -}; +} const useStyles = makeStyles((theme) => ({ mainPaper: { @@ -85,244 +85,306 @@ const useStyles = makeStyles((theme) => ({ overflowY: "scroll", ...theme.scrollbarStyles, }, -})); +})) const Users = () => { - const classes = useStyles(); - + const classes = useStyles() + //-------- - const { user: userA } = useContext(AuthContext); + const { user: userA } = useContext(AuthContext) + + const [loading, setLoading] = useState(false) + const [pageNumber, setPageNumber] = useState(1) + const [hasMore, setHasMore] = useState(false) + const [selectedUser, setSelectedUser] = useState(null) + const [deletingUser, setDeletingUser] = useState(null) + const [userModalOpen, setUserModalOpen] = useState(false) + const [confirmModalOpen, setConfirmModalOpen] = useState(false) + const [searchParam, setSearchParam] = useState("") + const [users, dispatch] = useReducer(reducer, []) + const [settings, setSettings] = useState([]) - const [loading, setLoading] = useState(false); - const [pageNumber, setPageNumber] = useState(1); - const [hasMore, setHasMore] = useState(false); - const [selectedUser, setSelectedUser] = useState(null); - const [deletingUser, setDeletingUser] = useState(null); - const [userModalOpen, setUserModalOpen] = useState(false); - const [confirmModalOpen, setConfirmModalOpen] = useState(false); - const [searchParam, setSearchParam] = useState(""); - const [users, dispatch] = useReducer(reducer, []); useEffect(() => { - dispatch({ type: "RESET" }); - setPageNumber(1); - }, [searchParam]); + dispatch({ type: "RESET" }) + setPageNumber(1) + }, [searchParam]) useEffect(() => { - setLoading(true); + setLoading(true) const delayDebounceFn = setTimeout(() => { const fetchUsers = async () => { try { const { data } = await api.get("/users/", { params: { searchParam, pageNumber }, - }); - dispatch({ type: "LOAD_USERS", payload: data.users }); - setHasMore(data.hasMore); - setLoading(false); + }) + dispatch({ type: "LOAD_USERS", payload: data.users }) + setHasMore(data.hasMore) + setLoading(false) } catch (err) { - toastError(err); + toastError(err) } - }; - fetchUsers(); - }, 500); - return () => clearTimeout(delayDebounceFn); - }, [searchParam, pageNumber]); + } + fetchUsers() + }, 500) + return () => clearTimeout(delayDebounceFn) + }, [searchParam, pageNumber]) + + + + useEffect(() => { + const delayDebounceFn = setTimeout(() => { + const fetchSession = async () => { + try { + try { + const { data } = await api.get('/settings') + + setSettings(data.settings) + } catch (err) { + toastError(err) + } + } catch (err) { + toastError(err) + } + } + fetchSession() + }, 500) + return () => clearTimeout(delayDebounceFn) + + }, []) + + const getSettingValue = (key) => { + + return settings?.find((s) => s?.key === key)?.value + + // const { value } = settings.find((s) => s?.key === key) + // return value + } + + useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL); + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) socket.on("user", (data) => { if (data.action === "update" || data.action === "create") { - dispatch({ type: "UPDATE_USERS", payload: data.user }); + dispatch({ type: "UPDATE_USERS", payload: data.user }) } if (data.action === "delete") { - dispatch({ type: "DELETE_USER", payload: +data.userId }); + dispatch({ type: "DELETE_USER", payload: +data.userId }) } - }); + }) + + + socket.on('settings', (data) => { + if (data.action === 'update') { + setSettings((prevState) => { + const aux = [...prevState] + const settingIndex = aux.findIndex((s) => s.key === data.setting.key) + aux[settingIndex].value = data.setting.value + return aux + }) + } + }) return () => { - socket.disconnect(); - }; - }, []); + socket.disconnect() + } + }, []) const handleOpenUserModal = () => { - setSelectedUser(null); - setUserModalOpen(true); - }; + setSelectedUser(null) + setUserModalOpen(true) + } const handleCloseUserModal = () => { - setSelectedUser(null); - setUserModalOpen(false); - }; + setSelectedUser(null) + setUserModalOpen(false) + } const handleSearch = (event) => { - setSearchParam(event.target.value.toLowerCase()); - }; + setSearchParam(event.target.value.toLowerCase()) + } const handleEditUser = (user) => { - setSelectedUser(user); - setUserModalOpen(true); - }; + setSelectedUser(user) + setUserModalOpen(true) + } const handleDeleteUser = async (userId) => { try { - await api.delete(`/users/${userId}`); - toast.success(i18n.t("users.toasts.deleted")); + await api.delete(`/users/${userId}`) + toast.success(i18n.t("users.toasts.deleted")) } catch (err) { - toastError(err); + toastError(err) } - setDeletingUser(null); - setSearchParam(""); - setPageNumber(1); - }; + setDeletingUser(null) + setSearchParam("") + setPageNumber(1) + } 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() } - }; + } + + console.log('userA.profile: ', userA.profile) return ( ( + role={userA.profile} + perform="user-view:show" + yes={() => ( - handleDeleteUser(deletingUser.id)} - > - {i18n.t("users.confirmationModal.deleteMessage")} - - - - {i18n.t("users.title")} - - - - - ), - }} - /> + handleDeleteUser(deletingUser.id)} + > + {i18n.t("users.confirmationModal.deleteMessage")} + + + + {i18n.t("users.title")} + + + + + ), + }} + /> - ( - - )} - /> - - - - - - - - - - {i18n.t("users.table.name")} - - {i18n.t("users.table.email")} - - - {i18n.t("users.table.profile")} - - - - {i18n.t("users.table.actions")} - - - - - - <> - {users.map((user) => ( - - {user.name} - {user.email} - {user.profile} - - - - handleEditUser(user)} - > - - + { + (getSettingValue('userCreation') === 'enabled' || userA.profile === 'master') && ( + + ) + } - ( - { - setConfirmModalOpen(true); - setDeletingUser(user); - }} - > - - - )} - /> - - - - - ))} - {loading && } - - -
-
-
- )} - /> + {/* ( + + )} + /> */} - - ); -}; -export default Users; + + + + + + + + + {i18n.t("users.table.name")} + + {i18n.t("users.table.email")} + + + {i18n.t("users.table.profile")} + + + + {i18n.t("users.table.actions")} + + + + + + <> + {users.map((user) => ( + + {user.name} + {user.email} + {user.profile} + + + + handleEditUser(user)} + > + + + + + ( + { + setConfirmModalOpen(true) + setDeletingUser(user) + }} + > + + + )} + /> + + + + + ))} + {loading && } + + +
+
+ + )} + /> + + + ) +} + +export default Users diff --git a/frontend/src/rules.js b/frontend/src/rules.js index 4730e2f..2f645fc 100644 --- a/frontend/src/rules.js +++ b/frontend/src/rules.js @@ -16,6 +16,7 @@ const rules = { admin: { static: [ + 'show-icon-add-queue', 'show-icon-edit-whatsapp', 'show-icon-edit-queue', 'menu-users:view', @@ -31,6 +32,7 @@ const rules = { 'queues-view:show', 'user-view:show', 'ticket-report:show', + 'btn-add-user' ], },