chore: update reports tab for supervisors, add reminders functionality, and include user roles

Details:
- Updated the reports tab to enhance functionality for supervisors.
- Implemented a reminders feature for improved user experience.
- Added user roles to enhance user management capabilities.
pull/22/head
gustavo.pinho 2024-02-01 17:46:23 -03:00
parent 0751374fb7
commit 6a5a51ff3f
14 changed files with 99 additions and 59 deletions

View File

@ -1,14 +0,0 @@
NODE_ENV=
BACKEND_URL=http://localhost
FRONTEND_URL=http://localhost:3000
PROXY_PORT=8080
PORT=8080
DB_DIALECT=
DB_HOST=
DB_USER=
DB_PASS=
DB_NAME=
JWT_SECRET=
JWT_REFRESH_SECRET=

View File

@ -34,6 +34,7 @@ type IndexQuery = {
startDate: string; startDate: string;
endDate: string; endDate: string;
pageNumber: string; pageNumber: string;
userQueues: [];
}; };
type ReportOnQueue = { type ReportOnQueue = {
@ -52,12 +53,11 @@ export const reportUserByDateStartDateEnd = async (req: Request, res: Response):
throw new AppError("ERR_NO_PERMISSION", 403); throw new AppError("ERR_NO_PERMISSION", 403);
} }
const { userId, startDate, endDate, pageNumber } = req.query as IndexQuery const { userId, startDate, endDate, pageNumber, userQueues } = req.query as IndexQuery
console.log("userId, startDate, endDate, pageNumber: ", userId, startDate, endDate, pageNumber); console.log("userId, startDate, endDate, pageNumber: ", userId, startDate, endDate, pageNumber);
const { tickets, count, hasMore } = await ShowTicketReport({ userId, startDate, endDate, pageNumber }); const { tickets, count, hasMore } = await ShowTicketReport({ userId, startDate, endDate, pageNumber });
// console.log('kkkkkkkkkkkkkkkkkk tickets: ', JSON.stringify(tickets, null, 6)) // console.log('kkkkkkkkkkkkkkkkkk tickets: ', JSON.stringify(tickets, null, 6))
return res.status(200).json({ tickets, count, hasMore }); return res.status(200).json({ tickets, count, hasMore });

View File

@ -60,7 +60,7 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
auxUsers.push(user); auxUsers.push(user);
} }
} }
return res.json({ users: auxUsers, count, hasMore }); return res.json({ users: auxUsers, count, hasMore });
} }
@ -134,7 +134,7 @@ export const all = async (req: Request, res: Response): Promise<Response> => {
}; };
export const store = async (req: Request, res: Response): Promise<Response> => { export const store = async (req: Request, res: Response): Promise<Response> => {
const { email, password, name, profile, queueIds } = req.body; const { email, password, name, profile, positionCompany, queueIds } = req.body;
if ( if (
req.url === "/signup" && req.url === "/signup" &&
@ -149,6 +149,7 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
email, email,
password, password,
name, name,
positionCompany,
profile, profile,
queueIds queueIds
}); });

View File

@ -11,6 +11,7 @@ import { convertBytes } from "./ConvertBytes";
import { deleteScheduleByTicketIdCache } from "./SchedulingNotifyCache"; import { deleteScheduleByTicketIdCache } from "./SchedulingNotifyCache";
import SchedulingNotify from "../models/SchedulingNotify"; import SchedulingNotify from "../models/SchedulingNotify";
import Ticket from "../models/Ticket"; import Ticket from "../models/Ticket";
import User from "../models/User";
import { Sequelize, Op } from "sequelize"; import { Sequelize, Op } from "sequelize";
@ -57,20 +58,29 @@ const monitor = async () => {
status: { [Op.in]: ['open', 'pending'] } status: { [Op.in]: ['open', 'pending'] }
} }
}) })
await deleteScheduleByTicketIdCache(schedulingNotifies[i].ticketId) await deleteScheduleByTicketIdCache(schedulingNotifies[i].ticketId)
await DeleteSchedulingNotifyService(schedulingNotifies[i].id) await DeleteSchedulingNotifyService(schedulingNotifies[i].id)
if (_ticket) continue if (_ticket) continue
if (ticket.dataValues.status == 'closed') { if (ticket.dataValues.status == 'closed') {
await ticket.update({ status: 'pending' }) await ticket.update({ status: 'open' })
} }
const userId: number = ticket.getDataValue('userId');
let userN = '';
if(userId){
const useer = await User.findByPk(userId);
if(useer){
const userName: string = useer.getDataValue('name');
userN = `*${userName}:* \n`;
}
}
await new Promise(f => setTimeout(f, 3000)); await new Promise(f => setTimeout(f, 3000));
await SendWhatsAppMessage({ await SendWhatsAppMessage({
body: schedulingNotifies[i].message, ticket body: userN+schedulingNotifies[i].message, ticket
}); });
@ -80,33 +90,33 @@ const monitor = async () => {
} }
exec("df -h /", (error: any, stdout: any, stderr: any) => { // exec("df -h /", (error: any, stdout: any, stderr: any) => {
if (error) { // if (error) {
console.log(`exec error: ${error.message}`); // console.log(`exec error: ${error.message}`);
return; // return;
} // }
if (stderr) { // if (stderr) {
console.log(`exec stderr: ${stderr}`); // console.log(`exec stderr: ${stderr}`);
return; // return;
} // }
stdout = stdout.split(/\r?\n/) // stdout = stdout.split(/\r?\n/)
stdout = stdout[1].trim().split(/\s+/) // stdout = stdout[1].trim().split(/\s+/)
// DISK SPACE MONITORING // // DISK SPACE MONITORING
const io = getIO(); // const io = getIO();
io.emit("diskSpaceMonit", { // io.emit("diskSpaceMonit", {
action: "update", // action: "update",
diskSpace: { // diskSpace: {
size: stdout[1], // size: stdout[1],
used: stdout[2], // used: stdout[2],
available: stdout[3], // available: stdout[3],
use: stdout[4] // use: stdout[4]
} // }
}); // });
}); // });

View File

@ -4,6 +4,7 @@ import User from "../models/User";
interface SerializedUser { interface SerializedUser {
id: number; id: number;
name: string; name: string;
positionCompany: string;
email: string; email: string;
profile: string; profile: string;
queues: Queue[]; queues: Queue[];
@ -13,6 +14,7 @@ export const SerializeUser = (user: User): SerializedUser => {
return { return {
id: user.id, id: user.id,
name: user.name, name: user.name,
positionCompany: user.positionCompany,
email: user.email, email: user.email,
profile: user.profile, profile: user.profile,
queues: user.queues queues: user.queues

View File

@ -41,7 +41,10 @@ class User extends Model<User> {
@Default(0) @Default(0)
@Column @Column
tokenVersion: number; tokenVersion: number;
@Column
positionCompany: string;
@Default("admin") @Default("admin")
@Column @Column
profile: string; profile: string;
@ -51,7 +54,6 @@ class User extends Model<User> {
@UpdatedAt @UpdatedAt
updatedAt: Date; updatedAt: Date;
@HasMany(() => Ticket) @HasMany(() => Ticket)
tickets: Ticket[]; tickets: Ticket[];

View File

@ -10,6 +10,7 @@ import Queue from "../../models/Queue";
interface SerializedUser { interface SerializedUser {
id: number; id: number;
name: string; name: string;
positionCompany: string;
email: string; email: string;
profile: string; profile: string;
queues: Queue[]; queues: Queue[];

View File

@ -8,6 +8,7 @@ interface Request {
email: string; email: string;
password: string; password: string;
name: string; name: string;
positionCompany?: string;
queueIds?: number[]; queueIds?: number[];
profile?: string; profile?: string;
} }
@ -15,6 +16,7 @@ interface Request {
interface Response { interface Response {
email: string; email: string;
name: string; name: string;
positionCompany: string;
id: number; id: number;
profile: string; profile: string;
} }
@ -23,6 +25,7 @@ const CreateUserService = async ({
email, email,
password, password,
name, name,
positionCompany,
queueIds = [], queueIds = [],
profile = "master" profile = "master"
}: Request): Promise<Response> => { }: Request): Promise<Response> => {
@ -70,6 +73,7 @@ const CreateUserService = async ({
email, email,
password, password,
name, name,
positionCompany,
profile profile
}, },
{ include: ["queues"] } { include: ["queues"] }

View File

@ -40,7 +40,7 @@ const ListUser = async ({ profile, userId, raw, userIds, profiles }: Request): P
const users = await User.findAll({ const users = await User.findAll({
where: where_clause, where: where_clause,
raw, raw,
attributes: ["id", "name", "email"], attributes: ["id", "name", "email", "positionCompany"],
include: [ include: [
{ model: Queue, as: "queues", attributes: ["id", "name", "color"] } { model: Queue, as: "queues", attributes: ["id", "name", "color"] }

View File

@ -63,7 +63,7 @@ const ListUsersService = async ({
const { count, rows: users } = await User.findAndCountAll({ const { count, rows: users } = await User.findAndCountAll({
where: whereCondition, where: whereCondition,
attributes: ["name", "id", "email", "profile", "createdAt"], attributes: ["name", "id", "email","positionCompany", "profile", "createdAt"],
limit, limit,
offset, offset,
order: [["createdAt", "DESC"]], order: [["createdAt", "DESC"]],

View File

@ -1 +0,0 @@
REACT_APP_BACKEND_URL = http://localhost:8080/

View File

@ -84,6 +84,7 @@ const UserModal = ({ open, onClose, userId }) => {
name: "", name: "",
email: "", email: "",
password: "", password: "",
position: "",
profile: "user", profile: "user",
}; };
@ -220,10 +221,9 @@ const UserModal = ({ open, onClose, userId }) => {
fullWidth fullWidth
/> />
</div> </div>
<div className={classes.multFieldLine}>
<Field <Field
as={TextField} as={TextField}
label={i18n.t("userModal.form.email")} label='Login'
name="email" name="email"
error={touched.email && Boolean(errors.email)} error={touched.email && Boolean(errors.email)}
helperText={touched.email && errors.email} helperText={touched.email && errors.email}
@ -231,6 +231,18 @@ const UserModal = ({ open, onClose, userId }) => {
margin="dense" margin="dense"
fullWidth fullWidth
/> />
<div className={classes.multFieldLine}>
<Field
as={TextField}
label="Cargo"
autoFocus
name="positionCompany"
error={touched.name && Boolean(errors.name)}
helperText={touched.name && errors.name}
variant="outlined"
margin="dense"
fullWidth
/>
<FormControl <FormControl
variant="outlined" variant="outlined"
className={classes.formControl} className={classes.formControl}
@ -262,6 +274,7 @@ const UserModal = ({ open, onClose, userId }) => {
/> />
</FormControl> </FormControl>
</div> </div>
<Can <Can
role={loggedInUser.profile} role={loggedInUser.profile}
perform="user-modal:editQueues" perform="user-modal:editQueues"

View File

@ -221,6 +221,19 @@ let columnsData = [
{ title: `${i18n.t("reports.listColumns.column1_8")}`, field: 'updatedAt' }, { title: `${i18n.t("reports.listColumns.column1_8")}`, field: 'updatedAt' },
{ title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' }] { title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' }]
let columnsDataSuper = [
{ title: `${i18n.t("reports.listColumns.column1_1")}`, field: 'whatsapp.name' },
{ title: `${i18n.t("reports.listColumns.column1_2")}`, field: 'user.name' },
{ title: `${i18n.t("reports.listColumns.column0_3")}`, field: 'contact.name' },
{ title: `${i18n.t("reports.listColumns.column1_5")}`, field: 'queue.name' },
{ 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' }
]
// function convertAndFormatDate(dateString) { // function convertAndFormatDate(dateString) {
@ -245,7 +258,6 @@ let columnsData = [
const Report = () => { const Report = () => {
const { user: userA } = useContext(AuthContext) const { user: userA } = useContext(AuthContext)
//-------- //--------
const [searchParam] = useState("") const [searchParam] = useState("")
@ -316,21 +328,28 @@ const Report = () => {
setLoading(true) setLoading(true)
const fetchQueries = async () => { const fetchQueries = async () => {
try { try {
if (reportOption === '1') { if (reportOption === '1') {
// const { data } = await api.get("/reports/", { params: { userId: userId ? userId : 0, startDate: convertAndFormatDate(startDate), endDate: convertAndFormatDate(endDate), pageNumber: pageNumberTickets }, }) // const { data } = await api.get("/reports/", { params: { userId: userId ? userId : 0, startDate: convertAndFormatDate(startDate), endDate: convertAndFormatDate(endDate), pageNumber: pageNumberTickets }, })
const { data } = await api.get("/reports/", { params: { userId, startDate, endDate, pageNumber: pageNumberTickets }, }) const { data } = await api.get("/reports/", { params: { userId, startDate, endDate, pageNumber: pageNumberTickets }, userQueues: userA.queues})
let ticketsQueue = data.tickets;
let userQueues = userA.queues;
let filterQueuesTickets = [];
if(userQueues.length > 1){
filterQueuesTickets = ticketsQueue.filter(ticket => userQueues.some(queue => queue.name === ticket.queue.name));
}else if(userQueues.length > 0) {
filterQueuesTickets = ticketsQueue.filter(ticket => ticket.queue.name === userQueues[0].name);
}
data.tickets = filterQueuesTickets;
dispatchQ({ type: "LOAD_QUERY", payload: data.tickets }) dispatchQ({ type: "LOAD_QUERY", payload: data.tickets })
setHasMore(data.hasMore) setHasMore(data.hasMore)
setTotalCountTickets(data.count) setTotalCountTickets(data.count)
setLoading(false) setLoading(false)
} }
else if (reportOption === '2') { else if (reportOption === '2') {
@ -684,7 +703,7 @@ const Report = () => {
<> <>
<MTable data={query} <MTable data={query}
columns={columnsData} columns={userA.profile !== 'supervisor' ?columnsData:columnsDataSuper}
hasChild={true} hasChild={true}
removeClickRow={false} removeClickRow={false}

View File

@ -267,7 +267,9 @@ const Users = () => {
<TableCell align="center"> <TableCell align="center">
{i18n.t("users.table.profile")} {i18n.t("users.table.profile")}
</TableCell> </TableCell>
<TableCell align="center">
Cargo
</TableCell>
<TableCell align="center"> <TableCell align="center">
{i18n.t("users.table.actions")} {i18n.t("users.table.actions")}
</TableCell> </TableCell>
@ -281,6 +283,7 @@ const Users = () => {
<TableCell align="center">{user.name}</TableCell> <TableCell align="center">{user.name}</TableCell>
<TableCell align="center">{user.email}</TableCell> <TableCell align="center">{user.email}</TableCell>
<TableCell align="center">{user.profile}</TableCell> <TableCell align="center">{user.profile}</TableCell>
<TableCell align="center">{user.positionCompany}</TableCell>
<TableCell align="center"> <TableCell align="center">