Compare commits
2 Commits
beb4fd64c4
...
cc12cafb99
Author | SHA1 | Date |
---|---|---|
gustavo-gsp | cc12cafb99 | |
gustavo-gsp | 860d462d37 |
|
@ -8,6 +8,7 @@
|
||||||
"watch": "tsc -w",
|
"watch": "tsc -w",
|
||||||
"start": "nodemon --expose-gc dist/server.js",
|
"start": "nodemon --expose-gc dist/server.js",
|
||||||
"dev:server": "ts-node-dev --respawn --transpile-only --ignore node_modules src/server.ts",
|
"dev:server": "ts-node-dev --respawn --transpile-only --ignore node_modules src/server.ts",
|
||||||
|
"dev": "nodemon --exec npm run dev:server",
|
||||||
"pretest": "NODE_ENV=test sequelize db:migrate && NODE_ENV=test sequelize db:seed:all",
|
"pretest": "NODE_ENV=test sequelize db:migrate && NODE_ENV=test sequelize db:seed:all",
|
||||||
"test": "NODE_ENV=test jest",
|
"test": "NODE_ENV=test jest",
|
||||||
"posttest": "NODE_ENV=test sequelize db:migrate:undo:all"
|
"posttest": "NODE_ENV=test sequelize db:migrate:undo:all"
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { QueryInterface } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.bulkInsert(
|
||||||
|
"Settings",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: "blockAudioVideoMedia",
|
||||||
|
value: "disabled",
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date()
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.bulkDelete("Settings", {});
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { QueryInterface } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.bulkInsert(
|
||||||
|
"Settings",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: "waitingTimeTickets",
|
||||||
|
value: "disabled",
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date()
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.bulkDelete("Settings", {});
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { QueryInterface } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.bulkInsert(
|
||||||
|
"Settings",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: "notificationTransferQueue",
|
||||||
|
value: "disabled",
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date()
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.bulkDelete("Settings", {});
|
||||||
|
}
|
||||||
|
};
|
|
@ -169,7 +169,7 @@ const verifyMediaMessage = async (
|
||||||
if (!media) {
|
if (!media) {
|
||||||
throw new Error("ERR_WAPP_DOWNLOAD_MEDIA");
|
throw new Error("ERR_WAPP_DOWNLOAD_MEDIA");
|
||||||
}
|
}
|
||||||
|
let mediaAuthorized = true;
|
||||||
let messageData = {
|
let messageData = {
|
||||||
id: msg.id.id,
|
id: msg.id.id,
|
||||||
ticketId: ticket.id,
|
ticketId: ticket.id,
|
||||||
|
@ -183,7 +183,9 @@ const verifyMediaMessage = async (
|
||||||
phoneNumberId: msg?.phoneNumberId,
|
phoneNumberId: msg?.phoneNumberId,
|
||||||
fromAgent: false
|
fromAgent: false
|
||||||
};
|
};
|
||||||
|
if(messageData.mediaType === 'video' || messageData.mediaType === 'audio' && getSettingValue('blockAudioVideoMedia')?.value === 'enabled'){
|
||||||
|
mediaAuthorized = false;
|
||||||
|
}
|
||||||
if (msg?.fromMe) {
|
if (msg?.fromMe) {
|
||||||
messageData = { ...messageData, fromAgent: true };
|
messageData = { ...messageData, fromAgent: true };
|
||||||
}
|
}
|
||||||
|
@ -198,7 +200,7 @@ const verifyMediaMessage = async (
|
||||||
body: media.filename
|
body: media.filename
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if(mediaAuthorized){
|
||||||
try {
|
try {
|
||||||
await writeFileAsync(
|
await writeFileAsync(
|
||||||
join(__dirname, "..", "..", "..", "..", "..", "public", media.filename),
|
join(__dirname, "..", "..", "..", "..", "..", "public", media.filename),
|
||||||
|
@ -210,11 +212,24 @@ const verifyMediaMessage = async (
|
||||||
logger.error(`There was an error: wbotMessageLitener.ts: ${err}`);
|
logger.error(`There was an error: wbotMessageLitener.ts: ${err}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if(mediaAuthorized){
|
||||||
await ticket.update({ lastMessage: msg.body || media.filename });
|
await ticket.update({ lastMessage: msg.body || media.filename });
|
||||||
const newMessage = await CreateMessageService({ messageData });
|
const newMessage = await CreateMessageService({ messageData });
|
||||||
|
|
||||||
return newMessage;
|
return newMessage;
|
||||||
|
}else{
|
||||||
|
if (ticket.status !== "queueChoice") {
|
||||||
|
botSendMessage(
|
||||||
|
ticket,
|
||||||
|
`Atenção! Mensagem ignorada, tipo de mídia não suportado.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
messageData.body = `Mensagem de *${messageData.mediaType}* ignorada, tipo de mídia não suportado.`;
|
||||||
|
messageData.mediaUrl = '';
|
||||||
|
await ticket.update({ lastMessage: `Mensagem de *${messageData.mediaType}* ignorada, tipo de mídia não suportado.`});
|
||||||
|
const newMessage = await CreateMessageService({ messageData });
|
||||||
|
console.log(`--------->>> Mensagem do tipo: ${messageData.mediaType}, ignorada!`)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// const verifyMediaMessage = async (
|
// const verifyMediaMessage = async (
|
||||||
|
@ -412,13 +427,15 @@ const verifyQueue = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = "";
|
let body = "";
|
||||||
|
const io = getIO();
|
||||||
if (botOptions.length > 0) {
|
if (botOptions.length > 0) {
|
||||||
body = `\u200e${choosenQueue.greetingMessage}\n\n${botOptions}\n${final_message.msg}`;
|
body = `\u200e${choosenQueue.greetingMessage}\n\n${botOptions}\n${final_message.msg}`;
|
||||||
} else {
|
} else {
|
||||||
body = `\u200e${choosenQueue.greetingMessage}`;
|
body = `\u200e${choosenQueue.greetingMessage}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
io.emit('notifyPeding', {data: {ticket, queue: choosenQueue}});
|
||||||
|
|
||||||
sendWhatsAppMessageSocket(ticket, body);
|
sendWhatsAppMessageSocket(ticket, body);
|
||||||
} else {
|
} else {
|
||||||
//test del transfere o atendimento se entrar na ura infinita
|
//test del transfere o atendimento se entrar na ura infinita
|
||||||
|
|
|
@ -20,6 +20,9 @@ import useTickets from "../../hooks/useTickets"
|
||||||
import alertSound from "../../assets/sound.mp3"
|
import alertSound from "../../assets/sound.mp3"
|
||||||
import { AuthContext } from "../../context/Auth/AuthContext"
|
import { AuthContext } from "../../context/Auth/AuthContext"
|
||||||
|
|
||||||
|
import api from "../../services/api";
|
||||||
|
import toastError from "../../errors/toastError";
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const useStyles = makeStyles(theme => ({
|
||||||
tabContainer: {
|
tabContainer: {
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
|
@ -83,7 +86,7 @@ const NotificationsPopOver = () => {
|
||||||
const historyRef = useRef(history)
|
const historyRef = useRef(history)
|
||||||
|
|
||||||
const { handleLogout } = useContext(AuthContext)
|
const { handleLogout } = useContext(AuthContext)
|
||||||
|
const [settings, setSettings] = useState([]);
|
||||||
// const [lastRef] = useState(+history.location.pathname.split("/")[2])
|
// const [lastRef] = useState(+history.location.pathname.split("/")[2])
|
||||||
|
|
||||||
|
|
||||||
|
@ -110,7 +113,22 @@ const NotificationsPopOver = () => {
|
||||||
ticketIdRef.current = ticketIdUrl
|
ticketIdRef.current = ticketIdUrl
|
||||||
}, [ticketIdUrl])
|
}, [ticketIdUrl])
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
|
@ -255,18 +273,22 @@ const NotificationsPopOver = () => {
|
||||||
|
|
||||||
if (shouldNotNotificate) return
|
if (shouldNotNotificate) return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
handleNotifications(data)
|
handleNotifications(data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
socket.on('notifyPeding', data =>{
|
||||||
|
if(settings?.length > 0 && getSettingValue('notificationTransferQueue') === 'enabled') handleNotifications("", data);
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
socket.disconnect()
|
socket.disconnect()
|
||||||
}
|
}
|
||||||
}, [user])
|
}, [user, settings])
|
||||||
|
|
||||||
const handleNotifications = data => {
|
const handleNotifications = (data, notify) => {
|
||||||
|
let isQueue = false;
|
||||||
|
if(!notify){
|
||||||
const { message, contact, ticket } = data
|
const { message, contact, ticket } = data
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
|
@ -297,7 +319,34 @@ const NotificationsPopOver = () => {
|
||||||
}
|
}
|
||||||
return [notification, ...prevState]
|
return [notification, ...prevState]
|
||||||
})
|
})
|
||||||
|
}else{
|
||||||
|
user.queues.forEach(queue =>{
|
||||||
|
if(queue.id === notify.data?.queue?.id){
|
||||||
|
isQueue = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if(!isQueue){
|
||||||
|
return;
|
||||||
|
}else {
|
||||||
|
const notification = new Notification(`${i18n.t("tickets.notification.messagePeding")} ${notify.data?.queue?.name}`);
|
||||||
|
notification.onclick = e => {
|
||||||
|
e.preventDefault()
|
||||||
|
window.focus()
|
||||||
|
historyRef.current.push(`/tickets`)
|
||||||
|
}
|
||||||
|
|
||||||
|
setDesktopNotifications(prevState => {
|
||||||
|
const notfiticationIndex = prevState.findIndex(
|
||||||
|
n => n.tag === notification.tag
|
||||||
|
)
|
||||||
|
if (notfiticationIndex !== -1) {
|
||||||
|
prevState[notfiticationIndex] = notification
|
||||||
|
return [...prevState]
|
||||||
|
}
|
||||||
|
return [notification, ...prevState]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
soundAlertRef.current()
|
soundAlertRef.current()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,9 @@ 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";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
ticketsWrapper: {
|
ticketsWrapper: {
|
||||||
|
@ -157,6 +160,10 @@ const TicketsManager = () => {
|
||||||
|
|
||||||
const [openTooltipSearch, setOpenTooltipSearch] = useState(false)
|
const [openTooltipSearch, setOpenTooltipSearch] = useState(false)
|
||||||
|
|
||||||
|
const [waitingTime, setWaitingTime] = useState('00:00');
|
||||||
|
const [tickets, setTickets] = useState([]);
|
||||||
|
const [settings, setSettings] = useState([])
|
||||||
|
|
||||||
let searchTimeout;
|
let searchTimeout;
|
||||||
let searchContentTimeout;
|
let searchContentTimeout;
|
||||||
|
|
||||||
|
@ -178,6 +185,76 @@ const TicketsManager = () => {
|
||||||
|
|
||||||
}, [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
|
||||||
|
}
|
||||||
|
|
||||||
|
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, 7000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [selectedQueueIds, settings]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
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]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
// clearTimeout(searchContentTimeout);
|
// clearTimeout(searchContentTimeout);
|
||||||
|
@ -203,7 +280,7 @@ const TicketsManager = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
|
//console.log(selectedQueueIds);
|
||||||
if (tabOption === 'open') {
|
if (tabOption === 'open') {
|
||||||
|
|
||||||
setTabOption('')
|
setTabOption('')
|
||||||
|
@ -448,7 +525,17 @@ const TicketsManager = () => {
|
||||||
</Badge>
|
</Badge>
|
||||||
}
|
}
|
||||||
value={"pending"}
|
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'}}>
|
||||||
|
<i>ESPERA</i>
|
||||||
|
</label>
|
||||||
|
<label style={{color: 'gray',fontWeight: 'bold', padding: '.1rem', textDecoration: 'underline', fontSize: '13px'}}>
|
||||||
|
{waitingTime}
|
||||||
|
</label>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<Paper className={classes.ticketsWrapper}>
|
<Paper className={classes.ticketsWrapper}>
|
||||||
<TicketsList
|
<TicketsList
|
||||||
|
|
|
@ -237,6 +237,7 @@ let columnsData = [
|
||||||
{ title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' },
|
{ title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' },
|
||||||
{ title: `Espera`, field: 'waiting_time' },
|
{ title: `Espera`, field: 'waiting_time' },
|
||||||
{ title: `Mensagens`, field: 'messagesToFilter', searchable: true, hidden: true },
|
{ title: `Mensagens`, field: 'messagesToFilter', searchable: true, hidden: true },
|
||||||
|
{ title: `Link`, field: 'link', searchable: false, hidden: true, export: true },
|
||||||
]
|
]
|
||||||
|
|
||||||
let columnsDataSuper = [
|
let columnsDataSuper = [
|
||||||
|
@ -252,6 +253,7 @@ let columnsDataSuper = [
|
||||||
{ title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' },
|
{ title: `${i18n.t("reports.listColumns.column1_9")}`, field: 'statusChatEnd' },
|
||||||
{ title: `Espera`, field: 'waiting_time' },
|
{ title: `Espera`, field: 'waiting_time' },
|
||||||
{ title: `Mensagens`, field: 'messagesToFilter', searchable: true, hidden: true },
|
{ title: `Mensagens`, field: 'messagesToFilter', searchable: true, hidden: true },
|
||||||
|
{ title: `Link`, field: 'link', searchable: false, hidden: true, export: true },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -376,9 +378,9 @@ const Report = () => {
|
||||||
const tickets = data.tickets.map(ticket => ({
|
const tickets = data.tickets.map(ticket => ({
|
||||||
...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}`
|
||||||
}))
|
}))
|
||||||
dispatchQ({ type: "LOAD_QUERY", payload: tickets })
|
dispatchQ({ type: "LOAD_QUERY", payload: tickets })
|
||||||
console.log(tickets)
|
|
||||||
setHasMore(data.hasMore)
|
setHasMore(data.hasMore)
|
||||||
setTotalCountTickets(data.count)
|
setTotalCountTickets(data.count)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
@ -681,6 +683,7 @@ const Report = () => {
|
||||||
|
|
||||||
|
|
||||||
const renderSwitch = (param) => {
|
const renderSwitch = (param) => {
|
||||||
|
if(userA.profile !== 'supervisor'){
|
||||||
switch (param) {
|
switch (param) {
|
||||||
case 'empty':
|
case 'empty':
|
||||||
return (
|
return (
|
||||||
|
@ -731,6 +734,7 @@ const Report = () => {
|
||||||
return (<><span>WAITING...</span></>)
|
return (<><span>WAITING...</span></>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const handleChange = (event) => {
|
const handleChange = (event) => {
|
||||||
|
@ -839,7 +843,7 @@ const Report = () => {
|
||||||
<>
|
<>
|
||||||
<MTable data={query}
|
<MTable data={query}
|
||||||
columns={userA.profile !== 'supervisor' ? columnsData : columnsDataSuper}
|
columns={userA.profile !== 'supervisor' ? columnsData : columnsDataSuper}
|
||||||
hasChild={true}
|
hasChild={userA.profile !== 'supervisor' ? true :false}
|
||||||
removeClickRow={false}
|
removeClickRow={false}
|
||||||
|
|
||||||
handleScroll={handleScroll}
|
handleScroll={handleScroll}
|
||||||
|
|
Loading…
Reference in New Issue