diff --git a/backend/src/controllers/ContactController.ts b/backend/src/controllers/ContactController.ts index 1b194a1..af0ad0a 100644 --- a/backend/src/controllers/ContactController.ts +++ b/backend/src/controllers/ContactController.ts @@ -124,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/MessageController.ts b/backend/src/controllers/MessageController.ts index 2016bca..9555e14 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; @@ -97,7 +98,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) || 'pt_BR' + const { language }: any = + params?.find((p: any) => p?.language) || "pt_BR"; const { template_name } = name; diff --git a/backend/src/controllers/QueueController.ts b/backend/src/controllers/QueueController.ts index 68b9854..493b951 100644 --- a/backend/src/controllers/QueueController.ts +++ b/backend/src/controllers/QueueController.ts @@ -5,14 +5,12 @@ 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"; export const index = async (req: Request, res: Response): Promise => { - const queues = await ListQueuesService(); + const queues = await ListQueuesService(); return res.status(200).json(queues); }; @@ -125,7 +123,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 +162,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/TicketController.ts b/backend/src/controllers/TicketController.ts index cc7f7e8..2f62ba3 100644 --- a/backend/src/controllers/TicketController.ts +++ b/backend/src/controllers/TicketController.ts @@ -75,8 +75,9 @@ 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" -export const index = async (req: Request, res: Response): Promise => { +export const index = async (req: Request, res: Response): Promise => { const { pageNumber, status, @@ -93,9 +94,9 @@ export const index = async (req: Request, res: Response): Promise => { let queueIds: number[] = []; - if (queueIdsStringified) { + if (queueIdsStringified && queueIdsStringified.trim().length > 0) { queueIds = JSON.parse(queueIdsStringified); - } + } const { tickets, count, hasMore } = await ListTicketsService({ searchParam, @@ -109,7 +110,7 @@ export const index = async (req: Request, res: Response): Promise => { unlimited, searchParamContent }); - + return res.status(200).json({ tickets, count, hasMore }); }; diff --git a/backend/src/controllers/UserController.ts b/backend/src/controllers/UserController.ts index 88d463a..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; @@ -110,8 +111,7 @@ export const all = async (req: Request, res: Response): Promise => { getSettingValue("queueTransferByWhatsappScope")?.value ); - if (getSettingValue("queueTransferByWhatsappScope")?.value == "enabled") { - + if (getSettingValue("queueTransferByWhatsappScope")?.value == "enabled") { if (!userId) return res.json({ users: [], queues: [] }); const obj = await ListUserByWhatsappQueuesService( @@ -122,7 +122,7 @@ export const all = async (req: Request, res: Response): Promise => { const usersByWhatsqueue = obj.users; const queues = obj.queues; - let userIds = usersByWhatsqueue.map((w: any) => w.userId); + let userIds = usersByWhatsqueue.map((w: any) => w.userId); const users = await ListUser({ userIds @@ -167,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", @@ -270,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(); @@ -323,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 c71d883..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; @@ -229,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]; @@ -248,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, @@ -255,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; @@ -382,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(); @@ -421,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) { @@ -440,6 +479,7 @@ export const update = async ( } else if (!isOfficial) { whatsappData.phoneNumberId = ""; whatsappData.wabaId = ""; + whatsappData.number = ""; } const { whatsapp, oldDefaultWhatsapp } = await UpdateWhatsAppService({ @@ -454,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(); @@ -491,6 +541,8 @@ export const remove = async ( }); } + await del(`whatsapp:${whatsappId}`); + let whats = await ListWhatsAppsNumber(whatsappId); // Remove tickets business hours config @@ -572,5 +624,5 @@ const checkWhatsAppData = ({ 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/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/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/RedisClient.ts b/backend/src/helpers/RedisClient.ts index 93c9e0a..6fd90b2 100644 --- a/backend/src/helpers/RedisClient.ts +++ b/backend/src/helpers/RedisClient.ts @@ -6,28 +6,65 @@ type WhatsappData = { contactId: string; identifier: string; value?: string; + history?: string; +}; + +type getData = { + key: string; + value?: string; + parse?: boolean; }; export async function set(key: string, value: string | object) { - await redis.set(key, JSON.stringify(value)); -} - -export async function get(key: string) { - const value: any = await redis.get(key); - return JSON.parse(value); -} - -export async function clearAllKeys() { - // Retrieve all keys matching the pattern '*' - const keys = await redis.keys("user:*"); - - // If there are keys, delete them - if (keys.length > 0) { - console.log('keys: ', keys) - await redis.del(...keys); + if (typeof value == "object") await redis.set(key, JSON.stringify(value)); + else { + await redis.set(key, value); } } +export async function getSimple(key: string) { + const value: any = await redis.get(key); + 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, @@ -54,7 +91,7 @@ export async function findByContain( results.push(obj); } } - } + } return results; } @@ -62,7 +99,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( @@ -74,7 +112,9 @@ export async function createObject({ "identifier", identifier, "value", - value + value, + "history", + history ); await redis.expire(key, 300); @@ -102,7 +142,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/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/server.ts b/backend/src/server.ts index 6aa0b13..16c065a 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -23,7 +23,11 @@ import fs from "fs"; import dir from "path"; import { getSettingValue } from "./helpers/WhaticketSettings"; import loadSettings from "./helpers/LoadSettings"; -import { clearAllKeys, set } from "./helpers/RedisClient"; +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}`); @@ -39,32 +43,59 @@ const server = app.listen(process.env.PORT, () => { initIO(server); // StartAllWhatsAppsSessions(); -gracefulShutdown(server); +gracefulShutdown(server); (async () => { console.log("os.tmpdir(): ", os.tmpdir()); - await clearAllKeys(); + 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/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/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/ShowTicketReport.ts b/backend/src/services/TicketServices/ShowTicketReport.ts index c60156d..8e75349 100644 --- a/backend/src/services/TicketServices/ShowTicketReport.ts +++ b/backend/src/services/TicketServices/ShowTicketReport.ts @@ -7,14 +7,17 @@ import Queue from "../../models/Queue"; import Message from "../../models/Message"; import { userInfo } from "os"; -import { Op, where } from "sequelize"; +import { Op, QueryTypes, where } from "sequelize"; 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" interface Request { userId: string | number; @@ -41,43 +44,41 @@ const ShowTicketReport = async ({ queueId }: Request): Promise => { let where_clause: any = {}; - // let where_clause_msg: any = {}; + let query = ""; if (userId !== "0") { where_clause.userid = userId; + query = `AND t.userId = ${userId}`; } - if (createdOrUpdated === "updated") { - where_clause = { - ...where_clause, - updatedAt: { - [Op.gte]: startDate + " 00:00:00.000000", - [Op.lte]: endDate + " 23:59:59.999999" - } - }; - } - - if (createdOrUpdated === "created") { - where_clause = { - ...where_clause, - createdAt: { - [Op.gte]: startDate + " 00:00:00.000000", - [Op.lte]: endDate + " 23:59:59.999999" - } - }; - } - - let { userid, ...where_clause_msg } = where_clause; - if (queueId) { where_clause.queueId = queueId; + query = `AND t.queueId = ${queueId}`; } const limit = 40; const offset = limit * (+pageNumber - 1); + const createdAtOrUpdatedAt = + createdOrUpdated == "created" ? "createdAt" : "updatedAt"; + + const _ticketsId = await sequelize.query( + `SELECT t.id + FROM Tickets t + INNER JOIN ( + SELECT DISTINCT ticketId + FROM Messages + WHERE ${createdAtOrUpdatedAt} >= '${startDate} 00:00:00' AND ${createdAtOrUpdatedAt} <= '${endDate} 23:59:59' + ) AS m ON m.ticketId = t.id ${query};`, + { type: QueryTypes.SELECT } + ); + + console.log('QUERY: ', query) + const { count, rows: tickets } = await Ticket.findAndCountAll({ - where: where_clause, + where: { + id: { [Op.in]: _ticketsId.map((t: any) => t.id) } + }, limit, offset, @@ -108,7 +109,6 @@ const ShowTicketReport = async ({ model: Message, required: true, separate: true, - where: where_clause_msg , attributes: [ "body", @@ -143,10 +143,10 @@ const ShowTicketReport = async ({ model: Whatsapp, attributes: ["name"] } - ], + ], order: [["updatedAt", "DESC"]] }); - + const hasMore = count > offset + tickets.length; if (!tickets) { diff --git a/backend/src/services/WbotServices/CheckIsValidContact.ts b/backend/src/services/WbotServices/CheckIsValidContact.ts index 4596f67..cf9afec 100644 --- a/backend/src/services/WbotServices/CheckIsValidContact.ts +++ b/backend/src/services/WbotServices/CheckIsValidContact.ts @@ -1,27 +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, ignoreThrow?:boolean): 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}` + }); - if(ignoreThrow) return isValidNumber?.data?.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"); @@ -29,10 +62,6 @@ const CheckIsValidContact = async (number: string, ignoreThrow?:boolean): Promis 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 bb1c57e..d5da2bf 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -92,13 +92,14 @@ import { createObject, findByContain, findObject, - get + get, + getSimple } from "../../helpers/RedisClient"; 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 CreateContactService from "../ContactServices/CreateContactService"; var lst: any[] = getWhatsappIds(); @@ -175,9 +176,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]; @@ -280,18 +286,33 @@ 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 }); await CreateMessageService({ messageData }); @@ -318,13 +339,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) { @@ -348,13 +370,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); @@ -505,9 +528,7 @@ const transferTicket = async ( ticket: Ticket, sendGreetingMessage?: boolean ) => { - const botInfo = await BotIsOnQueue("botqueue"); - - console.log("kkkkkkkkkkkkkkkkkkkkk queueName: ", queueName); + const botInfo = await BotIsOnQueue("botqueue"); const queuesWhatsGreetingMessage = await queuesOutBot( wbot, @@ -610,7 +631,7 @@ const handleMessage = async ( return; } - } + } if (!isValidMsg(msg)) { return; @@ -780,33 +801,33 @@ const handleMessage = async ( await verifyQueue(wbot, msg, ticket, contact); } - if (msg.type === "vcard") { - try { - const array = msg.body.split("\n"); - const obj = []; - let 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) { - contact = values[ind + 1]; - } + if (msg.type === "vcard") { + try { + const array = msg.body.split("\n"); + const obj = []; + let 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) { + contact = values[ind + 1]; } } - for await (const ob of obj) { - const cont = await CreateContactService({ - name: contact, - number: ob.number.replace(/\D/g, "") - }); - } - } catch (error) { - console.log(error); } + for await (const ob of obj) { + const cont = await CreateContactService({ + name: contact, + number: ob.number.replace(/\D/g, "") + }); + } + } catch (error) { + console.log(error); } + } const botInfo = await BotIsOnQueue("botqueue"); @@ -820,7 +841,7 @@ const handleMessage = async ( ticket.status == "pending" || ticket.status == "queueChoice") ) { - const filteredUsers = await findByContain("user:*", "name", msg?.body); + const filteredUsers = await findByContain("user:*", "name", msg?.body); if (filteredUsers && filteredUsers.length > 0) { if (botInfo.isOnQueue) { @@ -964,7 +985,7 @@ const handleMessage = async ( 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]); @@ -973,7 +994,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}` }); } @@ -983,7 +1005,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 ) { @@ -993,23 +1015,49 @@ 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" + ); + + uraOptionSelected = uraOptionSelected[4].split("|"); - return data[1]; + 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; + } } } } @@ -1017,19 +1065,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; @@ -1038,26 +1083,42 @@ 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}` }); return data[1]; - } else { - console.log("INVALID SEARCH"); + } else if (userTyped == "#") { + let uraOptionSelected = await findObject(whatsappId, contactId, "ura"); - let response = await existSubMenu(); - if (response) return response; + uraOptionSelected = uraOptionSelected[4].split("|").filter(Boolean); - return { - value: data.find((o: any) => o.id == lastId[3])?.value - }; + let id = uraOptionSelected[0]; - // 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 ` - // }; - } + 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; + } } function existSubMenu() { @@ -1069,18 +1130,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; @@ -1189,6 +1261,31 @@ export { mediaTypeWhatsappOfficial, botSendMessage }; +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/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/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/ReportModalType/index.js b/frontend/src/components/ReportModalType/index.js new file mode 100644 index 0000000..eb7287a --- /dev/null +++ b/frontend/src/components/ReportModalType/index.js @@ -0,0 +1,135 @@ +import React, { useState, useEffect } from "react" +import Box from '@mui/material/Box' +import Button from '@mui/material/Button' +import Dialog from '@mui/material/Dialog' +import DialogActions from '@mui/material/DialogActions' +import DialogContent from '@mui/material/DialogContent' +import DialogContentText from '@mui/material/DialogContentText' +import DialogTitle from '@mui/material/DialogTitle' +import FormControl from '@mui/material/FormControl' + +import InputLabel from '@mui/material/InputLabel' +import MenuItem from '@mui/material/MenuItem' +import Select from '@mui/material/Select' + + + + +export default function MaxWidthDialog(props) { + const [open, setOpen] = useState(false) + const [fullWidth,] = useState(true) + const [currency, setCurrency] = useState(props.reportOption) + const [textOption, setTextOption] = useState('') + + + useEffect(() => { + + // props.func(currency) + + if(currency === '2' || currency === '3'){ + setTextOption('Retorna apenas tickets com status: fechado') + } + else{ + setTextOption('Retorna todos os tickets com status: aberto, fechado, pendente') + } + + }, [currency, props]) + + + const handleClickOpen = () => { + setOpen(true) + } + + const handleClose = () => { + + props.func(currency) + + setOpen(false) + } + + const handleMaxWidthChange = (event) => { + + setCurrency(event.target.value) + + + } + + + + return ( + + + + Relatórios + + + + Escolha uma opção do tipo de relatório abaixo + + + + + + opcoes + + + + + + {/* + } + label="Full width" + /> */} + + +
{textOption}
+ +
+ + + + + + +
+ +
+
+ ) +} diff --git a/frontend/src/components/WhatsAppModal/index.js b/frontend/src/components/WhatsAppModal/index.js index 0ee0533..8e8bbae 100644 --- a/frontend/src/components/WhatsAppModal/index.js +++ b/frontend/src/components/WhatsAppModal/index.js @@ -87,7 +87,7 @@ const WhatsAppModal = ({ open, onClose, whatsAppId, whatsAppOfficial }) => { if (!whatsAppId) return try { - const { data } = await api.get(`whatsapp/${whatsAppId}`) + const { data } = await api.get(`whatsapp/${whatsAppId}`) setWhatsApp(data) setIsOfficial(data?.isOfficial) diff --git a/frontend/src/pages/Report/index.js b/frontend/src/pages/Report/index.js index 75886f5..d8b0ee1 100644 --- a/frontend/src/pages/Report/index.js +++ b/frontend/src/pages/Report/index.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useReducer, useContext } from "react" +import React, { useState, useEffect, useReducer, useContext, useCallback } from "react" import MainContainer from "../../components/MainContainer" import api from "../../services/api" import SelectField from "../../components/Report/SelectField" @@ -17,6 +17,8 @@ import Checkbox from '@mui/material/Checkbox' import { Button } from "@material-ui/core" import ReportModal from "../../components/ReportModal" +import ReportModalType from "../../components/ReportModalType" + import MaterialTable from 'material-table' import LogoutIcon from '@material-ui/icons/CancelOutlined' @@ -34,6 +36,9 @@ import Switch from '@mui/material/Switch' const label = { inputProps: { 'aria-label': 'Size switch demo' } } const report = [{ 'value': '1', 'label': 'Atendimento por atendentes' }, { 'value': '2', 'label': 'Usuários online/offline' }] +const reportOptType = [{ 'value': '1', 'label': 'Padrão' }, { 'value': '2', 'label': 'Sintético' }, { 'value': '3', 'label': 'Analítico' }] + + const reducerQ = (state, action) => { @@ -276,21 +281,16 @@ const Report = () => { const [hasMore, setHasMore] = useState(false) const [pageNumberTickets, setTicketsPageNumber] = useState(1) const [totalCountTickets, setTotalCountTickets] = useState(0) - - - const [pageNumber, setPageNumber] = useState(1) const [users, dispatch] = useReducer(reducer, []) const [startDate, setDatePicker1] = useState(new Date()) const [endDate, setDatePicker2] = useState(new Date()) const [userId, setUser] = useState(null) const [query, dispatchQ] = useReducer(reducerQ, []) - const [reportOption, setReport] = useState('1') const [reporList,] = useState(report) const [profile, setProfile] = useState('') const [dataRows, setData] = useState([]) - const [onQueueStatus, setOnQueueProcessStatus] = useState(undefined) const [csvFile, setCsvFile] = useState() const [selectedValue, setSelectedValue] = useState('created') @@ -298,6 +298,11 @@ const Report = () => { const [queues, setQueues] = useState([]) const [queueId, setQueue] = useState(null) + const [reportTypeList,] = useState(reportOptType) + const [reportType, setReportType] = useState('1') + const [firstLoad, setFirstLoad] = useState(true); + + useEffect(() => { dispatch({ type: "RESET" }) dispatchQ({ type: "RESET" }) @@ -306,6 +311,14 @@ const Report = () => { }, [searchParam, profile]) + useEffect(() => { + if (firstLoad) { + setFirstLoad(false) + } else { + + } + }, [firstLoad]); + useEffect(() => { //setLoading(true); @@ -337,16 +350,14 @@ const Report = () => { useEffect(() => { //setLoading(true); + if (firstLoad) return - const delayDebounceFn = setTimeout(() => { - + const delayDebounceFn = setTimeout(() => { setLoading(true) const fetchQueries = async () => { try { - if (reportOption === '1') { - - // const { data } = await api.get("/reports/", { params: { userId: userId ? userId : 0, startDate: convertAndFormatDate(startDate), endDate: convertAndFormatDate(endDate), pageNumber: pageNumberTickets }, }) - + if (reportOption === '1') { + const { data } = await api.get("/reports/", { params: { userId, startDate, endDate, pageNumber: pageNumberTickets, createdOrUpdated: selectedValue, queueId }, userQueues: userA.queues }) let ticketsQueue = data.tickets @@ -424,8 +435,21 @@ const Report = () => { setChecked(true) } setReport(data) - } + } + // Get from report type option + const reportTypeValue = (data) => { + console.log('DATA: ', data) + let type = '1' + if (data === '1') type = 'default' + if (data === '2') type = 'synthetic' + if (data === '3') type = 'analytic' + + handleCSVMessages(type) + + setReportType(data) + } + useEffect(() => { if (reportOption === '1') { @@ -500,7 +524,7 @@ const Report = () => { - const handleCSVMessages = () => { + const handleCSVMessages = (type = 'default') => { const fetchQueries = async () => { @@ -519,7 +543,8 @@ const Report = () => { userId: userId, startDate: startDate, endDate: endDate - } + }, + query_type: type }) const onQueueStatus = querySavedOnQueue.data.queueStatus @@ -639,7 +664,10 @@ const Report = () => { case 'empty': return ( <> - + + */} ) case 'pending' || 'processing':