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,
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'
group by s.id;`,
{ type: QueryTypes.SELECT }
);
}
return countStatusChatEnd;
};

View File

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

View File

@ -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,

View File

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

View File

@ -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(() => {
@ -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])

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 { 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,139 +131,106 @@ 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(() => {
setSettings(setting)
}, [setting])
useEffect(() => {
if (user.profile.toUpperCase() === "ADMIN" ||
user.profile.toUpperCase() === "SUPERVISOR" ||
user.profile.toUpperCase() === "MASTER") {
setShowAllTickets(true);
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 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);
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) : '';
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;
}else return '00:00';
return averageTimeFormated
} else return '00:00'
}
setWaitingTime(calculateAverageTime());
},[tickets]);
setWaitingTime(calculateAverageTime())
const intervalId = setInterval(() => {
setWaitingTime(calculateAverageTime())
}, 10000)
return () => clearInterval(intervalId)
}, [tickets])
useEffect(() => {
@ -278,12 +248,12 @@ const TicketsManager = () => {
// }, 500);
clearTimeout(searchContentTimeout);
clearTimeout(searchContentTimeout)
setSearchParam(prev => ({ ...prev, searchParamContent: "" }))
}, [inputContentSearch, searchContentTimeout]);
}, [inputContentSearch, searchContentTimeout])
useEffect(() => {
@ -291,11 +261,11 @@ 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])
@ -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,10 +303,10 @@ const TicketsManager = () => {
searchTimeout = setTimeout(() => {
setSearchParam(prev => ({ ...prev, searchParam: searchedTerm }));
setSearchParam(prev => ({ ...prev, searchParam: searchedTerm }))
}, 500);
};
}, 500)
}
const handleContentSearch = e => {
@ -346,9 +316,9 @@ const TicketsManager = () => {
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 (
<Paper elevation={0} variant="outlined" className={classes.ticketsWrapper}>
@ -534,14 +504,22 @@ const TicketsManager = () => {
value={"pending"}
/>{
(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
arrow
placement="right"
title={"Tempo de espera aguardando"}
>
<span style={{ display: 'flex', alignItems: 'center', flexDirection: 'column', justifyContent: 'flex-start', marginRight: '20px', marginTop: '10px' }}>
{/* <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: '.1rem', textDecoration: 'underline', fontSize: '13px'}}>
</label> */}
<label style={{ color: 'gray', fontWeight: 'bold', padding: '5px'/*, textDecoration: 'underline'*/, fontSize: '13px' }}>
{waitingTime}
</label>
</span>
</Tooltip>
}
</Tabs>
<Paper className={classes.ticketsWrapper}>
@ -580,7 +558,7 @@ const TicketsManager = () => {
</TabPanel>
</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 [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(() => {

View File

@ -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)

View File

@ -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 = () => {
}
>
<SearchTicketProvider>
<TicketsProvider>
<TicketsManager />
</TicketsProvider>
</SearchTicketProvider>