feat: added users page

This commit is contained in:
canove
2020-09-02 21:01:02 -03:00
parent e0b6e9ce3f
commit dfde175c07
33 changed files with 1121 additions and 659 deletions

View File

@@ -48,14 +48,7 @@ const useStyles = makeStyles(theme => ({
padding: "8px 0px 8px 8px",
height: "100%",
overflowY: "scroll",
"&::-webkit-scrollbar": {
width: "8px",
height: "8px",
},
"&::-webkit-scrollbar-thumb": {
boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)",
backgroundColor: "#e8e8e8",
},
...theme.scrollbarStyles,
},
contactAvatar: {
@@ -80,17 +73,6 @@ const useStyles = makeStyles(theme => ({
padding: 8,
display: "flex",
flexDirection: "column",
// overflowX: "scroll",
// flex: 1,
// "&::-webkit-scrollbar": {
// width: "8px",
// height: "8px",
// },
// "&::-webkit-scrollbar-thumb": {
// // borderRadius: "2px",
// boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)",
// backgroundColor: "#e8e8e8",
// },
},
contactExtraInfo: {
marginTop: 4,

View File

@@ -1,6 +1,7 @@
import React, { useState, useEffect } from "react";
import { Formik, FieldArray } from "formik";
import * as Yup from "yup";
import { Formik, FieldArray, Form, Field } from "formik";
import { makeStyles } from "@material-ui/core/styles";
import { green } from "@material-ui/core/colors";
@@ -52,6 +53,15 @@ const useStyles = makeStyles(theme => ({
},
}));
const ContactSchema = Yup.object().shape({
name: Yup.string()
.min(2, "Too Short!")
.max(50, "Too Long!")
.required("Required"),
number: Yup.string().min(8, "Too Short!").max(50, "Too Long!"),
email: Yup.string().email("Invalid email"),
});
const ContactModal = ({ open, onClose, contactId }) => {
const classes = useStyles();
@@ -86,7 +96,7 @@ const ContactModal = ({ open, onClose, contactId }) => {
await api.post("/contacts", values);
}
} catch (err) {
alert(err.response.data.error);
alert(JSON.stringify(err.response.data, null, 2));
console.log(err);
}
handleClose();
@@ -94,69 +104,57 @@ const ContactModal = ({ open, onClose, contactId }) => {
return (
<div className={classes.root}>
<Dialog
open={open}
onClose={handleClose}
maxWidth="lg"
scroll="paper"
className={classes.modal}
>
<Dialog open={open} onClose={handleClose} maxWidth="lg" scroll="paper">
<DialogTitle id="form-dialog-title">
{contactId
? `${i18n.t("contactModal.title.edit")}`
: `${i18n.t("contactModal.title.add")}`}
</DialogTitle>
<Formik
initialValues={contact}
enableReinitialize={true}
onSubmit={(values, { setSubmitting }) => {
validationSchema={ContactSchema}
onSubmit={(values, actions) => {
setTimeout(() => {
handleSaveContact(values);
setSubmitting(false);
actions.setSubmitting(false);
}, 400);
}}
>
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
}) => (
<form onSubmit={handleSubmit}>
<DialogTitle id="form-dialog-title">
{contactId
? `${i18n.t("contactModal.title.edit")}`
: `${i18n.t("contactModal.title.add")}`}
</DialogTitle>
{({ values, errors, touched, isSubmitting }) => (
<Form>
<DialogContent dividers>
<Typography variant="subtitle1" gutterBottom>
{i18n.t("contactModal.form.mainInfo")}
</Typography>
<TextField
<Field
as={TextField}
label={i18n.t("contactModal.form.name")}
name="name"
value={values.name || ""}
onChange={handleChange}
error={touched.name && Boolean(errors.name)}
helperText={touched.name && errors.name}
variant="outlined"
margin="dense"
required
className={classes.textField}
/>
<TextField
<Field
as={TextField}
label={i18n.t("contactModal.form.number")}
name="number"
value={values.number || ""}
onChange={handleChange}
error={touched.number && Boolean(errors.number)}
helperText={touched.number && errors.number}
placeholder="5513912344321"
variant="outlined"
margin="dense"
required
/>
<div>
<TextField
<Field
as={TextField}
label={i18n.t("contactModal.form.email")}
name="email"
value={values.email || ""}
onChange={handleChange}
placeholder="Endereço de Email"
error={touched.email && Boolean(errors.email)}
helperText={touched.email && errors.email}
placeholder="Email address"
fullWidth
margin="dense"
variant="outlined"
@@ -179,25 +177,21 @@ const ContactModal = ({ open, onClose, contactId }) => {
className={classes.extraAttr}
key={`${index}-info`}
>
<TextField
<Field
as={TextField}
label={i18n.t("contactModal.form.extraName")}
name={`extraInfo[${index}].name`}
value={info.name || ""}
onChange={handleChange}
variant="outlined"
margin="dense"
required
className={classes.textField}
/>
<TextField
<Field
as={TextField}
label={i18n.t("contactModal.form.extraValue")}
name={`extraInfo[${index}].value`}
value={info.value || ""}
onChange={handleChange}
variant="outlined"
margin="dense"
className={classes.textField}
required
/>
<IconButton
size="small"
@@ -248,7 +242,7 @@ const ContactModal = ({ open, onClose, contactId }) => {
)}
</Button>
</DialogActions>
</form>
</Form>
)}
</Formik>
</Dialog>

View File

@@ -1,163 +0,0 @@
import React from "react";
import TableCell from "@material-ui/core/TableCell";
import TableRow from "@material-ui/core/TableRow";
import Skeleton from "@material-ui/lab/Skeleton";
const ContactsSekeleton = () => {
return (
<>
<TableRow>
<TableCell style={{ paddingRight: 0 }}>
<Skeleton animation="wave" variant="circle" width={40} height={40} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={80} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={70} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={90} />
</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingRight: 0 }}>
<Skeleton animation="wave" variant="circle" width={40} height={40} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={55} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={60} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={100} />
</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingRight: 0 }}>
<Skeleton animation="wave" variant="circle" width={40} height={40} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={80} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={70} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={90} />
</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingRight: 0 }}>
<Skeleton animation="wave" variant="circle" width={40} height={40} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={55} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={60} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={100} />
</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingRight: 0 }}>
<Skeleton animation="wave" variant="circle" width={40} height={40} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={80} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={70} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={90} />
</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingRight: 0 }}>
<Skeleton animation="wave" variant="circle" width={40} height={40} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={55} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={60} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={100} />
</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingRight: 0 }}>
<Skeleton animation="wave" variant="circle" width={40} height={40} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={80} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={70} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={90} />
</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingRight: 0 }}>
<Skeleton animation="wave" variant="circle" width={40} height={40} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={55} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={60} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={100} />
</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingRight: 0 }}>
<Skeleton animation="wave" variant="circle" width={40} height={40} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={80} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={70} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={90} />
</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingRight: 0 }}>
<Skeleton animation="wave" variant="circle" width={40} height={40} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={55} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={60} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={100} />
</TableCell>
<TableCell align="right"></TableCell>
</TableRow>
</>
);
};
export default ContactsSekeleton;

View File

@@ -0,0 +1,31 @@
import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import Container from "@material-ui/core/Container";
const useStyles = makeStyles(theme => ({
mainContainer: {
flex: 1,
padding: theme.spacing(2),
height: `calc(100% - 48px)`,
},
contentWrapper: {
height: "100%",
overflowY: "hidden",
display: "flex",
flexDirection: "column",
},
}));
const MainContainer = ({ children }) => {
const classes = useStyles();
return (
<Container className={classes.mainContainer}>
<div className={classes.contentWrapper}>{children}</div>
</Container>
);
};
export default MainContainer;

View File

@@ -0,0 +1,19 @@
import React from "react";
import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles(theme => ({
contactsHeader: {
display: "flex",
alignItems: "center",
padding: "0px 6px 6px 6px",
},
}));
const MainHeader = ({ children }) => {
const classes = useStyles();
return <div className={classes.contactsHeader}>{children}</div>;
};
export default MainHeader;

View File

@@ -0,0 +1,21 @@
import React from "react";
import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles(theme => ({
MainHeaderButtonsWrapper: {
flex: "none",
marginLeft: "auto",
"& > *": {
margin: theme.spacing(1),
},
},
}));
const MainHeaderButtonsWrapper = ({ children }) => {
const classes = useStyles();
return <div className={classes.MainHeaderButtonsWrapper}>{children}</div>;
};
export default MainHeaderButtonsWrapper;

View File

@@ -101,15 +101,7 @@ const useStyles = makeStyles(theme => ({
padding: "20px 20px 20px 20px",
// scrollBehavior: "smooth",
overflowY: "scroll",
"&::-webkit-scrollbar": {
width: "8px",
height: "8px",
},
"&::-webkit-scrollbar-thumb": {
// borderRadius: "2px",
boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)",
backgroundColor: "#e8e8e8",
},
...theme.scrollbarStyles,
},
circleLoading: {

View File

@@ -24,14 +24,7 @@ const useStyles = makeStyles(theme => ({
tabContainer: {
overflowY: "auto",
maxHeight: 350,
"&::-webkit-scrollbar": {
width: "8px",
height: "8px",
},
"&::-webkit-scrollbar-thumb": {
boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)",
backgroundColor: "#e8e8e8",
},
...theme.scrollbarStyles,
},
popoverPaper: {
width: "100%",

View File

@@ -1,71 +0,0 @@
import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import FirstPageIcon from "@material-ui/icons/FirstPage";
import KeyboardArrowLeft from "@material-ui/icons/KeyboardArrowLeft";
import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight";
import LastPageIcon from "@material-ui/icons/LastPage";
import IconButton from "@material-ui/core/IconButton";
const useStyles = makeStyles(theme => ({
root: {
flexShrink: 0,
marginLeft: theme.spacing(2.5),
},
}));
const PaginationActions = ({ count, page, rowsPerPage, onChangePage }) => {
const classes = useStyles();
const handleFirstPageButtonClick = event => {
onChangePage(event, 0);
};
const handleBackButtonClick = event => {
onChangePage(event, page - 1);
};
const handleNextButtonClick = event => {
onChangePage(event, page + 1);
};
const handleLastPageButtonClick = event => {
onChangePage(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
};
return (
<div className={classes.root}>
<IconButton
onClick={handleFirstPageButtonClick}
disabled={page === 0}
aria-label="first page"
>
{<FirstPageIcon />}
</IconButton>
<IconButton
onClick={handleBackButtonClick}
disabled={page === 0}
aria-label="previous page"
>
{<KeyboardArrowLeft />}
</IconButton>
<IconButton
onClick={handleNextButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="next page"
>
{<KeyboardArrowRight />}
</IconButton>
<IconButton
onClick={handleLastPageButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="last page"
>
{<LastPageIcon />}
</IconButton>
</div>
);
};
export default PaginationActions;

View File

@@ -0,0 +1,78 @@
import React from "react";
import TableCell from "@material-ui/core/TableCell";
import TableRow from "@material-ui/core/TableRow";
import Skeleton from "@material-ui/lab/Skeleton";
const TableRowSkeleton = () => {
return (
<>
<TableRow>
<TableCell style={{ paddingRight: 0 }}>
<Skeleton animation="wave" variant="circle" width={40} height={40} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={80} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={70} />
</TableCell>
<TableCell></TableCell>
<TableCell align="right"></TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingRight: 0 }}>
<Skeleton animation="wave" variant="circle" width={40} height={40} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={80} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={70} />
</TableCell>
<TableCell></TableCell>
<TableCell align="right"></TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingRight: 0 }}>
<Skeleton animation="wave" variant="circle" width={40} height={40} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={80} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={70} />
</TableCell>
<TableCell></TableCell>
<TableCell align="right"></TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingRight: 0 }}>
<Skeleton animation="wave" variant="circle" width={40} height={40} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={80} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={70} />
</TableCell>
<TableCell></TableCell>
<TableCell align="right"></TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingRight: 0 }}>
<Skeleton animation="wave" variant="circle" width={40} height={40} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={80} />
</TableCell>
<TableCell>
<Skeleton animation="wave" height={20} width={70} />
</TableCell>
<TableCell></TableCell>
<TableCell align="right"></TableCell>
</TableRow>
</>
);
};
export default TableRowSkeleton;

View File

@@ -23,14 +23,7 @@ const useStyles = makeStyles(theme => ({
ticketsList: {
flex: 1,
overflowY: "scroll",
"&::-webkit-scrollbar": {
width: "8px",
height: "8px",
},
"&::-webkit-scrollbar-thumb": {
boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)",
backgroundColor: "#e8e8e8",
},
...theme.scrollbarStyles,
borderTop: "2px solid rgba(0, 0, 0, 0.12)",
},

View File

@@ -0,0 +1,10 @@
import React from "react";
import Typography from "@material-ui/core/Typography";
export default function Title(props) {
return (
<Typography variant="h5" color="primary" gutterBottom>
{props.children}
</Typography>
);
}

View File

@@ -0,0 +1,212 @@
import React, { useState, useEffect } from "react";
import * as Yup from "yup";
import { Formik, Form, Field } 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";
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";
const useStyles = makeStyles(theme => ({
root: {
display: "flex",
flexWrap: "wrap",
},
textField: {
// marginLeft: theme.spacing(1),
marginRight: theme.spacing(1),
// width: "25ch",
flex: 1,
},
btnWrapper: {
// margin: theme.spacing(1),
position: "relative",
},
buttonProgress: {
color: green[500],
position: "absolute",
top: "50%",
left: "50%",
marginTop: -12,
marginLeft: -12,
},
formControl: {
margin: theme.spacing(1),
minWidth: 120,
},
}));
const UserSchema = Yup.object().shape({
name: Yup.string()
.min(2, "Too Short!")
.max(50, "Too Long!")
.required("Required"),
password: Yup.string().min(5, "Too Short!").max(50, "Too Long!"),
email: Yup.string().email("Invalid email").required("Required"),
});
const UserModal = ({ open, onClose, userId }) => {
const classes = useStyles();
const initialState = {
name: "",
email: "",
password: "",
profile: "user",
};
const [user, setUser] = useState(initialState);
useEffect(() => {
const fetchUser = async () => {
if (!userId) return;
const { data } = await api.get(`/users/${userId}`);
setUser(prevState => {
return { ...prevState, ...data };
});
};
fetchUser();
}, [userId, open]);
const handleClose = () => {
onClose();
setUser(initialState);
};
const handleSaveUser = async values => {
try {
if (userId) {
await api.put(`/users/${userId}`, values);
} else {
await api.post("/users", values);
}
} catch (err) {
alert(JSON.stringify(err.response.data, null, 2));
console.log(err);
}
handleClose();
};
return (
<div className={classes.root}>
<Dialog open={open} onClose={handleClose} maxWidth="lg" scroll="paper">
<DialogTitle id="form-dialog-title">
{userId ? `Edit User` : `New User`}
</DialogTitle>
<Formik
initialValues={user}
enableReinitialize={true}
validationSchema={UserSchema}
onSubmit={(values, actions) => {
setTimeout(() => {
handleSaveUser(values);
actions.setSubmitting(false);
}, 400);
}}
>
{({ touched, errors, isSubmitting }) => (
<Form>
<DialogContent dividers>
<Field
as={TextField}
label="Name"
name="name"
error={touched.name && Boolean(errors.name)}
helperText={touched.name && errors.name}
variant="outlined"
margin="dense"
className={classes.textField}
/>
<Field
as={TextField}
label="Email"
name="email"
error={touched.email && Boolean(errors.email)}
helperText={touched.email && errors.email}
variant="outlined"
margin="dense"
/>
<div>
<Field
as={TextField}
label="New Password"
type="password"
name="password"
error={touched.password && Boolean(errors.password)}
helperText={touched.password && errors.password}
variant="outlined"
margin="dense"
/>
<FormControl
variant="outlined"
className={classes.formControl}
margin="dense"
>
<InputLabel id="profile-selection-input-label">
Profile
</InputLabel>
<Field
as={Select}
label="Profile"
name="profile"
labelId="profile-selection-label"
id="profile-selection"
required
>
<MenuItem value="admin">Admin</MenuItem>
<MenuItem value="user">User</MenuItem>
</Field>
</FormControl>
</div>
</DialogContent>
<DialogActions>
<Button
onClick={handleClose}
color="secondary"
disabled={isSubmitting}
variant="outlined"
>
Cancel
</Button>
<Button
type="submit"
color="primary"
disabled={isSubmitting}
variant="contained"
className={classes.btnWrapper}
>
{"Ok"}
{isSubmitting && (
<CircularProgress
size={24}
className={classes.buttonProgress}
/>
)}
</Button>
</DialogActions>
</Form>
)}
</Formik>
</Dialog>
</div>
);
};
export default UserModal;

View File

@@ -60,12 +60,12 @@ const MainListItems = () => {
<Divider />
<ListSubheader inset>Administration</ListSubheader>
<ListItemLink
to="/chat"
to="/users"
primary={i18n.t("mainDrawer.listItems.users")}
icon={<GroupIcon />}
/>
<ListItemLink
to="/chat"
to="/settings"
primary={i18n.t("mainDrawer.listItems.settings")}
icon={<SettingsIcon />}
/>