From 255b1bb47bee42a558c0fd685202213ab7910e70 Mon Sep 17 00:00:00 2001 From: adriano Date: Tue, 30 Jan 2024 09:50:37 -0300 Subject: [PATCH] feat: Allow supervisor users to view dashboard and reports --- backend/src/controllers/ReportController.ts | 25 +- .../src/helpers/removeUserFromOnlineList.ts | 8 +- .../TicketServices/ListTicketsService.ts | 8 +- frontend/src/layout/MainListItems.js | 27 +- frontend/src/pages/Dashboard/index.js | 2 +- frontend/src/pages/Report/index.js | 355 +++++++++--------- frontend/src/rules.js | 5 +- 7 files changed, 237 insertions(+), 193 deletions(-) diff --git a/backend/src/controllers/ReportController.ts b/backend/src/controllers/ReportController.ts index f2c042f..664bfde 100644 --- a/backend/src/controllers/ReportController.ts +++ b/backend/src/controllers/ReportController.ts @@ -44,26 +44,33 @@ type ReportOnQueue = { export const reportUserByDateStartDateEnd = async (req: Request, res: Response): Promise => { - if (req.user.profile !== "master" && req.user.profile !== "admin") { + if ( + req.user.profile !== "master" && + req.user.profile !== "admin" && + req.user.profile !== "supervisor" + ) { throw new AppError("ERR_NO_PERMISSION", 403); } const { userId, startDate, endDate, pageNumber } = req.query as IndexQuery + console.log("userId, startDate, endDate, pageNumber: ", userId, startDate, endDate, pageNumber); + const { tickets, count, hasMore } = await ShowTicketReport({ userId, startDate, endDate, pageNumber }); + // console.log('kkkkkkkkkkkkkkkkkk tickets: ', JSON.stringify(tickets, null, 6)) + return res.status(200).json({ tickets, count, hasMore }); }; -export const reportUserService = async (req: Request, res: Response): Promise => { +export const reportUserService = async (req: Request, res: Response): Promise => { - if (req.user.profile !== "master" && req.user.profile !== "admin") { + if (req.user.profile !== "master" && req.user.profile !== "admin" && req.user.profile !=="supervisor") { throw new AppError("ERR_NO_PERMISSION", 403); } - const { userId, startDate, endDate } = req.query as IndexQuery - - + const { userId, startDate, endDate } = req.query as IndexQuery + // let usersProfile = await ListUserParamiterService({ profile: 'user' }) let usersProfile = await ListUserParamiterService({ profiles: ["user", "supervisor"], @@ -188,7 +195,11 @@ export const reportUserService = async (req: Request, res: Response): Promise => { - if (req.user.profile !== "master" && req.user.profile !== "admin") { + if ( + req.user.profile !== "master" && + req.user.profile !== "admin" && + req.user.profile !== "supervisor" + ) { throw new AppError("ERR_NO_PERMISSION", 403); } diff --git a/backend/src/helpers/removeUserFromOnlineList.ts b/backend/src/helpers/removeUserFromOnlineList.ts index 2096f82..06bb5fb 100644 --- a/backend/src/helpers/removeUserFromOnlineList.ts +++ b/backend/src/helpers/removeUserFromOnlineList.ts @@ -1,17 +1,17 @@ const usersSocket = require("../libs/socket"); export const removeUserFromOlineList = (userId: any) => { - let index = usersSocket.ob.listOnline.findIndex((o: any) => o.id == userId); + let index = usersSocket?.ob?.listOnline?.findIndex((o: any) => o?.id == userId); if (index != -1) { - usersSocket.ob.listOnline.splice(index, 1); + usersSocket?.ob?.listOnline?.splice(index, 1); } index = -1; - index = usersSocket.ob.listOnlineAux.findIndex((o: any) => o.id == userId); + index = usersSocket?.ob?.listOnlineAux?.findIndex((o: any) => o?.id == userId); if (index != -1) { - usersSocket.ob.listOnlineAux.splice(index, 1); + usersSocket?.ob?.listOnlineAux?.splice(index, 1); } }; diff --git a/backend/src/services/TicketServices/ListTicketsService.ts b/backend/src/services/TicketServices/ListTicketsService.ts index 76c655b..ac2e2fb 100644 --- a/backend/src/services/TicketServices/ListTicketsService.ts +++ b/backend/src/services/TicketServices/ListTicketsService.ts @@ -194,8 +194,12 @@ const ListTicketsService = async ({ const userProfile: any = await User.findByPk(userId) - if (userProfile.dataValues.profile != 'admin' && userProfile.dataValues.profile != 'master') { - whereCondition = { ...whereCondition, userId } + if ( + userProfile.dataValues.profile != "admin" && + userProfile.dataValues.profile != "master" + // userProfile.dataValues.profile != "supervisor" + ) { + whereCondition = { ...whereCondition, userId }; } diff --git a/frontend/src/layout/MainListItems.js b/frontend/src/layout/MainListItems.js index 9451b18..95112bc 100644 --- a/frontend/src/layout/MainListItems.js +++ b/frontend/src/layout/MainListItems.js @@ -103,11 +103,11 @@ const MainListItems = (props) => { primary={i18n.t('mainDrawer.listItems.quickAnswers')} icon={} /> - - + + ( <> @@ -117,11 +117,24 @@ const MainListItems = (props) => { primary={i18n.t("mainDrawer.listItems.users")} icon={} /> + } + /> + } + /> )} /> + + + { } /> - } - /> + /> */} - } - /> + /> */} { try { let date = new Date().toLocaleDateString("pt-BR").split("/") let dateToday = `${date[2]}-${date[1]}-${date[0]}` - + const { data } = await api.get("/reports/user/services", { params: { userId: null, startDate: dateToday, endDate: dateToday }, }) diff --git a/frontend/src/pages/Report/index.js b/frontend/src/pages/Report/index.js index d62f124..d1e2a58 100644 --- a/frontend/src/pages/Report/index.js +++ b/frontend/src/pages/Report/index.js @@ -1,47 +1,47 @@ -import React, { useState, useEffect, useReducer, useContext } from "react"; -import MainContainer from "../../components/MainContainer"; -import api from "../../services/api"; -import SelectField from "../../components/Report/SelectField"; +import React, { useState, useEffect, useReducer, useContext } from "react" +import MainContainer from "../../components/MainContainer" +import api from "../../services/api" +import SelectField from "../../components/Report/SelectField" import DatePicker1 from '../../components/Report/DatePicker' import DatePicker2 from '../../components/Report/DatePicker' -import MTable from "../../components/Report/MTable"; -import PropTypes from 'prop-types'; -import Box from '@mui/material/Box'; -import { AuthContext } from "../../context/Auth/AuthContext"; -import { Can } from "../../components/Can"; +import MTable from "../../components/Report/MTable" +import PropTypes from 'prop-types' +import Box from '@mui/material/Box' +import { AuthContext } from "../../context/Auth/AuthContext" +import { Can } from "../../components/Can" -import { Button } from "@material-ui/core"; +import { Button } from "@material-ui/core" -import ReportModal from "../../components/ReportModal"; -import MaterialTable from 'material-table'; +import ReportModal from "../../components/ReportModal" +import MaterialTable from 'material-table' -import LogoutIcon from '@material-ui/icons/CancelOutlined'; +import LogoutIcon from '@material-ui/icons/CancelOutlined' -import apiBroker from "../../services/apiBroker"; +import apiBroker from "../../services/apiBroker" import fileDownload from 'js-file-download' - -import openSocket from "socket.io-client"; -import { i18n } from "../../translate/i18n"; - +import openSocket from "socket.io-client" + +import { i18n } from "../../translate/i18n" + const report = [{ 'value': '1', 'label': 'Atendimento por atendentes' }, { 'value': '2', 'label': 'Usuários online/offline' }] - + const reducerQ = (state, action) => { if (action.type === "DELETE_USER_STATUS") { - const userId = action.payload; - - const userIndex = state.findIndex((u) => `${u.id}` === `${userId}`); + const userId = action.payload + + const userIndex = state.findIndex((u) => `${u.id}` === `${userId}`) if (userIndex !== -1) { - state.splice(userIndex, 1); + state.splice(userIndex, 1) } - return [...state]; + return [...state] } @@ -72,16 +72,16 @@ const reducerQ = (state, action) => { let onlineUser = action.payload let index = -1 - + if (onlineUser.sumOpen || onlineUser.sumClosed) { index = state.findIndex((e) => ((onlineUser.sumOpen && e.id === onlineUser.sumOpen.userId) || (onlineUser.sumClosed && e.id === onlineUser.sumClosed.userId))) } else { index = state.findIndex((e) => `${e.id}` === `${onlineUser.userId}`) } - - if (index !== -1) { + + if (index !== -1) { if (!("statusOnline" in state[index])) { state[index].statusOnline = onlineUser @@ -104,9 +104,9 @@ const reducerQ = (state, action) => { if (onlineUser.sumOpen) { - if ("sumOpen" in state[index]) { + if ("sumOpen" in state[index]) { state[index].sumOpen['count'] = onlineUser.sumOpen.count - } else if (!("sumOpen" in state[index])) { + } else if (!("sumOpen" in state[index])) { state[index].sumOpen = onlineUser.sumOpen } @@ -114,9 +114,9 @@ const reducerQ = (state, action) => { if (onlineUser.sumClosed) { - if ("sumClosed" in state[index]) { + if ("sumClosed" in state[index]) { state[index].sumClosed['count'] = onlineUser.sumClosed.count - } else if (!("sumClosed" in state[index])) { + } else if (!("sumClosed" in state[index])) { state[index].sumClosed = onlineUser.sumClosed } @@ -131,7 +131,7 @@ const reducerQ = (state, action) => { if (action.type === "RESET") { - return []; + return [] } } @@ -141,41 +141,41 @@ const reducerQ = (state, action) => { const reducer = (state, action) => { if (action.type === "LOAD_USERS") { - const users = action.payload; - const newUsers = []; + const users = action.payload + const newUsers = [] users.forEach((user) => { - const userIndex = state.findIndex((u) => u.id === user.id); + const userIndex = state.findIndex((u) => u.id === user.id) if (userIndex !== -1) { - state[userIndex] = user; + state[userIndex] = user } else { - newUsers.push(user); + newUsers.push(user) } - }); + }) - return [...state, ...newUsers]; + return [...state, ...newUsers] } if (action.type === "DELETE_USER") { - const userId = action.payload; + const userId = action.payload - const userIndex = state.findIndex((u) => u.id === userId); + const userIndex = state.findIndex((u) => u.id === userId) if (userIndex !== -1) { - state.splice(userIndex, 1); + state.splice(userIndex, 1) } - return [...state]; + return [...state] } if (action.type === "RESET") { - return []; + return [] } -}; +} function Item(props) { - const { sx, ...other } = props; + const { sx, ...other } = props return ( - ); + ) } Item.propTypes = { @@ -204,7 +204,7 @@ Item.propTypes = { PropTypes.func, PropTypes.object, ]), -}; +} @@ -218,51 +218,65 @@ let columnsData = [ { title: 'Status', field: 'status' }, { title: `${i18n.t("reports.listColumns.column1_7")}`, field: 'createdAt' }, - {title: `${i18n.t("reports.listColumns.column1_8")}`, field: 'updatedAt'}, - { title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' }]; + { title: `${i18n.t("reports.listColumns.column1_8")}`, field: 'updatedAt' }, + { title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' }] -const Report = () => { + +// function convertAndFormatDate(dateString) { +// // Check if the input date string is in the desired format +// const isDesiredFormat = /^\w{3} \w{3} \d{2} \d{4} \d{2}:\d{2}:\d{2} GMT[-+]\d{4} \([\w\s]+\)$/i.test(dateString) - const { user: userA } = useContext(AuthContext); +// if (isDesiredFormat) { +// // Convert the input date string to a JavaScript Date object +// const dateObj = new Date(dateString) + +// // Format the date to the desired string format (e.g., 'yyyy-MM-dd') in the Brazil time zone +// const formattedDate = `${dateObj.getUTCFullYear()}-${(dateObj.getUTCMonth() + 1).toString().padStart(2, '0')}-${dateObj.getUTCDate().toString().padStart(2, '0')}` + +// return formattedDate +// } + +// // If the date is not in the desired format, return the original input string +// return dateString +// } + + +const Report = () => { + + const { user: userA } = useContext(AuthContext) //-------- - const [searchParam] = useState(""); + const [searchParam] = useState("") - const [loading, setLoading] = useState(false); - const [hasMore, setHasMore] = useState(false); - const [pageNumberTickets, setTicketsPageNumber] = useState(1); - const [totalCountTickets, setTotalCountTickets] = useState(0); + const [loading, setLoading] = useState(false) + const [hasMore, setHasMore] = useState(false) + const [pageNumberTickets, setTicketsPageNumber] = useState(1) + const [totalCountTickets, setTotalCountTickets] = useState(0) - const [pageNumber, setPageNumber] = useState(1); - const [users, dispatch] = useReducer(reducer, []); + const [pageNumber, setPageNumber] = useState(1) + const [users, dispatch] = useReducer(reducer, []) const [startDate, setDatePicker1] = useState(new Date()) const [endDate, setDatePicker2] = useState(new Date()) const [userId, setUser] = useState(null) const [query, dispatchQ] = useReducer(reducerQ, []) - const [reportOption, setReport] = useState('1') const [reporList,] = useState(report) const [profile, setProfile] = useState('') - const [dataRows, setData] = useState([]); + const [dataRows, setData] = useState([]) const [onQueueStatus, setOnQueueProcessStatus] = useState(undefined) - const [csvFile, setCsvFile] = useState() - - - - + const [csvFile, setCsvFile] = useState() useEffect(() => { - dispatch({ type: "RESET" }); + dispatch({ type: "RESET" }) dispatchQ({ type: "RESET" }) setTicketsPageNumber(1) - setPageNumber(1); - }, [searchParam, profile]); - + setPageNumber(1) + }, [searchParam, profile]) useEffect(() => { @@ -272,24 +286,24 @@ const Report = () => { const fetchUsers = async () => { try { - + const { data } = await api.get("/users/", { params: { searchParam, pageNumber, profile }, - }); - - dispatch({ type: "LOAD_USERS", payload: data.users }); + }) + + dispatch({ type: "LOAD_USERS", payload: data.users }) //setLoading(false); } catch (err) { - console.log(err); + console.log(err) } - }; + } - fetchUsers(); + fetchUsers() - }, 500); - return () => clearTimeout(delayDebounceFn); - }, [searchParam, pageNumber, reportOption, profile]); + }, 500) + return () => clearTimeout(delayDebounceFn) + }, [searchParam, pageNumber, reportOption, profile]) @@ -299,42 +313,45 @@ const Report = () => { const delayDebounceFn = setTimeout(() => { - setLoading(true); + setLoading(true) const fetchQueries = async () => { try { - if (reportOption === '1') { + if (reportOption === '1') { - const { data } = await api.get("/reports/", { params: { userId, startDate, endDate, pageNumber: pageNumberTickets }, }); - - dispatchQ({ type: "LOAD_QUERY", payload: data.tickets }); + // const { data } = await api.get("/reports/", { params: { userId: userId ? userId : 0, startDate: convertAndFormatDate(startDate), endDate: convertAndFormatDate(endDate), pageNumber: pageNumberTickets }, }) - setHasMore(data.hasMore); + const { data } = await api.get("/reports/", { params: { userId, startDate, endDate, pageNumber: pageNumberTickets }, }) + + + dispatchQ({ type: "LOAD_QUERY", payload: data.tickets }) + + setHasMore(data.hasMore) setTotalCountTickets(data.count) - setLoading(false); + setLoading(false) } else if (reportOption === '2') { - const dataQuery = await api.get("/reports/user/services", { params: { userId, startDate, endDate }, }); + const dataQuery = await api.get("/reports/user/services", { params: { userId, startDate, endDate }, }) dispatchQ({ type: "RESET" }) - dispatchQ({ type: "LOAD_QUERY", payload: dataQuery.data }); + dispatchQ({ type: "LOAD_QUERY", payload: dataQuery.data }) //setLoading(false); } } catch (err) { - console.log(err); + console.log(err) } - }; + } - fetchQueries(); + fetchQueries() - }, 500); - return () => clearTimeout(delayDebounceFn); + }, 500) + return () => clearTimeout(delayDebounceFn) - }, [userId, startDate, endDate, reportOption, pageNumberTickets, totalCountTickets]); + }, [userId, startDate, endDate, reportOption, pageNumberTickets, totalCountTickets]) // Get from child 1 @@ -362,7 +379,7 @@ const Report = () => { const reportValue = (data) => { setReport(data) - + } useEffect(() => { @@ -391,7 +408,7 @@ const Report = () => { baseURL: process.env.REACT_APP_BACKEND_URL_PRIVATE, identifier: 'csv' } - }); + }) if (queryOnQueue.data) { @@ -401,52 +418,50 @@ const Report = () => { else { setOnQueueProcessStatus('empty') } - + } catch (err) { - console.log(err); + console.log(err) } - }; + } - fetchReportOnQueue(); + fetchReportOnQueue() - }, 500); - return () => clearTimeout(delayDebounceFn); + }, 500) + return () => clearTimeout(delayDebounceFn) - }, [ userA ]) - + }, [userA]) + + + const handleCSVDownload = async () => { - const handleCSVDownload = async () => { - setOnQueueProcessStatus('downloading') try { - let res = await apiBroker.get(`/reports/download/${csvFile}`, { responseType: 'blob' }); + let res = await apiBroker.get(`/reports/download/${csvFile}`, { responseType: 'blob' }) if (res) { - fileDownload(res.data, `${csvFile}`); - setOnQueueProcessStatus('empty') + fileDownload(res.data, `${csvFile}`) + setOnQueueProcessStatus('empty') } - + } catch (err) { - console.log(err); + console.log(err) } - + } - + const handleCSVMessages = () => { const fetchQueries = async () => { - try { - - // const dataQuery = await api.get("/reports/messages", { params: { userId, startDate, endDate }, }); - + try { + const querySavedOnQueue = await apiBroker.post("/reports/messages", { app: { @@ -460,26 +475,26 @@ const Report = () => { startDate: startDate, endDate: endDate } - }); + }) const onQueueStatus = querySavedOnQueue.data.queueStatus - setOnQueueProcessStatus(onQueueStatus) + setOnQueueProcessStatus(onQueueStatus) } catch (err) { - console.log(err); + console.log(err) } - }; + } - fetchQueries(); + fetchQueries() } - + useEffect(() => { - const socket = openSocket(process.env.REACT_APP_BACKEND_URL); + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) socket.on("queryOnQueueStatus", (data) => { if (data.action === 'update') { @@ -495,7 +510,7 @@ const Report = () => { }) if (reportOption === '2') { - + socket.on("onlineStatus", (data) => { @@ -503,24 +518,24 @@ const Report = () => { let dateToday = `${date[2]}-${date[1]}-${date[0]}` if (data.action === "logout" || (data.action === "update" && - ((`${startDate}` === `${endDate}`) && (`${endDate}` === `${dateToday}`) && (`${startDate}` === `${dateToday}`)))) { + ((`${startDate}` === `${endDate}`) && (`${endDate}` === `${dateToday}`) && (`${startDate}` === `${dateToday}`)))) { - dispatchQ({ type: "UPDATE_STATUS_ONLINE", payload: data.userOnlineTime }); + dispatchQ({ type: "UPDATE_STATUS_ONLINE", payload: data.userOnlineTime }) } else if (data.action === "delete") { - dispatchQ({ type: "DELETE_USER_STATUS", payload: data.userOnlineTime }); + dispatchQ({ type: "DELETE_USER_STATUS", payload: data.userOnlineTime }) } - }); + }) socket.on("user", (data) => { - if (data.action === "delete") { - dispatch({ type: "DELETE_USER", payload: +data.userId }); + if (data.action === "delete") { + dispatch({ type: "DELETE_USER", payload: +data.userId }) } - }); - + }) + } else if (reportOption === "1") { @@ -529,14 +544,14 @@ const Report = () => { } return () => { - socket.disconnect(); - }; + socket.disconnect() + } - }, [reportOption, startDate, endDate, userId, userA]); - + }, [reportOption, startDate, endDate, userId, userA]) - useEffect(() => { + + useEffect(() => { setData(query.map((column) => { return { ...column } })) @@ -545,7 +560,7 @@ const Report = () => { const handleLogouOnlineUser = async (userId) => { try { - await api.get(`/users/logout/${userId}`); + await api.get(`/users/logout/${userId}`) //toast.success(("Desloged!")); //handleDeleteRows(scheduleId) } catch (err) { @@ -553,25 +568,25 @@ const Report = () => { } - }; + } const loadMore = () => { - setTicketsPageNumber((prevState) => prevState + 1); - }; + setTicketsPageNumber((prevState) => prevState + 1) + } const handleScroll = (e) => { - if (!hasMore || loading) return; + if (!hasMore || loading) return - const { scrollTop, scrollHeight, clientHeight } = e.currentTarget; + const { scrollTop, scrollHeight, clientHeight } = e.currentTarget if (scrollHeight - (scrollTop + 1) < clientHeight) { - loadMore(); - + loadMore() + } - }; + } const renderSwitch = (param) => { @@ -589,36 +604,36 @@ const Report = () => { > {"CSV ALL"} - ); + ) case 'pending' || 'processing': return ( <> PROCESSING... - ); + ) case 'success': return ( <> - ); - case 'downloading': - return ( - <> - DOWNLOADING... - ); - + ) + case 'downloading': + return ( + <> + DOWNLOADING... + ) + default: - return (<>WAITING...); + return (<>WAITING...) } } @@ -664,7 +679,7 @@ const Report = () => { - {reportOption === '1' && + {reportOption === '1' && <> @@ -708,16 +723,16 @@ const Report = () => { if (rowData['statusOnline'].status === 'offline') { - return { color: "red" }; + return { color: "red" } } else if (rowData['statusOnline'].status === 'online') { - return { color: "green" }; + return { color: "green" } } else if (rowData['statusOnline'].status === 'logout...') { - return { color: "orange" }; + return { color: "orange" } } else if (rowData['statusOnline'].status === 'waiting...') { - return { color: "orange" }; + return { color: "orange" } } } @@ -734,7 +749,7 @@ const Report = () => { ] } - data={dataRows} + data={dataRows} actions={[ (rowData) => { @@ -748,7 +763,7 @@ const Report = () => { icon: LogoutIcon, tooltip: 'deslogar', disable: false, - onClick: (event, rowData) => { + onClick: (event, rowData) => { handleLogouOnlineUser(rowData.id) } } @@ -765,7 +780,7 @@ const Report = () => { selection: false, paging: false, padding: 'dense', - sorting: true, + sorting: true, searchFieldStyle: { width: 300, }, @@ -779,7 +794,7 @@ const Report = () => { rowStyle: { fontSize: 14, - } + } }} /> @@ -794,6 +809,6 @@ const Report = () => { )} /> ) -}; +} -export default Report; +export default Report diff --git a/frontend/src/rules.js b/frontend/src/rules.js index 10b27a5..4730e2f 100644 --- a/frontend/src/rules.js +++ b/frontend/src/rules.js @@ -7,7 +7,9 @@ const rules = { static: [ "menu-users:view", "user-view:show", - "user-modal:editQueues" + "user-modal:editQueues", + 'dashboard-view:show', + 'ticket-report:show' ] }, @@ -41,7 +43,6 @@ const rules = { 'show-icon-edit-queue', 'show-icon-delete-queue', 'space-disk-info:show', - 'drawer-admin-items:view', 'tickets-manager:showall', 'user-modal:editProfile',