[WMS-55] Create/Edit User

This commit is contained in:
m0n02hz
2022-03-02 00:41:24 +05:30
parent 315113630e
commit c2b632e33b
45 changed files with 41607 additions and 190 deletions

View File

@@ -52,6 +52,7 @@ import PublicRoutes from 'routes/PublicRoutes';
import reduxStore from './redux/Store';
import { protectedRoutes as routes } from './routes/index';
import PrivateRoute from './routes/PrivateRoute';
import MDAlert from 'components/MDAlert';
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
@@ -168,6 +169,7 @@ export default function App() {
{getRoutes(routes)}
<Route path="*" element={<Navigate to="/" />} />
</Routes>
{/* <MDAlert dismissible><span>Submitted Successfully!</span></MDAlert> */}
</ThemeProvider>
<ToastContainer />
</PersistGate>

View File

@@ -0,0 +1,3 @@
.c-AllocationManager {
}

View File

@@ -0,0 +1,40 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Grid, Typography } from '@mui/material';
import MDBox from 'components/MDBox';
import TransferList from 'components/MDTransferList';
import './AllocationManager.component.scss';
const AllocationManager = props => {
const {boxStyleOverride, component, gridStyleOverride, list, md, title, variant, xs} = props;
return <Grid item className="c-AllocationManager" xs={xs || 12} md={md || 6} sx={gridStyleOverride}>
<MDBox
sx={boxStyleOverride || ({
backgroundColor: '#fff',
border: '1px solid #c2c2c2',
borderTop: '7px solid #007aff',
padding: '12px',
borderRadius: '4px'
})}
>
<Typography gutterBottom variant={variant || 'h6'} component={component || 'div'}>
{title}
</Typography>
<TransferList list={list || []} />
</MDBox>
</Grid>;
};
AllocationManager.propTypes = {
boxStyleOverride: PropTypes.object,
component: PropTypes.string,
gridStyleOverride: PropTypes.object,
list: PropTypes.array,
md: PropTypes.number,
styleOverride: PropTypes.object,
title: PropTypes.string,
variant: PropTypes.string,
xs: PropTypes.number
};
export default AllocationManager;

View File

@@ -0,0 +1,8 @@
import React from "react";
import AllocationManager from "./AllocationManager";
describe("AllocationManager", () => {
it("renders without error", () => {
});
});

View File

@@ -0,0 +1,3 @@
import AllocationManager from './AllocationManager.jsx';
export default AllocationManager;

View File

@@ -3,7 +3,7 @@ import { Table } from '@mui/material';
import { makeStyles } from '@mui/styles';
import PropTypes from 'prop-types';
export default function TblContainer({ children, backgroundColor, color }) {
export default function TblContainer({ children, backgroundColor, color, id }) {
const useStyles = makeStyles({
headDisplay: {
display: 'revert'
@@ -31,7 +31,7 @@ export default function TblContainer({ children, backgroundColor, color }) {
const classes = useStyles();
return (
<>
<Table className={classes.table}>{children}</Table>
<Table className={classes.table} id={id}>{children}</Table>
</>
);
}
@@ -39,5 +39,6 @@ export default function TblContainer({ children, backgroundColor, color }) {
TblContainer.propTypes = {
children: PropTypes.node.isRequired,
backgroundColor: PropTypes.string.isRequired,
color: PropTypes.string.isRequired
color: PropTypes.string.isRequired,
id: PropTypes.string
};

View File

@@ -10,11 +10,11 @@ const useStyles = makeStyles(() => ({
}
}));
export default function BasicTable({ children, headCells, backgroundColor, color }) {
export default function BasicTable({ children, headCells, id, backgroundColor, color }) {
const classes = useStyles();
return (
<>
<TblContainer backgroundColor={backgroundColor} color={color}>
<TblContainer id={id} backgroundColor={backgroundColor} color={color}>
<TableHead className={classes.headDisplay}>
<TableRow>
{headCells.map((headCell) => (
@@ -36,5 +36,6 @@ BasicTable.propTypes = {
})
).isRequired,
backgroundColor: PropTypes.string,
color: PropTypes.string
color: PropTypes.string,
id: PropTypes.string
};

View File

@@ -1,11 +1,12 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField';
import AdapterDateFns from '@mui/lab/AdapterDateFns';
import LocalizationProvider from '@mui/lab/LocalizationProvider';
import DateTimePicker from '@mui/lab/DateTimePicker';
export default function DateTimeInput() {
export default function DateTimeInput({ disabled }) {
const [value, setValue] = React.useState(new Date());
const handleChange = (newValue) => {
@@ -16,6 +17,7 @@ export default function DateTimeInput() {
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Stack spacing={3}>
<DateTimePicker
disabled={disabled}
label=""
value={value}
renderInput={(params) => <TextField {...params} />}
@@ -25,3 +27,7 @@ export default function DateTimeInput() {
</LocalizationProvider>
);
}
DateTimeInput.propTypes = {
disabled: PropTypes.bool
};

View File

@@ -27,7 +27,7 @@ export default styled(Button)(({ theme, ownerState }) => {
const { borderRadius } = borders;
const { colored } = boxShadows;
// styles for the button with variant="contained"
// styles for the button with variant='contained'
const containedStyles = () => {
// background color value
const backgroundValue = palette[color] ? palette[color].main : white.main;
@@ -97,7 +97,7 @@ export default styled(Button)(({ theme, ownerState }) => {
};
};
// styles for the button with variant="outlined"
// styles for the button with variant='outlined'
const outliedStyles = () => {
// background color value
const backgroundValue = color === 'white' ? rgba(white.main, 0.1) : transparent.main;
@@ -145,7 +145,7 @@ export default styled(Button)(({ theme, ownerState }) => {
};
};
// styles for the button with variant="gradient"
// styles for the button with variant='gradient'
const gradientStyles = () => {
// background value
const backgroundValue =
@@ -202,7 +202,7 @@ export default styled(Button)(({ theme, ownerState }) => {
};
};
// styles for the button with variant="text"
// styles for the button with variant='text'
const textStyles = () => {
// color value
const colorValue = palette[color] ? palette[color].main : white.main;

View File

@@ -18,7 +18,7 @@ const MDButton = forwardRef(
<MDButtonRoot
{...rest}
ref={ref}
color="primary"
color='primary'
variant={variant === 'gradient' ? 'contained' : variant}
size={size}
ownerState={{ color, variant, size, circular, iconOnly, darkMode }}

View File

@@ -1,4 +1,5 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import Grid from '@mui/material/Grid';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
@@ -45,11 +46,11 @@ function intersection(a, b) {
return a.filter((value) => b.indexOf(value) !== -1);
}
export default function TransferList() {
export default function TransferList({list}) {
const classes = useStyles();
const [checked, setChecked] = React.useState([]);
const [left, setLeft] = React.useState([0, 1, 2, 3]);
const [right, setRight] = React.useState([4, 5, 6, 7]);
const [left, setLeft] = React.useState(list || []);
const [right, setRight] = React.useState([]);
const leftChecked = intersection(checked, left);
const rightChecked = intersection(checked, right);
@@ -89,17 +90,18 @@ export default function TransferList() {
setRight([]);
};
const customList = (items) => (
const customList = items => (
<List component="div" role="list">
{items.map((value) => {
{items.map((item, key) => {
const value = item.name;
const labelId = `transfer-list-item-${value}-label`;
return (
<ListItem button key={value} role="listitem" onClick={handleToggle(value)}>
<ListItem button key={value + '-' + key} role="listitem" onClick={handleToggle(item)}>
<ListItemIcon className={classes.unsetwidth}>
<Checkbox
disableRipple
checked={checked.indexOf(value) !== -1}
checked={checked.indexOf(item) !== -1}
tabIndex={-1}
inputProps={{
'aria-labelledby': labelId
@@ -110,7 +112,7 @@ export default function TransferList() {
<ListItemText
id={labelId}
className={classes.label}
primary={`Warehouse ${value + 1}`}
primary={`${value}`}
/>
</ListItem>
);
@@ -126,7 +128,7 @@ export default function TransferList() {
Unassigned
</Typography>
<Divider className={classes.line} />
{customList(left)}
{left && customList(left) }
</Grid>
<Grid item md={2}>
<Grid container direction="column" alignItems="center">
@@ -177,8 +179,12 @@ export default function TransferList() {
Assigned
</Typography>
<Divider className={classes.line} />
{customList(right)}
{right && customList(right)}
</Grid>
</Grid>
);
}
TransferList.propTypes = {
list: PropTypes.array
};

View File

@@ -1,11 +1,24 @@
import { TextField, InputAdornment, SvgIcon } from '@mui/material';
import { makeStyles } from '@mui/styles';
import Search from 'assets/images/SearchIcon';
function SearchBar() {
const useStyles = makeStyles(() => ({
textField: {
'& legend': {
width: 0
}
}
}));
const classes = useStyles();
return (
<>
<TextField
fullWidth
className={classes.textField}
InputProps={{
startAdornment: (
<InputAdornment position="start">

View File

@@ -1,12 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
export default function TabPanel({ children, value, index }) {
return <div>{value === index && <h1>{children}</h1>}</div>;
export default function TabPanel({ children, className, value, index }) {
return <div className={className ? className : ''}>{value === index && <h1>{children}</h1>}</div>;
}
TabPanel.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
value: PropTypes.number,
index: PropTypes.number
};

View File

@@ -0,0 +1,3 @@
.c-Toggles {
}

View File

@@ -0,0 +1,57 @@
import React from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '@mui/styles';
import { Box, Grid } from '@mui/material';
import Typography from '@mui/material/Typography';
import MDBox from 'components/MDBox';
import Switch from 'components/Switch';
import MDTypography from 'components/MDTypography';
import './Toggles.component.scss';
const useStyles = makeStyles(() => ({
switchSpacer: {
margin: '0',
'& .MuiFormControlLabel-root': {
margin: '0'
},
'& .MuiSwitch-root': {
margin: '0'
}
}
}));
const Toggles = props => {
const {boxSx, md, title, toggles, typoComponent, typoSx, typoVariant, xs} = props;
const classes = useStyles();
const switchRenders = toggles => toggles && toggles.map((toggle, key) => {
return <MDBox key={key} display='flex' justifyContent='space-between' alignItems='center' lineHeight={1} className={classes.switchSpacer}
sx={{ marginBottom: '20px !important' }}>
<MDTypography variant='body2'>{toggle}</MDTypography>
<Switch checked />
</MDBox>;
});
return <Grid item id='c-Toggles' xs={xs || 12} md={md || 3} >
<MDBox sx={boxSx || {backgroundColor: '#fff', border: '1px solid #c2c2c2', borderTop: '7px solid #007aff', borderRadius: '4px'}}>
<Typography gutterBottom variant={typoVariant || 'h6'} component={typoComponent || 'div'}
sx={typoSx || {borderBottom: '1px solid #c2c2c2', padding: '10px 20px', marginBottom: '20px'}}>
{title || "Title"}
</Typography>
<Box sx={{ padding: ' 0px 20px' }}>{toggles && switchRenders(toggles)}</Box>
</MDBox>
</Grid>;
};
Toggles.propTypes = {
boxSx: PropTypes.object,
md: PropTypes.number,
title: PropTypes.string,
toggles: PropTypes.array,
typoComponent: PropTypes.string,
typoSx: PropTypes.object,
typoVariant: PropTypes.string,
xs: PropTypes.number
};
export default Toggles;

View File

@@ -0,0 +1,8 @@
import React from "react";
import Toggles from "./Toggles";
describe("Toggles", () => {
it("renders without error", () => {
});
});

View File

@@ -0,0 +1,3 @@
import Toggles from './Toggles.jsx';
export default Toggles;

9
src/components/index.js Executable file
View File

@@ -0,0 +1,9 @@
/* PLOP_INJECT_IMPORT */
import Toggles from './Toggles';
import AllocationManager from './AllocationManager';
export {
/* PLOP_INJECT_EXPORT */
Toggles,
AllocationManager
};

View File

@@ -1,10 +1,12 @@
export default {
LOGIN_USER: '/user/login',
GET_WAREHOUSE_DATA: '/warehouse/all?page=0&perPage=50',
CREATE_WAREHOUSE: '/warehouse/',
GET_USERS_DATA: '/user/all?page=0&perPage=10',
CREATE_USER: '/user/create',
GET_USERS_DATA: '/user/all?page=0&perPage=20',
GET_ROLES_DATA: '/user-role/all?page=0&perPage=10',
CREATE_WAREHOUSE: '/warehouse/',
GET_WAREHOUSE_DATA: '/warehouse/all?page=0&perPage=50',
LOGIN_USER: '/user/login',
GET_CHILDREN_FROM_PARENT: '/dashboard/get-children-from-parent',
LOCATION_DELETE: '/dashboard/delete-location',
ADD_NEW_ZONE: '/zone',
ADD_NEW_AREA: '/area',
ADD_NEW_ROW: '/row',

View File

@@ -16,6 +16,7 @@ Coded by www.creative-tim.com
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import App from 'App';
// Soft UI Context Provider

View File

@@ -27,7 +27,7 @@ import MDBox from 'components/MDBox';
// Material Dashboard 2 PRO React context
import { useMaterialUIController, setLayout } from 'context';
function DashboardLayout({ children }) {
function DashboardLayout({ children, className }) {
const [controller, dispatch] = useMaterialUIController();
const { miniSidenav } = controller;
const { pathname } = useLocation();
@@ -38,6 +38,8 @@ function DashboardLayout({ children }) {
return (
<MDBox
id="dashboard-layout"
className={className ? className : ''}
sx={({ breakpoints, transitions, functions: { pxToRem } }) => ({
p: 0,
position: 'relative',
@@ -58,7 +60,8 @@ function DashboardLayout({ children }) {
// Typechecking props for the DashboardLayout
DashboardLayout.propTypes = {
children: PropTypes.node.isRequired
children: PropTypes.node.isRequired,
className: PropTypes.string
};
export default DashboardLayout;

View File

@@ -0,0 +1,308 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation, useNavigate } from 'react-router-dom';
import { useFormik } from 'formik';
import { makeStyles } from '@mui/styles';
import { Box, Grid } from '@mui/material';
import OutlinedInput from '@mui/material/OutlinedInput';
import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';
import WarehouseActions, { WarehouseSelectors } from 'redux/WarehouseRedux';
import InventoryActions, { InventorySelectors } from 'redux/InventoryRedux';
import RolesActions, { RolesSelectors } from 'redux/RolesRedux';
import { AuthSelectors } from 'redux/AuthRedux';
import UsersActions from 'redux/UsersRedux';
import schema from 'services/ValidationServices';
import MDBox from 'components/MDBox';
import Switch from 'components/Switch';
import DashboardNavbar from 'components/DashboardNavbar';
import { AllocationManager, Toggles } from 'components';
import DashboardLayout from 'layouts/DashboardLayout';
import MDButton from 'components/Button';
import DateTimeInput from 'components/DateTimePicker';
import MDInput from 'components/MDInput';
import { API } from 'constant';
import UserIcon from 'assets/images/userIcon.png';
import EditIcon from 'assets/images/edit-icon.png';
const useStyles = makeStyles(() => ({
labelSize: {
fontSize: '16px',
letterSpacing: '0.01em',
color: '#000',
marginBottom: '4px'
},
boxWrap: {
backgroundColor: '#fff',
border: '1px solid #c2c2c2',
borderTop: '3px solid #007aff',
display: 'inline-block',
padding: '12px',
borderRadius: '4px'
},
noLegend: {
display: 'none'
},
fullWidth: {
width: '100%',
borderColor: '#d2d6da',
borderRadius: '0.375rem'
},
createEditUserGlobal: {
'& legend': {
width: 0
}
}
}));
/* eslint-disable complexity */
function CreateEditUser(props) {
const { context } = props;
const classes = useStyles();
const dispatch = useDispatch();
const navigate = useNavigate();
const roles = useSelector(RolesSelectors.getRolesDetail);
const warehouses = useSelector(WarehouseSelectors.getWarehouseDetail);
const inventories = useSelector(InventorySelectors.getInventoryDetail);
const currentUser = useSelector(AuthSelectors.getUser);
const location = useLocation();
const [editedUser, setEditedUser] = useState();
const [userRoles, setUserRoles] = useState([]);
const formik = useFormik({
initialValues: {
fullName: editedUser ? editedUser.fullName : '',
phoneNumber: editedUser ? editedUser.phoneNumber : '',
roles: userRoles,
permissions: {},
isActive: editedUser && editedUser.isActive !== undefined ? editedUser.isActive : true,
createdBy: context === 'new' ? currentUser ? currentUser.fullName : '' : editedUser ? editedUser.createdBy?.fullName : '',
createdOn: new Date(),
updatedBy: context === 'new' ? currentUser ? currentUser.fullName : '' : editedUser ? editedUser.updatedBy?.fullName : '',
updatedOn: new Date()
},
validationSchema: schema.createUser,
onSubmit: (values, { setSubmitting }) =>
{
const onValidationFailed = () => {
setSubmitting(false);
};
// const onSuccessfulSubmission = data => {
const onSuccessfulSubmission = () => {
navigate('/setup/users-access');
// TODO
// dispatchAlert
};
values.roles = values.roles && values.roles.length > 0 ? values.roles.map(role => role._id) : [];
dispatch(
UsersActions.createUserAction({
loader: 'loading-request',
slug: API.CREATE_USER,
method: 'post',
data: values,
onValidationFailed,
onSuccessfulSubmission
})
);
}
});
useEffect(() => {
if (context === 'edit') {
const editedUser = location?.state?.user;
if (!editedUser) {
navigate('/setup/users-access');
} else {
setEditedUser(editedUser);
setUserRoles(editedUser.role_name ? editedUser.role_name.split(',') : []);
}
}
}, []);
useEffect(() => {
dispatch(WarehouseActions.warehouseDataAction({loader: 'loading-request', slug: API.GET_WAREHOUSE_DATA,method: 'get'}));
dispatch(InventoryActions.getInventoryAction({loader: 'loading-request', slug: API.GET_INVENTORY,method: 'get'}));
!roles || roles.length === 0 && dispatch(RolesActions.getRolesAction({loader: 'loading-request', slug: API.GET_ROLES_DATA, method: 'get'}));
dispatch(RolesActions.getRolesAction({loader: 'loading-request', slug: API.GET_ROLES_DATA, method: 'get'}));
dispatch(PermissionActions.getRolesAction({loader: 'loading-request', slug: API.GET_ROLES_DATA, method: 'get'}));
fetchPermissions();
fetchActions();
}, []);
const handleChange = event => {
const value = typeof event.target.value === 'string' ? event.target.value?.split(',') : event.target.value;
setUserRoles(value);
formik.handleChange(event);
};
return (
<DashboardLayout className={classes.createEditUserGlobal}>
<DashboardNavbar />
<MDBox component='form' role='form' px={2} sx={{ backgroundColor: '#fff' }} onSubmit={formik.handleSubmit}>
<MDBox mx={4} sx={{ border: '1px solid #C4C4C4', borderRadius: '4px', padding: '30px' }}>
<MDBox sx={{ width: '50%', margin: 'auto' }}>
<MDBox sx={{ width: '120px', margin: 'auto', position: 'relative' }}>
<img src={UserIcon} alt="img" />
<MDBox sx={{ position: 'absolute', bottom: '0', right: '0', cursor: 'pointer' }}>
<img src={EditIcon} alt="img" />
</MDBox>
</MDBox>
<MDBox sx={{ marginBottom: '24px' }}>
<Box component="div" sx={{}} className={classes.labelSize}>
User Name
</Box>
<MDInput fullWidth disabled={context === 'edit'} value={formik.values.fullName} name="fullName" type="text"
variant="outlined" error={formik.touched.fullName && Boolean(formik.errors.fullName)}
helperText={formik.touched.fullName && formik.errors.fullName} onChange={formik.handleChange} />
</MDBox>
<MDBox sx={{ marginBottom: '24px' }}>
<Box component="div" sx={{}} className={classes.labelSize}>
Phone Number
</Box>
<MDInput fullWidth value={formik.values.phoneNumber} name="phoneNumber" type="text"
variant="outlined" error={formik.touched.phoneNumber && Boolean(formik.errors.phoneNumber)}
helperText={formik.touched.phoneNumber && formik.errors.phoneNumber} onChange={formik.handleChange} />
</MDBox>
<MDBox sx={{ marginBottom: '24px' }}>
<Box component="div" sx={{}} className={classes.labelSize}>
Role
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', columnGap: '20px' }}>
<Box sx={{ width: '70%' }}>
<Select
multiple
displayEmpty
name='roles'
value={userRoles}
input={<OutlinedInput />}
error={formik.touched.roles && Boolean(formik.errors.roles)}
renderValue={() => {
if (userRoles.length === 0) {
return 'Please select a role';
}
return userRoles.map(role => role.name).join(',');
}}
inputProps={{ 'aria-label': 'Without label' }}
sx={{
width: '100%'
}}
onChange={handleChange}
>
{roles && roles.map((role, key) => <MenuItem key={key} value={role}>{role.name}</MenuItem>)}
</Select>
</Box>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: '30%',
border: '1px solid #C4C4C4',
borderRadius: '4px'
}}
>
<Box
component="div"
sx={{
fontSize: '16px',
lineHeight: '20px',
letterSpacing: '0.01em',
textTransform: 'capitalize',
color: '#000',
marginLeft: '10px'
}}
>
Access
</Box>
<Box
sx={{
display: 'flex',
alignItems: 'center',
position: 'relative',
left: '20px'
}}
>
<Switch checked={formik.values.isActive} />
</Box>
</Box>
</Box>
</MDBox>
<Grid container spacing={2} className={classes.margin}>
<Grid item xs={12}>
<Grid container spacing={2} className={classes.margin}>
<Grid item xs={6}>
<Box component="div" className={classes.labelSize}>
Created By
</Box>
<MDInput fullWidth disabled name="warehousename" type="text" value={currentUser ? currentUser.fullName : ''} variant="outlined" />
</Grid>
<Grid item xs={6}>
<Box component="div" className={classes.labelSize}>
Date &amp; Time
</Box>
<DateTimeInput disabled />
</Grid>
<Grid item xs={6}>
<Box component="div" className={classes.labelSize}>
Last Updated by
</Box>
<MDInput fullWidth disabled name="warehousename" type="text" value={currentUser ? currentUser.fullName : ''} variant="outlined" />
</Grid>
<Grid item xs={6}>
<Box component="div" className={classes.labelSize}>
Date &amp; Time
</Box>
<DateTimeInput disabled />
</Grid>
</Grid>
</Grid>
</Grid>
</MDBox>
</MDBox>
<Grid container spacing={4} sx={{ marginTop: '-6px' }}>
<AllocationManager gridStyleOverride={{ paddingLeft: '4rem !important' }} list={warehouses} title='Warehouse' />
<AllocationManager gridStyleOverride={{ paddingRight: '2rem' }} list={inventories} title='Inventory' />
</Grid>
<Grid container spacing={2} sx={{ marginTop: '12px', paddingLeft: '2rem' }}>
<Toggles title='Actions' />
<Toggles title='Application' />
</Grid>
<MDBox
display="flex"
justifyContent="center"
alignItems="center"
lineHeight={1}
sx={{ marginBottom: '15px', marginTop: '45px', paddingBottom: '30px' }}
>
<MDButton
size="medium"
color="error"
variant="outlined"
type="button"
sx={{ marginRight: '15px' }}
>
Cancel
</MDButton>
<MDButton size="medium" color="primary" variant="contained" type="submit">
{context === 'new' ? 'Create' : 'Save'}
</MDButton>
</MDBox>
</MDBox>
</DashboardLayout>
);
}
CreateEditUser.propTypes = {
context: PropTypes.string
};
export default CreateEditUser;

View File

@@ -28,8 +28,16 @@ const useStyles = makeStyles((theme) => ({
color: theme.palette.primary.light,
marginRight: '8px'
},
statusActive: {
color: 'green'
},
statusInactive: {
color: 'red'
},
margin: {
marginBottom: '20px'
marginBottom: '20px',
borderTop: '1px solid #ddd',
borderBottom: '1px solid #ddd'
},
wrap: {
display: 'flex'
@@ -41,30 +49,25 @@ const useStyles = makeStyles((theme) => ({
display: 'flex',
alignItems: 'center'
},
radialBorder: {
overflow: 'hidden',
borderRadius: '0.5rem'
},
tabs: {
borderRadius: 0,
'& .MuiButtonBase-root.MuiTab-root': {
padding: '12px 0px',
borderRadius: '0px'
borderRadius: '0px',
fontWeight: 'bold',
backgroundColor: '#eee',
border: '1px solid #ddd'
},
'& .Mui-selected': {
'& .MuiButtonBase-root.MuiTab-root.Mui-selected': {
backgroundColor: '#017AFF',
color: 'white'
color: 'white !important'
}
}
}));
const userHeadCells = [
{ id: 'fullName', label: 'Name' },
{ id: 'role_name', label: 'Roles' },
{ id: 'updated_at', label: 'Updated at' },
{ id: 'created_at', label: 'Created by and at' }
];
const rolesHeadCells = [
{ id: 'role', label: 'Role' },
{ id: 'permissions', label: 'Permissions' },
{ id: 'status', label: 'Status' }
];
// const permissionsHeadCells = [
// { id: 'permission', label: 'Permission' },
// { id: 'warehouse', label: 'Warehouse' },
@@ -84,6 +87,26 @@ function UserAccessScreen() {
const [rolesRecords, setRoleRecords] = useState([]);
const navigate = useNavigate();
const userHeadCells = [
{ id: 'full_name', label: 'User Name', isEditAnchor: true, value: record => record.fullName },
{ id: 'phone_number', label: 'Phone Number', value: record => record.phoneNumber },
{ id: 'role_name', label: 'Roles', value: record => record.role_name },
{ id: 'updated_by_at', label: 'Last Updated By & Date', value: record => `${record.updatedBy?.fullName} | ${moment(record.updatedAt).format('D/M/YYYY h:m:s A')}` },
{ id: 'created_by_at', label: 'Created By & Date', value: record => `${record.createdBy?.fullName} | ${moment(record.createdAt).format('D/M/YYYY h:m:s A')}` },
{ id: 'last_login', label: 'Last Login', value: record => record.lastLogin },
{
id: 'is_active', label: 'Access', value: record => record.isActive ? <span className={classes.statusActive}>Active</span>
: <span className={status.Inactive}>Inactive</span>
}
];
const rolesHeadCells = [
{ id: 'role', label: 'Role' },
{ id: 'permissions', label: 'Permissions' },
{ id: 'status', label: 'Status' }
];
const usersHandler = () => {
dispatch(
UsersActions.getUsersAction({
@@ -120,14 +143,11 @@ function UserAccessScreen() {
let roles = JSON.parse(JSON.stringify(rolesData));
roles = roles.map((item) => {
item.name = item.name.split('-').join(' ').toUpperCase();
item.permissions = item.permissions.map((permission) => permission.name).join(',');
item.permissions = item.permissions?.allowedUIModules?.join(',');
if (!item.permissions) {
item.permissions = 'NA';
}
item.status = 'INACTIVE';
if (item.status) {
item.status = 'ACTIVE';
}
item.status = item.status ? 'ACTIVE' : 'INACTIVE';
return item;
});
setRoleRecords(roles);
@@ -144,6 +164,17 @@ function UserAccessScreen() {
}
}));
const columnRenders = userRecords && userRecords.map(record => {
return <StyledTableRow key={record.id}>
{userHeadCells.map((columnConfig, key) => <TableCell key={key} onClick={() => columnConfig.isEditAnchor && navigate('/setup/users-access/edit-user', {state: {user: record}})}>
{columnConfig.isEditAnchor && <span className={classes.iconwrap}>
<EditIcon className={classes.iconSize}/>
</span>}
{columnConfig.value(record)}
</TableCell>)}
</StyledTableRow>;
});
return (
<DashboardLayout>
<DashboardNavbar />
@@ -154,79 +185,67 @@ function UserAccessScreen() {
{ name: 'Users and Access', path: '/setup/users-access' }
]}
/>
<MDBox px={2} py={3}>
<Grid container spacing={2} className={classes.margin}>
<Grid item xs={12} sm={4} md={4}>
<Tabs value={value} className={classes.tabs} onChange={handleTabs}>
<MDBox px={0} py={3}>
<Grid container spacing={1} className={classes.margin}>
<Grid item xs={12} sm={4} md={4} className='ps-2 pt-0'>
<Tabs value={value} className={`p-0 h-100 ${classes.tabs}`} onChange={handleTabs}>
<Tab label="Roles" onClick={() => rolesHandler()} />
<Tab label="Users" onClick={() => usersHandler()} />
</Tabs>
</Grid>
<Grid item xs={12} sm={4} md={6}>
<Grid item xs={12} sm={4} md={6} className='py-2' style={{ display: 'flex', alignItems: 'center' }}>
<SearchBar />
</Grid>
<Grid item xs={12} sm={4} md={2}>
<Grid item xs={12} sm={4} md={2} className='py-2' style={{ display: 'flex', alignItems: 'center' }}>
<MDButton
color="primary"
size="medium"
onClick={() => navigate('/setup/users-access/create-role')}
onClick={() => navigate(`/setup/users-access/${value === 0 ? 'create-role' : 'create-user'}`)}
>
{'+ CREATE USER'}
{value === 0 ? '+ CREATE ROLE' : '+ CREATE USER'}
</MDButton>
</Grid>
</Grid>
<TabPanel value={value} index={0}>
<BasicTable
headCells={rolesHeadCells}
records={rolesRecords}
backgroundColor="#007AFF"
color="#fff"
>
<TableBody>
{rolesRecords &&
rolesRecords.map((item) => (
<StyledTableRow key={item.id}>
<TableCell>
<div className={classes.iconwrap}>
<EditIcon className={classes.iconSize} />
{item.name}
</div>
</TableCell>
<TableCell>{item.permissions}</TableCell>
<TableCell>{item.status}</TableCell>
</StyledTableRow>
))}
</TableBody>
</BasicTable>
</TabPanel>
<TabPanel value={value} index={1}>
<BasicTable
headCells={userHeadCells}
records={userRecords}
backgroundColor="#007AFF"
color="#fff"
>
<TableBody>
{userRecords &&
userRecords.map((item) => (
<StyledTableRow key={item.id}>
<TableCell>
<div className={classes.iconwrap}>
<EditIcon className={classes.iconSize} />
{item.fullName}
</div>
</TableCell>
<TableCell>{item.role_name}</TableCell>
<TableCell>{moment(item.updatedAt).format('D/M/YYYY h:m:s A')}</TableCell>
<TableCell>
{item.createdBy ? item.createdBy?.fullName + ' | ' : null}
{moment(item.createdAt).format('D/M/YYYY h:m:s A')}
</TableCell>
</StyledTableRow>
))}
</TableBody>
</BasicTable>
</TabPanel>
<Grid px={2}>
<TabPanel value={value} index={0} className={classes.radialBorder}>
<BasicTable
headCells={rolesHeadCells}
records={rolesRecords}
backgroundColor="#007AFF"
color="#fff"
>
<TableBody>
{rolesRecords &&
rolesRecords.map((item) => (
<StyledTableRow key={item.id}>
<TableCell>
<div className={classes.iconwrap}>
<EditIcon className={classes.iconSize} />
{item.name}
</div>
</TableCell>
<TableCell>{item.permissions}</TableCell>
<TableCell>{item.status}</TableCell>
</StyledTableRow>
))}
</TableBody>
</BasicTable>
</TabPanel>
<TabPanel value={value} index={1} className={classes.radialBorder}>
<BasicTable
id="user-list"
headCells={userHeadCells}
records={userRecords}
backgroundColor="#007AFF"
color="#fff"
>
{userRecords && userRecords.length > 0
? <TableBody>
{columnRenders}
</TableBody> : 'No Records to Display'}
</BasicTable>
</TabPanel>
</Grid>
</MDBox>
</DashboardLayout>
);

View File

@@ -7,7 +7,10 @@ import { getFetchingValue, getErrorValue } from '../services/Utils';
const { Types, Creators } = createActions({
getUsersAction: ['payload'],
getUsersSuccess: ['data'],
getUsersFailure: ['error']
getUsersFailure: ['error'],
createUserAction: ['payload'],
createUserSuccess: ['data'],
createUserFailure: ['error']
});
export const UsersTypes = Types;
@@ -45,10 +48,30 @@ export const onGetUsersFailure = (state, { error }) =>
fetching: _.without(state.fetching, error?.loader),
error: { ...state.error, [error?.loader]: error?.error }
});
export const onCreateUserAction = (state, { payload }) =>
state.merge({
fetching: _.uniq([state.fetching, payload?.loader]),
error: getErrorValue(state?.error, payload?.loader)
});
export const onCreateUserSuccess = (state, { data }) =>
state.merge({
fetching: getFetchingValue(state.fetching, data?.loader),
error: getErrorValue(state?.error, data?.loader)
});
export const onCreateUserFailure = (state, { error }) =>
state.merge({
fetching: _.without(state.fetching, error?.loader),
error: { ...state.error, [error?.loader]: error?.error }
});
/* ------------- Hookup Reducers To Types ------------- */
export const usersReducer = createReducer(INITIAL_STATE, {
[Types.GET_USERS_ACTION]: onGetUsersAction,
[Types.GET_USERS_SUCCESS]: onGetUsersSuccess,
[Types.GET_USERS_FAILURE]: onGetUsersFailure
[Types.GET_USERS_FAILURE]: onGetUsersFailure,
[Types.CREATE_USER_ACTION]: onCreateUserAction,
[Types.CREATE_USER_SUCCESS]: onCreateUserSuccess,
[Types.CREATE_USER_FAILURE]: onCreateUserFailure
});

View File

@@ -1,65 +1,24 @@
/**
=========================================================
* Material Dashboard 2 PRO React - v2.0.0
=========================================================
* Product Page: https://www.creative-tim.com/product/material-dashboard-pro-react
* Copyright 2021 Creative Tim (https://www.creative-tim.com)
Coded by www.creative-tim.com
=========================================================
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
/**
All of the routes for the Material Dashboard 2 PRO React are added here,
You can add a new route, customize the routes and delete the routes here.
Once you add a new route on this file it will be visible automatically on
the Sidenav.
For adding a new route you can follow the existing routes in the routes array.
1. The `type` key with the `collapse` value is used for a route.
2. The `type` key with the `title` value is used for a title inside the Sidenav.
3. The `type` key with the `divider` value is used for a divider between Sidenav items.
4. The `name` key is used for the name of the route on the Sidenav.
5. The `key` key is used for the key of the route (It will help you with the key prop inside a loop).
6. The `icon` key is used for the icon of the route on the Sidenav, you have to add a node.
7. The `collapse` key is used for making a collapsible item on the Sidenav that contains other routes
inside (nested routes), you need to pass the nested routes inside an array as a value for the `collapse` key.
8. The `route` key is used to store the route location which is used for the react router.
9. The `href` key is used to store the external links location.
10. The `title` key is only for the item with the type of `title` and its used for the title text on the Sidenav.
10. The `component` key is used to store the component of its route.
*/
// Material Dashboard 2 PRO React layouts
import DashboardScreen from 'pages/dashboard';
import LoginScreen from 'pages/authentication';
// Material Dashboard 2 PRO React components
// import MDAvatar from 'components/MDAvatar';
// @mui icons
import Icon from '@mui/material/Icon';
import InventoryScreen from 'pages/inventory';
import WarehouseScreen from 'pages/warehouse';
import HomepageScreen from 'pages/homepage';
import DashboardScreen from 'pages/dashboard';
import LoginScreen from 'pages/authentication';
import LocationLabelingScreen from 'pages/labeling';
import UserAccessScreen from 'pages/useraccess';
import NewWarehouseDetails from 'pages/newWarehouseDetails';
import SetupHome from 'pages/setup';
import NewWarehouseDetails from 'pages/newWarehouseDetails';
import EditWarehouseDetails from 'pages/editWarehouseDetails';
import LabelingHome from 'pages/labellingHome';
import SetupInventory from 'pages/setupInventory';
import CreateUserRole from 'pages/createUserRole';
import CreateEditUser from 'pages/createEditUser';
import WidgetLabel from 'pages/widgetLabel';
import ItemListing from 'pages/itemListing';
import HomeIcon from 'assets/images/HomeIcon';
import SetupIcon from 'assets/images/SetupIcon';
import AddNewItem from '../pages/addNewProduct';
import CreateUserRole from 'pages/createUserRole';
import WidgetLabel from 'pages/widgetLabel';
import ItemListing from 'pages/itemListing';
// Images
// import profilePicture from 'assets/images/team-3.jpg';
@@ -211,6 +170,20 @@ const protectedRoutes = [
route: '/setup/users-access/create-role',
hide: true,
component: <CreateUserRole />
},
{
name: 'Create User',
key: 'create-user',
route: '/setup/users-access/create-user',
hide: true,
component: <CreateEditUser context='new' />
},
{
name: 'Edot User',
key: 'edit-user',
route: '/setup/users-access/edit-user',
hide: true,
component: <CreateEditUser context='edit' />
}
]
}

View File

@@ -27,4 +27,33 @@ export function* onRequestUsersData({ payload }) {
);
}
}
export default [takeLatest(UsersTypes.GET_USERS_ACTION, onRequestUsersData)];
export function* onCreateUserData({ payload }) {
const response = yield call(
ApiServices[payload?.method],
AuthorizedAPI,
payload?.slug,
payload?.data
);
if (response?.status === 200) {
payload.onSuccessfulSubmission(response.data?.data);
yield put(
UsersActions.createUserSuccess({
loader: payload?.loader,
usersDetail: response?.data?.data
})
);
} else {
payload.onValidationFailed(response.data?.error);
yield put(
UsersActions.createUserFailure({
loader: payload?.loader,
error: response?.data
})
);
}
}
export default [
takeLatest(UsersTypes.GET_USERS_ACTION, onRequestUsersData),
takeLatest(UsersTypes.CREATE_USER_ACTION, onCreateUserData)
];

View File

@@ -63,6 +63,12 @@ const schema = {
inventory_process: Yup.string()
}),
image: Yup.array()
}),
createUser: Yup.object({
fullName: Yup.string('Enter Full Name').required('User Name is required'),
phoneNumber: Yup.string('Enter Phone Numbe').required('Phone Number is required'),
roles: Yup.string('Select a role').required('At least one role is required'),
})
};