Fixes/ll inventory (#69)

* inventory data changes
* update Inventory changes
* Fixed: removed unnecessary imports
* Disabled: cycle count
* Added: Inventory types sagas
* Fix: null check
* Updated: policies
* Fixed: formik values
* update: allow single image
* Update: Policies control
* Updated: new inventory add form
* Update: new inventory conditional render
* Update: populate formik fields
* Added: Validation
* Added: edit functionality, disabled fields
* Update: housekeeping
* Fix: iconslug and key
* Update: route handling
* Added: endpoints
* Added: widget nested page
* Added: sagas
* Added: redux handling
* Update: new product page functionality
* Added: inventory page functionality
* Fixed: form validation
* Fix: route handling

Co-authored-by: evdigitech <evdigitech@gmail.com>
Co-authored-by: Llewellyn Dsouza <lledsouza2209@gmail.com>
This commit is contained in:
bluestreamlds
2022-03-01 03:18:16 +05:30
committed by GitHub
parent b3d7b0344a
commit af28c0b99c
15 changed files with 1151 additions and 515 deletions

View File

@@ -34,24 +34,24 @@ export default function Tile({ data, children }) {
</Box>
</AccordionSummary>
<AccordionDetails className={`${classes.row2} ${expand ? null : classes.remove}`}>
<Link to={data.path.update}>
<Link to={`/setup/inventory/update/${data.id}`}>
<Box className={classes.box}>
Update {data.name} <ArrowRightIcon />
</Box>
</Link>
<Link to={data.path.addNew}>
<Link to={`/setup/inventory/new-item/${data.widgetname}/${data.id}`}>
<Box className={`${classes.box} ${classes.boxEven}`}>
Add New {data.name} <ArrowRightIcon />
Add New {data.widgetname} <ArrowRightIcon />
</Box>
</Link>
<Link to={data.path.cycleCount}>
<Box className={classes.box}>
<Link to="#">
<Box className={classes.boxDisabled}>
Cycle Count <ArrowRightIcon />
</Box>
</Link>
<Link to={data.path.list}>
<Link to="/">
<Box className={`${classes.box} ${classes.boxEven}`}>
{data.name} List <ArrowRightIcon />
{data.widgetname} List <ArrowRightIcon />
</Box>
</Link>
</AccordionDetails>

View File

@@ -83,6 +83,23 @@ const useStyles = makeStyles({
},
remove: {
display: 'none'
},
boxDisabled: {
display: 'flex',
color: '#cccccc',
alignItems: 'center',
justifyContent: 'space-between',
padding: '13px 0 13px 10px',
borderTop: '1px solid lightgray',
'& svg': {
opacity: '0'
},
'&:hover': {
cursor: 'auto',
'& svg': {
opacity: '0'
}
}
}
});

View File

@@ -0,0 +1,329 @@
/* eslint-disable indent */
import {
Box,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Grid,
TextField
} from '@mui/material';
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
import IconButton from '@mui/material/IconButton';
import MDButton from 'components/Button';
import { useDispatch, useSelector } from 'react-redux';
import { WidgetSelectors } from 'redux/WidgetRedux';
import WidgetActions from 'redux/WidgetRedux';
import { API } from 'constant';
import { useFormik } from 'formik';
import LOGGER from 'services/Logger';
function MaterialForm({ formType, setFormOpen, selected, inventoryId }) {
const dispatch = useDispatch();
const formik = useFormik({
initialValues:
formType === 'family'
? { name: '', inventoryId }
: formType === 'subfamily'
? { name: '', inventoryId, parentId: selected._id || '' }
: { name: formType.name, inventoryId, parentId: formType.parent?._id || undefined },
onSubmit: (values) => {
LOGGER.log('Form values and field info', values);
['family', 'subfamily'].includes(formType)
? dispatch(
WidgetActions.editWidgetRequest({
loader: 'location-request',
slug: API.ADD_WIDGET_FAMILY,
method: 'post',
data: values,
type: 'add'
})
)
: dispatch(
WidgetActions.editWidgetRequest({
loader: 'location-request',
slug: `${API.EDIT_WIDGET_FAMILY}${inventoryId}`,
method: 'patch',
data: values,
type: 'edit'
})
);
setFormOpen(false);
}
});
return (
<Dialog
open
onClose={() => {
setFormOpen(false);
}}
>
<DialogTitle>
{['family', 'subfamily'].includes(formType) ? 'Create' : 'Edit'} material{' '}
{['family', 'subfamily'].includes(formType) ? formType : null}
</DialogTitle>
<DialogContent>
{/* <DialogContentText>Some more text if needed</DialogContentText> */}
<TextField
autoFocus
fullWidth
margin="dense"
label="Name"
type="text"
name="name"
variant="standard"
value={formik.values.name}
error={formik.touched.name && Boolean(formik.errors.name)}
helperText={formik.touched.name && formik.errors.name}
onChange={formik.handleChange}
/>
</DialogContent>
<DialogActions>
<MDButton
onClick={() => {
setFormOpen(false);
}}
>
Cancel
</MDButton>
<MDButton onClick={formik.handleSubmit}>Save</MDButton>
</DialogActions>
</Dialog>
);
}
MaterialForm.propTypes = {
formType: PropTypes.any,
setFormOpen: PropTypes.any,
selected: PropTypes.any,
inventoryId: PropTypes.any
};
function WidgetNestedDataTable({
data,
selected,
setSelected,
setFormOpen,
setFormType,
inventoryId
}) {
const [open, setOpen] = React.useState(false);
const widgetChildren = useSelector(WidgetSelectors.getWidgetsByParentId(data._id));
return (
<>
<Box
sx={{
borderLeftWidth: '2px',
borderLeftStyle: 'solid',
borderLeftColor: '#aedaed',
borderTopWidth: '1px',
borderTopStyle: 'solid',
borderTopColor: '#BDD0DB',
background:
selected?._id === data._id
? 'linear-gradient(135deg, ' + '#aedaed' + ' 0%, #f9f9f9 20%)'
: '#f9f9f9'
}}
>
<Grid container key={data._id}>
<Grid item xs={2}>
<IconButton
disabled={!widgetChildren?.length}
aria-label="expand row"
size="small"
sx={{ marginLeft: '12px' }}
onClick={() => setOpen(!open)}
>
{open ? <KeyboardArrowUpIcon /> : <KeyboardArrowRightIcon />}
</IconButton>
<MDButton
size="small"
variant="contained"
color="primary"
sx={{
textTransform: 'capitalize',
minWidth: '45px',
minHeight: '28px',
marginLeft: '5px',
marginRight: '20px',
boxShadow: 'none',
fontWeight: '500',
padding: '0'
}}
onClick={() => {
setFormType(data);
setFormOpen(true);
}}
>
EDIT
</MDButton>
</Grid>
<Grid
container
item
xs={10}
onClick={() => {
setSelected(data);
}}
>
<Grid item xs={9}>
{data.name}
</Grid>
<Grid item xs={1}>
<MDButton
disabled
size="small"
variant="contained"
color="error"
sx={{
textTransform: 'capitalize',
minWidth: '45px',
minHeight: '28px',
marginLeft: '5px',
marginRight: '20px',
boxShadow: 'none',
fontWeight: '500',
padding: '0 6'
}}
onClick={() => {
// dispatch(
// WarehouseLocationsActions.deleteLocationRequest({
// loader: 'location-request',
// slug: API.LOCATION_DELETE,
// method: 'post',
// data: { type: data.location, id: data.id }
// })
// );
}}
>
DELETE
</MDButton>
</Grid>
</Grid>
</Grid>
{open && widgetChildren ? (
<Box Box sx={{ marginLeft: '25px', marginBottom: '15px' }}>
{/* Add headers here */}
{widgetChildren.map((data) => (
<WidgetNestedDataTable
key={data._id}
data={data}
selected={selected}
setSelected={setSelected}
setFormOpen={setFormOpen}
setFormType={setFormType}
inventoryId={inventoryId}
/>
))}
</Box>
) : null}
</Box>
</>
);
}
WidgetNestedDataTable.propTypes = {
data: PropTypes.any,
selected: PropTypes.any,
setSelected: PropTypes.any,
setFormOpen: PropTypes.any,
setFormType: PropTypes.any,
inventoryId: PropTypes.any
};
const bottomButtonStyling = {
width: '200px',
marginTop: '25px',
textTransform: 'uppercase',
borderRadius: '100px',
padding: '13px 30px'
};
function WidgetNestedDataTableContainer({ inventoryId }) {
const dispatch = useDispatch();
const [selected, setSelected] = React.useState(null);
const [formType, setFormType] = React.useState('family');
const [formOpen, setFormOpen] = React.useState(false);
const widgetFamilyData = useSelector(WidgetSelectors.getWidgetFamiliesByInventoryId(inventoryId));
useEffect(() => {
dispatch(
WidgetActions.widgetRequest({
loader: 'location-request',
slug: `${API.GET_WIDGET_FAMILY_BY_INVENTORY}${inventoryId}`,
method: 'get'
})
);
}, []);
return (
<>
{widgetFamilyData &&
widgetFamilyData.map((p) => (
<WidgetNestedDataTable
key={p._id}
data={p}
selected={selected}
setSelected={setSelected}
setFormOpen={setFormOpen}
setFormType={setFormType}
/>
))}
<Grid container spacing={5} justifyContent="center">
<Grid item>
<MDButton
size="medium"
sx={bottomButtonStyling}
color="primary"
variant="contained"
onClick={() => {
setFormType('family');
setFormOpen(true);
}}
>
Add Family
</MDButton>
</Grid>
<Grid item>
<MDButton
size="medium"
sx={bottomButtonStyling}
disabled={selected?.parent}
color={selected && !selected?.parent ? 'primary' : 'secondary'}
variant="contained"
onClick={() => {
setFormType('subfamily');
setFormOpen(true);
}}
>
Add SubFamily
</MDButton>
</Grid>
</Grid>
{/* <pre>{JSON.stringify(selected, null, 4)}</pre> */}
{formOpen && (
<MaterialForm
formType={formType}
setFormOpen={setFormOpen}
selected={selected}
inventoryId={inventoryId}
/>
)}
</>
);
}
WidgetNestedDataTableContainer.propTypes = {
inventoryId: PropTypes.any
};
export default WidgetNestedDataTableContainer;

View File

@@ -13,5 +13,10 @@ export default {
ADD_NEW_SUBLEVEL: '/sublevel',
ADD_PRODUCT: '/item/',
ADD_INVENTORY: '/inventory',
LOCATION_DELETE: '/dashboard/delete-location'
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

@@ -15,11 +15,14 @@ import CrossIcon from 'assets/images/CrossIcon';
import { useFormik } from 'formik';
import schema from 'services/ValidationServices';
import MDInput from 'components/MDInput';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import ProductActions from 'redux/ProductsRedux';
import { API } from 'constant';
import LOGGER from 'services/Logger';
import Breadcrumbs from 'components/Breadcrumbs';
import { useParams } from 'react-router-dom';
import { WidgetSelectors } from 'redux/WidgetRedux';
import WidgetActions from 'redux/WidgetRedux';
const useStyles = makeStyles({
labelSize: {
@@ -43,47 +46,53 @@ const useStyles = makeStyles({
}
});
const inventoryTypes = ['Perishable', 'Material', 'Product', 'Inventory', 'Fleet'];
function AddNewProduct() {
function AddNewItem() {
const classes = useStyles();
const { widgetName, inventoryId } = useParams();
const dispatch = useDispatch();
const [Manufacturer, setManufacturer] = React.useState('');
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
React.useEffect(() => {
dispatch(
WidgetActions.widgetRequest({
loader: 'location-request',
slug: `${API.GET_WIDGET_FAMILY_BY_INVENTORY}${inventoryId}`,
method: 'get'
})
);
}, []);
const handleClose = () => {
setOpen(false);
};
const handleChange = (event) => {
setManufacturer(event.target.value);
};
const [pFam, setPFam] = React.useState(null);
const primaryFamily = useSelector(WidgetSelectors.getWidgetFamiliesByInventoryId(inventoryId));
const secondaryFamily = useSelector(WidgetSelectors.getWidgetsByParentId(pFam));
LOGGER.log({ primaryFamily, secondaryFamily });
const formik = useFormik({
initialValues: {
warehousename: '',
commonName: '',
formalName: '',
description: '',
manufacturer: '',
type: '',
unitofmaterial: '',
packagecount: '',
formalname: '',
size: '',
color: '',
unitcost: '',
countperpallet: '',
countperpalletpackage: '',
productfamilyassociation: '',
under: '',
over: '',
alert: '',
type: '',
unitOfMaterial: '',
unitCost: 0,
packageCount: 0,
countPerPallet: 0,
countPerPalletPackage: 0,
primaryWidgetFamilyId: '',
secondaryWidgetFamilyId: '',
policiesMetadata: {
underStockLevelCount: 0,
overStockLevelCount: 0,
alertStockLevelCount: 0,
reorderStockLevelCount: 0
},
images: []
},
validationSchema: schema.addNewProduct,
validationSchema: schema.addNewItem,
onSubmit: (values, onSubmitProps) => {
LOGGER.log('values', values);
dispatch(
@@ -92,25 +101,33 @@ function AddNewProduct() {
slug: API.ADD_PRODUCT,
method: 'post',
data: {
commonName: values.warehousename,
formalName: values.formalname,
commonName: values.commonName,
formalName: values.formalName,
description: values.description,
manufacturer: values.manufacturer,
size: values.size,
color: values.color,
type: values.type,
unitOfMaterial: values.unitofmaterial,
unitCost: values.unitcost,
packageCount: values.packagecount,
countPerPallet: values.countperpallet,
countPerPalletPackage: values.countperpalletpackage,
customAttributes: [
{ fieldName: 'someName', fieldType: 'String', fieldValue: 'someValue' }
],
widgetFamilyId: '61dcdd10699e8f55b44c606d'
unitOfMaterial: values.unitOfMaterial,
unitCost: values.unitCost,
packageCount: values.packageCount,
countPerPallet: values.countPerPallet,
countPerPalletPackage: values.countPerPalletPackage,
customAttributes: [],
policiesMetadata: {
underStockLevelCount: values.policiesMetadata.underStockLevelCount,
overStockLevelCount: values.policiesMetadata.overStockLevelCount,
alertStockLevelCount: values.policiesMetadata.alertStockLevelCount,
reorderStockLevelCount: values.policiesMetadata.reorderStockLevelCount
},
widgetFamilyId:
values.secondaryWidgetFamilyId === ''
? values.primaryWidgetFamilyId
: values.secondaryWidgetFamilyId
}
})
);
// navigate to inventory page?
onSubmitProps.resetForm();
}
});
@@ -124,8 +141,8 @@ function AddNewProduct() {
{ name: 'Home', path: '/home' },
{ name: 'Setup', path: '/setup' },
{ name: 'Inventory', path: '/setup/inventory' },
{ name: 'Products' },
{ name: 'Add New Product' }
{ name: `${widgetName || 'Item'}` },
{ name: `Add New ${widgetName || 'Item'}` }
]}
/>
<Box mx={3} my={3}>
@@ -135,16 +152,16 @@ function AddNewProduct() {
<Grid item xs={12} sm={6} md={6}>
<Box component="div" sx={{ marginBottom: '24px' }}>
<Box component="div" className={classes.labelSize}>
Warehouse name
{widgetName || 'Item'} name
</Box>
<MDInput
fullWidth
name="warehousename"
name="commonName"
type="text"
variant="outlined"
value={formik.values.warehousename}
error={formik.touched.warehousename && Boolean(formik.errors.warehousename)}
helperText={formik.touched.warehousename && formik.errors.warehousename}
value={formik.values.commonName}
error={formik.touched.commonName && Boolean(formik.errors.commonName)}
helperText={formik.touched.commonName && formik.errors.commonName}
onChange={formik.handleChange}
/>
</Box>
@@ -170,91 +187,46 @@ function AddNewProduct() {
<Box component="div" className={classes.labelSize}>
Manufacturer
</Box>
<FormControl fullWidth>
<Select
select
<MDInput
fullWidth
variant="outlined"
name="manufacturer"
type="text"
variant="outlined"
value={formik.values.manufacturer}
error={formik.touched.manufacturer && Boolean(formik.errors.manufacturer)}
helperText={formik.touched.manufacturer && formik.errors.manufacturer}
onChange={formik.handleChange}
>
<MenuItem key={''} value={''}>
None Selected
</MenuItem>
{inventoryTypes.map((name) => (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
))}
</Select>
<FormHelperText>
{formik.errors.manufacturer &&
formik.touched.manufacturer &&
formik.errors.manufacturer}
</FormHelperText>
</FormControl>
/>
</Box>
<Box component="div" sx={{ marginBottom: '24px' }}>
<Box component="div" className={classes.labelSize}>
Type
</Box>
<FormControl fullWidth>
<Select
select
<MDInput
fullWidth
variant="outlined"
name="type"
type="text"
variant="outlined"
value={formik.values.type}
error={formik.touched.type && Boolean(formik.errors.type)}
helperText={formik.touched.type && formik.errors.type}
onChange={formik.handleChange}
>
<MenuItem key={''} value={''}>
None Selected
</MenuItem>
{inventoryTypes.map((name) => (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
))}
</Select>
<FormHelperText>
{formik.errors.type && formik.touched.type && formik.errors.type}
</FormHelperText>
</FormControl>
/>
</Box>
<Box component="div" sx={{ marginBottom: '24px' }}>
<Box component="div" className={classes.labelSize}>
Unit of Material
</Box>
<FormControl fullWidth>
<Select
select
<MDInput
fullWidth
name="unitOfMaterial"
type="text"
variant="outlined"
name="unitofmaterial"
value={formik.values.unitofmaterial}
error={
formik.touched.unitofmaterial && Boolean(formik.errors.unitofmaterial)
}
value={formik.values.unitOfMaterial}
error={formik.touched.unitOfMaterial && Boolean(formik.errors.unitOfMaterial)}
helperText={formik.touched.unitOfMaterial && formik.errors.unitOfMaterial}
onChange={formik.handleChange}
>
<MenuItem key={''} value={''}>
None Selected
</MenuItem>
{inventoryTypes.map((name) => (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
))}
</Select>
<FormHelperText>
{formik.errors.unitofmaterial &&
formik.touched.unitofmaterial &&
formik.errors.unitofmaterial}
</FormHelperText>
</FormControl>
/>
</Box>
<Box component="div" sx={{ marginBottom: '24px' }}>
<Box component="div" className={classes.labelSize}>
@@ -262,12 +234,12 @@ function AddNewProduct() {
</Box>
<MDInput
fullWidth
name="packagecount"
type="text"
name="packageCount"
type="number"
variant="outlined"
value={formik.values.packagecount}
error={formik.touched.packagecount && Boolean(formik.errors.packagecount)}
helperText={formik.touched.packagecount && formik.errors.packagecount}
value={formik.values.packageCount}
error={formik.touched.packageCount && Boolean(formik.errors.packageCount)}
helperText={formik.touched.packageCount && formik.errors.packageCount}
onChange={formik.handleChange}
/>
</Box>
@@ -279,12 +251,12 @@ function AddNewProduct() {
</Box>
<MDInput
fullWidth
name="formalname"
name="formalName"
type="text"
variant="outlined"
value={formik.values.formalname}
error={formik.touched.formalname && Boolean(formik.errors.formalname)}
helperText={formik.touched.formalname && formik.errors.formalname}
value={formik.values.formalName}
error={formik.touched.formalName && Boolean(formik.errors.formalName)}
helperText={formik.touched.formalName && formik.errors.formalName}
onChange={formik.handleChange}
/>
</Box>
@@ -292,57 +264,31 @@ function AddNewProduct() {
<Box component="div" className={classes.labelSize}>
Size
</Box>
<FormControl fullWidth>
<Select
select
<MDInput
fullWidth
variant="outlined"
name="size"
type="text"
variant="outlined"
value={formik.values.size}
error={formik.touched.size && Boolean(formik.errors.size)}
helperText={formik.touched.size && formik.errors.size}
onChange={formik.handleChange}
>
<MenuItem key={''} value={''}>
None Selected
</MenuItem>
{inventoryTypes.map((name) => (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
))}
</Select>
<FormHelperText>
{formik.errors.size && formik.touched.size && formik.errors.size}
</FormHelperText>
</FormControl>
/>
</Box>
<Box component="div" sx={{ marginBottom: '24px' }}>
<Box component="div" className={classes.labelSize}>
Color
</Box>
<FormControl fullWidth>
<Select
select
<MDInput
fullWidth
variant="outlined"
name="color"
type="text"
variant="outlined"
value={formik.values.color}
error={formik.touched.color && Boolean(formik.errors.color)}
helperText={formik.touched.color && formik.errors.color}
onChange={formik.handleChange}
>
<MenuItem key={''} value={''}>
None Selected
</MenuItem>
{inventoryTypes.map((name) => (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
))}
</Select>
<FormHelperText>
{formik.errors.color && formik.touched.color && formik.errors.color}
</FormHelperText>
</FormControl>
/>
</Box>
<Box component="div" sx={{ marginBottom: '24px' }}>
<Box component="div" className={classes.labelSize}>
@@ -350,12 +296,12 @@ function AddNewProduct() {
</Box>
<MDInput
fullWidth
name="unitcost"
type="text"
name="unitCost"
type="number"
variant="outlined"
value={formik.values.unitcost}
error={formik.touched.unitcost && Boolean(formik.errors.unitcost)}
helperText={formik.touched.unitcost && formik.errors.unitcost}
value={formik.values.unitCost}
error={formik.touched.unitCost && Boolean(formik.errors.unitCost)}
helperText={formik.touched.unitCost && formik.errors.unitCost}
onChange={formik.handleChange}
/>
</Box>
@@ -365,12 +311,12 @@ function AddNewProduct() {
</Box>
<MDInput
fullWidth
name="countperpallet"
type="text"
name="countPerPallet"
type="number"
variant="outlined"
value={formik.values.countperpallet}
error={formik.touched.countperpallet && Boolean(formik.errors.countperpallet)}
helperText={formik.touched.countperpallet && formik.errors.countperpallet}
value={formik.values.countPerPallet}
error={formik.touched.countPerPallet && Boolean(formik.errors.countPerPallet)}
helperText={formik.touched.countPerPallet && formik.errors.countPerPallet}
onChange={formik.handleChange}
/>
</Box>
@@ -380,16 +326,16 @@ function AddNewProduct() {
</Box>
<MDInput
fullWidth
name="countperpalletpackage"
type="text"
name="countPerPalletPackage"
type="number"
variant="outlined"
value={formik.values.countperpalletpackage}
value={formik.values.countPerPalletPackage}
error={
formik.touched.countperpalletpackage &&
Boolean(formik.errors.countperpalletpackage)
formik.touched.countPerPalletPackage &&
Boolean(formik.errors.countPerPalletPackage)
}
helperText={
formik.touched.countperpalletpackage && formik.errors.countperpalletpackage
formik.touched.countPerPalletPackage && formik.errors.countPerPalletPackage
}
onChange={formik.handleChange}
/>
@@ -398,32 +344,36 @@ function AddNewProduct() {
<Box component="div" className={classes.labelSize}>
Product Family Association
</Box>
<FormControl fullWidth sx={{ marginBottom: '32px' }}>
<FormControl fullWidth>
<Select
select
fullWidth
variant="outlined"
name="productfamilyassociation"
value={formik.values.productfamilyassociation}
name="primaryWidgetFamilyId"
value={formik.values.primaryWidgetFamilyId}
error={
formik.touched.productfamilyassociation &&
Boolean(formik.errors.productfamilyassociation)
formik.touched.primaryWidgetFamilyId &&
Boolean(formik.errors.primaryWidgetFamilyId)
}
onChange={formik.handleChange}
onChange={(e, ...rest) => {
setPFam(e.target.value);
formik.handleChange(e, ...rest);
}}
>
<MenuItem key={''} value={''}>
<MenuItem key={'none'} value={''}>
None Selected
</MenuItem>
{inventoryTypes.map((name) => (
<MenuItem key={name} value={name}>
{name}
{primaryFamily &&
primaryFamily.map((fam) => (
<MenuItem key={fam._id} value={fam._id}>
{fam.name}
</MenuItem>
))}
</Select>
<FormHelperText>
{formik.errors.productfamilyassociation &&
formik.touched.productfamilyassociation &&
formik.errors.productfamilyassociation}
{formik.errors.primaryWidgetFamilyId &&
formik.touched.primaryWidgetFamilyId &&
formik.errors.primaryWidgetFamilyId}
</FormHelperText>
</FormControl>
<FormControl fullWidth>
@@ -431,27 +381,28 @@ function AddNewProduct() {
select
fullWidth
variant="outlined"
name="productfamilyassociation"
value={formik.values.productfamilyassociation}
name="secondaryWidgetFamilyId"
value={formik.values.secondaryWidgetFamilyId}
error={
formik.touched.productfamilyassociation &&
Boolean(formik.errors.productfamilyassociation)
formik.touched.secondaryWidgetFamilyId &&
Boolean(formik.errors.secondaryWidgetFamilyId)
}
onChange={formik.handleChange}
>
<MenuItem key={''} value={''}>
<MenuItem key={'none'} value={''}>
None Selected
</MenuItem>
{inventoryTypes.map((name) => (
<MenuItem key={name} value={name}>
{name}
{secondaryFamily &&
secondaryFamily.map((fam) => (
<MenuItem key={fam._id} value={fam._id}>
{fam.name}
</MenuItem>
))}
</Select>
<FormHelperText>
{formik.errors.productfamilyassociation &&
formik.touched.productfamilyassociation &&
formik.errors.productfamilyassociation}
{formik.errors.secondaryWidgetFamilyId &&
formik.touched.secondaryWidgetFamilyId &&
formik.errors.secondaryWidgetFamilyId}
</FormHelperText>
</FormControl>
</Box>
@@ -459,7 +410,6 @@ function AddNewProduct() {
</Grid>
<Box>
<ImageUpload
multiple
heading="Upload Product Image"
accept="image/*"
images={formik.values.images}
@@ -482,7 +432,9 @@ function AddNewProduct() {
size="large"
color="primary"
variant="outlined"
onClick={handleClickOpen}
onClick={() => {
setOpen(true);
}}
>
add custom fields
</MDButton>
@@ -508,12 +460,10 @@ function AddNewProduct() {
</Box>
<MDInput
fullWidth
name="under"
name="policiesMetadata.underStockLevelCount"
type="number"
variant="outlined"
value={formik.values.under}
error={formik.touched.under && Boolean(formik.errors.under)}
helperText={formik.touched.under && formik.errors.under}
value={formik.values.policiesMetadata.underStockLevelCount}
onChange={formik.handleChange}
/>
</Box>
@@ -523,12 +473,10 @@ function AddNewProduct() {
</Box>
<MDInput
fullWidth
name="over"
name="policiesMetadata.overStockLevelCount"
type="number"
variant="outlined"
value={formik.values.over}
error={formik.touched.over && Boolean(formik.errors.over)}
helperText={formik.touched.over && formik.errors.over}
value={formik.values.policiesMetadata.overStockLevelCount}
onChange={formik.handleChange}
/>
</Box>
@@ -538,12 +486,23 @@ function AddNewProduct() {
</Box>
<MDInput
fullWidth
name="alert"
name="policiesMetadata.alertStockLevelCount"
type="number"
variant="outlined"
value={formik.values.alert}
error={formik.touched.alert && Boolean(formik.errors.alert)}
helperText={formik.touched.alert && formik.errors.alert}
value={formik.values.policiesMetadata.alertStockLevelCount}
onChange={formik.handleChange}
/>
</Box>
<Box>
<Box component="div" className={classes.labelSize}>
Reorder
</Box>
<MDInput
fullWidth
name="policiesMetadata.reorderStockLevelCount"
type="number"
variant="outlined"
value={formik.values.policiesMetadata.reorderStockLevelCount}
onChange={formik.handleChange}
/>
</Box>
@@ -558,10 +517,10 @@ function AddNewProduct() {
}}
>
<MDButton size="medium" color="error" variant="outlined">
cancel
Cancel
</MDButton>
<MDButton size="medium" color="primary" variant="contained" type="submit">
add ITem
Add {widgetName}
</MDButton>
</Box>
</Box>
@@ -584,7 +543,12 @@ function AddNewProduct() {
padding: '10px 20px'
}}
>
<MDButton sx={{ padding: '0px', minWidth: '14px' }} onClick={handleClose}>
<MDButton
sx={{ padding: '0px', minWidth: '14px' }}
onClick={() => {
setOpen(false);
}}
>
<CrossIcon className={classes.cursorPointer} />
</MDButton>
</Box>
@@ -628,7 +592,9 @@ function AddNewProduct() {
return <em>Placeholder</em>;
}
}}
onChange={handleChange}
onChange={(event) => {
setManufacturer(event.target.value);
}}
>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
@@ -648,7 +614,9 @@ function AddNewProduct() {
return <em>Placeholder</em>;
}
}}
onChange={handleChange}
onChange={(event) => {
setManufacturer(event.target.value);
}}
>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
@@ -783,4 +751,4 @@ function AddNewProduct() {
);
}
export default AddNewProduct;
export default AddNewItem;

View File

@@ -1,21 +1,24 @@
import React from 'react';
import DashboardNavbar from 'components/DashboardNavbar';
import DashboardLayout from 'layouts/DashboardLayout';
import { Box, Grid, MenuItem, Select, TableBody, TableCell, TableRow } from '@mui/material';
import { Box, Grid, MenuItem, Select } from '@mui/material';
import MDInput from 'components/MDInput';
import ImageUpload from 'components/ImageUpload';
import Switch from 'components/Switch';
import MDTypography from 'components/MDTypography';
import MDBox from 'components/MDBox';
import Dropdown from 'components/Dropdown';
import MDButton from 'components/Button';
import BasicTable from 'components/BasicTable';
import { useFormik } from 'formik';
import schema from 'services/ValidationServices';
import { API } from 'constant';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import InventoryActions from 'redux/InventoryRedux';
import LOGGER from 'services/Logger';
import Breadcrumbs from 'components/Breadcrumbs';
import { useParams } from 'react-router-dom';
import { InventorySelectors } from 'redux/InventoryRedux';
import { useNavigate } from 'react-router-dom';
import WidgetNestedDataTable from 'components/WidgetNestedDataTable';
const customStyles = {
labelSize: {
@@ -49,110 +52,98 @@ const customStyles = {
}
};
const stockBox = [
const definedPolicies = [
{
text: 'Stock Tracking'
text: 'Order Tracking',
key: 'orderTracking'
},
{
text: 'Replenishment'
text: 'Replenishment',
key: 'replenishment'
},
{
text: 'Alerting'
text: 'Alerting',
key: 'alerting'
},
{
text: 'Check In/Out'
},
{
text: 'Maintenance'
},
{
text: 'Location'
}
];
const records = [
{
level1: 'Ipsum',
level2: 'Vivera'
},
{
level1: 'Ipsum',
level2: 'Vivera'
}
];
const headCells = [
{
id: 'level1',
label: 'Level 1'
},
{
id: 'level2',
label: 'Level 2'
}
];
const dropdownData = [
{
ID: '1',
displayname: 'Regular, full time'
},
{
ID: '2',
displayname: 'Regular, part time'
},
{
ID: '3',
displayname: 'Contractor- Arise Max'
}
];
const inventoryTypes = ['Perishable', 'Material', 'Product', 'Inventory', 'Fleet'];
const dataLevel = [
{
placeholder: 'Lorem Ipsum',
label: 'Level 1'
},
{
placeholder: 'Lorem Ipsum',
label: 'Level 2'
text: 'Location',
key: 'preferredLocations'
}
];
function InventoryScreen() {
const dispatch = useDispatch();
const navigate = useNavigate();
const { inventoryId } = useParams();
const currentInventoryData = useSelector(InventorySelectors.getInventoryDetailById(inventoryId));
LOGGER.log({ currentInventoryData });
// const [inventoryAllData, setInventoryAllData] = useState([]);
// const initialInventoryName='';
// useEffect(() => {
// const filterData = inventoryData.filter((item) => item._id === inventoryId);
// console.log('filterData', filterData);
// setInitialInventoryName(filterData[0].name)
// setInventoryAllData(filterData[0].widgetName);
// }, []);
// LOGGER.log('initialInventoryName', initialInventoryName);
/* eslint-disable indent */
const formik = useFormik({
initialValues: {
inventoryname: '',
inventorytype: '',
widgetname: '',
policies: false,
images: []
initialValues: inventoryId
? {
name: currentInventoryData.name,
widgetName: currentInventoryData.widgetName,
policies: {
orderTracking: currentInventoryData.policies.orderTracking,
alerting: currentInventoryData.policies.alerting,
replenishment: currentInventoryData.policies.replenishment,
preferredLocations: false, // TODO: change later when implemented on BE
inventory_process: currentInventoryData.policies.inventory_process
},
image: [{ src: currentInventoryData.image }]
}
: {
name: '',
widgetName: '',
policies: {
orderTracking: false,
alerting: false,
replenishment: false,
preferredLocations: false,
inventory_process: 'CCR'
},
image: []
},
validationSchema: schema.addInventory,
onSubmit: (values, onSubmitProps) => {
LOGGER.log('values', values);
dispatch(
inventoryId
? dispatch(
InventoryActions.updateInventoryAction({
loader: 'loading-request',
slug: `${API.ADD_INVENTORY}/${inventoryId}`,
method: 'patch',
data: {
...values,
image: values.image[0]?.file || null
}
})
)
: dispatch(
InventoryActions.addInventoryAction({
loader: 'loading-request',
slug: API.ADD_INVENTORY,
method: 'post',
data: {
name: values.inventoryname,
type: values.inventorytype,
policies: {
alerting: {
lowestStockLevel: true,
highestStockLevel: true,
alertStockLevel: true,
reOrderLevel: true
}
}
...values,
image: values.image[0]?.file || null
}
})
);
// navigate to edit inventory page
onSubmitProps.resetForm();
}
});
@@ -187,67 +178,43 @@ function InventoryScreen() {
</Box>
<MDInput
fullWidth
name="inventoryname"
disabled={inventoryId}
name="name"
type="text"
variant="outlined"
value={formik.values.inventoryname}
error={formik.touched.inventoryname && Boolean(formik.errors.inventoryname)}
helpertText={formik.touched.inventoryname && formik.errors.inventoryname}
value={formik.values.name}
error={formik.touched.name && Boolean(formik.errors.name)}
helpertText={formik.touched.name && formik.errors.name}
onChange={formik.handleChange}
/>
</Box>
<Box component="div" sx={{ marginBottom: '20px' }}>
<Box component="div" sx={customStyles.labelSize}>
Inventory Type
</Box>
<Select
select
fullWidth
variant="outlined"
name="inventorytype"
value={formik.values.inventorytype}
error={formik.touched.inventorytype && Boolean(formik.errors.inventorytype)}
helperText={formik.touched.inventorytype && formik.errors.inventorytype}
onChange={formik.handleChange}
>
<MenuItem key={''} value={''}>
None Selected
</MenuItem>
{inventoryTypes.map((name) => (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
))}
</Select>
</Box>
<Grid item xs={12} sm={12} md={12}>
<Grid item xs={12}>
<Box component="div" sx={customStyles.labelSize}>
Widget Name
</Box>
<MDInput
fullWidth
name="widgetname"
disabled={inventoryId}
name="widgetName"
type="text"
variant="outlined"
value={formik.values.widgetname}
error={formik.touched.widgetname && Boolean(formik.errors.widgetname)}
helpertext={formik.touched.widgetname && formik.errors.widgetname}
value={formik.values.widgetName}
error={formik.touched.widgetName && Boolean(formik.errors.widgetName)}
helpertext={formik.touched.widgetName && formik.errors.widgetName}
onChange={formik.handleChange}
/>
</Grid>
<MDBox sx={{ my: 4 }}>
<MDTypography variant="h5">Policies</MDTypography>
<MDTypography sx={customStyles.textSize}>
Egestas pulvinar ornare vulputate porttitor consectetur condimentum at tellus
quis. Leo pellentesque ipsum, a purus dignissim aliquam, orci. Elementum
ullamcorper a sit eleifend ante ullamcorper ornare mi pharetra.
Choose policies to be applied
</MDTypography>
</MDBox>
<MDBox
mr={{ xs: 0, xl: 8 }}
sx={{
width: '40%',
width: '60%',
padding: '12.5px 10px',
backgroundColor: '#fff',
border: 'solid 0.5px #c4c4c4',
@@ -256,87 +223,69 @@ function InventoryScreen() {
}}
>
<div sx={customStyles.wrap}>
{stockBox.map((item) => (
<>
<div sx={customStyles.gridWrap}>
{definedPolicies.map((item) => (
<div sx={customStyles.gridWrap} key={item.key}>
<MDTypography sx={customStyles.textWrap}>{item.text}</MDTypography>
<Switch
name="policies"
checked={formik.values.policies}
disabled={inventoryId}
name={`policies.${item.key}`}
checked={formik.values.policies[item.key]}
onChange={formik.handleChange}
/>
</div>
</>
))}
<Box component="div" sx={{ marginBottom: '20px' }}>
<Box component="div" sx={customStyles.labelSize}>
Inventory Process
</Box>
<Select
select
fullWidth
disabled={inventoryId}
variant="outlined"
name="policies.inventory_process"
value={formik.values.policies.inventory_process}
onChange={formik.handleChange}
>
<MenuItem key="CCR" value="CCR">
CCR
</MenuItem>
<MenuItem key="PPR" value="PPR">
PPR
</MenuItem>
</Select>
</Box>
</div>
</MDBox>
</Grid>
<Grid item xs={12} sm={6} md={6}>
<ImageUpload
multiple
heading="Upload Inventory Images"
heading="Upload Inventory Image"
accept="image/*"
images={formik.values.images}
images={formik.values.image}
setImages={(images) => {
formik.setFieldValue('images', images);
formik.setFieldValue('image', images);
}}
/>
</Grid>
<Grid item xs={12} sm={12} md={12}>
<Grid container spacing={1}>
{dataLevel &&
dataLevel.map((item, index) => (
<Grid item xs={12} sm={6} md={4} key={index}>
<Dropdown items={item} dropdownData={dropdownData} />
</Grid>
))}
<Grid item xs={12} sm={6} md={4}>
<MDButton color="primary" circular="true" sx={customStyles.marginTop}>
{'add hierarchy level'}
</MDButton>
</Grid>
</Grid>
</Grid>
<Grid item xs={12} sm={12} md={12}>
<MDBox
sx={{
// backgroundColor: '#E5E5E5',
width: '100%',
padding: '9px'
}}
>
<MDTypography
sx={{
backgroundColor: '#E5E5E5',
width: '100%',
padding: '9px'
}}
>
Widget hierarchy
</MDTypography>
<BasicTable
headCells={headCells}
records={records}
backgroundColor="#E5E5E5"
color="#343434"
>
<TableBody>
{records &&
records.map((item) => (
<TableRow key={item.id}>
<TableCell>{item.level1}</TableCell>
<TableCell>{item.level2}</TableCell>
</TableRow>
))}
</TableBody>
</BasicTable>
</MDBox>
</Grid>
<MDBox sx={{ ml: 'auto', mr: 'auto', mt: 3 }}>
<MDButton sx={{ ml: 3 }} color="error" variant="outlined">
<MDButton
sx={{ ml: 3 }}
color="error"
variant="outlined"
onClick={() => {
navigate('/setup/inventory');
}}
>
{'CANCEL'}
</MDButton>
<MDButton sx={{ ml: 3 }} color="primary" variant="outlined" type="submit">
<MDButton
sx={{ ml: 3 }}
color="primary"
variant="outlined"
disabled={inventoryId}
type="submit"
>
{'SAVE'}
</MDButton>
<MDButton sx={{ ml: 3 }} color="primary">
@@ -346,6 +295,7 @@ function InventoryScreen() {
</Grid>
</MDBox>
</form>
{inventoryId ? <WidgetNestedDataTable inventoryId={inventoryId} /> : null}
</MDBox>
</DashboardLayout>
);

View File

@@ -5,42 +5,53 @@ import EquipmentIcon from 'assets/images/EquimpmentIcon';
import ProductsIcon from 'assets/images/ProductsIcon';
import FleetIcon from 'assets/images/FleetIcon';
import RawMaterialIcon from 'assets/images/RawMaterialIcon';
import InventoryActions from 'redux/InventoryRedux';
import { InventorySelectors } from 'redux/InventoryRedux';
import { Grid } from '@mui/material';
import Tile from 'components/TileComponent';
import MDButton from 'components/Button';
import { useNavigate } from 'react-router-dom';
import Breadcrumbs from 'components/Breadcrumbs';
import { useDispatch, useSelector } from 'react-redux';
import { useEffect, useState } from 'react';
import { API } from 'constant';
function getIconFromSlug(slug) {
switch (slug) {
case 'equipment':
return <EquipmentIcon />;
case 'product':
return <ProductsIcon />;
case 'fleet':
return <FleetIcon />;
case 'rawmaterial':
default:
return <RawMaterialIcon />;
}
}
function SetupInventory() {
const navigate = useNavigate();
const dispatch = useDispatch();
const inventoryData = useSelector(InventorySelectors.getInventoryDetail);
const [inventoryAllData, setInventoryAllData] = useState([]);
const tiles = [
{
name: 'Raw Material',
path: { update: '/', addNew: '/', cycleCount: '/', list: '/' },
icon: <RawMaterialIcon />
},
{
name: 'Products',
path: {
update: '/',
addNew: '/setup/inventory/product/add-new-product',
cycleCount: '/',
list: '/'
},
icon: <ProductsIcon />
},
{
name: 'Equipment',
path: { update: '/', addNew: '/', cycleCount: '/', list: '/' },
icon: <EquipmentIcon />
},
{
name: 'Fleet',
path: { update: '/', addNew: '/', cycleCount: '/', list: '/' },
icon: <FleetIcon />
useEffect(() => {
if (inventoryData?.length) {
setInventoryAllData(inventoryData);
}
];
}, [inventoryData]);
useEffect(() => {
dispatch(
InventoryActions.getInventoryAction({
loader: 'loading-request',
slug: API.GET_INVENTORY,
method: 'get'
})
);
}, []);
return (
<DashboardLayout>
<DashboardNavbar />
@@ -51,20 +62,18 @@ function SetupInventory() {
{ name: 'Inventory' }
]}
>
<MDButton
sx={{ ml: 3 }}
color="primary"
onClick={() => navigate('/setup/inventory/inventory-new')}
>
<MDButton sx={{ ml: 3 }} color="primary" onClick={() => navigate('/setup/inventory/new')}>
Create Inventory
</MDButton>
</Breadcrumbs>
<MDBox px={2} py={3}>
<Grid container spacing={2}>
{tiles &&
tiles.map((tile) => (
<Grid item xs={12} sm={6} md={tiles.length > 4 ? 4 : 6} key={tile.name}>
<Tile data={{ name: tile.name, path: tile.path }}>{tile.icon}</Tile>
{inventoryAllData &&
inventoryAllData.map((tile) => (
<Grid item xs={12} sm={6} md={inventoryAllData?.length > 4 ? 4 : 6} key={tile._id}>
<Tile data={{ name: tile?.name, widgetname: tile?.widgetName, id: tile?._id }}>
{getIconFromSlug(tile.icon_slug)}
</Tile>
</Grid>
))}
</Grid>

View File

@@ -5,9 +5,18 @@ import { getFetchingValue, getErrorValue } from '../services/Utils';
/* ------------- Types and Action Creators ------------- */
const { Types, Creators } = createActions({
getInventoryAction: ['payload'],
getInventorySuccess: ['data'],
getInventoryFailure: ['error'],
addInventoryAction: ['payload'],
addInventorySuccess: ['data'],
addInventoryFailure: ['error']
addInventoryFailure: ['error'],
updateInventoryAction: ['payload'],
updateInventorySuccess: ['data'],
updateInventoryFailure: ['error'],
getInventoryTypesAction: ['payload'],
getInventoryTypesSuccess: ['data'],
getInventoryTypesFailure: ['error']
});
export const InventoryTypes = Types;
@@ -16,17 +25,44 @@ export default InventoryActions;
/* ------------- Initial State ------------- */
export const INITIAL_STATE = Immutable({
getInventoryDetail: [],
addInventoryDetail: [],
updateInventoryDetail: [],
addInventoryLoading: false,
addInventoryerror: {}
addInventoryerror: {},
inventoryTypes: []
});
/* ------------- Selectors ------------- */
export const InventorySelectors = {
addInventoryDetail: (state) => state.inventory.inventoryDetail
addInventoryDetail: (state) => state.inventory.inventoryDetail,
getInventoryDetail: (state) => state.inventory.getInventoryDetail,
getInventoryDetailById: (id) => (state) =>
state.inventory.getInventoryDetail.find((x) => x._id === id),
getInventoryTypes: (state) => state.inventory.inventoryTypes,
updateInventoryDetail: (state) => state.inventory.updateInventoryDetail
};
/* ------------- Reducers ------------- */
export const onGetInventoryAction = (state, { payload }) =>
state.merge({
fetching: _.uniq([state.fetching, payload?.loader]),
error: getErrorValue(state?.error, payload?.loader)
});
export const onGetInventorySuccess = (state, { data }) =>
state.merge({
fetching: getFetchingValue(state.fetching, data?.loader),
error: getErrorValue(state?.error, data?.loader),
getInventoryDetail: data.getInventoryDetail
});
export const onGetInventoryFailure = (state, { error }) =>
state.merge({
fetching: _.without(state.fetching, error?.loader),
error: { ...state.error, [error?.loader]: error?.error }
});
export const onAddInventoryAction = (state, { payload }) =>
state.merge({
fetching: _.uniq([state.fetching, payload?.loader]),
@@ -37,7 +73,7 @@ export const onAddInventorySuccess = (state, { data }) =>
state.merge({
fetching: getFetchingValue(state.fetching, data?.loader),
error: getErrorValue(state?.error, data?.loader),
addInventoryDetail: data.addInventoryDetail
getInventoryDetail: [...state.getInventoryDetail, data.newInventory]
});
export const onAddInventoryFailure = (state, { error }) =>
@@ -46,9 +82,59 @@ export const onAddInventoryFailure = (state, { error }) =>
error: { ...state.error, [error?.loader]: error?.error }
});
export const onUpdateInventoryAction = (state, { payload }) =>
state.merge({
fetching: _.uniq([state.fetching, payload?.loader]),
error: getErrorValue(state?.error, payload?.loader)
});
export const onUpdateInventorySuccess = (state, { data }) =>
state.merge({
fetching: getFetchingValue(state.fetching, data?.loader),
error: getErrorValue(state?.error, data?.loader),
getInventoryDetail: [
...state.getInventoryDetail.filter((x) => x._id !== data.newInventory._id),
data.newInventory
]
});
export const onUpdateInventoryFailure = (state, { error }) =>
state.merge({
fetching: _.without(state.fetching, error?.loader),
error: { ...state.error, [error?.loader]: error?.error }
});
export const onGetInventoryTypesAction = (state, { payload }) =>
state.merge({
fetching: _.uniq([state.fetching, payload?.loader]),
error: getErrorValue(state?.error, payload?.loader)
});
export const onGetInventoryTypesSuccess = (state, { data }) =>
state.merge({
fetching: getFetchingValue(state.fetching, data?.loader),
error: getErrorValue(state?.error, data?.loader),
inventoryTypes: data.inventoryTypes
});
export const onGetInventoryTypesFailure = (state, { error }) =>
state.merge({
fetching: _.without(state.fetching, error?.loader),
error: { ...state.error, [error?.loader]: error?.error }
});
/* ------------- Hookup Reducers To Types ------------- */
export const inventoryReducer = createReducer(INITIAL_STATE, {
[Types.GET_INVENTORY_ACTION]: onGetInventoryAction,
[Types.GET_INVENTORY_SUCCESS]: onGetInventorySuccess,
[Types.GET_INVENTORY_FAILURE]: onGetInventoryFailure,
[Types.ADD_INVENTORY_ACTION]: onAddInventoryAction,
[Types.ADD_INVENTORY_SUCCESS]: onAddInventorySuccess,
[Types.ADD_INVENTORY_FAILURE]: onAddInventoryFailure
[Types.ADD_INVENTORY_FAILURE]: onAddInventoryFailure,
[Types.UPDATE_INVENTORY_ACTION]: onUpdateInventoryAction,
[Types.UPDATE_INVENTORY_SUCCESS]: onUpdateInventorySuccess,
[Types.UPDATE_INVENTORY_FAILURE]: onUpdateInventoryFailure,
[Types.GET_INVENTORY_TYPES_ACTION]: onGetInventoryTypesAction,
[Types.GET_INVENTORY_TYPES_SUCCESS]: onGetInventoryTypesSuccess,
[Types.GET_INVENTORY_TYPES_FAILURE]: onGetInventoryTypesFailure
});

101
src/redux/WidgetRedux.js Normal file
View File

@@ -0,0 +1,101 @@
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({
widgetRequest: ['payload'],
widgetSuccess: ['data'],
editWidgetRequest: ['payload'],
editWidgetSuccess: ['data'],
widgetFailure: ['error'],
logout: null
});
export const WidgetTypes = Types;
const WidgetActions = Creators;
export default WidgetActions;
/* ------------- Initial State ------------- */
export const INITIAL_STATE = Immutable({
list: [],
fetching: [],
error: {}
});
/* ------------- Selectors ------------- */
export const WidgetSelectors = {
getWidgets: (state) => state.widgets.list,
getWidgetById: (id) => (state) => state.widgets.list.find((x) => x._id === id),
getWidgetsByInventoryId: (id) => (state) =>
state.widgets.list.filter((x) => x.inventory._id === id),
getWidgetFamiliesByInventoryId: (id) => (state) =>
state.widgets.list.filter((x) => !x.parent && x.inventory._id === id),
getWidgetsByParentId: (id) => (state) => state.widgets.list.filter((x) => x.parent?._id === id)
};
/* ------------- Reducers ------------- */
export const onWidgetRequest = (state, { payload }) =>
state.merge({
fetching: _.uniq([...state.fetching, payload?.loader]),
error: getErrorValue(state?.error, payload?.loader)
});
export const onEditWidgetRequest = (state, { payload }) =>
state.merge({
fetching: _.uniq([...state.fetching, payload?.loader]),
error: getErrorValue(state?.error, payload?.loader)
});
const mergeWidgetStates = (stateData, widgets) => {
if (!widgets) return stateData; // undefined check
const idsInNewWidgets = widgets.map((x) => x._id);
const newState = stateData.filter((x) => !idsInNewWidgets.includes(x._id));
return [...newState, ...widgets];
};
export const onWidgetSuccess = (state, { data }) =>
state.merge({
fetching: getFetchingValue(state.fetching, data?.loader),
error: getErrorValue(state?.error, data?.loader),
list: mergeWidgetStates(state.list, data.widgets)
});
const mergeEditWidgetStates = (stateList, widget, type) => {
if (!widget) return stateList; // undefined check
if (type === 'add') {
return [...stateList, widget];
} else if (type === 'edit') {
const newState = stateList.filter((x) => x._id !== widget._id);
return [...newState, widget];
} else {
return stateList;
}
};
export const onEditWidgetSuccess = (state, { data }) =>
state.merge({
fetching: getFetchingValue(state.fetching, data?.loader),
error: getErrorValue(state?.error, data?.loader),
list: mergeEditWidgetStates(state.list, data.widget, data.type)
});
export const onWidgetFailure = (state, { error }) =>
state.merge({
fetching: _.without(state.fetching, error?.loader),
error: { ...state.error, [error?.loader]: error?.error }
});
/* ------------- Hookup Reducers To Types ------------- */
export const widgetReducer = createReducer(INITIAL_STATE, {
[Types.WIDGET_REQUEST]: onWidgetRequest,
[Types.EDIT_WIDGET_REQUEST]: onEditWidgetRequest,
[Types.WIDGET_SUCCESS]: onWidgetSuccess,
[Types.EDIT_WIDGET_SUCCESS]: onEditWidgetSuccess,
[Types.WIDGET_FAILURE]: onWidgetFailure
});

View File

@@ -6,6 +6,7 @@ import { productReducer } from './ProductsRedux';
import { inventoryReducer } from './InventoryRedux';
import { rolesReducer } from './RolesRedux';
import { WarehouseLocationsReducer } from './WarehouseLocationsRedux';
import { widgetReducer } from './WidgetRedux';
// Combine all reducers.
const appReducer = combineReducers({
@@ -15,7 +16,8 @@ const appReducer = combineReducers({
roles: rolesReducer,
warehouseLocations: WarehouseLocationsReducer,
product: productReducer,
inventory: inventoryReducer
inventory: inventoryReducer,
widgets: widgetReducer
});
const rootReducer = (state, action) => {

View File

@@ -57,7 +57,7 @@ import LabelingHome from 'pages/labellingHome';
import SetupInventory from 'pages/setupInventory';
import HomeIcon from 'assets/images/HomeIcon';
import SetupIcon from 'assets/images/SetupIcon';
import AddNewProduct from '../pages/addNewProduct';
import AddNewItem from '../pages/addNewProduct';
import CreateUserRole from 'pages/createUserRole';
import WidgetLabel from 'pages/widgetLabel';
@@ -141,15 +141,22 @@ const protectedRoutes = [
name: 'Inventory Definition',
key: 'inventory-new',
hide: true,
route: '/setup/inventory/inventory-new',
route: '/setup/inventory/new',
component: <InventoryScreen />
},
{
name: 'Add New Product',
key: 'add-new-product',
name: 'Inventory Definition',
key: 'inventory-update',
hide: true,
route: '/setup/inventory/product/add-new-product',
component: <AddNewProduct />
route: '/setup/inventory/update/:inventoryId',
component: <InventoryScreen />
},
{
name: 'Add New Item',
key: 'add-new-item',
hide: true,
route: '/setup/inventory/new-item/:widgetName/:inventoryId',
component: <AddNewItem />
},
{
name: 'Location Labeling',

View File

@@ -1,21 +1,85 @@
import { AuthorizedAPI } from 'config';
import { takeLatest, call, put } from 'redux-saga/effects';
import { takeLatest, call, put, takeEvery } from 'redux-saga/effects';
import InventoryActions from 'redux/InventoryRedux';
import { InventoryTypes } from 'redux/InventoryRedux';
import ApiServices from 'services/API/ApiServices';
export function* onRequestAddInventoryData({ payload }) {
export function* onRequestGetInventoryData({ payload }) {
const response = yield call(
ApiServices[payload?.method],
AuthorizedAPI,
payload?.slug,
payload?.data
);
if (response?.status === 200) {
yield put(
InventoryActions.getInventorySuccess({
loader: payload?.loader,
getInventoryDetail: response?.data?.data
})
);
} else {
payload.onFailedGetInventoryData(response.data.error);
yield put(
InventoryActions.getInventoryFailure({
loader: payload?.loader,
error: response?.data
})
);
}
}
export function* onRequestGetInventoryTypesData({ payload }) {
const response = yield call(
ApiServices[payload?.method],
AuthorizedAPI,
payload?.slug,
payload?.data
);
if (response?.status === 200) {
yield put(
InventoryActions.getInventoryTypesSuccess({
loader: payload?.loader,
inventoryTypes: response?.data?.data
})
);
} else {
payload.onFailedGetInventoryData(response.data.error);
yield put(
InventoryActions.getInventoryTypesFailure({
loader: payload?.loader,
error: response?.data
})
);
}
}
const parseDataToFormData = (data) => {
var formData = new FormData();
formData.append('name', data.name);
formData.append('widgetName', data.widgetName);
formData.append('icon_slug', 'testslug');
formData.append('policies[orderTracking]', data.policies.orderTracking);
formData.append('policies[alerting]', data.policies.alerting);
formData.append('policies[replenishment]', data.policies.replenishment);
formData.append('policies[preferredLocations]', data.policies.preferredLocations);
formData.append('policies[inventory_process]', data.policies.inventory_process);
data.image && formData.append('image', data.image);
return formData;
};
export function* onRequestAddInventoryData({ payload }) {
const response = yield call(
ApiServices[payload?.method],
AuthorizedAPI,
payload?.slug,
parseDataToFormData(payload?.data)
);
if (response?.status === 200) {
yield put(
InventoryActions.addInventorySuccess({
loader: payload?.loader,
addInventoryDetail: response?.data?.data
newInventory: response?.data?.data?.inventoryData
})
);
} else {
@@ -28,4 +92,34 @@ export function* onRequestAddInventoryData({ payload }) {
);
}
}
export default [takeLatest(InventoryTypes.ADD_INVENTORY_ACTION, onRequestAddInventoryData)];
export function* onRequestUpdateInventoryData({ payload }) {
const response = yield call(
ApiServices[payload?.method],
AuthorizedAPI,
payload?.slug,
payload?.data
);
if (response?.status === 200) {
yield put(
InventoryActions.updateInventorySuccess({
loader: payload?.loader,
updateInventoryDetail: response?.data?.data
})
);
} else {
payload.onFailedUpdateInventoryData(response.data.error);
yield put(
InventoryActions.updateInventoryFailure({
loader: payload?.loader,
error: response?.data
})
);
}
}
export default [
takeLatest(InventoryTypes.GET_INVENTORY_ACTION, onRequestGetInventoryData),
takeLatest(InventoryTypes.ADD_INVENTORY_ACTION, onRequestAddInventoryData),
takeLatest(InventoryTypes.UPDATE_INVENTORY_ACTION, onRequestUpdateInventoryData),
takeEvery(InventoryTypes.GET_INVENTORY_TYPES_ACTION, onRequestGetInventoryTypesData)
];

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

@@ -0,0 +1,58 @@
import { AuthorizedAPI } from 'config';
import { call, put, takeEvery } from 'redux-saga/effects';
import ApiServices from 'services/API/ApiServices';
import WidgetActions, { WidgetTypes } from '../redux/WidgetRedux';
export function* onRequestWidget({ payload }) {
const response = yield call(
ApiServices[payload?.method],
AuthorizedAPI,
payload?.slug,
payload?.data
);
if (response?.status === 200) {
yield put(
WidgetActions.widgetSuccess({
loader: payload?.loader,
widgets: response?.data?.data
})
);
} else {
yield put(
WidgetActions.widgetFailure({
loader: payload?.loader,
error: response?.message
})
);
}
}
export function* onEditRequestWidget({ payload }) {
const response = yield call(
ApiServices[payload?.method],
AuthorizedAPI,
payload?.slug,
payload?.data
);
if (response?.status === 200) {
yield put(
WidgetActions.editWidgetSuccess({
loader: payload?.loader,
widget: response?.data?.data,
type: payload?.type
})
);
} else {
yield put(
WidgetActions.widgetFailure({
loader: payload?.loader,
error: response?.message
})
);
}
}
export default [
takeEvery(WidgetTypes.WIDGET_REQUEST, onRequestWidget),
takeEvery(WidgetTypes.EDIT_WIDGET_REQUEST, onEditRequestWidget)
];

View File

@@ -6,6 +6,7 @@ import ProductSaga from './Product';
import InventorySaga from './Inventory';
import RolesSaga from './Roles';
import WarehouseLocationsSaga from './WarehouseLocations';
import WidgetSaga from './Widget';
export default function* rootSaga() {
yield all([...AuthSaga]);
@@ -15,4 +16,5 @@ export default function* rootSaga() {
yield all([...InventorySaga]);
yield all([...RolesSaga]);
yield all([...WarehouseLocationsSaga]);
yield all([...WidgetSaga]);
}

View File

@@ -28,33 +28,41 @@ const schema = {
attributes: Yup.string('Enter other attributes')
}),
addNewProduct: Yup.object({
warehousename: Yup.string('Enter warehouse name').required('warehouse name is required'),
description: Yup.string('Enter Description').required('description is required'),
manufacturer: Yup.string('Enter manufacturer').required('manufacturer is required'),
type: Yup.string('Enter type').required('type is required'),
unitofmaterial: Yup.string('Enter unitofmaterial').required('Unit of material is required'),
packagecount: Yup.number('Enter packagecount').required('Package Count is required'),
formalname: Yup.string('Enter formal name').required('Formal Name is required'),
size: Yup.string('Enter Size').required('Size is required'),
color: Yup.string('Enter Color').required('Color is required'),
unitcost: Yup.number('Enter UnitCost').required('Unit Cost is required'),
countperpallet: Yup.number('Enter countperpallet').required('Count per pallet is required'),
countperpalletpackage: Yup.number('Enter countperpalletpackage').required(
'count per pallet package is required'
),
productfamilyassociation: Yup.string('Enter productfamilyassociation').required(
'product Family Association is required'
),
under: Yup.number().required('required'),
over: Yup.number().required('required'),
alert: Yup.number().required('required')
addNewItem: Yup.object({
commonName: Yup.string('Enter details').required(),
formalName: Yup.string('Enter details').required(),
description: Yup.string('Enter details').required(),
manufacturer: Yup.string('Enter details').required(),
size: Yup.string('Enter details').required(),
color: Yup.string('Enter details').required(),
type: Yup.string('Enter details').required(),
unitOfMaterial: Yup.string('Enter details').required(),
unitCost: Yup.number().test((val) => val >= 0),
packageCount: Yup.number().test((val) => val >= 0),
countPerPallet: Yup.number().test((val) => val >= 0),
countPerPalletPackage: Yup.number().test((val) => val >= 0),
primaryWidgetFamilyId: Yup.string('Enter details').required(),
secondaryWidgetFamilyId: Yup.string('Enter details'),
policiesMetadata: Yup.object({
underStockLevelCount: Yup.number().test((val) => val >= 0),
overStockLevelCount: Yup.number().test((val) => val >= 0),
alertStockLevelCount: Yup.number().test((val) => val >= 0),
reorderStockLevelCount: Yup.number().test((val) => val >= 0)
}),
images: Yup.array()
}),
addInventory: Yup.object({
inventoryname: Yup.string('Enter Inventory name').required('Inventory name is required'),
inventorytype: Yup.string('Enter inventory Type').required('inventory Type is required'),
widgetname: Yup.string('Enter Widget Name').required('Widget Name is required')
name: Yup.string('Enter Widget Name').required('Widget Name is required'),
widgetName: Yup.string('Enter Inventory name').required('Inventory name is required'),
policies: Yup.object({
orderTracking: Yup.boolean(),
alerting: Yup.boolean(),
replenishment: Yup.boolean(),
preferredLocations: Yup.boolean(),
inventory_process: Yup.string()
}),
image: Yup.array()
})
};