diff --git a/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts b/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts index 3a7cf0c..d6b28db 100644 --- a/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts +++ b/backend/src/services/StatusChatEndService/CountStatusChatEndService.ts @@ -14,13 +14,26 @@ const CountStatusChatEndService = async ( endDate: string, queueIds?: number[] ) => { - const countStatusChatEnd: any = await sequelize.query( + let countStatusChatEnd: any + + if(queueIds && queueIds.length > 0){ + countStatusChatEnd = 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' + AND t.queueId IN (${queueIds}) + group by s.id;`, + { type: QueryTypes.SELECT } + ); + } + else{ + countStatusChatEnd = 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' - AND t.queueId IN (${queueIds}) - group by s.id;`, - { type: QueryTypes.SELECT } - ); + t.statusChatEndId = s.id and DATE(t.createdAt) BETWEEN '${startDate} 00:00:00.000000' AND '${endDate} 23:59:59.999999' + group by s.id;`, + { type: QueryTypes.SELECT } + ); + } + return countStatusChatEnd; }; diff --git a/backend/src/services/TicketServices/ShowTicketReport.ts b/backend/src/services/TicketServices/ShowTicketReport.ts index 1c4b1ea..a3aa41e 100644 --- a/backend/src/services/TicketServices/ShowTicketReport.ts +++ b/backend/src/services/TicketServices/ShowTicketReport.ts @@ -98,6 +98,7 @@ const ShowTicketReport = async ({ "id", "status", "statusChatEnd", + "isRemote", [ Sequelize.fn( "DATE_FORMAT", diff --git a/backend/src/services/UserServices/ListUserParamiterService.ts b/backend/src/services/UserServices/ListUserParamiterService.ts index 2198936..de476cd 100644 --- a/backend/src/services/UserServices/ListUserParamiterService.ts +++ b/backend/src/services/UserServices/ListUserParamiterService.ts @@ -59,12 +59,18 @@ const ListUser = async ({ where_clause = { id: { [Op.in]: userIds } }; - } else if (profiles) { + } + else if (profiles && userIdInQueues.length > 0) { where_clause = { profile: { [Op.in]: profiles }, id: {[Op.in]: userIdInQueues} }; } + else if (profiles) { + where_clause = { + profile: { [Op.in]: profiles }, + }; + } const users = await User.findAll({ where: where_clause, diff --git a/backend/src/services/UserServices/UpdateUserService.ts b/backend/src/services/UserServices/UpdateUserService.ts index a4f88f4..2f721bb 100644 --- a/backend/src/services/UserServices/UpdateUserService.ts +++ b/backend/src/services/UserServices/UpdateUserService.ts @@ -12,7 +12,7 @@ interface UserData { positionId?: string; profile?: string; queueIds?: number[]; - transferToOtherQueues: boolean; + transferToOtherQueues?: boolean; } interface Request { diff --git a/frontend/src/components/TicketsList/index.js b/frontend/src/components/TicketsList/index.js index 9754d79..cbc0689 100644 --- a/frontend/src/components/TicketsList/index.js +++ b/frontend/src/components/TicketsList/index.js @@ -15,6 +15,7 @@ import { i18n } from "../../translate/i18n" import { AuthContext } from "../../context/Auth/AuthContext" import { SearchTicketContext } from "../../context/SearchTicket/SearchTicket" +import { ticketsContext } from "../../context/TicketsProvider/TicketsProvider" const useStyles = makeStyles(theme => ({ ticketsListWrapper: { @@ -184,6 +185,8 @@ const TicketsList = (props) => { const { user } = useContext(AuthContext) const { searchTicket } = useContext(SearchTicketContext) + const { setTickets } = useContext(ticketsContext) + useEffect(() => { @@ -197,7 +200,7 @@ const TicketsList = (props) => { searchParam, searchParamContent, status, - showAll, + showAll, queueIds: JSON.stringify(selectedQueueIds), tab, unlimited: status === 'open' ? "all" : "false" @@ -311,6 +314,12 @@ const TicketsList = (props) => { if (typeof updateCount === "function") { updateCount(ticketsList.length) } + if (ticketsList && status === "pending"){ + setTickets(ticketsList) + } + // else{ + // setTickets([]) + // } // eslint-disable-next-line react-hooks/exhaustive-deps }, [ticketsList]) diff --git a/frontend/src/components/TicketsManager/index.js b/frontend/src/components/TicketsManager/index.js index 21e8a87..b3e5cf9 100644 --- a/frontend/src/components/TicketsManager/index.js +++ b/frontend/src/components/TicketsManager/index.js @@ -1,42 +1,45 @@ -import React, { useContext, useEffect, useRef, useState } from "react"; +import React, { useContext, useEffect, useRef, useState } from "react" -import { makeStyles } from "@material-ui/core/styles"; -import { IconButton } from "@mui/material"; -import Paper from "@material-ui/core/Paper"; -import InputBase from "@material-ui/core/InputBase"; -import Tabs from "@material-ui/core/Tabs"; -import Tab from "@material-ui/core/Tab"; -import Badge from "@material-ui/core/Badge"; +import { makeStyles } from "@material-ui/core/styles" +import { IconButton } from "@mui/material" +import Paper from "@material-ui/core/Paper" +import InputBase from "@material-ui/core/InputBase" +import Tabs from "@material-ui/core/Tabs" +import Tab from "@material-ui/core/Tab" +import Badge from "@material-ui/core/Badge" -import Tooltip from "@material-ui/core/Tooltip"; +import Tooltip from "@material-ui/core/Tooltip" -import SearchIcon from "@material-ui/icons/Search"; -import MoveToInboxIcon from "@material-ui/icons/MoveToInbox"; -import CheckBoxIcon from "@material-ui/icons/CheckBox"; -import MenuIcon from "@material-ui/icons/Menu"; -import FindInPageIcon from '@material-ui/icons/FindInPage'; +import SearchIcon from "@material-ui/icons/Search" +import MoveToInboxIcon from "@material-ui/icons/MoveToInbox" +import CheckBoxIcon from "@material-ui/icons/CheckBox" +import MenuIcon from "@material-ui/icons/Menu" +import FindInPageIcon from '@material-ui/icons/FindInPage' -import FormControlLabel from "@material-ui/core/FormControlLabel"; -import Switch from "@material-ui/core/Switch"; +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"; -import TabPanel from "../TabPanel"; +import NewTicketModal from "../NewTicketModal" +import TicketsList from "../TicketsList" +import TabPanel from "../TabPanel" -import { i18n } from "../../translate/i18n"; -import { AuthContext } from "../../context/Auth/AuthContext"; -import { Can } from "../Can"; -import TicketsQueueSelect from "../TicketsQueueSelect"; -import { Button } from "@material-ui/core"; +import { i18n } from "../../translate/i18n" +import { AuthContext } from "../../context/Auth/AuthContext" +import { Can } from "../Can" +import TicketsQueueSelect from "../TicketsQueueSelect" +import { Button } from "@material-ui/core" -import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketHeaderOption"; +import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketHeaderOption" -import { SearchTicketContext } from "../../context/SearchTicket/SearchTicket"; +import { SearchTicketContext } from "../../context/SearchTicket/SearchTicket" import useTickets from "../../hooks/useTickets" -import api from "../../services/api"; -import toastError from "../../errors/toastError"; +import api from "../../services/api" +import toastError from "../../errors/toastError" + +import { ticketsContext } from "../../context/TicketsProvider/TicketsProvider" + const useStyles = makeStyles((theme) => ({ ticketsWrapper: { @@ -128,147 +131,114 @@ const useStyles = makeStyles((theme) => ({ hide: { display: "none !important", }, -})); +})) const DEFAULT_SEARCH_PARAM = { searchParam: "", searchParamContent: "" } const TicketsManager = () => { - const { tabOption, setTabOption } = useContext(TabTicketContext); + const { tabOption, setTabOption } = useContext(TabTicketContext) const { setSearchTicket } = useContext(SearchTicketContext) - const classes = useStyles(); + const classes = useStyles() - const [searchParam, setSearchParam] = useState(DEFAULT_SEARCH_PARAM); - const [tab, setTab] = useState("open"); - const [tabOpen, setTabOpen] = useState("open"); - const [newTicketModalOpen, setNewTicketModalOpen] = useState(false); - const [showAllTickets, setShowAllTickets] = useState(false); - const { user } = useContext(AuthContext); + const [searchParam, setSearchParam] = useState(DEFAULT_SEARCH_PARAM) + const [tab, setTab] = useState("open") + const [tabOpen, setTabOpen] = useState("open") + const [newTicketModalOpen, setNewTicketModalOpen] = useState(false) + const [showAllTickets, setShowAllTickets] = useState(false) + const { user, setting, getSettingValue } = useContext(AuthContext) - const [openCount, setOpenCount] = useState(0); - const [pendingCount, setPendingCount] = useState(0); + const [openCount, setOpenCount] = useState(0) + const [pendingCount, setPendingCount] = useState(0) - const userQueueIds = user.queues.map((q) => q.id); - const [selectedQueueIds, setSelectedQueueIds] = useState(userQueueIds || []); + const userQueueIds = user.queues.map((q) => q.id) + const [selectedQueueIds, setSelectedQueueIds] = useState(userQueueIds || []) const [showContentSearch, setShowContentSearch] = useState(false) - const searchInputRef = useRef(); - const searchContentInputRef = useRef(); - const [inputSearch, setInputSearch] = useState(''); + const searchInputRef = useRef() + const searchContentInputRef = useRef() + const [inputSearch, setInputSearch] = useState('') const [inputContentSearch, setInputContentSearch] = useState("") const [openTooltipSearch, setOpenTooltipSearch] = useState(false) - const [waitingTime, setWaitingTime] = useState('00:00'); - const [tickets, setTickets] = useState([]); + const [waitingTime, setWaitingTime] = useState('00:00') + // const [tickets, setTickets] = useState([]); const [settings, setSettings] = useState([]) - let searchTimeout; - let searchContentTimeout; + let searchTimeout + let searchContentTimeout + + const { tickets, } = useContext(ticketsContext) useEffect(() => { - if (user.profile.toUpperCase() === "ADMIN" || - user.profile.toUpperCase() === "SUPERVISOR" || - user.profile.toUpperCase() === "MASTER") { - setShowAllTickets(true); + setSettings(setting) + }, [setting]) + + useEffect(() => { + if (user.profile.toUpperCase() === "ADMIN" || + user.profile.toUpperCase() === "SUPERVISOR" || + user.profile.toUpperCase() === "MASTER") { + setShowAllTickets(true) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, []) useEffect(() => { if (tab === "search") { - searchInputRef.current.focus(); + searchInputRef.current.focus() } setTabOption(tab) - }, [tab, setTabOption]); + }, [tab, setTabOption]) useEffect(() => { - const fetchSession = async () => { - try { - const { data } = await api.get('/settings') - setSettings(data.settings) - } catch (err) { - toastError(err) - } - } - fetchSession() - }, []) - const getSettingValue = (key) => { - const { value } = settings.find((s) => s.key === key) - return value - } + if (settings?.length > 0 && getSettingValue('waitingTimeTickets') !== 'enabled') return - const fetchTickets = async () =>{ - try { - const { data } = await api.get("/tickets", { - params: { - status: 'pending', - queueIds: JSON.stringify(selectedQueueIds) - }, - }); - setTickets(data.tickets); - } catch (err) { - toastError(err); - } - - } - useEffect(() => { - if(settings?.length > 0 && getSettingValue('waitingTimeTickets') === 'enabled') { - fetchTickets(); - const intervalId = setInterval(fetchTickets, 55000); - const socket = openSocket(process.env.REACT_APP_BACKEND_URL) - - socket.on("ticketStatus", (data) => { - if (data.action === "update") { - fetchTickets(); - } - }) - return () => { - socket.disconnect() - clearInterval(intervalId) - } - } - }, [selectedQueueIds, settings]); - - useEffect(() => { - const calculateAverageTime = () => { - if(tickets.length > 0){ - const now = new Date(); + const calculateAverageTime = () => { + if (tickets.length > 0) { + const now = new Date() const differenceTime = tickets?.map(ticket => { - const updatedAt = new Date(ticket.updatedAt); - const difference = now - updatedAt; - return difference; - }); - const sumDifferences = differenceTime.reduce((total, difference) => total + difference, 0); - const averageTimeMilliseconds = sumDifferences / tickets?.length; - let hours = Math.floor(averageTimeMilliseconds / 3600000); - const minutes = Math.floor((averageTimeMilliseconds % 3600000) / 60000); - - let days = hours >= 24 ? parseInt(hours/24) : ''; - - if(days != '') hours = hours - (24*days); - - const averageTimeFormated = `${days != '' ? `${days}d ` : days}${hours.toString().padStart(2, '0')}h${minutes.toString().padStart(2, '0')}`; - - return averageTimeFormated; - }else return '00:00'; - } - - setWaitingTime(calculateAverageTime()); - },[tickets]); - + const createdAt = new Date(ticket.createdAt) + const difference = now - createdAt + return difference + }) + const sumDifferences = differenceTime.reduce((total, difference) => total + difference, 0) + const averageTimeMilliseconds = sumDifferences / tickets?.length + let hours = Math.floor(averageTimeMilliseconds / 3600000) + const minutes = Math.floor((averageTimeMilliseconds % 3600000) / 60000) + + let days = hours >= 24 ? parseInt(hours / 24) : '' + + if (days != '') hours = hours - (24 * days) + + const averageTimeFormated = `${days != '' ? `${days}d ` : days}${hours.toString().padStart(2, '0')}h${minutes.toString().padStart(2, '0')}` + + return averageTimeFormated + } else return '00:00' + } + + setWaitingTime(calculateAverageTime()) + + const intervalId = setInterval(() => { + setWaitingTime(calculateAverageTime()) + }, 10000) + + return () => clearInterval(intervalId) + + }, [tickets]) + useEffect(() => { // clearTimeout(searchContentTimeout); // setSearchParam(prev => ({ ...prev, searchParamContent: "" })) - if (!inputContentSearch) return + if (!inputContentSearch) return if (!searchContentTimeout) return @@ -278,12 +248,12 @@ const TicketsManager = () => { // }, 500); - clearTimeout(searchContentTimeout); + clearTimeout(searchContentTimeout) - setSearchParam(prev => ({ ...prev, searchParamContent: "" })) + setSearchParam(prev => ({ ...prev, searchParamContent: "" })) - }, [inputContentSearch, searchContentTimeout]); + }, [inputContentSearch, searchContentTimeout]) useEffect(() => { @@ -291,16 +261,16 @@ const TicketsManager = () => { if (tabOption === 'open') { setTabOption('') - setSearchParam(DEFAULT_SEARCH_PARAM); - setInputSearch(''); + setSearchParam(DEFAULT_SEARCH_PARAM) + setInputSearch('') setInputContentSearch('') - setTab("open"); - return; + setTab("open") + return } }, [tabOption, setTabOption]) - + const removeExtraSpace = (str) => { str = str.replace(/^\s+/g, '') @@ -315,14 +285,14 @@ const TicketsManager = () => { setSearchTicket(searchParam.searchParam) - clearTimeout(searchTimeout); + clearTimeout(searchTimeout) if (searchedTerm === "") { setSearchParam(prev => ({ ...prev, searchParam: searchedTerm })) setInputSearch(searchedTerm) setShowContentSearch(false) - setTab("open"); - return; + setTab("open") + return } if (searchedTerm.length < 4) { @@ -333,22 +303,22 @@ const TicketsManager = () => { searchTimeout = setTimeout(() => { - setSearchParam(prev => ({ ...prev, searchParam: searchedTerm })); + setSearchParam(prev => ({ ...prev, searchParam: searchedTerm })) - }, 500); - }; + }, 500) + } const handleContentSearch = e => { let searchedContentText = removeExtraSpace(e.target.value.toLowerCase()) - setInputContentSearch(searchedContentText) - + setInputContentSearch(searchedContentText) + searchContentTimeout = setTimeout(() => { - setSearchParam(prev => ({ ...prev, searchParamContent: searchedContentText })); + setSearchParam(prev => ({ ...prev, searchParamContent: searchedContentText })) - }, 500); + }, 500) } @@ -366,18 +336,18 @@ const TicketsManager = () => { } const handleChangeTab = (e, newValue) => { - setTab(newValue); - }; + setTab(newValue) + } const handleChangeTabOpen = (e, newValue) => { - setTabOpen(newValue); - }; + setTabOpen(newValue) + } const applyPanelStyle = (status) => { if (tabOpen !== status) { - return { width: 0, height: 0 }; + return { width: 0, height: 0 } } - }; + } return ( @@ -534,14 +504,22 @@ const TicketsManager = () => { value={"pending"} />{ (settings?.length > 0 && getSettingValue('waitingTimeTickets') === 'enabled') && - - - - + + + + {/* */} + + + + } @@ -580,7 +558,7 @@ const TicketsManager = () => { - ); -}; + ) +} -export default TicketsManager; \ No newline at end of file +export default TicketsManager \ No newline at end of file diff --git a/frontend/src/context/TicketsProvider/TicketsProvider.js b/frontend/src/context/TicketsProvider/TicketsProvider.js new file mode 100644 index 0000000..11b6ec9 --- /dev/null +++ b/frontend/src/context/TicketsProvider/TicketsProvider.js @@ -0,0 +1,17 @@ +import React, { useState, createContext } from "react" + +const ticketsContext = createContext() + + +const TicketsProvider = ({ children }) => { + + const [tickets, setTickets] = useState(0) + + return ( + + {children} + + ) +} + +export { ticketsContext, TicketsProvider } \ No newline at end of file diff --git a/frontend/src/pages/Dashboard/index.js b/frontend/src/pages/Dashboard/index.js index 3178c19..3fa4e2d 100644 --- a/frontend/src/pages/Dashboard/index.js +++ b/frontend/src/pages/Dashboard/index.js @@ -262,7 +262,7 @@ const Dashboard = () => { const [ticketsStatus, setTicktsStatus] = useState({ open: 0, openAll: 0, pending: 0, closed: 0 }) const [ticketStatusChatEnd, setTicketStatusChatEnd] = useState([]) - const userQueueIds = user.queues.map((q) => q.id); + const userQueueIds = user.queues?.map((q) => q.id); const [selectedQueue, setSelectedQueue] = useState(userQueueIds || []); useEffect(() => { diff --git a/frontend/src/pages/Report/index.js b/frontend/src/pages/Report/index.js index 5e7d6fe..8ddc8cd 100644 --- a/frontend/src/pages/Report/index.js +++ b/frontend/src/pages/Report/index.js @@ -224,6 +224,7 @@ Item.propTypes = { let columnsData = [ + { title: `Tipo`, field: 'isRemote' }, { 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_4")}`, field: 'contact.number' }, @@ -241,6 +242,8 @@ let columnsData = [ ] let columnsDataSuper = [ + { title: `Tipo`, field: 'isRemote' }, + { title: `${i18n.t("reports.listColumns.column1_0")}`, field: 'isRemote' }, { 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' }, @@ -375,11 +378,14 @@ const Report = () => { filterQueuesTickets = ticketsQueue.filter(ticket => ticket?.queue?.name === userQueues[0]?.name) } data.tickets = filterQueuesTickets - const tickets = data.tickets.map(ticket => ({ + const tickets = data.tickets.map(ticket => { + ticket.isRemote = ticket.isRemote ? 'Remoto' : 'Comum'; + return ({ ...ticket, messagesToFilter: ticket.messages.map(message => message.body).join(' '), link: `${process.env.REACT_APP_FRONTEND_URL}/tickets/${ticket.id}` - })) + }) + }) dispatchQ({ type: "LOAD_QUERY", payload: tickets }) setHasMore(data.hasMore) setTotalCountTickets(data.count) diff --git a/frontend/src/pages/Tickets/index.js b/frontend/src/pages/Tickets/index.js index 21a254d..0900471 100644 --- a/frontend/src/pages/Tickets/index.js +++ b/frontend/src/pages/Tickets/index.js @@ -11,6 +11,7 @@ import { i18n } from "../../translate/i18n"; import Hidden from "@material-ui/core/Hidden"; import { SearchTicketProvider } from "../../context/SearchTicket/SearchTicket"; +import { TicketsProvider } from "../../context/TicketsProvider/TicketsProvider" const useStyles = makeStyles((theme) => ({ chatContainer: { @@ -82,7 +83,9 @@ const Chat = () => { } > - + + +