diff --git a/backend/package.json b/backend/package.json index d4a7105..6d2d0d1 100644 --- a/backend/package.json +++ b/backend/package.json @@ -17,10 +17,12 @@ "license": "MIT", "dependencies": { "@sentry/node": "^5.29.2", + "@socket.io/redis-adapter": "^7.2.0", "@types/fluent-ffmpeg": "^2.1.21", "@types/pino": "^6.3.4", "axios": "^1.2.3", "bcryptjs": "^2.4.3", + "compression": "^1.7.4", "cookie-parser": "^1.4.5", "cors": "^2.8.5", "cpf-cnpj-validator": "^1.0.3", @@ -42,12 +44,13 @@ "pino": "^6.9.0", "pino-pretty": "^9.1.1", "qrcode-terminal": "^0.12.0", + "redis": "^4.6.13", "reflect-metadata": "^0.1.13", "sequelize": "^5.22.3", "sequelize-cli": "^5.5.1", "sequelize-typescript": "^1.1.0", "sharp": "^0.32.5", - "socket.io": "^3.0.5", + "socket.io": "^4.7.5", "socket.io-client": "^4.5.4", "uuid": "^8.3.2", "whatsapp-web.js": "github:pedroslopez/whatsapp-web.js", @@ -56,6 +59,7 @@ "devDependencies": { "@types/bcryptjs": "^2.4.2", "@types/bluebird": "^3.5.32", + "@types/compression": "^1.7.5", "@types/cookie-parser": "^1.4.2", "@types/cors": "^2.8.7", "@types/express": "^4.17.13", @@ -63,6 +67,7 @@ "@types/faker": "^5.1.3", "@types/jest": "^26.0.15", "@types/jsonwebtoken": "^8.5.0", + "@types/lodash": "4.14", "@types/multer": "^1.4.4", "@types/node": "^14.11.8", "@types/supertest": "^2.0.10", diff --git a/backend/src/app.ts b/backend/src/app.ts index fea4c81..7fe1d45 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -11,6 +11,7 @@ import uploadConfig from "./config/upload"; import AppError from "./errors/AppError"; import routes from "./routes"; import { logger } from "./utils/logger"; +import compression from 'compression'; Sentry.init({ dsn: process.env.SENTRY_DSN }); @@ -23,6 +24,7 @@ app.use( }) ); +app.use(compression()); app.use(cookieParser()); app.use(express.json()); app.use(Sentry.Handlers.requestHandler()); diff --git a/backend/src/controllers/WhatsAppController.ts b/backend/src/controllers/WhatsAppController.ts index 0158748..d6c2077 100644 --- a/backend/src/controllers/WhatsAppController.ts +++ b/backend/src/controllers/WhatsAppController.ts @@ -41,7 +41,10 @@ import { getSettingValue } from "../helpers/WhaticketSettings"; import ListWhatsAppsNumber from "../services/WhatsappService/ListWhatsAppsNumber"; import SettingTicket from "../models/SettingTicket"; import { Op } from "sequelize"; -import { del, get, set } from "../helpers/RedisClient"; +import { del, get, getKeysByPattern, set, setCBPWhatsappOfficial } from "../helpers/RedisClient"; + +import axios from "axios"; + interface WhatsappData { name: string; @@ -214,6 +217,48 @@ export const weebhook = async ( return res.sendStatus(500); } + if( + req?.body?.entry?.length > 0 && + req?.body?.entry[0]?.changes?.length > 0 && + req?.body?.entry[0]?.changes?.length > 0 && + req?.body?.entry[0]?.changes[0]?.value?.statuses?.length>0 && + req?.body?.entry[0]?.changes[0]?.value?.statuses[0]?.recipient_id && + req?.body?.entry[0]?.changes[0]?.value?.statuses[0]?.conversation?.origin?.type){ + + const company_phone = req?.body?.entry[0]?.changes[0]?.value?.metadata?.display_phone_number + const client_phone = req?.body?.entry[0]?.changes[0]?.value?.statuses[0]?.recipient_id + const conversation_type = req?.body?.entry[0]?.changes[0]?.value?.statuses[0].conversation.origin.type + const billable = req?.body?.entry[0]?.changes[0]?.value?.statuses[0].pricing.billable + const pricing_model = req?.body?.entry[0]?.changes[0]?.value?.statuses[0].pricing.pricing_model + const conversation_type_category = req?.body?.entry[0]?.changes[0]?.value?.statuses[0].pricing.category + const msg_id = req?.body?.entry[0]?.changes[0]?.value?.statuses[0].id + + const _contact_to_exist = await get({ + key: "whatsapp:*", + value: `${company_phone}` + }); + + if(_contact_to_exist){ + + const lst_services_cbp = await getKeysByPattern(company_phone, client_phone, conversation_type_category) + + if(lst_services_cbp && lst_services_cbp.length > 0){ + for(const item of lst_services_cbp){ + if(!item.split(':').includes(conversation_type_category)){ + await setCBP(msg_id, company_phone, client_phone, conversation_type_category, billable, pricing_model) + } + } + }else{ + await setCBP(msg_id, company_phone, client_phone, conversation_type_category, billable, pricing_model) + + } + + console.log('_contact_to_exist: ', _contact_to_exist) + } + + + } + // MESSAGE if (req.body.object) { if ( @@ -377,9 +422,11 @@ export const store = async (req: Request, res: Response): Promise => { } else if (!isOfficial) { phoneNumberId = ""; wabaId = ""; - number = ""; + //number = ""; + } + if(!number){ + number = getNumberFromName(name) } - let invalidPhoneName = validatePhoneName(name); if (invalidPhoneName) { @@ -479,9 +526,11 @@ export const update = async ( } else if (!isOfficial) { whatsappData.phoneNumberId = ""; whatsappData.wabaId = ""; - whatsappData.number = ""; + //whatsappData.number = ""; + } + if(!whatsappData?.number){ + whatsappData.number = getNumberFromName(whatsappData.name) } - const { whatsapp, oldDefaultWhatsapp } = await UpdateWhatsAppService({ whatsappData, whatsappId @@ -626,3 +675,56 @@ const checkWhatsAppData = ({ return { message: "urlApi is required!" }; } }; + +async function setCBP(msg_id: any, company_phone: any, client_phone: any, conversation_type_category: any, billable:string, pricing_model:string) { + const message = await Message.findByPk(msg_id) + + if (message) { + await setCBPWhatsappOfficial(company_phone, client_phone, conversation_type_category, msg_id, `${message.ticketId}`) + + await sendToAPIUsage(msg_id, + company_phone, + client_phone, + conversation_type_category, + `${message.ticketId}`, + billable, + pricing_model + ) + } +} + + + +async function sendToAPIUsage(msg_id: any, company_phone: any, client_phone: any, conversation_type_category: any, ticketId: any, billable:string, pricing_model:string) { + const data = JSON.stringify({ + "companyId": process.env.COMPANY_ID || company_phone, + "companyPhone": company_phone, + "clientPhone": client_phone, + "provider": "meta", + "product": "whatsapp", + "type": conversation_type_category, + "msgId": msg_id, + "ticketId": `${ticketId}`, + "ticketUrl": `${process.env.FRONTEND_URL}/tickets/${ticketId}`, + "billable": billable, + "pricing_model": pricing_model + }); + + const config = { + method: 'post', + url: 'http://172.31.187.24:6008/api/v1/billing/usage-whatsapp', + headers: { + 'Authorization': 'Bearer 2ivck10D3o9qAZi0pkKudVDl9bdEVXY2s8gdxZ0jYgL1DZWTgDz6wDiIjlWgYmJtVOoqf0b42ZTLBRrfo8WoAaScRsujz3jQUNXdchSg0o43YilZGmVhheGJNAeIQRknHEll4nRJ7avcFgmDGoYbEey7TSC8EHS4Z3gzeufYYSfnKNDBwwzBURIQrTOxYFe3tBHsGOzwnuD2lU5tnEx7tr2XRO4zRNYeNY4lMBOFM0mRuyAe4kuqTrKXmJ8As200', + 'Content-Type': 'application/json' + }, + data: data + }; + + try { + const response = await axios(config); + console.log('Response from whatsapp api usage: ',JSON.stringify(response.data)); + } catch (error) { + console.log('Error on try register the whatsapp usage: ', error); + } + +} \ No newline at end of file diff --git a/backend/src/helpers/RedisClient.ts b/backend/src/helpers/RedisClient.ts index 2520588..19c828d 100644 --- a/backend/src/helpers/RedisClient.ts +++ b/backend/src/helpers/RedisClient.ts @@ -158,6 +158,46 @@ export async function findObject( return result; } +export async function setCBPWhatsappOfficial( + company_phone:string, + client_phone:string, + conversation_type:string, + msg_id: string, + ticketId?: string +) { + const key = `company_phone:${company_phone}:client_phone:${client_phone}:conversation_type:${conversation_type}`; + const result = await redis.hmset( + key, + "company_phone", + company_phone, + "client_phone", + client_phone, + "conversation_type", + conversation_type, + "msg_id", + msg_id, + "ticketId", + ticketId + ); + + await redis.expire(key, 86400); +} + + +export async function getKeysByPattern(company_phone:string, client_phone:string, conversation_type:string,) { + const pattern = `company_phone:${company_phone}:client_phone:${client_phone}:conversation_type:${conversation_type}*`; + const keys = []; + let cursor = "0"; + + do { + const result = await redis.scan(cursor, "MATCH", pattern, "COUNT", 100); + cursor = result[0]; + keys.push(...result[1]); + } while (cursor !== "0"); + + return keys; +} + export async function deleteObject( whatsappId: string, contactId: string, diff --git a/backend/src/helpers/controllByNumber.ts b/backend/src/helpers/controllByNumber.ts index 0f48e2b..46dbde5 100644 --- a/backend/src/helpers/controllByNumber.ts +++ b/backend/src/helpers/controllByNumber.ts @@ -29,23 +29,23 @@ async function controllByNumber() { number = JSON.parse(number); ticketId = JSON.parse(ticketId); - const index = controll.findIndex((c: any) => c.number == number); + const index = controll?.findIndex((c: any) => c.number == number); if (index == -1) { - controll.push({ ticketId, number }); + controll?.push({ ticketId, number }); } } - const ticketIds = controll.map((c: any) => c.ticketId); + const ticketIds = controll?.map((c: any) => c.ticketId); - console.log("=======> ticketIds: ", ticketIds); + //console.log("=======> ticketIds: ", ticketIds); for (const ticketId of ticketIds) { const ticket: any = await Ticket.findByPk(ticketId); if(ticket){ const { status } = ticket; - - if (status == "pending") { + + if (status && status == "pending") { await UpdateTicketService({ ticketData: { statusChatEnd: uuidv4() }, ticketId: ticket.id diff --git a/backend/src/libs/socket.ts b/backend/src/libs/socket.ts index 368a46e..ac54ada 100644 --- a/backend/src/libs/socket.ts +++ b/backend/src/libs/socket.ts @@ -3,6 +3,9 @@ import { Server } from "http"; import AppError from "../errors/AppError"; import { logger } from "../utils/logger"; +import { createAdapter } from "@socket.io/redis-adapter"; +import { createClient } from 'redis'; + import { v4 as uuidv4 } from "uuid"; import ListUserParamiterService from "../services/UserServices/ListUserParamiterService"; import { @@ -27,6 +30,7 @@ import { } from "../services/WbotServices/wbotMessageListener"; import { join } from "path"; import Whatsapp from "../models/Whatsapp"; +import { get } from "../helpers/RedisClient" let count: number = 0; let listOnline: any[] = []; @@ -42,16 +46,32 @@ let dateTime = splitDateTime( new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR })) ); -export const initIO = (httpServer: Server): SocketIO => { + +const pubClient = createClient({ url: 'redis://172.31.187.29:6379' }); +const subClient = pubClient.duplicate(); + + +pubClient.connect().catch(console.error); +subClient.connect().catch(console.error); + + +export const initIO = (httpServer: Server): SocketIO => { + io = new SocketIO(httpServer, { cors: { - origin: process.env.FRONTEND_URL + origin: process.env.FRONTEND_URL, + //allowedHeaders: ["my-custom-header"], + //credentials: true }, - maxHttpBufferSize: 1e8 - }); + maxHttpBufferSize: 1e8, + // pingInterval: 25000, + // pingTimeout: 60000, + // adapter: createAdapter(pubClient, subClient) + }); + io.on("connection", socket => { - //logger.info("Client Connected"); + logger.info("Client Connected"); socket.on("joinWhatsSession", (whatsappId: string) => { //logger.info(`A client joined a joinWhatsSession channel: ${whatsappId}`); @@ -107,6 +127,7 @@ export const initIO = (httpServer: Server): SocketIO => { socket.on("online", (userId: any) => { // console.log('userId: ', userId) + return obj.uuid = uuidv4(); if (userId.logoutUserId) { @@ -233,13 +254,18 @@ export const initIO = (httpServer: Server): SocketIO => { if (rooms && rooms.size == 2 && ![...rooms][1].startsWith("session_")) return; - let whatsappIds: any = await Whatsapp.findAll({ - attributes: ["id"], - raw: true + let whatsappIds = await get({ + key: "whatsapp:*", + parse: true }); + // let whatsappIds: any = await Whatsapp.findAll({ + // attributes: ["id"], + // raw: true + // }); + if (whatsappIds && whatsappIds.length > 0) { - whatsappIds = whatsappIds.map((e: any) => `${e.id}`); + // whatsappIds = whatsappIds.map((e: any) => `${e.id}`); console.log( "whatsappIds whatsappIds whatsappIds whatsappIds whatsappIds: ", diff --git a/backend/src/services/ContactServices/CreateOrUpdateContactService.ts b/backend/src/services/ContactServices/CreateOrUpdateContactService.ts index 50d5d5f..e63085d 100644 --- a/backend/src/services/ContactServices/CreateOrUpdateContactService.ts +++ b/backend/src/services/ContactServices/CreateOrUpdateContactService.ts @@ -1,5 +1,6 @@ import { getIO } from "../../libs/socket"; import Contact from "../../models/Contact"; +const { Op } = require('sequelize'); import { createOrUpdateContactCache } from '../../helpers/ContactsCache' import { tr } from "date-fns/locale"; @@ -35,15 +36,37 @@ const CreateOrUpdateContactService = async ({ const io = getIO(); let contact: Contact | null; - - contact = await Contact.findOne({ where: { number } }); + const firstFourDigits = number.slice(0, 4); + const lastEightDigits = number.slice(-8); + + //const numberFormat = number?.length === 13 && number[4] == '9' ? number.slice(0, 4) + number.slice(0, 4) : number; + //contact = await Contact.findOne({ where: { number } }); + contact = await Contact.findOne({ + where: { + [Op.and]: [ + { + number: { + [Op.like]: `%${firstFourDigits}%` + } + }, + { + number: { + [Op.like]: `%${lastEightDigits}%` + } + } + ] + } + }); if (contact) { - contact.update({ profilePicUrl }); - // TEST DEL - await createOrUpdateContactCache(`contact:${contact.id}`, { profilePicUrl }) - // + if(contact.number == number){ + contact.update({ profilePicUrl }); + await createOrUpdateContactCache(`contact:${contact.id}`, { profilePicUrl }) + } else{ + contact.update({ profilePicUrl, number }); + await createOrUpdateContactCache(`contact:${contact.id}`, { profilePicUrl, number }) + } io.emit("contact", { action: "update", diff --git a/backend/src/services/TicketServices/FindOrCreateTicketServiceBot.ts b/backend/src/services/TicketServices/FindOrCreateTicketServiceBot.ts index 42d7d53..8599030 100644 --- a/backend/src/services/TicketServices/FindOrCreateTicketServiceBot.ts +++ b/backend/src/services/TicketServices/FindOrCreateTicketServiceBot.ts @@ -101,8 +101,6 @@ const FindOrCreateTicketServiceBot = async ( unreadMessages }); - console.log("lxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); - await dialogFlowStartContext(contact, ticket, botInfo); } } @@ -128,8 +126,6 @@ const FindOrCreateTicketServiceBot = async ( phoneNumberId }); - console.log("yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"); - await dialogFlowStartContext(contact, ticket, botInfo); } diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index e09af21..c9c7573 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -761,8 +761,6 @@ const handleMessage = async ( // console.log('----------> chat: ', JSON.parse(JSON.stringify(chat))) - console - if (chat.isGroup) { // let msgGroupContact; diff --git a/frontend/package.json b/frontend/package.json index 681cd4b..d30e4f4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,7 +43,7 @@ "yup": "^0.32.8" }, "scripts": { - "start": "react-scripts start", + "start": "PORT=3331 react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" diff --git a/frontend/src/components/ChatEnd/TimerPickerSelect/index.js b/frontend/src/components/ChatEnd/TimerPickerSelect/index.js index b49ccf7..b642246 100644 --- a/frontend/src/components/ChatEnd/TimerPickerSelect/index.js +++ b/frontend/src/components/ChatEnd/TimerPickerSelect/index.js @@ -45,7 +45,7 @@ import { import ptBrLocale from "date-fns/locale/pt-BR"; - +import esLocale from 'date-fns/locale/es'; const ResponsiveTimePickers = (props) => { @@ -63,7 +63,7 @@ const ResponsiveTimePickers = (props) => { - + { }} > {({ values, touched, errors, isSubmitting }) => ( - +
@@ -299,7 +300,7 @@ const ConfigModal = ({ open, onClose, change }) => { { { checked={values.businessTimeEnable} /> } - label={'Ativar/Desativar'} /> + label={i18n.t('configModal.titles.enableDisable')} />
{ { { checked={values.businessTimeEnableSaturday} /> } - label={'Ativar/Desativar'} /> + label={i18n.t('configModal.titles.enableDisable')} />
{
{/* SABADO FIM */}
- + {/* Saturday and Sunday date */}
@@ -430,13 +431,13 @@ const ConfigModal = ({ open, onClose, change }) => { checked={values.enableWeekendMessage} /> } - label={'Ativar/Desativar'} + label={i18n.t('configModal.titles.enableDisable')} />
{ @@ -475,13 +476,13 @@ const ConfigModal = ({ open, onClose, change }) => { checked={values.holidayDateEnable} /> } - label={'Ativar/Desativar'} + label={i18n.t('configModal.titles.enableDisable')} />
{ checked={values.ticketExpirationEnable} /> } - label={'Ativar/Desativar'} + label={i18n.t('configModal.titles.enableDisable')} />
{ size={24} className={classes.buttonProgress} /> - ) : 'Salvar'} + ) : i18n.t('configModal.titles.save')} diff --git a/frontend/src/components/DashboardUser/CardUser.jsx b/frontend/src/components/DashboardUser/CardUser.jsx index eea89d1..bd595dd 100644 --- a/frontend/src/components/DashboardUser/CardUser.jsx +++ b/frontend/src/components/DashboardUser/CardUser.jsx @@ -23,6 +23,8 @@ import CheckCircleIcon from "@material-ui/icons/CheckCircle"; import ErrorIcon from "@material-ui/icons/Error"; import RemoveCircleIcon from "@material-ui/icons/RemoveCircle"; +import { i18n } from "../../translate/i18n"; + const CardUser = ({ classes, usersOnlineInfo, logout }) => { const [search, setSearch] = React.useState(""); @@ -46,14 +48,14 @@ const CardUser = ({ classes, usersOnlineInfo, logout }) => { color="primary" style={{ marginBottom: "16px" }} > - Lista de Usuários + {i18n.t('dashboard.titles.listUser')} { Todos Online Offline - Não entrou + {i18n.t('dashboard.titles.notEnter')} @@ -161,7 +163,7 @@ const CardUser = ({ classes, usersOnlineInfo, logout }) => { {user.sumOnlineTime && user.sumOnlineTime.sum ? user.sumOnlineTime.sum - : "Não entrou Hoje"} + : i18n.t('dashboard.titles.notEnterToday')} diff --git a/frontend/src/components/DashboardUser/TableUser.jsx b/frontend/src/components/DashboardUser/TableUser.jsx index e574d56..899f98f 100644 --- a/frontend/src/components/DashboardUser/TableUser.jsx +++ b/frontend/src/components/DashboardUser/TableUser.jsx @@ -75,7 +75,7 @@ const TableUser = ({ classes, usersOnlineInfo, logout }) => { Todos Online Offline - Não entrou + {i18n.t('dashboard.titles.notEnter')} @@ -221,7 +221,7 @@ const TableUser = ({ classes, usersOnlineInfo, logout }) => {
- {user.sumOnlineTime ? user.sumOnlineTime.sum : "Não entrou"} + {user.sumOnlineTime ? user.sumOnlineTime.sum : i18n.t('dashboard.titles.notEnter')} {user.statusOnline && user.statusOnline.status === "online" ? ( diff --git a/frontend/src/components/MessagesList/index.js b/frontend/src/components/MessagesList/index.js index 7e9126c..60b7a71 100644 --- a/frontend/src/components/MessagesList/index.js +++ b/frontend/src/components/MessagesList/index.js @@ -1,7 +1,9 @@ import React, { useContext, useState, useEffect, useReducer, useRef } from "react" import { isSameDay, parseISO, format } from "date-fns" -import openSocket from "socket.io-client" +//import openSocket from "socket.io-client" +import { socket } from "../../services/socket" + import clsx from "clsx" import { AuthContext } from "../../context/Auth/AuthContext" @@ -34,7 +36,7 @@ import whatsBackground from "../../assets/wa-background.png" import api from "../../services/api" import toastError from "../../errors/toastError" -import { countTicketMsgContext } from "../../context/CountTicketMsgProvider/CountTicketMsgProvider" +import { countTicketMsgContext } from "../../context/CountTicketMsgProvider/CountTicketMsgProvider" const useStyles = makeStyles((theme) => ({ messagesListWrapper: { @@ -438,11 +440,18 @@ const MessagesList = ({ ticketId, isGroup }) => { }, [pageNumber, ticketId]) useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + //const socket = openSocket(process.env.REACT_APP_BACKEND_URL) - socket.on("connect", () => socket.emit("joinChatBox", ticketId)) + const onConnectMessagesList = () => { + socket.emit("joinChatBox", ticketId) + } - socket.on("appMessage", (data) => { + onConnectMessagesList() + + socket.on("connect", onConnectMessagesList) + + const onAppMessageMessagesList = (data) => { + if (+data.message.ticketId !== +ticketId) return if (data.action === "create") { @@ -454,10 +463,14 @@ const MessagesList = ({ ticketId, isGroup }) => { if (data.action === "update") { dispatch({ type: "UPDATE_MESSAGE", payload: data.message }) } - }) + } + + socket.on("appMessage", onAppMessageMessagesList) return () => { - socket.disconnect() + socket.emit("leaveChatBox", ticketId) + socket.off("connect", onConnectMessagesList) + socket.off("appMessage", onAppMessageMessagesList) } }, [ticketId]) @@ -949,4 +962,4 @@ const MessagesList = ({ ticketId, isGroup }) => { ) } -export default MessagesList \ No newline at end of file +export default MessagesList diff --git a/frontend/src/components/ModalUpdateScheduleReminder/DatePicker2/index.js b/frontend/src/components/ModalUpdateScheduleReminder/DatePicker2/index.js index a4e953e..0dd343c 100644 --- a/frontend/src/components/ModalUpdateScheduleReminder/DatePicker2/index.js +++ b/frontend/src/components/ModalUpdateScheduleReminder/DatePicker2/index.js @@ -13,7 +13,7 @@ import { import ptBrLocale from "date-fns/locale/pt-BR"; - +import esLocale from 'date-fns/locale/es'; function formatDateDatePicker(data){ return String(new Date(data).getFullYear())+'-'+ @@ -50,7 +50,7 @@ function ResponsiveDatePickers(props) { return ( - + { - + { useEffect(() => { const fetchSession = async () => { - try { - const { data } = await api.get('/settings') - setSettings(data.settings) - } catch (err) { - toastError(err) - } + try { + const { data } = await api.get('/settings') + setSettings(data.settings) + } catch (err) { + toastError(err) + } } fetchSession() - }, []) + }, []) - const getSettingValue = (key) => { + const getSettingValue = (key) => { const { value } = settings.find((s) => s.key === key) return value - } + } useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + //const socket = openSocket(process.env.REACT_APP_BACKEND_URL) socket.on("reload_page", (data) => { @@ -193,7 +194,8 @@ const NotificationsPopOver = () => { return () => { - socket.disconnect() + socket.removeAllListeners('reload_page'); + socket.removeAllListeners('onlineStatus'); } }, [user.id, handleLogout, user.profile]) @@ -201,12 +203,17 @@ const NotificationsPopOver = () => { useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + //const socket = openSocket(process.env.REACT_APP_BACKEND_URL) - socket.on("connect", () => socket.emit("joinNotification")) + const onConnectNotifications = () => { + socket.emit("joinNotification") + } + onConnectNotifications() - socket.on("ticket", data => { + socket.on("connect", onConnectNotifications) + + const onTicketNotifications = data => { if (data.action === "updateUnread" || data.action === "delete") { @@ -232,25 +239,18 @@ const NotificationsPopOver = () => { return prevState }) } - }) - - socket.on("appMessage", data => { - + } + socket.on("ticket", onTicketNotifications) + const onAppMessageNotifications = data => { if ( data.action === "create" && !data.message.read && (data.ticket.userId === user?.id || !data.ticket.userId) ) { - - - setNotifications(prevState => { - - - // prevState.forEach((e)=>{ // // }) @@ -265,8 +265,6 @@ const NotificationsPopOver = () => { return [data.ticket, ...prevState] }) - - const shouldNotNotificate = (data.message.ticketId === ticketIdRef.current && document.visibilityState === "visible") || (data.ticket.userId && data.ticket.userId !== user?.id) || data.ticket.isGroup || !data.ticket.userId @@ -275,20 +273,25 @@ const NotificationsPopOver = () => { handleNotifications(data) } - }) + } - socket.on('notifyPeding', data =>{ - if(settings?.length > 0 && getSettingValue('notificationTransferQueue') === 'enabled') handleNotifications("", data); + socket.on("appMessage", onAppMessageNotifications) + + socket.on('notifyPeding', data => { + if (settings?.length > 0 && getSettingValue('notificationTransferQueue') === 'enabled') handleNotifications("", data); }); return () => { - socket.disconnect() + socket.off('connect', onConnectNotifications); + socket.off('ticket', onTicketNotifications); + socket.off('appMessage', onAppMessageNotifications); + socket.removeAllListeners('notifyPeding'); } }, [user, settings]) - const handleNotifications = (data, notify) => { + const handleNotifications = (data, notify) => { let isQueue = false; - if(!notify){ + if (!notify) { const { message, contact, ticket } = data const options = { @@ -301,14 +304,14 @@ const NotificationsPopOver = () => { const notification = new Notification( `${i18n.t("tickets.notification.message")} ${contact.name}`, options - ) - - notification.onclick = e => { - e.preventDefault() + ) + + notification.onclick = e => { + e.preventDefault() window.focus() historyRef.current.push(`/tickets/${ticket.id}`) } - + setDesktopNotifications(prevState => { const notfiticationIndex = prevState.findIndex( n => n.tag === notification.tag @@ -319,15 +322,15 @@ const NotificationsPopOver = () => { } return [notification, ...prevState] }) - }else{ - user.queues.forEach(queue =>{ - if(queue.id === notify.data?.queue?.id){ + } else { + user.queues.forEach(queue => { + if (queue.id === notify.data?.queue?.id) { isQueue = true; } }) - if(!isQueue){ + if (!isQueue) { return; - }else { + } else { const notification = new Notification(`${i18n.t("tickets.notification.messagePeding")} ${notify.data?.queue?.name}`); notification.onclick = e => { e.preventDefault() @@ -346,7 +349,7 @@ const NotificationsPopOver = () => { return [notification, ...prevState] }) } - } + } soundAlertRef.current() } diff --git a/frontend/src/components/PositionModal/index.js b/frontend/src/components/PositionModal/index.js index 29dcde5..537b7cf 100644 --- a/frontend/src/components/PositionModal/index.js +++ b/frontend/src/components/PositionModal/index.js @@ -3,8 +3,8 @@ import React, { useState, useEffect, useRef, useContext } from "react" import * as Yup from "yup" import { Formik, Form, Field } from "formik" import { toast } from "react-toastify" -import openSocket from 'socket.io-client' - +//import openSocket from 'socket.io-client' +import { socket } from "../../services/socket" import { makeStyles, @@ -51,8 +51,8 @@ const useStyles = makeStyles((theme) => ({ })) const PositionSchema = Yup.object().shape({ - name: Yup.string() - .required("Required"), + name: Yup.string() + .required("Required"), }) const PositionModal = ({ @@ -66,14 +66,14 @@ const PositionModal = ({ const isMounted = useRef(true) const initialState = { - name: "", + name: "", } const [position, setPosition] = useState(initialState) // const [selectedQueueIds, setSelectedQueueIds] = useState([]) const { setting } = useContext(AuthContext) const [settings, setSettings] = useState(setting) - + useEffect(() => { return () => { @@ -87,9 +87,9 @@ const PositionModal = ({ useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + //const socket = openSocket(process.env.REACT_APP_BACKEND_URL) - socket.on('settings', (data) => { + const onSettingsPosition = (data) => { if (data.action === 'update') { setSettings((prevState) => { const aux = [...prevState] @@ -98,10 +98,12 @@ const PositionModal = ({ return aux }) } - }) + } + + socket.on('settings', onSettingsPosition) return () => { - socket.disconnect() + socket.off("settings", onSettingsPosition) } }, []) @@ -141,7 +143,7 @@ const PositionModal = ({ const handleSavePosition = async (values) => { try { - + if (positionId) { await api.put(`/positions/${positionId}`, values) handleClose() @@ -154,7 +156,7 @@ const PositionModal = ({ handleClose() toast.success("Cargo salvo com sucesso") } - + } catch (err) { toastError(err) } @@ -191,7 +193,7 @@ const PositionModal = ({
-
+ {/*
{ ((settings && getSettingValue('quickAnswerByQueue') === 'enabled')) && ( diff --git a/frontend/src/components/QrcodeModal/index.js b/frontend/src/components/QrcodeModal/index.js index dcca527..4c8bd23 100644 --- a/frontend/src/components/QrcodeModal/index.js +++ b/frontend/src/components/QrcodeModal/index.js @@ -1,6 +1,8 @@ import React, { useEffect, useState, useContext } from "react"; import QRCode from "qrcode.react"; -import openSocket from "socket.io-client"; +//import openSocket from "socket.io-client"; +import { socket } from "../../services/socket"; + import toastError from "../../errors/toastError"; import { Dialog, DialogContent, Paper, Typography } from "@material-ui/core"; @@ -31,9 +33,9 @@ const QrcodeModal = ({ open, onClose, whatsAppId }) => { useEffect(() => { if (!whatsAppId) return; - const socket = openSocket(process.env.REACT_APP_BACKEND_URL); + //const socket = openSocket(process.env.REACT_APP_BACKEND_URL); - socket.on("whatsappSession", data => { + const onWhatsAppSessionQrCode = data => { if (data.action === "update" && data.session.id === whatsAppId) { setQrCode(data.session.qrcode); } @@ -42,21 +44,23 @@ const QrcodeModal = ({ open, onClose, whatsAppId }) => { onClose(); } - if (data.action === "error") { + if (data.action === "error") { - console.log('user.profile: ', user.profile) + console.log('user.profile: ', user.profile) - if(user.profile === 'master'){ + if (user.profile === 'master') { alert(data.msg) - } - + } + } - }); + } + + socket.on("whatsappSession", onWhatsAppSessionQrCode); return () => { - socket.disconnect(); + socket.off("whatsappSession", onWhatsAppSessionQrCode); }; }, [whatsAppId, onClose, user.profile]); diff --git a/frontend/src/components/QueueModal/index.js b/frontend/src/components/QueueModal/index.js index eb82ee8..776f68d 100644 --- a/frontend/src/components/QueueModal/index.js +++ b/frontend/src/components/QueueModal/index.js @@ -23,8 +23,8 @@ import { IconButton, InputAdornment } from "@material-ui/core" import { Colorize } from "@material-ui/icons" import { AuthContext } from '../../context/Auth/AuthContext' -import openSocket from 'socket.io-client' - +//import openSocket from 'socket.io-client' +import { socket } from "../../services/socket" const useStyles = makeStyles(theme => ({ @@ -92,9 +92,9 @@ const QueueModal = ({ open, onClose, queueId }) => { }, [setting]) useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + //const socket = openSocket(process.env.REACT_APP_BACKEND_URL) - socket.on('settings', (data) => { + const onSettingsQueueModal = (data) => { if (data.action === 'update') { setSettings((prevState) => { const aux = [...prevState] @@ -103,10 +103,12 @@ const QueueModal = ({ open, onClose, queueId }) => { return aux }) } - }) + } + + socket.on('settings', onSettingsQueueModal) return () => { - socket.disconnect() + socket.off("settings", onSettingsQueueModal) } }, []) diff --git a/frontend/src/components/QuickAnswersModal/index.js b/frontend/src/components/QuickAnswersModal/index.js index 10008f8..748c3ce 100644 --- a/frontend/src/components/QuickAnswersModal/index.js +++ b/frontend/src/components/QuickAnswersModal/index.js @@ -3,8 +3,8 @@ import React, { useState, useEffect, useRef, useContext } from "react" import * as Yup from "yup" import { Formik, Form, Field } from "formik" import { toast } from "react-toastify" -import openSocket from 'socket.io-client' - +//import openSocket from 'socket.io-client' +import { socket } from "../../services/socket" import { makeStyles, @@ -79,7 +79,7 @@ const QuickAnswersModal = ({ const [quickAnswer, setQuickAnswer] = useState(initialState) const [selectedQueueIds, setSelectedQueueIds] = useState([]) const { user, setting, getSettingValue } = useContext(AuthContext) - const [settings, setSettings] = useState(setting) + const [settings, setSettings] = useState(setting) useEffect(() => { return () => { @@ -92,18 +92,18 @@ const QuickAnswersModal = ({ }, [setting]) useEffect(() => { - + setSelectedQueueIds([]) - if (open && selectedQueueIds.length === 0 && !quickAnswerId) { + if (open && selectedQueueIds.length === 0 && !quickAnswerId) { setSelectedQueueIds(user.queues.map(q => q.id)) } }, [open,]) useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + //const socket = openSocket(process.env.REACT_APP_BACKEND_URL) - socket.on('settings', (data) => { + const onSettingsQuickAnswersModal = (data) => { if (data.action === 'update') { setSettings((prevState) => { const aux = [...prevState] @@ -112,14 +112,16 @@ const QuickAnswersModal = ({ return aux }) } - }) + } + + socket.on('settings', onSettingsQuickAnswersModal) return () => { - socket.disconnect() + socket.off("settings", onSettingsQuickAnswersModal) } }, []) - useEffect(() => { + useEffect(() => { // setSelectedQueueIds([]) @@ -139,7 +141,7 @@ const QuickAnswersModal = ({ if (isMounted.current) { setQuickAnswer(data) - if (data?.queues) { + if (data?.queues) { const quickQueueIds = data.queues?.map((queue) => queue.id) setSelectedQueueIds(quickQueueIds) } diff --git a/frontend/src/components/Report/DatePicker/index.js b/frontend/src/components/Report/DatePicker/index.js index 64e9b80..81ffe6f 100644 --- a/frontend/src/components/Report/DatePicker/index.js +++ b/frontend/src/components/Report/DatePicker/index.js @@ -15,7 +15,7 @@ import { import ptBrLocale from "date-fns/locale/pt-BR"; - +import esLocale from 'date-fns/locale/es'; function formatDateDatePicker(data){ return String(new Date(data).getFullYear())+'-'+ @@ -52,7 +52,7 @@ function ResponsiveDatePickers(props) { return ( - + { const dataChat = props.data.map((dt) => { return { - 'fromMe': dt.fromMe ? 'Atendente' : 'Cliente', + 'fromMe': dt.fromMe ? i18n.t('dashboard.titles.attendant') : 'Cliente', 'body': dt.body, 'createdAt': dt.createdAt } diff --git a/frontend/src/components/ReportModal/index.js b/frontend/src/components/ReportModal/index.js index 35c8216..5ae5095 100644 --- a/frontend/src/components/ReportModal/index.js +++ b/frontend/src/components/ReportModal/index.js @@ -11,7 +11,8 @@ import FormControl from '@mui/material/FormControl'; import InputLabel from '@mui/material/InputLabel'; import MenuItem from '@mui/material/MenuItem'; import Select from '@mui/material/Select'; - + +import { i18n } from "../../translate/i18n"; @@ -56,11 +57,11 @@ useEffect(()=>{ open={open} onClose={handleClose} > - Relatórios + {i18n.t('dashboard.titles.dialogTitle')} - Escolha uma opção de relatório abaixo + {i18n.t('dashboard.titles.dialogContentText')} { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + //const socket = openSocket(process.env.REACT_APP_BACKEND_URL) - socket.on('settings', (data) => { + const onSettingsStatusChatEndModal = (data) => { if (data.action === 'update') { setSettings((prevState) => { const aux = [...prevState] @@ -102,10 +102,12 @@ const StatusChatEndModal = ({ return aux }) } - }) + } + + socket.on('settings', onSettingsStatusChatEndModal) return () => { - socket.disconnect() + socket.off("settings", onSettingsStatusChatEndModal) } }, []) diff --git a/frontend/src/components/Ticket/index.js b/frontend/src/components/Ticket/index.js index dbf78bf..fd7efce 100644 --- a/frontend/src/components/Ticket/index.js +++ b/frontend/src/components/Ticket/index.js @@ -2,7 +2,9 @@ import React, { useState, useEffect } from "react" import { useParams, useHistory } from "react-router-dom" import { toast } from "react-toastify" -import openSocket from "socket.io-client" +//import openSocket from "socket.io-client" +import { socket } from "../../services/socket" + import clsx from "clsx" import { Paper, makeStyles } from "@material-ui/core" @@ -132,22 +134,32 @@ const Ticket = () => { }, []) useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + //const socket = openSocket(process.env.REACT_APP_BACKEND_URL) - socket.on("connect", () => socket.emit("joinChatBox", ticketId)) + const onConnectTicket = () => socket.emit("joinChatBox", ticketId) + + onConnectTicket() + + socket.on("connect", onConnectTicket) + + const onTicketTicket = (data) => { + const isSameTicket = +data?.ticket?.id === +ticketId || +data.ticketId === +ticketId + if (!isSameTicket) return - socket.on("ticket", (data) => { if (data.action === "update") { setTicket(data.ticket) } - if (data.action === "delete") { + if (data.action === "deleteForever") { + console.log('delete forever') toast.success("Ticket deleted sucessfully.") history.push("/tickets") } - }) + } - socket.on("contact", (data) => { + socket.on("ticket", onTicketTicket) + + const onContactTicket = (data) => { if (data.action === "update") { setContact((prevState) => { if (prevState.id === data.contact?.id) { @@ -156,9 +168,11 @@ const Ticket = () => { return prevState }) } - }) + } - socket.on("remoteTickesControllIdleOpen", (data) => { + socket.on("contact", onContactTicket) + + const onRemoteTicketsControllIdleOpenTicket = (data) => { if (data.action === "update") { let url_ticketId try { @@ -175,10 +189,15 @@ const Ticket = () => { console.log('error on try do the send seen: ', error) } } - }) + } + + socket.on("remoteTickesControllIdleOpen", onRemoteTicketsControllIdleOpenTicket) return () => { - socket.disconnect() + socket.off("connect", onConnectTicket) + socket.off("ticket", onTicketTicket) + socket.off("contact", onContactTicket) + socket.off("remoteTickesControllIdleOpen", onRemoteTicketsControllIdleOpenTicket) } }, [ticketId, history]) diff --git a/frontend/src/components/TicketListItem/index.js b/frontend/src/components/TicketListItem/index.js index 417b9d1..55b5196 100644 --- a/frontend/src/components/TicketListItem/index.js +++ b/frontend/src/components/TicketListItem/index.js @@ -22,7 +22,8 @@ import MarkdownWrapper from "../MarkdownWrapper" import { Tooltip } from "@material-ui/core" import { AuthContext } from "../../context/Auth/AuthContext" import toastError from "../../errors/toastError" -import openSocket from 'socket.io-client' +//import openSocket from 'socket.io-client' +import { socket } from "../../services/socket" const useStyles = makeStyles(theme => ({ ticket: { @@ -151,17 +152,18 @@ const TicketListItem = ({ ticket, remoteTicketsControll, settings }) => { useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + //const socket = openSocket(process.env.REACT_APP_BACKEND_URL) - socket.on('remoteTickesControll', (data) => { + const onRemoteTickesControllTicketListItem = (data) => { console.log('REMOTE TICKETS CONTROLL UPDATE2: ', data.tickets) if (data.action === 'update') { setRemoteTicketsControll(data.tickets) } - }) + } + socket.on('remoteTickesControll', onRemoteTickesControllTicketListItem) - socket.on('settings', (data) => { + const onSettingsTicketListItem = (data) => { if (data.action === 'update') { setSettings((prevState) => { const aux = [...prevState] @@ -170,11 +172,13 @@ const TicketListItem = ({ ticket, remoteTicketsControll, settings }) => { return aux }) } - }) + } + socket.on('settings', onSettingsTicketListItem) return () => { - socket.disconnect() + socket.off('remoteTickesControll', onRemoteTickesControllTicketListItem); + socket.off('settings', onSettingsTicketListItem); } }, []) diff --git a/frontend/src/components/TicketsList/index.js b/frontend/src/components/TicketsList/index.js index a481700..20ebe2f 100644 --- a/frontend/src/components/TicketsList/index.js +++ b/frontend/src/components/TicketsList/index.js @@ -1,7 +1,7 @@ import React, { useState, useEffect, useReducer, useContext } from "react" - -import openSocket from "socket.io-client" +//import openSocket from "socket.io-client" +import { socket } from "../../services/socket" import { makeStyles } from "@material-ui/core/styles" import List from "@material-ui/core/List" @@ -243,27 +243,29 @@ const TicketsList = (props) => { // if (tab=='search')return - const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + //const socket = openSocket(process.env.REACT_APP_BACKEND_URL) const shouldUpdateTicket = ticket => + (status === ticket.status) && (!ticket.userId || ticket.userId === user?.id || showAll) && (!ticket.queueId || selectedQueueIds.indexOf(ticket.queueId) > -1) const notBelongsToUserQueues = ticket => ticket.queueId && selectedQueueIds.indexOf(ticket.queueId) === -1 - socket.on("connect", () => { + const onConnectTicketList = () => { if (status) { socket.emit("joinTickets", status) } else { socket.emit("joinNotification") } + } - }) + onConnectTicketList() + socket.on("connect", onConnectTicketList) - - socket.on("ticket", data => { + const onTicketTicketList = data => { if (data.action === "updateUnread") { @@ -292,10 +294,11 @@ const TicketsList = (props) => { if (data.action === "delete") { dispatch({ type: "DELETE_TICKET", payload: data.ticketId }) } - }) + } + socket.on("ticket", onTicketTicketList) - socket.on("appMessage", data => { + const onAppMessageTicketList = data => { if (data.action === "create" && shouldUpdateTicket(data.ticket)) { @@ -307,7 +310,9 @@ const TicketsList = (props) => { payload: data, }) } - }) + } + + socket.on("appMessage", onAppMessageTicketList) socket.on("contact", data => { if (data.action === "update") { @@ -318,16 +323,16 @@ const TicketsList = (props) => { } }) - - socket.on('remoteTickesControll', (data) => { + const onRemoteTickesControllTicketList = (data) => { console.log('REMOTE TICKETS CONTROLL UPDATE 1: ', data.tickets) if (data.action === 'update') { setRemoteTicketsControll(data.tickets) } - }) + } + socket.on('remoteTickesControll', onRemoteTickesControllTicketList) - socket.on('settings', (data) => { + const onSettingsTicketList = (data) => { if (data.action === 'update') { setSettings((prevState) => { const aux = [...prevState] @@ -336,11 +341,17 @@ const TicketsList = (props) => { return aux }) } - }) + } + socket.on('settings', onSettingsTicketList) return () => { - socket.disconnect() + socket.off("ticket", onTicketTicketList) + socket.off('appMessage', onAppMessageTicketList); + socket.removeAllListeners("contact") + socket.off('connect', onConnectTicketList); + socket.off('settings', onSettingsTicketList); + socket.off('remoteTickesControll', onRemoteTickesControllTicketList); } }, [status, showAll, user, selectedQueueIds, tab]) @@ -349,7 +360,7 @@ const TicketsList = (props) => { if (typeof updateCount === "function") { updateCount(ticketsList.length) } - if (ticketsList && status === "pending"){ + if (ticketsList && status === "pending") { setTickets(ticketsList) } // else{ diff --git a/frontend/src/components/TicketsManager/index.js b/frontend/src/components/TicketsManager/index.js index 19b922b..4699948 100644 --- a/frontend/src/components/TicketsManager/index.js +++ b/frontend/src/components/TicketsManager/index.js @@ -19,7 +19,6 @@ import FindInPageIcon from '@material-ui/icons/FindInPage' import FormControlLabel from "@material-ui/core/FormControlLabel" import Switch from "@material-ui/core/Switch" -import openSocket from "socket.io-client" import NewTicketModal from "../NewTicketModal" import TicketsList from "../TicketsList" @@ -153,7 +152,7 @@ const TicketsManager = () => { const [openCount, setOpenCount] = useState(0) const [pendingCount, setPendingCount] = useState(0) - const userQueueIds = user.queues.map((q) => q.id) + const userQueueIds = user?.queues?.map((q) => q?.id) const [selectedQueueIds, setSelectedQueueIds] = useState(userQueueIds || []) const [showContentSearch, setShowContentSearch] = useState(false) @@ -178,9 +177,9 @@ const TicketsManager = () => { }, [setting]) useEffect(() => { - if (user.profile.toUpperCase() === "ADMIN" || - user.profile.toUpperCase() === "SUPERVISOR" || - user.profile.toUpperCase() === "MASTER") { + if (user?.profile?.toUpperCase() === "ADMIN" || + user?.profile?.toUpperCase() === "SUPERVISOR" || + user?.profile?.toUpperCase() === "MASTER") { setShowAllTickets(true) } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -193,21 +192,21 @@ const TicketsManager = () => { setTabOption(tab) - }, [tab, setTabOption]) + }, [tab, setTabOption]) useEffect(() => { if (settings?.length > 0 && getSettingValue('waitingTimeTickets') !== 'enabled') return - const calculateAverageTime = () => { + const calculateAverageTime = () => { if (tickets.length > 0) { const now = new Date() const differenceTime = tickets?.map(ticket => { - const createdAt = new Date(ticket.createdAt) + const createdAt = new Date(ticket?.createdAt) const difference = now - createdAt return difference }) - const sumDifferences = differenceTime.reduce((total, difference) => total + difference, 0) + const sumDifferences = differenceTime?.reduce((total, difference) => total + difference, 0) const averageTimeMilliseconds = sumDifferences / tickets?.length let hours = Math.floor(averageTimeMilliseconds / 3600000) const minutes = Math.floor((averageTimeMilliseconds % 3600000) / 60000) @@ -222,10 +221,10 @@ const TicketsManager = () => { } else return '00:00' } - setWaitingTime(calculateAverageTime()) + setWaitingTime(calculateAverageTime()) const intervalId = setInterval(() => { - setWaitingTime(calculateAverageTime()) + setWaitingTime(calculateAverageTime()) }, 10000) return () => clearInterval(intervalId) diff --git a/frontend/src/components/TransferTicketModal/index.js b/frontend/src/components/TransferTicketModal/index.js index 4b54640..ba446d1 100644 --- a/frontend/src/components/TransferTicketModal/index.js +++ b/frontend/src/components/TransferTicketModal/index.js @@ -1,6 +1,7 @@ import React, { useState, useContext, useMemo, useEffect } from "react" import { useHistory } from "react-router-dom" -import openSocket from "socket.io-client" +//import openSocket from "socket.io-client" +import { socket } from "../../services/socket" import Button from "@material-ui/core/Button" import Dialog from "@material-ui/core/Dialog" @@ -120,9 +121,9 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => { useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + //const socket = openSocket(process.env.REACT_APP_BACKEND_URL) - socket.on('settings', (data) => { + const onSettingsTransferTicketModal = (data) => { console.log('settings updated ----------------------------xxxxxxxxxxxx') if (data.action === 'update') { @@ -133,10 +134,12 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => { return aux }) } - }) + } + + socket.on('settings', onSettingsTransferTicketModal) return () => { - socket.disconnect() + socket.off("settings", onSettingsTransferTicketModal) } }, []) @@ -253,7 +256,7 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => { {i18n.t("transferTicketModal.title")} - + {/* {i18n.t("transferTicketModal.fieldQueueLabel")} */} {'Usuário'} @@ -272,7 +275,7 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => { ))} - + {i18n.t("transferTicketModal.fieldQueuePlaceholder")} @@ -284,7 +287,7 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => { required >   - {queues.map((queue) => ( + {queues?.map((queue) => ( { fullWidth />
-
+
)} /> diff --git a/frontend/src/pages/StatusChatEnd/index.js b/frontend/src/pages/StatusChatEnd/index.js index c8de207..9a6b480 100644 --- a/frontend/src/pages/StatusChatEnd/index.js +++ b/frontend/src/pages/StatusChatEnd/index.js @@ -1,5 +1,6 @@ import React, { useState, useContext, useEffect, useReducer } from "react" -import openSocket from "socket.io-client" +//import openSocket from "socket.io-client" +import { socket } from "../../services/socket" import { Button, @@ -115,7 +116,7 @@ const StatusChatEnd = () => { const { data } = await api.get("/statusChatEnd", { params: { searchParam, pageNumber }, }) - + setChecked(data?.statusChatEnd?.map(s => s.isDefault ? true : false)) dispatch({ type: "LOAD_STATUS_CHAT_END", payload: data.statusChatEnd }) @@ -131,9 +132,9 @@ const StatusChatEnd = () => { }, [searchParam, pageNumber]) useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + //const socket = openSocket(process.env.REACT_APP_BACKEND_URL) - socket.on("statusChatEnd", (data) => { + const onStatusChatEndStatusChatEnd = (data) => { if (data.action === "update" || data.action === "create") { dispatch({ type: "UPDATE_STATUS_CHAT_END", payload: data.statusChatEnd }) } @@ -144,10 +145,12 @@ const StatusChatEnd = () => { payload: +data.statusChatEndId, }) } - }) + } + + socket.on("statusChatEnd", onStatusChatEndStatusChatEnd) return () => { - socket.disconnect() + socket.off("statusChatEnd", onStatusChatEndStatusChatEnd) } }, []) @@ -187,14 +190,14 @@ const StatusChatEnd = () => { } const handleChange = async (event, statusChatEnd, index) => { - + const newChecked = new Array(statusChatEnds.length).fill(false) newChecked[index] = event.target.checked setChecked(newChecked) try { const { id } = statusChatEnd - await api.put(`/statusChatEnd/${id}`, { isDefault: event.target.checked }) + await api.put(`/statusChatEnd/${id}`, { isDefault: event.target.checked }) toast.success("Status de encerramento padrão salvo com sucesso") } catch (error) { @@ -217,7 +220,7 @@ const StatusChatEnd = () => { { statusChatEndId={selectedStatusChatEnd && selectedStatusChatEnd.id} > - {"Status de encerramento"} + {i18n.t('dashboard.titles.status')} { - {"Status de encerramento"} + {i18n.t('dashboard.titles.status')} - {"Mensagem de despedida"} + {i18n.t('quickAnswers.table.farewellMessage')} - {"Padrão"} + {i18n.t('quickAnswers.table.standard')} {i18n.t("quickAnswers.table.actions")} diff --git a/frontend/src/pages/Users/index.js b/frontend/src/pages/Users/index.js index c6c3af2..4a090e7 100644 --- a/frontend/src/pages/Users/index.js +++ b/frontend/src/pages/Users/index.js @@ -1,6 +1,7 @@ import React, { useState, useEffect, useReducer, useContext } from "react" import { toast } from "react-toastify" -import openSocket from "socket.io-client" +//import openSocket from "socket.io-client" +import { socket } from "../../services/socket" import { makeStyles } from "@material-ui/core/styles" import Paper from "@material-ui/core/Paper" @@ -165,9 +166,9 @@ const Users = () => { useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL) + //const socket = openSocket(process.env.REACT_APP_BACKEND_URL) - socket.on("user", (data) => { + const onUserUsers = (data) => { if (data.action === "update" || data.action === "create") { dispatch({ type: "UPDATE_USERS", payload: data.user }) } @@ -175,10 +176,11 @@ const Users = () => { if (data.action === "delete") { dispatch({ type: "DELETE_USER", payload: +data.userId }) } - }) + } + socket.on("user", onUserUsers) - socket.on('settings', (data) => { + const onSettingsUsers = (data) => { if (data.action === 'update') { setSettings((prevState) => { const aux = [...prevState] @@ -187,10 +189,12 @@ const Users = () => { return aux }) } - }) + } + socket.on('settings', onSettingsUsers) return () => { - socket.disconnect() + socket.off("settings", onSettingsUsers) + socket.off("user", onUserUsers) } }, []) diff --git a/frontend/src/routes/Route.js b/frontend/src/routes/Route.js index fab61b3..91ce315 100644 --- a/frontend/src/routes/Route.js +++ b/frontend/src/routes/Route.js @@ -1,9 +1,14 @@ -import React, { useContext } from "react"; +import React, { useContext, Suspense, lazy } from "react"; import { Route as RouterRoute, Redirect } from "react-router-dom"; import { AuthContext } from "../context/Auth/AuthContext"; import BackdropLoading from "../components/BackdropLoading"; +// Exemplo de como você carregaria componentes de forma lazy +const Dashboard = lazy(() => import("../pages/Dashboard")); +const Login = lazy(() => import("../pages/Login")); +const Signup = lazy(() => import("../pages/Signup")); + const Route = ({ component: Component, isPrivate = false, ...rest }) => { const { isAuth, loading } = useContext(AuthContext); @@ -20,7 +25,7 @@ const Route = ({ component: Component, isPrivate = false, ...rest }) => { return ( <> {loading && } - ; + ); } @@ -28,9 +33,11 @@ const Route = ({ component: Component, isPrivate = false, ...rest }) => { return ( <> {loading && } - + }> + + ); }; -export default Route; +export default Route; \ No newline at end of file diff --git a/frontend/src/routes/index.js b/frontend/src/routes/index.js index 3400c44..8f053a4 100644 --- a/frontend/src/routes/index.js +++ b/frontend/src/routes/index.js @@ -1,94 +1,85 @@ -import React from 'react' +import React, { Suspense, lazy } from 'react'; import { BrowserRouter, Switch } from 'react-router-dom' import { ToastContainer } from 'react-toastify' -import LoggedInLayout from '../layout' -import Dashboard from '../pages/Dashboard/' - -import Report from '../pages/Report/' -import SchedulesReminder from '../pages/SchedulesReminder/' - -import Tickets from '../pages/Tickets/' -import Signup from '../pages/Signup/' -import Login from '../pages/Login/' -import Connections from '../pages/Connections/' -import Campaign from '../pages/Campaign' -import Settings from '../pages/Settings/' -import Users from '../pages/Users' -import Contacts from '../pages/Contacts/' -import QuickAnswers from '../pages/QuickAnswers/' -import StatusChatEnd from '../pages/StatusChatEnd/' -import Position from '../pages/Position/' - -import Queues from '../pages/Queues/' import { AuthProvider } from '../context/Auth/AuthContext' import { WhatsAppsProvider } from '../context/WhatsApp/WhatsAppsContext' +import LoggedInLayout from '../layout' import Route from './Route' +import BackdropLoading from "../components/BackdropLoading"; + +const Dashboard = lazy(() => import('../pages/Dashboard/')); +const Report = lazy(() => import('../pages/Report/')); +const SchedulesReminder = lazy(() => import('../pages/SchedulesReminder/')); +const Tickets = lazy(() => import('../pages/Tickets/')); +const Signup = lazy(() => import('../pages/Signup/')); +const Login = lazy(() => import('../pages/Login/')); +const Connections = lazy(() => import('../pages/Connections/')); +const Campaign = lazy(() => import('../pages/Campaign/')); +const Settings = lazy(() => import('../pages/Settings/')); +const Users = lazy(() => import('../pages/Users/')); +const Contacts = lazy(() => import('../pages/Contacts/')); +const QuickAnswers = lazy(() => import('../pages/QuickAnswers/')); +const StatusChatEnd = lazy(() => import('../pages/StatusChatEnd/')); +const Position = lazy(() => import('../pages/Position/')); +const Queues = lazy(() => import('../pages/Queues/')); const Routes = () => { return ( - - - - - - - - - - - - - - - - - - - - - - - - - - + }> + + + + + + + + + + + + + + + + + + + + + + - ) + ); } export default Routes diff --git a/frontend/src/services/socket.js b/frontend/src/services/socket.js new file mode 100644 index 0000000..b54fe15 --- /dev/null +++ b/frontend/src/services/socket.js @@ -0,0 +1,14 @@ +import { io } from 'socket.io-client'; + +// "undefined" means the URL will be computed from the `window.location` object +const URL = process.env.REACT_APP_BACKEND_URL + +export const socket = io(URL, +//{ +// withCredentials: true, +// extraHeaders: { +// "my-custom-header": "abcd" +// }, + // transports: ['websocket', 'polling'] +//} +); \ No newline at end of file diff --git a/frontend/src/translate/languages/en.js b/frontend/src/translate/languages/en.js index a499d94..1b28d36 100644 --- a/frontend/src/translate/languages/en.js +++ b/frontend/src/translate/languages/en.js @@ -51,14 +51,53 @@ const messages = { } }, - table_users:{ - title: 'User List', - column0: 'Name', - column1: 'In Service/Finished', - column2: 'Open by Queue', - column3: 'Closed by Queue', - column4: 'Online time', - column5: 'Actions', + table_users: { + title: 'User List', + column0: 'Name', + column1: 'In Service/Finished', + column2: 'Open by Queue', + column3: 'Closed by Queue', + column4: 'Online time', + column5: 'Actions', + }, + titles: { + selectQueues: 'Queues', + waiting: 'In Waiting', + inService: 'In Service', + users: 'Users', + name: 'Name', + attendant: 'Attendant', + dialogContentText: 'Choose a reporting option below.', + transfer: 'Transfer to other queues', + title: 'Closing tickets', + confirmationModal: 'Are you sure you want to remove this closing state: ', + status: 'Closing status', + listUser: 'List user', + user: 'user', + notEnter: 'Did not enter', + notEnterToday: 'Did not enter today', + ticketsClosed: 'Closed' + } + }, + reportOptType: { + listTitles: { + title1: 'Standard', + title2: 'Synthetic', + title3: 'Analytical' + } + }, + configModal: { + titles: { + startService: 'Start Service', + endService: 'End Service', + enableDisable: 'Enable/Disable', + outOfHours: 'Message outside opening hours', + outOfHoursSaturday: 'Message outside opening hours Saturday', + forTheWeekend: 'Weekend message', + holiday: 'Holiday date', + holidayMessage: 'Message for holidays', + inactivityMessage: 'Message due to lack of service activity', + save: 'Save' } }, connections: { @@ -294,8 +333,9 @@ const messages = { queues: "Queues", administration: "Administration", users: "Users", - settings: "Settings", - schedules: "Schedules" + settings: "Settings", + schedules: "Schedules", + reports: "Reports" }, appBar: { user: { @@ -309,9 +349,11 @@ const messages = { title0_1: "Reminders/Schedulings", title1_1: "Calls by attendants", title2_1: "Whatsapp chat", - title3_1: "Users online/offline" + title3_1: "Users online/offline", + title4_1: "Attendance report by numbers", + title5_1: "Queue service report" }, - listColumns:{ + listColumns: { column0_1: 'Actions', column0_2: 'Pic', column0_3: 'Name', @@ -322,7 +364,7 @@ const messages = { column0_8: 'Message', column1_1: 'Store', - column1_2: 'Attendant', + column1_2: 'Attendant', column1_5: 'Subject', column1_6: 'Status', column1_7: 'Created', @@ -330,13 +372,22 @@ const messages = { column1_9: 'Closing status', column2_1: 'Attendant/Client', + + column3_1: 'Unit', + column3_2: 'Row', + column3_3: 'Conversations started', + column3_4: 'Incoming conversations', + column3_5: 'Finished conversations', + column3_6: 'Average waiting time', + column3_7: 'Waiting', }, search: 'Number/Name...', dateStart: 'Start date', dateEnd: 'End date', - user: 'User' - - + user: 'User', + onlineTime: 'Online time', + inService: 'In service', + finished: 'Finished' }, notifications: { noTickets: "No notifications.", @@ -367,6 +418,8 @@ const messages = { shortcut: "Shortcut", message: "Quick Reply", actions: "Actions", + farewellMessage: "Farewell message", + standard: "Standard" }, buttons: { add: "Add Quick Reply", diff --git a/frontend/src/translate/languages/es.js b/frontend/src/translate/languages/es.js index a2c9f3a..b6a1d29 100644 --- a/frontend/src/translate/languages/es.js +++ b/frontend/src/translate/languages/es.js @@ -52,15 +52,55 @@ const messages = { } }, - table_users:{ - - title: 'Lista de usuarios', - column0: 'Nombre', - column1: 'En servicio/Terminado(S)', - column2: 'Abrir por cola', - column3: 'Cerrado por cola', - column4: 'Tiempo Online', - column5: 'Actions', + table_users: { + + title: 'Lista de usuarios', + column0: 'Nombre', + column1: 'En servicio/Terminado(S)', + column2: 'Abrir por cola', + column3: 'Cerrado por cola', + column4: 'Tiempo Online', + column5: 'Actions', + }, + titles: { + selectQueues: 'Colas', + waiting: 'En espera', + inService: 'En Atendimiento', + users: 'Usuarios', + name: 'Nombre', + attendant: 'Agente', + dialogTitle: 'Reportes', + dialogContentText: 'Escoja una opción de reporte abajo.', + transfer: 'Transferir para otras Colas', + title: 'Cierre de Tickets', + confirmationModal: 'Está seguro de que desea eliminar este estado de cierre: ', + status: 'Status de cierre', + listUser: 'Lista de Usuarios', + user: 'Usuario', + notEnter: 'No ingresó', + notEnterToday: 'No ingresó hoy', + ticketsClosed: 'Cerrados' + } + }, + reportOptType: { + listTitles: { + title1: 'Patrón', + title2: 'Sintético', + title3: 'Analítico' + } + }, + configModal: { + titles: { + startService: 'Inicio atención', + endService: 'Fin atención', + enableDisable: 'Activar/Desactivar', + outOfHours: 'Mensaje fuera de horario de atención', + outOfHoursSaturday: 'Mensaje fuera del horario de atención sábado', + forTheWeekend: 'Mensaje fin de semana', + holiday: 'Día Festivo', + holidayMessage: 'Mensaje para Festivos', + inactivityMessage: 'Mensaje por falta de actividad', + save: 'Guardar' } }, connections: { @@ -237,7 +277,7 @@ const messages = { }, }, ticketsQueueSelect: { - placeholder: "Linhas", + placeholder: "Líneas", }, tickets: { toasts: { @@ -296,11 +336,13 @@ const messages = { tickets: "Tickets", contacts: "Contactos", quickAnswers: "Respuestas rápidas", - queues: "Linhas", + queues: "Líneas", administration: "Administración", users: "Usuarios", settings: "Configuración", - schedules: "Recordatorio" + schedules: "Recordatorio", + reports: "Reportes", + campain: "Campañas" }, appBar: { user: { @@ -312,11 +354,13 @@ const messages = { reports: { listTitles: { title0_1: "Recordatorios/Programación", - title1_1: "Llamadas de asistentes", + title1_1: "Atención por agentes", title2_1: "Chat de whatsapp", - title3_1: "Usuarios online/offline" + title3_1: "Usuarios online/offline", + title4_1: "Reporte de atención por números", + title5_1: "Reporte de atención por colas" }, - listColumns:{ + listColumns: { column0_1: 'Acción', column0_2: 'Pic', column0_3: 'Nombre', @@ -327,7 +371,7 @@ const messages = { column0_8: 'Mensaje', column1_1: 'Almacenar', - column1_2: 'Secretario', + column1_2: 'Secretario', column1_5: 'Tema', column1_6: 'Status', column1_7: 'Creado', @@ -335,18 +379,28 @@ const messages = { column1_9: 'Estado de cierre', column2_1: 'Secretario/Cliente', + + column3_1: 'Unidad', + column3_2: 'Cola', + column3_3: 'Chats iniciados', + column3_4: 'Chats Recibidos', + column3_5: 'Chats Finalizados', + column3_6: 'Tiempo medio de espera', + column3_7: 'En Espera', }, search: 'Número/Nombre...', dateStart: 'Fecha de inicio', dateEnd: 'Fecha final', - user: 'Usuario' - + user: 'Usuario', + onlineTime: 'Tiempo online', + inService: 'En servicio', + finished: 'Finalizado' }, notifications: { noTickets: "Sin notificaciones.", }, queues: { - title: "Linhas", + title: "Líneas", table: { name: "Nombre", color: "Color", @@ -363,7 +417,7 @@ const messages = { }, }, queueSelect: { - inputLabel: "Linhas", + inputLabel: "Líneas", }, quickAnswers: { title: "Respuestas rápidas", @@ -371,6 +425,8 @@ const messages = { shortcut: "Atajo", message: "Respuesta rápida", actions: "Acciones", + farewellMessage: "Mensaje de despedida", + standard: "Patrón" }, buttons: { add: "Agregar respuesta rápida", diff --git a/frontend/src/translate/languages/pt.js b/frontend/src/translate/languages/pt.js index 6cdad45..01a2a19 100644 --- a/frontend/src/translate/languages/pt.js +++ b/frontend/src/translate/languages/pt.js @@ -59,6 +59,46 @@ const messages = { column3: 'Fechados Por Fila', column4: 'Tempo Online', column5: 'Ações', + }, + titles: { + selectQueues: 'Filas', + waiting: 'Em espera', + inService: 'Em Atendimento', + users: 'Usuários', + name: 'Nome', + attendant: 'Atendente', + dialogTitle: 'Relatórios', + dialogContentText: 'Escolha uma opção de relatório abaixo.', + transfer: 'Transferir para outras filas', + title: 'Tickets encerramento', + confirmationModal: 'Você tem certeza que quer excluir esta Status de encerramento: ' , + status: 'Status de encerramento', + listUser: 'Lista de Usuarios', + user: 'Usuario', + notEnter: 'Não entrou', + notEnterToday: 'Não entrou hoje', + ticketsClosed: 'Fechados' + } + }, + reportOptType: { + listTitles: { + title1: 'Padrão', + title2: 'Sintético', + title3: 'Analítico' + } + }, + configModal: { + titles: { + startService: 'Início atendimento', + endService: 'Fim atendimento', + enableDisable: 'Ativar/Desativar', + outOfHours: 'Mensagem fora do horário de atendimento', + outOfHoursSaturday: 'Mensagem fora do horário de atendimento sábado', + forTheWeekend: 'Mensagem fim de semana', + holiday: 'Data do feriado', + holidayMessage: 'Mensagem para feriados', + inactivityMessage: 'Mensagem por falta de atividade no atendimento', + save: 'Salvar' } }, connections: { @@ -299,7 +339,9 @@ const messages = { administration: "Administração", users: "Usuários", settings: "Configurações", - schedules: "Lembretes" + schedules: "Lembretes", + reports: "Relatórios", + campaign: "Campanha" }, appBar: { user: { @@ -336,14 +378,22 @@ const messages = { column1_9: 'Status de encerramento', column2_1: 'Atendente/Cliente', - + + column3_1: 'Unidade', + column3_2: 'Fila', + column3_3: 'Conversas iniciadas', + column3_4: 'Conversas recebidas', + column3_5: 'Conversas finalizadas', + column3_6: 'Tempo médio de espera', + column3_7: 'Aguardando', }, search: 'Numer/Nome...', dateStart: 'Data início', dateEnd: 'Data fim', - user: 'Usuário' - - + user: 'Usuário', + onlineTime: 'Tempo online', + inService: 'Em atendimento', + finished: 'Finalizado' }, notifications: { noTickets: "Nenhuma notificação.", @@ -374,6 +424,8 @@ const messages = { shortcut: "Atalho", message: "Resposta Rápida", actions: "Ações", + farewellMessage: "Mensagem de despedida", + standard: "Padrão" }, buttons: { add: "Adicionar Resposta Rápida", diff --git a/package-lock.json b/package-lock.json index f1ff13b..e17b739 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "whaticket", + "name": "projeto-hit", "lockfileVersion": 2, "requires": true, "packages": {