add quick answers

This commit is contained in:
ertprs
2021-09-05 22:30:57 -03:00
parent 85fe927385
commit 258b5db4a1
19 changed files with 2853 additions and 1789 deletions

View File

@@ -0,0 +1,117 @@
import * as Yup from "yup";
import { Request, Response } from "express";
import { getIO } from "../libs/socket";
import ListQuickAnswerService from "../services/QuickAnswerService/ListQuickAnswerService";
import CreateQuickAnswerService from "../services/QuickAnswerService/CreateQuickAnswerService";
import ShowQuickAnswerService from "../services/QuickAnswerService/ShowQuickAnswerService";
import UpdateQuickAnswerService from "../services/QuickAnswerService/UpdateQuickAnswerService";
import DeleteQuickAnswerService from "../services/QuickAnswerService/DeleteQuickAnswerService";
import AppError from "../errors/AppError";
type IndexQuery = {
searchParam: string;
pageNumber: string;
};
interface QuickAnswerData {
shortcut: string;
message: string;
}
export const index = async (req: Request, res: Response): Promise<Response> => {
const { searchParam, pageNumber } = req.query as IndexQuery;
const { quickAnswers, count, hasMore } = await ListQuickAnswerService({
searchParam,
pageNumber
});
return res.json({ quickAnswers, count, hasMore });
};
export const store = async (req: Request, res: Response): Promise<Response> => {
const newQuickAnswer: QuickAnswerData = req.body;
const QuickAnswerSchema = Yup.object().shape({
shortcut: Yup.string().required(),
message: Yup.string().required()
});
try {
await QuickAnswerSchema.validate(newQuickAnswer);
} catch (err) {
throw new AppError(err.message);
}
const quickAnswer = await CreateQuickAnswerService({
...newQuickAnswer
});
const io = getIO();
io.emit("quickAnswer", {
action: "create",
quickAnswer
});
return res.status(200).json(quickAnswer);
};
export const show = async (req: Request, res: Response): Promise<Response> => {
const { quickAnswerId } = req.params;
const quickAnswer = await ShowQuickAnswerService(quickAnswerId);
return res.status(200).json(quickAnswer);
};
export const update = async (
req: Request,
res: Response
): Promise<Response> => {
const quickAnswerData: QuickAnswerData = req.body;
const schema = Yup.object().shape({
shortcut: Yup.string(),
message: Yup.string()
});
try {
await schema.validate(quickAnswerData);
} catch (err) {
throw new AppError(err.message);
}
const { quickAnswerId } = req.params;
const quickAnswer = await UpdateQuickAnswerService({
quickAnswerData,
quickAnswerId
});
const io = getIO();
io.emit("quickAnswer", {
action: "update",
quickAnswer
});
return res.status(200).json(quickAnswer);
};
export const remove = async (
req: Request,
res: Response
): Promise<Response> => {
const { quickAnswerId } = req.params;
await DeleteQuickAnswerService(quickAnswerId);
const io = getIO();
io.emit("quickAnswer", {
action: "delete",
quickAnswerId
});
return res.status(200).json({ message: "Quick Answer deleted" });
};

View File

@@ -9,6 +9,7 @@ import Message from "../models/Message";
import Queue from "../models/Queue"; import Queue from "../models/Queue";
import WhatsappQueue from "../models/WhatsappQueue"; import WhatsappQueue from "../models/WhatsappQueue";
import UserQueue from "../models/UserQueue"; import UserQueue from "../models/UserQueue";
import QuickAnswer from "../models/QuickAnswer";
// eslint-disable-next-line // eslint-disable-next-line
const dbConfig = require("../config/database"); const dbConfig = require("../config/database");
@@ -26,7 +27,8 @@ const models = [
Setting, Setting,
Queue, Queue,
WhatsappQueue, WhatsappQueue,
UserQueue UserQueue,
QuickAnswer
]; ];
sequelize.addModels(models); sequelize.addModels(models);

View File

@@ -0,0 +1,34 @@
import { QueryInterface, DataTypes } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.createTable("quickAnswers", {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false
},
shortcut: {
type: DataTypes.TEXT,
allowNull: false
},
message: {
type: DataTypes.TEXT,
allowNull: false
},
createdAt: {
type: DataTypes.DATE,
allowNull: false
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false
}
});
},
down: (queryInterface: QueryInterface) => {
return queryInterface.dropTable("quickAnswers");
}
};

View File

@@ -0,0 +1,32 @@
import {
Table,
Column,
DataType,
CreatedAt,
UpdatedAt,
Model,
PrimaryKey,
AutoIncrement
} from "sequelize-typescript";
@Table
class QuickAnswer extends Model<QuickAnswer> {
@PrimaryKey
@AutoIncrement
@Column
id: number;
@Column(DataType.TEXT)
shortcut: string;
@Column(DataType.TEXT)
message: string;
@CreatedAt
createdAt: Date;
@UpdatedAt
updatedAt: Date;
}
export default QuickAnswer;

View File

@@ -9,6 +9,7 @@ import whatsappRoutes from "./whatsappRoutes";
import messageRoutes from "./messageRoutes"; import messageRoutes from "./messageRoutes";
import whatsappSessionRoutes from "./whatsappSessionRoutes"; import whatsappSessionRoutes from "./whatsappSessionRoutes";
import queueRoutes from "./queueRoutes"; import queueRoutes from "./queueRoutes";
import quickAnswerRoutes from "./quickAnswerRoutes";
const routes = Router(); const routes = Router();
@@ -22,5 +23,6 @@ routes.use(messageRoutes);
routes.use(messageRoutes); routes.use(messageRoutes);
routes.use(whatsappSessionRoutes); routes.use(whatsappSessionRoutes);
routes.use(queueRoutes); routes.use(queueRoutes);
routes.use(quickAnswerRoutes);
export default routes; export default routes;

View File

@@ -0,0 +1,30 @@
import express from "express";
import isAuth from "../middleware/isAuth";
import * as QuickAnswerController from "../controllers/QuickAnswerController";
const quickAnswerRoutes = express.Router();
quickAnswerRoutes.get("/quickAnswers", isAuth, QuickAnswerController.index);
quickAnswerRoutes.get(
"/quickAnswers/:quickAnswerId",
isAuth,
QuickAnswerController.show
);
quickAnswerRoutes.post("/quickAnswers", isAuth, QuickAnswerController.store);
quickAnswerRoutes.put(
"/quickAnswers/:quickAnswerId",
isAuth,
QuickAnswerController.update
);
quickAnswerRoutes.delete(
"/quickAnswers/:quickAnswerId",
isAuth,
QuickAnswerController.remove
);
export default quickAnswerRoutes;

View File

@@ -0,0 +1,26 @@
import AppError from "../../errors/AppError";
import QuickAnswer from "../../models/QuickAnswer";
interface Request {
shortcut: string;
message: string;
}
const CreateQuickAnswerService = async ({
shortcut,
message
}: Request): Promise<QuickAnswer> => {
const nameExists = await QuickAnswer.findOne({
where: { shortcut }
});
if (nameExists) {
throw new AppError("ERR__SHORTCUT_DUPLICATED");
}
const quickAnswer = await QuickAnswer.create({ shortcut, message });
return quickAnswer;
};
export default CreateQuickAnswerService;

View File

@@ -0,0 +1,16 @@
import QuickAnswer from "../../models/QuickAnswer";
import AppError from "../../errors/AppError";
const DeleteQuickAnswerService = async (id: string): Promise<void> => {
const quickAnswer = await QuickAnswer.findOne({
where: { id }
});
if (!quickAnswer) {
throw new AppError("ERR_NO_QUICK_ANSWER_FOUND", 404);
}
await quickAnswer.destroy();
};
export default DeleteQuickAnswerService;

View File

@@ -0,0 +1,45 @@
import { Sequelize } from "sequelize";
import QuickAnswer from "../../models/QuickAnswer";
interface Request {
searchParam?: string;
pageNumber?: string;
}
interface Response {
quickAnswers: QuickAnswer[];
count: number;
hasMore: boolean;
}
const ListQuickAnswerService = async ({
searchParam = "",
pageNumber = "1"
}: Request): Promise<Response> => {
const whereCondition = {
message: Sequelize.where(
Sequelize.fn("LOWER", Sequelize.col("message")),
"LIKE",
`%${searchParam.toLowerCase().trim()}%`
)
};
const limit = 20;
const offset = limit * (+pageNumber - 1);
const { count, rows: quickAnswers } = await QuickAnswer.findAndCountAll({
where: whereCondition,
limit,
offset,
order: [["message", "ASC"]]
});
const hasMore = count > offset + quickAnswers.length;
return {
quickAnswers,
count,
hasMore
};
};
export default ListQuickAnswerService;

View File

@@ -0,0 +1,14 @@
import QuickAnswer from "../../models/QuickAnswer";
import AppError from "../../errors/AppError";
const ShowQuickAnswerService = async (id: string): Promise<QuickAnswer> => {
const quickAnswer = await QuickAnswer.findByPk(id);
if (!quickAnswer) {
throw new AppError("ERR_NO_QUICK_ANSWERS_FOUND", 404);
}
return quickAnswer;
};
export default ShowQuickAnswerService;

View File

@@ -0,0 +1,40 @@
import QuickAnswer from "../../models/QuickAnswer";
import AppError from "../../errors/AppError";
interface QuickAnswerData {
shortcut?: string;
message?: string;
}
interface Request {
quickAnswerData: QuickAnswerData;
quickAnswerId: string;
}
const UpdateQuickAnswerService = async ({
quickAnswerData,
quickAnswerId
}: Request): Promise<QuickAnswer> => {
const { shortcut, message } = quickAnswerData;
const quickAnswer = await QuickAnswer.findOne({
where: { id: quickAnswerId },
attributes: ["id", "shortcut", "message"]
});
if (!quickAnswer) {
throw new AppError("ERR_NO_QUICK_ANSWERS_FOUND", 404);
}
await quickAnswer.update({
shortcut,
message
});
await quickAnswer.reload({
attributes: ["id", "shortcut", "message"]
});
return quickAnswer;
};
export default UpdateQuickAnswerService;

View File

@@ -20,6 +20,7 @@ import MicIcon from "@material-ui/icons/Mic";
import CheckCircleOutlineIcon from "@material-ui/icons/CheckCircleOutline"; import CheckCircleOutlineIcon from "@material-ui/icons/CheckCircleOutline";
import HighlightOffIcon from "@material-ui/icons/HighlightOff"; import HighlightOffIcon from "@material-ui/icons/HighlightOff";
import { FormControlLabel, Switch } from "@material-ui/core"; import { FormControlLabel, Switch } from "@material-ui/core";
import ClickAwayListener from "@material-ui/core/ClickAwayListener";
import { i18n } from "../../translate/i18n"; import { i18n } from "../../translate/i18n";
import api from "../../services/api"; import api from "../../services/api";
@@ -31,483 +32,555 @@ import toastError from "../../errors/toastError";
const Mp3Recorder = new MicRecorder({ bitRate: 128 }); const Mp3Recorder = new MicRecorder({ bitRate: 128 });
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles((theme) => ({
mainWrapper: { mainWrapper: {
background: "#eee", background: "#eee",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
alignItems: "center", alignItems: "center",
borderTop: "1px solid rgba(0, 0, 0, 0.12)", borderTop: "1px solid rgba(0, 0, 0, 0.12)",
}, },
newMessageBox: { newMessageBox: {
background: "#eee", background: "#eee",
width: "100%", width: "100%",
display: "flex", display: "flex",
padding: "7px", padding: "7px",
alignItems: "center", alignItems: "center",
}, },
messageInputWrapper: { messageInputWrapper: {
padding: 6, padding: 6,
marginRight: 7, marginRight: 7,
background: "#fff", background: "#fff",
display: "flex", display: "flex",
borderRadius: 20, borderRadius: 20,
flex: 1, flex: 1,
}, },
messageInput: { messageInput: {
paddingLeft: 10, paddingLeft: 10,
flex: 1, flex: 1,
border: "none", border: "none",
}, },
sendMessageIcons: { sendMessageIcons: {
color: "grey", color: "grey",
}, },
uploadInput: { uploadInput: {
display: "none", display: "none",
}, },
viewMediaInputWrapper: { viewMediaInputWrapper: {
display: "flex", display: "flex",
padding: "10px 13px", padding: "10px 13px",
position: "relative", position: "relative",
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "center", alignItems: "center",
backgroundColor: "#eee", backgroundColor: "#eee",
borderTop: "1px solid rgba(0, 0, 0, 0.12)", borderTop: "1px solid rgba(0, 0, 0, 0.12)",
}, },
emojiBox: { emojiBox: {
position: "absolute", position: "absolute",
bottom: 63, bottom: 63,
width: 40, width: 40,
borderTop: "1px solid #e8e8e8", borderTop: "1px solid #e8e8e8",
}, },
circleLoading: { circleLoading: {
color: green[500], color: green[500],
opacity: "70%", opacity: "70%",
position: "absolute", position: "absolute",
top: "20%", top: "20%",
left: "50%", left: "50%",
marginLeft: -12, marginLeft: -12,
}, },
audioLoading: { audioLoading: {
color: green[500], color: green[500],
opacity: "70%", opacity: "70%",
}, },
recorderWrapper: { recorderWrapper: {
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
alignContent: "middle", alignContent: "middle",
}, },
cancelAudioIcon: { cancelAudioIcon: {
color: "red", color: "red",
}, },
sendAudioIcon: { sendAudioIcon: {
color: "green", color: "green",
}, },
replyginMsgWrapper: { replyginMsgWrapper: {
display: "flex", display: "flex",
width: "100%", width: "100%",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
paddingTop: 8, paddingTop: 8,
paddingLeft: 73, paddingLeft: 73,
paddingRight: 7, paddingRight: 7,
}, },
replyginMsgContainer: { replyginMsgContainer: {
flex: 1, flex: 1,
marginRight: 5, marginRight: 5,
overflowY: "hidden", overflowY: "hidden",
backgroundColor: "rgba(0, 0, 0, 0.05)", backgroundColor: "rgba(0, 0, 0, 0.05)",
borderRadius: "7.5px", borderRadius: "7.5px",
display: "flex", display: "flex",
position: "relative", position: "relative",
}, },
replyginMsgBody: { replyginMsgBody: {
padding: 10, padding: 10,
height: "auto", height: "auto",
display: "block", display: "block",
whiteSpace: "pre-wrap", whiteSpace: "pre-wrap",
overflow: "hidden", overflow: "hidden",
}, },
replyginContactMsgSideColor: { replyginContactMsgSideColor: {
flex: "none", flex: "none",
width: "4px", width: "4px",
backgroundColor: "#35cd96", backgroundColor: "#35cd96",
}, },
replyginSelfMsgSideColor: { replyginSelfMsgSideColor: {
flex: "none", flex: "none",
width: "4px", width: "4px",
backgroundColor: "#6bcbef", backgroundColor: "#6bcbef",
}, },
messageContactName: { messageContactName: {
display: "flex", display: "flex",
color: "#6bcbef", color: "#6bcbef",
fontWeight: 500, fontWeight: 500,
}, },
messageQuickAnswersWrapper: {
margin: 0,
position: "absolute",
bottom: "50px",
background: "#ffffff",
padding: "2px",
border: "1px solid #CCC",
left: 0,
width: "100%",
"& li": {
listStyle: "none",
"& a": {
display: "block",
padding: "8px",
textOverflow: "ellipsis",
overflow: "hidden",
maxHeight: "32px",
"&:hover": {
background: "#F1F1F1",
cursor: "pointer",
},
},
},
},
})); }));
const MessageInput = ({ ticketStatus }) => { const MessageInput = ({ ticketStatus }) => {
const classes = useStyles(); const classes = useStyles();
const { ticketId } = useParams(); const { ticketId } = useParams();
const [medias, setMedias] = useState([]); const [medias, setMedias] = useState([]);
const [inputMessage, setInputMessage] = useState(""); const [inputMessage, setInputMessage] = useState("");
const [showEmoji, setShowEmoji] = useState(false); const [showEmoji, setShowEmoji] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [recording, setRecording] = useState(false); const [recording, setRecording] = useState(false);
const inputRef = useRef(); const [quickAnswers, setQuickAnswer] = useState([]);
const { setReplyingMessage, replyingMessage } = useContext( const [typeBar, setTypeBar] = useState(false);
ReplyMessageContext const inputRef = useRef();
); const { setReplyingMessage, replyingMessage } =
const { user } = useContext(AuthContext); useContext(ReplyMessageContext);
const { user } = useContext(AuthContext);
const [signMessage, setSignMessage] = useLocalStorage("signOption", true); const [signMessage, setSignMessage] = useLocalStorage("signOption", true);
useEffect(() => { useEffect(() => {
inputRef.current.focus(); inputRef.current.focus();
}, [replyingMessage]); }, [replyingMessage]);
useEffect(() => { useEffect(() => {
inputRef.current.focus(); inputRef.current.focus();
return () => { return () => {
setInputMessage(""); setInputMessage("");
setShowEmoji(false); setShowEmoji(false);
setMedias([]); setMedias([]);
setReplyingMessage(null); setReplyingMessage(null);
}; };
}, [ticketId, setReplyingMessage]); }, [ticketId, setReplyingMessage]);
const handleChangeInput = e => { const handleChangeInput = (e) => {
setInputMessage(e.target.value); setInputMessage(e.target.value);
}; handleLoadQuickAnswer(e.target.value);
};
const handleAddEmoji = e => { const handleQuickAnswersClick = (value) => {
let emoji = e.native; setInputMessage(value);
setInputMessage(prevState => prevState + emoji); setTypeBar(false);
}; };
const handleChangeMedias = e => { const handleAddEmoji = (e) => {
if (!e.target.files) { let emoji = e.native;
return; setInputMessage((prevState) => prevState + emoji);
} };
const selectedMedias = Array.from(e.target.files); const handleChangeMedias = (e) => {
setMedias(selectedMedias); if (!e.target.files) {
}; return;
}
const handleInputPaste = e => { const selectedMedias = Array.from(e.target.files);
if (e.clipboardData.files[0]) { setMedias(selectedMedias);
setMedias([e.clipboardData.files[0]]); };
}
};
const handleUploadMedia = async e => { const handleInputPaste = (e) => {
setLoading(true); if (e.clipboardData.files[0]) {
e.preventDefault(); setMedias([e.clipboardData.files[0]]);
}
};
const formData = new FormData(); const handleUploadMedia = async (e) => {
formData.append("fromMe", true); setLoading(true);
medias.forEach(media => { e.preventDefault();
formData.append("medias", media);
formData.append("body", media.name);
});
try { const formData = new FormData();
await api.post(`/messages/${ticketId}`, formData); formData.append("fromMe", true);
} catch (err) { medias.forEach((media) => {
toastError(err); formData.append("medias", media);
} formData.append("body", media.name);
});
setLoading(false); try {
setMedias([]); await api.post(`/messages/${ticketId}`, formData);
}; } catch (err) {
toastError(err);
}
const handleSendMessage = async () => { setLoading(false);
if (inputMessage.trim() === "") return; setMedias([]);
setLoading(true); };
const message = { const handleSendMessage = async () => {
read: 1, if (inputMessage.trim() === "") return;
fromMe: true, setLoading(true);
mediaUrl: "",
body: signMessage
? `*${user?.name}:*\n${inputMessage.trim()}`
: inputMessage.trim(),
quotedMsg: replyingMessage,
};
try {
await api.post(`/messages/${ticketId}`, message);
} catch (err) {
toastError(err);
}
setInputMessage(""); const message = {
setShowEmoji(false); read: 1,
setLoading(false); fromMe: true,
setReplyingMessage(null); mediaUrl: "",
}; body: signMessage
? `*${user?.name}:*\n${inputMessage.trim()}`
: inputMessage.trim(),
quotedMsg: replyingMessage,
};
try {
await api.post(`/messages/${ticketId}`, message);
} catch (err) {
toastError(err);
}
const handleStartRecording = async () => { setInputMessage("");
setLoading(true); setShowEmoji(false);
try { setLoading(false);
await navigator.mediaDevices.getUserMedia({ audio: true }); setReplyingMessage(null);
await Mp3Recorder.start(); };
setRecording(true);
setLoading(false);
} catch (err) {
toastError(err);
setLoading(false);
}
};
const handleUploadAudio = async () => { const handleStartRecording = async () => {
setLoading(true); setLoading(true);
try { try {
const [, blob] = await Mp3Recorder.stop().getMp3(); await navigator.mediaDevices.getUserMedia({ audio: true });
if (blob.size < 10000) { await Mp3Recorder.start();
setLoading(false); setRecording(true);
setRecording(false); setLoading(false);
return; } catch (err) {
} toastError(err);
setLoading(false);
}
};
const formData = new FormData(); const handleLoadQuickAnswer = async (value) => {
const filename = `${new Date().getTime()}.mp3`; if (value && value.indexOf("/") === 0) {
formData.append("medias", blob, filename); try {
formData.append("body", filename); const { data } = await api.get("/quickAnswers/", {
formData.append("fromMe", true); params: { searchParam: inputMessage.substring(1) },
});
setQuickAnswer(data.quickAnswers);
if (data.quickAnswers.length > 0) {
setTypeBar(true);
} else {
setTypeBar(false);
}
} catch (err) {
setTypeBar(false);
}
} else {
setTypeBar(false);
}
};
await api.post(`/messages/${ticketId}`, formData); const handleUploadAudio = async () => {
} catch (err) { setLoading(true);
toastError(err); try {
} const [, blob] = await Mp3Recorder.stop().getMp3();
if (blob.size < 10000) {
setLoading(false);
setRecording(false);
return;
}
setRecording(false); const formData = new FormData();
setLoading(false); const filename = `${new Date().getTime()}.mp3`;
}; formData.append("medias", blob, filename);
formData.append("body", filename);
formData.append("fromMe", true);
const handleCancelAudio = async () => { await api.post(`/messages/${ticketId}`, formData);
try { } catch (err) {
await Mp3Recorder.stop().getMp3(); toastError(err);
setRecording(false); }
} catch (err) {
toastError(err);
}
};
const renderReplyingMessage = message => { setRecording(false);
return ( setLoading(false);
<div className={classes.replyginMsgWrapper}> };
<div className={classes.replyginMsgContainer}>
<span
className={clsx(classes.replyginContactMsgSideColor, {
[classes.replyginSelfMsgSideColor]: !message.fromMe,
})}
></span>
<div className={classes.replyginMsgBody}>
{!message.fromMe && (
<span className={classes.messageContactName}>
{message.contact?.name}
</span>
)}
{message.body}
</div>
</div>
<IconButton
aria-label="showRecorder"
component="span"
disabled={loading || ticketStatus !== "open"}
onClick={() => setReplyingMessage(null)}
>
<ClearIcon className={classes.sendMessageIcons} />
</IconButton>
</div>
);
};
if (medias.length > 0) const handleCancelAudio = async () => {
return ( try {
<Paper elevation={0} square className={classes.viewMediaInputWrapper}> await Mp3Recorder.stop().getMp3();
<IconButton setRecording(false);
aria-label="cancel-upload" } catch (err) {
component="span" toastError(err);
onClick={e => setMedias([])} }
> };
<CancelIcon className={classes.sendMessageIcons} />
</IconButton>
{loading ? ( const renderReplyingMessage = (message) => {
<div> return (
<CircularProgress className={classes.circleLoading} /> <div className={classes.replyginMsgWrapper}>
</div> <div className={classes.replyginMsgContainer}>
) : ( <span
<span> className={clsx(classes.replyginContactMsgSideColor, {
{medias[0]?.name} [classes.replyginSelfMsgSideColor]: !message.fromMe,
{/* <img src={media.preview} alt=""></img> */} })}
</span> ></span>
)} <div className={classes.replyginMsgBody}>
<IconButton {!message.fromMe && (
aria-label="send-upload" <span className={classes.messageContactName}>
component="span" {message.contact?.name}
onClick={handleUploadMedia} </span>
disabled={loading} )}
> {message.body}
<SendIcon className={classes.sendMessageIcons} /> </div>
</IconButton> </div>
</Paper> <IconButton
); aria-label="showRecorder"
else { component="span"
return ( disabled={loading || ticketStatus !== "open"}
<Paper square elevation={0} className={classes.mainWrapper}> onClick={() => setReplyingMessage(null)}
{replyingMessage && renderReplyingMessage(replyingMessage)} >
<div className={classes.newMessageBox}> <ClearIcon className={classes.sendMessageIcons} />
<IconButton </IconButton>
aria-label="emojiPicker" </div>
component="span" );
disabled={loading || recording || ticketStatus !== "open"} };
onClick={e => setShowEmoji(prevState => !prevState)}
>
<MoodIcon className={classes.sendMessageIcons} />
</IconButton>
{showEmoji ? (
<div className={classes.emojiBox}>
<Picker
perLine={16}
showPreview={false}
showSkinTones={false}
onSelect={handleAddEmoji}
/>
</div>
) : null}
<input if (medias.length > 0)
multiple return (
type="file" <Paper elevation={0} square className={classes.viewMediaInputWrapper}>
id="upload-button" <IconButton
disabled={loading || recording || ticketStatus !== "open"} aria-label="cancel-upload"
className={classes.uploadInput} component="span"
onChange={handleChangeMedias} onClick={(e) => setMedias([])}
/> >
<label htmlFor="upload-button"> <CancelIcon className={classes.sendMessageIcons} />
<IconButton </IconButton>
aria-label="upload"
component="span"
disabled={loading || recording || ticketStatus !== "open"}
>
<AttachFileIcon className={classes.sendMessageIcons} />
</IconButton>
</label>
<FormControlLabel
style={{ marginRight: 7, color: "gray" }}
label={i18n.t("messagesInput.signMessage")}
labelPlacement="start"
control={
<Switch
size="small"
checked={signMessage}
onChange={e => {
setSignMessage(e.target.checked);
}}
name="showAllTickets"
color="primary"
/>
}
/>
<div className={classes.messageInputWrapper}>
<InputBase
inputRef={input => {
input && input.focus();
input && (inputRef.current = input);
}}
className={classes.messageInput}
placeholder={
ticketStatus === "open"
? i18n.t("messagesInput.placeholderOpen")
: i18n.t("messagesInput.placeholderClosed")
}
multiline
rowsMax={5}
value={inputMessage}
onChange={handleChangeInput}
disabled={recording || loading || ticketStatus !== "open"}
onPaste={e => {
ticketStatus === "open" && handleInputPaste(e);
}}
onKeyPress={e => {
if (loading || e.shiftKey) return;
else if (e.key === "Enter") {
handleSendMessage();
}
}}
/>
</div>
{inputMessage ? (
<IconButton
aria-label="sendMessage"
component="span"
onClick={handleSendMessage}
disabled={loading}
>
<SendIcon className={classes.sendMessageIcons} />
</IconButton>
) : recording ? (
<div className={classes.recorderWrapper}>
<IconButton
aria-label="cancelRecording"
component="span"
fontSize="large"
disabled={loading}
onClick={handleCancelAudio}
>
<HighlightOffIcon className={classes.cancelAudioIcon} />
</IconButton>
{loading ? (
<div>
<CircularProgress className={classes.audioLoading} />
</div>
) : (
<RecordingTimer />
)}
<IconButton {loading ? (
aria-label="sendRecordedAudio" <div>
component="span" <CircularProgress className={classes.circleLoading} />
onClick={handleUploadAudio} </div>
disabled={loading} ) : (
> <span>
<CheckCircleOutlineIcon className={classes.sendAudioIcon} /> {medias[0]?.name}
</IconButton> {/* <img src={media.preview} alt=""></img> */}
</div> </span>
) : ( )}
<IconButton <IconButton
aria-label="showRecorder" aria-label="send-upload"
component="span" component="span"
disabled={loading || ticketStatus !== "open"} onClick={handleUploadMedia}
onClick={handleStartRecording} disabled={loading}
> >
<MicIcon className={classes.sendMessageIcons} /> <SendIcon className={classes.sendMessageIcons} />
</IconButton> </IconButton>
)} </Paper>
</div> );
</Paper> else {
); return (
} <Paper square elevation={0} className={classes.mainWrapper}>
{replyingMessage && renderReplyingMessage(replyingMessage)}
<div className={classes.newMessageBox}>
<IconButton
aria-label="emojiPicker"
component="span"
disabled={loading || recording || ticketStatus !== "open"}
onClick={(e) => setShowEmoji((prevState) => !prevState)}
>
<MoodIcon className={classes.sendMessageIcons} />
</IconButton>
{showEmoji ? (
<div className={classes.emojiBox}>
<ClickAwayListener onClickAway={(e) => setShowEmoji(false)}>
<Picker
perLine={16}
showPreview={false}
showSkinTones={false}
onSelect={handleAddEmoji}
/>
</ClickAwayListener>
</div>
) : null}
<input
multiple
type="file"
id="upload-button"
disabled={loading || recording || ticketStatus !== "open"}
className={classes.uploadInput}
onChange={handleChangeMedias}
/>
<label htmlFor="upload-button">
<IconButton
aria-label="upload"
component="span"
disabled={loading || recording || ticketStatus !== "open"}
>
<AttachFileIcon className={classes.sendMessageIcons} />
</IconButton>
</label>
<FormControlLabel
style={{ marginRight: 7, color: "gray" }}
label={i18n.t("messagesInput.signMessage")}
labelPlacement="start"
control={
<Switch
size="small"
checked={signMessage}
onChange={(e) => {
setSignMessage(e.target.checked);
}}
name="showAllTickets"
color="primary"
/>
}
/>
<div className={classes.messageInputWrapper}>
<InputBase
inputRef={(input) => {
input && input.focus();
input && (inputRef.current = input);
}}
className={classes.messageInput}
placeholder={
ticketStatus === "open"
? i18n.t("messagesInput.placeholderOpen")
: i18n.t("messagesInput.placeholderClosed")
}
multiline
rowsMax={5}
value={inputMessage}
onChange={handleChangeInput}
disabled={recording || loading || ticketStatus !== "open"}
onPaste={(e) => {
ticketStatus === "open" && handleInputPaste(e);
}}
onKeyPress={(e) => {
if (loading || e.shiftKey) return;
else if (e.key === "Enter") {
handleSendMessage();
}
}}
/>
{typeBar ? (
<ul className={classes.messageQuickAnswersWrapper}>
{quickAnswers.map((value, index) => {
return (
<li
className={classes.messageQuickAnswersWrapperItem}
key={index}
>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a onClick={() => handleQuickAnswersClick(value.message)}>
{`${value.shortcut} - ${value.message}`}
</a>
</li>
);
})}
</ul>
) : (
<div></div>
)}
</div>
{inputMessage ? (
<IconButton
aria-label="sendMessage"
component="span"
onClick={handleSendMessage}
disabled={loading}
>
<SendIcon className={classes.sendMessageIcons} />
</IconButton>
) : recording ? (
<div className={classes.recorderWrapper}>
<IconButton
aria-label="cancelRecording"
component="span"
fontSize="large"
disabled={loading}
onClick={handleCancelAudio}
>
<HighlightOffIcon className={classes.cancelAudioIcon} />
</IconButton>
{loading ? (
<div>
<CircularProgress className={classes.audioLoading} />
</div>
) : (
<RecordingTimer />
)}
<IconButton
aria-label="sendRecordedAudio"
component="span"
onClick={handleUploadAudio}
disabled={loading}
>
<CheckCircleOutlineIcon className={classes.sendAudioIcon} />
</IconButton>
</div>
) : (
<IconButton
aria-label="showRecorder"
component="span"
disabled={loading || ticketStatus !== "open"}
onClick={handleStartRecording}
>
<MicIcon className={classes.sendMessageIcons} />
</IconButton>
)}
</div>
</Paper>
);
}
}; };
export default MessageInput; export default MessageInput;

View File

@@ -0,0 +1,222 @@
import React, { useState, useEffect, useRef } from "react";
import * as Yup from "yup";
import { Formik, Form, Field } from "formik";
import { toast } from "react-toastify";
import {
makeStyles,
Button,
TextField,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
CircularProgress,
} from "@material-ui/core";
import { green } from "@material-ui/core/colors";
import { i18n } from "../../translate/i18n";
import api from "../../services/api";
import toastError from "../../errors/toastError";
const useStyles = makeStyles((theme) => ({
root: {
flexWrap: "wrap",
},
textField: {
marginRight: theme.spacing(1),
width: "100%",
},
btnWrapper: {
position: "relative",
},
buttonProgress: {
color: green[500],
position: "absolute",
top: "50%",
left: "50%",
marginTop: -12,
marginLeft: -12,
},
textQuickAnswerContainer: {
width: "100%",
},
}));
const QuickAnswerSchema = Yup.object().shape({
shortcut: Yup.string()
.min(2, "Too Short!")
.max(15, "Too Long!")
.required("Required"),
message: Yup.string()
.min(8, "Too Short!")
.max(30000, "Too Long!")
.required("Required"),
});
const QuickAnswersModal = ({
open,
onClose,
quickAnswerId,
initialValues,
onSave,
}) => {
const classes = useStyles();
const isMounted = useRef(true);
const initialState = {
shortcut: "",
message: "",
};
const [quickAnswer, setQuickAnswer] = useState(initialState);
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
useEffect(() => {
const fetchQuickAnswer = async () => {
if (initialValues) {
setQuickAnswer((prevState) => {
return { ...prevState, ...initialValues };
});
}
if (!quickAnswerId) return;
try {
const { data } = await api.get(`/quickAnswers/${quickAnswerId}`);
if (isMounted.current) {
setQuickAnswer(data);
}
} catch (err) {
toastError(err);
}
};
fetchQuickAnswer();
}, [quickAnswerId, open, initialValues]);
const handleClose = () => {
onClose();
setQuickAnswer(initialState);
};
const handleSaveQuickAnswer = async (values) => {
try {
if (quickAnswerId) {
await api.put(`/quickAnswers/${quickAnswerId}`, values);
handleClose();
} else {
const { data } = await api.post("/quickAnswers", values);
if (onSave) {
onSave(data);
}
handleClose();
}
toast.success(i18n.t("quickAnswersModal.success"));
} catch (err) {
toastError(err);
}
};
return (
<div className={classes.root}>
<Dialog
open={open}
onClose={handleClose}
maxWidth="sm"
fullWidth
scroll="paper"
>
<DialogTitle id="form-dialog-title">
{quickAnswerId
? `${i18n.t("quickAnswersModal.title.edit")}`
: `${i18n.t("quickAnswersModal.title.add")}`}
</DialogTitle>
<Formik
initialValues={quickAnswer}
enableReinitialize={true}
validationSchema={QuickAnswerSchema}
onSubmit={(values, actions) => {
setTimeout(() => {
handleSaveQuickAnswer(values);
actions.setSubmitting(false);
}, 400);
}}
>
{({ values, errors, touched, isSubmitting }) => (
<Form>
<DialogContent dividers>
<div className={classes.textQuickAnswerContainer}>
<Field
as={TextField}
label={i18n.t("quickAnswersModal.form.shortcut")}
name="shortcut"
autoFocus
error={touched.shortcut && Boolean(errors.shortcut)}
helperText={touched.shortcut && errors.shortcut}
variant="outlined"
margin="dense"
className={classes.textField}
fullWidth
/>
</div>
<div className={classes.textQuickAnswerContainer}>
<Field
as={TextField}
label={i18n.t("quickAnswersModal.form.message")}
name="message"
error={touched.message && Boolean(errors.message)}
helperText={touched.message && errors.message}
variant="outlined"
margin="dense"
className={classes.textField}
multiline
rows={5}
fullWidth
/>
</div>
</DialogContent>
<DialogActions>
<Button
onClick={handleClose}
color="secondary"
disabled={isSubmitting}
variant="outlined"
>
{i18n.t("quickAnswersModal.buttons.cancel")}
</Button>
<Button
type="submit"
color="primary"
disabled={isSubmitting}
variant="contained"
className={classes.btnWrapper}
>
{quickAnswerId
? `${i18n.t("quickAnswersModal.buttons.okEdit")}`
: `${i18n.t("quickAnswersModal.buttons.okAdd")}`}
{isSubmitting && (
<CircularProgress
size={24}
className={classes.buttonProgress}
/>
)}
</Button>
</DialogActions>
</Form>
)}
</Formik>
</Dialog>
</div>
);
};
export default QuickAnswersModal;

View File

@@ -14,6 +14,7 @@ import SettingsOutlinedIcon from "@material-ui/icons/SettingsOutlined";
import PeopleAltOutlinedIcon from "@material-ui/icons/PeopleAltOutlined"; import PeopleAltOutlinedIcon from "@material-ui/icons/PeopleAltOutlined";
import ContactPhoneOutlinedIcon from "@material-ui/icons/ContactPhoneOutlined"; import ContactPhoneOutlinedIcon from "@material-ui/icons/ContactPhoneOutlined";
import AccountTreeOutlinedIcon from "@material-ui/icons/AccountTreeOutlined"; import AccountTreeOutlinedIcon from "@material-ui/icons/AccountTreeOutlined";
import QuestionAnswerOutlinedIcon from "@material-ui/icons/QuestionAnswerOutlined";
import { i18n } from "../translate/i18n"; import { i18n } from "../translate/i18n";
import { WhatsAppsContext } from "../context/WhatsApp/WhatsAppsContext"; import { WhatsAppsContext } from "../context/WhatsApp/WhatsAppsContext";
@@ -21,109 +22,114 @@ import { AuthContext } from "../context/Auth/AuthContext";
import { Can } from "../components/Can"; import { Can } from "../components/Can";
function ListItemLink(props) { function ListItemLink(props) {
const { icon, primary, to, className } = props; const { icon, primary, to, className } = props;
const renderLink = React.useMemo( const renderLink = React.useMemo(
() => () =>
React.forwardRef((itemProps, ref) => ( React.forwardRef((itemProps, ref) => (
<RouterLink to={to} ref={ref} {...itemProps} /> <RouterLink to={to} ref={ref} {...itemProps} />
)), )),
[to] [to]
); );
return ( return (
<li> <li>
<ListItem button component={renderLink} className={className}> <ListItem button component={renderLink} className={className}>
{icon ? <ListItemIcon>{icon}</ListItemIcon> : null} {icon ? <ListItemIcon>{icon}</ListItemIcon> : null}
<ListItemText primary={primary} /> <ListItemText primary={primary} />
</ListItem> </ListItem>
</li> </li>
); );
} }
const MainListItems = () => { const MainListItems = () => {
const { whatsApps } = useContext(WhatsAppsContext); const { whatsApps } = useContext(WhatsAppsContext);
const { user } = useContext(AuthContext); const { user } = useContext(AuthContext);
const [connectionWarning, setConnectionWarning] = useState(false); const [connectionWarning, setConnectionWarning] = useState(false);
useEffect(() => { useEffect(() => {
const delayDebounceFn = setTimeout(() => { const delayDebounceFn = setTimeout(() => {
if (whatsApps.length > 0) { if (whatsApps.length > 0) {
const offlineWhats = whatsApps.filter(whats => { const offlineWhats = whatsApps.filter((whats) => {
return ( return (
whats.status === "qrcode" || whats.status === "qrcode" ||
whats.status === "PAIRING" || whats.status === "PAIRING" ||
whats.status === "DISCONNECTED" || whats.status === "DISCONNECTED" ||
whats.status === "TIMEOUT" || whats.status === "TIMEOUT" ||
whats.status === "OPENING" whats.status === "OPENING"
); );
}); });
if (offlineWhats.length > 0) { if (offlineWhats.length > 0) {
setConnectionWarning(true); setConnectionWarning(true);
} else { } else {
setConnectionWarning(false); setConnectionWarning(false);
} }
} }
}, 2000); }, 2000);
return () => clearTimeout(delayDebounceFn); return () => clearTimeout(delayDebounceFn);
}, [whatsApps]); }, [whatsApps]);
return ( return (
<div> <div>
<ListItemLink <ListItemLink
to="/" to="/"
primary="Dashboard" primary="Dashboard"
icon={<DashboardOutlinedIcon />} icon={<DashboardOutlinedIcon />}
/> />
<ListItemLink <ListItemLink
to="/connections" to="/connections"
primary={i18n.t("mainDrawer.listItems.connections")} primary={i18n.t("mainDrawer.listItems.connections")}
icon={ icon={
<Badge badgeContent={connectionWarning ? "!" : 0} color="error"> <Badge badgeContent={connectionWarning ? "!" : 0} color="error">
<SyncAltIcon /> <SyncAltIcon />
</Badge> </Badge>
} }
/> />
<ListItemLink <ListItemLink
to="/tickets" to="/tickets"
primary={i18n.t("mainDrawer.listItems.tickets")} primary={i18n.t("mainDrawer.listItems.tickets")}
icon={<WhatsAppIcon />} icon={<WhatsAppIcon />}
/> />
<ListItemLink <ListItemLink
to="/contacts" to="/contacts"
primary={i18n.t("mainDrawer.listItems.contacts")} primary={i18n.t("mainDrawer.listItems.contacts")}
icon={<ContactPhoneOutlinedIcon />} icon={<ContactPhoneOutlinedIcon />}
/> />
<Can <ListItemLink
role={user.profile} to="/quickAnswers"
perform="drawer-admin-items:view" primary={i18n.t("mainDrawer.listItems.quickAnswers")}
yes={() => ( icon={<QuestionAnswerOutlinedIcon />}
<> />
<Divider /> <Can
<ListSubheader inset> role={user.profile}
{i18n.t("mainDrawer.listItems.administration")} perform="drawer-admin-items:view"
</ListSubheader> yes={() => (
<ListItemLink <>
to="/users" <Divider />
primary={i18n.t("mainDrawer.listItems.users")} <ListSubheader inset>
icon={<PeopleAltOutlinedIcon />} {i18n.t("mainDrawer.listItems.administration")}
/> </ListSubheader>
<ListItemLink <ListItemLink
to="/queues" to="/users"
primary={i18n.t("mainDrawer.listItems.queues")} primary={i18n.t("mainDrawer.listItems.users")}
icon={<AccountTreeOutlinedIcon />} icon={<PeopleAltOutlinedIcon />}
/> />
<ListItemLink <ListItemLink
to="/settings" to="/queues"
primary={i18n.t("mainDrawer.listItems.settings")} primary={i18n.t("mainDrawer.listItems.queues")}
icon={<SettingsOutlinedIcon />} icon={<AccountTreeOutlinedIcon />}
/> />
</> <ListItemLink
)} to="/settings"
/> primary={i18n.t("mainDrawer.listItems.settings")}
</div> icon={<SettingsOutlinedIcon />}
); />
</>
)}
/>
</div>
);
}; };
export default MainListItems; export default MainListItems;

View File

@@ -0,0 +1,288 @@
import React, { useState, useEffect, useReducer } from "react";
import openSocket from "socket.io-client";
import {
Button,
IconButton,
makeStyles,
Paper,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
InputAdornment,
TextField,
} from "@material-ui/core";
import { Edit, DeleteOutline } from "@material-ui/icons";
import SearchIcon from "@material-ui/icons/Search";
import MainContainer from "../../components/MainContainer";
import MainHeader from "../../components/MainHeader";
import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper";
import Title from "../../components/Title";
import api from "../../services/api";
import { i18n } from "../../translate/i18n";
import TableRowSkeleton from "../../components/TableRowSkeleton";
import QuickAnswersModal from "../../components/QuickAnswersModal";
import ConfirmationModal from "../../components/ConfirmationModal";
import { toast } from "react-toastify";
import toastError from "../../errors/toastError";
const reducer = (state, action) => {
if (action.type === "LOAD_QUICK_ANSWERS") {
const quickAnswers = action.payload;
const newQuickAnswers = [];
quickAnswers.forEach((quickAnswer) => {
const quickAnswerIndex = state.findIndex((q) => q.id === quickAnswer.id);
if (quickAnswerIndex !== -1) {
state[quickAnswerIndex] = quickAnswer;
} else {
newQuickAnswers.push(quickAnswer);
}
});
return [...state, ...newQuickAnswers];
}
if (action.type === "UPDATE_QUICK_ANSWERS") {
const quickAnswer = action.payload;
const quickAnswerIndex = state.findIndex((q) => q.id === quickAnswer.id);
if (quickAnswerIndex !== -1) {
state[quickAnswerIndex] = quickAnswer;
return [...state];
} else {
return [quickAnswer, ...state];
}
}
if (action.type === "DELETE_QUICK_ANSWERS") {
const quickAnswerId = action.payload;
const quickAnswerIndex = state.findIndex((q) => q.id === quickAnswerId);
if (quickAnswerIndex !== -1) {
state.splice(quickAnswerIndex, 1);
}
return [...state];
}
if (action.type === "RESET") {
return [];
}
};
const useStyles = makeStyles((theme) => ({
mainPaper: {
flex: 1,
padding: theme.spacing(1),
overflowY: "scroll",
...theme.scrollbarStyles,
},
}));
const QuickAnswers = () => {
const classes = useStyles();
const [loading, setLoading] = useState(false);
const [pageNumber, setPageNumber] = useState(1);
const [searchParam, setSearchParam] = useState("");
const [quickAnswers, dispatch] = useReducer(reducer, []);
const [selectedQuickAnswers, setSelectedQuickAnswers] = useState(null);
const [quickAnswersModalOpen, setQuickAnswersModalOpen] = useState(false);
const [deletingQuickAnswers, setDeletingQuickAnswers] = useState(null);
const [confirmModalOpen, setConfirmModalOpen] = useState(false);
const [hasMore, setHasMore] = useState(false);
useEffect(() => {
dispatch({ type: "RESET" });
setPageNumber(1);
}, [searchParam]);
useEffect(() => {
setLoading(true);
const delayDebounceFn = setTimeout(() => {
const fetchQuickAnswers = async () => {
try {
const { data } = await api.get("/quickAnswers/", {
params: { searchParam, pageNumber },
});
dispatch({ type: "LOAD_QUICK_ANSWERS", payload: data.quickAnswers });
setHasMore(data.hasMore);
setLoading(false);
} catch (err) {
toastError(err);
}
};
fetchQuickAnswers();
}, 500);
return () => clearTimeout(delayDebounceFn);
}, [searchParam, pageNumber]);
useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
socket.on("quickAnswer", (data) => {
if (data.action === "update" || data.action === "create") {
dispatch({ type: "UPDATE_QUICK_ANSWERS", payload: data.quickAnswer });
}
if (data.action === "delete") {
dispatch({
type: "DELETE_QUICK_ANSWERS",
payload: +data.quickAnswerId,
});
}
});
return () => {
socket.disconnect();
};
}, []);
const handleSearch = (event) => {
setSearchParam(event.target.value.toLowerCase());
};
const handleOpenQuickAnswersModal = () => {
setSelectedQuickAnswers(null);
setQuickAnswersModalOpen(true);
};
const handleCloseQuickAnswersModal = () => {
setSelectedQuickAnswers(null);
setQuickAnswersModalOpen(false);
};
const handleEditQuickAnswers = (quickAnswer) => {
setSelectedQuickAnswers(quickAnswer);
setQuickAnswersModalOpen(true);
};
const handleDeleteQuickAnswers = async (quickAnswerId) => {
try {
await api.delete(`/quickAnswers/${quickAnswerId}`);
toast.success(i18n.t("quickAnswers.toasts.deleted"));
} catch (err) {
toastError(err);
}
setDeletingQuickAnswers(null);
setSearchParam("");
setPageNumber(1);
};
const loadMore = () => {
setPageNumber((prevState) => prevState + 1);
};
const handleScroll = (e) => {
if (!hasMore || loading) return;
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
if (scrollHeight - (scrollTop + 100) < clientHeight) {
loadMore();
}
};
return (
<MainContainer>
<ConfirmationModal
title={
deletingQuickAnswers &&
`${i18n.t("quickAnswers.confirmationModal.deleteTitle")} ${
deletingQuickAnswers.shortcut
}?`
}
open={confirmModalOpen}
onClose={setConfirmModalOpen}
onConfirm={() => handleDeleteQuickAnswers(deletingQuickAnswers.id)}
>
{i18n.t("quickAnswers.confirmationModal.deleteMessage")}
</ConfirmationModal>
<QuickAnswersModal
open={quickAnswersModalOpen}
onClose={handleCloseQuickAnswersModal}
aria-labelledby="form-dialog-title"
quickAnswerId={selectedQuickAnswers && selectedQuickAnswers.id}
></QuickAnswersModal>
<MainHeader>
<Title>{i18n.t("quickAnswers.title")}</Title>
<MainHeaderButtonsWrapper>
<TextField
placeholder={i18n.t("quickAnswers.searchPlaceholder")}
type="search"
value={searchParam}
onChange={handleSearch}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon style={{ color: "gray" }} />
</InputAdornment>
),
}}
/>
<Button
variant="contained"
color="primary"
onClick={handleOpenQuickAnswersModal}
>
{i18n.t("quickAnswers.buttons.add")}
</Button>
</MainHeaderButtonsWrapper>
</MainHeader>
<Paper
className={classes.mainPaper}
variant="outlined"
onScroll={handleScroll}
>
<Table size="small">
<TableHead>
<TableRow>
<TableCell align="center">
{i18n.t("quickAnswers.table.shortcut")}
</TableCell>
<TableCell align="center">
{i18n.t("quickAnswers.table.message")}
</TableCell>
<TableCell align="center">
{i18n.t("quickAnswers.table.actions")}
</TableCell>
</TableRow>
</TableHead>
<TableBody>
<>
{quickAnswers.map((quickAnswer) => (
<TableRow key={quickAnswer.id}>
<TableCell align="center">{quickAnswer.shortcut}</TableCell>
<TableCell align="center">{quickAnswer.message}</TableCell>
<TableCell align="center">
<IconButton
size="small"
onClick={() => handleEditQuickAnswers(quickAnswer)}
>
<Edit />
</IconButton>
<IconButton
size="small"
onClick={(e) => {
setConfirmModalOpen(true);
setDeletingQuickAnswers(quickAnswer);
}}
>
<DeleteOutline />
</IconButton>
</TableCell>
</TableRow>
))}
{loading && <TableRowSkeleton columns={3} />}
</>
</TableBody>
</Table>
</Paper>
</MainContainer>
);
};
export default QuickAnswers;

View File

@@ -11,44 +11,51 @@ import Connections from "../pages/Connections/";
import Settings from "../pages/Settings/"; import Settings from "../pages/Settings/";
import Users from "../pages/Users"; import Users from "../pages/Users";
import Contacts from "../pages/Contacts/"; import Contacts from "../pages/Contacts/";
import QuickAnswers from "../pages/QuickAnswers/";
import Queues from "../pages/Queues/"; import Queues from "../pages/Queues/";
import { AuthProvider } from "../context/Auth/AuthContext"; import { AuthProvider } from "../context/Auth/AuthContext";
import { WhatsAppsProvider } from "../context/WhatsApp/WhatsAppsContext"; import { WhatsAppsProvider } from "../context/WhatsApp/WhatsAppsContext";
import Route from "./Route"; import Route from "./Route";
const Routes = () => { const Routes = () => {
return ( return (
<BrowserRouter> <BrowserRouter>
<AuthProvider> <AuthProvider>
<Switch> <Switch>
<Route exact path="/login" component={Login} /> <Route exact path="/login" component={Login} />
<Route exact path="/signup" component={Signup} /> <Route exact path="/signup" component={Signup} />
<WhatsAppsProvider> <WhatsAppsProvider>
<LoggedInLayout> <LoggedInLayout>
<Route exact path="/" component={Dashboard} isPrivate /> <Route exact path="/" component={Dashboard} isPrivate />
<Route <Route
exact exact
path="/tickets/:ticketId?" path="/tickets/:ticketId?"
component={Tickets} component={Tickets}
isPrivate isPrivate
/> />
<Route <Route
exact exact
path="/connections" path="/connections"
component={Connections} component={Connections}
isPrivate isPrivate
/> />
<Route exact path="/contacts" component={Contacts} isPrivate /> <Route exact path="/contacts" component={Contacts} isPrivate />
<Route exact path="/users" component={Users} isPrivate /> <Route exact path="/users" component={Users} isPrivate />
<Route exact path="/Settings" component={Settings} isPrivate /> <Route
<Route exact path="/Queues" component={Queues} isPrivate /> exact
</LoggedInLayout> path="/quickAnswers"
</WhatsAppsProvider> component={QuickAnswers}
</Switch> isPrivate
<ToastContainer autoClose={3000} /> />
</AuthProvider> <Route exact path="/Settings" component={Settings} isPrivate />
</BrowserRouter> <Route exact path="/Queues" component={Queues} isPrivate />
); </LoggedInLayout>
</WhatsAppsProvider>
</Switch>
<ToastContainer autoClose={3000} />
</AuthProvider>
</BrowserRouter>
);
}; };
export default Routes; export default Routes;

View File

@@ -1,412 +1,448 @@
const messages = { const messages = {
en: { en: {
translations: { translations: {
signup: { signup: {
title: "Sign up", title: "Sign up",
toasts: { toasts: {
success: "User created successfully! Please login!", success: "User created successfully! Please login!",
fail: "Error creating user. Check the reported data.", fail: "Error creating user. Check the reported data.",
}, },
form: { form: {
name: "Name", name: "Name",
email: "Email", email: "Email",
password: "Password", password: "Password",
}, },
buttons: { buttons: {
submit: "Register", submit: "Register",
login: "Already have an account? Log in!", login: "Already have an account? Log in!",
}, },
}, },
login: { login: {
title: "Login", title: "Login",
form: { form: {
email: "Email", email: "Email",
password: "Password", password: "Password",
}, },
buttons: { buttons: {
submit: "Enter", submit: "Enter",
register: "Don't have an account? Register!", register: "Don't have an account? Register!",
}, },
}, },
auth: { auth: {
toasts: { toasts: {
success: "Login successfully!", success: "Login successfully!",
}, },
}, },
dashboard: { dashboard: {
charts: { charts: {
perDay: { perDay: {
title: "Tickets today: ", title: "Tickets today: ",
}, },
}, },
}, },
connections: { connections: {
title: "Connections", title: "Connections",
toasts: { toasts: {
deleted: "WhatsApp connection deleted sucessfully!", deleted: "WhatsApp connection deleted sucessfully!",
}, },
confirmationModal: { confirmationModal: {
deleteTitle: "Delete", deleteTitle: "Delete",
deleteMessage: "Are you sure? It cannot be reverted.", deleteMessage: "Are you sure? It cannot be reverted.",
disconnectTitle: "Disconnect", disconnectTitle: "Disconnect",
disconnectMessage: "Are you sure? You'll need to read QR Code again.", disconnectMessage: "Are you sure? You'll need to read QR Code again.",
}, },
buttons: { buttons: {
add: "Add WhatsApp", add: "Add WhatsApp",
disconnect: "Disconnect", disconnect: "Disconnect",
tryAgain: "Try Again", tryAgain: "Try Again",
qrcode: "QR CODE", qrcode: "QR CODE",
newQr: "New QR CODE", newQr: "New QR CODE",
connecting: "Connectiing", connecting: "Connectiing",
}, },
toolTips: { toolTips: {
disconnected: { disconnected: {
title: "Failed to start WhatsApp session", title: "Failed to start WhatsApp session",
content: content:
"Make sure your cell phone is connected to the internet and try again, or request a new QR Code", "Make sure your cell phone is connected to the internet and try again, or request a new QR Code",
}, },
qrcode: { qrcode: {
title: "Waiting for QR Code read", title: "Waiting for QR Code read",
content: content:
"Click on 'QR CODE' button and read the QR Code with your cell phone to start session", "Click on 'QR CODE' button and read the QR Code with your cell phone to start session",
}, },
connected: { connected: {
title: "Connection established", title: "Connection established",
}, },
timeout: { timeout: {
title: "Connection with cell phone has been lost", title: "Connection with cell phone has been lost",
content: content:
"Make sure your cell phone is connected to the internet and WhatsApp is open, or click on 'Disconnect' button to get a new QRcode", "Make sure your cell phone is connected to the internet and WhatsApp is open, or click on 'Disconnect' button to get a new QRcode",
}, },
}, },
table: { table: {
name: "Name", name: "Name",
status: "Status", status: "Status",
lastUpdate: "Last Update", lastUpdate: "Last Update",
default: "Default", default: "Default",
actions: "Actions", actions: "Actions",
session: "Session", session: "Session",
}, },
}, },
whatsappModal: { whatsappModal: {
title: { title: {
add: "Add WhatsApp", add: "Add WhatsApp",
edit: "Edit WhatsApp", edit: "Edit WhatsApp",
}, },
form: { form: {
name: "Name", name: "Name",
default: "Default", default: "Default",
}, },
buttons: { buttons: {
okAdd: "Add", okAdd: "Add",
okEdit: "Save", okEdit: "Save",
cancel: "Cancel", cancel: "Cancel",
}, },
success: "WhatsApp saved successfully.", success: "WhatsApp saved successfully.",
}, },
qrCode: { qrCode: {
message: "Read QrCode to start the session", message: "Read QrCode to start the session",
}, },
contacts: { contacts: {
title: "Contacts", title: "Contacts",
toasts: { toasts: {
deleted: "Contact deleted sucessfully!", deleted: "Contact deleted sucessfully!",
}, },
searchPlaceholder: "Search ...", searchPlaceholder: "Search ...",
confirmationModal: { confirmationModal: {
deleteTitle: "Delete", deleteTitle: "Delete",
importTitlte: "Import contacts", importTitlte: "Import contacts",
deleteMessage: deleteMessage:
"Are you sure you want to delete this contact? All related tickets will be lost.", "Are you sure you want to delete this contact? All related tickets will be lost.",
importMessage: "Do you want to import all contacts from the phone?", importMessage: "Do you want to import all contacts from the phone?",
}, },
buttons: { buttons: {
import: "Import Contacts", import: "Import Contacts",
add: "Add Contact", add: "Add Contact",
}, },
table: { table: {
name: "Name", name: "Name",
whatsapp: "WhatsApp", whatsapp: "WhatsApp",
email: "Email", email: "Email",
actions: "Actions", actions: "Actions",
}, },
}, },
contactModal: { contactModal: {
title: { title: {
add: "Add contact", add: "Add contact",
edit: "Edit contact", edit: "Edit contact",
}, },
form: { form: {
mainInfo: "Contact details", mainInfo: "Contact details",
extraInfo: "Additional information", extraInfo: "Additional information",
name: "Name", name: "Name",
number: "Whatsapp number", number: "Whatsapp number",
email: "Email", email: "Email",
extraName: "Field name", extraName: "Field name",
extraValue: "Value", extraValue: "Value",
}, },
buttons: { buttons: {
addExtraInfo: "Add information", addExtraInfo: "Add information",
okAdd: "Add", okAdd: "Add",
okEdit: "Save", okEdit: "Save",
cancel: "Cancel", cancel: "Cancel",
}, },
success: "Contact saved successfully.", success: "Contact saved successfully.",
}, },
queueModal: { quickAnswersModal: {
title: { title: {
add: "Add queue", add: "Add Quick Reply",
edit: "Edit queue", edit: "Edit Quick Answer",
}, },
form: { form: {
name: "Name", shortcut: "Shortcut",
color: "Color", message: "Quick Reply",
greetingMessage: "Greeting Message", },
}, buttons: {
buttons: { okAdd: "Add",
okAdd: "Add", okEdit: "Save",
okEdit: "Save", cancel: "Cancel",
cancel: "Cancel", },
}, success: "Quick Reply saved successfully.",
}, },
userModal: { queueModal: {
title: { title: {
add: "Add user", add: "Add queue",
edit: "Edit user", edit: "Edit queue",
}, },
form: { form: {
name: "Name", name: "Name",
email: "Email", color: "Color",
password: "Password", greetingMessage: "Greeting Message",
profile: "Profile", },
}, buttons: {
buttons: { okAdd: "Add",
okAdd: "Add", okEdit: "Save",
okEdit: "Save", cancel: "Cancel",
cancel: "Cancel", },
}, },
success: "User saved successfully.", userModal: {
}, title: {
chat: { add: "Add user",
noTicketMessage: "Select a ticket to start chatting.", edit: "Edit user",
}, },
ticketsManager: { form: {
buttons: { name: "Name",
newTicket: "New", email: "Email",
}, password: "Password",
}, profile: "Profile",
ticketsQueueSelect: { },
placeholder: "Queues", buttons: {
}, okAdd: "Add",
tickets: { okEdit: "Save",
toasts: { cancel: "Cancel",
deleted: "The ticket you were on has been deleted.", },
}, success: "User saved successfully.",
notification: { },
message: "Message from", chat: {
}, noTicketMessage: "Select a ticket to start chatting.",
tabs: { },
open: { title: "Inbox" }, ticketsManager: {
closed: { title: "Resolved" }, buttons: {
search: { title: "Search" }, newTicket: "New",
}, },
search: { },
placeholder: "Search tickets and messages.", ticketsQueueSelect: {
}, placeholder: "Queues",
buttons: { },
showAll: "All", tickets: {
}, toasts: {
}, deleted: "The ticket you were on has been deleted.",
transferTicketModal: { },
title: "Transfer Ticket", notification: {
fieldLabel: "Type to search for users", message: "Message from",
noOptions: "No user found with this name", },
buttons: { tabs: {
ok: "Transfer", open: { title: "Inbox" },
cancel: "Cancel", closed: { title: "Resolved" },
}, search: { title: "Search" },
}, },
ticketsList: { search: {
pendingHeader: "Queue", placeholder: "Search tickets and messages.",
assignedHeader: "Working on", },
noTicketsTitle: "Nothing here!", buttons: {
noTicketsMessage: "No tickets found with this status or search term.", showAll: "All",
buttons: { },
accept: "Accept", },
}, transferTicketModal: {
}, title: "Transfer Ticket",
newTicketModal: { fieldLabel: "Type to search for users",
title: "Create Ticket", noOptions: "No user found with this name",
fieldLabel: "Type to search for a contact", buttons: {
add: "Add", ok: "Transfer",
buttons: { cancel: "Cancel",
ok: "Save", },
cancel: "Cancel", },
}, ticketsList: {
}, pendingHeader: "Queue",
mainDrawer: { assignedHeader: "Working on",
listItems: { noTicketsTitle: "Nothing here!",
dashboard: "Dashboard", noTicketsMessage: "No tickets found with this status or search term.",
connections: "Connections", buttons: {
tickets: "Tickets", accept: "Accept",
contacts: "Contacts", },
queues: "Queues", },
administration: "Administration", newTicketModal: {
users: "Users", title: "Create Ticket",
settings: "Settings", fieldLabel: "Type to search for a contact",
}, add: "Add",
appBar: { buttons: {
user: { ok: "Save",
profile: "Profile", cancel: "Cancel",
logout: "Logout", },
}, },
}, mainDrawer: {
}, listItems: {
notifications: { dashboard: "Dashboard",
noTickets: "No notifications.", connections: "Connections",
}, tickets: "Tickets",
queues: { contacts: "Contacts",
title: "Queues", quickAnswers: "Quick Answers",
table: { queues: "Queues",
name: "Name", administration: "Administration",
color: "Color", users: "Users",
greeting: "Greeting message", settings: "Settings",
actions: "Actions", },
}, appBar: {
buttons: { user: {
add: "Add queue", profile: "Profile",
}, logout: "Logout",
confirmationModal: { },
deleteTitle: "Delete", },
deleteMessage: },
"Are you sure? It cannot be reverted! Tickets in this queue will still exist, but will not have any queues assigned.", notifications: {
}, noTickets: "No notifications.",
}, },
queueSelect: { queues: {
inputLabel: "Queues", title: "Queues",
}, table: {
users: { name: "Name",
title: "Users", color: "Color",
table: { greeting: "Greeting message",
name: "Name", actions: "Actions",
email: "Email", },
profile: "Profile", buttons: {
actions: "Actions", add: "Add queue",
}, },
buttons: { confirmationModal: {
add: "Add user", deleteTitle: "Delete",
}, deleteMessage:
toasts: { "Are you sure? It cannot be reverted! Tickets in this queue will still exist, but will not have any queues assigned.",
deleted: "User deleted sucessfully.", },
}, },
confirmationModal: { queueSelect: {
deleteTitle: "Delete", inputLabel: "Queues",
deleteMessage: },
"All user data will be lost. Users' open tickets will be moved to queue.", quickAnswers: {
}, title: "Quick Answers",
}, table: {
settings: { shortcut: "Shortcut",
success: "Settings saved successfully.", message: "Quick Reply",
title: "Settings", actions: "Actions",
settings: { },
userCreation: { buttons: {
name: "User creation", add: "Add Quick Reply",
options: { },
enabled: "Enabled", toasts: {
disabled: "Disabled", deleted: "Quick Reply deleted successfully.",
}, },
}, searchPlaceholder: "Search...",
}, confirmationModal: {
}, deleteTitle: "Are you sure you want to delete this Quick Reply: ",
messagesList: { deleteMessage: "This action cannot be undone.",
header: { },
assignedTo: "Assigned to:", },
buttons: { users: {
return: "Return", title: "Users",
resolve: "Resolve", table: {
reopen: "Reopen", name: "Name",
accept: "Accept", email: "Email",
}, profile: "Profile",
}, actions: "Actions",
}, },
messagesInput: { buttons: {
placeholderOpen: "Type a message", add: "Add user",
placeholderClosed: "Reopen or accept this ticket to send a message.", },
signMessage: "Sign", toasts: {
}, deleted: "User deleted sucessfully.",
contactDrawer: { },
header: "Contact details", confirmationModal: {
buttons: { deleteTitle: "Delete",
edit: "Edit contact", deleteMessage:
}, "All user data will be lost. Users' open tickets will be moved to queue.",
extraInfo: "Other information", },
}, },
ticketOptionsMenu: { settings: {
delete: "Delete", success: "Settings saved successfully.",
transfer: "Transfer", title: "Settings",
confirmationModal: { settings: {
title: "Delete ticket #", userCreation: {
titleFrom: "from contact ", name: "User creation",
message: "Attention! All ticket's related messages will be lost.", options: {
}, enabled: "Enabled",
buttons: { disabled: "Disabled",
delete: "Delete", },
cancel: "Cancel", },
}, },
}, },
confirmationModal: { messagesList: {
buttons: { header: {
confirm: "Ok", assignedTo: "Assigned to:",
cancel: "Cancel", buttons: {
}, return: "Return",
}, resolve: "Resolve",
messageOptionsMenu: { reopen: "Reopen",
delete: "Delete", accept: "Accept",
reply: "Reply", },
confirmationModal: { },
title: "Delete message?", },
message: "This action cannot be reverted.", messagesInput: {
}, placeholderOpen: "Type a message",
}, placeholderClosed: "Reopen or accept this ticket to send a message.",
backendErrors: { signMessage: "Sign",
ERR_NO_OTHER_WHATSAPP: },
"There must be at lest one default WhatsApp connection.", contactDrawer: {
ERR_NO_DEF_WAPP_FOUND: header: "Contact details",
"No default WhatsApp found. Check connections page.", buttons: {
ERR_WAPP_NOT_INITIALIZED: edit: "Edit contact",
"This WhatsApp session is not initialized. Check connections page.", },
ERR_WAPP_CHECK_CONTACT: extraInfo: "Other information",
"Could not check WhatsApp contact. Check connections page.", },
ERR_WAPP_INVALID_CONTACT: "This is not a valid whatsapp number.", ticketOptionsMenu: {
ERR_WAPP_DOWNLOAD_MEDIA: delete: "Delete",
"Could not download media from WhatsApp. Check connections page.", transfer: "Transfer",
ERR_INVALID_CREDENTIALS: "Authentication error. Please try again.", confirmationModal: {
ERR_SENDING_WAPP_MSG: title: "Delete ticket #",
"Error sending WhatsApp message. Check connections page.", titleFrom: "from contact ",
ERR_DELETE_WAPP_MSG: "Couldn't delete message from WhatsApp.", message: "Attention! All ticket's related messages will be lost.",
ERR_OTHER_OPEN_TICKET: },
"There's already an open ticket for this contact.", buttons: {
ERR_SESSION_EXPIRED: "Session expired. Please login.", delete: "Delete",
ERR_USER_CREATION_DISABLED: cancel: "Cancel",
"User creation was disabled by administrator.", },
ERR_NO_PERMISSION: "You don't have permission to access this resource.", },
ERR_DUPLICATED_CONTACT: "A contact with this number already exists.", confirmationModal: {
ERR_NO_SETTING_FOUND: "No setting found with this ID.", buttons: {
ERR_NO_CONTACT_FOUND: "No contact found with this ID.", confirm: "Ok",
ERR_NO_TICKET_FOUND: "No ticket found with this ID.", cancel: "Cancel",
ERR_NO_USER_FOUND: "No user found with this ID.", },
ERR_NO_WAPP_FOUND: "No WhatsApp found with this ID.", },
ERR_CREATING_MESSAGE: "Error while creating message on database.", messageOptionsMenu: {
ERR_CREATING_TICKET: "Error while creating ticket on database.", delete: "Delete",
ERR_FETCH_WAPP_MSG: reply: "Reply",
"Error fetching the message in WhtasApp, maybe it is too old.", confirmationModal: {
ERR_QUEUE_COLOR_ALREADY_EXISTS: title: "Delete message?",
"This color is already in use, pick another one.", message: "This action cannot be reverted.",
ERR_WAPP_GREETING_REQUIRED: },
"Greeting message is required if there is more than one queue.", },
}, backendErrors: {
}, ERR_NO_OTHER_WHATSAPP:
}, "There must be at lest one default WhatsApp connection.",
ERR_NO_DEF_WAPP_FOUND:
"No default WhatsApp found. Check connections page.",
ERR_WAPP_NOT_INITIALIZED:
"This WhatsApp session is not initialized. Check connections page.",
ERR_WAPP_CHECK_CONTACT:
"Could not check WhatsApp contact. Check connections page.",
ERR_WAPP_INVALID_CONTACT: "This is not a valid whatsapp number.",
ERR_WAPP_DOWNLOAD_MEDIA:
"Could not download media from WhatsApp. Check connections page.",
ERR_INVALID_CREDENTIALS: "Authentication error. Please try again.",
ERR_SENDING_WAPP_MSG:
"Error sending WhatsApp message. Check connections page.",
ERR_DELETE_WAPP_MSG: "Couldn't delete message from WhatsApp.",
ERR_OTHER_OPEN_TICKET:
"There's already an open ticket for this contact.",
ERR_SESSION_EXPIRED: "Session expired. Please login.",
ERR_USER_CREATION_DISABLED:
"User creation was disabled by administrator.",
ERR_NO_PERMISSION: "You don't have permission to access this resource.",
ERR_DUPLICATED_CONTACT: "A contact with this number already exists.",
ERR_NO_SETTING_FOUND: "No setting found with this ID.",
ERR_NO_CONTACT_FOUND: "No contact found with this ID.",
ERR_NO_TICKET_FOUND: "No ticket found with this ID.",
ERR_NO_USER_FOUND: "No user found with this ID.",
ERR_NO_WAPP_FOUND: "No WhatsApp found with this ID.",
ERR_CREATING_MESSAGE: "Error while creating message on database.",
ERR_CREATING_TICKET: "Error while creating ticket on database.",
ERR_FETCH_WAPP_MSG:
"Error fetching the message in WhtasApp, maybe it is too old.",
ERR_QUEUE_COLOR_ALREADY_EXISTS:
"This color is already in use, pick another one.",
ERR_WAPP_GREETING_REQUIRED:
"Greeting message is required if there is more than one queue.",
},
},
},
}; };
export { messages }; export { messages };

View File

@@ -1,418 +1,455 @@
const messages = { const messages = {
es: { es: {
translations: { translations: {
signup: { signup: {
title: "Registro", title: "Registro",
toasts: { toasts: {
success: success:
"¡El usuario ha sido creado satisfactoriamente! ¡Ahora inicia sesión!", "¡El usuario ha sido creado satisfactoriamente! ¡Ahora inicia sesión!",
fail: "Error creando el usuario. Verifica la data reportada.", fail: "Error creando el usuario. Verifica la data reportada.",
}, },
form: { form: {
name: "Nombre", name: "Nombre",
email: "Correo Electrónico", email: "Correo Electrónico",
password: "Contraseña", password: "Contraseña",
}, },
buttons: { buttons: {
submit: "Regístrate", submit: "Regístrate",
login: "¿Ya tienes una cuenta? ¡Inicia sesión!", login: "¿Ya tienes una cuenta? ¡Inicia sesión!",
}, },
}, },
login: { login: {
title: "Inicio de Sesión", title: "Inicio de Sesión",
form: { form: {
email: "Correo Electrónico", email: "Correo Electrónico",
password: "Contraseña", password: "Contraseña",
}, },
buttons: { buttons: {
submit: "Ingresa", submit: "Ingresa",
register: "¿No tienes cuenta? ¡Regístrate!", register: "¿No tienes cuenta? ¡Regístrate!",
}, },
}, },
auth: { auth: {
toasts: { toasts: {
success: "¡Inicio de sesión exitoso!", success: "¡Inicio de sesión exitoso!",
}, },
}, },
dashboard: { dashboard: {
charts: { charts: {
perDay: { perDay: {
title: "Tickets hoy: ", title: "Tickets hoy: ",
}, },
}, },
}, },
connections: { connections: {
title: "Conexiones", title: "Conexiones",
toasts: { toasts: {
deleted: deleted:
"¡La conexión de WhatsApp ha sido borrada satisfactoriamente!", "¡La conexión de WhatsApp ha sido borrada satisfactoriamente!",
}, },
confirmationModal: { confirmationModal: {
deleteTitle: "Borrar", deleteTitle: "Borrar",
deleteMessage: "¿Estás seguro? Este proceso no puede ser revertido.", deleteMessage: "¿Estás seguro? Este proceso no puede ser revertido.",
disconnectTitle: "Desconectar", disconnectTitle: "Desconectar",
disconnectMessage: "Estás seguro? Deberá volver a leer el código QR", disconnectMessage: "Estás seguro? Deberá volver a leer el código QR",
}, },
buttons: { buttons: {
add: "Agrega WhatsApp", add: "Agrega WhatsApp",
disconnect: "Desconectar", disconnect: "Desconectar",
tryAgain: "Inténtalo de nuevo", tryAgain: "Inténtalo de nuevo",
qrcode: "QR CODE", qrcode: "QR CODE",
newQr: "Nuevo QR CODE", newQr: "Nuevo QR CODE",
connecting: "Conectando", connecting: "Conectando",
}, },
toolTips: { toolTips: {
disconnected: { disconnected: {
title: "No se pudo iniciar la sesión de WhatsApp", title: "No se pudo iniciar la sesión de WhatsApp",
content: content:
"Asegúrese de que su teléfono celular esté conectado a Internet y vuelva a intentarlo o solicite un nuevo código QR", "Asegúrese de que su teléfono celular esté conectado a Internet y vuelva a intentarlo o solicite un nuevo código QR",
}, },
qrcode: { qrcode: {
title: "Esperando la lectura del código QR", title: "Esperando la lectura del código QR",
content: content:
"Haga clic en el botón 'CÓDIGO QR' y lea el Código QR con su teléfono celular para iniciar la sesión", "Haga clic en el botón 'CÓDIGO QR' y lea el Código QR con su teléfono celular para iniciar la sesión",
}, },
connected: { connected: {
title: "Conexión establecida", title: "Conexión establecida",
}, },
timeout: { timeout: {
title: "Se perdió la conexión con el teléfono celular", title: "Se perdió la conexión con el teléfono celular",
content: content:
"Asegúrese de que su teléfono celular esté conectado a Internet y que WhatsApp esté abierto, o haga clic en el botón 'Desconectar' para obtener un nuevo código QR", "Asegúrese de que su teléfono celular esté conectado a Internet y que WhatsApp esté abierto, o haga clic en el botón 'Desconectar' para obtener un nuevo código QR",
}, },
}, },
table: { table: {
name: "Nombre", name: "Nombre",
status: "Estado", status: "Estado",
lastUpdate: "Última Actualización", lastUpdate: "Última Actualización",
default: "Por Defecto", default: "Por Defecto",
actions: "Acciones", actions: "Acciones",
session: "Sesión", session: "Sesión",
}, },
}, },
whatsappModal: { whatsappModal: {
title: { title: {
add: "Agrega WhatsApp", add: "Agrega WhatsApp",
edit: "Edita WhatsApp", edit: "Edita WhatsApp",
}, },
form: { form: {
name: "Nombre", name: "Nombre",
default: "Por Defecto", default: "Por Defecto",
}, },
buttons: { buttons: {
okAdd: "Agregar", okAdd: "Agregar",
okEdit: "Guardar", okEdit: "Guardar",
cancel: "Cancelar", cancel: "Cancelar",
}, },
success: "WhatsApp guardado satisfactoriamente.", success: "WhatsApp guardado satisfactoriamente.",
}, },
qrCode: { qrCode: {
message: "Lée el código QR para empezar la sesión.", message: "Lée el código QR para empezar la sesión.",
}, },
contacts: { contacts: {
title: "Contactos", title: "Contactos",
toasts: { toasts: {
deleted: "¡Contacto borrado satisfactoriamente!", deleted: "¡Contacto borrado satisfactoriamente!",
}, },
searchPlaceholder: "Buscar...", searchPlaceholder: "Buscar...",
confirmationModal: { confirmationModal: {
deleteTitle: "Borrar", deleteTitle: "Borrar",
importTitlte: "Importar contactos", importTitlte: "Importar contactos",
deleteMessage: deleteMessage:
"¿Estás seguro que deseas borrar este contacto? Todos los tickets relacionados se perderán.", "¿Estás seguro que deseas borrar este contacto? Todos los tickets relacionados se perderán.",
importMessage: importMessage:
"¿Quieres importar todos los contactos desde tu teléfono?", "¿Quieres importar todos los contactos desde tu teléfono?",
}, },
buttons: { buttons: {
import: "Importar Contactos", import: "Importar Contactos",
add: "Agregar Contacto", add: "Agregar Contacto",
}, },
table: { table: {
name: "Nombre", name: "Nombre",
whatsapp: "WhatsApp", whatsapp: "WhatsApp",
email: "Correo Electrónico", email: "Correo Electrónico",
actions: "Acciones", actions: "Acciones",
}, },
}, },
contactModal: { contactModal: {
title: { title: {
add: "Agregar contacto", add: "Agregar contacto",
edit: "Editar contacto", edit: "Editar contacto",
}, },
form: { form: {
mainInfo: "Detalles del contacto", mainInfo: "Detalles del contacto",
extraInfo: "Información adicional", extraInfo: "Información adicional",
name: "Nombre", name: "Nombre",
number: "Número de Whatsapp", number: "Número de Whatsapp",
email: "Correo Electrónico", email: "Correo Electrónico",
extraName: "Nombre del Campo", extraName: "Nombre del Campo",
extraValue: "Valor", extraValue: "Valor",
}, },
buttons: { buttons: {
addExtraInfo: "Agregar información", addExtraInfo: "Agregar información",
okAdd: "Agregar", okAdd: "Agregar",
okEdit: "Guardar", okEdit: "Guardar",
cancel: "Cancelar", cancel: "Cancelar",
}, },
success: "Contacto guardado satisfactoriamente.", success: "Contacto guardado satisfactoriamente.",
}, },
queueModal: { quickAnswersModal: {
title: { title: {
add: "Agregar cola", add: "Agregar respuesta rápida",
edit: "Editar cola", edit: "Editar respuesta rápida",
}, },
form: { form: {
name: "Nombre", shortcut: "Atajo",
color: "Color", message: "Respuesta rápida",
greetingMessage: "Mensaje de saludo", },
}, buttons: {
buttons: { okAdd: "Agregar",
okAdd: "Añadir", okEdit: "Guardar",
okEdit: "Ahorrar", cancel: "Cancelar",
cancel: "Cancelar", },
}, success: "Respuesta rápida guardada correctamente.",
}, },
userModal: { queueModal: {
title: { title: {
add: "Agregar usuario", add: "Agregar cola",
edit: "Editar usuario", edit: "Editar cola",
}, },
form: { form: {
name: "Nombre", name: "Nombre",
email: "Correo Electrónico", color: "Color",
password: "Contraseña", greetingMessage: "Mensaje de saludo",
profile: "Perfil", },
}, buttons: {
buttons: { okAdd: "Añadir",
okAdd: "Agregar", okEdit: "Ahorrar",
okEdit: "Guardar", cancel: "Cancelar",
cancel: "Cancelar", },
}, },
success: "Usuario guardado satisfactoriamente.", userModal: {
}, title: {
chat: { add: "Agregar usuario",
noTicketMessage: "Selecciona un ticket para empezar a chatear.", edit: "Editar usuario",
}, },
ticketsManager: { form: {
buttons: { name: "Nombre",
newTicket: "Nuevo", email: "Correo Electrónico",
}, password: "Contraseña",
}, profile: "Perfil",
ticketsQueueSelect: { },
placeholder: "Linhas", buttons: {
}, okAdd: "Agregar",
tickets: { okEdit: "Guardar",
toasts: { cancel: "Cancelar",
deleted: "El ticket en el que estabas ha sido borrado.", },
}, success: "Usuario guardado satisfactoriamente.",
notification: { },
message: "Mensaje de", chat: {
}, noTicketMessage: "Selecciona un ticket para empezar a chatear.",
tabs: { },
open: { title: "Bandeja" }, ticketsManager: {
closed: { title: "Resueltos" }, buttons: {
search: { title: "Buscar" }, newTicket: "Nuevo",
}, },
search: { },
placeholder: "Buscar tickets y mensajes.", ticketsQueueSelect: {
}, placeholder: "Linhas",
buttons: { },
showAll: "Todos", tickets: {
}, toasts: {
}, deleted: "El ticket en el que estabas ha sido borrado.",
transferTicketModal: { },
title: "Transferir Ticket", notification: {
fieldLabel: "Escriba para buscar usuarios", message: "Mensaje de",
noOptions: "No se encontraron usuarios con ese nombre", },
buttons: { tabs: {
ok: "Transferir", open: { title: "Bandeja" },
cancel: "Cancelar", closed: { title: "Resueltos" },
}, search: { title: "Buscar" },
}, },
ticketsList: { search: {
pendingHeader: "Cola", placeholder: "Buscar tickets y mensajes.",
assignedHeader: "Trabajando en", },
noTicketsTitle: "¡Nada acá!", buttons: {
noTicketsMessage: showAll: "Todos",
"No se encontraron tickets con este estado o término de búsqueda", },
buttons: { },
accept: "Acceptar", transferTicketModal: {
}, title: "Transferir Ticket",
}, fieldLabel: "Escriba para buscar usuarios",
newTicketModal: { noOptions: "No se encontraron usuarios con ese nombre",
title: "Crear Ticket", buttons: {
fieldLabel: "Escribe para buscar un contacto", ok: "Transferir",
add: "Añadir", cancel: "Cancelar",
buttons: { },
ok: "Guardar", },
cancel: "Cancelar", ticketsList: {
}, pendingHeader: "Cola",
}, assignedHeader: "Trabajando en",
mainDrawer: { noTicketsTitle: "¡Nada acá!",
listItems: { noTicketsMessage:
dashboard: "Dashboard", "No se encontraron tickets con este estado o término de búsqueda",
connections: "Conexiones", buttons: {
tickets: "Tickets", accept: "Acceptar",
contacts: "Contactos", },
queues: "Linhas", },
administration: "Administración", newTicketModal: {
users: "Usuarios", title: "Crear Ticket",
settings: "Configuración", fieldLabel: "Escribe para buscar un contacto",
}, add: "Añadir",
appBar: { buttons: {
user: { ok: "Guardar",
profile: "Perfil", cancel: "Cancelar",
logout: "Cerrar Sesión", },
}, },
}, mainDrawer: {
}, listItems: {
notifications: { dashboard: "Dashboard",
noTickets: "Sin notificaciones.", connections: "Conexiones",
}, tickets: "Tickets",
queues: { contacts: "Contactos",
title: "Linhas", quickAnswers: "Respuestas rápidas",
table: { queues: "Linhas",
name: "Nombre", administration: "Administración",
color: "Color", users: "Usuarios",
greeting: "Mensaje de saludo", settings: "Configuración",
actions: "Comportamiento", },
}, appBar: {
buttons: { user: {
add: "Agregar cola", profile: "Perfil",
}, logout: "Cerrar Sesión",
confirmationModal: { },
deleteTitle: "Eliminar", },
deleteMessage: },
"¿Estás seguro? ¡Esta acción no se puede revertir! Los tickets en esa cola seguirán existiendo, pero ya no tendrán ninguna cola asignada.", notifications: {
}, noTickets: "Sin notificaciones.",
}, },
queueSelect: { queues: {
inputLabel: "Linhas", title: "Linhas",
}, table: {
users: { name: "Nombre",
title: "Usuarios", color: "Color",
table: { greeting: "Mensaje de saludo",
name: "Nombre", actions: "Comportamiento",
email: "Correo Electrónico", },
profile: "Perfil", buttons: {
actions: "Acciones", add: "Agregar cola",
}, },
buttons: { confirmationModal: {
add: "Agregar usuario", deleteTitle: "Eliminar",
}, deleteMessage:
toasts: { "¿Estás seguro? ¡Esta acción no se puede revertir! Los tickets en esa cola seguirán existiendo, pero ya no tendrán ninguna cola asignada.",
deleted: "Usuario borrado satisfactoriamente.", },
}, },
confirmationModal: { queueSelect: {
deleteTitle: "Borrar", inputLabel: "Linhas",
deleteMessage: },
"Toda la información del usuario se perderá. Los tickets abiertos de los usuarios se moverán a la cola.", quickAnswers: {
}, title: "Respuestas rápidas",
}, table: {
settings: { shortcut: "Atajo",
success: "Configuración guardada satisfactoriamente.", message: "Respuesta rápida",
title: "Configuración", actions: "Acciones",
settings: { },
userCreation: { buttons: {
name: "Creación de usuarios", add: "Agregar respuesta rápida",
options: { },
enabled: "Habilitado", toasts: {
disabled: "Deshabilitado", deleted: "Respuesta rápida eliminada correctamente",
}, },
}, searchPlaceholder: "Buscar ...",
}, confirmationModal: {
}, deleteTitle:
messagesList: { "¿Está seguro de que desea eliminar esta respuesta rápida?",
header: { deleteMessage: "Esta acción no se puede deshacer.",
assignedTo: "Asignado a:", },
buttons: { },
return: "Devolver", users: {
resolve: "Resolver", title: "Usuarios",
reopen: "Reabrir", table: {
accept: "Aceptar", name: "Nombre",
}, email: "Correo Electrónico",
}, profile: "Perfil",
}, actions: "Acciones",
messagesInput: { },
placeholderOpen: "Escribe un mensaje", buttons: {
placeholderClosed: add: "Agregar usuario",
"Vuelva a abrir o acepte este ticket para enviar un mensaje.", },
signMessage: "Firmar", toasts: {
}, deleted: "Usuario borrado satisfactoriamente.",
contactDrawer: { },
header: "Detalles del contacto", confirmationModal: {
buttons: { deleteTitle: "Borrar",
edit: "Editar contacto", deleteMessage:
}, "Toda la información del usuario se perderá. Los tickets abiertos de los usuarios se moverán a la cola.",
extraInfo: "Otra información", },
}, },
ticketOptionsMenu: { settings: {
delete: "Borrar", success: "Configuración guardada satisfactoriamente.",
transfer: "Transferir", title: "Configuración",
confirmationModal: { settings: {
title: "¿Borrar ticket #", userCreation: {
titleFrom: "del contacto ", name: "Creación de usuarios",
message: options: {
"¡Atención! Todos los mensajes Todos los mensajes relacionados con el ticket se perderán.", enabled: "Habilitado",
}, disabled: "Deshabilitado",
buttons: { },
delete: "Borrar", },
cancel: "Cancelar", },
}, },
}, messagesList: {
confirmationModal: { header: {
buttons: { assignedTo: "Asignado a:",
confirm: "Ok", buttons: {
cancel: "Cancelar", return: "Devolver",
}, resolve: "Resolver",
}, reopen: "Reabrir",
messageOptionsMenu: { accept: "Aceptar",
delete: "Borrar", },
reply: "Responder", },
confirmationModal: { },
title: "¿Borrar mensaje?", messagesInput: {
message: "Esta acción no puede ser revertida.", placeholderOpen: "Escribe un mensaje",
}, placeholderClosed:
}, "Vuelva a abrir o acepte este ticket para enviar un mensaje.",
backendErrors: { signMessage: "Firmar",
ERR_NO_OTHER_WHATSAPP: },
"Debe haber al menos una conexión de WhatsApp predeterminada.", contactDrawer: {
ERR_NO_DEF_WAPP_FOUND: header: "Detalles del contacto",
"No se encontró WhatsApp predeterminado. Verifique la página de conexiones.", buttons: {
ERR_WAPP_NOT_INITIALIZED: edit: "Editar contacto",
"Esta sesión de WhatsApp no está inicializada. Verifique la página de conexiones.", },
ERR_WAPP_CHECK_CONTACT: extraInfo: "Otra información",
"No se pudo verificar el contacto de WhatsApp. Verifique la página de conexiones.", },
ERR_WAPP_INVALID_CONTACT: "Este no es un número de whatsapp válido.", ticketOptionsMenu: {
ERR_WAPP_DOWNLOAD_MEDIA: delete: "Borrar",
"No se pudieron descargar los medios de WhatsApp. Verifique la página de conexiones.", transfer: "Transferir",
ERR_INVALID_CREDENTIALS: "Error de autenticación. Vuelva a intentarlo.", confirmationModal: {
ERR_SENDING_WAPP_MSG: title: "¿Borrar ticket #",
"Error al enviar el mensaje de WhatsApp. Verifique la página de conexiones.", titleFrom: "del contacto ",
ERR_DELETE_WAPP_MSG: "No se pudo borrar el mensaje de WhatsApp.", message:
ERR_OTHER_OPEN_TICKET: "Ya hay un ticket abierto para este contacto.", "¡Atención! Todos los mensajes Todos los mensajes relacionados con el ticket se perderán.",
ERR_SESSION_EXPIRED: "Sesión caducada. Inicie sesión.", },
ERR_USER_CREATION_DISABLED: buttons: {
"La creación de usuarios fue deshabilitada por el administrador.", delete: "Borrar",
ERR_NO_PERMISSION: "No tienes permiso para acceder a este recurso.", cancel: "Cancelar",
ERR_DUPLICATED_CONTACT: "Ya existe un contacto con este número.", },
ERR_NO_SETTING_FOUND: },
"No se encontró ninguna configuración con este ID.", confirmationModal: {
ERR_NO_CONTACT_FOUND: "No se encontró ningún contacto con este ID.", buttons: {
ERR_NO_TICKET_FOUND: "No se encontró ningún ticket con este ID.", confirm: "Ok",
ERR_NO_USER_FOUND: "No se encontró ningún usuario con este ID.", cancel: "Cancelar",
ERR_NO_WAPP_FOUND: "No se encontró WhatsApp con este ID.", },
ERR_CREATING_MESSAGE: "Error al crear el mensaje en la base de datos.", },
ERR_CREATING_TICKET: "Error al crear el ticket en la base de datos.", messageOptionsMenu: {
ERR_FETCH_WAPP_MSG: delete: "Borrar",
"Error al obtener el mensaje en WhtasApp, tal vez sea demasiado antiguo.", reply: "Responder",
ERR_QUEUE_COLOR_ALREADY_EXISTS: confirmationModal: {
"Este color ya está en uso, elija otro.", title: "¿Borrar mensaje?",
ERR_WAPP_GREETING_REQUIRED: message: "Esta acción no puede ser revertida.",
"El mensaje de saludo es obligatorio cuando hay más de una cola.", },
}, },
}, backendErrors: {
}, ERR_NO_OTHER_WHATSAPP:
"Debe haber al menos una conexión de WhatsApp predeterminada.",
ERR_NO_DEF_WAPP_FOUND:
"No se encontró WhatsApp predeterminado. Verifique la página de conexiones.",
ERR_WAPP_NOT_INITIALIZED:
"Esta sesión de WhatsApp no está inicializada. Verifique la página de conexiones.",
ERR_WAPP_CHECK_CONTACT:
"No se pudo verificar el contacto de WhatsApp. Verifique la página de conexiones.",
ERR_WAPP_INVALID_CONTACT: "Este no es un número de whatsapp válido.",
ERR_WAPP_DOWNLOAD_MEDIA:
"No se pudieron descargar los medios de WhatsApp. Verifique la página de conexiones.",
ERR_INVALID_CREDENTIALS: "Error de autenticación. Vuelva a intentarlo.",
ERR_SENDING_WAPP_MSG:
"Error al enviar el mensaje de WhatsApp. Verifique la página de conexiones.",
ERR_DELETE_WAPP_MSG: "No se pudo borrar el mensaje de WhatsApp.",
ERR_OTHER_OPEN_TICKET: "Ya hay un ticket abierto para este contacto.",
ERR_SESSION_EXPIRED: "Sesión caducada. Inicie sesión.",
ERR_USER_CREATION_DISABLED:
"La creación de usuarios fue deshabilitada por el administrador.",
ERR_NO_PERMISSION: "No tienes permiso para acceder a este recurso.",
ERR_DUPLICATED_CONTACT: "Ya existe un contacto con este número.",
ERR_NO_SETTING_FOUND:
"No se encontró ninguna configuración con este ID.",
ERR_NO_CONTACT_FOUND: "No se encontró ningún contacto con este ID.",
ERR_NO_TICKET_FOUND: "No se encontró ningún ticket con este ID.",
ERR_NO_USER_FOUND: "No se encontró ningún usuario con este ID.",
ERR_NO_WAPP_FOUND: "No se encontró WhatsApp con este ID.",
ERR_CREATING_MESSAGE: "Error al crear el mensaje en la base de datos.",
ERR_CREATING_TICKET: "Error al crear el ticket en la base de datos.",
ERR_FETCH_WAPP_MSG:
"Error al obtener el mensaje en WhtasApp, tal vez sea demasiado antiguo.",
ERR_QUEUE_COLOR_ALREADY_EXISTS:
"Este color ya está en uso, elija otro.",
ERR_WAPP_GREETING_REQUIRED:
"El mensaje de saludo es obligatorio cuando hay más de una cola.",
},
},
},
}; };
export { messages }; export { messages };

View File

@@ -1,414 +1,451 @@
const messages = { const messages = {
pt: { pt: {
translations: { translations: {
signup: { signup: {
title: "Cadastre-se", title: "Cadastre-se",
toasts: { toasts: {
success: "Usuário criado com sucesso! Faça seu login!!!.", success: "Usuário criado com sucesso! Faça seu login!!!.",
fail: "Erro ao criar usuário. Verifique os dados informados.", fail: "Erro ao criar usuário. Verifique os dados informados.",
}, },
form: { form: {
name: "Nome", name: "Nome",
email: "Email", email: "Email",
password: "Senha", password: "Senha",
}, },
buttons: { buttons: {
submit: "Cadastrar", submit: "Cadastrar",
login: "Já tem uma conta? Entre!", login: "Já tem uma conta? Entre!",
}, },
}, },
login: { login: {
title: "Login", title: "Login",
form: { form: {
email: "Email", email: "Email",
password: "Senha", password: "Senha",
}, },
buttons: { buttons: {
submit: "Entrar", submit: "Entrar",
register: "Não tem um conta? Cadastre-se!", register: "Não tem um conta? Cadastre-se!",
}, },
}, },
auth: { auth: {
toasts: { toasts: {
success: "Login efetuado com sucesso!", success: "Login efetuado com sucesso!",
}, },
}, },
dashboard: { dashboard: {
charts: { charts: {
perDay: { perDay: {
title: "Tickets hoje: ", title: "Tickets hoje: ",
}, },
}, },
}, },
connections: { connections: {
title: "Conexões", title: "Conexões",
toasts: { toasts: {
deleted: "Conexão com o WhatsApp excluída com sucesso!", deleted: "Conexão com o WhatsApp excluída com sucesso!",
}, },
confirmationModal: { confirmationModal: {
deleteTitle: "Deletar", deleteTitle: "Deletar",
deleteMessage: "Você tem certeza? Essa ação não pode ser revertida.", deleteMessage: "Você tem certeza? Essa ação não pode ser revertida.",
disconnectTitle: "Desconectar", disconnectTitle: "Desconectar",
disconnectMessage: disconnectMessage:
"Tem certeza? Você precisará ler o QR Code novamente.", "Tem certeza? Você precisará ler o QR Code novamente.",
}, },
buttons: { buttons: {
add: "Adicionar WhatsApp", add: "Adicionar WhatsApp",
disconnect: "desconectar", disconnect: "desconectar",
tryAgain: "Tentar novamente", tryAgain: "Tentar novamente",
qrcode: "QR CODE", qrcode: "QR CODE",
newQr: "Novo QR CODE", newQr: "Novo QR CODE",
connecting: "Conectando", connecting: "Conectando",
}, },
toolTips: { toolTips: {
disconnected: { disconnected: {
title: "Falha ao iniciar sessão do WhatsApp", title: "Falha ao iniciar sessão do WhatsApp",
content: content:
"Certifique-se de que seu celular esteja conectado à internet e tente novamente, ou solicite um novo QR Code", "Certifique-se de que seu celular esteja conectado à internet e tente novamente, ou solicite um novo QR Code",
}, },
qrcode: { qrcode: {
title: "Esperando leitura do QR Code", title: "Esperando leitura do QR Code",
content: content:
"Clique no botão 'QR CODE' e leia o QR Code com o seu celular para iniciar a sessão", "Clique no botão 'QR CODE' e leia o QR Code com o seu celular para iniciar a sessão",
}, },
connected: { connected: {
title: "Conexão estabelecida!", title: "Conexão estabelecida!",
}, },
timeout: { timeout: {
title: "A conexão com o celular foi perdida", title: "A conexão com o celular foi perdida",
content: content:
"Certifique-se de que seu celular esteja conectado à internet e o WhatsApp esteja aberto, ou clique no botão 'Desconectar' para obter um novo QR Code", "Certifique-se de que seu celular esteja conectado à internet e o WhatsApp esteja aberto, ou clique no botão 'Desconectar' para obter um novo QR Code",
}, },
}, },
table: { table: {
name: "Nome", name: "Nome",
status: "Status", status: "Status",
lastUpdate: "Última atualização", lastUpdate: "Última atualização",
default: "Padrão", default: "Padrão",
actions: "Ações", actions: "Ações",
session: "Sessão", session: "Sessão",
}, },
}, },
whatsappModal: { whatsappModal: {
title: { title: {
add: "Adicionar WhatsApp", add: "Adicionar WhatsApp",
edit: "Editar WhatsApp", edit: "Editar WhatsApp",
}, },
form: { form: {
name: "Nome", name: "Nome",
default: "Padrão", default: "Padrão",
}, },
buttons: { buttons: {
okAdd: "Adicionar", okAdd: "Adicionar",
okEdit: "Salvar", okEdit: "Salvar",
cancel: "Cancelar", cancel: "Cancelar",
}, },
success: "WhatsApp salvo com sucesso.", success: "WhatsApp salvo com sucesso.",
}, },
qrCode: { qrCode: {
message: "Leia o QrCode para iniciar a sessão", message: "Leia o QrCode para iniciar a sessão",
}, },
contacts: { contacts: {
title: "Contatos", title: "Contatos",
toasts: { toasts: {
deleted: "Contato excluído com sucesso!", deleted: "Contato excluído com sucesso!",
}, },
searchPlaceholder: "Pesquisar...", searchPlaceholder: "Pesquisar...",
confirmationModal: { confirmationModal: {
deleteTitle: "Deletar ", deleteTitle: "Deletar ",
importTitlte: "Importar contatos", importTitlte: "Importar contatos",
deleteMessage: deleteMessage:
"Tem certeza que deseja deletar este contato? Todos os tickets relacionados serão perdidos.", "Tem certeza que deseja deletar este contato? Todos os tickets relacionados serão perdidos.",
importMessage: "Deseja importas todos os contatos do telefone?", importMessage: "Deseja importas todos os contatos do telefone?",
}, },
buttons: { buttons: {
import: "Importar Contatos", import: "Importar Contatos",
add: "Adicionar Contato", add: "Adicionar Contato",
}, },
table: { table: {
name: "Nome", name: "Nome",
whatsapp: "WhatsApp", whatsapp: "WhatsApp",
email: "Email", email: "Email",
actions: "Ações", actions: "Ações",
}, },
}, },
contactModal: { contactModal: {
title: { title: {
add: "Adicionar contato", add: "Adicionar contato",
edit: "Editar contato", edit: "Editar contato",
}, },
form: { form: {
mainInfo: "Dados do contato", mainInfo: "Dados do contato",
extraInfo: "Informações adicionais", extraInfo: "Informações adicionais",
name: "Nome", name: "Nome",
number: "Número do Whatsapp", number: "Número do Whatsapp",
email: "Email", email: "Email",
extraName: "Nome do campo", extraName: "Nome do campo",
extraValue: "Valor", extraValue: "Valor",
}, },
buttons: { buttons: {
addExtraInfo: "Adicionar informação", addExtraInfo: "Adicionar informação",
okAdd: "Adicionar", okAdd: "Adicionar",
okEdit: "Salvar", okEdit: "Salvar",
cancel: "Cancelar", cancel: "Cancelar",
}, },
success: "Contato salvo com sucesso.", success: "Contato salvo com sucesso.",
}, },
queueModal: { quickAnswersModal: {
title: { title: {
add: "Adicionar fila", add: "Adicionar Resposta Rápida",
edit: "Editar fila", edit: "Editar Resposta Rápida",
}, },
form: { form: {
name: "Nome", shortcut: "Atalho",
color: "Cor", message: "Resposta Rápida",
greetingMessage: "Mensagem de saudação", },
}, buttons: {
buttons: { okAdd: "Adicionar",
okAdd: "Adicionar", okEdit: "Salvar",
okEdit: "Salvar", cancel: "Cancelar",
cancel: "Cancelar", },
}, success: "Resposta Rápida salva com sucesso.",
}, },
userModal: { queueModal: {
title: { title: {
add: "Adicionar usuário", add: "Adicionar fila",
edit: "Editar usuário", edit: "Editar fila",
}, },
form: { form: {
name: "Nome", name: "Nome",
email: "Email", color: "Cor",
password: "Senha", greetingMessage: "Mensagem de saudação",
profile: "Perfil", },
}, buttons: {
buttons: { okAdd: "Adicionar",
okAdd: "Adicionar", okEdit: "Salvar",
okEdit: "Salvar", cancel: "Cancelar",
cancel: "Cancelar", },
}, },
success: "Usuário salvo com sucesso.", userModal: {
}, title: {
chat: { add: "Adicionar usuário",
noTicketMessage: "Selecione um ticket para começar a conversar.", edit: "Editar usuário",
}, },
ticketsManager: { form: {
buttons: { name: "Nome",
newTicket: "Novo", email: "Email",
}, password: "Senha",
}, profile: "Perfil",
ticketsQueueSelect: { },
placeholder: "Filas", buttons: {
}, okAdd: "Adicionar",
tickets: { okEdit: "Salvar",
toasts: { cancel: "Cancelar",
deleted: "O ticket que você estava foi deletado.", },
}, success: "Usuário salvo com sucesso.",
notification: { },
message: "Mensagem de", chat: {
}, noTicketMessage: "Selecione um ticket para começar a conversar.",
tabs: { },
open: { title: "Inbox" }, ticketsManager: {
closed: { title: "Resolvidos" }, buttons: {
search: { title: "Busca" }, newTicket: "Novo",
}, },
search: { },
placeholder: "Buscar tickets e mensagens", ticketsQueueSelect: {
}, placeholder: "Filas",
buttons: { },
showAll: "Todos", tickets: {
}, toasts: {
}, deleted: "O ticket que você estava foi deletado.",
transferTicketModal: { },
title: "Transferir Ticket", notification: {
fieldLabel: "Digite para buscar usuários", message: "Mensagem de",
noOptions: "Nenhum usuário encontrado com esse nome", },
buttons: { tabs: {
ok: "Transferir", open: { title: "Inbox" },
cancel: "Cancelar", closed: { title: "Resolvidos" },
}, search: { title: "Busca" },
}, },
ticketsList: { search: {
pendingHeader: "Aguardando", placeholder: "Buscar tickets e mensagens",
assignedHeader: "Atendendo", },
noTicketsTitle: "Nada aqui!", buttons: {
noTicketsMessage: showAll: "Todos",
"Nenhum ticket encontrado com esse status ou termo pesquisado", },
buttons: { },
accept: "Aceitar", transferTicketModal: {
}, title: "Transferir Ticket",
}, fieldLabel: "Digite para buscar usuários",
newTicketModal: { noOptions: "Nenhum usuário encontrado com esse nome",
title: "Criar Ticket", buttons: {
fieldLabel: "Digite para pesquisar o contato", ok: "Transferir",
add: "Adicionar", cancel: "Cancelar",
buttons: { },
ok: "Salvar", },
cancel: "Cancelar", ticketsList: {
}, pendingHeader: "Aguardando",
}, assignedHeader: "Atendendo",
mainDrawer: { noTicketsTitle: "Nada aqui!",
listItems: { noTicketsMessage:
dashboard: "Dashboard", "Nenhum ticket encontrado com esse status ou termo pesquisado",
connections: "Conexões", buttons: {
tickets: "Tickets", accept: "Aceitar",
contacts: "Contatos", },
queues: "Filas", },
administration: "Administração", newTicketModal: {
users: "Usuários", title: "Criar Ticket",
settings: "Configurações", fieldLabel: "Digite para pesquisar o contato",
}, add: "Adicionar",
appBar: { buttons: {
user: { ok: "Salvar",
profile: "Perfil", cancel: "Cancelar",
logout: "Sair", },
}, },
}, mainDrawer: {
}, listItems: {
notifications: { dashboard: "Dashboard",
noTickets: "Nenhuma notificação.", connections: "Conexões",
}, tickets: "Tickets",
queues: { contacts: "Contatos",
title: "Filas", quickAnswers: "Respostas Rápidas",
table: { queues: "Filas",
name: "Nome", administration: "Administração",
color: "Cor", users: "Usuários",
greeting: "Mensagem de saudação", settings: "Configurações",
actions: "Ações", },
}, appBar: {
buttons: { user: {
add: "Adicionar fila", profile: "Perfil",
}, logout: "Sair",
confirmationModal: { },
deleteTitle: "Excluir", },
deleteMessage: },
"Você tem certeza? Essa ação não pode ser revertida! Os tickets dessa fila continuarão existindo, mas não terão mais nenhuma fila atribuída.", notifications: {
}, noTickets: "Nenhuma notificação.",
}, },
queueSelect: { queues: {
inputLabel: "Filas", title: "Filas",
}, table: {
users: { name: "Nome",
title: "Usuários", color: "Cor",
table: { greeting: "Mensagem de saudação",
name: "Nome", actions: "Ações",
email: "Email", },
profile: "Perfil", buttons: {
actions: "Ações", add: "Adicionar fila",
}, },
buttons: { confirmationModal: {
add: "Adicionar usuário", deleteTitle: "Excluir",
}, deleteMessage:
toasts: { "Você tem certeza? Essa ação não pode ser revertida! Os tickets dessa fila continuarão existindo, mas não terão mais nenhuma fila atribuída.",
deleted: "Usuário excluído com sucesso.", },
}, },
confirmationModal: { queueSelect: {
deleteTitle: "Excluir", inputLabel: "Filas",
deleteMessage: },
"Todos os dados do usuário serão perdidos. Os tickets abertos deste usuário serão movidos para a fila.", quickAnswers: {
}, title: "Respostas Rápidas",
}, table: {
settings: { shortcut: "Atalho",
success: "Configurações salvas com sucesso.", message: "Resposta Rápida",
title: "Configurações", actions: "Ações",
settings: { },
userCreation: { buttons: {
name: "Criação de usuário", add: "Adicionar Resposta Rápida",
options: { },
enabled: "Ativado", toasts: {
disabled: "Desativado", deleted: "Resposta Rápida excluída com sucesso.",
}, },
}, searchPlaceholder: "Pesquisar...",
}, confirmationModal: {
}, deleteTitle:
messagesList: { "Você tem certeza que quer excluir esta Resposta Rápida: ",
header: { deleteMessage: "Esta ação não pode ser revertida.",
assignedTo: "Atribuído à:", },
buttons: { },
return: "Retornar", users: {
resolve: "Resolver", title: "Usuários",
reopen: "Reabrir", table: {
accept: "Aceitar", name: "Nome",
}, email: "Email",
}, profile: "Perfil",
}, actions: "Ações",
messagesInput: { },
placeholderOpen: "Digite uma mensagem", buttons: {
placeholderClosed: add: "Adicionar usuário",
"Reabra ou aceite esse ticket para enviar uma mensagem.", },
signMessage: "Assinar", toasts: {
}, deleted: "Usuário excluído com sucesso.",
contactDrawer: { },
header: "Dados do contato", confirmationModal: {
buttons: { deleteTitle: "Excluir",
edit: "Editar contato", deleteMessage:
}, "Todos os dados do usuário serão perdidos. Os tickets abertos deste usuário serão movidos para a fila.",
extraInfo: "Outras informações", },
}, },
ticketOptionsMenu: { settings: {
delete: "Deletar", success: "Configurações salvas com sucesso.",
transfer: "Transferir", title: "Configurações",
confirmationModal: { settings: {
title: "Deletar o ticket do contato", userCreation: {
message: name: "Criação de usuário",
"Atenção! Todas as mensagens relacionadas ao ticket serão perdidas.", options: {
}, enabled: "Ativado",
buttons: { disabled: "Desativado",
delete: "Excluir", },
cancel: "Cancelar", },
}, },
}, },
confirmationModal: { messagesList: {
buttons: { header: {
confirm: "Ok", assignedTo: "Atribuído à:",
cancel: "Cancelar", buttons: {
}, return: "Retornar",
}, resolve: "Resolver",
messageOptionsMenu: { reopen: "Reabrir",
delete: "Deletar", accept: "Aceitar",
reply: "Responder", },
confirmationModal: { },
title: "Apagar mensagem?", },
message: "Esta ação não pode ser revertida.", messagesInput: {
}, placeholderOpen: "Digite uma mensagem",
}, placeholderClosed:
backendErrors: { "Reabra ou aceite esse ticket para enviar uma mensagem.",
ERR_NO_OTHER_WHATSAPP: "Deve haver pelo menos um WhatsApp padrão.", signMessage: "Assinar",
ERR_NO_DEF_WAPP_FOUND: },
"Nenhum WhatsApp padrão encontrado. Verifique a página de conexões.", contactDrawer: {
ERR_WAPP_NOT_INITIALIZED: header: "Dados do contato",
"Esta sessão do WhatsApp não foi inicializada. Verifique a página de conexões.", buttons: {
ERR_WAPP_CHECK_CONTACT: edit: "Editar contato",
"Não foi possível verificar o contato do WhatsApp. Verifique a página de conexões", },
ERR_WAPP_INVALID_CONTACT: "Este não é um número de Whatsapp válido.", extraInfo: "Outras informações",
ERR_WAPP_DOWNLOAD_MEDIA: },
"Não foi possível baixar mídia do WhatsApp. Verifique a página de conexões.", ticketOptionsMenu: {
ERR_INVALID_CREDENTIALS: delete: "Deletar",
"Erro de autenticação. Por favor, tente novamente.", transfer: "Transferir",
ERR_SENDING_WAPP_MSG: confirmationModal: {
"Erro ao enviar mensagem do WhatsApp. Verifique a página de conexões.", title: "Deletar o ticket do contato",
ERR_DELETE_WAPP_MSG: "Não foi possível excluir a mensagem do WhatsApp.", message:
ERR_OTHER_OPEN_TICKET: "Já existe um tíquete aberto para este contato.", "Atenção! Todas as mensagens relacionadas ao ticket serão perdidas.",
ERR_SESSION_EXPIRED: "Sessão expirada. Por favor entre.", },
ERR_USER_CREATION_DISABLED: buttons: {
"A criação do usuário foi desabilitada pelo administrador.", delete: "Excluir",
ERR_NO_PERMISSION: "Você não tem permissão para acessar este recurso.", cancel: "Cancelar",
ERR_DUPLICATED_CONTACT: "Já existe um contato com este número.", },
ERR_NO_SETTING_FOUND: "Nenhuma configuração encontrada com este ID.", },
ERR_NO_CONTACT_FOUND: "Nenhum contato encontrado com este ID.", confirmationModal: {
ERR_NO_TICKET_FOUND: "Nenhum tíquete encontrado com este ID.", buttons: {
ERR_NO_USER_FOUND: "Nenhum usuário encontrado com este ID.", confirm: "Ok",
ERR_NO_WAPP_FOUND: "Nenhum WhatsApp encontrado com este ID.", cancel: "Cancelar",
ERR_CREATING_MESSAGE: "Erro ao criar mensagem no banco de dados.", },
ERR_CREATING_TICKET: "Erro ao criar tíquete no banco de dados.", },
ERR_FETCH_WAPP_MSG: messageOptionsMenu: {
"Erro ao buscar a mensagem no WhtasApp, talvez ela seja muito antiga.", delete: "Deletar",
ERR_QUEUE_COLOR_ALREADY_EXISTS: reply: "Responder",
"Esta cor já está em uso, escolha outra.", confirmationModal: {
ERR_WAPP_GREETING_REQUIRED: title: "Apagar mensagem?",
"A mensagem de saudação é obrigatório quando há mais de uma fila.", message: "Esta ação não pode ser revertida.",
}, },
}, },
}, backendErrors: {
ERR_NO_OTHER_WHATSAPP: "Deve haver pelo menos um WhatsApp padrão.",
ERR_NO_DEF_WAPP_FOUND:
"Nenhum WhatsApp padrão encontrado. Verifique a página de conexões.",
ERR_WAPP_NOT_INITIALIZED:
"Esta sessão do WhatsApp não foi inicializada. Verifique a página de conexões.",
ERR_WAPP_CHECK_CONTACT:
"Não foi possível verificar o contato do WhatsApp. Verifique a página de conexões",
ERR_WAPP_INVALID_CONTACT: "Este não é um número de Whatsapp válido.",
ERR_WAPP_DOWNLOAD_MEDIA:
"Não foi possível baixar mídia do WhatsApp. Verifique a página de conexões.",
ERR_INVALID_CREDENTIALS:
"Erro de autenticação. Por favor, tente novamente.",
ERR_SENDING_WAPP_MSG:
"Erro ao enviar mensagem do WhatsApp. Verifique a página de conexões.",
ERR_DELETE_WAPP_MSG: "Não foi possível excluir a mensagem do WhatsApp.",
ERR_OTHER_OPEN_TICKET: "Já existe um tíquete aberto para este contato.",
ERR_SESSION_EXPIRED: "Sessão expirada. Por favor entre.",
ERR_USER_CREATION_DISABLED:
"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_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.",
ERR_NO_USER_FOUND: "Nenhum usuário encontrado com este ID.",
ERR_NO_WAPP_FOUND: "Nenhum WhatsApp encontrado com este ID.",
ERR_CREATING_MESSAGE: "Erro ao criar mensagem no banco de dados.",
ERR_CREATING_TICKET: "Erro ao criar tíquete no banco de dados.",
ERR_FETCH_WAPP_MSG:
"Erro ao buscar a mensagem no WhtasApp, talvez ela seja muito antiga.",
ERR_QUEUE_COLOR_ALREADY_EXISTS:
"Esta cor já está em uso, escolha outra.",
ERR_WAPP_GREETING_REQUIRED:
"A mensagem de saudação é obrigatório quando há mais de uma fila.",
},
},
},
}; };
export { messages }; export { messages };