feat: Configure business hours for WhatsApp sessions and add setting to hide campaign module
parent
0751374fb7
commit
a2f946d849
|
@ -30,6 +30,8 @@ app.get('/', function (req, res) {
|
||||||
app.post('/api/session', async function (req, res) {
|
app.post('/api/session', async function (req, res) {
|
||||||
let { app_name, whatsappId, client_url, number } = req.body
|
let { app_name, whatsappId, client_url, number } = req.body
|
||||||
|
|
||||||
|
let oldNumber = ''
|
||||||
|
|
||||||
if (app_name) {
|
if (app_name) {
|
||||||
app_name = app_name.trim()
|
app_name = app_name.trim()
|
||||||
}
|
}
|
||||||
|
@ -67,6 +69,7 @@ app.post('/api/session', async function (req, res) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let appPort = []
|
let appPort = []
|
||||||
|
|
||||||
let existSubDir = false
|
let existSubDir = false
|
||||||
|
@ -98,7 +101,7 @@ app.post('/api/session', async function (req, res) {
|
||||||
path.join(sessionsPath, directoriesInDIrectory[i], subDir[x])
|
path.join(sessionsPath, directoriesInDIrectory[i], subDir[x])
|
||||||
)
|
)
|
||||||
|
|
||||||
let oldNumber = subDir[x].split('_')[1]
|
oldNumber = subDir[x].split('_')[1]
|
||||||
|
|
||||||
if (oldNumber != number) {
|
if (oldNumber != number) {
|
||||||
deletePm2Process(subDir[x], currPath)
|
deletePm2Process(subDir[x], currPath)
|
||||||
|
@ -197,7 +200,7 @@ app.post('/api/session', async function (req, res) {
|
||||||
) {
|
) {
|
||||||
const whatsapp_numbers = await new Promise((resolve, reject) => {
|
const whatsapp_numbers = await new Promise((resolve, reject) => {
|
||||||
mysql_conn(db_credentials.db_conf).query(
|
mysql_conn(db_credentials.db_conf).query(
|
||||||
'SELECT name FROM Whatsapps WHERE name LIKE ?',
|
'SELECT name, number FROM Whatsapps WHERE name LIKE ?',
|
||||||
[`%${number}%`],
|
[`%${number}%`],
|
||||||
(err, result) => {
|
(err, result) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -209,8 +212,6 @@ app.post('/api/session', async function (req, res) {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('whatsapp_numbers: ', whatsapp_numbers)
|
|
||||||
|
|
||||||
let session_num = []
|
let session_num = []
|
||||||
|
|
||||||
if (whatsapp_numbers && whatsapp_numbers.length > 0) {
|
if (whatsapp_numbers && whatsapp_numbers.length > 0) {
|
||||||
|
@ -341,6 +342,7 @@ app.post('/api/session', async function (req, res) {
|
||||||
stream.write('# NUMBER AND NAME THAT WILL BE DISPLAYED ON CONSOLE\n')
|
stream.write('# NUMBER AND NAME THAT WILL BE DISPLAYED ON CONSOLE\n')
|
||||||
stream.write(`MOBILEUID=${number}\n`)
|
stream.write(`MOBILEUID=${number}\n`)
|
||||||
stream.write(`MOBILENAME=${whatsappName}\n`)
|
stream.write(`MOBILENAME=${whatsappName}\n`)
|
||||||
|
stream.write(`OLD_MOBILEUID=${oldNumber}\n`)
|
||||||
stream.write('\n')
|
stream.write('\n')
|
||||||
|
|
||||||
stream.write('# PORT NUMBER FOR THIS API\n')
|
stream.write('# PORT NUMBER FOR THIS API\n')
|
||||||
|
|
|
@ -288,8 +288,6 @@ client.on("ready", async () => {
|
||||||
|
|
||||||
let url = process.env.CLIENT_URL + '/whatsapp/connection/number'
|
let url = process.env.CLIENT_URL + '/whatsapp/connection/number'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
await client.logout()
|
await client.logout()
|
||||||
|
@ -304,6 +302,48 @@ client.on("ready", async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (process.env.OLD_MOBILEUID) {
|
||||||
|
const ticketSettingsId = await new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
dbcc.query("select id from SettingTickets where number = ?", [process.env.OLD_MOBILEUID,], (err, result) => {
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// resolve(result)
|
||||||
|
const idArray = result.map(row => row.id)
|
||||||
|
resolve(idArray)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
if (ticketSettingsId?.length > 0) {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const idsToUpdate = ticketSettingsId // Assuming ticketSettingsId is an array of IDs
|
||||||
|
|
||||||
|
// Create placeholders for the IN clause based on the number of elements in idsToUpdate
|
||||||
|
const placeholders = Array(idsToUpdate.length).fill('?').join(',')
|
||||||
|
|
||||||
|
dbcc.query(
|
||||||
|
`UPDATE SettingTickets SET number = ? WHERE id IN (${placeholders})`,
|
||||||
|
[client.info["wid"]["user"], ...idsToUpdate], // Spread the array to pass individual values
|
||||||
|
function (err, result) {
|
||||||
|
if (err) {
|
||||||
|
console.log("ERROR: " + err)
|
||||||
|
reject(err)
|
||||||
|
} else {
|
||||||
|
resolve(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
@ -324,7 +364,6 @@ client.on("ready", async () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let url = process.env.CLIENT_URL + '/whatsapp/connection/qrcode'
|
let url = process.env.CLIENT_URL + '/whatsapp/connection/qrcode'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -1023,7 +1062,7 @@ const sendCampaignMessage = async () => {
|
||||||
try {
|
try {
|
||||||
clearInterval(scheduler_campaign_monitor)
|
clearInterval(scheduler_campaign_monitor)
|
||||||
|
|
||||||
|
|
||||||
let internetConnection = await checkInternetConnection()
|
let internetConnection = await checkInternetConnection()
|
||||||
|
|
||||||
if (!internetConnection) {
|
if (!internetConnection) {
|
||||||
|
@ -1193,9 +1232,9 @@ function comercialBuss(until_hour) {
|
||||||
|
|
||||||
scheduler_monitor = setInterval(monitor, 10000)
|
scheduler_monitor = setInterval(monitor, 10000)
|
||||||
|
|
||||||
scheduler_campaign_monitor = setInterval(sendCampaignMessage, 3000)
|
// scheduler_campaign_monitor = setInterval(sendCampaignMessage, 3000)
|
||||||
|
|
||||||
scheduler_internet_conn = setInterval(internetMonitor, 60000)
|
// scheduler_internet_conn = setInterval(internetMonitor, 60000)
|
||||||
|
|
||||||
app.listen(process.env.PORT || 8003, function () {
|
app.listen(process.env.PORT || 8003, function () {
|
||||||
console.log("\u26A1[server]: Server is running at Port ::: " + process.env.PORT || 8003)
|
console.log("\u26A1[server]: Server is running at Port ::: " + process.env.PORT || 8003)
|
||||||
|
|
|
@ -18,9 +18,20 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
|
||||||
|
|
||||||
const settings = await ListSettingsService();
|
const settings = await ListSettingsService();
|
||||||
|
|
||||||
const config = await SettingTicket.findAll();
|
// const config = await SettingTicket.findAll();
|
||||||
|
|
||||||
return res.status(200).json({ settings, config });
|
return res.status(200).json({ settings, });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ticketSettings = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Response> => {
|
||||||
|
const { number } = req.params;
|
||||||
|
|
||||||
|
const config = await SettingTicket.findAll({ where: { number } });
|
||||||
|
|
||||||
|
return res.status(200).json({ config });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateTicketSettings = async (
|
export const updateTicketSettings = async (
|
||||||
|
@ -28,6 +39,7 @@ export const updateTicketSettings = async (
|
||||||
res: Response
|
res: Response
|
||||||
): Promise<Response> => {
|
): Promise<Response> => {
|
||||||
const {
|
const {
|
||||||
|
number,
|
||||||
outBusinessHours,
|
outBusinessHours,
|
||||||
ticketExpiration,
|
ticketExpiration,
|
||||||
weekend,
|
weekend,
|
||||||
|
@ -35,46 +47,54 @@ export const updateTicketSettings = async (
|
||||||
sunday,
|
sunday,
|
||||||
holiday
|
holiday
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
|
if (!number) throw new AppError("No number selected", 400);
|
||||||
|
|
||||||
if (outBusinessHours && Object.keys(outBusinessHours).length > 0) {
|
if (outBusinessHours && Object.keys(outBusinessHours).length > 0) {
|
||||||
await updateSettingTicket({
|
await updateSettingTicket({
|
||||||
...outBusinessHours,
|
...outBusinessHours,
|
||||||
key: "outBusinessHours"
|
key: "outBusinessHours",
|
||||||
|
number
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ticketExpiration && Object.keys(ticketExpiration).length > 0) {
|
if (ticketExpiration && Object.keys(ticketExpiration).length > 0) {
|
||||||
await updateSettingTicket({
|
await updateSettingTicket({
|
||||||
...ticketExpiration,
|
...ticketExpiration,
|
||||||
key: "ticketExpiration"
|
key: "ticketExpiration",
|
||||||
|
number
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (weekend && Object.keys(weekend).length > 0) {
|
if (weekend && Object.keys(weekend).length > 0) {
|
||||||
await updateSettingTicket({
|
await updateSettingTicket({
|
||||||
...weekend,
|
...weekend,
|
||||||
key: "weekend"
|
key: "weekend",
|
||||||
|
number
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (saturday && Object.keys(saturday).length > 0) {
|
if (saturday && Object.keys(saturday).length > 0) {
|
||||||
await updateSettingTicket({
|
await updateSettingTicket({
|
||||||
...saturday,
|
...saturday,
|
||||||
key: "saturday"
|
key: "saturday",
|
||||||
|
number
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sunday && Object.keys(sunday).length > 0) {
|
if (sunday && Object.keys(sunday).length > 0) {
|
||||||
await updateSettingTicket({
|
await updateSettingTicket({
|
||||||
...sunday,
|
...sunday,
|
||||||
key: "sunday"
|
key: "sunday",
|
||||||
|
number
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (holiday && Object.keys(holiday).length > 0) {
|
if (holiday && Object.keys(holiday).length > 0) {
|
||||||
await updateSettingTicket({
|
await updateSettingTicket({
|
||||||
...holiday,
|
...holiday,
|
||||||
key: "holiday"
|
key: "holiday",
|
||||||
|
number
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -251,9 +251,7 @@ export const update = async (
|
||||||
// Para aparecer pendente para todos usuarios que estao na fila
|
// Para aparecer pendente para todos usuarios que estao na fila
|
||||||
if (req.body.transfer) {
|
if (req.body.transfer) {
|
||||||
req.body.userId = null;
|
req.body.userId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("REQ.BODY: ", JSON.stringify(req.body, null, 6));
|
|
||||||
|
|
||||||
let ticketData: TicketData = req.body;
|
let ticketData: TicketData = req.body;
|
||||||
|
|
||||||
|
|
|
@ -136,15 +136,26 @@ export const all = async (req: Request, res: Response): Promise<Response> => {
|
||||||
export const store = async (req: Request, res: Response): Promise<Response> => {
|
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||||
const { email, password, name, profile, queueIds } = req.body;
|
const { email, password, name, profile, queueIds } = req.body;
|
||||||
|
|
||||||
|
console.log("===========> req.url: ", req.url);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
req.url === "/user" &&
|
||||||
|
getSettingValue("userCreation")?.value == "disabled" &&
|
||||||
|
req.user.profile == "admin"
|
||||||
|
) {
|
||||||
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
|
} else if (
|
||||||
req.url === "/signup" &&
|
req.url === "/signup" &&
|
||||||
(await CheckSettingsHelper("userCreation")) === "disabled"
|
getSettingValue("userCreation")?.value == "disabled"
|
||||||
) {
|
) {
|
||||||
throw new AppError("ERR_USER_CREATION_DISABLED", 403);
|
throw new AppError("ERR_USER_CREATION_DISABLED", 403);
|
||||||
} else if (req.url !== "/signup" && req.user.profile !== "master") {
|
} else if (
|
||||||
|
req.user.profile !== "master"
|
||||||
|
) {
|
||||||
throw new AppError("ERR_NO_PERMISSION", 403);
|
throw new AppError("ERR_NO_PERMISSION", 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const user = await CreateUserService({
|
const user = await CreateUserService({
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
|
|
|
@ -39,6 +39,9 @@ import receiveWhatsAppMediaOfficialAPI from "../helpers/ReceiveWhatsAppMediaOffi
|
||||||
import whatsappOfficialAPI from "../helpers/WhatsappOfficialAPI";
|
import whatsappOfficialAPI from "../helpers/WhatsappOfficialAPI";
|
||||||
import whatsappOfficialNumberInfo from "../helpers/WhatsappOfficialNumberInfo";
|
import whatsappOfficialNumberInfo from "../helpers/WhatsappOfficialNumberInfo";
|
||||||
import { getSettingValue } from "../helpers/WhaticketSettings";
|
import { getSettingValue } from "../helpers/WhaticketSettings";
|
||||||
|
import ListWhatsAppsNumber from "../services/WhatsappService/ListWhatsAppsNumber";
|
||||||
|
import SettingTicket from "../models/SettingTicket";
|
||||||
|
import { Op } from "sequelize";
|
||||||
|
|
||||||
interface WhatsappData {
|
interface WhatsappData {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -405,7 +408,7 @@ export const update = async (
|
||||||
res: Response
|
res: Response
|
||||||
): Promise<Response> => {
|
): Promise<Response> => {
|
||||||
const { whatsappId } = req.params;
|
const { whatsappId } = req.params;
|
||||||
const whatsappData = req.body;
|
const whatsappData = req.body;
|
||||||
|
|
||||||
let invalidPhoneName = validatePhoneName(whatsappData.name);
|
let invalidPhoneName = validatePhoneName(whatsappData.name);
|
||||||
|
|
||||||
|
@ -483,6 +486,38 @@ export const remove = async (
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let whats = await ListWhatsAppsNumber(whatsappId);
|
||||||
|
|
||||||
|
// Remove tickets business hours config
|
||||||
|
if (whats?.whatsapps?.length == 1) {
|
||||||
|
const configIds = await SettingTicket.findAll({
|
||||||
|
where: { number: whats?.whatsapps[0]?.number },
|
||||||
|
raw: true,
|
||||||
|
attributes: ["id"]
|
||||||
|
});
|
||||||
|
|
||||||
|
const whatsappTicketConfig = await SettingTicket.findOne({
|
||||||
|
where: { number: whats.whatsapps[0].number }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (whatsappTicketConfig) {
|
||||||
|
try {
|
||||||
|
await SettingTicket.destroy({
|
||||||
|
where: {
|
||||||
|
id: {
|
||||||
|
[Op.in]: configIds.map(config => config.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(
|
||||||
|
"Error on delete SettingTicket by number: ",
|
||||||
|
whats?.whatsapps[0]?.number
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await DeleteWhatsAppService(whatsappId);
|
await DeleteWhatsAppService(whatsappId);
|
||||||
|
|
||||||
removeDir(
|
removeDir(
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("SettingTickets", "number", {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("SettingTickets", "number");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { QueryInterface, DataTypes } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.addColumn("Users", "positionCompany", {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.removeColumn("Users", "positionCompany");
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,26 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface, Sequelize) => {
|
||||||
|
/*
|
||||||
|
Add altering commands here.
|
||||||
|
Return a promise to correctly handle asynchronicity.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
return queryInterface.bulkInsert('People', [{
|
||||||
|
name: 'John Doe',
|
||||||
|
isBetaMember: false
|
||||||
|
}], {});
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface, Sequelize) => {
|
||||||
|
/*
|
||||||
|
Add reverting commands here.
|
||||||
|
Return a promise to correctly handle asynchronicity.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
return queryInterface.bulkDelete('People', null, {});
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { QueryInterface } from "sequelize";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.bulkInsert(
|
||||||
|
"Settings",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: "hasCampaign",
|
||||||
|
value: "disabled",
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date()
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.bulkDelete("Settings", {});
|
||||||
|
}
|
||||||
|
};
|
|
@ -12,6 +12,7 @@ import {
|
||||||
|
|
||||||
import ptBR from "date-fns/locale/pt-BR";
|
import ptBR from "date-fns/locale/pt-BR";
|
||||||
import { splitDateTime } from "./SplitDateTime";
|
import { splitDateTime } from "./SplitDateTime";
|
||||||
|
import Whatsapp from "../models/Whatsapp";
|
||||||
|
|
||||||
const fsPromises = require("fs/promises");
|
const fsPromises = require("fs/promises");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
@ -24,37 +25,43 @@ const AutoCloseTickets = async () => {
|
||||||
|
|
||||||
// if (!botInfo.userIdBot) return
|
// if (!botInfo.userIdBot) return
|
||||||
|
|
||||||
const ticketExpiration = await SettingTicket.findOne({
|
const whatsapps = await Whatsapp.findAll({ group: ["number"] });
|
||||||
where: { key: "ticketExpiration" }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ticketExpiration && ticketExpiration.value == "enabled") {
|
for (const whatsapp of whatsapps) {
|
||||||
const startTime = splitDateTime(
|
// console.log("-------> whatsapp: ", JSON.stringify(whatsapps, null, 6));
|
||||||
new Date(
|
|
||||||
_format(new Date(ticketExpiration.startTime), "yyyy-MM-dd HH:mm:ss", {
|
|
||||||
locale: ptBR
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const seconds = timeStringToSeconds(startTime.fullTime);
|
const ticketExpiration = await SettingTicket.findOne({
|
||||||
|
where: { key: "ticketExpiration", number: whatsapp.number }
|
||||||
// console.log("Ticket seconds: ", seconds);
|
|
||||||
|
|
||||||
let tickets: any = await ListTicketTimeLife({
|
|
||||||
timeseconds: seconds,
|
|
||||||
status: "open"
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log("tickets: ", tickets);
|
if (ticketExpiration && ticketExpiration.value == "enabled") {
|
||||||
|
const startTime = splitDateTime(
|
||||||
|
new Date(
|
||||||
|
_format(
|
||||||
|
new Date(ticketExpiration.startTime),
|
||||||
|
"yyyy-MM-dd HH:mm:ss",
|
||||||
|
{
|
||||||
|
locale: ptBR
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
for (let i = 0; i < tickets.length; i++) {
|
const seconds = timeStringToSeconds(startTime.fullTime);
|
||||||
|
|
||||||
await UpdateTicketService({
|
let tickets: any = await ListTicketTimeLife({
|
||||||
ticketData: { status: "closed", statusChatEnd: "FINALIZADO" },
|
timeseconds: seconds,
|
||||||
ticketId: tickets[i].ticket_id,
|
status: "open",
|
||||||
msg: ticketExpiration.message
|
number: whatsapp.number
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < tickets.length; i++) {
|
||||||
|
await UpdateTicketService({
|
||||||
|
ticketData: { status: "closed", statusChatEnd: "FINALIZADO" },
|
||||||
|
ticketId: tickets[i].ticket_id,
|
||||||
|
msg: ticketExpiration.message
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -13,11 +13,11 @@ import {
|
||||||
} from "date-fns";
|
} from "date-fns";
|
||||||
import ptBR from "date-fns/locale/pt-BR";
|
import ptBR from "date-fns/locale/pt-BR";
|
||||||
|
|
||||||
const isHoliday = async () => {
|
const isHoliday = async (number: string | number) => {
|
||||||
let obj = { set: false, msg: "" };
|
let obj = { set: false, msg: "" };
|
||||||
|
|
||||||
const holiday = await SettingTicket.findOne({
|
const holiday = await SettingTicket.findOne({
|
||||||
where: { key: "holiday" }
|
where: { key: "holiday", number }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -50,19 +50,18 @@ const isHoliday = async () => {
|
||||||
return obj;
|
return obj;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isWeekend = async () => {
|
const isWeekend = async (number: string | number) => {
|
||||||
let obj = { set: false, msg: "" };
|
let obj = { set: false, msg: "" };
|
||||||
|
|
||||||
const weekend = await SettingTicket.findOne({
|
const weekend = await SettingTicket.findOne({
|
||||||
where: { key: "weekend" }
|
where: { key: "weekend", number }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
weekend &&
|
weekend &&
|
||||||
weekend.value == "enabled" &&
|
weekend.value == "enabled" &&
|
||||||
weekend.message?.trim()?.length > 0
|
weekend.message?.trim()?.length > 0
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// Specify your desired timezone
|
// Specify your desired timezone
|
||||||
const brazilTimeZone = "America/Sao_Paulo";
|
const brazilTimeZone = "America/Sao_Paulo";
|
||||||
|
|
||||||
|
@ -100,20 +99,19 @@ const isWeekend = async () => {
|
||||||
obj.set = true;
|
obj.set = true;
|
||||||
obj.msg = weekend.message;
|
obj.msg = weekend.message;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
// obj.set = true;
|
// obj.set = true;
|
||||||
// obj.msg = weekend.message;
|
// obj.msg = weekend.message;
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function isOutBusinessTime() {
|
async function isOutBusinessTime(number: string | number) {
|
||||||
let obj = { set: false, msg: "" };
|
let obj = { set: false, msg: "" };
|
||||||
|
|
||||||
const outBusinessHours = await SettingTicket.findOne({
|
const outBusinessHours = await SettingTicket.findOne({
|
||||||
where: { key: "outBusinessHours" }
|
where: { key: "outBusinessHours", number }
|
||||||
});
|
});
|
||||||
|
|
||||||
let isWithinRange = false;
|
let isWithinRange = false;
|
||||||
|
@ -164,9 +162,9 @@ async function isOutBusinessTime() {
|
||||||
timeInterval.end = nextDay;
|
timeInterval.end = nextDay;
|
||||||
}
|
}
|
||||||
|
|
||||||
isWithinRange = isWithinInterval(parsedTimeToCheck, timeInterval);
|
isWithinRange = isWithinInterval(parsedTimeToCheck, timeInterval);
|
||||||
|
|
||||||
if (!isWithinRange) {
|
if (!isWithinRange) {
|
||||||
obj.set = true;
|
obj.set = true;
|
||||||
obj.msg = outBusinessHours.message;
|
obj.msg = outBusinessHours.message;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,9 @@ class SettingTicket extends Model<SettingTicket> {
|
||||||
@Column
|
@Column
|
||||||
key: string;
|
key: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
number: string;
|
||||||
|
|
||||||
@CreatedAt
|
@CreatedAt
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ const settingRoutes = Router();
|
||||||
|
|
||||||
settingRoutes.get("/settings", SettingController.index);
|
settingRoutes.get("/settings", SettingController.index);
|
||||||
|
|
||||||
|
settingRoutes.get("/settings/ticket/:number", SettingController.ticketSettings);
|
||||||
|
|
||||||
// routes.get("/settings/:settingKey", isAuth, SettingsController.show);
|
// routes.get("/settings/:settingKey", isAuth, SettingsController.show);
|
||||||
|
|
||||||
settingRoutes.put(
|
settingRoutes.put(
|
||||||
|
|
|
@ -7,6 +7,7 @@ interface Request {
|
||||||
endTime: string;
|
endTime: string;
|
||||||
value: string;
|
value: string;
|
||||||
message: string;
|
message: string;
|
||||||
|
number: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateSettingTicket = async ({
|
const updateSettingTicket = async ({
|
||||||
|
@ -14,16 +15,30 @@ const updateSettingTicket = async ({
|
||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
value,
|
value,
|
||||||
message
|
message,
|
||||||
|
number
|
||||||
}: Request): Promise<SettingTicket | undefined> => {
|
}: Request): Promise<SettingTicket | undefined> => {
|
||||||
try {
|
try {
|
||||||
const businessHours = await SettingTicket.findOne({ where: { key } });
|
let businessHours = await SettingTicket.findOne({ where: { key, number } });
|
||||||
|
|
||||||
if (!businessHours) {
|
if (!businessHours) {
|
||||||
throw new AppError("ERR_NO_SETTING_FOUND", 404);
|
// throw new AppError("ERR_NO_SETTING_FOUND", 404);
|
||||||
|
|
||||||
|
businessHours = await SettingTicket.create({
|
||||||
|
key,
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
value,
|
||||||
|
message,
|
||||||
|
number
|
||||||
|
});
|
||||||
|
return businessHours;
|
||||||
}
|
}
|
||||||
|
|
||||||
await businessHours.update({ startTime, endTime, message, value });
|
await businessHours.update(
|
||||||
|
{ startTime, endTime, message, value, number },
|
||||||
|
{ where: { key, number } }
|
||||||
|
);
|
||||||
|
|
||||||
return businessHours;
|
return businessHours;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
|
@ -1,47 +1,60 @@
|
||||||
|
import { Sequelize } from "sequelize";
|
||||||
import { Sequelize, } from "sequelize";
|
|
||||||
|
|
||||||
const dbConfig = require("../../config/database");
|
const dbConfig = require("../../config/database");
|
||||||
const sequelize = new Sequelize(dbConfig);
|
const sequelize = new Sequelize(dbConfig);
|
||||||
const { QueryTypes } = require('sequelize');
|
const { QueryTypes } = require("sequelize");
|
||||||
|
|
||||||
import { splitDateTime } from "../../helpers/SplitDateTime";
|
import { splitDateTime } from "../../helpers/SplitDateTime";
|
||||||
import format from 'date-fns/format';
|
import format from "date-fns/format";
|
||||||
import ptBR from 'date-fns/locale/pt-BR';
|
import ptBR from "date-fns/locale/pt-BR";
|
||||||
|
|
||||||
|
interface Request {
|
||||||
interface Request {
|
timeseconds: string | number;
|
||||||
timeseconds: string | number;
|
status: string;
|
||||||
status: string;
|
number?: string;
|
||||||
userId?: string | number;
|
userId?: string | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListTicketTimeLife = async ({timeseconds, status, userId }: Request): Promise<any[]> => {
|
const ListTicketTimeLife = async ({
|
||||||
|
timeseconds,
|
||||||
|
status,
|
||||||
|
number,
|
||||||
|
userId
|
||||||
|
}: Request): Promise<any[]> => {
|
||||||
|
let tickets = [];
|
||||||
|
|
||||||
let tickets = []
|
let currentDate = format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR });
|
||||||
|
|
||||||
let currentDate = format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR })
|
// console.log('------------------> currentDate: ', currentDate)
|
||||||
|
|
||||||
// console.log('------------------> currentDate: ', currentDate)
|
if (userId) {
|
||||||
|
// CONSULTANDO FILAS PELO ID DO USUARIO
|
||||||
if (userId) {
|
tickets = await sequelize.query(
|
||||||
// CONSULTANDO FILAS PELO ID DO USUARIO
|
`select user.id as user_id, user.name as user_name, t.id as ticket_id from Tickets as t inner join Users as user on
|
||||||
tickets = await sequelize.query(`select user.id as user_id, user.name as user_name, t.id as ticket_id from Tickets as t inner join Users as user on
|
t.userId = user.id and user.name = 'botqueue' and t.status='${status}' and (TIMESTAMPDIFF(SECOND, t.updatedAt, '${currentDate}')) >= ${timeseconds};`,
|
||||||
t.userId = user.id and user.name = 'botqueue' and t.status='${status}' and (TIMESTAMPDIFF(SECOND, t.updatedAt, '${currentDate}')) >= ${timeseconds};`, { type: QueryTypes.SELECT });
|
{ type: QueryTypes.SELECT }
|
||||||
|
);
|
||||||
|
} else if (number) {
|
||||||
|
// CONSULTANDO TICKETS PELO WHATSAPP
|
||||||
|
tickets = await sequelize.query(
|
||||||
|
`SELECT t.id AS ticket_id
|
||||||
|
FROM Tickets t
|
||||||
|
JOIN Whatsapps w ON t.whatsappId = w.id
|
||||||
|
AND w.number = ${number}
|
||||||
|
WHERE t.status = 'open'
|
||||||
|
AND TIMESTAMPDIFF(SECOND, t.updatedAt, '${currentDate}') >= ${timeseconds};`,
|
||||||
|
{ type: QueryTypes.SELECT }
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// CONSULTANDO FILAS PELO USUARIO
|
||||||
|
tickets = await sequelize.query(
|
||||||
|
`select id as ticket_id from Tickets where status='${status}' and
|
||||||
|
(TIMESTAMPDIFF(SECOND, updatedAt, '${currentDate}')) >= ${timeseconds};`,
|
||||||
|
{ type: QueryTypes.SELECT }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
return tickets;
|
||||||
|
|
||||||
// CONSULTANDO FILAS PELO USUARIO
|
|
||||||
tickets = await sequelize.query(`select id as ticket_id from Tickets where status='${status}' and
|
|
||||||
(TIMESTAMPDIFF(SECOND, updatedAt, '${currentDate}')) >= ${timeseconds};`, { type: QueryTypes.SELECT });
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return tickets;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ListTicketTimeLife;
|
export default ListTicketTimeLife;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,7 @@ import { Op } from "sequelize";
|
||||||
|
|
||||||
import SettingTicket from "../../models/SettingTicket";
|
import SettingTicket from "../../models/SettingTicket";
|
||||||
import mostRepeatedPhrase from "../../helpers/MostRepeatedPhrase";
|
import mostRepeatedPhrase from "../../helpers/MostRepeatedPhrase";
|
||||||
|
import ListWhatsAppsNumber from "../WhatsappService/ListWhatsAppsNumber";
|
||||||
|
|
||||||
var lst: any[] = getWhatsappIds();
|
var lst: any[] = getWhatsappIds();
|
||||||
|
|
||||||
|
@ -152,7 +153,7 @@ const verifyMediaMessage = async (
|
||||||
if (!media) {
|
if (!media) {
|
||||||
throw new Error("ERR_WAPP_DOWNLOAD_MEDIA");
|
throw new Error("ERR_WAPP_DOWNLOAD_MEDIA");
|
||||||
}
|
}
|
||||||
|
|
||||||
let messageData = {
|
let messageData = {
|
||||||
id: msg.id.id,
|
id: msg.id.id,
|
||||||
ticketId: ticket.id,
|
ticketId: ticket.id,
|
||||||
|
@ -165,7 +166,6 @@ const verifyMediaMessage = async (
|
||||||
quotedMsgId: quotedMsg,
|
quotedMsgId: quotedMsg,
|
||||||
phoneNumberId: msg?.phoneNumberId
|
phoneNumberId: msg?.phoneNumberId
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if (!ticket?.phoneNumberId) {
|
if (!ticket?.phoneNumberId) {
|
||||||
if (!media.filename) {
|
if (!media.filename) {
|
||||||
|
@ -176,7 +176,7 @@ const verifyMediaMessage = async (
|
||||||
mediaUrl: media.filename,
|
mediaUrl: media.filename,
|
||||||
body: media.filename
|
body: media.filename
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await writeFileAsync(
|
await writeFileAsync(
|
||||||
|
@ -195,7 +195,7 @@ const verifyMediaMessage = async (
|
||||||
|
|
||||||
return newMessage;
|
return newMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
// const verifyMediaMessage = async (
|
// const verifyMediaMessage = async (
|
||||||
// msg: any,
|
// msg: any,
|
||||||
// ticket: Ticket,
|
// ticket: Ticket,
|
||||||
|
@ -263,10 +263,6 @@ const verifyMediaMessage = async (
|
||||||
// return newMessage;
|
// return newMessage;
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const verifyMessage = async (
|
const verifyMessage = async (
|
||||||
msg: any,
|
msg: any,
|
||||||
ticket: Ticket,
|
ticket: Ticket,
|
||||||
|
@ -347,7 +343,9 @@ const verifyQueue = async (
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
|
|
||||||
const outService = await outOfService();
|
let whatsapp: any = await whatsappInfo(ticket?.whatsappId);
|
||||||
|
|
||||||
|
const outService = await outOfService(whatsapp?.number);
|
||||||
|
|
||||||
if (outService.length > 0) {
|
if (outService.length > 0) {
|
||||||
const { type, msg: msgOutService } = outService[0];
|
const { type, msg: msgOutService } = outService[0];
|
||||||
|
@ -552,20 +550,24 @@ const handleMessage = async (
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let msgContact: any = wbot.msgContact;
|
let msgContact: any = wbot.msgContact;
|
||||||
// let groupContact: Contact | undefined;
|
// let groupContact: Contact | undefined;
|
||||||
|
|
||||||
if (msg.fromMe) {
|
if (msg.fromMe) {
|
||||||
const ticketExpiration = await SettingTicket.findOne({
|
const whatsapp = await whatsappInfo(wbot.id);
|
||||||
where: { key: "ticketExpiration" }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
if (whatsapp?.number) {
|
||||||
ticketExpiration &&
|
const ticketExpiration = await SettingTicket.findOne({
|
||||||
ticketExpiration.value == "enabled" &&
|
where: { key: "ticketExpiration", number: whatsapp.number }
|
||||||
ticketExpiration?.message.trim() == msg.body.trim()
|
});
|
||||||
) {
|
|
||||||
console.log("*********** TICKET EXPIRATION");
|
if (
|
||||||
return;
|
ticketExpiration &&
|
||||||
|
ticketExpiration.value == "enabled" &&
|
||||||
|
ticketExpiration?.message.trim() == msg.body.trim()
|
||||||
|
) {
|
||||||
|
console.log("*********** TICKET EXPIRATION");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// messages sent automatically by wbot have a special character in front of it
|
// messages sent automatically by wbot have a special character in front of it
|
||||||
|
@ -694,22 +696,23 @@ const handleMessage = async (
|
||||||
ticketHasQueue = true;
|
ticketHasQueue = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ticketHasQueue && ticket.status != "open") {
|
if (ticketHasQueue && ticket.status != "open") {
|
||||||
const outService = await outOfService();
|
let whatsapp: any = await whatsappInfo(ticket?.whatsappId);
|
||||||
|
|
||||||
if (outService.length > 0) {
|
const outService = await outOfService(whatsapp.number);
|
||||||
const { type, msg: msgOutService } = outService[0];
|
|
||||||
|
|
||||||
if (msg.fromMe && msgOutService == msg.body) {
|
if (outService.length > 0) {
|
||||||
console.log(`${type} message ignored`);
|
const { type, msg: msgOutService } = outService[0];
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
botSendMessage(ticket, msgOutService);
|
if (msg.fromMe && msgOutService == msg.body) {
|
||||||
return;
|
console.log(`${type} message ignored`);
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
botSendMessage(ticket, msgOutService);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
console.log("Error handling whatsapp message: Err: ", err);
|
console.log("Error handling whatsapp message: Err: ", err);
|
||||||
|
@ -778,9 +781,9 @@ const wbotMessageListener = (wbot: Session): void => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const outOfService = async () => {
|
const outOfService = async (number: string) => {
|
||||||
// MESSAGE TO HOLIDAY
|
// MESSAGE TO HOLIDAY
|
||||||
const holiday: any = await isHoliday();
|
const holiday: any = await isHoliday(number);
|
||||||
|
|
||||||
let objs: any = [];
|
let objs: any = [];
|
||||||
|
|
||||||
|
@ -789,14 +792,14 @@ const outOfService = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MESSAGES TO SATURDAY OR SUNDAY
|
// MESSAGES TO SATURDAY OR SUNDAY
|
||||||
const weekend: any = await isWeekend();
|
const weekend: any = await isWeekend(number);
|
||||||
|
|
||||||
if (weekend && weekend.set) {
|
if (weekend && weekend.set) {
|
||||||
objs.push({ type: "weekend", msg: weekend.msg });
|
objs.push({ type: "weekend", msg: weekend.msg });
|
||||||
}
|
}
|
||||||
|
|
||||||
// MESSAGE TO BUSINESS TIME
|
// MESSAGE TO BUSINESS TIME
|
||||||
const businessTime = await isOutBusinessTime();
|
const businessTime = await isOutBusinessTime(number);
|
||||||
|
|
||||||
if (businessTime && businessTime.set) {
|
if (businessTime && businessTime.set) {
|
||||||
objs.push({ type: "businessTime", msg: businessTime.msg });
|
objs.push({ type: "businessTime", msg: businessTime.msg });
|
||||||
|
@ -816,3 +819,6 @@ export {
|
||||||
isValidMsg,
|
isValidMsg,
|
||||||
mediaTypeWhatsappOfficial
|
mediaTypeWhatsappOfficial
|
||||||
};
|
};
|
||||||
|
async function whatsappInfo(whatsappId: string | number) {
|
||||||
|
return await Whatsapp.findByPk(whatsappId);
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState, useEffect, } from 'react'
|
import React, { useState, useEffect, useContext } from 'react'
|
||||||
// import * as Yup from 'yup'
|
// import * as Yup from 'yup'
|
||||||
import { Formik, Form, Field, } from 'formik'
|
import { Formik, Form, Field, } from 'formik'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
|
@ -12,7 +12,7 @@ import DateFnsUtils from '@date-io/date-fns'
|
||||||
|
|
||||||
import ptBrLocale from "date-fns/locale/pt-BR"
|
import ptBrLocale from "date-fns/locale/pt-BR"
|
||||||
|
|
||||||
|
import { WhatsAppsContext } from "../../context/WhatsApp/WhatsAppsContext"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MuiPickersUtilsProvider,
|
MuiPickersUtilsProvider,
|
||||||
|
@ -28,12 +28,16 @@ import {
|
||||||
TextField,
|
TextField,
|
||||||
Switch,
|
Switch,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
|
Divider,
|
||||||
} from '@material-ui/core'
|
} from '@material-ui/core'
|
||||||
|
|
||||||
import api from '../../services/api'
|
import api from '../../services/api'
|
||||||
import { i18n } from '../../translate/i18n'
|
import { i18n } from '../../translate/i18n'
|
||||||
import toastError from '../../errors/toastError'
|
import toastError from '../../errors/toastError'
|
||||||
|
|
||||||
|
import Select from "@material-ui/core/Select"
|
||||||
|
import MenuItem from "@material-ui/core/MenuItem"
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
@ -87,19 +91,41 @@ const ConfigModal = ({ open, onClose, change }) => {
|
||||||
enableWeekendMessage: false
|
enableWeekendMessage: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { whatsApps } = useContext(WhatsAppsContext)
|
||||||
|
const [selectedNumber, setSelectedNumber] = useState('')
|
||||||
|
|
||||||
const [config, setConfig] = useState(initialState)
|
const [config, setConfig] = useState(initialState)
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('selectedNumber: ', selectedNumber)
|
||||||
|
|
||||||
|
if (selectedNumber?.trim().length === 0) {
|
||||||
|
setConfig(initialState)
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [selectedNumber])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchSession = async () => {
|
const fetchSession = async () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await api.get('/settings')
|
// const { data } = await api.get('/settings')
|
||||||
|
|
||||||
|
if (!selectedNumber) return
|
||||||
|
|
||||||
|
const { data } = await api.get(`/settings/ticket/${selectedNumber}`)
|
||||||
|
|
||||||
|
if (data?.config && data.config.length === 0) {
|
||||||
|
setConfig(initialState)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const outBusinessHours = data.config.find((c) => c.key === "outBusinessHours")
|
const outBusinessHours = data.config.find((c) => c.key === "outBusinessHours")
|
||||||
const ticketExpiration = data.config.find((c) => c.key === "ticketExpiration")
|
const ticketExpiration = data.config.find((c) => c.key === "ticketExpiration")
|
||||||
const saturday = data.config.find((c) => c.key === "saturday")
|
const saturday = data.config.find((c) => c.key === "saturday")
|
||||||
const sunday = data.config.find((c) => c.key === "sunday")
|
const sunday = data.config.find((c) => c.key === "sunday")
|
||||||
const weekend = data.config.find((c) => c.key === "weekend")
|
const weekend = data.config.find((c) => c.key === "weekend")
|
||||||
const holiday = data.config.find((c) => c.key === "holiday")
|
const holiday = data.config.find((c) => c.key === "holiday")
|
||||||
|
|
||||||
setConfig({
|
setConfig({
|
||||||
|
@ -107,7 +133,7 @@ const ConfigModal = ({ open, onClose, change }) => {
|
||||||
endTimeBus: outBusinessHours.endTime,
|
endTimeBus: outBusinessHours.endTime,
|
||||||
messageBus: outBusinessHours.message,
|
messageBus: outBusinessHours.message,
|
||||||
businessTimeEnable: outBusinessHours.value === 'enabled' ? true : false,
|
businessTimeEnable: outBusinessHours.value === 'enabled' ? true : false,
|
||||||
|
|
||||||
ticketTimeExpiration: ticketExpiration.startTime,
|
ticketTimeExpiration: ticketExpiration.startTime,
|
||||||
ticketExpirationMsg: ticketExpiration.message,
|
ticketExpirationMsg: ticketExpiration.message,
|
||||||
ticketExpirationEnable: ticketExpiration.value === 'enabled' ? true : false,
|
ticketExpirationEnable: ticketExpiration.value === 'enabled' ? true : false,
|
||||||
|
@ -127,11 +153,12 @@ const ConfigModal = ({ open, onClose, change }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchSession()
|
fetchSession()
|
||||||
}, [change])
|
}, [change, selectedNumber])
|
||||||
|
|
||||||
const handleSaveConfig = async (values) => {
|
const handleSaveConfig = async (values) => {
|
||||||
|
|
||||||
values = {
|
values = {
|
||||||
|
number: selectedNumber,
|
||||||
outBusinessHours: {
|
outBusinessHours: {
|
||||||
startTime: values.startTimeBus,
|
startTime: values.startTimeBus,
|
||||||
endTime: values.endTimeBus,
|
endTime: values.endTimeBus,
|
||||||
|
@ -143,11 +170,11 @@ const ConfigModal = ({ open, onClose, change }) => {
|
||||||
message: values.ticketExpirationMsg,
|
message: values.ticketExpirationMsg,
|
||||||
value: values.ticketExpirationEnable ? 'enabled' : 'disabled'
|
value: values.ticketExpirationEnable ? 'enabled' : 'disabled'
|
||||||
},
|
},
|
||||||
weekend: {
|
weekend: {
|
||||||
message: values.weekendMessage,
|
message: values.weekendMessage,
|
||||||
value: values.enableWeekendMessage ? 'enabled' : 'disabled'
|
value: values.enableWeekendMessage ? 'enabled' : 'disabled'
|
||||||
},
|
},
|
||||||
saturday:{
|
saturday: {
|
||||||
value: values.checkboxSaturdayValue ? 'enabled' : 'disabled'
|
value: values.checkboxSaturdayValue ? 'enabled' : 'disabled'
|
||||||
},
|
},
|
||||||
sunday: {
|
sunday: {
|
||||||
|
@ -211,6 +238,39 @@ const ConfigModal = ({ open, onClose, change }) => {
|
||||||
|
|
||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Select
|
||||||
|
value={selectedNumber}
|
||||||
|
onChange={(e) => setSelectedNumber(e.target.value)}
|
||||||
|
label={i18n.t("transferTicketModal.fieldQueuePlaceholder")}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<MenuItem style={{ background: "white", }} value={''}> </MenuItem>
|
||||||
|
|
||||||
|
{whatsApps.reduce((acc, curr) => {
|
||||||
|
const existingObject = acc.find(item => item.number === curr.number)
|
||||||
|
|
||||||
|
if (!existingObject) {
|
||||||
|
acc.push(curr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, []).map((whatsapp) => (
|
||||||
|
<MenuItem
|
||||||
|
key={whatsapp.id}
|
||||||
|
value={whatsapp.number}
|
||||||
|
>
|
||||||
|
{whatsapp.number}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<br />
|
||||||
|
|
||||||
<div className={classes.multFieldLine}>
|
<div className={classes.multFieldLine}>
|
||||||
<Field
|
<Field
|
||||||
component={TimePicker}
|
component={TimePicker}
|
||||||
|
@ -443,7 +503,7 @@ const ConfigModal = ({ open, onClose, change }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default React.memo(ConfigModal)
|
export default React.memo(ConfigModal)
|
||||||
|
|
|
@ -227,9 +227,30 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => {
|
||||||
<DialogTitle id="form-dialog-title">
|
<DialogTitle id="form-dialog-title">
|
||||||
{i18n.t("transferTicketModal.title")}
|
{i18n.t("transferTicketModal.title")}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent dividers>
|
<DialogContent dividers >
|
||||||
|
<FormControl variant="outlined" className={classes.maxWidth} style={{marginBottom: '8px'}}>
|
||||||
|
{/* <InputLabel>{i18n.t("transferTicketModal.fieldQueueLabel")}</InputLabel> */}
|
||||||
|
|
||||||
|
<InputLabel>{'Usuário'}</InputLabel>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
value={selectedUser}
|
||||||
|
onChange={e => setSelectedUser(e.target.value)}
|
||||||
|
label={'Transfeir para fila'}
|
||||||
|
>
|
||||||
|
<MenuItem style={{ background: "white", }} value={''}> </MenuItem>
|
||||||
|
{users.map((user) => (
|
||||||
|
<MenuItem
|
||||||
|
key={user.id}
|
||||||
|
value={user.id}
|
||||||
|
>{user.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
<FormControl variant="outlined" className={classes.maxWidth}>
|
<FormControl variant="outlined" className={classes.maxWidth}>
|
||||||
<InputLabel>{i18n.t("transferTicketModal.fieldQueueLabel")}</InputLabel>
|
<InputLabel>{i18n.t("transferTicketModal.fieldQueuePlaceholder")}</InputLabel>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
value={selectedQueue}
|
value={selectedQueue}
|
||||||
|
@ -253,24 +274,6 @@ const TransferTicketModal = ({ modalOpen, onClose, ticketid }) => {
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<Select
|
|
||||||
value={selectedUser}
|
|
||||||
onChange={e => setSelectedUser(e.target.value)}
|
|
||||||
label={'Transfeir para usuario'}
|
|
||||||
>
|
|
||||||
<MenuItem style={{ background: "white", }} value={''}> </MenuItem>
|
|
||||||
{users.map((user) => (
|
|
||||||
<MenuItem
|
|
||||||
key={user.id}
|
|
||||||
value={user.id}
|
|
||||||
>{user.name}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
|
|
|
@ -9,8 +9,8 @@ import Divider from '@material-ui/core/Divider'
|
||||||
import { Badge } from '@material-ui/core'
|
import { Badge } from '@material-ui/core'
|
||||||
import DashboardOutlinedIcon from '@material-ui/icons/DashboardOutlined'
|
import DashboardOutlinedIcon from '@material-ui/icons/DashboardOutlined'
|
||||||
|
|
||||||
import ReportOutlinedIcon from '@material-ui/icons/ReportOutlined'
|
import ReportOutlinedIcon from '@material-ui/icons/ReportOutlined'
|
||||||
import CampaignIcon from '@material-ui/icons/Send';
|
import CampaignIcon from '@material-ui/icons/Send'
|
||||||
|
|
||||||
|
|
||||||
import SendOutlined from '@material-ui/icons/SendOutlined'
|
import SendOutlined from '@material-ui/icons/SendOutlined'
|
||||||
|
@ -29,6 +29,10 @@ import { i18n } from '../translate/i18n'
|
||||||
import { WhatsAppsContext } from '../context/WhatsApp/WhatsAppsContext'
|
import { WhatsAppsContext } from '../context/WhatsApp/WhatsAppsContext'
|
||||||
import { AuthContext } from '../context/Auth/AuthContext'
|
import { AuthContext } from '../context/Auth/AuthContext'
|
||||||
import { Can } from '../components/Can'
|
import { Can } from '../components/Can'
|
||||||
|
import openSocket from 'socket.io-client'
|
||||||
|
import api from '../services/api'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function ListItemLink(props) {
|
function ListItemLink(props) {
|
||||||
const { icon, primary, to, className } = props
|
const { icon, primary, to, className } = props
|
||||||
|
@ -56,6 +60,8 @@ const MainListItems = (props) => {
|
||||||
const { whatsApps } = useContext(WhatsAppsContext)
|
const { whatsApps } = useContext(WhatsAppsContext)
|
||||||
const { user } = useContext(AuthContext)
|
const { user } = useContext(AuthContext)
|
||||||
const [connectionWarning, setConnectionWarning] = useState(false)
|
const [connectionWarning, setConnectionWarning] = useState(false)
|
||||||
|
const [settings, setSettings] = useState([])
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const delayDebounceFn = setTimeout(() => {
|
const delayDebounceFn = setTimeout(() => {
|
||||||
|
@ -79,6 +85,55 @@ const MainListItems = (props) => {
|
||||||
return () => clearTimeout(delayDebounceFn)
|
return () => clearTimeout(delayDebounceFn)
|
||||||
}, [whatsApps])
|
}, [whatsApps])
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const delayDebounceFn = setTimeout(() => {
|
||||||
|
const fetchSession = async () => {
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
const { data } = await api.get('/settings')
|
||||||
|
|
||||||
|
setSettings(data.settings)
|
||||||
|
} catch (err) {
|
||||||
|
// toastError(err)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// toastError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchSession()
|
||||||
|
}, 500)
|
||||||
|
return () => clearTimeout(delayDebounceFn)
|
||||||
|
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const getSettingValue = (key) => {
|
||||||
|
|
||||||
|
return settings?.find((s) => s?.key === key)?.value
|
||||||
|
|
||||||
|
// const { value } = settings.find((s) => s?.key === key)
|
||||||
|
// return value
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const socket = openSocket(process.env.REACT_APP_BACKEND_URL)
|
||||||
|
|
||||||
|
socket.on('settings', (data) => {
|
||||||
|
if (data.action === 'update') {
|
||||||
|
setSettings((prevState) => {
|
||||||
|
const aux = [...prevState]
|
||||||
|
const settingIndex = aux.findIndex((s) => s.key === data.setting.key)
|
||||||
|
aux[settingIndex].value = data.setting.value
|
||||||
|
return aux
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.disconnect()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
//Solicitado pelo Adriano: Click no LinkItem e fechar o menu!
|
//Solicitado pelo Adriano: Click no LinkItem e fechar o menu!
|
||||||
<div onClick={() => setDrawerOpen(false)}>
|
<div onClick={() => setDrawerOpen(false)}>
|
||||||
|
@ -103,8 +158,8 @@ const MainListItems = (props) => {
|
||||||
primary={i18n.t('mainDrawer.listItems.quickAnswers')}
|
primary={i18n.t('mainDrawer.listItems.quickAnswers')}
|
||||||
icon={<QuestionAnswerOutlinedIcon />}
|
icon={<QuestionAnswerOutlinedIcon />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<Can
|
<Can
|
||||||
role={user.profile}
|
role={user.profile}
|
||||||
perform="dashboard-view:show"
|
perform="dashboard-view:show"
|
||||||
|
@ -112,11 +167,7 @@ const MainListItems = (props) => {
|
||||||
<>
|
<>
|
||||||
<Divider />
|
<Divider />
|
||||||
<ListSubheader inset>{i18n.t("mainDrawer.listItems.administration")}</ListSubheader>
|
<ListSubheader inset>{i18n.t("mainDrawer.listItems.administration")}</ListSubheader>
|
||||||
<ListItemLink
|
|
||||||
to="/users"
|
|
||||||
primary={i18n.t("mainDrawer.listItems.users")}
|
|
||||||
icon={<PeopleAltOutlinedIcon />}
|
|
||||||
/>
|
|
||||||
<ListItemLink
|
<ListItemLink
|
||||||
to="/"
|
to="/"
|
||||||
primary="Dashboard"
|
primary="Dashboard"
|
||||||
|
@ -132,7 +183,7 @@ const MainListItems = (props) => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<Can
|
<Can
|
||||||
|
@ -140,7 +191,11 @@ const MainListItems = (props) => {
|
||||||
perform="drawer-admin-items:view"
|
perform="drawer-admin-items:view"
|
||||||
yes={() => (
|
yes={() => (
|
||||||
<>
|
<>
|
||||||
|
<ListItemLink
|
||||||
|
to="/users"
|
||||||
|
primary={i18n.t("mainDrawer.listItems.users")}
|
||||||
|
icon={<PeopleAltOutlinedIcon />}
|
||||||
|
/>
|
||||||
<ListItemLink
|
<ListItemLink
|
||||||
to="/queues"
|
to="/queues"
|
||||||
primary={i18n.t('mainDrawer.listItems.queues')}
|
primary={i18n.t('mainDrawer.listItems.queues')}
|
||||||
|
@ -169,11 +224,16 @@ const MainListItems = (props) => {
|
||||||
icon={<ReportOutlinedIcon />}
|
icon={<ReportOutlinedIcon />}
|
||||||
/> */}
|
/> */}
|
||||||
|
|
||||||
<ListItemLink
|
{
|
||||||
to="/campaign"
|
(getSettingValue('hasCampaign') === 'enabled' || user.profile === 'master') && (
|
||||||
primary="Campanha"
|
<ListItemLink
|
||||||
icon={<CampaignIcon />}
|
to="/campaign"
|
||||||
/>
|
primary="Campanha"
|
||||||
|
icon={<CampaignIcon />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
<Can
|
<Can
|
||||||
role={user.profile}
|
role={user.profile}
|
||||||
|
|
|
@ -103,7 +103,7 @@ const Queues = () => {
|
||||||
const [settings, setSettings] = useState([])
|
const [settings, setSettings] = useState([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
;(async () => {
|
; (async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const { data } = await api.get('/queue')
|
const { data } = await api.get('/queue')
|
||||||
|
@ -203,8 +203,7 @@ const Queues = () => {
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
title={
|
title={
|
||||||
selectedQueue &&
|
selectedQueue &&
|
||||||
`${i18n.t('queues.confirmationModal.deleteTitle')} ${
|
`${i18n.t('queues.confirmationModal.deleteTitle')} ${selectedQueue.name
|
||||||
selectedQueue.name
|
|
||||||
}?`
|
}?`
|
||||||
}
|
}
|
||||||
open={confirmModalOpen}
|
open={confirmModalOpen}
|
||||||
|
@ -301,28 +300,36 @@ const Queues = () => {
|
||||||
settings.length > 0 &&
|
settings.length > 0 &&
|
||||||
getSettingValue('editQueue') &&
|
getSettingValue('editQueue') &&
|
||||||
getSettingValue('editQueue') === 'enabled') |
|
getSettingValue('editQueue') === 'enabled') |
|
||||||
(user.profile === 'master') ? (
|
(user.profile === 'master') ? (
|
||||||
<IconButton
|
|
||||||
size="small"
|
<>
|
||||||
onClick={() => handleEditQueue(queue)}
|
<IconButton
|
||||||
>
|
size="small"
|
||||||
<Edit />
|
onClick={() => handleEditQueue(queue)}
|
||||||
</IconButton>
|
>
|
||||||
|
<Edit />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedQueue(queue)
|
||||||
|
setConfirmModalOpen(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteOutline />
|
||||||
|
</IconButton>
|
||||||
|
</>
|
||||||
|
|
||||||
|
|
||||||
) : (
|
) : (
|
||||||
<div></div>
|
<div></div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
// <IconButton
|
|
||||||
// size="small"
|
|
||||||
// onClick={() => handleEditQueue(queue)}
|
|
||||||
// >
|
|
||||||
// <Edit />
|
|
||||||
// </IconButton>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Can
|
{/* <Can
|
||||||
role={user.profile}
|
role={user.profile}
|
||||||
perform="show-icon-delete-queue"
|
perform="show-icon-delete-queue"
|
||||||
yes={() => (
|
yes={() => (
|
||||||
|
@ -336,7 +343,7 @@ const Queues = () => {
|
||||||
<DeleteOutline />
|
<DeleteOutline />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
/>
|
/> */}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -285,6 +285,33 @@ const Settings = () => {
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={classes.root}>
|
||||||
|
<Container className={classes.container} maxWidth="sm">
|
||||||
|
<Paper className={classes.paper}>
|
||||||
|
<Typography variant="body1">
|
||||||
|
Modulo campanha
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
margin="dense"
|
||||||
|
variant="outlined"
|
||||||
|
native
|
||||||
|
id="hasCampaign-setting"
|
||||||
|
name="hasCampaign"
|
||||||
|
value={
|
||||||
|
settings &&
|
||||||
|
settings.length > 0 &&
|
||||||
|
getSettingValue('hasCampaign')
|
||||||
|
}
|
||||||
|
className={classes.settingOption}
|
||||||
|
onChange={handleChangeSetting}
|
||||||
|
>
|
||||||
|
<option value="enabled">Ativado</option>
|
||||||
|
<option value="disabled">Desativado</option>
|
||||||
|
</Select>
|
||||||
|
</Paper>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,82 +1,82 @@
|
||||||
import React, { useState, useEffect, useReducer, useContext} from "react";
|
import React, { useState, useEffect, useReducer, useContext } from "react"
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify"
|
||||||
import openSocket from "socket.io-client";
|
import openSocket from "socket.io-client"
|
||||||
|
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles } from "@material-ui/core/styles"
|
||||||
import Paper from "@material-ui/core/Paper";
|
import Paper from "@material-ui/core/Paper"
|
||||||
import Button from "@material-ui/core/Button";
|
import Button from "@material-ui/core/Button"
|
||||||
import Table from "@material-ui/core/Table";
|
import Table from "@material-ui/core/Table"
|
||||||
import TableBody from "@material-ui/core/TableBody";
|
import TableBody from "@material-ui/core/TableBody"
|
||||||
import TableCell from "@material-ui/core/TableCell";
|
import TableCell from "@material-ui/core/TableCell"
|
||||||
import TableHead from "@material-ui/core/TableHead";
|
import TableHead from "@material-ui/core/TableHead"
|
||||||
import TableRow from "@material-ui/core/TableRow";
|
import TableRow from "@material-ui/core/TableRow"
|
||||||
import IconButton from "@material-ui/core/IconButton";
|
import IconButton from "@material-ui/core/IconButton"
|
||||||
import SearchIcon from "@material-ui/icons/Search";
|
import SearchIcon from "@material-ui/icons/Search"
|
||||||
import TextField from "@material-ui/core/TextField";
|
import TextField from "@material-ui/core/TextField"
|
||||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
import InputAdornment from "@material-ui/core/InputAdornment"
|
||||||
|
|
||||||
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
|
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline"
|
||||||
import EditIcon from "@material-ui/icons/Edit";
|
import EditIcon from "@material-ui/icons/Edit"
|
||||||
|
|
||||||
import MainContainer from "../../components/MainContainer";
|
import MainContainer from "../../components/MainContainer"
|
||||||
import MainHeader from "../../components/MainHeader";
|
import MainHeader from "../../components/MainHeader"
|
||||||
import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper";
|
import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper"
|
||||||
import Title from "../../components/Title";
|
import Title from "../../components/Title"
|
||||||
|
|
||||||
import api from "../../services/api";
|
import api from "../../services/api"
|
||||||
import { i18n } from "../../translate/i18n";
|
import { i18n } from "../../translate/i18n"
|
||||||
import TableRowSkeleton from "../../components/TableRowSkeleton";
|
import TableRowSkeleton from "../../components/TableRowSkeleton"
|
||||||
import UserModal from "../../components/UserModal";
|
import UserModal from "../../components/UserModal"
|
||||||
import ConfirmationModal from "../../components/ConfirmationModal";
|
import ConfirmationModal from "../../components/ConfirmationModal"
|
||||||
import toastError from "../../errors/toastError";
|
import toastError from "../../errors/toastError"
|
||||||
|
|
||||||
//--------
|
//--------
|
||||||
import { AuthContext } from "../../context/Auth/AuthContext";
|
import { AuthContext } from "../../context/Auth/AuthContext"
|
||||||
import { Can } from "../../components/Can";
|
import { Can } from "../../components/Can"
|
||||||
|
|
||||||
const reducer = (state, action) => {
|
const reducer = (state, action) => {
|
||||||
if (action.type === "LOAD_USERS") {
|
if (action.type === "LOAD_USERS") {
|
||||||
const users = action.payload;
|
const users = action.payload
|
||||||
const newUsers = [];
|
const newUsers = []
|
||||||
|
|
||||||
users.forEach((user) => {
|
users.forEach((user) => {
|
||||||
const userIndex = state.findIndex((u) => u.id === user.id);
|
const userIndex = state.findIndex((u) => u.id === user.id)
|
||||||
if (userIndex !== -1) {
|
if (userIndex !== -1) {
|
||||||
state[userIndex] = user;
|
state[userIndex] = user
|
||||||
} else {
|
} else {
|
||||||
newUsers.push(user);
|
newUsers.push(user)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
return [...state, ...newUsers];
|
return [...state, ...newUsers]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === "UPDATE_USERS") {
|
if (action.type === "UPDATE_USERS") {
|
||||||
const user = action.payload;
|
const user = action.payload
|
||||||
const userIndex = state.findIndex((u) => u.id === user.id);
|
const userIndex = state.findIndex((u) => u.id === user.id)
|
||||||
|
|
||||||
if (userIndex !== -1) {
|
if (userIndex !== -1) {
|
||||||
state[userIndex] = user;
|
state[userIndex] = user
|
||||||
return [...state];
|
return [...state]
|
||||||
} else {
|
} else {
|
||||||
return [user, ...state];
|
return [user, ...state]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === "DELETE_USER") {
|
if (action.type === "DELETE_USER") {
|
||||||
const userId = action.payload;
|
const userId = action.payload
|
||||||
|
|
||||||
const userIndex = state.findIndex((u) => u.id === userId);
|
const userIndex = state.findIndex((u) => u.id === userId)
|
||||||
if (userIndex !== -1) {
|
if (userIndex !== -1) {
|
||||||
state.splice(userIndex, 1);
|
state.splice(userIndex, 1)
|
||||||
}
|
}
|
||||||
return [...state];
|
return [...state]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === "RESET") {
|
if (action.type === "RESET") {
|
||||||
return [];
|
return []
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
mainPaper: {
|
mainPaper: {
|
||||||
|
@ -85,244 +85,306 @@ const useStyles = makeStyles((theme) => ({
|
||||||
overflowY: "scroll",
|
overflowY: "scroll",
|
||||||
...theme.scrollbarStyles,
|
...theme.scrollbarStyles,
|
||||||
},
|
},
|
||||||
}));
|
}))
|
||||||
|
|
||||||
const Users = () => {
|
const Users = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles()
|
||||||
|
|
||||||
//--------
|
//--------
|
||||||
const { user: userA } = useContext(AuthContext);
|
const { user: userA } = useContext(AuthContext)
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [pageNumber, setPageNumber] = useState(1)
|
||||||
|
const [hasMore, setHasMore] = useState(false)
|
||||||
|
const [selectedUser, setSelectedUser] = useState(null)
|
||||||
|
const [deletingUser, setDeletingUser] = useState(null)
|
||||||
|
const [userModalOpen, setUserModalOpen] = useState(false)
|
||||||
|
const [confirmModalOpen, setConfirmModalOpen] = useState(false)
|
||||||
|
const [searchParam, setSearchParam] = useState("")
|
||||||
|
const [users, dispatch] = useReducer(reducer, [])
|
||||||
|
const [settings, setSettings] = useState([])
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [pageNumber, setPageNumber] = useState(1);
|
|
||||||
const [hasMore, setHasMore] = useState(false);
|
|
||||||
const [selectedUser, setSelectedUser] = useState(null);
|
|
||||||
const [deletingUser, setDeletingUser] = useState(null);
|
|
||||||
const [userModalOpen, setUserModalOpen] = useState(false);
|
|
||||||
const [confirmModalOpen, setConfirmModalOpen] = useState(false);
|
|
||||||
const [searchParam, setSearchParam] = useState("");
|
|
||||||
const [users, dispatch] = useReducer(reducer, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch({ type: "RESET" });
|
dispatch({ type: "RESET" })
|
||||||
setPageNumber(1);
|
setPageNumber(1)
|
||||||
}, [searchParam]);
|
}, [searchParam])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
setLoading(true)
|
||||||
const delayDebounceFn = setTimeout(() => {
|
const delayDebounceFn = setTimeout(() => {
|
||||||
const fetchUsers = async () => {
|
const fetchUsers = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.get("/users/", {
|
const { data } = await api.get("/users/", {
|
||||||
params: { searchParam, pageNumber },
|
params: { searchParam, pageNumber },
|
||||||
});
|
})
|
||||||
dispatch({ type: "LOAD_USERS", payload: data.users });
|
dispatch({ type: "LOAD_USERS", payload: data.users })
|
||||||
setHasMore(data.hasMore);
|
setHasMore(data.hasMore)
|
||||||
setLoading(false);
|
setLoading(false)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toastError(err);
|
toastError(err)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
fetchUsers();
|
fetchUsers()
|
||||||
}, 500);
|
}, 500)
|
||||||
return () => clearTimeout(delayDebounceFn);
|
return () => clearTimeout(delayDebounceFn)
|
||||||
}, [searchParam, pageNumber]);
|
}, [searchParam, pageNumber])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const delayDebounceFn = setTimeout(() => {
|
||||||
|
const fetchSession = async () => {
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
const { data } = await api.get('/settings')
|
||||||
|
|
||||||
|
setSettings(data.settings)
|
||||||
|
} catch (err) {
|
||||||
|
toastError(err)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
toastError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchSession()
|
||||||
|
}, 500)
|
||||||
|
return () => clearTimeout(delayDebounceFn)
|
||||||
|
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const getSettingValue = (key) => {
|
||||||
|
|
||||||
|
return settings?.find((s) => s?.key === key)?.value
|
||||||
|
|
||||||
|
// const { value } = settings.find((s) => s?.key === key)
|
||||||
|
// return value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
const socket = openSocket(process.env.REACT_APP_BACKEND_URL)
|
||||||
|
|
||||||
socket.on("user", (data) => {
|
socket.on("user", (data) => {
|
||||||
if (data.action === "update" || data.action === "create") {
|
if (data.action === "update" || data.action === "create") {
|
||||||
dispatch({ type: "UPDATE_USERS", payload: data.user });
|
dispatch({ type: "UPDATE_USERS", payload: data.user })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.action === "delete") {
|
if (data.action === "delete") {
|
||||||
dispatch({ type: "DELETE_USER", payload: +data.userId });
|
dispatch({ type: "DELETE_USER", payload: +data.userId })
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
|
|
||||||
|
socket.on('settings', (data) => {
|
||||||
|
if (data.action === 'update') {
|
||||||
|
setSettings((prevState) => {
|
||||||
|
const aux = [...prevState]
|
||||||
|
const settingIndex = aux.findIndex((s) => s.key === data.setting.key)
|
||||||
|
aux[settingIndex].value = data.setting.value
|
||||||
|
return aux
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
socket.disconnect();
|
socket.disconnect()
|
||||||
};
|
}
|
||||||
}, []);
|
}, [])
|
||||||
|
|
||||||
const handleOpenUserModal = () => {
|
const handleOpenUserModal = () => {
|
||||||
setSelectedUser(null);
|
setSelectedUser(null)
|
||||||
setUserModalOpen(true);
|
setUserModalOpen(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleCloseUserModal = () => {
|
const handleCloseUserModal = () => {
|
||||||
setSelectedUser(null);
|
setSelectedUser(null)
|
||||||
setUserModalOpen(false);
|
setUserModalOpen(false)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSearch = (event) => {
|
const handleSearch = (event) => {
|
||||||
setSearchParam(event.target.value.toLowerCase());
|
setSearchParam(event.target.value.toLowerCase())
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleEditUser = (user) => {
|
const handleEditUser = (user) => {
|
||||||
setSelectedUser(user);
|
setSelectedUser(user)
|
||||||
setUserModalOpen(true);
|
setUserModalOpen(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleDeleteUser = async (userId) => {
|
const handleDeleteUser = async (userId) => {
|
||||||
try {
|
try {
|
||||||
await api.delete(`/users/${userId}`);
|
await api.delete(`/users/${userId}`)
|
||||||
toast.success(i18n.t("users.toasts.deleted"));
|
toast.success(i18n.t("users.toasts.deleted"))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toastError(err);
|
toastError(err)
|
||||||
}
|
}
|
||||||
setDeletingUser(null);
|
setDeletingUser(null)
|
||||||
setSearchParam("");
|
setSearchParam("")
|
||||||
setPageNumber(1);
|
setPageNumber(1)
|
||||||
};
|
}
|
||||||
|
|
||||||
const loadMore = () => {
|
const loadMore = () => {
|
||||||
setPageNumber((prevState) => prevState + 1);
|
setPageNumber((prevState) => prevState + 1)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleScroll = (e) => {
|
const handleScroll = (e) => {
|
||||||
if (!hasMore || loading) return;
|
if (!hasMore || loading) return
|
||||||
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
|
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget
|
||||||
if (scrollHeight - (scrollTop + 100) < clientHeight) {
|
if (scrollHeight - (scrollTop + 100) < clientHeight) {
|
||||||
loadMore();
|
loadMore()
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
console.log('userA.profile: ', userA.profile)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<Can
|
<Can
|
||||||
role={userA.profile}
|
role={userA.profile}
|
||||||
perform="user-view:show"
|
perform="user-view:show"
|
||||||
yes={() => (
|
yes={() => (
|
||||||
<MainContainer>
|
<MainContainer>
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
title={
|
title={
|
||||||
deletingUser &&
|
deletingUser &&
|
||||||
`${i18n.t("users.confirmationModal.deleteTitle")} ${
|
`${i18n.t("users.confirmationModal.deleteTitle")} ${deletingUser.name
|
||||||
deletingUser.name
|
}?`
|
||||||
}?`
|
}
|
||||||
}
|
open={confirmModalOpen}
|
||||||
open={confirmModalOpen}
|
onClose={setConfirmModalOpen}
|
||||||
onClose={setConfirmModalOpen}
|
onConfirm={() => handleDeleteUser(deletingUser.id)}
|
||||||
onConfirm={() => handleDeleteUser(deletingUser.id)}
|
>
|
||||||
>
|
{i18n.t("users.confirmationModal.deleteMessage")}
|
||||||
{i18n.t("users.confirmationModal.deleteMessage")}
|
</ConfirmationModal>
|
||||||
</ConfirmationModal>
|
<UserModal
|
||||||
<UserModal
|
open={userModalOpen}
|
||||||
open={userModalOpen}
|
onClose={handleCloseUserModal}
|
||||||
onClose={handleCloseUserModal}
|
aria-labelledby="form-dialog-title"
|
||||||
aria-labelledby="form-dialog-title"
|
userId={selectedUser && selectedUser.id}
|
||||||
userId={selectedUser && selectedUser.id}
|
/>
|
||||||
/>
|
<MainHeader>
|
||||||
<MainHeader>
|
<Title>{i18n.t("users.title")}</Title>
|
||||||
<Title>{i18n.t("users.title")}</Title>
|
<MainHeaderButtonsWrapper>
|
||||||
<MainHeaderButtonsWrapper>
|
<TextField
|
||||||
<TextField
|
placeholder={i18n.t("contacts.searchPlaceholder")}
|
||||||
placeholder={i18n.t("contacts.searchPlaceholder")}
|
type="search"
|
||||||
type="search"
|
value={searchParam}
|
||||||
value={searchParam}
|
onChange={handleSearch}
|
||||||
onChange={handleSearch}
|
InputProps={{
|
||||||
InputProps={{
|
startAdornment: (
|
||||||
startAdornment: (
|
<InputAdornment position="start">
|
||||||
<InputAdornment position="start">
|
<SearchIcon style={{ color: "gray" }} />
|
||||||
<SearchIcon style={{ color: "gray" }} />
|
</InputAdornment>
|
||||||
</InputAdornment>
|
),
|
||||||
),
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
<Can
|
|
||||||
role={userA.profile}
|
|
||||||
perform="btn-add-user"
|
|
||||||
yes={() => (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
onClick={handleOpenUserModal}
|
|
||||||
>
|
|
||||||
{i18n.t("users.buttons.add")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
{
|
||||||
|
(getSettingValue('userCreation') === 'enabled' || userA.profile === 'master') && (
|
||||||
|
<Button
|
||||||
</MainHeaderButtonsWrapper>
|
variant="contained"
|
||||||
</MainHeader>
|
color="primary"
|
||||||
<Paper
|
onClick={handleOpenUserModal}
|
||||||
className={classes.mainPaper}
|
>
|
||||||
variant="outlined"
|
{i18n.t("users.buttons.add")}
|
||||||
onScroll={handleScroll}
|
</Button>
|
||||||
>
|
)
|
||||||
<Table size="small">
|
}
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell align="center">{i18n.t("users.table.name")}</TableCell>
|
|
||||||
<TableCell align="center">
|
|
||||||
{i18n.t("users.table.email")}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="center">
|
|
||||||
{i18n.t("users.table.profile")}
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell align="center">
|
|
||||||
{i18n.t("users.table.actions")}
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
<>
|
|
||||||
{users.map((user) => (
|
|
||||||
<TableRow key={user.id}>
|
|
||||||
<TableCell align="center">{user.name}</TableCell>
|
|
||||||
<TableCell align="center">{user.email}</TableCell>
|
|
||||||
<TableCell align="center">{user.profile}</TableCell>
|
|
||||||
|
|
||||||
<TableCell align="center">
|
|
||||||
|
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
onClick={() => handleEditUser(user)}
|
|
||||||
>
|
|
||||||
<EditIcon />
|
|
||||||
</IconButton>
|
|
||||||
|
|
||||||
|
|
||||||
<Can
|
{/* <Can
|
||||||
role={userA.profile}
|
role={userA.profile}
|
||||||
perform="icon-remove-user"
|
perform="btn-add-user"
|
||||||
yes={() => (
|
yes={() => (
|
||||||
<IconButton
|
<Button
|
||||||
size="small"
|
variant="contained"
|
||||||
onClick={(e) => {
|
color="primary"
|
||||||
setConfirmModalOpen(true);
|
onClick={handleOpenUserModal}
|
||||||
setDeletingUser(user);
|
>
|
||||||
}}
|
{i18n.t("users.buttons.add")}
|
||||||
>
|
</Button>
|
||||||
<DeleteOutlineIcon />
|
)}
|
||||||
</IconButton>
|
/> */}
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
{loading && <TableRowSkeleton columns={4} />}
|
|
||||||
</>
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</Paper>
|
|
||||||
</MainContainer>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Users;
|
|
||||||
|
|
||||||
|
</MainHeaderButtonsWrapper>
|
||||||
|
</MainHeader>
|
||||||
|
<Paper
|
||||||
|
className={classes.mainPaper}
|
||||||
|
variant="outlined"
|
||||||
|
onScroll={handleScroll}
|
||||||
|
>
|
||||||
|
<Table size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell align="center">{i18n.t("users.table.name")}</TableCell>
|
||||||
|
<TableCell align="center">
|
||||||
|
{i18n.t("users.table.email")}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center">
|
||||||
|
{i18n.t("users.table.profile")}
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell align="center">
|
||||||
|
{i18n.t("users.table.actions")}
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
<>
|
||||||
|
{users.map((user) => (
|
||||||
|
<TableRow key={user.id}>
|
||||||
|
<TableCell align="center">{user.name}</TableCell>
|
||||||
|
<TableCell align="center">{user.email}</TableCell>
|
||||||
|
<TableCell align="center">{user.profile}</TableCell>
|
||||||
|
|
||||||
|
<TableCell align="center">
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => handleEditUser(user)}
|
||||||
|
>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
|
||||||
|
<Can
|
||||||
|
role={userA.profile}
|
||||||
|
perform="icon-remove-user"
|
||||||
|
yes={() => (
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={(e) => {
|
||||||
|
setConfirmModalOpen(true)
|
||||||
|
setDeletingUser(user)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteOutlineIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
{loading && <TableRowSkeleton columns={4} />}
|
||||||
|
</>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</Paper>
|
||||||
|
</MainContainer>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Users
|
||||||
|
|
|
@ -16,6 +16,7 @@ const rules = {
|
||||||
|
|
||||||
admin: {
|
admin: {
|
||||||
static: [
|
static: [
|
||||||
|
'show-icon-add-queue',
|
||||||
'show-icon-edit-whatsapp',
|
'show-icon-edit-whatsapp',
|
||||||
'show-icon-edit-queue',
|
'show-icon-edit-queue',
|
||||||
'menu-users:view',
|
'menu-users:view',
|
||||||
|
@ -31,6 +32,7 @@ const rules = {
|
||||||
'queues-view:show',
|
'queues-view:show',
|
||||||
'user-view:show',
|
'user-view:show',
|
||||||
'ticket-report:show',
|
'ticket-report:show',
|
||||||
|
'btn-add-user'
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue