diff --git a/backend/src/controllers/ReportController.ts b/backend/src/controllers/ReportController.ts index d7d70b3..36c0c7c 100644 --- a/backend/src/controllers/ReportController.ts +++ b/backend/src/controllers/ReportController.ts @@ -65,7 +65,7 @@ export const reportUserByDateStartDateEnd = async ( endDate, pageNumber, createdOrUpdated, - queueId + queueId, }); const queues = await Queue.findAll({ attributes: ["id", "name"] }); @@ -84,11 +84,12 @@ export const reportUserService = async ( ) { throw new AppError("ERR_NO_PERMISSION", 403); } - const { userId, startDate, endDate } = req.query as IndexQuery; + const { userId, startDate, endDate, userQueues} = req.query as IndexQuery; // let usersProfile = await ListUserParamiterService({ profile: 'user' }) let usersProfile = await ListUserParamiterService({ profiles: ["user", "supervisor"], + userQueues: userQueues ? userQueues : undefined, raw: true }); @@ -351,15 +352,18 @@ export const reportTicksCountByStatusChatEnds = async ( throw new AppError("ERR_NO_PERMISSION", 403); } - const { startDate, endDate } = req.query as IndexQuery; + const { startDate, endDate, userQueues } = req.query as IndexQuery; const dateToday = splitDateTime( new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR })) ); + + const queueIds = userQueues ? userQueues.map(queue => parseInt(queue)) : []; const reportStatusChatEnd = await CountStatusChatEndService( startDate || dateToday.fullDate, - endDate || dateToday.fullDate + endDate || dateToday.fullDate, + queueIds ); return res.status(200).json({ reportStatusChatEnd }); diff --git a/backend/src/controllers/TicketController.ts b/backend/src/controllers/TicketController.ts index 272bc10..c4a59a8 100644 --- a/backend/src/controllers/TicketController.ts +++ b/backend/src/controllers/TicketController.ts @@ -362,9 +362,8 @@ export const show = async (req: Request, res: Response): Promise => { export const count = async (req: Request, res: Response): Promise => { // type indexQ = { status: string; date?: string; }; - const { status, date } = req.query as IndexQuery; - - const ticketCount = await CountTicketService(status, date); + const { status, date, queueIds } = req.query as IndexQuery; + const ticketCount = await CountTicketService(status, date, queueIds); return res.status(200).json(ticketCount); }; diff --git a/backend/src/controllers/UserController.ts b/backend/src/controllers/UserController.ts index 7bfcf5f..20dc4ee 100644 --- a/backend/src/controllers/UserController.ts +++ b/backend/src/controllers/UserController.ts @@ -100,7 +100,7 @@ export const index = async (req: Request, res: Response): Promise => { // }; export const all = async (req: Request, res: Response): Promise => { - let { userId, profile }: any = req.query as IndexQuery; + let { userId, profile, transferToOtherQueues }: any = req.query as IndexQuery; console.log( "userId: ", @@ -111,7 +111,7 @@ export const all = async (req: Request, res: Response): Promise => { getSettingValue("queueTransferByWhatsappScope")?.value ); - if (getSettingValue("queueTransferByWhatsappScope")?.value == "enabled") { + if (getSettingValue("queueTransferByWhatsappScope")?.value == "enabled" && !transferToOtherQueues) { if (!userId) return res.json({ users: [], queues: [] }); const obj = await ListUserByWhatsappQueuesService( @@ -145,7 +145,8 @@ export const store = async (req: Request, res: Response): Promise => { profile, positionCompany, positionId, - queueIds + queueIds, + transferToOtherQueues } = req.body; console.log("===========> req.url: ", req.url); @@ -172,7 +173,8 @@ export const store = async (req: Request, res: Response): Promise => { positionCompany, positionId, profile, - queueIds + queueIds, + transferToOtherQueues }); if (user) { diff --git a/backend/src/database/migrations/20240425190416-add-transfer-to-other-queues-to-users.ts b/backend/src/database/migrations/20240425190416-add-transfer-to-other-queues-to-users.ts new file mode 100644 index 0000000..8a653a8 --- /dev/null +++ b/backend/src/database/migrations/20240425190416-add-transfer-to-other-queues-to-users.ts @@ -0,0 +1,15 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Users", "transferToOtherQueues", { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Users", "transferToOtherQueues"); + } +}; \ No newline at end of file diff --git a/backend/src/models/User.ts b/backend/src/models/User.ts index 1729d96..f4db2f7 100644 --- a/backend/src/models/User.ts +++ b/backend/src/models/User.ts @@ -51,6 +51,9 @@ class User extends Model { @Column secondaryId: string; + @Column + transferToOtherQueues: boolean; + @Default("admin") @Column profile: string; diff --git a/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts b/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts index bffbb5d..3a7cf0c 100644 --- a/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts +++ b/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts @@ -11,13 +11,14 @@ const { QueryTypes } = require("sequelize"); const CountStatusChatEndService = async ( startDate: string, - endDate: string + endDate: string, + queueIds?: number[] ) => { - const countStatusChatEnd: any = await sequelize.query( `select t.id, s.name, count(t.id) as count from Tickets t join StatusChatEnds s on -t.statusChatEndId = s.id and DATE(t.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' -group by s.id;`, + t.statusChatEndId = s.id and DATE(t.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + AND t.queueId IN (${queueIds}) + group by s.id;`, { type: QueryTypes.SELECT } ); diff --git a/backend/src/services/TicketServices/CountTicketService.ts b/backend/src/services/TicketServices/CountTicketService.ts index 2cdde93..08f9de3 100644 --- a/backend/src/services/TicketServices/CountTicketService.ts +++ b/backend/src/services/TicketServices/CountTicketService.ts @@ -9,7 +9,7 @@ import ptBR from 'date-fns/locale/pt-BR'; import { splitDateTime } from "../../helpers/SplitDateTime"; const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }))) -const CountTicketService = async (status: string, date?: string): Promise => { +const CountTicketService = async (status: string, date?: string, queueIds?: string): Promise => { let where_clause = {} @@ -30,8 +30,8 @@ const CountTicketService = async (status: string, date?: string): Promise = // } } - - where_clause = { ...where_clause, status: status } + if(queueIds) where_clause = { ...where_clause, status: status, queueId: { [Op.or]: [queueIds, null] } }; + else where_clause = { ...where_clause, status: status}; const ticket = await Ticket.findAll({ where: where_clause, diff --git a/backend/src/services/TicketServices/ListTicketsService.ts b/backend/src/services/TicketServices/ListTicketsService.ts index 4d31e3f..1b8d881 100644 --- a/backend/src/services/TicketServices/ListTicketsService.ts +++ b/backend/src/services/TicketServices/ListTicketsService.ts @@ -204,7 +204,8 @@ const ListTicketsService = async ({ whereCondition = { createdAt: { [Op.between]: [+startOfDay(parseISO(date)), +endOfDay(parseISO(date))] - } + }, + queueId: { [Op.or]: [queueIds, null] }, }; } diff --git a/backend/src/services/UserServices/CreateUserService.ts b/backend/src/services/UserServices/CreateUserService.ts index f866a9b..9e4117c 100644 --- a/backend/src/services/UserServices/CreateUserService.ts +++ b/backend/src/services/UserServices/CreateUserService.ts @@ -14,6 +14,7 @@ interface Request { queueIds?: number[]; profile?: string; ignoreThrow?: boolean; + transferToOtherQueues?: boolean; } interface Response { @@ -23,6 +24,7 @@ interface Response { positionId: string; id: number; profile: string; + transferToOtherQueues: boolean; } const CreateUserService = async ({ @@ -33,7 +35,8 @@ const CreateUserService = async ({ positionId, queueIds = [], profile = "master", - ignoreThrow = false + ignoreThrow = false, + transferToOtherQueues }: Request): Promise => { try { const schema = Yup.object().shape({ @@ -84,7 +87,8 @@ const CreateUserService = async ({ name, positionCompany, positionId: !positionId ? null : positionId, - profile + profile, + transferToOtherQueues: transferToOtherQueues? transferToOtherQueues : false }, { include: ["queues"] } ); diff --git a/backend/src/services/UserServices/ListUserParamiterService.ts b/backend/src/services/UserServices/ListUserParamiterService.ts index 2d92498..2198936 100644 --- a/backend/src/services/UserServices/ListUserParamiterService.ts +++ b/backend/src/services/UserServices/ListUserParamiterService.ts @@ -10,6 +10,7 @@ interface Request { profiles?: Array; raw?: boolean; userIds?: string | number; + userQueues?: string[]; } const ListUser = async ({ @@ -17,10 +18,31 @@ const ListUser = async ({ userId, raw, userIds, - profiles + profiles, + userQueues: userQueuesToNumber }: Request): Promise => { let where_clause = {}; + let userIdInQueues: number[] = []; + + if(userQueuesToNumber !== undefined){ + let userQueues = userQueuesToNumber.map(id => parseInt(id)); + const userQueuesFiltered = await UserQueue.findAll({ + where: { queueId: { [Op.or]: [userQueues, null] } }, + order: [ + ['userId', 'ASC'] + ], + raw: true + }); + if(userQueuesFiltered) for(let queueId of userQueues){ + for(let userQueue of userQueuesFiltered){ + if(queueId == userQueue.queueId){ + const isAlready = userIdInQueues.indexOf(userQueue.userId); + if(isAlready === -1) userIdInQueues.push(userQueue.userId); + } + } + } + } if (userId && profile) { where_clause = { [Op.and]: [{ userId: userId }, { profile: profile }] @@ -39,14 +61,15 @@ const ListUser = async ({ }; } else if (profiles) { where_clause = { - profile: { [Op.in]: profiles } + profile: { [Op.in]: profiles }, + id: {[Op.in]: userIdInQueues} }; } const users = await User.findAll({ where: where_clause, raw, - attributes: ["id", "name", "email", "positionCompany"], + attributes: ["id", "name", "email", "positionCompany", "transferToOtherQueues"], include: [ { model: Queue, as: "queues", attributes: ["id", "name", "color"] } diff --git a/backend/src/services/UserServices/ListUsersService.ts b/backend/src/services/UserServices/ListUsersService.ts index a55ca5c..bfcb4d3 100644 --- a/backend/src/services/UserServices/ListUsersService.ts +++ b/backend/src/services/UserServices/ListUsersService.ts @@ -66,7 +66,8 @@ const ListUsersService = async ({ "email", "positionCompany", "profile", - "createdAt" + "createdAt", + "transferToOtherQueues" ], limit, offset, diff --git a/backend/src/services/UserServices/ShowUserService.ts b/backend/src/services/UserServices/ShowUserService.ts index 1f3d272..8b9d6c8 100644 --- a/backend/src/services/UserServices/ShowUserService.ts +++ b/backend/src/services/UserServices/ShowUserService.ts @@ -12,7 +12,8 @@ const ShowUserService = async (id: string | number): Promise => { "profile", "positionCompany", "positionId", - "tokenVersion" + "tokenVersion", + "transferToOtherQueues" ], include: [ { model: Queue, as: "queues", attributes: ["id", "name", "color"] }, diff --git a/backend/src/services/UserServices/UpdateUserService.ts b/backend/src/services/UserServices/UpdateUserService.ts index 78a5fd2..a4f88f4 100644 --- a/backend/src/services/UserServices/UpdateUserService.ts +++ b/backend/src/services/UserServices/UpdateUserService.ts @@ -12,6 +12,7 @@ interface UserData { positionId?: string; profile?: string; queueIds?: number[]; + transferToOtherQueues: boolean; } interface Request { @@ -75,7 +76,8 @@ const UpdateUserService = async ({ name, positionCompany, positionId, - queueIds = [] + queueIds = [], + transferToOtherQueues } = userData; try { @@ -90,7 +92,8 @@ const UpdateUserService = async ({ profile, positionCompany, positionId: !positionId ? null : positionId, - name + name, + transferToOtherQueues }); await user.$set("queues", queueIds); @@ -117,7 +120,8 @@ const UpdateUserService = async ({ profile: _user.profile, queues: _user.queues, positionId: _user?.positionId, - position: _user.position + position: _user.position, + transferToOtherQueues: _user.transferToOtherQueues }; return serializedUser; diff --git a/frontend/src/components/Report/SelectField/index.js b/frontend/src/components/Report/SelectField/index.js index ef14f72..b8b48c0 100644 --- a/frontend/src/components/Report/SelectField/index.js +++ b/frontend/src/components/Report/SelectField/index.js @@ -10,11 +10,19 @@ const SelectTextFields = (props) => { if (!props.textBoxFieldSelected) { props.currencies.push({ 'value': 0, 'label': '' }) } + + if(props.textBoxFieldSelected === 'All'){ + const already = props.currencies.findIndex(obj => obj.value === 'All'); + if (already === -1) { + props.currencies.push({ 'value': 'All', 'label': 'All' }) + } + } + useEffect(() => { props.func(currency) - }, [currency, props]) + }, [currency, props.textBoxFieldSelected]) const handleChange = (event) => { diff --git a/frontend/src/components/TicketsManager/index.js b/frontend/src/components/TicketsManager/index.js index ee0db3e..21e8a87 100644 --- a/frontend/src/components/TicketsManager/index.js +++ b/frontend/src/components/TicketsManager/index.js @@ -19,6 +19,7 @@ import FindInPageIcon from '@material-ui/icons/FindInPage'; import FormControlLabel from "@material-ui/core/FormControlLabel"; import Switch from "@material-ui/core/Switch"; +import openSocket from "socket.io-client" import NewTicketModal from "../NewTicketModal"; import TicketsList from "../TicketsList"; @@ -219,12 +220,18 @@ const TicketsManager = () => { useEffect(() => { if(settings?.length > 0 && getSettingValue('waitingTimeTickets') === 'enabled') { fetchTickets(); + const intervalId = setInterval(fetchTickets, 55000); + const socket = openSocket(process.env.REACT_APP_BACKEND_URL) - const intervalId = setInterval(fetchTickets, 7000); - - return () => { - clearInterval(intervalId); - }; + socket.on("ticketStatus", (data) => { + if (data.action === "update") { + fetchTickets(); + } + }) + return () => { + socket.disconnect() + clearInterval(intervalId) + } } }, [selectedQueueIds, settings]); diff --git a/frontend/src/components/TransferTicketModal/index.js b/frontend/src/components/TransferTicketModal/index.js index 472565b..02acdfc 100644 --- a/frontend/src/components/TransferTicketModal/index.js +++ b/frontend/src/components/TransferTicketModal/index.js @@ -97,7 +97,7 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => { } else { - if (settings?.find(e => e?.key === 'queueTransferByWhatsappScope')?.value === 'enabled') { + if (settings?.find(e => e?.key === 'queueTransferByWhatsappScope')?.value === 'enabled' && !user.transferToOtherQueues) { setQueues(_queues) } else { @@ -190,7 +190,7 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => { try { - if (settings?.find(e => e?.key === 'queueTransferByWhatsappScope')?.value === 'enabled') { + if (settings?.find(e => e?.key === 'queueTransferByWhatsappScope')?.value === 'enabled' && !user.transferToOtherQueues) { const { data } = await api.get(`/users/all`, { params: { userId: user.id }, }) @@ -202,7 +202,7 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => { else { const { data } = await api.get(`/users/all`, { - params: { profile: 'user' }, + params: { profile: 'user', transferToOtherQueues: user.transferToOtherQueues }, }) setUsers(data.users) diff --git a/frontend/src/components/UserModal/index.js b/frontend/src/components/UserModal/index.js index 51d660d..976fc8c 100644 --- a/frontend/src/components/UserModal/index.js +++ b/frontend/src/components/UserModal/index.js @@ -32,6 +32,7 @@ import toastError from "../../errors/toastError" import QueueSelect from "../QueueSelect" import { AuthContext } from "../../context/Auth/AuthContext" import { Can } from "../Can" +import Switch from '@mui/material/Switch' const useStyles = makeStyles(theme => ({ root: { @@ -95,6 +96,7 @@ const UserModal = ({ open, onClose, userId, }) => { const [showPassword, setShowPassword] = useState(false) const [positions, setPositions] = useState([]) const [selectedPosition, setSelectedPosition] = useState('') + const [checked, setChecked] = useState(false) useEffect(() => { const fetchUser = async () => { @@ -112,6 +114,9 @@ const UserModal = ({ open, onClose, userId, }) => { setSelectedPosition(data.positionId) else setSelectedPosition('') + + + if(data.transferToOtherQueues) setChecked(data.transferToOtherQueues); } catch (err) { toastError(err) } @@ -136,10 +141,15 @@ const UserModal = ({ open, onClose, userId, }) => { const handleClose = () => { onClose() setUser(initialState) + setChecked(false); + } + + const handleChange = (event) => { + setChecked(event.target.checked) } const handleSaveUser = async values => { - const userData = { ...values, queueIds: selectedQueueIds, positionId: selectedPosition } + const userData = { ...values, queueIds: selectedQueueIds, positionId: selectedPosition, transferToOtherQueues: checked} try { if (userId) { @@ -252,7 +262,7 @@ const UserModal = ({ open, onClose, userId, }) => { fullWidth />
- { variant="outlined" margin="dense" fullWidth - /> + /> */} + + { const theme = useTheme(); const date = useRef(new Date().toISOString()); - let { tickets } = useTickets({ date: date.current, unlimited: "current" }); + const queueIds = JSON.stringify( props.selectedQueue) || {}; + let {tickets} = useTickets({ date: date.current, unlimited: "current", queueIds }); - const [chartData, setChartData] = useState([ + const modelChar = [ { time: "08:00", amount: 0 }, { time: "09:00", amount: 0 }, { time: "10:00", amount: 0 }, @@ -35,11 +36,12 @@ const Chart = (props) => { { time: "17:00", amount: 0 }, { time: "18:00", amount: 0 }, { time: "19:00", amount: 0 }, - ]); + ] + const [chartData, setChartData] = useState(modelChar); useEffect(() => { setChartData(prevState => { - let aux = [...prevState]; + let aux = modelChar; aux.forEach(a => { tickets.forEach(ticket => { format(startOfHour(parseISO(ticket.createdAt)), "HH:mm") === a.time && a.amount++; }); diff --git a/frontend/src/pages/Dashboard/index.js b/frontend/src/pages/Dashboard/index.js index ca6fe4e..3178c19 100644 --- a/frontend/src/pages/Dashboard/index.js +++ b/frontend/src/pages/Dashboard/index.js @@ -1,4 +1,4 @@ -import React, { useContext, useReducer, useEffect, useState } from "react" +import React, { useContext, useReducer, useEffect, useState, useCallback } from "react" import { addHours, addMinutes, addSeconds, intervalToDuration } from "date-fns" @@ -11,6 +11,7 @@ import Tooltip from "@mui/material/Tooltip" import Zoom from "@mui/material/Zoom" import IconButton from "@mui/material/IconButton" import Info from "@material-ui/icons/Info" +import SelectField from "../../components/Report/SelectField" import { AuthContext } from "../../context/Auth/AuthContext" // import { i18n } from "../../translate/i18n"; @@ -254,12 +255,15 @@ const reducer = (state, action) => { } const Dashboard = () => { + const { user } = useContext(AuthContext) const classes = useStyles() const [usersOnlineInfo, dispatch] = useReducer(reducer, []) const [ticketStatusChange, setStatus] = useState() const [ticketsStatus, setTicktsStatus] = useState({ open: 0, openAll: 0, pending: 0, closed: 0 }) const [ticketStatusChatEnd, setTicketStatusChatEnd] = useState([]) - const { user } = useContext(AuthContext) + + const userQueueIds = user.queues.map((q) => q.id); + const [selectedQueue, setSelectedQueue] = useState(userQueueIds || []); useEffect(() => { dispatch({ type: "RESET" }) @@ -286,14 +290,14 @@ const Dashboard = () => { let dateToday = `${date[2]}-${date[1]}-${date[0]}` const { data } = await api.get("/reports/user/services", { - params: { userId: null, startDate: dateToday, endDate: dateToday }, + params: { userId: null, startDate: dateToday, endDate: dateToday, userQueues: selectedQueue }, }) dispatch({ type: "RESET" }) dispatch({ type: "LOAD_QUERY", payload: data.usersProfile }) const { data: ticketStatusChatEndData } = await api.get("/reports/count/statusChatEnd", { - params: { startDate: dateToday, endDate: dateToday }, + params: { startDate: dateToday, endDate: dateToday, userQueues: selectedQueue }, }) setTicketStatusChatEnd(ticketStatusChatEndData.reportStatusChatEnd) @@ -306,7 +310,7 @@ const Dashboard = () => { fetchQueries() }, 500) return () => clearTimeout(delayDebounceFn) - }, []) + }, [selectedQueue]) useEffect(() => { @@ -380,6 +384,18 @@ const Dashboard = () => { socket.disconnect() } }, []) + + const handleSelectedQueue = useCallback((queueSelected) => { + if(queueSelected !== 'All'){ + const queueIndex = user?.queues?.findIndex((q) => q.id === parseInt(queueSelected)); + const queueIds = [] + queueIds.push(user?.queues[queueIndex]?.id); + setSelectedQueue(queueIds); + }else{ + const queueIds = user?.queues?.map((queue) => queue.id); + setSelectedQueue(queueIds); + } + },[user, setSelectedQueue]) useEffect(() => { if (ticketStatusChange === "") return @@ -390,17 +406,17 @@ const Dashboard = () => { let dateToday = `${date[2]}-${date[1]}-${date[0]}` const _open = await api.get("/tickets/count", { - params: { status: "open", date: dateToday }, + params: { status: "open", date: dateToday, queueIds: selectedQueue }, }) const _closed = await api.get("/tickets/count", { - params: { status: "closed", date: dateToday }, + params: { status: "closed", date: dateToday, queueIds: selectedQueue }, }) const _pending = await api.get("/tickets/count", { - params: { status: "pending" }, + params: { status: "pending", queueIds: selectedQueue }, }) const _openAll = await api.get("/tickets/count", { - params: { status: "open" }, + params: { status: "open", queueIds: selectedQueue }, }) setTicktsStatus({ open: _open.data.count, @@ -419,7 +435,7 @@ const Dashboard = () => { fetchQueries() }, 500) return () => clearTimeout(delayDebounceFn) - }, [ticketStatusChange]) + }, [ticketStatusChange, selectedQueue]) return ( { + + { + return { 'value': obj.id, 'label': obj.name } + })} /> + { - + diff --git a/frontend/src/pages/Settings/index.js b/frontend/src/pages/Settings/index.js index 0fe21e2..097d6d4 100644 --- a/frontend/src/pages/Settings/index.js +++ b/frontend/src/pages/Settings/index.js @@ -446,6 +446,88 @@ const Settings = () => {
+
+ + + + Noficar quando entrar novo ticket na fila + + + + + +
+
+ + + + Bloquear mídias de Audio e Video + + + + + +
+ +
+ + + + Mostrar tempo de espera dos tickets aguardando + + + + + +
)} />