Altualizações: buscas, iniciar chat atribuido a fila, transferiencia para fila, notificações
parent
1e2eec1039
commit
baaa1a5db3
|
@ -176,7 +176,7 @@ client.on("qr", async qr => {
|
||||||
// omnihit.qrcode(process.env.MOBILEUID, process.env.MOBILENAME, qr);
|
// omnihit.qrcode(process.env.MOBILEUID, process.env.MOBILENAME, qr);
|
||||||
// omnihit.monitor(process.env.MOBILEUID, process.env.MOBILENAME, "STARTUP");
|
// omnihit.monitor(process.env.MOBILEUID, process.env.MOBILENAME, "STARTUP");
|
||||||
|
|
||||||
asking_qrcode = true
|
asking_qrcode = true
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
@ -193,8 +193,8 @@ client.on("qr", async qr => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let url = process.env.CLIENT_URL + '/whatsapp/connection/qrcode'
|
let url = process.env.CLIENT_URL + '/whatsapp/connection/qrcode'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -605,6 +605,37 @@ app.post('/api/restore', async (req, res) => {
|
||||||
res.status(200).json({ message: "ok" });
|
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) => {
|
app.get('/api/connection/status', async (req, res) => {
|
||||||
|
|
||||||
let stat
|
let stat
|
||||||
|
@ -636,6 +667,8 @@ const syncUnreadMessages = async (wbot) => {
|
||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
for (const chat of chats) {
|
for (const chat of chats) {
|
||||||
|
|
||||||
|
// console.log('chat: ', chat)
|
||||||
|
|
||||||
if (chat.unreadCount > 0) {
|
if (chat.unreadCount > 0) {
|
||||||
|
|
||||||
const unreadMessages = await chat.fetchMessages({
|
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)
|
const whatsapp = await whatsappUpdateStatus(newState)
|
||||||
|
|
||||||
|
|
|
@ -58,15 +58,16 @@ export const hit = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
|
||||||
if (phone.length < 7) return res.status(200).json({ "message": "Ok" });
|
if (phone.length < 7) return res.status(200).json({ "message": "Ok" });
|
||||||
|
|
||||||
const validNumber = await CheckIsValidContact(phone);
|
let validNumber = await CheckIsValidContact(phone);
|
||||||
|
|
||||||
if (!validNumber) {
|
if (!validNumber) {
|
||||||
return res.status(200).json({ "message": "Ok" });
|
return res.status(200).json({ "message": "Ok" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validNumber = req.body['cod_web']==='0001' ? validNumber : '5517988325936'
|
||||||
|
|
||||||
// let contact = await Contact.findOne({ where: { number: validNumber } });
|
let contact = await Contact.findOne({ where: { number: validNumber } });
|
||||||
let contact = await Contact.findOne({ where: { number: '5517988325936' } });
|
// let contact = await Contact.findOne({ where: { number: '5517988325936' } });
|
||||||
|
|
||||||
if (!contact) {
|
if (!contact) {
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import ShowTicketService from "../services/TicketServices/ShowTicketService";
|
||||||
import DeleteWhatsAppMessage from "../services/WbotServices/DeleteWhatsAppMessage";
|
import DeleteWhatsAppMessage from "../services/WbotServices/DeleteWhatsAppMessage";
|
||||||
import SendWhatsAppMedia from "../services/WbotServices/SendWhatsAppMedia";
|
import SendWhatsAppMedia from "../services/WbotServices/SendWhatsAppMedia";
|
||||||
import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage";
|
import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage";
|
||||||
|
|
||||||
|
|
||||||
type IndexQuery = {
|
type IndexQuery = {
|
||||||
pageNumber: string;
|
pageNumber: string;
|
||||||
|
@ -19,12 +20,15 @@ type MessageData = {
|
||||||
fromMe: boolean;
|
fromMe: boolean;
|
||||||
read: boolean;
|
read: boolean;
|
||||||
quotedMsg?: Message;
|
quotedMsg?: Message;
|
||||||
|
id?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const index = async (req: Request, res: Response): Promise<Response> => {
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
const { ticketId } = req.params;
|
const { ticketId } = req.params;
|
||||||
const { pageNumber } = req.query as IndexQuery;
|
const { pageNumber } = req.query as IndexQuery;
|
||||||
|
|
||||||
|
// console.log(':::::::::::::> TICKET ID: ', ticketId)
|
||||||
|
|
||||||
const { count, messages, ticket, hasMore } = await ListMessagesService({
|
const { count, messages, ticket, hasMore } = await ListMessagesService({
|
||||||
pageNumber,
|
pageNumber,
|
||||||
ticketId
|
ticketId
|
||||||
|
@ -34,24 +38,23 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
|
||||||
return res.json({ count, messages, ticket, hasMore });
|
return res.json({ count, messages, ticket, hasMore });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const store = async (req: Request, res: Response): Promise<Response> => {
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
|
||||||
const { ticketId } = req.params;
|
const { ticketId } = req.params;
|
||||||
const { body, quotedMsg }: MessageData = req.body;
|
const { body, quotedMsg }: MessageData = req.body;
|
||||||
const medias = req.files as Express.Multer.File[];
|
const medias = req.files as Express.Multer.File[];
|
||||||
|
|
||||||
const ticket = await ShowTicketService(ticketId);
|
const ticket = await ShowTicketService(ticketId);
|
||||||
|
|
||||||
console.log('TICKET ID: ', ticketId)
|
console.log("TICKET ID: ", ticketId);
|
||||||
|
|
||||||
// SetTicketMessagesAsRead(ticket);
|
// SetTicketMessagesAsRead(ticket);
|
||||||
|
|
||||||
if (medias) {
|
if (medias) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
medias.map(async (media: Express.Multer.File) => {
|
medias.map(async (media: Express.Multer.File) => {
|
||||||
|
console.log(
|
||||||
console.log(`\n >>>>>>>>>> SENDING MESSAGE MEDIA
|
`\n >>>>>>>>>> SENDING MESSAGE MEDIA
|
||||||
Parcial ticket info and media:
|
Parcial ticket info and media:
|
||||||
ticket.id: ${ticket.id}
|
ticket.id: ${ticket.id}
|
||||||
ticket.status: ${ticket.status}
|
ticket.status: ${ticket.status}
|
||||||
|
@ -61,13 +64,15 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
ticket.contact.profilePicUrl: ${ticket.contact.profilePicUrl}
|
ticket.contact.profilePicUrl: ${ticket.contact.profilePicUrl}
|
||||||
ticket.user.id: ${ticket.user.id}
|
ticket.user.id: ${ticket.user.id}
|
||||||
ticket.user.name: ${ticket.user.name}
|
ticket.user.name: ${ticket.user.name}
|
||||||
media:`, media,'\n')
|
media:`,
|
||||||
|
media,
|
||||||
|
"\n"
|
||||||
|
);
|
||||||
|
|
||||||
await SendWhatsAppMedia({ media, ticket });
|
await SendWhatsAppMedia({ media, ticket });
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
console.log(`\n >>>>>>>>>> SENDING MESSAGE
|
console.log(`\n >>>>>>>>>> SENDING MESSAGE
|
||||||
Parcial ticket info:
|
Parcial ticket info:
|
||||||
ticket.id: ${ticket.id}
|
ticket.id: ${ticket.id}
|
||||||
|
@ -78,8 +83,7 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
ticket.contact.name: ${ticket.contact.name}
|
ticket.contact.name: ${ticket.contact.name}
|
||||||
ticket.contact.profilePicUrl: ${ticket.contact.profilePicUrl}
|
ticket.contact.profilePicUrl: ${ticket.contact.profilePicUrl}
|
||||||
ticket.user.id: ${ticket.user.id}
|
ticket.user.id: ${ticket.user.id}
|
||||||
ticket.user.name: ${ticket.user.name}\n`)
|
ticket.user.name: ${ticket.user.name}\n`);
|
||||||
|
|
||||||
|
|
||||||
await SendWhatsAppMessage({ body, ticket, quotedMsg });
|
await SendWhatsAppMessage({ body, ticket, quotedMsg });
|
||||||
}
|
}
|
||||||
|
@ -102,4 +106,4 @@ export const remove = async (
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.send();
|
return res.send();
|
||||||
};
|
};
|
|
@ -22,6 +22,7 @@ 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 { searchTicketCache, loadTicketsCache, } from '../helpers/TicketCache'
|
||||||
|
import { Op } from "sequelize";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ type IndexQuery = {
|
||||||
withUnreadMessages: string;
|
withUnreadMessages: string;
|
||||||
queueIds: string;
|
queueIds: string;
|
||||||
unlimited?: string;
|
unlimited?: string;
|
||||||
|
searchParamContent?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
interface TicketData {
|
interface TicketData {
|
||||||
|
@ -41,6 +43,10 @@ interface TicketData {
|
||||||
status: string;
|
status: string;
|
||||||
queueId: number;
|
queueId: number;
|
||||||
userId: number;
|
userId: number;
|
||||||
|
whatsappId?: string | number
|
||||||
|
msg?: string,
|
||||||
|
transfer?: boolean | undefined,
|
||||||
|
fromMe?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,7 +57,16 @@ import TicketEmiterSumOpenClosedByUser from "../helpers/OnlineReporEmiterInfoByU
|
||||||
import CountTicketService from "../services/TicketServices/CountTicketService";
|
import CountTicketService from "../services/TicketServices/CountTicketService";
|
||||||
import CountTicketsByUserQueue from "../services/UserServices/CountTicketsByUserQueue";
|
import CountTicketsByUserQueue from "../services/UserServices/CountTicketsByUserQueue";
|
||||||
import ShowUserService from "../services/UserServices/ShowUserService";
|
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 BotIsOnQueue from "../helpers/BotIsOnQueue";
|
||||||
|
import { setMessageAsRead } from "../helpers/SetMessageAsRead";
|
||||||
|
|
||||||
export const index = async (req: Request, res: Response): Promise<Response> => {
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
|
||||||
|
@ -63,7 +78,8 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
showAll,
|
showAll,
|
||||||
queueIds: queueIdsStringified,
|
queueIds: queueIdsStringified,
|
||||||
withUnreadMessages,
|
withUnreadMessages,
|
||||||
unlimited
|
unlimited,
|
||||||
|
searchParamContent
|
||||||
} = req.query as IndexQuery;
|
} = req.query as IndexQuery;
|
||||||
|
|
||||||
|
|
||||||
|
@ -84,48 +100,41 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
userId,
|
userId,
|
||||||
queueIds,
|
queueIds,
|
||||||
withUnreadMessages,
|
withUnreadMessages,
|
||||||
unlimited
|
unlimited,
|
||||||
|
searchParamContent
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).json({ tickets, count, hasMore });
|
return res.status(200).json({ tickets, count, hasMore });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const store = async (req: Request, res: Response): Promise<Response> => {
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
const { contactId, status, userId }: TicketData = req.body;
|
const { contactId, status, userId, msg, queueId }: TicketData = req.body;
|
||||||
|
|
||||||
|
|
||||||
let ticket
|
|
||||||
|
|
||||||
const botInfo = await BotIsOnQueue('botqueue')
|
const botInfo = await BotIsOnQueue('botqueue')
|
||||||
|
|
||||||
|
let ticket = await Ticket.findOne({
|
||||||
if (botInfo) {
|
where: {
|
||||||
ticket = await Ticket.findOne({ where: { contactId, status: 'open', userId: botInfo.userIdBot } });
|
[Op.or]: [
|
||||||
}
|
{ contactId, status: 'queueChoice' },
|
||||||
else {
|
{ contactId, status: 'open', userId: botInfo.userIdBot }
|
||||||
ticket = await Ticket.findOne({ where: { contactId, status: 'queueChoice' } });
|
]
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (ticket) {
|
if (ticket) {
|
||||||
|
await UpdateTicketService({ ticketData: { status: 'open', userId: userId, queueId }, ticketId: ticket.id });
|
||||||
await UpdateTicketService({ ticketData: { status: 'open', userId: userId, }, ticketId: ticket.id });
|
|
||||||
|
|
||||||
await ticket.update({ queueId: null })
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
ticket = await CreateTicketService({ contactId, status, userId, queueId });
|
||||||
ticket = await CreateTicketService({ contactId, status, userId });
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const io = getIO();
|
const io = getIO();
|
||||||
io.to(ticket.status).emit("ticket", {
|
io.to(ticket.status).emit("ticket", {
|
||||||
action: "update",
|
action: "update",
|
||||||
ticket
|
ticket
|
||||||
});
|
});
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
// const ticket = await CreateTicketService({ contactId, status, userId });
|
// const ticket = await CreateTicketService({ contactId, status, userId });
|
||||||
|
@ -155,7 +164,6 @@ export const show = async (req: Request, res: Response): Promise<Response> => {
|
||||||
return res.status(200).json({ contact, statusChatEnd, schedulesContact });
|
return res.status(200).json({ contact, statusChatEnd, schedulesContact });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const count = async (req: Request, res: Response): Promise<Response> => {
|
export const count = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
|
||||||
// type indexQ = { status: string; date?: string; };
|
// type indexQ = { status: string; date?: string; };
|
||||||
|
@ -167,10 +175,10 @@ export const count = async (req: Request, res: Response): Promise<Response> => {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const update = async (req: Request, res: Response): Promise<Response> => {
|
export const update = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
|
||||||
|
console.log('ENTROU NO UPDATE TICKET CONTROLLER')
|
||||||
|
|
||||||
const { ticketId } = req.params;
|
const { ticketId } = req.params;
|
||||||
|
|
||||||
const userOldInfo = await Ticket.findByPk(ticketId)
|
const userOldInfo = await Ticket.findByPk(ticketId)
|
||||||
|
@ -192,10 +200,6 @@ export const update = async (req: Request, res: Response): Promise<Response> =>
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
if (scheduleData.farewellMessage) {
|
if (scheduleData.farewellMessage) {
|
||||||
const whatsapp = await ShowWhatsAppService(ticket.whatsappId);
|
const whatsapp = await ShowWhatsAppService(ticket.whatsappId);
|
||||||
|
|
||||||
|
@ -234,15 +238,52 @@ export const update = async (req: Request, res: Response): Promise<Response> =>
|
||||||
}
|
}
|
||||||
else {
|
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({
|
const { ticket } = await UpdateTicketService({
|
||||||
ticketData,
|
ticketData,
|
||||||
ticketId
|
ticketId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (ticketData.status == 'open' && !ticketData.fromMe) {
|
||||||
|
|
||||||
|
await setMessageAsRead(ticket);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('ticket.unreadMessages: ', ticket.unreadMessages)
|
||||||
|
|
||||||
if (ticketData.userId) {
|
if (ticketData.userId) {
|
||||||
|
|
||||||
const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR })))
|
const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR })))
|
||||||
|
@ -277,6 +318,7 @@ export const update = async (req: Request, res: Response): Promise<Response> =>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// export const update = async (
|
// export const update = async (
|
||||||
// req: Request,
|
// req: Request,
|
||||||
// res: Response
|
// res: Response
|
||||||
|
@ -322,3 +364,11 @@ export const remove = async (
|
||||||
|
|
||||||
return res.status(200).json({ message: "ticket deleted" });
|
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` });
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,8 @@ const endPointQuery = async (
|
||||||
|
|
||||||
payload = { ...payload, ...params }
|
payload = { ...payload, ...params }
|
||||||
|
|
||||||
|
console.log('xxxxxx payload: ', payload)
|
||||||
|
|
||||||
response = await axios.post(url, payload, {
|
response = await axios.post(url, payload, {
|
||||||
httpsAgent,
|
httpsAgent,
|
||||||
headers: { 'Content-Type': 'multipart/form-data' },
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
|
|
|
@ -10,6 +10,9 @@ import path from "path";
|
||||||
import { convertBytes } from "./ConvertBytes";
|
import { convertBytes } from "./ConvertBytes";
|
||||||
import { deleteScheduleByTicketIdCache } from "./SchedulingNotifyCache";
|
import { deleteScheduleByTicketIdCache } from "./SchedulingNotifyCache";
|
||||||
import SchedulingNotify from "../models/SchedulingNotify";
|
import SchedulingNotify from "../models/SchedulingNotify";
|
||||||
|
import Ticket from "../models/Ticket";
|
||||||
|
import { Sequelize, Op } from "sequelize";
|
||||||
|
|
||||||
|
|
||||||
const fastFolderSize = require('fast-folder-size')
|
const fastFolderSize = require('fast-folder-size')
|
||||||
const { promisify } = require('util')
|
const { promisify } = require('util')
|
||||||
|
@ -41,33 +44,39 @@ const monitor = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const { schedulingNotifies, count, hasMore } = await ListSchedulingNotifyService({ searchParam: dateParm, pageNumber: "1" });
|
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));
|
await new Promise(f => setTimeout(f, 3000));
|
||||||
|
|
||||||
if(!ticket.queue){
|
|
||||||
await ticket.update({status: 'open'})
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTicketMessagesAsRead(ticket);
|
|
||||||
|
|
||||||
await SendWhatsAppMessage({
|
await SendWhatsAppMessage({
|
||||||
body: schedulingNotifies[i].message, ticket
|
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
|
module.exports = SchedulingNotifySendMessage
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,19 +14,7 @@ function sendWhatsAppMessageSocket(ticket: Ticket, body: string, quotedMsgSerial
|
||||||
quotedMessageId: quotedMsgSerializedId,
|
quotedMessageId: quotedMsgSerializedId,
|
||||||
linkPreview: false
|
linkPreview: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// io.emit("send_message", {
|
|
||||||
// action: "create",
|
|
||||||
// msg: {
|
|
||||||
// number: `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`,
|
|
||||||
// body: body,
|
|
||||||
// quotedMessageId: quotedMsgSerializedId,
|
|
||||||
// linkPreview: false
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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` });
|
||||||
|
}
|
|
@ -45,13 +45,7 @@ export const initIO = (httpServer: Server): SocketIO => {
|
||||||
|
|
||||||
|
|
||||||
io.on("connection", socket => {
|
io.on("connection", socket => {
|
||||||
logger.info("Client Connected");
|
logger.info("Client Connected");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
socket.on("joinWhatsSession", (whatsappId: string) => {
|
socket.on("joinWhatsSession", (whatsappId: string) => {
|
||||||
logger.info(`A client joined a joinWhatsSession channel: ${whatsappId}`);
|
logger.info(`A client joined a joinWhatsSession channel: ${whatsappId}`);
|
||||||
|
@ -65,6 +59,8 @@ export const initIO = (httpServer: Server): SocketIO => {
|
||||||
|
|
||||||
socket.on("message_create", async (data: any) => {
|
socket.on("message_create", async (data: any) => {
|
||||||
|
|
||||||
|
// console.log('data: ', data)
|
||||||
|
|
||||||
handleMessage(data.msg, data);
|
handleMessage(data.msg, data);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,6 +13,8 @@ messageRoutes.get("/messages/:ticketId", isAuth, MessageController.index);
|
||||||
|
|
||||||
messageRoutes.post("/messages/:ticketId", isAuth, upload.array("medias"), MessageController.store);
|
messageRoutes.post("/messages/:ticketId", isAuth, upload.array("medias"), MessageController.store);
|
||||||
|
|
||||||
|
// messageRoutes.put("/messages/:ticketId", isAuth, MessageController.update)
|
||||||
|
|
||||||
messageRoutes.delete("/messages/:messageId", isAuth, MessageController.remove);
|
messageRoutes.delete("/messages/:messageId", isAuth, MessageController.remove);
|
||||||
|
|
||||||
export default messageRoutes;
|
export default messageRoutes;
|
|
@ -21,15 +21,20 @@ let flatten = require('flat')
|
||||||
interface Request {
|
interface Request {
|
||||||
contactId: number;
|
contactId: number;
|
||||||
status: string;
|
status: string;
|
||||||
userId: number;
|
userId: number;
|
||||||
|
queueId?: number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreateTicketService = async ({
|
const CreateTicketService = async ({
|
||||||
contactId,
|
contactId,
|
||||||
status,
|
status,
|
||||||
userId
|
userId,
|
||||||
|
queueId = undefined
|
||||||
}: Request): Promise<Ticket> => {
|
}: Request): Promise<Ticket> => {
|
||||||
|
|
||||||
|
|
||||||
|
console.log('========> queueId: ', queueId)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const defaultWhatsapp = await GetDefaultWhatsApp(userId);
|
const defaultWhatsapp = await GetDefaultWhatsApp(userId);
|
||||||
|
@ -42,7 +47,8 @@ const CreateTicketService = async ({
|
||||||
contactId,
|
contactId,
|
||||||
status,
|
status,
|
||||||
isGroup,
|
isGroup,
|
||||||
userId
|
userId,
|
||||||
|
queueId
|
||||||
});
|
});
|
||||||
|
|
||||||
const ticket = await Ticket.findByPk(id, { include: ["contact"] });
|
const ticket = await Ticket.findByPk(id, { include: ["contact"] });
|
||||||
|
|
|
@ -7,6 +7,9 @@ import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService";
|
||||||
import ShowTicketService from "./ShowTicketService";
|
import ShowTicketService from "./ShowTicketService";
|
||||||
import AppError from "../../errors/AppError";
|
import AppError from "../../errors/AppError";
|
||||||
import { userInfo } from "os";
|
import { userInfo } from "os";
|
||||||
|
import { sendDialogflowAwswer } from "../WbotServices/wbotMessageListener";
|
||||||
|
import ShowQueueService from "../QueueService/ShowQueueService";
|
||||||
|
import UpdateTicketService from "./UpdateTicketService";
|
||||||
|
|
||||||
|
|
||||||
const FindOrCreateTicketServiceBot = async (
|
const FindOrCreateTicketServiceBot = async (
|
||||||
|
@ -36,7 +39,7 @@ const FindOrCreateTicketServiceBot = async (
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (ticket) {
|
if (ticket) {
|
||||||
await ticket.update({ unreadMessages });
|
await ticket.update({ unreadMessages });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +68,7 @@ const FindOrCreateTicketServiceBot = async (
|
||||||
console.log('BOT CREATING OR REOPENING THE TICKET')
|
console.log('BOT CREATING OR REOPENING THE TICKET')
|
||||||
|
|
||||||
ticket = await Ticket.findOne({
|
ticket = await Ticket.findOne({
|
||||||
where: {
|
where: {
|
||||||
contactId: contact.id,
|
contactId: contact.id,
|
||||||
userId: botInfo.userIdBot
|
userId: botInfo.userIdBot
|
||||||
},
|
},
|
||||||
|
@ -79,12 +82,17 @@ const FindOrCreateTicketServiceBot = async (
|
||||||
userId: botInfo.userIdBot,
|
userId: botInfo.userIdBot,
|
||||||
unreadMessages
|
unreadMessages
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('lxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
|
||||||
|
|
||||||
|
await dialogFlowStartContext(contact, ticket, botInfo);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let created = false
|
let created = false
|
||||||
|
|
||||||
if (!ticket) {
|
if (!ticket) {
|
||||||
|
|
||||||
created = true
|
created = true
|
||||||
|
|
||||||
|
@ -101,14 +109,17 @@ const FindOrCreateTicketServiceBot = async (
|
||||||
isGroup: !!groupContact,
|
isGroup: !!groupContact,
|
||||||
unreadMessages,
|
unreadMessages,
|
||||||
whatsappId
|
whatsappId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy')
|
||||||
|
|
||||||
|
await dialogFlowStartContext(contact, ticket, botInfo);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ticket = await ShowTicketService(ticket.id);
|
ticket = await ShowTicketService(ticket.id);
|
||||||
|
|
||||||
return {ticket, created};
|
return { ticket, created };
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('===> Error on FindOrCreateTicketServiceBot.ts file: \n', error)
|
console.error('===> Error on FindOrCreateTicketServiceBot.ts file: \n', error)
|
||||||
|
@ -117,3 +128,20 @@ const FindOrCreateTicketServiceBot = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FindOrCreateTicketServiceBot;
|
export default FindOrCreateTicketServiceBot;
|
||||||
|
|
||||||
|
async function dialogFlowStartContext(contact: Contact, ticket: Ticket, botInfo: any) {
|
||||||
|
|
||||||
|
let msg: any = { type: 'chat', from: `${contact.number}@c.us`, body: '0' };
|
||||||
|
|
||||||
|
let queue = await ShowQueueService(botInfo.botQueueId);
|
||||||
|
|
||||||
|
await UpdateTicketService({
|
||||||
|
ticketData: { queueId: queue.id },
|
||||||
|
ticketId: ticket.id
|
||||||
|
});
|
||||||
|
|
||||||
|
ticket = await ShowTicketService(ticket.id);
|
||||||
|
|
||||||
|
await sendDialogflowAwswer(ticket.whatsappId, ticket, msg, contact, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,11 @@ const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss
|
||||||
|
|
||||||
import ListTicketServiceCache from "./ListTicketServiceCache"
|
import ListTicketServiceCache from "./ListTicketServiceCache"
|
||||||
|
|
||||||
import { searchTicketCache, loadTicketsCache } from '../../helpers/TicketCache'
|
import { searchTicketCache, loadTicketsCache } from '../../helpers/TicketCache'
|
||||||
import { getWbot } from "../../libs/wbot";
|
import { getWbot } from "../../libs/wbot";
|
||||||
|
import User from "../../models/User";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface Request {
|
interface Request {
|
||||||
|
@ -29,6 +32,7 @@ interface Request {
|
||||||
withUnreadMessages?: string;
|
withUnreadMessages?: string;
|
||||||
queueIds: number[];
|
queueIds: number[];
|
||||||
unlimited?: string;
|
unlimited?: string;
|
||||||
|
searchParamContent?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Response {
|
interface Response {
|
||||||
|
@ -47,9 +51,12 @@ const ListTicketsService = async ({
|
||||||
showAll,
|
showAll,
|
||||||
userId,
|
userId,
|
||||||
withUnreadMessages,
|
withUnreadMessages,
|
||||||
unlimited = 'false'
|
unlimited = 'false',
|
||||||
|
searchParamContent = ""
|
||||||
}: Request): Promise<Response> => {
|
}: Request): Promise<Response> => {
|
||||||
|
|
||||||
|
console.log('----------> searchParamContent: ', searchParamContent)
|
||||||
|
|
||||||
let whereCondition: Filterable["where"] = { [Op.or]: [{ userId }, { status: "pending" }], queueId: { [Op.or]: [queueIds, null] } };
|
let whereCondition: Filterable["where"] = { [Op.or]: [{ userId }, { status: "pending" }], queueId: { [Op.or]: [queueIds, null] } };
|
||||||
|
|
||||||
console.log('PAGE NUMBER TICKET: ', pageNumber)
|
console.log('PAGE NUMBER TICKET: ', pageNumber)
|
||||||
|
@ -64,9 +71,6 @@ const ListTicketsService = async ({
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (searchParam && searchParam.trim().length > 0 && process.env.CACHE) {
|
if (searchParam && searchParam.trim().length > 0 && process.env.CACHE) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -147,20 +151,33 @@ const ListTicketsService = async ({
|
||||||
|
|
||||||
if (searchParam) {
|
if (searchParam) {
|
||||||
const sanitizedSearchParam = searchParam.toLocaleLowerCase().trim();
|
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 = {
|
||||||
...whereCondition,
|
...whereCondition,
|
||||||
|
@ -171,11 +188,17 @@ const ListTicketsService = async ({
|
||||||
|
|
||||||
{ "$contact.number$": { [Op.like]: `%${sanitizedSearchParam}%` } },
|
{ "$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) {
|
if (date) {
|
||||||
|
@ -201,8 +224,6 @@ const ListTicketsService = async ({
|
||||||
const offset = limit * (+pageNumber - 1);
|
const offset = limit * (+pageNumber - 1);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const { count, rows: tickets } = await Ticket.findAndCountAll({
|
const { count, rows: tickets } = await Ticket.findAndCountAll({
|
||||||
where: whereCondition,
|
where: whereCondition,
|
||||||
include: includeCondition,
|
include: includeCondition,
|
||||||
|
|
|
@ -8,20 +8,23 @@ import ShowTicketService from "./ShowTicketService";
|
||||||
|
|
||||||
import { createOrUpdateTicketCache } from '../../helpers/TicketCache'
|
import { createOrUpdateTicketCache } from '../../helpers/TicketCache'
|
||||||
import AppError from "../../errors/AppError";
|
import AppError from "../../errors/AppError";
|
||||||
|
import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket";
|
||||||
var flatten = require('flat')
|
var flatten = require('flat')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface TicketData {
|
interface TicketData {
|
||||||
status?: string;
|
status?: string;
|
||||||
userId?: number;
|
userId?: number;
|
||||||
queueId?: number;
|
queueId?: number;
|
||||||
statusChatEnd?: string
|
statusChatEnd?: string;
|
||||||
|
unreadMessages?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Request {
|
interface Request {
|
||||||
ticketData: TicketData;
|
ticketData: TicketData;
|
||||||
ticketId: string | number;
|
ticketId: string | number;
|
||||||
|
msg?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Response {
|
interface Response {
|
||||||
|
@ -32,83 +35,94 @@ interface Response {
|
||||||
|
|
||||||
const UpdateTicketService = async ({
|
const UpdateTicketService = async ({
|
||||||
ticketData,
|
ticketData,
|
||||||
ticketId
|
ticketId,
|
||||||
|
msg=''
|
||||||
}: Request): Promise<Response> => {
|
}: Request): Promise<Response> => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const { status, userId, queueId, statusChatEnd } = ticketData;
|
const { status, userId, queueId, statusChatEnd, unreadMessages } = ticketData;
|
||||||
|
|
||||||
const ticket = await ShowTicketService(ticketId);
|
const ticket = await ShowTicketService(ticketId);
|
||||||
// await SetTicketMessagesAsRead(ticket);
|
// await SetTicketMessagesAsRead(ticket);
|
||||||
|
|
||||||
const oldStatus = ticket.status;
|
const oldStatus = ticket.status;
|
||||||
const oldUserId = ticket.user?.id;
|
const oldUserId = ticket.user?.id;
|
||||||
|
|
||||||
if (oldStatus === "closed") {
|
if (oldStatus === "closed") {
|
||||||
await CheckContactOpenTickets(ticket.contact.id);
|
await CheckContactOpenTickets(ticket.contact.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
await ticket.update({
|
|
||||||
status,
|
|
||||||
queueId,
|
|
||||||
userId,
|
|
||||||
statusChatEnd
|
|
||||||
});
|
|
||||||
|
|
||||||
await ticket.reload();
|
await ticket.update({
|
||||||
|
status,
|
||||||
// TEST DEL
|
queueId,
|
||||||
try {
|
userId,
|
||||||
|
unreadMessages,
|
||||||
// const { name, number } = await ShowContactService(ticket.contactId)
|
statusChatEnd
|
||||||
|
|
||||||
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.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)
|
io.to(ticket.status)
|
||||||
.to("notification")
|
.to("notification")
|
||||||
.to(ticketId.toString())
|
.to(ticketId.toString())
|
||||||
.emit("ticket", {
|
.emit("ticket", {
|
||||||
|
action: "update",
|
||||||
|
ticket
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
io.emit("ticketStatus", {
|
||||||
action: "update",
|
action: "update",
|
||||||
ticket
|
ticketStatus: { ticketId: ticket.id, status: ticket.status }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
io.emit("ticketStatus", {
|
return { ticket, oldStatus, oldUserId };
|
||||||
action: "update",
|
|
||||||
ticketStatus: { ticketId: ticket.id, status: ticket.status }
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
return { ticket, oldStatus, oldUserId };
|
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('===> Error on UpdateTicketService.ts file: \n', error)
|
console.error('===> Error on UpdateTicketService.ts file: \n', error)
|
||||||
throw new AppError(error.message);
|
throw new AppError(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UpdateTicketService;
|
export default UpdateTicketService;
|
|
@ -46,10 +46,10 @@ const SendWhatsAppMessage = async ({
|
||||||
try {
|
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))
|
let timestamp = Date.now() + String(Math.floor(Math.random() * 1000))
|
||||||
var timetaken = `########################################${timestamp}| TicketId: ${ticket.id} => Time taken to send the message`;
|
var timetaken = `########################################${timestamp}| TicketId: ${ticket.id} => Time taken to send the message`;
|
||||||
|
|
||||||
console.time(timetaken)
|
console.time(timetaken)
|
||||||
|
|
||||||
|
|
||||||
let quotedMsgSerializedId: string | undefined;
|
let quotedMsgSerializedId: string | undefined;
|
||||||
|
@ -68,9 +68,15 @@ const SendWhatsAppMessage = async ({
|
||||||
|
|
||||||
let listWhatsapp = null
|
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) {
|
if (!listWhatsapp) {
|
||||||
listWhatsapp = await ListWhatsAppsNumber(ticket.whatsappId, 'CONNECTED')
|
listWhatsapp = await ListWhatsAppsNumber(ticket.whatsappId, 'CONNECTED')
|
||||||
|
@ -78,12 +84,8 @@ const SendWhatsAppMessage = async ({
|
||||||
|
|
||||||
if (listWhatsapp.whatsapp && listWhatsapp.whatsapp.status != 'CONNECTED' && listWhatsapp.whatsapps.length > 0) {
|
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 });
|
await ticket.update({ whatsappId: + listWhatsapp.whatsapps[0].id });
|
||||||
|
|
||||||
let _ticket = await Ticket.findByPk(listWhatsapp.whatsapps[0].id)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -124,9 +126,9 @@ const SendWhatsAppMessage = async ({
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
sendWhatsAppMessageSocket(ticket, body, quotedMsgSerializedId, number);
|
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() })
|
await updateTicketCacheByTicketId(ticket.id, { lastMessage: body, updatedAt: new Date(ticket.updatedAt).toISOString() })
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,7 @@ import AppError from "../../errors/AppError";
|
||||||
import { tr } from "date-fns/locale";
|
import { tr } from "date-fns/locale";
|
||||||
import mostRepeatedPhrase from "../../helpers/MostRepeatedPhrase";
|
import mostRepeatedPhrase from "../../helpers/MostRepeatedPhrase";
|
||||||
import FindOrCreateTicketServiceBot from "../TicketServices/FindOrCreateTicketServiceBot";
|
import FindOrCreateTicketServiceBot from "../TicketServices/FindOrCreateTicketServiceBot";
|
||||||
|
import { setMessageAsRead } from "../../helpers/SetMessageAsRead";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -400,7 +401,7 @@ async function sendDelayedMessages(wbot: any, ticket: Ticket, contact: Contact,
|
||||||
|
|
||||||
if (valid && valid.data.result == 'open') {
|
if (valid && valid.data.result == 'open') {
|
||||||
|
|
||||||
botSendMessage(ticket, `Protocolo validado, por favor, pode digitar o texto a ser adicionado no histórico do protocolo *${params[1]}*\n_Digite *0* sair dessa operação e voltar ao menu principal._`)
|
botSendMessage(ticket, `Protocolo validado, por favor, pode digitar o texto a ser adicionado no histórico do protocolo *${params[1]}*`)
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (valid && valid.data.result == 'notfound') {
|
else if (valid && valid.data.result == 'notfound') {
|
||||||
|
@ -414,7 +415,7 @@ async function sendDelayedMessages(wbot: any, ticket: Ticket, contact: Contact,
|
||||||
ticket,
|
ticket,
|
||||||
`O protocolo *${params[1]}* foi encerrado. Não é mais possível adicionar informação. Se desejar consultar o historico digite *1*`)
|
`O protocolo *${params[1]}* foi encerrado. Não é mais possível adicionar informação. Se desejar consultar o historico digite *1*`)
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -989,8 +990,6 @@ const handleMessage = async (
|
||||||
|
|
||||||
// console.log('----------> contact: ', JSON.parse(JSON.stringify(contact)))
|
// console.log('----------> contact: ', JSON.parse(JSON.stringify(contact)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (unreadMessages === 0 && whatsapp.farewellMessage && whatsapp.farewellMessage === msg.body) return;
|
if (unreadMessages === 0 && whatsapp.farewellMessage && whatsapp.farewellMessage === msg.body) return;
|
||||||
|
|
||||||
let ticket
|
let ticket
|
||||||
|
@ -1083,7 +1082,6 @@ const handleMessage = async (
|
||||||
await verifyQueue(wbot, msg, ticket, contact);
|
await verifyQueue(wbot, msg, ticket, contact);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// O bot interage com o cliente e encaminha o atendimento para fila de atendende quando o usuário escolhe a opção falar com atendente
|
// O bot interage com o cliente e encaminha o atendimento para fila de atendende quando o usuário escolhe a opção falar com atendente
|
||||||
|
|
||||||
//Habilitar esse caso queira usar o bot
|
//Habilitar esse caso queira usar o bot
|
||||||
|
@ -1141,7 +1139,7 @@ const handleMessage = async (
|
||||||
|
|
||||||
if (response && response.data.result) {
|
if (response && response.data.result) {
|
||||||
|
|
||||||
botSendMessage(ticket, `${response.data.result}\n_Digite *0* para falar com a HIT._`)
|
botSendMessage(ticket, `${response.data.result}`)
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -1179,11 +1177,13 @@ const handleMessage = async (
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (msg && !msg.fromMe && ticket.status == 'pending') {
|
||||||
|
|
||||||
|
await setMessageAsRead(ticket)
|
||||||
|
|
||||||
// if (msg.body.trim() == 'broken') {
|
}
|
||||||
// throw new Error('Throw makes it go boom!')
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
|
|
Binary file not shown.
|
@ -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 <LinearProgress />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={modalOpen} onClose={handleClose} maxWidth="xs" scroll="paper" classes={{ paper: classes.paper }}>
|
||||||
|
<DialogTitle id="form-dialog-title">
|
||||||
|
{i18n.t("newTicketModal.title")}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<FormControl variant="outlined" className={classes.maxWidth}>
|
||||||
|
<InputLabel>{i18n.t("Selecionar Fila")}</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={selectedQueue}
|
||||||
|
onChange={(e) => setSelectedQueue(e.target.value)}
|
||||||
|
label={i18n.t("Filas")}
|
||||||
|
>
|
||||||
|
<MenuItem value={''}> </MenuItem>
|
||||||
|
{queues.map(({ id, color, name }) => (
|
||||||
|
<MenuItem
|
||||||
|
key={id}
|
||||||
|
value={id}
|
||||||
|
onMouseEnter={() => setItemHover(id)}
|
||||||
|
onMouseLeave={() => setItemHover(-1)}
|
||||||
|
style={{
|
||||||
|
background: id !== itemHover ? "white" : color,
|
||||||
|
}}
|
||||||
|
>{name[0].toUpperCase() + name.slice(1).toLowerCase()}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={handleClose}
|
||||||
|
color="secondary"
|
||||||
|
disabled={loading}
|
||||||
|
variant="outlined"
|
||||||
|
>
|
||||||
|
{i18n.t("newTicketModal.buttons.cancel")}
|
||||||
|
</Button>
|
||||||
|
<ButtonWithSpinner
|
||||||
|
onClick={() => handleSaveTicket(contactId, user.id, selectedQueue)}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
{i18n.t("newTicketModal.buttons.ok")}
|
||||||
|
</ButtonWithSpinner>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ContactCreateTicketModal;
|
|
@ -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 { isSameDay, parseISO, format } from "date-fns";
|
||||||
import openSocket from "socket.io-client";
|
import openSocket from "socket.io-client";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import { AuthContext } from "../../context/Auth/AuthContext";
|
||||||
|
|
||||||
import { green } from "@material-ui/core/colors";
|
import { green } from "@material-ui/core/colors";
|
||||||
import {
|
import {
|
||||||
|
@ -318,6 +319,9 @@ const MessagesList = ({ ticketId, isGroup }) => {
|
||||||
const [anchorEl, setAnchorEl] = useState(null);
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
const messageOptionsMenuOpen = Boolean(anchorEl);
|
const messageOptionsMenuOpen = Boolean(anchorEl);
|
||||||
const currentTicketId = useRef(ticketId);
|
const currentTicketId = useRef(ticketId);
|
||||||
|
const [sendSeen, setSendSeen] = useState(false)
|
||||||
|
|
||||||
|
const { user } = useContext(AuthContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch({ type: "RESET" });
|
dispatch({ type: "RESET" });
|
||||||
|
@ -327,13 +331,77 @@ const MessagesList = ({ ticketId, isGroup }) => {
|
||||||
}, [ticketId]);
|
}, [ticketId]);
|
||||||
|
|
||||||
useEffect(() => {
|
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);
|
setLoading(true);
|
||||||
const delayDebounceFn = setTimeout(() => {
|
const delayDebounceFn = setTimeout(() => {
|
||||||
const fetchMessages = async () => {
|
const fetchMessages = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.get("/messages/" + ticketId, {
|
const { data } = await api.get("/messages/" + ticketId, {
|
||||||
params: { pageNumber },
|
params: { pageNumber },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (currentTicketId.current === ticketId) {
|
if (currentTicketId.current === ticketId) {
|
||||||
dispatch({ type: "LOAD_MESSAGES", payload: data.messages });
|
dispatch({ type: "LOAD_MESSAGES", payload: data.messages });
|
||||||
|
@ -365,17 +433,12 @@ const MessagesList = ({ ticketId, isGroup }) => {
|
||||||
|
|
||||||
if (data.action === "create") {
|
if (data.action === "create") {
|
||||||
|
|
||||||
console.log('ADD_MESSAGE: ', data.message)
|
|
||||||
|
|
||||||
dispatch({ type: "ADD_MESSAGE", payload: data.message });
|
dispatch({ type: "ADD_MESSAGE", payload: data.message });
|
||||||
|
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.action === "update") {
|
if (data.action === "update") {
|
||||||
|
|
||||||
console.log('joinChatBox update: ',data.action)
|
|
||||||
|
|
||||||
dispatch({ type: "UPDATE_MESSAGE", payload: data.message });
|
dispatch({ type: "UPDATE_MESSAGE", payload: data.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -391,7 +454,11 @@ const MessagesList = ({ ticketId, isGroup }) => {
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
if (lastMessageRef.current) {
|
if (lastMessageRef.current) {
|
||||||
|
|
||||||
|
setSendSeen(true)
|
||||||
|
|
||||||
lastMessageRef.current.scrollIntoView({});
|
lastMessageRef.current.scrollIntoView({});
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -513,7 +580,7 @@ const MessagesList = ({ ticketId, isGroup }) => {
|
||||||
if (index === messagesList.length - 1) {
|
if (index === messagesList.length - 1) {
|
||||||
|
|
||||||
let messageDay = parseISO(messagesList[index].createdAt);
|
let messageDay = parseISO(messagesList[index].createdAt);
|
||||||
let previousMessageDay = parseISO(messagesList[index - 1].createdAt);
|
let previousMessageDay = parseISO(messagesList[index - 1].createdAt);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -682,4 +749,4 @@ const MessagesList = ({ ticketId, isGroup }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MessagesList;
|
export default MessagesList;
|
|
@ -116,17 +116,13 @@ const NotificationsPopOver = () => {
|
||||||
|
|
||||||
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
||||||
|
|
||||||
socket.on("reload_page", (data) => {
|
socket.on("reload_page", (data) => {
|
||||||
|
|
||||||
console.log('UPDATING THE PAGE: ', data.userId, ' | user.id: ', user.id)
|
|
||||||
|
|
||||||
if (user.id === data.userId) {
|
if (user.id === data.userId) {
|
||||||
|
|
||||||
window.location.reload(true);
|
window.location.reload(true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -167,8 +163,7 @@ const NotificationsPopOver = () => {
|
||||||
clearInterval(_fifo);
|
clearInterval(_fifo);
|
||||||
}
|
}
|
||||||
|
|
||||||
_fifo = setInterval(() => {
|
_fifo = setInterval(() => {
|
||||||
console.log('user.id: ', user.id)
|
|
||||||
socket.emit("online", user.id)
|
socket.emit("online", user.id)
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import { i18n } from "../../../translate/i18n";
|
// import { i18n } from "../../../translate/i18n";
|
||||||
|
|
||||||
|
|
||||||
import ptBrLocale from "date-fns/locale/pt-BR";
|
import ptBrLocale from "date-fns/locale/pt-BR";
|
||||||
|
|
|
@ -38,14 +38,14 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
|
||||||
|
|
||||||
// const [useDialogflow, setUseDialogflow] = useState(ticket.contact.useDialogflow);
|
// const [useDialogflow, setUseDialogflow] = useState(ticket.contact.useDialogflow);
|
||||||
|
|
||||||
const [useDialogflow, setUseDialogflow] = useState(() => {
|
// const [/*useDialogflow*/, setUseDialogflow] = useState(() => {
|
||||||
if (Object.keys(ticket).length != 0) {
|
// if (Object.keys(ticket).length !== 0) {
|
||||||
return ticket.contact.useDialogflow;
|
// return ticket.contact.useDialogflow;
|
||||||
} else {
|
// } else {
|
||||||
// Set a default value if `ticket.contact.useDialogflow` is null
|
// // Set a default value if `ticket.contact.useDialogflow` is null
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
const ticketOptionsMenuOpen = Boolean(anchorEl);
|
const ticketOptionsMenuOpen = Boolean(anchorEl);
|
||||||
const { user } = useContext(AuthContext);
|
const { user } = useContext(AuthContext);
|
||||||
|
@ -132,17 +132,17 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleContactToggleUseDialogflow = async () => {
|
// const handleContactToggleUseDialogflow = async () => {
|
||||||
setLoading(true);
|
// setLoading(true);
|
||||||
try {
|
// try {
|
||||||
const contact = await api.put(`/contacts/toggleUseDialogflow/${ticket.contact.id}`);
|
// const contact = await api.put(`/contacts/toggleUseDialogflow/${ticket.contact.id}`);
|
||||||
setUseDialogflow(contact.data.useDialogflow);
|
// setUseDialogflow(contact.data.useDialogflow);
|
||||||
setLoading(false);
|
// setLoading(false);
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
setLoading(false);
|
// setLoading(false);
|
||||||
toastError(err);
|
// toastError(err);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import React, { useState, useEffect, useReducer, useContext } from "react";
|
import React, { useState, useEffect, useReducer, useContext } from "react";
|
||||||
|
|
||||||
|
|
||||||
import openSocket from "socket.io-client";
|
import openSocket from "socket.io-client";
|
||||||
|
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
@ -175,7 +177,7 @@ const reducer = (state, action) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const TicketsList = (props) => {
|
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 classes = useStyles();
|
||||||
const [pageNumber, setPageNumber] = useState(1);
|
const [pageNumber, setPageNumber] = useState(1);
|
||||||
const [ticketsList, dispatch] = useReducer(reducer, []);
|
const [ticketsList, dispatch] = useReducer(reducer, []);
|
||||||
|
@ -184,16 +186,16 @@ const TicketsList = (props) => {
|
||||||
const { searchTicket } = useContext(SearchTicketContext)
|
const { searchTicket } = useContext(SearchTicketContext)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
dispatch({ type: "RESET" });
|
dispatch({ type: "RESET" });
|
||||||
setPageNumber(1);
|
setPageNumber(1);
|
||||||
|
|
||||||
}, [status, searchParam, showAll, selectedQueueIds, searchTicket]);
|
}, [status, searchParam, searchParamContent, showAll, selectedQueueIds, searchTicket]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const { tickets, hasMore, loading } = useTickets({
|
const { tickets, hasMore, loading } = useTickets({
|
||||||
pageNumber,
|
pageNumber,
|
||||||
searchParam,
|
searchParam,
|
||||||
|
searchParamContent,
|
||||||
status,
|
status,
|
||||||
showAll,
|
showAll,
|
||||||
queueIds: JSON.stringify(selectedQueueIds),
|
queueIds: JSON.stringify(selectedQueueIds),
|
||||||
|
@ -201,6 +203,7 @@ const TicketsList = (props) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
if (!status && !searchParam) return;
|
if (!status && !searchParam) return;
|
||||||
|
|
||||||
// if (searchParam) {
|
// if (searchParam) {
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
import React, { useContext, useEffect, useRef, useState } from "react";
|
import React, { useContext, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import { IconButton } from "@mui/material";
|
||||||
import Paper from "@material-ui/core/Paper";
|
import Paper from "@material-ui/core/Paper";
|
||||||
import SearchIcon from "@material-ui/icons/Search";
|
|
||||||
import InputBase from "@material-ui/core/InputBase";
|
import InputBase from "@material-ui/core/InputBase";
|
||||||
import Tabs from "@material-ui/core/Tabs";
|
import Tabs from "@material-ui/core/Tabs";
|
||||||
import Tab from "@material-ui/core/Tab";
|
import Tab from "@material-ui/core/Tab";
|
||||||
import Badge from "@material-ui/core/Badge";
|
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 MoveToInboxIcon from "@material-ui/icons/MoveToInbox";
|
||||||
import CheckBoxIcon from "@material-ui/icons/CheckBox";
|
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 FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||||
import Switch from "@material-ui/core/Switch";
|
import Switch from "@material-ui/core/Switch";
|
||||||
|
@ -63,6 +70,25 @@ const useStyles = makeStyles((theme) => ({
|
||||||
},
|
},
|
||||||
|
|
||||||
serachInputWrapper: {
|
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,
|
flex: 1,
|
||||||
background: "#fff",
|
background: "#fff",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
@ -78,6 +104,11 @@ const useStyles = makeStyles((theme) => ({
|
||||||
alignSelf: "center",
|
alignSelf: "center",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
menuSearch: {
|
||||||
|
color: "grey",
|
||||||
|
alignSelf: "center",
|
||||||
|
},
|
||||||
|
|
||||||
searchInput: {
|
searchInput: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
border: "none",
|
border: "none",
|
||||||
|
@ -95,20 +126,21 @@ const useStyles = makeStyles((theme) => ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const DEFAULT_SEARCH_PARAM = { searchParam: "", searchParamContent: "" }
|
||||||
|
|
||||||
const TicketsManager = () => {
|
const TicketsManager = () => {
|
||||||
|
|
||||||
const { tabOption, setTabOption } = useContext(TabTicketContext);
|
const { tabOption, setTabOption } = useContext(TabTicketContext);
|
||||||
|
|
||||||
const {setSearchTicket} = useContext(SearchTicketContext)
|
const { setSearchTicket } = useContext(SearchTicketContext)
|
||||||
|
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
const [searchParam, setSearchParam] = useState("");
|
const [searchParam, setSearchParam] = useState(DEFAULT_SEARCH_PARAM);
|
||||||
const [tab, setTab] = useState("open");
|
const [tab, setTab] = useState("open");
|
||||||
const [tabOpen, setTabOpen] = useState("open");
|
const [tabOpen, setTabOpen] = useState("open");
|
||||||
const [newTicketModalOpen, setNewTicketModalOpen] = useState(false);
|
const [newTicketModalOpen, setNewTicketModalOpen] = useState(false);
|
||||||
const [showAllTickets, setShowAllTickets] = useState(false);
|
const [showAllTickets, setShowAllTickets] = useState(false);
|
||||||
const searchInputRef = useRef();
|
|
||||||
const { user } = useContext(AuthContext);
|
const { user } = useContext(AuthContext);
|
||||||
|
|
||||||
const [openCount, setOpenCount] = useState(0);
|
const [openCount, setOpenCount] = useState(0);
|
||||||
|
@ -117,7 +149,16 @@ const TicketsManager = () => {
|
||||||
const userQueueIds = user.queues.map((q) => q.id);
|
const userQueueIds = user.queues.map((q) => q.id);
|
||||||
const [selectedQueueIds, setSelectedQueueIds] = useState(userQueueIds || []);
|
const [selectedQueueIds, setSelectedQueueIds] = useState(userQueueIds || []);
|
||||||
|
|
||||||
|
const [showContentSearch, setShowContentSearch] = useState(false)
|
||||||
|
const searchInputRef = useRef();
|
||||||
|
const searchContentInputRef = useRef();
|
||||||
const [inputSearch, setInputSearch] = useState('');
|
const [inputSearch, setInputSearch] = useState('');
|
||||||
|
const [inputContentSearch, setInputContentSearch] = useState("")
|
||||||
|
|
||||||
|
const [openTooltipSearch, setOpenTooltipSearch] = useState(false)
|
||||||
|
|
||||||
|
let searchTimeout;
|
||||||
|
let searchContentTimeout;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user.profile.toUpperCase() === "ADMIN") {
|
if (user.profile.toUpperCase() === "ADMIN") {
|
||||||
|
@ -135,22 +176,45 @@ const TicketsManager = () => {
|
||||||
|
|
||||||
}, [tab, setTabOption]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
|
|
||||||
if (tabOption === 'open') {
|
if (tabOption === 'open') {
|
||||||
|
|
||||||
setTabOption('')
|
setTabOption('')
|
||||||
setSearchParam('');
|
setSearchParam(DEFAULT_SEARCH_PARAM);
|
||||||
setInputSearch('');
|
setInputSearch('');
|
||||||
|
setInputContentSearch('')
|
||||||
setTab("open");
|
setTab("open");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [tabOption, setTabOption])
|
}, [tabOption, setTabOption])
|
||||||
|
|
||||||
let searchTimeout;
|
|
||||||
|
|
||||||
const removeExtraSpace = (str) => {
|
const removeExtraSpace = (str) => {
|
||||||
|
|
||||||
str = str.replace(/^\s+/g, '')
|
str = str.replace(/^\s+/g, '')
|
||||||
|
@ -163,24 +227,58 @@ const TicketsManager = () => {
|
||||||
|
|
||||||
setInputSearch(removeExtraSpace(searchedTerm))
|
setInputSearch(removeExtraSpace(searchedTerm))
|
||||||
|
|
||||||
setSearchTicket(searchParam)
|
setSearchTicket(searchParam.searchParam)
|
||||||
|
|
||||||
clearTimeout(searchTimeout);
|
clearTimeout(searchTimeout);
|
||||||
|
|
||||||
if (searchedTerm === "") {
|
if (searchedTerm === "") {
|
||||||
setSearchParam(searchedTerm);
|
setSearchParam(prev => ({ ...prev, searchParam: searchedTerm }))
|
||||||
setInputSearch(searchedTerm)
|
setInputSearch(searchedTerm)
|
||||||
|
setShowContentSearch(false)
|
||||||
setTab("open");
|
setTab("open");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (searchedTerm.length < 4) {
|
||||||
|
setSearchParam(prev => ({ ...prev, searchParamContent: "" }))
|
||||||
|
setInputContentSearch('')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
searchTimeout = setTimeout(() => {
|
searchTimeout = setTimeout(() => {
|
||||||
|
|
||||||
setSearchParam(searchedTerm);
|
setSearchParam(prev => ({ ...prev, searchParam: searchedTerm }));
|
||||||
|
|
||||||
}, 500);
|
}, 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) => {
|
const handleChangeTab = (e, newValue) => {
|
||||||
setTab(newValue);
|
setTab(newValue);
|
||||||
};
|
};
|
||||||
|
@ -233,15 +331,50 @@ const TicketsManager = () => {
|
||||||
<Paper square elevation={0} className={classes.ticketOptionsBox}>
|
<Paper square elevation={0} className={classes.ticketOptionsBox}>
|
||||||
{tab === "search" ? (
|
{tab === "search" ? (
|
||||||
<div className={classes.serachInputWrapper}>
|
<div className={classes.serachInputWrapper}>
|
||||||
<SearchIcon className={classes.searchIcon} />
|
<div className={classes.searchInputHeader}>
|
||||||
<InputBase
|
<SearchIcon className={classes.searchIcon} />
|
||||||
className={classes.searchInput}
|
<InputBase
|
||||||
inputRef={searchInputRef}
|
className={classes.searchInput}
|
||||||
placeholder={i18n.t("tickets.search.placeholder")}
|
inputRef={searchInputRef}
|
||||||
type="search"
|
placeholder={i18n.t("tickets.search.placeholder")}
|
||||||
value={inputSearch}
|
type="search"
|
||||||
onChange={handleSearch}
|
value={inputSearch}
|
||||||
/>
|
onChange={handleSearch}
|
||||||
|
/>
|
||||||
|
{/* <IconButton onClick={() => setShowContentSearch(prev => !prev)}>
|
||||||
|
<MenuIcon className={classes.menuSearch} />
|
||||||
|
</IconButton> */}
|
||||||
|
<Tooltip
|
||||||
|
open={openTooltipSearch}
|
||||||
|
onOpen={() => handleOpenTooltipSearch()}
|
||||||
|
onClose={() => handleCloseTooltipSearch()}
|
||||||
|
title="Digite pelo menos 4 caracteres"
|
||||||
|
arrow>
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
disabled={searchParam.searchParam.length < 4}
|
||||||
|
onClick={() => setShowContentSearch(prev => !prev)}
|
||||||
|
>
|
||||||
|
<MenuIcon className={classes.menuSearch} />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
// showContentSearch ?
|
||||||
|
(showContentSearch && searchParam.searchParam.length >= 4) ?
|
||||||
|
(<div className={classes.searchContentInput}>
|
||||||
|
<FindInPageIcon className={classes.searchIcon} />
|
||||||
|
<InputBase
|
||||||
|
className={classes.searchInput}
|
||||||
|
inputRef={searchContentInputRef}
|
||||||
|
placeholder={i18n.t("Busca por conteúdo")}
|
||||||
|
type="search"
|
||||||
|
value={inputContentSearch}
|
||||||
|
onChange={(e) => handleContentSearch(e)}
|
||||||
|
/>
|
||||||
|
</div>) : null
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
@ -340,17 +473,18 @@ const TicketsManager = () => {
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value={tab} name="search" className={classes.ticketsWrapper}>
|
<TabPanel value={tab} name="search" className={classes.ticketsWrapper}>
|
||||||
|
|
||||||
|
|
||||||
<TicketsList
|
<TicketsList
|
||||||
searchParam={searchParam}
|
searchParam={searchParam.searchParam}
|
||||||
tab={tab}
|
searchParamContent={searchParam.searchParamContent}
|
||||||
showAll={true}
|
tab={tab}
|
||||||
selectedQueueIds={selectedQueueIds}
|
showAll={true}
|
||||||
/>
|
selectedQueueIds={selectedQueueIds}
|
||||||
|
/>
|
||||||
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TicketsManager;
|
export default TicketsManager;
|
|
@ -1,8 +1,7 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useContext, useMemo } from "react";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
|
|
||||||
import Button from "@material-ui/core/Button";
|
import Button from "@material-ui/core/Button";
|
||||||
import TextField from "@material-ui/core/TextField";
|
|
||||||
import Dialog from "@material-ui/core/Dialog";
|
import Dialog from "@material-ui/core/Dialog";
|
||||||
import Select from "@material-ui/core/Select";
|
import Select from "@material-ui/core/Select";
|
||||||
import FormControl from "@material-ui/core/FormControl";
|
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 DialogActions from "@material-ui/core/DialogActions";
|
||||||
import DialogContent from "@material-ui/core/DialogContent";
|
import DialogContent from "@material-ui/core/DialogContent";
|
||||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
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 { i18n } from "../../translate/i18n";
|
||||||
import api from "../../services/api";
|
import api from "../../services/api";
|
||||||
import ButtonWithSpinner from "../ButtonWithSpinner";
|
import ButtonWithSpinner from "../ButtonWithSpinner";
|
||||||
import toastError from "../../errors/toastError";
|
import toastError from "../../errors/toastError";
|
||||||
import useQueues from "../../hooks/useQueues";
|
|
||||||
|
import { WhatsAppsContext } from "../../context/WhatsApp/WhatsAppsContext";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
maxWidth: {
|
maxWidth: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const filterOptions = createFilterOptions({
|
// Receive array of queues arrays
|
||||||
trim: true,
|
// 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 TransferTicketModal = ({ modalOpen, onClose, ticketid }) => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [options, setOptions] = useState([]);
|
const { whatsApps } = useContext(WhatsAppsContext);
|
||||||
const [queues, setQueues] = useState([]);
|
|
||||||
const [allQueues, setAllQueues] = useState([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [searchParam, setSearchParam] = useState("");
|
|
||||||
const [selectedUser, setSelectedUser] = useState(null);
|
|
||||||
const [selectedQueue, setSelectedQueue] = useState('');
|
const [selectedQueue, setSelectedQueue] = useState('');
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { findAll: findAllQueues } = useQueues();
|
const queues = useMemo(() => {
|
||||||
|
if (!whatsApps) return []
|
||||||
useEffect(() => {
|
const whatsAppsQueues = whatsApps.map(({ queues }) => queues)
|
||||||
const loadQueues = async () => {
|
//const whatsAppsQueues = whatsApps.filter(({ status }) => status === "CONNECTED" ).map(({ queues }) => queues)
|
||||||
const list = await findAllQueues();
|
const uniqueQueuesAvailable = queueArraysToOneArray(whatsAppsQueues)
|
||||||
setAllQueues(list);
|
return uniqueQueuesAvailable
|
||||||
setQueues(list);
|
}, [whatsApps])
|
||||||
}
|
const [itemHover, setItemHover] = useState(-1)
|
||||||
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 handleClose = () => {
|
const handleClose = () => {
|
||||||
onClose();
|
onClose();
|
||||||
setSearchParam("");
|
|
||||||
setSelectedUser(null);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveTicket = async e => {
|
const handleSaveTicket = async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!ticketid) return;
|
if (!ticketid) return;
|
||||||
|
if (!selectedQueue) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
let data = {};
|
let data = {};
|
||||||
|
|
||||||
if (selectedUser) {
|
|
||||||
data.userId = selectedUser.id
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedQueue && selectedQueue !== null) {
|
if (selectedQueue && selectedQueue !== null) {
|
||||||
data.queueId = selectedQueue
|
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
|
// 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.status = 'pending'
|
||||||
|
data.transfer = true
|
||||||
|
|
||||||
await api.put(`/tickets/${ticketid}`, data);
|
await api.put(`/tickets/${ticketid}`, data);
|
||||||
|
|
||||||
|
@ -127,56 +96,26 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => {
|
||||||
{i18n.t("transferTicketModal.title")}
|
{i18n.t("transferTicketModal.title")}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
<Autocomplete
|
|
||||||
style={{ width: 300, marginBottom: 20 }}
|
|
||||||
getOptionLabel={option => `${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 => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
label={i18n.t("transferTicketModal.fieldLabel")}
|
|
||||||
variant="outlined"
|
|
||||||
required
|
|
||||||
autoFocus
|
|
||||||
onChange={e => setSearchParam(e.target.value)}
|
|
||||||
InputProps={{
|
|
||||||
...params.InputProps,
|
|
||||||
endAdornment: (
|
|
||||||
<React.Fragment>
|
|
||||||
{loading ? (
|
|
||||||
<CircularProgress color="inherit" size={20} />
|
|
||||||
) : null}
|
|
||||||
{params.InputProps.endAdornment}
|
|
||||||
</React.Fragment>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormControl variant="outlined" className={classes.maxWidth}>
|
<FormControl variant="outlined" className={classes.maxWidth}>
|
||||||
<InputLabel>{i18n.t("transferTicketModal.fieldQueueLabel")}</InputLabel>
|
<InputLabel>{i18n.t("transferTicketModal.fieldQueueLabel")}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={selectedQueue}
|
value={selectedQueue}
|
||||||
onChange={(e) => setSelectedQueue(e.target.value)}
|
onChange={(e) => setSelectedQueue(e.target.value)}
|
||||||
label={i18n.t("transferTicketModal.fieldQueuePlaceholder")}
|
label={i18n.t("transferTicketModal.fieldQueuePlaceholder")}
|
||||||
|
required
|
||||||
>
|
>
|
||||||
<MenuItem value={''}> </MenuItem>
|
<MenuItem style={{ background: "white", }} value={''}> </MenuItem>
|
||||||
{queues.map((queue) => (
|
{queues.map((queue) => (
|
||||||
<MenuItem key={queue.id} value={queue.id}>{queue.name}</MenuItem>
|
<MenuItem
|
||||||
|
key={queue.id}
|
||||||
|
value={queue.id}
|
||||||
|
onMouseEnter={() => setItemHover(queue.id)}
|
||||||
|
onMouseLeave={() => setItemHover(-1)}
|
||||||
|
style={{
|
||||||
|
background: queue.id !== itemHover ? "white" : queue.color,
|
||||||
|
}}
|
||||||
|
>{queue.name}
|
||||||
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
@ -200,8 +139,8 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => {
|
||||||
</ButtonWithSpinner>
|
</ButtonWithSpinner>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</form>
|
</form>
|
||||||
</Dialog>
|
</Dialog >
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TransferTicketModal;
|
export default TransferTicketModal;
|
|
@ -5,6 +5,7 @@ import api from "../../services/api";
|
||||||
|
|
||||||
const useTickets = ({
|
const useTickets = ({
|
||||||
searchParam,
|
searchParam,
|
||||||
|
searchParamContent,
|
||||||
pageNumber,
|
pageNumber,
|
||||||
status,
|
status,
|
||||||
date,
|
date,
|
||||||
|
@ -35,6 +36,7 @@ const useTickets = ({
|
||||||
const { data } = await api.get("/tickets", {
|
const { data } = await api.get("/tickets", {
|
||||||
params: {
|
params: {
|
||||||
searchParam,
|
searchParam,
|
||||||
|
searchParamContent,
|
||||||
pageNumber,
|
pageNumber,
|
||||||
status,
|
status,
|
||||||
date,
|
date,
|
||||||
|
@ -63,6 +65,7 @@ const useTickets = ({
|
||||||
return () => clearTimeout(delayDebounceFn);
|
return () => clearTimeout(delayDebounceFn);
|
||||||
}, [
|
}, [
|
||||||
searchParam,
|
searchParam,
|
||||||
|
searchParamContent,
|
||||||
pageNumber,
|
pageNumber,
|
||||||
status,
|
status,
|
||||||
date,
|
date,
|
||||||
|
|
|
@ -37,8 +37,9 @@ import { Can } from "../../components/Can";
|
||||||
|
|
||||||
import apiBroker from "../../services/apiBroker";
|
import apiBroker from "../../services/apiBroker";
|
||||||
import fileDownload from 'js-file-download'
|
import fileDownload from 'js-file-download'
|
||||||
|
import ContactCreateTicketModal from "../../components/ContactCreateTicketModal";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const reducer = (state, action) => {
|
const reducer = (state, action) => {
|
||||||
|
|
||||||
|
@ -111,6 +112,7 @@ const Contacts = () => {
|
||||||
const [contacts, dispatch] = useReducer(reducer, []);
|
const [contacts, dispatch] = useReducer(reducer, []);
|
||||||
const [selectedContactId, setSelectedContactId] = useState(null);
|
const [selectedContactId, setSelectedContactId] = useState(null);
|
||||||
const [contactModalOpen, setContactModalOpen] = useState(false);
|
const [contactModalOpen, setContactModalOpen] = useState(false);
|
||||||
|
const [isCreateTicketModalOpen, setIsCreateTicketModalOpen] = useState(false)
|
||||||
const [deletingContact, setDeletingContact] = useState(null);
|
const [deletingContact, setDeletingContact] = useState(null);
|
||||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||||
const [hasMore, setHasMore] = useState(false);
|
const [hasMore, setHasMore] = useState(false);
|
||||||
|
@ -118,17 +120,17 @@ const Contacts = () => {
|
||||||
|
|
||||||
const [onQueueStatus, setOnQueueProcessStatus] = useState(undefined)
|
const [onQueueStatus, setOnQueueProcessStatus] = useState(undefined)
|
||||||
|
|
||||||
const [zipfile, setZipFile] = useState()
|
const [zipfile, setZipFile] = useState()
|
||||||
|
|
||||||
|
|
||||||
async function handleChange(event) {
|
async function handleChange(event) {
|
||||||
|
|
||||||
try {
|
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!')
|
alert('Arquivo não pode ser maior que 4 MB!')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("adminId", user.id);
|
formData.append("adminId", user.id);
|
||||||
|
@ -302,21 +304,30 @@ const Contacts = () => {
|
||||||
setContactModalOpen(false);
|
setContactModalOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveTicket = async (contactId) => {
|
const handleOpenCreateTicketModal = (contactId) => {
|
||||||
if (!contactId) return;
|
setSelectedContactId(contactId)
|
||||||
setLoading(true);
|
setIsCreateTicketModalOpen(true)
|
||||||
try {
|
}
|
||||||
const { data: ticket } = await api.post("/tickets", {
|
|
||||||
contactId: contactId,
|
const handleCloseCreateTicketModal = () => {
|
||||||
userId: user?.id,
|
setIsCreateTicketModalOpen(false)
|
||||||
status: "open",
|
}
|
||||||
});
|
|
||||||
history.push(`/tickets/${ticket.id}`);
|
// const handleSaveTicket = async (contactId) => {
|
||||||
} catch (err) {
|
// if (!contactId) return;
|
||||||
toastError(err);
|
// setLoading(true);
|
||||||
}
|
// try {
|
||||||
setLoading(false);
|
// 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) => {
|
const hadleEditContact = (contactId) => {
|
||||||
setSelectedContactId(contactId);
|
setSelectedContactId(contactId);
|
||||||
|
@ -415,21 +426,21 @@ const Contacts = () => {
|
||||||
switch (param) {
|
switch (param) {
|
||||||
case 'empty':
|
case 'empty':
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
accept=".csv"
|
accept=".csv"
|
||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
id="contained-button-file"
|
id="contained-button-file"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<label htmlFor="contained-button-file">
|
<label htmlFor="contained-button-file">
|
||||||
<Button variant="contained" color="primary" component="span">
|
<Button variant="contained" color="primary" component="span">
|
||||||
CSV UPLOAD
|
CSV UPLOAD
|
||||||
</Button>
|
</Button>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{/* <Button
|
{/* <Button
|
||||||
disabled={query && query.length > 0 ? false : true}
|
disabled={query && query.length > 0 ? false : true}
|
||||||
|
@ -495,6 +506,11 @@ const Contacts = () => {
|
||||||
aria-labelledby="form-dialog-title"
|
aria-labelledby="form-dialog-title"
|
||||||
contactId={selectedContactId}
|
contactId={selectedContactId}
|
||||||
></ContactModal>
|
></ContactModal>
|
||||||
|
<ContactCreateTicketModal
|
||||||
|
modalOpen={isCreateTicketModalOpen}
|
||||||
|
onClose={handleCloseCreateTicketModal}
|
||||||
|
contactId={selectedContactId}
|
||||||
|
/>
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
title={
|
title={
|
||||||
deletingContact
|
deletingContact
|
||||||
|
@ -609,7 +625,7 @@ const Contacts = () => {
|
||||||
<TableCell align="center">
|
<TableCell align="center">
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => handleSaveTicket(contact.id)}
|
onClick={() => handleOpenCreateTicketModal(contact.id)}
|
||||||
>
|
>
|
||||||
<WhatsAppIcon />
|
<WhatsAppIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -646,4 +662,4 @@ const Contacts = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Contacts;
|
export default Contacts;
|
|
@ -16,7 +16,7 @@ const useLoadData = (setLoading, dispatch, route, dispatchType) => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, []);
|
}, [setLoading, dispatch, route, dispatchType]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useLoadData;
|
export default useLoadData;
|
|
@ -18,7 +18,7 @@ const useSocket = (dispatch, socketEvent, typeUpdate, typeDelete, payloadUpdate,
|
||||||
return () => {
|
return () => {
|
||||||
socket.disconnect();
|
socket.disconnect();
|
||||||
};
|
};
|
||||||
}, []);
|
}, [dispatch, socketEvent, typeUpdate, typeDelete, payloadUpdate, payloadDelete]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useSocket;
|
export default useSocket;
|
Loading…
Reference in New Issue