Implementação de solução para consultas dos tickets e dos contatos usando Redis como banco de dados em memória. Ajustes no frontend para corrigir bugs de datas exibidas nas mensagens

pull/20/head
adriano 2022-10-25 11:16:36 -03:00
parent 603e5fa2dd
commit bde6058455
68 changed files with 1399 additions and 342 deletions

Binary file not shown.

View File

@ -26,8 +26,10 @@
"express": "^4.17.1", "express": "^4.17.1",
"express-async-errors": "^3.1.1", "express-async-errors": "^3.1.1",
"fast-folder-size": "^1.7.0", "fast-folder-size": "^1.7.0",
"flat": "^5.0.2",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"http-graceful-shutdown": "^2.3.2", "http-graceful-shutdown": "^2.3.2",
"ioredis": "^5.2.3",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"multer": "^1.4.2", "multer": "^1.4.2",
"mysql2": "^2.2.5", "mysql2": "^2.2.5",

View File

@ -13,6 +13,12 @@ import CheckIsValidContact from "../services/WbotServices/CheckIsValidContact";
import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl"; import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl";
import AppError from "../errors/AppError"; import AppError from "../errors/AppError";
import { searchContactCache } from '../helpers/ContactsCache'
import { off } from "process";
type IndexQuery = { type IndexQuery = {
searchParam: string; searchParam: string;
pageNumber: string; pageNumber: string;
@ -30,12 +36,34 @@ interface ContactData {
} }
export const index = async (req: Request, res: Response): Promise<Response> => { export const index = async (req: Request, res: Response): Promise<Response> => {
const { searchParam, pageNumber } = req.query as IndexQuery; let { searchParam, pageNumber } = req.query as IndexQuery;
const { contacts, count, hasMore } = await ListContactsService({ // TEST DEL
searchParam, if (searchParam && searchParam.trim().length > 0) {
pageNumber
}); try {
console.log('QUERY CONTACTS FROM CACHE SEARCH PARAM: ', searchParam)
const offset = 20 * (+pageNumber - 1);
const data = await searchContactCache(searchParam, offset, 20)
if (data) {
console.log('QUERY CONTACTS FROM CACHE QUERY LENGTH: ', data.length)
return res.json({ contacts: data, count: data.length, hasMore: data.length > 0 ? true : false });
}
} catch (error) {
console.log('There was an error on search ContactController.ts search cache: ', error)
}
}
const { contacts, count, hasMore } = await ListContactsService({ searchParam, pageNumber });
return res.json({ contacts, count, hasMore }); return res.json({ contacts, count, hasMore });
}; };
@ -58,8 +86,8 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
} }
await CheckIsValidContact(newContact.number); await CheckIsValidContact(newContact.number);
const validNumber : any = await CheckContactNumber(newContact.number) const validNumber: any = await CheckContactNumber(newContact.number)
const profilePicUrl = await GetProfilePicUrl(validNumber); const profilePicUrl = await GetProfilePicUrl(validNumber);
let name = newContact.name let name = newContact.name

View File

@ -8,7 +8,7 @@ import CreateSchedulingNotifyService from "../services/SchedulingNotifyServices
// const test = await ListSchedulingNotifyContactService('5517988310949','2022-03-18','2022-03-19'); // const test = await ListSchedulingNotifyContactService('5517988310949','2022-03-18','2022-03-19');
// const test = await ListSchedulingNotifyContactService('','2022-03-18','2022-03-19'); // const test = await ListSchedulingNotifyContactService('','2022-03-18','2022-03-19');
// const test = await ListSchedulingNotifyContactService('5517988310949'); // const test = await ListSchedulingNotifyContactService('5517988310949');
// console.log('$$$$$$$$$$$$$$$$$$$$$$$$$$ test:\n', test) //
@ -40,7 +40,7 @@ export const createOrUpdateScheduleNotify = async (req: Request, res: Response):
const scheduleData = req.body; const scheduleData = req.body;
console.log(' +++++++++++ scheduleData: ', scheduleData)
const schedulingNotifyCreate = await CreateSchedulingNotifyService( const schedulingNotifyCreate = await CreateSchedulingNotifyService(
{ {
@ -63,7 +63,7 @@ export const createOrUpdateScheduleNotify = async (req: Request, res: Response):
export const remove = async ( req: Request, res: Response ): Promise<Response> => { export const remove = async ( req: Request, res: Response ): Promise<Response> => {
console.log('EEEEEEEEEEEEEEEEEEEEEEEEEEE')
const { scheduleId } = req.params; const { scheduleId } = req.params;

View File

@ -19,7 +19,9 @@ import ptBR from 'date-fns/locale/pt-BR';
import { splitDateTime } from "../helpers/SplitDateTime"; import { splitDateTime } from "../helpers/SplitDateTime";
import format from 'date-fns/format'; import format from 'date-fns/format';
import ListTicketsServiceCache from "../services/TicketServices/ListTicketServiceCache";
import { searchTicketCache, loadTicketsCache, } from '../helpers/TicketCache'
@ -89,19 +91,14 @@ export const index = 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 { contactId, status, userId }: TicketData = req.body; const { contactId, status, userId }: TicketData = req.body;
// naty
// console.log(
// `contactId: ${contactId} \n| status: ${status} \n| userId: ${userId}`
// )
// test del // test del
let ticket = await Ticket.findOne({ where: { contactId, status: 'queueChoice' } }); let ticket = await Ticket.findOne({ where: { contactId, status: 'queueChoice' } });
if (ticket) { if (ticket) {
await UpdateTicketService({ ticketData: { status: 'open', userId: userId, }, ticketId: ticket.id }); await UpdateTicketService({ ticketData: { status: 'open', userId: userId, }, ticketId: ticket.id });
console.log('TICKET QUEUE CHOICE !!!!!!!')
} }
else { else {
ticket = await CreateTicketService({ contactId, status, userId }); ticket = await CreateTicketService({ contactId, status, userId });
@ -145,7 +142,7 @@ export const show = async (req: Request, res: Response): Promise<Response> => {
export const count = async (req: Request, res: Response): Promise<Response> => { export const count = async (req: Request, res: Response): Promise<Response> => {
type indexQ = { status: string; date?: string; }; // type indexQ = { status: string; date?: string; };
const { status, date } = req.query as IndexQuery const { status, date } = req.query as IndexQuery
const ticketCount = await CountTicketService(status, date); const ticketCount = await CountTicketService(status, date);
@ -153,10 +150,7 @@ export const count = async (req: Request, res: Response): Promise<Response> => {
return res.status(200).json(ticketCount); return res.status(200).json(ticketCount);
}; };
export const update = async (req: Request, res: Response): Promise<Response> => { export const update = async (req: Request, res: Response): Promise<Response> => {
@ -184,7 +178,7 @@ export const update = async (req: Request, res: Response): Promise<Response> =>
/////////////////////////////// ///////////////////////////////
//console.log('------- scheduleData.farewellMessage: ', scheduleData.farewellMessage) //
if (scheduleData.farewellMessage) { if (scheduleData.farewellMessage) {
const whatsapp = await ShowWhatsAppService(ticket.whatsappId); const whatsapp = await ShowWhatsAppService(ticket.whatsappId);
@ -195,15 +189,11 @@ export const update = async (req: Request, res: Response): Promise<Response> =>
await SendWhatsAppMessage({ body: farewellMessage, ticket }); await SendWhatsAppMessage({ body: farewellMessage, ticket });
} }
} }
///////////////////////////////
// lembrete // agendamento // lembrete // agendamento
if (scheduleData.statusChatEndId === '2' || scheduleData.statusChatEndId === '3') { if (scheduleData.statusChatEndId === '2' || scheduleData.statusChatEndId === '3') {
console.log('*** schedulingDate: ', scheduleData.schedulingDate)
console.log('*** schedulingTime: ', scheduleData.schedulingTime)
if (isScheduling(scheduleData.schedulingDate, scheduleData.schedulingTime)) { if (isScheduling(scheduleData.schedulingDate, scheduleData.schedulingTime)) {
console.log('*** É AGENDAMENTO!') console.log('*** É AGENDAMENTO!')
@ -255,9 +245,7 @@ export const update = async (req: Request, res: Response): Promise<Response> =>
const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }))) const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR })))
if (userOldInfo.userId) { if (userOldInfo.userId) {
// console.log('FECHOU...')
TicketEmiterSumOpenClosedByUser(userOldInfo.userId.toString(), dateToday.fullDate, dateToday.fullDate) TicketEmiterSumOpenClosedByUser(userOldInfo.userId.toString(), dateToday.fullDate, dateToday.fullDate)

View File

@ -112,11 +112,7 @@ export const show = async (req: Request, res: Response): Promise<Response> => {
export const logoutUser = async (req: Request, res: Response): Promise<Response> => { export const logoutUser = async (req: Request, res: Response): Promise<Response> => {
const { userId } = req.params; const { userId } = req.params;
//const user = await ShowUserService(userId);
console.log('userId: ', userId)
//test del
await stopWhoIsOnlineMonitor() await stopWhoIsOnlineMonitor()
let onlineTime = { let onlineTime = {

View File

@ -123,9 +123,7 @@ export const remove = async (
removeDir(path.join(process.cwd(), '.wwebjs_auth', `session-bd_${whatsappId}`)) removeDir(path.join(process.cwd(), '.wwebjs_auth', `session-bd_${whatsappId}`))
removeWbot(+whatsappId); removeWbot(+whatsappId);
console.log('Deleteou o whatsapp service com id = ', whatsappId)
const io = getIO(); const io = getIO();
io.emit("whatsapp", { io.emit("whatsapp", {

View File

@ -56,14 +56,7 @@ const remove = async (req: Request, res: Response): Promise<Response> => {
const { whatsappId } = req.params; const { whatsappId } = req.params;
const whatsapp = await ShowWhatsAppService(whatsappId); const whatsapp = await ShowWhatsAppService(whatsappId);
const wbot = getWbot(whatsapp.id); const wbot = getWbot(whatsapp.id);
console.log(
'Desconectou s whatsapp.id: ', whatsapp.id,
' | PATH TO DELETE',path.join(process.cwd(),'.wwebjs_auth', `session-bd_${whatsapp.id}`)
)
//removeDir(path.join(process.cwd(),'.wwebjs_auth', `session-bd_${whatsapp.id}`))
await wbot.logout(); await wbot.logout();

View File

@ -0,0 +1,209 @@
import Redis from 'ioredis'
import { type } from 'os'
const unflatten = require('flat').unflatten
var flatten = require('flat')
import ListContactsServiceCache from "../services/ContactServices/ListContactsServiceCache"
import { redisConn } from './TicketCache'
const deleteContactsByIdCache = async (id: string | number) => {
const redis: any = await redisConn();
if (redis.status !== 'connect') return
const contact_cache: any = await redis.hgetall(`contact:${id}`)
try {
if (contact_cache && Object.keys(contact_cache).length > 0) {
await redis.del(`contact:${id}`)
console.log(`Contacts cache number ${contact_cache['number']} deleted!`)
}
else {
console.log('CONTACT CACHE NOT FOUND!')
}
} catch (error) {
console.log(`There was an error on deleteContactsByIdCache: ${error}`)
}
redis.quit()
}
const updateContactCache = async (hash: any, json_object: any) => {
const redis: any = await redisConn();
if (redis.status !== 'connect') return
const pipeline = redis.pipeline()
let entries = Object.entries(json_object)
entries.forEach((e: any) => {
pipeline.hset(hash, e[0], e[1])
})
await pipeline.exec(() => { console.log("Key/value inserted/updated") });
redis.quit()
}
const updateContactCacheById = async (id: string | number, update_fields: object | any) => {
const redis: any = await redisConn();
if (redis.status !== 'connect') return
const contact_cache: any = await redis.hgetall(`contact:${id}`)
try {
if (contact_cache && Object.keys(contact_cache).length > 0) {
// await redis.del(`contact:${id}`)
update_fields.escaped_name = escapeCharCache(update_fields.name)
await updateContactCache(`contact:${id}`, update_fields)
console.log(`CONTACT ${contact_cache['number']} CACHE WAS UPDATED!`)
}
else {
console.log('CONTACT CACHE NOT FOUND!')
}
} catch (error) {
console.log(`There was an error on updateContactCacheById: ${error}`)
}
redis.quit()
}
const createOrUpdateContactCache = async (hash: any, contact: any) => {
const redis: any = await redisConn();
if (redis.status !== 'connect') return
if (contact.name) {
contact.escaped_name = escapeCharCache(contact.name)
}
await redis.hmset(hash, contact);
redis.quit()
}
async function searchContactCache(search: string, offset: number, limit: number) {
const redis: any = await redisConn();
if (redis.status !== 'connect') return null
search = escapeCharCache(search)
const response: any = await redis.call('FT.SEARCH', 'idx_contact', `(@escaped_name:*${search}*)|(@number:*${search}*)`, 'LIMIT', offset, limit, 'SORTBY', 'escaped_name', 'ASC')
redis.quit()
if (response.length === 1) {
return []
}
const results: any = []
for (let n = 2; n < response.length; n += 2) {
const result: any = {}
const fieldNamesAndValues = response[n]
for (let m = 0; m < fieldNamesAndValues.length; m += 2) {
const k = fieldNamesAndValues[m]
const v = fieldNamesAndValues[m + 1]
result[k] = v
}
results.push(result)
}
return results
}
const removeExtraSpace = (str: string) => {
str = str.replace(/^\s+/g, '')
return str.replace(/\s+/g, ' ')
}
const escapeCharCache = (str: string) => {
const pattern = /[\'|\"|\.|\,|\;|\<|\>|\{|\}|\[|\]|\"|\'|\=|\~|\*|\:|\#|\+|\^|\$|\@|\%|\!|\&|\)|\(|/|\-|\\)]/g; // no match, use replace function.
let newStr = str.replace(pattern, (t1) => `\\${t1}`);
newStr = removeExtraSpace(newStr)
return newStr.trim()
}
const loadContactsCache = async () => {
await createContactIndexCache('idx_contact')
const redis: any = await redisConn();
if (redis.status !== 'connect') return
let contacts = await ListContactsServiceCache()
const pipeline = redis.pipeline()
for (let i = 0; i < contacts.length; i++) {
contacts[i].createdAt = new Date(contacts[i].createdAt).toISOString()
contacts[i].updatedAt = new Date(contacts[i].updatedAt).toISOString()
contacts[i].escaped_name = escapeCharCache(contacts[i].name)
pipeline.hmset(`contact:${contacts[i].id}`, contacts[i]);
}
await pipeline.exec(() => { console.log(`${contacts.length} CONTACTS INSERTED IN CACHE!`) });
redis.quit()
}
const createContactIndexCache = async (hashIndex: string) => {
const redis: any = await redisConn();
if (redis.status !== 'connect') return
try {
const lst_index_redis: any = await redis.call('FT._LIST')
if (lst_index_redis.includes(hashIndex)) {
console.log('entrou...')
await redis.call('FT.DROPINDEX', hashIndex)
}
const response = await redis.call('FT.CREATE', hashIndex, 'ON', 'HASH', 'PREFIX', '1', 'contact:', 'SCHEMA', 'escaped_name', 'TEXT', 'SORTABLE', 'number', 'TEXT', 'SORTABLE')
console.log('Contact index created: ', response)
} catch (error) {
console.log('There was an error on createContactIndexCache: ', error)
}
redis.quit()
}
export {
loadContactsCache,
searchContactCache,
deleteContactsByIdCache,
updateContactCacheById,
createOrUpdateContactCache,
escapeCharCache
}

View File

@ -10,9 +10,7 @@ export const WhatsIndex = (whatsapps: Object[]) => {
if (WhatsQueueIndex.getIndex() >= whatsapps.length) { if (WhatsQueueIndex.getIndex() >= whatsapps.length) {
WhatsQueueIndex.setIndex(0) WhatsQueueIndex.setIndex(0)
} }
// console.log('WhatsQueueIndex.getIndex(): ', WhatsQueueIndex.getIndex())
index = +WhatsQueueIndex.getIndex() index = +WhatsQueueIndex.getIndex()

View File

@ -90,21 +90,20 @@ const monitor = async () => {
whatsapps.forEach(async whats => { whatsapps.forEach(async whats => {
// console.log('whats id: ', whats.id)
const sourcePath = path.join(__dirname, `../../.wwebjs_auth/`, `session-bd_${whats.id}`) const sourcePath = path.join(__dirname, `../../.wwebjs_auth/`, `session-bd_${whats.id}`)
if (fs.existsSync(sourcePath)) { if (fs.existsSync(sourcePath)) {
try { try {
// console.log('dir path exist!')
const fastFolderSizeAsync = promisify(fastFolderSize) const fastFolderSizeAsync = promisify(fastFolderSize)
let size = await fastFolderSizeAsync(sourcePath) let size = await fastFolderSizeAsync(sourcePath)
size = convertBytes(size) size = convertBytes(size)
// console.log(size)
// SESSION MONITORING // SESSION MONITORING
const io = getIO(); const io = getIO();

View File

@ -50,17 +50,15 @@ const SetTicketMessagesAsRead = async (ticket: Ticket): Promise<void> => {
const whatsapp = await ShowWhatsAppService(ticket.whatsappId); const whatsapp = await ShowWhatsAppService(ticket.whatsappId);
if (whatsapp && whatsapp.status == 'CONNECTED') { if (whatsapp && whatsapp.status == 'CONNECTED') {
console.log('SetTicketMessagesAsRead.ts - ENTROU NO RESTORE...')
let timestamp = Math.floor(Date.now() / 1000) let timestamp = Math.floor(Date.now() / 1000)
fs.writeFile(`${sourcePath}/${timestamp}_SetTicketMessagesAsRead.txt`, `Whatsapp id: ${whatsapp.id} \nDate: ${dateToday.fullDate} ${dateToday.fullTime} \nFile: SetTicketMessagesAsRead.ts \nError: ${err}`, (error)=>{}); fs.writeFile(`${sourcePath}/${timestamp}_SetTicketMessagesAsRead.txt`, `Whatsapp id: ${whatsapp.id} \nDate: ${dateToday.fullDate} ${dateToday.fullTime} \nFile: SetTicketMessagesAsRead.ts \nError: ${err}`, (error)=>{});
await restartWhatsSession(whatsapp) await restartWhatsSession(whatsapp)
console.log('...PASSOU O RESTORE - SetTicketMessagesAsRead.ts ')
} }
} }

View File

@ -8,8 +8,7 @@ import { addHours, addMinutes, addSeconds, intervalToDuration, add } from "date-
onlineTime.setUTCHours(new Date(oldOnlineTimeSum.onlineTime).getHours()) onlineTime.setUTCHours(new Date(oldOnlineTimeSum.onlineTime).getHours())
onlineTime.setUTCMinutes(new Date(oldOnlineTimeSum.onlineTime).getMinutes()) onlineTime.setUTCMinutes(new Date(oldOnlineTimeSum.onlineTime).getMinutes())
onlineTime.setUTCSeconds(new Date(oldOnlineTimeSum.onlineTime).getSeconds()) onlineTime.setUTCSeconds(new Date(oldOnlineTimeSum.onlineTime).getSeconds())
// console.log('_________________oldOnlineTimeSum.updatedAt: ', oldOnlineTimeSum.updatedAt)
let newtTime = intervalToDuration({ start: new Date(oldOnlineTimeSum.updatedAt), end: new Date() }) let newtTime = intervalToDuration({ start: new Date(oldOnlineTimeSum.updatedAt), end: new Date() })
@ -25,8 +24,7 @@ import { addHours, addMinutes, addSeconds, intervalToDuration, add } from "date-
} }
const isoDate = new Date(onlineTime); const isoDate = new Date(onlineTime);
const newOnlinetime = isoDate.toJSON().slice(0, 19).replace('T', ' '); const newOnlinetime = isoDate.toJSON().slice(0, 19).replace('T', ' ');
//console.log('sum new online time: ', newOnlinetime)
return newOnlinetime return newOnlinetime
} }

View File

@ -0,0 +1,389 @@
import Redis from 'ioredis'
import { List } from 'whatsapp-web.js'
const unflatten = require('flat').unflatten
var flatten = require('flat')
import ListTicketServiceCache from "../services/TicketServices/ListTicketServiceCache"
import { escapeCharCache } from './ContactsCache'
const redisConn = async () => {
try {
const redis = new Redis();
const conn = () => new Promise((resolve, reject) => {
redis.on('error', (err) => {
if (err.code === 'ECONNREFUSED') {
console.error(`Redis connection error: ${err}.`)
redis.quit()
}
else {
console.error(`Redis encountered an error: ${err.message}.`)
}
reject(err)
})
redis.on('connect', () => {
resolve(redis);
})
});
return await conn();
} catch (e) {
console.error(e);
return Promise.resolve([]);
}
}
const flushCache = async () => {
const redis: any = await redisConn();
if (redis.status === 'connect') {
console.log('TICKETS CACHE REMOVED')
await redis.call('FLUSHALL')
redis.quit()
}
}
const cacheSize = async () => {
const redis: any = await redisConn();
if (redis.status !== 'connect') {
return -1
}
const size = await redis.call('dbsize')
redis.quit()
return size
}
const loadTicketsCache = async () => {
const redis: any = await redisConn();
if (redis.status !== 'connect') return
await createTicketIndexCache('idx_ticket')
let tickets = await ListTicketServiceCache({})
const pipeline = redis.pipeline()
for (let i = 0; i < tickets.length; i++) {
tickets[i].createdAt = new Date(tickets[i].createdAt).toISOString()
tickets[i].updatedAt = new Date(tickets[i].updatedAt).toISOString()
tickets[i].escaped_name = escapeCharCache(tickets[i]['contact.name'])
// tickets[i]['contact_name'] = tickets[i]['contact.name']
// delete tickets[i]['contact.name']
tickets[i]['contact_number'] = tickets[i]['contact.number']
delete tickets[i]['contact.number']
pipeline.hmset(`ticket:${tickets[i].id}`, tickets[i]);
}
await pipeline.exec(() => { console.log(`${tickets.length} TICKETS INSERTED IN CACHE!`) });
redis.quit()
}
const createTicketIndexCache = async (hashIndex: string) => {
const redis: any = await redisConn();
if (redis.status !== 'connect') return
try {
const lst_index_redis: any = await redis.call('FT._LIST')
if (lst_index_redis.includes(hashIndex)) {
console.log('entrou...')
await redis.call('FT.DROPINDEX', hashIndex)
}
const response = await redis.call('FT.CREATE', hashIndex, 'ON', 'HASH', 'PREFIX', '1', 'ticket:', 'SCHEMA', 'escaped_name', 'TEXT', 'SORTABLE', 'contact_number', 'TEXT', 'SORTABLE', 'status', 'TAG', 'SORTABLE')
console.log('Ticket index created: ', response)
} catch (error) {
console.log('There was an error on createTicketIndexCache: ', error)
}
redis.quit()
}
const updateTicketCache = async (hash: any, json_object: any) => {
const redis: any = await redisConn();
if (redis.status !== 'connect') return
const pipeline = redis.pipeline()
let entries = Object.entries(json_object)
entries.forEach((e: any) => {
pipeline.hset(hash, e[0], e[1])
})
await pipeline.exec(() => { console.log("updateTicketCache Key/value inserted/updated") });
redis.quit()
}
const updateTicketCacheByTicketId = async (ticketId: string | number, update_fields: any) => {
const redis: any = await redisConn();
if (redis.status !== 'connect') return
const ticket_cache = await redis.hgetall(`ticket:${ticketId}`)
try {
if (ticket_cache && Object.keys(ticket_cache).length > 0) {
if (update_fields.escaped_name) {
update_fields.escaped_name = escapeCharCache(update_fields['contact.name'])
}
await updateTicketCache(`ticket:${ticketId}`, update_fields)
console.log(`updateTicketCacheByTicketId TICKET ${ticket_cache['contact_number']} CACHE WAS UPDATED!`)
}
else {
console.log('TICKET CACHE NOT FOUND!')
}
} catch (error) {
console.log(`There was an error on updateTicketCacheByTicketId: ${error}`)
}
redis.quit()
}
const createOrUpdateTicketCache = async (hash: any, ticket: any) => {
const redis: any = await redisConn();
if (redis.status !== 'connect') return
if (redis.status !== 'connect') return
ticket.escaped_name = escapeCharCache(ticket['contact.name'])
ticket['contact_number'] = ticket['contact.number']
delete ticket['contact.number']
await redis.hmset(hash, ticket);
console.log('CREATED/UPDATED TICKET CACHE')
redis.quit()
}
const deleteTicketsByIdCache = async (ticketId: string | number) => {
const redis: any = await redisConn();
if (redis.status !== 'connect') return
const ticket_cache = await redis.hgetall(`ticket:${ticketId}`)
try {
if (ticket_cache && Object.keys(ticket_cache).length > 0) {
await redis.del(`ticket:${ticketId}`)
console.log(`TICKET ${ticket_cache['id']} CACHE WAS DELETED!`)
}
else {
console.log('TICKET CACHE NOT FOUND!')
}
} catch (error) {
console.log(`There was an error on deleteTicketsByIdCache: ${error}`)
}
redis.quit()
}
const deleteTicketsFieldsCache = async (tickets: any, del_fields: any) => {
const redis: any = await redisConn();
if (redis.status !== 'connect') return
const pipeline = redis.pipeline()
if (tickets && tickets.length > 0) {
try {
for (let i = 0; i < tickets.length; i++) {
pipeline.hdel(`ticket:${tickets[i]['id']}`, del_fields)
}
await pipeline.exec(() => { console.log(`Tickets cache contact updated!`) })
} catch (error) {
console.log('There was an error on deleteTicketsFieldsByContactsCache function: ', error)
}
}
redis.quit()
}
const updateTicketsByContactsCache = async (oldNumber: string, newName: string, newNumber: string) => {
const redis: any = await redisConn();
if (redis.status !== 'connect') return
const pipeline = redis.pipeline()
const tickets = await searchTicketCache(oldNumber)
if (tickets && tickets.length > 0) {
try {
for (let i = 0; i < tickets.length; i++) {
tickets[i]['contact.name'] = newName
tickets[i].escaped_name = escapeCharCache(newName)
tickets[i]['contact_number'] = newNumber
pipeline.hmset(`ticket:${tickets[i]['id']}`, tickets[i])
}
await pipeline.exec(() => { console.log(`updateTicketsByContactsCache Tickets cache contact updated!`) })
} catch (error) {
console.log('There was an error on updateTicketsByContactsCache function: ', error)
}
}
redis.quit()
}
const deleteTicketsByContactsCache = async (number: string) => {
const redis: any = await redisConn();
if (redis.status !== 'connect') return
const pipeline = redis.pipeline()
const tickets = await searchTicketCache(number)
if (tickets && tickets.length > 0) {
try {
for (let i = 0; i < tickets.length; i++) {
pipeline.del(`ticket:${tickets[i]['id']}`)
}
await pipeline.exec(() => { console.log(`Tickets cache number ${tickets[0]['contact_number']} deleted!`) })
} catch (error) {
console.log('There was an error on deleteTicketsByContactsCache function: ', error)
}
}
redis.quit()
}
const deleteTicketCache = async (hash: any) => {
const redis: any = await redisConn();
if (redis.status !== 'connect') return
await redis.del(hash)
redis.quit()
}
async function searchTicketCache(search: string, offset?: number, limit?: number) {
const redis:any = await redisConn();
if(redis.status!=='connect') return null
search = escapeCharCache(search)
let response: any = undefined
if (offset != undefined && limit != undefined) {
response = await redis.call('FT.SEARCH', 'idx_ticket', `(@escaped_name:*${search}*)|(@contact_number:*${search}*)`, 'LIMIT', offset, limit, 'SORTBY', 'status', 'DESC')
}
else {
response = await redis.call('FT.SEARCH', 'idx_ticket', `(@escaped_name:*${search}*)|(@contact_number:*${search}*)`)
}
redis.quit()
// console.log('response: ', response)
if (response.length === 1) {
return []
}
const results: any = []
for (let n = 2; n < response.length; n += 2) {
const result: any = {}
const fieldNamesAndValues = response[n]
for (let m = 0; m < fieldNamesAndValues.length; m += 2) {
const k = fieldNamesAndValues[m]
const v = fieldNamesAndValues[m + 1]
result[k] = v
}
results.push(result)
}
return results
}
export {
loadTicketsCache,
updateTicketCache,
createOrUpdateTicketCache,
searchTicketCache,
updateTicketCacheByTicketId,
deleteTicketsByContactsCache,
updateTicketsByContactsCache,
deleteTicketsByIdCache,
flushCache,
deleteTicketsFieldsCache,
cacheSize,
redisConn
}

View File

@ -1,16 +1,16 @@
import Ticket from "../models/Ticket"; import Ticket from "../models/Ticket";
import UpdateTicketService from "../services/TicketServices/UpdateTicketService"; import UpdateTicketService from "../services/TicketServices/UpdateTicketService";
const UpdateDeletedUserOpenTicketsStatus = async ( const UpdateDeletedUserOpenTicketsStatus = async (
tickets: Ticket[] tickets: Ticket[]
): Promise<void> => { ): Promise<void> => {
tickets.forEach(async t => { tickets.forEach(async t => {
const ticketId = t.id.toString();
const ticketId = t.id.toString();
await UpdateTicketService({ ticketData: { status: "pending" }, ticketId });
await UpdateTicketService({
ticketData: { status: "pending" },
ticketId
});
}); });
}; };

View File

@ -97,8 +97,7 @@ const monitor = async () => {
const indexAux = lstOnline.findIndex((e: any) => e.id == el.id) const indexAux = lstOnline.findIndex((e: any) => e.id == el.id)
if (indexAux == -1) { if (indexAux == -1) {
console.log(' entrou indexAux: ', indexAux)
const userOnline = await createOrUpdateOnlineUserService({ userId: el.id, status: 'online' }) const userOnline = await createOrUpdateOnlineUserService({ userId: el.id, status: 'online' })

View File

@ -79,8 +79,7 @@ export const initIO = (httpServer: Server): SocketIO => {
if (index == -1) { if (index == -1) {
listOnlineAux.push({ 'id': userId }) listOnlineAux.push({ 'id': userId })
} }
else { else {
console.log(' -------------- PULO FORA')
return return
} }

View File

@ -37,9 +37,7 @@ class Message extends Model<Message> {
@Column(DataType.STRING) @Column(DataType.STRING)
get mediaUrl(): string | null { get mediaUrl(): string | null {
if (this.getDataValue("mediaUrl")) { if (this.getDataValue("mediaUrl")) {
return `${process.env.BACKEND_URL}:${ return `${process.env.BACKEND_URL}:${process.env.PROXY_PORT}/public/${this.getDataValue("mediaUrl")}`;
process.env.PROXY_PORT
}/public/${this.getDataValue("mediaUrl")}`;
} }
return null; return null;
} }

View File

@ -6,6 +6,8 @@ import * as TicketController from "../controllers/TicketController";
const ticketRoutes = express.Router(); const ticketRoutes = express.Router();
// ticketRoutes.get("/tickets/cache", isAuth, TicketController.ticketsCache);
ticketRoutes.get("/tickets/count", isAuth, TicketController.count); ticketRoutes.get("/tickets/count", isAuth, TicketController.count);
ticketRoutes.get("/tickets", isAuth, TicketController.index); ticketRoutes.get("/tickets", isAuth, TicketController.index);

View File

@ -7,6 +7,9 @@ import { StartAllWhatsAppsSessions } from "./services/WbotServices/StartAllWhats
import { startSchedulingMonitor } from "./helpers/SchedulingNotifySendMessage" import { startSchedulingMonitor } from "./helpers/SchedulingNotifySendMessage"
import { startWhoIsOnlineMonitor } from "./helpers/WhoIsOnlineMonitor" import { startWhoIsOnlineMonitor } from "./helpers/WhoIsOnlineMonitor"
import { loadTicketsCache, flushCache, cacheSize } from './helpers/TicketCache'
import { loadContactsCache } from './helpers/ContactsCache'
const server = app.listen(process.env.PORT, () => { const server = app.listen(process.env.PORT, () => {
logger.info(`Server started on port: ${process.env.PORT}`); logger.info(`Server started on port: ${process.env.PORT}`);
}); });
@ -15,5 +18,20 @@ initIO(server);
StartAllWhatsAppsSessions(); StartAllWhatsAppsSessions();
gracefulShutdown(server); gracefulShutdown(server);
(async()=>{
const cacheLength = await cacheSize()
if(cacheLength == 0){
console.log('Loading from cache...')
await flushCache()
await loadContactsCache()
await loadTicketsCache()
}
})()
startSchedulingMonitor(5000) startSchedulingMonitor(5000)
startWhoIsOnlineMonitor(50000) startWhoIsOnlineMonitor(3000)

View File

@ -1,6 +1,8 @@
import AppError from "../../errors/AppError"; import AppError from "../../errors/AppError";
import Contact from "../../models/Contact"; import Contact from "../../models/Contact";
import { createOrUpdateContactCache } from '../../helpers/ContactsCache'
interface ExtraInfo { interface ExtraInfo {
name: string; name: string;
value: string; value: string;
@ -40,6 +42,12 @@ const CreateContactService = async ({
} }
); );
// TEST DEL
await createOrUpdateContactCache(`contact:${contact.id}`, {id: contact.id, name, number, profilePicUrl:'', isGroup:'false', extraInfo, email })
//
return contact; return contact;
}; };

View File

@ -1,6 +1,8 @@
import { getIO } from "../../libs/socket"; import { getIO } from "../../libs/socket";
import Contact from "../../models/Contact"; import Contact from "../../models/Contact";
import { createOrUpdateContactCache } from '../../helpers/ContactsCache'
interface ExtraInfo { interface ExtraInfo {
name: string; name: string;
value: string; value: string;
@ -28,10 +30,15 @@ const CreateOrUpdateContactService = async ({
const io = getIO(); const io = getIO();
let contact: Contact | null; let contact: Contact | null;
contact = await Contact.findOne({ where: { number } }); contact = await Contact.findOne({ where: { number } });
if (contact) { if (contact) {
contact.update({ profilePicUrl }); contact.update({ profilePicUrl });
// TEST DEL
await createOrUpdateContactCache(`contact:${contact.id}`, { profilePicUrl })
//
io.emit("contact", { io.emit("contact", {
action: "update", action: "update",
@ -47,6 +54,11 @@ const CreateOrUpdateContactService = async ({
extraInfo extraInfo
}); });
// TEST DEL
await createOrUpdateContactCache(`contact:${contact.id}`, {id: contact.id, name, number, profilePicUrl, isGroup, extraInfo, email })
//
io.emit("contact", { io.emit("contact", {
action: "create", action: "create",
contact contact

View File

@ -1,6 +1,9 @@
import Contact from "../../models/Contact"; import Contact from "../../models/Contact";
import AppError from "../../errors/AppError"; import AppError from "../../errors/AppError";
import { deleteTicketsByContactsCache } from '../../helpers/TicketCache'
import {deleteContactsByIdCache} from '../../helpers/ContactsCache'
const DeleteContactService = async (id: string): Promise<void> => { const DeleteContactService = async (id: string): Promise<void> => {
const contact = await Contact.findOne({ const contact = await Contact.findOne({
where: { id } where: { id }
@ -10,6 +13,11 @@ const DeleteContactService = async (id: string): Promise<void> => {
throw new AppError("ERR_NO_CONTACT_FOUND", 404); throw new AppError("ERR_NO_CONTACT_FOUND", 404);
} }
// TEST DEL
await deleteTicketsByContactsCache(contact.number)
await deleteContactsByIdCache(contact.id)
//
await contact.destroy(); await contact.destroy();
}; };

View File

@ -29,14 +29,14 @@ const ListContactsService = async ({
] ]
}; };
const limit = 20; const limit = 20;
const offset = limit * (+pageNumber - 1); const offset = limit * (+pageNumber - 1);
const { count, rows: contacts } = await Contact.findAndCountAll({ const { count, rows: contacts } = await Contact.findAndCountAll({
where: whereCondition, where: whereCondition,
limit, limit,
offset, offset,
order: [["name", "ASC"]] order: [["name", "ASC"]]
}); });
const hasMore = count > offset + contacts.length; const hasMore = count > offset + contacts.length;

View File

@ -0,0 +1,11 @@
import { Sequelize, Op } from "sequelize";
import Contact from "../../models/Contact";
const ListContactsServiceCache = async (): Promise<any> => {
const contacts = await Contact.findAll({ where: {}, raw:true, order: [["name", "ASC"]] });
return contacts
};
export default ListContactsServiceCache;

View File

@ -2,6 +2,9 @@ import AppError from "../../errors/AppError";
import Contact from "../../models/Contact"; import Contact from "../../models/Contact";
import ContactCustomField from "../../models/ContactCustomField"; import ContactCustomField from "../../models/ContactCustomField";
import { updateTicketsByContactsCache } from '../../helpers/TicketCache'
import { updateContactCacheById } from '../../helpers/ContactsCache'
interface ExtraInfo { interface ExtraInfo {
id?: number; id?: number;
name: string; name: string;
@ -18,6 +21,7 @@ interface Request {
contactData: ContactData; contactData: ContactData;
contactId: string; contactId: string;
} }
const UpdateContactService = async ({ const UpdateContactService = async ({
contactData, contactData,
@ -51,19 +55,31 @@ const UpdateContactService = async ({
} }
}) })
); );
} }
const oldNumber = contact.number
await contact.update({ await contact.update({
name, name,
number, number,
email email
}); });
//TEST DEL
await updateTicketsByContactsCache(oldNumber, contact.name, contact.number)
//
await contact.reload({ await contact.reload({
attributes: ["id", "name", "number", "email", "profilePicUrl"], attributes: ["id", "name", "number", "email", "profilePicUrl"],
include: ["extraInfo"] include: ["extraInfo"]
}); });
// console.log('contactcontactcontactcontact: ',flatten(JSON.parse(JSON.stringify(contact))))
await updateContactCacheById(contact.id, JSON.parse(JSON.stringify(contact)))
return contact; return contact;
}; };

View File

@ -1,3 +1,4 @@
import { updateTicketCacheByTicketId } from "../../helpers/TicketCache";
import { getIO } from "../../libs/socket"; import { getIO } from "../../libs/socket";
import Message from "../../models/Message"; import Message from "../../models/Message";
import Ticket from "../../models/Ticket"; import Ticket from "../../models/Ticket";
@ -16,7 +17,7 @@ interface Request {
messageData: MessageData; messageData: MessageData;
} }
const CreateMessageService = async ({messageData}: Request): Promise<Message> => { const CreateMessageService = async ({ messageData }: Request): Promise<Message> => {
await Message.upsert(messageData); await Message.upsert(messageData);
const message = await Message.findByPk(messageData.id, { const message = await Message.findByPk(messageData.id, {
@ -39,9 +40,20 @@ const CreateMessageService = async ({messageData}: Request): Promise<Message> =>
throw new Error("ERR_CREATING_MESSAGE"); throw new Error("ERR_CREATING_MESSAGE");
} }
if (message.ticket.status != 'queueChoice') {
if(message.ticket.status!='queueChoice'){
// TEST DEL
await updateTicketCacheByTicketId(message.ticket.id,
{
lastMessage: message.body,
updatedAt: new Date(message.ticket.updatedAt).toISOString(),
'contact.profilePicUrl': message.ticket.contact.profilePicUrl,
unreadMessages: message.ticket.unreadMessages
})
//
const io = getIO(); const io = getIO();
io.to(message.ticketId.toString()) io.to(message.ticketId.toString())
@ -54,8 +66,8 @@ const CreateMessageService = async ({messageData}: Request): Promise<Message> =>
contact: message.ticket.contact contact: message.ticket.contact
}); });
} }
return message; return message;
}; };

View File

@ -2,14 +2,31 @@ import ShowQueueService from "./ShowQueueService";
import UserQueue from "../../models/UserQueue"; import UserQueue from "../../models/UserQueue";
import ListTicketsServiceCache from "../TicketServices/ListTicketServiceCache";
import { deleteTicketsFieldsCache } from '../../helpers/TicketCache'
const DeleteQueueService = async (queueId: number | string): Promise<void> => { const DeleteQueueService = async (queueId: number | string): Promise<void> => {
const queue = await ShowQueueService(queueId); const queue = await ShowQueueService(queueId);
if (queue.id) {
const tickets = await ListTicketsServiceCache({ queueId })
await deleteTicketsFieldsCache(tickets, ['queue.id', 'queue.name', 'queue.color'])
}
try { try {
await UserQueue.destroy({ where: {queueId: queueId } });
await UserQueue.destroy({ where: { queueId: queueId } });
} catch (error) { } catch (error) {
console.log('Error on delete UserQueue by queueId: ',queueId)
} console.log('Error on delete UserQueue by queueId: ', queueId)
}
await queue.destroy(); await queue.destroy();
}; };

View File

@ -23,9 +23,7 @@ const CreateSchedulingNotifyService = async ({
let schedulingNotify = null; let schedulingNotify = null;
if(schedulingNotifyId){ if(schedulingNotifyId){
console.log('000000000000000000000000000 ATUALIZOU!')
schedulingNotify = await SchedulingNotify.findOne({ where: { id: schedulingNotifyId } }); schedulingNotify = await SchedulingNotify.findOne({ where: { id: schedulingNotifyId } });
@ -48,9 +46,7 @@ const CreateSchedulingNotifyService = async ({
} }
if(!schedulingNotify){ if(!schedulingNotify){
console.log('111111111111111111111111111 criou!')
schedulingNotify = await SchedulingNotify.create( schedulingNotify = await SchedulingNotify.create(
{ {
ticketId, ticketId,

View File

@ -13,6 +13,11 @@ import ptBR from 'date-fns/locale/pt-BR';
import { splitDateTime } from "../../helpers/SplitDateTime"; import { splitDateTime } from "../../helpers/SplitDateTime";
import TicketEmiterSumOpenClosedByUser from "../../helpers/OnlineReporEmiterInfoByUser"; import TicketEmiterSumOpenClosedByUser from "../../helpers/OnlineReporEmiterInfoByUser";
import { createOrUpdateTicketCache } from '../../helpers/TicketCache'
let flatten = require('flat')
interface Request { interface Request {
contactId: number; contactId: number;
status: string; status: string;
@ -41,16 +46,29 @@ const CreateTicketService = async ({
if (!ticket) { if (!ticket) {
throw new AppError("ERR_CREATING_TICKET"); throw new AppError("ERR_CREATING_TICKET");
} }
// console.log('CONTACT ticket.id: ', ticket.id)
//test del // TEST DEL
// 2022-05-11T05:20:33.000Z, try {
// const dateToday = ticket.createdAt.toISOString().split('T')[0]
let jsonString = JSON.stringify(ticket); //convert to string to remove the sequelize specific meta data
let ticket_obj = JSON.parse(jsonString); //to make plain json
delete ticket_obj['contact']['extraInfo']
ticket_obj = flatten(ticket_obj)
await createOrUpdateTicketCache(`ticket:${ticket.id}`, ticket_obj)
} catch (error) {
console.log('There was an error on UpdateTicketService.ts on createTicketCache from user: ', error)
}
//
const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR })))
const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR })))
//openByUser : [ { id: 13, status: 'online' } ]
TicketEmiterSumOpenClosedByUser(userId.toString(), dateToday.fullDate, dateToday.fullDate) TicketEmiterSumOpenClosedByUser(userId.toString(), dateToday.fullDate, dateToday.fullDate)
@ -58,10 +76,7 @@ const CreateTicketService = async ({
io.emit("ticketStatus", { io.emit("ticketStatus", {
action: "update", action: "update",
ticketStatus: {ticketId: ticket.id, status: ticket.status} ticketStatus: {ticketId: ticket.id, status: ticket.status}
}); });
//
return ticket; return ticket;

View File

@ -1,6 +1,8 @@
import Ticket from "../../models/Ticket"; import Ticket from "../../models/Ticket";
import AppError from "../../errors/AppError"; import AppError from "../../errors/AppError";
import { deleteTicketsByIdCache } from '../../helpers/TicketCache'
const DeleteTicketService = async (id: string): Promise<Ticket> => { const DeleteTicketService = async (id: string): Promise<Ticket> => {
const ticket = await Ticket.findOne({ const ticket = await Ticket.findOne({
where: { id } where: { id }
@ -10,7 +12,11 @@ const DeleteTicketService = async (id: string): Promise<Ticket> => {
throw new AppError("ERR_NO_TICKET_FOUND", 404); throw new AppError("ERR_NO_TICKET_FOUND", 404);
} }
await ticket.destroy(); await ticket.destroy();
// TEST DEL
await deleteTicketsByIdCache(id)
//
return ticket; return ticket;
}; };

View File

@ -4,9 +4,7 @@ import BotIsOnQueue from "../../helpers/BotIsOnQueue";
import Contact from "../../models/Contact"; import Contact from "../../models/Contact";
import Ticket from "../../models/Ticket"; import Ticket from "../../models/Ticket";
import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService";
import ShowTicketService from "./ShowTicketService"; import ShowTicketService from "./ShowTicketService";
const FindOrCreateTicketService = async ( const FindOrCreateTicketService = async (
@ -22,18 +20,18 @@ const FindOrCreateTicketService = async (
}, },
contactId: groupContact ? groupContact.id : contact.id contactId: groupContact ? groupContact.id : contact.id
} }
}); });
const { queues, greetingMessage } = await ShowWhatsAppService(whatsappId);
const { queues, greetingMessage } = await ShowWhatsAppService(whatsappId);
//Habilitar esse caso queira usar o bot //Habilitar esse caso queira usar o bot
// const botInfo = await BotIsOnQueue('botqueue') // const botInfo = await BotIsOnQueue('botqueue')
const botInfo = {isOnQueue: false} const botInfo = { isOnQueue: false }
if (ticket) {
if (ticket) {
await ticket.update({ unreadMessages }); await ticket.update({ unreadMessages });
} }
@ -47,7 +45,7 @@ const FindOrCreateTicketService = async (
if (ticket) { if (ticket) {
await ticket.update({ await ticket.update({
status: "pending", status: "pending",
@ -57,8 +55,8 @@ const FindOrCreateTicketService = async (
} }
} }
if (!ticket && !groupContact) { if (!ticket && !groupContact) {
ticket = await Ticket.findOne({ ticket = await Ticket.findOne({
where: { where: {
updatedAt: { updatedAt: {
@ -68,15 +66,15 @@ const FindOrCreateTicketService = async (
//[Op.between]: [+subMinutes(new Date(), 30), +new Date()] //[Op.between]: [+subMinutes(new Date(), 30), +new Date()]
// Sub seconds // Sub seconds
[Op.between]: [+subSeconds(new Date(), 0), +new Date()] [Op.between]: [+subSeconds(new Date(), 0), +new Date()]
}, },
contactId: contact.id contactId: contact.id
}, },
order: [["updatedAt", "DESC"]] order: [["updatedAt", "DESC"]]
}); });
if (ticket) { if (ticket) {
await ticket.update({ await ticket.update({
status: "pending", status: "pending",
userId: null, userId: null,
@ -85,14 +83,14 @@ const FindOrCreateTicketService = async (
} }
} }
if (!ticket) { if (!ticket) {
let status = "pending" let status = "pending"
if(queues.length > 1 && !botInfo.isOnQueue){ if (queues.length > 1 && !botInfo.isOnQueue) {
status = "queueChoice" status = "queueChoice"
} }
ticket = await Ticket.create({ ticket = await Ticket.create({
contactId: groupContact ? groupContact.id : contact.id, contactId: groupContact ? groupContact.id : contact.id,
status: status, status: status,
@ -100,10 +98,18 @@ const FindOrCreateTicketService = async (
unreadMessages, unreadMessages,
whatsappId whatsappId
}); });
// TEST DEL
// const { name } = await ShowContactService(contact.id);
// console.log('FIND OR CREATE TICKET SERVICE NAME: ', contact.name, ' STATUS: ', status)
//
} }
ticket = await ShowTicketService(ticket.id); ticket = await ShowTicketService(ticket.id);
return ticket; return ticket;
}; };

View File

@ -11,10 +11,25 @@ import Contact from "../../models/Contact";
import Queue from "../../models/Queue"; import Queue from "../../models/Queue";
const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }))) const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR })))
const ListTicketsServiceCache = async (status?: string, date?: string): Promise<any> => {
interface Request {
status?: string;
date?: string;
userId?: string | number;
queueId?: string | number
}
const ListTicketsServiceCache = async ({
status,
date,
userId,
queueId
}:Request): Promise<any> => {
let where_clause = {} let where_clause = {}
console.log(' QUEUE ID: ', queueId)
if (date) { if (date) {
where_clause = { where_clause = {
createdAt: { createdAt: {
@ -23,6 +38,19 @@ const ListTicketsServiceCache = async (status?: string, date?: string): Promise<
} }
} }
} }
else if(queueId){
where_clause = {
queueId: queueId
}
}
else if(userId){
where_clause = {
userId: userId
}
}
else { else {
// where_clause = { // where_clause = {
// createdAt: { // createdAt: {

View File

@ -8,11 +8,17 @@ import Message from "../../models/Message";
import Queue from "../../models/Queue"; import Queue from "../../models/Queue";
import ShowUserService from "../UserServices/ShowUserService"; import ShowUserService from "../UserServices/ShowUserService";
const unflatten = require('flat').unflatten
import { splitDateTime } from "../../helpers/SplitDateTime"; import { splitDateTime } from "../../helpers/SplitDateTime";
const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR }))) const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR })))
import ListTicketServiceCache from "./ListTicketServiceCache" import ListTicketServiceCache from "./ListTicketServiceCache"
import { searchTicketCache, loadTicketsCache } from '../../helpers/TicketCache'
interface Request { interface Request {
searchParam?: string; searchParam?: string;
@ -32,6 +38,7 @@ interface Response {
hasMore: boolean; hasMore: boolean;
} }
const ListTicketsService = async ({ const ListTicketsService = async ({
searchParam = "", searchParam = "",
pageNumber = "1", pageNumber = "1",
@ -43,17 +50,48 @@ const ListTicketsService = async ({
withUnreadMessages, withUnreadMessages,
unlimited = 'false' unlimited = 'false'
}: Request): Promise<Response> => { }: Request): Promise<Response> => {
let whereCondition: Filterable["where"] = {
[Op.or]: [{ userId }, { status: "pending" }], let whereCondition: Filterable["where"] = { [Op.or]: [{ userId }, { status: "pending" }], queueId: { [Op.or]: [queueIds, null] } };
queueId: { [Op.or]: [queueIds, null] }
}; if (searchParam && searchParam.trim().length > 0) {
try {
const offset = 40 * (+pageNumber - 1);
console.log('QUERY TICKET SEARCH PARAM FROM CACHE: ', searchParam)
let tickets: any = await searchTicketCache(searchParam, offset, 40);
if (tickets) {
console.log('QUERY TICKET SEARCH PARAM FROM CACHE LENGTH...: ', tickets.length)
tickets.map((t: any) => {
t['contact.number'] = t['contact_number']
delete t['contact_number']
return { ...['contact_number'] }
})
tickets = tickets.map((e: any) => unflatten(e))
return { tickets, count: tickets.length, hasMore: tickets.length > 0 ? true : false };
}
} catch (error) {
console.log('There was an error on search ListTicketservice.ts search cache: ', error)
}
console.log('QUERY TICKETS FROM DATABASE...')
}
let includeCondition: Includeable[]; let includeCondition: Includeable[];
// test del
// const test = await ListTicketServiceCache()
// console.log('TEST:\n', test)
//
includeCondition = [ includeCondition = [
@ -77,9 +115,8 @@ const ListTicketsService = async ({
whereCondition = { ...whereCondition, status }; whereCondition = { ...whereCondition, status };
// console.log('TEST unlimited: ', unlimited)
if (unlimited === 'true' && status !== 'pending') { if (unlimited === 'true' && status !== 'pending') {
whereCondition = { whereCondition = {
...whereCondition, ...whereCondition,
@ -95,10 +132,6 @@ const ListTicketsService = async ({
if (searchParam) { if (searchParam) {
const sanitizedSearchParam = searchParam.toLocaleLowerCase().trim(); const sanitizedSearchParam = searchParam.toLocaleLowerCase().trim();
//othavio
console.log('sanitizedSearchParam:'+ sanitizedSearchParam, ' | date: ',date)
// includeCondition = [ // includeCondition = [
// ...includeCondition, // ...includeCondition,
// { // {
@ -119,7 +152,7 @@ const ListTicketsService = async ({
{ {
"$contact.name$": where(fn("LOWER", col("contact.name")), "LIKE", `%${sanitizedSearchParam}%`) "$contact.name$": where(fn("LOWER", col("contact.name")), "LIKE", `%${sanitizedSearchParam}%`)
}, },
{ "$contact.number$": { [Op.like]: `%${sanitizedSearchParam}%` } }, { "$contact.number$": { [Op.like]: `%${sanitizedSearchParam}%` } },
// { // {
@ -152,7 +185,8 @@ const ListTicketsService = async ({
const offset = limit * (+pageNumber - 1); const offset = limit * (+pageNumber - 1);
// console.log('whereCondition: ', JSON.stringify(whereCondition))
console.log('ENTROU NO LIST TICKET SERVICE')
const { count, rows: tickets } = await Ticket.findAndCountAll({ const { count, rows: tickets } = await Ticket.findAndCountAll({
where: whereCondition, where: whereCondition,
@ -165,7 +199,7 @@ const ListTicketsService = async ({
const hasMore = count > offset + tickets.length; const hasMore = count > offset + tickets.length;
return { return {
tickets, tickets,

View File

@ -6,6 +6,11 @@ import SendWhatsAppMessage from "../WbotServices/SendWhatsAppMessage";
import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService";
import ShowTicketService from "./ShowTicketService"; import ShowTicketService from "./ShowTicketService";
import { createOrUpdateTicketCache } from '../../helpers/TicketCache'
var flatten = require('flat')
interface TicketData { interface TicketData {
status?: string; status?: string;
userId?: number; userId?: number;
@ -38,8 +43,8 @@ const UpdateTicketService = async ({
if (oldStatus === "closed") { if (oldStatus === "closed") {
await CheckContactOpenTickets(ticket.contact.id); await CheckContactOpenTickets(ticket.contact.id);
} }
await ticket.update({ await ticket.update({
status, status,
queueId, queueId,
@ -47,10 +52,27 @@ const UpdateTicketService = async ({
statusChatEnd statusChatEnd
}); });
await ticket.reload(); await ticket.reload();
// TEST DEL
try {
// const { name, number } = await ShowContactService(ticket.contactId)
let jsonString = JSON.stringify(ticket); //convert to string to remove the sequelize specific meta data
let ticket_obj = JSON.parse(jsonString); //to make plain json
delete ticket_obj['contact']['extraInfo']
delete ticket_obj['user']
ticket_obj = flatten(ticket_obj)
await createOrUpdateTicketCache(`ticket:${ticket.id}`, ticket_obj)
} catch (error) {
console.log('There was an error on UpdateTicketService.ts on createTicketCache: ', error)
}
//
let io = getIO(); let io = getIO();
if (ticket.status !== oldStatus || ticket.user?.id !== oldUserId) { if (ticket.status !== oldStatus || ticket.user?.id !== oldUserId) {
@ -69,7 +91,7 @@ const UpdateTicketService = async ({
ticket ticket
}); });
io.emit("ticketStatus", { io.emit("ticketStatus", {
action: "update", action: "update",
ticketStatus: { ticketId: ticket.id, status: ticket.status } ticketStatus: { ticketId: ticket.id, status: ticket.status }

View File

@ -33,16 +33,16 @@ const CreateOrUpdateUserOnlineTime = async ({
userId, userId,
status, status,
}: Request): Promise<UserOnlineTime> => { }: Request): Promise<UserOnlineTime> => {
const io = getIO(); const io = getIO();
let userOnlineTime:any = null; let userOnlineTime: any = null;
const user = await User.findOne({ where: { id: userId } }); const user = await User.findOne({ where: { id: userId } });
if (!user) { if (!user) {
console.log(' yyyyyyyyyyyyyyyyyyyyyyyyyy não tem mais esse usuario id: ', userId)
return userOnlineTime return userOnlineTime
} }
@ -68,27 +68,21 @@ const CreateOrUpdateUserOnlineTime = async ({
let oldStatus = userOnlineTime.status let oldStatus = userOnlineTime.status
console.log('>>>>>>>>> oldStatus: ', oldStatus, ' | new status', status)
if (oldStatus == 'online' && status === 'offline') { if (oldStatus == 'online' && status === 'offline') {
//updatedAt //updatedAt
let newtTime = intervalToDuration({ start: userOnlineTime.updatedAt, end: new Date() }) let newtTime = intervalToDuration({ start: userOnlineTime.updatedAt, end: new Date() })
console.log('TESTANDO INTERVAL DURATION: ', newtTime)
console.log('hours: ', newtTime.hours, ' | minutes: ', newtTime.minutes, ' | seconds: ', newtTime.seconds)
let onlineTime = new Date() let onlineTime = new Date()
onlineTime.setUTCHours(userOnlineTime.onlineTime.getHours()) onlineTime.setUTCHours(userOnlineTime.onlineTime.getHours())
onlineTime.setUTCMinutes(userOnlineTime.onlineTime.getMinutes()) onlineTime.setUTCMinutes(userOnlineTime.onlineTime.getMinutes())
onlineTime.setUTCSeconds(userOnlineTime.onlineTime.getSeconds()) onlineTime.setUTCSeconds(userOnlineTime.onlineTime.getSeconds())
console.log('userOnlineTime.onlineTime: ', userOnlineTime.onlineTime)
console.log('userOnlineTime.onlineTime.getHours(): ', userOnlineTime.onlineTime.getHours())
console.log('userOnlineTime.onlineTime.getMinutes(): ', userOnlineTime.onlineTime.getMinutes())
console.log('userOnlineTime.onlineTime.getSeconds(): ', userOnlineTime.onlineTime.getSeconds())
console.log('online time 3: ', onlineTime)
if (newtTime.hours && +newtTime.hours > 0) { if (newtTime.hours && +newtTime.hours > 0) {
@ -101,11 +95,11 @@ const CreateOrUpdateUserOnlineTime = async ({
onlineTime = addSeconds(onlineTime, newtTime.seconds) onlineTime = addSeconds(onlineTime, newtTime.seconds)
} }
console.log('online time 4: ', onlineTime)
const isoDate = new Date(onlineTime); const isoDate = new Date(onlineTime);
const mySQLDateString = isoDate.toJSON().slice(0, 19).replace('T', ' '); const mySQLDateString = isoDate.toJSON().slice(0, 19).replace('T', ' ');
console.log('mySQLDateString: ', mySQLDateString)
await userOnlineTime.update({ status, onlineTime: mySQLDateString }) await userOnlineTime.update({ status, onlineTime: mySQLDateString })
@ -114,10 +108,10 @@ const CreateOrUpdateUserOnlineTime = async ({
const updatedAtString = formatDateTimeString(userOnlineTime.updatedAt) const updatedAtString = formatDateTimeString(userOnlineTime.updatedAt)
const createdAtString = formatDateTimeString(userOnlineTime.createdAt) const createdAtString = formatDateTimeString(userOnlineTime.createdAt)
console.log('CreatedAt string: ', createdAtString)
console.log('UpdatedAt string: ', updatedAtString)
// //
io.emit("onlineStatus", { io.emit("onlineStatus", {
action: "update", action: "update",
@ -133,7 +127,7 @@ const CreateOrUpdateUserOnlineTime = async ({
} }
else if (oldStatus == 'offline' && status === 'online') { else if (oldStatus == 'offline' && status === 'online') {
await userOnlineTime.update({ status }) await userOnlineTime.update({ status })
io.emit("onlineStatus", { io.emit("onlineStatus", {
action: "update", action: "update",
userOnlineTime: { userOnlineTime: {

View File

@ -104,11 +104,7 @@ const ShowUserServiceReport = async ({
}); });
} }
// console.log('>>>>>>>>>>>>>> objQuery: ', objQuery)
if (!objQuery) { if (!objQuery) {
throw new AppError("ERR_NO_OBJ_QUERY_FOUND", 404); throw new AppError("ERR_NO_OBJ_QUERY_FOUND", 404);

View File

@ -3,6 +3,8 @@ import { getWbot } from "../../libs/wbot";
import Contact from "../../models/Contact"; import Contact from "../../models/Contact";
import { logger } from "../../utils/logger"; import { logger } from "../../utils/logger";
import { createOrUpdateContactCache } from '../../helpers/ContactsCache'
const ImportContactsService = async (): Promise<void> => { const ImportContactsService = async (): Promise<void> => {
const defaultWhatsapp = await GetDefaultWhatsApp(); const defaultWhatsapp = await GetDefaultWhatsApp();
@ -32,7 +34,16 @@ const ImportContactsService = async (): Promise<void> => {
if (numberExists) return null; if (numberExists) return null;
return Contact.create({ number, name }); let contact = await Contact.create({ number, name });
// await contact.reload()
// TEST DEL
await createOrUpdateContactCache(`contact:${contact.id}`, {id:contact.id, name, number, profilePicUrl: contact.profilePicUrl, isGroup: contact.isGroup, extraInfo: '', email:'' })
//
// return Contact.create({ number, name });
return contact
}) })
); );
} }

View File

@ -4,6 +4,9 @@ import AppError from "../../errors/AppError";
import GetTicketWbot from "../../helpers/GetTicketWbot"; import GetTicketWbot from "../../helpers/GetTicketWbot";
import Ticket from "../../models/Ticket"; import Ticket from "../../models/Ticket";
import { updateTicketCacheByTicketId } from '../../helpers/TicketCache'
import { date } from "faker";
interface Request { interface Request {
media: Express.Multer.File; media: Express.Multer.File;
ticket: Ticket; ticket: Ticket;
@ -22,6 +25,10 @@ const SendWhatsAppMedia = async ({
await ticket.update({ lastMessage: media.filename }); await ticket.update({ lastMessage: media.filename });
// TEST DEL
await updateTicketCacheByTicketId(ticket.id, { lastMessage: media.filename, updatedAt: new Date(ticket.updatedAt).toISOString() })
//
fs.unlinkSync(media.path); fs.unlinkSync(media.path);
return sentMessage; return sentMessage;

View File

@ -11,6 +11,7 @@ import wbotByUserQueue from '../../helpers/GetWbotByUserQueue'
import { WhatsIndex } from "../../helpers/LoadBalanceWhatsSameQueue"; import { WhatsIndex } from "../../helpers/LoadBalanceWhatsSameQueue";
import { updateTicketCacheByTicketId } from '../../helpers/TicketCache'
interface Request { interface Request {
body: string; body: string;
@ -61,6 +62,12 @@ const SendWhatsAppMessage = async ({
try { try {
const sentMessage = await wbot.sendMessage(`${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`, body, { quotedMessageId: quotedMsgSerializedId, linkPreview: false }); const sentMessage = await wbot.sendMessage(`${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`, body, { quotedMessageId: quotedMsgSerializedId, linkPreview: false });
await ticket.update({ lastMessage: body }); await ticket.update({ lastMessage: body });
// TEST DEL
await updateTicketCacheByTicketId(ticket.id, { lastMessage: body, updatedAt: new Date(ticket.updatedAt).toISOString() })
//
return sentMessage; return sentMessage;
} catch (err) { } catch (err) {
throw new AppError("ERR_SENDING_WAPP_MSG"); throw new AppError("ERR_SENDING_WAPP_MSG");

View File

@ -10,6 +10,8 @@ import path from 'path';
import { format } from "date-fns"; import { format } from "date-fns";
import ptBR from 'date-fns/locale/pt-BR'; import ptBR from 'date-fns/locale/pt-BR';
import { import {
Contact as WbotContact, Contact as WbotContact,
Message as WbotMessage, Message as WbotMessage,
@ -49,7 +51,9 @@ import final_message from "./ura_final_message";
import SendWhatsAppMessage from "./SendWhatsAppMessage"; import SendWhatsAppMessage from "./SendWhatsAppMessage";
import Whatsapp from "../../models/Whatsapp"; import Whatsapp from "../../models/Whatsapp";
import { splitDateTime } from "../../helpers/SplitDateTime"; import { splitDateTime } from "../../helpers/SplitDateTime";
// //
import { updateTicketCacheByTicketId } from '../../helpers/TicketCache'
@ -159,6 +163,10 @@ const verifyMessage = async (
await ticket.update({ lastMessage: msg.body }); await ticket.update({ lastMessage: msg.body });
// TEST DEL
// await updateTicketCacheByTicketId(ticket.id, { lastMessage: msg.body, updatedAt: new Date(ticket.updatedAt).toISOString() })
//
await CreateMessageService({ messageData }); await CreateMessageService({ messageData });
}; };
@ -227,7 +235,7 @@ const verifyQueue = async (
ticketData: { queueId: choosenQueue.id }, ticketData: { queueId: choosenQueue.id },
ticketId: ticket.id ticketId: ticket.id
}); });
let botOptions = '' let botOptions = ''
@ -255,7 +263,8 @@ const verifyQueue = async (
const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, body); const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, body);
await verifyMessage(sentMessage, ticket, contact); await verifyMessage(sentMessage, ticket, contact);
} else { }
else {
//test del transfere o atendimento se entrar na ura infinita //test del transfere o atendimento se entrar na ura infinita
@ -288,8 +297,7 @@ const verifyQueue = async (
} }
//console.log('TICKET MESSAGE ON QUEUE CHOICE: ', ticket_message)
//
} }
}; };
@ -378,7 +386,7 @@ const handleMessage = async (
msgContact = await msg.getContact(); msgContact = await msg.getContact();
// console.log('-----msgContact TESTE MSG2: ', msgContact, ' | msg: ', msg) //
console.log(`\n <<<<<<<<<< RECEIVING MESSAGE: console.log(`\n <<<<<<<<<< RECEIVING MESSAGE:
Parcial msg and msgContact info: Parcial msg and msgContact info:
@ -412,7 +420,8 @@ const handleMessage = async (
const unreadMessages = msg.fromMe ? 0 : chat.unreadCount; const unreadMessages = msg.fromMe ? 0 : chat.unreadCount;
const contact = await verifyContact(msgContact); const contact = await verifyContact(msgContact);
if (unreadMessages === 0 && whatsapp.farewellMessage && whatsapp.farewellMessage === msg.body) return; if (unreadMessages === 0 && whatsapp.farewellMessage && whatsapp.farewellMessage === msg.body) return;
@ -422,12 +431,15 @@ const handleMessage = async (
wbot.id!, wbot.id!,
unreadMessages, unreadMessages,
groupContact groupContact
); );
//
// await updateTicketCacheByTicketId(ticket.id, {'contact.profilePicUrl': ticket.contact.profilePicUrl})
// Para responder para o cliente pelo mesmo whatsapp que ele enviou a mensagen // Para responder para o cliente pelo mesmo whatsapp que ele enviou a mensagen
if (wbot.id != ticket.whatsappId) { if (wbot.id != ticket.whatsappId) {
console.log('>>> entrou wbot.id: ', wbot.id, ' | ', ticket.whatsappId)
await ticket.update({ whatsappId: wbot.id }); await ticket.update({ whatsappId: wbot.id });
} }
@ -495,7 +507,7 @@ const handleMessage = async (
opt_user_attendant = data_ura[indexAttendant].id opt_user_attendant = data_ura[indexAttendant].id
} }
// console.log('¨¨¨¨¨¨¨¨¨¨¨¨¨¨ indexAttendant: ',indexAttendant, ' | opt_user_attendant: ', opt_user_attendant) //
let ticket_message = await ShowTicketMessage(ticket.id, true, ura_length, `^[0-${ura_length}}]$`); let ticket_message = await ShowTicketMessage(ticket.id, true, ura_length, `^[0-${ura_length}}]$`);
@ -529,9 +541,9 @@ const handleMessage = async (
// console.log('----------------- ticket_message: ', ticket_message) //
//console.log('¨¨¨¨¨¨¨¨¨¨¨¨¨¨ MSG.BODY: ', msg.body , ' | opt_user_attendant: ',opt_user_attendant, ' | lastOption: ', lastOption) //
// È numero // È numero
if (!Number.isNaN(Number(msg.body.trim())) && (+msg.body >= 0 && +msg.body <= data_ura.length)) { if (!Number.isNaN(Number(msg.body.trim())) && (+msg.body >= 0 && +msg.body <= data_ura.length)) {
@ -546,7 +558,7 @@ const handleMessage = async (
console.log('TICKET MESSAGE: ', ticket_message)
// test del // test del
let next = true let next = true
@ -780,7 +792,7 @@ const handleMessage = async (
if (whatsapp.status == 'CONNECTED') { if (whatsapp.status == 'CONNECTED') {
console.log('wbotMessageListener.ts - ENTROU NO RESTORE...')
let timestamp = Math.floor(Date.now() / 1000) let timestamp = Math.floor(Date.now() / 1000)
@ -789,7 +801,7 @@ const handleMessage = async (
await restartWhatsSession(whatsapp) await restartWhatsSession(whatsapp)
console.log('...PASSOU O RESTORE - wbotMessageListener.ts ')
} }
} }

View File

@ -5,6 +5,8 @@ import "react-toastify/dist/ReactToastify.css";
import { createTheme, ThemeProvider } from "@material-ui/core/styles"; import { createTheme, ThemeProvider } from "@material-ui/core/styles";
import { ptBR } from "@material-ui/core/locale"; import { ptBR } from "@material-ui/core/locale";
import { TabTicketProvider } from "../src/context/TabTicketHeaderOption/TabTicketHeaderOption";
const App = () => { const App = () => {
const [locale, setLocale] = useState(); const [locale, setLocale] = useState();
@ -18,7 +20,7 @@ const App = () => {
"&::-webkit-scrollbar-thumb": { "&::-webkit-scrollbar-thumb": {
boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)", boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)",
backgroundColor: "#e8e8e8", backgroundColor: "#e8e8e8",
}, },
}, },
palette: { palette: {
@ -41,7 +43,13 @@ const App = () => {
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<Routes />
{/*TabTicketProvider Context to manipulate the entire state of selected option from user click on tickets options header */}
<TabTicketProvider>
<Routes />
</TabTicketProvider>
</ThemeProvider> </ThemeProvider>
); );
}; };

View File

@ -165,7 +165,7 @@ const Modal = (props) => {
// Get from child 2 // Get from child 2
const datePickerValue = (data) => { const datePickerValue = (data) => {
console.log('datePickerValue: ', (data));
setDatePicker(data) setDatePicker(data)
@ -173,7 +173,7 @@ const Modal = (props) => {
// Get from child 3 // Get from child 3
const timerPickerValue = (data) => { const timerPickerValue = (data) => {
console.log('timerPickerValue: ', (data));
setTimerPicker(data) setTimerPicker(data)
@ -207,7 +207,7 @@ const Modal = (props) => {
if (statusChatEndId === '2' || statusChatEndId === '3') { if (statusChatEndId === '2' || statusChatEndId === '3') {
console.log('Entrou! textArea1: ', textArea1)
if (startDate.trim().length === 0) { if (startDate.trim().length === 0) {
@ -255,18 +255,18 @@ const Modal = (props) => {
let sendMessageDayBefore = currenciesTimeBefore.filter(i => i.label.indexOf('24 HORAS ANTES DO HORÁRIO DO AGENDAMENTO') >= 0); let sendMessageDayBefore = currenciesTimeBefore.filter(i => i.label.indexOf('24 HORAS ANTES DO HORÁRIO DO AGENDAMENTO') >= 0);
if (sendMessageDayBefore.length > 0 && timeBefore === formatedTimeHour(timerPicker)) { if (sendMessageDayBefore.length > 0 && timeBefore === formatedTimeHour(timerPicker)) {
console.log('ENVIAR MENSAGEM UM DIA ANTES!')
console.log('MENSAGEM SERÁ ENVIA NO DIA: ', dateCurrentFormated(new Date(subDays(new Date(startDate + ' ' + formatedTimeHour(new Date(`${startDate} ${timerPicker.getHours()}:${timerPicker.getMinutes()}:00`))), 1))))
dateSendMessage = dateCurrentFormated(new Date(subDays(new Date(startDate + ' ' + formatedTimeHour(new Date(`${startDate} ${timerPicker.getHours()}:${timerPicker.getMinutes()}:00`))), 1))) dateSendMessage = dateCurrentFormated(new Date(subDays(new Date(startDate + ' ' + formatedTimeHour(new Date(`${startDate} ${timerPicker.getHours()}:${timerPicker.getMinutes()}:00`))), 1)))
} }
console.log('AGENDAMENTO ENVIO MENSAGEM1: ', `${dateSendMessage} ${timeBefore}:00`)
} else if (statusChatEndId === '2') { } else if (statusChatEndId === '2') {
console.log('AGENDAMENTO ENVIO MENSAGEM2: ', startDate + ' ' + formatedTimeHour(new Date(`${startDate} ${timerPicker.getHours()}:${timerPicker.getMinutes()}:00`)))
} }
@ -297,21 +297,21 @@ const Modal = (props) => {
let hours = [] let hours = []
let hour = 1 let hour = 1
console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>> startDate: ', startDate)
console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>> dateCurrentFormated: ', dateCurrentFormated())
console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>> startDate: ', typeof (startDate))
console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>> startDate: ', (startDate))
if (typeof (startDate) === 'string' && startDate.trim().length > 0 && startDate === dateCurrentFormated()) { if (typeof (startDate) === 'string' && startDate.trim().length > 0 && startDate === dateCurrentFormated()) {
console.log('HOJE++++')
while (subHours(timer, hour).getHours() >= 6 && while (subHours(timer, hour).getHours() >= 6 &&
subHours(timer, hour).getHours() >= new Date().getHours() && subHours(timer, hour).getHours() >= new Date().getHours() &&
subHours(timer, hour).getHours() <= 20) { subHours(timer, hour).getHours() <= 20) {
console.log('******** TIMER: ', formatedTimeHour(subHours(timer, hour)))
hours.push({ value: formatedTimeHour(subHours(timer, hour)), label: `${hour} HORA ANTES DO HORÁRIO DO AGENDAMENTO` }) hours.push({ value: formatedTimeHour(subHours(timer, hour)), label: `${hour} HORA ANTES DO HORÁRIO DO AGENDAMENTO` })
@ -319,7 +319,7 @@ const Modal = (props) => {
} }
if (hours.length > 1) { if (hours.length > 1) {
console.log('entrou----------------------: ', hours.length)
hours.pop() hours.pop()
setCurrency(hours[0].value) setCurrency(hours[0].value)
} }
@ -332,7 +332,7 @@ const Modal = (props) => {
while (subHours(timer, hour).getHours() >= 6 && subHours(timer, hour).getHours() <= 20) { while (subHours(timer, hour).getHours() >= 6 && subHours(timer, hour).getHours() <= 20) {
console.log('******** another day TIMER: ', formatedTimeHour(subHours(timer, hour)))
hours.push( hours.push(
{ {
@ -344,7 +344,7 @@ const Modal = (props) => {
} }
if (hours.length > 0) { if (hours.length > 0) {
console.log('entrou----------------------: ', hours.length)
setCurrency(hours[0].value) setCurrency(hours[0].value)
} }
else { else {
@ -358,18 +358,18 @@ const Modal = (props) => {
hours.push({ value: formatedTimeHour(timerPicker), label: `24 HORAS ANTES DO HORÁRIO DO AGENDAMENTO` }) hours.push({ value: formatedTimeHour(timerPicker), label: `24 HORAS ANTES DO HORÁRIO DO AGENDAMENTO` })
console.log('#subDays: ', dateCurrentFormated(new Date(subDays(new Date(startDate + ' ' + formatedTimeHour(new Date(`${startDate} ${timerPicker.getHours()}:${timerPicker.getMinutes()}:00`))), 1))))
} }
console.log('hourshourshourshourshourshourshourshourshourshourshourshours ', hours)
return { time: hours, hour: hour } return { time: hours, hour: hour }
} }
console.log('===================================== addDays: ', addDays(new Date(), 1))
@ -394,7 +394,7 @@ const Modal = (props) => {
// Get from child 1 // Get from child 1
const textFieldSelect = (data) => { const textFieldSelect = (data) => {
console.log('textFieldSelect: ', data);
setStatusChatEnd(data) setStatusChatEnd(data)
} }
@ -408,7 +408,7 @@ const Modal = (props) => {
const handleCheckBoxChange = (event) => { const handleCheckBoxChange = (event) => {
//console.log('event.target.checked: ', event.target.checked)
setChecked(event.target.checked); setChecked(event.target.checked);
}; };
@ -416,11 +416,11 @@ const Modal = (props) => {
const handleChangeHourBefore = (event) => { const handleChangeHourBefore = (event) => {
console.log('textFihandleChangeHourBefore: ', event.target.value);
// var matchedTime = currenciesTimeBefore.filter(i => i.label.indexOf('24 HORAS ANTES DO HORÁRIO DO AGENDAMENTO') >= 0); // var matchedTime = currenciesTimeBefore.filter(i => i.label.indexOf('24 HORAS ANTES DO HORÁRIO DO AGENDAMENTO') >= 0);
// console.log('textFihandleChangeHourBefore matchedTime: ',matchedTime);
setCurrency(event.target.value); setCurrency(event.target.value);

View File

@ -17,8 +17,8 @@ import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import FormControl from "@mui/material/FormControl"; import FormControl from "@mui/material/FormControl";
import Tooltip from "@mui/material/Tooltip"; // import Tooltip from "@mui/material/Tooltip";
import Zoom from "@mui/material/Zoom"; // import Zoom from "@mui/material/Zoom";
import CancelIcon from "@material-ui/icons/Cancel"; import CancelIcon from "@material-ui/icons/Cancel";
import CheckCircleIcon from "@material-ui/icons/CheckCircle"; import CheckCircleIcon from "@material-ui/icons/CheckCircle";

View File

@ -37,6 +37,10 @@ import { AuthContext } from "../../context/Auth/AuthContext";
import { useLocalStorage } from "../../hooks/useLocalStorage"; import { useLocalStorage } from "../../hooks/useLocalStorage";
import toastError from "../../errors/toastError"; import toastError from "../../errors/toastError";
// import TicketsManager from "../../components/TicketsManager/";
import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketHeaderOption";
const Mp3Recorder = new MicRecorder({ bitRate: 128 }); const Mp3Recorder = new MicRecorder({ bitRate: 128 });
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
@ -202,6 +206,9 @@ const useStyles = makeStyles((theme) => ({
})); }));
const MessageInput = ({ ticketStatus }) => { const MessageInput = ({ ticketStatus }) => {
const { tabOption, setTabOption } = useContext(TabTicketContext);
const classes = useStyles(); const classes = useStyles();
const { ticketId } = useParams(); const { ticketId } = useParams();
@ -214,8 +221,7 @@ const MessageInput = ({ ticketStatus }) => {
const [typeBar, setTypeBar] = useState(false); const [typeBar, setTypeBar] = useState(false);
const inputRef = useRef(); const inputRef = useRef();
const [anchorEl, setAnchorEl] = useState(null); const [anchorEl, setAnchorEl] = useState(null);
const { setReplyingMessage, replyingMessage } = const { setReplyingMessage, replyingMessage } = useContext(ReplyMessageContext);
useContext(ReplyMessageContext);
const { user } = useContext(AuthContext); const { user } = useContext(AuthContext);
const [signMessage, setSignMessage] = useLocalStorage("signOption", true); const [signMessage, setSignMessage] = useLocalStorage("signOption", true);
@ -268,6 +274,12 @@ const MessageInput = ({ ticketStatus }) => {
setLoading(true); setLoading(true);
e.preventDefault(); e.preventDefault();
if (tabOption === 'search') {
setTabOption('open')
}
const formData = new FormData(); const formData = new FormData();
formData.append("fromMe", true); formData.append("fromMe", true);
medias.forEach((media) => { medias.forEach((media) => {
@ -288,6 +300,12 @@ const MessageInput = ({ ticketStatus }) => {
const handleSendMessage = async () => { const handleSendMessage = async () => {
if (inputMessage.trim() === "") return; if (inputMessage.trim() === "") return;
setLoading(true); setLoading(true);
if (tabOption === 'search') {
setTabOption('open')
}
const message = { const message = {
read: 1, read: 1,
@ -310,7 +328,7 @@ const MessageInput = ({ ticketStatus }) => {
setReplyingMessage(null); setReplyingMessage(null);
}; };
const handleStartRecording = async () => { const handleStartRecording = async () => {
setLoading(true); setLoading(true);
try { try {
@ -346,6 +364,13 @@ const MessageInput = ({ ticketStatus }) => {
const handleUploadAudio = async () => { const handleUploadAudio = async () => {
setLoading(true); setLoading(true);
if (tabOption === 'search') {
setTabOption('open')
}
try { try {
const [, blob] = await Mp3Recorder.stop().getMp3(); const [, blob] = await Mp3Recorder.stop().getMp3();
if (blob.size < 10000) { if (blob.size < 10000) {

View File

@ -283,7 +283,7 @@ const reducer = (state, action) => {
state[messageIndex] = newMessage; state[messageIndex] = newMessage;
} else { } else {
state.push(newMessage); state.push(newMessage);
// console.log(' TESTANDO NOVA MENSAGEM: ', newMessage)
} }
return [...state]; return [...state];
@ -333,7 +333,7 @@ const MessagesList = ({ ticketId, isGroup }) => {
try { try {
const { data } = await api.get("/messages/" + ticketId, { const { data } = await api.get("/messages/" + ticketId, {
params: { pageNumber }, params: { pageNumber },
}); });
if (currentTicketId.current === ticketId) { if (currentTicketId.current === ticketId) {
dispatch({ type: "LOAD_MESSAGES", payload: data.messages }); dispatch({ type: "LOAD_MESSAGES", payload: data.messages });
@ -362,17 +362,15 @@ const MessagesList = ({ ticketId, isGroup }) => {
socket.on("connect", () => socket.emit("joinChatBox", ticketId)); socket.on("connect", () => socket.emit("joinChatBox", ticketId));
socket.on("appMessage", (data) => { socket.on("appMessage", (data) => {
if (data.action === "create") { if (data.action === "create") {
dispatch({ type: "ADD_MESSAGE", payload: data.message }); dispatch({ type: "ADD_MESSAGE", payload: data.message });
scrollToBottom(); scrollToBottom();
} }
if (data.action === "update") { if (data.action === "update") {
console.log('2 THIS IS THE DATA: ', data)
dispatch({ type: "UPDATE_MESSAGE", payload: data.message }); dispatch({ type: "UPDATE_MESSAGE", payload: data.message });
} }
}); });
@ -423,7 +421,7 @@ const MessagesList = ({ ticketId, isGroup }) => {
return <ModalImageCors imageUrl={message.mediaUrl} />; return <ModalImageCors imageUrl={message.mediaUrl} />;
} }
if (message.mediaType === "audio") { if (message.mediaType === "audio") {
return ( return (
<audio controls> <audio controls>
<source src={message.mediaUrl} type="audio/ogg"></source> <source src={message.mediaUrl} type="audio/ogg"></source>
@ -487,11 +485,13 @@ const MessagesList = ({ ticketId, isGroup }) => {
</span> </span>
); );
} }
if (index < messagesList.length - 1) { if (index < messagesList.length - 1) {
let messageDay = parseISO(messagesList[index].createdAt); let messageDay = parseISO(messagesList[index].createdAt);
let previousMessageDay = parseISO(messagesList[index - 1].createdAt); let previousMessageDay = parseISO(messagesList[index - 1].createdAt);
if (!isSameDay(messageDay, previousMessageDay)) { if (!isSameDay(messageDay, previousMessageDay)) {
return ( return (
<span <span
className={classes.dailyTimestamp} className={classes.dailyTimestamp}
@ -504,13 +504,30 @@ const MessagesList = ({ ticketId, isGroup }) => {
); );
} }
} }
if (index === messagesList.length - 1) { if (index === messagesList.length - 1) {
let messageDay = parseISO(messagesList[index].createdAt);
let previousMessageDay = parseISO(messagesList[index - 1].createdAt);
return ( return (
<div <>
key={`ref-${message.createdAt}`} {!isSameDay(messageDay, previousMessageDay) &&
ref={lastMessageRef} <span
style={{ float: "left", clear: "both" }} className={classes.dailyTimestamp}
/> key={`timestamp-${message.id}`}
>
<div className={classes.dailyTimestampText}>
{format(parseISO(messagesList[index].createdAt), "dd/MM/yyyy")}
</div>
</span>
}
<div
key={`ref-${message.createdAt}`}
ref={lastMessageRef}
style={{ float: "left", clear: "both" }}
/>
</>
); );
} }
}; };

View File

@ -63,14 +63,14 @@ const NotificationsPopOver = () => {
// const [lastRef] = useState(+history.location.pathname.split("/")[2]) // const [lastRef] = useState(+history.location.pathname.split("/")[2])
// console.log('ticketIdRef: ',ticketIdRef, ' | lastRef: ',lastRef)
useEffect(() => { useEffect(() => {
soundAlertRef.current = play; soundAlertRef.current = play;
if (!("Notification" in window)) { if (!("Notification" in window)) {
console.log("This browser doesn't support notifications");
} else { } else {
Notification.requestPermission(); Notification.requestPermission();
} }
@ -82,7 +82,7 @@ const NotificationsPopOver = () => {
useEffect(() => { useEffect(() => {
// console.log('888888888888888888888888888888888888888888888888888888888888888888')
ticketIdRef.current = ticketIdUrl; ticketIdRef.current = ticketIdUrl;
}, [ticketIdUrl]); }, [ticketIdUrl]);
@ -97,7 +97,7 @@ const NotificationsPopOver = () => {
if(data.action === "logout"){ if(data.action === "logout"){
console.log('___________data.userId: ', data.userOnlineTime['status'])
if(`${user.id}` === data.userOnlineTime['userId']){ if(`${user.id}` === data.userOnlineTime['userId']){
@ -112,7 +112,7 @@ const NotificationsPopOver = () => {
socket.on("isOnline", (data) => { socket.on("isOnline", (data) => {
// console.log('___________data.userId: ', data.userId)
if(data.action === "online"){ if(data.action === "online"){
@ -169,7 +169,7 @@ const NotificationsPopOver = () => {
socket.on("appMessage", data => { socket.on("appMessage", data => {
// console.log('******************* DATA: ', data)
if ( if (
data.action === "create" && data.action === "create" &&
@ -177,24 +177,24 @@ const NotificationsPopOver = () => {
(data.ticket.userId === user?.id || !data.ticket.userId) (data.ticket.userId === user?.id || !data.ticket.userId)
) { ) {
// console.log('entrou.............')
setNotifications(prevState => { setNotifications(prevState => {
// console.log('prevState: ', prevState)
// prevState.forEach((e)=>{ // prevState.forEach((e)=>{
// console.log(`>>> e.id: ${e.id} | data.ticket.Id: ${data.ticket.id}`) //
// }) // })
const ticketIndex = prevState.findIndex(t => t.id === data.ticket.id); const ticketIndex = prevState.findIndex(t => t.id === data.ticket.id);
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
// console.log(` data.ticket 1 `)
prevState[ticketIndex] = data.ticket; prevState[ticketIndex] = data.ticket;
return [...prevState]; return [...prevState];
} }
// console.log(` data.ticket 2 `)
return [data.ticket, ...prevState]; return [data.ticket, ...prevState];
}); });
@ -206,7 +206,7 @@ const NotificationsPopOver = () => {
if (shouldNotNotificate) return; if (shouldNotNotificate) return;
//console.log('PASSOU!!!!!!!')
handleNotifications(data); handleNotifications(data);
} }

View File

@ -27,7 +27,7 @@ const MTable = (props) => {
// useEffect(() => { // useEffect(() => {
// console.log(`You have clicked the button ${selectedRow} times`) //
// }, [selectedRow]); // }, [selectedRow]);

View File

@ -40,7 +40,7 @@ useEffect(()=>{
setCurrency( event.target.value) setCurrency( event.target.value)
console.log('event.target.value: ', event.target.value)
}; };

View File

@ -1,6 +1,8 @@
import React from "react"; import React from "react";
const TabPanel = ({ children, value, name, ...rest }) => { const TabPanel = ({ children, value, name, ...rest }) => {
if (value === name) { if (value === name) {
return ( return (
<div <div

View File

@ -15,6 +15,8 @@ import { AuthContext } from "../../context/Auth/AuthContext";
import Modal from "../ChatEnd/ModalChatEnd"; import Modal from "../ChatEnd/ModalChatEnd";
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketHeaderOption";
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
actionButtons: { actionButtons: {
marginRight: 6, marginRight: 6,
@ -34,6 +36,8 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const ticketOptionsMenuOpen = Boolean(anchorEl); const ticketOptionsMenuOpen = Boolean(anchorEl);
const { user } = useContext(AuthContext); const { user } = useContext(AuthContext);
const { tabOption, setTabOption } = useContext(TabTicketContext);
const handleOpenTicketOptionsMenu = e => { const handleOpenTicketOptionsMenu = e => {
setAnchorEl(e.currentTarget); setAnchorEl(e.currentTarget);
@ -50,7 +54,7 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
data = {...data, 'ticketId': ticket.id} data = {...data, 'ticketId': ticket.id}
console.log('ChatEnd: ',(data));
handleUpdateTicketStatus(null, "closed", user?.id, data) handleUpdateTicketStatus(null, "closed", user?.id, data)
@ -78,6 +82,10 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
if(status==='closed'){ if(status==='closed'){
if (tabOption === 'search') {
setTabOption('open')
}
await api.put(`/tickets/${ticket.id}`, { await api.put(`/tickets/${ticket.id}`, {
status: status, status: status,
userId: userId || null, userId: userId || null,
@ -86,6 +94,10 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
} }
else{ else{
if (tabOption === 'search') {
setTabOption('open')
}
await api.put(`/tickets/${ticket.id}`, { await api.put(`/tickets/${ticket.id}`, {
status: status, status: status,
@ -96,6 +108,8 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
setLoading(false); setLoading(false);
if (status === "open") { if (status === "open") {
history.push(`/tickets/${ticket.id}`); history.push(`/tickets/${ticket.id}`);
} else { } else {
history.push("/tickets"); history.push("/tickets");
@ -124,7 +138,7 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
)} )}
{ticket.status === "open" && ( {ticket.status === "open" && (
<> <>
<ButtonWithSpinner <ButtonWithSpinner style={{ marginRight: "70px" }}
loading={loading} loading={loading}
startIcon={<Replay />} startIcon={<Replay />}
size="small" size="small"
@ -132,14 +146,14 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
> >
{i18n.t("messagesList.header.buttons.return")} {i18n.t("messagesList.header.buttons.return")}
</ButtonWithSpinner> </ButtonWithSpinner>
<ButtonWithSpinner <ButtonWithSpinner
loading={loading} loading={loading}
size="small" size="small"
variant="contained" variant="contained"
color="primary" color="primary"
onClick={e => { onClick={e => {
handleModal() handleModal()
// handleUpdateTicketStatus(e, "closed", user?.id) // handleUpdateTicketStatus(e, "closed", user?.id)

View File

@ -118,6 +118,7 @@ const TicketListItem = ({ ticket }) => {
const handleAcepptTicket = async id => { const handleAcepptTicket = async id => {
setLoading(true); setLoading(true);
try { try {
await api.put(`/tickets/${id}`, { await api.put(`/tickets/${id}`, {
status: "open", status: "open",
userId: user?.id, userId: user?.id,
@ -129,6 +130,9 @@ const TicketListItem = ({ ticket }) => {
if (isMounted.current) { if (isMounted.current) {
setLoading(false); setLoading(false);
} }
history.push(`/tickets/${id}`); history.push(`/tickets/${id}`);
}; };
@ -216,7 +220,7 @@ const TicketListItem = ({ ticket }) => {
<Badge <Badge
className={classes.newMessagesCount} className={classes.newMessagesCount}
badgeContent={ticket.unreadMessages} badgeContent={+ticket.unreadMessages}
classes={{ classes={{
badge: classes.badgeStyle, badge: classes.badgeStyle,
}} }}

View File

@ -10,8 +10,9 @@ import TicketsListSkeleton from "../TicketsListSkeleton";
import useTickets from "../../hooks/useTickets"; import useTickets from "../../hooks/useTickets";
import { i18n } from "../../translate/i18n"; import { i18n } from "../../translate/i18n";
import { AuthContext } from "../../context/Auth/AuthContext"; import { AuthContext } from "../../context/Auth/AuthContext";
import { SearchTicketContext } from "../../context/SearchTicket/SearchTicket";
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
ticketsListWrapper: { ticketsListWrapper: {
@ -79,12 +80,10 @@ const reducer = (state, action) => {
newTickets.forEach(ticket => { newTickets.forEach(ticket => {
// console.log('* ticket.unreadMessages: ',ticket.unreadMessages) const ticketIndex = state.findIndex(t => +t.id === +ticket.id);
const ticketIndex = state.findIndex(t => t.id === ticket.id);
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
state[ticketIndex] = ticket; state[ticketIndex] = ticket;
if (ticket.unreadMessages > 0) { if (+ticket.unreadMessages > 0) {
state.unshift(state.splice(ticketIndex, 1)[0]); state.unshift(state.splice(ticketIndex, 1)[0]);
} }
} else { } else {
@ -98,7 +97,10 @@ const reducer = (state, action) => {
if (action.type === "RESET_UNREAD") { if (action.type === "RESET_UNREAD") {
const ticketId = action.payload; const ticketId = action.payload;
const ticketIndex = state.findIndex(t => t.id === ticketId); const ticketIndex = state.findIndex(t => +t.id === +ticketId);
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
state[ticketIndex].unreadMessages = 0; state[ticketIndex].unreadMessages = 0;
} }
@ -109,9 +111,8 @@ const reducer = (state, action) => {
if (action.type === "UPDATE_TICKET") { if (action.type === "UPDATE_TICKET") {
const ticket = action.payload; const ticket = action.payload;
// console.log('++++++++++++ UPDATE_TICKET: ',ticket) const ticketIndex = state.findIndex(t => +t.id === +ticket.id);
const ticketIndex = state.findIndex(t => t.id === ticket.id);
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
state[ticketIndex] = ticket; state[ticketIndex] = ticket;
} else { } else {
@ -127,13 +128,13 @@ const reducer = (state, action) => {
const ticket = action.payload.ticket; const ticket = action.payload.ticket;
const ticketIndex = state.findIndex(t => t.id === ticket.id); const ticketIndex = state.findIndex(t => +t.id === +ticket.id);
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
// console.log('>>>>>> ticketIndex: ', ticketIndex)
// console.log('&&&&&&& UPDATE_TICKET_UNREAD_MESSAGES ticket: ',ticket, ' |\n MESSAGE: ', message)
if (!message.fromMe) { if (!message.fromMe) {
ticket.unreadMessages += 1 ticket.unreadMessages += 1
@ -151,7 +152,7 @@ const reducer = (state, action) => {
if (action.type === "UPDATE_TICKET_CONTACT") { if (action.type === "UPDATE_TICKET_CONTACT") {
const contact = action.payload; const contact = action.payload;
const ticketIndex = state.findIndex(t => t.contactId === contact.id); const ticketIndex = state.findIndex(t => +t.contactId === +contact.id);
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
state[ticketIndex].contact = contact; state[ticketIndex].contact = contact;
} }
@ -160,7 +161,7 @@ const reducer = (state, action) => {
if (action.type === "DELETE_TICKET") { if (action.type === "DELETE_TICKET") {
const ticketId = action.payload; const ticketId = action.payload;
const ticketIndex = state.findIndex(t => t.id === ticketId); const ticketIndex = state.findIndex(t => +t.id === +ticketId);
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
state.splice(ticketIndex, 1); state.splice(ticketIndex, 1);
} }
@ -174,16 +175,21 @@ const reducer = (state, action) => {
}; };
const TicketsList = (props) => { const TicketsList = (props) => {
const { status, searchParam, showAll, selectedQueueIds, updateCount, style } = props; const { status, searchParam, showAll, selectedQueueIds, updateCount, style, tab } = props;
const classes = useStyles(); const classes = useStyles();
const [pageNumber, setPageNumber] = useState(1); const [pageNumber, setPageNumber] = useState(1);
const [ticketsList, dispatch] = useReducer(reducer, []); const [ticketsList, dispatch] = useReducer(reducer, []);
const { user } = useContext(AuthContext); const { user } = useContext(AuthContext);
const { searchTicket } = useContext(SearchTicketContext)
useEffect(() => { useEffect(() => {
dispatch({ type: "RESET" }); dispatch({ type: "RESET" });
setPageNumber(1); setPageNumber(1);
}, [status, searchParam, dispatch, showAll, selectedQueueIds]);
}, [status, searchParam, showAll, selectedQueueIds, searchTicket]);
const { tickets, hasMore, loading } = useTickets({ const { tickets, hasMore, loading } = useTickets({
pageNumber, pageNumber,
@ -191,20 +197,31 @@ const TicketsList = (props) => {
status, status,
showAll, showAll,
queueIds: JSON.stringify(selectedQueueIds), queueIds: JSON.stringify(selectedQueueIds),
tab
}); });
useEffect(() => { useEffect(() => {
if (!status && !searchParam) return; if (!status && !searchParam) return;
// console.log('lllllllllllllllllllllllllllllllll') // if (searchParam) {
//
// dispatch({ type: "RESET" });
// }
dispatch({ if ((searchParam && searchParam.trim().length > 0) &&
type: "LOAD_TICKETS", (tickets && tickets.length === 0) &&
payload: tickets, pageNumber === 1) {
}); dispatch({ type: "RESET" })
}, [tickets, status, searchParam]); }
dispatch({ type: "LOAD_TICKETS", payload: tickets, });
}, [tickets, status, searchParam, pageNumber]);
useEffect(() => { useEffect(() => {
// if (tab=='search')return
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
const shouldUpdateTicket = ticket => const shouldUpdateTicket = ticket =>
@ -223,9 +240,14 @@ const TicketsList = (props) => {
}); });
socket.on("ticket", data => {
socket.on("ticket", data => {
if (data.action === "updateUnread") { if (data.action === "updateUnread") {
if (tab === 'search' && data.ticket && data.ticket.status === 'pending') return
dispatch({ dispatch({
type: "RESET_UNREAD", type: "RESET_UNREAD",
payload: data.ticketId, payload: data.ticketId,
@ -233,6 +255,9 @@ const TicketsList = (props) => {
} }
if (data.action === "update" && shouldUpdateTicket(data.ticket)) { if (data.action === "update" && shouldUpdateTicket(data.ticket)) {
if (tab === 'search' && data.ticket && (data.ticket.status === 'pending' || data.ticket.status === 'closed')) return
dispatch({ dispatch({
type: "UPDATE_TICKET", type: "UPDATE_TICKET",
payload: data.ticket, payload: data.ticket,
@ -245,13 +270,15 @@ const TicketsList = (props) => {
if (data.action === "delete") { if (data.action === "delete") {
dispatch({ type: "DELETE_TICKET", payload: data.ticketId }); dispatch({ type: "DELETE_TICKET", payload: data.ticketId });
} }
}); });
socket.on("appMessage", data => { socket.on("appMessage", data => {
if (data.action === "create" && shouldUpdateTicket(data.ticket)) { if (data.action === "create" && shouldUpdateTicket(data.ticket)) {
// console.log('((((((((((((((((((( DATA.MESSAGE: ', data.message)
if (tab === 'search') return
dispatch({ dispatch({
type: "UPDATE_TICKET_UNREAD_MESSAGES", type: "UPDATE_TICKET_UNREAD_MESSAGES",
@ -273,7 +300,7 @@ const TicketsList = (props) => {
return () => { return () => {
socket.disconnect(); socket.disconnect();
}; };
}, [status, showAll, user, selectedQueueIds]); }, [status, showAll, user, selectedQueueIds, tab]);
useEffect(() => { useEffect(() => {
@ -296,6 +323,9 @@ const TicketsList = (props) => {
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget; const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
if (scrollHeight - (scrollTop + 100) < clientHeight) { if (scrollHeight - (scrollTop + 100) < clientHeight) {
loadMore(); loadMore();
} }
}; };

View File

@ -23,6 +23,10 @@ import { Can } from "../Can";
import TicketsQueueSelect from "../TicketsQueueSelect"; import TicketsQueueSelect from "../TicketsQueueSelect";
import { Button } from "@material-ui/core"; import { Button } from "@material-ui/core";
import { TabTicketContext } from "../../context/TabTicketHeaderOption/TabTicketHeaderOption";
import { SearchTicketContext } from "../../context/SearchTicket/SearchTicket";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
ticketsWrapper: { ticketsWrapper: {
position: "relative", position: "relative",
@ -92,6 +96,11 @@ const useStyles = makeStyles((theme) => ({
})); }));
const TicketsManager = () => { const TicketsManager = () => {
const { tabOption, setTabOption } = useContext(TabTicketContext);
const {setSearchTicket} = useContext(SearchTicketContext)
const classes = useStyles(); const classes = useStyles();
const [searchParam, setSearchParam] = useState(""); const [searchParam, setSearchParam] = useState("");
@ -108,6 +117,8 @@ const TicketsManager = () => {
const userQueueIds = user.queues.map((q) => q.id); const userQueueIds = user.queues.map((q) => q.id);
const [selectedQueueIds, setSelectedQueueIds] = useState(userQueueIds || []); const [selectedQueueIds, setSelectedQueueIds] = useState(userQueueIds || []);
const [inputSearch, setInputSearch] = useState('');
useEffect(() => { useEffect(() => {
if (user.profile.toUpperCase() === "ADMIN") { if (user.profile.toUpperCase() === "ADMIN") {
setShowAllTickets(true); setShowAllTickets(true);
@ -119,25 +130,55 @@ const TicketsManager = () => {
if (tab === "search") { if (tab === "search") {
searchInputRef.current.focus(); searchInputRef.current.focus();
} }
}, [tab]);
setTabOption(tab)
}, [tab, setTabOption]);
useEffect(() => {
if (tabOption === 'open') {
setTabOption('')
setSearchParam('');
setInputSearch('');
setTab("open");
return;
}
}, [tabOption, setTabOption])
let searchTimeout; let searchTimeout;
const removeExtraSpace = (str) => {
str = str.replace(/^\s+/g, '')
return str.replace(/\s+/g, ' ')
}
const handleSearch = (e) => { const handleSearch = (e) => {
let searchedTerm = e.target.value.toLowerCase() let searchedTerm = e.target.value.toLowerCase()
setInputSearch(removeExtraSpace(searchedTerm))
setSearchTicket(searchParam)
clearTimeout(searchTimeout); clearTimeout(searchTimeout);
if (searchedTerm === "") { if (searchedTerm === "") {
setSearchParam(searchedTerm); setSearchParam(searchedTerm);
setInputSearch(searchedTerm)
setTab("open"); setTab("open");
return; return;
} }
searchTimeout = setTimeout(() => { searchTimeout = setTimeout(() => {
setSearchParam(searchedTerm); setSearchParam(searchedTerm);
}, 1000);
}, 500);
}; };
const handleChangeTab = (e, newValue) => { const handleChangeTab = (e, newValue) => {
@ -197,7 +238,8 @@ const TicketsManager = () => {
className={classes.searchInput} className={classes.searchInput}
inputRef={searchInputRef} inputRef={searchInputRef}
placeholder={i18n.t("tickets.search.placeholder")} placeholder={i18n.t("tickets.search.placeholder")}
type="search" type="search"
value={inputSearch}
onChange={handleSearch} onChange={handleSearch}
/> />
</div> </div>
@ -286,7 +328,7 @@ const TicketsManager = () => {
selectedQueueIds={selectedQueueIds} selectedQueueIds={selectedQueueIds}
updateCount={(val) => setPendingCount(val)} updateCount={(val) => setPendingCount(val)}
style={applyPanelStyle("pending")} style={applyPanelStyle("pending")}
/> />
</Paper> </Paper>
</TabPanel> </TabPanel>
<TabPanel value={tab} name="closed" className={classes.ticketsWrapper}> <TabPanel value={tab} name="closed" className={classes.ticketsWrapper}>
@ -297,11 +339,15 @@ const TicketsManager = () => {
/> />
</TabPanel> </TabPanel>
<TabPanel value={tab} name="search" className={classes.ticketsWrapper}> <TabPanel value={tab} name="search" className={classes.ticketsWrapper}>
<TicketsList
searchParam={searchParam}
showAll={true} <TicketsList
selectedQueueIds={selectedQueueIds} searchParam={searchParam}
/> tab={tab}
showAll={true}
selectedQueueIds={selectedQueueIds}
/>
</TabPanel> </TabPanel>
</Paper> </Paper>
); );

View File

@ -7,7 +7,7 @@ const AuthContext = createContext();
const AuthProvider = ({ children }) => { const AuthProvider = ({ children }) => {
const { loading, user, isAuth, handleLogin, handleLogout } = useAuth(); const { loading, user, isAuth, handleLogin, handleLogout } = useAuth();
//{console.log('authContext teste: ', user)} //{
return ( return (
<AuthContext.Provider <AuthContext.Provider

View File

@ -0,0 +1,17 @@
import React, { useState, createContext } from "react";
const SearchTicketContext = createContext();
const SearchTicketProvider = ({ children }) => {
const [searchTicket, setSearchTicket] = useState(0);
return (
<SearchTicketContext.Provider value={{ searchTicket, setSearchTicket }}>
{children}
</SearchTicketContext.Provider>
);
};
export { SearchTicketContext, SearchTicketProvider };

View File

@ -0,0 +1,33 @@
import React, { useState, createContext } from "react";
// export const TabTicketContext = createContext();
const TabTicketContext = createContext();
// export const TabTicketProvider= ({ children }) => {
// const [tabOption, setTabOption] = useState(0);
// return (
// <TabTicketContext.Provider value={{ tabOption, setTabOption }}>
// {children}
// </TabTicketContext.Provider>
// );
// };
const TabTicketProvider = ({ children }) => {
const [tabOption, setTabOption] = useState(0);
return (
<TabTicketContext.Provider value={{ tabOption, setTabOption }}>
{children}
</TabTicketContext.Provider>
);
};
export { TabTicketContext, TabTicketProvider };

View File

@ -11,23 +11,26 @@ const useTickets = ({
showAll, showAll,
queueIds, queueIds,
withUnreadMessages, withUnreadMessages,
unlimited unlimited,
tab
}) => { }) => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [hasMore, setHasMore] = useState(false); const [hasMore, setHasMore] = useState(false);
const [tickets, setTickets] = useState([]); const [tickets, setTickets] = useState([]);
useEffect(() => { useEffect(() => {
setLoading(true); setLoading(true);
const delayDebounceFn = setTimeout(() => { const delayDebounceFn = setTimeout(() => {
const fetchTickets = async () => { const fetchTickets = async () => {
try { try {
if(searchParam){ if ((tab === 'search') && ( !searchParam || searchParam.trim().length === 0 || searchParam.trim().length >40 || searchParam.endsWith(' '))) {
console.log('searchParam: ', searchParam)
return return
} }
const { data } = await api.get("/tickets", { const { data } = await api.get("/tickets", {
params: { params: {
@ -40,15 +43,16 @@ const useTickets = ({
withUnreadMessages, withUnreadMessages,
unlimited unlimited
}, },
}); });
console.log('data.tickets: ', data.tickets)
// console.log('data.hasMore: ', data.hasMore)
setTickets(data.tickets); setTickets(data.tickets);
setHasMore(data.hasMore); setHasMore(data.hasMore);
setLoading(false); setLoading(false);
} catch (err) { } catch (err) {
setLoading(false); setLoading(false);
toastError(err); toastError(err);
@ -65,7 +69,7 @@ const useTickets = ({
showAll, showAll,
queueIds, queueIds,
withUnreadMessages, withUnreadMessages,
tab,
unlimited unlimited
]); ]);

View File

@ -124,7 +124,7 @@ const useWhatsApps = () => {
socket.on("whatsappSessionMonit", data => { socket.on("whatsappSessionMonit", data => {
if (data.action === "update") { if (data.action === "update") {
// console.log('data.whatsappSessionSize: ', data.whatsappSessionSize)
dispatch({ type: "UPDATE_WHATSAPPS_SESSION_MONIT", payload: data.whatsappSessionSize }); dispatch({ type: "UPDATE_WHATSAPPS_SESSION_MONIT", payload: data.whatsappSessionSize });
} }

View File

@ -47,7 +47,7 @@ function ListItemLink(props) {
} }
const MainListItems = (props) => { const MainListItems = (props) => {
const { drawerClose, setDrawerOpen, drawerOpen } = props; const { setDrawerOpen } = 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);

View File

@ -44,9 +44,9 @@ const reducer = (state, action) => {
const newContacts = []; const newContacts = [];
contacts.forEach((contact) => { contacts.forEach((contact) => {
const contactIndex = state.findIndex((c) => c.id === contact.id); const contactIndex = state.findIndex((c) => +c.id === +contact.id);
if (contactIndex !== -1) { if (contactIndex !== -1) {
state[contactIndex] = contact; state[contactIndex] = contact;
} else { } else {
@ -60,7 +60,7 @@ const reducer = (state, action) => {
if (action.type === "UPDATE_CONTACTS") { if (action.type === "UPDATE_CONTACTS") {
const contact = action.payload; const contact = action.payload;
const contactIndex = state.findIndex((c) => c.id === contact.id); const contactIndex = state.findIndex((c) => +c.id === +contact.id);
if (contactIndex !== -1) { if (contactIndex !== -1) {
state[contactIndex] = contact; state[contactIndex] = contact;
@ -73,7 +73,7 @@ const reducer = (state, action) => {
if (action.type === "DELETE_CONTACT") { if (action.type === "DELETE_CONTACT") {
const contactId = action.payload; const contactId = action.payload;
const contactIndex = state.findIndex((c) => c.id === contactId); const contactIndex = state.findIndex((c) => +c.id === +contactId);
if (contactIndex !== -1) { if (contactIndex !== -1) {
state.splice(contactIndex, 1); state.splice(contactIndex, 1);
} }
@ -98,7 +98,7 @@ const Contacts = () => {
const classes = useStyles(); const classes = useStyles();
const history = useHistory(); const history = useHistory();
const { user } = useContext(AuthContext); const { user } = useContext(AuthContext);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [pageNumber, setPageNumber] = useState(1); const [pageNumber, setPageNumber] = useState(1);
@ -110,25 +110,46 @@ const Contacts = () => {
const [confirmOpen, setConfirmOpen] = useState(false); const [confirmOpen, setConfirmOpen] = useState(false);
const [hasMore, setHasMore] = useState(false); const [hasMore, setHasMore] = useState(false);
useEffect(() => { useEffect(() => {
dispatch({ type: "RESET" }); dispatch({ type: "RESET" });
setPageNumber(1); setPageNumber(1);
}, [searchParam]); }, [searchParam]);
useEffect(() => { useEffect(() => {
if (searchParam.trim().length > 0 && searchParam.endsWith(' ')) {
return
}
setLoading(true); setLoading(true);
const delayDebounceFn = setTimeout(() => { const delayDebounceFn = setTimeout(() => {
const fetchContacts = async () => { const fetchContacts = async () => {
try { try {
const { data } = await api.get("/contacts/", {
params: { searchParam, pageNumber }, if (searchParam.trim().length > 40) {
}); return
}
const { data } = await api.get("/contacts/", { params: { searchParam, pageNumber }, });
dispatch({ type: "LOAD_CONTACTS", payload: data.contacts }); dispatch({ type: "LOAD_CONTACTS", payload: data.contacts });
setHasMore(data.hasMore); setHasMore(data.hasMore);
setLoading(false); setLoading(false);
} catch (err) { } catch (err) {
toastError(err); toastError(err);
} }
}; };
fetchContacts(); fetchContacts();
}, 500); }, 500);
@ -153,8 +174,15 @@ const Contacts = () => {
}; };
}, []); }, []);
const removeExtraSpace = (str) => {
str = str.replace(/^\s+/g, '')
return str.replace(/\s+/g, ' ')
}
const handleSearch = (event) => { const handleSearch = (event) => {
setSearchParam(event.target.value.toLowerCase()); setSearchParam(removeExtraSpace(event.target.value.toLowerCase()));
}; };
const handleOpenContactModal = () => { const handleOpenContactModal = () => {
@ -217,7 +245,7 @@ const Contacts = () => {
if (!hasMore || loading) return; if (!hasMore || loading) return;
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget; const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
// console.log('scrollTop: ', scrollTop, ' | scrollHeight: ', scrollHeight, ' | clientHeight: ', clientHeight)
if (scrollHeight - (scrollTop + 100) < clientHeight) { if (scrollHeight - (scrollTop + 100) < clientHeight) {
loadMore(); loadMore();
@ -235,9 +263,8 @@ const Contacts = () => {
<ConfirmationModal <ConfirmationModal
title={ title={
deletingContact deletingContact
? `${i18n.t("contacts.confirmationModal.deleteTitle")} ${ ? `${i18n.t("contacts.confirmationModal.deleteTitle")} ${deletingContact.name
deletingContact.name }?`
}?`
: `${i18n.t("contacts.confirmationModal.importTitlte")}` : `${i18n.t("contacts.confirmationModal.importTitlte")}`
} }
open={confirmOpen} open={confirmOpen}

View File

@ -11,7 +11,7 @@ import IconButton from "@mui/material/IconButton";
import Info from "@material-ui/icons/Info"; import Info from "@material-ui/icons/Info";
import { AuthContext } from "../../context/Auth/AuthContext"; import { AuthContext } from "../../context/Auth/AuthContext";
import { i18n } from "../../translate/i18n"; // import { i18n } from "../../translate/i18n";
import Chart from "./Chart"; import Chart from "./Chart";
import openSocket from "socket.io-client"; import openSocket from "socket.io-client";
import api from "../../services/api"; import api from "../../services/api";
@ -241,7 +241,7 @@ const Dashboard = () => {
dispatch({ type: "RESET" }); dispatch({ type: "RESET" });
dispatch({ type: "LOAD_QUERY", payload: dataQuery.data }); dispatch({ type: "LOAD_QUERY", payload: dataQuery.data });
} catch (err) { } catch (err) {
console.log(err);
} }
}; };
@ -280,7 +280,7 @@ const Dashboard = () => {
}, []); }, []);
useEffect(() => { useEffect(() => {
if (ticketStatusChange === "") return console.log("foi"); if (ticketStatusChange === "") return
const delayDebounceFn = setTimeout(() => { const delayDebounceFn = setTimeout(() => {
const fetchQueries = async () => { const fetchQueries = async () => {
try { try {

View File

@ -21,8 +21,7 @@ import apiBroker from "../../services/apiBroker";
import fileDownload from 'js-file-download' import fileDownload from 'js-file-download'
import openSocket from "socket.io-client"; import openSocket from "socket.io-client";
import { TramOutlined } from "@material-ui/icons";
const report = [{ 'value': '1', 'label': 'Atendimento por atendentes' }, { 'value': '2', 'label': 'Usuários online/offline' }] const report = [{ 'value': '1', 'label': 'Atendimento por atendentes' }, { 'value': '2', 'label': 'Usuários online/offline' }]
@ -216,7 +215,7 @@ let columnsData = [
{ title: 'Status', field: 'status' }, { title: 'Status', field: 'status' },
{ title: 'Criado', field: 'createdAt' }, { title: 'Criado', field: 'createdAt' },
//{title: 'Atualizado', field: 'updatedAt'}, {title: 'Atualizado', field: 'updatedAt'},
{ title: 'Status de encerramento', field: 'statusChatEnd' }]; { title: 'Status de encerramento', field: 'statusChatEnd' }];
@ -305,7 +304,7 @@ const Report = () => {
const { data } = await api.get("/reports/", { params: { userId, startDate, endDate, pageNumber: pageNumberTickets }, }); const { data } = await api.get("/reports/", { params: { userId, startDate, endDate, pageNumber: pageNumberTickets }, });
dispatchQ({ type: "LOAD_QUERY", payload: data.tickets }); dispatchQ({ type: "LOAD_QUERY", payload: data.tickets });
setHasMore(data.hasMore); setHasMore(data.hasMore);
setTotalCountTickets(data.count) setTotalCountTickets(data.count)
@ -317,13 +316,8 @@ const Report = () => {
const dataQuery = await api.get("/reports/user/services", { params: { userId, startDate, endDate }, }); const dataQuery = await api.get("/reports/user/services", { params: { userId, startDate, endDate }, });
dispatchQ({ type: "RESET" }) dispatchQ({ type: "RESET" })
dispatchQ({ type: "LOAD_QUERY", payload: dataQuery.data }); dispatchQ({ type: "LOAD_QUERY", payload: dataQuery.data });
//setLoading(false);
//setLoading(false);
console.log('REPORT 2 dataQuery : ', dataQuery.data)
//console.log()
} }

View File

@ -100,7 +100,7 @@ const Settings = () => {
// const handleEdit = () => { // const handleEdit = () => {
// console.log('Editar....') //
// } // }

View File

@ -10,6 +10,8 @@ import Ticket from "../../components/Ticket/";
import { i18n } from "../../translate/i18n"; import { i18n } from "../../translate/i18n";
import Hidden from "@material-ui/core/Hidden"; import Hidden from "@material-ui/core/Hidden";
import { SearchTicketProvider } from "../../context/SearchTicket/SearchTicket";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
chatContainer: { chatContainer: {
flex: 1, flex: 1,
@ -78,8 +80,12 @@ const Chat = () => {
className={ className={
ticketId ? classes.contactsWrapperSmall : classes.contactsWrapper ticketId ? classes.contactsWrapperSmall : classes.contactsWrapper
} }
> >
<TicketsManager /> <SearchTicketProvider>
<TicketsManager />
</SearchTicketProvider>
</Grid> </Grid>
<Grid item xs={12} md={8} className={classes.messagessWrapper}> <Grid item xs={12} md={8} className={classes.messagessWrapper}>
{/* <Grid item xs={8} className={classes.messagessWrapper}> */} {/* <Grid item xs={8} className={classes.messagessWrapper}> */}

View File

@ -21,7 +21,7 @@ import { AuthProvider } from "../context/Auth/AuthContext";
import { WhatsAppsProvider } from "../context/WhatsApp/WhatsAppsContext"; import { WhatsAppsProvider } from "../context/WhatsApp/WhatsAppsContext";
import Route from "./Route"; import Route from "./Route";
//console.log('---AuthProvider: ',AuthProvider)
const Routes = () => { const Routes = () => {
return ( return (

View File

@ -241,7 +241,7 @@ const messages = {
search: { title: "Busca" }, search: { title: "Busca" },
}, },
search: { search: {
placeholder: "Buscar tickets e mensagens", placeholder: "Busca telefone/nome",
}, },
buttons: { buttons: {
showAll: "Todos", showAll: "Todos",