diff --git a/.gitignore b/.gitignore index c86c332..19b5ead 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,10 @@ WWebJS .env.development.local .env.test.local .env.production.local +.env.save +nano.save npm-debug.log* yarn-debug.log* -yarn-error.log* \ No newline at end of file +yarn-error.log* + diff --git a/TEST_SERVER1/whats/app.js b/TEST_SERVER1/whats/app.js index 62f316e..f8c030e 100644 --- a/TEST_SERVER1/whats/app.js +++ b/TEST_SERVER1/whats/app.js @@ -183,11 +183,17 @@ socketIo.on('connect_error', async function (err) { }) // +const wwebVersion = '2.2402.5'; + //NOVA OPÇÃO MD client = new Client({ authStrategy: new LocalAuth({ clientId: 'omnihit_sesssion' }), puppeteer: { args: ['--no-sandbox', '--disable-setuid-sandbox'], executablePath: process.env.CHROME_BIN || '/usr/bin/google-chrome-stable' }, + webVersionCache: { + type: 'remote', + remotePath: `https://raw.githubusercontent.com/wppconnect-team/wa-version/main/html/${wwebVersion}.html`, + }, }) client.initialize() diff --git a/backend/src/controllers/ContactController.ts b/backend/src/controllers/ContactController.ts index 7ab9363..af0ad0a 100644 --- a/backend/src/controllers/ContactController.ts +++ b/backend/src/controllers/ContactController.ts @@ -20,12 +20,18 @@ import { } from "../helpers/ContactsCache"; import { off } from "process"; +import GetContactService from "../services/ContactServices/GetContactService" type IndexQuery = { searchParam: string; pageNumber: string; }; +type IndexGetContactQuery = { + name: string; + number: string; +}; + interface ExtraInfo { name: string; value: string; @@ -84,6 +90,20 @@ export const index = async (req: Request, res: Response): Promise => { return res.json({ contacts, count, hasMore }); }; +export const getContact = async ( + req: Request, + res: Response +): Promise => { + const { name, number } = req.body as IndexGetContactQuery; + + const contact = await GetContactService({ + name, + number + }); + + return res.status(200).json(contact); +}; + export const store = async (req: Request, res: Response): Promise => { const newContact: ContactData = req.body; newContact.number = newContact.number.replace("-", "").replace(" ", ""); @@ -104,22 +124,13 @@ export const store = async (req: Request, res: Response): Promise => { newContact.number = addStartPhoneNumber(newContact.number); - const validNumber = await CheckIsValidContact(newContact.number); - - // const validNumber: any = await CheckContactNumber(newContact.number) + const validNumber = await CheckIsValidContact(newContact.number); if (!validNumber) { throw new AppError("ERR_WAPP_CHECK_CONTACT"); } - const profilePicUrl = await GetProfilePicUrl(validNumber); - - console.log("xxxxxxxxxxx profilePicUrl: ", profilePicUrl); - - // console.log(`newContact.name: ${newContact.name}\n - // newContact.number: ${newContact.number}\n - // newContact.email: ${newContact.email}\n - // newContact.extraInfo: ${newContact.extraInfo}`) + const profilePicUrl = await GetProfilePicUrl(validNumber); let name = newContact.name; let number = validNumber; diff --git a/backend/src/controllers/IAMControllerEL.ts b/backend/src/controllers/IAMControllerEL.ts new file mode 100644 index 0000000..c3cd8b7 --- /dev/null +++ b/backend/src/controllers/IAMControllerEL.ts @@ -0,0 +1,494 @@ +import { Request, Response } from "express"; +import { getIO } from "../libs/socket"; +import { Op } from "sequelize"; +import CreateUserService from "../services/UserServices/CreateUserService"; +import UpdateUserService from "../services/UserServices/UpdateUserService"; +import DeleteUserService from "../services/UserServices/DeleteUserService"; +import { del, get, set } from "../helpers/RedisClient"; + +import { + startWhoIsOnlineMonitor, + stopWhoIsOnlineMonitor +} from "../helpers/WhoIsOnlineMonitor"; + +import User from "../models/User"; + +export const createUser = async ( + req: Request, + res: Response +): Promise => { + const { user_id, user_first_name, user_tax_id, user_email, user_title }: any = + req.body; + + const invalid = invalidProperties(req.body, [ + "user_id", + "user_tax_id", + "user_first_name" + ]); + + if (invalid) { + return res.status(400).json(response("1", `${invalid}`, "0", "createUser")); + } + + const auxUser = await User.findOne({ where: { secondaryId: user_id } }); + + if (auxUser) { + return res + .status(400) + .json( + response("1", `The user ${user_id} already exist`, "0", "createUser") + ); + } + + const user = await CreateUserService({ + email: user_tax_id || user_email, + password: "12345", + name: user_first_name, + positionCompany: user_title, + profile: "user", + ignoreThrow: true + }); + + if (user?.error) { + return res + .status(user?.status) + .json(response("0", `${user?.msg}`, "0", "createUser")); + } + + if (!user?.error) { + const _user = await User.findByPk(user.id); + _user?.update({ secondaryId: user_id }); + + const { id, name } = user; + await set(`user:${id}`, { id, name }); + + const io = getIO(); + io.emit("user", { + action: "create", + user + }); + + await startWhoIsOnlineMonitor(); + } + + return res + .status(200) + .json(response("1", `User ${user_id} created`, "1", "createUser")); +}; + +export const deleteUser = async ( + req: Request, + res: Response +): Promise => { + const { user_id }: any = req.body; + + const invalid = invalidProperties(req.body, ["user_id"]); + + if (invalid) { + return res.status(400).json(response("1", `${invalid}`, "0", "deleteUser")); + } + + const _user = await User.findOne({ where: { secondaryId: user_id } }); + + if (_user) { + const user = await DeleteUserService(_user.id, true); + + if (user?.error) { + return res + .status(user?.status) + .json(response("0", `${user?.msg}`, "0", "deleteUser")); + } + + if (!user?.error) { + del(`user:${_user.id}`); + + const io = getIO(); + io.emit("user", { + action: "delete", + userId: _user.id + }); + + await stopWhoIsOnlineMonitor(); + + io.emit("onlineStatus", { + action: "delete", + userOnlineTime: _user.id + }); + + await startWhoIsOnlineMonitor(); + + return res + .status(200) + .json(response("1", `User ${user_id} deleted`, "1", "deleteUser")); + } + } + + return res + .status(500) + .json(response("0", "Internal server error", "0", "deleteUser")); +}; + +export const listAllUsers = async ( + req: Request, + res: Response +): Promise => { + const _users: any = await User.findAll({ + where: { + secondaryId: { + [Op.ne]: "" + } + }, + attributes: ["secondaryId", "name"] + }); + + if (_users) { + const user_list = _users.map((user: any) => { + const { secondaryId, name } = user; + return { user_id: secondaryId, full_name: name }; + }); + + return res + .status(200) + .json(response("1", "Success", user_list, "listAllUsers")); + } + + return res + .status(500) + .json(response("0", "Internal server error", [], "listAllUsers")); +}; + +export const checkUser = async ( + req: Request, + res: Response +): Promise => { + const { user_id }: any = req.body; + + const invalid = invalidProperties(req.body, ["user_id"]); + + if (invalid) { + return res.status(400).json(response("1", `${invalid}`, "0", "checkUser")); + } + + const _user = await User.findOne({ where: { secondaryId: user_id } }); + + if (_user) { + return res + .status(200) + .json(response("1", `User ${user_id} exist`, "1", "checkUser")); + } + + return res + .status(404) + .json(response("1", `User ${user_id} not exist`, "0", "checkUser")); +}; + +export const updateUser = async ( + req: Request, + res: Response +): Promise => { + const { user_id, user_first_name, user_tax_id, user_email, user_title }: any = + req.body; + + const invalid = invalidProperties(req.body, ["user_id"]); + + if (invalid) { + return res.status(400).json(response("1", `${invalid}`, "0", "checkUser")); + } + + const _user: any = await User.findOne({ where: { secondaryId: user_id } }); + + if (!_user) + return res + .status(404) + .json(response("1", `User ${user_id} not exist`, "0", "updateUser")); + + const userData = { + email: user_tax_id || user_email, + name: user_first_name, + positionCompany: user_title + }; + + let user: any = await UpdateUserService({ + userData, + userId: _user.id, + ignoreThrow: true + }); + + if (user?.error) { + return res + .status(user?.status) + .json(response("0", `${user?.msg}`, "0", "updateUser")); + } + + if (user) { + const { id, name } = user; + await set(`user:${id}`, { id, name }); + } + + const io = getIO(); + io.emit("user", { + action: "update", + user + }); + + return res + .status(200) + .json(response("1", `User ${user_id} updated`, "1", "updateUser")); +}; + +export const resetPassword = async ( + req: Request, + res: Response +): Promise => { + const { user_id, user_password }: any = req.body; + + const invalid = invalidProperties(req.body, ["user_id", "user_password"]); + + if (invalid) { + return res + .status(400) + .json(response("1", `${invalid}`, "0", "resetPassword")); + } + + const _user = await User.findOne({ where: { secondaryId: user_id } }); + + if (!_user) { + return res + .status(404) + .json(response("1", `User ${user_id} not exist`, "0", "resetPassword")); + } + + const userData = { + password: user_password, + email: _user.email + }; + + let user: any = await UpdateUserService({ + userData, + userId: _user.id, + ignoreThrow: true + }); + + if (user?.error) { + return res + .status(user?.status) + .json(response("0", `${user?.msg}`, "0", "resetPassword")); + } + + await logoutUser(_user.id); + + return res + .status(200) + .json( + response("1", `User ${user_id} password updated`, "1", "resetPassword") + ); +}; + +export const linkUserAndUserRight = async ( + req: Request, + res: Response +): Promise => { + const { user_id, user_right_id, user_right_title }: any = req.body; + + const invalid = invalidProperties(req.body, ["user_id", "user_right_id"]); + + if (invalid) { + return res + .status(400) + .json(response("1", `${invalid}`, "0", "linkUserAndUserRight")); + } + + if ( + (user_right_id && + !["admin", "user", "supervisor"].includes( + user_right_id?.trim().toLocaleLowerCase() + )) || + (user_right_title && + !["admin", "user", "supervisor"].includes( + user_right_title?.trim().toLocaleLowerCase() + )) + ) { + return res + .status(400) + .json( + response( + "1", + `The user profile ${ + user_right_title || user_right_id + } provided by the property user_right_title or user_right_id does not match the following profiles: admin, user, supervisor`, + "0", + "linkUserAndUserRight" + ) + ); + } + + const _user: any = await User.findOne({ where: { secondaryId: user_id } }); + + if (!_user) + return res + .status(404) + .json( + response("1", `User ${user_id} not exist`, "0", "linkUserAndUserRight") + ); + + const userData = { + profile: user_right_title || user_right_id, + email: _user.email + }; + + let user: any = await UpdateUserService({ + userData, + userId: _user.id, + ignoreThrow: true + }); + + if (user?.error) { + return res + .status(user?.status) + .json(response("0", `${user?.msg}`, "0", "linkUserAndUserRight")); + } + + await logoutUser(_user.id); + + return res + .status(200) + .json( + response( + "1", + `User ${user_id} associated with ${ + user_right_title || user_right_id + } profile`, + "1", + "linkUserAndUserRight" + ) + ); +}; + +export const checkUserRight = async ( + req: Request, + res: Response +): Promise => { + const { user_id, user_right_id, user_right_title }: any = req.body; + + const invalid = invalidProperties(req.body, ["user_id", "user_right_id"]); + + if (invalid) { + return res + .status(400) + .json(response("1", `${invalid}`, "0", "checkUserRight")); + } + + if ( + (user_right_id && + !["admin", "user", "supervisor"].includes( + user_right_id?.trim().toLocaleLowerCase() + )) || + (user_right_title && + !["admin", "user", "supervisor"].includes( + user_right_title?.trim().toLocaleLowerCase() + )) + ) { + return res + .status(400) + .json( + response( + "1", + `The user profile ${ + user_right_title || user_right_id + } provided by the property user_right_title or user_right_id does not match the following profiles: admin, user, supervisor`, + "0", + "checkUserRight" + ) + ); + } + + const _user: any = await User.findOne({ + where: { + secondaryId: user_id + } + }); + + if (!_user) + return res + .status(404) + .json(response("1", `User ${user_id} not exist`, "0", "checkUserRight")); + + if ( + (user_right_id && _user.profile != user_right_id) || + (user_right_title && _user.profile != user_right_title) + ) { + return res + .status(403) + .json( + response( + "1", + `User ${user_id} does not have this profile`, + "0", + "checkUserRight" + ) + ); + } + + return res + .status(200) + .json( + response( + "1", + `User ${user_id} has ${user_right_title || user_right_id} profile`, + "1", + "checkUserRight" + ) + ); +}; + +async function logoutUser(userId: any) { + await stopWhoIsOnlineMonitor(); + + let onlineTime = { + userId: `${userId}`, + status: "logout..." + }; + + const io = getIO(); + io.emit("onlineStatus", { + action: "logout", + userOnlineTime: onlineTime + }); + + await startWhoIsOnlineMonitor(); +} + +function response(code: string, msg: string, obj: any, type: string) { + let payload = { return_code: code, return_msg: msg }; + + switch (type) { + case "createUser": + return { ...payload, user_created: obj }; + case "deleteUser": + return { ...payload, user_removed: obj }; + case "listAllUsers": + return { ...payload, user_list: obj }; + case "checkUser": + return { ...payload, user_exists: obj }; + case "updateUser": + return { ...payload, user_updated: obj }; + case "resetPassword": + return { ...payload, password_set: obj }; + case "linkUserAndUserRight": + return { ...payload, user_right_linked: obj }; + case "checkUserRight": + return { ...payload, user_right_exists: obj }; + default: + return payload; + } +} + +function invalidProperties(body: any, pros: any[]) { + for (const field of pros) { + console.log("body[field]: ", body[field], " field: ", field); + if (!body[field]) { + return `${field} is required`; + } + } + return false; +} diff --git a/backend/src/controllers/MessageController.ts b/backend/src/controllers/MessageController.ts index f41823b..6d47bf4 100644 --- a/backend/src/controllers/MessageController.ts +++ b/backend/src/controllers/MessageController.ts @@ -21,6 +21,7 @@ import sendWhatsAppMessageOfficialAPI from "../helpers/sendWhatsAppMessageOffici import Whatsapp from "../models/Whatsapp"; import checkLastClientMsg24hs from "../helpers/CheckLastClientMsg24hs"; import AppError from "../errors/AppError"; +import { get } from "../helpers/RedisClient"; type IndexQuery = { pageNumber: string; @@ -35,7 +36,7 @@ type MessageData = { params: any; }; -export const index = async (req: Request, res: Response): Promise => { +export const index = async (req: Request, res: Response): Promise => { const { ticketId } = req.params; const { pageNumber } = req.query as IndexQuery; @@ -78,7 +79,7 @@ export const store = async (req: Request, res: Response): Promise => { let payloadComponents = []; - try { + try { for (let i in params) { const { parameters, language, type } = params[i]; if (type == "BODY") { @@ -99,7 +100,8 @@ export const store = async (req: Request, res: Response): Promise => { } const name = params.find((p: any) => p?.template_name); - const { language }: any = params.find((p: any) => p?.language); + const { language }: any = + params?.find((p: any) => p?.language) || "pt_BR"; const { template_name } = name; @@ -112,9 +114,10 @@ export const store = async (req: Request, res: Response): Promise => { } }; + console.log("TEMPLATE: ", template); + sendWhatsAppMessageOfficialAPI(ticket, body, null, template); - console.log("TEMPLATE: ", template); return res.send(); } } catch (error: any) { diff --git a/backend/src/controllers/QueueController.ts b/backend/src/controllers/QueueController.ts index 68b9854..eeb3703 100644 --- a/backend/src/controllers/QueueController.ts +++ b/backend/src/controllers/QueueController.ts @@ -5,18 +5,71 @@ import DeleteQueueService from "../services/QueueService/DeleteQueueService"; import ListQueuesService from "../services/QueueService/ListQueuesService"; import ShowQueueService from "../services/QueueService/ShowQueueService"; import UpdateQueueService from "../services/QueueService/UpdateQueueService"; -import Queue from "../models/Queue" -import AppError from "../errors/AppError" -import { get, set } from "../helpers/RedisClient"; - - +import Queue from "../models/Queue"; +import AppError from "../errors/AppError"; +import { del, get, set } from "../helpers/RedisClient"; +import { Op } from "sequelize"; +import ListWhatsAppsService from "../services/WhatsappService/ListWhatsAppsService"; +import Whatsapp from "../models/Whatsapp"; export const index = async (req: Request, res: Response): Promise => { - const queues = await ListQueuesService(); + const queues = await ListQueuesService(); return res.status(200).json(queues); }; +export const listQueues = async ( + req: Request, + res: Response +): Promise => { + const whatsapps = await Whatsapp.findAll({ + where: { + name: { [Op.ne]: "botqueue" }, + number: { [Op.ne]: "" }, + phoneNumberId: false + }, + attributes: ["number"], + include: [ + { + model: Queue, + as: "queues", + attributes: ["id", "name"] + } + ] + }); + + const whats = whatsapps + ?.filter((w: any) => w?.queues?.length > 0) + ?.map((w: any) => { + const { number, queues } = w; + return { + number, + queues: queues?.map((q: any) => { + const { id, name } = q; + return { id, name }; + }) + }; + }); + + let _queues: any = []; + + for (const w of whats) { + const { queues } = w; + + for (const q of queues) { + const { id: queueId, name } = q; + + const auxQueue = _queues.findIndex((q: any) => q.queueId == queueId); + + if (auxQueue == -1) { + _queues.push({ queueId, name }); + } + } + } + + return res.status(200).json(_queues); +}; + export const store = async (req: Request, res: Response): Promise => { const { name, color, greetingMessage } = req.body; @@ -125,7 +178,7 @@ export const customization = async ( await set("ura", ura); - const _ura = await get("ura"); + const _ura = await get({ key: "ura", parse: true }); console.log("_URA: ", _ura); return res.status(200).json({ new_queues }); @@ -164,6 +217,8 @@ export const remove = async ( await DeleteQueueService(queueId); + await del(`queue:${queueId}`); + const io = getIO(); io.emit("queue", { action: "delete", diff --git a/backend/src/controllers/ReportController.ts b/backend/src/controllers/ReportController.ts index bced8df..d7d70b3 100644 --- a/backend/src/controllers/ReportController.ts +++ b/backend/src/controllers/ReportController.ts @@ -4,35 +4,30 @@ import { Request, Response } from "express"; import AppError from "../errors/AppError"; import ShowTicketReport from "../services/TicketServices/ShowTicketReport"; import ShowMessageReport from "../services/MessageServices/ShowMessageReport"; - import onlineUserService from "../services/UserServices/CreateOrUpdateOnlineUserService"; import User from "../models/User"; import Queue from "../models/Queue"; import UserOnlineTime from "../models/UserOnlineTime"; - import { Op, Sequelize, literal } from "sequelize"; -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 { splitDateTime } from "../helpers/SplitDateTime"; import ListUserOnlineOffline from "../services/UserServices/ListUsersOnlineOfflineService"; import ListUserParamiterService from "../services/UserServices/ListUserParamiterService"; - import ShowUserServiceReport from "../services/UserServices/ShowUserServiceReport"; - import CountTicketsByUserQueue from "../services/UserServices/CountTicketsByUserQueue"; - import ShowQueuesByUser from "../services/UserServices/ShowQueuesByUser"; - - -// import { filter } from "bluebird"; - import { getIO } from "../libs/socket"; import { Json } from "sequelize/types/lib/utils"; +import ReportByNumberQueueService from "../services/ReportServices/ReportByNumberQueueService"; +import CountStatusChatEndService from "../services/StatusChatEndService/CountStatusChatEndService"; type IndexQuery = { userId: string; startDate: string; endDate: string; + createdOrUpdated: string; + queueId: string; pageNumber: string; userQueues: []; }; @@ -40,11 +35,12 @@ type IndexQuery = { type ReportOnQueue = { userId: string; identifier: string; -} - - -export const reportUserByDateStartDateEnd = async (req: Request, res: Response): Promise => { +}; +export const reportUserByDateStartDateEnd = async ( + req: Request, + res: Response +): Promise => { if ( req.user.profile !== "master" && req.user.profile !== "admin" && @@ -53,79 +49,132 @@ export const reportUserByDateStartDateEnd = async (req: Request, res: Response): throw new AppError("ERR_NO_PERMISSION", 403); } - const { userId, startDate, endDate, pageNumber, userQueues } = req.query as IndexQuery + const { + userId, + startDate, + endDate, + pageNumber, + userQueues, + createdOrUpdated, + queueId + } = req.query as IndexQuery; - console.log("userId, startDate, endDate, pageNumber: ", userId, startDate, endDate, pageNumber); + const { tickets, count, hasMore } = await ShowTicketReport({ + userId, + startDate, + endDate, + pageNumber, + createdOrUpdated, + queueId + }); - const { tickets, count, hasMore } = await ShowTicketReport({ userId, startDate, endDate, pageNumber }); - // console.log('kkkkkkkkkkkkkkkkkk tickets: ', JSON.stringify(tickets, null, 6)) - - return res.status(200).json({ tickets, count, hasMore }); + const queues = await Queue.findAll({ attributes: ["id", "name"] }); + + return res.status(200).json({ tickets, count, hasMore, queues }); }; - -export const reportUserService = async (req: Request, res: Response): Promise => { - - if (req.user.profile !== "master" && req.user.profile !== "admin" && req.user.profile !=="supervisor") { +export const reportUserService = async ( + req: Request, + res: Response +): Promise => { + if ( + req.user.profile !== "master" && + req.user.profile !== "admin" && + req.user.profile !== "supervisor" + ) { throw new AppError("ERR_NO_PERMISSION", 403); } - const { userId, startDate, endDate } = req.query as IndexQuery - + const { userId, startDate, endDate } = req.query as IndexQuery; + // let usersProfile = await ListUserParamiterService({ profile: 'user' }) let usersProfile = await ListUserParamiterService({ profiles: ["user", "supervisor"], raw: true }); - const sumUserOlineTime = await ShowUserServiceReport({ startDate, endDate, userId }); - const closedByUser = await ShowUserServiceReport({ startDate, endDate, ticketStatus: 'closed', userId }); - const openByUser = await ShowUserServiceReport({ startDate, endDate, ticketStatus: 'open', userId }); + const sumUserOlineTime = await ShowUserServiceReport({ + startDate, + endDate, + userId + }); + const closedByUser = await ShowUserServiceReport({ + startDate, + endDate, + ticketStatus: "closed", + userId + }); + const openByUser = await ShowUserServiceReport({ + startDate, + endDate, + ticketStatus: "open", + userId + }); - let dateTime = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }))) - const onlineUsers = await ListUserOnlineOffline({ date: dateTime.fullDate }) + let dateTime = splitDateTime( + new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR })) + ); + const onlineUsers = await ListUserOnlineOffline({ date: dateTime.fullDate }); - const openByUserOnQueue = await CountTicketsByUserQueue({ startDate: startDate, endDate: endDate, status: 'open', clientChatStart: true }) - const openByUserOutQueue = await CountTicketsByUserQueue({ startDate: startDate, endDate: endDate, status: 'open', clientChatStart: false }) + const openByUserOnQueue = await CountTicketsByUserQueue({ + startDate: startDate, + endDate: endDate, + status: "open", + clientChatStart: true + }); + const openByUserOutQueue = await CountTicketsByUserQueue({ + startDate: startDate, + endDate: endDate, + status: "open", + clientChatStart: false + }); - const closedByUserOnQueue = await CountTicketsByUserQueue({ startDate: startDate, endDate: endDate, status: 'closed', clientChatStart: true }) - const closedUserOutQueue = await CountTicketsByUserQueue({ startDate: startDate, endDate: endDate, status: 'closed', clientChatStart: false }) + const closedByUserOnQueue = await CountTicketsByUserQueue({ + startDate: startDate, + endDate: endDate, + status: "closed", + clientChatStart: true + }); + const closedUserOutQueue = await CountTicketsByUserQueue({ + startDate: startDate, + endDate: endDate, + status: "closed", + clientChatStart: false + }); // let openQueueInOut = openByUserOnQueue.concat(openByUserOutQueue) // let closedQueueInOut = closedByUserOnQueue.concat(closedUserOutQueue) + const queuesByUser = await ShowQueuesByUser({ profile: "user" }); - const queuesByUser = await ShowQueuesByUser({ profile: 'user' }) - - let openCloseOnQueue = openByUserOnQueue.concat(closedByUserOnQueue) - let openCloseOutQueue = openByUserOutQueue.concat(closedUserOutQueue) - + let openCloseOnQueue = openByUserOnQueue.concat(closedByUserOnQueue); + let openCloseOutQueue = openByUserOutQueue.concat(closedUserOutQueue); // console.log('onlineUsers: ',JSON.parse(JSON.stringify(onlineUsers))) // console.log('sumUserOlineTime: ', JSON.parse(JSON.stringify(sumUserOlineTime))) for (let i = 0; i < queuesByUser.length; i++) { - - queuesByUser[i].countOpen = 0 - queuesByUser[i].countClosed = 0 + queuesByUser[i].countOpen = 0; + queuesByUser[i].countClosed = 0; for (let x = 0; x < openCloseOnQueue.length; x++) { - if ((queuesByUser[i].userId == openCloseOnQueue[x].userId) && - (queuesByUser[i].queueId == openCloseOnQueue[x].queueId && openCloseOnQueue[x].status == 'open')) { - queuesByUser[i].countOpen = openCloseOnQueue[x].totAttendance + if ( + queuesByUser[i].userId == openCloseOnQueue[x].userId && + queuesByUser[i].queueId == openCloseOnQueue[x].queueId && + openCloseOnQueue[x].status == "open" + ) { + queuesByUser[i].countOpen = openCloseOnQueue[x].totAttendance; + } else if ( + queuesByUser[i].userId == openCloseOnQueue[x].userId && + queuesByUser[i].queueId == openCloseOnQueue[x].queueId && + openCloseOnQueue[x].status == "closed" + ) { + queuesByUser[i].countClosed = openCloseOnQueue[x].totAttendance; } - else if ((queuesByUser[i].userId == openCloseOnQueue[x].userId) && - (queuesByUser[i].queueId == openCloseOnQueue[x].queueId && openCloseOnQueue[x].status == 'closed')) { - queuesByUser[i].countClosed = openCloseOnQueue[x].totAttendance - } - } } - - usersProfile.map((user: any) => { - - let index = sumUserOlineTime.findIndex((e: any) => e.userId == user.id) + let index = sumUserOlineTime.findIndex((e: any) => e.userId == user.id); if (index != -1) { user.sumOnlineTime = sumUserOlineTime[index]; @@ -133,68 +182,67 @@ export const reportUserService = async (req: Request, res: Response): Promise e.userId == user.id) + index = closedByUser.findIndex((e: any) => e.userId == user.id); if (index != -1) { user.sumClosed = closedByUser[index]; } - index = openByUser.findIndex((e: any) => e.userId == user.id) + index = openByUser.findIndex((e: any) => e.userId == user.id); if (index != -1) { - user.sumOpen = openByUser[index] + user.sumOpen = openByUser[index]; } - // OPEN, CLOSED TICKETS STARTED BY USERS - let openClosedOutQueue = {} - let open = openCloseOutQueue.filter((e) => e.userId == user.id && e.status == 'open') - let closed = openCloseOutQueue.filter((e) => e.userId == user.id && e.status == 'closed') + let openClosedOutQueue = {}; + let open = openCloseOutQueue.filter( + e => e.userId == user.id && e.status == "open" + ); + let closed = openCloseOutQueue.filter( + e => e.userId == user.id && e.status == "closed" + ); openClosedOutQueue = { ...openClosedOutQueue, userId: user.id, countOpen: open && open.length > 0 ? open[0].totAttendance : 0, countClosed: closed && closed.length > 0 ? closed[0].totAttendance : 0 - } - - user.openClosedOutQueue = openClosedOutQueue + }; + user.openClosedOutQueue = openClosedOutQueue; // OPEN, CLOSED TICKETS STARTED BY CLIENTS - let openClosedInQueue = queuesByUser.filter((e) => e.userId == user.id) + let openClosedInQueue = queuesByUser.filter(e => e.userId == user.id); if (openClosedInQueue && openClosedInQueue.length > 0) { - user.openClosedInQueue = openClosedInQueue + user.openClosedInQueue = openClosedInQueue; } - - index = onlineUsers.findIndex((e: any) => e.userId == user.id) + index = onlineUsers.findIndex((e: any) => e.userId == user.id); if (index != -1) { - user.statusOnline = onlineUsers[index] + user.statusOnline = onlineUsers[index]; } - if (startDate.length > 0 && startDate.split('-').length == 3) { - let date = startDate.split('-') - user.startDate = `${date[2]}/${date[1]}/${date[0]}` + if (startDate.length > 0 && startDate.split("-").length == 3) { + let date = startDate.split("-"); + user.startDate = `${date[2]}/${date[1]}/${date[0]}`; } - if (endDate.length > 0 && endDate.split('-').length == 3) { - let date = endDate.split('-') - user.endDate = `${date[2]}/${date[1]}/${date[0]}` + if (endDate.length > 0 && endDate.split("-").length == 3) { + let date = endDate.split("-"); + user.endDate = `${date[2]}/${date[1]}/${date[0]}`; } + }); - }) - - return res.status(200).json({usersProfile: usersProfile}); - + return res.status(200).json({ usersProfile: usersProfile }); }; - - -export const reportMessagesUserByDateStartDateEnd = async (req: Request, res: Response): Promise => { - +export const reportMessagesUserByDateStartDateEnd = async ( + req: Request, + res: Response +): Promise => { if ( req.user.profile !== "master" && req.user.profile !== "admin" && @@ -203,35 +251,30 @@ export const reportMessagesUserByDateStartDateEnd = async (req: Request, res: Re throw new AppError("ERR_NO_PERMISSION", 403); } - const { userId, startDate, endDate } = req.query as IndexQuery + const { userId, startDate, endDate } = req.query as IndexQuery; let data_query_messages = await ShowMessageReport(userId, startDate, endDate); for (var i = 0; i < data_query_messages.length; i++) { - if (data_query_messages[i].fromMe) { - data_query_messages[i].fromMe = 'Atendente' - } - else { - data_query_messages[i].fromMe = 'Cliente' + data_query_messages[i].fromMe = "Atendente"; + } else { + data_query_messages[i].fromMe = "Cliente"; } - data_query_messages[i].id = (i + 1) - - console.log('data_query_messages: ', data_query_messages[i]) + data_query_messages[i].id = i + 1; } return res.status(200).json(data_query_messages); - }; - - -export const reportOnQueue = async (req: Request, res: Response): Promise => { - +export const reportOnQueue = async ( + req: Request, + res: Response +): Promise => { // console.log(req.body) - const { adminId, identifier, queueStatus, file } = req.body + const { adminId, identifier, queueStatus, file } = req.body; const io = getIO(); io.emit("queryOnQueueStatus", { @@ -244,10 +287,80 @@ export const reportOnQueue = async (req: Request, res: Response): Promise => { + if ( + req.user.profile !== "master" && + req.user.profile !== "admin" && + req.user.profile !== "supervisor" + ) { + throw new AppError("ERR_NO_PERMISSION", 403); + } + const { startDate, endDate, queueId } = req.query as IndexQuery; + console.log( + `startDate: ${startDate} | endDate: ${endDate} | queueId: ${queueId}` + ); + const reportService = await ReportByNumberQueueService({ + startDate, + endDate + }); + return res.status(200).json({ reportService }); +}; + +export const reportServiceByQueue = async ( + req: Request, + res: Response +): Promise => { + if ( + req.user.profile !== "master" && + req.user.profile !== "admin" && + req.user.profile !== "supervisor" + ) { + throw new AppError("ERR_NO_PERMISSION", 403); + } + + const { startDate, endDate, queueId } = req.query as IndexQuery; + + const reportService = await ReportByNumberQueueService({ + startDate, + endDate, + queue: true + }); + + return res.status(200).json({ reportService }); +}; + +export const reportTicksCountByStatusChatEnds = async ( + req: Request, + res: Response +): Promise => { + if ( + req.user.profile !== "master" && + req.user.profile !== "admin" && + req.user.profile !== "supervisor" + ) { + throw new AppError("ERR_NO_PERMISSION", 403); + } + + const { startDate, endDate } = req.query as IndexQuery; + + const dateToday = splitDateTime( + new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR })) + ); + + const reportStatusChatEnd = await CountStatusChatEndService( + startDate || dateToday.fullDate, + endDate || dateToday.fullDate + ); + + return res.status(200).json({ reportStatusChatEnd }); +}; diff --git a/backend/src/controllers/SettingController.ts b/backend/src/controllers/SettingController.ts index d131ceb..e1d0e46 100644 --- a/backend/src/controllers/SettingController.ts +++ b/backend/src/controllers/SettingController.ts @@ -20,14 +20,14 @@ export const index = async (req: Request, res: Response): Promise => { // const config = await SettingTicket.findAll(); - return res.status(200).json({ settings, }); + return res.status(200).json({ settings }); }; export const ticketSettings = async ( req: Request, res: Response ): Promise => { - const { number } = req.params; + const { number } = req.params; const config = await SettingTicket.findAll({ where: { number } }); @@ -40,6 +40,7 @@ export const updateTicketSettings = async ( ): Promise => { const { number, + saturdayBusinessTime, outBusinessHours, ticketExpiration, weekend, @@ -47,7 +48,7 @@ export const updateTicketSettings = async ( sunday, holiday } = req.body; - + if (!number) throw new AppError("No number selected", 400); if (outBusinessHours && Object.keys(outBusinessHours).length > 0) { @@ -58,6 +59,14 @@ export const updateTicketSettings = async ( }); } + if (saturdayBusinessTime && Object.keys(saturdayBusinessTime).length > 0) { + await updateSettingTicket({ + ...saturdayBusinessTime, + key: "saturdayBusinessTime", + number + }); + } + if (ticketExpiration && Object.keys(ticketExpiration).length > 0) { await updateSettingTicket({ ...ticketExpiration, diff --git a/backend/src/controllers/TicketController.ts b/backend/src/controllers/TicketController.ts index 4f2bd61..0a60271 100644 --- a/backend/src/controllers/TicketController.ts +++ b/backend/src/controllers/TicketController.ts @@ -22,7 +22,7 @@ import format from "date-fns/format"; import ListTicketsServiceCache from "../services/TicketServices/ListTicketServiceCache"; import { searchTicketCache, loadTicketsCache } from "../helpers/TicketCache"; -import { Op } from "sequelize"; +import { Op, where } from "sequelize"; type IndexQuery = { searchParam: string; @@ -68,6 +68,15 @@ import ListWhatsAppsForQueueService from "../services/WhatsappService/ListWhatsA import ListWhatsAppsNumber from "../services/WhatsappService/ListWhatsAppsNumber"; import Whatsapp from "../models/Whatsapp"; import AppError from "../errors/AppError"; +import CreateOrUpdateContactService from "../services/ContactServices/CreateOrUpdateContactService"; +import FindOrCreateTicketService from "../services/TicketServices/FindOrCreateTicketService"; +import CheckIsValidContact from "../services/WbotServices/CheckIsValidContact"; +import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl"; +import CreateContactService from "../services/ContactServices/CreateContactService"; +import { botSendMessage } from "../services/WbotServices/wbotMessageListener"; +import WhatsappQueue from "../models/WhatsappQueue"; +import { get } from "../helpers/RedisClient"; +import CountStatusChatEndService from "../services/StatusChatEndService/CountStatusChatEndService"; export const index = async (req: Request, res: Response): Promise => { const { @@ -86,7 +95,7 @@ export const index = async (req: Request, res: Response): Promise => { let queueIds: number[] = []; - if (queueIdsStringified) { + if (queueIdsStringified && queueIdsStringified.trim().length > 0) { queueIds = JSON.parse(queueIdsStringified); } @@ -106,6 +115,158 @@ export const index = async (req: Request, res: Response): Promise => { return res.status(200).json({ tickets, count, hasMore }); }; +export const remoteTicketCreation = async ( + req: Request, + res: Response +): Promise => { + let { queueId, contact_from, contact_to, msg, contact_name }: any = req.body; + + let whatsappId: any; + + if (!queueId && !contact_from) { + return res + .status(400) + .json({ error: `Property 'queueId' or 'contact_from' is required.` }); + } + + const validate = ["contact_to", "msg"]; + const validateOnlyNumber = ["queueId", "contact_to", "contact_from"]; + + for (let prop of validate) { + if (!req.body[prop]) + return res + .status(400) + .json({ error: `Property '${prop}' is undefined.` }); + + if (validateOnlyNumber.includes(prop)) { + if (!/^\d+$/.test(req.body[prop])) { + return res + .status(400) + .json({ error: `The property '${prop}' must be a number` }); + } + } + } + + if (queueId) { + const whatsapps = await ListWhatsAppsForQueueService(queueId, "CONNECTED"); + + if (!whatsapps || whatsapps?.length == 0) { + return res.status(500).json({ + msg: `queueId ${queueId} does not have a WhatsApp number associated with it or the number's session is disconnected.` + }); + } + + const { id } = whatsapps[0]; + + whatsappId = id; + } else if (contact_from) { + const whatsapp = await Whatsapp.findOne({ + where: { number: contact_from, status: "CONNECTED" } + }); + + if (!whatsapp) { + return res.status(404).json({ + msg: `Whatsapp number ${contact_from} not found or disconnected!` + }); + } + + const { id } = whatsapp; + + const { queues } = await ShowWhatsAppService(id); + + if (!queues || queues.length == 0) { + return res.status(500).json({ + msg: `The WhatsApp number ${contact_from} is not associated with any queue! ` + }); + } + + queueId = queues[0].id; + whatsappId = id; + } + + // const validNumber = await CheckIsValidContact(contact_to, true); + const validNumber = contact_to; + + if (validNumber) { + let contact = await Contact.findOne({ where: { number: validNumber } }); + + if (!contact) { + // const profilePicUrl = await GetProfilePicUrl(validNumber); + + contact = await CreateContactService({ + name: contact_name ? contact_name : contact_to, + number: validNumber + // profilePicUrl + }); + + const io = getIO(); + io.emit("contact", { + action: "create", + contact + }); + } + + const { id: contactId } = contact; + + const botInfo = await BotIsOnQueue("botqueue"); + + let ticket = await Ticket.findOne({ + where: { + [Op.or]: [ + { contactId, status: "queueChoice" }, + { contactId, status: "open", userId: botInfo.userIdBot } + ] + } + }); + + if (getSettingValue("whatsaAppCloudApi")?.value == "enabled") { + if (ticket) { + await UpdateTicketService({ + ticketData: { status: "closed" }, + ticketId: ticket.id + }); + ticket = null; + } + } else { + if (ticket) { + await UpdateTicketService({ + ticketData: { status: "closed" }, + ticketId: ticket.id + }); + } + } + + if (!ticket) { + ticket = await FindOrCreateTicketService( + contact, + whatsappId, + 0, + undefined, + queueId + ); + botSendMessage(ticket, `${msg}`); + } + + const io = getIO(); + io.to(ticket.status).emit("ticket", { + action: "update", + ticket + }); + + console.log( + `REMOTE TICKET CREATION FROM ENDPOINT | STATUS: 200 | MSG: success` + ); + return res.status(200).json({ msg: "success" }); + } + + console.log( + `REMOTE TICKET CREATION FROM ENDPOINT | STATUS: 500 | MSG: The number ${contact_to} does not exist on WhatsApp` + ); + return res + .status(500) + .json({ msg: `The number ${contact_to} does not exist on WhatsApp` }); +}; + export const store = async (req: Request, res: Response): Promise => { const { contactId, status, userId, msg, queueId, whatsappId }: TicketData = req.body; @@ -211,7 +372,8 @@ export const update = async ( ticketData: { status: status, userId: userId, - statusChatEnd: statusChatEndName.name + statusChatEnd: statusChatEndName.name, + statusChatEndId: statusChatEndName.id }, ticketId }); @@ -269,7 +431,6 @@ export const update = async ( for (const w of whatsappsByqueue) { let whats = await ListWhatsAppsNumber(w.id); - console.log("-------> WHATS: ", JSON.stringify(whats, null, 6)); const ticket = await Ticket.findOne({ where: { [Op.and]: [ @@ -426,10 +587,3 @@ export const remove = async ( return res.status(200).json({ message: "ticket deleted" }); }; -// export async function setMessageAsRead(ticket: Ticket) { -// const wbot_url = await getWbot(ticket.whatsappId); - -// console.log('wbot_url: ', wbot_url, ' | ticket.contact.number: ', ticket.contact.number); - -// await endPointQuery(`${wbot_url}/api/sendSeen`, { number: `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us` }); -// } diff --git a/backend/src/controllers/UserController.ts b/backend/src/controllers/UserController.ts index d36b7ff..013eeb6 100644 --- a/backend/src/controllers/UserController.ts +++ b/backend/src/controllers/UserController.ts @@ -12,7 +12,7 @@ import DeleteUserService from "../services/UserServices/DeleteUserService"; import ListUser from "../services/UserServices/ListUserParamiterService"; import User from "../models/User"; -import { get, set } from "../helpers/RedisClient"; +import { del, get, set } from "../helpers/RedisClient"; import { startWhoIsOnlineMonitor, @@ -27,6 +27,7 @@ import { splitDateTime } from "../helpers/SplitDateTime"; import ListUserByWhatsappQueuesService from "../services/UserServices/ListUserByWhatsappQueuesService"; import { json } from "sequelize"; import { getSettingValue } from "../helpers/WhaticketSettings"; +import { setBotInfo } from "../helpers/SetBotInfo"; type IndexQuery = { searchParam: string; @@ -99,7 +100,7 @@ export const index = async (req: Request, res: Response): Promise => { // }; export const all = async (req: Request, res: Response): Promise => { - const { userId, profile } = req.query as IndexQuery; + let { userId, profile }: any = req.query as IndexQuery; console.log( "userId: ", @@ -111,6 +112,8 @@ export const all = async (req: Request, res: Response): Promise => { ); if (getSettingValue("queueTransferByWhatsappScope")?.value == "enabled") { + if (!userId) return res.json({ users: [], queues: [] }); + const obj = await ListUserByWhatsappQueuesService( userId, '"admin", "user", "supervisor"' @@ -164,6 +167,11 @@ export const store = async (req: Request, res: Response): Promise => { queueIds }); + if (user) { + const { id, name } = user; + await set(`user:${id}`, { id, name }); + } + const io = getIO(); io.emit("user", { action: "create", @@ -267,34 +275,11 @@ export const update = async ( let user: any = await UpdateUserService({ userData, userId }); - if (user?.name?.trim() == "botqueue") { - let botInfo; + await setBotInfo(user); - if ( - user?.queues?.length > 0 && - user.queues[0]?.name?.trim() == "botqueue" - ) { - botInfo = JSON.stringify({ - userId: user.id, - queueId: user.queues[0].id, - botIsOnQueue: true - }); - botInfo = JSON.parse(botInfo); - - await set("botInfo", botInfo); - } else if ( - user?.queues?.length == 0 || - user.queues[0]?.name?.trim() != "botqueue" - ) { - botInfo = JSON.stringify({ - userId: user.id, - queueId: 0, - botIsOnQueue: false - }); - botInfo = JSON.parse(botInfo); - - await set("botInfo", botInfo); - } + if (user) { + const { id, name } = user; + await set(`user:${id}`, { id, name }); } const io = getIO(); @@ -320,6 +305,8 @@ export const remove = async ( await DeleteUserService(userId); + del(`user:${userId}`); + const io = getIO(); io.emit("user", { action: "delete", diff --git a/backend/src/controllers/WhatsAppController.ts b/backend/src/controllers/WhatsAppController.ts index 9b0e532..3a0dda6 100644 --- a/backend/src/controllers/WhatsAppController.ts +++ b/backend/src/controllers/WhatsAppController.ts @@ -42,6 +42,7 @@ import { getSettingValue } from "../helpers/WhaticketSettings"; import ListWhatsAppsNumber from "../services/WhatsappService/ListWhatsAppsNumber"; import SettingTicket from "../models/SettingTicket"; import { Op } from "sequelize"; +import { del, get, set } from "../helpers/RedisClient"; interface WhatsappData { name: string; @@ -54,6 +55,7 @@ interface WhatsappData { isDefault?: boolean; isOfficial?: boolean; phoneNumberId?: string; + number?: string; wabaId?: string; } @@ -228,6 +230,25 @@ export const weebhook = async ( req.body.entry[0].changes[0].value.metadata.display_phone_number; let type = message.type; + const contact_to_exist = await get({ + key: "whatsapp:*", + value: `${contact_to}` + }); + + if (contact_to_exist == null) { + console.log( + "WHATSAPP OFFICIAL", + " | CONCTACT_FROM: ", + contact_from, + " | CONTACT_TO: ", + contact_to + ); + console.log( + "NUMBER IGNORED. WHATSAPP NUMBER FROM ANOTHER OMNIHIT APPLICATION!" + ); + return res.status(403).json({ error: "Unauthorized" }); + } + let wbot = {}; let msg = {}; let contacts = req.body.entry[0].changes[0].value.contacts[0]; @@ -247,6 +268,10 @@ export const weebhook = async ( }); if (type == "text") { + if (!message?.text?.body) { + return res.status(400).json({ error: "body not found" }); + } + type = "chat"; msg = { ...msg, @@ -254,6 +279,10 @@ export const weebhook = async ( type }; } else { + if (!message[message?.type]?.id) { + return res.status(400).json({ error: "id not found" }); + } + const mediaId = message[message.type].id; const mimetype = message[message.type].mime_type; @@ -322,7 +351,8 @@ export const store = async (req: Request, res: Response): Promise => { urlApi, phoneNumberId, wabaId, - isOfficial + isOfficial, + number }: WhatsappData = req.body; if (req.user.profile !== "master") { @@ -333,7 +363,8 @@ export const store = async (req: Request, res: Response): Promise => { urlApi, isOfficial, phoneNumberId, - wabaId + wabaId, + number }); if (invalid) { @@ -346,6 +377,7 @@ export const store = async (req: Request, res: Response): Promise => { } else if (!isOfficial) { phoneNumberId = ""; wabaId = ""; + number = ""; } let invalidPhoneName = validatePhoneName(name); @@ -365,7 +397,8 @@ export const store = async (req: Request, res: Response): Promise => { queueIds, phoneNumberId, wabaId, - isOfficial + isOfficial, + number }); console.log("whatsapp.id: ", whatsapp.id); @@ -377,6 +410,16 @@ export const store = async (req: Request, res: Response): Promise => { number: getNumberFromName(name), client_url: `${process.env.BACKEND_URL_RAW}:${process.env.PORT}` }); + } else { + await set( + `whatsapp:${whatsapp.id}`, + JSON.stringify({ + number: whatsapp?.number, + id: whatsapp?.id, + greetingMessage: whatsapp?.greetingMessage, + phoneNumberId: whatsapp?.phoneNumberId + }) + ); } const io = getIO(); @@ -408,7 +451,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); @@ -416,13 +459,14 @@ export const update = async ( return res.status(200).json({ message: invalidPhoneName }); } - const { urlApi, isOfficial, phoneNumberId, wabaId } = whatsappData; + const { urlApi, isOfficial, phoneNumberId, number, wabaId } = whatsappData; const invalid = checkWhatsAppData({ urlApi, isOfficial, phoneNumberId, - wabaId + wabaId, + number }); if (invalid) { @@ -435,6 +479,7 @@ export const update = async ( } else if (!isOfficial) { whatsappData.phoneNumberId = ""; whatsappData.wabaId = ""; + whatsappData.number = ""; } const { whatsapp, oldDefaultWhatsapp } = await UpdateWhatsAppService({ @@ -449,6 +494,16 @@ export const update = async ( number: getNumberFromName(whatsapp.name), client_url: `${process.env.BACKEND_URL_RAW}:${process.env.PORT}` }); + } else { + await set( + `whatsapp:${whatsapp.id}`, + JSON.stringify({ + number: whatsapp?.number, + id: whatsapp?.id, + greetingMessage: whatsapp?.greetingMessage, + phoneNumberId: whatsapp?.phoneNumberId + }) + ); } const io = getIO(); @@ -486,6 +541,8 @@ export const remove = async ( }); } + await del(`whatsapp:${whatsappId}`); + let whats = await ListWhatsAppsNumber(whatsappId); // Remove tickets business hours config @@ -549,18 +606,22 @@ interface WhatsappDataValidate { isOfficial?: boolean; phoneNumberId?: string; wabaId?: string; + number?: string; } const checkWhatsAppData = ({ urlApi, isOfficial, phoneNumberId, - wabaId + wabaId, + number }: WhatsappDataValidate) => { if (isOfficial && (!phoneNumberId || phoneNumberId.trim() == "")) { return { message: "Phone number Id is required!" }; } else if (isOfficial && (!wabaId || wabaId.trim() == "")) { return { message: "WABA ID is required!" }; + } else if (isOfficial && (!number || number.trim() == "")) { + return { message: "Phone number is required!" }; } else if (!isOfficial && (!urlApi || urlApi.trim() == "")) { return { message: "urlApi is required!" }; } diff --git a/backend/src/database/migrations/20240312130345-add-fromAgent-to-message.ts b/backend/src/database/migrations/20240312130345-add-fromAgent-to-message.ts new file mode 100644 index 0000000..39f1158 --- /dev/null +++ b/backend/src/database/migrations/20240312130345-add-fromAgent-to-message.ts @@ -0,0 +1,15 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Messages", "fromAgent", { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Messages", "fromAgent"); + } +}; diff --git a/backend/src/database/migrations/20240315203708-add-secondaryId-to-users.ts b/backend/src/database/migrations/20240315203708-add-secondaryId-to-users.ts new file mode 100644 index 0000000..a2c590e --- /dev/null +++ b/backend/src/database/migrations/20240315203708-add-secondaryId-to-users.ts @@ -0,0 +1,14 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Users", "secondaryId", { + type: DataTypes.STRING, + allowNull: true + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Users", "secondaryId"); + } +}; diff --git a/backend/src/database/migrations/20240325193138-change-column-reference-statusChatEndId-to-tickets.ts b/backend/src/database/migrations/20240325193138-change-column-reference-statusChatEndId-to-tickets.ts new file mode 100644 index 0000000..9043cea --- /dev/null +++ b/backend/src/database/migrations/20240325193138-change-column-reference-statusChatEndId-to-tickets.ts @@ -0,0 +1,17 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Tickets", "statusChatEndId", { + type: DataTypes.INTEGER, + references: { model: "StatusChatEnds", key: "id" }, + onUpdate: "CASCADE", + onDelete: "SET NULL", + allowNull: true + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Tickets", "statusChatEndId"); + } +}; diff --git a/backend/src/database/seeds/20240328181254-add-setting-ticket-saturday-business-time.ts b/backend/src/database/seeds/20240328181254-add-setting-ticket-saturday-business-time.ts new file mode 100644 index 0000000..60c411d --- /dev/null +++ b/backend/src/database/seeds/20240328181254-add-setting-ticket-saturday-business-time.ts @@ -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: "saturdayBusinessTime", + createdAt: new Date(), + updatedAt: new Date() + } + ], + {} + ); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.bulkDelete("SettingTickets", {}); + } +}; diff --git a/backend/src/errors/AppError.ts b/backend/src/errors/AppError.ts index a8b1209..eed88bd 100644 --- a/backend/src/errors/AppError.ts +++ b/backend/src/errors/AppError.ts @@ -1,5 +1,5 @@ class AppError { - public readonly message: string; + public message: string; public readonly statusCode: number; diff --git a/backend/src/helpers/BotIsOnQueue.ts b/backend/src/helpers/BotIsOnQueue.ts index 5955fb7..c534bb4 100644 --- a/backend/src/helpers/BotIsOnQueue.ts +++ b/backend/src/helpers/BotIsOnQueue.ts @@ -4,9 +4,8 @@ const fs = require("fs"); import ListUsersService from "../services/UserServices/ListUsersService"; import { get } from "./RedisClient"; -const _botIsOnQueue = async (botName: string) => { - - const botInfo = await get("botInfo"); +const _botIsOnQueue = async (botName: string) => { + const botInfo = await get({ key: "botInfo", parse: true }); if ( botInfo && @@ -19,8 +18,8 @@ const _botIsOnQueue = async (botName: string) => { botQueueId: botInfo.queueId, isOnQueue: botInfo.botIsOnQueue }; - } - return { userIdBot: null, botQueueId: null, isOnQueue: false }; + } + return { userIdBot: null, botQueueId: null, isOnQueue: false }; }; export default _botIsOnQueue; diff --git a/backend/src/helpers/CheckContactOpenTickets.ts b/backend/src/helpers/CheckContactOpenTickets.ts index 6cfc24d..0b4a52e 100644 --- a/backend/src/helpers/CheckContactOpenTickets.ts +++ b/backend/src/helpers/CheckContactOpenTickets.ts @@ -1,6 +1,7 @@ import { Op } from "sequelize"; import AppError from "../errors/AppError"; import Ticket from "../models/Ticket"; +import User from "../models/User"; import ListWhatsAppsNumber from "../services/WhatsappService/ListWhatsAppsNumber"; import { getSettingValue } from "./WhaticketSettings"; import ListWhatsAppsForQueueService from "../services/WhatsappService/ListWhatsAppsForQueueService"; @@ -15,9 +16,9 @@ const CheckContactOpenTickets = async ( if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") { let whats = await ListWhatsAppsNumber(whatsappId); - console.log("contactId: ", contactId, " | whatsappId: ", whatsappId); + // console.log("contactId: ", contactId, " | whatsappId: ", whatsappId); - console.log("WHATS: ", JSON.stringify(whats, null, 6)); + // console.log("WHATS: ", JSON.stringify(whats, null, 6)); ticket = await Ticket.findOne({ where: { @@ -38,8 +39,13 @@ const CheckContactOpenTickets = async ( if (ticket) { if (handle) return true; - - throw new AppError("ERR_OTHER_OPEN_TICKET"); + const userName = await User.findOne({ + where:{ id: ticket.userId } + }); + const error = new AppError("ERR_OTHER_OPEN_TICKET"); + error.message = `Erro: já existe um ticket criado com esse contato. Responsável: ${userName? userName.name.toUpperCase() : 'Aguardando'}`; + + throw error; } diff --git a/backend/src/helpers/GetDefaultWhatsApp.ts b/backend/src/helpers/GetDefaultWhatsApp.ts index 5c1033f..6adc34d 100644 --- a/backend/src/helpers/GetDefaultWhatsApp.ts +++ b/backend/src/helpers/GetDefaultWhatsApp.ts @@ -6,27 +6,26 @@ import UserQueue from "../models/UserQueue"; import { Op, where } from "sequelize"; -import wbotByUserQueue from "../helpers/GetWbotByUserQueue"; +import wbotByUserQueue from "../helpers/GetWbotByUserQueue"; import { WhatsIndex } from "./LoadBalanceWhatsSameQueue"; interface Request { userId?: string | number; queueId?: string | number; + ignoreNoWhatsappFound?: boolean } - -//const GetDefaultWhatsApp = async (userId?: string | number): Promise => { - + const GetDefaultWhatsApp = async ({ userId, - queueId -}: Request): Promise => { - // test del + queueId, + ignoreNoWhatsappFound = false +}: Request): Promise => { let defaultWhatsapp = await Whatsapp.findOne({ where: { isDefault: true } }); - if (!defaultWhatsapp) { + if (!defaultWhatsapp) { if (userId) { let whatsapps = await wbotByUserQueue({ userId, queueId }); @@ -54,19 +53,18 @@ const GetDefaultWhatsApp = async ({ where: { status: "CONNECTED" } }); } - } else { + } else { defaultWhatsapp = await Whatsapp.findOne({ - where: { status: "CONNECTED" } + where: { status: "CONNECTED", isOfficial: false } }); } - } - - if (!defaultWhatsapp) { + } + + if (!defaultWhatsapp && !ignoreNoWhatsappFound) { throw new AppError("ERR_NO_DEF_WAPP_FOUND"); } - return defaultWhatsapp; - // + return defaultWhatsapp; }; export default GetDefaultWhatsApp; diff --git a/backend/src/helpers/OmnhitDashboardSession.ts b/backend/src/helpers/OmnhitDashboardSession.ts index 075501a..3b49843 100644 --- a/backend/src/helpers/OmnhitDashboardSession.ts +++ b/backend/src/helpers/OmnhitDashboardSession.ts @@ -15,9 +15,9 @@ async function omnihitDashboardSession(data: any) { } ); } catch (error) { - console.log( - `Post request error to ${process.env.URL_DASHBOARD_SESSIONS}/api/v1/omnihit/monitor` - ); + // console.log( + // `Post request error to ${process.env.URL_DASHBOARD_SESSIONS}/api/v1/omnihit/monitor` + // ); } } diff --git a/backend/src/helpers/RedisClient.ts b/backend/src/helpers/RedisClient.ts index 59074ec..b268353 100644 --- a/backend/src/helpers/RedisClient.ts +++ b/backend/src/helpers/RedisClient.ts @@ -6,17 +6,93 @@ type WhatsappData = { contactId: string | number; identifier: string; value?: string; + history?: string; }; -export async function set(key: string, value: string, expire: boolean = false) { - await redis.set(key, JSON.stringify(value)); +type getData = { + key: string; + value?: string; + parse?: boolean; +}; - if (expire) await redis.expire(key, 300); +export async function set(key: string, value: string | object) { + if (typeof value == "object") await redis.set(key, JSON.stringify(value)); + else { + await redis.set(key, value); + } } -export async function get(key: string) { +export async function getSimple(key: string) { const value: any = await redis.get(key); - return JSON.parse(value); + return value; +} + +export async function get({ key, value, parse }: getData) { + if (key.includes("*")) { + const keys = await redis.keys(key); + if (keys.length > 0) { + for (const key of keys) { + const val = await redis.get(key); + if (val.includes(value)) { + if (parse) return JSON.parse(val); + return val; + } + } + } + return null; + } else { + const value: any = await redis.get(key); + + if (parse) return JSON.parse(value); + + return value; + } +} + +export async function del(key: string) { + await redis.del(key); +} + +export async function clearAllKeys(...keys: string[]) { + for (const key of keys) { + // Retrieve all keys matching the pattern '*' + const del_keys = await redis.keys(key); + + // If there are keys, delete them + if (del_keys.length > 0) { + console.log("del_keys: ", del_keys); + await redis.del(...del_keys); + } + } +} + +export async function findByContain( + key: string, + keyName: string, + substring: string +) { + // const keys = await redis.keys("*" + substring + "*"); + // const keys = await redis.keys("user:*"); + + const keys = await redis.keys(key); + + const results: any[] = []; + + for (const key of keys) { + const value = await redis.get(key); + if (value) { + const obj = JSON.parse(value); + if ( + substring + ?.trim() + ?.toLowerCase() + .includes(obj[keyName]?.trim()?.toLowerCase()) + ) { + results.push(obj); + } + } + } + return results; } export async function del(key: string) { await redis.del(key); @@ -26,7 +102,8 @@ export async function createObject({ whatsappId, contactId, identifier, - value + value, + history = "" }: WhatsappData) { const key = `whatsappId:${whatsappId}:contactId:${contactId}:identifier:${identifier}`; const result = await redis.hmset( @@ -38,7 +115,9 @@ export async function createObject({ "identifier", identifier, "value", - value + value, + "history", + history ); await redis.expire(key, 300); @@ -66,7 +145,8 @@ export async function findObject( "whatsappId", "contactId", "identifier", - "value" + "value", + "history" ); return result; } diff --git a/backend/src/helpers/SetBotInfo.ts b/backend/src/helpers/SetBotInfo.ts new file mode 100644 index 0000000..04dfd6b --- /dev/null +++ b/backend/src/helpers/SetBotInfo.ts @@ -0,0 +1,33 @@ +import { get, set } from "../helpers/RedisClient"; + +export async function setBotInfo(user: any) { + if (user?.name?.trim() == "botqueue") { + let botInfo; + + if ( + user?.queues?.length > 0 && + user.queues[0]?.name?.trim() == "botqueue" + ) { + botInfo = JSON.stringify({ + userId: user.id, + queueId: user.queues[0].id, + botIsOnQueue: true + }); + botInfo = JSON.parse(botInfo); + + await set("botInfo", botInfo); + } else if ( + user?.queues?.length == 0 || + user.queues[0]?.name?.trim() != "botqueue" + ) { + botInfo = JSON.stringify({ + userId: user.id, + queueId: 0, + botIsOnQueue: false + }); + botInfo = JSON.parse(botInfo); + + await set("botInfo", botInfo); + } + } +} diff --git a/backend/src/helpers/TicketConfig.ts b/backend/src/helpers/TicketConfig.ts index 0f7416e..fe29a61 100644 --- a/backend/src/helpers/TicketConfig.ts +++ b/backend/src/helpers/TicketConfig.ts @@ -39,7 +39,7 @@ const isHoliday = async (number: string | number) => { locale: ptBR }) ) - ); + ); if (currentDate.fullDate == startTime.fullDate) { obj.set = true; @@ -62,21 +62,8 @@ const isWeekend = async (number: string | number) => { 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); + const localDate = localDateConvert(); // Check if it's Saturday or Sunday if (isSaturday(localDate)) { @@ -173,8 +160,104 @@ async function isOutBusinessTime(number: string | number) { return obj; } -export { - isWeekend, - isHoliday, - isOutBusinessTime -}; +async function isOutBusinessTimeSaturday(number: string | number) { + let obj = { set: false, msg: "" }; + + // Convert parsed date to Brazil time zone + const localDate = localDateConvert(); + + // Check if it's Saturday or Sunday + if (!isSaturday(localDate)) { + return obj; + } + + const outBusinessHoursSaturday = await SettingTicket.findOne({ + where: { key: "saturdayBusinessTime", number } + }); + + let isWithinRange = false; + + if ( + outBusinessHoursSaturday && + outBusinessHoursSaturday.value == "enabled" && + outBusinessHoursSaturday?.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(outBusinessHoursSaturday.startTime), + "yyyy-MM-dd HH:mm:ss", + { + locale: ptBR + } + ) + ) + ); + + const endTime = splitDateTime( + new Date( + _format( + new Date(outBusinessHoursSaturday.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 = outBusinessHoursSaturday.message; + } + } + + return obj; +} + +function localDateConvert() { + 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); + return localDate; +} + +export { isWeekend, isHoliday, isOutBusinessTime, isOutBusinessTimeSaturday }; diff --git a/backend/src/middleware/isAuth.ts b/backend/src/middleware/isAuth.ts index 86428d3..91061fc 100644 --- a/backend/src/middleware/isAuth.ts +++ b/backend/src/middleware/isAuth.ts @@ -13,16 +13,24 @@ interface TokenPayload { } const isAuth = (req: Request, res: Response, next: NextFunction): void => { - const authHeader = req.headers.authorization; + const authHeader = req.headers.authorization; if (!authHeader) { throw new AppError("ERR_SESSION_EXPIRED", 401); } - const [, token] = authHeader.split(" "); + const [, token] = authHeader.split(" "); + + if ( + (req.originalUrl == "/queue/remote/list" || + req.originalUrl == "/tickets/remote/create") && + token === process.env.TOKEN_REMOTE_TICKET_CREATION + ) { + return next(); + } try { - const decoded = verify(token, authConfig.secret); + const decoded = verify(token, authConfig.secret); const { id, profile } = decoded as TokenPayload; diff --git a/backend/src/middleware/verifyAPIKey.ts b/backend/src/middleware/verifyAPIKey.ts new file mode 100644 index 0000000..61cc6f7 --- /dev/null +++ b/backend/src/middleware/verifyAPIKey.ts @@ -0,0 +1,23 @@ +import { Request, Response, NextFunction } from "express"; +import AppError from "../errors/AppError"; +const verifyAPIKey = (req: Request, res: Response, next: NextFunction): void => { + const authHeader = req.headers.authorization; + + if (!authHeader) { + throw new AppError("ERR_SESSION_EXPIRED", 401); + } + + const [, token] = authHeader.split(" "); + + const apiKeyIsValid = token === process.env.TOKEN_IAM_HORACIUS_EL + if (!apiKeyIsValid) { + throw new AppError( + "Invalid token", + 401 + ); + } + + return next(); +}; + +export default verifyAPIKey; diff --git a/backend/src/models/Message.ts b/backend/src/models/Message.ts index 40fe9f8..7455195 100644 --- a/backend/src/models/Message.ts +++ b/backend/src/models/Message.ts @@ -31,6 +31,10 @@ class Message extends Model { @Column fromMe: boolean; + @Default(false) + @Column + fromAgent: boolean; + @Column(DataType.TEXT) body: string; diff --git a/backend/src/models/StatusChatEnd.ts b/backend/src/models/StatusChatEnd.ts index dde5775..fc4505f 100644 --- a/backend/src/models/StatusChatEnd.ts +++ b/backend/src/models/StatusChatEnd.ts @@ -10,25 +10,29 @@ import { } from "sequelize-typescript"; import SchedulingNotify from "./SchedulingNotify"; +import Ticket from "./Ticket" @Table class StatusChatEnd extends Model { @PrimaryKey @AutoIncrement @Column - id: number; - + id: number; + @Column name: string; - + @CreatedAt createdAt: Date; - + @UpdatedAt updatedAt: Date; @HasMany(() => SchedulingNotify) SchedulingNotifies: SchedulingNotify[]; + + @HasMany(() => Ticket) + tickets: Ticket[]; } export default StatusChatEnd; diff --git a/backend/src/models/Ticket.ts b/backend/src/models/Ticket.ts index d3b95c9..42175c8 100644 --- a/backend/src/models/Ticket.ts +++ b/backend/src/models/Ticket.ts @@ -21,6 +21,7 @@ import User from "./User"; import Whatsapp from "./Whatsapp"; import SchedulingNotify from "./SchedulingNotify"; +import StatusChatEnd from "./StatusChatEnd" @Table class Ticket extends Model { @@ -42,6 +43,10 @@ class Ticket extends Model { @Column isGroup: boolean; + @ForeignKey(() => StatusChatEnd) + @Column + statusChatEndId: number; + @Column statusChatEnd: string; diff --git a/backend/src/models/User.ts b/backend/src/models/User.ts index fb2109e..a2fde6a 100644 --- a/backend/src/models/User.ts +++ b/backend/src/models/User.ts @@ -30,7 +30,7 @@ class User extends Model { name: string; @Column - email: string; + email: string; @Column(DataType.VIRTUAL) password: string; @@ -41,10 +41,13 @@ class User extends Model { @Default(0) @Column tokenVersion: number; - + @Column positionCompany: string; - + + @Column + secondaryId: string; + @Default("admin") @Column profile: string; @@ -56,9 +59,9 @@ class User extends Model { updatedAt: Date; @HasMany(() => Ticket) tickets: Ticket[]; - + @HasMany(() => UserOnlineTime) - UserOnlineTime: UserOnlineTime[]; + UserOnlineTime: UserOnlineTime[]; @BelongsToMany(() => Queue, () => UserQueue) queues: Queue[]; diff --git a/backend/src/routes/contactRoutes.ts b/backend/src/routes/contactRoutes.ts index 158159c..8c77791 100644 --- a/backend/src/routes/contactRoutes.ts +++ b/backend/src/routes/contactRoutes.ts @@ -16,6 +16,8 @@ contactRoutes.get("/contacts/:contactId", isAuth, ContactController.show); contactRoutes.post("/contacts", isAuth, ContactController.store); +contactRoutes.post("/contact", isAuth, ContactController.getContact); + contactRoutes.put("/contacts/:contactId", isAuth, ContactController.update); contactRoutes.delete("/contacts/:contactId", isAuth, ContactController.remove); diff --git a/backend/src/routes/iamRoutesEL.ts b/backend/src/routes/iamRoutesEL.ts new file mode 100644 index 0000000..2181547 --- /dev/null +++ b/backend/src/routes/iamRoutesEL.ts @@ -0,0 +1,56 @@ +import { Router } from "express"; + +import * as IAMControllerEL from "../controllers/IAMControllerEL"; +import verifyAPIKey from "../middleware/verifyAPIKey"; + +const iamRoutesEL = Router(); + +iamRoutesEL.post( + "/iam/horacius/createUser", + verifyAPIKey, + IAMControllerEL.createUser +); + +iamRoutesEL.put( + "/iam/horacius/updateUser", + verifyAPIKey, + IAMControllerEL.updateUser +); + +iamRoutesEL.delete( + "/iam/horacius/deleteUser", + verifyAPIKey, + IAMControllerEL.deleteUser +); + +iamRoutesEL.get( + "/iam/horacius/listAllUsers", + verifyAPIKey, + IAMControllerEL.listAllUsers +); + +iamRoutesEL.get( + "/iam/horacius/checkUser", + verifyAPIKey, + IAMControllerEL.checkUser +); + +iamRoutesEL.patch( + "/iam/horacius/linkUserAndUserRight", + verifyAPIKey, + IAMControllerEL.linkUserAndUserRight +); + +iamRoutesEL.post( + "/iam/horacius/linkUserAndUserRight", + verifyAPIKey, + IAMControllerEL.checkUserRight +); + +iamRoutesEL.patch( + "/iam/horacius/resetPassword", + verifyAPIKey, + IAMControllerEL.resetPassword +); + +export default iamRoutesEL; diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index 63ba288..55b4516 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -15,10 +15,12 @@ import schedulingNotifiyRoutes from "./SchedulingNotifyRoutes"; import statusChatEndRoutes from "./statusChatEndRoutes"; import hitRoutes from "./hitRoutes"; import wbotMonitorRoutes from "./wbotMonitorRoutes"; +import iamRoutesEL from "./iamRoutesEL"; const routes = Router(); +routes.use(iamRoutesEL); routes.use(userRoutes); routes.use("/auth", authRoutes); routes.use(settingRoutes); diff --git a/backend/src/routes/queueRoutes.ts b/backend/src/routes/queueRoutes.ts index 6de13d9..96bb509 100644 --- a/backend/src/routes/queueRoutes.ts +++ b/backend/src/routes/queueRoutes.ts @@ -11,6 +11,8 @@ queueRoutes.post("/queue", isAuth, QueueController.store); queueRoutes.post("/queue/customization", QueueController.customization); +queueRoutes.get("/queue/remote/list", isAuth, QueueController.listQueues); + queueRoutes.get("/queue/:queueId", isAuth, QueueController.show); queueRoutes.put("/queue/:queueId", isAuth, QueueController.update); diff --git a/backend/src/routes/reportRoutes.ts b/backend/src/routes/reportRoutes.ts index 0c84aba..ecaf41a 100644 --- a/backend/src/routes/reportRoutes.ts +++ b/backend/src/routes/reportRoutes.ts @@ -1,18 +1,48 @@ //relatorio -import express from "express"; +import express from "express"; -import isAuth from "../middleware/isAuth"; - -import * as ReportController from "../controllers/ReportController"; +import isAuth from "../middleware/isAuth"; -const reportRoutes = express.Router(); +import * as ReportController from "../controllers/ReportController"; -reportRoutes.get("/reports", isAuth, ReportController.reportUserByDateStartDateEnd); +const reportRoutes = express.Router(); -reportRoutes.post("/reports/onqueue", ReportController.reportOnQueue); +reportRoutes.get( + "/reports", + isAuth, + ReportController.reportUserByDateStartDateEnd +); -reportRoutes.get("/reports/user/services", isAuth, ReportController.reportUserService); +reportRoutes.post("/reports/onqueue", ReportController.reportOnQueue); -reportRoutes.get("/reports/messages", isAuth, ReportController.reportMessagesUserByDateStartDateEnd); +reportRoutes.get( + "/reports/user/services", + isAuth, + ReportController.reportUserService +); + +reportRoutes.get( + "/reports/services/numbers", + isAuth, + ReportController.reportService +); + +reportRoutes.get( + "/reports/services/queues", + isAuth, + ReportController.reportServiceByQueue +); + +reportRoutes.get( + "/reports/messages", + isAuth, + ReportController.reportMessagesUserByDateStartDateEnd +); + +reportRoutes.get( + "/reports/count/statusChatEnd", + isAuth, + ReportController.reportTicksCountByStatusChatEnds +); export default reportRoutes; diff --git a/backend/src/routes/ticketRoutes.ts b/backend/src/routes/ticketRoutes.ts index 0524925..80fa9d3 100644 --- a/backend/src/routes/ticketRoutes.ts +++ b/backend/src/routes/ticketRoutes.ts @@ -5,7 +5,6 @@ import * as TicketController from "../controllers/TicketController"; const ticketRoutes = express.Router(); - // ticketRoutes.get("/tickets/cache", isAuth, TicketController.ticketsCache); ticketRoutes.get("/tickets/count", isAuth, TicketController.count); @@ -14,6 +13,11 @@ ticketRoutes.get("/tickets", isAuth, TicketController.index); ticketRoutes.get("/tickets/:ticketId", isAuth, TicketController.show); +ticketRoutes.post( + "/tickets/remote/create", isAuth, + TicketController.remoteTicketCreation +); + ticketRoutes.post("/tickets", isAuth, TicketController.store); ticketRoutes.put("/tickets/:ticketId", isAuth, TicketController.update); diff --git a/backend/src/server.ts b/backend/src/server.ts index 2845f75..16c065a 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -23,6 +23,11 @@ import fs from "fs"; import dir from "path"; import { getSettingValue } from "./helpers/WhaticketSettings"; import loadSettings from "./helpers/LoadSettings"; +import { clearAllKeys, get, set } from "./helpers/RedisClient"; +import ShowUserService from "./services/UserServices/ShowUserService"; +import { json } from "sequelize"; +import { setBotInfo } from "./helpers/SetBotInfo"; +import Queue from "./models/Queue"; const server = app.listen(process.env.PORT, () => { logger.info(`Server started on port: ${process.env.PORT}`); @@ -43,18 +48,54 @@ gracefulShutdown(server); (async () => { console.log("os.tmpdir(): ", os.tmpdir()); + await clearAllKeys("user:*", "whatsapp:*", "queue:*"); + + const users = await User.findAll(); + + for (const user of users) { + const { id, name } = user; + + if (name == "botqueue") { + const userService = await ShowUserService(id); + await setBotInfo(userService); + } + + await set(`user:${id}`, { id, name }); + } + + // const queues = await Queue.findAll(); + + // for (const queue of queues) { + // const { id, greetingMessage, name } = queue; + // await set(`queue:${id}`, { id, name, greetingMessage }); + // } + loadSettings(); let whatsapps: any = await Whatsapp.findAll({ - attributes: ["id", "url", "phoneNumberId"] - }); + attributes: ["id", "url", "phoneNumberId", "number", "greetingMessage"] + }); if (whatsapps && whatsapps.length > 0) { for (let i = 0; i < whatsapps.length; i++) { try { - const { phoneNumberId } = whatsapps[i]; + const { phoneNumberId, id, greetingMessage } = whatsapps[i]; - if (phoneNumberId) continue; + if (phoneNumberId) { + await set( + `whatsapp:${whatsapps[i].dataValues.id}`, + JSON.stringify({ + number: whatsapps[i].dataValues.number, + id, + greetingMessage, + phoneNumberId + }) + ); + } + + if (phoneNumberId) { + continue; + } console.log( `API URL: ${whatsapps[i].dataValues.url}/api/connection/status` diff --git a/backend/src/services/ContactServices/GetContactService.ts b/backend/src/services/ContactServices/GetContactService.ts new file mode 100644 index 0000000..3865bc5 --- /dev/null +++ b/backend/src/services/ContactServices/GetContactService.ts @@ -0,0 +1,38 @@ +import AppError from "../../errors/AppError"; +import Contact from "../../models/Contact"; +import CreateContactService from "./CreateContactService"; + +interface ExtraInfo { + name: string; + value: string; +} + +interface Request { + name: string; + number: string; + email?: string; + profilePicUrl?: string; + extraInfo?: ExtraInfo[]; +} + +const GetContactService = async ({ name, number }: Request): Promise => { + const numberExists = await Contact.findOne({ + where: { number } + }); + + if (!numberExists) { + const contact = await CreateContactService({ + name, + number, + }) + + if (contact == null) + throw new AppError("CONTACT_NOT_FIND") + else + return contact + } + + return numberExists +}; + +export default GetContactService; \ No newline at end of file diff --git a/backend/src/services/MessageServices/CreateMessageService.ts b/backend/src/services/MessageServices/CreateMessageService.ts index 95af0e3..1c37495 100644 --- a/backend/src/services/MessageServices/CreateMessageService.ts +++ b/backend/src/services/MessageServices/CreateMessageService.ts @@ -13,6 +13,7 @@ interface MessageData { read?: boolean; mediaType?: string; mediaUrl?: string; + fromAgent?: boolean; } interface Request { messageData: MessageData; diff --git a/backend/src/services/QueueService/CreateQueueService.ts b/backend/src/services/QueueService/CreateQueueService.ts index b783da8..528d1b1 100644 --- a/backend/src/services/QueueService/CreateQueueService.ts +++ b/backend/src/services/QueueService/CreateQueueService.ts @@ -1,6 +1,7 @@ import * as Yup from "yup"; import AppError from "../../errors/AppError"; import Queue from "../../models/Queue"; +import { set } from "../../helpers/RedisClient"; interface QueueData { name: string; @@ -9,68 +10,67 @@ interface QueueData { } const CreateQueueService = async (queueData: QueueData): Promise => { - try { - const { color, name } = queueData; - const queueSchema = Yup.object().shape({ - name: Yup.string() - .min(2, "ERR_QUEUE_INVALID_NAME") - .required("ERR_QUEUE_INVALID_NAME") - .test( - "Check-unique-name", - "ERR_QUEUE_NAME_ALREADY_EXISTS", - async value => { - if (value) { - const queueWithSameName = await Queue.findOne({ - where: { name: value } - }); + const queueSchema = Yup.object().shape({ + name: Yup.string() + .min(2, "ERR_QUEUE_INVALID_NAME") + .required("ERR_QUEUE_INVALID_NAME") + .test( + "Check-unique-name", + "ERR_QUEUE_NAME_ALREADY_EXISTS", + async value => { + if (value) { + const queueWithSameName = await Queue.findOne({ + where: { name: value } + }); - return !queueWithSameName; + return !queueWithSameName; + } + return false; + } + ), + color: Yup.string() + .required("ERR_QUEUE_INVALID_COLOR") + .test("Check-color", "ERR_QUEUE_INVALID_COLOR", async value => { + if (value) { + const colorTestRegex = /^#[0-9a-f]{3,6}$/i; + return colorTestRegex.test(value); } return false; - } - ), - color: Yup.string() - .required("ERR_QUEUE_INVALID_COLOR") - .test("Check-color", "ERR_QUEUE_INVALID_COLOR", async value => { - if (value) { - const colorTestRegex = /^#[0-9a-f]{3,6}$/i; - return colorTestRegex.test(value); - } - return false; - }) - .test( - "Check-color-exists", - "ERR_QUEUE_COLOR_ALREADY_EXISTS", - async value => { - if (value) { - const queueWithSameColor = await Queue.findOne({ - where: { color: value } - }); - return !queueWithSameColor; + }) + .test( + "Check-color-exists", + "ERR_QUEUE_COLOR_ALREADY_EXISTS", + async value => { + if (value) { + const queueWithSameColor = await Queue.findOne({ + where: { color: value } + }); + return !queueWithSameColor; + } + return false; } - return false; - } - ) - }); + ) + }); - try { - await queueSchema.validate({ color, name }); - } catch (err: any) { - throw new AppError(err.message); - } + try { + await queueSchema.validate({ color, name }); + } catch (err: any) { + throw new AppError(err.message); + } - const queue = await Queue.create(queueData); + const queue = await Queue.create(queueData); - return queue; + // const { id, greetingMessage } = queue; + // await set(`queue:${id}`, { id, name, greetingMessage }); + return queue; } catch (error: any) { - console.error('===> Error on CreateQueueService.ts file: \n', error) + console.error("===> Error on CreateQueueService.ts file: \n", error); throw new AppError(error.message); } - }; export default CreateQueueService; diff --git a/backend/src/services/QueueService/UpdateQueueService.ts b/backend/src/services/QueueService/UpdateQueueService.ts index d52da18..59a6077 100644 --- a/backend/src/services/QueueService/UpdateQueueService.ts +++ b/backend/src/services/QueueService/UpdateQueueService.ts @@ -3,6 +3,7 @@ import * as Yup from "yup"; import AppError from "../../errors/AppError"; import Queue from "../../models/Queue"; import ShowQueueService from "./ShowQueueService"; +import { set } from "../../helpers/RedisClient" interface QueueData { name?: string; @@ -14,9 +15,7 @@ const UpdateQueueService = async ( queueId: number | string, queueData: QueueData ): Promise => { - try { - const { color, name } = queueData; const queueSchema = Yup.object().shape({ @@ -30,7 +29,7 @@ const UpdateQueueService = async ( const queueWithSameName = await Queue.findOne({ where: { name: value, id: { [Op.not]: queueId } } }); - + return !queueWithSameName; } return true; @@ -59,24 +58,25 @@ const UpdateQueueService = async ( } ) }); - + try { await queueSchema.validate({ color, name }); } catch (err: any) { throw new AppError(err.message); } - + const queue = await ShowQueueService(queueId); - + await queue.update(queueData); - + + // const { id, greetingMessage } = queue; + // await set(`queue:${id}`, { id, name, greetingMessage }); + return queue; - } catch (error: any) { - console.error('===> Error on UpdateQueueService.ts file: \n', error) + console.error("===> Error on UpdateQueueService.ts file: \n", error); throw new AppError(error.message); - } - + } }; export default UpdateQueueService; diff --git a/backend/src/services/ReportServices/ReportByNumberQueueService.ts b/backend/src/services/ReportServices/ReportByNumberQueueService.ts new file mode 100644 index 0000000..02c07dd --- /dev/null +++ b/backend/src/services/ReportServices/ReportByNumberQueueService.ts @@ -0,0 +1,333 @@ +import { Sequelize } from "sequelize"; + +const dbConfig = require("../../config/database"); +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 Whatsapp from "../../models/Whatsapp"; +import { number } from "yup"; +import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; + +interface Request { + startDate: string | number; + endDate: string; + queue?: boolean; +} + +const ReportByNumberQueueService = async ({ + startDate, + endDate, + queue = false +}: Request): Promise => { + let reportServiceData: any[] = []; + + const whatsapps = await Whatsapp.findAll(); + + if (!queue) { + for (const whatsapp of whatsapps) { + const { id, name, number } = whatsapp; + + if ( + !number || + reportServiceData.findIndex((w: any) => w?.number == number) != -1 + ) + continue; + + console.log("NUMBER: ", number); + + // CHAT STARTED BY AGENT + const startedByAgent: any = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id) + AND m.fromAgent = 1 + AND w.number = ${number};`, + { type: QueryTypes.SELECT } + ); + + // CHAT STARTED BY CLIENT + const startedByClient: any = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id) + AND m.fromMe = 0 + AND w.number = ${number};`, + { type: QueryTypes.SELECT } + ); + + // CHAT CLOSED + const closedChat: any = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND t.status = 'closed' + AND w.number = ${number};`, + { type: QueryTypes.SELECT } + ); + + // CHAT WAINTING TIME + const avgChatWaitingTime: any = await sequelize.query( + ` + SELECT TIME_FORMAT( + SEC_TO_TIME( + TIMESTAMPDIFF( + SECOND, + ( + SELECT createdAt + FROM Messages + WHERE ticketId = m.ticketId + AND fromMe = 0 + ORDER BY createdAt ASC + LIMIT 1 + ), + ( + SELECT createdAt + FROM Messages + WHERE ticketId = m.ticketId + AND fromAgent = 1 + ORDER BY createdAt ASC + LIMIT 1 + ) + ) + ), '%H:%i:%s') AS WAITING_TIME + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id) + AND m.fromMe = 0 + -- AND q.id = 2 + AND w.number = ${number} + AND t.status IN ('open', 'closed') + HAVING WAITING_TIME IS NOT NULL + ORDER BY + WAITING_TIME;`, + { type: QueryTypes.SELECT } + ); + + // CHAT PENDING + const pendingChat: any = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND t.status = 'pending' + AND w.number = ${number};`, + + { type: QueryTypes.SELECT } + ); + + reportServiceData.push({ + id, + name, + number, + startedByAgent: startedByAgent[0]?.ticket_count, + startedByClient: startedByClient[0]?.ticket_count, + closedChat: closedChat[0]?.ticket_count, + avgChatWaitingTime: avg(avgChatWaitingTime), + pendingChat: pendingChat[0]?.ticket_count + }); + } + } else { + for (const whatsapp of whatsapps) { + const { id, name, number } = whatsapp; + + if ( + !number || + reportServiceData.findIndex((w: any) => w?.number == number) != -1 + ) + continue; + + const data = await ShowWhatsAppService(id); + + const queues: any = data.queues.map((q: any) => { + const { id, name, color } = q; + return { id, name, color }; + }); + + console.log("NUMBER 2: ", number); + + for (const q of queues) { + // CHAT STARTED BY AGENT + const startedByAgent: any = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id) + AND m.fromAgent = 1 + AND q.id = ${q.id};`, + { type: QueryTypes.SELECT } + ); + + // CHAT STARTED BY CLIENT + const startedByClient: any = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id) + AND m.fromMe = 0 + AND q.id = ${q.id};`, + { type: QueryTypes.SELECT } + ); + + // CHAT CLOSED + const closedChat: any = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND t.status = 'closed' + AND q.id = ${q.id};`, + { type: QueryTypes.SELECT } + ); + + // CHAT WAINTING TIME + const avgChatWaitingTime: any = await sequelize.query( + `SELECT TIME_FORMAT( + SEC_TO_TIME( + TIMESTAMPDIFF( + SECOND, + ( + SELECT createdAt + FROM Messages + WHERE ticketId = m.ticketId + AND fromMe = 0 + ORDER BY createdAt ASC + LIMIT 1 + ), + ( + SELECT createdAt + FROM Messages + WHERE ticketId = m.ticketId + AND fromAgent = 1 + ORDER BY createdAt ASC + LIMIT 1 + ) + ) + ), '%H:%i:%s') AS WAITING_TIME + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id) + AND m.fromMe = 0 + AND q.id = ${q.id} + AND t.status IN ('open', 'closed') + HAVING WAITING_TIME IS NOT NULL + ORDER BY + WAITING_TIME;`, + { type: QueryTypes.SELECT } + ); + + // CHAT PENDING + const pendingChat: any = await sequelize.query( + `SELECT COUNT(DISTINCT t.id) AS ticket_count + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND t.status = 'pending' + AND q.id = ${q.id};`, + + { type: QueryTypes.SELECT } + ); + + reportServiceData.push({ + id, + name, + number, + queueName: q.name, + queueColor: q.color, + startedByAgent: startedByAgent[0]?.ticket_count, + startedByClient: startedByClient[0]?.ticket_count, + closedChat: closedChat[0]?.ticket_count, + avgChatWaitingTime: avg(avgChatWaitingTime), + pendingChat: pendingChat[0]?.ticket_count + }); + } + } + } + + return reportServiceData; +}; + +export default ReportByNumberQueueService; + +function avg(avgChatWaitingTime: any) { + let waitingAVG: any = avgChatWaitingTime + .filter((t: any) => t?.WAITING_TIME) + .map((t: any) => t.WAITING_TIME) + + if (waitingAVG.length > 0) { + let midIndex = Math.floor((0 + waitingAVG.length) / 2) + + if (waitingAVG.length % 2 == 1) { + waitingAVG = waitingAVG[midIndex] + } else { + waitingAVG = calculateAverageTime( + waitingAVG[midIndex - 1], + waitingAVG[midIndex] + ) + } + } else { + waitingAVG = 0 + } + return waitingAVG +} + +function calculateAverageTime(time1: string, time2: string) { + // Function to parse time string to seconds + function timeStringToSeconds(timeString: string) { + const [hours, minutes, seconds] = timeString.split(":").map(Number); + return hours * 3600 + minutes * 60 + seconds; + } + + // Function to convert seconds to time string + function secondsToTimeString(seconds: number) { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const remainingSeconds = seconds % 60; + return `${hours.toString().padStart(2, "0")}:${minutes + .toString() + .padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`; + } + + // Convert time strings to seconds + const time1Seconds = timeStringToSeconds(time1); + const time2Seconds = timeStringToSeconds(time2); + + // Calculate average seconds + const averageSeconds = Math.round((time1Seconds + time2Seconds) / 2); + + // Convert average seconds back to time string + const averageTime = secondsToTimeString(averageSeconds); + + return averageTime; +} diff --git a/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts b/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts new file mode 100644 index 0000000..bffbb5d --- /dev/null +++ b/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts @@ -0,0 +1,27 @@ +import StatusChatEnd from "../../models/StatusChatEnd"; +import AppError from "../../errors/AppError"; + +import { Sequelize } from "sequelize"; +import { splitDateTime } from "../../helpers/SplitDateTime" +import ptBR from "date-fns/locale/pt-BR"; +import { format } from "date-fns" +const dbConfig = require("../../config/database"); +const sequelize = new Sequelize(dbConfig); +const { QueryTypes } = require("sequelize"); + +const CountStatusChatEndService = async ( + startDate: string, + endDate: string +) => { + + const countStatusChatEnd: any = await sequelize.query( + `select t.id, s.name, count(t.id) as count from Tickets t join StatusChatEnds s on +t.statusChatEndId = s.id and DATE(t.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' +group by s.id;`, + { type: QueryTypes.SELECT } + ); + + return countStatusChatEnd; +}; + +export default CountStatusChatEndService; diff --git a/backend/src/services/TicketServices/FindOrCreateTicketService.ts b/backend/src/services/TicketServices/FindOrCreateTicketService.ts index 66d51a7..29fe820 100644 --- a/backend/src/services/TicketServices/FindOrCreateTicketService.ts +++ b/backend/src/services/TicketServices/FindOrCreateTicketService.ts @@ -13,15 +13,12 @@ const FindOrCreateTicketService = async ( contact: Contact, whatsappId: number, unreadMessages: number, - groupContact?: Contact, + groupContact?: Contact, + queueId?: number | string ): Promise => { try { let ticket; - // else if (getSettingValue("whatsaAppCloudApi")?.value == "enabled") { - - // } - if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") { let whats = await ListWhatsAppsNumber(whatsappId); @@ -45,7 +42,8 @@ const FindOrCreateTicketService = async ( }); } - const { queues, greetingMessage, phoneNumberId } = await ShowWhatsAppService(whatsappId); + const { queues, greetingMessage, phoneNumberId } = + await ShowWhatsAppService(whatsappId); const botInfo = { isOnQueue: false }; @@ -97,7 +95,7 @@ const FindOrCreateTicketService = async ( if (!ticket) { let status = "pending"; - if (queues.length > 1 && !botInfo.isOnQueue) { + if (queues.length > 1 && !botInfo.isOnQueue && !queueId) { status = "queueChoice"; } @@ -105,6 +103,7 @@ const FindOrCreateTicketService = async ( contactId: groupContact ? groupContact.id : contact.id, status: status, isGroup: !!groupContact, + queueId, unreadMessages, whatsappId, phoneNumberId diff --git a/backend/src/services/TicketServices/FindOrCreateTicketServiceBot.ts b/backend/src/services/TicketServices/FindOrCreateTicketServiceBot.ts index 47d6d9f..38d7f6b 100644 --- a/backend/src/services/TicketServices/FindOrCreateTicketServiceBot.ts +++ b/backend/src/services/TicketServices/FindOrCreateTicketServiceBot.ts @@ -29,7 +29,8 @@ const FindOrCreateTicketServiceBot = async ( } }); - const { queues, greetingMessage } = await ShowWhatsAppService(whatsappId); + const { queues, greetingMessage, phoneNumberId } = + await ShowWhatsAppService(whatsappId); //Habilitar esse caso queira usar o bot @@ -102,12 +103,13 @@ const FindOrCreateTicketServiceBot = async ( } ticket = await Ticket.create({ - contactId: groupContact ? groupContact.id : contact.id, - status: status, - userId: botInfo.userIdBot, - isGroup: !!groupContact, - unreadMessages, - whatsappId + contactId: groupContact ? groupContact.id : contact.id, + status: status, + userId: botInfo.userIdBot, + isGroup: !!groupContact, + unreadMessages, + whatsappId, + phoneNumberId }); console.log('yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy') diff --git a/backend/src/services/TicketServices/ListTicketsService.ts b/backend/src/services/TicketServices/ListTicketsService.ts index ac2e2fb..1ea2cc6 100644 --- a/backend/src/services/TicketServices/ListTicketsService.ts +++ b/backend/src/services/TicketServices/ListTicketsService.ts @@ -59,13 +59,8 @@ const ListTicketsService = async ({ let whereCondition: Filterable["where"] = { [Op.or]: [{ userId }, { status: "pending" }], queueId: { [Op.or]: [queueIds, null] } }; - console.log('PAGE NUMBER TICKET: ', pageNumber) - - //TEST DEL - // const url = await getWbot(46) - // console.log('---------> URL: ', url) - // - + console.log('PAGE NUMBER TICKET: ', pageNumber) + if (pageNumber.trim().length == 0) { pageNumber = '1' } @@ -136,15 +131,14 @@ const ListTicketsService = async ({ whereCondition = { ...whereCondition, status }; - if (unlimited === 'true' && status !== 'pending') { - + if (unlimited === "current" && status !== "pending") { whereCondition = { ...whereCondition, createdAt: { - [Op.gte]: dateToday.fullDate + ' 00:00:00.000000', - [Op.lte]: dateToday.fullDate + ' 23:59:59.999999' + [Op.gte]: dateToday.fullDate + " 00:00:00.000000", + [Op.lte]: dateToday.fullDate + " 23:59:59.999999" } - } + }; } } @@ -196,8 +190,8 @@ const ListTicketsService = async ({ if ( userProfile.dataValues.profile != "admin" && - userProfile.dataValues.profile != "master" - // userProfile.dataValues.profile != "supervisor" + userProfile.dataValues.profile != "master" && + userProfile.dataValues.profile != "supervisor" ) { whereCondition = { ...whereCondition, userId }; } @@ -224,9 +218,10 @@ const ListTicketsService = async ({ }; } - const limit = unlimited === 'true' ? 100000 : 40; - const offset = limit * (+pageNumber - 1); + const limit = unlimited === "current" || unlimited === "all" ? 100000 : 40; + const offset = limit * (+pageNumber - 1); + console.log("kkkkkkkkk limit: ", limit); const { count, rows: tickets } = await Ticket.findAndCountAll({ where: whereCondition, diff --git a/backend/src/services/TicketServices/ShowTicketReport.ts b/backend/src/services/TicketServices/ShowTicketReport.ts index 6fe5bf9..1c4b1ea 100644 --- a/backend/src/services/TicketServices/ShowTicketReport.ts +++ b/backend/src/services/TicketServices/ShowTicketReport.ts @@ -7,23 +7,28 @@ import Queue from "../../models/Queue"; import Message from "../../models/Message"; import { userInfo } from "os"; -import { Op, where } from "sequelize"; +import { Op, QueryTypes, json, where } from "sequelize"; -import { Sequelize } from "sequelize"; -import moment from 'moment'; - -import { startOfDay, endOfDay, parseISO, getDate} from "date-fns"; +import { Sequelize } from "sequelize"; +import moment from "moment"; +const dbConfig = require("../../config/database"); +const sequelize = new Sequelize(dbConfig); + +import { startOfDay, endOfDay, parseISO, getDate } from "date-fns"; import { string } from "yup/lib/locale"; import Whatsapp from "../../models/Whatsapp"; +import Query from "mysql2/typings/mysql/lib/protocol/sequences/Query"; +import { te } from "date-fns/locale"; interface Request { userId: string | number; startDate: string; endDate: string; + createdOrUpdated?: string; + queueId?: string; pageNumber?: string; } - interface Response { tickets: Ticket[]; count: number; @@ -35,89 +40,182 @@ const ShowTicketReport = async ({ userId, startDate, endDate, - pageNumber = "1" -}: Request): Promise => { - - let where_clause = {} + pageNumber = "1", + createdOrUpdated = "created", + queueId +}: Request): Promise => { + // let where_clause: any = {}; + // let query = ""; - if(userId=='0'){ - where_clause = { - updatedAt: { - [Op.gte]: startDate+' 00:00:00.000000', - [Op.lte]: endDate +' 23:59:59.999999' - }, - } - } - else{ - where_clause = { - userid: userId, - updatedAt: { - [Op.gte]: startDate+' 00:00:00.000000', - [Op.lte]: endDate +' 23:59:59.999999' - }, - } + // if (userId !== "0") { + // where_clause.userid = userId; + // query = `AND t.userId = ${userId}`; + // } + + // if (queueId) { + // where_clause.queueId = queueId; + // query = `AND t.queueId = ${queueId}`; + // } + + const createdAtOrUpdatedAt = + createdOrUpdated == "created" ? "createdAt" : "updatedAt"; + + let where_clause = {}; + + if (queueId) { + where_clause = { + queueId: queueId, + [createdAtOrUpdatedAt]: { + [Op.gte]: startDate + " 00:00:00.000000", + [Op.lte]: endDate + " 23:59:59.999999" + } + }; + } else if (userId == "0") { + where_clause = { + [createdAtOrUpdatedAt]: { + [Op.gte]: startDate + " 00:00:00.000000", + [Op.lte]: endDate + " 23:59:59.999999" + } + }; + } else if (userId != "0") { + where_clause = { + userid: userId, + [createdAtOrUpdatedAt]: { + [Op.gte]: startDate + " 00:00:00.000000", + [Op.lte]: endDate + " 23:59:59.999999" + } + }; } - const limit = 40; const offset = limit * (+pageNumber - 1); - - const {count, rows: tickets} = await Ticket.findAndCountAll({ - where: where_clause , + let { count, rows: tickets }: any = await Ticket.findAndCountAll({ + where: where_clause, limit, offset, - //attributes: ['id', 'status', 'createdAt', 'updatedAt'], - - attributes: ['id', 'status', 'statusChatEnd', [Sequelize.fn("DATE_FORMAT",Sequelize.col("Ticket.createdAt"),"%d/%m/%Y %H:%i:%s"),"createdAt"], - [Sequelize.fn("DATE_FORMAT",Sequelize.col("Ticket.updatedAt"),"%d/%m/%Y %H:%i:%s"),"updatedAt"]], + attributes: [ + "id", + "status", + "statusChatEnd", + [ + Sequelize.fn( + "DATE_FORMAT", + Sequelize.col("Ticket.createdAt"), + "%d/%m/%Y %H:%i:%s" + ), + "createdAt" + ], + [ + Sequelize.fn( + "DATE_FORMAT", + Sequelize.col("Ticket.updatedAt"), + "%d/%m/%Y %H:%i:%s" + ), + "updatedAt" + ] + ], include: [ { model: Message, - required:true, + required: true, separate: true, - - // attributes: ['body', 'read', 'mediaType','fromMe', 'mediaUrl','createdAt'], - attributes: ['body', 'read', 'mediaType','fromMe', 'mediaUrl', [Sequelize.fn("DATE_FORMAT",Sequelize.col("createdAt"),"%d/%m/%Y %H:%i:%s"),"createdAt"]], - - order: [ - ['createdAt', 'ASC'] - ] - }, - { - model: Contact, - attributes: ['name', 'number'] + attributes: [ + "body", + "read", + "mediaType", + "fromMe", + "mediaUrl", + [ + Sequelize.fn( + "DATE_FORMAT", + Sequelize.col("createdAt"), + "%d/%m/%Y %H:%i:%s" + ), + "createdAt" + ] + ], + order: [["createdAt", "ASC"]] }, { - model: User, - attributes: ['name', 'email'] + model: Contact, + attributes: ["name", "number"] }, { - model: Queue, - attributes: ['name'] + model: User, + attributes: ["name", "email"] }, { - model: Whatsapp, - attributes: ['name'] + model: Queue, + attributes: ["name"] }, - ], - - order: [ - ['id', 'ASC'] - ] - - }); + { + model: Whatsapp, + attributes: ["name"] + } + ], + order: [["updatedAt", "DESC"]] + }); const hasMore = count > offset + tickets.length; - - + if (!tickets) { throw new AppError("ERR_NO_TICKET_FOUND", 404); } - return {tickets, count, hasMore}; -}; + if (tickets.length > 0) { + const waiting_time: any = await sequelize.query( + `SELECT t.id as ticketId, t.status, TIME_FORMAT( + SEC_TO_TIME( + TIMESTAMPDIFF( + SECOND, + ( + SELECT createdAt + FROM Messages + WHERE ticketId = m.ticketId + AND fromMe = 0 + ORDER BY createdAt ASC + LIMIT 1 + ), + ( + SELECT createdAt + FROM Messages + WHERE ticketId = m.ticketId + AND fromAgent = 1 + ORDER BY createdAt ASC + LIMIT 1 + ) + ) + ), '%H:%i:%s') AS WAITING_TIME + FROM Tickets t + JOIN Messages m ON t.id = m.ticketId + JOIN Whatsapps w ON t.whatsappId = w.id + JOIN Queues q ON q.id = t.queueId + WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND t.id IN (${tickets.map((t: any) => t.id).join()}) + AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id) + AND m.fromMe = 0 + AND t.status IN ('open', 'closed') + HAVING WAITING_TIME IS NOT NULL + ORDER BY + WAITING_TIME;`, + { type: QueryTypes.SELECT } + ); + + for (let w of waiting_time) { + const { ticketId, status, WAITING_TIME } = w; + + const index = tickets.findIndex((t: any) => +t?.id == +ticketId); + + if (index != -1) { + tickets[index].dataValues.waiting_time = WAITING_TIME; + } + } + } + + return { tickets, count, hasMore }; +}; export default ShowTicketReport; diff --git a/backend/src/services/TicketServices/UpdateTicketService.ts b/backend/src/services/TicketServices/UpdateTicketService.ts index df0e321..943015b 100644 --- a/backend/src/services/TicketServices/UpdateTicketService.ts +++ b/backend/src/services/TicketServices/UpdateTicketService.ts @@ -10,7 +10,7 @@ import { createOrUpdateTicketCache } from "../../helpers/TicketCache"; import AppError from "../../errors/AppError"; import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket"; import BotIsOnQueue from "../../helpers/BotIsOnQueue"; -import { deleteObject } from "../../helpers/RedisClient" +import { deleteObject } from "../../helpers/RedisClient"; var flatten = require("flat"); interface TicketData { @@ -18,6 +18,7 @@ interface TicketData { userId?: number; queueId?: number; statusChatEnd?: string; + statusChatEndId?: number; unreadMessages?: number; whatsappId?: string | number; } @@ -46,6 +47,7 @@ const UpdateTicketService = async ({ queueId, statusChatEnd, unreadMessages, + statusChatEndId, whatsappId } = ticketData; @@ -66,13 +68,14 @@ const UpdateTicketService = async ({ if (oldStatus === "closed") { await CheckContactOpenTickets(ticket.contact.id, ticket.whatsappId); } - + await ticket.update({ status, queueId, userId, unreadMessages, statusChatEnd, + statusChatEndId, whatsappId }); @@ -80,7 +83,7 @@ const UpdateTicketService = async ({ if (msg?.trim().length > 0) { setTimeout(async () => { - sendWhatsAppMessageSocket(ticket, msg); + sendWhatsAppMessageSocket(ticket, `\u200e${msg}`); }, 2000); } diff --git a/backend/src/services/UserServices/CreateUserService.ts b/backend/src/services/UserServices/CreateUserService.ts index 5c6badb..9177289 100644 --- a/backend/src/services/UserServices/CreateUserService.ts +++ b/backend/src/services/UserServices/CreateUserService.ts @@ -11,6 +11,7 @@ interface Request { positionCompany?: string; queueIds?: number[]; profile?: string; + ignoreThrow?: boolean; } interface Response { @@ -27,25 +28,27 @@ const CreateUserService = async ({ name, positionCompany, queueIds = [], - profile = "master" -}: Request): Promise => { - + profile = "master", + ignoreThrow = false +}: Request): Promise => { try { - const schema = Yup.object().shape({ name: Yup.string().required().min(2), - email: Yup.string().required().trim().test( - "Check-email", - "An user with this email already exists.", - async value => { - if (!value) return false; - const emailExists = await User.findOne({ - where: { email: value } - }); - return !emailExists; - } - ), + email: Yup.string() + .required() + .trim() + .test( + "Check-email", + "An user with this email already exists.", + async value => { + if (!value) return false; + const emailExists = await User.findOne({ + where: { email: value } + }); + return !emailExists; + } + ), // email: Yup.string().email().required().test( // "Check-email", @@ -65,6 +68,8 @@ const CreateUserService = async ({ try { await schema.validate({ email, password, name }); } catch (err: any) { + if (ignoreThrow) return { error: true, msg: err.message, status: 400 }; + throw new AppError(err.message); } @@ -86,12 +91,14 @@ const CreateUserService = async ({ const serializedUser = SerializeUser(user); return serializedUser; - } catch (error: any) { - console.error('===> Error on CreateUserService.ts file: \n', error) + console.error("===> Error on CreateUserService.ts file: \n", error); + + if (ignoreThrow) + return { error: true, msg: "Create user error", status: 500 }; + throw new AppError(error.message); } - }; export default CreateUserService; diff --git a/backend/src/services/UserServices/DeleteUserService.ts b/backend/src/services/UserServices/DeleteUserService.ts index 7209cf2..e1e6a2c 100644 --- a/backend/src/services/UserServices/DeleteUserService.ts +++ b/backend/src/services/UserServices/DeleteUserService.ts @@ -2,14 +2,24 @@ import User from "../../models/User"; import AppError from "../../errors/AppError"; import Ticket from "../../models/Ticket"; import UpdateDeletedUserOpenTicketsStatus from "../../helpers/UpdateDeletedUserOpenTicketsStatus"; -import { set } from "../../helpers/RedisClient" +import { set } from "../../helpers/RedisClient"; -const DeleteUserService = async (id: string | number): Promise => { +const DeleteUserService = async ( + id: string | number, + ignoreThrow = false +): Promise => { const user = await User.findOne({ where: { id } }); if (!user) { + if (ignoreThrow) + return { + error: true, + msg: `No user found with this id ${id}`, + status: 404 + }; + throw new AppError("ERR_NO_USER_FOUND", 404); } diff --git a/backend/src/services/UserServices/ListUserParamiterService.ts b/backend/src/services/UserServices/ListUserParamiterService.ts index ebb610b..2d92498 100644 --- a/backend/src/services/UserServices/ListUserParamiterService.ts +++ b/backend/src/services/UserServices/ListUserParamiterService.ts @@ -2,7 +2,7 @@ import { Op, Sequelize } from "sequelize"; import Queue from "../../models/Queue"; import User from "../../models/User"; import UserQueue from "../../models/UserQueue"; -import { List } from "whatsapp-web.js" +import { List } from "whatsapp-web.js"; interface Request { userId?: string | number; @@ -12,7 +12,13 @@ interface Request { userIds?: string | number; } -const ListUser = async ({ profile, userId, raw, userIds, profiles }: Request): Promise => { +const ListUser = async ({ + profile, + userId, + raw, + userIds, + profiles +}: Request): Promise => { let where_clause = {}; if (userId && profile) { @@ -47,7 +53,7 @@ const ListUser = async ({ profile, userId, raw, userIds, profiles }: Request): P ], order: [["id", "ASC"]], - group: ["User.id"] + group: userIds ? undefined : ["User.id"] }); return users; diff --git a/backend/src/services/UserServices/ListWhatsappQueuesByUserQueue.ts b/backend/src/services/UserServices/ListWhatsappQueuesByUserQueue.ts new file mode 100644 index 0000000..9c68e32 --- /dev/null +++ b/backend/src/services/UserServices/ListWhatsappQueuesByUserQueue.ts @@ -0,0 +1,37 @@ +import { Op, Sequelize } from "sequelize"; +import Whatsapp from "../../models/Whatsapp"; +import WhatsappQueue from "../../models/WhatsappQueue"; +import { List } from "whatsapp-web.js"; +import UserQueue from "../../models/UserQueue"; +import Queue from "../../models/Queue"; +const dbConfig = require("../../config/database"); +const { QueryTypes } = require("sequelize"); + +const sequelize = new Sequelize(dbConfig); + +const ListWhatsappQueuesByUserQueue = async (userId: string | number) => { + try { + let userQueue: any = await UserQueue.findAll({ + where: { userId }, + attributes: ["queueId"], + raw: true + }); + + if (userQueue && userQueue.length > 0) { + userQueue = userQueue.map((u: any) => u.queueId); + + const result = await sequelize.query( + `select w.id, w.number, wq.whatsappId, wq.queueId, q.id, q.name from WhatsappQueues wq join Queues q on +wq.queueId = q.id join Whatsapps w +on wq.whatsappId = w.id where q.id in (${userQueue.join()})`, + { type: QueryTypes.SELECT } + ); + return result; + } + } catch (error) { + console.error("Error fetching joined data:", error); + } + return []; +}; + +export default ListWhatsappQueuesByUserQueue; diff --git a/backend/src/services/UserServices/UpdateUserService.ts b/backend/src/services/UserServices/UpdateUserService.ts index 8a0a4ba..329186c 100644 --- a/backend/src/services/UserServices/UpdateUserService.ts +++ b/backend/src/services/UserServices/UpdateUserService.ts @@ -16,6 +16,7 @@ interface UserData { interface Request { userData: UserData; userId: string | number; + ignoreThrow?: boolean; } interface Response { @@ -27,41 +28,53 @@ interface Response { const UpdateUserService = async ({ userData, - userId -}: Request): Promise => { - + userId, + ignoreThrow = false +}: Request): Promise => { try { - const user = await ShowUserService(userId); const schema = Yup.object().shape({ name: Yup.string().min(2), - // email: Yup.string().min(2), + // email: Yup.string().min(2), profile: Yup.string(), password: Yup.string(), - email: Yup.string().trim().required().test( - "Check-email", - "An user with this email already exists.", - async value => { + email: Yup.string() + .trim() + .required() + .test( + "Check-email", + "An user with this email already exists.", + async value => { + if (!value) return false; - if (!value) return false; + const emailExists = await User.findOne({ + where: { email: value }, + raw: true, + attributes: ["email", "id"] + }); - const emailExists = await User.findOne({ where: { email: value }, raw: true, attributes: ['email', 'id'] }); + if (emailExists && user.id != emailExists?.id) { + console.error( + "The email already exists in another user profile!" + ); + return !emailExists; + } - if (emailExists && user.id != emailExists?.id) { - - console.error('The email already exists in another user profile!') - return !emailExists; + return true; } - - return true - } - ), - + ) }); - const { email, password, profile, name, positionCompany, queueIds = [] } = userData; + const { + email, + password, + profile, + name, + positionCompany, + queueIds = [] + } = userData; try { await schema.validate({ email, password, profile, name }); @@ -69,7 +82,6 @@ const UpdateUserService = async ({ throw new AppError(err.message); } - await user.update({ email, password, @@ -91,13 +103,18 @@ const UpdateUserService = async ({ }; return serializedUser; + } catch (err: any) { + console.error("===> Error on UpdateUserService.ts file: \n", err); - } catch (error: any) { - console.error('===> Error on UpdateUserService.ts file: \n', error) - throw new AppError(error.message); + if (ignoreThrow) + return { + error: true, + msg: err.message, + status: 500 + }; + + throw new AppError(err.message); } - - }; export default UpdateUserService; diff --git a/backend/src/services/WbotServices/CheckIsValidContact.ts b/backend/src/services/WbotServices/CheckIsValidContact.ts index 888146a..cf9afec 100644 --- a/backend/src/services/WbotServices/CheckIsValidContact.ts +++ b/backend/src/services/WbotServices/CheckIsValidContact.ts @@ -1,25 +1,60 @@ +import axios from "axios"; import AppError from "../../errors/AppError"; import endPointQuery from "../../helpers/EndPointQuery"; import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp"; import { getWbot } from "../../libs/wbot"; -const CheckIsValidContact = async (number: string): Promise => { +const CheckIsValidContact = async ( + number: string, + ignoreThrow?: boolean +): Promise => { + const defaultWhatsapp = await GetDefaultWhatsApp({ + ignoreNoWhatsappFound: true + }); - const defaultWhatsapp = await GetDefaultWhatsApp({}); + let isValidNumber; - const wbot_url = await getWbot(defaultWhatsapp.id); + if (defaultWhatsapp) { + const wbot_url = await getWbot(defaultWhatsapp.id); - const isValidNumber = await endPointQuery(`${wbot_url}/api/validate`, { mobile: `${number}`, }) + let { data } = await endPointQuery(`${wbot_url}/api/validate`, { + mobile: `${number}` + }); - // console.log('isValidNumber.data.number: ', isValidNumber.data.number) + if (data?.isValid) { + isValidNumber = data; + } + } try { + let _status: any; - // const isValidNumber = await wbot.isRegisteredUser(`${number}@c.us`); + if (!isValidNumber) { - if (!isValidNumber || isValidNumber && !isValidNumber.data.isValid) { + const { data, status } = await axios.post( + `${process.env.WHATS_NUMBER_VALIDATOR_URL}/api/validate`, + { mobile: number }, + { + headers: { + "Content-Type": "application/json" + } + } + ); + + isValidNumber = data; + _status = status; + } + if (ignoreThrow) return isValidNumber?.number; + + console.log('_status: ', _status) + + if (_status && _status == 422) throw new AppError("ERR_NO_WAPP_FOUND"); + + if (!isValidNumber || (isValidNumber && !isValidNumber.isValid)) { throw new AppError("invalidNumber"); } + + if (isValidNumber && isValidNumber?.isValid) return isValidNumber.number; } catch (err: any) { if (err.message === "invalidNumber") { throw new AppError("ERR_WAPP_INVALID_CONTACT"); @@ -27,10 +62,6 @@ const CheckIsValidContact = async (number: string): Promise => { throw new AppError("ERR_WAPP_CHECK_CONTACT"); } - - if (isValidNumber && isValidNumber.data.isValid) - return isValidNumber.data.number - }; export default CheckIsValidContact; diff --git a/backend/src/services/WbotServices/GetProfilePicUrl.ts b/backend/src/services/WbotServices/GetProfilePicUrl.ts index 82be7d2..0fc3ea7 100644 --- a/backend/src/services/WbotServices/GetProfilePicUrl.ts +++ b/backend/src/services/WbotServices/GetProfilePicUrl.ts @@ -1,23 +1,47 @@ +import axios from "axios"; import endPointQuery from "../../helpers/EndPointQuery"; import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp"; import { getWbot } from "../../libs/wbot"; const GetProfilePicUrl = async (number: string): Promise => { + const defaultWhatsapp = await GetDefaultWhatsApp({ + ignoreNoWhatsappFound: true + }); + + let profilePicUrl; - const defaultWhatsapp = await GetDefaultWhatsApp({}); + if (defaultWhatsapp) { + const wbot_url = await getWbot(defaultWhatsapp.id); - const wbot_url = await getWbot(defaultWhatsapp.id); + const {data} = await endPointQuery(`${wbot_url}/api/GetProfilePicUrl`, { + number: `${number}` + }); - let profilePicUrl = await endPointQuery(`${wbot_url}/api/GetProfilePicUrl`, { number: `${number}`, }) - - console.log('profilePicUrl.data.data: ', profilePicUrl.data.data) - - if (profilePicUrl && profilePicUrl.data.data) { - return profilePicUrl.data.data; + if (data?.data) { + profilePicUrl = data.data; + } } + + try { + if (!profilePicUrl) { + const { data } = await axios.post( + `${process.env.WHATS_NUMBER_VALIDATOR_URL}/api/GetProfilePicUrl`, + { number }, + { + headers: { + "Content-Type": "application/json" + } + } + ); + profilePicUrl = data?.data; + } + + if (profilePicUrl) { + return profilePicUrl; + } + } catch (error) {} - return null - + return null; }; export default GetProfilePicUrl; diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index d770ec5..d333ab5 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -10,6 +10,7 @@ import path from "path"; import { isHoliday, isOutBusinessTime, + isOutBusinessTimeSaturday, isWeekend } from "../../helpers/TicketConfig"; @@ -45,7 +46,7 @@ import FindOrCreateTicketService from "../TicketServices/FindOrCreateTicketServi import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; import { debounce } from "../../helpers/Debounce"; import UpdateTicketService from "../TicketServices/UpdateTicketService"; -import { date } from "faker"; +import { date, name } from "faker"; import ShowQueueService from "../QueueService/ShowQueueService"; import ShowTicketMessage from "../TicketServices/ShowTicketMessage"; @@ -87,20 +88,24 @@ import { setMessageAsRead } from "../../helpers/SetMessageAsRead"; import FindOrCreateTicketServiceBot from "../TicketServices/FindOrCreateTicketServiceBot"; import { getSettingValue } from "../../helpers/WhaticketSettings"; -import { Op } from "sequelize"; +import { Op, json } from "sequelize"; import SettingTicket from "../../models/SettingTicket"; import mostRepeatedPhrase from "../../helpers/MostRepeatedPhrase"; import ListWhatsAppsNumber from "../WhatsappService/ListWhatsAppsNumber"; import { createObject, - del, + findByContain, findObject, get, - set + getSimple } from "../../helpers/RedisClient"; -import ShowContactCustomFieldService from "../ContactServices/ShowContactCustomFieldsService"; -import ShowTicketService from "../TicketServices/ShowTicketService" +import FindOrCreateTicketServiceBot from "../TicketServices/FindOrCreateTicketServiceBot"; +import ShowTicketService from "../TicketServices/ShowTicketService"; +import ShowQueuesByUser from "../UserServices/ShowQueuesByUser"; +import ListWhatsappQueuesByUserQueue from "../UserServices/ListWhatsappQueuesByUserQueue"; +import CreateContactService from "../ContactServices/CreateContactService"; +import { number } from "yup"; var lst: any[] = getWhatsappIds(); @@ -177,9 +182,14 @@ const verifyMediaMessage = async ( mediaUrl: media.filename, mediaType: media.mimetype.split("/")[0], quotedMsgId: quotedMsg, - phoneNumberId: msg?.phoneNumberId + phoneNumberId: msg?.phoneNumberId, + fromAgent: false }; + if (msg?.fromMe) { + messageData = { ...messageData, fromAgent: true }; + } + if (!ticket?.phoneNumberId) { if (!media.filename) { const ext = media.mimetype.split("/")[1].split(";")[0]; @@ -215,20 +225,43 @@ const verifyMessage = async ( contact: Contact, quotedMsg?: any ) => { - const messageData = { + let messageData = { id: msg.id.id, ticketId: ticket.id, contactId: msg.fromMe ? undefined : contact.id, body: msg.body, fromMe: msg.fromMe, + fromAgent: false, mediaType: msg.type, read: msg.fromMe, quotedMsgId: quotedMsg, phoneNumberId: msg?.phoneNumberId }; + if (msg?.fromMe) { + const botInfo = await BotIsOnQueue("botqueue"); + + if (botInfo.isOnQueue) { + const ura: any = await get({ key: "ura" }); + + if (ura && !ura.includes(JSON.stringify(msg?.body))) { + messageData = { ...messageData, fromAgent: true }; + } + } else if (msg?.body?.trim().length > 0 && !/\u200e/.test(msg.body[0])) { + messageData = { ...messageData, fromAgent: true }; + } + } + await ticket.update({ lastMessage: msg.body }); + if (!msg?.fromMe && msg?.vCards && msg?.vCards?.length > 0) { + if (msg.vCards.length == 1) { + messageData = { ...messageData, body: msg.vCards[0] }; + } else { + messageData = { ...messageData, body: JSON.stringify(msg.vCards) }; + } + } + await CreateMessageService({ messageData }); }; @@ -621,13 +654,14 @@ const verifyQueue = async ( selectedOption = 1; choosenQueue = queues[+selectedOption - 1]; } else { - selectedOption = msg.body; + selectedOption = msg?.body; - //////////////// EXTRAIR APENAS O NÚMERO /////////////////// - selectedOption = selectedOption.replace(/[^1-9]/g, ""); - /////////////////////////////////// - - choosenQueue = queues[+selectedOption - 1]; + if (selectedOption && selectedOption.trim().length > 0) { + //////////////// EXTRAIR APENAS O NÚMERO /////////////////// + selectedOption = selectedOption.replace(/[^1-9]/g, ""); + /////////////////////////////////// + choosenQueue = queues[+selectedOption - 1]; + } } if (choosenQueue) { @@ -651,13 +685,14 @@ const verifyQueue = async ( ticketId: ticket.id }); - const data = await get("ura"); + const data = await get({ key: "ura", parse: true }); await createObject({ whatsappId: `${ticket.whatsappId}`, contactId: `${ticket.contactId}`, identifier: "ura", - value: data[1].id + value: data[1].id, + history: `|${data[1].id}` }); botSendMessage(ticket, data[1].value); @@ -672,7 +707,7 @@ const verifyQueue = async ( if (outService.length > 0) { const { type, msg: msgOutService } = outService[0]; console.log(`${type} message ignored on queue`); - botSendMessage(ticket, msgOutService); + botSendMessage(ticket, `\u200e${msgOutService}`); return; } @@ -689,7 +724,7 @@ const verifyQueue = async ( //test del transfere o atendimento se entrar na ura infinita const repet: any = await mostRepeatedPhrase(ticket.id); - if (repet.occurrences > 4) { + if (repet.occurrences > 10) { await UpdateTicketService({ ticketData: { status: "pending", queueId: queues[0].id }, ticketId: ticket.id @@ -794,6 +829,7 @@ const isValidMsg = (msg: any): boolean => { msg.type === "image" || msg.type === "document" || msg.type === "vcard" || + msg.type === "multi_vcard" || msg.type === "sticker" ) return true; @@ -812,10 +848,14 @@ const queuesOutBot = async (wbot: Session, botId: string | number) => { return { queues, greetingMessage }; }; -const transferTicket = async (queueName: any, wbot: any, ticket: Ticket) => { +const transferTicket = async ( + queueName: any, + wbot: any, + ticket: Ticket, + sendGreetingMessage?: boolean +) => { const botInfo = await BotIsOnQueue("botqueue"); - - console.log("kkkkkkkkkkkkkkkkkkkkk queueName: ", queueName); + const io = getIO(); const queuesWhatsGreetingMessage = await queuesOutBot( wbot, @@ -826,27 +866,48 @@ const transferTicket = async (queueName: any, wbot: any, ticket: Ticket) => { const queues = queuesWhatsGreetingMessage.queues; - // console.log("queues ---> ", console.log(JSON.stringify(queues, null, 6))); - if (typeof queueName == "string") { queue = queues.find( (q: any) => q?.name?.toLowerCase() == queueName.trim().toLowerCase() ); - // await deleteObject(wbot.id, `${ticket.contactId}`, "ura"); } else if (typeof queueName == "number") { queue = queues[queueName]; } - if (queue) await botTransferTicket(queue, ticket); + if (queue) await botTransferTicket(queue, ticket, sendGreetingMessage); + io.emit("notifyPeding", { data: { ticket, queue } }); }; -const botTransferTicket = async (queues: Queue, ticket: Ticket) => { +const botTransferTicket = async ( + queues: Queue, + ticket: Ticket, + sendGreetingMessage?: boolean +) => { await ticket.update({ userId: null }); await UpdateTicketService({ ticketData: { status: "pending", queueId: queues.id }, ticketId: ticket.id }); + + if (sendGreetingMessage && queues?.greetingMessage?.length > 0) { + botSendMessage(ticket, queues.greetingMessage); + } +}; + +const botTransferTicketToUser = async ( + userId: number, + ticket: Ticket, + queueId?: number | undefined +) => { + console.log("USER ID: ", userId); + + // await ticket.update({ userId: userId }); + + await UpdateTicketService({ + ticketData: { status: "open", userId, queueId }, + ticketId: ticket.id + }); }; const botSendMessage = (ticket: Ticket, msg: string) => { @@ -909,31 +970,20 @@ const handleMessage = async ( // let groupContact: Contact | undefined; if (msg.fromMe) { - const whatsapp = await whatsappInfo(wbot.id); - - 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 // if so, this message was already been stored in database; - // if (/\u200e/.test(msg.body[0])) return; + if (/\u200e/.test(msg.body[0])) return; // media messages sent from me from cell phone, first comes with "hasMedia = false" and type = "image/ptt/etc" // in this case, return and let this message be handled by "media_uploaded" event, when it will have "hasMedia = true" - if (!msg.hasMedia && msg.type !== "chat" && msg.type !== "vcard") return; + if ( + !msg.hasMedia && + msg.type !== "chat" && + msg.type !== "vcard" && + msg.type !== "multi_vcard" + ) + return; } else { console.log(`\n <<<<<<<<<< RECEIVING MESSAGE: Parcial msg and msgContact info: @@ -1062,11 +1112,55 @@ const handleMessage = async ( await verifyQueue(wbot, msg, ticket, contact); } - // O bot interage com o cliente e encaminha o atendimento para fila de atendende quando o usuário escolhe a opção falar com atendente + if (msg.type === "vcard" || msg.type === "multi_vcard") { + await vcard(msg); + } - //Habilitar esse caso queira usar o bot const botInfo = await BotIsOnQueue("botqueue"); - // const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 }; + + // Transfer to agent + // O bot interage com o cliente e encaminha o atendimento para fila de atendende quando o usuário escolhe a opção falar com atendente + if ( + !msg.fromMe && + ((ticket.status == "open" && + botInfo && + ticket.userId == +botInfo.userIdBot) || + ticket.status == "pending" || + ticket.status == "queueChoice") + ) { + const filteredUsers = await findByContain("user:*", "name", msg?.body); + + if (filteredUsers && filteredUsers.length > 0) { + if (botInfo.isOnQueue) { + transferTicket(filteredUsers[0].name, wbot, ticket, true); + return; + } + + const whatsappQueues = await ListWhatsappQueuesByUserQueue( + +filteredUsers[0].id + ); + + const obj: any = whatsappQueues.find( + (ob: any) => ob.whatsappId == wbot.id + ); + + if (obj) { + await botTransferTicketToUser( + +filteredUsers[0].id, + ticket, + +obj.queueId + ); + + await botSendMessage( + ticket, + `Você foi transferido para falar com o agente ${filteredUsers[0].name}, aguarde.` + ); + } + + return; + } + } + // if ( botInfo.isOnQueue && @@ -1077,7 +1171,7 @@ const handleMessage = async ( console.log("repet.occurrences: ", repet.occurrences); - if (repet.occurrences > 4) { + if (repet.occurrences > 10) { await transferTicket(0, wbot, ticket); await SendWhatsAppMessage({ @@ -1156,11 +1250,6 @@ dialog_actions=request_endpoint=https://sos.espacolaser.com.br/api/whatsapp/tick menuMsg?.transferToQueue && menuMsg.transferToQueue.trim().length > 0 ) { - console.log( - "YYYYYYYYYYYYYYYYYYYY menuMsg.transferToQueue: ", - menuMsg.transferToQueue - ); - transferTicket(menuMsg.transferToQueue.trim(), wbot, ticket); } else if (menuMsg?.query) { await set( @@ -1187,6 +1276,29 @@ dialog_actions=request_endpoint=https://sos.espacolaser.com.br/api/whatsapp/tick msg.body == "0" && ticket.status == "pending" && ticket.queueId + ) { + if (botInfo.isOnQueue) { + let choosenQueue = await ShowQueueService(botInfo.botQueueId); + + await UpdateTicketService({ + ticketData: { + status: "open", + userId: botInfo.userIdBot, + queueId: choosenQueue.id + }, + ticketId: ticket.id + }); + const menuMsg: any = await menu(msg.body, wbot.id, contact.id); + await botSendMessage(ticket, menuMsg.value); + } + + return; + } else if ( + !msg.fromMe && + msg.body == "#" && + ticket.status == "pending" && + ticket.queueId && + botInfo.isOnQueue ) { let choosenQueue = await ShowQueueService(botInfo.botQueueId); @@ -1198,10 +1310,20 @@ dialog_actions=request_endpoint=https://sos.espacolaser.com.br/api/whatsapp/tick }, ticketId: ticket.id }); + // const menuMsg: any = await menu(msg.body, wbot.id, contact.id); + // await botSendMessage(ticket, menuMsg.value); + + // const data: any = await get({ key: "ura", parse: true }); + + // return await backUra(ticket.whatsappId, ticket.contactId, data); + const menuMsg: any = await menu(msg.body, wbot.id, contact.id); + + console.log("menuMsg: ", menuMsg); + await botSendMessage(ticket, menuMsg.value); - return; + // return; } if (msg && !msg.fromMe && ticket.status == "pending") { @@ -1227,7 +1349,7 @@ dialog_actions=request_endpoint=https://sos.espacolaser.com.br/api/whatsapp/tick return; } - botSendMessage(ticket, msgOutService); + botSendMessage(ticket, `\u200e${msgOutService}`); return; } } @@ -1240,7 +1362,7 @@ dialog_actions=request_endpoint=https://sos.espacolaser.com.br/api/whatsapp/tick const menu = async (userTyped: string, whatsappId: any, contactId: any) => { let lastId = await findObject(whatsappId, contactId, "ura"); - const data: any = await get("ura"); + const data: any = await get({ key: "ura", parse: true }); console.log("lastId[0]: ", lastId[0]); @@ -1249,7 +1371,8 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { whatsappId, contactId, identifier: "ura", - value: data[1].id + value: data[1].id, + history: `|${data[1].id}` }); } @@ -1259,7 +1382,7 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { if ( lastId && - lastId.length == 4 && + (lastId.length == 4 || lastId.length == 5) && lastId[3] && lastId[3].trim().length > 0 ) { @@ -1269,23 +1392,48 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { o.value.toLowerCase() == userTyped.toLowerCase() ); - // TEST DEL - console.log("OPTION: ", option); - - if (!option && userTyped != "0") { + if (!option && userTyped != "0" && userTyped != "#") { if (!existSubMenu()) { const response = await mainOptionsMenu(userTyped); if (response) return response; else { - console.log("kkkkkkkkkkkkkkkkkkk"); - await createObject({ + let uraOptionSelected = await findObject( whatsappId, contactId, - identifier: "ura", - value: data[1].id - }); + "ura" + ); - return data[1]; + uraOptionSelected = uraOptionSelected[4].split("|"); + + if (uraOptionSelected.length == 1) { + await createObject({ + whatsappId, + contactId, + identifier: "ura", + value: data[1].id, + history: `|${data[1].id}` + }); + + return data[1]; + } else if (uraOptionSelected.length > 1) { + const id = uraOptionSelected[uraOptionSelected.length - 1]; + + console.log(" ID FROM THE MENU/SUBMENU: ", id); + + const history = await historyUra(whatsappId, contactId, id); + + await createObject({ + whatsappId, + contactId, + identifier: "ura", + value: id, + history + }); + + let response: any = data.find((o: any) => o.id == id); + + return response; + } } } } @@ -1293,19 +1441,16 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { if (option) { let response: any = data.find((o: any) => o.idmaster == option.id); + console.log(" RESPONSE OPTION: ", response, " | OPTION: ", option); - console.log( - "RRRRRRRRRRRRRRRRRRRRRRRRRRRRR response: ", - response, - " | option: ", - option - ); + let history: any = await historyUra(whatsappId, contactId, response.id); await createObject({ whatsappId, contactId, identifier: "ura", - value: response.id + value: response.id, + history }); return response; @@ -1322,21 +1467,8 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { ); return data[1]; - } else { - console.log("INVALID SEARCH"); - - let response = await existSubMenu(); - if (response) return response; - - return { - value: data.find((o: any) => o.id == lastId[3])?.value - }; - - // return { - // value: `Você digitou uma opçao inválida!\n\n${ - // data.find((o: any) => o.id == lastId[3])?.value - // }\n\nDigite 0 para voltar ao menu ` - // }; + } else if (userTyped == "#") { + return await backUra(whatsappId, contactId, data); } } @@ -1349,18 +1481,29 @@ const menu = async (userTyped: string, whatsappId: any, contactId: any) => { } async function mainOptionsMenu(userTyped: any) { + let currentMenu = await findObject(whatsappId, contactId, "ura"); + + const menuValues = data + .filter((m: any) => m.idmaster == currentMenu[3]) + .map((m: any) => m.value); + let menuOption = data.find( - (o: any) => o.value.toLowerCase() == userTyped.toLowerCase() + (o: any) => + o.value.toLowerCase() == userTyped.toLowerCase() && + menuValues.includes(userTyped.toLowerCase()) ); - console.log("============> menuOption OPTION: ", menuOption); + if (menuOption) { let response = data.find((o: any) => o.idmaster == menuOption.id); if (response) { + let history = await historyUra(whatsappId, contactId, response.id); + await createObject({ whatsappId, contactId, identifier: "ura", - value: response.id + value: response.id, + history }); return response; @@ -1440,6 +1583,13 @@ const outOfService = async (number: string) => { objs.push({ type: "holiday", msg: holiday.msg }); } + // MESSAGE TO SATURDAY BUSINESS TIME + const businessTimeSaturday = await isOutBusinessTimeSaturday(number); + + if (businessTimeSaturday && businessTimeSaturday.set) { + objs.push({ type: "saturdayBusinessTime", msg: businessTimeSaturday.msg }); + } + // MESSAGES TO SATURDAY OR SUNDAY const weekend: any = await isWeekend(number); @@ -1466,8 +1616,119 @@ export { verifyMediaMessage, verifyContact, isValidMsg, - mediaTypeWhatsappOfficial + mediaTypeWhatsappOfficial, + botSendMessage }; +async function vcard(msg: any) { + let array: any[] = []; + let contact: any; + let obj: any[] = []; + + try { + const multi_vcard = msg?.vCards?.length === 0 ? false : true; + + if (multi_vcard) { + array = msg?.vCards; + contact = []; + } else { + array = msg.body.split("\n"); + contact = ""; + } + + for (let index = 0; index < array.length; index++) { + const v = array[index]; + const values = v.split(":"); + for (let ind = 0; ind < values.length; ind++) { + if (values[ind].indexOf("+") !== -1) { + obj.push({ number: values[ind] }); + } + if (values[ind].indexOf("FN") !== -1) { + if (multi_vcard) + contact.push({ name: values[ind + 1].split("\n")[0] }); + else contact = values[ind + 1]; + } + } + } + + for (const i in obj) { + let data: any = {}; + + if (multi_vcard) { + data = { + name: contact[i].name, + number: obj[i].number.replace(/\D/g, "") + }; + } else { + data = { + name: contact, + number: obj[i].number.replace(/\D/g, "") + }; + } + + const cont = await CreateContactService(data); + } + } catch (error) { + console.log(error); + } +} + +async function backUra(whatsappId: any, contactId: any, data: any) { + let uraOptionSelected = await findObject(whatsappId, contactId, "ura"); + + uraOptionSelected = uraOptionSelected[4].split("|").filter(Boolean); + + let id = uraOptionSelected[0]; + + let history = `|${uraOptionSelected[0]}`; + + if (uraOptionSelected.length > 1) { + const idRemove = uraOptionSelected[uraOptionSelected.length - 1]; + + history = await historyUra(whatsappId, contactId, idRemove, true); + + const lstIds = history.split("|").filter(Boolean); + + id = lstIds[lstIds.length - 1]; + } + + await createObject({ + whatsappId, + contactId, + identifier: "ura", + value: id, + history + }); + + let response: any = data.find((o: any) => o.id == id); + + return response; +} + +async function historyUra( + whatsappId: any, + contactId: any, + id: any, + remove?: boolean +) { + let uraOptionSelected = await findObject(whatsappId, contactId, "ura"); + let history = ""; + + console.log("SELECED OPTION uraOptionSelected: ", uraOptionSelected); + + if (remove) { + return uraOptionSelected[4]?.replace(`|${id}`, ""); + } + + if (uraOptionSelected && uraOptionSelected.length == 5) { + if (!uraOptionSelected[4]?.includes(`${id}`)) + history += `${uraOptionSelected[4]}|${id}`; + else history = `${uraOptionSelected[4]}`; + } else { + history = `|${id}`; + } + return history; +} + async function whatsappInfo(whatsappId: string | number) { return await Whatsapp.findByPk(whatsappId); } diff --git a/backend/src/services/WhatsappService/CreateWhatsAppService.ts b/backend/src/services/WhatsappService/CreateWhatsAppService.ts index 2693709..879bde4 100644 --- a/backend/src/services/WhatsappService/CreateWhatsAppService.ts +++ b/backend/src/services/WhatsappService/CreateWhatsAppService.ts @@ -16,6 +16,7 @@ interface Request { phoneNumberId?: string; wabaId?: string; isOfficial?: boolean; + number?: string; } interface Response { @@ -34,7 +35,8 @@ const CreateWhatsAppService = async ({ isDefault = false, isOfficial = false, phoneNumberId, - wabaId + wabaId, + number }: Request): Promise => { try { const schema = Yup.object().shape({ @@ -98,6 +100,7 @@ const CreateWhatsAppService = async ({ phoneNumberId, wabaId, isOfficial, + number, classification }, { include: ["queues"] } diff --git a/backend/src/services/WhatsappService/ListWhatsAppsForQueueService.ts b/backend/src/services/WhatsappService/ListWhatsAppsForQueueService.ts index 1f16647..836ef9e 100644 --- a/backend/src/services/WhatsappService/ListWhatsAppsForQueueService.ts +++ b/backend/src/services/WhatsappService/ListWhatsAppsForQueueService.ts @@ -6,17 +6,30 @@ const { QueryTypes } = require("sequelize"); const sequelize = new Sequelize(dbConfig); -const ListWhatsAppsForQueueService = async (queueId: number | string): Promise => { - const distinctWhatsapps = await sequelize.query( - `SELECT w.id, w.number, w.status, wq.whatsappId, wq.queueId -FROM Whatsapps w -JOIN WhatsappQueues wq ON w.id = wq.whatsappId AND wq.queueId = ${queueId} -GROUP BY w.number;`, - { type: QueryTypes.SELECT } - ); +const ListWhatsAppsForQueueService = async ( + queueId: number | string, + status?: string +): Promise => { + let distinctWhatsapps: any; + + if (status) { + distinctWhatsapps = await sequelize.query( + `SELECT w.id, w.number, w.status, wq.whatsappId, wq.queueId FROM Whatsapps w + JOIN WhatsappQueues wq ON w.id = wq.whatsappId AND wq.queueId = ${queueId} AND w.status = '${status}' + AND phoneNumberId = false + GROUP BY w.number;`, + { type: QueryTypes.SELECT } + ); + } else { + distinctWhatsapps = await sequelize.query( + `SELECT w.id, w.number, w.status, wq.whatsappId, wq.queueId FROM Whatsapps w + JOIN WhatsappQueues wq ON w.id = wq.whatsappId AND wq.queueId = ${queueId} + GROUP BY w.number;`, + { type: QueryTypes.SELECT } + ); + } return distinctWhatsapps; }; export default ListWhatsAppsForQueueService; - \ No newline at end of file diff --git a/backend/src/services/WhatsappService/ListWhatsAppsNumber.ts b/backend/src/services/WhatsappService/ListWhatsAppsNumber.ts index 8bb9758..928b8d5 100644 --- a/backend/src/services/WhatsappService/ListWhatsAppsNumber.ts +++ b/backend/src/services/WhatsappService/ListWhatsAppsNumber.ts @@ -9,12 +9,12 @@ const ListWhatsAppsNumber = async ( let whatsapps: any = []; if (whatsapp) { - if (status) { + if (status) { whatsapps = await Whatsapp.findAll({ raw: true, where: { number: whatsapp.number, status: status, }, attributes: ["id", "number", "status", "isDefault", "url", 'isOfficial'] - }); + }); } else { whatsapps = await Whatsapp.findAll({ raw: true, diff --git a/backend/src/services/WhatsappService/UpdateWhatsAppService.ts b/backend/src/services/WhatsappService/UpdateWhatsAppService.ts index 982407b..3487d15 100644 --- a/backend/src/services/WhatsappService/UpdateWhatsAppService.ts +++ b/backend/src/services/WhatsappService/UpdateWhatsAppService.ts @@ -22,6 +22,7 @@ interface WhatsappData { greetingMessage?: string; farewellMessage?: string; queueIds?: number[]; + number?:string; } interface Request { @@ -52,6 +53,7 @@ const UpdateWhatsAppService = async ({ phoneNumberId, wabaId, isOfficial, + number, url, urlApi, session, @@ -116,6 +118,7 @@ const UpdateWhatsAppService = async ({ isOfficial, phoneNumberId, wabaId, + number, classification }); diff --git a/frontend/src/components/Audio/index.jsx b/frontend/src/components/Audio/index.jsx new file mode 100644 index 0000000..cc0c7cc --- /dev/null +++ b/frontend/src/components/Audio/index.jsx @@ -0,0 +1,62 @@ +import { Button } from "@material-ui/core"; +import React, { useRef } from "react"; +import { useEffect } from "react"; +import { useState } from "react"; + +const LS_NAME = 'audioMessageRate'; + +export default function({url}) { + const audioRef = useRef(null); + const [audioRate, setAudioRate] = useState( parseFloat(localStorage.getItem(LS_NAME) || "1") ); + const [showButtonRate, setShowButtonRate] = useState(false); + + useEffect(() => { + audioRef.current.playbackRate = audioRate; + localStorage.setItem(LS_NAME, audioRate); + }, [audioRate]); + + useEffect(() => { + audioRef.current.onplaying = () => { + setShowButtonRate(true); + }; + audioRef.current.onpause = () => { + setShowButtonRate(false); + }; + audioRef.current.onended = () => { + setShowButtonRate(false); + }; + }, []); + + const toogleRate = () => { + let newRate = null; + + switch(audioRate) { + case 0.5: + newRate = 1; + break; + case 1: + newRate = 1.5; + break; + case 1.5: + newRate = 2; + break; + case 2: + newRate = 0.5; + break; + default: + newRate = 1; + break; + } + + setAudioRate(newRate); + }; + + return ( + <> + + {showButtonRate && } + + ); +} \ No newline at end of file diff --git a/frontend/src/components/ConfigModal/index.js b/frontend/src/components/ConfigModal/index.js index 40099ea..cf41fa3 100644 --- a/frontend/src/components/ConfigModal/index.js +++ b/frontend/src/components/ConfigModal/index.js @@ -77,8 +77,16 @@ const ConfigModal = ({ open, onClose, change }) => { const initialState = { startTimeBus: new Date(), endTimeBus: new Date(), + + startTimeBusSaturday: new Date(), + endTimeBusSaturday: new Date(), + messageBus: '', + messageBusSaturday: '', + businessTimeEnable: false, + businessTimeEnableSaturday: false, + ticketTimeExpiration: new Date(), ticketExpirationMsg: '', ticketExpirationEnable: false, @@ -115,13 +123,16 @@ const ConfigModal = ({ open, onClose, change }) => { 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 saturdayBusinessTime = data.config.find((c) => c.key === "saturdayBusinessTime") + 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") @@ -134,6 +145,11 @@ const ConfigModal = ({ open, onClose, change }) => { messageBus: outBusinessHours.message, businessTimeEnable: outBusinessHours.value === 'enabled' ? true : false, + startTimeBusSaturday: saturdayBusinessTime.startTime, + endTimeBusSaturday: saturdayBusinessTime.endTime, + messageBusSaturday: saturdayBusinessTime.message, + businessTimeEnableSaturday: saturdayBusinessTime.value === 'enabled' ? true : false, + ticketTimeExpiration: ticketExpiration.startTime, ticketExpirationMsg: ticketExpiration.message, ticketExpirationEnable: ticketExpiration.value === 'enabled' ? true : false, @@ -165,6 +181,14 @@ const ConfigModal = ({ open, onClose, change }) => { message: values.messageBus, value: values.businessTimeEnable ? 'enabled' : 'disabled' }, + + saturdayBusinessTime: { + startTime: values.startTimeBusSaturday, + endTime: values.endTimeBusSaturday, + message: values.messageBusSaturday, + value: values.businessTimeEnableSaturday ? 'enabled' : 'disabled' + }, + ticketExpiration: { startTime: values.ticketTimeExpiration, message: values.ticketExpirationMsg, @@ -205,7 +229,7 @@ const ConfigModal = ({ open, onClose, change }) => { onClose() // setConfig(initialState) } - + return (
{
+
+ {/* SABADO INICIO */} +
+ + {' '} + + + + } + label={'Ativar/Desativar'} /> +
+ +
+ +
+ {/* SABADO FIM */}
diff --git a/frontend/src/components/ContactCreateTicketModal/index.js b/frontend/src/components/ContactCreateTicketModal/index.js index 3681c6c..3ba9281 100644 --- a/frontend/src/components/ContactCreateTicketModal/index.js +++ b/frontend/src/components/ContactCreateTicketModal/index.js @@ -157,7 +157,7 @@ const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => { const { data } = await api.get("/whatsapp/official/matchQueue", { params: { userId: user.id, queueId: selectedQueue }, }) - console.log('WHATSAPP DATA: ', data) + // console.log('WHATSAPP DATA: ', data) setWhatsQueue(data) diff --git a/frontend/src/components/ContactModal/index.js b/frontend/src/components/ContactModal/index.js index d67f01c..95e16e9 100644 --- a/frontend/src/components/ContactModal/index.js +++ b/frontend/src/components/ContactModal/index.js @@ -1,26 +1,26 @@ -import React, { useState, useEffect, useRef } from "react"; +import React, { useState, useEffect, useRef } from "react" -import * as Yup from "yup"; -import { Formik, FieldArray, Form, Field } from "formik"; -import { toast } from "react-toastify"; +import * as Yup from "yup" +import { Formik, FieldArray, Form, Field } from "formik" +import { toast } from "react-toastify" -import { makeStyles } from "@material-ui/core/styles"; -import { green } from "@material-ui/core/colors"; -import Button from "@material-ui/core/Button"; -import TextField from "@material-ui/core/TextField"; -import Dialog from "@material-ui/core/Dialog"; -import DialogActions from "@material-ui/core/DialogActions"; -import DialogContent from "@material-ui/core/DialogContent"; -import DialogTitle from "@material-ui/core/DialogTitle"; -import Typography from "@material-ui/core/Typography"; -import IconButton from "@material-ui/core/IconButton"; -import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline"; -import CircularProgress from "@material-ui/core/CircularProgress"; +import { makeStyles } from "@material-ui/core/styles" +import { green } from "@material-ui/core/colors" +import Button from "@material-ui/core/Button" +import TextField from "@material-ui/core/TextField" +import Dialog from "@material-ui/core/Dialog" +import DialogActions from "@material-ui/core/DialogActions" +import DialogContent from "@material-ui/core/DialogContent" +import DialogTitle from "@material-ui/core/DialogTitle" +import Typography from "@material-ui/core/Typography" +import IconButton from "@material-ui/core/IconButton" +import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline" +import CircularProgress from "@material-ui/core/CircularProgress" -import { i18n } from "../../translate/i18n"; +import { i18n } from "../../translate/i18n" -import api from "../../services/api"; -import toastError from "../../errors/toastError"; +import api from "../../services/api" +import toastError from "../../errors/toastError" const useStyles = makeStyles(theme => ({ root: { @@ -50,7 +50,7 @@ const useStyles = makeStyles(theme => ({ marginTop: -12, marginLeft: -12, }, -})); +})) const ContactSchema = Yup.object().shape({ name: Yup.string() @@ -60,75 +60,77 @@ const ContactSchema = Yup.object().shape({ number: Yup.string().min(8, "Too Short!").max(50, "Too Long!"), email: Yup.string().min(2, "Too Short!") - .max(50, "Too Long!"), + .max(50, "Too Long!"), // email: Yup.string().email("Invalid email"), -}); +}) const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => { - const classes = useStyles(); - const isMounted = useRef(true); + const classes = useStyles() + const isMounted = useRef(true) const initialState = { name: "", number: "", email: "", useDialogflow: true, - }; + } - const [contact, setContact] = useState(initialState); + const [contact, setContact] = useState(initialState) + const [isSaving, setSaving] = useState(false) useEffect(() => { return () => { - isMounted.current = false; - }; - }, []); + isMounted.current = false + } + }, []) useEffect(() => { const fetchContact = async () => { if (initialValues) { setContact(prevState => { - return { ...prevState, ...initialValues }; - }); + return { ...prevState, ...initialValues } + }) } - if (!contactId) return; + if (!contactId) return try { - const { data } = await api.get(`/contacts/${contactId}`); + const { data } = await api.get(`/contacts/${contactId}`) if (isMounted.current) { - setContact(data); + setContact(data) } } catch (err) { - toastError(err); + toastError(err) } - }; + } - fetchContact(); - }, [contactId, open, initialValues]); + fetchContact() + }, [contactId, open, initialValues]) const handleClose = () => { - onClose(); - setContact(initialState); - }; + onClose() + setContact(initialState) + } const handleSaveContact = async values => { - try { + try { if (contactId) { - await api.put(`/contacts/${contactId}`, values); - handleClose(); + await api.put(`/contacts/${contactId}`, values) + handleClose() } else { - const { data } = await api.post("/contacts", values); + const { data } = await api.post("/contacts", values) if (onSave) { - onSave(data); + onSave(data) } - handleClose(); + handleClose() } - toast.success(i18n.t("contactModal.success")); + toast.success(i18n.t("contactModal.success")) } catch (err) { - toastError(err); + toastError(err) } - }; + setSaving(false) + } return (
@@ -143,10 +145,11 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => { enableReinitialize={true} validationSchema={ContactSchema} onSubmit={(values, actions) => { + setSaving(true) setTimeout(() => { - handleSaveContact(values); - actions.setSubmitting(false); - }, 400); + handleSaveContact(values) + actions.setSubmitting(false) + }, 400) }} > {({ values, errors, touched, isSubmitting }) => ( @@ -256,14 +259,14 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => {
- ); -}; + ) +} -export default ContactModal; +export default ContactModal diff --git a/frontend/src/components/LocationPreview/index.js b/frontend/src/components/LocationPreview/index.js new file mode 100644 index 0000000..9187faf --- /dev/null +++ b/frontend/src/components/LocationPreview/index.js @@ -0,0 +1,53 @@ +import React, { useEffect } from 'react'; +import toastError from "../../errors/toastError"; + +import Typography from "@material-ui/core/Typography"; +import Grid from "@material-ui/core/Grid"; + +import { Button, Divider, } from "@material-ui/core"; + +const LocationPreview = ({ image, link, description }) => { + useEffect(() => {}, [image, link, description]); + + const handleLocation = async() => { + try { + window.open(link); + } catch (err) { + toastError(err); + } + } + + return ( + <> +
+
+
+ +
+ { description && ( +
+ +
') }}>
+
+
+ )} +
+
+ + +
+
+
+ + ); + +}; + +export default LocationPreview; \ No newline at end of file diff --git a/frontend/src/components/MarkdownWrapper/index.js b/frontend/src/components/MarkdownWrapper/index.js index 64764b2..3d2c01b 100644 --- a/frontend/src/components/MarkdownWrapper/index.js +++ b/frontend/src/components/MarkdownWrapper/index.js @@ -1,5 +1,5 @@ -import React from "react"; -import Markdown from "markdown-to-jsx"; +import React from "react" +import Markdown from "markdown-to-jsx" const elements = [ "a", @@ -139,25 +139,32 @@ const elements = [ "svg", "text", "tspan", -]; +] -const allowedElements = ["a", "b", "strong", "em", "u", "code", "del"]; +const allowedElements = ["a", "b", "strong", "em", "u", "code", "del"] const CustomLink = ({ children, ...props }) => ( {children} -); +) const MarkdownWrapper = ({ children }) => { - const boldRegex = /\*(.*?)\*/g; - const tildaRegex = /~(.*?)~/g; + const boldRegex = /\*(.*?)\*/g + const tildaRegex = /~(.*?)~/g + + if (children && children.includes('BEGIN:VCARD')) + //children = "Diga olá ao seu novo contato clicando em *conversar*!"; + children = null + + if (children && children.includes('data:image/')) + children = null if (children && boldRegex.test(children)) { - children = children.replace(boldRegex, "**$1**"); + children = children.replace(boldRegex, "**$1**") } if (children && tildaRegex.test(children)) { - children = children.replace(tildaRegex, "~~$1~~"); + children = children.replace(tildaRegex, "~~$1~~") } const options = React.useMemo(() => { @@ -167,20 +174,20 @@ const MarkdownWrapper = ({ children }) => { overrides: { a: { component: CustomLink }, }, - }; + } elements.forEach(element => { if (!allowedElements.includes(element)) { - markdownOptions.overrides[element] = el => el.children || null; + markdownOptions.overrides[element] = el => el.children || null } - }); + }) - return markdownOptions; - }, []); + return markdownOptions + }, []) - if (!children) return null; + if (!children) return null - return {children}; -}; + return {children} +} -export default MarkdownWrapper; +export default MarkdownWrapper \ No newline at end of file diff --git a/frontend/src/components/MessageInput/index.js b/frontend/src/components/MessageInput/index.js index fb4370d..134756e 100644 --- a/frontend/src/components/MessageInput/index.js +++ b/frontend/src/components/MessageInput/index.js @@ -311,9 +311,7 @@ const MessageInput = ({ ticketStatus }) => { } const handleSendMessage = async (templateParams = null) => { - - console.log('templateParams: ', templateParams, ' | inputMessage: ', inputMessage) - + if (inputMessage.trim() === "") return setLoading(true) @@ -323,9 +321,7 @@ const MessageInput = ({ ticketStatus }) => { if (templateParams) { for (let key in templateParams) { - if (templateParams.hasOwnProperty(key)) { - // let value = templateParams[key] - // console.log('key: ', key, ' | ', 'VALUE: ', value) + if (templateParams.hasOwnProperty(key)) { if (key === '_reactName') { templateParams = null @@ -348,7 +344,7 @@ const MessageInput = ({ ticketStatus }) => { } - try { + try { const { data } = await api.post(`/messages/${ticketId}`, message) setParams(null) @@ -371,7 +367,11 @@ const MessageInput = ({ ticketStatus }) => { if (!params) return - const body_params = params.find(p => p?.type === 'BODY') + const body_params = params?.find(p => p?.type === 'BODY') + + console.log('------------> body_params: ', body_params) + + if(!body_params) return let { text } = body_params diff --git a/frontend/src/components/MessagesList/index.js b/frontend/src/components/MessagesList/index.js index 236601a..5a99c79 100644 --- a/frontend/src/components/MessagesList/index.js +++ b/frontend/src/components/MessagesList/index.js @@ -1,18 +1,18 @@ -import React, { useContext, useState, useEffect, useReducer, useRef } from "react"; +import React, { useContext, useState, useEffect, useReducer, useRef } from "react" -import { isSameDay, parseISO, format } from "date-fns"; -import openSocket from "socket.io-client"; -import clsx from "clsx"; -import { AuthContext } from "../../context/Auth/AuthContext"; +import { isSameDay, parseISO, format } from "date-fns" +import openSocket from "socket.io-client" +import clsx from "clsx" +import { AuthContext } from "../../context/Auth/AuthContext" -import { green } from "@material-ui/core/colors"; +import { green } from "@material-ui/core/colors" import { Button, CircularProgress, Divider, IconButton, makeStyles, -} from "@material-ui/core"; +} from "@material-ui/core" import { AccessTime, Block, @@ -20,15 +20,20 @@ import { DoneAll, ExpandMore, GetApp, -} from "@material-ui/icons"; +} from "@material-ui/icons" -import MarkdownWrapper from "../MarkdownWrapper"; -import ModalImageCors from "../ModalImageCors"; -import MessageOptionsMenu from "../MessageOptionsMenu"; -import whatsBackground from "../../assets/wa-background.png"; +import MarkdownWrapper from "../MarkdownWrapper" +import VcardPreview from "../VcardPreview" +import LocationPreview from "../LocationPreview" +import Audio from "../Audio" -import api from "../../services/api"; -import toastError from "../../errors/toastError"; + +import ModalImageCors from "../ModalImageCors" +import MessageOptionsMenu from "../MessageOptionsMenu" +import whatsBackground from "../../assets/wa-background.png" + +import api from "../../services/api" +import toastError from "../../errors/toastError" const useStyles = makeStyles((theme) => ({ messagesListWrapper: { @@ -257,78 +262,78 @@ const useStyles = makeStyles((theme) => ({ backgroundColor: "inherit", padding: 10, }, -})); +})) const reducer = (state, action) => { if (action.type === "LOAD_MESSAGES") { - const messages = action.payload; - const newMessages = []; + const messages = action.payload + const newMessages = [] messages.forEach((message) => { - const messageIndex = state.findIndex((m) => m.id === message.id); + const messageIndex = state.findIndex((m) => m.id === message.id) if (messageIndex !== -1) { - state[messageIndex] = message; + state[messageIndex] = message } else { - newMessages.push(message); + newMessages.push(message) } - }); + }) - return [...newMessages, ...state]; + return [...newMessages, ...state] } if (action.type === "ADD_MESSAGE") { - const newMessage = action.payload; - const messageIndex = state.findIndex((m) => m.id === newMessage.id); + const newMessage = action.payload + const messageIndex = state.findIndex((m) => m.id === newMessage.id) if (messageIndex !== -1) { - state[messageIndex] = newMessage; + state[messageIndex] = newMessage } else { - state.push(newMessage); + state.push(newMessage) } - return [...state]; + return [...state] } if (action.type === "UPDATE_MESSAGE") { - const messageToUpdate = action.payload; - const messageIndex = state.findIndex((m) => m.id === messageToUpdate.id); + const messageToUpdate = action.payload + const messageIndex = state.findIndex((m) => m.id === messageToUpdate.id) if (messageIndex !== -1) { - state[messageIndex] = messageToUpdate; + state[messageIndex] = messageToUpdate } - return [...state]; + return [...state] } if (action.type === "RESET") { - return []; + return [] } -}; +} const MessagesList = ({ ticketId, isGroup }) => { - const classes = useStyles(); + const classes = useStyles() - const [messagesList, dispatch] = useReducer(reducer, []); - const [pageNumber, setPageNumber] = useState(1); - const [hasMore, setHasMore] = useState(false); - const [loading, setLoading] = useState(false); - const lastMessageRef = useRef(); + const [messagesList, dispatch] = useReducer(reducer, []) + const [pageNumber, setPageNumber] = useState(1) + const [hasMore, setHasMore] = useState(false) + const [loading, setLoading] = useState(false) + const lastMessageRef = useRef() - const [selectedMessage, setSelectedMessage] = useState({}); - const [anchorEl, setAnchorEl] = useState(null); - const messageOptionsMenuOpen = Boolean(anchorEl); - const currentTicketId = useRef(ticketId); + const [selectedMessage, setSelectedMessage] = useState({}) + const [anchorEl, setAnchorEl] = useState(null) + const messageOptionsMenuOpen = Boolean(anchorEl) + const currentTicketId = useRef(ticketId) const [sendSeen, setSendSeen] = useState(false) - const { user } = useContext(AuthContext); + const { user } = useContext(AuthContext) useEffect(() => { - dispatch({ type: "RESET" }); - setPageNumber(1); + dispatch({ type: "RESET" }) + setPageNumber(1) - currentTicketId.current = ticketId; - }, [ticketId]); + currentTicketId.current = ticketId + }, [ticketId]) useEffect(() => { @@ -354,7 +359,7 @@ const MessagesList = ({ ticketId, isGroup }) => { try { const { data } = await api.get("/messages/" + ticketId, { params: { pageNumber }, - }); + }) setSendSeen(false) @@ -377,138 +382,239 @@ const MessagesList = ({ ticketId, isGroup }) => { } } catch (err) { - setLoading(false); - toastError(err); + setLoading(false) + toastError(err) } - }; - sendSeenMessage(); - }, 500); + } + sendSeenMessage() + }, 500) return () => { - clearTimeout(delayDebounceFn); - }; + clearTimeout(delayDebounceFn) + } - }, [sendSeen, pageNumber, ticketId, user.id]); + }, [sendSeen, pageNumber, ticketId, user.id]) useEffect(() => { - setLoading(true); + setLoading(true) const delayDebounceFn = setTimeout(() => { const fetchMessages = async () => { try { const { data } = await api.get("/messages/" + ticketId, { params: { pageNumber }, - }); + }) if (currentTicketId.current === ticketId) { - dispatch({ type: "LOAD_MESSAGES", payload: data.messages }); - setHasMore(data.hasMore); - setLoading(false); + dispatch({ type: "LOAD_MESSAGES", payload: data.messages }) + setHasMore(data.hasMore) + setLoading(false) } if (pageNumber === 1 && data.messages.length > 1) { - scrollToBottom(); + scrollToBottom() } } catch (err) { - setLoading(false); - toastError(err); + setLoading(false) + toastError(err) } - }; - fetchMessages(); - }, 500); + } + fetchMessages() + }, 500) return () => { - clearTimeout(delayDebounceFn); - }; - }, [pageNumber, ticketId]); + clearTimeout(delayDebounceFn) + } + }, [pageNumber, ticketId]) useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL); + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) - socket.on("connect", () => socket.emit("joinChatBox", ticketId)); + socket.on("connect", () => socket.emit("joinChatBox", ticketId)) socket.on("appMessage", (data) => { if (data.action === "create") { - dispatch({ type: "ADD_MESSAGE", payload: data.message }); + dispatch({ type: "ADD_MESSAGE", payload: data.message }) - scrollToBottom(); + scrollToBottom() } if (data.action === "update") { - dispatch({ type: "UPDATE_MESSAGE", payload: data.message }); + dispatch({ type: "UPDATE_MESSAGE", payload: data.message }) } - }); + }) return () => { - socket.disconnect(); - }; - }, [ticketId]); + socket.disconnect() + } + }, [ticketId]) const loadMore = () => { - setPageNumber((prevPageNumber) => prevPageNumber + 1); - }; + setPageNumber((prevPageNumber) => prevPageNumber + 1) + } const scrollToBottom = () => { if (lastMessageRef.current) { setSendSeen(true) - lastMessageRef.current.scrollIntoView({}); + lastMessageRef.current.scrollIntoView({}) } - }; + } const handleScroll = (e) => { - if (!hasMore) return; - const { scrollTop } = e.currentTarget; + if (!hasMore) return + const { scrollTop } = e.currentTarget if (scrollTop === 0) { - document.getElementById("messagesList").scrollTop = 1; + document.getElementById("messagesList").scrollTop = 1 } if (loading) { - return; + return } if (scrollTop < 50) { - loadMore(); + loadMore() } - }; + } const handleOpenMessageOptionsMenu = (e, message) => { - setAnchorEl(e.currentTarget); - setSelectedMessage(message); - }; + setAnchorEl(e.currentTarget) + setSelectedMessage(message) + } const handleCloseMessageOptionsMenu = (e) => { - setAnchorEl(null); - }; + setAnchorEl(null) + } + + // const checkMessageMedia = (message) => { + // if (message.mediaType === "image") { + // return ; + // } + // if (message.mediaType === "audio") { + + // return ( + // + // ); + // } + + // if (message.mediaType === "video") { + // return ( + //