Compare commits

..

No commits in common. "cc12cafb9915d024551fbbb7a5abbe48724848cf" and "42073cc821e2bcf29d8f12479e59652d65927349" have entirely different histories.

8 changed files with 99 additions and 323 deletions

View File

@ -8,7 +8,6 @@
"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"

View File

@ -1,22 +0,0 @@
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", {});
}
};

View File

@ -1,22 +0,0 @@
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", {});
}
};

View File

@ -1,22 +0,0 @@
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", {});
}
};

View File

@ -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,9 +183,7 @@ 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 };
} }
@ -200,36 +198,23 @@ 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),
media.data, media.data,
"base64" "base64"
);
} catch (err) {
Sentry.captureException(err);
logger.error(`There was an error: wbotMessageLitener.ts: ${err}`);
}
}
}
if(mediaAuthorized){
await ticket.update({ lastMessage: msg.body || media.filename });
const newMessage = await CreateMessageService({ messageData });
return newMessage;
}else{
if (ticket.status !== "queueChoice") {
botSendMessage(
ticket,
`Atenção! Mensagem ignorada, tipo de mídia não suportado.`
); );
} catch (err) {
Sentry.captureException(err);
logger.error(`There was an error: wbotMessageLitener.ts: ${err}`);
} }
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!`)
} }
await ticket.update({ lastMessage: msg.body || media.filename });
const newMessage = await CreateMessageService({ messageData });
return newMessage;
}; };
// const verifyMediaMessage = async ( // const verifyMediaMessage = async (
@ -427,15 +412,13 @@ 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

View File

@ -20,9 +20,6 @@ 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",
@ -86,7 +83,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])
@ -113,22 +110,7 @@ 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(() => {
@ -273,80 +255,49 @@ 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, settings]) }, [user])
const handleNotifications = (data, notify) => { const handleNotifications = data => {
let isQueue = false; const { message, contact, ticket } = data
if(!notify){
const { message, contact, ticket } = data
const options = { const options = {
body: `${message.body} - ${format(new Date(), "HH:mm")}`, body: `${message.body} - ${format(new Date(), "HH:mm")}`,
icon: contact.profilePicUrl, icon: contact.profilePicUrl,
tag: ticket.id, tag: ticket.id,
renotify: true, renotify: true,
}
const notification = new Notification(
`${i18n.t("tickets.notification.message")} ${contact.name}`,
options
)
notification.onclick = e => {
e.preventDefault()
window.focus()
historyRef.current.push(`/tickets/${ticket.id}`)
}
setDesktopNotifications(prevState => {
const notfiticationIndex = prevState.findIndex(
n => n.tag === notification.tag
)
if (notfiticationIndex !== -1) {
prevState[notfiticationIndex] = notification
return [...prevState]
} }
return [notification, ...prevState]
})
const notification = new Notification(
`${i18n.t("tickets.notification.message")} ${contact.name}`,
options
)
notification.onclick = e => {
e.preventDefault()
window.focus()
historyRef.current.push(`/tickets/${ticket.id}`)
}
setDesktopNotifications(prevState => {
const notfiticationIndex = prevState.findIndex(
n => n.tag === notification.tag
)
if (notfiticationIndex !== -1) {
prevState[notfiticationIndex] = notification
return [...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()
} }

View File

@ -33,9 +33,6 @@ 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: {
@ -160,10 +157,6 @@ 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;
@ -185,76 +178,6 @@ 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);
@ -280,7 +203,7 @@ const TicketsManager = () => {
useEffect(() => { useEffect(() => {
//console.log(selectedQueueIds);
if (tabOption === 'open') { if (tabOption === 'open') {
setTabOption('') setTabOption('')
@ -525,17 +448,7 @@ 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

View File

@ -237,8 +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 = [
{ title: `${i18n.t("reports.listColumns.column1_1")}`, field: 'whatsapp.name' }, { title: `${i18n.t("reports.listColumns.column1_1")}`, field: 'whatsapp.name' },
@ -253,7 +252,6 @@ 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 },
] ]
@ -378,9 +376,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)
@ -683,56 +681,54 @@ const Report = () => {
const renderSwitch = (param) => { const renderSwitch = (param) => {
if(userA.profile !== 'supervisor'){ switch (param) {
switch (param) { case 'empty':
case 'empty': return (
return ( <>
<> {query && query.length > 0 &&
{query && query.length > 0 && <ReportModalType currencies={reportTypeList} func={reportTypeValue} reportOption={reportType} />
<ReportModalType currencies={reportTypeList} func={reportTypeValue} reportOption={reportType} /> }
} {/* <Button
{/* <Button disabled={query && query.length > 0 ? false : true}
disabled={query && query.length > 0 ? false : true} variant="contained"
variant="contained" color="primary"
color="primary" onClick={(e) => {
onClick={(e) => { handleCSVMessages()
handleCSVMessages() }}
}} >
> {"CSV ALL"}
{"CSV ALL"}
</Button> */}
</Button> */} </>)
</>)
case 'pending' || 'processing': case 'pending' || 'processing':
return ( return (
<> <>
<span>PROCESSING...</span> <span>PROCESSING...</span>
</>) </>)
case 'success': case 'success':
return ( return (
<> <>
<Button <Button
variant="contained" variant="contained"
color="primary" color="primary"
onClick={(e) => { onClick={(e) => {
handleCSVDownload(e) handleCSVDownload(e)
}} }}
> >
{'CSV DOWNLOAD'} {'CSV DOWNLOAD'}
</Button> </Button>
</>) </>)
case 'downloading': case 'downloading':
return ( return (
<> <>
<span>DOWNLOADING...</span> <span>DOWNLOADING...</span>
</>) </>)
default: default:
return (<><span>WAITING...</span></>) return (<><span>WAITING...</span></>)
}
} }
} }
@ -843,7 +839,7 @@ const Report = () => {
<> <>
<MTable data={query} <MTable data={query}
columns={userA.profile !== 'supervisor' ? columnsData : columnsDataSuper} columns={userA.profile !== 'supervisor' ? columnsData : columnsDataSuper}
hasChild={userA.profile !== 'supervisor' ? true :false} hasChild={true}
removeClickRow={false} removeClickRow={false}
handleScroll={handleScroll} handleScroll={handleScroll}