Merge branch 'master' of github.com:AdrianoRobson/projeto-hit

pull/22/head
Adriano 2024-03-04 16:46:25 -03:00
commit e5dff2a6d5
29 changed files with 1148 additions and 404 deletions

View File

@ -20,12 +20,18 @@ import {
} from "../helpers/ContactsCache"; } from "../helpers/ContactsCache";
import { off } from "process"; import { off } from "process";
import GetContactService from "../services/ContactServices/GetContactService"
type IndexQuery = { type IndexQuery = {
searchParam: string; searchParam: string;
pageNumber: string; pageNumber: string;
}; };
type IndexGetContactQuery = {
name: string;
number: string;
};
interface ExtraInfo { interface ExtraInfo {
name: string; name: string;
value: string; value: string;
@ -84,6 +90,20 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
return res.json({ contacts, count, hasMore }); return res.json({ contacts, count, hasMore });
}; };
export const getContact = async (
req: Request,
res: Response
): Promise<Response> => {
const { name, number } = req.body as IndexGetContactQuery;
const contact = await GetContactService({
name,
number
});
return res.status(200).json(contact);
};
export const store = async (req: Request, res: Response): Promise<Response> => { export const store = async (req: Request, res: Response): Promise<Response> => {
const newContact: ContactData = req.body; const newContact: ContactData = req.body;
newContact.number = newContact.number.replace("-", "").replace(" ", ""); newContact.number = newContact.number.replace("-", "").replace(" ", "");

View File

@ -76,7 +76,7 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
let payloadComponents = []; let payloadComponents = [];
try { try {
for (let i in params) { for (let i in params) {
const { parameters, language, type } = params[i]; const { parameters, language, type } = params[i];
if (type == "BODY") { if (type == "BODY") {
@ -97,7 +97,7 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
} }
const name = params.find((p: any) => p?.template_name); const name = params.find((p: any) => p?.template_name);
const { language }: any = params.find((p: any) => p?.language); const { language }: any = params?.find((p: any) => p?.language) || 'pt_BR'
const { template_name } = name; const { template_name } = name;
@ -110,9 +110,10 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
} }
}; };
console.log("TEMPLATE: ", template);
sendWhatsAppMessageOfficialAPI(ticket, body, null, template); sendWhatsAppMessageOfficialAPI(ticket, body, null, template);
console.log("TEMPLATE: ", template);
return res.send(); return res.send();
} }
} catch (error: any) { } catch (error: any) {

View File

@ -4,28 +4,19 @@ import { Request, Response } from "express";
import AppError from "../errors/AppError"; import AppError from "../errors/AppError";
import ShowTicketReport from "../services/TicketServices/ShowTicketReport"; import ShowTicketReport from "../services/TicketServices/ShowTicketReport";
import ShowMessageReport from "../services/MessageServices/ShowMessageReport"; import ShowMessageReport from "../services/MessageServices/ShowMessageReport";
import onlineUserService from "../services/UserServices/CreateOrUpdateOnlineUserService"; import onlineUserService from "../services/UserServices/CreateOrUpdateOnlineUserService";
import User from "../models/User"; import User from "../models/User";
import Queue from "../models/Queue"; import Queue from "../models/Queue";
import UserOnlineTime from "../models/UserOnlineTime"; import UserOnlineTime from "../models/UserOnlineTime";
import { Op, Sequelize, literal } from "sequelize"; import { Op, Sequelize, literal } from "sequelize";
import format from 'date-fns/format'; import format from "date-fns/format";
import ptBR from 'date-fns/locale/pt-BR'; import ptBR from "date-fns/locale/pt-BR";
import { splitDateTime } from "../helpers/SplitDateTime"; import { splitDateTime } from "../helpers/SplitDateTime";
import ListUserOnlineOffline from "../services/UserServices/ListUsersOnlineOfflineService"; import ListUserOnlineOffline from "../services/UserServices/ListUsersOnlineOfflineService";
import ListUserParamiterService from "../services/UserServices/ListUserParamiterService"; import ListUserParamiterService from "../services/UserServices/ListUserParamiterService";
import ShowUserServiceReport from "../services/UserServices/ShowUserServiceReport"; import ShowUserServiceReport from "../services/UserServices/ShowUserServiceReport";
import CountTicketsByUserQueue from "../services/UserServices/CountTicketsByUserQueue"; import CountTicketsByUserQueue from "../services/UserServices/CountTicketsByUserQueue";
import ShowQueuesByUser from "../services/UserServices/ShowQueuesByUser"; import ShowQueuesByUser from "../services/UserServices/ShowQueuesByUser";
// import { filter } from "bluebird";
import { getIO } from "../libs/socket"; import { getIO } from "../libs/socket";
import { Json } from "sequelize/types/lib/utils"; import { Json } from "sequelize/types/lib/utils";
@ -33,6 +24,8 @@ type IndexQuery = {
userId: string; userId: string;
startDate: string; startDate: string;
endDate: string; endDate: string;
createdOrUpdated: string;
queueId: string;
pageNumber: string; pageNumber: string;
userQueues: []; userQueues: [];
}; };
@ -40,11 +33,12 @@ type IndexQuery = {
type ReportOnQueue = { type ReportOnQueue = {
userId: string; userId: string;
identifier: string; identifier: string;
} };
export const reportUserByDateStartDateEnd = async (req: Request, res: Response): Promise<Response> => {
export const reportUserByDateStartDateEnd = async (
req: Request,
res: Response
): Promise<Response> => {
if ( if (
req.user.profile !== "master" && req.user.profile !== "master" &&
req.user.profile !== "admin" && req.user.profile !== "admin" &&
@ -53,79 +47,145 @@ export const reportUserByDateStartDateEnd = async (req: Request, res: Response):
throw new AppError("ERR_NO_PERMISSION", 403); throw new AppError("ERR_NO_PERMISSION", 403);
} }
const { userId, startDate, endDate, pageNumber, userQueues } = req.query as IndexQuery const {
userId,
startDate,
endDate,
pageNumber,
userQueues,
createdOrUpdated,
queueId
} = req.query as IndexQuery;
console.log("userId, startDate, endDate, pageNumber: ", userId, startDate, endDate, pageNumber); console.log(
"userId, startDate, endDate, pageNumber, userQueues, createdOrUpdated, queueId: ",
userId,
startDate,
endDate,
pageNumber,
userQueues,
createdOrUpdated,
queueId
);
const { tickets, count, hasMore } = await ShowTicketReport({ userId, startDate, endDate, pageNumber }); // return res.status(200).json({ tickets:[], count:0, hasMore:false, queues:[] });
// console.log('kkkkkkkkkkkkkkkkkk tickets: ', JSON.stringify(tickets, null, 6))
const { tickets, count, hasMore } = await ShowTicketReport({
return res.status(200).json({ tickets, count, hasMore }); userId,
startDate,
endDate,
pageNumber,
createdOrUpdated,
queueId
});
const queues = await Queue.findAll({ attributes: ["id", "name"] });
return res.status(200).json({ tickets, count, hasMore, queues });
}; };
export const reportUserService = async (
export const reportUserService = async (req: Request, res: Response): Promise<Response> => { req: Request,
res: Response
if (req.user.profile !== "master" && req.user.profile !== "admin" && req.user.profile !=="supervisor") { ): Promise<Response> => {
if (
req.user.profile !== "master" &&
req.user.profile !== "admin" &&
req.user.profile !== "supervisor"
) {
throw new AppError("ERR_NO_PERMISSION", 403); throw new AppError("ERR_NO_PERMISSION", 403);
} }
const { userId, startDate, endDate } = req.query as IndexQuery const { userId, startDate, endDate } = req.query as IndexQuery;
// let usersProfile = await ListUserParamiterService({ profile: 'user' }) // let usersProfile = await ListUserParamiterService({ profile: 'user' })
let usersProfile = await ListUserParamiterService({ let usersProfile = await ListUserParamiterService({
profiles: ["user", "supervisor"], profiles: ["user", "supervisor"],
raw: true raw: true
}); });
const sumUserOlineTime = await ShowUserServiceReport({ startDate, endDate, userId }); const sumUserOlineTime = await ShowUserServiceReport({
const closedByUser = await ShowUserServiceReport({ startDate, endDate, ticketStatus: 'closed', userId }); startDate,
const openByUser = await ShowUserServiceReport({ startDate, endDate, ticketStatus: 'open', userId }); endDate,
userId
});
const closedByUser = await ShowUserServiceReport({
startDate,
endDate,
ticketStatus: "closed",
userId
});
const openByUser = await ShowUserServiceReport({
startDate,
endDate,
ticketStatus: "open",
userId
});
let dateTime = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }))) let dateTime = splitDateTime(
const onlineUsers = await ListUserOnlineOffline({ date: dateTime.fullDate }) new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR }))
);
const onlineUsers = await ListUserOnlineOffline({ date: dateTime.fullDate });
const openByUserOnQueue = await CountTicketsByUserQueue({ startDate: startDate, endDate: endDate, status: 'open', clientChatStart: true }) const openByUserOnQueue = await CountTicketsByUserQueue({
const openByUserOutQueue = await CountTicketsByUserQueue({ startDate: startDate, endDate: endDate, status: 'open', clientChatStart: false }) startDate: startDate,
endDate: endDate,
status: "open",
clientChatStart: true
});
const openByUserOutQueue = await CountTicketsByUserQueue({
startDate: startDate,
endDate: endDate,
status: "open",
clientChatStart: false
});
const closedByUserOnQueue = await CountTicketsByUserQueue({ startDate: startDate, endDate: endDate, status: 'closed', clientChatStart: true }) const closedByUserOnQueue = await CountTicketsByUserQueue({
const closedUserOutQueue = await CountTicketsByUserQueue({ startDate: startDate, endDate: endDate, status: 'closed', clientChatStart: false }) startDate: startDate,
endDate: endDate,
status: "closed",
clientChatStart: true
});
const closedUserOutQueue = await CountTicketsByUserQueue({
startDate: startDate,
endDate: endDate,
status: "closed",
clientChatStart: false
});
// let openQueueInOut = openByUserOnQueue.concat(openByUserOutQueue) // let openQueueInOut = openByUserOnQueue.concat(openByUserOutQueue)
// let closedQueueInOut = closedByUserOnQueue.concat(closedUserOutQueue) // let closedQueueInOut = closedByUserOnQueue.concat(closedUserOutQueue)
const queuesByUser = await ShowQueuesByUser({ profile: "user" });
const queuesByUser = await ShowQueuesByUser({ profile: 'user' }) let openCloseOnQueue = openByUserOnQueue.concat(closedByUserOnQueue);
let openCloseOutQueue = openByUserOutQueue.concat(closedUserOutQueue);
let openCloseOnQueue = openByUserOnQueue.concat(closedByUserOnQueue)
let openCloseOutQueue = openByUserOutQueue.concat(closedUserOutQueue)
// console.log('onlineUsers: ',JSON.parse(JSON.stringify(onlineUsers))) // console.log('onlineUsers: ',JSON.parse(JSON.stringify(onlineUsers)))
// console.log('sumUserOlineTime: ', JSON.parse(JSON.stringify(sumUserOlineTime))) // console.log('sumUserOlineTime: ', JSON.parse(JSON.stringify(sumUserOlineTime)))
for (let i = 0; i < queuesByUser.length; i++) { for (let i = 0; i < queuesByUser.length; i++) {
queuesByUser[i].countOpen = 0;
queuesByUser[i].countOpen = 0 queuesByUser[i].countClosed = 0;
queuesByUser[i].countClosed = 0
for (let x = 0; x < openCloseOnQueue.length; x++) { for (let x = 0; x < openCloseOnQueue.length; x++) {
if ((queuesByUser[i].userId == openCloseOnQueue[x].userId) && if (
(queuesByUser[i].queueId == openCloseOnQueue[x].queueId && openCloseOnQueue[x].status == 'open')) { queuesByUser[i].userId == openCloseOnQueue[x].userId &&
queuesByUser[i].countOpen = openCloseOnQueue[x].totAttendance queuesByUser[i].queueId == openCloseOnQueue[x].queueId &&
openCloseOnQueue[x].status == "open"
) {
queuesByUser[i].countOpen = openCloseOnQueue[x].totAttendance;
} else if (
queuesByUser[i].userId == openCloseOnQueue[x].userId &&
queuesByUser[i].queueId == openCloseOnQueue[x].queueId &&
openCloseOnQueue[x].status == "closed"
) {
queuesByUser[i].countClosed = openCloseOnQueue[x].totAttendance;
} }
else if ((queuesByUser[i].userId == openCloseOnQueue[x].userId) &&
(queuesByUser[i].queueId == openCloseOnQueue[x].queueId && openCloseOnQueue[x].status == 'closed')) {
queuesByUser[i].countClosed = openCloseOnQueue[x].totAttendance
}
} }
} }
usersProfile.map((user: any) => { usersProfile.map((user: any) => {
let index = sumUserOlineTime.findIndex((e: any) => e.userId == user.id);
let index = sumUserOlineTime.findIndex((e: any) => e.userId == user.id)
if (index != -1) { if (index != -1) {
user.sumOnlineTime = sumUserOlineTime[index]; user.sumOnlineTime = sumUserOlineTime[index];
@ -133,68 +193,67 @@ export const reportUserService = async (req: Request, res: Response): Promise<Re
// console.log('user.sumOlineTime: 'user.sumOnlineTime) // console.log('user.sumOlineTime: 'user.sumOnlineTime)
} }
index = closedByUser.findIndex((e: any) => e.userId == user.id) index = closedByUser.findIndex((e: any) => e.userId == user.id);
if (index != -1) { if (index != -1) {
user.sumClosed = closedByUser[index]; user.sumClosed = closedByUser[index];
} }
index = openByUser.findIndex((e: any) => e.userId == user.id) index = openByUser.findIndex((e: any) => e.userId == user.id);
if (index != -1) { if (index != -1) {
user.sumOpen = openByUser[index] user.sumOpen = openByUser[index];
} }
// OPEN, CLOSED TICKETS STARTED BY USERS // OPEN, CLOSED TICKETS STARTED BY USERS
let openClosedOutQueue = {} let openClosedOutQueue = {};
let open = openCloseOutQueue.filter((e) => e.userId == user.id && e.status == 'open') let open = openCloseOutQueue.filter(
let closed = openCloseOutQueue.filter((e) => e.userId == user.id && e.status == 'closed') e => e.userId == user.id && e.status == "open"
);
let closed = openCloseOutQueue.filter(
e => e.userId == user.id && e.status == "closed"
);
openClosedOutQueue = { openClosedOutQueue = {
...openClosedOutQueue, ...openClosedOutQueue,
userId: user.id, userId: user.id,
countOpen: open && open.length > 0 ? open[0].totAttendance : 0, countOpen: open && open.length > 0 ? open[0].totAttendance : 0,
countClosed: closed && closed.length > 0 ? closed[0].totAttendance : 0 countClosed: closed && closed.length > 0 ? closed[0].totAttendance : 0
} };
user.openClosedOutQueue = openClosedOutQueue
user.openClosedOutQueue = openClosedOutQueue;
// OPEN, CLOSED TICKETS STARTED BY CLIENTS // OPEN, CLOSED TICKETS STARTED BY CLIENTS
let openClosedInQueue = queuesByUser.filter((e) => e.userId == user.id) let openClosedInQueue = queuesByUser.filter(e => e.userId == user.id);
if (openClosedInQueue && openClosedInQueue.length > 0) { if (openClosedInQueue && openClosedInQueue.length > 0) {
user.openClosedInQueue = openClosedInQueue user.openClosedInQueue = openClosedInQueue;
} }
index = onlineUsers.findIndex((e: any) => e.userId == user.id);
index = onlineUsers.findIndex((e: any) => e.userId == user.id)
if (index != -1) { if (index != -1) {
user.statusOnline = onlineUsers[index] user.statusOnline = onlineUsers[index];
} }
if (startDate.length > 0 && startDate.split('-').length == 3) { if (startDate.length > 0 && startDate.split("-").length == 3) {
let date = startDate.split('-') let date = startDate.split("-");
user.startDate = `${date[2]}/${date[1]}/${date[0]}` user.startDate = `${date[2]}/${date[1]}/${date[0]}`;
} }
if (endDate.length > 0 && endDate.split('-').length == 3) { if (endDate.length > 0 && endDate.split("-").length == 3) {
let date = endDate.split('-') let date = endDate.split("-");
user.endDate = `${date[2]}/${date[1]}/${date[0]}` user.endDate = `${date[2]}/${date[1]}/${date[0]}`;
} }
});
}) return res.status(200).json({ usersProfile: usersProfile });
return res.status(200).json({usersProfile: usersProfile});
}; };
export const reportMessagesUserByDateStartDateEnd = async (
req: Request,
export const reportMessagesUserByDateStartDateEnd = async (req: Request, res: Response): Promise<Response> => { res: Response
): Promise<Response> => {
if ( if (
req.user.profile !== "master" && req.user.profile !== "master" &&
req.user.profile !== "admin" && req.user.profile !== "admin" &&
@ -203,35 +262,32 @@ export const reportMessagesUserByDateStartDateEnd = async (req: Request, res: Re
throw new AppError("ERR_NO_PERMISSION", 403); throw new AppError("ERR_NO_PERMISSION", 403);
} }
const { userId, startDate, endDate } = req.query as IndexQuery const { userId, startDate, endDate } = req.query as IndexQuery;
let data_query_messages = await ShowMessageReport(userId, startDate, endDate); let data_query_messages = await ShowMessageReport(userId, startDate, endDate);
for (var i = 0; i < data_query_messages.length; i++) { for (var i = 0; i < data_query_messages.length; i++) {
if (data_query_messages[i].fromMe) { if (data_query_messages[i].fromMe) {
data_query_messages[i].fromMe = 'Atendente' data_query_messages[i].fromMe = "Atendente";
} } else {
else { data_query_messages[i].fromMe = "Cliente";
data_query_messages[i].fromMe = 'Cliente'
} }
data_query_messages[i].id = (i + 1) data_query_messages[i].id = i + 1;
console.log('data_query_messages: ', data_query_messages[i]) console.log("data_query_messages: ", data_query_messages[i]);
} }
return res.status(200).json(data_query_messages); return res.status(200).json(data_query_messages);
}; };
export const reportOnQueue = async (
req: Request,
export const reportOnQueue = async (req: Request, res: Response): Promise<Response> => { res: Response
): Promise<Response> => {
// console.log(req.body) // console.log(req.body)
const { adminId, identifier, queueStatus, file } = req.body const { adminId, identifier, queueStatus, file } = req.body;
const io = getIO(); const io = getIO();
io.emit("queryOnQueueStatus", { io.emit("queryOnQueueStatus", {
@ -244,10 +300,5 @@ export const reportOnQueue = async (req: Request, res: Response): Promise<Respon
} }
}); });
return res.status(200).json({ message: 'ok' }) return res.status(200).json({ message: "ok" });
}; };

View File

@ -95,7 +95,7 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
if (queueIdsStringified) { if (queueIdsStringified) {
queueIds = JSON.parse(queueIdsStringified); queueIds = JSON.parse(queueIdsStringified);
} }
const { tickets, count, hasMore } = await ListTicketsService({ const { tickets, count, hasMore } = await ListTicketsService({
searchParam, searchParam,
@ -109,7 +109,7 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
unlimited, unlimited,
searchParamContent searchParamContent
}); });
return res.status(200).json({ tickets, count, hasMore }); return res.status(200).json({ tickets, count, hasMore });
}; };
@ -120,12 +120,21 @@ export const remoteTicketCreation = async (
const { contact_from, contact_to, msg, contact_name }: any = req.body; const { contact_from, contact_to, msg, contact_name }: any = req.body;
const validate = ["contact_from", "contact_to", "msg"]; const validate = ["contact_from", "contact_to", "msg"];
const validateOnlyNumber = ["contact_from", "contact_to"];
for (let prop of validate) { for (let prop of validate) {
if (!req.body[prop]) if (!req.body[prop])
return res return res
.status(400) .status(400)
.json({ error: `Property '${prop}' is undefined.` }); .json({ error: `Property '${prop}' is undefined.` });
if (validateOnlyNumber.includes(prop)) {
if (!/^\d+$/.test(req.body[prop])) {
return res
.status(400)
.json({ error: `The property '${prop}' must be a number` });
}
}
} }
const whatsapp = await Whatsapp.findOne({ const whatsapp = await Whatsapp.findOne({
@ -192,7 +201,7 @@ export const remoteTicketCreation = async (
ticketId: ticket.id ticketId: ticket.id
}); });
} }
} }
if (!ticket) { if (!ticket) {
ticket = await FindOrCreateTicketService( ticket = await FindOrCreateTicketService(
@ -226,11 +235,11 @@ export const remoteTicketCreation = async (
} }
console.log( console.log(
`REMOTE TICKET CREATION FROM ENDPOINT | STATUS: 500 | MSG: Whatsapp number ${contact_from} disconnected` `REMOTE TICKET CREATION FROM ENDPOINT | STATUS: 500 | MSG: Whatsapp number ${contact_from} disconnected or it doesn't exist in omnihit`
); );
return res return res.status(500).json({
.status(500) msg: `Whatsapp number ${contact_from} disconnected or it doesn't exist in omnihit`
.json({ msg: `Whatsapp number ${contact_from} disconnected` }); });
}; };
export const store = async (req: Request, res: Response): Promise<Response> => { export const store = async (req: Request, res: Response): Promise<Response> => {

View File

@ -99,7 +99,7 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
// }; // };
export const all = async (req: Request, res: Response): Promise<Response> => { export const all = async (req: Request, res: Response): Promise<Response> => {
const { userId, profile } = req.query as IndexQuery; let { userId, profile }: any = req.query as IndexQuery;
console.log( console.log(
"userId: ", "userId: ",
@ -110,7 +110,10 @@ export const all = async (req: Request, res: Response): Promise<Response> => {
getSettingValue("queueTransferByWhatsappScope")?.value getSettingValue("queueTransferByWhatsappScope")?.value
); );
if (getSettingValue("queueTransferByWhatsappScope")?.value == "enabled") { if (getSettingValue("queueTransferByWhatsappScope")?.value == "enabled") {
if (!userId) return res.json({ users: [], queues: [] });
const obj = await ListUserByWhatsappQueuesService( const obj = await ListUserByWhatsappQueuesService(
userId, userId,
'"admin", "user", "supervisor"' '"admin", "user", "supervisor"'
@ -119,7 +122,7 @@ export const all = async (req: Request, res: Response): Promise<Response> => {
const usersByWhatsqueue = obj.users; const usersByWhatsqueue = obj.users;
const queues = obj.queues; const queues = obj.queues;
let userIds = usersByWhatsqueue.map((w: any) => w.userId); let userIds = usersByWhatsqueue.map((w: any) => w.userId);
const users = await ListUser({ const users = await ListUser({
userIds userIds

View File

@ -54,6 +54,7 @@ interface WhatsappData {
isDefault?: boolean; isDefault?: boolean;
isOfficial?: boolean; isOfficial?: boolean;
phoneNumberId?: string; phoneNumberId?: string;
number?: string;
wabaId?: string; wabaId?: string;
} }
@ -322,7 +323,8 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
urlApi, urlApi,
phoneNumberId, phoneNumberId,
wabaId, wabaId,
isOfficial isOfficial,
number
}: WhatsappData = req.body; }: WhatsappData = req.body;
if (req.user.profile !== "master") { if (req.user.profile !== "master") {
@ -333,7 +335,8 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
urlApi, urlApi,
isOfficial, isOfficial,
phoneNumberId, phoneNumberId,
wabaId wabaId,
number
}); });
if (invalid) { if (invalid) {
@ -346,6 +349,7 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
} else if (!isOfficial) { } else if (!isOfficial) {
phoneNumberId = ""; phoneNumberId = "";
wabaId = ""; wabaId = "";
number = "";
} }
let invalidPhoneName = validatePhoneName(name); let invalidPhoneName = validatePhoneName(name);
@ -365,7 +369,8 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
queueIds, queueIds,
phoneNumberId, phoneNumberId,
wabaId, wabaId,
isOfficial isOfficial,
number
}); });
console.log("whatsapp.id: ", whatsapp.id); console.log("whatsapp.id: ", whatsapp.id);
@ -408,7 +413,7 @@ export const update = async (
res: Response res: Response
): Promise<Response> => { ): Promise<Response> => {
const { whatsappId } = req.params; const { whatsappId } = req.params;
const whatsappData = req.body; const whatsappData = req.body;
let invalidPhoneName = validatePhoneName(whatsappData.name); let invalidPhoneName = validatePhoneName(whatsappData.name);
@ -549,19 +554,23 @@ interface WhatsappDataValidate {
isOfficial?: boolean; isOfficial?: boolean;
phoneNumberId?: string; phoneNumberId?: string;
wabaId?: string; wabaId?: string;
number?: string;
} }
const checkWhatsAppData = ({ const checkWhatsAppData = ({
urlApi, urlApi,
isOfficial, isOfficial,
phoneNumberId, phoneNumberId,
wabaId wabaId,
number
}: WhatsappDataValidate) => { }: WhatsappDataValidate) => {
if (isOfficial && (!phoneNumberId || phoneNumberId.trim() == "")) { if (isOfficial && (!phoneNumberId || phoneNumberId.trim() == "")) {
return { message: "Phone number Id is required!" }; return { message: "Phone number Id is required!" };
} else if (isOfficial && (!wabaId || wabaId.trim() == "")) { } else if (isOfficial && (!wabaId || wabaId.trim() == "")) {
return { message: "WABA ID is required!" }; return { message: "WABA ID is required!" };
} else if (isOfficial && (!number || number.trim() == "")) {
return { message: "Phone number is required!" };
} else if (!isOfficial && (!urlApi || urlApi.trim() == "")) { } else if (!isOfficial && (!urlApi || urlApi.trim() == "")) {
return { message: "urlApi is required!" }; return { message: "urlApi is required!" };
} }
}; };

View File

@ -15,9 +15,9 @@ const CheckContactOpenTickets = async (
if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") { if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") {
let whats = await ListWhatsAppsNumber(whatsappId); let whats = await ListWhatsAppsNumber(whatsappId);
console.log("contactId: ", contactId, " | whatsappId: ", whatsappId); // console.log("contactId: ", contactId, " | whatsappId: ", whatsappId);
console.log("WHATS: ", JSON.stringify(whats, null, 6)); // console.log("WHATS: ", JSON.stringify(whats, null, 6));
ticket = await Ticket.findOne({ ticket = await Ticket.findOne({
where: { where: {

View File

@ -17,6 +17,18 @@ export async function get(key: string) {
return JSON.parse(value); return JSON.parse(value);
} }
export async function clearAllKeys() {
// Retrieve all keys matching the pattern '*'
const keys = await redis.keys("user:*");
// If there are keys, delete them
if (keys.length > 0) {
console.log('keys: ', keys)
await redis.del(...keys);
}
}
export async function findByContain( export async function findByContain(
key: string, key: string,
keyName: string, keyName: string,

View File

@ -16,6 +16,8 @@ contactRoutes.get("/contacts/:contactId", isAuth, ContactController.show);
contactRoutes.post("/contacts", isAuth, ContactController.store); contactRoutes.post("/contacts", isAuth, ContactController.store);
contactRoutes.post("/contact", isAuth, ContactController.getContact);
contactRoutes.put("/contacts/:contactId", isAuth, ContactController.update); contactRoutes.put("/contacts/:contactId", isAuth, ContactController.update);
contactRoutes.delete("/contacts/:contactId", isAuth, ContactController.remove); contactRoutes.delete("/contacts/:contactId", isAuth, ContactController.remove);

View File

@ -5,7 +5,6 @@ import * as TicketController from "../controllers/TicketController";
const ticketRoutes = express.Router(); const ticketRoutes = express.Router();
// ticketRoutes.get("/tickets/cache", isAuth, TicketController.ticketsCache); // ticketRoutes.get("/tickets/cache", isAuth, TicketController.ticketsCache);
ticketRoutes.get("/tickets/count", isAuth, TicketController.count); ticketRoutes.get("/tickets/count", isAuth, TicketController.count);

View File

@ -23,7 +23,7 @@ import fs from "fs";
import dir from "path"; import dir from "path";
import { getSettingValue } from "./helpers/WhaticketSettings"; import { getSettingValue } from "./helpers/WhaticketSettings";
import loadSettings from "./helpers/LoadSettings"; import loadSettings from "./helpers/LoadSettings";
import { set } from "./helpers/RedisClient"; import { clearAllKeys, set } from "./helpers/RedisClient";
const server = app.listen(process.env.PORT, () => { const server = app.listen(process.env.PORT, () => {
logger.info(`Server started on port: ${process.env.PORT}`); logger.info(`Server started on port: ${process.env.PORT}`);
@ -39,11 +39,13 @@ const server = app.listen(process.env.PORT, () => {
initIO(server); initIO(server);
// StartAllWhatsAppsSessions(); // StartAllWhatsAppsSessions();
gracefulShutdown(server); gracefulShutdown(server);
(async () => { (async () => {
console.log("os.tmpdir(): ", os.tmpdir()); console.log("os.tmpdir(): ", os.tmpdir());
await clearAllKeys();
const users = await User.findAll(); const users = await User.findAll();
for (const user of users) { for (const user of users) {

View File

@ -0,0 +1,38 @@
import AppError from "../../errors/AppError";
import Contact from "../../models/Contact";
import CreateContactService from "./CreateContactService";
interface ExtraInfo {
name: string;
value: string;
}
interface Request {
name: string;
number: string;
email?: string;
profilePicUrl?: string;
extraInfo?: ExtraInfo[];
}
const GetContactService = async ({ name, number }: Request): Promise<Contact> => {
const numberExists = await Contact.findOne({
where: { number }
});
if (!numberExists) {
const contact = await CreateContactService({
name,
number,
})
if (contact == null)
throw new AppError("CONTACT_NOT_FIND")
else
return contact
}
return numberExists
};
export default GetContactService;

View File

@ -59,13 +59,8 @@ const ListTicketsService = async ({
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)
//TEST DEL
// const url = await getWbot(46)
// console.log('---------> URL: ', url)
//
if (pageNumber.trim().length == 0) { if (pageNumber.trim().length == 0) {
pageNumber = '1' pageNumber = '1'
} }
@ -136,15 +131,14 @@ const ListTicketsService = async ({
whereCondition = { ...whereCondition, status }; whereCondition = { ...whereCondition, status };
if (unlimited === 'true' && status !== 'pending') { if (unlimited === "current" && status !== "pending") {
whereCondition = { whereCondition = {
...whereCondition, ...whereCondition,
createdAt: { createdAt: {
[Op.gte]: dateToday.fullDate + ' 00:00:00.000000', [Op.gte]: dateToday.fullDate + " 00:00:00.000000",
[Op.lte]: dateToday.fullDate + ' 23:59:59.999999' [Op.lte]: dateToday.fullDate + " 23:59:59.999999"
} }
} };
} }
} }
@ -196,8 +190,8 @@ const ListTicketsService = async ({
if ( if (
userProfile.dataValues.profile != "admin" && userProfile.dataValues.profile != "admin" &&
userProfile.dataValues.profile != "master" userProfile.dataValues.profile != "master" &&
// userProfile.dataValues.profile != "supervisor" userProfile.dataValues.profile != "supervisor"
) { ) {
whereCondition = { ...whereCondition, userId }; whereCondition = { ...whereCondition, userId };
} }
@ -224,9 +218,10 @@ const ListTicketsService = async ({
}; };
} }
const limit = unlimited === 'true' ? 100000 : 40; const limit = unlimited === "current" || unlimited === "all" ? 100000 : 40;
const offset = limit * (+pageNumber - 1); const offset = limit * (+pageNumber - 1);
console.log("kkkkkkkkk limit: ", limit);
const { count, rows: tickets } = await Ticket.findAndCountAll({ const { count, rows: tickets } = await Ticket.findAndCountAll({
where: whereCondition, where: whereCondition,

View File

@ -9,10 +9,10 @@ import { userInfo } from "os";
import { Op, where } from "sequelize"; import { Op, where } from "sequelize";
import { Sequelize } from "sequelize"; import { Sequelize } from "sequelize";
import moment from 'moment'; import moment from "moment";
import { startOfDay, endOfDay, parseISO, getDate} from "date-fns"; import { startOfDay, endOfDay, parseISO, getDate } from "date-fns";
import { string } from "yup/lib/locale"; import { string } from "yup/lib/locale";
import Whatsapp from "../../models/Whatsapp"; import Whatsapp from "../../models/Whatsapp";
@ -20,10 +20,11 @@ interface Request {
userId: string | number; userId: string | number;
startDate: string; startDate: string;
endDate: string; endDate: string;
createdOrUpdated?: string;
queueId?: string;
pageNumber?: string; pageNumber?: string;
} }
interface Response { interface Response {
tickets: Ticket[]; tickets: Ticket[];
count: number; count: number;
@ -35,89 +36,124 @@ const ShowTicketReport = async ({
userId, userId,
startDate, startDate,
endDate, endDate,
pageNumber = "1" pageNumber = "1",
}: Request): Promise<Response> => { createdOrUpdated = "created",
queueId
let where_clause = {} }: Request): Promise<Response> => {
let where_clause: any = {};
// let where_clause_msg: any = {};
if(userId=='0'){ if (userId !== "0") {
where_clause = { where_clause.userid = userId;
updatedAt: { }
[Op.gte]: startDate+' 00:00:00.000000',
[Op.lte]: endDate +' 23:59:59.999999' if (createdOrUpdated === "updated") {
}, where_clause = {
} ...where_clause,
} updatedAt: {
else{ [Op.gte]: startDate + " 00:00:00.000000",
where_clause = { [Op.lte]: endDate + " 23:59:59.999999"
userid: userId, }
updatedAt: { };
[Op.gte]: startDate+' 00:00:00.000000', }
[Op.lte]: endDate +' 23:59:59.999999'
}, if (createdOrUpdated === "created") {
} where_clause = {
...where_clause,
createdAt: {
[Op.gte]: startDate + " 00:00:00.000000",
[Op.lte]: endDate + " 23:59:59.999999"
}
};
}
let { userid, ...where_clause_msg } = where_clause;
if (queueId) {
where_clause.queueId = queueId;
} }
const limit = 40; const limit = 40;
const offset = limit * (+pageNumber - 1); const offset = limit * (+pageNumber - 1);
const {count, rows: tickets} = await Ticket.findAndCountAll({
where: where_clause , const { count, rows: tickets } = await Ticket.findAndCountAll({
where: where_clause,
limit, limit,
offset, offset,
//attributes: ['id', 'status', 'createdAt', 'updatedAt'],
attributes: ['id', 'status', 'statusChatEnd', [Sequelize.fn("DATE_FORMAT",Sequelize.col("Ticket.createdAt"),"%d/%m/%Y %H:%i:%s"),"createdAt"], attributes: [
[Sequelize.fn("DATE_FORMAT",Sequelize.col("Ticket.updatedAt"),"%d/%m/%Y %H:%i:%s"),"updatedAt"]], "id",
"status",
"statusChatEnd",
[
Sequelize.fn(
"DATE_FORMAT",
Sequelize.col("Ticket.createdAt"),
"%d/%m/%Y %H:%i:%s"
),
"createdAt"
],
[
Sequelize.fn(
"DATE_FORMAT",
Sequelize.col("Ticket.updatedAt"),
"%d/%m/%Y %H:%i:%s"
),
"updatedAt"
]
],
include: [ include: [
{ {
model: Message, model: Message,
required:true, required: true,
separate: true, separate: true,
where: where_clause_msg ,
// attributes: ['body', 'read', 'mediaType','fromMe', 'mediaUrl','createdAt'],
attributes: ['body', 'read', 'mediaType','fromMe', 'mediaUrl', [Sequelize.fn("DATE_FORMAT",Sequelize.col("createdAt"),"%d/%m/%Y %H:%i:%s"),"createdAt"]], attributes: [
"body",
order: [ "read",
['createdAt', 'ASC'] "mediaType",
] "fromMe",
}, "mediaUrl",
{ [
model: Contact, Sequelize.fn(
attributes: ['name', 'number'] "DATE_FORMAT",
Sequelize.col("createdAt"),
"%d/%m/%Y %H:%i:%s"
),
"createdAt"
]
],
order: [["createdAt", "ASC"]]
}, },
{ {
model: User, model: Contact,
attributes: ['name', 'email'] attributes: ["name", "number"]
}, },
{ {
model: Queue, model: User,
attributes: ['name'] attributes: ["name", "email"]
}, },
{ {
model: Whatsapp, model: Queue,
attributes: ['name'] attributes: ["name"]
}, },
{
model: Whatsapp,
attributes: ["name"]
}
], ],
order: [["updatedAt", "DESC"]]
order: [ });
['id', 'ASC']
]
});
const hasMore = count > offset + tickets.length;
const hasMore = count > offset + tickets.length;
if (!tickets) { if (!tickets) {
throw new AppError("ERR_NO_TICKET_FOUND", 404); throw new AppError("ERR_NO_TICKET_FOUND", 404);
} }
return {tickets, count, hasMore}; return { tickets, count, hasMore };
}; };
export default ShowTicketReport; export default ShowTicketReport;

View File

@ -2,7 +2,7 @@ import { Op, Sequelize } from "sequelize";
import Queue from "../../models/Queue"; import Queue from "../../models/Queue";
import User from "../../models/User"; import User from "../../models/User";
import UserQueue from "../../models/UserQueue"; import UserQueue from "../../models/UserQueue";
import { List } from "whatsapp-web.js" import { List } from "whatsapp-web.js";
interface Request { interface Request {
userId?: string | number; userId?: string | number;
@ -12,7 +12,13 @@ interface Request {
userIds?: string | number; userIds?: string | number;
} }
const ListUser = async ({ profile, userId, raw, userIds, profiles }: Request): Promise<User[]> => { const ListUser = async ({
profile,
userId,
raw,
userIds,
profiles
}: Request): Promise<User[]> => {
let where_clause = {}; let where_clause = {};
if (userId && profile) { if (userId && profile) {
@ -47,7 +53,7 @@ const ListUser = async ({ profile, userId, raw, userIds, profiles }: Request): P
], ],
order: [["id", "ASC"]], order: [["id", "ASC"]],
group: ["User.id"] group: userIds ? undefined : ["User.id"]
}); });
return users; return users;

View File

@ -98,6 +98,7 @@ import FindOrCreateTicketServiceBot from "../TicketServices/FindOrCreateTicketSe
import ShowTicketService from "../TicketServices/ShowTicketService"; import ShowTicketService from "../TicketServices/ShowTicketService";
import ShowQueuesByUser from "../UserServices/ShowQueuesByUser"; import ShowQueuesByUser from "../UserServices/ShowQueuesByUser";
import ListWhatsappQueuesByUserQueue from "../UserServices/ListWhatsappQueuesByUserQueue"; import ListWhatsappQueuesByUserQueue from "../UserServices/ListWhatsappQueuesByUserQueue";
import CreateContactService from "../ContactServices/CreateContactService"
var lst: any[] = getWhatsappIds(); var lst: any[] = getWhatsappIds();
@ -479,6 +480,7 @@ const isValidMsg = (msg: any): boolean => {
msg.type === "image" || msg.type === "image" ||
msg.type === "document" || msg.type === "document" ||
msg.type === "vcard" || msg.type === "vcard" ||
// msg.type === "multi_vcard" ||
msg.type === "sticker" msg.type === "sticker"
) )
return true; return true;
@ -497,7 +499,12 @@ const queuesOutBot = async (wbot: Session, botId: string | number) => {
return { queues, greetingMessage }; return { queues, greetingMessage };
}; };
const transferTicket = async (queueName: any, wbot: any, ticket: Ticket) => { const transferTicket = async (
queueName: any,
wbot: any,
ticket: Ticket,
sendGreetingMessage?: boolean
) => {
const botInfo = await BotIsOnQueue("botqueue"); const botInfo = await BotIsOnQueue("botqueue");
console.log("kkkkkkkkkkkkkkkkkkkkk queueName: ", queueName); console.log("kkkkkkkkkkkkkkkkkkkkk queueName: ", queueName);
@ -519,16 +526,24 @@ const transferTicket = async (queueName: any, wbot: any, ticket: Ticket) => {
queue = queues[queueName]; queue = queues[queueName];
} }
if (queue) await botTransferTicket(queue, ticket); if (queue) await botTransferTicket(queue, ticket, sendGreetingMessage);
}; };
const botTransferTicket = async (queues: Queue, ticket: Ticket) => { const botTransferTicket = async (
queues: Queue,
ticket: Ticket,
sendGreetingMessage?: boolean
) => {
await ticket.update({ userId: null }); await ticket.update({ userId: null });
await UpdateTicketService({ await UpdateTicketService({
ticketData: { status: "pending", queueId: queues.id }, ticketData: { status: "pending", queueId: queues.id },
ticketId: ticket.id ticketId: ticket.id
}); });
if (sendGreetingMessage && queues?.greetingMessage?.length > 0) {
botSendMessage(ticket, queues.greetingMessage);
}
}; };
const botTransferTicketToUser = async ( const botTransferTicketToUser = async (
@ -595,7 +610,7 @@ const handleMessage = async (
return; return;
} }
} }
if (!isValidMsg(msg)) { if (!isValidMsg(msg)) {
return; return;
@ -630,7 +645,13 @@ const handleMessage = async (
// media messages sent from me from cell phone, first comes with "hasMedia = false" and type = "image/ptt/etc" // media messages sent from me from cell phone, first comes with "hasMedia = false" and type = "image/ptt/etc"
// in this case, return and let this message be handled by "media_uploaded" event, when it will have "hasMedia = true" // in this case, return and let this message be handled by "media_uploaded" event, when it will have "hasMedia = true"
if (!msg.hasMedia && msg.type !== "chat" && msg.type !== "vcard") return; if (
!msg.hasMedia &&
msg.type !== "chat" &&
msg.type !== "vcard" &&
msg.type !== "multi_vcard"
)
return;
} else { } else {
console.log(`\n <<<<<<<<<< RECEIVING MESSAGE: console.log(`\n <<<<<<<<<< RECEIVING MESSAGE:
Parcial msg and msgContact info: Parcial msg and msgContact info:
@ -759,12 +780,54 @@ const handleMessage = async (
await verifyQueue(wbot, msg, ticket, contact); await verifyQueue(wbot, msg, ticket, contact);
} }
if (msg.type === "vcard") {
try {
const array = msg.body.split("\n");
const obj = [];
let contact = "";
for (let index = 0; index < array.length; index++) {
const v = array[index];
const values = v.split(":");
for (let ind = 0; ind < values.length; ind++) {
if (values[ind].indexOf("+") !== -1) {
obj.push({ number: values[ind] });
}
if (values[ind].indexOf("FN") !== -1) {
contact = values[ind + 1];
}
}
}
for await (const ob of obj) {
const cont = await CreateContactService({
name: contact,
number: ob.number.replace(/\D/g, "")
});
}
} catch (error) {
console.log(error);
}
}
const botInfo = await BotIsOnQueue("botqueue");
// Transfer to agent // Transfer to agent
if (!msg.fromMe) { // 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
const filteredUsers = await findByContain("user:*", "name", msg?.body); if (
!msg.fromMe &&
((ticket.status == "open" &&
botInfo &&
ticket.userId == +botInfo.userIdBot) ||
ticket.status == "pending" ||
ticket.status == "queueChoice")
) {
const filteredUsers = await findByContain("user:*", "name", msg?.body);
if (filteredUsers && filteredUsers.length > 0) { if (filteredUsers && filteredUsers.length > 0) {
if (botInfo.isOnQueue) {
transferTicket(filteredUsers[0].name, wbot, ticket, true);
return;
}
const whatsappQueues = await ListWhatsappQueuesByUserQueue( const whatsappQueues = await ListWhatsappQueuesByUserQueue(
+filteredUsers[0].id +filteredUsers[0].id
); );
@ -791,12 +854,6 @@ const handleMessage = async (
} }
// //
// 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
const botInfo = await BotIsOnQueue("botqueue");
// const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 };
if ( if (
botInfo.isOnQueue && botInfo.isOnQueue &&
!msg.fromMe && !msg.fromMe &&
@ -844,11 +901,6 @@ const handleMessage = async (
menuMsg?.transferToQueue && menuMsg?.transferToQueue &&
menuMsg.transferToQueue.trim().length > 0 menuMsg.transferToQueue.trim().length > 0
) { ) {
console.log(
"YYYYYYYYYYYYYYYYYYYY menuMsg.transferToQueue: ",
menuMsg.transferToQueue
);
transferTicket(menuMsg.transferToQueue.trim(), wbot, ticket); transferTicket(menuMsg.transferToQueue.trim(), wbot, ticket);
} }
} }

View File

@ -16,6 +16,7 @@ interface Request {
phoneNumberId?: string; phoneNumberId?: string;
wabaId?: string; wabaId?: string;
isOfficial?: boolean; isOfficial?: boolean;
number?: string;
} }
interface Response { interface Response {
@ -34,7 +35,8 @@ const CreateWhatsAppService = async ({
isDefault = false, isDefault = false,
isOfficial = false, isOfficial = false,
phoneNumberId, phoneNumberId,
wabaId wabaId,
number
}: Request): Promise<Response> => { }: Request): Promise<Response> => {
try { try {
const schema = Yup.object().shape({ const schema = Yup.object().shape({
@ -98,6 +100,7 @@ const CreateWhatsAppService = async ({
phoneNumberId, phoneNumberId,
wabaId, wabaId,
isOfficial, isOfficial,
number,
classification classification
}, },
{ include: ["queues"] } { include: ["queues"] }

View File

@ -0,0 +1,62 @@
import { Button } from "@material-ui/core";
import React, { useRef } from "react";
import { useEffect } from "react";
import { useState } from "react";
const LS_NAME = 'audioMessageRate';
export default function({url}) {
const audioRef = useRef(null);
const [audioRate, setAudioRate] = useState( parseFloat(localStorage.getItem(LS_NAME) || "1") );
const [showButtonRate, setShowButtonRate] = useState(false);
useEffect(() => {
audioRef.current.playbackRate = audioRate;
localStorage.setItem(LS_NAME, audioRate);
}, [audioRate]);
useEffect(() => {
audioRef.current.onplaying = () => {
setShowButtonRate(true);
};
audioRef.current.onpause = () => {
setShowButtonRate(false);
};
audioRef.current.onended = () => {
setShowButtonRate(false);
};
}, []);
const toogleRate = () => {
let newRate = null;
switch(audioRate) {
case 0.5:
newRate = 1;
break;
case 1:
newRate = 1.5;
break;
case 1.5:
newRate = 2;
break;
case 2:
newRate = 0.5;
break;
default:
newRate = 1;
break;
}
setAudioRate(newRate);
};
return (
<>
<audio ref={audioRef} controls>
<source src={url} type="audio/ogg"></source>
</audio>
{showButtonRate && <Button style={{marginLeft: "5px", marginTop: "-45px"}} onClick={toogleRate}>{audioRate}x</Button>}
</>
);
}

View File

@ -0,0 +1,53 @@
import React, { useEffect } from 'react';
import toastError from "../../errors/toastError";
import Typography from "@material-ui/core/Typography";
import Grid from "@material-ui/core/Grid";
import { Button, Divider, } from "@material-ui/core";
const LocationPreview = ({ image, link, description }) => {
useEffect(() => {}, [image, link, description]);
const handleLocation = async() => {
try {
window.open(link);
} catch (err) {
toastError(err);
}
}
return (
<>
<div style={{
minWidth: "250px",
}}>
<div>
<div style={{ float: "left" }}>
<img src={image} onClick={handleLocation} style={{ width: "100px" }} />
</div>
{ description && (
<div style={{ display: "flex", flexWrap: "wrap" }}>
<Typography style={{ marginTop: "12px", marginLeft: "15px", marginRight: "15px", float: "left" }} variant="subtitle1" color="primary" gutterBottom>
<div dangerouslySetInnerHTML={{ __html: description.replace('\\n', '<br />') }}></div>
</Typography>
</div>
)}
<div style={{ display: "block", content: "", clear: "both" }}></div>
<div>
<Divider />
<Button
fullWidth
color="primary"
onClick={handleLocation}
disabled={!link}
>Visualizar</Button>
</div>
</div>
</div>
</>
);
};
export default LocationPreview;

View File

@ -1,5 +1,5 @@
import React from "react"; import React from "react"
import Markdown from "markdown-to-jsx"; import Markdown from "markdown-to-jsx"
const elements = [ const elements = [
"a", "a",
@ -139,25 +139,32 @@ const elements = [
"svg", "svg",
"text", "text",
"tspan", "tspan",
]; ]
const allowedElements = ["a", "b", "strong", "em", "u", "code", "del"]; const allowedElements = ["a", "b", "strong", "em", "u", "code", "del"]
const CustomLink = ({ children, ...props }) => ( const CustomLink = ({ children, ...props }) => (
<a {...props} target="_blank" rel="noopener noreferrer"> <a {...props} target="_blank" rel="noopener noreferrer">
{children} {children}
</a> </a>
); )
const MarkdownWrapper = ({ children }) => { const MarkdownWrapper = ({ children }) => {
const boldRegex = /\*(.*?)\*/g; const boldRegex = /\*(.*?)\*/g
const tildaRegex = /~(.*?)~/g; const tildaRegex = /~(.*?)~/g
if (children && children.includes('BEGIN:VCARD'))
//children = "Diga olá ao seu novo contato clicando em *conversar*!";
children = null
if (children && children.includes('data:image/'))
children = null
if (children && boldRegex.test(children)) { if (children && boldRegex.test(children)) {
children = children.replace(boldRegex, "**$1**"); children = children.replace(boldRegex, "**$1**")
} }
if (children && tildaRegex.test(children)) { if (children && tildaRegex.test(children)) {
children = children.replace(tildaRegex, "~~$1~~"); children = children.replace(tildaRegex, "~~$1~~")
} }
const options = React.useMemo(() => { const options = React.useMemo(() => {
@ -167,20 +174,20 @@ const MarkdownWrapper = ({ children }) => {
overrides: { overrides: {
a: { component: CustomLink }, a: { component: CustomLink },
}, },
}; }
elements.forEach(element => { elements.forEach(element => {
if (!allowedElements.includes(element)) { if (!allowedElements.includes(element)) {
markdownOptions.overrides[element] = el => el.children || null; markdownOptions.overrides[element] = el => el.children || null
} }
}); })
return markdownOptions; return markdownOptions
}, []); }, [])
if (!children) return null; if (!children) return null
return <Markdown options={options}>{children}</Markdown>; return <Markdown options={options}>{children}</Markdown>
}; }
export default MarkdownWrapper; export default MarkdownWrapper

View File

@ -350,6 +350,8 @@ const MessageInput = ({ ticketStatus }) => {
try { try {
console.log('kkkkkkkkkkkkkkkkkkk message: ', message)
const { data } = await api.post(`/messages/${ticketId}`, message) const { data } = await api.post(`/messages/${ticketId}`, message)
setParams(null) setParams(null)
if (data && data?.data && Array.isArray(data.data)) { if (data && data?.data && Array.isArray(data.data)) {
@ -371,7 +373,11 @@ const MessageInput = ({ ticketStatus }) => {
if (!params) return if (!params) return
const body_params = params.find(p => p?.type === 'BODY') const body_params = params?.find(p => p?.type === 'BODY')
console.log('------------> body_params: ', body_params)
if(!body_params) return
let { text } = body_params let { text } = body_params

View File

@ -23,6 +23,11 @@ import {
} from "@material-ui/icons"; } from "@material-ui/icons";
import MarkdownWrapper from "../MarkdownWrapper"; import MarkdownWrapper from "../MarkdownWrapper";
import VcardPreview from "../VcardPreview";
import LocationPreview from "../LocationPreview";
import Audio from "../Audio";
import ModalImageCors from "../ModalImageCors"; import ModalImageCors from "../ModalImageCors";
import MessageOptionsMenu from "../MessageOptionsMenu"; import MessageOptionsMenu from "../MessageOptionsMenu";
import whatsBackground from "../../assets/wa-background.png"; import whatsBackground from "../../assets/wa-background.png";
@ -488,27 +493,109 @@ const MessagesList = ({ ticketId, isGroup }) => {
setAnchorEl(null); setAnchorEl(null);
}; };
// const checkMessageMedia = (message) => {
// if (message.mediaType === "image") {
// return <ModalImageCors imageUrl={message.mediaUrl} />;
// }
// if (message.mediaType === "audio") {
// return (
// <audio controls>
// <source src={message.mediaUrl} type="audio/ogg"></source>
// </audio>
// );
// }
// if (message.mediaType === "video") {
// return (
// <video
// className={classes.messageMedia}
// src={message.mediaUrl}
// controls
// />
// );
// } else {
// return (
// <>
// <div className={classes.downloadMedia}>
// <Button
// startIcon={<GetApp />}
// color="primary"
// variant="outlined"
// target="_blank"
// href={message.mediaUrl}
// >
// Download
// </Button>
// </div>
// <Divider />
// </>
// );
// }
// };
const checkMessageMedia = (message) => { const checkMessageMedia = (message) => {
if (message.mediaType === "image") { if (message.mediaType === "location" && message.body.split('|').length >= 2) {
return <ModalImageCors imageUrl={message.mediaUrl} />; let locationParts = message.body.split('|')
} let imageLocation = locationParts[0]
if (message.mediaType === "audio") { let linkLocation = locationParts[1]
return ( let descriptionLocation = null
<audio controls>
<source src={message.mediaUrl} type="audio/ogg"></source>
</audio>
);
}
if (message.mediaType === "video") { if (locationParts.length > 2)
descriptionLocation = message.body.split('|')[2]
return <LocationPreview image={imageLocation} link={linkLocation} description={descriptionLocation} />
}
else if (message.mediaType === "vcard") {
//console.log("vcard")
//console.log(message)
let array = message.body.split("\n")
let obj = []
let contact = ""
for (let index = 0; index < array.length; index++) {
const v = array[index]
let values = v.split(":")
for (let ind = 0; ind < values.length; ind++) {
if (values[ind].indexOf("+") !== -1) {
obj.push({ number: values[ind] })
}
if (values[ind].indexOf("FN") !== -1) {
contact = values[ind + 1]
}
}
}
return <VcardPreview contact={contact} numbers={obj[0]?.number} />
}
/*else if (message.mediaType === "multi_vcard") {
console.log("multi_vcard")
console.log(message)
if(message.body !== null && message.body !== "") {
let newBody = JSON.parse(message.body)
return (
<>
{
newBody.map(v => (
<VcardPreview contact={v.name} numbers={v.number} />
))
}
</>
)
} else return (<></>)
}*/
else if (/^.*\.(jpe?g|png|gif)?$/i.exec(message.mediaUrl) && message.mediaType === "image") {
return <ModalImageCors imageUrl={message.mediaUrl} />
} else if (message.mediaType === "audio") {
return <Audio url={message.mediaUrl} />
} else if (message.mediaType === "video") {
return ( return (
<video <video
className={classes.messageMedia} className={classes.messageMedia}
src={message.mediaUrl} src={message.mediaUrl}
controls controls
/> />
); )
} else { } else {
return ( return (
<> <>
@ -525,7 +612,7 @@ const MessagesList = ({ ticketId, isGroup }) => {
</div> </div>
<Divider /> <Divider />
</> </>
); )
} }
}; };
@ -643,6 +730,88 @@ const MessagesList = ({ ticketId, isGroup }) => {
); );
}; };
// const renderMessages = () => {
// if (messagesList.length > 0) {
// const viewMessagesList = messagesList.map((message, index) => {
// if (!message.fromMe) {
// return (
// <React.Fragment key={message.id}>
// {renderDailyTimestamps(message, index)}
// {renderMessageDivider(message, index)}
// <div className={classes.messageLeft}>
// <IconButton
// variant="contained"
// size="small"
// id="messageActionsButton"
// disabled={message.isDeleted}
// className={classes.messageActionsButton}
// onClick={(e) => handleOpenMessageOptionsMenu(e, message)}
// >
// <ExpandMore />
// </IconButton>
// {isGroup && (
// <span className={classes.messageContactName}>
// {message.contact?.name}
// </span>
// )}
// {message.mediaUrl && checkMessageMedia(message)}
// <div className={classes.textContentItem}>
// {message.quotedMsg && renderQuotedMessage(message)}
// <MarkdownWrapper>{message.body}</MarkdownWrapper>
// <span className={classes.timestamp}>
// {format(parseISO(message.createdAt), "HH:mm")}
// </span>
// </div>
// </div>
// </React.Fragment>
// );
// } else {
// return (
// <React.Fragment key={message.id}>
// {renderDailyTimestamps(message, index)}
// {renderMessageDivider(message, index)}
// <div className={classes.messageRight}>
// <IconButton
// variant="contained"
// size="small"
// id="messageActionsButton"
// disabled={message.isDeleted}
// className={classes.messageActionsButton}
// onClick={(e) => handleOpenMessageOptionsMenu(e, message)}
// >
// <ExpandMore />
// </IconButton>
// {message.mediaUrl && checkMessageMedia(message)}
// <div
// className={clsx(classes.textContentItem, {
// [classes.textContentItemDeleted]: message.isDeleted,
// })}
// >
// {message.isDeleted && (
// <Block
// color="disabled"
// fontSize="small"
// className={classes.deletedIcon}
// />
// )}
// {message.quotedMsg && renderQuotedMessage(message)}
// <MarkdownWrapper>{message.body}</MarkdownWrapper>
// <span className={classes.timestamp}>
// {format(parseISO(message.createdAt), "HH:mm")}
// {renderMessageAck(message)}
// </span>
// </div>
// </div>
// </React.Fragment>
// );
// }
// });
// return viewMessagesList;
// } else {
// return <div>Say hello to your new contact!</div>;
// }
// };
const renderMessages = () => { const renderMessages = () => {
if (messagesList.length > 0) { if (messagesList.length > 0) {
const viewMessagesList = messagesList.map((message, index) => { const viewMessagesList = messagesList.map((message, index) => {
@ -667,7 +836,9 @@ const MessagesList = ({ ticketId, isGroup }) => {
{message.contact?.name} {message.contact?.name}
</span> </span>
)} )}
{message.mediaUrl && checkMessageMedia(message)} {(message.mediaUrl || message.mediaType === "location" || message.mediaType === "vcard"
//|| message.mediaType === "multi_vcard"
) && checkMessageMedia(message)}
<div className={classes.textContentItem}> <div className={classes.textContentItem}>
{message.quotedMsg && renderQuotedMessage(message)} {message.quotedMsg && renderQuotedMessage(message)}
<MarkdownWrapper>{message.body}</MarkdownWrapper> <MarkdownWrapper>{message.body}</MarkdownWrapper>
@ -677,7 +848,7 @@ const MessagesList = ({ ticketId, isGroup }) => {
</div> </div>
</div> </div>
</React.Fragment> </React.Fragment>
); )
} else { } else {
return ( return (
<React.Fragment key={message.id}> <React.Fragment key={message.id}>
@ -694,7 +865,9 @@ const MessagesList = ({ ticketId, isGroup }) => {
> >
<ExpandMore /> <ExpandMore />
</IconButton> </IconButton>
{message.mediaUrl && checkMessageMedia(message)} {(message.mediaUrl || message.mediaType === "location" || message.mediaType === "vcard"
//|| message.mediaType === "multi_vcard"
) && checkMessageMedia(message)}
<div <div
className={clsx(classes.textContentItem, { className={clsx(classes.textContentItem, {
[classes.textContentItemDeleted]: message.isDeleted, [classes.textContentItemDeleted]: message.isDeleted,
@ -716,12 +889,12 @@ const MessagesList = ({ ticketId, isGroup }) => {
</div> </div>
</div> </div>
</React.Fragment> </React.Fragment>
); )
} }
}); })
return viewMessagesList; return viewMessagesList
} else { } else {
return <div>Say hello to your new contact!</div>; return <div>Say hello to your new contact!</div>
} }
}; };

View File

@ -1,13 +1,13 @@
import React, { useState, useEffect, useRef, } from 'react' import React, { useState, useEffect, useRef, } from 'react'
import Button from '@mui/material/Button' import Button from '@mui/material/Button'
import Dialog from '@mui/material/Dialog' import Dialog from '@mui/material/Dialog'
import DialogActions from '@mui/material/DialogActions' import DialogActions from '@mui/material/DialogActions'
import DialogContent from '@mui/material/DialogContent' import DialogContent from '@mui/material/DialogContent'
import DialogContentText from '@mui/material/DialogContentText' import DialogContentText from '@mui/material/DialogContentText'
import DialogTitle from '@mui/material/DialogTitle' import DialogTitle from '@mui/material/DialogTitle'
import SelectField from "../Report/SelectField" import SelectField from "../Report/SelectField"
import TextField from '@mui/material/TextField' import TextField from '@mui/material/TextField'
@ -16,12 +16,14 @@ const ModalTemplate = ({ templates, modal_header, func }) => {
templates = [{}, ...templates] templates = [{}, ...templates]
// console.log('TEMPLATES: ', templates)
const [open, setOpen] = useState(true) const [open, setOpen] = useState(true)
const [scroll, /*setScroll*/] = useState('body') const [scroll, /*setScroll*/] = useState('body')
const [templateId, setTemplateId] = useState(null) const [templateId, setTemplateId] = useState(null)
const [templateComponents, setTemplateComponents] = useState(null) const [templateComponents, setTemplateComponents] = useState(null)
const [language, setLanguage] = useState(null) const [language, setLanguage] = useState(null)
const [params, setParams] = useState([]) const [params, setParams] = useState([])
const handleCancel = (event, reason) => { const handleCancel = (event, reason) => {
@ -34,7 +36,25 @@ const ModalTemplate = ({ templates, modal_header, func }) => {
const handleChatEnd = () => { const handleChatEnd = () => {
console.log('PARAMS TO SEND TO MESSAGE INPUT: ', params) console.log('PARAMS TO SEND TO MESSAGE INPUT: ', params)
func(params) console.log('templateComponents: ', templateComponents)
if (params && params.length === 1) {
const bodyObject = templateComponents.find(obj => obj?.type === 'BODY')
if (bodyObject) {
const { text } = bodyObject
func([...params, {
"type": "BODY",
"text": text,
"language": "pt_BR",
}])
}
}
else {
func(params)
}
setOpen(false) setOpen(false)
} }
@ -100,11 +120,11 @@ const ModalTemplate = ({ templates, modal_header, func }) => {
const handleTextChange = (value, index, type, text, language,) => { const handleTextChange = (value, index, type, text, language,) => {
if (!params) return if (!params) return
setParams((params) => { setParams((params) => {
const _index = params.findIndex(({ type }) => type === 'BODY') const _index = params.findIndex(({ type }) => type === 'BODY')
if (_index !== -1) { if (_index !== -1) {
@ -127,7 +147,9 @@ const ModalTemplate = ({ templates, modal_header, func }) => {
useEffect(() => { useEffect(() => {
console.log('---------> PARAMS: ', params) console.log('---------> PARAMS: ', params)
}, [params]) console.log('---------> templateComponents: ', templateComponents)
}, [params, templateComponents])
const dinamicTextField = (replicateItems, func, type, text, language) => { const dinamicTextField = (replicateItems, func, type, text, language) => {
@ -204,7 +226,7 @@ const ModalTemplate = ({ templates, modal_header, func }) => {
{text && {text &&
<div style={{ margin: 0, padding: 0, 'marginBottom': '15px' }}> <div style={{ margin: 0, padding: 0, 'marginBottom': '15px' }}>
<p style={{ margin: 0, padding: 0, fontSize: 12 }}>{text}</p> <p style={{ margin: 0, padding: 0, fontSize: 12 }}>{text}</p>
{type && (type === 'BODY') && dinamicTextField(body_params.length, handleTextChange, type, text, language)} {type && (type === 'BODY') && body_params && dinamicTextField(body_params.length, handleTextChange, type, text, language)}
</div>} </div>}
{buttons && <div>{buttons.map((b) => { {buttons && <div>{buttons.map((b) => {
const { type, text, url } = b const { type, text, url } = b

View File

@ -1,20 +1,20 @@
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"
import List from "@material-ui/core/List"; import List from "@material-ui/core/List"
import Paper from "@material-ui/core/Paper"; import Paper from "@material-ui/core/Paper"
import TicketListItem from "../TicketListItem"; import TicketListItem from "../TicketListItem"
import TicketsListSkeleton from "../TicketsListSkeleton"; import TicketsListSkeleton from "../TicketsListSkeleton"
import useTickets from "../../hooks/useTickets"; import useTickets from "../../hooks/useTickets"
import { i18n } from "../../translate/i18n"; import { i18n } from "../../translate/i18n"
import { AuthContext } from "../../context/Auth/AuthContext"; import { AuthContext } from "../../context/Auth/AuthContext"
import { SearchTicketContext } from "../../context/SearchTicket/SearchTicket"; import { SearchTicketContext } from "../../context/SearchTicket/SearchTicket"
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
ticketsListWrapper: { ticketsListWrapper: {
@ -73,64 +73,64 @@ const useStyles = makeStyles(theme => ({
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
}, },
})); }))
const reducer = (state, action) => { const reducer = (state, action) => {
if (action.type === "LOAD_TICKETS") { if (action.type === "LOAD_TICKETS") {
const newTickets = action.payload; const newTickets = action.payload
newTickets.forEach(ticket => { newTickets.forEach(ticket => {
const ticketIndex = state.findIndex(t => +t.id === +ticket.id); const ticketIndex = state.findIndex(t => +t.id === +ticket.id)
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
state[ticketIndex] = ticket; state[ticketIndex] = ticket
if (+ticket.unreadMessages > 0) { if (+ticket.unreadMessages > 0) {
state.unshift(state.splice(ticketIndex, 1)[0]); state.unshift(state.splice(ticketIndex, 1)[0])
} }
} else { } else {
state.push(ticket); state.push(ticket)
} }
}); })
return [...state]; return [...state]
} }
if (action.type === "RESET_UNREAD") { if (action.type === "RESET_UNREAD") {
const ticketId = action.payload; const ticketId = action.payload
const ticketIndex = state.findIndex(t => +t.id === +ticketId); const ticketIndex = state.findIndex(t => +t.id === +ticketId)
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
state[ticketIndex].unreadMessages = 0; state[ticketIndex].unreadMessages = 0
} }
return [...state]; return [...state]
} }
if (action.type === "UPDATE_TICKET") { if (action.type === "UPDATE_TICKET") {
const ticket = action.payload; const ticket = action.payload
const ticketIndex = state.findIndex(t => +t.id === +ticket.id); const ticketIndex = state.findIndex(t => +t.id === +ticket.id)
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
state[ticketIndex] = ticket; state[ticketIndex] = ticket
} else { } else {
state.unshift(ticket); state.unshift(ticket)
} }
return [...state]; return [...state]
} }
if (action.type === "UPDATE_TICKET_UNREAD_MESSAGES") { if (action.type === "UPDATE_TICKET_UNREAD_MESSAGES") {
const message = action.payload.message const message = action.payload.message
const ticket = action.payload.ticket; const ticket = action.payload.ticket
const ticketIndex = state.findIndex(t => +t.id === +ticket.id); const ticketIndex = state.findIndex(t => +t.id === +ticket.id)
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
@ -142,69 +142,70 @@ const reducer = (state, action) => {
ticket.unreadMessages += 1 ticket.unreadMessages += 1
} }
state[ticketIndex] = ticket; state[ticketIndex] = ticket
state.unshift(state.splice(ticketIndex, 1)[0]); state.unshift(state.splice(ticketIndex, 1)[0])
} else { } else {
state.unshift(ticket); state.unshift(ticket)
} }
return [...state]; return [...state]
} }
if (action.type === "UPDATE_TICKET_CONTACT") { if (action.type === "UPDATE_TICKET_CONTACT") {
const contact = action.payload; const contact = action.payload
const ticketIndex = state.findIndex(t => +t.contactId === +contact.id); const ticketIndex = state.findIndex(t => +t.contactId === +contact.id)
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
state[ticketIndex].contact = contact; state[ticketIndex].contact = contact
} }
return [...state]; return [...state]
} }
if (action.type === "DELETE_TICKET") { if (action.type === "DELETE_TICKET") {
const ticketId = action.payload; const ticketId = action.payload
const ticketIndex = state.findIndex(t => +t.id === +ticketId); const ticketIndex = state.findIndex(t => +t.id === +ticketId)
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
state.splice(ticketIndex, 1); state.splice(ticketIndex, 1)
} }
return [...state]; return [...state]
} }
if (action.type === "RESET") { if (action.type === "RESET") {
return []; return []
} }
}; }
const TicketsList = (props) => { const TicketsList = (props) => {
const { status, searchParam, searchParamContent, 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, [])
const { user } = useContext(AuthContext); const { user } = useContext(AuthContext)
const { searchTicket } = useContext(SearchTicketContext) const { searchTicket } = useContext(SearchTicketContext)
useEffect(() => { useEffect(() => {
dispatch({ type: "RESET" }); dispatch({ type: "RESET" })
setPageNumber(1); setPageNumber(1)
}, [status, searchParam, searchParamContent, showAll, selectedQueueIds, searchTicket]); }, [status, searchParam, searchParamContent, showAll, selectedQueueIds, searchTicket])
const { tickets, hasMore, loading } = useTickets({ const { tickets, hasMore, loading } = useTickets({
pageNumber, pageNumber,
searchParam, searchParam,
searchParamContent, searchParamContent,
status, status,
showAll, showAll,
queueIds: JSON.stringify(selectedQueueIds), queueIds: JSON.stringify(selectedQueueIds),
tab tab,
}); unlimited: status === 'open' ? "all" : "false"
})
useEffect(() => { useEffect(() => {
if (!status && !searchParam) return; if (!status && !searchParam) return
// if (searchParam) { // if (searchParam) {
// //
@ -217,31 +218,31 @@ const TicketsList = (props) => {
dispatch({ type: "RESET" }) dispatch({ type: "RESET" })
} }
dispatch({ type: "LOAD_TICKETS", payload: tickets, }); dispatch({ type: "LOAD_TICKETS", payload: tickets, })
}, [tickets, status, searchParam, pageNumber]); }, [tickets, status, searchParam, pageNumber])
useEffect(() => { useEffect(() => {
// if (tab=='search')return // if (tab=='search')return
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket(process.env.REACT_APP_BACKEND_URL)
const shouldUpdateTicket = ticket => const shouldUpdateTicket = ticket =>
(!ticket.userId || ticket.userId === user?.id || showAll) && (!ticket.userId || ticket.userId === user?.id || showAll) &&
(!ticket.queueId || selectedQueueIds.indexOf(ticket.queueId) > -1); (!ticket.queueId || selectedQueueIds.indexOf(ticket.queueId) > -1)
const notBelongsToUserQueues = ticket => const notBelongsToUserQueues = ticket =>
ticket.queueId && selectedQueueIds.indexOf(ticket.queueId) === -1; ticket.queueId && selectedQueueIds.indexOf(ticket.queueId) === -1
socket.on("connect", () => { socket.on("connect", () => {
if (status) { if (status) {
socket.emit("joinTickets", status); socket.emit("joinTickets", status)
} else { } else {
socket.emit("joinNotification"); socket.emit("joinNotification")
} }
}); })
@ -254,7 +255,7 @@ const TicketsList = (props) => {
dispatch({ dispatch({
type: "RESET_UNREAD", type: "RESET_UNREAD",
payload: data.ticketId, payload: data.ticketId,
}); })
} }
if (data.action === "update" && shouldUpdateTicket(data.ticket)) { if (data.action === "update" && shouldUpdateTicket(data.ticket)) {
@ -264,17 +265,17 @@ const TicketsList = (props) => {
dispatch({ dispatch({
type: "UPDATE_TICKET", type: "UPDATE_TICKET",
payload: data.ticket, payload: data.ticket,
}); })
} }
if (data.action === "update" && notBelongsToUserQueues(data.ticket)) { if (data.action === "update" && notBelongsToUserQueues(data.ticket)) {
dispatch({ type: "DELETE_TICKET", payload: data.ticket.id }); dispatch({ type: "DELETE_TICKET", payload: data.ticket.id })
} }
if (data.action === "delete") { if (data.action === "delete") {
dispatch({ type: "DELETE_TICKET", payload: data.ticketId }); dispatch({ type: "DELETE_TICKET", payload: data.ticketId })
} }
}); })
socket.on("appMessage", data => { socket.on("appMessage", data => {
@ -287,51 +288,49 @@ const TicketsList = (props) => {
type: "UPDATE_TICKET_UNREAD_MESSAGES", type: "UPDATE_TICKET_UNREAD_MESSAGES",
// payload: data.ticket, // payload: data.ticket,
payload: data, payload: data,
}); })
} }
}); })
socket.on("contact", data => { socket.on("contact", data => {
if (data.action === "update") { if (data.action === "update") {
dispatch({ dispatch({
type: "UPDATE_TICKET_CONTACT", type: "UPDATE_TICKET_CONTACT",
payload: data.contact, payload: data.contact,
}); })
} }
}); })
return () => { return () => {
socket.disconnect(); socket.disconnect()
}; }
}, [status, showAll, user, selectedQueueIds, tab]); }, [status, showAll, user, selectedQueueIds, tab])
useEffect(() => { useEffect(() => {
if (typeof updateCount === "function") { if (typeof updateCount === "function") {
updateCount(ticketsList.length); updateCount(ticketsList.length)
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ticketsList]); }, [ticketsList])
const loadMore = () => { const loadMore = () => {
setPageNumber(prevState => prevState + 1); setPageNumber(prevState => prevState + 1)
}; }
const handleScroll = e => { const handleScroll = e => {
if (!hasMore || loading) return; if (!hasMore || loading) return
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget; const { scrollTop, scrollHeight, clientHeight } = e.currentTarget
if (scrollHeight - (scrollTop + 100) < clientHeight) { if (scrollHeight - (scrollTop + 100) < clientHeight) {
loadMore(); loadMore()
} }
}; }
return ( return (
<Paper className={classes.ticketsListWrapper} style={style}> <Paper className={classes.ticketsListWrapper} style={style}>
@ -363,7 +362,7 @@ const TicketsList = (props) => {
</List> </List>
</Paper> </Paper>
</Paper> </Paper>
); )
}; }
export default TicketsList; export default TicketsList

View File

@ -194,7 +194,7 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => {
const { data } = await api.get(`/users/all`, { const { data } = await api.get(`/users/all`, {
params: { userId: user.id }, params: { userId: user.id },
}) })
setUsers(data.users) setUsers(data.users)
setQueuesByWhats(data.queues) setQueuesByWhats(data.queues)
setQueues(data.queues) setQueues(data.queues)

View File

@ -0,0 +1,91 @@
import React, { useEffect, useState, useContext } from 'react';
import { useHistory } from "react-router-dom";
import toastError from "../../errors/toastError";
import api from "../../services/api";
import Avatar from "@material-ui/core/Avatar";
import Typography from "@material-ui/core/Typography";
import Grid from "@material-ui/core/Grid";
import { AuthContext } from "../../context/Auth/AuthContext";
import { Button, Divider, } from "@material-ui/core";
const VcardPreview = ({ contact, numbers }) => {
const history = useHistory();
const { user } = useContext(AuthContext);
const [selectedContact, setContact] = useState({
name: "",
number: 0,
profilePicUrl: ""
});
useEffect(() => {
const delayDebounceFn = setTimeout(() => {
const fetchContacts = async () => {
try {
let contactObj = {
name: contact,
// number: numbers.replace(/\D/g, ""),
number: numbers !== undefined && numbers.replace(/\D/g, ""),
email: ""
}
const { data } = await api.post("/contact", contactObj);
setContact(data)
} catch (err) {
console.log(err)
toastError(err);
}
};
fetchContacts();
}, 500);
return () => clearTimeout(delayDebounceFn);
}, [contact, numbers]);
const handleNewChat = async () => {
try {
const { data: ticket } = await api.post("/tickets", {
contactId: selectedContact.id,
userId: user.id,
status: "open",
});
history.push(`/tickets/${ticket.id}`);
} catch (err) {
toastError(err);
}
}
return (
<>
<div style={{
minWidth: "250px",
}}>
<Grid container spacing={1}>
<Grid item xs={2}>
<Avatar src={selectedContact.profilePicUrl} />
</Grid>
<Grid item xs={9}>
<Typography style={{ marginTop: "12px", marginLeft: "10px" }} variant="subtitle1" color="primary" gutterBottom>
{selectedContact.name}
</Typography>
</Grid>
<Grid item xs={12}>
<Divider />
<Button
fullWidth
color="primary"
onClick={handleNewChat}
disabled={!selectedContact.number}
>Conversar</Button>
</Grid>
</Grid>
</div>
</>
);
};
export default VcardPreview;

View File

@ -70,6 +70,7 @@ const WhatsAppModal = ({ open, onClose, whatsAppId, whatsAppOfficial }) => {
farewellMessage: '', farewellMessage: '',
isDefault: false, isDefault: false,
isOfficial: false, isOfficial: false,
number: '',
phoneNumberId: '', phoneNumberId: '',
wabaId: '' wabaId: ''
} }
@ -109,6 +110,7 @@ const WhatsAppModal = ({ open, onClose, whatsAppId, whatsAppOfficial }) => {
if (!isOfficial) { if (!isOfficial) {
values.phoneNumberId = '' values.phoneNumberId = ''
values.wabaId = '' values.wabaId = ''
values.number = ''
} }
@ -276,6 +278,18 @@ const WhatsAppModal = ({ open, onClose, whatsAppId, whatsAppOfficial }) => {
margin="dense" margin="dense"
className={classes.textField} className={classes.textField}
/> />
<Field
as={TextField}
label="Phone number"
autoFocus
name="number"
error={touched.name && Boolean(errors.name)}
helperText={touched.name && errors.name}
variant="outlined"
margin="dense"
className={classes.textField}
/>
</div> </div>

View File

@ -20,7 +20,7 @@ const Chart = (props) => {
const theme = useTheme(); const theme = useTheme();
const date = useRef(new Date().toISOString()); const date = useRef(new Date().toISOString());
let { tickets } = useTickets({ date: date.current, unlimited: "true" }); let { tickets } = useTickets({ date: date.current, unlimited: "current" });
const [chartData, setChartData] = useState([ const [chartData, setChartData] = useState([
{ time: "08:00", amount: 0 }, { time: "08:00", amount: 0 },

View File

@ -9,6 +9,10 @@ import PropTypes from 'prop-types'
import Box from '@mui/material/Box' import Box from '@mui/material/Box'
import { AuthContext } from "../../context/Auth/AuthContext" import { AuthContext } from "../../context/Auth/AuthContext"
import { Can } from "../../components/Can" import { Can } from "../../components/Can"
import FormControlLabel from "@mui/material/FormControlLabel"
import Checkbox from '@mui/material/Checkbox'
import { Button } from "@material-ui/core" import { Button } from "@material-ui/core"
@ -25,6 +29,9 @@ import openSocket from "socket.io-client"
import { i18n } from "../../translate/i18n" import { i18n } from "../../translate/i18n"
import Switch from '@mui/material/Switch'
const label = { inputProps: { 'aria-label': 'Size switch demo' } }
const report = [{ 'value': '1', 'label': 'Atendimento por atendentes' }, { 'value': '2', 'label': 'Usuários online/offline' }] const report = [{ 'value': '1', 'label': 'Atendimento por atendentes' }, { 'value': '2', 'label': 'Usuários online/offline' }]
@ -48,9 +55,13 @@ const reducerQ = (state, action) => {
if (action.type === 'LOAD_QUERY') { if (action.type === 'LOAD_QUERY') {
const queries = action.payload let queries = action.payload
const newQueries = [] const newQueries = []
if (queries?.hasOwnProperty('usersProfile')) {
queries = queries.usersProfile
}
queries.forEach((query) => { queries.forEach((query) => {
const queryIndex = state.findIndex((q) => q.id === query.id) const queryIndex = state.findIndex((q) => q.id === query.id)
@ -235,7 +246,7 @@ let columnsDataSuper = [
] ]
// function convertAndFormatDate(dateString) { // function convertAndFormatDate(dateString) {
// // Check if the input date string is in the desired format // // Check if the input date string is in the desired format
// const isDesiredFormat = /^\w{3} \w{3} \d{2} \d{4} \d{2}:\d{2}:\d{2} GMT[-+]\d{4} \([\w\s]+\)$/i.test(dateString) // const isDesiredFormat = /^\w{3} \w{3} \d{2} \d{4} \d{2}:\d{2}:\d{2} GMT[-+]\d{4} \([\w\s]+\)$/i.test(dateString)
@ -273,7 +284,7 @@ const Report = () => {
const [startDate, setDatePicker1] = useState(new Date()) const [startDate, setDatePicker1] = useState(new Date())
const [endDate, setDatePicker2] = useState(new Date()) const [endDate, setDatePicker2] = useState(new Date())
const [userId, setUser] = useState(null) const [userId, setUser] = useState(null)
const [query, dispatchQ] = useReducer(reducerQ, []) const [query, dispatchQ] = useReducer(reducerQ, [])
const [reportOption, setReport] = useState('1') const [reportOption, setReport] = useState('1')
const [reporList,] = useState(report) const [reporList,] = useState(report)
@ -281,14 +292,18 @@ const Report = () => {
const [dataRows, setData] = useState([]) const [dataRows, setData] = useState([])
const [onQueueStatus, setOnQueueProcessStatus] = useState(undefined) const [onQueueStatus, setOnQueueProcessStatus] = useState(undefined)
const [csvFile, setCsvFile] = useState() const [csvFile, setCsvFile] = useState()
const [selectedValue, setSelectedValue] = useState('created')
const [checked, setChecked] = useState(true)
const [queues, setQueues] = useState([])
const [queueId, setQueue] = useState(null)
useEffect(() => { useEffect(() => {
dispatch({ type: "RESET" }) dispatch({ type: "RESET" })
dispatchQ({ type: "RESET" }) dispatchQ({ type: "RESET" })
setTicketsPageNumber(1) setTicketsPageNumber(1)
setPageNumber(1) setPageNumber(1)
}, [searchParam, profile]) }, [searchParam, profile])
useEffect(() => { useEffect(() => {
@ -302,7 +317,7 @@ const Report = () => {
const { data } = await api.get("/users/", { const { data } = await api.get("/users/", {
params: { searchParam, pageNumber, profile }, params: { searchParam, pageNumber, profile },
}) })
dispatch({ type: "LOAD_USERS", payload: data.users }) dispatch({ type: "LOAD_USERS", payload: data.users })
//setLoading(false); //setLoading(false);
@ -328,28 +343,29 @@ const Report = () => {
setLoading(true) setLoading(true)
const fetchQueries = async () => { const fetchQueries = async () => {
try { try {
if (reportOption === '1') { if (reportOption === '1') {
// const { data } = await api.get("/reports/", { params: { userId: userId ? userId : 0, startDate: convertAndFormatDate(startDate), endDate: convertAndFormatDate(endDate), pageNumber: pageNumberTickets }, }) // const { data } = await api.get("/reports/", { params: { userId: userId ? userId : 0, startDate: convertAndFormatDate(startDate), endDate: convertAndFormatDate(endDate), pageNumber: pageNumberTickets }, })
const { data } = await api.get("/reports/", { params: { userId, startDate, endDate, pageNumber: pageNumberTickets }, userQueues: userA.queues}) const { data } = await api.get("/reports/", { params: { userId, startDate, endDate, pageNumber: pageNumberTickets, createdOrUpdated: selectedValue, queueId }, userQueues: userA.queues })
let ticketsQueue = data.tickets; let ticketsQueue = data.tickets
let userQueues = userA.queues; let userQueues = userA.queues
let filterQueuesTickets = []; let filterQueuesTickets = []
if(userQueues.length > 1){ if (userQueues.length > 1) {
filterQueuesTickets = ticketsQueue.filter(ticket => userQueues.some(queue => queue?.name === ticket?.queue?.name)); filterQueuesTickets = ticketsQueue.filter(ticket => userQueues.some(queue => queue?.name === ticket?.queue?.name))
}else if(userQueues.length > 0) { } else if (userQueues.length > 0) {
filterQueuesTickets = ticketsQueue.filter(ticket => ticket?.queue?.name === userQueues[0]?.name); filterQueuesTickets = ticketsQueue.filter(ticket => ticket?.queue?.name === userQueues[0]?.name)
} }
data.tickets = filterQueuesTickets; data.tickets = filterQueuesTickets
dispatchQ({ type: "LOAD_QUERY", payload: data.tickets }) dispatchQ({ type: "LOAD_QUERY", payload: data.tickets })
setHasMore(data.hasMore) setHasMore(data.hasMore)
setTotalCountTickets(data.count) setTotalCountTickets(data.count)
setLoading(false) setLoading(false)
setQueues(data.queues)
} }
else if (reportOption === '2') { else if (reportOption === '2') {
@ -370,9 +386,13 @@ const Report = () => {
}, 500) }, 500)
return () => clearTimeout(delayDebounceFn) return () => clearTimeout(delayDebounceFn)
}, [userId, startDate, endDate, reportOption, pageNumberTickets, totalCountTickets]) }, [userId, queueId, checked, startDate, endDate, reportOption, pageNumberTickets, totalCountTickets, selectedValue])
const handleCheckBoxChange = (value) => {
setSelectedValue(value)
}
// Get from child 1 // Get from child 1
const datePicker1Value = (data) => { const datePicker1Value = (data) => {
@ -387,18 +407,23 @@ const Report = () => {
// Get from child 3 // Get from child 3
const textFieldSelectUser = (data) => { const textFieldSelectUser = (data) => {
setQueue(null)
setUser(data) setUser(data)
} }
const textFieldSelectQueue = (data) => {
setUser(0)
setQueue(data)
}
// Get from report option // Get from report option
const reportValue = (data) => { const reportValue = (data) => {
if (data === '2') {
setChecked(true)
}
setReport(data) setReport(data)
} }
useEffect(() => { useEffect(() => {
@ -479,8 +504,8 @@ const Report = () => {
const fetchQueries = async () => { const fetchQueries = async () => {
try { try {
const querySavedOnQueue = await apiBroker.post("/reports/messages", const querySavedOnQueue = await apiBroker.post("/reports/messages",
{ {
app: { app: {
@ -490,6 +515,7 @@ const Report = () => {
identifier: 'csv' identifier: 'csv'
}, },
query_params: { query_params: {
queueId: queueId,
userId: userId, userId: userId,
startDate: startDate, startDate: startDate,
endDate: endDate endDate: endDate
@ -567,7 +593,7 @@ const Report = () => {
} }
}, [reportOption, startDate, endDate, userId, userA]) }, [reportOption, startDate, endDate, userId, queueId, checked, userA, selectedValue])
useEffect(() => { useEffect(() => {
@ -657,6 +683,10 @@ const Report = () => {
} }
const handleChange = (event) => {
setChecked(event.target.checked)
}
return ( return (
<Can <Can
@ -665,16 +695,67 @@ const Report = () => {
yes={() => ( yes={() => (
<MainContainer> <MainContainer>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)' }}> <Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 2fr) repeat(2, 1fr)', gap: '8px' }}>
<Item><SelectField func={textFieldSelectUser} emptyField={true} header={i18n.t("reports.user")} currencies={users.map((obj) => { <Box sx={{ display: 'flex', flexDirection: 'column', padding: '10px 0', alignItems: 'center', }}>
return { 'value': obj.id, 'label': obj.name }
})} /></Item>
<Item><DatePicker1 func={datePicker1Value} minDate={false} startEmpty={false} title={i18n.t("reports.dateStart")} /></Item> {checked ?
<Item><DatePicker2 func={datePicker2Value} minDate={false} startEmpty={false} title={i18n.t("reports.dateEnd")} /></Item> <SelectField
func={textFieldSelectUser}
emptyField={true}
header={i18n.t("reports.user")}
currencies={users.map((obj) => {
return { 'value': obj.id, 'label': obj.name }
})} /> :
<SelectField
func={textFieldSelectQueue}
emptyField={true}
header={'Filas'}
currencies={queues.map((obj) => {
return { 'value': obj.id, 'label': obj.name }
})} />
}
<Item sx={{ display: 'grid', gridColumn: '4 / 5', }}> {reportOption === '1' &&
<div>
<label>
Filas
<Switch
checked={checked}
onChange={handleChange}
inputProps={{ 'aria-label': 'controlled' }}
/>
Usuarios
</label>
</div>
}
</Box>
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<DatePicker1 func={datePicker1Value} minDate={false} startEmpty={false} title={i18n.t("reports.dateStart")} />
</Box>
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}><DatePicker2 func={datePicker2Value} minDate={false} startEmpty={false} title={i18n.t("reports.dateEnd")} /></Box>
{reportOption === '1' ?
<Box sx={{ display: 'flex', flexDirection: 'column', padding: '10px 0', }}>
<FormControlLabel
control={<Checkbox checked={selectedValue === 'created'} onChange={() => handleCheckBoxChange('created')} />}
label="Criado"
/>
<FormControlLabel
control={<Checkbox checked={selectedValue === 'updated'} onChange={() => handleCheckBoxChange('updated')} />}
label="Atualizado"
/>
</Box> :
<Box sx={{ display: 'flex', flexDirection: 'column', padding: '10px 0', }}>
</Box>
}
<Box sx={{ display: 'flex', flexDirection: 'column', padding: '10px 0', gap: '8px', width: '100px', alignItems: 'center' }}>
<ReportModal currencies={reporList} func={reportValue} reportOption={reportOption} /> <ReportModal currencies={reporList} func={reportValue} reportOption={reportOption} />
@ -688,7 +769,7 @@ const Report = () => {
} }
</Item> </Box>
</Box> </Box>
@ -701,16 +782,14 @@ const Report = () => {
{reportOption === '1' && {reportOption === '1' &&
<> <>
<MTable data={query} <MTable data={query}
columns={userA.profile !== 'supervisor' ?columnsData:columnsDataSuper} columns={userA.profile !== 'supervisor' ? columnsData : columnsDataSuper}
hasChild={true} hasChild={true}
removeClickRow={false} removeClickRow={false}
handleScroll={handleScroll} handleScroll={handleScroll}
table_title={i18n.t("reports.listTitles.title1_1")} /> table_title={i18n.t("reports.listTitles.title1_1")} />
</> </>
} }