git commit -m "feat: Implement online/offline status handling and enhance ticket creation"

adriano 2024-01-22 15:52:30 -03:00
parent 605e4035b9
commit 52619bfd26
9 changed files with 263 additions and 374 deletions

View File

@ -8,18 +8,19 @@ import ShowContactService from "../services/ContactServices/ShowContactService";
import UpdateContactService from "../services/ContactServices/UpdateContactService";
import DeleteContactService from "../services/ContactServices/DeleteContactService";
import CheckContactNumber from "../services/WbotServices/CheckNumber"
import CheckContactNumber from "../services/WbotServices/CheckNumber";
import CheckIsValidContact from "../services/WbotServices/CheckIsValidContact";
import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl";
import AppError from "../errors/AppError";
import { searchContactCache, insertContactsCache, escapeCharCache } from '../helpers/ContactsCache'
import {
searchContactCache,
insertContactsCache,
escapeCharCache
} from "../helpers/ContactsCache";
import { off } from "process";
type IndexQuery = {
searchParam: string;
pageNumber: string;
@ -39,42 +40,46 @@ interface ContactData {
export const index = async (req: Request, res: Response): Promise<Response> => {
let { searchParam, pageNumber } = req.query as IndexQuery;
console.log('PAGE NUMBER CONTACT: ', pageNumber)
console.log("PAGE NUMBER CONTACT: ", pageNumber);
if (pageNumber === undefined || pageNumber.trim().length == 0) {
pageNumber = '1'
pageNumber = "1";
}
// TEST DEL
if (searchParam && searchParam.trim().length > 0 && process.env.CACHE) {
try {
const offset = 20 * (+pageNumber - 1);
searchParam = searchParam.replace(/\s+/g, ' ').trim().toLowerCase();
searchParam = searchParam.replace(/\s+/g, " ").trim().toLowerCase();
const data = await searchContactCache(searchParam, offset, 20)
const data = await searchContactCache(searchParam, offset, 20);
if (data) {
console.log("QUERY CONTACTS FROM CACHE SEARCH PARAM: ", searchParam);
console.log('QUERY CONTACTS FROM CACHE SEARCH PARAM: ', searchParam)
console.log("QUERY CONTACTS FROM CACHE QUERY LENGTH: ", data.length);
console.log('QUERY CONTACTS FROM CACHE QUERY LENGTH: ', data.length)
return res.json({ contacts: data, count: data.length, hasMore: data.length > 0 ? true : false });
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)
console.log(
"There was an error on search ContactController.ts search cache: ",
error
);
}
}
console.log("QUERY CONTACTS FROM DATABASE SEARCH PARAM: ", searchParam);
}
console.log('QUERY CONTACTS FROM DATABASE SEARCH PARAM: ', searchParam)
const { contacts, count, hasMore } = await ListContactsService({ searchParam, pageNumber });
const { contacts, count, hasMore } = await ListContactsService({
searchParam,
pageNumber
});
return res.json({ contacts, count, hasMore });
};
@ -83,6 +88,8 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
const newContact: ContactData = req.body;
newContact.number = newContact.number.replace("-", "").replace(" ", "");
throw new AppError("ERR_NO_ADD_CONTACT");
const schema = Yup.object().shape({
name: Yup.string().required(),
number: Yup.string()
@ -109,25 +116,24 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
const profilePicUrl = await GetProfilePicUrl(validNumber);
console.log('xxxxxxxxxxx profilePicUrl: ', profilePicUrl)
console.log("xxxxxxxxxxx profilePicUrl: ", profilePicUrl);
// console.log(`newContact.name: ${newContact.name}\n
// newContact.number: ${newContact.number}\n
// newContact.email: ${newContact.email}\n
// newContact.extraInfo: ${newContact.extraInfo}`)
let name = newContact.name
let number = validNumber
let email = newContact.email
let extraInfo = newContact.extraInfo
let name = newContact.name;
let number = validNumber;
let email = newContact.email;
let extraInfo = newContact.extraInfo;
const contact = await CreateContactService({
name,
number,
email,
profilePicUrl: profilePicUrl,
extraInfo,
extraInfo
});
const io = getIO();
@ -155,8 +161,10 @@ export const update = async (
const schema = Yup.object().shape({
name: Yup.string(),
number: Yup.string()
.matches(/^\d+$/, "Invalid number format. Only numbers is allowed.")
number: Yup.string().matches(
/^\d+$/,
"Invalid number format. Only numbers is allowed."
)
// .matches(/^55\d+$/, "The number must start with 55.")
});
@ -200,13 +208,14 @@ export const remove = async (
return res.status(200).json({ message: "Contact deleted" });
};
export const contacsBulkInsertOnQueue = async (req: Request, res: Response): Promise<Response> => {
export const contacsBulkInsertOnQueue = async (
req: Request,
res: Response
): Promise<Response> => {
// console.log('THE BODY: ', req.body)
const { adminId, identifier, queueStatus, file, contacts_inserted } = req.body
const { adminId, identifier, queueStatus, file, contacts_inserted } =
req.body;
const io = getIO();
io.emit("contactsBulkInsertOnQueueStatus", {
@ -219,24 +228,19 @@ export const contacsBulkInsertOnQueue = async (req: Request, res: Response): Pro
}
});
if (process.env.CACHE && contacts_inserted) {
await insertContactsCache(contacts_inserted)
await insertContactsCache(contacts_inserted);
}
return res.status(200).json({ message: 'ok' })
return res.status(200).json({ message: "ok" });
};
function addStartPhoneNumber(phoneNumber: string) {
const regex = /^55/;
if (!regex.test(phoneNumber)) {
phoneNumber = '55' + phoneNumber;
phoneNumber = "55" + phoneNumber;
}
return phoneNumber
return phoneNumber;
}

View File

@ -5,6 +5,8 @@ import AuthUserService from "../services/UserServices/AuthUserService";
import { SendRefreshToken } from "../helpers/SendRefreshToken";
import { RefreshTokenService } from "../services/AuthServices/RefreshTokenService";
import createOrUpdateOnlineUserService from "../services/UserServices/CreateOrUpdateOnlineUserService";
export const store = async (req: Request, res: Response): Promise<Response> => {
const { email, password } = req.body;
@ -13,6 +15,13 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
password
});
console.log("serializedUser.id: ", serializedUser.id);
const userOnline = await createOrUpdateOnlineUserService({
userId: serializedUser.id,
status: "online"
});
SendRefreshToken(res, refreshToken);
return res.status(200).json({
@ -47,5 +56,11 @@ export const remove = async (
): Promise<Response> => {
res.clearCookie("jrt");
const { userId } = req.params;
const userOnline = await createOrUpdateOnlineUserService({
userId,
status: "offline"
});
return res.send();
};

View File

@ -47,12 +47,6 @@ export const initIO = (httpServer: Server): SocketIO => {
io.on("connection", socket => {
logger.info("Client Connected");
socket.on("joinWhatsSession", (whatsappId: string) => {
logger.info(`A client joined a joinWhatsSession channel: ${whatsappId}`);
socket.join(`session_${whatsappId}`);
@ -82,15 +76,6 @@ export const initIO = (httpServer: Server): SocketIO => {
});
socket.on("online", (userId: any) => {
// console.log('userId: ', userId)
@ -179,6 +164,8 @@ export const initIO = (httpServer: Server): SocketIO => {
e.try += 1
console.log("try.......: ", e.try);
if (e.try > 1) {
e.status = 'waiting...'
}

View File

@ -11,6 +11,6 @@ authRoutes.post("/login", SessionController.store);
authRoutes.post("/refresh_token", SessionController.update);
authRoutes.delete("/logout", isAuth, SessionController.remove);
authRoutes.delete("/logout/:userId", isAuth, SessionController.remove);
export default authRoutes;

View File

@ -1,26 +1,26 @@
import React, { useState, useEffect, useRef } from "react";
import React, { useState, useEffect, useRef } from "react"
import * as Yup from "yup";
import { Formik, FieldArray, Form, Field } from "formik";
import { toast } from "react-toastify";
import * as Yup from "yup"
import { Formik, FieldArray, Form, Field } from "formik"
import { toast } from "react-toastify"
import { makeStyles } from "@material-ui/core/styles";
import { green } from "@material-ui/core/colors";
import Button from "@material-ui/core/Button";
import TextField from "@material-ui/core/TextField";
import Dialog from "@material-ui/core/Dialog";
import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogTitle from "@material-ui/core/DialogTitle";
import Typography from "@material-ui/core/Typography";
import IconButton from "@material-ui/core/IconButton";
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
import CircularProgress from "@material-ui/core/CircularProgress";
import { makeStyles } from "@material-ui/core/styles"
import { green } from "@material-ui/core/colors"
import Button from "@material-ui/core/Button"
import TextField from "@material-ui/core/TextField"
import Dialog from "@material-ui/core/Dialog"
import DialogActions from "@material-ui/core/DialogActions"
import DialogContent from "@material-ui/core/DialogContent"
import DialogTitle from "@material-ui/core/DialogTitle"
import Typography from "@material-ui/core/Typography"
import IconButton from "@material-ui/core/IconButton"
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline"
import CircularProgress from "@material-ui/core/CircularProgress"
import { i18n } from "../../translate/i18n";
import { i18n } from "../../translate/i18n"
import api from "../../services/api";
import toastError from "../../errors/toastError";
import api from "../../services/api"
import toastError from "../../errors/toastError"
const useStyles = makeStyles(theme => ({
root: {
@ -50,7 +50,7 @@ const useStyles = makeStyles(theme => ({
marginTop: -12,
marginLeft: -12,
},
}));
}))
const ContactSchema = Yup.object().shape({
name: Yup.string()
@ -63,29 +63,29 @@ const ContactSchema = Yup.object().shape({
.max(50, "Too Long!"),
// email: Yup.string().email("Invalid email"),
});
})
const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => {
const classes = useStyles();
const isMounted = useRef(true);
const classes = useStyles()
const isMounted = useRef(true)
const initialState = {
name: "",
number: "",
email: "",
};
}
const [contact, setContact] = useState(initialState);
const [contact, setContact] = useState(initialState)
const [phone, setPhone] = useState('')
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
isMounted.current = false
}
}, [])
// useEffect(() => {
// console.log('1 Contact: ', contact)
@ -103,64 +103,66 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => {
const fetchContact = async () => {
if (initialValues) {
if (initialValues?.number)
setPhone(initialValues.number)
setContact(prevState => {
return { ...prevState, ...initialValues };
});
return { ...prevState, ...initialValues }
})
}
if (!contactId) return;
if (!contactId) return
try {
const { data } = await api.get(`/contacts/${contactId}`);
const { data } = await api.get(`/contacts/${contactId}`)
if (isMounted.current) {
setPhone(data.number)
setContact(data);
setContact(data)
}
} catch (err) {
toastError(err);
toastError(err)
}
}
};
fetchContact();
}, [contactId, open, initialValues]);
fetchContact()
}, [contactId, open, initialValues])
const handleClose = () => {
onClose();
setContact(initialState);
};
onClose()
console.log('INITIAL STATE: ', initialState)
setContact(initialState)
}
const handleSaveContact = async values => {
values = { ...values, number: phone };
console.log('submit values', values)
values = { ...values, number: phone }
try {
if (contactId) {
await api.put(`/contacts/${contactId}`, values);
handleClose();
await api.put(`/contacts/${contactId}`, values)
handleClose()
} else {
const { data } = await api.post("/contacts", values);
const { data } = await api.post("/contacts", values)
if (onSave) {
onSave(data);
onSave(data)
}
handleClose();
handleClose()
}
toast.success(i18n.t("contactModal.success"));
toast.success(i18n.t("contactModal.success"))
} catch (err) {
toastError(err);
toastError(err)
}
}
};
const handleChange = (event) => {
const regex = /^[0-9\b]+$/; // Regular expression to match only numbers
const regex = /^[0-9\b]+$/ // Regular expression to match only numbers
setPhone(event.target.value);
setPhone(event.target.value)
setTimeout(() => {
@ -174,9 +176,9 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => {
setPhone(newValue)
}, 100);
}, 100)
};
}
return (
<div className={classes.root}>
@ -192,12 +194,12 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => {
validationSchema={ContactSchema}
onSubmit={(values, actions) => {
setTimeout(() => {
handleSaveContact(values);
actions.setSubmitting(false);
}, 400);
handleSaveContact(values)
actions.setSubmitting(false)
}, 400)
}}
>
{({ values, errors, touched, isSubmitting }) => (
{({ values, errors, touched, isSubmitting, resetForm }) => (
<Form>
<DialogContent dividers>
<Typography variant="subtitle1" gutterBottom>
@ -298,7 +300,9 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => {
</DialogContent>
<DialogActions>
<Button
onClick={handleClose}
onClick={()=>{
handleClose()
}}
color="secondary"
disabled={isSubmitting}
variant="outlined"
@ -328,7 +332,7 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => {
</Formik>
</Dialog>
</div>
);
};
)
}
export default ContactModal;
export default ContactModal

View File

@ -1,256 +1,154 @@
import React, { useState, useEffect, useContext } from "react";
import { useHistory } from "react-router-dom";
import React, { useState, useEffect, useContext } from "react"
import { useHistory } from "react-router-dom"
import Button from "@material-ui/core/Button";
import TextField from "@material-ui/core/TextField";
import Dialog from "@material-ui/core/Dialog";
import Button from "@material-ui/core/Button"
import TextField from "@material-ui/core/TextField"
import Dialog from "@material-ui/core/Dialog"
import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogTitle from "@material-ui/core/DialogTitle";
// import Autocomplete, {
// createFilterOptions,
// } from "@material-ui/lab/Autocomplete";
// import CircularProgress from "@material-ui/core/CircularProgress";
import DialogActions from "@material-ui/core/DialogActions"
import DialogContent from "@material-ui/core/DialogContent"
import DialogTitle from "@material-ui/core/DialogTitle"
import Autocomplete, {
createFilterOptions,
} from "@material-ui/lab/Autocomplete"
import CircularProgress from "@material-ui/core/CircularProgress"
import { i18n } from "../../translate/i18n";
import api from "../../services/api";
import ButtonWithSpinner from "../ButtonWithSpinner";
import ContactModal from "../ContactModal";
import toastError from "../../errors/toastError";
import { AuthContext } from "../../context/Auth/AuthContext";
import { i18n } from "../../translate/i18n"
import api from "../../services/api"
import ButtonWithSpinner from "../ButtonWithSpinner"
import ContactModal from "../ContactModal"
import toastError from "../../errors/toastError"
import { AuthContext } from "../../context/Auth/AuthContext"
// const filter = createFilterOptions({
// trim: true,
// });
const filter = createFilterOptions({
trim: true,
})
const NewTicketModal = ({ modalOpen, onClose }) => {
const history = useHistory();
const history = useHistory()
// const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const [searchParam, setSearchParam] = useState("");
const [selectedContact, setSelectedContact] = useState(null);
// const [newContact, setNewContact] = useState({});
const [contactModalOpen, setContactModalOpen] = useState(false);
const { user } = useContext(AuthContext);
const [options, setOptions] = useState([])
const [loading, setLoading] = useState(false)
const [searchParam, setSearchParam] = useState("")
const [selectedContact, setSelectedContact] = useState(null)
const [newContact, setNewContact] = useState({})
const [contactModalOpen, setContactModalOpen] = useState(false)
const { user } = useContext(AuthContext)
useEffect(() => {
const regex = /^[0-9\b]+$/; // Regular expression to match only numbers
if (searchParam && !searchParam.match(regex)) return
if (searchParam && searchParam.length > 9) {
setSelectedContact({})
if (!modalOpen || searchParam.length < 3) {
setLoading(false)
return
}
else {
setLoading(true)
const delayDebounceFn = setTimeout(() => {
const fetchContacts = async () => {
try {
const { data } = await api.get("contacts", {
params: { searchParam },
})
setOptions(data.contacts)
setLoading(false)
} catch (err) {
setLoading(false)
toastError(err)
}
}
fetchContacts()
}, 500)
return () => clearTimeout(delayDebounceFn)
}, [searchParam, modalOpen])
const handleClose = () => {
onClose()
setSearchParam("")
setSelectedContact(null)
}
if (!modalOpen || searchParam.length < 5) {
setLoading(false);
return;
}
// setLoading(true);
// const delayDebounceFn = setTimeout(() => {
// const fetchContacts = async () => {
// try {
// const { data } = await api.get("contacts", {
// params: { searchParam },
// });
// // console.log('data.contacts: ', data.contacts)
// setOptions(data.contacts);
// setLoading(false);
// } catch (err) {
// setLoading(false);
// toastError(err);
// }
// };
// fetchContacts();
// }, 500);
// return () => clearTimeout(delayDebounceFn);
}, [searchParam, modalOpen]);
const handleClose = () => {
onClose();
setSearchParam("");
setSelectedContact(null);
};
const handleSaveTicket = async contactId => {
if (!contactId) return
setLoading(true)
try {
const { data: data0 } = await api.get("/contacts/", { params: { searchParam, pageNumber: "1" }, });
if (data0 && data0.contacts.length > 0) {
console.log('-----> data: ', data0.contacts[0].id)
contactId = data0.contacts[0].id
}
else {
console.log('NO CONTACT whith this searchParam: ', searchParam)
const values = {
name: searchParam,
number: searchParam,
};
const { data: data1 } = await api.post("/contacts", values);
console.log('data1: ', data1)
contactId = data1.id
}
const { data: ticket } = await api.post("/tickets", {
contactId: contactId,
userId: user.id,
status: "open",
});
window.location.reload();
history.push(`/tickets/${ticket.id}`);
window.location.reload();
setLoading(false);
handleClose();
})
history.push(`/tickets/${ticket.id}`)
} catch (err) {
setLoading(false);
toastError(err);
toastError(err)
}
// if (!contactId) return;
// setLoading(true);
// try {
// const { data: ticket } = await api.post("/tickets", {
// contactId: contactId,
// userId: user.id,
// status: "open",
// });
// window.location.reload();
// history.push(`/tickets/${ticket.id}`);
// window.location.reload();
// } catch (err) {
// toastError(err);
// }
};
const handleChange = (number) => {
const regex = /^[0-9\b]+$/; // Regular expression to match only numbers
setSearchParam(number)
setTimeout(() => {
let newValue = ''
for (const char of number) {
// console.log('char: ', char)
if (char.match(regex)) {
newValue += char
setLoading(false)
handleClose()
}
}
// console.log('newValue: ', newValue)
setSearchParam(newValue)
}, 30);
};
// const handleSelectOption = (e, newValue) => {
// if (newValue?.number) {
// console.log('newValue: ', newValue)
// setSelectedContact(newValue);
// setSelectedContact(newValue)
// } else if (newValue?.name) {
// // console.log('newValue?.name: ', newValue?.name)
// const regex = /^[0-9\b]+$/; // Regular expression to match only numbers
// if (newValue.name.match(regex)) {
// console.log('==========> newValue.name', newValue.name)
// setNewContact({ name: newValue.name, number: newValue.name });
// setNewContact({ name: newValue.name })
// setContactModalOpen(true)
// }
// else {
// setNewContact({ name: newValue.name });
// setContactModalOpen(true);
// }
const handleSelectOption = (e, newValue) => {
if (newValue?.number) {
setSelectedContact(newValue)
} else if (newValue?.name) {
// }
// };
if (/^-?\d+(\.\d+)?$/.test(newValue?.name)){
setNewContact({ number: newValue.name })
}
else{
setNewContact({ name: newValue.name })
}
setContactModalOpen(true)
}
}
const handleCloseContactModal = () => {
setContactModalOpen(false);
};
setContactModalOpen(false)
}
const handleAddNewContactTicket = contact => {
handleSaveTicket(contact.id);
};
handleSaveTicket(contact.id)
}
// const createAddContactOption = (filterOptions, params) => {
// const filtered = filter(filterOptions, params);
const createAddContactOption = (filterOptions, params) => {
const filtered = filter(filterOptions, params)
// if (params.inputValue !== "" && !loading && searchParam.length >= 3) {
// filtered.push({
// name: `${params.inputValue}`,
// });
// }
if (params.inputValue !== "" && !loading && searchParam.length >= 3) {
filtered.push({
name: `${params.inputValue}`,
})
}
// return filtered;
// };
return filtered
}
// const renderOption = option => {
// if (option.number) {
// return `${option.name} - ${option.number}`;
// } else {
// return `${i18n.t("newTicketModal.add")} ${option.name}`;
// }
// };
const renderOption = option => {
if (option.number) {
return `${option.name} - ${option.number}`
} else {
return `${i18n.t("newTicketModal.add")} ${option.name}`
}
}
// const renderOptionLabel = option => {
// if (option.number) {
// return `${option.name} - ${option.number}`;
// } else {
// return `${option.name}`;
// }
// };
const renderOptionLabel = option => {
if (option.number) {
return `${option.name} - ${option.number}`
} else {
return `${option.name}`
}
}
return (
<>
<ContactModal
open={contactModalOpen}
// initialValues={newContact}
initialValues={newContact}
onClose={handleCloseContactModal}
onSave={handleAddNewContactTicket}
></ContactModal>
@ -258,29 +156,7 @@ const NewTicketModal = ({ modalOpen, onClose }) => {
<DialogTitle id="form-dialog-title">
{i18n.t("newTicketModal.title")}
</DialogTitle>
<DialogContent dividers>
<TextField
label={i18n.t("newTicketModal.fieldLabel")}
variant="outlined"
autoFocus
value={searchParam}
onChange={e => handleChange(e.target.value)}
// onChange={e => setSearchParam(e.target.value)}
onKeyPress={e => {
if (loading || !selectedContact) return;
else if (e.key === "Enter") {
handleSaveTicket(selectedContact.id);
}
}}
/>
</DialogContent>
{/* <DialogContent dividers>
<Autocomplete
options={options}
loading={loading}
@ -301,9 +177,9 @@ const NewTicketModal = ({ modalOpen, onClose }) => {
autoFocus
onChange={e => setSearchParam(e.target.value)}
onKeyPress={e => {
if (loading || !selectedContact) return;
if (loading || !selectedContact) return
else if (e.key === "Enter") {
handleSaveTicket(selectedContact.id);
handleSaveTicket(selectedContact.id)
}
}}
InputProps={{
@ -320,7 +196,7 @@ const NewTicketModal = ({ modalOpen, onClose }) => {
/>
)}
/>
</DialogContent> */}
</DialogContent>
<DialogActions>
<Button
onClick={handleClose}
@ -343,7 +219,7 @@ const NewTicketModal = ({ modalOpen, onClose }) => {
</DialogActions>
</Dialog>
</>
);
};
)
}
export default NewTicketModal;
export default NewTicketModal

View File

@ -105,8 +105,10 @@ const useAuth = () => {
const handleLogout = async () => {
setLoading(true);
console.log('USER: ', user)
try {
await api.delete("/auth/logout");
await api.delete(`/auth/logout/${user.id}`);
setIsAuth(false);
setUser({});
localStorage.removeItem("token");

View File

@ -280,7 +280,7 @@ const messages = {
},
newTicketModal: {
title: "Criar Ticket",
fieldLabel: "Digite o numero para criar um ticket",
fieldLabel: "Digite o numero/nome para criar um ticket",
add: "Adicionar",
buttons: {
ok: "Salvar",
@ -491,6 +491,7 @@ const messages = {
"A criação do usuário foi desabilitada pelo administrador.",
ERR_NO_PERMISSION: "Você não tem permissão para acessar este recurso.",
ERR_DUPLICATED_CONTACT: "Já existe um contato com este número.",
ERR_NO_ADD_CONTACT: "Não é permitido adicionar um novo contado na agenda.",
ERR_NO_SETTING_FOUND: "Nenhuma configuração encontrada com este ID.",
ERR_NO_CONTACT_FOUND: "Nenhum contato encontrado com este ID.",
ERR_NO_TICKET_FOUND: "Nenhum tíquete encontrado com este ID.",