Compare commits
2 Commits
beb4fd64c4
...
cc12cafb99
Author | SHA1 | Date |
---|---|---|
gustavo-gsp | cc12cafb99 | |
gustavo-gsp | 860d462d37 |
|
@ -8,6 +8,7 @@
|
||||||
"watch": "tsc -w",
|
"watch": "tsc -w",
|
||||||
"start": "nodemon --expose-gc dist/server.js",
|
"start": "nodemon --expose-gc dist/server.js",
|
||||||
"dev:server": "ts-node-dev --respawn --transpile-only --ignore node_modules src/server.ts",
|
"dev:server": "ts-node-dev --respawn --transpile-only --ignore node_modules src/server.ts",
|
||||||
|
"dev": "nodemon --exec npm run dev:server",
|
||||||
"pretest": "NODE_ENV=test sequelize db:migrate && NODE_ENV=test sequelize db:seed:all",
|
"pretest": "NODE_ENV=test sequelize db:migrate && NODE_ENV=test sequelize db:seed:all",
|
||||||
"test": "NODE_ENV=test jest",
|
"test": "NODE_ENV=test jest",
|
||||||
"posttest": "NODE_ENV=test sequelize db:migrate:undo:all"
|
"posttest": "NODE_ENV=test sequelize db:migrate:undo:all"
|
||||||
|
|
|
@ -30,7 +30,6 @@ type IndexQuery = {
|
||||||
queueId: string;
|
queueId: string;
|
||||||
pageNumber: string;
|
pageNumber: string;
|
||||||
userQueues: [];
|
userQueues: [];
|
||||||
isRemote: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type ReportOnQueue = {
|
type ReportOnQueue = {
|
||||||
|
@ -263,7 +262,7 @@ export const reportMessagesUserByDateStartDateEnd = async (
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json(data_query_messages);
|
return res.status(200).json(data_query_messages);
|
||||||
|
@ -303,19 +302,15 @@ export const reportService = async (
|
||||||
throw new AppError("ERR_NO_PERMISSION", 403);
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { startDate, endDate, queueId, isRemote } = req.query as IndexQuery;
|
const { startDate, endDate, queueId } = req.query as IndexQuery;
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`startDate: ${startDate} | endDate: ${endDate} | queueId: ${queueId}`
|
`startDate: ${startDate} | endDate: ${endDate} | queueId: ${queueId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("IS REMOTE: ", isRemote);
|
|
||||||
console.log("isRemote: ", isRemote && isRemote == "true" ? true : false);
|
|
||||||
|
|
||||||
const reportService = await ReportByNumberQueueService({
|
const reportService = await ReportByNumberQueueService({
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate
|
||||||
isRemote: isRemote && isRemote == "true" ? true : false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).json({ reportService });
|
return res.status(200).json({ reportService });
|
||||||
|
@ -333,13 +328,12 @@ export const reportServiceByQueue = async (
|
||||||
throw new AppError("ERR_NO_PERMISSION", 403);
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { startDate, endDate, queueId, isRemote } = req.query as IndexQuery;
|
const { startDate, endDate, queueId } = req.query as IndexQuery;
|
||||||
|
|
||||||
const reportService = await ReportByNumberQueueService({
|
const reportService = await ReportByNumberQueueService({
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
queue: true,
|
queue: true
|
||||||
isRemote: isRemote && isRemote == "true" ? true : false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).json({ reportService });
|
return res.status(200).json({ reportService });
|
||||||
|
|
|
@ -123,12 +123,11 @@ export const update = async (
|
||||||
throw new AppError("ERR_NO_PERMISSION", 403);
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
}
|
}
|
||||||
const { settingKey: key } = req.params;
|
const { settingKey: key } = req.params;
|
||||||
const { value, obj } = req.body;
|
const { value } = req.body;
|
||||||
|
|
||||||
const setting = await UpdateSettingService({
|
const setting = await UpdateSettingService({
|
||||||
key,
|
key,
|
||||||
value,
|
value
|
||||||
obj
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (key && key == "whatsaAppCloudApi") {
|
if (key && key == "whatsaAppCloudApi") {
|
||||||
|
@ -166,7 +165,7 @@ export const update = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
const io = getIO();
|
const io = getIO();
|
||||||
io.emit("settings", {
|
io.emit("settings", {
|
||||||
action: "update",
|
action: "update",
|
||||||
|
|
|
@ -75,11 +75,10 @@ import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl";
|
||||||
import CreateContactService from "../services/ContactServices/CreateContactService";
|
import CreateContactService from "../services/ContactServices/CreateContactService";
|
||||||
import { botSendMessage } from "../services/WbotServices/wbotMessageListener";
|
import { botSendMessage } from "../services/WbotServices/wbotMessageListener";
|
||||||
import WhatsappQueue from "../models/WhatsappQueue";
|
import WhatsappQueue from "../models/WhatsappQueue";
|
||||||
import { del, get, set } from "../helpers/RedisClient";
|
import { get } from "../helpers/RedisClient";
|
||||||
import CountStatusChatEndService from "../services/StatusChatEndService/CountStatusChatEndService";
|
import CountStatusChatEndService from "../services/StatusChatEndService/CountStatusChatEndService";
|
||||||
import Queue from "../models/Queue";
|
import Queue from "../models/Queue";
|
||||||
import StatusChatEnd from "../models/StatusChatEnd";
|
import StatusChatEnd from "../models/StatusChatEnd";
|
||||||
import controllByNumber from "../helpers/controllByNumber";
|
|
||||||
|
|
||||||
export const index = async (req: Request, res: Response): Promise<Response> => {
|
export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
const {
|
const {
|
||||||
|
@ -102,23 +101,20 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
queueIds = JSON.parse(queueIdsStringified);
|
queueIds = JSON.parse(queueIdsStringified);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { tickets, count, hasMore, remoteTicketsControll } =
|
const { tickets, count, hasMore } = await ListTicketsService({
|
||||||
await ListTicketsService({
|
searchParam,
|
||||||
searchParam,
|
pageNumber,
|
||||||
pageNumber,
|
status,
|
||||||
status,
|
date,
|
||||||
date,
|
showAll,
|
||||||
showAll,
|
userId,
|
||||||
userId,
|
queueIds,
|
||||||
queueIds,
|
withUnreadMessages,
|
||||||
withUnreadMessages,
|
unlimited,
|
||||||
unlimited,
|
searchParamContent
|
||||||
searchParamContent
|
});
|
||||||
});
|
|
||||||
|
|
||||||
return res
|
return res.status(200).json({ tickets, count, hasMore });
|
||||||
.status(200)
|
|
||||||
.json({ tickets, count, hasMore, remoteTicketsControll });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const remoteTicketCreation = async (
|
export const remoteTicketCreation = async (
|
||||||
|
@ -236,8 +232,7 @@ export const remoteTicketCreation = async (
|
||||||
|
|
||||||
const botInfo = await BotIsOnQueue("botqueue");
|
const botInfo = await BotIsOnQueue("botqueue");
|
||||||
|
|
||||||
// ticket from queueChoice or bot
|
let ticket = await Ticket.findOne({
|
||||||
let ticket: any = await Ticket.findOne({
|
|
||||||
where: {
|
where: {
|
||||||
[Op.or]: [
|
[Op.or]: [
|
||||||
{ contactId, status: "queueChoice" },
|
{ contactId, status: "queueChoice" },
|
||||||
|
@ -263,69 +258,27 @@ export const remoteTicketCreation = async (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ticket = await Ticket.findOne({
|
if (!ticket) {
|
||||||
where: {
|
ticket = await FindOrCreateTicketService(
|
||||||
[Op.or]: [
|
contact,
|
||||||
{ contactId, status: "pending" },
|
whatsappId,
|
||||||
{ contactId, status: "open" }
|
0,
|
||||||
]
|
undefined,
|
||||||
}
|
queueId,
|
||||||
});
|
true
|
||||||
|
|
||||||
if (ticket) {
|
|
||||||
console.log(
|
|
||||||
`THE CAMPAIGN TICKET WAS NOT CREATED BECAUSE THE TICKET IS PENDING OR OPEN`
|
|
||||||
);
|
);
|
||||||
|
botSendMessage(ticket, `${msg}`);
|
||||||
return res.status(422).json({
|
|
||||||
msg: `The campaign ticket was not created because the number ${contact_to} already has a ticket open or pending`
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ticket = await FindOrCreateTicketService(
|
|
||||||
contact,
|
|
||||||
whatsappId,
|
|
||||||
0,
|
|
||||||
undefined,
|
|
||||||
queueId,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
// botSendMessage(ticket, `${msg}`);
|
|
||||||
|
|
||||||
await ticket.update({
|
|
||||||
lastMessage: msg
|
|
||||||
});
|
|
||||||
|
|
||||||
await set(
|
|
||||||
`remote:ticketId:${ticket.id}`,
|
|
||||||
JSON.stringify({
|
|
||||||
id: ticket.id,
|
|
||||||
createdAt: ticket.createdAt,
|
|
||||||
updatedAt: ticket.updatedAt,
|
|
||||||
whatsappId: ticket.whatsappId
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
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 obj = await controllByNumber();
|
|
||||||
|
|
||||||
if (obj?.tickets) {
|
|
||||||
io.emit("remoteTickesControll", {
|
|
||||||
action: "update",
|
|
||||||
tickets: obj.ticketIds
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`REMOTE TICKET CREATION FROM ENDPOINT | STATUS: 200 | MSG: success`
|
`REMOTE TICKET CREATION FROM ENDPOINT | STATUS: 200 | MSG: success`
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).json({ msg: "success" });
|
return res.status(200).json({ msg: "success" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,7 +465,7 @@ export const update = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
const schedulingNotifyCreate = await CreateSchedulingNotifyService({
|
const schedulingNotifyCreate = await CreateSchedulingNotifyService({
|
||||||
ticketId: scheduleData.ticketId,
|
ticketId: scheduleData.ticketId,
|
||||||
statusChatEndId: `${statusChatEnd.id}`,
|
statusChatEndId: `${statusChatEnd.id}`,
|
||||||
schedulingDate: scheduleData.schedulingDate,
|
schedulingDate: scheduleData.schedulingDate,
|
||||||
schedulingTime: scheduleData.schedulingTime,
|
schedulingTime: scheduleData.schedulingTime,
|
||||||
|
@ -695,7 +648,5 @@ export const remove = async (
|
||||||
ticketId: +ticketId
|
ticketId: +ticketId
|
||||||
});
|
});
|
||||||
|
|
||||||
await del(`remote:ticketId:${ticketId}`);
|
|
||||||
|
|
||||||
return res.status(200).json({ message: "ticket deleted" });
|
return res.status(200).json({ message: "ticket deleted" });
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { QueryInterface, DataTypes } from "sequelize";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: (queryInterface: QueryInterface) => {
|
|
||||||
return queryInterface.addColumn("Tickets", "remoteDone", {
|
|
||||||
type: DataTypes.BOOLEAN,
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: false
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: (queryInterface: QueryInterface) => {
|
|
||||||
return queryInterface.removeColumn("Tickets", "remoteDone");
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { QueryInterface, DataTypes } from "sequelize";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
up: (queryInterface: QueryInterface) => {
|
|
||||||
return queryInterface.addColumn("Settings", "obj", {
|
|
||||||
type: DataTypes.STRING,
|
|
||||||
allowNull: true
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: (queryInterface: QueryInterface) => {
|
|
||||||
return queryInterface.removeColumn("Settings", "obj");
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -6,8 +6,8 @@ module.exports = {
|
||||||
"Settings",
|
"Settings",
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
key: "remoteTicketSendControll",
|
key: "blockAudioVideoMedia",
|
||||||
value: "true",
|
value: "disabled",
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date()
|
updatedAt: new Date()
|
||||||
}
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { QueryInterface } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.bulkInsert(
|
||||||
|
"Settings",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: "waitingTimeTickets",
|
||||||
|
value: "disabled",
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date()
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.bulkDelete("Settings", {});
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { QueryInterface } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.bulkInsert(
|
||||||
|
"Settings",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: "notificationTransferQueue",
|
||||||
|
value: "disabled",
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date()
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.bulkDelete("Settings", {});
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,78 +0,0 @@
|
||||||
import { intervalToDuration } from "date-fns";
|
|
||||||
import { del, get, set } from "./RedisClient";
|
|
||||||
import { getIO } from "../libs/socket";
|
|
||||||
import controllByNumber from "./controllByNumber";
|
|
||||||
|
|
||||||
let timer: any;
|
|
||||||
|
|
||||||
const AutoRemoteTickets = async () => {
|
|
||||||
try {
|
|
||||||
let obj: any = await controllByNumber();
|
|
||||||
|
|
||||||
if (!obj?.tickets) return;
|
|
||||||
|
|
||||||
for (const ticket of obj.tickets) {
|
|
||||||
if (!ticket.includes("messageDateTime")) continue;
|
|
||||||
|
|
||||||
let match = ticket.match(/"messageDateTime":("[^"]*")/);
|
|
||||||
let messageDateTime = match ? match[1] : null;
|
|
||||||
console.log("messageDateTime: ", messageDateTime);
|
|
||||||
|
|
||||||
match = ticket.match(/"whatsappId":(\d+)/);
|
|
||||||
let whatsappId = match ? match[1] : null;
|
|
||||||
console.log("whatsappId: ", whatsappId);
|
|
||||||
|
|
||||||
const whatsapp = await get({
|
|
||||||
key: `whatsapp:${whatsappId}`
|
|
||||||
});
|
|
||||||
|
|
||||||
match = whatsapp.match(/"number":"(\d+)"/);
|
|
||||||
let number = match ? match[1] : null;
|
|
||||||
console.log("number: ", number);
|
|
||||||
|
|
||||||
match = ticket.match(/"id":(\d+)/);
|
|
||||||
let ticketId = match ? match[1] : null;
|
|
||||||
console.log("ticketId: ", ticketId);
|
|
||||||
|
|
||||||
number = JSON.parse(number);
|
|
||||||
ticketId = JSON.parse(ticketId);
|
|
||||||
|
|
||||||
let timeDiff: any = intervalToDuration({
|
|
||||||
start: new Date(JSON.parse(messageDateTime)),
|
|
||||||
end: new Date()
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("______timeDiff: ", timeDiff);
|
|
||||||
|
|
||||||
if (timeDiff.seconds > 50) {
|
|
||||||
del(`remote:ticketId:${ticketId}`);
|
|
||||||
|
|
||||||
obj = await controllByNumber();
|
|
||||||
|
|
||||||
const io = getIO();
|
|
||||||
io.emit("remoteTickesControll", {
|
|
||||||
action: "update",
|
|
||||||
tickets: obj.ticketIds
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log("There was an error on auto remote tickets service: ", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const schedule = async () => {
|
|
||||||
try {
|
|
||||||
clearInterval(timer);
|
|
||||||
|
|
||||||
await AutoRemoteTickets();
|
|
||||||
} catch (error) {
|
|
||||||
console.log("error on schedule: ", error);
|
|
||||||
} finally {
|
|
||||||
timer = setInterval(schedule, 5000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
timer = setInterval(schedule, 5000);
|
|
||||||
|
|
||||||
export default schedule;
|
|
|
@ -29,24 +29,14 @@ export async function getSimple(key: string) {
|
||||||
|
|
||||||
export async function get({ key, value, parse }: getData) {
|
export async function get({ key, value, parse }: getData) {
|
||||||
if (key.includes("*")) {
|
if (key.includes("*")) {
|
||||||
const keys = await redis.keys(key);
|
const keys = await redis.keys(key);
|
||||||
if (keys.length > 0) {
|
if (keys.length > 0) {
|
||||||
if (value) {
|
for (const key of keys) {
|
||||||
for (const key of keys) {
|
const val = await redis.get(key);
|
||||||
const val = await redis.get(key);
|
if (val.includes(value)) {
|
||||||
if (val.includes(value)) {
|
if (parse) return JSON.parse(val);
|
||||||
if (parse) return JSON.parse(val);
|
return val;
|
||||||
return val;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
let res: any[] = [];
|
|
||||||
for (const key of keys) {
|
|
||||||
const val = await redis.get(key);
|
|
||||||
if (parse) res.push(JSON.parse(val));
|
|
||||||
res.push(val);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
import { get, set } from "./RedisClient";
|
|
||||||
|
|
||||||
async function controllByNumber() {
|
|
||||||
let tickets = await get({ key: "remote:ticketId*", parse: false });
|
|
||||||
|
|
||||||
if (!tickets) return { ticketIds: [], tickets: null };
|
|
||||||
|
|
||||||
let controll: any[] = [];
|
|
||||||
|
|
||||||
for (const ticket of tickets) {
|
|
||||||
let match = ticket.match(/"whatsappId":(\d+)/);
|
|
||||||
let whatsappId = match ? match[1] : null;
|
|
||||||
|
|
||||||
const whatsapp = await get({
|
|
||||||
key: `whatsapp:${whatsappId}`
|
|
||||||
});
|
|
||||||
|
|
||||||
match = whatsapp.match(/"number":"(\d+)"/);
|
|
||||||
let number = match ? match[1] : null;
|
|
||||||
|
|
||||||
match = ticket.match(/"id":(\d+)/);
|
|
||||||
let ticketId = match ? match[1] : null;
|
|
||||||
|
|
||||||
number = JSON.parse(number);
|
|
||||||
ticketId = JSON.parse(ticketId);
|
|
||||||
|
|
||||||
const index = controll.findIndex((c: any) => c.number == number);
|
|
||||||
|
|
||||||
if (index == -1) {
|
|
||||||
controll.push({ ticketId, number });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ticketIds = controll.map((c: any) => c.ticketId);
|
|
||||||
|
|
||||||
set(`remote:controll`, JSON.stringify(ticketIds));
|
|
||||||
|
|
||||||
return { ticketIds, tickets };
|
|
||||||
}
|
|
||||||
|
|
||||||
export default controllByNumber;
|
|
|
@ -16,9 +16,6 @@ class Setting extends Model<Setting> {
|
||||||
@Column
|
@Column
|
||||||
value: string;
|
value: string;
|
||||||
|
|
||||||
@Column
|
|
||||||
obj: string;
|
|
||||||
|
|
||||||
@CreatedAt
|
@CreatedAt
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import User from "./User";
|
||||||
import Whatsapp from "./Whatsapp";
|
import Whatsapp from "./Whatsapp";
|
||||||
|
|
||||||
import SchedulingNotify from "./SchedulingNotify";
|
import SchedulingNotify from "./SchedulingNotify";
|
||||||
import StatusChatEnd from "./StatusChatEnd";
|
import StatusChatEnd from "./StatusChatEnd"
|
||||||
|
|
||||||
@Table
|
@Table
|
||||||
class Ticket extends Model<Ticket> {
|
class Ticket extends Model<Ticket> {
|
||||||
|
@ -47,10 +47,6 @@ class Ticket extends Model<Ticket> {
|
||||||
@Column
|
@Column
|
||||||
isRemote: boolean;
|
isRemote: boolean;
|
||||||
|
|
||||||
@Default(false)
|
|
||||||
@Column
|
|
||||||
remoteDone: boolean;
|
|
||||||
|
|
||||||
@ForeignKey(() => StatusChatEnd)
|
@ForeignKey(() => StatusChatEnd)
|
||||||
@Column
|
@Column
|
||||||
statusChatEndId: number;
|
statusChatEndId: number;
|
||||||
|
|
|
@ -9,7 +9,7 @@ settingRoutes.get("/settings", SettingController.index);
|
||||||
|
|
||||||
settingRoutes.get("/settings/ticket/:number", SettingController.ticketSettings);
|
settingRoutes.get("/settings/ticket/:number", SettingController.ticketSettings);
|
||||||
|
|
||||||
// settingRoutes.get("/settings/:settingKey", isAuth, SettingsController.show);
|
// routes.get("/settings/:settingKey", isAuth, SettingsController.show);
|
||||||
|
|
||||||
settingRoutes.put(
|
settingRoutes.put(
|
||||||
"/settings/ticket",
|
"/settings/ticket",
|
||||||
|
|
|
@ -13,7 +13,6 @@ import { loadContactsCache } from "./helpers/ContactsCache";
|
||||||
import { loadSchedulesCache } from "./helpers/SchedulingNotifyCache";
|
import { loadSchedulesCache } from "./helpers/SchedulingNotifyCache";
|
||||||
import { delRestoreControllFile } from "./helpers/RestoreControll";
|
import { delRestoreControllFile } from "./helpers/RestoreControll";
|
||||||
import "./helpers/AutoCloseTickets";
|
import "./helpers/AutoCloseTickets";
|
||||||
import "./helpers/AutoRemoteTickets"
|
|
||||||
|
|
||||||
import "./helpers/SchedulingNotifySendMessage";
|
import "./helpers/SchedulingNotifySendMessage";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
@ -91,27 +90,17 @@ gracefulShutdown(server);
|
||||||
const { phoneNumberId, id, greetingMessage } = whatsapps[i];
|
const { phoneNumberId, id, greetingMessage } = whatsapps[i];
|
||||||
|
|
||||||
if (phoneNumberId) {
|
if (phoneNumberId) {
|
||||||
// await set(
|
await set(
|
||||||
// `whatsapp:${whatsapps[i].dataValues.id}`,
|
`whatsapp:${whatsapps[i].dataValues.id}`,
|
||||||
// JSON.stringify({
|
JSON.stringify({
|
||||||
// number: whatsapps[i].dataValues.number,
|
number: whatsapps[i].dataValues.number,
|
||||||
// id,
|
id,
|
||||||
// greetingMessage,
|
greetingMessage,
|
||||||
// phoneNumberId
|
phoneNumberId
|
||||||
// })
|
})
|
||||||
// );
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await set(
|
|
||||||
`whatsapp:${whatsapps[i].dataValues.id}`,
|
|
||||||
JSON.stringify({
|
|
||||||
number: whatsapps[i].dataValues.number,
|
|
||||||
id,
|
|
||||||
greetingMessage,
|
|
||||||
phoneNumberId
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (phoneNumberId) {
|
if (phoneNumberId) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import AppError from "../../errors/AppError";
|
import AppError from "../../errors/AppError";
|
||||||
import { get, set } from "../../helpers/RedisClient";
|
|
||||||
import { updateTicketCacheByTicketId } from "../../helpers/TicketCache";
|
import { updateTicketCacheByTicketId } from "../../helpers/TicketCache";
|
||||||
import { getIO } from "../../libs/socket";
|
import { getIO } from "../../libs/socket";
|
||||||
import Message from "../../models/Message";
|
import Message from "../../models/Message";
|
||||||
|
@ -22,8 +21,10 @@ interface Request {
|
||||||
|
|
||||||
const CreateMessageService = async ({
|
const CreateMessageService = async ({
|
||||||
messageData
|
messageData
|
||||||
}: Request): Promise<Message> => {
|
}: Request): Promise<Message> => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
await Message.upsert(messageData);
|
await Message.upsert(messageData);
|
||||||
|
|
||||||
const message = await Message.findByPk(messageData.id, {
|
const message = await Message.findByPk(messageData.id, {
|
||||||
|
@ -46,35 +47,13 @@ const CreateMessageService = async ({
|
||||||
throw new Error("ERR_CREATING_MESSAGE");
|
throw new Error("ERR_CREATING_MESSAGE");
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////// SETTINGS ///////////////////////////
|
if (message.ticket.status != "queueChoice") {
|
||||||
let ticketRemote = await get({
|
|
||||||
key: `remote:ticketId:${message.ticket.id}`
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ticketRemote && !ticketRemote.includes("messageDateTime")) {
|
|
||||||
ticketRemote = JSON.parse(ticketRemote);
|
|
||||||
|
|
||||||
ticketRemote = {
|
|
||||||
...ticketRemote,
|
|
||||||
...{ messageDateTime: message.createdAt }
|
|
||||||
};
|
|
||||||
|
|
||||||
const ticket = await Ticket.findByPk(message.ticket.id);
|
|
||||||
ticket?.update({ remoteDone: true });
|
|
||||||
|
|
||||||
console.log('MESSAGE SERVICE: XXXXXXXXXXXXXXXXXXXXXXX')
|
|
||||||
|
|
||||||
set(`remote:ticketId:${message.ticket.id}`, JSON.stringify(ticketRemote));
|
|
||||||
}
|
|
||||||
//////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
if (message.ticket.status != "queueChoice") {
|
|
||||||
await updateTicketCacheByTicketId(message.ticket.id, {
|
await updateTicketCacheByTicketId(message.ticket.id, {
|
||||||
lastMessage: message.body,
|
lastMessage: message.body,
|
||||||
updatedAt: new Date(message.ticket.updatedAt).toISOString(),
|
updatedAt: new Date(message.ticket.updatedAt).toISOString(),
|
||||||
"contact.profilePicUrl": message.ticket.contact.profilePicUrl,
|
"contact.profilePicUrl": message.ticket.contact.profilePicUrl,
|
||||||
unreadMessages: message.ticket.unreadMessages
|
unreadMessages: message.ticket.unreadMessages
|
||||||
});
|
});
|
||||||
|
|
||||||
const io = getIO();
|
const io = getIO();
|
||||||
io.to(message.ticketId.toString())
|
io.to(message.ticketId.toString())
|
||||||
|
|
|
@ -15,17 +15,14 @@ interface Request {
|
||||||
startDate: string | number;
|
startDate: string | number;
|
||||||
endDate: string;
|
endDate: string;
|
||||||
queue?: boolean;
|
queue?: boolean;
|
||||||
isRemote?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReportByNumberQueueService = async ({
|
const ReportByNumberQueueService = async ({
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
queue = false,
|
queue = false
|
||||||
isRemote = false
|
|
||||||
}: Request): Promise<any[]> => {
|
}: Request): Promise<any[]> => {
|
||||||
let reportServiceData: any[] = [];
|
let reportServiceData: any[] = [];
|
||||||
const includeIsRemote = isRemote ? "t.isRemote = true AND" : "";
|
|
||||||
|
|
||||||
const whatsapps = await Whatsapp.findAll();
|
const whatsapps = await Whatsapp.findAll();
|
||||||
|
|
||||||
|
@ -33,11 +30,6 @@ const ReportByNumberQueueService = async ({
|
||||||
for (const whatsapp of whatsapps) {
|
for (const whatsapp of whatsapps) {
|
||||||
const { id, name, number } = whatsapp;
|
const { id, name, number } = whatsapp;
|
||||||
|
|
||||||
let startedByClient: any;
|
|
||||||
let avgChatWaitingTime: any;
|
|
||||||
let pendingChat: any;
|
|
||||||
let closedChat: any;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!number ||
|
!number ||
|
||||||
reportServiceData.findIndex((w: any) => w?.number == number) != -1
|
reportServiceData.findIndex((w: any) => w?.number == number) != -1
|
||||||
|
@ -53,17 +45,16 @@ const ReportByNumberQueueService = async ({
|
||||||
JOIN Messages m ON t.id = m.ticketId
|
JOIN Messages m ON t.id = m.ticketId
|
||||||
JOIN Whatsapps w ON t.whatsappId = w.id
|
JOIN Whatsapps w ON t.whatsappId = w.id
|
||||||
JOIN Queues q ON q.id = t.queueId
|
JOIN Queues q ON q.id = t.queueId
|
||||||
WHERE ${includeIsRemote} DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
||||||
AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id)
|
AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id)
|
||||||
AND m.fromAgent = 1
|
AND m.fromAgent = 1
|
||||||
AND w.number = ${number};`,
|
AND w.number = ${number};`,
|
||||||
{ type: QueryTypes.SELECT }
|
{ type: QueryTypes.SELECT }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isRemote) {
|
// CHAT STARTED BY CLIENT
|
||||||
// CHAT STARTED BY CLIENT
|
const startedByClient: any = await sequelize.query(
|
||||||
startedByClient = await sequelize.query(
|
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
||||||
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
|
||||||
FROM Tickets t
|
FROM Tickets t
|
||||||
JOIN Messages m ON t.id = m.ticketId
|
JOIN Messages m ON t.id = m.ticketId
|
||||||
JOIN Whatsapps w ON t.whatsappId = w.id
|
JOIN Whatsapps w ON t.whatsappId = w.id
|
||||||
|
@ -72,28 +63,12 @@ const ReportByNumberQueueService = async ({
|
||||||
AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id)
|
AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id)
|
||||||
AND m.fromMe = 0
|
AND m.fromMe = 0
|
||||||
AND w.number = ${number};`,
|
AND w.number = ${number};`,
|
||||||
{ type: QueryTypes.SELECT }
|
{ type: QueryTypes.SELECT }
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
// CHAT RESPONSE BY CLIENT
|
|
||||||
startedByClient = await sequelize.query(
|
|
||||||
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
|
||||||
FROM Tickets t
|
|
||||||
JOIN Messages m ON t.id = m.ticketId
|
|
||||||
JOIN Whatsapps w ON t.whatsappId = w.id
|
|
||||||
JOIN Queues q ON q.id = t.queueId
|
|
||||||
WHERE ${includeIsRemote} DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
|
||||||
AND m.createdAt = (SELECT MAX(createdAt) FROM Messages WHERE ticketId = t.id)
|
|
||||||
AND m.fromMe = 0
|
|
||||||
AND w.number = ${number};`,
|
|
||||||
{ type: QueryTypes.SELECT }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isRemote) {
|
// CHAT CLOSED
|
||||||
// CHAT CLOSED
|
const closedChat: any = await sequelize.query(
|
||||||
closedChat = await sequelize.query(
|
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
||||||
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
|
||||||
FROM Tickets t
|
FROM Tickets t
|
||||||
JOIN Messages m ON t.id = m.ticketId
|
JOIN Messages m ON t.id = m.ticketId
|
||||||
JOIN Whatsapps w ON t.whatsappId = w.id
|
JOIN Whatsapps w ON t.whatsappId = w.id
|
||||||
|
@ -101,26 +76,12 @@ const ReportByNumberQueueService = async ({
|
||||||
WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
||||||
AND t.status = 'closed'
|
AND t.status = 'closed'
|
||||||
AND w.number = ${number};`,
|
AND w.number = ${number};`,
|
||||||
{ type: QueryTypes.SELECT }
|
{ type: QueryTypes.SELECT }
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
// CHAT CLOSED
|
|
||||||
closedChat = await sequelize.query(
|
|
||||||
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
|
||||||
FROM Tickets t
|
|
||||||
JOIN Whatsapps w ON t.whatsappId = w.id
|
|
||||||
JOIN Queues q ON q.id = t.queueId
|
|
||||||
WHERE ${includeIsRemote} DATE(t.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
|
||||||
AND t.status = 'closed'
|
|
||||||
AND w.number = ${number};`,
|
|
||||||
{ type: QueryTypes.SELECT }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isRemote) {
|
// CHAT WAINTING TIME
|
||||||
// CHAT WAINTING TIME
|
const avgChatWaitingTime: any = await sequelize.query(
|
||||||
avgChatWaitingTime = await sequelize.query(
|
`
|
||||||
`
|
|
||||||
SELECT TIME_FORMAT(
|
SELECT TIME_FORMAT(
|
||||||
SEC_TO_TIME(
|
SEC_TO_TIME(
|
||||||
TIMESTAMPDIFF(
|
TIMESTAMPDIFF(
|
||||||
|
@ -143,27 +104,25 @@ const ReportByNumberQueueService = async ({
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
), '%H:%i:%s') AS WAITING_TIME
|
), '%H:%i:%s') AS WAITING_TIME
|
||||||
FROM Tickets t
|
FROM Tickets t
|
||||||
JOIN Messages m ON t.id = m.ticketId
|
JOIN Messages m ON t.id = m.ticketId
|
||||||
JOIN Whatsapps w ON t.whatsappId = w.id
|
JOIN Whatsapps w ON t.whatsappId = w.id
|
||||||
JOIN Queues q ON q.id = t.queueId
|
JOIN Queues q ON q.id = t.queueId
|
||||||
WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
||||||
AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id)
|
AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id)
|
||||||
AND m.fromMe = 0
|
AND m.fromMe = 0
|
||||||
-- AND q.id = 2
|
-- AND q.id = 2
|
||||||
AND w.number = ${number}
|
AND w.number = ${number}
|
||||||
AND t.status IN ('open', 'closed')
|
AND t.status IN ('open', 'closed')
|
||||||
HAVING WAITING_TIME IS NOT NULL
|
HAVING WAITING_TIME IS NOT NULL
|
||||||
ORDER BY
|
ORDER BY
|
||||||
WAITING_TIME;`,
|
WAITING_TIME;`,
|
||||||
{ type: QueryTypes.SELECT }
|
{ type: QueryTypes.SELECT }
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (!isRemote) {
|
// CHAT PENDING
|
||||||
// CHAT PENDING
|
const pendingChat: any = await sequelize.query(
|
||||||
pendingChat = await sequelize.query(
|
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
||||||
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
|
||||||
FROM Tickets t
|
FROM Tickets t
|
||||||
JOIN Messages m ON t.id = m.ticketId
|
JOIN Messages m ON t.id = m.ticketId
|
||||||
JOIN Whatsapps w ON t.whatsappId = w.id
|
JOIN Whatsapps w ON t.whatsappId = w.id
|
||||||
|
@ -172,41 +131,23 @@ const ReportByNumberQueueService = async ({
|
||||||
AND t.status = 'pending'
|
AND t.status = 'pending'
|
||||||
AND w.number = ${number};`,
|
AND w.number = ${number};`,
|
||||||
|
|
||||||
{ type: QueryTypes.SELECT }
|
{ type: QueryTypes.SELECT }
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
// CHAT PENDING REMOTE
|
|
||||||
pendingChat = await sequelize.query(
|
|
||||||
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
|
||||||
FROM Tickets t
|
|
||||||
JOIN Whatsapps w ON t.whatsappId = w.id
|
|
||||||
JOIN Queues q ON q.id = t.queueId
|
|
||||||
WHERE ${includeIsRemote} DATE(t.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
|
||||||
AND t.status = 'pending'
|
|
||||||
AND w.number = ${number};`,
|
|
||||||
|
|
||||||
{ type: QueryTypes.SELECT }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
reportServiceData.push({
|
reportServiceData.push({
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
number,
|
number,
|
||||||
startedByAgent: startedByAgent[0]?.ticket_count,
|
startedByAgent: startedByAgent[0]?.ticket_count,
|
||||||
startedByClient: startedByClient ? startedByClient[0]?.ticket_count : 0,
|
startedByClient: startedByClient[0]?.ticket_count,
|
||||||
closedChat: closedChat[0]?.ticket_count,
|
closedChat: closedChat[0]?.ticket_count,
|
||||||
avgChatWaitingTime: avgChatWaitingTime ? avg(avgChatWaitingTime) : 0,
|
avgChatWaitingTime: avg(avgChatWaitingTime),
|
||||||
pendingChat: pendingChat[0]?.ticket_count
|
pendingChat: pendingChat[0]?.ticket_count
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const whatsapp of whatsapps) {
|
for (const whatsapp of whatsapps) {
|
||||||
const { id, name, number } = whatsapp;
|
const { id, name, number } = whatsapp;
|
||||||
let startedByClient: any;
|
|
||||||
let avgChatWaitingTime: any;
|
|
||||||
let pendingChat: any;
|
|
||||||
let closedChat: any;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!number ||
|
!number ||
|
||||||
|
@ -231,17 +172,16 @@ const ReportByNumberQueueService = async ({
|
||||||
JOIN Messages m ON t.id = m.ticketId
|
JOIN Messages m ON t.id = m.ticketId
|
||||||
JOIN Whatsapps w ON t.whatsappId = w.id
|
JOIN Whatsapps w ON t.whatsappId = w.id
|
||||||
JOIN Queues q ON q.id = t.queueId
|
JOIN Queues q ON q.id = t.queueId
|
||||||
WHERE ${includeIsRemote} DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
||||||
AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id)
|
AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id)
|
||||||
AND m.fromAgent = 1
|
AND m.fromAgent = 1
|
||||||
AND q.id = ${q.id};`,
|
AND q.id = ${q.id};`,
|
||||||
{ type: QueryTypes.SELECT }
|
{ type: QueryTypes.SELECT }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isRemote) {
|
// CHAT STARTED BY CLIENT
|
||||||
// CHAT STARTED BY CLIENT
|
const startedByClient: any = await sequelize.query(
|
||||||
startedByClient = await sequelize.query(
|
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
||||||
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
|
||||||
FROM Tickets t
|
FROM Tickets t
|
||||||
JOIN Messages m ON t.id = m.ticketId
|
JOIN Messages m ON t.id = m.ticketId
|
||||||
JOIN Whatsapps w ON t.whatsappId = w.id
|
JOIN Whatsapps w ON t.whatsappId = w.id
|
||||||
|
@ -250,97 +190,64 @@ const ReportByNumberQueueService = async ({
|
||||||
AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id)
|
AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id)
|
||||||
AND m.fromMe = 0
|
AND m.fromMe = 0
|
||||||
AND q.id = ${q.id};`,
|
AND q.id = ${q.id};`,
|
||||||
{ type: QueryTypes.SELECT }
|
{ type: QueryTypes.SELECT }
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
// CHAT RESPONSE BY CLIENT
|
// CHAT CLOSED
|
||||||
startedByClient = await sequelize.query(
|
const closedChat: any = await sequelize.query(
|
||||||
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
||||||
FROM Tickets t
|
FROM Tickets t
|
||||||
JOIN Messages m ON t.id = m.ticketId
|
JOIN Messages m ON t.id = m.ticketId
|
||||||
JOIN Whatsapps w ON t.whatsappId = w.id
|
JOIN Whatsapps w ON t.whatsappId = w.id
|
||||||
JOIN Queues q ON q.id = t.queueId
|
JOIN Queues q ON q.id = t.queueId
|
||||||
WHERE ${includeIsRemote} DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
||||||
AND m.createdAt = (SELECT MAX(createdAt) FROM Messages WHERE ticketId = t.id)
|
AND t.status = 'closed'
|
||||||
AND m.fromMe = 0
|
AND q.id = ${q.id};`,
|
||||||
AND q.id = ${q.id};`,
|
{ type: QueryTypes.SELECT }
|
||||||
{ type: QueryTypes.SELECT }
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isRemote) {
|
// CHAT WAINTING TIME
|
||||||
// CHAT CLOSED
|
const avgChatWaitingTime: any = await sequelize.query(
|
||||||
closedChat = await sequelize.query(
|
`SELECT TIME_FORMAT(
|
||||||
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
SEC_TO_TIME(
|
||||||
FROM Tickets t
|
TIMESTAMPDIFF(
|
||||||
JOIN Messages m ON t.id = m.ticketId
|
SECOND,
|
||||||
JOIN Whatsapps w ON t.whatsappId = w.id
|
(
|
||||||
JOIN Queues q ON q.id = t.queueId
|
SELECT createdAt
|
||||||
WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
FROM Messages
|
||||||
AND t.status = 'closed'
|
WHERE ticketId = m.ticketId
|
||||||
AND q.id = ${q.id};`,
|
AND fromMe = 0
|
||||||
{ type: QueryTypes.SELECT }
|
ORDER BY createdAt ASC
|
||||||
);
|
LIMIT 1
|
||||||
}
|
),
|
||||||
else{
|
(
|
||||||
// CHAT CLOSED REMOTE
|
SELECT createdAt
|
||||||
closedChat = await sequelize.query(
|
FROM Messages
|
||||||
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
WHERE ticketId = m.ticketId
|
||||||
FROM Tickets t
|
AND fromAgent = 1
|
||||||
JOIN Whatsapps w ON t.whatsappId = w.id
|
ORDER BY createdAt ASC
|
||||||
JOIN Queues q ON q.id = t.queueId
|
LIMIT 1
|
||||||
WHERE ${includeIsRemote} DATE(t.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
)
|
||||||
AND t.status = 'closed'
|
)
|
||||||
AND q.id = ${q.id};`,
|
), '%H:%i:%s') AS WAITING_TIME
|
||||||
{ type: QueryTypes.SELECT }
|
FROM Tickets t
|
||||||
);
|
JOIN Messages m ON t.id = m.ticketId
|
||||||
}
|
JOIN Whatsapps w ON t.whatsappId = w.id
|
||||||
|
JOIN Queues q ON q.id = t.queueId
|
||||||
|
WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
||||||
|
AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id)
|
||||||
|
AND m.fromMe = 0
|
||||||
|
AND q.id = ${q.id}
|
||||||
|
AND t.status IN ('open', 'closed')
|
||||||
|
HAVING WAITING_TIME IS NOT NULL
|
||||||
|
ORDER BY
|
||||||
|
WAITING_TIME;`,
|
||||||
|
{ type: QueryTypes.SELECT }
|
||||||
|
);
|
||||||
|
|
||||||
if (!isRemote) {
|
// CHAT PENDING
|
||||||
// CHAT WAINTING TIME
|
const pendingChat: any = await sequelize.query(
|
||||||
avgChatWaitingTime = await sequelize.query(
|
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
||||||
`SELECT TIME_FORMAT(
|
|
||||||
SEC_TO_TIME(
|
|
||||||
TIMESTAMPDIFF(
|
|
||||||
SECOND,
|
|
||||||
(
|
|
||||||
SELECT createdAt
|
|
||||||
FROM Messages
|
|
||||||
WHERE ticketId = m.ticketId
|
|
||||||
AND fromMe = 0
|
|
||||||
ORDER BY createdAt ASC
|
|
||||||
LIMIT 1
|
|
||||||
),
|
|
||||||
(
|
|
||||||
SELECT createdAt
|
|
||||||
FROM Messages
|
|
||||||
WHERE ticketId = m.ticketId
|
|
||||||
AND fromAgent = 1
|
|
||||||
ORDER BY createdAt ASC
|
|
||||||
LIMIT 1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
), '%H:%i:%s') AS WAITING_TIME
|
|
||||||
FROM Tickets t
|
|
||||||
JOIN Messages m ON t.id = m.ticketId
|
|
||||||
JOIN Whatsapps w ON t.whatsappId = w.id
|
|
||||||
JOIN Queues q ON q.id = t.queueId
|
|
||||||
WHERE DATE(m.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
|
||||||
AND m.createdAt = (SELECT MIN(createdAt) FROM Messages WHERE ticketId = t.id)
|
|
||||||
AND m.fromMe = 0
|
|
||||||
AND q.id = ${q.id}
|
|
||||||
AND t.status IN ('open', 'closed')
|
|
||||||
HAVING WAITING_TIME IS NOT NULL
|
|
||||||
ORDER BY
|
|
||||||
WAITING_TIME;`,
|
|
||||||
{ type: QueryTypes.SELECT }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isRemote) {
|
|
||||||
// CHAT PENDING
|
|
||||||
pendingChat = await sequelize.query(
|
|
||||||
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
|
||||||
FROM Tickets t
|
FROM Tickets t
|
||||||
JOIN Messages m ON t.id = m.ticketId
|
JOIN Messages m ON t.id = m.ticketId
|
||||||
JOIN Whatsapps w ON t.whatsappId = w.id
|
JOIN Whatsapps w ON t.whatsappId = w.id
|
||||||
|
@ -349,22 +256,8 @@ const ReportByNumberQueueService = async ({
|
||||||
AND t.status = 'pending'
|
AND t.status = 'pending'
|
||||||
AND q.id = ${q.id};`,
|
AND q.id = ${q.id};`,
|
||||||
|
|
||||||
{ type: QueryTypes.SELECT }
|
{ type: QueryTypes.SELECT }
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
// CHAT PENDING REMOTE
|
|
||||||
pendingChat = await sequelize.query(
|
|
||||||
`SELECT COUNT(DISTINCT t.id) AS ticket_count
|
|
||||||
FROM Tickets t
|
|
||||||
JOIN Whatsapps w ON t.whatsappId = w.id
|
|
||||||
JOIN Queues q ON q.id = t.queueId
|
|
||||||
WHERE ${includeIsRemote} DATE(t.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999'
|
|
||||||
AND t.status = 'pending'
|
|
||||||
AND q.id = ${q.id};`,
|
|
||||||
|
|
||||||
{ type: QueryTypes.SELECT }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
reportServiceData.push({
|
reportServiceData.push({
|
||||||
id,
|
id,
|
||||||
|
@ -373,11 +266,9 @@ const ReportByNumberQueueService = async ({
|
||||||
queueName: q.name,
|
queueName: q.name,
|
||||||
queueColor: q.color,
|
queueColor: q.color,
|
||||||
startedByAgent: startedByAgent[0]?.ticket_count,
|
startedByAgent: startedByAgent[0]?.ticket_count,
|
||||||
startedByClient: startedByClient
|
startedByClient: startedByClient[0]?.ticket_count,
|
||||||
? startedByClient[0]?.ticket_count
|
|
||||||
: 0,
|
|
||||||
closedChat: closedChat[0]?.ticket_count,
|
closedChat: closedChat[0]?.ticket_count,
|
||||||
avgChatWaitingTime: avgChatWaitingTime ? avg(avgChatWaitingTime) : 0,
|
avgChatWaitingTime: avg(avgChatWaitingTime),
|
||||||
pendingChat: pendingChat[0]?.ticket_count
|
pendingChat: pendingChat[0]?.ticket_count
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -392,23 +283,23 @@ export default ReportByNumberQueueService;
|
||||||
function avg(avgChatWaitingTime: any) {
|
function avg(avgChatWaitingTime: any) {
|
||||||
let waitingAVG: any = avgChatWaitingTime
|
let waitingAVG: any = avgChatWaitingTime
|
||||||
.filter((t: any) => t?.WAITING_TIME)
|
.filter((t: any) => t?.WAITING_TIME)
|
||||||
.map((t: any) => t.WAITING_TIME);
|
.map((t: any) => t.WAITING_TIME)
|
||||||
|
|
||||||
if (waitingAVG.length > 0) {
|
if (waitingAVG.length > 0) {
|
||||||
let midIndex = Math.floor((0 + waitingAVG.length) / 2);
|
let midIndex = Math.floor((0 + waitingAVG.length) / 2)
|
||||||
|
|
||||||
if (waitingAVG.length % 2 == 1) {
|
if (waitingAVG.length % 2 == 1) {
|
||||||
waitingAVG = waitingAVG[midIndex];
|
waitingAVG = waitingAVG[midIndex]
|
||||||
} else {
|
} else {
|
||||||
waitingAVG = calculateAverageTime(
|
waitingAVG = calculateAverageTime(
|
||||||
waitingAVG[midIndex - 1],
|
waitingAVG[midIndex - 1],
|
||||||
waitingAVG[midIndex]
|
waitingAVG[midIndex]
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
waitingAVG = 0;
|
waitingAVG = 0
|
||||||
}
|
}
|
||||||
return waitingAVG;
|
return waitingAVG
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateAverageTime(time1: string, time2: string) {
|
function calculateAverageTime(time1: string, time2: string) {
|
||||||
|
|
|
@ -4,15 +4,12 @@ import Setting from "../../models/Setting";
|
||||||
interface Request {
|
interface Request {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
obj?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const UpdateSettingService = async ({
|
const UpdateSettingService = async ({
|
||||||
key,
|
key,
|
||||||
value,
|
value
|
||||||
obj
|
|
||||||
}: Request): Promise<Setting | undefined> => {
|
}: Request): Promise<Setting | undefined> => {
|
||||||
console.log("key: ", key, " | value: ", value, " | obj: ", obj);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const setting = await Setting.findOne({
|
const setting = await Setting.findOne({
|
||||||
|
@ -23,16 +20,12 @@ const UpdateSettingService = async ({
|
||||||
throw new AppError("ERR_NO_SETTING_FOUND", 404);
|
throw new AppError("ERR_NO_SETTING_FOUND", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (obj) {
|
await setting.update({ value });
|
||||||
obj = JSON.stringify(obj);
|
|
||||||
}
|
|
||||||
await setting.update({ value, obj });
|
|
||||||
|
|
||||||
await setting.reload();
|
|
||||||
|
|
||||||
return setting;
|
return setting;
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("===> Error on UpdateSettingService.ts file: \n", error);
|
console.error('===> Error on UpdateSettingService.ts file: \n', error)
|
||||||
throw new AppError(error.message);
|
throw new AppError(error.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,7 +20,6 @@ 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";
|
import User from "../../models/User";
|
||||||
import { get } from "../../helpers/RedisClient";
|
|
||||||
|
|
||||||
interface Request {
|
interface Request {
|
||||||
searchParam?: string;
|
searchParam?: string;
|
||||||
|
@ -39,7 +38,6 @@ interface Response {
|
||||||
tickets: Ticket[];
|
tickets: Ticket[];
|
||||||
count: number;
|
count: number;
|
||||||
hasMore: boolean;
|
hasMore: boolean;
|
||||||
remoteTicketsControll?: object[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListTicketsService = async ({
|
const ListTicketsService = async ({
|
||||||
|
@ -232,7 +230,7 @@ const ListTicketsService = async ({
|
||||||
|
|
||||||
console.log("kkkkkkkkk limit: ", limit);
|
console.log("kkkkkkkkk limit: ", limit);
|
||||||
|
|
||||||
let { count, rows: tickets } = await Ticket.findAndCountAll({
|
const { count, rows: tickets } = await Ticket.findAndCountAll({
|
||||||
where: whereCondition,
|
where: whereCondition,
|
||||||
include: includeCondition,
|
include: includeCondition,
|
||||||
distinct: true,
|
distinct: true,
|
||||||
|
@ -243,13 +241,10 @@ const ListTicketsService = async ({
|
||||||
|
|
||||||
const hasMore = count > offset + tickets.length;
|
const hasMore = count > offset + tickets.length;
|
||||||
|
|
||||||
const ticketIds = await get({ key: `remote:controll`, parse: true });
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tickets,
|
tickets,
|
||||||
count,
|
count,
|
||||||
hasMore,
|
hasMore
|
||||||
remoteTicketsControll: ticketIds ? ticketIds : []
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { createOrUpdateTicketCache } from "../../helpers/TicketCache";
|
||||||
import AppError from "../../errors/AppError";
|
import AppError from "../../errors/AppError";
|
||||||
import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket";
|
import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket";
|
||||||
import BotIsOnQueue from "../../helpers/BotIsOnQueue";
|
import BotIsOnQueue from "../../helpers/BotIsOnQueue";
|
||||||
import { del, deleteObject, get, set } from "../../helpers/RedisClient";
|
import { deleteObject } from "../../helpers/RedisClient";
|
||||||
var flatten = require("flat");
|
var flatten = require("flat");
|
||||||
|
|
||||||
interface TicketData {
|
interface TicketData {
|
||||||
|
@ -71,23 +71,6 @@ const UpdateTicketService = async ({
|
||||||
await CheckContactOpenTickets(ticket.contact.id, ticket.whatsappId);
|
await CheckContactOpenTickets(ticket.contact.id, ticket.whatsappId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == "closed") {
|
|
||||||
del(`remote:ticketId:${ticket.id}`);
|
|
||||||
|
|
||||||
let ticketsIds = await get({ key: `remote:controll`, parse: true });
|
|
||||||
const index = ticketsIds.findIndex((t: any) => t == ticket.id);
|
|
||||||
|
|
||||||
console.log("ticketsIds 1: ", ticketsIds);
|
|
||||||
|
|
||||||
if (index != -1) {
|
|
||||||
ticketsIds.splice(index, 1);
|
|
||||||
|
|
||||||
console.log("ticketsIds 2: ", ticketsIds);
|
|
||||||
|
|
||||||
set(`remote:controll`, JSON.stringify(ticketsIds));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await ticket.update({
|
await ticket.update({
|
||||||
status,
|
status,
|
||||||
queueId,
|
queueId,
|
||||||
|
|
|
@ -94,8 +94,7 @@ import {
|
||||||
findByContain,
|
findByContain,
|
||||||
findObject,
|
findObject,
|
||||||
get,
|
get,
|
||||||
getSimple,
|
getSimple
|
||||||
set
|
|
||||||
} from "../../helpers/RedisClient";
|
} from "../../helpers/RedisClient";
|
||||||
import FindOrCreateTicketServiceBot from "../TicketServices/FindOrCreateTicketServiceBot";
|
import FindOrCreateTicketServiceBot from "../TicketServices/FindOrCreateTicketServiceBot";
|
||||||
import ShowTicketService from "../TicketServices/ShowTicketService";
|
import ShowTicketService from "../TicketServices/ShowTicketService";
|
||||||
|
@ -170,7 +169,7 @@ const verifyMediaMessage = async (
|
||||||
if (!media) {
|
if (!media) {
|
||||||
throw new Error("ERR_WAPP_DOWNLOAD_MEDIA");
|
throw new Error("ERR_WAPP_DOWNLOAD_MEDIA");
|
||||||
}
|
}
|
||||||
|
let mediaAuthorized = true;
|
||||||
let messageData = {
|
let messageData = {
|
||||||
id: msg.id.id,
|
id: msg.id.id,
|
||||||
ticketId: ticket.id,
|
ticketId: ticket.id,
|
||||||
|
@ -184,7 +183,9 @@ const verifyMediaMessage = async (
|
||||||
phoneNumberId: msg?.phoneNumberId,
|
phoneNumberId: msg?.phoneNumberId,
|
||||||
fromAgent: false
|
fromAgent: false
|
||||||
};
|
};
|
||||||
|
if(messageData.mediaType === 'video' || messageData.mediaType === 'audio' && getSettingValue('blockAudioVideoMedia')?.value === 'enabled'){
|
||||||
|
mediaAuthorized = false;
|
||||||
|
}
|
||||||
if (msg?.fromMe) {
|
if (msg?.fromMe) {
|
||||||
messageData = { ...messageData, fromAgent: true };
|
messageData = { ...messageData, fromAgent: true };
|
||||||
}
|
}
|
||||||
|
@ -199,23 +200,36 @@ const verifyMediaMessage = async (
|
||||||
body: media.filename
|
body: media.filename
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if(mediaAuthorized){
|
||||||
try {
|
try {
|
||||||
await writeFileAsync(
|
await writeFileAsync(
|
||||||
join(__dirname, "..", "..", "..", "..", "..", "public", media.filename),
|
join(__dirname, "..", "..", "..", "..", "..", "public", media.filename),
|
||||||
media.data,
|
media.data,
|
||||||
"base64"
|
"base64"
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
logger.error(`There was an error: wbotMessageLitener.ts: ${err}`);
|
logger.error(`There was an error: wbotMessageLitener.ts: ${err}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(mediaAuthorized){
|
||||||
await ticket.update({ lastMessage: msg.body || media.filename });
|
await ticket.update({ lastMessage: msg.body || media.filename });
|
||||||
const newMessage = await CreateMessageService({ messageData });
|
const newMessage = await CreateMessageService({ messageData });
|
||||||
|
return newMessage;
|
||||||
return newMessage;
|
}else{
|
||||||
|
if (ticket.status !== "queueChoice") {
|
||||||
|
botSendMessage(
|
||||||
|
ticket,
|
||||||
|
`Atenção! Mensagem ignorada, tipo de mídia não suportado.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
messageData.body = `Mensagem de *${messageData.mediaType}* ignorada, tipo de mídia não suportado.`;
|
||||||
|
messageData.mediaUrl = '';
|
||||||
|
await ticket.update({ lastMessage: `Mensagem de *${messageData.mediaType}* ignorada, tipo de mídia não suportado.`});
|
||||||
|
const newMessage = await CreateMessageService({ messageData });
|
||||||
|
console.log(`--------->>> Mensagem do tipo: ${messageData.mediaType}, ignorada!`)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// const verifyMediaMessage = async (
|
// const verifyMediaMessage = async (
|
||||||
|
@ -326,7 +340,7 @@ const verifyMessage = async (
|
||||||
} else {
|
} else {
|
||||||
messageData = { ...messageData, body: JSON.stringify(msg.vCards) };
|
messageData = { ...messageData, body: JSON.stringify(msg.vCards) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await CreateMessageService({ messageData });
|
await CreateMessageService({ messageData });
|
||||||
};
|
};
|
||||||
|
@ -358,7 +372,7 @@ const verifyQueue = async (
|
||||||
//////////////// EXTRAIR APENAS O NÚMERO ///////////////////
|
//////////////// EXTRAIR APENAS O NÚMERO ///////////////////
|
||||||
selectedOption = selectedOption.match(/\d+/);
|
selectedOption = selectedOption.match(/\d+/);
|
||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
|
|
||||||
if (selectedOption) choosenQueue = queues[+selectedOption - 1];
|
if (selectedOption) choosenQueue = queues[+selectedOption - 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -413,13 +427,15 @@ const verifyQueue = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = "";
|
let body = "";
|
||||||
|
const io = getIO();
|
||||||
if (botOptions.length > 0) {
|
if (botOptions.length > 0) {
|
||||||
body = `\u200e${choosenQueue.greetingMessage}\n\n${botOptions}\n${final_message.msg}`;
|
body = `\u200e${choosenQueue.greetingMessage}\n\n${botOptions}\n${final_message.msg}`;
|
||||||
} else {
|
} else {
|
||||||
body = `\u200e${choosenQueue.greetingMessage}`;
|
body = `\u200e${choosenQueue.greetingMessage}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
io.emit('notifyPeding', {data: {ticket, queue: choosenQueue}});
|
||||||
|
|
||||||
sendWhatsAppMessageSocket(ticket, body);
|
sendWhatsAppMessageSocket(ticket, body);
|
||||||
} else {
|
} else {
|
||||||
//test del transfere o atendimento se entrar na ura infinita
|
//test del transfere o atendimento se entrar na ura infinita
|
||||||
|
@ -762,11 +778,14 @@ const handleMessage = async (
|
||||||
unreadMessages
|
unreadMessages
|
||||||
// groupContact
|
// groupContact
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getSettingValue("oneContactChatWithManyWhats")?.value == "disabled") {
|
if (getSettingValue("oneContactChatWithManyWhats")?.value == "disabled") {
|
||||||
// Para responder para o cliente pelo mesmo whatsapp que ele enviou a mensagen
|
// Para responder para o cliente pelo mesmo whatsapp que ele enviou a mensagen
|
||||||
if (wbot.id != ticket.whatsappId) {
|
if (wbot.id != ticket.whatsappId) {
|
||||||
|
// console.log('PARA RESPONDER PELO MEMOS WHATSAPP wbot.id: ', wbot.id, ' | wbot.status: ', wbot.status)
|
||||||
|
// console.log('WHATSAPP STATUS ticket.whatsappId: ', ticket.whatsappId)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ticket.update({ whatsappId: wbot.id });
|
await ticket.update({ whatsappId: wbot.id });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
|
@ -43,7 +43,6 @@ import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketH
|
||||||
import ModalTemplate from "../ModalTemplate"
|
import ModalTemplate from "../ModalTemplate"
|
||||||
|
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
import { countTicketMsgContext } from "../../context/CountTicketMsgProvider/CountTicketMsgProvider"
|
|
||||||
|
|
||||||
const Mp3Recorder = new MicRecorder({ bitRate: 128 })
|
const Mp3Recorder = new MicRecorder({ bitRate: 128 })
|
||||||
|
|
||||||
|
@ -209,12 +208,10 @@ const useStyles = makeStyles((theme) => ({
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const MessageInput = ({ ticketStatus, ticketLastMessage, ticketIsRemote }) => {
|
const MessageInput = ({ ticketStatus }) => {
|
||||||
|
|
||||||
const { tabOption, setTabOption } = useContext(TabTicketContext)
|
const { tabOption, setTabOption } = useContext(TabTicketContext)
|
||||||
|
|
||||||
const { countTicketMsg, setCountTicketMsg } = useContext(countTicketMsgContext)
|
|
||||||
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const { ticketId } = useParams()
|
const { ticketId } = useParams()
|
||||||
|
|
||||||
|
@ -236,21 +233,11 @@ const MessageInput = ({ ticketStatus, ticketLastMessage, ticketIsRemote }) => {
|
||||||
|
|
||||||
const isRun = useRef(false)
|
const isRun = useRef(false)
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
inputRef.current.focus()
|
inputRef.current.focus()
|
||||||
}, [replyingMessage])
|
}, [replyingMessage])
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
|
|
||||||
if (ticketIsRemote && countTicketMsg === 0 && ticketLastMessage && ticketLastMessage.trim().length > 0) {
|
|
||||||
setInputMessage(ticketLastMessage)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setInputMessage("")
|
|
||||||
}
|
|
||||||
}, [countTicketMsg, ticketIsRemote, ticketLastMessage])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
inputRef.current.focus()
|
inputRef.current.focus()
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -365,8 +352,6 @@ const MessageInput = ({ ticketStatus, ticketLastMessage, ticketIsRemote }) => {
|
||||||
setTemplates(data.data)
|
setTemplates(data.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
setCountTicketMsg(1)
|
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toastError(err)
|
toastError(err)
|
||||||
}
|
}
|
||||||
|
@ -459,7 +444,7 @@ const MessageInput = ({ ticketStatus, ticketLastMessage, ticketIsRemote }) => {
|
||||||
const { data } = await api.get("/quickAnswers/", {
|
const { data } = await api.get("/quickAnswers/", {
|
||||||
params: { searchParam: inputMessage.substring(1), userId: user.id },
|
params: { searchParam: inputMessage.substring(1), userId: user.id },
|
||||||
})
|
})
|
||||||
|
|
||||||
setQuickAnswer(data.quickAnswers)
|
setQuickAnswer(data.quickAnswers)
|
||||||
if (data.quickAnswers.length > 0) {
|
if (data.quickAnswers.length > 0) {
|
||||||
setTypeBar(true)
|
setTypeBar(true)
|
||||||
|
|
|
@ -34,7 +34,6 @@ import whatsBackground from "../../assets/wa-background.png"
|
||||||
|
|
||||||
import api from "../../services/api"
|
import api from "../../services/api"
|
||||||
import toastError from "../../errors/toastError"
|
import toastError from "../../errors/toastError"
|
||||||
import { CountTicketMsgProvider, countTicketMsgContext } from "../../context/CountTicketMsgProvider/CountTicketMsgProvider"
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
messagesListWrapper: {
|
messagesListWrapper: {
|
||||||
|
@ -329,8 +328,6 @@ const MessagesList = ({ ticketId, isGroup }) => {
|
||||||
|
|
||||||
const { user } = useContext(AuthContext)
|
const { user } = useContext(AuthContext)
|
||||||
|
|
||||||
const { setCountTicketMsg } = useContext(countTicketMsgContext)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch({ type: "RESET" })
|
dispatch({ type: "RESET" })
|
||||||
setPageNumber(1)
|
setPageNumber(1)
|
||||||
|
@ -412,11 +409,6 @@ const MessagesList = ({ ticketId, isGroup }) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (currentTicketId.current === ticketId) {
|
if (currentTicketId.current === ticketId) {
|
||||||
|
|
||||||
if (data?.messages) {
|
|
||||||
setCountTicketMsg(data.messages.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch({ type: "LOAD_MESSAGES", payload: data.messages })
|
dispatch({ type: "LOAD_MESSAGES", payload: data.messages })
|
||||||
setHasMore(data.hasMore)
|
setHasMore(data.hasMore)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
|
|
@ -20,6 +20,9 @@ import useTickets from "../../hooks/useTickets"
|
||||||
import alertSound from "../../assets/sound.mp3"
|
import alertSound from "../../assets/sound.mp3"
|
||||||
import { AuthContext } from "../../context/Auth/AuthContext"
|
import { AuthContext } from "../../context/Auth/AuthContext"
|
||||||
|
|
||||||
|
import api from "../../services/api";
|
||||||
|
import toastError from "../../errors/toastError";
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const useStyles = makeStyles(theme => ({
|
||||||
tabContainer: {
|
tabContainer: {
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
|
@ -83,7 +86,7 @@ const NotificationsPopOver = () => {
|
||||||
const historyRef = useRef(history)
|
const historyRef = useRef(history)
|
||||||
|
|
||||||
const { handleLogout } = useContext(AuthContext)
|
const { handleLogout } = useContext(AuthContext)
|
||||||
|
const [settings, setSettings] = useState([]);
|
||||||
// const [lastRef] = useState(+history.location.pathname.split("/")[2])
|
// const [lastRef] = useState(+history.location.pathname.split("/")[2])
|
||||||
|
|
||||||
|
|
||||||
|
@ -110,7 +113,22 @@ const NotificationsPopOver = () => {
|
||||||
ticketIdRef.current = ticketIdUrl
|
ticketIdRef.current = ticketIdUrl
|
||||||
}, [ticketIdUrl])
|
}, [ticketIdUrl])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchSession = async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await api.get('/settings')
|
||||||
|
setSettings(data.settings)
|
||||||
|
} catch (err) {
|
||||||
|
toastError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchSession()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const getSettingValue = (key) => {
|
||||||
|
const { value } = settings.find((s) => s.key === key)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
|
@ -255,49 +273,80 @@ const NotificationsPopOver = () => {
|
||||||
|
|
||||||
if (shouldNotNotificate) return
|
if (shouldNotNotificate) return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
handleNotifications(data)
|
handleNotifications(data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
socket.on('notifyPeding', data =>{
|
||||||
|
if(settings?.length > 0 && getSettingValue('notificationTransferQueue') === 'enabled') handleNotifications("", data);
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
socket.disconnect()
|
socket.disconnect()
|
||||||
}
|
}
|
||||||
}, [user])
|
}, [user, settings])
|
||||||
|
|
||||||
const handleNotifications = data => {
|
const handleNotifications = (data, notify) => {
|
||||||
const { message, contact, ticket } = data
|
let isQueue = false;
|
||||||
|
if(!notify){
|
||||||
|
const { message, contact, ticket } = data
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
body: `${message.body} - ${format(new Date(), "HH:mm")}`,
|
body: `${message.body} - ${format(new Date(), "HH:mm")}`,
|
||||||
icon: contact.profilePicUrl,
|
icon: contact.profilePicUrl,
|
||||||
tag: ticket.id,
|
tag: ticket.id,
|
||||||
renotify: true,
|
renotify: true,
|
||||||
}
|
|
||||||
|
|
||||||
const notification = new Notification(
|
|
||||||
`${i18n.t("tickets.notification.message")} ${contact.name}`,
|
|
||||||
options
|
|
||||||
)
|
|
||||||
|
|
||||||
notification.onclick = e => {
|
|
||||||
e.preventDefault()
|
|
||||||
window.focus()
|
|
||||||
historyRef.current.push(`/tickets/${ticket.id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
setDesktopNotifications(prevState => {
|
|
||||||
const notfiticationIndex = prevState.findIndex(
|
|
||||||
n => n.tag === notification.tag
|
|
||||||
)
|
|
||||||
if (notfiticationIndex !== -1) {
|
|
||||||
prevState[notfiticationIndex] = notification
|
|
||||||
return [...prevState]
|
|
||||||
}
|
}
|
||||||
return [notification, ...prevState]
|
|
||||||
})
|
|
||||||
|
|
||||||
|
const notification = new Notification(
|
||||||
|
`${i18n.t("tickets.notification.message")} ${contact.name}`,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
|
||||||
|
notification.onclick = e => {
|
||||||
|
e.preventDefault()
|
||||||
|
window.focus()
|
||||||
|
historyRef.current.push(`/tickets/${ticket.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
setDesktopNotifications(prevState => {
|
||||||
|
const notfiticationIndex = prevState.findIndex(
|
||||||
|
n => n.tag === notification.tag
|
||||||
|
)
|
||||||
|
if (notfiticationIndex !== -1) {
|
||||||
|
prevState[notfiticationIndex] = notification
|
||||||
|
return [...prevState]
|
||||||
|
}
|
||||||
|
return [notification, ...prevState]
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
user.queues.forEach(queue =>{
|
||||||
|
if(queue.id === notify.data?.queue?.id){
|
||||||
|
isQueue = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if(!isQueue){
|
||||||
|
return;
|
||||||
|
}else {
|
||||||
|
const notification = new Notification(`${i18n.t("tickets.notification.messagePeding")} ${notify.data?.queue?.name}`);
|
||||||
|
notification.onclick = e => {
|
||||||
|
e.preventDefault()
|
||||||
|
window.focus()
|
||||||
|
historyRef.current.push(`/tickets`)
|
||||||
|
}
|
||||||
|
|
||||||
|
setDesktopNotifications(prevState => {
|
||||||
|
const notfiticationIndex = prevState.findIndex(
|
||||||
|
n => n.tag === notification.tag
|
||||||
|
)
|
||||||
|
if (notfiticationIndex !== -1) {
|
||||||
|
prevState[notfiticationIndex] = notification
|
||||||
|
return [...prevState]
|
||||||
|
}
|
||||||
|
return [notification, ...prevState]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
soundAlertRef.current()
|
soundAlertRef.current()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ import MessagesList from "../MessagesList"
|
||||||
import api from "../../services/api"
|
import api from "../../services/api"
|
||||||
import { ReplyMessageProvider } from "../../context/ReplyingMessage/ReplyingMessageContext"
|
import { ReplyMessageProvider } from "../../context/ReplyingMessage/ReplyingMessageContext"
|
||||||
import toastError from "../../errors/toastError"
|
import toastError from "../../errors/toastError"
|
||||||
import { CountTicketMsgProvider } from "../../context/CountTicketMsgProvider/CountTicketMsgProvider"
|
|
||||||
|
|
||||||
const drawerWidth = 320
|
const drawerWidth = 320
|
||||||
|
|
||||||
|
@ -193,21 +192,11 @@ const Ticket = () => {
|
||||||
</div>
|
</div>
|
||||||
</TicketHeader>
|
</TicketHeader>
|
||||||
<ReplyMessageProvider>
|
<ReplyMessageProvider>
|
||||||
|
<MessagesList
|
||||||
<CountTicketMsgProvider>
|
ticketId={ticketId}
|
||||||
|
isGroup={ticket.isGroup}
|
||||||
<MessagesList
|
></MessagesList>
|
||||||
ticketId={ticketId}
|
<MessageInput ticketStatus={ticket.status} />
|
||||||
isGroup={ticket.isGroup}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MessageInput
|
|
||||||
ticketStatus={ticket.status}
|
|
||||||
ticketLastMessage={ticket?.lastMessage}
|
|
||||||
ticketIsRemote={ticket?.isRemote} />
|
|
||||||
|
|
||||||
</CountTicketMsgProvider>
|
|
||||||
|
|
||||||
</ReplyMessageProvider>
|
</ReplyMessageProvider>
|
||||||
</Paper>
|
</Paper>
|
||||||
<ContactDrawer
|
<ContactDrawer
|
||||||
|
|
|
@ -22,7 +22,6 @@ import MarkdownWrapper from "../MarkdownWrapper"
|
||||||
import { Tooltip } from "@material-ui/core"
|
import { Tooltip } from "@material-ui/core"
|
||||||
import { AuthContext } from "../../context/Auth/AuthContext"
|
import { AuthContext } from "../../context/Auth/AuthContext"
|
||||||
import toastError from "../../errors/toastError"
|
import toastError from "../../errors/toastError"
|
||||||
import openSocket from 'socket.io-client'
|
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const useStyles = makeStyles(theme => ({
|
||||||
ticket: {
|
ticket: {
|
||||||
|
@ -102,15 +101,13 @@ const useStyles = makeStyles(theme => ({
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const TicketListItem = ({ ticket, remoteTicketsControll, settings }) => {
|
const TicketListItem = ({ ticket }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const { ticketId } = useParams()
|
const { ticketId } = useParams()
|
||||||
const isMounted = useRef(true)
|
const isMounted = useRef(true)
|
||||||
const { user, getSettingValue } = useContext(AuthContext)
|
const { user } = useContext(AuthContext)
|
||||||
const [_remoteTicketsControll, setRemoteTicketsControll] = useState([])
|
|
||||||
const [_settings, setSettings] = useState(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -118,14 +115,6 @@ const TicketListItem = ({ ticket, remoteTicketsControll, settings }) => {
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSettings(settings)
|
|
||||||
}, [settings])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setRemoteTicketsControll(remoteTicketsControll)
|
|
||||||
}, [remoteTicketsControll, settings])
|
|
||||||
|
|
||||||
const handleAcepptTicket = async id => {
|
const handleAcepptTicket = async id => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
|
@ -142,6 +131,8 @@ const TicketListItem = ({ ticket, remoteTicketsControll, settings }) => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
history.push(`/tickets/${id}`)
|
history.push(`/tickets/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,192 +140,126 @@ const TicketListItem = ({ ticket, remoteTicketsControll, settings }) => {
|
||||||
history.push(`/tickets/${id}`)
|
history.push(`/tickets/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const socket = openSocket(process.env.REACT_APP_BACKEND_URL)
|
|
||||||
|
|
||||||
socket.on('remoteTickesControll', (data) => {
|
|
||||||
console.log('REMOTE TICKETS CONTROLL UPDATE2: ', data.tickets)
|
|
||||||
if (data.action === 'update') {
|
|
||||||
setRemoteTicketsControll(data.tickets)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
socket.on('settings', (data) => {
|
|
||||||
if (data.action === 'update') {
|
|
||||||
setSettings((prevState) => {
|
|
||||||
const aux = [...prevState]
|
|
||||||
const settingIndex = aux.findIndex((s) => s.key === data.setting.key)
|
|
||||||
aux[settingIndex].value = data.setting.value
|
|
||||||
return aux
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
socket.disconnect()
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={ticket.id}>
|
<React.Fragment key={ticket.id}>
|
||||||
<Tooltip
|
<ListItem
|
||||||
arrow
|
dense
|
||||||
placement="right"
|
button
|
||||||
title={
|
onClick={e => {
|
||||||
(ticket?.isRemote && ticket?.remoteDone && ticket.status === 'pending') ? "Mensagem de campanha enviada" : (ticket?.isRemote && ticket.status === 'pending') ? "Mensagem de campanha ainda não enviada" :
|
if (ticket.status === "pending") return
|
||||||
""
|
handleSelectTicket(ticket.id)
|
||||||
}
|
}}
|
||||||
|
selected={ticketId && +ticketId === ticket.id}
|
||||||
|
className={clsx(classes.ticket, {
|
||||||
|
[classes.pendingTicket]: ticket.status === "pending",
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<ListItem
|
<Tooltip
|
||||||
dense
|
arrow
|
||||||
button
|
placement="right"
|
||||||
onClick={e => {
|
title={ticket.queue?.name || "Sem fila"}
|
||||||
if (ticket.status === "pending") return
|
|
||||||
handleSelectTicket(ticket.id)
|
|
||||||
}}
|
|
||||||
selected={ticketId && +ticketId === ticket.id}
|
|
||||||
className={clsx(classes.ticket, {
|
|
||||||
[classes.pendingTicket]: ticket.status === "pending",
|
|
||||||
})}
|
|
||||||
|
|
||||||
style={((ticket.status === "open" || ticket.status === "closed") && ticket?.isRemote) ? {
|
|
||||||
border: (ticket.status === "open" || ticket.status === "closed") ? "1px solid rgba(121,123,127,0.9)" : "1px solid transparent",
|
|
||||||
} : {}}
|
|
||||||
|
|
||||||
>
|
>
|
||||||
<Tooltip
|
<span
|
||||||
arrow
|
style={{ backgroundColor: ticket.queue?.color || "#7C7C7C" }}
|
||||||
placement="right"
|
className={classes.ticketQueueColor}
|
||||||
title={ticket.queue?.name || "Sem fila"}
|
></span>
|
||||||
>
|
</Tooltip>
|
||||||
<span
|
<ListItemAvatar>
|
||||||
style={{ backgroundColor: ticket.queue?.color || "#7C7C7C" }}
|
<Avatar src={ticket?.contact?.profilePicUrl} />
|
||||||
className={classes.ticketQueueColor}
|
</ListItemAvatar>
|
||||||
></span>
|
<ListItemText
|
||||||
</Tooltip>
|
disableTypography
|
||||||
<ListItemAvatar>
|
primary={
|
||||||
<Avatar src={ticket?.contact?.profilePicUrl} />
|
<span className={classes.contactNameWrapper}>
|
||||||
</ListItemAvatar>
|
<Typography
|
||||||
<ListItemText
|
noWrap
|
||||||
disableTypography
|
component="span"
|
||||||
primary={
|
variant="body2"
|
||||||
<span className={classes.contactNameWrapper}>
|
color="textPrimary"
|
||||||
|
>
|
||||||
|
{ticket.contact.name}
|
||||||
|
</Typography>
|
||||||
|
{ticket.status === "closed" && (
|
||||||
|
<Badge
|
||||||
|
className={classes.closedBadge}
|
||||||
|
badgeContent={"closed"}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{ticket.lastMessage && (
|
||||||
<Typography
|
<Typography
|
||||||
noWrap
|
className={classes.lastMessageTime}
|
||||||
component="span"
|
|
||||||
variant="body2"
|
|
||||||
color="textPrimary"
|
|
||||||
>
|
|
||||||
{ticket.contact.name}
|
|
||||||
</Typography>
|
|
||||||
{ticket.status === "closed" && (
|
|
||||||
<Badge
|
|
||||||
className={classes.closedBadge}
|
|
||||||
badgeContent={"closed"}
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{ticket.lastMessage && (
|
|
||||||
<Typography
|
|
||||||
className={classes.lastMessageTime}
|
|
||||||
component="span"
|
|
||||||
variant="body2"
|
|
||||||
color="textSecondary"
|
|
||||||
>
|
|
||||||
{ticket?.phoneNumberId && <span style={{ 'fontWeight': 'bold' }}>Oficial</span>}{" "}
|
|
||||||
{isSameDay(parseISO(ticket.updatedAt), new Date()) ? (
|
|
||||||
<>{format(parseISO(ticket.updatedAt), "HH:mm")}</>
|
|
||||||
) : (
|
|
||||||
<>{format(parseISO(ticket.updatedAt), "dd/MM/yyyy")}</>
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
secondary={
|
|
||||||
<span className={classes.contactNameWrapper}>
|
|
||||||
<Typography
|
|
||||||
className={classes.contactLastMessage}
|
|
||||||
noWrap
|
|
||||||
component="span"
|
component="span"
|
||||||
variant="body2"
|
variant="body2"
|
||||||
color="textSecondary"
|
color="textSecondary"
|
||||||
>
|
>
|
||||||
{ticket.lastMessage ? (
|
{ticket?.phoneNumberId && <span style={{ 'fontWeight': 'bold' }}>Oficial</span>}{" "}
|
||||||
<MarkdownWrapper>{ticket.lastMessage}</MarkdownWrapper>
|
{isSameDay(parseISO(ticket.updatedAt), new Date()) ? (
|
||||||
|
<>{format(parseISO(ticket.updatedAt), "HH:mm")}</>
|
||||||
) : (
|
) : (
|
||||||
<br />
|
<>{format(parseISO(ticket.updatedAt), "dd/MM/yyyy")}</>
|
||||||
)}
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
secondary={
|
||||||
|
<span className={classes.contactNameWrapper}>
|
||||||
|
<Typography
|
||||||
|
className={classes.contactLastMessage}
|
||||||
|
noWrap
|
||||||
|
component="span"
|
||||||
|
variant="body2"
|
||||||
|
color="textSecondary"
|
||||||
|
>
|
||||||
|
{ticket.lastMessage ? (
|
||||||
|
<MarkdownWrapper>{ticket.lastMessage}</MarkdownWrapper>
|
||||||
|
) : (
|
||||||
|
<br />
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
<Badge
|
<Badge
|
||||||
className={classes.newMessagesCount}
|
className={classes.newMessagesCount}
|
||||||
badgeContent={+ticket.unreadMessages}
|
badgeContent={+ticket.unreadMessages}
|
||||||
classes={{
|
classes={{
|
||||||
badge: classes.badgeStyle,
|
badge: classes.badgeStyle,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* <Badge
|
{/* <Badge
|
||||||
className={classes.newMessagesCount}
|
className={classes.newMessagesCount}
|
||||||
badgeContent={ticket.unreadMessages}
|
badgeContent={ticket.unreadMessages}
|
||||||
classes={{
|
classes={{
|
||||||
badge: classes.badgeStyle,
|
badge: classes.badgeStyle,
|
||||||
}}
|
}}
|
||||||
/> */}
|
/> */}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{ticket.status === "pending" && (
|
{ticket.status === "pending" && (
|
||||||
|
<ButtonWithSpinner
|
||||||
|
color="primary"
|
||||||
|
variant="contained"
|
||||||
|
className={classes.acceptButton}
|
||||||
|
size="small"
|
||||||
|
loading={loading}
|
||||||
|
onClick={e => handleAcepptTicket(ticket.id)}
|
||||||
|
>
|
||||||
|
{/* {i18n.t("ticketsList.buttons.accept")} */}
|
||||||
|
<>
|
||||||
|
{/* {i18n.t("ticketsList.buttons.accept")}<br />CAMPANHA */}
|
||||||
|
|
||||||
|
{ticket?.isRemote ? (
|
||||||
|
<>{i18n.t("ticketsList.buttons.accept")}<br />CAMPANHA</>
|
||||||
|
) : (
|
||||||
|
<>{i18n.t("ticketsList.buttons.accept")}</>
|
||||||
|
)}
|
||||||
|
|
||||||
<ButtonWithSpinner
|
</>
|
||||||
// color="primary"
|
</ButtonWithSpinner>
|
||||||
|
)}
|
||||||
{...((ticket?.isRemote) ?
|
</ListItem>
|
||||||
((settings &&
|
|
||||||
settings.length > 0 &&
|
|
||||||
getSettingValue('remoteTicketSendControll') &&
|
|
||||||
getSettingValue('remoteTicketSendControll') === 'enabled') && !ticket?.remoteDone && !_remoteTicketsControll?.includes(+ticket.id)) ?
|
|
||||||
{ style: { backgroundColor: "rgba(121,123,127,0.5)", color: "white" } } :
|
|
||||||
{ style: { backgroundColor: "rgba(121,123,127,0.9)", color: "white" } } :
|
|
||||||
|
|
||||||
{ color: "primary" })}
|
|
||||||
|
|
||||||
variant="contained"
|
|
||||||
|
|
||||||
disabled={true ? ((settings &&
|
|
||||||
settings.length > 0 &&
|
|
||||||
getSettingValue('remoteTicketSendControll') &&
|
|
||||||
getSettingValue('remoteTicketSendControll') === 'enabled') && ticket?.isRemote && !ticket?.remoteDone && !_remoteTicketsControll?.includes(+ticket.id)) : false}
|
|
||||||
|
|
||||||
className={classes.acceptButton}
|
|
||||||
size="small"
|
|
||||||
loading={loading}
|
|
||||||
onClick={e => handleAcepptTicket(ticket.id)}
|
|
||||||
>
|
|
||||||
|
|
||||||
<>
|
|
||||||
{(ticket?.isRemote && !ticket?.remoteDone) ? (
|
|
||||||
<>{i18n.t("ticketsList.buttons.accept")}<br />CAMPANHA</>
|
|
||||||
) : (
|
|
||||||
<>{i18n.t("ticketsList.buttons.accept")}</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</>
|
|
||||||
|
|
||||||
</ButtonWithSpinner>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
)}
|
|
||||||
|
|
||||||
</ListItem>
|
|
||||||
</Tooltip>
|
|
||||||
<Divider variant="inset" component="li" />
|
<Divider variant="inset" component="li" />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,7 +15,6 @@ 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"
|
||||||
import { Divider } from "@material-ui/core"
|
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const useStyles = makeStyles(theme => ({
|
||||||
ticketsListWrapper: {
|
ticketsListWrapper: {
|
||||||
|
@ -182,18 +181,10 @@ const TicketsList = (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, setting, } = useContext(AuthContext)
|
const { user } = useContext(AuthContext)
|
||||||
|
|
||||||
const { searchTicket } = useContext(SearchTicketContext)
|
const { searchTicket } = useContext(SearchTicketContext)
|
||||||
|
|
||||||
const [_remoteTicketsControll, setRemoteTicketsControll] = useState([])
|
|
||||||
|
|
||||||
const [settings, setSettings] = useState([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSettings(setting)
|
|
||||||
}, [setting])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
dispatch({ type: "RESET" })
|
dispatch({ type: "RESET" })
|
||||||
|
@ -201,22 +192,17 @@ const TicketsList = (props) => {
|
||||||
|
|
||||||
}, [status, searchParam, searchParamContent, showAll, selectedQueueIds, searchTicket])
|
}, [status, searchParam, searchParamContent, showAll, selectedQueueIds, searchTicket])
|
||||||
|
|
||||||
let { tickets, hasMore, loading, remoteTicketsControll } = 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"
|
unlimited: status === 'open' ? "all" : "false"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setRemoteTicketsControll(remoteTicketsControll)
|
|
||||||
}, [remoteTicketsControll])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
if (!status && !searchParam) return
|
if (!status && !searchParam) return
|
||||||
|
@ -315,27 +301,6 @@ const TicketsList = (props) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
socket.on('remoteTickesControll', (data) => {
|
|
||||||
console.log('REMOTE TICKETS CONTROLL UPDATE 1: ', data.tickets)
|
|
||||||
if (data.action === 'update') {
|
|
||||||
setRemoteTicketsControll(data.tickets)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
socket.on('settings', (data) => {
|
|
||||||
if (data.action === 'update') {
|
|
||||||
setSettings((prevState) => {
|
|
||||||
const aux = [...prevState]
|
|
||||||
const settingIndex = aux.findIndex((s) => s.key === data.setting.key)
|
|
||||||
aux[settingIndex].value = data.setting.value
|
|
||||||
return aux
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
socket.disconnect()
|
socket.disconnect()
|
||||||
}
|
}
|
||||||
|
@ -389,7 +354,7 @@ const TicketsList = (props) => {
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{ticketsList.map(ticket => (
|
{ticketsList.map(ticket => (
|
||||||
<TicketListItem ticket={ticket} key={ticket.id} remoteTicketsControll={_remoteTicketsControll} settings={settings} />
|
<TicketListItem ticket={ticket} key={ticket.id} />
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -33,6 +33,9 @@ import { Button } from "@material-ui/core";
|
||||||
import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketHeaderOption";
|
import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketHeaderOption";
|
||||||
|
|
||||||
import { SearchTicketContext } from "../../context/SearchTicket/SearchTicket";
|
import { SearchTicketContext } from "../../context/SearchTicket/SearchTicket";
|
||||||
|
import useTickets from "../../hooks/useTickets"
|
||||||
|
import api from "../../services/api";
|
||||||
|
import toastError from "../../errors/toastError";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
ticketsWrapper: {
|
ticketsWrapper: {
|
||||||
|
@ -157,6 +160,10 @@ const TicketsManager = () => {
|
||||||
|
|
||||||
const [openTooltipSearch, setOpenTooltipSearch] = useState(false)
|
const [openTooltipSearch, setOpenTooltipSearch] = useState(false)
|
||||||
|
|
||||||
|
const [waitingTime, setWaitingTime] = useState('00:00');
|
||||||
|
const [tickets, setTickets] = useState([]);
|
||||||
|
const [settings, setSettings] = useState([])
|
||||||
|
|
||||||
let searchTimeout;
|
let searchTimeout;
|
||||||
let searchContentTimeout;
|
let searchContentTimeout;
|
||||||
|
|
||||||
|
@ -178,6 +185,76 @@ const TicketsManager = () => {
|
||||||
|
|
||||||
}, [tab, setTabOption]);
|
}, [tab, setTabOption]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchSession = async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await api.get('/settings')
|
||||||
|
setSettings(data.settings)
|
||||||
|
} catch (err) {
|
||||||
|
toastError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchSession()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const getSettingValue = (key) => {
|
||||||
|
const { value } = settings.find((s) => s.key === key)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchTickets = async () =>{
|
||||||
|
try {
|
||||||
|
const { data } = await api.get("/tickets", {
|
||||||
|
params: {
|
||||||
|
status: 'pending',
|
||||||
|
queueIds: JSON.stringify(selectedQueueIds)
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setTickets(data.tickets);
|
||||||
|
} catch (err) {
|
||||||
|
toastError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
if(settings?.length > 0 && getSettingValue('waitingTimeTickets') === 'enabled') {
|
||||||
|
fetchTickets();
|
||||||
|
|
||||||
|
const intervalId = setInterval(fetchTickets, 7000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [selectedQueueIds, settings]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const calculateAverageTime = () => {
|
||||||
|
if(tickets.length > 0){
|
||||||
|
const now = new Date();
|
||||||
|
const differenceTime = tickets?.map(ticket => {
|
||||||
|
const updatedAt = new Date(ticket.updatedAt);
|
||||||
|
const difference = now - updatedAt;
|
||||||
|
return difference;
|
||||||
|
});
|
||||||
|
const sumDifferences = differenceTime.reduce((total, difference) => total + difference, 0);
|
||||||
|
const averageTimeMilliseconds = sumDifferences / tickets?.length;
|
||||||
|
let hours = Math.floor(averageTimeMilliseconds / 3600000);
|
||||||
|
const minutes = Math.floor((averageTimeMilliseconds % 3600000) / 60000);
|
||||||
|
|
||||||
|
let days = hours >= 24 ? parseInt(hours/24) : '';
|
||||||
|
|
||||||
|
if(days != '') hours = hours - (24*days);
|
||||||
|
|
||||||
|
const averageTimeFormated = `${days != '' ? `${days}d ` : days}${hours.toString().padStart(2, '0')}h${minutes.toString().padStart(2, '0')}`;
|
||||||
|
|
||||||
|
return averageTimeFormated;
|
||||||
|
}else return '00:00';
|
||||||
|
}
|
||||||
|
|
||||||
|
setWaitingTime(calculateAverageTime());
|
||||||
|
},[tickets]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
// clearTimeout(searchContentTimeout);
|
// clearTimeout(searchContentTimeout);
|
||||||
|
@ -203,7 +280,7 @@ const TicketsManager = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
|
//console.log(selectedQueueIds);
|
||||||
if (tabOption === 'open') {
|
if (tabOption === 'open') {
|
||||||
|
|
||||||
setTabOption('')
|
setTabOption('')
|
||||||
|
@ -448,7 +525,17 @@ const TicketsManager = () => {
|
||||||
</Badge>
|
</Badge>
|
||||||
}
|
}
|
||||||
value={"pending"}
|
value={"pending"}
|
||||||
/>
|
/>{
|
||||||
|
(settings?.length > 0 && getSettingValue('waitingTimeTickets') === 'enabled') &&
|
||||||
|
<span style={{display: 'flex', alignItems: 'center', flexDirection:'column', justifyContent: 'flex-start'}}>
|
||||||
|
<label style ={{color: 'red',fontWeight: 'bold', padding: '.1rem', fontSize: '8px', textAlign:'center', margin:'0'}}>
|
||||||
|
<i>ESPERA</i>
|
||||||
|
</label>
|
||||||
|
<label style={{color: 'gray',fontWeight: 'bold', padding: '.1rem', textDecoration: 'underline', fontSize: '13px'}}>
|
||||||
|
{waitingTime}
|
||||||
|
</label>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<Paper className={classes.ticketsWrapper}>
|
<Paper className={classes.ticketsWrapper}>
|
||||||
<TicketsList
|
<TicketsList
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
import React, { useState, createContext } from "react"
|
|
||||||
|
|
||||||
const countTicketMsgContext = createContext()
|
|
||||||
|
|
||||||
|
|
||||||
const CountTicketMsgProvider = ({ children }) => {
|
|
||||||
|
|
||||||
const [countTicketMsg, setCountTicketMsg] = useState(0)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<countTicketMsgContext.Provider value={{ countTicketMsg, setCountTicketMsg }}>
|
|
||||||
{children}
|
|
||||||
</countTicketMsgContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { countTicketMsgContext, CountTicketMsgProvider }
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useState, useEffect } from "react"
|
import { useState, useEffect } from "react";
|
||||||
import toastError from "../../errors/toastError"
|
import toastError from "../../errors/toastError";
|
||||||
|
|
||||||
import api from "../../services/api"
|
import api from "../../services/api";
|
||||||
|
|
||||||
const useTickets = ({
|
const useTickets = ({
|
||||||
searchParam,
|
searchParam,
|
||||||
|
@ -15,21 +15,21 @@ const useTickets = ({
|
||||||
unlimited,
|
unlimited,
|
||||||
tab
|
tab
|
||||||
}) => {
|
}) => {
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true);
|
||||||
const [hasMore, setHasMore] = useState(false)
|
const [hasMore, setHasMore] = useState(false);
|
||||||
const [tickets, setTickets] = useState([])
|
const [tickets, setTickets] = useState([]);
|
||||||
const [remoteTicketsControll, setRemoteTicketsControll] = useState([])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
setLoading(true)
|
setLoading(true);
|
||||||
const delayDebounceFn = setTimeout(() => {
|
const delayDebounceFn = setTimeout(() => {
|
||||||
const fetchTickets = async () => {
|
const fetchTickets = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if ((tab === 'search') && (!searchParam || searchParam.trim().length === 0 || searchParam.trim().length > 40 || searchParam.endsWith(' '))) {
|
if ((tab === 'search') && ( !searchParam || searchParam.trim().length === 0 || searchParam.trim().length >40 || searchParam.endsWith(' '))) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,26 +45,24 @@ const useTickets = ({
|
||||||
withUnreadMessages,
|
withUnreadMessages,
|
||||||
unlimited
|
unlimited
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
setTickets(data.tickets)
|
|
||||||
setHasMore(data.hasMore)
|
|
||||||
setLoading(false)
|
|
||||||
|
|
||||||
if (data?.remoteTicketsControll) {
|
|
||||||
setRemoteTicketsControll(data.remoteTicketsControll.map(t => +t))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setTickets(data.tickets);
|
||||||
|
setHasMore(data.hasMore);
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setLoading(false)
|
setLoading(false);
|
||||||
toastError(err)
|
toastError(err);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
fetchTickets()
|
fetchTickets();
|
||||||
}, 500)
|
}, 500);
|
||||||
return () => clearTimeout(delayDebounceFn)
|
return () => clearTimeout(delayDebounceFn);
|
||||||
}, [
|
}, [
|
||||||
searchParam,
|
searchParam,
|
||||||
searchParamContent,
|
searchParamContent,
|
||||||
|
@ -76,9 +74,9 @@ const useTickets = ({
|
||||||
withUnreadMessages,
|
withUnreadMessages,
|
||||||
tab,
|
tab,
|
||||||
unlimited
|
unlimited
|
||||||
])
|
]);
|
||||||
|
|
||||||
return { tickets, loading, hasMore, remoteTicketsControll }
|
return { tickets, loading, hasMore };
|
||||||
}
|
};
|
||||||
|
|
||||||
export default useTickets
|
export default useTickets;
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { AuthContext } from "../../context/Auth/AuthContext"
|
||||||
import { Can } from "../../components/Can"
|
import { Can } from "../../components/Can"
|
||||||
import FormControlLabel from "@mui/material/FormControlLabel"
|
import FormControlLabel from "@mui/material/FormControlLabel"
|
||||||
import Checkbox from '@mui/material/Checkbox'
|
import Checkbox from '@mui/material/Checkbox'
|
||||||
import { Button, Tooltip } from "@material-ui/core"
|
import { Button } from "@material-ui/core"
|
||||||
import ReportModal from "../../components/ReportModal"
|
import ReportModal from "../../components/ReportModal"
|
||||||
import ReportModalType from "../../components/ReportModalType"
|
import ReportModalType from "../../components/ReportModalType"
|
||||||
import MaterialTable from 'material-table'
|
import MaterialTable from 'material-table'
|
||||||
|
@ -237,7 +237,8 @@ let columnsData = [
|
||||||
{ title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' },
|
{ title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' },
|
||||||
{ title: `Espera`, field: 'waiting_time' },
|
{ title: `Espera`, field: 'waiting_time' },
|
||||||
{ title: `Mensagens`, field: 'messagesToFilter', searchable: true, hidden: true },
|
{ title: `Mensagens`, field: 'messagesToFilter', searchable: true, hidden: true },
|
||||||
]
|
{ title: `Link`, field: 'link', searchable: false, hidden: true, export: true },
|
||||||
|
]
|
||||||
|
|
||||||
let columnsDataSuper = [
|
let columnsDataSuper = [
|
||||||
{ title: `${i18n.t("reports.listColumns.column1_1")}`, field: 'whatsapp.name' },
|
{ title: `${i18n.t("reports.listColumns.column1_1")}`, field: 'whatsapp.name' },
|
||||||
|
@ -252,6 +253,7 @@ let columnsDataSuper = [
|
||||||
{ title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' },
|
{ title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' },
|
||||||
{ title: `Espera`, field: 'waiting_time' },
|
{ title: `Espera`, field: 'waiting_time' },
|
||||||
{ title: `Mensagens`, field: 'messagesToFilter', searchable: true, hidden: true },
|
{ title: `Mensagens`, field: 'messagesToFilter', searchable: true, hidden: true },
|
||||||
|
{ title: `Link`, field: 'link', searchable: false, hidden: true, export: true },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -299,7 +301,6 @@ const Report = () => {
|
||||||
const [csvFile, setCsvFile] = useState()
|
const [csvFile, setCsvFile] = useState()
|
||||||
const [selectedValue, setSelectedValue] = useState('created')
|
const [selectedValue, setSelectedValue] = useState('created')
|
||||||
const [checked, setChecked] = useState(true)
|
const [checked, setChecked] = useState(true)
|
||||||
const [checkedRemote, setCheckedRemote] = useState(false)
|
|
||||||
const [queues, setQueues] = useState([])
|
const [queues, setQueues] = useState([])
|
||||||
const [queueId, setQueue] = useState(null)
|
const [queueId, setQueue] = useState(null)
|
||||||
|
|
||||||
|
@ -364,7 +365,7 @@ const Report = () => {
|
||||||
if (reportOption === '1') {
|
if (reportOption === '1') {
|
||||||
|
|
||||||
const { data } = await api.get("/reports/", { params: { userId, startDate, endDate, pageNumber: pageNumberTickets, createdOrUpdated: selectedValue, queueId }, 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 = []
|
||||||
|
@ -377,9 +378,9 @@ const Report = () => {
|
||||||
const tickets = data.tickets.map(ticket => ({
|
const tickets = data.tickets.map(ticket => ({
|
||||||
...ticket,
|
...ticket,
|
||||||
messagesToFilter: ticket.messages.map(message => message.body).join(' '),
|
messagesToFilter: ticket.messages.map(message => message.body).join(' '),
|
||||||
|
link: `${process.env.REACT_APP_FRONTEND_URL}/tickets/${ticket.id}`
|
||||||
}))
|
}))
|
||||||
dispatchQ({ type: "LOAD_QUERY", payload: tickets })
|
dispatchQ({ type: "LOAD_QUERY", payload: tickets })
|
||||||
// console.log(tickets)
|
|
||||||
setHasMore(data.hasMore)
|
setHasMore(data.hasMore)
|
||||||
setTotalCountTickets(data.count)
|
setTotalCountTickets(data.count)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
@ -396,13 +397,15 @@ const Report = () => {
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (reportOption === '3') {
|
else if (reportOption === '3') {
|
||||||
const dataQuery = await api.get("/reports/services/numbers", { params: { startDate, endDate, isRemote: checkedRemote }, })
|
const dataQuery = await api.get("/reports/services/numbers", { params: { startDate, endDate }, })
|
||||||
|
|
||||||
dispatchQ({ type: "RESET" })
|
dispatchQ({ type: "RESET" })
|
||||||
dispatchQ({ type: "LOAD_QUERY", payload: dataQuery?.data?.reportService })
|
dispatchQ({ type: "LOAD_QUERY", payload: dataQuery?.data?.reportService })
|
||||||
}
|
}
|
||||||
else if (reportOption === '4') {
|
else if (reportOption === '4') {
|
||||||
const dataQuery = await api.get("/reports/services/queues", { params: { startDate, endDate, isRemote: checkedRemote }, })
|
const dataQuery = await api.get("/reports/services/queues", { params: { startDate, endDate }, })
|
||||||
|
|
||||||
|
console.log(' dataQuery?.data?.reportService: ', dataQuery?.data?.reportService)
|
||||||
|
|
||||||
dispatchQ({ type: "RESET" })
|
dispatchQ({ type: "RESET" })
|
||||||
dispatchQ({ type: "LOAD_QUERY", payload: dataQuery?.data?.reportService })
|
dispatchQ({ type: "LOAD_QUERY", payload: dataQuery?.data?.reportService })
|
||||||
|
@ -418,7 +421,7 @@ const Report = () => {
|
||||||
}, 500)
|
}, 500)
|
||||||
return () => clearTimeout(delayDebounceFn)
|
return () => clearTimeout(delayDebounceFn)
|
||||||
|
|
||||||
}, [userId, queueId, checked, checkedRemote, startDate, endDate, reportOption, pageNumberTickets, totalCountTickets, selectedValue])
|
}, [userId, queueId, checked, startDate, endDate, reportOption, pageNumberTickets, totalCountTickets, selectedValue])
|
||||||
|
|
||||||
|
|
||||||
const handleCheckBoxChange = (value) => {
|
const handleCheckBoxChange = (value) => {
|
||||||
|
@ -459,7 +462,7 @@ const Report = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get from report type option
|
// Get from report type option
|
||||||
const reportTypeValue = (data) => {
|
const reportTypeValue = (data) => {
|
||||||
let type = '1'
|
let type = '1'
|
||||||
if (data === '1') type = 'default'
|
if (data === '1') type = 'default'
|
||||||
if (data === '2') type = 'synthetic'
|
if (data === '2') type = 'synthetic'
|
||||||
|
@ -680,54 +683,56 @@ const Report = () => {
|
||||||
|
|
||||||
|
|
||||||
const renderSwitch = (param) => {
|
const renderSwitch = (param) => {
|
||||||
switch (param) {
|
if(userA.profile !== 'supervisor'){
|
||||||
case 'empty':
|
switch (param) {
|
||||||
return (
|
case 'empty':
|
||||||
<>
|
return (
|
||||||
{query && query.length > 0 &&
|
<>
|
||||||
<ReportModalType currencies={reportTypeList} func={reportTypeValue} reportOption={reportType} />
|
{query && query.length > 0 &&
|
||||||
}
|
<ReportModalType currencies={reportTypeList} func={reportTypeValue} reportOption={reportType} />
|
||||||
{/* <Button
|
}
|
||||||
disabled={query && query.length > 0 ? false : true}
|
{/* <Button
|
||||||
variant="contained"
|
disabled={query && query.length > 0 ? false : true}
|
||||||
color="primary"
|
variant="contained"
|
||||||
onClick={(e) => {
|
color="primary"
|
||||||
handleCSVMessages()
|
onClick={(e) => {
|
||||||
}}
|
handleCSVMessages()
|
||||||
>
|
}}
|
||||||
{"CSV ALL"}
|
>
|
||||||
|
{"CSV ALL"}
|
||||||
</Button> */}
|
|
||||||
</>)
|
</Button> */}
|
||||||
|
</>)
|
||||||
|
|
||||||
case 'pending' || 'processing':
|
case 'pending' || 'processing':
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span>PROCESSING...</span>
|
<span>PROCESSING...</span>
|
||||||
</>)
|
</>)
|
||||||
|
|
||||||
case 'success':
|
case 'success':
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
handleCSVDownload(e)
|
handleCSVDownload(e)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{'CSV DOWNLOAD'}
|
{'CSV DOWNLOAD'}
|
||||||
</Button>
|
</Button>
|
||||||
</>)
|
</>)
|
||||||
case 'downloading':
|
case 'downloading':
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span>DOWNLOADING...</span>
|
<span>DOWNLOADING...</span>
|
||||||
</>)
|
</>)
|
||||||
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return (<><span>WAITING...</span></>)
|
return (<><span>WAITING...</span></>)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -736,10 +741,6 @@ const Report = () => {
|
||||||
setChecked(event.target.checked)
|
setChecked(event.target.checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChangeRemote = (event) => {
|
|
||||||
setCheckedRemote(event.target.checked)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<Can
|
<Can
|
||||||
|
@ -785,35 +786,6 @@ const Report = () => {
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{(reportOption === '3' || reportOption === '4') &&
|
|
||||||
<div>
|
|
||||||
|
|
||||||
<Tooltip
|
|
||||||
arrow
|
|
||||||
placement="top"
|
|
||||||
title="Todos os tickets incluindo os criados remotamente"
|
|
||||||
|
|
||||||
><label>
|
|
||||||
normal
|
|
||||||
</label>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Switch
|
|
||||||
checked={checkedRemote}
|
|
||||||
onChange={handleChangeRemote}
|
|
||||||
inputProps={{ 'aria-label': 'controlled' }}
|
|
||||||
/>
|
|
||||||
<Tooltip
|
|
||||||
arrow
|
|
||||||
placement="top"
|
|
||||||
title="Apenas tickets criados remotamente"
|
|
||||||
|
|
||||||
><label>
|
|
||||||
remoto
|
|
||||||
</label>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -871,7 +843,7 @@ const Report = () => {
|
||||||
<>
|
<>
|
||||||
<MTable data={query}
|
<MTable data={query}
|
||||||
columns={userA.profile !== 'supervisor' ? columnsData : columnsDataSuper}
|
columns={userA.profile !== 'supervisor' ? columnsData : columnsDataSuper}
|
||||||
hasChild={true}
|
hasChild={userA.profile !== 'supervisor' ? true :false}
|
||||||
removeClickRow={false}
|
removeClickRow={false}
|
||||||
|
|
||||||
handleScroll={handleScroll}
|
handleScroll={handleScroll}
|
||||||
|
@ -991,7 +963,7 @@ const Report = () => {
|
||||||
|
|
||||||
title={i18n.t("reports.listTitles.title4_1")}
|
title={i18n.t("reports.listTitles.title4_1")}
|
||||||
columns={
|
columns={
|
||||||
!checkedRemote ? [
|
[
|
||||||
{ title: 'Unidade', field: 'name', cellStyle: { whiteSpace: 'nowrap' }, },
|
{ title: 'Unidade', field: 'name', cellStyle: { whiteSpace: 'nowrap' }, },
|
||||||
{ title: 'Conversas iniciadas', field: 'startedByAgent', },
|
{ title: 'Conversas iniciadas', field: 'startedByAgent', },
|
||||||
{ title: 'Conversas recebidas', field: 'startedByClient' },
|
{ title: 'Conversas recebidas', field: 'startedByClient' },
|
||||||
|
@ -999,15 +971,7 @@ const Report = () => {
|
||||||
{ title: `Tempo médio de espera`, field: 'avgChatWaitingTime' },
|
{ title: `Tempo médio de espera`, field: 'avgChatWaitingTime' },
|
||||||
{ title: 'Aguardando', field: 'pendingChat' }
|
{ title: 'Aguardando', field: 'pendingChat' }
|
||||||
|
|
||||||
] :
|
]
|
||||||
[
|
|
||||||
{ title: 'Unidade', field: 'name', cellStyle: { whiteSpace: 'nowrap' }, },
|
|
||||||
{ title: 'Conversas iniciadas', field: 'startedByAgent', },
|
|
||||||
{ title: 'Conversas respondidas', field: 'startedByClient' },
|
|
||||||
{ title: `Conversas finalizadas`, field: 'closedChat' },
|
|
||||||
{ title: 'Aguardando', field: 'pendingChat' }
|
|
||||||
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
data={dataRows}
|
data={dataRows}
|
||||||
|
|
||||||
|
@ -1043,7 +1007,7 @@ const Report = () => {
|
||||||
|
|
||||||
title={i18n.t("reports.listTitles.title5_1")}
|
title={i18n.t("reports.listTitles.title5_1")}
|
||||||
columns={
|
columns={
|
||||||
!checkedRemote ? [
|
[
|
||||||
|
|
||||||
{ title: 'Unidade', field: 'name', cellStyle: { whiteSpace: 'nowrap' }, },
|
{ title: 'Unidade', field: 'name', cellStyle: { whiteSpace: 'nowrap' }, },
|
||||||
{
|
{
|
||||||
|
@ -1063,28 +1027,7 @@ const Report = () => {
|
||||||
{ title: `Tempo médio de espera`, field: 'avgChatWaitingTime' },
|
{ title: `Tempo médio de espera`, field: 'avgChatWaitingTime' },
|
||||||
{ title: 'Aguardando', field: 'pendingChat' }
|
{ title: 'Aguardando', field: 'pendingChat' }
|
||||||
|
|
||||||
] :
|
]
|
||||||
|
|
||||||
[
|
|
||||||
|
|
||||||
{ title: 'Unidade', field: 'name', cellStyle: { whiteSpace: 'nowrap' }, },
|
|
||||||
{
|
|
||||||
title: 'Fila', field: 'queueName',
|
|
||||||
cellStyle: (evt, rowData) => {
|
|
||||||
return {
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
backgroundColor: rowData?.queueColor || 'inherit',
|
|
||||||
color: 'white'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
{ title: 'Conversas iniciadas', field: 'startedByAgent', },
|
|
||||||
{ title: 'Conversas respondidas', field: 'startedByClient' },
|
|
||||||
{ title: `Conversas finalizadas`, field: 'closedChat' },
|
|
||||||
{ title: 'Aguardando', field: 'pendingChat' }
|
|
||||||
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
data={dataRows}
|
data={dataRows}
|
||||||
|
|
||||||
|
|
|
@ -12,38 +12,13 @@ import api from '../../services/api'
|
||||||
import { i18n } from '../../translate/i18n.js'
|
import { i18n } from '../../translate/i18n.js'
|
||||||
import toastError from '../../errors/toastError'
|
import toastError from '../../errors/toastError'
|
||||||
|
|
||||||
import TextField from '@material-ui/core/TextField'
|
|
||||||
import Button from '@material-ui/core/Button'
|
|
||||||
|
|
||||||
|
|
||||||
//--------
|
//--------
|
||||||
import { AuthContext } from '../../context/Auth/AuthContext'
|
import { AuthContext } from '../../context/Auth/AuthContext'
|
||||||
|
|
||||||
import { Can } from '../../components/Can'
|
import { Can } from '../../components/Can'
|
||||||
import { boolean } from 'yup'
|
|
||||||
|
|
||||||
// import Button from "@material-ui/core/Button";
|
// import Button from "@material-ui/core/Button";
|
||||||
|
|
||||||
const IntegerInput = ({ value, onChange }) => {
|
|
||||||
const handleChange = (event) => {
|
|
||||||
const inputValue = event.target.value
|
|
||||||
// Only allow digits 0-9
|
|
||||||
if (/^\d{0,3}$/.test(inputValue)) {
|
|
||||||
onChange(inputValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TextField
|
|
||||||
type="text"
|
|
||||||
variant="outlined"
|
|
||||||
value={value}
|
|
||||||
onChange={handleChange}
|
|
||||||
style={{ marginRight: '10px' }}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
@ -73,48 +48,11 @@ const Settings = () => {
|
||||||
|
|
||||||
const [settings, setSettings] = useState([])
|
const [settings, setSettings] = useState([])
|
||||||
|
|
||||||
|
|
||||||
const [number1, setNumber1] = useState('')
|
|
||||||
const [number2, setNumber2] = useState('')
|
|
||||||
|
|
||||||
const handleNumber1Change = (value) => {
|
|
||||||
setNumber1(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleNumber2Change = (value) => {
|
|
||||||
setNumber2(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleGetValues = () => {
|
|
||||||
let e = {
|
|
||||||
target: {
|
|
||||||
value: 'enabled', name: 'remoteTicketSendControll', obj: (number1.trim().length > 0 && number2.trim().length > 0) ? { seconds1: number1, seconds2: number2 } : null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChangeSetting(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchSession = async () => {
|
const fetchSession = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.get('/settings')
|
const { data } = await api.get('/settings')
|
||||||
console.log('data.settings: ', data.settings)
|
|
||||||
setSettings(data.settings)
|
setSettings(data.settings)
|
||||||
|
|
||||||
if (data?.settings) {
|
|
||||||
let { obj } = data.settings.find((s) => s.key === 'remoteTicketSendControll')
|
|
||||||
|
|
||||||
if (!obj) return
|
|
||||||
|
|
||||||
obj = JSON.parse(obj)
|
|
||||||
console.log('SETTING obj: ', obj)
|
|
||||||
|
|
||||||
setNumber1(obj.seconds1)
|
|
||||||
setNumber2(obj.seconds2)
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toastError(err)
|
toastError(err)
|
||||||
}
|
}
|
||||||
|
@ -122,7 +60,6 @@ const Settings = () => {
|
||||||
fetchSession()
|
fetchSession()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const socket = openSocket(process.env.REACT_APP_BACKEND_URL)
|
const socket = openSocket(process.env.REACT_APP_BACKEND_URL)
|
||||||
|
|
||||||
|
@ -144,15 +81,17 @@ const Settings = () => {
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// console.log('------> settings: ', settings)
|
||||||
|
// }, [settings])
|
||||||
|
|
||||||
const handleChangeSetting = async (e) => {
|
const handleChangeSetting = async (e) => {
|
||||||
const selectedValue = e.target.value
|
const selectedValue = e.target.value
|
||||||
const settingKey = e.target.name
|
const settingKey = e.target.name
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.put(`/settings/${settingKey}`, {
|
await api.put(`/settings/${settingKey}`, {
|
||||||
value: selectedValue,
|
value: selectedValue,
|
||||||
// obj: e.target?.obj ? e.target.obj : null
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (settingKey === 'farewellMessageByQueue' &&
|
if (settingKey === 'farewellMessageByQueue' &&
|
||||||
|
@ -178,20 +117,12 @@ const Settings = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSettingValue = (key, _obj = false) => {
|
const getSettingValue = (key) => {
|
||||||
const { value, obj } = settings.find((s) => s.key === key)
|
const { value } = settings.find((s) => s.key === key)
|
||||||
|
|
||||||
if (_obj)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSaveDisabled = (settings &&
|
|
||||||
settings.length > 0 &&
|
|
||||||
getSettingValue('remoteTicketSendControll') === 'disabled')
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Can
|
<Can
|
||||||
role={user.profile}
|
role={user.profile}
|
||||||
|
@ -515,57 +446,10 @@ const Settings = () => {
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={classes.root}>
|
|
||||||
<Container className={classes.container} maxWidth="sm">
|
|
||||||
<Paper className={classes.paper}>
|
|
||||||
<Typography variant="body1">
|
|
||||||
Controle de envio de mensagem de ticket remoto por numero
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Select
|
|
||||||
margin="dense"
|
|
||||||
variant="outlined"
|
|
||||||
native
|
|
||||||
id="remoteTicketSendControll-setting"
|
|
||||||
name="remoteTicketSendControll"
|
|
||||||
value={
|
|
||||||
settings &&
|
|
||||||
settings.length > 0 &&
|
|
||||||
getSettingValue('remoteTicketSendControll')
|
|
||||||
}
|
|
||||||
className={classes.settingOption}
|
|
||||||
onChange={handleChangeSetting}
|
|
||||||
>
|
|
||||||
<option value="enabled">Ativado</option>
|
|
||||||
<option value="disabled">Desativado</option>
|
|
||||||
</Select>
|
|
||||||
</Paper>
|
|
||||||
{/* <Paper>
|
|
||||||
<div style={{ padding: '10px', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
|
||||||
<h3>Tempo aleatorio em segundos</h3>
|
|
||||||
|
|
||||||
<div style={{ marginBottom: '20px', display: 'flex' }}>
|
|
||||||
<IntegerInput title="Number 1" value={number1} onChange={handleNumber1Change} />
|
|
||||||
<IntegerInput title="Number 2" value={number2} onChange={handleNumber2Change} />
|
|
||||||
<Button variant="contained" color="primary" onClick={handleGetValues} disabled={
|
|
||||||
(isSaveDisabled) ? true : false
|
|
||||||
}>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</Paper> */}
|
|
||||||
|
|
||||||
</Container>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default Settings
|
export default Settings
|
||||||
|
|
Loading…
Reference in New Issue