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
parent
1246f59441
commit
53d3b2b09e
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
@ -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")}
|
||||
|
|
|
@ -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"
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue