fix: correct code errors

Details:
- Addressed various code errors and made necessary fixes.

feat: add column 'type' to the Reports Mtable to differentiate remote tickets from regular ones

Details:
- Added a new column named 'type' to the Reports Mtable to differentiate between remote tickets and regular ones.
feat-scaling-ticket-remote-creation
gustavo-gsp 2024-04-30 16:20:48 -03:00
parent 0e8fbd8400
commit fea60cf80c
10 changed files with 209 additions and 176 deletions

View File

@ -14,13 +14,26 @@ const CountStatusChatEndService = async (
endDate: string, endDate: string,
queueIds?: number[] 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 `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' 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;`,
group by s.id;`, { type: QueryTypes.SELECT }
{ type: QueryTypes.SELECT } );
); }
return countStatusChatEnd; return countStatusChatEnd;
}; };

View File

@ -98,6 +98,7 @@ const ShowTicketReport = async ({
"id", "id",
"status", "status",
"statusChatEnd", "statusChatEnd",
"isRemote",
[ [
Sequelize.fn( Sequelize.fn(
"DATE_FORMAT", "DATE_FORMAT",

View File

@ -59,12 +59,18 @@ const ListUser = async ({
where_clause = { where_clause = {
id: { [Op.in]: userIds } id: { [Op.in]: userIds }
}; };
} else if (profiles) { }
else if (profiles && userIdInQueues.length > 0) {
where_clause = { where_clause = {
profile: { [Op.in]: profiles }, profile: { [Op.in]: profiles },
id: {[Op.in]: userIdInQueues} id: {[Op.in]: userIdInQueues}
}; };
} }
else if (profiles) {
where_clause = {
profile: { [Op.in]: profiles },
};
}
const users = await User.findAll({ const users = await User.findAll({
where: where_clause, where: where_clause,

View File

@ -12,7 +12,7 @@ interface UserData {
positionId?: string; positionId?: string;
profile?: string; profile?: string;
queueIds?: number[]; queueIds?: number[];
transferToOtherQueues: boolean; transferToOtherQueues?: boolean;
} }
interface Request { interface Request {

View File

@ -15,6 +15,7 @@ import { i18n } from "../../translate/i18n"
import { AuthContext } from "../../context/Auth/AuthContext" import { AuthContext } from "../../context/Auth/AuthContext"
import { SearchTicketContext } from "../../context/SearchTicket/SearchTicket" import { SearchTicketContext } from "../../context/SearchTicket/SearchTicket"
import { ticketsContext } from "../../context/TicketsProvider/TicketsProvider"
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
ticketsListWrapper: { ticketsListWrapper: {
@ -184,6 +185,8 @@ const TicketsList = (props) => {
const { user } = useContext(AuthContext) const { user } = useContext(AuthContext)
const { searchTicket } = useContext(SearchTicketContext) const { searchTicket } = useContext(SearchTicketContext)
const { setTickets } = useContext(ticketsContext)
useEffect(() => { useEffect(() => {
@ -311,6 +314,12 @@ const TicketsList = (props) => {
if (typeof updateCount === "function") { if (typeof updateCount === "function") {
updateCount(ticketsList.length) updateCount(ticketsList.length)
} }
if (ticketsList && status === "pending"){
setTickets(ticketsList)
}
// else{
// setTickets([])
// }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ticketsList]) }, [ticketsList])

View File

@ -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 { makeStyles } from "@material-ui/core/styles"
import { IconButton } from "@mui/material"; import { IconButton } from "@mui/material"
import Paper from "@material-ui/core/Paper"; import Paper from "@material-ui/core/Paper"
import InputBase from "@material-ui/core/InputBase"; import InputBase from "@material-ui/core/InputBase"
import Tabs from "@material-ui/core/Tabs"; import Tabs from "@material-ui/core/Tabs"
import Tab from "@material-ui/core/Tab"; import Tab from "@material-ui/core/Tab"
import Badge from "@material-ui/core/Badge"; 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 SearchIcon from "@material-ui/icons/Search"
import MoveToInboxIcon from "@material-ui/icons/MoveToInbox"; import MoveToInboxIcon from "@material-ui/icons/MoveToInbox"
import CheckBoxIcon from "@material-ui/icons/CheckBox"; import CheckBoxIcon from "@material-ui/icons/CheckBox"
import MenuIcon from "@material-ui/icons/Menu"; import MenuIcon from "@material-ui/icons/Menu"
import FindInPageIcon from '@material-ui/icons/FindInPage'; import FindInPageIcon from '@material-ui/icons/FindInPage'
import FormControlLabel from "@material-ui/core/FormControlLabel"; import FormControlLabel from "@material-ui/core/FormControlLabel"
import Switch from "@material-ui/core/Switch"; import Switch from "@material-ui/core/Switch"
import openSocket from "socket.io-client" import openSocket from "socket.io-client"
import NewTicketModal from "../NewTicketModal"; import NewTicketModal from "../NewTicketModal"
import TicketsList from "../TicketsList"; import TicketsList from "../TicketsList"
import TabPanel from "../TabPanel"; import TabPanel from "../TabPanel"
import { i18n } from "../../translate/i18n"; import { i18n } from "../../translate/i18n"
import { AuthContext } from "../../context/Auth/AuthContext"; import { AuthContext } from "../../context/Auth/AuthContext"
import { Can } from "../Can"; import { Can } from "../Can"
import TicketsQueueSelect from "../TicketsQueueSelect"; import TicketsQueueSelect from "../TicketsQueueSelect"
import { Button } from "@material-ui/core"; 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 useTickets from "../../hooks/useTickets"
import api from "../../services/api"; import api from "../../services/api"
import toastError from "../../errors/toastError"; import toastError from "../../errors/toastError"
import { ticketsContext } from "../../context/TicketsProvider/TicketsProvider"
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
ticketsWrapper: { ticketsWrapper: {
@ -128,139 +131,106 @@ const useStyles = makeStyles((theme) => ({
hide: { hide: {
display: "none !important", display: "none !important",
}, },
})); }))
const DEFAULT_SEARCH_PARAM = { searchParam: "", searchParamContent: "" } const DEFAULT_SEARCH_PARAM = { searchParam: "", searchParamContent: "" }
const TicketsManager = () => { const TicketsManager = () => {
const { tabOption, setTabOption } = useContext(TabTicketContext); const { tabOption, setTabOption } = useContext(TabTicketContext)
const { setSearchTicket } = useContext(SearchTicketContext) const { setSearchTicket } = useContext(SearchTicketContext)
const classes = useStyles(); const classes = useStyles()
const [searchParam, setSearchParam] = useState(DEFAULT_SEARCH_PARAM); const [searchParam, setSearchParam] = useState(DEFAULT_SEARCH_PARAM)
const [tab, setTab] = useState("open"); const [tab, setTab] = useState("open")
const [tabOpen, setTabOpen] = useState("open"); const [tabOpen, setTabOpen] = useState("open")
const [newTicketModalOpen, setNewTicketModalOpen] = useState(false); const [newTicketModalOpen, setNewTicketModalOpen] = useState(false)
const [showAllTickets, setShowAllTickets] = useState(false); const [showAllTickets, setShowAllTickets] = useState(false)
const { user } = useContext(AuthContext); const { user, setting, getSettingValue } = useContext(AuthContext)
const [openCount, setOpenCount] = useState(0); const [openCount, setOpenCount] = useState(0)
const [pendingCount, setPendingCount] = useState(0); const [pendingCount, setPendingCount] = useState(0)
const userQueueIds = user.queues.map((q) => q.id); const userQueueIds = user.queues.map((q) => q.id)
const [selectedQueueIds, setSelectedQueueIds] = useState(userQueueIds || []); const [selectedQueueIds, setSelectedQueueIds] = useState(userQueueIds || [])
const [showContentSearch, setShowContentSearch] = useState(false) const [showContentSearch, setShowContentSearch] = useState(false)
const searchInputRef = useRef(); const searchInputRef = useRef()
const searchContentInputRef = useRef(); const searchContentInputRef = useRef()
const [inputSearch, setInputSearch] = useState(''); const [inputSearch, setInputSearch] = useState('')
const [inputContentSearch, setInputContentSearch] = useState("") const [inputContentSearch, setInputContentSearch] = useState("")
const [openTooltipSearch, setOpenTooltipSearch] = useState(false) const [openTooltipSearch, setOpenTooltipSearch] = useState(false)
const [waitingTime, setWaitingTime] = useState('00:00'); const [waitingTime, setWaitingTime] = useState('00:00')
const [tickets, setTickets] = useState([]); // const [tickets, setTickets] = useState([]);
const [settings, setSettings] = useState([]) const [settings, setSettings] = useState([])
let searchTimeout; let searchTimeout
let searchContentTimeout; let searchContentTimeout
const { tickets, } = useContext(ticketsContext)
useEffect(() => {
setSettings(setting)
}, [setting])
useEffect(() => { useEffect(() => {
if (user.profile.toUpperCase() === "ADMIN" || if (user.profile.toUpperCase() === "ADMIN" ||
user.profile.toUpperCase() === "SUPERVISOR" || user.profile.toUpperCase() === "SUPERVISOR" ||
user.profile.toUpperCase() === "MASTER") { user.profile.toUpperCase() === "MASTER") {
setShowAllTickets(true); setShowAllTickets(true)
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, [])
useEffect(() => { useEffect(() => {
if (tab === "search") { if (tab === "search") {
searchInputRef.current.focus(); searchInputRef.current.focus()
} }
setTabOption(tab) setTabOption(tab)
}, [tab, setTabOption]); }, [tab, setTabOption])
useEffect(() => { useEffect(() => {
const fetchSession = async () => {
try {
const { data } = await api.get('/settings')
setSettings(data.settings)
} catch (err) {
toastError(err)
}
}
fetchSession()
}, [])
const getSettingValue = (key) => { if (settings?.length > 0 && getSettingValue('waitingTimeTickets') !== 'enabled') return
const { value } = settings.find((s) => s.key === key)
return value
}
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 = () => { const calculateAverageTime = () => {
if(tickets.length > 0){ if (tickets.length > 0) {
const now = new Date(); const now = new Date()
const differenceTime = tickets?.map(ticket => { const differenceTime = tickets?.map(ticket => {
const updatedAt = new Date(ticket.updatedAt); const createdAt = new Date(ticket.createdAt)
const difference = now - updatedAt; const difference = now - createdAt
return difference; return difference
}); })
const sumDifferences = differenceTime.reduce((total, difference) => total + difference, 0); const sumDifferences = differenceTime.reduce((total, difference) => total + difference, 0)
const averageTimeMilliseconds = sumDifferences / tickets?.length; const averageTimeMilliseconds = sumDifferences / tickets?.length
let hours = Math.floor(averageTimeMilliseconds / 3600000); let hours = Math.floor(averageTimeMilliseconds / 3600000)
const minutes = Math.floor((averageTimeMilliseconds % 3600000) / 60000); const minutes = Math.floor((averageTimeMilliseconds % 3600000) / 60000)
let days = hours >= 24 ? parseInt(hours/24) : ''; let days = hours >= 24 ? parseInt(hours / 24) : ''
if(days != '') hours = hours - (24*days); if (days != '') hours = hours - (24 * days)
const averageTimeFormated = `${days != '' ? `${days}d ` : days}${hours.toString().padStart(2, '0')}h${minutes.toString().padStart(2, '0')}`; const averageTimeFormated = `${days != '' ? `${days}d ` : days}${hours.toString().padStart(2, '0')}h${minutes.toString().padStart(2, '0')}`
return averageTimeFormated; return averageTimeFormated
}else return '00:00'; } else return '00:00'
} }
setWaitingTime(calculateAverageTime()); setWaitingTime(calculateAverageTime())
},[tickets]);
const intervalId = setInterval(() => {
setWaitingTime(calculateAverageTime())
}, 10000)
return () => clearInterval(intervalId)
}, [tickets])
useEffect(() => { useEffect(() => {
@ -278,12 +248,12 @@ const TicketsManager = () => {
// }, 500); // }, 500);
clearTimeout(searchContentTimeout); clearTimeout(searchContentTimeout)
setSearchParam(prev => ({ ...prev, searchParamContent: "" })) setSearchParam(prev => ({ ...prev, searchParamContent: "" }))
}, [inputContentSearch, searchContentTimeout]); }, [inputContentSearch, searchContentTimeout])
useEffect(() => { useEffect(() => {
@ -291,11 +261,11 @@ const TicketsManager = () => {
if (tabOption === 'open') { if (tabOption === 'open') {
setTabOption('') setTabOption('')
setSearchParam(DEFAULT_SEARCH_PARAM); setSearchParam(DEFAULT_SEARCH_PARAM)
setInputSearch(''); setInputSearch('')
setInputContentSearch('') setInputContentSearch('')
setTab("open"); setTab("open")
return; return
} }
}, [tabOption, setTabOption]) }, [tabOption, setTabOption])
@ -315,14 +285,14 @@ const TicketsManager = () => {
setSearchTicket(searchParam.searchParam) setSearchTicket(searchParam.searchParam)
clearTimeout(searchTimeout); clearTimeout(searchTimeout)
if (searchedTerm === "") { if (searchedTerm === "") {
setSearchParam(prev => ({ ...prev, searchParam: searchedTerm })) setSearchParam(prev => ({ ...prev, searchParam: searchedTerm }))
setInputSearch(searchedTerm) setInputSearch(searchedTerm)
setShowContentSearch(false) setShowContentSearch(false)
setTab("open"); setTab("open")
return; return
} }
if (searchedTerm.length < 4) { if (searchedTerm.length < 4) {
@ -333,10 +303,10 @@ const TicketsManager = () => {
searchTimeout = setTimeout(() => { searchTimeout = setTimeout(() => {
setSearchParam(prev => ({ ...prev, searchParam: searchedTerm })); setSearchParam(prev => ({ ...prev, searchParam: searchedTerm }))
}, 500); }, 500)
}; }
const handleContentSearch = e => { const handleContentSearch = e => {
@ -346,9 +316,9 @@ const TicketsManager = () => {
searchContentTimeout = setTimeout(() => { searchContentTimeout = setTimeout(() => {
setSearchParam(prev => ({ ...prev, searchParamContent: searchedContentText })); setSearchParam(prev => ({ ...prev, searchParamContent: searchedContentText }))
}, 500); }, 500)
} }
@ -366,18 +336,18 @@ const TicketsManager = () => {
} }
const handleChangeTab = (e, newValue) => { const handleChangeTab = (e, newValue) => {
setTab(newValue); setTab(newValue)
}; }
const handleChangeTabOpen = (e, newValue) => { const handleChangeTabOpen = (e, newValue) => {
setTabOpen(newValue); setTabOpen(newValue)
}; }
const applyPanelStyle = (status) => { const applyPanelStyle = (status) => {
if (tabOpen !== status) { if (tabOpen !== status) {
return { width: 0, height: 0 }; return { width: 0, height: 0 }
} }
}; }
return ( return (
<Paper elevation={0} variant="outlined" className={classes.ticketsWrapper}> <Paper elevation={0} variant="outlined" className={classes.ticketsWrapper}>
@ -534,14 +504,22 @@ const TicketsManager = () => {
value={"pending"} value={"pending"}
/>{ />{
(settings?.length > 0 && getSettingValue('waitingTimeTickets') === 'enabled') && (settings?.length > 0 && getSettingValue('waitingTimeTickets') === 'enabled') &&
<span style={{display: 'flex', alignItems: 'center', flexDirection:'column', justifyContent: 'flex-start'}}>
<label style ={{color: 'red',fontWeight: 'bold', padding: '.1rem', fontSize: '8px', textAlign:'center', margin:'0'}}> <Tooltip
<i>ESPERA</i> arrow
</label> placement="right"
<label style={{color: 'gray',fontWeight: 'bold', padding: '.1rem', textDecoration: 'underline', fontSize: '13px'}}> title={"Tempo de espera aguardando"}
{waitingTime} >
</label> <span style={{ display: 'flex', alignItems: 'center', flexDirection: 'column', justifyContent: 'flex-start', marginRight: '20px', marginTop: '10px' }}>
</span> {/* <label style={{ color: 'red', fontWeight: 'bold', padding: '.1rem', fontSize: '8px', textAlign: 'center', margin: '0' }}>
<i>ESPERA</i>
</label> */}
<label style={{ color: 'gray', fontWeight: 'bold', padding: '5px'/*, textDecoration: 'underline'*/, fontSize: '13px' }}>
{waitingTime}
</label>
</span>
</Tooltip>
} }
</Tabs> </Tabs>
<Paper className={classes.ticketsWrapper}> <Paper className={classes.ticketsWrapper}>
@ -580,7 +558,7 @@ const TicketsManager = () => {
</TabPanel> </TabPanel>
</Paper> </Paper>
); )
}; }
export default TicketsManager; export default TicketsManager

View File

@ -0,0 +1,17 @@
import React, { useState, createContext } from "react"
const ticketsContext = createContext()
const TicketsProvider = ({ children }) => {
const [tickets, setTickets] = useState(0)
return (
<ticketsContext.Provider value={{ tickets, setTickets }}>
{children}
</ticketsContext.Provider>
)
}
export { ticketsContext, TicketsProvider }

View File

@ -262,7 +262,7 @@ const Dashboard = () => {
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 [ticketStatusChatEnd, setTicketStatusChatEnd] = useState([])
const userQueueIds = user.queues.map((q) => q.id); const userQueueIds = user.queues?.map((q) => q.id);
const [selectedQueue, setSelectedQueue] = useState(userQueueIds || []); const [selectedQueue, setSelectedQueue] = useState(userQueueIds || []);
useEffect(() => { useEffect(() => {

View File

@ -224,6 +224,7 @@ Item.propTypes = {
let columnsData = [ let columnsData = [
{ title: `Tipo`, field: 'isRemote' },
{ title: `${i18n.t("reports.listColumns.column1_1")}`, field: 'whatsapp.name' }, { 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.column1_2")}`, field: 'user.name' },
{ title: `${i18n.t("reports.listColumns.column0_4")}`, field: 'contact.number' }, { title: `${i18n.t("reports.listColumns.column0_4")}`, field: 'contact.number' },
@ -241,6 +242,8 @@ let columnsData = [
] ]
let columnsDataSuper = [ 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_1")}`, field: 'whatsapp.name' },
{ title: `${i18n.t("reports.listColumns.column1_2")}`, field: 'user.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.column0_3")}`, field: 'contact.name' },
@ -375,11 +378,14 @@ const Report = () => {
filterQueuesTickets = ticketsQueue.filter(ticket => ticket?.queue?.name === userQueues[0]?.name) filterQueuesTickets = ticketsQueue.filter(ticket => ticket?.queue?.name === userQueues[0]?.name)
} }
data.tickets = filterQueuesTickets data.tickets = filterQueuesTickets
const tickets = data.tickets.map(ticket => ({ const tickets = data.tickets.map(ticket => {
ticket.isRemote = ticket.isRemote ? 'Remoto' : 'Comum';
return ({
...ticket, ...ticket,
messagesToFilter: ticket.messages.map(message => message.body).join(' '), messagesToFilter: ticket.messages.map(message => message.body).join(' '),
link: `${process.env.REACT_APP_FRONTEND_URL}/tickets/${ticket.id}` link: `${process.env.REACT_APP_FRONTEND_URL}/tickets/${ticket.id}`
})) })
})
dispatchQ({ type: "LOAD_QUERY", payload: tickets }) dispatchQ({ type: "LOAD_QUERY", payload: tickets })
setHasMore(data.hasMore) setHasMore(data.hasMore)
setTotalCountTickets(data.count) setTotalCountTickets(data.count)

View File

@ -11,6 +11,7 @@ import { i18n } from "../../translate/i18n";
import Hidden from "@material-ui/core/Hidden"; import Hidden from "@material-ui/core/Hidden";
import { SearchTicketProvider } from "../../context/SearchTicket/SearchTicket"; import { SearchTicketProvider } from "../../context/SearchTicket/SearchTicket";
import { TicketsProvider } from "../../context/TicketsProvider/TicketsProvider"
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
chatContainer: { chatContainer: {
@ -82,7 +83,9 @@ const Chat = () => {
} }
> >
<SearchTicketProvider> <SearchTicketProvider>
<TicketsManager /> <TicketsProvider>
<TicketsManager />
</TicketsProvider>
</SearchTicketProvider> </SearchTicketProvider>