feat: Improve display of queue and WhatsApp number-based interaction report
parent
8447628fbf
commit
9f040ce953
|
@ -331,10 +331,7 @@ export const reportServiceByQueue = async (
|
||||||
|
|
||||||
const { startDate, endDate, queueId } = req.query as IndexQuery;
|
const { startDate, endDate, queueId } = req.query as IndexQuery;
|
||||||
|
|
||||||
console.log(
|
|
||||||
`startDate: ${startDate} | endDate: ${endDate} | queueId: ${queueId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const reportService = await ReportByNumberQueueService({
|
const reportService = await ReportByNumberQueueService({
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
|
|
|
@ -30,7 +30,7 @@ class User extends Model<User> {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@Column
|
@Column
|
||||||
email: string;
|
email: string;
|
||||||
|
|
||||||
@Column(DataType.VIRTUAL)
|
@Column(DataType.VIRTUAL)
|
||||||
password: string;
|
password: string;
|
||||||
|
|
|
@ -79,41 +79,43 @@ const ReportByNumberQueueService = async ({
|
||||||
{ type: QueryTypes.SELECT }
|
{ type: QueryTypes.SELECT }
|
||||||
);
|
);
|
||||||
|
|
||||||
// CHAT AVG WAINTING TIME
|
// CHAT WAINTING TIME
|
||||||
const avgChatWaitingTime: any = await sequelize.query(
|
const avgChatWaitingTime: any = await sequelize.query(
|
||||||
`SELECT SEC_TO_TIME(
|
`
|
||||||
AVG(
|
SELECT TIME_FORMAT(
|
||||||
TIMESTAMPDIFF(
|
SEC_TO_TIME(
|
||||||
SECOND,
|
TIMESTAMPDIFF(
|
||||||
(
|
SECOND,
|
||||||
SELECT createdAt
|
(
|
||||||
FROM Messages
|
SELECT createdAt
|
||||||
WHERE ticketId = m.ticketId
|
FROM Messages
|
||||||
AND fromMe = 0
|
WHERE ticketId = m.ticketId
|
||||||
ORDER BY createdAt ASC
|
AND fromMe = 0
|
||||||
LIMIT 1
|
ORDER BY createdAt ASC
|
||||||
),
|
LIMIT 1
|
||||||
(
|
),
|
||||||
SELECT createdAt
|
(
|
||||||
FROM Messages
|
SELECT createdAt
|
||||||
WHERE ticketId = m.ticketId
|
FROM Messages
|
||||||
AND fromAgent = 1
|
WHERE ticketId = m.ticketId
|
||||||
ORDER BY createdAt ASC
|
AND fromAgent = 1
|
||||||
LIMIT 1
|
ORDER BY createdAt ASC
|
||||||
)
|
LIMIT 1
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
) AS AVG_AWAITING_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 = 'open' OR t.status = 'closed');`,
|
AND (t.status = 'open' OR t.status = 'closed')
|
||||||
|
ORDER BY
|
||||||
|
WAITING_TIME;`,
|
||||||
{ type: QueryTypes.SELECT }
|
{ type: QueryTypes.SELECT }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -129,8 +131,8 @@ const ReportByNumberQueueService = async ({
|
||||||
AND w.number = ${number};`,
|
AND w.number = ${number};`,
|
||||||
|
|
||||||
{ type: QueryTypes.SELECT }
|
{ type: QueryTypes.SELECT }
|
||||||
);
|
);
|
||||||
|
|
||||||
reportServiceData.push({
|
reportServiceData.push({
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
|
@ -138,9 +140,7 @@ const ReportByNumberQueueService = async ({
|
||||||
startedByAgent: startedByAgent[0]?.ticket_count,
|
startedByAgent: startedByAgent[0]?.ticket_count,
|
||||||
startedByClient: startedByClient[0]?.ticket_count,
|
startedByClient: startedByClient[0]?.ticket_count,
|
||||||
closedChat: closedChat[0]?.ticket_count,
|
closedChat: closedChat[0]?.ticket_count,
|
||||||
avgChatWaitingTime: avgChatWaitingTime[0]?.AVG_AWAITING_TIME
|
avgChatWaitingTime: avg(avgChatWaitingTime),
|
||||||
? avgChatWaitingTime[0]?.AVG_AWAITING_TIME
|
|
||||||
: 0,
|
|
||||||
pendingChat: pendingChat[0]?.ticket_count
|
pendingChat: pendingChat[0]?.ticket_count
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -205,31 +205,30 @@ const ReportByNumberQueueService = async ({
|
||||||
{ type: QueryTypes.SELECT }
|
{ type: QueryTypes.SELECT }
|
||||||
);
|
);
|
||||||
|
|
||||||
// CHAT AVG WAINTING TIME
|
// CHAT WAINTING TIME
|
||||||
const avgChatWaitingTime: any = await sequelize.query(
|
const avgChatWaitingTime: any = await sequelize.query(
|
||||||
`SELECT SEC_TO_TIME(
|
`SELECT TIME_FORMAT(
|
||||||
AVG(
|
SEC_TO_TIME(
|
||||||
TIMESTAMPDIFF(
|
TIMESTAMPDIFF(
|
||||||
SECOND,
|
SECOND,
|
||||||
(
|
(
|
||||||
SELECT createdAt
|
SELECT createdAt
|
||||||
FROM Messages
|
FROM Messages
|
||||||
WHERE ticketId = m.ticketId
|
WHERE ticketId = m.ticketId
|
||||||
AND fromMe = 0
|
AND fromMe = 0
|
||||||
ORDER BY createdAt ASC
|
ORDER BY createdAt ASC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
SELECT createdAt
|
SELECT createdAt
|
||||||
FROM Messages
|
FROM Messages
|
||||||
WHERE ticketId = m.ticketId
|
WHERE ticketId = m.ticketId
|
||||||
AND fromAgent = 1
|
AND fromAgent = 1
|
||||||
ORDER BY createdAt ASC
|
ORDER BY createdAt ASC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
), '%H:%i:%s') AS WAITING_TIME
|
||||||
) AS AVG_AWAITING_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
|
||||||
|
@ -238,7 +237,9 @@ 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}
|
||||||
AND (t.status = 'open' OR t.status = 'closed');`,
|
AND (t.status = 'open' OR t.status = 'closed')
|
||||||
|
ORDER BY
|
||||||
|
WAITING_TIME;`,
|
||||||
{ type: QueryTypes.SELECT }
|
{ type: QueryTypes.SELECT }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -254,7 +255,7 @@ const ReportByNumberQueueService = async ({
|
||||||
AND q.id = ${q.id};`,
|
AND q.id = ${q.id};`,
|
||||||
|
|
||||||
{ type: QueryTypes.SELECT }
|
{ type: QueryTypes.SELECT }
|
||||||
);
|
);
|
||||||
|
|
||||||
reportServiceData.push({
|
reportServiceData.push({
|
||||||
id,
|
id,
|
||||||
|
@ -265,9 +266,7 @@ const ReportByNumberQueueService = async ({
|
||||||
startedByAgent: startedByAgent[0]?.ticket_count,
|
startedByAgent: startedByAgent[0]?.ticket_count,
|
||||||
startedByClient: startedByClient[0]?.ticket_count,
|
startedByClient: startedByClient[0]?.ticket_count,
|
||||||
closedChat: closedChat[0]?.ticket_count,
|
closedChat: closedChat[0]?.ticket_count,
|
||||||
avgChatWaitingTime: avgChatWaitingTime[0]?.AVG_AWAITING_TIME
|
avgChatWaitingTime: avg(avgChatWaitingTime),
|
||||||
? avgChatWaitingTime[0]?.AVG_AWAITING_TIME
|
|
||||||
: 0,
|
|
||||||
pendingChat: pendingChat[0]?.ticket_count
|
pendingChat: pendingChat[0]?.ticket_count
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -278,3 +277,55 @@ const ReportByNumberQueueService = async ({
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ReportByNumberQueueService;
|
export default ReportByNumberQueueService;
|
||||||
|
|
||||||
|
function avg(avgChatWaitingTime: any) {
|
||||||
|
let waitingAVG: any = avgChatWaitingTime
|
||||||
|
.filter((t: any) => t?.WAITING_TIME)
|
||||||
|
.map((t: any) => t.WAITING_TIME)
|
||||||
|
|
||||||
|
if (waitingAVG.length > 0) {
|
||||||
|
let midIndex = Math.floor((0 + waitingAVG.length) / 2)
|
||||||
|
|
||||||
|
if (waitingAVG.length % 2 == 1) {
|
||||||
|
waitingAVG = waitingAVG[midIndex]
|
||||||
|
} else {
|
||||||
|
waitingAVG = calculateAverageTime(
|
||||||
|
waitingAVG[midIndex - 1],
|
||||||
|
waitingAVG[midIndex]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
waitingAVG = 0
|
||||||
|
}
|
||||||
|
return waitingAVG
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateAverageTime(time1: string, time2: string) {
|
||||||
|
// Function to parse time string to seconds
|
||||||
|
function timeStringToSeconds(timeString: string) {
|
||||||
|
const [hours, minutes, seconds] = timeString.split(":").map(Number);
|
||||||
|
return hours * 3600 + minutes * 60 + seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to convert seconds to time string
|
||||||
|
function secondsToTimeString(seconds: number) {
|
||||||
|
const hours = Math.floor(seconds / 3600);
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
const remainingSeconds = seconds % 60;
|
||||||
|
return `${hours.toString().padStart(2, "0")}:${minutes
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert time strings to seconds
|
||||||
|
const time1Seconds = timeStringToSeconds(time1);
|
||||||
|
const time2Seconds = timeStringToSeconds(time2);
|
||||||
|
|
||||||
|
// Calculate average seconds
|
||||||
|
const averageSeconds = Math.round((time1Seconds + time2Seconds) / 2);
|
||||||
|
|
||||||
|
// Convert average seconds back to time string
|
||||||
|
const averageTime = secondsToTimeString(averageSeconds);
|
||||||
|
|
||||||
|
return averageTime;
|
||||||
|
}
|
||||||
|
|
|
@ -395,15 +395,13 @@ const Report = () => {
|
||||||
else if (reportOption === '3') {
|
else if (reportOption === '3') {
|
||||||
const dataQuery = await api.get("/reports/services/numbers", { params: { startDate, endDate }, })
|
const dataQuery = await api.get("/reports/services/numbers", { params: { startDate, endDate }, })
|
||||||
|
|
||||||
console.log('DATA QUERY.data numbers: ', dataQuery.data)
|
|
||||||
|
|
||||||
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 }, })
|
const dataQuery = await api.get("/reports/services/queues", { params: { startDate, endDate }, })
|
||||||
|
|
||||||
console.log('DATA QUERY.data queues: ', dataQuery.data)
|
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 })
|
||||||
|
@ -958,48 +956,11 @@ const Report = () => {
|
||||||
|
|
||||||
<MaterialTable
|
<MaterialTable
|
||||||
|
|
||||||
localization={{
|
|
||||||
|
|
||||||
// header: {
|
|
||||||
// actions: 'Deslogar'
|
|
||||||
// },
|
|
||||||
|
|
||||||
}}
|
|
||||||
|
|
||||||
title={i18n.t("reports.listTitles.title4_1")}
|
title={i18n.t("reports.listTitles.title4_1")}
|
||||||
columns={
|
columns={
|
||||||
[
|
[
|
||||||
|
|
||||||
// { title: 'Foto', field: 'ticket.contact.profilePicUrl', render: rowData => <img src={rowData['ticket.contact.profilePicUrl']} alt="imagem de perfil do whatsapp" style={{ width: 40, borderRadius: '50%' }} /> },
|
|
||||||
{ title: 'Unidade', field: 'name', cellStyle: { whiteSpace: 'nowrap' }, },
|
{ title: 'Unidade', field: 'name', cellStyle: { whiteSpace: 'nowrap' }, },
|
||||||
|
{ title: 'Conversas iniciadas', field: 'startedByAgent', },
|
||||||
{
|
|
||||||
title: 'Conversas iniciadas', field: 'startedByAgent',
|
|
||||||
|
|
||||||
// cellStyle: (e, rowData) => {
|
|
||||||
|
|
||||||
// if (rowData['statusOnline'] && rowData['statusOnline'].status) {
|
|
||||||
|
|
||||||
// if (rowData['statusOnline'].status === 'offline') {
|
|
||||||
|
|
||||||
// return { color: "red" }
|
|
||||||
// }
|
|
||||||
// else if (rowData['statusOnline'].status === 'online') {
|
|
||||||
// return { color: "green" }
|
|
||||||
// }
|
|
||||||
// else if (rowData['statusOnline'].status === 'logout...') {
|
|
||||||
// return { color: "orange" }
|
|
||||||
// }
|
|
||||||
// else if (rowData['statusOnline'].status === 'waiting...') {
|
|
||||||
// return { color: "orange" }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// },
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
{ title: 'Conversas recebidas', field: 'startedByClient' },
|
{ title: 'Conversas recebidas', field: 'startedByClient' },
|
||||||
{ title: `Conversas finalizadas`, field: 'closedChat' },
|
{ title: `Conversas finalizadas`, field: 'closedChat' },
|
||||||
{ title: `Tempo médio de espera`, field: 'avgChatWaitingTime' },
|
{ title: `Tempo médio de espera`, field: 'avgChatWaitingTime' },
|
||||||
|
@ -1009,29 +970,6 @@ const Report = () => {
|
||||||
}
|
}
|
||||||
data={dataRows}
|
data={dataRows}
|
||||||
|
|
||||||
// actions={[
|
|
||||||
// (rowData) => {
|
|
||||||
|
|
||||||
// if (rowData.statusOnline &&
|
|
||||||
// rowData.statusOnline['status'] &&
|
|
||||||
// rowData.statusOnline['status'] === 'online') {
|
|
||||||
|
|
||||||
|
|
||||||
// return {
|
|
||||||
// icon: LogoutIcon,
|
|
||||||
// tooltip: 'deslogar',
|
|
||||||
// disable: false,
|
|
||||||
// onClick: (event, rowData) => {
|
|
||||||
// handleLogouOnlineUser(rowData.id)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// ]}
|
|
||||||
|
|
||||||
|
|
||||||
options={
|
options={
|
||||||
{
|
{
|
||||||
search: true,
|
search: true,
|
||||||
|
@ -1062,49 +1000,23 @@ const Report = () => {
|
||||||
|
|
||||||
<MaterialTable
|
<MaterialTable
|
||||||
|
|
||||||
localization={{
|
|
||||||
|
|
||||||
// header: {
|
|
||||||
// actions: 'Deslogar'
|
|
||||||
// },
|
|
||||||
|
|
||||||
}}
|
|
||||||
|
|
||||||
title={i18n.t("reports.listTitles.title5_1")}
|
title={i18n.t("reports.listTitles.title5_1")}
|
||||||
columns={
|
columns={
|
||||||
[
|
[
|
||||||
|
|
||||||
// { title: 'Foto', field: 'ticket.contact.profilePicUrl', render: rowData => <img src={rowData['ticket.contact.profilePicUrl']} alt="imagem de perfil do whatsapp" style={{ width: 40, borderRadius: '50%' }} /> },
|
|
||||||
|
|
||||||
{ title: 'Unidade', field: 'name', cellStyle: { whiteSpace: 'nowrap' }, },
|
{ title: 'Unidade', field: 'name', cellStyle: { whiteSpace: 'nowrap' }, },
|
||||||
{ title: 'Fila', field: 'queueName', cellStyle: { whiteSpace: 'nowrap' }, },
|
|
||||||
{
|
{
|
||||||
title: 'Conversas iniciadas', field: 'startedByAgent',
|
title: 'Fila', field: 'queueName',
|
||||||
|
cellStyle: (evt, rowData) => {
|
||||||
// cellStyle: (e, rowData) => {
|
return {
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
// if (rowData['statusOnline'] && rowData['statusOnline'].status) {
|
backgroundColor: rowData?.queueColor || 'inherit',
|
||||||
|
color: 'white'
|
||||||
// if (rowData['statusOnline'].status === 'offline') {
|
}
|
||||||
|
}
|
||||||
// return { color: "red" }
|
|
||||||
// }
|
|
||||||
// else if (rowData['statusOnline'].status === 'online') {
|
|
||||||
// return { color: "green" }
|
|
||||||
// }
|
|
||||||
// else if (rowData['statusOnline'].status === 'logout...') {
|
|
||||||
// return { color: "orange" }
|
|
||||||
// }
|
|
||||||
// else if (rowData['statusOnline'].status === 'waiting...') {
|
|
||||||
// return { color: "orange" }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// },
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
{ title: 'Conversas iniciadas', field: 'startedByAgent', },
|
||||||
{ title: 'Conversas recebidas', field: 'startedByClient' },
|
{ title: 'Conversas recebidas', field: 'startedByClient' },
|
||||||
{ title: `Conversas finalizadas`, field: 'closedChat' },
|
{ title: `Conversas finalizadas`, field: 'closedChat' },
|
||||||
{ title: `Tempo médio de espera`, field: 'avgChatWaitingTime' },
|
{ title: `Tempo médio de espera`, field: 'avgChatWaitingTime' },
|
||||||
|
@ -1114,29 +1026,6 @@ const Report = () => {
|
||||||
}
|
}
|
||||||
data={dataRows}
|
data={dataRows}
|
||||||
|
|
||||||
// actions={[
|
|
||||||
// (rowData) => {
|
|
||||||
|
|
||||||
// if (rowData.statusOnline &&
|
|
||||||
// rowData.statusOnline['status'] &&
|
|
||||||
// rowData.statusOnline['status'] === 'online') {
|
|
||||||
|
|
||||||
|
|
||||||
// return {
|
|
||||||
// icon: LogoutIcon,
|
|
||||||
// tooltip: 'deslogar',
|
|
||||||
// disable: false,
|
|
||||||
// onClick: (event, rowData) => {
|
|
||||||
// handleLogouOnlineUser(rowData.id)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// ]}
|
|
||||||
|
|
||||||
|
|
||||||
options={
|
options={
|
||||||
{
|
{
|
||||||
search: true,
|
search: true,
|
||||||
|
@ -1159,6 +1048,11 @@ const Report = () => {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rowStyle: rowData => ({
|
||||||
|
// backgroundColor: rowData.queueColor || 'inherit',
|
||||||
|
// fontSize: 14
|
||||||
|
// })
|
||||||
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue