diff --git a/backend/package.json b/backend/package.json
index 1c8646e..1e5bd13 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -8,6 +8,7 @@
"watch": "tsc -w",
"start": "nodemon --expose-gc dist/server.js",
"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",
"test": "NODE_ENV=test jest",
"posttest": "NODE_ENV=test sequelize db:migrate:undo:all"
diff --git a/backend/src/database/seeds/20240416141716-add-enable-block-audio-video-media-settings.ts b/backend/src/database/seeds/20240416141716-add-enable-block-audio-video-media-settings.ts
new file mode 100644
index 0000000..511de98
--- /dev/null
+++ b/backend/src/database/seeds/20240416141716-add-enable-block-audio-video-media-settings.ts
@@ -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", {});
+ }
+};
diff --git a/backend/src/database/seeds/20240417151300-add-enable-waiting-time-tickets-settings.ts b/backend/src/database/seeds/20240417151300-add-enable-waiting-time-tickets-settings.ts
new file mode 100644
index 0000000..8bb6828
--- /dev/null
+++ b/backend/src/database/seeds/20240417151300-add-enable-waiting-time-tickets-settings.ts
@@ -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", {});
+ }
+};
diff --git a/backend/src/database/seeds/20240417185331-add-enable-notification-transfer-queue.ts b/backend/src/database/seeds/20240417185331-add-enable-notification-transfer-queue.ts
new file mode 100644
index 0000000..00c4069
--- /dev/null
+++ b/backend/src/database/seeds/20240417185331-add-enable-notification-transfer-queue.ts
@@ -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", {});
+ }
+};
diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts
index 2739c73..f76c338 100644
--- a/backend/src/services/WbotServices/wbotMessageListener.ts
+++ b/backend/src/services/WbotServices/wbotMessageListener.ts
@@ -165,7 +165,7 @@ const verifyMediaMessage = async (
if (!media) {
throw new Error("ERR_WAPP_DOWNLOAD_MEDIA");
}
-
+ let mediaAuthorized = true;
let messageData = {
id: msg.id.id,
ticketId: ticket.id,
@@ -179,7 +179,9 @@ const verifyMediaMessage = async (
phoneNumberId: msg?.phoneNumberId,
fromAgent: false
};
-
+ if(messageData.mediaType === 'video' || messageData.mediaType === 'audio' && getSettingValue('blockAudioVideoMedia')?.value === 'enabled'){
+ mediaAuthorized = false;
+ }
if (msg?.fromMe) {
messageData = { ...messageData, fromAgent: true };
}
@@ -194,23 +196,36 @@ const verifyMediaMessage = async (
body: media.filename
};
}
-
- try {
- await writeFileAsync(
- join(__dirname, "..", "..", "..", "..", "..", "public", media.filename),
- media.data,
- "base64"
- );
- } catch (err) {
- Sentry.captureException(err);
- logger.error(`There was an error: wbotMessageLitener.ts: ${err}`);
+ if(mediaAuthorized){
+ try {
+ await writeFileAsync(
+ join(__dirname, "..", "..", "..", "..", "..", "public", media.filename),
+ media.data,
+ "base64"
+ );
+ } catch (err) {
+ Sentry.captureException(err);
+ logger.error(`There was an error: wbotMessageLitener.ts: ${err}`);
+ }
}
}
-
- await ticket.update({ lastMessage: msg.body || media.filename });
- const newMessage = await CreateMessageService({ messageData });
-
- return newMessage;
+ 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.`
+ );
+ }
+ 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 (
@@ -397,13 +412,15 @@ const verifyQueue = async (
}
let body = "";
-
+ const io = getIO();
if (botOptions.length > 0) {
body = `\u200e${choosenQueue.greetingMessage}\n\n${botOptions}\n${final_message.msg}`;
} else {
body = `\u200e${choosenQueue.greetingMessage}`;
}
+ io.emit('notifyPeding', {data: {ticket, queue: choosenQueue}});
+
sendWhatsAppMessageSocket(ticket, body);
} else {
//test del transfere o atendimento se entrar na ura infinita
diff --git a/frontend/src/components/NotificationsPopOver/index.js b/frontend/src/components/NotificationsPopOver/index.js
index 7840796..1e46dd1 100644
--- a/frontend/src/components/NotificationsPopOver/index.js
+++ b/frontend/src/components/NotificationsPopOver/index.js
@@ -20,6 +20,9 @@ import useTickets from "../../hooks/useTickets"
import alertSound from "../../assets/sound.mp3"
import { AuthContext } from "../../context/Auth/AuthContext"
+import api from "../../services/api";
+import toastError from "../../errors/toastError";
+
const useStyles = makeStyles(theme => ({
tabContainer: {
overflowY: "auto",
@@ -83,7 +86,7 @@ const NotificationsPopOver = () => {
const historyRef = useRef(history)
const { handleLogout } = useContext(AuthContext)
-
+ const [settings, setSettings] = useState([]);
// const [lastRef] = useState(+history.location.pathname.split("/")[2])
@@ -110,7 +113,22 @@ const NotificationsPopOver = () => {
ticketIdRef.current = 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(() => {
@@ -255,49 +273,80 @@ const NotificationsPopOver = () => {
if (shouldNotNotificate) return
-
-
handleNotifications(data)
}
})
+ socket.on('notifyPeding', data =>{
+ if(settings?.length > 0 && getSettingValue('notificationTransferQueue') === 'enabled') handleNotifications("", data);
+ });
+
return () => {
socket.disconnect()
}
- }, [user])
+ }, [user, settings])
- const handleNotifications = data => {
- const { message, contact, ticket } = data
+ const handleNotifications = (data, notify) => {
+ let isQueue = false;
+ if(!notify){
+ const { message, contact, ticket } = data
- const options = {
- body: `${message.body} - ${format(new Date(), "HH:mm")}`,
- icon: contact.profilePicUrl,
- tag: ticket.id,
- 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]
+ const options = {
+ body: `${message.body} - ${format(new Date(), "HH:mm")}`,
+ icon: contact.profilePicUrl,
+ tag: ticket.id,
+ renotify: true,
}
- 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()
}
diff --git a/frontend/src/components/TicketsManager/index.js b/frontend/src/components/TicketsManager/index.js
index 06ac9c9..ee0db3e 100644
--- a/frontend/src/components/TicketsManager/index.js
+++ b/frontend/src/components/TicketsManager/index.js
@@ -33,6 +33,9 @@ import { Button } from "@material-ui/core";
import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketHeaderOption";
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) => ({
ticketsWrapper: {
@@ -157,6 +160,10 @@ const TicketsManager = () => {
const [openTooltipSearch, setOpenTooltipSearch] = useState(false)
+ const [waitingTime, setWaitingTime] = useState('00:00');
+ const [tickets, setTickets] = useState([]);
+ const [settings, setSettings] = useState([])
+
let searchTimeout;
let searchContentTimeout;
@@ -178,6 +185,76 @@ const TicketsManager = () => {
}, [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(() => {
// clearTimeout(searchContentTimeout);
@@ -203,7 +280,7 @@ const TicketsManager = () => {
useEffect(() => {
-
+ //console.log(selectedQueueIds);
if (tabOption === 'open') {
setTabOption('')
@@ -448,7 +525,17 @@ const TicketsManager = () => {
}
value={"pending"}
- />
+ />{
+ (settings?.length > 0 && getSettingValue('waitingTimeTickets') === 'enabled') &&
+
+
+
+
+ }
{
const tickets = data.tickets.map(ticket => ({
...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 })
- console.log(tickets)
setHasMore(data.hasMore)
setTotalCountTickets(data.count)
setLoading(false)
@@ -682,54 +684,56 @@ const Report = () => {
const renderSwitch = (param) => {
- switch (param) {
- case 'empty':
- return (
- <>
- {query && query.length > 0 &&
-
- }
- {/* */}
- >)
+ if(userA.profile !== 'supervisor'){
+ switch (param) {
+ case 'empty':
+ return (
+ <>
+ {query && query.length > 0 &&
+
+ }
+ {/* */}
+ >)
- case 'pending' || 'processing':
- return (
- <>
- PROCESSING...
- >)
+ case 'pending' || 'processing':
+ return (
+ <>
+ PROCESSING...
+ >)
- case 'success':
- return (
- <>
-
- >)
- case 'downloading':
- return (
- <>
- DOWNLOADING...
- >)
+ case 'success':
+ return (
+ <>
+
+ >)
+ case 'downloading':
+ return (
+ <>
+ DOWNLOADING...
+ >)
- default:
- return (<>WAITING...>)
+ default:
+ return (<>WAITING...>)
+ }
}
}
@@ -840,7 +844,7 @@ const Report = () => {
<>