mirror of
https://github.com/cheveguerra/whaticket-community.git
synced 2026-04-17 19:37:02 +00:00
feat: start internatinalization
This commit is contained in:
5
frontend/.gitignore
vendored
5
frontend/.gitignore
vendored
@@ -5,6 +5,7 @@
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
@@ -22,6 +23,6 @@
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
yarn.lock
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"date-fns": "^2.14.0",
|
||||
"emoji-mart": "^3.0.0",
|
||||
"formik": "^2.1.5",
|
||||
"i18next": "^19.6.3",
|
||||
"i18next-browser-languagedetector": "^5.0.1",
|
||||
"mic-recorder-to-mp3": "^2.2.1",
|
||||
"qrcode.react": "^1.0.0",
|
||||
"react": "^16.13.1",
|
||||
|
||||
@@ -2,10 +2,11 @@ import React, { useState, useEffect } from "react";
|
||||
|
||||
import { Formik, FieldArray } from "formik";
|
||||
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import { green } from "@material-ui/core/colors";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
@@ -13,9 +14,8 @@ import Typography from "@material-ui/core/Typography";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
|
||||
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||
import { green } from "@material-ui/core/colors";
|
||||
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import { i18n } from "../../translate/i18n";
|
||||
|
||||
import api from "../../services/api";
|
||||
|
||||
@@ -121,14 +121,16 @@ const ContactModal = ({ open, onClose, contactId }) => {
|
||||
}) => (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
{contactId ? "Editar contato" : "Adicionar contato"}
|
||||
{contactId
|
||||
? `${i18n.t("contactModal.title.edit")}`
|
||||
: `${i18n.t("contactModal.title.add")}`}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
Dados do contato
|
||||
{i18n.t("contactModal.form.mainInfo")}
|
||||
</Typography>
|
||||
<TextField
|
||||
label="Nome"
|
||||
label={i18n.t("contactModal.form.name")}
|
||||
name="name"
|
||||
value={values.name || ""}
|
||||
onChange={handleChange}
|
||||
@@ -138,18 +140,18 @@ const ContactModal = ({ open, onClose, contactId }) => {
|
||||
className={classes.textField}
|
||||
/>
|
||||
<TextField
|
||||
label="Número do Whatsapp"
|
||||
label={i18n.t("contactModal.form.number")}
|
||||
name="number"
|
||||
value={values.number || ""}
|
||||
onChange={handleChange}
|
||||
placeholder="Ex: 5513912344321"
|
||||
placeholder="5513912344321"
|
||||
variant="outlined"
|
||||
margin="dense"
|
||||
required
|
||||
/>
|
||||
<div>
|
||||
<TextField
|
||||
label="Email"
|
||||
label={i18n.t("contactModal.form.email")}
|
||||
name="email"
|
||||
value={values.email || ""}
|
||||
onChange={handleChange}
|
||||
@@ -163,7 +165,7 @@ const ContactModal = ({ open, onClose, contactId }) => {
|
||||
style={{ marginBottom: 8, marginTop: 12 }}
|
||||
variant="subtitle1"
|
||||
>
|
||||
Informações adicionais
|
||||
{i18n.t("contactModal.form.extraInfo")}
|
||||
</Typography>
|
||||
|
||||
<FieldArray name="extraInfo">
|
||||
@@ -177,7 +179,7 @@ const ContactModal = ({ open, onClose, contactId }) => {
|
||||
key={`${index}-info`}
|
||||
>
|
||||
<TextField
|
||||
label="Nome do campo"
|
||||
label={i18n.t("contactModal.form.extraName")}
|
||||
name={`extraInfo[${index}].name`}
|
||||
value={info.name || ""}
|
||||
onChange={handleChange}
|
||||
@@ -187,7 +189,7 @@ const ContactModal = ({ open, onClose, contactId }) => {
|
||||
className={classes.textField}
|
||||
/>
|
||||
<TextField
|
||||
label="Valor"
|
||||
label={i18n.t("contactModal.form.extraValue")}
|
||||
name={`extraInfo[${index}].value`}
|
||||
value={info.value || ""}
|
||||
onChange={handleChange}
|
||||
@@ -211,7 +213,7 @@ const ContactModal = ({ open, onClose, contactId }) => {
|
||||
color="primary"
|
||||
onClick={() => push({ name: "", value: "" })}
|
||||
>
|
||||
+ Adicionar atributo
|
||||
{`+ ${i18n.t("contactModal.buttons.addExtraInfo")}`}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
@@ -225,7 +227,7 @@ const ContactModal = ({ open, onClose, contactId }) => {
|
||||
disabled={isSubmitting}
|
||||
variant="outlined"
|
||||
>
|
||||
Cancelar
|
||||
{i18n.t("contactModal.buttons.cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
@@ -234,7 +236,9 @@ const ContactModal = ({ open, onClose, contactId }) => {
|
||||
variant="contained"
|
||||
className={classes.btnWrapper}
|
||||
>
|
||||
{contactId ? "Salvar" : "Adicionar"}
|
||||
{contactId
|
||||
? `${i18n.t("contactModal.buttons.okEdit")}`
|
||||
: `${i18n.t("contactModal.buttons.okAdd")}`}
|
||||
{isSubmitting && (
|
||||
<CircularProgress
|
||||
size={24}
|
||||
|
||||
@@ -1,28 +1,16 @@
|
||||
import React from "react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
|
||||
import List from "@material-ui/core/List";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemIcon from "@material-ui/core/ListItemIcon";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
// import ListSubheader from "@material-ui/core/ListSubheader";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
|
||||
import DashboardIcon from "@material-ui/icons/Dashboard";
|
||||
import WhatsAppIcon from "@material-ui/icons/WhatsApp";
|
||||
import SyncAltIcon from "@material-ui/icons/SyncAlt";
|
||||
import ChatIcon from "@material-ui/icons/Chat";
|
||||
import LayersIcon from "@material-ui/icons/Layers";
|
||||
import ExpandLess from "@material-ui/icons/ExpandLess";
|
||||
import ExpandMore from "@material-ui/icons/ExpandMore";
|
||||
|
||||
import ContactPhoneIcon from "@material-ui/icons/ContactPhone";
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
nested: {
|
||||
paddingLeft: theme.spacing(4),
|
||||
},
|
||||
}));
|
||||
import { i18n } from "../../translate/i18n";
|
||||
|
||||
function ListItemLink(props) {
|
||||
const { icon, primary, to, className } = props;
|
||||
@@ -46,76 +34,27 @@ function ListItemLink(props) {
|
||||
}
|
||||
|
||||
const MainListItems = () => {
|
||||
const classes = useStyles();
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const handleClick = () => {
|
||||
setOpen(!open);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ListItemLink to="/" primary="Dashboard" icon={<DashboardIcon />} />
|
||||
<ListItemLink
|
||||
to="/whats-auth"
|
||||
primary={i18n.t("mainDrawer.listItems.connection")}
|
||||
icon={<SyncAltIcon />}
|
||||
/>
|
||||
<ListItemLink
|
||||
to="/chat"
|
||||
primary={i18n.t("mainDrawer.listItems.tickets")}
|
||||
icon={<WhatsAppIcon />}
|
||||
/>
|
||||
|
||||
<ListItem button onClick={handleClick}>
|
||||
<ListItemIcon>
|
||||
<WhatsAppIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="WhatsApp" />
|
||||
{open ? <ExpandLess /> : <ExpandMore />}
|
||||
</ListItem>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<List component="div" disablePadding>
|
||||
<ListItemLink
|
||||
className={classes.nested}
|
||||
to="/whats-auth"
|
||||
primary="Conexão"
|
||||
icon={<SyncAltIcon />}
|
||||
/>
|
||||
<ListItemLink
|
||||
className={classes.nested}
|
||||
to="/chat"
|
||||
primary="Chat"
|
||||
icon={<ChatIcon />}
|
||||
/>
|
||||
</List>
|
||||
</Collapse>
|
||||
<ListItemLink
|
||||
to="/contacts"
|
||||
primary="Contatos"
|
||||
primary={i18n.t("mainDrawer.listItems.contacts")}
|
||||
icon={<ContactPhoneIcon />}
|
||||
/>
|
||||
<ListItem button disabled>
|
||||
<ListItemIcon>
|
||||
<LayersIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Integrações" />
|
||||
</ListItem>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
// export const secondaryListItems = (
|
||||
// <div>
|
||||
// <ListSubheader inset>Saved reports</ListSubheader>
|
||||
// <ListItem button>
|
||||
// <ListItemIcon>
|
||||
// <AssignmentIcon />
|
||||
// </ListItemIcon>
|
||||
// <ListItemText primary="Current month" />
|
||||
// </ListItem>
|
||||
// <ListItem button>
|
||||
// <ListItemIcon>
|
||||
// <AssignmentIcon />
|
||||
// </ListItemIcon>
|
||||
// <ListItemText primary="Last quarter" />
|
||||
// </ListItem>
|
||||
// <ListItem button>
|
||||
// <ListItemIcon>
|
||||
// <AssignmentIcon />
|
||||
// </ListItemIcon>
|
||||
// <ListItemText primary="Year-end sale" />
|
||||
// </ListItem>
|
||||
// </div>
|
||||
// );
|
||||
|
||||
export default MainListItems;
|
||||
|
||||
@@ -221,8 +221,6 @@ const useStyles = makeStyles(theme => ({
|
||||
},
|
||||
}));
|
||||
|
||||
let socket;
|
||||
|
||||
const MessagesList = () => {
|
||||
const { ticketId } = useParams();
|
||||
const history = useHistory();
|
||||
@@ -272,17 +270,9 @@ const MessagesList = () => {
|
||||
}, [pageNumber, ticketId, history]);
|
||||
|
||||
useEffect(() => {
|
||||
socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
||||
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
||||
socket.emit("joinChatBox", ticketId, () => {});
|
||||
|
||||
return () => {
|
||||
socket.disconnect();
|
||||
setPageNumber(1);
|
||||
setMessagesList([]);
|
||||
};
|
||||
}, [ticketId]);
|
||||
|
||||
useEffect(() => {
|
||||
socket.on("appMessage", data => {
|
||||
if (loading) return;
|
||||
|
||||
@@ -300,7 +290,13 @@ const MessagesList = () => {
|
||||
setContact(data.contact);
|
||||
}
|
||||
});
|
||||
}, [loading]);
|
||||
|
||||
return () => {
|
||||
socket.disconnect();
|
||||
setPageNumber(1);
|
||||
setMessagesList([]);
|
||||
};
|
||||
}, [ticketId, loading]);
|
||||
|
||||
const loadMore = () => {
|
||||
setPageNumber(prevPageNumber => prevPageNumber + 1);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { green } from "@material-ui/core/colors";
|
||||
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
|
||||
import { i18n } from "../../translate/i18n";
|
||||
import api from "../../services/api";
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
@@ -101,7 +102,9 @@ const NewTicketModal = ({ modalOpen, onClose, contactId }) => {
|
||||
scroll="paper"
|
||||
>
|
||||
<form onSubmit={handleSaveTicket}>
|
||||
<DialogTitle id="form-dialog-title">Criar Ticket</DialogTitle>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
{i18n.t("newTicketModal.title")}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Autocomplete
|
||||
id="asynchronous-demo"
|
||||
@@ -115,7 +118,7 @@ const NewTicketModal = ({ modalOpen, onClose, contactId }) => {
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Digite para pesquisar o contato"
|
||||
label={i18n.t("newTicketModal.fieldLabel")}
|
||||
variant="outlined"
|
||||
required
|
||||
autoFocus
|
||||
@@ -143,7 +146,7 @@ const NewTicketModal = ({ modalOpen, onClose, contactId }) => {
|
||||
disabled={loading}
|
||||
variant="outlined"
|
||||
>
|
||||
Cancelar
|
||||
{i18n.t("newTicketModal.buttons.cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
@@ -152,7 +155,7 @@ const NewTicketModal = ({ modalOpen, onClose, contactId }) => {
|
||||
variant="contained"
|
||||
className={classes.btnWrapper}
|
||||
>
|
||||
Salvar
|
||||
{i18n.t("newTicketModal.buttons.ok")}
|
||||
{loading && (
|
||||
<CircularProgress
|
||||
size={24}
|
||||
|
||||
@@ -2,11 +2,13 @@ import React from "react";
|
||||
import QRCode from "qrcode.react";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
|
||||
import { i18n } from "../../translate/i18n";
|
||||
|
||||
const Qrcode = ({ qrCode }) => {
|
||||
return (
|
||||
<div>
|
||||
<Typography color="primary" gutterBottom>
|
||||
Leia o QrCode para iniciar a sessão
|
||||
{i18n.t("qrCode.message")}
|
||||
</Typography>
|
||||
<QRCode value={qrCode} size={256} />
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import React from "react";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
|
||||
import { i18n } from "../../translate/i18n";
|
||||
|
||||
const SessionInfo = ({ session }) => {
|
||||
console.log(session);
|
||||
return (
|
||||
<div>
|
||||
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
||||
{`Status: ${session.status}`}
|
||||
{`${i18n.t("sessionInfo.status")}${session.status}`}
|
||||
</Typography>
|
||||
<Typography component="p" variant="h6">
|
||||
{`Bateria: ${session.battery}%`}
|
||||
{`${i18n.t("sessionInfo.battery")}${session.battery}%`}
|
||||
</Typography>
|
||||
<Typography color="textSecondary">
|
||||
{`Carregando: ${session.plugged} `}
|
||||
{`${i18n.t("sessionInfo.charging")}${session.plugged} `}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -23,10 +23,9 @@ import NewTicketModal from "../NewTicketModal";
|
||||
import TicketsList from "../TicketsList";
|
||||
import TabPanel from "../TabPanel";
|
||||
|
||||
import { i18n } from "../../translate/i18n";
|
||||
import api from "../../services/api";
|
||||
|
||||
let socket;
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
ticketsWrapper: {
|
||||
position: "relative",
|
||||
@@ -209,15 +208,9 @@ const Tickets = () => {
|
||||
}, [searchParam, pageNumber, token, tab]);
|
||||
|
||||
useEffect(() => {
|
||||
socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
||||
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
||||
socket.emit("joinNotification");
|
||||
|
||||
return () => {
|
||||
socket.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
socket.on("ticket", data => {
|
||||
if (loading) return;
|
||||
|
||||
@@ -232,7 +225,7 @@ const Tickets = () => {
|
||||
if (data.action === "delete") {
|
||||
deleteTicket(data);
|
||||
if (ticketId && data.ticketId === +ticketId) {
|
||||
toast.warn("O ticket que você estava foi deletado.");
|
||||
toast.warn(i18n.t("tickets.toasts.deleted"));
|
||||
history.push("/chat");
|
||||
}
|
||||
}
|
||||
@@ -253,6 +246,10 @@ const Tickets = () => {
|
||||
showDesktopNotification(data);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.disconnect();
|
||||
};
|
||||
}, [history, ticketId, userId, loading]);
|
||||
|
||||
const loadMore = () => {
|
||||
@@ -262,16 +259,11 @@ const Tickets = () => {
|
||||
const updateTickets = ({ ticket }) => {
|
||||
setTickets(prevState => {
|
||||
const ticketIndex = prevState.findIndex(t => t.id === ticket.id);
|
||||
|
||||
if (prevState.length >= 20) {
|
||||
if (ticketIndex !== -1) {
|
||||
let aux = [...prevState];
|
||||
aux[ticketIndex] = ticket;
|
||||
aux.unshift(aux.splice(ticketIndex, 1)[0]);
|
||||
return aux;
|
||||
} else {
|
||||
return [ticket, ...prevState];
|
||||
}
|
||||
if (ticketIndex !== -1) {
|
||||
let aux = [...prevState];
|
||||
aux[ticketIndex] = ticket;
|
||||
aux.unshift(aux.splice(ticketIndex, 1)[0]);
|
||||
return aux;
|
||||
} else {
|
||||
return [ticket, ...prevState];
|
||||
}
|
||||
@@ -298,7 +290,10 @@ const Tickets = () => {
|
||||
icon: contact.profilePicUrl,
|
||||
tag: ticket.id,
|
||||
};
|
||||
let notification = new Notification(`Mensagem de ${contact.name}`, options);
|
||||
let notification = new Notification(
|
||||
`${i18n.t("tickets.notification.message")} ${contact.name}`,
|
||||
options
|
||||
);
|
||||
|
||||
notification.onclick = function (event) {
|
||||
event.preventDefault(); //
|
||||
@@ -396,19 +391,19 @@ const Tickets = () => {
|
||||
<Tab
|
||||
value={"open"}
|
||||
icon={<MoveToInboxIcon />}
|
||||
label="Inbox"
|
||||
label={i18n.t("tickets.tabs.open.title")}
|
||||
classes={{ root: classes.tab }}
|
||||
/>
|
||||
<Tab
|
||||
value={"closed"}
|
||||
icon={<CheckBoxIcon />}
|
||||
label="Resolvidos"
|
||||
label={i18n.t("tickets.tabs.closed.title")}
|
||||
classes={{ root: classes.tab }}
|
||||
/>
|
||||
<Tab
|
||||
value={"search"}
|
||||
icon={<SearchIcon />}
|
||||
label="Busca"
|
||||
label={i18n.t("tickets.tabs.search.title")}
|
||||
classes={{ root: classes.tab }}
|
||||
/>
|
||||
</Tabs>
|
||||
@@ -418,7 +413,7 @@ const Tickets = () => {
|
||||
<SearchIcon className={classes.searchIcon} />
|
||||
<InputBase
|
||||
className={classes.contactsSearchInput}
|
||||
placeholder="Pesquisar tickets e mensagens"
|
||||
placeholder={i18n.t("tickets.search.placeholder")}
|
||||
type="search"
|
||||
onChange={handleSearchContact}
|
||||
/>
|
||||
@@ -432,19 +427,19 @@ const Tickets = () => {
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
<div className={classes.ticketsListHeader}>
|
||||
Atendendo
|
||||
{i18n.t("tickets.tabs.open.assignedHeader")}
|
||||
<span className={classes.ticketsCount}>
|
||||
{countTickets("open", userId)}
|
||||
</span>
|
||||
<div className={classes.ticketsListActions}>
|
||||
<FormControlLabel
|
||||
label="Todos"
|
||||
label={i18n.t("tickets.buttons.showAll")}
|
||||
labelPlacement="start"
|
||||
control={
|
||||
<Switch
|
||||
size="small"
|
||||
checked={showAllTickets}
|
||||
onChange={e => setShowAllTickets(prevState => !prevState)}
|
||||
onChange={() => setShowAllTickets(prevState => !prevState)}
|
||||
name="showAllTickets"
|
||||
color="primary"
|
||||
/>
|
||||
@@ -467,8 +462,10 @@ const Tickets = () => {
|
||||
showAllTickets={showAllTickets}
|
||||
ticketId={ticketId}
|
||||
handleAcepptTicket={handleAcepptTicket}
|
||||
noTicketsTitle="Pronto pra mais?"
|
||||
noTicketsMessage="Aceite um ticket da fila para começar."
|
||||
noTicketsTitle={i18n.t("tickets.tabs.open.openNoTicketsTitle")}
|
||||
noTicketsMessage={i18n.t(
|
||||
"tickets.tabs.open.openNoTicketsMessage"
|
||||
)}
|
||||
status="open"
|
||||
userId={userId}
|
||||
/>
|
||||
@@ -482,7 +479,7 @@ const Tickets = () => {
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
<div className={classes.ticketsListHeader}>
|
||||
Aguardando
|
||||
{i18n.t("tickets.tabs.open.pendingHeader")}
|
||||
<span className={classes.ticketsCount}>
|
||||
{countTickets("pending", null)}
|
||||
</span>
|
||||
@@ -495,8 +492,10 @@ const Tickets = () => {
|
||||
showAllTickets={showAllTickets}
|
||||
ticketId={ticketId}
|
||||
handleAcepptTicket={handleAcepptTicket}
|
||||
noTicketsTitle="Tudo resolvido!"
|
||||
noTicketsMessage="Nenhum ticket pendente."
|
||||
noTicketsTitle={i18n.t("tickets.tabs.open.pendingNoTicketsTitle")}
|
||||
noTicketsMessage={i18n.t(
|
||||
"tickets.tabs.open.pendingNoTicketsMessage"
|
||||
)}
|
||||
status="pending"
|
||||
userId={null}
|
||||
/>
|
||||
@@ -541,8 +540,8 @@ const Tickets = () => {
|
||||
showAllTickets={showAllTickets}
|
||||
ticketId={ticketId}
|
||||
handleAcepptTicket={handleAcepptTicket}
|
||||
noTicketsTitle="Nada encontrado!"
|
||||
noTicketsMessage="Tente buscar por outro termo."
|
||||
noTicketsTitle={i18n.t("tickets.tabs.search.noTicketsTitle")}
|
||||
noTicketsMessage={i18n.t("tickets.tabs.search.noTicketsMessage")}
|
||||
status="all"
|
||||
/>
|
||||
{loading && <TicketsSkeleton />}
|
||||
|
||||
@@ -12,6 +12,8 @@ import Divider from "@material-ui/core/Divider";
|
||||
import Badge from "@material-ui/core/Badge";
|
||||
import Button from "@material-ui/core/Button";
|
||||
|
||||
import { i18n } from "../../translate/i18n";
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
ticket: {
|
||||
position: "relative",
|
||||
@@ -185,7 +187,7 @@ const TicketsList = ({
|
||||
className="hidden-button"
|
||||
onClick={e => handleAcepptTicket(ticket.id)}
|
||||
>
|
||||
Aceitar
|
||||
{i18n.t("ticketsList.buttons.accept")}
|
||||
</Button>
|
||||
) : null}
|
||||
</ListItem>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useHistory } from "react-router-dom";
|
||||
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
import { i18n } from "../../translate/i18n";
|
||||
import api from "../../services/api";
|
||||
|
||||
const useAuth = () => {
|
||||
@@ -23,7 +24,6 @@ const useAuth = () => {
|
||||
history.location.pathname === "/signup"
|
||||
) {
|
||||
setLoading(false);
|
||||
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -35,7 +35,7 @@ const useAuth = () => {
|
||||
} catch (err) {
|
||||
setLoading(false);
|
||||
setIsAuth(false);
|
||||
toast.error("Erro de autenticação. Por favor, faça login novamente");
|
||||
toast.error(i18n.t("auth.toasts.fail"));
|
||||
}
|
||||
};
|
||||
checkAuth();
|
||||
@@ -50,9 +50,10 @@ const useAuth = () => {
|
||||
localStorage.setItem("userId", res.data.userId);
|
||||
api.defaults.headers.Authorization = `Bearer ${res.data.token}`;
|
||||
setIsAuth(true);
|
||||
toast.success(i18n.t("auth.toasts.success"));
|
||||
history.push("/chat");
|
||||
} catch (err) {
|
||||
toast.error("Erro de autenticação. Verifique os dados de login");
|
||||
toast.error(i18n.t("auth.toasts.fail"));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import { makeStyles } from "@material-ui/core/styles";
|
||||
import Tickets from "../../components/Tickets/";
|
||||
import MessagesList from "../../components/MessagesList/";
|
||||
|
||||
import { i18n } from "../../translate/i18n";
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
chatContainer: {
|
||||
flex: 1,
|
||||
@@ -61,7 +63,7 @@ const Chat = () => {
|
||||
</>
|
||||
) : (
|
||||
<Paper square variant="outlined" className={classes.welcomeMsg}>
|
||||
<span>Selecione um contato para começar a conversar</span>
|
||||
<span>{i18n.t("chat.noTicketMessage")}</span>
|
||||
</Paper>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
@@ -28,7 +28,7 @@ import ContactsSekeleton from "../../components/ContactsSekeleton";
|
||||
import ContactModal from "../../components/ContactModal";
|
||||
import ConfirmationModal from "../../components/ConfirmationModal/";
|
||||
|
||||
let socket;
|
||||
import { i18n } from "../../translate/i18n";
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
mainContainer: {
|
||||
@@ -105,13 +105,7 @@ const Contacts = () => {
|
||||
}, [searchParam, page, rowsPerPage]);
|
||||
|
||||
useEffect(() => {
|
||||
socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
||||
return () => {
|
||||
socket.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
||||
socket.on("contact", data => {
|
||||
if ((data.action === "update" || data.action === "create") && !loading) {
|
||||
updateContacts(data.contact);
|
||||
@@ -121,6 +115,10 @@ const Contacts = () => {
|
||||
deleteContact(data.contactId);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.disconnect();
|
||||
};
|
||||
}, [loading]);
|
||||
|
||||
const updateContacts = contact => {
|
||||
@@ -206,8 +204,10 @@ const Contacts = () => {
|
||||
<ConfirmationModal
|
||||
title={
|
||||
deletingContact
|
||||
? `Deletar ${deletingContact.name}?`
|
||||
: `Importar contatos`
|
||||
? `${i18n.t("contacts.confirmationModal.deleteTitle")} ${
|
||||
deletingContact.name
|
||||
}?`
|
||||
: `${i18n.t("contacts.confirmationModal.importTitlte")}`
|
||||
}
|
||||
open={confirmOpen}
|
||||
setOpen={setConfirmOpen}
|
||||
@@ -218,17 +218,17 @@ const Contacts = () => {
|
||||
}
|
||||
>
|
||||
{deletingContact
|
||||
? "Tem certeza que deseja deletar este contato? Todos os tickets relacionados serão perdidos."
|
||||
: "Deseja importas todos os contatos do telefone? Essa função é experimental, você terá que recarregar a página após a importação "}
|
||||
? `${i18n.t("contacts.confirmationModal.deleteMessage")}`
|
||||
: `${i18n.t("contacts.confirmationModal.importMessage")}`}
|
||||
</ConfirmationModal>
|
||||
<div className={classes.contactsHeader}>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
Contatos
|
||||
{i18n.t("contacts.title")}
|
||||
</Typography>
|
||||
|
||||
<div className={classes.actionButtons}>
|
||||
<TextField
|
||||
placeholder="Pesquisar..."
|
||||
placeholder={i18n.t("contacts.searchPlaceholder")}
|
||||
type="search"
|
||||
value={searchParam}
|
||||
onChange={handleSearch}
|
||||
@@ -245,14 +245,14 @@ const Contacts = () => {
|
||||
color="primary"
|
||||
onClick={e => setConfirmOpen(true)}
|
||||
>
|
||||
Importar contatos
|
||||
{i18n.t("contacts.buttons.import")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleOpenContactModal}
|
||||
>
|
||||
Adicionar contato
|
||||
{i18n.t("contacts.buttons.add")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -261,10 +261,12 @@ const Contacts = () => {
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell padding="checkbox" />
|
||||
<TableCell>Nome</TableCell>
|
||||
<TableCell>Whatsapp</TableCell>
|
||||
<TableCell>Email</TableCell>
|
||||
<TableCell align="right">Ações</TableCell>
|
||||
<TableCell>{i18n.t("contacts.table.name")}</TableCell>
|
||||
<TableCell>{i18n.t("contacts.table.whatsapp")}</TableCell>
|
||||
<TableCell>{i18n.t("contacts.table.email")}</TableCell>
|
||||
<TableCell align="right">
|
||||
{i18n.t("contacts.table.actions")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
|
||||
6
frontend/src/pages/Dashboard/Chart.js
vendored
6
frontend/src/pages/Dashboard/Chart.js
vendored
@@ -11,6 +11,8 @@ import {
|
||||
} from "recharts";
|
||||
import { startOfHour, parseISO, format } from "date-fns";
|
||||
|
||||
import { i18n } from "../../translate/i18n";
|
||||
|
||||
import Title from "./Title";
|
||||
import api from "../../services/api";
|
||||
|
||||
@@ -69,7 +71,9 @@ const Chart = () => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title>{`Tickets hoje: ${tickets.length}`}</Title>
|
||||
<Title>{`${i18n.t("dashboard.charts.perDay.title")}${
|
||||
tickets.length
|
||||
}`}</Title>
|
||||
<ResponsiveContainer>
|
||||
<BarChart
|
||||
data={chartData}
|
||||
|
||||
@@ -5,8 +5,6 @@ import Avatar from "@material-ui/core/Avatar";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import CssBaseline from "@material-ui/core/CssBaseline";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
// import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
// import Checkbox from "@material-ui/core/Checkbox";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Box from "@material-ui/core/Box";
|
||||
@@ -15,13 +13,15 @@ import Typography from "@material-ui/core/Typography";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Container from "@material-ui/core/Container";
|
||||
|
||||
import { i18n } from "../../translate/i18n";
|
||||
|
||||
import { AuthContext } from "../../context/Auth/AuthContext";
|
||||
|
||||
const Copyright = () => {
|
||||
return (
|
||||
<Typography variant="body2" color="textSecondary" align="center">
|
||||
{"Copyright © "}
|
||||
<Link color="inherit" href="https://material-ui.com/">
|
||||
<Link color="inherit" href="https://economicros.com.br/">
|
||||
Canove
|
||||
</Link>{" "}
|
||||
{new Date().getFullYear()}
|
||||
@@ -50,7 +50,7 @@ const useStyles = makeStyles(theme => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const Login = ({ showToast }) => {
|
||||
const Login = () => {
|
||||
const classes = useStyles();
|
||||
|
||||
const [user, setUser] = useState({ email: "", password: "" });
|
||||
@@ -69,7 +69,7 @@ const Login = ({ showToast }) => {
|
||||
<LockOutlinedIcon />
|
||||
</Avatar>
|
||||
<Typography component="h1" variant="h5">
|
||||
Login
|
||||
{i18n.t("login.title")}
|
||||
</Typography>
|
||||
<form
|
||||
className={classes.form}
|
||||
@@ -82,7 +82,7 @@ const Login = ({ showToast }) => {
|
||||
required
|
||||
fullWidth
|
||||
id="email"
|
||||
label="Email"
|
||||
label={i18n.t("login.form.email")}
|
||||
name="email"
|
||||
value={user.email}
|
||||
onChange={handleChangeInput}
|
||||
@@ -95,17 +95,13 @@ const Login = ({ showToast }) => {
|
||||
required
|
||||
fullWidth
|
||||
name="password"
|
||||
label="Senha"
|
||||
label={i18n.t("login.form.password")}
|
||||
type="password"
|
||||
id="password"
|
||||
value={user.password}
|
||||
onChange={handleChangeInput}
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
{/* <FormControlLabel
|
||||
control={<Checkbox value="remember" color="primary" />}
|
||||
label="Lembrar"
|
||||
/> */}
|
||||
<Button
|
||||
type="submit"
|
||||
fullWidth
|
||||
@@ -113,14 +109,9 @@ const Login = ({ showToast }) => {
|
||||
color="primary"
|
||||
className={classes.submit}
|
||||
>
|
||||
Entrar
|
||||
{i18n.t("login.buttons.submit")}
|
||||
</Button>
|
||||
<Grid container>
|
||||
{/* <Grid item xs>
|
||||
<Link href="#" variant="body2">
|
||||
Forgot password?
|
||||
</Link>
|
||||
</Grid> */}
|
||||
<Grid item>
|
||||
<Link
|
||||
href="#"
|
||||
@@ -128,7 +119,7 @@ const Login = ({ showToast }) => {
|
||||
component={RouterLink}
|
||||
to="/signup"
|
||||
>
|
||||
{"Não tem uma conta? Cadastre-se!"}
|
||||
{i18n.t("login.buttons.register")}
|
||||
</Link>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -16,6 +16,8 @@ import Typography from "@material-ui/core/Typography";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Container from "@material-ui/core/Container";
|
||||
|
||||
import { i18n } from "../../translate/i18n";
|
||||
|
||||
import api from "../../services/api";
|
||||
|
||||
const Copyright = () => {
|
||||
@@ -65,10 +67,10 @@ const SignUp = () => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await api.post("/auth/signup", user);
|
||||
toast.success("Usuário criado com sucesso! Faça seu login.");
|
||||
toast.success(i18n.t("signup.toast.success"));
|
||||
history.push("/login");
|
||||
} catch (err) {
|
||||
toast.error("Erro ao criar usuário. Verifique os dados informados.");
|
||||
toast.error(i18n.t("signup.toast.fail"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -80,7 +82,7 @@ const SignUp = () => {
|
||||
<LockOutlinedIcon />
|
||||
</Avatar>
|
||||
<Typography component="h1" variant="h5">
|
||||
Cadastre-se
|
||||
{i18n.t("signup.title")}
|
||||
</Typography>
|
||||
<form className={classes.form} noValidate onSubmit={handleSignUp}>
|
||||
<Grid container spacing={2}>
|
||||
@@ -92,7 +94,7 @@ const SignUp = () => {
|
||||
required
|
||||
fullWidth
|
||||
id="name"
|
||||
label="Nome"
|
||||
label={i18n.t("signup.form.name")}
|
||||
value={user.name}
|
||||
onChange={handleChangeInput}
|
||||
autoFocus
|
||||
@@ -105,7 +107,7 @@ const SignUp = () => {
|
||||
required
|
||||
fullWidth
|
||||
id="email"
|
||||
label="Email"
|
||||
label={i18n.t("signup.form.email")}
|
||||
name="email"
|
||||
autoComplete="email"
|
||||
value={user.email}
|
||||
@@ -118,7 +120,7 @@ const SignUp = () => {
|
||||
required
|
||||
fullWidth
|
||||
name="password"
|
||||
label="Senha"
|
||||
label={i18n.t("signup.form.password")}
|
||||
type="password"
|
||||
id="password"
|
||||
autoComplete="current-password"
|
||||
@@ -134,12 +136,12 @@ const SignUp = () => {
|
||||
color="primary"
|
||||
className={classes.submit}
|
||||
>
|
||||
Cadastrar
|
||||
{i18n.t("signup.buttons.submit")}
|
||||
</Button>
|
||||
<Grid container justify="flex-end">
|
||||
<Grid item>
|
||||
<Link href="#" variant="body2" component={RouterLink} to="/login">
|
||||
Já tem uma conta? Entre!
|
||||
{i18n.t("signup.buttons.login")}
|
||||
</Link>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -10,8 +10,6 @@ import Paper from "@material-ui/core/Paper";
|
||||
import SessionInfo from "../../components/SessionInfo";
|
||||
import Qrcode from "../../components/Qrcode";
|
||||
|
||||
let socket;
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
display: "flex",
|
||||
@@ -55,14 +53,8 @@ const WhatsAuth = () => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
||||
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
||||
|
||||
return () => {
|
||||
socket.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
socket.on("qrcode", data => {
|
||||
if (data.action === "update") {
|
||||
setQrCode(data.qr);
|
||||
@@ -76,6 +68,10 @@ const WhatsAuth = () => {
|
||||
history.push("/chat");
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.disconnect();
|
||||
};
|
||||
}, [history]);
|
||||
|
||||
return (
|
||||
|
||||
14
frontend/src/translate/i18n.js
Normal file
14
frontend/src/translate/i18n.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import i18n from "i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
|
||||
import { messages } from "./languages";
|
||||
|
||||
i18n.use(LanguageDetector).init({
|
||||
debug: false,
|
||||
defaultNS: ["translations"],
|
||||
fallbackLng: "pt",
|
||||
ns: ["translations"],
|
||||
resources: messages,
|
||||
});
|
||||
|
||||
export { i18n };
|
||||
24
frontend/src/translate/languages/en.js
Normal file
24
frontend/src/translate/languages/en.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const messages = {
|
||||
en: {
|
||||
translations: {
|
||||
signup: {
|
||||
title: "Signup",
|
||||
toasts: {
|
||||
success: "User sucessfully registered. Login!",
|
||||
fail: "Error creating user account. Verify your data.",
|
||||
},
|
||||
form: {
|
||||
name: "Name",
|
||||
email: "Email",
|
||||
password: "Password",
|
||||
},
|
||||
buttons: {
|
||||
submit: "SIGNUP",
|
||||
login: "Already registred? Go to login!",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export { messages };
|
||||
9
frontend/src/translate/languages/index.js
Normal file
9
frontend/src/translate/languages/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { messages as portugueseMessages } from "./pt";
|
||||
import { messages as englishMessages } from "./en";
|
||||
|
||||
const messages = {
|
||||
...portugueseMessages,
|
||||
...englishMessages,
|
||||
};
|
||||
|
||||
export { messages };
|
||||
154
frontend/src/translate/languages/pt.js
Normal file
154
frontend/src/translate/languages/pt.js
Normal file
@@ -0,0 +1,154 @@
|
||||
const messages = {
|
||||
pt: {
|
||||
translations: {
|
||||
signup: {
|
||||
title: "Cadastre-se",
|
||||
toasts: {
|
||||
success: "Usuário criado com sucesso! Faça seu login!!!.",
|
||||
fail: "Erro ao criar usuário. Verifique os dados informados.",
|
||||
},
|
||||
form: {
|
||||
name: "Nome",
|
||||
email: "Email",
|
||||
password: "Senha",
|
||||
},
|
||||
buttons: {
|
||||
submit: "Cadastrar",
|
||||
login: "Já tem uma conta? Entre!",
|
||||
},
|
||||
},
|
||||
login: {
|
||||
title: "Login",
|
||||
form: {
|
||||
email: "Email",
|
||||
password: "Senha",
|
||||
},
|
||||
buttons: {
|
||||
submit: "Entrar",
|
||||
register: "Não tem um conta? Cadastre-se!",
|
||||
},
|
||||
},
|
||||
auth: {
|
||||
toasts: {
|
||||
success: "Login efetuado com sucesso!",
|
||||
fail: "Erro de autenticação. Por favor, faça login novamente",
|
||||
},
|
||||
},
|
||||
dashboard: {
|
||||
charts: {
|
||||
perDay: {
|
||||
title: "Tickets hoje: ",
|
||||
},
|
||||
},
|
||||
},
|
||||
sessionInfo: {
|
||||
status: "Status: ",
|
||||
battery: "Bateria: ",
|
||||
charging: "Carregando: ",
|
||||
},
|
||||
qrCode: {
|
||||
message: "Leia o QrCode para iniciar a sessão",
|
||||
},
|
||||
contacts: {
|
||||
title: "Contatos",
|
||||
searchPlaceholder: "Pesquisar...",
|
||||
confirmationModal: {
|
||||
deleteTitle: "Deletar ",
|
||||
importTitlte: "Importar contatos",
|
||||
deleteMessage:
|
||||
"Tem certeza que deseja deletar este contato? Todos os tickets relacionados serão perdidos.",
|
||||
importMessage:
|
||||
"Deseja importas todos os contatos do telefone? Essa função é experimental, você terá que recarregar a página após a importação.",
|
||||
},
|
||||
buttons: {
|
||||
import: "Importar Contatos",
|
||||
add: "Adicionar Contato",
|
||||
},
|
||||
table: {
|
||||
name: "Nome",
|
||||
whatsapp: "WhatsApp",
|
||||
email: "Email",
|
||||
actions: "Ações",
|
||||
},
|
||||
},
|
||||
contactModal: {
|
||||
title: {
|
||||
add: "Adicionar contato",
|
||||
edit: "Editar contato",
|
||||
},
|
||||
form: {
|
||||
mainInfo: "Dados do contato",
|
||||
extraInfo: "Informações adicionais",
|
||||
name: "Nome",
|
||||
number: "Número do Whatsapp",
|
||||
email: "Email",
|
||||
extraName: "Nome do campo",
|
||||
extraValue: "Valor",
|
||||
},
|
||||
buttons: {
|
||||
addExtraInfo: "Adicionar informação",
|
||||
okAdd: "Adicionar",
|
||||
okEdit: "Salvar",
|
||||
cancel: "Cancelar",
|
||||
},
|
||||
},
|
||||
chat: {
|
||||
noTicketMessage: "Selecione um ticket para começar a conversar.",
|
||||
},
|
||||
tickets: {
|
||||
toasts: {
|
||||
deleted: "O ticket que você estava foi deletado.",
|
||||
},
|
||||
notification: {
|
||||
message: "Mensagem de",
|
||||
},
|
||||
tabs: {
|
||||
open: {
|
||||
title: "Inbox",
|
||||
assignedHeader: "Atendendo",
|
||||
pendingHeader: "Aguardando",
|
||||
openNoTicketsTitle: "Pronto pra mais?",
|
||||
openNoTicketsMessage: "Aceite um ticket da fila para começar.",
|
||||
pendingNoTicketsTitle: "Tudo resolvido!",
|
||||
pendingNoTicketsMessage: "Nenhum ticket pendente.",
|
||||
},
|
||||
closed: { title: "Resolvidos" },
|
||||
search: {
|
||||
title: "Busca",
|
||||
noTicketsTitle: "Nada encontrado!",
|
||||
noTicketsMessage: "Tente pesquisar por outro termo.",
|
||||
},
|
||||
},
|
||||
search: {
|
||||
placeholder: "Pesquisar tickets e mensagens.",
|
||||
},
|
||||
buttons: {
|
||||
showAll: "Todos",
|
||||
},
|
||||
},
|
||||
ticketsList: {
|
||||
buttons: {
|
||||
accept: "Aceitar",
|
||||
},
|
||||
},
|
||||
newTicketModal: {
|
||||
title: "Criar Ticket",
|
||||
fieldLabel: "Digite para pesquisar o contato",
|
||||
buttons: {
|
||||
ok: "Salvar",
|
||||
cancel: "Cancelar",
|
||||
},
|
||||
},
|
||||
mainDrawer: {
|
||||
listItems: {
|
||||
dashboard: "Dashboard",
|
||||
connection: "Conexão",
|
||||
tickets: "Tickets",
|
||||
contacts: "Contatos",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export { messages };
|
||||
11768
frontend/yarn.lock
11768
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user