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-async-errors": "^3.1.1",
"fast-folder-size": "^1.7.0",
"flat": "^5.0.2",
"fs-extra": "^10.1.0",
"http-graceful-shutdown": "^2.3.2",
"ioredis": "^5.2.3",
"jsonwebtoken": "^8.5.1",
"multer": "^1.4.2",
"mysql2": "^2.2.5",

View File

@ -13,6 +13,12 @@ import CheckIsValidContact from "../services/WbotServices/CheckIsValidContact";
import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl";
import AppError from "../errors/AppError";
import { searchContactCache } from '../helpers/ContactsCache'
import { off } from "process";
type IndexQuery = {
searchParam: string;
pageNumber: string;
@ -30,12 +36,34 @@ interface ContactData {
}
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({
searchParam,
pageNumber
});
// TEST DEL
if (searchParam && searchParam.trim().length > 0) {
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 });
};
@ -58,8 +86,8 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
}
await CheckIsValidContact(newContact.number);
const validNumber : any = await CheckContactNumber(newContact.number)
const validNumber: any = await CheckContactNumber(newContact.number)
const profilePicUrl = await GetProfilePicUrl(validNumber);
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('','2022-03-18','2022-03-19');
// 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;
console.log(' +++++++++++ scheduleData: ', scheduleData)
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> => {
console.log('EEEEEEEEEEEEEEEEEEEEEEEEEEE')
const { scheduleId } = req.params;

View File

@ -19,7 +19,9 @@ import ptBR from 'date-fns/locale/pt-BR';
import { splitDateTime } from "../helpers/SplitDateTime";
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> => {
const { contactId, status, userId }: TicketData = req.body;
// naty
// console.log(
// `contactId: ${contactId} \n| status: ${status} \n| userId: ${userId}`
// )
// test del
let ticket = await Ticket.findOne({ where: { contactId, status: 'queueChoice' } });
if (ticket) {
await UpdateTicketService({ ticketData: { status: 'open', userId: userId, }, ticketId: ticket.id });
console.log('TICKET QUEUE CHOICE !!!!!!!')
}
else {
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> => {
type indexQ = { status: string; date?: string; };
// type indexQ = { status: string; date?: string; };
const { status, date } = req.query as IndexQuery
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);
};
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) {
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 });
}
}
///////////////////////////////
// lembrete // agendamento
if (scheduleData.statusChatEndId === '2' || scheduleData.statusChatEndId === '3') {
console.log('*** schedulingDate: ', scheduleData.schedulingDate)
console.log('*** schedulingTime: ', scheduleData.schedulingTime)
if (isScheduling(scheduleData.schedulingDate, scheduleData.schedulingTime)) {
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 })))
if (userOldInfo.userId) {
// console.log('FECHOU...')
if (userOldInfo.userId) {
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> => {
const { userId } = req.params;
//const user = await ShowUserService(userId);
console.log('userId: ', userId)
//test del
await stopWhoIsOnlineMonitor()
let onlineTime = {

View File

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

View File

@ -56,14 +56,7 @@ const remove = async (req: Request, res: Response): Promise<Response> => {
const { whatsappId } = req.params;
const whatsapp = await ShowWhatsAppService(whatsappId);
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}`))
const wbot = getWbot(whatsapp.id);
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) {
WhatsQueueIndex.setIndex(0)
}
// console.log('WhatsQueueIndex.getIndex(): ', WhatsQueueIndex.getIndex())
}
index = +WhatsQueueIndex.getIndex()

View File

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

View File

@ -50,17 +50,15 @@ const SetTicketMessagesAsRead = async (ticket: Ticket): Promise<void> => {
const whatsapp = await ShowWhatsAppService(ticket.whatsappId);
if (whatsapp && whatsapp.status == 'CONNECTED') {
console.log('SetTicketMessagesAsRead.ts - ENTROU NO RESTORE...')
if (whatsapp && whatsapp.status == 'CONNECTED') {
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)=>{});
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.setUTCMinutes(new Date(oldOnlineTimeSum.onlineTime).getMinutes())
onlineTime.setUTCSeconds(new Date(oldOnlineTimeSum.onlineTime).getSeconds())
// console.log('_________________oldOnlineTimeSum.updatedAt: ', oldOnlineTimeSum.updatedAt)
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 newOnlinetime = isoDate.toJSON().slice(0, 19).replace('T', ' ');
//console.log('sum new online time: ', newOnlinetime)
const newOnlinetime = isoDate.toJSON().slice(0, 19).replace('T', ' ');
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 UpdateTicketService from "../services/TicketServices/UpdateTicketService";
import UpdateTicketService from "../services/TicketServices/UpdateTicketService";
const UpdateDeletedUserOpenTicketsStatus = async (
tickets: Ticket[]
): Promise<void> => {
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)
if (indexAux == -1) {
console.log(' entrou indexAux: ', indexAux)
if (indexAux == -1) {
const userOnline = await createOrUpdateOnlineUserService({ userId: el.id, status: 'online' })

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
import AppError from "../../errors/AppError";
import Contact from "../../models/Contact";
import { createOrUpdateContactCache } from '../../helpers/ContactsCache'
interface ExtraInfo {
name: 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;
};

View File

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

View File

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

View File

@ -29,14 +29,14 @@ const ListContactsService = async ({
]
};
const limit = 20;
const offset = limit * (+pageNumber - 1);
const offset = limit * (+pageNumber - 1);
const { count, rows: contacts } = await Contact.findAndCountAll({
where: whereCondition,
limit,
offset,
order: [["name", "ASC"]]
});
});
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 ContactCustomField from "../../models/ContactCustomField";
import { updateTicketsByContactsCache } from '../../helpers/TicketCache'
import { updateContactCacheById } from '../../helpers/ContactsCache'
interface ExtraInfo {
id?: number;
name: string;
@ -18,6 +21,7 @@ interface Request {
contactData: ContactData;
contactId: string;
}
const UpdateContactService = async ({
contactData,
@ -51,19 +55,31 @@ const UpdateContactService = async ({
}
})
);
}
}
const oldNumber = contact.number
await contact.update({
name,
number,
email
});
});
//TEST DEL
await updateTicketsByContactsCache(oldNumber, contact.name, contact.number)
//
await contact.reload({
attributes: ["id", "name", "number", "email", "profilePicUrl"],
include: ["extraInfo"]
});
// console.log('contactcontactcontactcontact: ',flatten(JSON.parse(JSON.stringify(contact))))
await updateContactCacheById(contact.id, JSON.parse(JSON.stringify(contact)))
return contact;
};

View File

@ -1,3 +1,4 @@
import { updateTicketCacheByTicketId } from "../../helpers/TicketCache";
import { getIO } from "../../libs/socket";
import Message from "../../models/Message";
import Ticket from "../../models/Ticket";
@ -16,7 +17,7 @@ interface Request {
messageData: MessageData;
}
const CreateMessageService = async ({messageData}: Request): Promise<Message> => {
const CreateMessageService = async ({ messageData }: Request): Promise<Message> => {
await Message.upsert(messageData);
const message = await Message.findByPk(messageData.id, {
@ -39,9 +40,20 @@ const CreateMessageService = async ({messageData}: Request): Promise<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();
io.to(message.ticketId.toString())
@ -54,8 +66,8 @@ const CreateMessageService = async ({messageData}: Request): Promise<Message> =>
contact: message.ticket.contact
});
}
}
return message;
};

View File

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

View File

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

View File

@ -13,6 +13,11 @@ import ptBR from 'date-fns/locale/pt-BR';
import { splitDateTime } from "../../helpers/SplitDateTime";
import TicketEmiterSumOpenClosedByUser from "../../helpers/OnlineReporEmiterInfoByUser";
import { createOrUpdateTicketCache } from '../../helpers/TicketCache'
let flatten = require('flat')
interface Request {
contactId: number;
status: string;
@ -41,16 +46,29 @@ const CreateTicketService = async ({
if (!ticket) {
throw new AppError("ERR_CREATING_TICKET");
}
}
// console.log('CONTACT ticket.id: ', ticket.id)
//test del
// 2022-05-11T05:20:33.000Z,
// const dateToday = ticket.createdAt.toISOString().split('T')[0]
// TEST DEL
try {
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 })))
//openByUser : [ { id: 13, status: 'online' } ]
const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR })))
TicketEmiterSumOpenClosedByUser(userId.toString(), dateToday.fullDate, dateToday.fullDate)
@ -58,10 +76,7 @@ const CreateTicketService = async ({
io.emit("ticketStatus", {
action: "update",
ticketStatus: {ticketId: ticket.id, status: ticket.status}
});
//
});
return ticket;

View File

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

View File

@ -4,9 +4,7 @@ import BotIsOnQueue from "../../helpers/BotIsOnQueue";
import Contact from "../../models/Contact";
import Ticket from "../../models/Ticket";
import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService";
import ShowTicketService from "./ShowTicketService";
import ShowTicketService from "./ShowTicketService";
const FindOrCreateTicketService = async (
@ -22,18 +20,18 @@ const FindOrCreateTicketService = async (
},
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
// const botInfo = await BotIsOnQueue('botqueue')
const botInfo = {isOnQueue: false}
const botInfo = { isOnQueue: false }
if (ticket) {
if (ticket) {
await ticket.update({ unreadMessages });
}
@ -47,7 +45,7 @@ const FindOrCreateTicketService = async (
if (ticket) {
if (ticket) {
await ticket.update({
status: "pending",
@ -57,8 +55,8 @@ const FindOrCreateTicketService = async (
}
}
if (!ticket && !groupContact) {
if (!ticket && !groupContact) {
ticket = await Ticket.findOne({
where: {
updatedAt: {
@ -68,15 +66,15 @@ const FindOrCreateTicketService = async (
//[Op.between]: [+subMinutes(new Date(), 30), +new Date()]
// Sub seconds
[Op.between]: [+subSeconds(new Date(), 0), +new Date()]
[Op.between]: [+subSeconds(new Date(), 0), +new Date()]
},
contactId: contact.id
},
order: [["updatedAt", "DESC"]]
});
if (ticket) {
if (ticket) {
await ticket.update({
status: "pending",
userId: null,
@ -85,14 +83,14 @@ const FindOrCreateTicketService = async (
}
}
if (!ticket) {
if (!ticket) {
let status = "pending"
if(queues.length > 1 && !botInfo.isOnQueue){
if (queues.length > 1 && !botInfo.isOnQueue) {
status = "queueChoice"
}
}
ticket = await Ticket.create({
contactId: groupContact ? groupContact.id : contact.id,
status: status,
@ -100,10 +98,18 @@ const FindOrCreateTicketService = async (
unreadMessages,
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);
return ticket;
};

View File

@ -11,10 +11,25 @@ import Contact from "../../models/Contact";
import Queue from "../../models/Queue";
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 = {}
console.log(' QUEUE ID: ', queueId)
if (date) {
where_clause = {
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 {
// where_clause = {
// createdAt: {

View File

@ -8,11 +8,17 @@ import Message from "../../models/Message";
import Queue from "../../models/Queue";
import ShowUserService from "../UserServices/ShowUserService";
const unflatten = require('flat').unflatten
import { splitDateTime } from "../../helpers/SplitDateTime";
const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR })))
import ListTicketServiceCache from "./ListTicketServiceCache"
import { searchTicketCache, loadTicketsCache } from '../../helpers/TicketCache'
interface Request {
searchParam?: string;
@ -32,6 +38,7 @@ interface Response {
hasMore: boolean;
}
const ListTicketsService = async ({
searchParam = "",
pageNumber = "1",
@ -43,17 +50,48 @@ const ListTicketsService = async ({
withUnreadMessages,
unlimited = 'false'
}: Request): Promise<Response> => {
let whereCondition: Filterable["where"] = {
[Op.or]: [{ userId }, { status: "pending" }],
queueId: { [Op.or]: [queueIds, null] }
};
let whereCondition: Filterable["where"] = { [Op.or]: [{ userId }, { status: "pending" }], 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[];
// test del
// const test = await ListTicketServiceCache()
// console.log('TEST:\n', test)
//
includeCondition = [
@ -77,9 +115,8 @@ const ListTicketsService = async ({
whereCondition = { ...whereCondition, status };
// console.log('TEST unlimited: ', unlimited)
if (unlimited === 'true' && status !== 'pending') {
if (unlimited === 'true' && status !== 'pending') {
whereCondition = {
...whereCondition,
@ -95,10 +132,6 @@ const ListTicketsService = async ({
if (searchParam) {
const sanitizedSearchParam = searchParam.toLocaleLowerCase().trim();
//othavio
console.log('sanitizedSearchParam:'+ sanitizedSearchParam, ' | date: ',date)
// includeCondition = [
// ...includeCondition,
// {
@ -119,7 +152,7 @@ const ListTicketsService = async ({
{
"$contact.name$": where(fn("LOWER", col("contact.name")), "LIKE", `%${sanitizedSearchParam}%`)
},
{ "$contact.number$": { [Op.like]: `%${sanitizedSearchParam}%` } },
// {
@ -152,7 +185,8 @@ const ListTicketsService = async ({
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({
where: whereCondition,
@ -165,7 +199,7 @@ const ListTicketsService = async ({
const hasMore = count > offset + tickets.length;
return {
tickets,

View File

@ -6,6 +6,11 @@ import SendWhatsAppMessage from "../WbotServices/SendWhatsAppMessage";
import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService";
import ShowTicketService from "./ShowTicketService";
import { createOrUpdateTicketCache } from '../../helpers/TicketCache'
var flatten = require('flat')
interface TicketData {
status?: string;
userId?: number;
@ -38,8 +43,8 @@ const UpdateTicketService = async ({
if (oldStatus === "closed") {
await CheckContactOpenTickets(ticket.contact.id);
}
}
await ticket.update({
status,
queueId,
@ -47,10 +52,27 @@ const UpdateTicketService = async ({
statusChatEnd
});
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();
if (ticket.status !== oldStatus || ticket.user?.id !== oldUserId) {
@ -69,7 +91,7 @@ const UpdateTicketService = async ({
ticket
});
io.emit("ticketStatus", {
action: "update",
ticketStatus: { ticketId: ticket.id, status: ticket.status }

View File

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

View File

@ -104,11 +104,7 @@ const ShowUserServiceReport = async ({
});
}
// console.log('>>>>>>>>>>>>>> objQuery: ', objQuery)
if (!objQuery) {
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 { logger } from "../../utils/logger";
import { createOrUpdateContactCache } from '../../helpers/ContactsCache'
const ImportContactsService = async (): Promise<void> => {
const defaultWhatsapp = await GetDefaultWhatsApp();
@ -32,7 +34,16 @@ const ImportContactsService = async (): Promise<void> => {
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 Ticket from "../../models/Ticket";
import { updateTicketCacheByTicketId } from '../../helpers/TicketCache'
import { date } from "faker";
interface Request {
media: Express.Multer.File;
ticket: Ticket;
@ -22,6 +25,10 @@ const SendWhatsAppMedia = async ({
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);
return sentMessage;

View File

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

View File

@ -10,6 +10,8 @@ import path from 'path';
import { format } from "date-fns";
import ptBR from 'date-fns/locale/pt-BR';
import {
Contact as WbotContact,
Message as WbotMessage,
@ -49,7 +51,9 @@ import final_message from "./ura_final_message";
import SendWhatsAppMessage from "./SendWhatsAppMessage";
import Whatsapp from "../../models/Whatsapp";
import { splitDateTime } from "../../helpers/SplitDateTime";
//
//
import { updateTicketCacheByTicketId } from '../../helpers/TicketCache'
@ -159,6 +163,10 @@ const verifyMessage = async (
await ticket.update({ lastMessage: msg.body });
// TEST DEL
// await updateTicketCacheByTicketId(ticket.id, { lastMessage: msg.body, updatedAt: new Date(ticket.updatedAt).toISOString() })
//
await CreateMessageService({ messageData });
};
@ -227,7 +235,7 @@ const verifyQueue = async (
ticketData: { queueId: choosenQueue.id },
ticketId: ticket.id
});
let botOptions = ''
@ -255,7 +263,8 @@ const verifyQueue = async (
const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, body);
await verifyMessage(sentMessage, ticket, contact);
} else {
}
else {
//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();
// console.log('-----msgContact TESTE MSG2: ', msgContact, ' | msg: ', msg)
//
console.log(`\n <<<<<<<<<< RECEIVING MESSAGE:
Parcial msg and msgContact info:
@ -412,7 +420,8 @@ const handleMessage = async (
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;
@ -422,12 +431,15 @@ const handleMessage = async (
wbot.id!,
unreadMessages,
groupContact
);
);
//
// await updateTicketCacheByTicketId(ticket.id, {'contact.profilePicUrl': ticket.contact.profilePicUrl})
// Para responder para o cliente pelo mesmo whatsapp que ele enviou a mensagen
if (wbot.id != ticket.whatsappId) {
console.log('>>> entrou wbot.id: ', wbot.id, ' | ', ticket.whatsappId)
await ticket.update({ whatsappId: wbot.id });
}
@ -495,7 +507,7 @@ const handleMessage = async (
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}}]$`);
@ -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
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
let next = true
@ -780,7 +792,7 @@ const handleMessage = async (
if (whatsapp.status == 'CONNECTED') {
console.log('wbotMessageListener.ts - ENTROU NO RESTORE...')
let timestamp = Math.floor(Date.now() / 1000)
@ -789,7 +801,7 @@ const handleMessage = async (
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 { ptBR } from "@material-ui/core/locale";
import { TabTicketProvider } from "../src/context/TabTicketHeaderOption/TabTicketHeaderOption";
const App = () => {
const [locale, setLocale] = useState();
@ -18,7 +20,7 @@ const App = () => {
"&::-webkit-scrollbar-thumb": {
boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)",
backgroundColor: "#e8e8e8",
backgroundColor: "#e8e8e8",
},
},
palette: {
@ -41,7 +43,13 @@ const App = () => {
return (
<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>
);
};

View File

@ -165,7 +165,7 @@ const Modal = (props) => {
// Get from child 2
const datePickerValue = (data) => {
console.log('datePickerValue: ', (data));
setDatePicker(data)
@ -173,7 +173,7 @@ const Modal = (props) => {
// Get from child 3
const timerPickerValue = (data) => {
console.log('timerPickerValue: ', (data));
setTimerPicker(data)
@ -207,7 +207,7 @@ const Modal = (props) => {
if (statusChatEndId === '2' || statusChatEndId === '3') {
console.log('Entrou! textArea1: ', textArea1)
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);
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)))
}
console.log('AGENDAMENTO ENVIO MENSAGEM1: ', `${dateSendMessage} ${timeBefore}:00`)
} 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 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()) {
console.log('HOJE++++')
while (subHours(timer, hour).getHours() >= 6 &&
subHours(timer, hour).getHours() >= new Date().getHours() &&
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` })
@ -319,7 +319,7 @@ const Modal = (props) => {
}
if (hours.length > 1) {
console.log('entrou----------------------: ', hours.length)
hours.pop()
setCurrency(hours[0].value)
}
@ -332,7 +332,7 @@ const Modal = (props) => {
while (subHours(timer, hour).getHours() >= 6 && subHours(timer, hour).getHours() <= 20) {
console.log('******** another day TIMER: ', formatedTimeHour(subHours(timer, hour)))
hours.push(
{
@ -344,7 +344,7 @@ const Modal = (props) => {
}
if (hours.length > 0) {
console.log('entrou----------------------: ', hours.length)
setCurrency(hours[0].value)
}
else {
@ -358,18 +358,18 @@ const Modal = (props) => {
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 }
}
console.log('===================================== addDays: ', addDays(new Date(), 1))
@ -394,7 +394,7 @@ const Modal = (props) => {
// Get from child 1
const textFieldSelect = (data) => {
console.log('textFieldSelect: ', data);
setStatusChatEnd(data)
}
@ -408,7 +408,7 @@ const Modal = (props) => {
const handleCheckBoxChange = (event) => {
//console.log('event.target.checked: ', event.target.checked)
setChecked(event.target.checked);
};
@ -416,11 +416,11 @@ const Modal = (props) => {
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);
// console.log('textFihandleChangeHourBefore matchedTime: ',matchedTime);
setCurrency(event.target.value);

View File

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

View File

@ -283,7 +283,7 @@ const reducer = (state, action) => {
state[messageIndex] = newMessage;
} else {
state.push(newMessage);
// console.log(' TESTANDO NOVA MENSAGEM: ', newMessage)
}
return [...state];
@ -333,7 +333,7 @@ const MessagesList = ({ ticketId, isGroup }) => {
try {
const { data } = await api.get("/messages/" + ticketId, {
params: { pageNumber },
});
});
if (currentTicketId.current === ticketId) {
dispatch({ type: "LOAD_MESSAGES", payload: data.messages });
@ -362,17 +362,15 @@ const MessagesList = ({ ticketId, isGroup }) => {
socket.on("connect", () => socket.emit("joinChatBox", ticketId));
socket.on("appMessage", (data) => {
if (data.action === "create") {
dispatch({ type: "ADD_MESSAGE", payload: data.message });
dispatch({ type: "ADD_MESSAGE", payload: data.message });
scrollToBottom();
}
if (data.action === "update") {
console.log('2 THIS IS THE DATA: ', data)
dispatch({ type: "UPDATE_MESSAGE", payload: data.message });
}
});
@ -423,7 +421,7 @@ const MessagesList = ({ ticketId, isGroup }) => {
return <ModalImageCors imageUrl={message.mediaUrl} />;
}
if (message.mediaType === "audio") {
return (
<audio controls>
<source src={message.mediaUrl} type="audio/ogg"></source>
@ -487,11 +485,13 @@ const MessagesList = ({ ticketId, isGroup }) => {
</span>
);
}
if (index < messagesList.length - 1) {
let messageDay = parseISO(messagesList[index].createdAt);
let previousMessageDay = parseISO(messagesList[index - 1].createdAt);
if (!isSameDay(messageDay, previousMessageDay)) {
return (
<span
className={classes.dailyTimestamp}
@ -504,13 +504,30 @@ const MessagesList = ({ ticketId, isGroup }) => {
);
}
}
if (index === messagesList.length - 1) {
let messageDay = parseISO(messagesList[index].createdAt);
let previousMessageDay = parseISO(messagesList[index - 1].createdAt);
return (
<div
key={`ref-${message.createdAt}`}
ref={lastMessageRef}
style={{ float: "left", clear: "both" }}
/>
<>
{!isSameDay(messageDay, previousMessageDay) &&
<span
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])
// console.log('ticketIdRef: ',ticketIdRef, ' | lastRef: ',lastRef)
useEffect(() => {
soundAlertRef.current = play;
if (!("Notification" in window)) {
console.log("This browser doesn't support notifications");
} else {
Notification.requestPermission();
}
@ -82,7 +82,7 @@ const NotificationsPopOver = () => {
useEffect(() => {
// console.log('888888888888888888888888888888888888888888888888888888888888888888')
ticketIdRef.current = ticketIdUrl;
}, [ticketIdUrl]);
@ -97,7 +97,7 @@ const NotificationsPopOver = () => {
if(data.action === "logout"){
console.log('___________data.userId: ', data.userOnlineTime['status'])
if(`${user.id}` === data.userOnlineTime['userId']){
@ -112,7 +112,7 @@ const NotificationsPopOver = () => {
socket.on("isOnline", (data) => {
// console.log('___________data.userId: ', data.userId)
if(data.action === "online"){
@ -169,7 +169,7 @@ const NotificationsPopOver = () => {
socket.on("appMessage", data => {
// console.log('******************* DATA: ', data)
if (
data.action === "create" &&
@ -177,24 +177,24 @@ const NotificationsPopOver = () => {
(data.ticket.userId === user?.id || !data.ticket.userId)
) {
// console.log('entrou.............')
setNotifications(prevState => {
// console.log('prevState: ', prevState)
// 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);
if (ticketIndex !== -1) {
// console.log(` data.ticket 1 `)
prevState[ticketIndex] = data.ticket;
return [...prevState];
}
// console.log(` data.ticket 2 `)
return [data.ticket, ...prevState];
});
@ -206,7 +206,7 @@ const NotificationsPopOver = () => {
if (shouldNotNotificate) return;
//console.log('PASSOU!!!!!!!')
handleNotifications(data);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ const AuthContext = createContext();
const AuthProvider = ({ children }) => {
const { loading, user, isAuth, handleLogin, handleLogout } = useAuth();
//{console.log('authContext teste: ', user)}
//{
return (
<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,
queueIds,
withUnreadMessages,
unlimited
unlimited,
tab
}) => {
const [loading, setLoading] = useState(true);
const [hasMore, setHasMore] = useState(false);
const [tickets, setTickets] = useState([]);
const [tickets, setTickets] = useState([]);
useEffect(() => {
setLoading(true);
const delayDebounceFn = setTimeout(() => {
const fetchTickets = async () => {
try {
try {
if(searchParam){
console.log('searchParam: ', searchParam)
if ((tab === 'search') && ( !searchParam || searchParam.trim().length === 0 || searchParam.trim().length >40 || searchParam.endsWith(' '))) {
return
}
const { data } = await api.get("/tickets", {
params: {
@ -40,15 +43,16 @@ const useTickets = ({
withUnreadMessages,
unlimited
},
});
});
console.log('data.tickets: ', data.tickets)
// console.log('data.hasMore: ', data.hasMore)
setTickets(data.tickets);
setHasMore(data.hasMore);
setLoading(false);
} catch (err) {
setLoading(false);
toastError(err);
@ -65,7 +69,7 @@ const useTickets = ({
showAll,
queueIds,
withUnreadMessages,
tab,
unlimited
]);

View File

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

View File

@ -47,7 +47,7 @@ function ListItemLink(props) {
}
const MainListItems = (props) => {
const { drawerClose, setDrawerOpen, drawerOpen } = props;
const { setDrawerOpen } = props;
const { whatsApps } = useContext(WhatsAppsContext);
const { user } = useContext(AuthContext);
const [connectionWarning, setConnectionWarning] = useState(false);

View File

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

View File

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

View File

@ -21,8 +21,7 @@ import apiBroker from "../../services/apiBroker";
import fileDownload from 'js-file-download'
import openSocket from "socket.io-client";
import { TramOutlined } from "@material-ui/icons";
import openSocket from "socket.io-client";
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: 'Criado', field: 'createdAt' },
//{title: 'Atualizado', field: 'updatedAt'},
{title: 'Atualizado', field: 'updatedAt'},
{ title: 'Status de encerramento', field: 'statusChatEnd' }];
@ -305,7 +304,7 @@ const Report = () => {
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);
setTotalCountTickets(data.count)
@ -317,13 +316,8 @@ const Report = () => {
const dataQuery = await api.get("/reports/user/services", { params: { userId, startDate, endDate }, });
dispatchQ({ type: "RESET" })
dispatchQ({ type: "LOAD_QUERY", payload: dataQuery.data });
//setLoading(false);
console.log('REPORT 2 dataQuery : ', dataQuery.data)
//console.log()
dispatchQ({ type: "LOAD_QUERY", payload: dataQuery.data });
//setLoading(false);
}

View File

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

View File

@ -10,6 +10,8 @@ import Ticket from "../../components/Ticket/";
import { i18n } from "../../translate/i18n";
import Hidden from "@material-ui/core/Hidden";
import { SearchTicketProvider } from "../../context/SearchTicket/SearchTicket";
const useStyles = makeStyles((theme) => ({
chatContainer: {
flex: 1,
@ -78,8 +80,12 @@ const Chat = () => {
className={
ticketId ? classes.contactsWrapperSmall : classes.contactsWrapper
}
>
<TicketsManager />
>
<SearchTicketProvider>
<TicketsManager />
</SearchTicketProvider>
</Grid>
<Grid item xs={12} md={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 Route from "./Route";
//console.log('---AuthProvider: ',AuthProvider)
const Routes = () => {
return (

View File

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