feat: start adding queues page

This commit is contained in:
canove
2021-01-10 16:18:23 -03:00
parent e34305b976
commit e64a77513f
14 changed files with 594 additions and 19 deletions

View File

@@ -19,6 +19,7 @@
"mic-recorder-to-mp3": "^2.2.2",
"qrcode.react": "^1.0.0",
"react": "^16.13.1",
"react-color": "^2.19.3",
"react-dom": "^16.13.1",
"react-modal-image": "^2.5.0",
"react-router-dom": "^5.2.0",

View File

@@ -0,0 +1,25 @@
import React, { useState } from "react";
import { GithubPicker } from "react-color";
const ColorPicker = ({ onChange, currentColor }) => {
const [color, setColor] = useState(currentColor);
const handleChange = color => {
setColor(color.hex);
};
return (
<div>
<GithubPicker
width={"100%"}
triangle="hide"
color={color}
onChange={handleChange}
onChangeComplete={color => onChange(color.hex)}
/>
</div>
);
};
export default ColorPicker;

View File

@@ -8,11 +8,11 @@ import Typography from "@material-ui/core/Typography";
import { i18n } from "../../translate/i18n";
const ConfirmationModal = ({ title, children, open, setOpen, onConfirm }) => {
const ConfirmationModal = ({ title, children, open, onClose, onConfirm }) => {
return (
<Dialog
open={open}
onClose={() => setOpen(false)}
onClose={() => onClose(false)}
aria-labelledby="confirm-dialog"
>
<DialogTitle id="confirm-dialog">{title}</DialogTitle>
@@ -22,7 +22,7 @@ const ConfirmationModal = ({ title, children, open, setOpen, onConfirm }) => {
<DialogActions>
<Button
variant="contained"
onClick={() => setOpen(false)}
onClick={() => onClose(false)}
color="default"
>
{i18n.t("confirmationModal.buttons.cancel")}
@@ -30,7 +30,7 @@ const ConfirmationModal = ({ title, children, open, setOpen, onConfirm }) => {
<Button
variant="contained"
onClick={() => {
setOpen(false);
onClose(false);
onConfirm();
}}
color="secondary"

View File

@@ -36,7 +36,7 @@ const MessageOptionsMenu = ({ message, menuOpen, handleClose, anchorEl }) => {
<ConfirmationModal
title={i18n.t("messageOptionsMenu.confirmationModal.title")}
open={confirmationOpen}
setOpen={setConfirmationOpen}
onClose={setConfirmationOpen}
onConfirm={handleDeleteMessage}
>
{i18n.t("messageOptionsMenu.confirmationModal.message")}

View File

@@ -0,0 +1,238 @@
import React, { useState, useEffect } from "react";
import * as Yup from "yup";
import { Formik, Form, Field } from "formik";
import { toast } from "react-toastify";
import { makeStyles } from "@material-ui/core/styles";
import { green } from "@material-ui/core/colors";
import Button from "@material-ui/core/Button";
import TextField from "@material-ui/core/TextField";
import Dialog from "@material-ui/core/Dialog";
import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogTitle from "@material-ui/core/DialogTitle";
import CircularProgress from "@material-ui/core/CircularProgress";
import Select from "@material-ui/core/Select";
import InputLabel from "@material-ui/core/InputLabel";
import MenuItem from "@material-ui/core/MenuItem";
import FormControl from "@material-ui/core/FormControl";
import { i18n } from "../../translate/i18n";
import api from "../../services/api";
import toastError from "../../errors/toastError";
import ColorPicker from "../ColorPicker";
import { InputAdornment } from "@material-ui/core";
const useStyles = makeStyles(theme => ({
root: {
display: "flex",
flexWrap: "wrap",
},
textField: {
marginRight: theme.spacing(1),
flex: 1,
},
btnWrapper: {
position: "relative",
},
buttonProgress: {
color: green[500],
position: "absolute",
top: "50%",
left: "50%",
marginTop: -12,
marginLeft: -12,
},
formControl: {
margin: theme.spacing(1),
minWidth: 120,
},
colorAdorment: {
width: 20,
height: 20,
},
}));
const QueueSchema = Yup.object().shape({
name: Yup.string()
.min(2, "Too Short!")
.max(50, "Too Long!")
.required("Required"),
color: Yup.string().min(3, "Too Short!").max(9, "Too Long!").required(),
greetingMessage: Yup.string(),
});
const QueueModal = ({ open, onClose, queueId }) => {
const classes = useStyles();
const initialState = {
name: "",
color: "",
greetingMessage: "",
};
const [queue, setQueue] = useState(initialState);
useEffect(() => {
(async () => {
if (!queueId) return;
try {
const { data } = await api.get(`/queue/${queueId}`);
setQueue(prevState => {
return { ...prevState, ...data };
});
} catch (err) {
toastError(err);
}
})();
return () => {
setQueue({
name: "",
color: "",
greetingMessage: "",
});
};
}, [queueId, open]);
const handleClose = () => {
onClose();
setQueue(initialState);
};
const handleSaveQueue = async values => {
try {
if (queueId) {
await api.put(`/queue/${queueId}`, values);
} else {
await api.post("/queue", values);
}
toast.success("Queue saved successfully");
handleClose();
} catch (err) {
toastError(err);
}
};
return (
<div className={classes.root}>
<Dialog open={open} onClose={handleClose} maxWidth="lg" scroll="paper">
<DialogTitle>
{queueId
? `${i18n.t("queueModal.title.edit")}`
: `${i18n.t("queueModal.title.add")}`}
</DialogTitle>
<Formik
initialValues={queue}
enableReinitialize={true}
validationSchema={QueueSchema}
onSubmit={(values, actions) => {
setTimeout(() => {
handleSaveQueue(values);
actions.setSubmitting(false);
}, 400);
}}
>
{({ touched, errors, isSubmitting, values }) => (
<Form>
<DialogContent dividers>
<Field
as={TextField}
label={i18n.t("queueModal.form.name")}
autoFocus
name="name"
error={touched.name && Boolean(errors.name)}
helperText={touched.name && errors.name}
variant="outlined"
margin="dense"
className={classes.textField}
/>
<Field
as={TextField}
label={i18n.t("queueModal.form.color")}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<div
style={{ backgroundColor: values.color }}
className={classes.colorAdorment}
></div>
</InputAdornment>
),
}}
name="color"
error={touched.color && Boolean(errors.color)}
helperText={touched.color && errors.color}
variant="outlined"
margin="dense"
/>
<ColorPicker
label={"Pick Color"}
currentColor={queue.color}
onChange={color => {
values.color = color;
setQueue(prevState => {
return { ...prevState, color };
});
}}
/>
<div>
<Field
as={TextField}
label={i18n.t("queueModal.form.greetingMessage")}
type="greetingMessage"
multiline
rows={5}
fullWidth
name="greetingMessage"
error={
touched.greetingMessage && Boolean(errors.greetingMessage)
}
helperText={
touched.greetingMessage && errors.greetingMessage
}
variant="outlined"
margin="dense"
/>
</div>
</DialogContent>
<DialogActions>
<Button
onClick={handleClose}
color="secondary"
disabled={isSubmitting}
variant="outlined"
>
{i18n.t("queueModal.buttons.cancel")}
</Button>
<Button
type="submit"
color="primary"
disabled={isSubmitting}
variant="contained"
className={classes.btnWrapper}
>
{queueId
? `${i18n.t("queueModal.buttons.okEdit")}`
: `${i18n.t("queueModal.buttons.okAdd")}`}
{isSubmitting && (
<CircularProgress
size={24}
className={classes.buttonProgress}
/>
)}
</Button>
</DialogActions>
</Form>
)}
</Formik>
</Dialog>
</div>
);
};
export default QueueModal;

View File

@@ -76,7 +76,7 @@ const TicketOptionsMenu = ({ ticket, menuOpen, handleClose, anchorEl }) => {
ticket.contact.name
}?`}
open={confirmationOpen}
setOpen={setConfirmationOpen}
onClose={setConfirmationOpen}
onConfirm={handleDeleteTicket}
>
{i18n.t("ticketOptionsMenu.confirmationModal.message")}

View File

@@ -7,12 +7,13 @@ import ListItemText from "@material-ui/core/ListItemText";
import ListSubheader from "@material-ui/core/ListSubheader";
import Divider from "@material-ui/core/Divider";
import { Badge } from "@material-ui/core";
import DashboardIcon from "@material-ui/icons/Dashboard";
import DashboardOutlinedIcon from "@material-ui/icons/DashboardOutlined";
import WhatsAppIcon from "@material-ui/icons/WhatsApp";
import SyncAltIcon from "@material-ui/icons/SyncAlt";
import SettingsIcon from "@material-ui/icons/Settings";
import GroupIcon from "@material-ui/icons/Group";
import ContactPhoneIcon from "@material-ui/icons/ContactPhone";
import SettingsOutlinedIcon from "@material-ui/icons/SettingsOutlined";
import PeopleAltOutlinedIcon from "@material-ui/icons/PeopleAltOutlined";
import ContactPhoneOutlinedIcon from "@material-ui/icons/ContactPhoneOutlined";
import AccountTreeOutlinedIcon from "@material-ui/icons/AccountTreeOutlined";
import { i18n } from "../translate/i18n";
import { WhatsAppsContext } from "../context/WhatsApp/WhatsAppsContext";
@@ -69,7 +70,11 @@ const MainListItems = () => {
return (
<div>
<ListItemLink to="/" primary="Dashboard" icon={<DashboardIcon />} />
<ListItemLink
to="/"
primary="Dashboard"
icon={<DashboardOutlinedIcon />}
/>
<ListItemLink
to="/connections"
primary={i18n.t("mainDrawer.listItems.connections")}
@@ -88,7 +93,7 @@ const MainListItems = () => {
<ListItemLink
to="/contacts"
primary={i18n.t("mainDrawer.listItems.contacts")}
icon={<ContactPhoneIcon />}
icon={<ContactPhoneOutlinedIcon />}
/>
{userProfile === "admin" && (
<>
@@ -99,12 +104,17 @@ const MainListItems = () => {
<ListItemLink
to="/users"
primary={i18n.t("mainDrawer.listItems.users")}
icon={<GroupIcon />}
icon={<PeopleAltOutlinedIcon />}
/>
<ListItemLink
to="/queues"
primary={i18n.t("mainDrawer.listItems.queues")}
icon={<AccountTreeOutlinedIcon />}
/>
<ListItemLink
to="/settings"
primary={i18n.t("mainDrawer.listItems.settings")}
icon={<SettingsIcon />}
icon={<SettingsOutlinedIcon />}
/>
</>
)}

View File

@@ -294,7 +294,7 @@ const Connections = () => {
<ConfirmationModal
title={confirmModalInfo.title}
open={confirmModalOpen}
setOpen={setConfirmModalOpen}
onClose={setConfirmModalOpen}
onConfirm={handleSubmitConfirmationModal}
>
{confirmModalInfo.message}

View File

@@ -229,7 +229,7 @@ const Contacts = () => {
: `${i18n.t("contacts.confirmationModal.importTitlte")}`
}
open={confirmOpen}
setOpen={setConfirmOpen}
onClose={setConfirmOpen}
onConfirm={e =>
deletingContact
? handleDeleteContact(deletingContact.id)

View File

@@ -1,7 +1,206 @@
import React from "react";
import React, { useEffect, useState } from "react";
import {
Button,
IconButton,
makeStyles,
Paper,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
Typography,
} from "@material-ui/core";
import MainContainer from "../../components/MainContainer";
import MainHeader from "../../components/MainHeader";
import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper";
import TableRowSkeleton from "../../components/TableRowSkeleton";
import Title from "../../components/Title";
import { i18n } from "../../translate/i18n";
import toastError from "../../errors/toastError";
import api from "../../services/api";
import { DeleteOutline, Edit } from "@material-ui/icons";
import QueueModal from "../../components/QueueModal";
import { toast } from "react-toastify";
import ConfirmationModal from "../../components/ConfirmationModal";
const useStyles = makeStyles(theme => ({
mainPaper: {
flex: 1,
padding: theme.spacing(1),
overflowY: "scroll",
...theme.scrollbarStyles,
},
customTableCell: {
display: "flex",
alignItems: "center",
justifyContent: "center",
},
}));
const Queues = () => {
return <div></div>;
const classes = useStyles();
const [queues, setQueue] = useState([]);
const [loading, setLoading] = useState(false);
const [queueModalOpen, setQueueModalOpen] = useState(false);
const [selectedQueue, setSelectedQueue] = useState(null);
const [confirmModalOpen, setConfirmModalOpen] = useState(false);
useEffect(() => {
(async () => {
setLoading(true);
try {
const { data } = await api.get("/queue");
setQueue(data);
setLoading(false);
} catch (err) {
toastError(err);
setLoading(false);
}
})();
}, []);
const handleOpenQueueModal = () => {
setQueueModalOpen(true);
setSelectedQueue(null);
};
const handleCloseQueueModal = () => {
setQueueModalOpen(false);
setSelectedQueue(null);
};
const handleEditQueue = queue => {
setSelectedQueue(queue);
setQueueModalOpen(true);
};
const handleCloseConfirmationModal = () => {
setConfirmModalOpen(false);
setSelectedQueue(null);
};
const handleDeleteQueue = async queueId => {
try {
await api.delete(`/queue/${queueId}`);
toast.success(i18n.t("Queue deleted successfully!"));
} catch (err) {
toastError(err);
}
setSelectedQueue(null);
};
return (
<MainContainer>
<ConfirmationModal
title={
selectedQueue &&
`${i18n.t("queues.confirmationModal.deleteTitle")} ${
selectedQueue.name
}?`
}
open={confirmModalOpen}
onClose={handleCloseConfirmationModal}
onConfirm={() => handleDeleteQueue(selectedQueue.id)}
>
{i18n.t("queues.confirmationModal.deleteMessage")}
</ConfirmationModal>
<QueueModal
open={queueModalOpen}
onClose={handleCloseQueueModal}
queueId={selectedQueue?.id}
/>
<MainHeader>
<Title>{i18n.t("queues.title")}</Title>
<MainHeaderButtonsWrapper>
<Button
variant="contained"
color="primary"
onClick={handleOpenQueueModal}
>
{i18n.t("queues.buttons.add")}
</Button>
</MainHeaderButtonsWrapper>
</MainHeader>
<Paper className={classes.mainPaper} variant="outlined">
<Table size="small">
<TableHead>
<TableRow>
<TableCell align="center">
{i18n.t("queues.table.name")}
</TableCell>
<TableCell align="center">
{i18n.t("queues.table.color")}
</TableCell>
<TableCell align="center">
{i18n.t("queues.table.greeting")}
</TableCell>
<TableCell align="center">
{i18n.t("queues.table.actions")}
</TableCell>
</TableRow>
</TableHead>
<TableBody>
<>
{queues.map(queue => (
<TableRow key={queue.id}>
<TableCell align="center">{queue.name}</TableCell>
<TableCell align="center">
<div className={classes.customTableCell}>
<span
style={{
backgroundColor: queue.color,
width: 60,
height: 20,
alignSelf: "center",
}}
/>
</div>
</TableCell>
<TableCell align="center">
<div className={classes.customTableCell}>
<Typography
style={{ width: 300, align: "center" }}
noWrap
variant="body2"
>
{queue.greetingMessage}
</Typography>
</div>
</TableCell>
<TableCell align="center">
<IconButton
size="small"
onClick={() => handleEditQueue(queue)}
>
<Edit />
</IconButton>
<IconButton
size="small"
onClick={() => {
setSelectedQueue(queue);
setConfirmModalOpen(true);
}}
>
<DeleteOutline />
</IconButton>
</TableCell>
</TableRow>
))}
{loading && <TableRowSkeleton />}
</>
</TableBody>
</Table>
</Paper>
</MainContainer>
);
};
export default Queues;

View File

@@ -193,7 +193,7 @@ const Users = () => {
}?`
}
open={confirmModalOpen}
setOpen={setConfirmModalOpen}
onClose={setConfirmModalOpen}
onConfirm={() => handleDeleteUser(deletingUser.id)}
>
{i18n.t("users.confirmationModal.deleteMessage")}

View File

@@ -153,6 +153,22 @@ const messages = {
},
success: "Contact saved successfully.",
},
queueModal: {
title: {
add: "Add queue",
edit: "Edit queue",
},
form: {
name: "Name",
color: "Color",
greetingMessage: "Greeting Message",
},
buttons: {
okAdd: "Add",
okEdit: "Save",
cancel: "Cancel",
},
},
userModal: {
title: {
add: "Add user",
@@ -226,6 +242,7 @@ const messages = {
connections: "Connections",
tickets: "Tickets",
contacts: "Contacts",
queues: "Queues",
administration: "Administration",
users: "Users",
settings: "Settings",
@@ -240,6 +257,23 @@ const messages = {
notifications: {
noTickets: "No notifications.",
},
queues: {
title: "Queues",
table: {
name: "Name",
color: "Color",
greeting: "Greeting message",
actions: "Actions",
},
buttons: {
add: "Add queue",
},
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.",
},
},
users: {
title: "Users",
table: {

View File

@@ -156,6 +156,22 @@ const messages = {
},
success: "Contacto guardado satisfactoriamente.",
},
queueModal: {
title: {
add: "Agregar cola",
edit: "Editar cola",
},
form: {
name: "Nombre",
color: "Color",
greetingMessage: "Mensaje de saludo",
},
buttons: {
okAdd: "Añadir",
okEdit: "Ahorrar",
cancel: "Cancelar",
},
},
userModal: {
title: {
add: "Agregar usuario",
@@ -230,6 +246,7 @@ const messages = {
connections: "Conexiones",
tickets: "Tickets",
contacts: "Contactos",
queues: "Linhas",
administration: "Administración",
users: "Usuarios",
settings: "Configuración",
@@ -244,6 +261,23 @@ const messages = {
notifications: {
noTickets: "Sin notificaciones.",
},
queues: {
title: "Linhas",
table: {
name: "Nombre",
color: "Color",
greeting: "Mensaje de saludo",
actions: "Comportamiento",
},
buttons: {
add: "Agregar cola",
},
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.",
},
},
users: {
title: "Usuarios",
table: {

View File

@@ -154,6 +154,22 @@ const messages = {
},
success: "Contato salvo com sucesso.",
},
queueModal: {
title: {
add: "Adicionar fila",
edit: "Editar fila",
},
form: {
name: "Nome",
color: "Cor",
greetingMessage: "Mensagem de saudação",
},
buttons: {
okAdd: "Adicionar",
okEdit: "Salvar",
cancel: "Cancelar",
},
},
userModal: {
title: {
add: "Adicionar usuário",
@@ -228,6 +244,7 @@ const messages = {
connections: "Conexões",
tickets: "Tickets",
contacts: "Contatos",
queues: "Filas",
administration: "Administração",
users: "Usuários",
settings: "Configurações",
@@ -242,6 +259,23 @@ const messages = {
notifications: {
noTickets: "Nenhuma notificação.",
},
queues: {
title: "Filas",
table: {
name: "Nome",
color: "Cor",
greeting: "Mensagem de saudação",
actions: "Ações",
},
buttons: {
add: "Adicionar fila",
},
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.",
},
},
users: {
title: "Usuários",
table: {