[WMS-55] User Create and Edit Final

This commit is contained in:
m0n02hz
2022-03-03 04:18:01 +05:30
parent c2b632e33b
commit 213bff309c
14 changed files with 448 additions and 142 deletions

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, {useState} from 'react';
import PropTypes from 'prop-types';
import { Grid, Typography } from '@mui/material';
import MDBox from 'components/MDBox';
@@ -6,8 +6,16 @@ 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}>
const {boxStyleOverride, component, gridStyleOverride, initlist, list, matchProp, md, onChange, title, variant, xs} = props;
// eslint-disable-next-line no-unused-vars
const [allocationStatus, setAllocationStatus] = useState();
const handleAllocationChange = state => {
setAllocationStatus(state);
onChange && onChange(state.assigned?.map(obj => obj._id).join(','));
};
return <Grid item className='c-AllocationManager' xs={xs || 12} md={md || 6} sx={gridStyleOverride}>
<MDBox
sx={boxStyleOverride || ({
backgroundColor: '#fff',
@@ -20,7 +28,7 @@ const AllocationManager = props => {
<Typography gutterBottom variant={variant || 'h6'} component={component || 'div'}>
{title}
</Typography>
<TransferList list={list || []} />
<TransferList list={list || []} initlist={initlist} matchProp={matchProp} onChange={handleAllocationChange} />
</MDBox>
</Grid>;
};
@@ -29,8 +37,14 @@ AllocationManager.propTypes = {
boxStyleOverride: PropTypes.object,
component: PropTypes.string,
gridStyleOverride: PropTypes.object,
initlist: PropTypes.oneOfType([
PropTypes.string,
PropTypes.array
]),
list: PropTypes.array,
matchProp: PropTypes.object,
md: PropTypes.number,
onChange: PropTypes.func,
styleOverride: PropTypes.object,
title: PropTypes.string,
variant: PropTypes.string,

View File

@@ -1,4 +1,4 @@
import * as React from 'react';
import React, {useEffect, useState} from 'react';
import PropTypes from 'prop-types';
import Grid from '@mui/material/Grid';
import List from '@mui/material/List';
@@ -11,6 +11,7 @@ import Typography from '@mui/material/Typography';
import Divider from '@mui/material/Divider';
import { makeStyles } from '@mui/styles';
import {intersection, not, notBy, intersectionBy} from 'services/Utils';
const useStyles = makeStyles({
boxStyling: {
@@ -38,23 +39,25 @@ const useStyles = makeStyles({
}
});
function not(a, b) {
return a.filter((value) => b.indexOf(value) === -1);
}
function intersection(a, b) {
return a.filter((value) => b.indexOf(value) !== -1);
}
export default function TransferList({list}) {
export default function TransferList({list, initlist, matchProp, onChange}) {
const classes = useStyles();
const [checked, setChecked] = React.useState([]);
const [left, setLeft] = React.useState(list || []);
const [right, setRight] = React.useState([]);
const [checked, setChecked] = useState([]);
const [left, setLeft] = useState(list || []);
const [right, setRight] = useState([]);
const leftChecked = intersection(checked, left);
const rightChecked = intersection(checked, right);
useEffect(() => {
if (initlist) {
const initlistClone = typeof initlist === 'object' ? initlist : initlist.split(',');
const left = notBy(matchProp, list, initlistClone);
const right = intersectionBy(matchProp, list, initlistClone);
setLeft(left);
setRight(right);
}
}, []);
const handleToggle = (value) => () => {
const currentIndex = checked.indexOf(value);
const newChecked = [...checked];
@@ -69,25 +72,37 @@ export default function TransferList({list}) {
};
const handleAllRight = () => {
setRight(right.concat(left));
setLeft([]);
const rightNew = right.concat(left);
const leftNew = [];
setRight(rightNew);
setLeft(leftNew);
onChange({unassigned: leftNew, assigned: rightNew});
};
const handleCheckedRight = () => {
setRight(right.concat(leftChecked));
setLeft(not(left, leftChecked));
const rightNew = right.concat(leftChecked);
const leftNew = not(left, leftChecked);
setRight(rightNew);
setLeft(leftNew);
setChecked(not(checked, leftChecked));
onChange({unassigned: leftNew, assigned: rightNew});
};
const handleCheckedLeft = () => {
setLeft(left.concat(rightChecked));
setRight(not(right, rightChecked));
const rightNew = not(right, rightChecked);
const leftNew = left.concat(rightChecked);
setLeft(leftNew);
setRight(rightNew);
setChecked(not(checked, rightChecked));
onChange({unassigned: leftNew, assigned: rightNew});
};
const handleAllLeft = () => {
setLeft(left.concat(right));
setRight([]);
const rightNew = [];
const leftNew = left.concat(right);
setLeft(leftNew);
setRight(rightNew);
onChange({unassigned: leftNew, assigned: rightNew});
};
const customList = items => (
@@ -186,5 +201,11 @@ export default function TransferList({list}) {
}
TransferList.propTypes = {
list: PropTypes.array
initlist: PropTypes.oneOfType([
PropTypes.string,
PropTypes.array
]),
list: PropTypes.array,
matchProp: PropTypes.object,
onChange: PropTypes.func
};

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, {useEffect, useState} from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '@mui/styles';
import { Box, Grid } from '@mui/material';
@@ -21,14 +21,31 @@ const useStyles = makeStyles(() => ({
}));
const Toggles = props => {
const {boxSx, md, title, toggles, typoComponent, typoSx, typoVariant, xs} = props;
const {boxSx, inittoggles, md, onChange, title, toggles, typoComponent, typoSx, typoVariant, xs} = props;
const [toggleState, updateToggleState] = useState({});
const classes = useStyles();
useEffect(() => {
if (inittoggles && typeof inittoggles === 'string') {
const initToggleState = {};
inittoggles.split(',').forEach(iToggle => initToggleState[iToggle] = true);
updateToggleState(initToggleState);
}
}, []);
const handleToggle = (e, toggle) => {
const toggleStateClone = {...toggleState, [toggle]: e.target.checked};
updateToggleState(toggleStateClone);
onChange && onChange(Object.keys(toggleStateClone).join(','));
};
const switchRenders = toggles => toggles && toggles.map((toggle, key) => {
toggle = typeof toggle === 'string' ? toggle : toggle.name;
const id = 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 />
<Switch id={id} checked={toggleState[toggle] === undefined ? false : toggleState[toggle]} onChange={e => handleToggle(e, toggle)} />
</MDBox>;
});
@@ -36,7 +53,7 @@ const Toggles = props => {
<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"}
{title || 'Title'}
</Typography>
<Box sx={{ padding: ' 0px 20px' }}>{toggles && switchRenders(toggles)}</Box>
</MDBox>
@@ -45,7 +62,9 @@ const Toggles = props => {
Toggles.propTypes = {
boxSx: PropTypes.object,
inittoggles: PropTypes.string,
md: PropTypes.number,
onChange: PropTypes.func,
title: PropTypes.string,
toggles: PropTypes.array,
typoComponent: PropTypes.string,

View File

@@ -1,10 +1,13 @@
export default {
LOGIN_USER: '/user/login',
CREATE_USER: '/user/create',
UPDATE_USER: '/user/:id',
GET_USERS_DATA: '/user/all?page=0&perPage=20',
GET_ROLES_DATA: '/user-role/all?page=0&perPage=10',
GET_PERMISSIONS_DATA: '/user-permission/all?page=0&perPage=10',
GET_ACTIONS_DATA: '/user-permission/actions/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',
@@ -17,7 +20,6 @@ export default {
ADD_INVENTORY: '/inventory',
GET_INVENTORY: '/inventory/all?page=0&perPage=50',
GET_INVENTORY_TYPES: '/inventory/types',
LOCATION_DELETE: '/dashboard/delete-location',
GET_WIDGET_FAMILY_BY_INVENTORY: '/widget-family/search-by-inventory?inventory=',
ADD_WIDGET_FAMILY: '/widget-family',
EDIT_WIDGET_FAMILY: '/widget-family/',

View File

@@ -13,6 +13,7 @@ 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 PermissionsActions, { PermissionsSelectors } from 'redux/PermissionsRedux';
import { AuthSelectors } from 'redux/AuthRedux';
import UsersActions from 'redux/UsersRedux';
@@ -70,49 +71,12 @@ function CreateEditUser(props) {
const roles = useSelector(RolesSelectors.getRolesDetail);
const warehouses = useSelector(WarehouseSelectors.getWarehouseDetail);
const inventories = useSelector(InventorySelectors.getInventoryDetail);
const actions = useSelector(PermissionsSelectors.getActionsDetail);
const permissions = useSelector(PermissionsSelectors.getPermissionsDetail);
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
})
);
}
});
const [editedUser, setEditedUser] = useState(location?.state?.user);
const [selectedRoles, setSelectedRoles] = useState([]);
useEffect(() => {
if (context === 'edit') {
@@ -121,26 +85,100 @@ function CreateEditUser(props) {
navigate('/setup/users-access');
} else {
setEditedUser(editedUser);
setUserRoles(editedUser.role_name ? editedUser.role_name.split(',') : []);
setSelectedRoles(editedUser.roles);
}
}
}, []);
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();
(!warehouses || warehouses.length === 0) && dispatch(WarehouseActions.warehouseDataAction({loader: 'loading-request', slug: API.GET_WAREHOUSE_DATA,method: 'get'}));
(!inventories || inventories.length === 0) && 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'}));
(!permissions || permissions.length === 0) && dispatch(PermissionsActions.getPermissionsAction({loader: 'loading-request', slug: API.GET_PERMISSIONS_DATA, method: 'get'}));
(!actions || actions.length === 0) && dispatch(PermissionsActions.getActionsAction({loader: 'loading-request', slug: API.GET_ACTIONS_DATA, method: 'get'}));
}, []);
const formik = useFormik({
initialValues: context === 'new' ? {
fullName: '',
phoneNumber: '',
email: '',
password: '',
roles: '',
warehouses: '',
inventories: '',
actions: '',
visibilities: '',
isActive: true,
createdBy: currentUser ? currentUser.fullName : '',
createdAt: new Date(),
updatedBy: currentUser ? currentUser.fullName : '',
updatedAt: new Date()
} : {
fullName: editedUser ? editedUser.fullName : '',
phoneNumber: editedUser ? editedUser.phoneNumber : '',
email: editedUser ? editedUser.email : '',
password: '',
roles: editedUser ? editedUser.roles.map(role => role.name).join(', ') : '',
warehouses: editedUser?.permissions?.warehouseScopes ? editedUser.permissions.warehouseScopes.map(sc => sc.id).join(',') : '',
inventories: editedUser?.permissions?.inventoryScopes ? editedUser.permissions.inventoryScopes.map(sc => sc.id).join(',') : '',
actions: editedUser?.permissions?.actions ? editedUser.permissions.actions.join(',') : '',
visibilities: editedUser?.permissions?.allowedUIModules ? editedUser.permissions.allowedUIModules.join(',') : '',
isActive: editedUser && editedUser.isActive !== undefined ? editedUser.isActive : true,
createdBy: editedUser ? editedUser.createdBy?.fullName : '',
createdAt: editedUser ? editedUser.createdAt : '',
updatedBy: editedUser ? editedUser.updatedBy?.fullName : '',
updatedAt: editedUser ? editedUser.updatedAt : ''
},
validationSchema: schema.createUser,
onSubmit: (values, { setSubmitting }) =>
{
const onValidationFailed = () => {
setSubmitting(false);
};
const onSuccessfulSubmission = () => {
navigate('/setup/users-access');
};
const adaptPayload = values => {
const valuesClone = {...values};
valuesClone.permissions = {};
valuesClone.permissions.inventoryScopes = values.inventories ? values.inventories.split(',').map(inv => ({id: inv, type: 'Inventory'})) : [];
valuesClone.permissions.warehouseScopes = values.warehouses ? values.warehouses.split(',').map(wh => ({id: wh, type: 'Warehouse'})) : [];
valuesClone.permissions.actions = values.actions ? values.actions.split(',') : [];
valuesClone.permissions.allowedUIModules = values.visibilities ? values.visibilities.split(',') : [];
delete valuesClone.inventories;
delete valuesClone.warehouses;
delete valuesClone.actions;
delete valuesClone.visibilities;
return valuesClone;
};
values.roles = selectedRoles && selectedRoles.length > 0 ? selectedRoles.map(role => role._id) : [];
dispatch(
UsersActions.createUserAction({
loader: 'loading-request',
slug: context === 'edit' ? API.UPDATE_USER.replace(':id', editedUser._id): API.CREATE_USER,
method: 'post',
data: adaptPayload(values),
onValidationFailed,
onSuccessfulSubmission,
toastMessage: context === 'edit' ? 'Updated user __placeholder__successfully' : 'Added user __placeholder__successfully'
})
);
}
});
const handleChange = event => {
const value = typeof event.target.value === 'string' ? event.target.value?.split(',') : event.target.value;
setUserRoles(value);
formik.handleChange(event);
const handleMultiSelectChange = e => {
const uniqueRoles = [];
e.target.value.forEach(role => {
const roleIndex = uniqueRoles.findIndex(uRole => uRole._id === role._id);
if (roleIndex > -1) {
uniqueRoles.splice(roleIndex, 1);
} else {
uniqueRoles.push(role);
}
});
formik.handleChange('roles')(uniqueRoles.map(role => role.name).join());
setSelectedRoles(uniqueRoles);
};
return (
@@ -150,29 +188,45 @@ function CreateEditUser(props) {
<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" />
<img src={UserIcon} alt='img' />
<MDBox sx={{ position: 'absolute', bottom: '0', right: '0', cursor: 'pointer' }}>
<img src={EditIcon} alt="img" />
<img src={EditIcon} alt='img' />
</MDBox>
</MDBox>
<MDBox sx={{ marginBottom: '24px' }}>
<Box component="div" sx={{}} className={classes.labelSize}>
<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)}
<MDInput fullWidth 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}>
<Box component='div' sx={{}} className={classes.labelSize}>
Email
</Box>
<MDInput fullWidth disabled={context === 'edit'} value={formik.values.email} name='email' type='email'
variant='outlined' error={formik.touched.email && Boolean(formik.errors.email)}
helperText={formik.touched.email && formik.errors.email} onChange={formik.handleChange} />
</MDBox>
<MDBox sx={{ marginBottom: '24px' }}>
<Box component='div' sx={{}} className={classes.labelSize}>
Password
</Box>
<MDInput fullWidth value={formik.values.password} name='password' type='password'
variant='outlined' error={formik.touched.password && Boolean(formik.errors.password)}
helperText={formik.touched.password && formik.errors.password} 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)}
<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}>
<Box component='div' sx={{}} className={classes.labelSize}>
Role
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', columnGap: '20px' }}>
@@ -181,21 +235,13 @@ function CreateEditUser(props) {
multiple
displayEmpty
name='roles'
value={userRoles}
value={selectedRoles}
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(',');
}}
renderValue={() => selectedRoles?.length === 0 ? 'Please select a role' : selectedRoles?.map(role => role.name).join(', ')}
inputProps={{ 'aria-label': 'Without label' }}
sx={{
width: '100%'
}}
onChange={handleChange}
sx={{width: '100%'}}
onChange={handleMultiSelectChange}
>
{roles && roles.map((role, key) => <MenuItem key={key} value={role}>{role.name}</MenuItem>)}
</Select>
@@ -206,12 +252,13 @@ function CreateEditUser(props) {
alignItems: 'center',
justifyContent: 'space-between',
width: '30%',
paddingRight: '1rem',
border: '1px solid #C4C4C4',
borderRadius: '4px'
}}
>
<Box
component="div"
component='div'
sx={{
fontSize: '16px',
lineHeight: '20px',
@@ -231,7 +278,7 @@ function CreateEditUser(props) {
left: '20px'
}}
>
<Switch checked={formik.values.isActive} />
<Switch name='isActive' checked={formik.values.isActive} onChange={formik.handleChange} />
</Box>
</Box>
</Box>
@@ -240,28 +287,28 @@ function CreateEditUser(props) {
<Grid item xs={12}>
<Grid container spacing={2} className={classes.margin}>
<Grid item xs={6}>
<Box component="div" className={classes.labelSize}>
<Box component='div' className={classes.labelSize}>
Created By
</Box>
<MDInput fullWidth disabled name="warehousename" type="text" value={currentUser ? currentUser.fullName : ''} variant="outlined" />
<MDInput fullWidth disabled name='createdBy' type='text' value={formik.values.createdBy} variant='outlined' />
</Grid>
<Grid item xs={6}>
<Box component="div" className={classes.labelSize}>
<Box component='div' className={classes.labelSize}>
Date &amp; Time
</Box>
<DateTimeInput disabled />
<DateTimeInput disabled name='createdAt' value={formik.values.createdAt} />
</Grid>
<Grid item xs={6}>
<Box component="div" className={classes.labelSize}>
<Box component='div' className={classes.labelSize}>
Last Updated by
</Box>
<MDInput fullWidth disabled name="warehousename" type="text" value={currentUser ? currentUser.fullName : ''} variant="outlined" />
<MDInput fullWidth disabled name='updatedBy' type='text' value={formik.values.updatedBy} variant='outlined' />
</Grid>
<Grid item xs={6}>
<Box component="div" className={classes.labelSize}>
<Box component='div' className={classes.labelSize}>
Date &amp; Time
</Box>
<DateTimeInput disabled />
<DateTimeInput disabled name='updatedAt' value={formik.values.updatedAt} />
</Grid>
</Grid>
</Grid>
@@ -269,30 +316,33 @@ function CreateEditUser(props) {
</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' />
<AllocationManager name='warehouses' gridStyleOverride={{ paddingLeft: '4rem !important' }} initlist={formik.values.warehouses}
list={warehouses} matchProp={{a: '_id'}} title='Warehouse' onChange={val => formik.handleChange('warehouses')(val)} />
<AllocationManager name='inventories' gridStyleOverride={{ paddingRight: '2rem' }} initlist={formik.values.inventories}
list={inventories} matchProp={{a: '_id'}} title='Inventory' onChange={val => formik.handleChange('inventories')(val)} />
</Grid>
<Grid container spacing={2} sx={{ marginTop: '12px', paddingLeft: '2rem' }}>
<Toggles title='Actions' />
<Toggles title='Application' />
<Toggles name='actions' title='Actions' toggles={actions} inittoggles={formik.values.actions} onChange={val => formik.handleChange('actions')(val)} />
<Toggles name='visibilities' title='Application' toggles={permissions} inittoggles={formik.values.visibilities} onChange={val => formik.handleChange('visibilities')(val)} />
</Grid>
<MDBox
display="flex"
justifyContent="center"
alignItems="center"
display='flex'
justifyContent='center'
alignItems='center'
lineHeight={1}
sx={{ marginBottom: '15px', marginTop: '45px', paddingBottom: '30px' }}
>
<MDButton
size="medium"
color="error"
variant="outlined"
type="button"
size='medium'
color='error'
variant='outlined'
type='button'
sx={{ marginRight: '15px' }}
onClick={() => navigate('/setup/users-access')}
>
Cancel
</MDButton>
<MDButton size="medium" color="primary" variant="contained" type="submit">
<MDButton size='medium' color='primary' variant='contained' type='submit'>
{context === 'new' ? 'Create' : 'Save'}
</MDButton>
</MDBox>

View File

@@ -14,6 +14,7 @@ import { Tabs, Tab } from '@mui/material';
import TabPanel from 'components/Tabs';
import UsersActions, { UsersSelectors } from 'redux/UsersRedux';
import RolesActions, { RolesSelectors } from 'redux/RolesRedux';
import { AuthSelectors } from 'redux/AuthRedux';
import { API } from 'constant';
import { useDispatch, useSelector } from 'react-redux';
import moment from 'moment';
@@ -22,9 +23,8 @@ import Breadcrumbs from 'components/Breadcrumbs';
const useStyles = makeStyles((theme) => ({
iconSize: {
width: '2%',
height: '2%',
marginBottom: '10px',
width: '1.5rem',
height: '1.5rem',
color: theme.palette.primary.light,
marginRight: '8px'
},
@@ -83,6 +83,7 @@ function UserAccessScreen() {
const [value, setValue] = useState(0);
const usersData = useSelector(UsersSelectors.getUsersDetail);
const rolesData = useSelector(RolesSelectors.getRolesDetail);
const currentUser = useSelector(AuthSelectors.getUser);
const [userRecords, setUserRecords] = useState([]);
const [rolesRecords, setRoleRecords] = useState([]);
const navigate = useNavigate();
@@ -91,12 +92,12 @@ function UserAccessScreen() {
{ 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: 'updated_by_at', label: 'Last Updated By & Date', value: record => `${record.updatedBy?.fullName ? 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 ? 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>
: <span className={classes.statusInactive}>Inactive</span>
}
];
@@ -164,13 +165,14 @@ function UserAccessScreen() {
}
}));
const columnRenders = userRecords && userRecords.map(record => {
const rowRenders = userRecords && userRecords.map(record => {
const canEdit = columnConfig => columnConfig.isEditAnchor && currentUser.email !== record.email;
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}>
{userHeadCells.map((columnConfig, key) => <TableCell key={key} onClick={() => canEdit(columnConfig) && navigate('/setup/users-access/edit-user', {state: {user: record}})}>
{canEdit(columnConfig) ? <span className={classes.iconwrap}>
<EditIcon className={classes.iconSize}/>
</span>}
{columnConfig.value(record)}
{columnConfig.value(record)}
</span> : <span>{columnConfig.value(record)}</span>}
</TableCell>)}
</StyledTableRow>;
});
@@ -241,7 +243,7 @@ function UserAccessScreen() {
>
{userRecords && userRecords.length > 0
? <TableBody>
{columnRenders}
{rowRenders}
</TableBody> : 'No Records to Display'}
</BasicTable>
</TabPanel>

View File

@@ -0,0 +1,84 @@
import { createActions, createReducer } from 'reduxsauce';
import Immutable from 'seamless-immutable';
import _ from 'underscore';
import { getFetchingValue, getErrorValue } from '../services/Utils';
/* ------------- Types and Action Creators ------------- */
const { Types, Creators } = createActions({
getPermissionsAction: ['payload'],
getPermissionsSuccess: ['data'],
getPermissionsFailure: ['error'],
getActionsAction: ['payload'],
getActionsSuccess: ['data'],
getActionsFailure: ['error']
});
export const PermissionsTypes = Types;
const PermissionsActions = Creators;
export default PermissionsActions;
/* ------------- Initial State ------------- */
export const INITIAL_STATE = Immutable({
permissionsDetail: [],
permissionsLoading: false,
permissionserror: {},
actionsDetail: [],
actionsLoading: false,
actionsError: {}
});
/* ------------- Selectors ------------- */
export const PermissionsSelectors = {
getPermissionsDetail: (state) => state.permissions.permissionsDetail,
getActionsDetail: (state) => state.permissions.actionsDetail
};
/* ------------- Reducers ------------- */
export const onGetPermissionsAction = (state, { payload }) =>
state.merge({
fetching: _.uniq([state.fetching, payload?.loader]),
error: getErrorValue(state?.error, payload?.loader)
});
export const onGetPermissionsSuccess = (state, { data }) =>
state.merge({
fetching: getFetchingValue(state.fetching, data?.loader),
error: getErrorValue(state?.error, data?.loader),
permissionsDetail: data.permissionsDetail
});
export const onGetPermissionsFailure = (state, { error }) =>
state.merge({
fetching: _.without(state.fetching, error?.loader),
error: { ...state.error, [error?.loader]: error?.error }
});
export const onGetActionsFailure = (state, { error }) =>
state.merge({
fetching: _.without(state.fetching, error?.loader),
error: { ...state.error, [error?.loader]: error?.error }
});
export const onGetActionsAction = (state, { payload }) =>
state.merge({
fetching: _.uniq([state.fetching, payload?.loader]),
error: getErrorValue(state?.error, payload?.loader)
});
export const onGetActionsSuccess = (state, { data }) =>
state.merge({
fetching: getFetchingValue(state.fetching, data?.loader),
error: getErrorValue(state?.error, data?.loader),
actionsDetail: data.actionsDetail
});
/* ------------- Hookup Reducers To Types ------------- */
export const permissionsReducer = createReducer(INITIAL_STATE, {
[Types.GET_PERMISSIONS_ACTION]: onGetPermissionsAction,
[Types.GET_PERMISSIONS_SUCCESS]: onGetPermissionsSuccess,
[Types.GET_PERMISSIONS_FAILURE]: onGetPermissionsFailure,
[Types.GET_ACTIONS_ACTION]: onGetActionsAction,
[Types.GET_ACTIONS_SUCCESS]: onGetActionsSuccess,
[Types.GET_ACTIONS_FAILURE]: onGetActionsFailure
});

View File

@@ -5,6 +5,7 @@ import { usersReducer } from './UsersRedux';
import { productReducer } from './ProductsRedux';
import { inventoryReducer } from './InventoryRedux';
import { rolesReducer } from './RolesRedux';
import { permissionsReducer } from './PermissionsRedux';
import { WarehouseLocationsReducer } from './WarehouseLocationsRedux';
import { widgetReducer } from './WidgetRedux';
import { itemReducer } from './ItemRedux';
@@ -15,6 +16,7 @@ const appReducer = combineReducers({
warehouse: warehouseReducer,
users: usersReducer,
roles: rolesReducer,
permissions: permissionsReducer,
warehouseLocations: WarehouseLocationsReducer,
product: productReducer,
inventory: inventoryReducer,

58
src/sagas/Permissions.js Normal file
View File

@@ -0,0 +1,58 @@
import { AuthorizedAPI } from 'config';
import { takeLatest, call, put } from 'redux-saga/effects';
import PermissionsActions, { PermissionsTypes } from '../redux/PermissionsRedux';
import ApiServices from 'services/API/ApiServices';
export function* onRequestPermissionsData({ payload }) {
const response = yield call(
ApiServices[payload?.method],
AuthorizedAPI,
payload?.slug,
payload?.data
);
if (response?.status === 200) {
yield put(
PermissionsActions.getPermissionsSuccess({
loader: payload?.loader,
permissionsDetail: response?.data?.data
})
);
} else {
payload.onFailedPermissionsData(response.data.error);
yield put(
PermissionsActions.getPermissionsFailure({
loader: payload?.loader,
error: response?.data
})
);
}
}
export function* onRequestActionsData({ payload }) {
const response = yield call(
ApiServices[payload?.method],
AuthorizedAPI,
payload?.slug,
payload?.data
);
if (response?.status === 200) {
yield put(
PermissionsActions.getActionsSuccess({
loader: payload?.loader,
actionsDetail: response?.data?.data
})
);
} else {
payload.onFailedActionsData(response.data.error);
yield put(
PermissionsActions.getActionsFailure({
loader: payload?.loader,
error: response?.data
})
);
}
}
export default [
takeLatest(PermissionsTypes.GET_PERMISSIONS_ACTION, onRequestPermissionsData),
takeLatest(PermissionsTypes.GET_ACTIONS_ACTION, onRequestActionsData)
];

View File

@@ -1,5 +1,6 @@
import { AuthorizedAPI } from 'config';
import { takeLatest, call, put } from 'redux-saga/effects';
import { toast } from 'react-toastify';
import UsersActions, { UsersTypes } from '../redux/UsersRedux';
import ApiServices from 'services/API/ApiServices';
@@ -36,7 +37,10 @@ export function* onCreateUserData({ payload }) {
payload?.data
);
if (response?.status === 200) {
payload.onSuccessfulSubmission(response.data?.data);
const data = response.data?.data;
const msg = payload.toastMessage.replace('__placeholder__', data && data.fullName ? '"' + data.fullName + '" ' : '');
toast(msg);
payload.onSuccessfulSubmission(data);
yield put(
UsersActions.createUserSuccess({
loader: payload?.loader,
@@ -44,6 +48,7 @@ export function* onCreateUserData({ payload }) {
})
);
} else {
toast('Something went wrong!');
payload.onValidationFailed(response.data?.error);
yield put(
UsersActions.createUserFailure({

View File

@@ -5,6 +5,7 @@ import UsersSaga from './Users';
import ProductSaga from './Product';
import InventorySaga from './Inventory';
import RolesSaga from './Roles';
import PermissionsSaga from './Permissions';
import WarehouseLocationsSaga from './WarehouseLocations';
import WidgetSaga from './Widget';
import ItemSaga from './Item';
@@ -16,6 +17,7 @@ export default function* rootSaga() {
yield all([...ProductSaga]);
yield all([...InventorySaga]);
yield all([...RolesSaga]);
yield all([...PermissionsSaga]);
yield all([...WarehouseLocationsSaga]);
yield all([...WidgetSaga]);
yield all([...ItemSaga]);

View File

@@ -15,3 +15,29 @@ export const getFetchingValue = (data, type) => {
return data;
}
};
export const not = (a, b) => {
return a.filter((value) => b.indexOf(value) === -1);
};
export const notBy = (matchProp, a, b) => {
if (matchProp && typeof matchProp === 'object') {
return a.filter((aItem) => b.findIndex(bItem => (matchProp.a ? aItem[matchProp.a] : aItem) === (matchProp.b ? bItem[matchProp.b] : bItem)) === -1);
}
// eslint-disable-next-line no-console
console.error('Incorrect match prop received');
return [];
};
export const intersection = (a, b) => {
return a.filter((value) => b.indexOf(value) !== -1);
};
export const intersectionBy = (matchProp, a, b) => {
if (matchProp && typeof matchProp === 'object') {
return a.filter((aItem) => b.findIndex(bItem => (matchProp.a ? aItem[matchProp.a] : aItem) === (matchProp.b ? bItem[matchProp.b] : bItem)) !== -1);
}
// eslint-disable-next-line no-console
console.error('Incorrect match prop received');
return [];
};

View File

@@ -68,7 +68,7 @@ const schema = {
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'),
roles: Yup.string('Please select at least one role').required('At least one role is required')
})
};