Alterações para atribuir chats iniciados pelo atendente a mesma fila que ele esta atribuido.

Alteração para remover caracteres especias ao inserir o numero do whatsapp.
Criação do nivel supervisor para gerenciar filas
adriano 2023-06-02 15:45:14 -03:00
parent 1246f59441
commit 53d3b2b09e
11 changed files with 233 additions and 31 deletions

View File

@ -3,7 +3,7 @@ const db = [
{
client_url: "http://localhost:8080",
db_conf: {
DB: "whaticket",
DB: "whaticket_recrutamento",
DB_HOST: "localhost",
DB_USER: "whaticket",
DB_PASS: "strongpassword",

View File

@ -15,6 +15,10 @@ import User from "../models/User";
import { startWhoIsOnlineMonitor, stopWhoIsOnlineMonitor } from "../helpers/WhoIsOnlineMonitor"
import UserOnlineTIme from '../models/UserOnlineTime'
import CountTicketsByUserQueue from "../services/UserServices/CountTicketsByUserQueue";
import { splitDateTime } from "../helpers/SplitDateTime";
import { format } from "date-fns";
import { ptBR } from "date-fns/locale";
type IndexQuery = {
searchParam: string;
@ -37,6 +41,9 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
for (var user of users) {
if (user.profile !== 'master') {
if (req.user.profile == 'supervisor' && (user.profile == 'admin')) continue
auxUsers.push(user)
}
}
@ -112,7 +119,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;
await stopWhoIsOnlineMonitor()
let onlineTime = {
@ -137,14 +144,49 @@ export const update = async (
req: Request,
res: Response
): Promise<Response> => {
if (req.user.profile !== "admin" && req.user.profile !== "master") {
if (req.user.profile !== "admin" && req.user.profile !== "master" && req.user.profile !== "supervisor") {
throw new AppError("ERR_NO_PERMISSION", 403);
}
const { userId } = req.params;
const userData = req.body;
const user = await UpdateUserService({ userData, userId });
const dateToday = splitDateTime(new Date(format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ptBR })))
const openByUserOnQueue: any[] = await CountTicketsByUserQueue({ startDate: dateToday.fullDate, endDate: dateToday.fullDate, status: 'open', clientChatStart: true, userId: userId })
// console.log('------> openByUserOnQueue: ', openByUserOnQueue)
// console.log()
// console.log('------> 1 userData.queueIds: ', userData.queueIds)
let userQueuesAttendance = []
if ((openByUserOnQueue && openByUserOnQueue.length) > 0) {
userQueuesAttendance = openByUserOnQueue.filter((e: any) => !userData.queueIds.includes(e.queueId))
if (userQueuesAttendance && userQueuesAttendance.length > 0) {
const queueInAttendance = userQueuesAttendance.map((e) => e.queueId)
const mergedSet = new Set([...userData.queueIds, ...queueInAttendance])
// Convert the Set back to an array
userData.queueIds = Array.from(mergedSet)
// console.log('------> 2 userData.queueIds: ', userData.queueIds)
}
}
// console.log('userQueuesAttendance: ', userQueuesAttendance)
// return res.status(200).json({});
let user: any = await UpdateUserService({ userData, userId });
const io = getIO();
io.emit("user", {
@ -152,6 +194,8 @@ export const update = async (
user
});
user.userQueuesAttendance = userQueuesAttendance
return res.status(200).json(user);
};

View File

@ -0,0 +1,26 @@
import Whatsapp from "../models/Whatsapp";
import ShowQueuesByUser from "../services/UserServices/ShowQueuesByUser";
import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService";
async function whatsappQueueMatchingUserQueue(userId: number, whatsapp: Whatsapp) {
const userQueues = await ShowQueuesByUser({ profile: 'user', userId: userId });
if (!userQueues || userQueues && userQueues.length == 0) return
console.log('-----> userQueues: ', userQueues);
let whats: any = await ShowWhatsAppService(whatsapp.id);
if (!whats.queues || whats.queues && whats.queues.length == 0) return
const whatsappQueues = whats.queues.map((e: any) => e.dataValues.name);
console.log('-----> whatsappQueues: ', whatsappQueues);
const matchingQueue = userQueues.find(queue => whatsappQueues.includes(queue.name));
return matchingQueue
}
export default whatsappQueueMatchingUserQueue

View File

@ -14,6 +14,10 @@ import { splitDateTime } from "../../helpers/SplitDateTime";
import TicketEmiterSumOpenClosedByUser from "../../helpers/OnlineReporEmiterInfoByUser";
import { createOrUpdateTicketCache } from '../../helpers/TicketCache'
import ShowQueuesByUser from "../UserServices/ShowQueuesByUser";
import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService";
import Whatsapp from "../../models/Whatsapp";
import whatsappQueueMatchingUserQueue from "../../helpers/whatsappQueueMatchingUserQueue";
let flatten = require('flat')
@ -32,8 +36,14 @@ const CreateTicketService = async ({
try {
console.log('Create contact service........')
const defaultWhatsapp = await GetDefaultWhatsApp(userId);
const matchingQueue = await whatsappQueueMatchingUserQueue(userId, defaultWhatsapp);
console.log('matchingQueue: ', matchingQueue)
const queueId = matchingQueue ? matchingQueue.queueId : undefined
await CheckContactOpenTickets(contactId);
const { isGroup } = await ShowContactService(contactId);
@ -42,9 +52,12 @@ const CreateTicketService = async ({
contactId,
status,
isGroup,
userId
userId,
queueId
});
console.log('TICKET CREATED!')
const ticket = await Ticket.findByPk(id, { include: ["contact"] });
if (!ticket) {
@ -54,7 +67,6 @@ const CreateTicketService = async ({
// console.log('CONTACT ticket.id: ', ticket.id)
// TEST DEL
try {
let jsonString = JSON.stringify(ticket); //convert to string to remove the sequelize specific meta data
@ -68,7 +80,6 @@ const CreateTicketService = async ({
} 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 })))
@ -92,3 +103,6 @@ const CreateTicketService = async ({
};
export default CreateTicketService;

View File

@ -927,7 +927,7 @@ const handleMsgAck = async (msg_id: any, ack: any) => {
}
await messageToUpdate.update({ ack });
console.log('ACK messageToUpdate: ', JSON.parse(JSON.stringify(messageToUpdate)))
// console.log('ACK messageToUpdate: ', JSON.parse(JSON.stringify(messageToUpdate)))
io.to(messageToUpdate.ticketId.toString()).emit("appMessage", {
action: "update",

View File

@ -60,11 +60,13 @@ const ContactSchema = Yup.object().shape({
number: Yup.string().min(8, "Too Short!").max(50, "Too Long!"),
email: Yup.string().min(2, "Too Short!")
.max(50, "Too Long!"),
.max(50, "Too Long!"),
// email: Yup.string().email("Invalid email"),
});
const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => {
const classes = useStyles();
const isMounted = useRef(true);
@ -76,6 +78,8 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => {
};
const [contact, setContact] = useState(initialState);
const [phone, setPhone] = useState('')
useEffect(() => {
return () => {
@ -83,9 +87,22 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => {
};
}, []);
// useEffect(() => {
// console.log('1 Contact: ', contact)
// setContact({
// ...contact,
// number: phone,
// });
// console.log('2 Contact: ', contact)
// }, [phone]);
useEffect(() => {
const fetchContact = async () => {
if (initialValues) {
setContact(prevState => {
return { ...prevState, ...initialValues };
});
@ -95,9 +112,14 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => {
try {
const { data } = await api.get(`/contacts/${contactId}`);
if (isMounted.current) {
setPhone(data.number)
setContact(data);
}
} catch (err) {
toastError(err);
}
@ -112,6 +134,11 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => {
};
const handleSaveContact = async values => {
values = { ...values, number: phone };
console.log('submit values', values)
try {
if (contactId) {
await api.put(`/contacts/${contactId}`, values);
@ -129,6 +156,28 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => {
}
};
const handleChange = (event) => {
const regex = /^[0-9\b]+$/; // Regular expression to match only numbers
setPhone(event.target.value);
setTimeout(() => {
let newValue = ''
for (const char of event.target.value) {
if (char.match(regex)) {
newValue += char
}
}
setPhone(newValue)
}, 100);
};
return (
<div className={classes.root}>
<Dialog open={open} onClose={handleClose} maxWidth="lg" scroll="paper">
@ -169,6 +218,10 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => {
as={TextField}
label={i18n.t("contactModal.form.number")}
name="number"
value={phone}
onChange={handleChange}
error={touched.number && Boolean(errors.number)}
helperText={touched.number && errors.number}
placeholder="5513912344321"

View File

@ -74,8 +74,12 @@ const NewTicketModal = ({ modalOpen, onClose }) => {
contactId: contactId,
userId: user.id,
status: "open",
});
});
window.location.reload();
history.push(`/tickets/${ticket.id}`);
window.location.reload();
} catch (err) {
toastError(err);
}

View File

@ -18,7 +18,7 @@ import {
TextField,
InputAdornment,
IconButton
} from '@material-ui/core';
} from '@material-ui/core';
import { Visibility, VisibilityOff } from '@material-ui/icons';
@ -71,9 +71,9 @@ const UserSchema = Yup.object().shape({
password: Yup.string().min(5, "Too Short!").max(50, "Too Long!"),
email: Yup.string().min(2, "Too Short!")
.max(50, "Too Long!")
.required("Required"),
.max(50, "Too Long!")
.required("Required"),
// email: Yup.string().email("Invalid email").required("Required"),
});
@ -98,12 +98,20 @@ const UserModal = ({ open, onClose, userId }) => {
const fetchUser = async () => {
if (!userId) return;
try {
// console.log('window.location.href: ',window.location.href)
const { data } = await api.get(`/users/${userId}`);
setUser(prevState => {
return { ...prevState, ...data };
});
const userQueueIds = data.queues?.map(queue => queue.id);
setSelectedQueueIds(userQueueIds);
// console.log('data: ', data)
// console.log('loggedInUser.email: ', loggedInUser.email)
} catch (err) {
toastError(err);
}
@ -121,7 +129,29 @@ const UserModal = ({ open, onClose, userId }) => {
const userData = { ...values, queueIds: selectedQueueIds };
try {
if (userId) {
await api.put(`/users/${userId}`, userData);
const user = await api.put(`/users/${userId}`, userData);
// console.log('USER: ', user.data)
if (user && user.data.userQueuesAttendance.length > 0) {
const userQueueInAttendance = user.data.userQueuesAttendance.map((e) => ({ "queue": e.queueName, "open": e.totAttendance }))
console.log('userQueueInAttendance: ', userQueueInAttendance)
let msg = '\nAVISO \n\nO atendente possui atendimento(s) em aberto na(s) fila(s) abaixo: \n\n'
userQueueInAttendance.forEach((e) => {
msg += `Fila: ${e.queue}\nAberto: ${e.open}\n\n`
})
msg += 'Para remover o atendente da(s) fila(s) acima é necessário que o mesmo encerre os atendimentos aberto(s) nessas filas.\n'
alert(msg)
}
} else {
await api.post("/users", userData);
}
@ -166,6 +196,7 @@ const UserModal = ({ open, onClose, userId }) => {
label={i18n.t("userModal.form.name")}
autoFocus
name="name"
disabled={loggedInUser.profile === 'admin' || loggedInUser.profile === 'master' ? false : true}
error={touched.name && Boolean(errors.name)}
helperText={touched.name && errors.name}
variant="outlined"
@ -175,6 +206,7 @@ const UserModal = ({ open, onClose, userId }) => {
<Field
as={TextField}
name="password"
disabled={loggedInUser.profile === 'admin' || loggedInUser.profile === 'master' ? false : true}
variant="outlined"
margin="dense"
label={i18n.t("userModal.form.password")}
@ -182,16 +214,16 @@ const UserModal = ({ open, onClose, userId }) => {
helperText={touched.password && errors.password}
type={showPassword ? 'text' : 'password'}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={() => setShowPassword((e) => !e)}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
)
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={() => setShowPassword((e) => !e)}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
)
}}
fullWidth
/>
@ -201,6 +233,7 @@ const UserModal = ({ open, onClose, userId }) => {
as={TextField}
label={i18n.t("userModal.form.email")}
name="email"
disabled={loggedInUser.profile === 'admin' || loggedInUser.profile === 'master' ? false : true}
error={touched.email && Boolean(errors.email)}
helperText={touched.email && errors.email}
variant="outlined"
@ -230,6 +263,7 @@ const UserModal = ({ open, onClose, userId }) => {
required
>
<MenuItem value="admin">Admin</MenuItem>
<MenuItem value="supervisor">Supervisor</MenuItem>
<MenuItem value="user">User</MenuItem>
</Field>
</>

View File

@ -94,9 +94,11 @@ const MainListItems = (props) => {
primary={i18n.t("mainDrawer.listItems.quickAnswers")}
icon={<QuestionAnswerOutlinedIcon />}
/>
<Can
role={user.profile}
perform="drawer-admin-items:view"
perform="menu-users:view"
yes={() => (
<>
<Divider />
@ -106,6 +108,23 @@ const MainListItems = (props) => {
primary={i18n.t("mainDrawer.listItems.users")}
icon={<PeopleAltOutlinedIcon />}
/>
</>
)}
/>
<Can
role={user.profile}
perform="drawer-admin-items:view"
yes={() => (
<>
{/* <Divider />
<ListSubheader inset>{i18n.t("mainDrawer.listItems.administration")}</ListSubheader> */}
{/* <ListItemLink
to="/users"
primary={i18n.t("mainDrawer.listItems.users")}
icon={<PeopleAltOutlinedIcon />}
/> */}
<ListItemLink
to="/queues"
primary={i18n.t("mainDrawer.listItems.queues")}

View File

@ -3,6 +3,14 @@ const rules = {
static: [],
},
supervisor: {
static: [
"menu-users:view",
"user-view:show",
"user-modal:editQueues"
]
},
admin: {
static: [
//"show-icon-edit-whatsapp",
@ -19,7 +27,8 @@ const rules = {
"queues-view:show",
"user-view:show",
"ticket-report:show",
"menu-users:view"
],
},
@ -31,8 +40,6 @@ const rules = {
"show-icon-edit-queue",
"show-icon-delete-queue",
"space-disk-info:show",
"drawer-admin-items:view",
"tickets-manager:showall",
"user-modal:editProfile",
@ -51,7 +58,8 @@ const rules = {
"btn-add-whatsapp",
"btn-remove-whatsapp",
"ticket-report:show",
"connection-button:show"
"connection-button:show",
"menu-users:view"
],
},
};

View File

@ -280,7 +280,7 @@ const messages = {
},
newTicketModal: {
title: "Criar Ticket",
fieldLabel: "Digite para pesquisar o contato",
fieldLabel: "Digite o nome para pesquisar o contato",
add: "Adicionar",
buttons: {
ok: "Salvar",