From 1246f59441d3c5900cb0489332390143f1f17a52 Mon Sep 17 00:00:00 2001 From: adriano Date: Mon, 29 May 2023 17:04:26 -0300 Subject: [PATCH 1/3] =?UTF-8?q?Altera=C3=A7=C3=A3o=20no=20tempo=20do=20ack?= =?UTF-8?q?(Status=20de=20envio=20da=20mensagem).=20De=20500=20milisegundo?= =?UTF-8?q?s=20para=204000=20milesegundos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/services/WbotServices/wbotMessageListener.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index bac461e..2c11f4a 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -906,7 +906,7 @@ const handleMessage = async ( const handleMsgAck = async (msg_id: any, ack: any) => { - await new Promise(r => setTimeout(r, 500)); + await new Promise(r => setTimeout(r, 4000)); const io = getIO(); From 92f7d8b4db69c0db4d107b550666968939cd5aa7 Mon Sep 17 00:00:00 2001 From: adriano Date: Wed, 12 Jul 2023 11:54:29 -0300 Subject: [PATCH 2/3] =?UTF-8?q?Atualiza=C3=A7=C3=A3o:=20ao=20criar=20ticke?= =?UTF-8?q?t=20associar=20a=20uma=20fila,=20busca=20por=20ticket=20e=20con?= =?UTF-8?q?teudo,=20trasnferencia=20de=20ticket=20sem=20necessidade=20de?= =?UTF-8?q?=20escolher=20usuario,=20corre=C3=A7=C3=A3o=20de=20notifica?= =?UTF-8?q?=C3=A7=C3=B5es=20de=20mensagens=20recebidas=20para=20ter=20o=20?= =?UTF-8?q?mesmo=20comportamento=20do=20whatsapp=20web.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TEST_SERVER1/whats/app.js | 41 +++- backend/src/controllers/TicketController.ts | 112 ++++++++--- .../helpers/SchedulingNotifySendMessage.ts | 45 +++-- .../src/helpers/SendWhatsappMessageSocket.ts | 18 +- backend/src/helpers/SetMessageAsRead.ts | 12 ++ backend/src/helpers/old_EndPointQuery.ts | 39 ++++ .../TicketServices/CreateTicketService.ts | 14 +- .../TicketServices/ListTicketsService.ts | 62 ++++-- .../TicketServices/UpdateTicketService.ts | 136 +++++++------ .../WbotServices/SendWhatsAppMessage.ts | 26 ++- .../WbotServices/wbotMessageListener.ts | 17 +- .../ContactCreateTicketModal/index.js | 156 ++++++++++++++ frontend/src/components/ContactModal/index.js | 1 + frontend/src/components/MessagesList/index.js | 85 +++++++- .../components/NotificationsPopOver/index.js | 11 +- frontend/src/components/QueueModal/index.js | 2 +- .../src/components/Report/DatePicker/index.js | 2 + frontend/src/components/Ticket/index.js | 10 +- .../components/TicketActionButtons/index.js | 157 ++++++++------- frontend/src/components/TicketsList/index.js | 11 +- .../src/components/TicketsManager/index.js | 190 +++++++++++++++--- .../components/TransferTicketModal/index.js | 157 +++++---------- frontend/src/hooks/useTickets/index.js | 3 + frontend/src/pages/Contacts/index.js | 86 ++++---- 24 files changed, 960 insertions(+), 433 deletions(-) create mode 100644 backend/src/helpers/SetMessageAsRead.ts create mode 100644 backend/src/helpers/old_EndPointQuery.ts create mode 100644 frontend/src/components/ContactCreateTicketModal/index.js diff --git a/TEST_SERVER1/whats/app.js b/TEST_SERVER1/whats/app.js index db0bdc8..4ccffe4 100644 --- a/TEST_SERVER1/whats/app.js +++ b/TEST_SERVER1/whats/app.js @@ -176,7 +176,7 @@ client.on("qr", async qr => { // omnihit.qrcode(process.env.MOBILEUID, process.env.MOBILENAME, qr); // omnihit.monitor(process.env.MOBILEUID, process.env.MOBILENAME, "STARTUP"); - asking_qrcode = true + asking_qrcode = true await new Promise((resolve, reject) => { @@ -193,8 +193,8 @@ client.on("qr", async qr => { } }); - }) - + }) + let url = process.env.CLIENT_URL + '/whatsapp/connection/qrcode' try { @@ -605,6 +605,37 @@ app.post('/api/restore', async (req, res) => { res.status(200).json({ message: "ok" }); }) +app.post('/api/sendSeen', async (req, res) => { + + let stat + + const { number } = req.body + + try { + + stat = await client.getState(); + + // await syncUnreadMessages(client) + + const wbotChat = await client.getChatById(number); + + wbotChat.sendSeen(); + + // const chatMessages = await wbotChat.fetchMessages({ limit: 100 }); + + // console.log('=============> wbotChat: ', chatMessages) + + } catch (err) { + + let terr = err.message; + + stat = (terr.search('Session closed') > -1 ? 'SESSIONCLOSED' : 'UNKNOWN') + + } + + res.status(200).json({ message: "ok" }); +}) + app.get('/api/connection/status', async (req, res) => { let stat @@ -636,6 +667,8 @@ const syncUnreadMessages = async (wbot) => { /* eslint-disable no-await-in-loop */ for (const chat of chats) { + // console.log('chat: ', chat) + if (chat.unreadCount > 0) { const unreadMessages = await chat.fetchMessages({ @@ -698,7 +731,7 @@ const getWbotMessage = async (messageId, number, limit,) => { } -async function whatsappMonitor(newState, omnihit_url, data) { +async function whatsappMonitor(newState, omnihit_url, data) { const whatsapp = await whatsappUpdateStatus(newState) diff --git a/backend/src/controllers/TicketController.ts b/backend/src/controllers/TicketController.ts index eb3bc3a..f19751d 100644 --- a/backend/src/controllers/TicketController.ts +++ b/backend/src/controllers/TicketController.ts @@ -19,9 +19,10 @@ import ptBR from 'date-fns/locale/pt-BR'; import { splitDateTime } from "../helpers/SplitDateTime"; import format from 'date-fns/format'; -import ListTicketsServiceCache from "../services/TicketServices/ListTicketServiceCache"; +import ListTicketsServiceCache from "../services/TicketServices/ListTicketServiceCache"; import { searchTicketCache, loadTicketsCache, } from '../helpers/TicketCache' +import { Op } from "sequelize"; @@ -34,6 +35,7 @@ type IndexQuery = { withUnreadMessages: string; queueIds: string; unlimited?: string; + searchParamContent?: string }; interface TicketData { @@ -41,6 +43,10 @@ interface TicketData { status: string; queueId: number; userId: number; + whatsappId?: string | number + msg?: string, + transfer?: boolean | undefined, + fromMe?: boolean } @@ -51,6 +57,16 @@ import TicketEmiterSumOpenClosedByUser from "../helpers/OnlineReporEmiterInfoByU import CountTicketService from "../services/TicketServices/CountTicketService"; import CountTicketsByUserQueue from "../services/UserServices/CountTicketsByUserQueue"; import ShowUserService from "../services/UserServices/ShowUserService"; +import axios from "axios"; +import User from "../models/User"; +import CheckContactOpenTickets from "../helpers/CheckContactOpenTickets"; +import QueuesByUser from "../services/UserServices/ShowQueuesByUser"; +import GetDefaultWhatsApp from "../helpers/GetDefaultWhatsApp"; +import { getWbot } from "../libs/wbot"; +import endPointQuery from "../helpers/old_EndPointQuery"; +import Contact from "../models/Contact"; +import BotIsOnQueue from "../helpers/BotIsOnQueue"; +import { setMessageAsRead } from "../helpers/SetMessageAsRead"; export const index = async (req: Request, res: Response): Promise => { @@ -62,7 +78,8 @@ export const index = async (req: Request, res: Response): Promise => { showAll, queueIds: queueIdsStringified, withUnreadMessages, - unlimited + unlimited, + searchParamContent } = req.query as IndexQuery; @@ -83,26 +100,33 @@ export const index = async (req: Request, res: Response): Promise => { userId, queueIds, withUnreadMessages, - unlimited + unlimited, + searchParamContent }); return res.status(200).json({ tickets, count, hasMore }); }; export const store = async (req: Request, res: Response): Promise => { - const { contactId, status, userId }: TicketData = req.body; - - console.log('TICKET CREATE: ', 'contactId: ', contactId, ' | status: ', status, ' | userId: ', userId) + const { contactId, status, userId, msg, queueId }: TicketData = req.body; - // test del - let ticket = await Ticket.findOne({ where: { contactId, status: 'queueChoice' } }); + const botInfo = await BotIsOnQueue('botqueue') + + let ticket = await Ticket.findOne({ + where: { + [Op.or]: [ + { contactId, status: 'queueChoice' }, + { contactId, status: 'open', userId: botInfo.userIdBot } + ] + } + }); if (ticket) { - await UpdateTicketService({ ticketData: { status: 'open', userId: userId, }, ticketId: ticket.id }); - + await UpdateTicketService({ ticketData: { status: 'open', userId: userId, queueId }, ticketId: ticket.id }); + } else { - ticket = await CreateTicketService({ contactId, status, userId }); + ticket = await CreateTicketService({ contactId, status, userId, queueId }); } const io = getIO(); @@ -140,7 +164,6 @@ export const show = async (req: Request, res: Response): Promise => { return res.status(200).json({ contact, statusChatEnd, schedulesContact }); }; - export const count = async (req: Request, res: Response): Promise => { // type indexQ = { status: string; date?: string; }; @@ -151,11 +174,11 @@ export const count = async (req: Request, res: Response): Promise => { return res.status(200).json(ticketCount); }; - - export const update = async (req: Request, res: Response): Promise => { + console.log('ENTROU NO UPDATE TICKET CONTROLLER') + const { ticketId } = req.params; const userOldInfo = await Ticket.findByPk(ticketId) @@ -177,10 +200,6 @@ export const update = async (req: Request, res: Response): Promise => }); - /////////////////////////////// - - // - if (scheduleData.farewellMessage) { const whatsapp = await ShowWhatsAppService(ticket.whatsappId); @@ -190,11 +209,11 @@ export const update = async (req: Request, res: Response): Promise => await SendWhatsAppMessage({ body: farewellMessage, ticket }); } } - + // lembrete // agendamento if (scheduleData.statusChatEndId === '2' || scheduleData.statusChatEndId === '3') { - + if (isScheduling(scheduleData.schedulingDate, scheduleData.schedulingTime)) { console.log('*** É AGENDAMENTO!') @@ -219,15 +238,52 @@ export const update = async (req: Request, res: Response): Promise => } else { - const ticketData: TicketData = req.body; + // Para aparecer pendente para todos usuarios que estao na fila + if (req.body.transfer) { + req.body.userId = null + } + + let ticketData: TicketData = req.body; + + // console.log('ticketData: ', ticketData) + // console.log('ticketData.transfer', ticketData.transfer) + + // return res.send() + + + // if (ticketData.transfer) { + + // const defaultWhatsapp: any = await GetDefaultWhatsApp(ticketData.userId); + + // const _ticket: any = await Ticket.findByPk(ticketId) + + // if (defaultWhatsapp && ticketData.status != 'open') { + + // await CheckContactOpenTickets(_ticket.dataValues.contactId, defaultWhatsapp.dataValues.id) + + // } + + // ticketData.whatsappId = defaultWhatsapp.dataValues.id + + // } + + console.log('--------> ticketData.status: ', ticketData.status, ' | ticketData.fromMe: ', ticketData.fromMe) - //ticketData: { status: 'open', userId: 4 } , ticketId const { ticket } = await UpdateTicketService({ ticketData, - ticketId + ticketId, }); + + if (ticketData.status == 'open' && !ticketData.fromMe) { + + await setMessageAsRead(ticket); + + } + + console.log('ticket.unreadMessages: ', ticket.unreadMessages) + if (ticketData.userId) { const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }))) @@ -246,7 +302,7 @@ export const update = async (req: Request, res: Response): Promise => const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }))) - if (userOldInfo.userId) { + if (userOldInfo.userId) { TicketEmiterSumOpenClosedByUser(userOldInfo.userId.toString(), dateToday.fullDate, dateToday.fullDate) @@ -308,3 +364,11 @@ 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/helpers/SchedulingNotifySendMessage.ts b/backend/src/helpers/SchedulingNotifySendMessage.ts index 405cc13..93326a2 100644 --- a/backend/src/helpers/SchedulingNotifySendMessage.ts +++ b/backend/src/helpers/SchedulingNotifySendMessage.ts @@ -10,6 +10,9 @@ import path from "path"; import { convertBytes } from "./ConvertBytes"; import { deleteScheduleByTicketIdCache } from "./SchedulingNotifyCache"; import SchedulingNotify from "../models/SchedulingNotify"; +import Ticket from "../models/Ticket"; +import { Sequelize, Op } from "sequelize"; + const fastFolderSize = require('fast-folder-size') const { promisify } = require('util') @@ -41,33 +44,39 @@ const monitor = async () => { try { const { schedulingNotifies, count, hasMore } = await ListSchedulingNotifyService({ searchParam: dateParm, pageNumber: "1" }); - - if (schedulingNotifies && schedulingNotifies.length > 0) { - for (let i = 0; i < schedulingNotifies.length; i++) { + if (schedulingNotifies && schedulingNotifies.length > 0) { + for (let i = 0; i < schedulingNotifies.length; i++) { + + const ticket: any = await ShowTicketService(+schedulingNotifies[i].ticketId); + + let _ticket = await Ticket.findOne({ + where: { + contactId: ticket.contactId, + status: { [Op.in]: ['open', 'pending'] } + } + }) + + await deleteScheduleByTicketIdCache(schedulingNotifies[i].ticketId) + + await DeleteSchedulingNotifyService(schedulingNotifies[i].id) + + if (_ticket) continue + + if (ticket.dataValues.status == 'closed') { + await ticket.update({ status: 'pending' }) + } - const ticket = await ShowTicketService(+schedulingNotifies[i].ticketId); - await new Promise(f => setTimeout(f, 3000)); - - if(!ticket.queue){ - await ticket.update({status: 'open'}) - } - - // SetTicketMessagesAsRead(ticket); - await SendWhatsAppMessage({ body: schedulingNotifies[i].message, ticket }); - await deleteScheduleByTicketIdCache(schedulingNotifies[i].ticketId) - - await DeleteSchedulingNotifyService(schedulingNotifies[i].id) - } - + } + } @@ -173,7 +182,7 @@ _fifo = setInterval(SchedulingNotifySendMessage, 5000); module.exports = SchedulingNotifySendMessage - + diff --git a/backend/src/helpers/SendWhatsappMessageSocket.ts b/backend/src/helpers/SendWhatsappMessageSocket.ts index 837b343..c9485f3 100644 --- a/backend/src/helpers/SendWhatsappMessageSocket.ts +++ b/backend/src/helpers/SendWhatsappMessageSocket.ts @@ -2,31 +2,19 @@ import { getIO } from "../libs/socket"; import Ticket from "../models/Ticket"; -function sendWhatsAppMessageSocket(ticket: Ticket, body: string, quotedMsgSerializedId?: string | undefined) { +function sendWhatsAppMessageSocket(ticket: Ticket, body: string, quotedMsgSerializedId?: string | undefined, number?: string ) { const io = getIO(); io.to(`session_${ticket.whatsappId.toString()}`).emit("send_message", { action: "create", msg: { - number: `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`, + number: number ? number : `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`, body: body, quotedMessageId: quotedMsgSerializedId, linkPreview: false } - }); - - - // io.emit("send_message", { - // action: "create", - // msg: { - // number: `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`, - // body: body, - // quotedMessageId: quotedMsgSerializedId, - // linkPreview: false - // } - // }); - + }); } diff --git a/backend/src/helpers/SetMessageAsRead.ts b/backend/src/helpers/SetMessageAsRead.ts new file mode 100644 index 0000000..3f073fa --- /dev/null +++ b/backend/src/helpers/SetMessageAsRead.ts @@ -0,0 +1,12 @@ +import { getWbot } from "../libs/wbot"; +import Ticket from "../models/Ticket"; +import endPointQuery from "./old_EndPointQuery"; + +export async function setMessageAsRead(ticket: Ticket) { + + const wbot_url = await getWbot(ticket.whatsappId); + + console.log('from wbotMessagelistener 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` }); +} \ No newline at end of file diff --git a/backend/src/helpers/old_EndPointQuery.ts b/backend/src/helpers/old_EndPointQuery.ts new file mode 100644 index 0000000..bfe975b --- /dev/null +++ b/backend/src/helpers/old_EndPointQuery.ts @@ -0,0 +1,39 @@ +const fsPromises = require("fs/promises"); +const fs = require('fs') +import axios from 'axios'; +import * as https from "https"; + +const endPointQuery = async (url: string, data: any) => { + + let response: any = null + + try { + + response = await axios.post(url, data); + + console.log(`TEST URL CLIENT POST ROUTE: ${url} | STATUS CODE: ${response.status}`); + + + } catch (err: any) { + + if (err.response) { + // The client was given an error response (5xx, 4xx) + // console.log('err.response: ', err.response) + console.log('err.response: ', err.response) + + // return { data: err.response.data, status: err.response.status } + + } else if (err.request) { + // The client never received a response, and the request was never left + console.log('err.request: ', err.request) + } else { + // Anything else + console.error(`Erro ao consultar endpoint ${url}: ${err}`); + } + } + + return response + +} + +export default endPointQuery; \ No newline at end of file diff --git a/backend/src/services/TicketServices/CreateTicketService.ts b/backend/src/services/TicketServices/CreateTicketService.ts index f3bd64a..376a90c 100644 --- a/backend/src/services/TicketServices/CreateTicketService.ts +++ b/backend/src/services/TicketServices/CreateTicketService.ts @@ -21,18 +21,23 @@ let flatten = require('flat') interface Request { contactId: number; status: string; - userId: number; + userId: number; + queueId?: number | undefined; } const CreateTicketService = async ({ contactId, status, - userId + userId, + queueId = undefined }: Request): Promise => { + + console.log('========> queueId: ', queueId) + try { - const defaultWhatsapp = await GetDefaultWhatsApp(userId); + const defaultWhatsapp = await GetDefaultWhatsApp(userId); await CheckContactOpenTickets(contactId); @@ -42,7 +47,8 @@ const CreateTicketService = async ({ contactId, status, isGroup, - userId + userId, + queueId }); const ticket = await Ticket.findByPk(id, { include: ["contact"] }); diff --git a/backend/src/services/TicketServices/ListTicketsService.ts b/backend/src/services/TicketServices/ListTicketsService.ts index 3f542d9..76c655b 100644 --- a/backend/src/services/TicketServices/ListTicketsService.ts +++ b/backend/src/services/TicketServices/ListTicketsService.ts @@ -17,6 +17,7 @@ import ListTicketServiceCache from "./ListTicketServiceCache" import { searchTicketCache, loadTicketsCache } from '../../helpers/TicketCache' import { getWbot } from "../../libs/wbot"; +import User from "../../models/User"; @@ -31,6 +32,7 @@ interface Request { withUnreadMessages?: string; queueIds: number[]; unlimited?: string; + searchParamContent?: string; } interface Response { @@ -49,9 +51,12 @@ const ListTicketsService = async ({ showAll, userId, withUnreadMessages, - unlimited = 'false' + unlimited = 'false', + searchParamContent = "" }: Request): Promise => { + console.log('----------> searchParamContent: ', searchParamContent) + let whereCondition: Filterable["where"] = { [Op.or]: [{ userId }, { status: "pending" }], queueId: { [Op.or]: [queueIds, null] } }; console.log('PAGE NUMBER TICKET: ', pageNumber) @@ -146,20 +151,33 @@ const ListTicketsService = async ({ if (searchParam) { const sanitizedSearchParam = searchParam.toLocaleLowerCase().trim(); + const sanitizedSearchParamContent = searchParamContent.toLocaleLowerCase().trim(); + + + if (searchParamContent.length > 0) { + + includeCondition = [ + ...includeCondition, + { + model: Message, + as: "messages", + attributes: ["id", "body"], + where: { + body: where(fn("LOWER", col("body")), "LIKE", `%${sanitizedSearchParamContent}%`) + }, + required: false, + duplicating: false + } + ]; + + whereCondition = { + ...whereCondition, + "$message.body$": where(fn("LOWER", col("body")), "LIKE", `%${sanitizedSearchParamContent}%`) + }; + + } + - // includeCondition = [ - // ...includeCondition, - // { - // model: Message, - // as: "messages", - // attributes: ["id", "body"], - // where: { - // body: where(fn("LOWER", col("body")), "LIKE", `%${sanitizedSearchParam}%`) - // }, - // required: false, - // duplicating: false - // } - // ]; whereCondition = { ...whereCondition, @@ -170,11 +188,17 @@ const ListTicketsService = async ({ { "$contact.number$": { [Op.like]: `%${sanitizedSearchParam}%` } }, - // { - // "$message.body$": where(fn("LOWER", col("body")), "LIKE", `%${sanitizedSearchParam}%`) - // } - ] + ], }; + + + const userProfile: any = await User.findByPk(userId) + + if (userProfile.dataValues.profile != 'admin' && userProfile.dataValues.profile != 'master') { + whereCondition = { ...whereCondition, userId } + } + + } if (date) { @@ -200,8 +224,6 @@ const ListTicketsService = async ({ const offset = limit * (+pageNumber - 1); - - const { count, rows: tickets } = await Ticket.findAndCountAll({ where: whereCondition, include: includeCondition, diff --git a/backend/src/services/TicketServices/UpdateTicketService.ts b/backend/src/services/TicketServices/UpdateTicketService.ts index 3cafa11..eed34da 100644 --- a/backend/src/services/TicketServices/UpdateTicketService.ts +++ b/backend/src/services/TicketServices/UpdateTicketService.ts @@ -8,20 +8,23 @@ import ShowTicketService from "./ShowTicketService"; import { createOrUpdateTicketCache } from '../../helpers/TicketCache' import AppError from "../../errors/AppError"; +import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket"; var flatten = require('flat') - + interface TicketData { status?: string; userId?: number; queueId?: number; - statusChatEnd?: string + statusChatEnd?: string; + unreadMessages?: number; } interface Request { ticketData: TicketData; - ticketId: string | number; + ticketId: string | number; + msg?: string } interface Response { @@ -32,83 +35,94 @@ interface Response { const UpdateTicketService = async ({ ticketData, - ticketId + ticketId, + msg='' }: Request): Promise => { try { - const { status, userId, queueId, statusChatEnd } = ticketData; + const { status, userId, queueId, statusChatEnd, unreadMessages } = ticketData; - const ticket = await ShowTicketService(ticketId); - // await SetTicketMessagesAsRead(ticket); + const ticket = await ShowTicketService(ticketId); + // await SetTicketMessagesAsRead(ticket); - const oldStatus = ticket.status; - const oldUserId = ticket.user?.id; + const oldStatus = ticket.status; + const oldUserId = ticket.user?.id; - if (oldStatus === "closed") { - await CheckContactOpenTickets(ticket.contact.id); - } - - await ticket.update({ - status, - queueId, - userId, - statusChatEnd - }); + if (oldStatus === "closed") { + await CheckContactOpenTickets(ticket.contact.id); + } - await ticket.reload(); - - // TEST DEL - try { - - // const { name, number } = await ShowContactService(ticket.contactId) - - let jsonString = JSON.stringify(ticket); //convert to string to remove the sequelize specific meta data - let ticket_obj = JSON.parse(jsonString); //to make plain json - delete ticket_obj['contact']['extraInfo'] - delete ticket_obj['user'] - - ticket_obj = flatten(ticket_obj) - - await createOrUpdateTicketCache(`ticket:${ticket.id}`, ticket_obj) - - } catch (error) { - console.log('There was an error on UpdateTicketService.ts on createTicketCache: ', error) - } - // - - let io = getIO(); - - if (ticket.status !== oldStatus || ticket.user?.id !== oldUserId) { - io.to(oldStatus).emit("ticket", { - action: "delete", - ticketId: ticket.id + await ticket.update({ + status, + queueId, + userId, + unreadMessages, + statusChatEnd }); - } + + await ticket.reload(); + + if (msg.length > 0) { + + setTimeout(async () => { + + sendWhatsAppMessageSocket(ticket, msg) + + }, 2000) + } + + // TEST DEL + try { + + // const { name, number } = await ShowContactService(ticket.contactId) + + let jsonString = JSON.stringify(ticket); //convert to string to remove the sequelize specific meta data + let ticket_obj = JSON.parse(jsonString); //to make plain json + delete ticket_obj['contact']['extraInfo'] + delete ticket_obj['user'] + + ticket_obj = flatten(ticket_obj) + + await createOrUpdateTicketCache(`ticket:${ticket.id}`, ticket_obj) + + } catch (error) { + console.log('There was an error on UpdateTicketService.ts on createTicketCache: ', error) + } + // + + let io = getIO(); + + if (ticket.status !== oldStatus || ticket.user?.id !== oldUserId) { + io.to(oldStatus).emit("ticket", { + action: "delete", + ticketId: ticket.id + }); + } - io.to(ticket.status) - .to("notification") - .to(ticketId.toString()) - .emit("ticket", { + io.to(ticket.status) + .to("notification") + .to(ticketId.toString()) + .emit("ticket", { + action: "update", + ticket + }); + + + io.emit("ticketStatus", { action: "update", - ticket + ticketStatus: { ticketId: ticket.id, status: ticket.status } }); - io.emit("ticketStatus", { - action: "update", - ticketStatus: { ticketId: ticket.id, status: ticket.status } - }); + return { ticket, oldStatus, oldUserId }; - - return { ticket, oldStatus, oldUserId }; - } catch (error: any) { console.error('===> Error on UpdateTicketService.ts file: \n', error) throw new AppError(error.message); } - + }; -export default UpdateTicketService; +export default UpdateTicketService; \ No newline at end of file diff --git a/backend/src/services/WbotServices/SendWhatsAppMessage.ts b/backend/src/services/WbotServices/SendWhatsAppMessage.ts index c48b2fd..183aaf3 100644 --- a/backend/src/services/WbotServices/SendWhatsAppMessage.ts +++ b/backend/src/services/WbotServices/SendWhatsAppMessage.ts @@ -33,21 +33,25 @@ interface Request { body: string; ticket: Ticket; quotedMsg?: Message; + number?: string } const SendWhatsAppMessage = async ({ body, ticket, - quotedMsg + quotedMsg, + number }: Request): Promise => { try { - let timestamp = Math.floor(Date.now() / 1000) + // let timestamp = Math.floor(Date.now() / 1000) + let timestamp = Date.now() + String(Math.floor(Math.random() * 1000)) var timetaken = `########################################${timestamp}| TicketId: ${ticket.id} => Time taken to send the message`; console.time(timetaken) + let quotedMsgSerializedId: string | undefined; if (quotedMsg) { @@ -64,9 +68,15 @@ const SendWhatsAppMessage = async ({ let listWhatsapp = null - // listWhatsapp = await searchWhatsappCache(`${ticket.whatsappId}`, 'CONNECTED') + // listWhatsapp = await searchWhatsappCache(`${ticket.whatsappId}`, 'CONNECTED') - console.log('ticket.whatsappIdticket.whatsappIdticket.whatsappIdticket: ', ticket.whatsappId) + if (!ticket.whatsappId) { + + const defaultWhatsapp: any = await GetDefaultWhatsApp(ticket.userId); + + await ticket.update({ whatsappId: +defaultWhatsapp.id }); + + } if (!listWhatsapp) { listWhatsapp = await ListWhatsAppsNumber(ticket.whatsappId, 'CONNECTED') @@ -74,12 +84,8 @@ const SendWhatsAppMessage = async ({ if (listWhatsapp.whatsapp && listWhatsapp.whatsapp.status != 'CONNECTED' && listWhatsapp.whatsapps.length > 0) { - // console.log('kkkkkkkkkkkkkkkkkkkkkkkkkkkk: ', listWhatsapp.whatsapps[0].id) - await ticket.update({ whatsappId: + listWhatsapp.whatsapps[0].id }); - let _ticket = await Ticket.findByPk(listWhatsapp.whatsapps[0].id) - } @@ -120,9 +126,9 @@ const SendWhatsAppMessage = async ({ try { - sendWhatsAppMessageSocket(ticket, body, quotedMsgSerializedId); + sendWhatsAppMessageSocket(ticket, body, quotedMsgSerializedId, number); - await ticket.update({ lastMessage: body }); + await ticket.update({ lastMessage: body }); await updateTicketCacheByTicketId(ticket.id, { lastMessage: body, updatedAt: new Date(ticket.updatedAt).toISOString() }) diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index 2c11f4a..053b625 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -60,6 +60,7 @@ import { _restore } from "../../helpers/RestoreControll"; import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket"; import { getWhatsappIds, setWhatsappId } from "../../helpers/WhatsappIdMultiSessionControl"; import AppError from "../../errors/AppError"; +import { setMessageAsRead } from "../../helpers/SetMessageAsRead"; @@ -888,15 +889,13 @@ const handleMessage = async ( } - // + + if (msg && !msg.fromMe && ticket.status == 'pending') { + + await setMessageAsRead(ticket) - // test del - - // if (msg.body.trim() == 'broken') { - // throw new Error('Throw makes it go boom!') - // } - // + } } catch (err) { Sentry.captureException(err); @@ -926,9 +925,7 @@ const handleMsgAck = async (msg_id: any, ack: any) => { return; } await messageToUpdate.update({ ack }); - - console.log('ACK messageToUpdate: ', JSON.parse(JSON.stringify(messageToUpdate))) - + io.to(messageToUpdate.ticketId.toString()).emit("appMessage", { action: "update", message: messageToUpdate diff --git a/frontend/src/components/ContactCreateTicketModal/index.js b/frontend/src/components/ContactCreateTicketModal/index.js new file mode 100644 index 0000000..55fa06b --- /dev/null +++ b/frontend/src/components/ContactCreateTicketModal/index.js @@ -0,0 +1,156 @@ +import React, { useState, useEffect, useContext, useRef, useCallback } from "react"; +import { useHistory } from "react-router-dom"; +import { toast } from "react-toastify"; + +import Button from "@material-ui/core/Button"; +import Dialog from "@material-ui/core/Dialog"; +import Select from "@material-ui/core/Select"; +import FormControl from "@material-ui/core/FormControl"; +import InputLabel from "@material-ui/core/InputLabel"; +import MenuItem from "@material-ui/core/MenuItem"; +import LinearProgress from "@material-ui/core/LinearProgress"; +import { makeStyles } from "@material-ui/core"; + +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; +import DialogTitle from "@material-ui/core/DialogTitle"; + +import { i18n } from "../../translate/i18n"; +import ButtonWithSpinner from "../ButtonWithSpinner"; +import { AuthContext } from "../../context/Auth/AuthContext"; + +import toastError from "../../errors/toastError"; + +import api from "../../services/api"; + +const useStyles = makeStyles((theme) => ({ + maxWidth: { + width: "100%", + }, + paper: { + minWidth: "300px" + }, + linearProgress: { + marginTop: "5px" + } +})); + +const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => { + const { user } = useContext(AuthContext); + let isMounted = useRef(true) + + const history = useHistory(); + const [queues, setQueues] = useState([]); + const [loading, setLoading] = useState(false); + const [selectedQueue, setSelectedQueue] = useState(''); + const [itemHover, setItemHover] = useState(-1) + const classes = useStyles(); + + useEffect(() => { + const userQueues = user.queues.map(({ id, name, color }) => { return { id, name, color } }) + if (userQueues.length === 1) setSelectedQueue(userQueues[0].id) + setQueues(userQueues) + }, [user]); + + const handleClose = () => { + onClose(); + }; + + const handleSaveTicket = useCallback(async (contactId, userId, queueId) => { + if (!contactId || !userId) { + console.log("Missing contactId or userId") + return + }; + if (!queueId) { + toast.warning("Nenhuma Fila Selecionada") + return + } + if (isMounted.current) setLoading(true); + + + const delayDebounceFn = setTimeout(() => { + const ticketCreate = async () => { + try { + const { data: ticket } = await api.post("/tickets", { + contactId: contactId, + userId: userId, + queueId: queueId, + status: "open", + }); + history.push(`/tickets/${ticket.id}`); + } catch (err) { + toastError(err); + } + if (isMounted.current) setLoading(false); + + }; + ticketCreate(); + }, 300); + return () => clearTimeout(delayDebounceFn); + + }, [history]) + + useEffect(() => { + if (modalOpen && queues.length <= 1) { + handleSaveTicket(contactId, user.id, selectedQueue) + } + return () => { + isMounted.current = false; + }; + }, [modalOpen, contactId, user.id, selectedQueue, handleSaveTicket, queues.length]); + + if (modalOpen && queues.length <= 1) { + return + } + + return ( + + + {i18n.t("newTicketModal.title")} + + + + {i18n.t("Selecionar Fila")} + + + + + + handleSaveTicket(contactId, user.id, selectedQueue)} + variant="contained" + color="primary" + loading={loading} + > + {i18n.t("newTicketModal.buttons.ok")} + + + + ); +}; + +export default ContactCreateTicketModal; \ No newline at end of file diff --git a/frontend/src/components/ContactModal/index.js b/frontend/src/components/ContactModal/index.js index 8305524..d67f01c 100644 --- a/frontend/src/components/ContactModal/index.js +++ b/frontend/src/components/ContactModal/index.js @@ -73,6 +73,7 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => { name: "", number: "", email: "", + useDialogflow: true, }; const [contact, setContact] = useState(initialState); diff --git a/frontend/src/components/MessagesList/index.js b/frontend/src/components/MessagesList/index.js index 5c02bf0..236601a 100644 --- a/frontend/src/components/MessagesList/index.js +++ b/frontend/src/components/MessagesList/index.js @@ -1,8 +1,9 @@ -import React, { 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 { green } from "@material-ui/core/colors"; import { @@ -318,6 +319,9 @@ const MessagesList = ({ ticketId, isGroup }) => { const [anchorEl, setAnchorEl] = useState(null); const messageOptionsMenuOpen = Boolean(anchorEl); const currentTicketId = useRef(ticketId); + const [sendSeen, setSendSeen] = useState(false) + + const { user } = useContext(AuthContext); useEffect(() => { dispatch({ type: "RESET" }); @@ -327,13 +331,77 @@ const MessagesList = ({ ticketId, isGroup }) => { }, [ticketId]); useEffect(() => { + + let url_split + let url_ticketId + + try { + + url_split = window.location.href.split('tickets') + + url_ticketId = url_split[url_split.length - 1].match(/\d+/)[0] + + } catch (error) { + console.log('error on try do the send seen: ', error) + } + + if (!url_ticketId) return + + if (!sendSeen) return + + const delayDebounceFn = setTimeout(() => { + const sendSeenMessage = async () => { + try { + const { data } = await api.get("/messages/" + ticketId, { + params: { pageNumber }, + }); + + setSendSeen(false) + + if (!data) return + + if (data.ticket.status === "open" && /*data.ticket.unreadMessages > 0 &&*/ + data.ticket.userId === user.id) { + + let fromMe = false + + if (data.messages.length > 0) { + fromMe = data.messages[data.messages.length - 1].fromMe + } + + // Atualiza Unread messages para 0 + // Atualizei função no back-end para receber o novo parametro unreadMessages + const ticketUpdate = { ...data.ticket, unreadMessages: 0, fromMe } + await api.put("/tickets/" + ticketId, ticketUpdate) + + } + + } catch (err) { + setLoading(false); + toastError(err); + } + }; + sendSeenMessage(); + }, 500); + + + return () => { + clearTimeout(delayDebounceFn); + }; + + + + }, [sendSeen, pageNumber, ticketId, user.id]); + + useEffect(() => { + 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 }); @@ -365,17 +433,12 @@ const MessagesList = ({ ticketId, isGroup }) => { if (data.action === "create") { - console.log('ADD_MESSAGE: ', data.message) - dispatch({ type: "ADD_MESSAGE", payload: data.message }); scrollToBottom(); } if (data.action === "update") { - - console.log('joinChatBox update: ',data.action) - dispatch({ type: "UPDATE_MESSAGE", payload: data.message }); } }); @@ -391,7 +454,11 @@ const MessagesList = ({ ticketId, isGroup }) => { const scrollToBottom = () => { if (lastMessageRef.current) { + + setSendSeen(true) + lastMessageRef.current.scrollIntoView({}); + } }; @@ -513,7 +580,7 @@ const MessagesList = ({ ticketId, isGroup }) => { if (index === messagesList.length - 1) { let messageDay = parseISO(messagesList[index].createdAt); - let previousMessageDay = parseISO(messagesList[index - 1].createdAt); + let previousMessageDay = parseISO(messagesList[index - 1].createdAt); return ( <> @@ -682,4 +749,4 @@ const MessagesList = ({ ticketId, isGroup }) => { ); }; -export default MessagesList; +export default MessagesList; \ No newline at end of file diff --git a/frontend/src/components/NotificationsPopOver/index.js b/frontend/src/components/NotificationsPopOver/index.js index 86b8363..1386087 100644 --- a/frontend/src/components/NotificationsPopOver/index.js +++ b/frontend/src/components/NotificationsPopOver/index.js @@ -116,17 +116,13 @@ const NotificationsPopOver = () => { const socket = openSocket(process.env.REACT_APP_BACKEND_URL); - socket.on("reload_page", (data) => { - - console.log('UPDATING THE PAGE: ', data.userId, ' | user.id: ', user.id) + socket.on("reload_page", (data) => { if (user.id === data.userId) { window.location.reload(true); - } - - + } }) @@ -167,8 +163,7 @@ const NotificationsPopOver = () => { clearInterval(_fifo); } - _fifo = setInterval(() => { - console.log('user.id: ', user.id) + _fifo = setInterval(() => { socket.emit("online", user.id) }, 3000); diff --git a/frontend/src/components/QueueModal/index.js b/frontend/src/components/QueueModal/index.js index b86580a..fa5caa9 100644 --- a/frontend/src/components/QueueModal/index.js +++ b/frontend/src/components/QueueModal/index.js @@ -249,4 +249,4 @@ const QueueModal = ({ open, onClose, queueId }) => { ); }; -export default QueueModal; +export default QueueModal; \ No newline at end of file diff --git a/frontend/src/components/Report/DatePicker/index.js b/frontend/src/components/Report/DatePicker/index.js index db4ae5c..64e9b80 100644 --- a/frontend/src/components/Report/DatePicker/index.js +++ b/frontend/src/components/Report/DatePicker/index.js @@ -11,6 +11,8 @@ import { +// import { i18n } from "../../../translate/i18n"; + import ptBrLocale from "date-fns/locale/pt-BR"; diff --git a/frontend/src/components/Ticket/index.js b/frontend/src/components/Ticket/index.js index 6b4a1cb..a88507b 100644 --- a/frontend/src/components/Ticket/index.js +++ b/frontend/src/components/Ticket/index.js @@ -83,14 +83,14 @@ const Ticket = () => { const [contact, setContact] = useState({}); const [ticket, setTicket] = useState({}); - const [statusChatEnd, setStatusChatEnd] = useState({}) + const [statusChatEnd, setStatusChatEnd] = useState({}) useEffect(() => { setLoading(true); const delayDebounceFn = setTimeout(() => { const fetchTicket = async () => { try { - + // maria julia const { data } = await api.get("/tickets/" + ticketId); @@ -101,7 +101,7 @@ const Ticket = () => { setContact(data.contact.contact); setTicket(data.contact); - setStatusChatEnd(data.statusChatEnd) + setStatusChatEnd(data.statusChatEnd) setLoading(false); } catch (err) { @@ -171,8 +171,8 @@ const Ticket = () => { onClick={handleDrawerOpen} /> -
- +
+
diff --git a/frontend/src/components/TicketActionButtons/index.js b/frontend/src/components/TicketActionButtons/index.js index cdfc297..8382add 100644 --- a/frontend/src/components/TicketActionButtons/index.js +++ b/frontend/src/components/TicketActionButtons/index.js @@ -12,7 +12,7 @@ import ButtonWithSpinner from "../ButtonWithSpinner"; import toastError from "../../errors/toastError"; import { AuthContext } from "../../context/Auth/AuthContext"; -import Modal from "../ChatEnd/ModalChatEnd"; +import Modal from "../ChatEnd/ModalChatEnd"; import { render } from '@testing-library/react'; import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketHeaderOption"; @@ -29,17 +29,30 @@ const useStyles = makeStyles(theme => ({ }, })); -const TicketActionButtons = ({ ticket, statusChatEnd }) => { +const TicketActionButtons = ({ ticket, statusChatEnd }) => { const classes = useStyles(); - const history = useHistory(); + const history = useHistory(); + const [anchorEl, setAnchorEl] = useState(null); const [loading, setLoading] = useState(false); + + // const [useDialogflow, setUseDialogflow] = useState(ticket.contact.useDialogflow); + + // const [/*useDialogflow*/, setUseDialogflow] = useState(() => { + // if (Object.keys(ticket).length !== 0) { + // return ticket.contact.useDialogflow; + // } else { + // // Set a default value if `ticket.contact.useDialogflow` is null + // return true + // } + // }); + const ticketOptionsMenuOpen = Boolean(anchorEl); const { user } = useContext(AuthContext); const { tabOption, setTabOption } = useContext(TabTicketContext); - - const handleOpenTicketOptionsMenu = e => { + + const handleOpenTicketOptionsMenu = e => { setAnchorEl(e.currentTarget); }; @@ -47,83 +60,91 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => { setAnchorEl(null); }; - - const chatEndVal = (data) => { - - if(data){ - data = {...data, 'ticketId': ticket.id} - - + const chatEndVal = (data) => { + + if (data) { + + data = { ...data, 'ticketId': ticket.id } handleUpdateTicketStatus(null, "closed", user?.id, data) - } - + } + } - const handleModal = (/*status, userId*/) => { + const handleModal = (/*status, userId*/) => { + + render() - render() - }; - - const handleUpdateTicketStatus = async (e, status, userId, schedulingData={}) => { - - setLoading(true); - try { - - if(status==='closed'){ - if (tabOption === 'search') { - setTabOption('open') - } + const handleUpdateTicketStatus = async (e, status, userId, schedulingData = {}) => { - await api.put(`/tickets/${ticket.id}`, { - status: status, - userId: userId || null, - schedulingNotifyData: JSON.stringify(schedulingData) - }); + setLoading(true); + try { + if (status === 'closed') { + + if (tabOption === 'search') { + setTabOption('open') } - else{ - if (tabOption === 'search') { - setTabOption('open') - } - - await api.put(`/tickets/${ticket.id}`, { - status: status, - userId: userId || null - }); + await api.put(`/tickets/${ticket.id}`, { + status: status, + userId: userId || null, + schedulingNotifyData: JSON.stringify(schedulingData) + }); - } + } + else { - setLoading(false); - if (status === "open") { - - - history.push(`/tickets/${ticket.id}`); - } else { - history.push("/tickets"); + if (tabOption === 'search') { + setTabOption('open') } - } catch (err) { - setLoading(false); - toastError(err); - } - - - + await api.put(`/tickets/${ticket.id}`, { + status: status, + userId: userId || null + }); + + } + + setLoading(false); + if (status === "open") { + + + history.push(`/tickets/${ticket.id}`); + } else { + history.push("/tickets"); + } + } catch (err) { + setLoading(false); + toastError(err); + } + }; + // const handleContactToggleUseDialogflow = async () => { + // setLoading(true); + // try { + // const contact = await api.put(`/contacts/toggleUseDialogflow/${ticket.contact.id}`); + // setUseDialogflow(contact.data.useDialogflow); + // setLoading(false); + // } catch (err) { + // setLoading(false); + // toastError(err); + // } + // }; + + return (
{ticket.status === "closed" && ( @@ -138,7 +159,7 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => { )} {ticket.status === "open" && ( <> - } size="small" @@ -146,16 +167,16 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => { > {i18n.t("messagesList.header.buttons.return")} - - { - - - handleModal() + onClick={e => { + + + handleModal() // handleUpdateTicketStatus(e, "closed", user?.id) }} diff --git a/frontend/src/components/TicketsList/index.js b/frontend/src/components/TicketsList/index.js index fb2ba3c..6e5bf4b 100644 --- a/frontend/src/components/TicketsList/index.js +++ b/frontend/src/components/TicketsList/index.js @@ -1,4 +1,6 @@ import React, { useState, useEffect, useReducer, useContext } from "react"; + + import openSocket from "socket.io-client"; import { makeStyles } from "@material-ui/core/styles"; @@ -175,7 +177,7 @@ const reducer = (state, action) => { }; const TicketsList = (props) => { - const { status, searchParam, showAll, selectedQueueIds, updateCount, style, tab } = props; + const { status, searchParam, searchParamContent, showAll, selectedQueueIds, updateCount, style, tab } = props; const classes = useStyles(); const [pageNumber, setPageNumber] = useState(1); const [ticketsList, dispatch] = useReducer(reducer, []); @@ -184,16 +186,16 @@ const TicketsList = (props) => { const { searchTicket } = useContext(SearchTicketContext) useEffect(() => { + dispatch({ type: "RESET" }); setPageNumber(1); - }, [status, searchParam, showAll, selectedQueueIds, searchTicket]); - - + }, [status, searchParam, searchParamContent, showAll, selectedQueueIds, searchTicket]); const { tickets, hasMore, loading } = useTickets({ pageNumber, searchParam, + searchParamContent, status, showAll, queueIds: JSON.stringify(selectedQueueIds), @@ -201,6 +203,7 @@ const TicketsList = (props) => { }); useEffect(() => { + if (!status && !searchParam) return; // if (searchParam) { diff --git a/frontend/src/components/TicketsManager/index.js b/frontend/src/components/TicketsManager/index.js index 038601e..a6f80d5 100644 --- a/frontend/src/components/TicketsManager/index.js +++ b/frontend/src/components/TicketsManager/index.js @@ -1,14 +1,21 @@ import React, { useContext, useEffect, useRef, useState } from "react"; import { makeStyles } from "@material-ui/core/styles"; +import { IconButton } from "@mui/material"; import Paper from "@material-ui/core/Paper"; -import SearchIcon from "@material-ui/icons/Search"; import InputBase from "@material-ui/core/InputBase"; import Tabs from "@material-ui/core/Tabs"; import Tab from "@material-ui/core/Tab"; import Badge from "@material-ui/core/Badge"; + +import Tooltip from "@material-ui/core/Tooltip"; + + +import SearchIcon from "@material-ui/icons/Search"; import MoveToInboxIcon from "@material-ui/icons/MoveToInbox"; import CheckBoxIcon from "@material-ui/icons/CheckBox"; +import MenuIcon from "@material-ui/icons/Menu"; +import FindInPageIcon from '@material-ui/icons/FindInPage'; import FormControlLabel from "@material-ui/core/FormControlLabel"; import Switch from "@material-ui/core/Switch"; @@ -63,6 +70,25 @@ const useStyles = makeStyles((theme) => ({ }, serachInputWrapper: { + flex: 1, + display: "flex", + flexDirection: "column", + gap: "10px", + borderRadius: 40, + padding: 4, + marginRight: theme.spacing(1), + }, + + searchInputHeader: { + flex: 1, + background: "#fff", + display: "flex", + borderRadius: 40, + padding: 4, + marginRight: theme.spacing(1), + }, + + searchContentInput: { flex: 1, background: "#fff", display: "flex", @@ -78,6 +104,11 @@ const useStyles = makeStyles((theme) => ({ alignSelf: "center", }, + menuSearch: { + color: "grey", + alignSelf: "center", + }, + searchInput: { flex: 1, border: "none", @@ -95,20 +126,21 @@ const useStyles = makeStyles((theme) => ({ }, })); +const DEFAULT_SEARCH_PARAM = { searchParam: "", searchParamContent: "" } + const TicketsManager = () => { const { tabOption, setTabOption } = useContext(TabTicketContext); - const {setSearchTicket} = useContext(SearchTicketContext) + const { setSearchTicket } = useContext(SearchTicketContext) const classes = useStyles(); - const [searchParam, setSearchParam] = useState(""); + const [searchParam, setSearchParam] = useState(DEFAULT_SEARCH_PARAM); const [tab, setTab] = useState("open"); const [tabOpen, setTabOpen] = useState("open"); const [newTicketModalOpen, setNewTicketModalOpen] = useState(false); const [showAllTickets, setShowAllTickets] = useState(false); - const searchInputRef = useRef(); const { user } = useContext(AuthContext); const [openCount, setOpenCount] = useState(0); @@ -117,7 +149,16 @@ const TicketsManager = () => { const userQueueIds = user.queues.map((q) => q.id); const [selectedQueueIds, setSelectedQueueIds] = useState(userQueueIds || []); + const [showContentSearch, setShowContentSearch] = useState(false) + const searchInputRef = useRef(); + const searchContentInputRef = useRef(); const [inputSearch, setInputSearch] = useState(''); + const [inputContentSearch, setInputContentSearch] = useState("") + + const [openTooltipSearch, setOpenTooltipSearch] = useState(false) + + let searchTimeout; + let searchContentTimeout; useEffect(() => { if (user.profile.toUpperCase() === "ADMIN") { @@ -135,22 +176,45 @@ const TicketsManager = () => { }, [tab, setTabOption]); + useEffect(() => { + + // clearTimeout(searchContentTimeout); + + // setSearchParam(prev => ({ ...prev, searchParamContent: "" })) + + if (!inputContentSearch) return + + if (!searchContentTimeout) return + + // searchContentTimeout = setTimeout(() => { + + // setSearchParam(prev => ({ ...prev, searchParamContent: inputContentSearch })); + + // }, 500); + + clearTimeout(searchContentTimeout); + + setSearchParam(prev => ({ ...prev, searchParamContent: "" })) + + + }, [inputContentSearch, searchContentTimeout]); + useEffect(() => { if (tabOption === 'open') { setTabOption('') - setSearchParam(''); + setSearchParam(DEFAULT_SEARCH_PARAM); setInputSearch(''); + setInputContentSearch('') setTab("open"); return; } }, [tabOption, setTabOption]) - let searchTimeout; - + const removeExtraSpace = (str) => { str = str.replace(/^\s+/g, '') @@ -163,24 +227,58 @@ const TicketsManager = () => { setInputSearch(removeExtraSpace(searchedTerm)) - setSearchTicket(searchParam) + setSearchTicket(searchParam.searchParam) clearTimeout(searchTimeout); if (searchedTerm === "") { - setSearchParam(searchedTerm); + setSearchParam(prev => ({ ...prev, searchParam: searchedTerm })) setInputSearch(searchedTerm) + setShowContentSearch(false) setTab("open"); return; } + if (searchedTerm.length < 4) { + setSearchParam(prev => ({ ...prev, searchParamContent: "" })) + setInputContentSearch('') + } + + searchTimeout = setTimeout(() => { - setSearchParam(searchedTerm); + setSearchParam(prev => ({ ...prev, searchParam: searchedTerm })); }, 500); }; + const handleContentSearch = e => { + + let searchedContentText = removeExtraSpace(e.target.value.toLowerCase()) + + setInputContentSearch(searchedContentText) + + searchContentTimeout = setTimeout(() => { + + setSearchParam(prev => ({ ...prev, searchParamContent: searchedContentText })); + + }, 500); + + } + + const handleOpenTooltipSearch = () => { + if (searchParam.searchParam.length < 4) { + setOpenTooltipSearch(true) + } + } + + const handleCloseTooltipSearch = () => { + setOpenTooltipSearch(false) + if (searchParam.searchParam.length < 4) { + searchInputRef.current.focus() + } + } + const handleChangeTab = (e, newValue) => { setTab(newValue); }; @@ -233,15 +331,50 @@ const TicketsManager = () => { {tab === "search" ? (
- - +
+ + + {/* setShowContentSearch(prev => !prev)}> + + */} + handleOpenTooltipSearch()} + onClose={() => handleCloseTooltipSearch()} + title="Digite pelo menos 4 caracteres" + arrow> + + setShowContentSearch(prev => !prev)} + > + + + + +
+ { + // showContentSearch ? + (showContentSearch && searchParam.searchParam.length >= 4) ? + (
+ + handleContentSearch(e)} + /> +
) : null + }
) : ( <> @@ -340,17 +473,18 @@ const TicketsManager = () => { - - - + + +
); }; -export default TicketsManager; +export default TicketsManager; \ No newline at end of file diff --git a/frontend/src/components/TransferTicketModal/index.js b/frontend/src/components/TransferTicketModal/index.js index 2707969..8680578 100644 --- a/frontend/src/components/TransferTicketModal/index.js +++ b/frontend/src/components/TransferTicketModal/index.js @@ -1,8 +1,7 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useContext, useMemo } from "react"; import { useHistory } from "react-router-dom"; import Button from "@material-ui/core/Button"; -import TextField from "@material-ui/core/TextField"; import Dialog from "@material-ui/core/Dialog"; import Select from "@material-ui/core/Select"; import FormControl from "@material-ui/core/FormControl"; @@ -13,102 +12,72 @@ import { makeStyles } from "@material-ui/core"; import DialogActions from "@material-ui/core/DialogActions"; import DialogContent from "@material-ui/core/DialogContent"; import DialogTitle from "@material-ui/core/DialogTitle"; -import Autocomplete, { - createFilterOptions, -} from "@material-ui/lab/Autocomplete"; -import CircularProgress from "@material-ui/core/CircularProgress"; import { i18n } from "../../translate/i18n"; import api from "../../services/api"; import ButtonWithSpinner from "../ButtonWithSpinner"; import toastError from "../../errors/toastError"; -import useQueues from "../../hooks/useQueues"; + +import { WhatsAppsContext } from "../../context/WhatsApp/WhatsAppsContext"; const useStyles = makeStyles((theme) => ({ - maxWidth: { - width: "100%", - }, + maxWidth: { + width: "100%", + }, })); -const filterOptions = createFilterOptions({ - trim: true, -}); +// Receive array of queues arrays +// Return a new array with unique queues from all arrays has passed by the parameter +const queueArraysToOneArray = (array) => { + if (!array) return [] + const map = {} + const uniqueQueuesAvailable = [] + array.forEach((queues) => { + queues.forEach(({ id, name, color }) => { + if (!map[id]) { + map[id] = true + uniqueQueuesAvailable.push({ id, name, color }) + } + }) + }) + return uniqueQueuesAvailable +} + const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => { const history = useHistory(); - const [options, setOptions] = useState([]); - const [queues, setQueues] = useState([]); - const [allQueues, setAllQueues] = useState([]); + const { whatsApps } = useContext(WhatsAppsContext); const [loading, setLoading] = useState(false); - const [searchParam, setSearchParam] = useState(""); - const [selectedUser, setSelectedUser] = useState(null); const [selectedQueue, setSelectedQueue] = useState(''); const classes = useStyles(); - const { findAll: findAllQueues } = useQueues(); - - useEffect(() => { - const loadQueues = async () => { - const list = await findAllQueues(); - setAllQueues(list); - setQueues(list); - } - loadQueues(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - if (!modalOpen || searchParam.length < 3) { - setLoading(false); - return; - } - setLoading(true); - const delayDebounceFn = setTimeout(() => { - const fetchUsers = async () => { - try { - const { data } = await api.get("/users/", { - params: { searchParam }, - }); - setOptions(data.users); - setLoading(false); - } catch (err) { - setLoading(false); - toastError(err); - } - }; - - fetchUsers(); - }, 500); - return () => clearTimeout(delayDebounceFn); - }, [searchParam, modalOpen]); + const queues = useMemo(() => { + if (!whatsApps) return [] + const whatsAppsQueues = whatsApps.map(({ queues }) => queues) + //const whatsAppsQueues = whatsApps.filter(({ status }) => status === "CONNECTED" ).map(({ queues }) => queues) + const uniqueQueuesAvailable = queueArraysToOneArray(whatsAppsQueues) + return uniqueQueuesAvailable + }, [whatsApps]) + const [itemHover, setItemHover] = useState(-1) const handleClose = () => { onClose(); - setSearchParam(""); - setSelectedUser(null); }; const handleSaveTicket = async e => { e.preventDefault(); if (!ticketid) return; + if (!selectedQueue) return; setLoading(true); try { let data = {}; - if (selectedUser) { - data.userId = selectedUser.id - } - if (selectedQueue && selectedQueue !== null) { data.queueId = selectedQueue - - if (!selectedUser) { - data.status = 'pending'; - data.userId = null; - } } // test del PARA APARECER NA FILA DE OUTRO ATENDENTE E O MESMO CLICAR EM ACEITAR AO INVES DE ENVIAR PARA ATENDENDO data.status = 'pending' + data.transfer = true await api.put(`/tickets/${ticketid}`, data); @@ -127,56 +96,26 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => { {i18n.t("transferTicketModal.title")} - `${option.name}`} - onChange={(e, newValue) => { - setSelectedUser(newValue); - if (newValue != null && Array.isArray(newValue.queues)) { - setQueues(newValue.queues); - } else { - setQueues(allQueues); - setSelectedQueue(''); - } - }} - options={options} - filterOptions={filterOptions} - freeSolo - autoHighlight - noOptionsText={i18n.t("transferTicketModal.noOptions")} - loading={loading} - renderInput={params => ( - setSearchParam(e.target.value)} - InputProps={{ - ...params.InputProps, - endAdornment: ( - - {loading ? ( - - ) : null} - {params.InputProps.endAdornment} - - ), - }} - /> - )} - /> {i18n.t("transferTicketModal.fieldQueueLabel")} @@ -200,8 +139,8 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => {
- + ); }; -export default TransferTicketModal; +export default TransferTicketModal; \ No newline at end of file diff --git a/frontend/src/hooks/useTickets/index.js b/frontend/src/hooks/useTickets/index.js index e02baa9..7dfe366 100644 --- a/frontend/src/hooks/useTickets/index.js +++ b/frontend/src/hooks/useTickets/index.js @@ -5,6 +5,7 @@ import api from "../../services/api"; const useTickets = ({ searchParam, + searchParamContent, pageNumber, status, date, @@ -35,6 +36,7 @@ const useTickets = ({ const { data } = await api.get("/tickets", { params: { searchParam, + searchParamContent, pageNumber, status, date, @@ -63,6 +65,7 @@ const useTickets = ({ return () => clearTimeout(delayDebounceFn); }, [ searchParam, + searchParamContent, pageNumber, status, date, diff --git a/frontend/src/pages/Contacts/index.js b/frontend/src/pages/Contacts/index.js index 44f1265..d67298b 100644 --- a/frontend/src/pages/Contacts/index.js +++ b/frontend/src/pages/Contacts/index.js @@ -37,8 +37,9 @@ import { Can } from "../../components/Can"; import apiBroker from "../../services/apiBroker"; import fileDownload from 'js-file-download' +import ContactCreateTicketModal from "../../components/ContactCreateTicketModal"; + - const reducer = (state, action) => { @@ -111,6 +112,7 @@ const Contacts = () => { const [contacts, dispatch] = useReducer(reducer, []); const [selectedContactId, setSelectedContactId] = useState(null); const [contactModalOpen, setContactModalOpen] = useState(false); + const [isCreateTicketModalOpen, setIsCreateTicketModalOpen] = useState(false) const [deletingContact, setDeletingContact] = useState(null); const [confirmOpen, setConfirmOpen] = useState(false); const [hasMore, setHasMore] = useState(false); @@ -118,17 +120,17 @@ const Contacts = () => { const [onQueueStatus, setOnQueueProcessStatus] = useState(undefined) - const [zipfile, setZipFile] = useState() - + const [zipfile, setZipFile] = useState() + async function handleChange(event) { try { - if (event.target.files[0].size > 1024 * 1024 * 4){ + if (event.target.files[0].size > 1024 * 1024 * 4) { alert('Arquivo não pode ser maior que 4 MB!') return - } + } const formData = new FormData(); formData.append("adminId", user.id); @@ -302,21 +304,30 @@ const Contacts = () => { setContactModalOpen(false); }; - const handleSaveTicket = async (contactId) => { - if (!contactId) return; - setLoading(true); - try { - const { data: ticket } = await api.post("/tickets", { - contactId: contactId, - userId: user?.id, - status: "open", - }); - history.push(`/tickets/${ticket.id}`); - } catch (err) { - toastError(err); - } - setLoading(false); - }; + const handleOpenCreateTicketModal = (contactId) => { + setSelectedContactId(contactId) + setIsCreateTicketModalOpen(true) + } + + const handleCloseCreateTicketModal = () => { + setIsCreateTicketModalOpen(false) + } + + // const handleSaveTicket = async (contactId) => { + // if (!contactId) return; + // setLoading(true); + // try { + // const { data: ticket } = await api.post("/tickets", { + // contactId: contactId, + // userId: user?.id, + // status: "open", + // }); + // history.push(`/tickets/${ticket.id}`); + // } catch (err) { + // toastError(err); + // } + // setLoading(false); + // }; const hadleEditContact = (contactId) => { setSelectedContactId(contactId); @@ -415,21 +426,21 @@ const Contacts = () => { switch (param) { case 'empty': return ( - <> + <> - + - + {/* + + + + )} + + +
+ ) +} -
- -
- - -
- -
- setSelectedQueueIds(selectedIds)} - /> - - - - - - - )} - - -
- ); -}; - -export default React.memo(WhatsAppModal); +export default React.memo(WhatsAppModal) diff --git a/frontend/src/context/Auth/AuthContext.js b/frontend/src/context/Auth/AuthContext.js index d13b342..5368f33 100644 --- a/frontend/src/context/Auth/AuthContext.js +++ b/frontend/src/context/Auth/AuthContext.js @@ -1,21 +1,22 @@ -import React, { createContext } from "react"; +import React, { createContext } from 'react' -import useAuth from "../../hooks/useAuth.js"; +import useAuth from '../../hooks/useAuth.js' -const AuthContext = createContext(); +const AuthContext = createContext() const AuthProvider = ({ children }) => { - const { loading, user, isAuth, handleLogin, handleLogout } = useAuth(); + const { loading, user, isAuth, handleLogin, handleLogout, setSetting } = + useAuth() - //{ + //{ - return ( - - {children} - - ); -}; + return ( + + {children} + + ) +} -export { AuthContext, AuthProvider }; +export { AuthContext, AuthProvider } diff --git a/frontend/src/hooks/useAuth.js/index.js b/frontend/src/hooks/useAuth.js/index.js index dc5d3d3..70ffe21 100644 --- a/frontend/src/hooks/useAuth.js/index.js +++ b/frontend/src/hooks/useAuth.js/index.js @@ -1,125 +1,158 @@ -import { useState, useEffect } from "react"; -import { useHistory } from "react-router-dom"; -import openSocket from "socket.io-client"; +import { useState, useEffect } from 'react' +import { useHistory } from 'react-router-dom' +import openSocket from 'socket.io-client' -import { toast } from "react-toastify"; +import { toast } from 'react-toastify' -import { i18n } from "../../translate/i18n"; -import api from "../../services/api"; -import toastError from "../../errors/toastError"; +import { i18n } from '../../translate/i18n' +import api from '../../services/api' +import toastError from '../../errors/toastError' const useAuth = () => { - const history = useHistory(); - const [isAuth, setIsAuth] = useState(false); - const [loading, setLoading] = useState(true); - const [user, setUser] = useState({}); + const history = useHistory() + const [isAuth, setIsAuth] = useState(false) + const [loading, setLoading] = useState(true) + const [user, setUser] = useState({}) - api.interceptors.request.use( - config => { - const token = localStorage.getItem("token"); - if (token) { - config.headers["Authorization"] = `Bearer ${JSON.parse(token)}`; - setIsAuth(true); - } - return config; - }, - error => { - Promise.reject(error); - } - ); + const [setting, setSetting] = useState({}) - api.interceptors.response.use( - response => { - return response; - }, - async error => { - const originalRequest = error.config; - if (error?.response?.status === 403 && !originalRequest._retry) { - originalRequest._retry = true; + api.interceptors.request.use( + (config) => { + const token = localStorage.getItem('token') + if (token) { + config.headers['Authorization'] = `Bearer ${JSON.parse(token)}` + setIsAuth(true) + } + return config + }, + (error) => { + Promise.reject(error) + } + ) - const { data } = await api.post("/auth/refresh_token"); - if (data) { - localStorage.setItem("token", JSON.stringify(data.token)); - api.defaults.headers.Authorization = `Bearer ${data.token}`; - } - return api(originalRequest); - } - if (error?.response?.status === 401) { - localStorage.removeItem("token"); - api.defaults.headers.Authorization = undefined; - setIsAuth(false); - } - return Promise.reject(error); - } - ); + api.interceptors.response.use( + (response) => { + return response + }, + async (error) => { + const originalRequest = error.config + if (error?.response?.status === 403 && !originalRequest._retry) { + originalRequest._retry = true - useEffect(() => { - const token = localStorage.getItem("token"); - (async () => { - if (token) { - try { - const { data } = await api.post("/auth/refresh_token"); - api.defaults.headers.Authorization = `Bearer ${data.token}`; - setIsAuth(true); - setUser(data.user); - } catch (err) { - toastError(err); - } - } - setLoading(false); - })(); - }, []); + const { data } = await api.post('/auth/refresh_token') + if (data) { + localStorage.setItem('token', JSON.stringify(data.token)) + api.defaults.headers.Authorization = `Bearer ${data.token}` + } + return api(originalRequest) + } + if (error?.response?.status === 401) { + localStorage.removeItem('token') + api.defaults.headers.Authorization = undefined + setIsAuth(false) + } + return Promise.reject(error) + } + ) - useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL); + useEffect(() => { + const token = localStorage.getItem('token') + ;(async () => { + if (token) { + try { + const { data } = await api.post('/auth/refresh_token') + api.defaults.headers.Authorization = `Bearer ${data.token}` + setIsAuth(true) + setUser(data.user) + } catch (err) { + toastError(err) + } + } + setLoading(false) + })() + }, []) - socket.on("user", data => { - if (data.action === "update" && data.user.id === user.id) { - setUser(data.user); - } - }); + useEffect(() => { + const fetchSession = async () => { + try { + const { data } = await api.get('/settings') + setSetting(data) + } catch (err) { + toastError(err) + } + } + fetchSession() + }, []) - return () => { - socket.disconnect(); - }; - }, [user]); + useEffect(() => { + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) - const handleLogin = async userData => { - setLoading(true); + socket.on('user', (data) => { + if (data.action === 'update' && data.user.id === user.id) { + setUser(data.user) + } + }) - try { - const { data } = await api.post("/auth/login", userData); - localStorage.setItem("token", JSON.stringify(data.token)); - api.defaults.headers.Authorization = `Bearer ${data.token}`; - setUser(data.user); - setIsAuth(true); - toast.success(i18n.t("auth.toasts.success")); - history.push("/tickets"); - setLoading(false); - } catch (err) { - toastError(err); - setLoading(false); - } - }; + socket.on('settings', (data) => { + if (data.action === 'update') { + setSetting((prevState) => { + const aux = [...prevState] + const settingIndex = aux.findIndex((s) => s.key === data.setting.key) + aux[settingIndex].value = data.setting.value + return aux + }) + } + }) - const handleLogout = async () => { - setLoading(true); + return () => { + socket.disconnect() + } + }, [user]) - try { - await api.delete("/auth/logout"); - setIsAuth(false); - setUser({}); - localStorage.removeItem("token"); - api.defaults.headers.Authorization = undefined; - setLoading(false); - history.push("/login"); - } catch (err) { - toastError(err); - setLoading(false); - } - }; + const handleLogin = async (userData) => { + setLoading(true) - return { isAuth, user, loading, handleLogin, handleLogout }; -}; + try { + const { data } = await api.post('/auth/login', userData) + localStorage.setItem('token', JSON.stringify(data.token)) + api.defaults.headers.Authorization = `Bearer ${data.token}` + setUser(data.user) + setIsAuth(true) + toast.success(i18n.t('auth.toasts.success')) + history.push('/tickets') + setLoading(false) + } catch (err) { + toastError(err) + setLoading(false) + } + } -export default useAuth; + const handleLogout = async () => { + setLoading(true) + + try { + await api.delete('/auth/logout') + setIsAuth(false) + setUser({}) + localStorage.removeItem('token') + api.defaults.headers.Authorization = undefined + setLoading(false) + history.push('/login') + } catch (err) { + toastError(err) + setLoading(false) + } + } + + return { + isAuth, + user, + loading, + handleLogin, + handleLogout, + setting, + setSetting, + } +} + +export default useAuth diff --git a/frontend/src/pages/Connections/index.js b/frontend/src/pages/Connections/index.js index aa04cc9..a8c5fe4 100644 --- a/frontend/src/pages/Connections/index.js +++ b/frontend/src/pages/Connections/index.js @@ -1,368 +1,371 @@ -import React, { useState, useCallback, useEffect, useContext } from "react"; -import { toast } from "react-toastify"; -import { format, parseISO } from "date-fns"; +import React, { useState, useCallback, useEffect, useContext } from 'react' +import { toast } from 'react-toastify' +import { format, parseISO } from 'date-fns' -import openSocket from "socket.io-client"; +import openSocket from 'socket.io-client' -import { makeStyles } from "@material-ui/core/styles"; -import { green } from "@material-ui/core/colors"; +import { makeStyles } from '@material-ui/core/styles' +import { green } from '@material-ui/core/colors' import { - Button, - TableBody, - TableRow, - TableCell, - IconButton, - Table, - TableHead, - Paper, - Tooltip, - Typography, - CircularProgress, -} from "@material-ui/core"; + Button, + TableBody, + TableRow, + TableCell, + IconButton, + Table, + TableHead, + Paper, + Tooltip, + Typography, + CircularProgress, +} from '@material-ui/core' import { - Edit, - CheckCircle, - SignalCellularConnectedNoInternet2Bar, - SignalCellularConnectedNoInternet0Bar, - SignalCellular4Bar, - CropFree, - DeleteOutline, - // Restore -} from "@material-ui/icons"; + Edit, + CheckCircle, + SignalCellularConnectedNoInternet2Bar, + SignalCellularConnectedNoInternet0Bar, + SignalCellular4Bar, + CropFree, + DeleteOutline, + // Restore +} from '@material-ui/icons' -import MainContainer from "../../components/MainContainer"; -import MainHeader from "../../components/MainHeader"; -import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper"; -import Title from "../../components/Title"; -import TableRowSkeleton from "../../components/TableRowSkeleton"; +import MainContainer from '../../components/MainContainer' +import MainHeader from '../../components/MainHeader' +import MainHeaderButtonsWrapper from '../../components/MainHeaderButtonsWrapper' +import Title from '../../components/Title' +import TableRowSkeleton from '../../components/TableRowSkeleton' -import api from "../../services/api"; -import WhatsAppModal from "../../components/WhatsAppModal"; -import ConfirmationModal from "../../components/ConfirmationModal"; -import QrcodeModal from "../../components/QrcodeModal"; -import { i18n } from "../../translate/i18n"; -import { WhatsAppsContext } from "../../context/WhatsApp/WhatsAppsContext"; -import toastError from "../../errors/toastError"; +import api from '../../services/api' +import WhatsAppModal from '../../components/WhatsAppModal' +import ConfirmationModal from '../../components/ConfirmationModal' +import QrcodeModal from '../../components/QrcodeModal' +import { i18n } from '../../translate/i18n' +import { WhatsAppsContext } from '../../context/WhatsApp/WhatsAppsContext' +import toastError from '../../errors/toastError' //-------- -import { AuthContext } from "../../context/Auth/AuthContext"; -import { Can } from "../../components/Can"; +import { AuthContext } from '../../context/Auth/AuthContext' +import { Can } from '../../components/Can' - -const useStyles = makeStyles(theme => ({ - mainPaper: { - flex: 1, - padding: theme.spacing(1), - overflowY: "scroll", - ...theme.scrollbarStyles, - }, - customTableCell: { - display: "flex", - alignItems: "center", - justifyContent: "center", - }, - tooltip: { - backgroundColor: "#f5f5f9", - color: "rgba(0, 0, 0, 0.87)", - fontSize: theme.typography.pxToRem(14), - border: "1px solid #dadde9", - maxWidth: 450, - }, - tooltipPopper: { - textAlign: "center", - }, - buttonProgress: { - color: green[500], - }, -})); +const useStyles = makeStyles((theme) => ({ + mainPaper: { + flex: 1, + padding: theme.spacing(1), + overflowY: 'scroll', + ...theme.scrollbarStyles, + }, + customTableCell: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + tooltip: { + backgroundColor: '#f5f5f9', + color: 'rgba(0, 0, 0, 0.87)', + fontSize: theme.typography.pxToRem(14), + border: '1px solid #dadde9', + maxWidth: 450, + }, + tooltipPopper: { + textAlign: 'center', + }, + buttonProgress: { + color: green[500], + }, +})) const CustomToolTip = ({ title, content, children }) => { - const classes = useStyles(); + const classes = useStyles() - return ( - - - {title} - - {content && {content}} - - } - > - {children} - - ); -}; + return ( + + + {title} + + {content && {content}} + + } + > + {children} + + ) +} const Connections = () => { + //-------- + const { user } = useContext(AuthContext) - //-------- - const { user } = useContext(AuthContext); + const classes = useStyles() + const { whatsApps, loading } = useContext(WhatsAppsContext) + const [whatsAppModalOpen, setWhatsAppModalOpen] = useState(false) + const [qrModalOpen, setQrModalOpen] = useState(false) + const [selectedWhatsApp, setSelectedWhatsApp] = useState(null) + const [confirmModalOpen, setConfirmModalOpen] = useState(false) - const classes = useStyles(); + const [diskSpaceInfo, setDiskSpaceInfo] = useState({}) - const { whatsApps, loading } = useContext(WhatsAppsContext); - const [whatsAppModalOpen, setWhatsAppModalOpen] = useState(false); - const [qrModalOpen, setQrModalOpen] = useState(false); - const [selectedWhatsApp, setSelectedWhatsApp] = useState(null); - const [confirmModalOpen, setConfirmModalOpen] = useState(false); + const [disabled, setDisabled] = useState(true) - const [diskSpaceInfo, setDiskSpaceInfo] = useState({}); + const [settings, setSettings] = useState([]) - const [disabled, setDisabled] = useState(true); + const [buttons, setClicks] = useState([]) - const [buttons, setClicks] = useState([]) + const confirmationModalInitialState = { + action: '', + title: '', + message: '', + whatsAppId: '', + open: false, + } + const [confirmModalInfo, setConfirmModalInfo] = useState( + confirmationModalInitialState + ) + useEffect(() => { + const fetchSession = async () => { + try { + const { data } = await api.get('/settings') + setSettings(data) + } catch (err) { + toastError(err) + } + } + fetchSession() + }, []) - const confirmationModalInitialState = { - action: "", - title: "", - message: "", - whatsAppId: "", - open: false, - }; - const [confirmModalInfo, setConfirmModalInfo] = useState( - confirmationModalInitialState - ); + const getSettingValue = (key) => { + const { value } = settings.find((s) => s.key === key) - const handleStartWhatsAppSession = async whatsAppId => { - try { - await api.post(`/whatsappsession/${whatsAppId}`); - } catch (err) { - toastError(err); - } - }; + return value + } + const handleStartWhatsAppSession = async (whatsAppId) => { + try { + await api.post(`/whatsappsession/${whatsAppId}`) + } catch (err) { + toastError(err) + } + } + const handleRestartWhatsAppSession = async (whatsapp) => { + try { + whatsapp.disabled = true + setClicks([...buttons, whatsapp.id]) - const handleRestartWhatsAppSession = async whatsapp => { - try { + function enable_button(whatsappId) { + setClicks((buttons) => + buttons.filter((id) => { + return +id !== +whatsappId + }) + ) + } - whatsapp.disabled = true + setTimeout(enable_button, 25000, whatsapp.id) - setClicks([...buttons, whatsapp.id]) + await api.post(`/restartwhatsappsession/${whatsapp.id}`) + } catch (err) { + toastError(err) + } + } - function enable_button(whatsappId) { + useEffect(() => { + // whatsApps.map((e) => { + // if (buttons.includes(e.id)) { + // e.disabled = true + // } + // }) - setClicks(buttons => buttons.filter(id => { return +id !== +whatsappId }),); + for (let i = 0; i < whatsApps.length; i++) { + if (buttons.includes(whatsApps[i].id)) { + whatsApps[i].disabled = true + } + } + }, [whatsApps, buttons]) - } + const handleRequestNewQrCode = async (whatsAppId) => { + try { + await api.put(`/whatsappsession/${whatsAppId}`) + } catch (err) { + toastError(err) + } + } - setTimeout(enable_button, 25000, whatsapp.id) + const handleOpenWhatsAppModal = () => { + setSelectedWhatsApp(null) + setWhatsAppModalOpen(true) + } - await api.post(`/restartwhatsappsession/${whatsapp.id}`); + const handleCloseWhatsAppModal = useCallback(() => { + setWhatsAppModalOpen(false) + setSelectedWhatsApp(null) + }, [setSelectedWhatsApp, setWhatsAppModalOpen]) - } catch (err) { - toastError(err); - } - }; + const handleOpenQrModal = (whatsApp) => { + setSelectedWhatsApp(whatsApp) + setQrModalOpen(true) + } + const handleCloseQrModal = useCallback(() => { + setSelectedWhatsApp(null) + setQrModalOpen(false) + }, [setQrModalOpen, setSelectedWhatsApp]) - useEffect(() => { + const handleEditWhatsApp = (whatsApp) => { + setSelectedWhatsApp(whatsApp) + setWhatsAppModalOpen(true) + } - // whatsApps.map((e) => { - // if (buttons.includes(e.id)) { - // e.disabled = true - // } - // }) + const handleOpenConfirmationModal = (action, whatsAppId) => { + if (action === 'disconnect') { + setConfirmModalInfo({ + action: action, + title: i18n.t('connections.confirmationModal.disconnectTitle'), + message: i18n.t('connections.confirmationModal.disconnectMessage'), + whatsAppId: whatsAppId, + }) + } + if (action === 'delete') { + setConfirmModalInfo({ + action: action, + title: i18n.t('connections.confirmationModal.deleteTitle'), + message: i18n.t('connections.confirmationModal.deleteMessage'), + whatsAppId: whatsAppId, + }) + } + setConfirmModalOpen(true) + } - for (let i = 0; i < whatsApps.length; i++) { - if (buttons.includes(whatsApps[i].id)) { - whatsApps[i].disabled = true - } - } + const handleSubmitConfirmationModal = async () => { + if (confirmModalInfo.action === 'disconnect') { + try { + await api.delete(`/whatsappsession/${confirmModalInfo.whatsAppId}`) + } catch (err) { + toastError(err) + } + } - }, [whatsApps, buttons]) + if (confirmModalInfo.action === 'delete') { + try { + await api.delete(`/whatsapp/${confirmModalInfo.whatsAppId}`) + toast.success(i18n.t('connections.toasts.deleted')) + } catch (err) { + toastError(err) + } + } + setConfirmModalInfo(confirmationModalInitialState) + } - const handleRequestNewQrCode = async whatsAppId => { - try { - await api.put(`/whatsappsession/${whatsAppId}`); - } catch (err) { - toastError(err); - } - }; + const renderActionButtons = (whatsApp) => { + return ( + ( + <> + {whatsApp.status === 'qrcode' && ( + + )} + {whatsApp.status === 'DISCONNECTED' && ( + <> + {' '} + + + )} + {(whatsApp.status === 'CONNECTED' || + whatsApp.status === 'PAIRING' || + whatsApp.status === 'TIMEOUT') && ( + + )} + {whatsApp.status === 'OPENING' && ( + + )} + + )} + /> + ) + } - const handleOpenWhatsAppModal = () => { - setSelectedWhatsApp(null); - setWhatsAppModalOpen(true); - }; + const renderStatusToolTips = (whatsApp) => { + return ( +
+ {whatsApp.status === 'DISCONNECTED' && ( + + + + )} + {whatsApp.status === 'OPENING' && ( + + )} + {whatsApp.status === 'qrcode' && ( + + + + )} + {whatsApp.status === 'CONNECTED' && ( + + + + )} + {(whatsApp.status === 'TIMEOUT' || whatsApp.status === 'PAIRING') && ( + + + + )} - const handleCloseWhatsAppModal = useCallback(() => { - setWhatsAppModalOpen(false); - setSelectedWhatsApp(null); - }, [setSelectedWhatsApp, setWhatsAppModalOpen]); - - const handleOpenQrModal = whatsApp => { - setSelectedWhatsApp(whatsApp); - setQrModalOpen(true); - }; - - const handleCloseQrModal = useCallback(() => { - setSelectedWhatsApp(null); - setQrModalOpen(false); - }, [setQrModalOpen, setSelectedWhatsApp]); - - const handleEditWhatsApp = whatsApp => { - setSelectedWhatsApp(whatsApp); - setWhatsAppModalOpen(true); - }; - - const handleOpenConfirmationModal = (action, whatsAppId) => { - if (action === "disconnect") { - setConfirmModalInfo({ - action: action, - title: i18n.t("connections.confirmationModal.disconnectTitle"), - message: i18n.t("connections.confirmationModal.disconnectMessage"), - whatsAppId: whatsAppId, - }); - } - - if (action === "delete") { - setConfirmModalInfo({ - action: action, - title: i18n.t("connections.confirmationModal.deleteTitle"), - message: i18n.t("connections.confirmationModal.deleteMessage"), - whatsAppId: whatsAppId, - }); - } - setConfirmModalOpen(true); - }; - - const handleSubmitConfirmationModal = async () => { - if (confirmModalInfo.action === "disconnect") { - try { - await api.delete(`/whatsappsession/${confirmModalInfo.whatsAppId}`); - } catch (err) { - toastError(err); - } - } - - if (confirmModalInfo.action === "delete") { - try { - await api.delete(`/whatsapp/${confirmModalInfo.whatsAppId}`); - toast.success(i18n.t("connections.toasts.deleted")); - } catch (err) { - toastError(err); - } - } - - setConfirmModalInfo(confirmationModalInitialState); - }; - - const renderActionButtons = whatsApp => { - return ( - - ( - - <> - {whatsApp.status === "qrcode" && ( - - )} - {whatsApp.status === "DISCONNECTED" && ( - - <> - {" "} - - - )} - {(whatsApp.status === "CONNECTED" || - whatsApp.status === "PAIRING" || - whatsApp.status === "TIMEOUT") && ( - - )} - {whatsApp.status === "OPENING" && ( - - )} - - - )} - /> - - ); - }; - - const renderStatusToolTips = whatsApp => { - return ( -
- {whatsApp.status === "DISCONNECTED" && ( - - - - )} - {whatsApp.status === "OPENING" && ( - - )} - {whatsApp.status === "qrcode" && ( - - - - )} - {whatsApp.status === "CONNECTED" && ( - - - - )} - {(whatsApp.status === "TIMEOUT" || whatsApp.status === "PAIRING") && ( - - - - )} - - {/* {whatsApp.status === "RESTORING" && ( + {/* {whatsApp.status === "RESTORING" && ( { )} */} -
- ); - }; - - - - - useEffect(() => { - - const delayDebounceFn = setTimeout(() => { - - const fetchQueries = async () => { - try { - - await api.post(`/restartwhatsappsession/0`, { params: { status: 'status' }, }); - - setDisabled(false) - - setClicks(buttons => buttons.map((e) => { return { id: e.id, disabled: false } })) - - } catch (err) { - console.log(err); - } - }; - - fetchQueries(); - - }, 500); - return () => clearTimeout(delayDebounceFn); - - }, []); - - - useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL); - - socket.on("diskSpaceMonit", data => { - if (data.action === "update") { - - setDiskSpaceInfo(data.diskSpace) - - } - }); - - return () => { - socket.disconnect(); - }; - }, []); - - return ( - - ( - - - - {confirmModalInfo.message} - - - - - - - - - - - {i18n.t("connections.title")} - - - ( - - )} - /> - - - - - - - - <> - - ( - <> - - - - - Size - - - Used - - - Available - - - Use% - - - - - - - {diskSpaceInfo.size} - {diskSpaceInfo.used} - {diskSpaceInfo.available} - {diskSpaceInfo.use} - - -
-
- - )} - /> - - - - - - - {i18n.t("connections.table.name")} - - - - {i18n.t("connections.table.status")} - - - ( - - {i18n.t("connections.table.session")} - - )} - /> - - - - - ( - - Restore - - )} - /> - - - - ( - - Session MB - - )} - /> - - - - - - {i18n.t("connections.table.lastUpdate")} - - - {i18n.t("connections.table.default")} - - - {i18n.t("connections.table.actions")} - - - - - {loading ? ( - - ) : ( - <> - {whatsApps?.length > 0 && - whatsApps.map(whatsApp => ( - - {whatsApp.name} - - - {renderStatusToolTips(whatsApp)} - - - ( - - {renderActionButtons(whatsApp)} - - )} - /> - - - - - - - ( - - - - - - - )} - /> - - - - ( - - -
{whatsApp.sessionSize}
-
-
- )} - /> - - - - - - - {format(parseISO(whatsApp.updatedAt), "dd/MM/yy HH:mm")} - - - - - {whatsApp.isDefault && ( -
- -
- )} -
- - - - - ( - handleEditWhatsApp(whatsApp)} - > - - - )} - /> - - - ( - { - handleOpenConfirmationModal("delete", whatsApp.id); - }} - > - - - )} - /> - - -
- ))} - - )} -
-
- - -
-
- )} - /> - - ); -}; - -export default Connections; +
+ ) + } + + useEffect(() => { + const delayDebounceFn = setTimeout(() => { + const fetchQueries = async () => { + try { + await api.post(`/restartwhatsappsession/0`, { + params: { status: 'status' }, + }) + + setDisabled(false) + + setClicks((buttons) => + buttons.map((e) => { + return { id: e.id, disabled: false } + }) + ) + } catch (err) { + console.log(err) + } + } + + fetchQueries() + }, 500) + return () => clearTimeout(delayDebounceFn) + }, []) + + useEffect(() => { + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + + socket.on('diskSpaceMonit', (data) => { + if (data.action === 'update') { + setDiskSpaceInfo(data.diskSpace) + } + }) + + socket.on('settings', (data) => { + if (data.action === 'update') { + setSettings((prevState) => { + const aux = [...prevState] + const settingIndex = aux.findIndex((s) => s.key === data.setting.key) + aux[settingIndex].value = data.setting.value + return aux + }) + } + }) + + return () => { + socket.disconnect() + } + }, []) + + return ( + ( + + + {confirmModalInfo.message} + + + + + + + + {i18n.t('connections.title')} + + + ( + + )} + /> + + + + + <> + ( + <> + + + + Size + Used + Available + Use% + + + + + + + {diskSpaceInfo.size} + + + {diskSpaceInfo.used} + + + {diskSpaceInfo.available} + + + {diskSpaceInfo.use} + + + +
+
+ + )} + /> + + + + + + {i18n.t('connections.table.name')} + + + + {i18n.t('connections.table.status')} + + + ( + + {i18n.t('connections.table.session')} + + )} + /> + + Restore} + /> + + ( + Session MB + )} + /> + + + {i18n.t('connections.table.lastUpdate')} + + + {i18n.t('connections.table.default')} + + + {i18n.t('connections.table.actions')} + + + + + {loading ? ( + + ) : ( + <> + {whatsApps?.length > 0 && + whatsApps.map((whatsApp) => ( + + + {whatsApp.name} + + + + {renderStatusToolTips(whatsApp)} + + + ( + + {renderActionButtons(whatsApp)} + + )} + /> + + ( + + + + )} + /> + + ( + + +
{whatsApp.sessionSize}
+
+
+ )} + /> + + + {format( + parseISO(whatsApp.updatedAt), + 'dd/MM/yy HH:mm' + )} + + + + {whatsApp.isDefault && ( +
+ +
+ )} +
+ + + ( + // disabled={ + // whatsApp.disabled || disabled + // ? true + // : false + // } +
+ {(settings && + settings.length > 0 && + getSettingValue('editURA') && + getSettingValue('editURA') === + 'enabled') | + (user.profile === 'master') ? ( + + handleEditWhatsApp(whatsApp) + } + > + + + ) : ( +
+ )} +
+ )} + /> + + ( + { + handleOpenConfirmationModal( + 'delete', + whatsApp.id + ) + }} + > + + + )} + /> +
+
+ ))} + + )} +
+
+ +
+
+ )} + /> + ) +} + +export default Connections diff --git a/frontend/src/pages/Queues/index.js b/frontend/src/pages/Queues/index.js index 4df3e78..99b6eab 100644 --- a/frontend/src/pages/Queues/index.js +++ b/frontend/src/pages/Queues/index.js @@ -1,6 +1,6 @@ -import React, { useEffect, useReducer, useState, useContext } from "react"; +import React, { useEffect, useReducer, useState, useContext } from 'react' -import openSocket from "socket.io-client"; +import openSocket from 'socket.io-client' import { Button, @@ -13,155 +13,186 @@ import { TableHead, TableRow, Typography, -} from "@material-ui/core"; +} from '@material-ui/core' -import MainContainer from "../../components/MainContainer"; -import MainHeader from "../../components/MainHeader"; -import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper"; -import TableRowSkeleton from "../../components/TableRowSkeleton"; -import Title from "../../components/Title"; -import { i18n } from "../../translate/i18n"; -import toastError from "../../errors/toastError"; -import api from "../../services/api"; -import { DeleteOutline, Edit } from "@material-ui/icons"; -import QueueModal from "../../components/QueueModal"; -import { toast } from "react-toastify"; -import ConfirmationModal from "../../components/ConfirmationModal"; +import MainContainer from '../../components/MainContainer' +import MainHeader from '../../components/MainHeader' +import MainHeaderButtonsWrapper from '../../components/MainHeaderButtonsWrapper' +import TableRowSkeleton from '../../components/TableRowSkeleton' +import Title from '../../components/Title' +import { i18n } from '../../translate/i18n' +import toastError from '../../errors/toastError' +import api from '../../services/api' +import { DeleteOutline, Edit } from '@material-ui/icons' +import QueueModal from '../../components/QueueModal' +import { toast } from 'react-toastify' +import ConfirmationModal from '../../components/ConfirmationModal' -import { AuthContext } from "../../context/Auth/AuthContext"; -import { Can } from "../../components/Can"; +import { AuthContext } from '../../context/Auth/AuthContext' +import { Can } from '../../components/Can' const useStyles = makeStyles((theme) => ({ mainPaper: { flex: 1, padding: theme.spacing(1), - overflowY: "scroll", + overflowY: 'scroll', ...theme.scrollbarStyles, }, customTableCell: { - display: "flex", - alignItems: "center", - justifyContent: "center", + display: 'flex', + alignItems: 'center', + justifyContent: 'center', }, -})); +})) const reducer = (state, action) => { - if (action.type === "LOAD_QUEUES") { - const queues = action.payload; - const newQueues = []; + if (action.type === 'LOAD_QUEUES') { + const queues = action.payload + const newQueues = [] queues.forEach((queue) => { - const queueIndex = state.findIndex((q) => q.id === queue.id); + const queueIndex = state.findIndex((q) => q.id === queue.id) if (queueIndex !== -1) { - state[queueIndex] = queue; + state[queueIndex] = queue } else { - newQueues.push(queue); + newQueues.push(queue) } - }); + }) - return [...state, ...newQueues]; + return [...state, ...newQueues] } - if (action.type === "UPDATE_QUEUES") { - const queue = action.payload; - const queueIndex = state.findIndex((u) => u.id === queue.id); + if (action.type === 'UPDATE_QUEUES') { + const queue = action.payload + const queueIndex = state.findIndex((u) => u.id === queue.id) if (queueIndex !== -1) { - state[queueIndex] = queue; - return [...state]; + state[queueIndex] = queue + return [...state] } else { - return [queue, ...state]; + return [queue, ...state] } } - if (action.type === "DELETE_QUEUE") { - const queueId = action.payload; - const queueIndex = state.findIndex((q) => q.id === queueId); + if (action.type === 'DELETE_QUEUE') { + const queueId = action.payload + const queueIndex = state.findIndex((q) => q.id === queueId) if (queueIndex !== -1) { - state.splice(queueIndex, 1); + state.splice(queueIndex, 1) } - return [...state]; + return [...state] } - if (action.type === "RESET") { - return []; + if (action.type === 'RESET') { + return [] } -}; +} const Queues = () => { - const classes = useStyles(); + const classes = useStyles() - const { user } = useContext(AuthContext); + const { user } = useContext(AuthContext) - const [queues, dispatch] = useReducer(reducer, []); - const [loading, setLoading] = useState(false); + const [queues, dispatch] = useReducer(reducer, []) + const [loading, setLoading] = useState(false) - const [queueModalOpen, setQueueModalOpen] = useState(false); - const [selectedQueue, setSelectedQueue] = useState(null); - const [confirmModalOpen, setConfirmModalOpen] = useState(false); + const [queueModalOpen, setQueueModalOpen] = useState(false) + const [selectedQueue, setSelectedQueue] = useState(null) + const [confirmModalOpen, setConfirmModalOpen] = useState(false) + + const [settings, setSettings] = useState([]) useEffect(() => { - (async () => { - setLoading(true); + ;(async () => { + setLoading(true) try { - const { data } = await api.get("/queue"); - dispatch({ type: "LOAD_QUEUES", payload: data }); + const { data } = await api.get('/queue') + dispatch({ type: 'LOAD_QUEUES', payload: data }) - setLoading(false); + setLoading(false) } catch (err) { - toastError(err); - setLoading(false); + toastError(err) + setLoading(false) } - })(); - }, []); + })() + }, []) useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL); + const fetchSession = async () => { + try { + const { data } = await api.get('/settings') + setSettings(data) + } catch (err) { + toastError(err) + } + } + fetchSession() + }, []) - socket.on("queue", (data) => { - if (data.action === "update" || data.action === "create") { - dispatch({ type: "UPDATE_QUEUES", payload: data.queue }); + const getSettingValue = (key) => { + const { value } = settings.find((s) => s.key === key) + + return value + } + + useEffect(() => { + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + + socket.on('queue', (data) => { + if (data.action === 'update' || data.action === 'create') { + dispatch({ type: 'UPDATE_QUEUES', payload: data.queue }) } - if (data.action === "delete") { - dispatch({ type: "DELETE_QUEUE", payload: data.queueId }); + if (data.action === 'delete') { + dispatch({ type: 'DELETE_QUEUE', payload: data.queueId }) } - }); + }) + + socket.on('settings', (data) => { + if (data.action === 'update') { + setSettings((prevState) => { + const aux = [...prevState] + const settingIndex = aux.findIndex((s) => s.key === data.setting.key) + aux[settingIndex].value = data.setting.value + return aux + }) + } + }) return () => { - socket.disconnect(); - }; - }, []); + socket.disconnect() + } + }, []) const handleOpenQueueModal = () => { - setQueueModalOpen(true); - setSelectedQueue(null); - }; + setQueueModalOpen(true) + setSelectedQueue(null) + } const handleCloseQueueModal = () => { - setQueueModalOpen(false); - setSelectedQueue(null); - }; + setQueueModalOpen(false) + setSelectedQueue(null) + } const handleEditQueue = (queue) => { - setSelectedQueue(queue); - setQueueModalOpen(true); - }; + setSelectedQueue(queue) + setQueueModalOpen(true) + } const handleCloseConfirmationModal = () => { - setConfirmModalOpen(false); - setSelectedQueue(null); - }; + setConfirmModalOpen(false) + setSelectedQueue(null) + } const handleDeleteQueue = async (queueId) => { try { - await api.delete(`/queue/${queueId}`); - toast.success(i18n.t("Queue deleted successfully!")); + await api.delete(`/queue/${queueId}`) + toast.success(i18n.t('Queue deleted successfully!')) } catch (err) { - toastError(err); + toastError(err) } - setSelectedQueue(null); - }; + setSelectedQueue(null) + } return ( { handleDeleteQueue(selectedQueue.id)} > - {i18n.t("queues.confirmationModal.deleteMessage")} + {i18n.t('queues.confirmationModal.deleteMessage')} { queueId={selectedQueue?.id} /> - {i18n.t("queues.title")} - + {i18n.t('queues.title')} { color="primary" onClick={handleOpenQueueModal} > - {i18n.t("queues.buttons.add")} + {i18n.t('queues.buttons.add')} )} /> - - - {i18n.t("queues.table.name")} + {i18n.t('queues.table.name')} - {i18n.t("queues.table.color")} + {i18n.t('queues.table.color')} - {i18n.t("queues.table.greeting")} + {i18n.t('queues.table.greeting')} - {i18n.t("queues.table.actions")} + {i18n.t('queues.table.actions')} @@ -238,7 +267,7 @@ const Queues = () => { backgroundColor: queue.color, width: 60, height: 20, - alignSelf: "center", + alignSelf: 'center', }} /> @@ -246,7 +275,7 @@ const Queues = () => {
@@ -255,25 +284,44 @@ const Queues = () => {
- - - - ( - handleEditQueue(queue)} +
- - + {(settings && + settings.length > 0 && + getSettingValue('editQueue') && + getSettingValue('editQueue') === 'enabled') | + (user.profile === 'master') ? ( + handleEditQueue(queue)} + > + + + ) : ( +
+ )} +
+ + // handleEditQueue(queue)} + // > + // + // )} /> - { { - setSelectedQueue(queue); - setConfirmModalOpen(true); + setSelectedQueue(queue) + setConfirmModalOpen(true) }} > )} /> -
))} @@ -301,7 +348,7 @@ const Queues = () => { )} /> - ); -}; + ) +} -export default Queues; +export default Queues diff --git a/frontend/src/pages/Settings/index.js b/frontend/src/pages/Settings/index.js index 2912d6d..ab79873 100644 --- a/frontend/src/pages/Settings/index.js +++ b/frontend/src/pages/Settings/index.js @@ -1,190 +1,207 @@ -import React, { useState, useEffect, useContext} from "react"; -import openSocket from "socket.io-client"; +import React, { useState, useEffect, useContext } from 'react' +import openSocket from 'socket.io-client' -import { makeStyles } from "@material-ui/core/styles"; -import Paper from "@material-ui/core/Paper"; -import Typography from "@material-ui/core/Typography"; -import Container from "@material-ui/core/Container"; -import Select from "@material-ui/core/Select"; -import { toast } from "react-toastify"; +import { makeStyles } from '@material-ui/core/styles' +import Paper from '@material-ui/core/Paper' +import Typography from '@material-ui/core/Typography' +import Container from '@material-ui/core/Container' +import Select from '@material-ui/core/Select' +import { toast } from 'react-toastify' -import api from "../../services/api"; -import { i18n } from "../../translate/i18n.js"; -import toastError from "../../errors/toastError"; +import api from '../../services/api' +import { i18n } from '../../translate/i18n.js' +import toastError from '../../errors/toastError' //-------- -import { AuthContext } from "../../context/Auth/AuthContext"; -import { Can } from "../../components/Can"; +import { AuthContext } from '../../context/Auth/AuthContext' + +import { Can } from '../../components/Can' // import Button from "@material-ui/core/Button"; -const useStyles = makeStyles(theme => ({ - root: { - display: "flex", - alignItems: "center", - padding: theme.spacing(4), - }, +const useStyles = makeStyles((theme) => ({ + root: { + display: 'flex', + alignItems: 'center', + padding: theme.spacing(4), + }, - paper: { - padding: theme.spacing(2), - display: "flex", - alignItems: "center", - }, + paper: { + padding: theme.spacing(2), + display: 'flex', + alignItems: 'center', + }, - settingOption: { - marginLeft: "auto", - }, - margin: { - margin: theme.spacing(1), - }, -})); + settingOption: { + marginLeft: 'auto', + }, + margin: { + margin: theme.spacing(1), + }, +})) const Settings = () => { - const classes = useStyles(); + const classes = useStyles() - //-------- - const { user } = useContext(AuthContext); + //-------- + const { user } = useContext(AuthContext) - const [settings, setSettings] = useState([]); + const [settings, setSettings] = useState([]) - useEffect(() => { - const fetchSession = async () => { - try { - const { data } = await api.get("/settings"); - setSettings(data); - } catch (err) { - toastError(err); - } - }; - fetchSession(); - }, []); + useEffect(() => { + const fetchSession = async () => { + try { + const { data } = await api.get('/settings') + setSettings(data) + } catch (err) { + toastError(err) + } + } + fetchSession() + }, []) - useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL); + useEffect(() => { + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) - socket.on("settings", data => { - if (data.action === "update") { - setSettings(prevState => { - const aux = [...prevState]; - const settingIndex = aux.findIndex(s => s.key === data.setting.key); - aux[settingIndex].value = data.setting.value; - return aux; - }); - } - }); + socket.on('settings', (data) => { + console.log('settings updated ----------------------------') - return () => { - socket.disconnect(); - }; - }, []); + if (data.action === 'update') { + setSettings((prevState) => { + const aux = [...prevState] + const settingIndex = aux.findIndex((s) => s.key === data.setting.key) + aux[settingIndex].value = data.setting.value + return aux + }) + } + }) - const handleChangeSetting = async e => { - const selectedValue = e.target.value; - const settingKey = e.target.name; + return () => { + socket.disconnect() + } + }, []) - try { - await api.put(`/settings/${settingKey}`, { - value: selectedValue, - }); - toast.success(i18n.t("settings.success")); - } catch (err) { - toastError(err); - } - }; + useEffect(() => { + console.log('------> settings: ', settings) + }, [settings]) - const getSettingValue = key => { - const { value } = settings.find(s => s.key === key); - return value; - }; + const handleChangeSetting = async (e) => { + const selectedValue = e.target.value + const settingKey = e.target.name + try { + await api.put(`/settings/${settingKey}`, { + value: selectedValue, + }) + toast.success(i18n.t('settings.success')) + } catch (err) { + toastError(err) + } + } - // const handleEdit = () => { - - // - - // } + const getSettingValue = (key) => { + const { value } = settings.find((s) => s.key === key) - return ( + return value + } + return ( + ( +
+
+ + + {i18n.t('settings.title')} + - ( + + + {i18n.t('settings.settings.userCreation.name')} + -
+ + + +
-
- - - {i18n.t("settings.title")} - - - - - {i18n.t("settings.settings.userCreation.name")} - - - - - -
- +
+ + + Editar ura - {/*
- - - Application name - - - + + + +
- - Estudio Face - +
+ + + Editar fila + + + +
+
+ )} + /> + ) +} - - -
- -
-
*/} - -
- - )} - /> - - ); -}; - -export default Settings; +export default Settings diff --git a/frontend/src/rules.js b/frontend/src/rules.js index 2ea72e4..1b7c1ad 100644 --- a/frontend/src/rules.js +++ b/frontend/src/rules.js @@ -1,59 +1,58 @@ const rules = { - user: { - static: [], - }, + user: { + static: [], + }, - admin: { - static: [ - //"show-icon-edit-whatsapp", + admin: { + static: [ + 'show-icon-edit-whatsapp', + 'show-icon-edit-queue', - "drawer-admin-items:view", - "tickets-manager:showall", - "user-modal:editProfile", - "user-modal:editQueues", - "ticket-options:deleteTicket", - "contacts-page:deleteContact", - "contacts-page:import-csv-contacts", - "connections-view:show", - "dashboard-view:show", - "queues-view:show", - "user-view:show", - "ticket-report:show", - - ], - }, + 'drawer-admin-items:view', + 'tickets-manager:showall', + 'user-modal:editProfile', + 'user-modal:editQueues', + 'ticket-options:deleteTicket', + 'contacts-page:deleteContact', + 'contacts-page:import-csv-contacts', + 'connections-view:show', + 'dashboard-view:show', + 'queues-view:show', + 'user-view:show', + 'ticket-report:show', + ], + }, - master: { - static: [ + master: { + static: [ + 'url-remote-session:show', + 'show-icon-edit-whatsapp', + 'show-icon-add-queue', + 'show-icon-edit-queue', + 'show-icon-delete-queue', + 'space-disk-info:show', - "show-icon-edit-whatsapp", - "show-icon-add-queue", - "show-icon-edit-queue", - "show-icon-delete-queue", - "space-disk-info:show", - + 'drawer-admin-items:view', + 'tickets-manager:showall', + 'user-modal:editProfile', + 'user-modal:editQueues', + 'ticket-options:deleteTicket', + 'contacts-page:deleteContact', + 'contacts-page:import-contacts', + 'contacts-page:import-csv-contacts', + 'connections-view:show', + 'dashboard-view:show', + 'queues-view:show', + 'user-view:show', + 'settings-view:show', + 'btn-add-user', + 'icon-remove-user', + 'btn-add-whatsapp', + 'btn-remove-whatsapp', + 'ticket-report:show', + 'connection-button:show', + ], + }, +} - "drawer-admin-items:view", - "tickets-manager:showall", - "user-modal:editProfile", - "user-modal:editQueues", - "ticket-options:deleteTicket", - "contacts-page:deleteContact", - "contacts-page:import-contacts", - "contacts-page:import-csv-contacts", - "connections-view:show", - "dashboard-view:show", - "queues-view:show", - "user-view:show", - "settings-view:show", - "btn-add-user", - "icon-remove-user", - "btn-add-whatsapp", - "btn-remove-whatsapp", - "ticket-report:show", - "connection-button:show" - ], - }, -}; - -export default rules; +export default rules