Compare commits

..

5 Commits

5 changed files with 219 additions and 69 deletions

View File

@ -263,8 +263,6 @@ export const reportMessagesUserByDateStartDateEnd = async (
} }
data_query_messages[i].id = i + 1; data_query_messages[i].id = i + 1;
console.log("data_query_messages: ", data_query_messages[i]);
} }
return res.status(200).json(data_query_messages); return res.status(200).json(data_query_messages);

View File

@ -18,6 +18,7 @@ import { startOfDay, endOfDay, parseISO, getDate } from "date-fns";
import { string } from "yup/lib/locale"; import { string } from "yup/lib/locale";
import Whatsapp from "../../models/Whatsapp"; import Whatsapp from "../../models/Whatsapp";
import Query from "mysql2/typings/mysql/lib/protocol/sequences/Query"; import Query from "mysql2/typings/mysql/lib/protocol/sequences/Query";
import { te } from "date-fns/locale";
interface Request { interface Request {
userId: string | number; userId: string | number;
@ -43,43 +44,56 @@ const ShowTicketReport = async ({
createdOrUpdated = "created", createdOrUpdated = "created",
queueId queueId
}: Request): Promise<Response> => { }: Request): Promise<Response> => {
let where_clause: any = {}; // let where_clause: any = {};
let query = ""; // let query = "";
if (userId !== "0") { // if (userId !== "0") {
where_clause.userid = userId; // where_clause.userid = userId;
query = `AND t.userId = ${userId}`; // query = `AND t.userId = ${userId}`;
} // }
// if (queueId) {
// where_clause.queueId = queueId;
// query = `AND t.queueId = ${queueId}`;
// }
const createdAtOrUpdatedAt =
createdOrUpdated == "created" ? "createdAt" : "updatedAt";
let where_clause = {};
if (queueId) { if (queueId) {
where_clause.queueId = queueId; where_clause = {
query = `AND t.queueId = ${queueId}`; queueId: queueId,
[createdAtOrUpdatedAt]: {
[Op.gte]: startDate + " 00:00:00.000000",
[Op.lte]: endDate + " 23:59:59.999999"
}
};
} else if (userId == "0") {
where_clause = {
[createdAtOrUpdatedAt]: {
[Op.gte]: startDate + " 00:00:00.000000",
[Op.lte]: endDate + " 23:59:59.999999"
}
};
} else if (userId != "0") {
where_clause = {
userid: userId,
[createdAtOrUpdatedAt]: {
[Op.gte]: startDate + " 00:00:00.000000",
[Op.lte]: endDate + " 23:59:59.999999"
}
};
} }
const limit = 40; const limit = 40;
const offset = limit * (+pageNumber - 1); const offset = limit * (+pageNumber - 1);
const createdAtOrUpdatedAt =
createdOrUpdated == "created" ? "createdAt" : "updatedAt";
const _ticketsId = await sequelize.query(
`SELECT t.id
FROM Tickets t
INNER JOIN (
SELECT DISTINCT ticketId
FROM Messages
WHERE ${createdAtOrUpdatedAt} >= '${startDate} 00:00:00' AND ${createdAtOrUpdatedAt} <= '${endDate} 23:59:59'
) AS m ON m.ticketId = t.id ${query};`,
{ type: QueryTypes.SELECT }
);
let { count, rows: tickets }: any = await Ticket.findAndCountAll({ let { count, rows: tickets }: any = await Ticket.findAndCountAll({
where: { where: where_clause,
id: { [Op.in]: _ticketsId.map((t: any) => t.id) }
},
limit, limit,
offset, offset,
attributes: [ attributes: [
"id", "id",
"status", "status",
@ -151,9 +165,7 @@ const ShowTicketReport = async ({
throw new AppError("ERR_NO_TICKET_FOUND", 404); throw new AppError("ERR_NO_TICKET_FOUND", 404);
} }
const ticketIds = tickets.map((t: any) => t.id); if (tickets.length > 0) {
if (ticketIds.length > 0) {
const waiting_time: any = await sequelize.query( const waiting_time: any = await sequelize.query(
`SELECT t.id as ticketId, t.status, TIME_FORMAT( `SELECT t.id as ticketId, t.status, TIME_FORMAT(
SEC_TO_TIME( SEC_TO_TIME(
@ -182,7 +194,7 @@ const ShowTicketReport = async ({
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 t.id IN (${ticketIds.join()}) AND t.id IN (${tickets.map((t: any) => t.id).join()})
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 t.status IN ('open', 'closed') AND t.status IN ('open', 'closed')

View File

@ -0,0 +1,127 @@
import { Box } from '@material-ui/core';
import React from 'react';
import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord';
import { PieChart as RechartsPieChart, Pie, Sector, Cell, ResponsiveContainer } from 'recharts';
import Title from './Title';
const dataExample = [
{
"id": 3366,
"name": "FINALIZADO",
"count": 5
},
{
"id": 3369,
"name": "LEMBRETE",
"count": 1
},
{
"id": 3367,
"name": "EXEMPLO",
"count": 3
},
{
"id": 3364,
"name": "EXEMPLO 2",
"count": 3
},
{
"id": 3364,
"name": "EXEMPLO 3",
"count": 6
},
]
const COLORS = [
'#0088FE', // Azul escuro
'#00C49F', // Verde menta
'#FFBB28', // Laranja escuro
'#FF8042', // Vermelho escuro
'#9D38BD', // Roxo escuro
'#FFD166', // Laranja claro
'#331F00', // Marrom escuro
'#C0FFC0', // Verde Claro
'#C4E538', // Verde-amarelo vibrante
'#A2A2A2', // Cinza claro
];;
const RADIAN = Math.PI / 180;
const renderCustomizedLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, count }) => {
const radius = innerRadius + (outerRadius - innerRadius) * 0.75;
const x = cx + radius * Math.cos(-midAngle * RADIAN);
const y = cy + radius * Math.sin(-midAngle * RADIAN);
return (
<text x={x} y={y} fill="white" textAnchor={x > cx ? 'start' : 'end'} dominantBaseline="central">
{count}
</text>
);
};
/**
* @param data array de objetos no formato
* {
"id": number | string,
"name": string,
"count": number
* }
*/
const PieChart = ({ data = dataExample }) => {
return (
<Box width="100%" height="100%" position="relative" display="flex">
<Box sx={{ position: "absolute" }}>
<Title>Tickets status de encerramento</Title>
</Box>
<Box
component="ul"
sx={{
position: "absolute",
top: 0, right: 0,
display: "flex",
flexDirection: "column",
gap: "4px"
}}>
{data.map((entry, index) => {
return (
<Box
component="li"
key={entry.id}
sx={{
display: "flex", gap: "2px",
color: COLORS[index % COLORS.length],
alignItems: "center"
}}>
<FiberManualRecordIcon fill={COLORS[index % COLORS.length]} />
<text style={{ color: 'black' }}>{entry.name}</text>
</Box>
)
})}
</Box>
<Box width="100%" height="100%" alignSelf="flex-end">
<ResponsiveContainer width="100%" height="100%">
<RechartsPieChart width={400} height={400}>
<Pie
data={data}
cx="40%"
cy="60%"
labelLine={false}
label={renderCustomizedLabel}
outerRadius={100}
fill="#8884d8"
dataKey="count"
>
{data.map((entry, index) => (
<Cell key={`cell-${entry.id}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
</RechartsPieChart>
</ResponsiveContainer>
</Box>
</Box >
);
}
export default PieChart

View File

@ -15,6 +15,7 @@ import Info from "@material-ui/icons/Info"
import { AuthContext } from "../../context/Auth/AuthContext" import { AuthContext } from "../../context/Auth/AuthContext"
// import { i18n } from "../../translate/i18n"; // import { i18n } from "../../translate/i18n";
import Chart from "./Chart" import Chart from "./Chart"
import PieChart from "./PieChart"
import openSocket from "socket.io-client" import openSocket from "socket.io-client"
import api from "../../services/api" import api from "../../services/api"
@ -31,7 +32,7 @@ const useStyles = makeStyles((theme) => ({
display: "flex", display: "flex",
overflow: "auto", overflow: "auto",
flexDirection: "column", flexDirection: "column",
height: 240, height: 280,
}, },
customFixedHeightPaper: { customFixedHeightPaper: {
padding: theme.spacing(2), padding: theme.spacing(2),
@ -257,7 +258,7 @@ const Dashboard = () => {
const [usersOnlineInfo, dispatch] = useReducer(reducer, []) const [usersOnlineInfo, dispatch] = useReducer(reducer, [])
const [ticketStatusChange, setStatus] = useState() const [ticketStatusChange, setStatus] = useState()
const [ticketsStatus, setTicktsStatus] = useState({ open: 0, openAll: 0, pending: 0, closed: 0 }) const [ticketsStatus, setTicktsStatus] = useState({ open: 0, openAll: 0, pending: 0, closed: 0 })
const [ticketStatusChatEnd, setTicketStatusChatEnd] = useState([])
const { user } = useContext(AuthContext) const { user } = useContext(AuthContext)
useEffect(() => { useEffect(() => {
@ -288,10 +289,15 @@ const Dashboard = () => {
params: { userId: null, startDate: dateToday, endDate: dateToday }, params: { userId: null, startDate: dateToday, endDate: dateToday },
}) })
// console.log('data.data: ', data.usersProfile)
dispatch({ type: "RESET" }) dispatch({ type: "RESET" })
dispatch({ type: "LOAD_QUERY", payload: data.usersProfile }) dispatch({ type: "LOAD_QUERY", payload: data.usersProfile })
const { data: ticketStatusChatEndData } = await api.get("/reports/count/statusChatEnd", {
params: { startDate: '2024-03-21', endDate: '2024-03-28' },
})
setTicketStatusChatEnd(ticketStatusChatEndData.reportStatusChatEnd)
} catch (err) { } catch (err) {
} }
@ -497,11 +503,18 @@ const Dashboard = () => {
</Grid> </Grid>
</Paper> </Paper>
</Grid> </Grid>
<Grid item xs={12}> <Grid item container spacing={3}>
<Grid item xs={12} sm={12} md={6} lg={6}>
<Paper className={classes.fixedHeightPaper} variant="outlined"> <Paper className={classes.fixedHeightPaper} variant="outlined">
<Chart allTickets={usersOnlineInfo} /> <Chart allTickets={usersOnlineInfo} />
</Paper> </Paper>
</Grid> </Grid>
<Grid item xs={12} sm={12} md={6} lg={6}>
<Paper className={classes.fixedHeightPaper} variant="outlined">
<PieChart data={ticketStatusChatEnd} />
</Paper>
</Grid>
</Grid>
</Grid> </Grid>
</Paper> </Paper>
<Paper className={classes.containerPaperFix} style={{ marginTop: "21px" }} sx={12}> <Paper className={classes.containerPaperFix} style={{ marginTop: "21px" }} sx={12}>