Fixes/items-and-images (#73)

* Fixed: warehouse preferred inventories upload
* Fix: item redux saga
* Item listing
* Added: add item
* Added: edit handler
* Update: add item
* Update: multi images
* Fix: multiple images
* Fix: item api mapping
* Update: added keys

Co-authored-by: Llewellyn Dsouza <lledsouza2209@gmail.com>
This commit is contained in:
bluestreamlds
2022-03-02 23:09:36 +05:30
committed by GitHub
parent 92e5325fdc
commit e794421938
14 changed files with 688 additions and 218 deletions

View File

@@ -44,6 +44,7 @@ const buildBreadcrumbs = (route, light) => {
fontWeight="regular"
textTransform="capitalize"
color={light ? 'white' : 'dark'}
key={el}
sx={{ lineHeight: 0 }}
>
{el.name}

View File

@@ -1,12 +1,12 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import PropTypes from 'prop-types';
import SearchBar from 'components/SearchBar';
// import SearchBar from 'components/SearchBar';
import MDButton from 'components/Button';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import Fade from '@mui/material/Fade';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
// import Menu from '@mui/material/Menu';
// import MenuItem from '@mui/material/MenuItem';
// import Fade from '@mui/material/Fade';
// import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import { styled } from '@mui/material/styles';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
@@ -19,6 +19,7 @@ import Paper from '@mui/material/Paper';
// import IconButton from '@mui/material/IconButton';
// import Collapse from '@mui/material/Collapse';
import TablePagination from 'components/TablePagination';
import { MenuItem, Select } from '@mui/material';
const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
@@ -47,16 +48,16 @@ const StyledTableRow = styled(TableRow)(({ theme }) => ({
Row.propTypes = {
rowData: PropTypes.array,
tHeads: PropTypes.array
tHeads: PropTypes.array,
editHandler: PropTypes.any
};
function Row({ tHeads, rowData }) {
function Row({ tHeads, rowData, editHandler }) {
return (
<React.Fragment>
<StyledTableRow sx={{ '&odd > *': { borderBottom: 'unset' } }}>
<StyledTableCell sx={{ width: '10%', display: 'flex', alignItems: 'center' }}>
<MDButton
disabled
size="small"
variant="contained"
color="primary"
@@ -69,6 +70,9 @@ function Row({ tHeads, rowData }) {
fontWeight: '500',
padding: '0'
}}
onClick={() => {
editHandler(rowData._id);
}}
>
EDIT
</MDButton>
@@ -82,7 +86,7 @@ function Row({ tHeads, rowData }) {
);
}
function EnhancedTable({ data, tHeads }) {
function EnhancedTable({ count, page, setPage, perPage, setPerPage, data, tHeads, editHandler }) {
// const [anchorEl, setAnchorEl] = React.useState(false);
// const open = Boolean(anchorEl);
// const handleClick = (event) => {
@@ -101,7 +105,7 @@ function EnhancedTable({ data, tHeads }) {
overflow: 'hidden'
}}
>
<Box
{/* <Box
sx={{
display: 'flex',
alignItems: 'center',
@@ -160,7 +164,7 @@ function EnhancedTable({ data, tHeads }) {
<MenuItem>Logout</MenuItem>
</Menu>
</Box>
</Box>
</Box> */}
{/* Table-row- */}
<TableContainer component={Paper} sx={{ borderRadius: '0 !important', boxShadow: 'none' }}>
<Table aria-label="collapsible table" sx={{ minWidth: 700 }}>
@@ -174,7 +178,14 @@ function EnhancedTable({ data, tHeads }) {
</TableHead>
<TableBody>
{data &&
data.map((rowData) => <Row key={rowData._id} rowData={rowData} tHeads={tHeads} />)}
data.map((rowData) => (
<Row
key={rowData._id}
editHandler={editHandler}
rowData={rowData}
tHeads={tHeads}
/>
))}
</TableBody>
</Table>
</TableContainer>
@@ -265,10 +276,29 @@ function EnhancedTable({ data, tHeads }) {
</Box>
</Box> */}
{/*---- pagination- */}
<Box>
<TablePagination />
<Box sx={{ fontSize: '14px', color: '#000' }}>
Per page:{' '}
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={perPage}
label="Age"
onChange={(e) => {
setPerPage(e.target.value);
}}
>
<MenuItem value={10}>5</MenuItem>
<MenuItem value={20}>10</MenuItem>
<MenuItem value={30}>20</MenuItem>
</Select>
</Box>
<Box>
<TablePagination count={Math.ceil(count / perPage)} page={page} setPage={setPage} />
</Box>
<Box sx={{ fontSize: '14px', color: '#000' }}>
[{(page - 1) * perPage + 1} to {perPage * page > count ? count : perPage * page} of{' '}
{count}]
</Box>
<Box sx={{ fontSize: '14px', color: '#000' }}>[1 to 10 of 92]</Box>
</Box>
</Box>
</>
@@ -276,8 +306,14 @@ function EnhancedTable({ data, tHeads }) {
}
EnhancedTable.propTypes = {
count: PropTypes.number,
page: PropTypes.number,
setPage: PropTypes.any,
perPage: PropTypes.number,
setPerPage: PropTypes.any,
data: PropTypes.array,
tHeads: PropTypes.array
tHeads: PropTypes.array,
editHandler: PropTypes.any
};
export default EnhancedTable;

View File

@@ -0,0 +1,128 @@
/* eslint-disable no-case-declarations */
import MDBox from 'components/MDBox';
import PropTypes from 'prop-types';
import UploadIcon from 'assets/images/UploadIcon';
import MDTypography from 'components/MDTypography';
import pxToRem from 'assets/theme-dark/functions/pxToRem';
import { Button } from '@mui/material';
import Close from 'assets/images/Close';
function ImageUpload({ heading, accept, multiple, images, setImages }) {
const addImage = (e) => {
setImages([
...images,
{ src: URL.createObjectURL(e.target.files[0]), file: e.target.files[0] }
]);
};
const removeImage = (index) => {
setImages(images.filter((_val, idx) => idx !== index));
};
return (
<>
<MDBox
sx={{
border: '1px solid #c4c4c4',
borderRadius: pxToRem(8),
padding: pxToRem(16)
}}
>
<MDBox
sx={{
border: '1px dashed #C4C4C4',
borderRadius: pxToRem(6),
cursor: 'pointer',
position: 'relative',
textAlign: 'center',
minHeight: pxToRem(200),
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginBottom: '16px'
}}
>
<MDBox
component="input"
name="file"
disabled={!multiple && images.length}
accept={accept}
type="file"
sx={{
width: '100%',
opacity: '0',
cursor: 'pointer',
position: 'absolute',
top: '0',
left: '0',
right: '0',
bottom: '0'
}}
onChange={addImage}
/>
<MDBox component="span">
{!multiple && images.length ? null : <UploadIcon />}
<MDTypography
component="span"
sx={{ color: '#000', letterSpacing: '0.01em', display: 'block' }}
>
{!multiple && images.length ? 'Cannot add more images' : heading}
</MDTypography>
</MDBox>
</MDBox>
{/* -----------img-preview----------- */}
<MDBox sx={{ marginBottom: '-10px' }}>
{images &&
images.map((item, idx) => {
return (
<MDBox
key={idx}
component="span"
sx={{
width: '80px',
height: '63px',
marginRight: '16px',
display: 'inline-block',
borderRadius: '4px',
position: 'relative'
}}
>
<img src={item.src} alt="placeholder" width="100%" />
<Button
sx={{
backgroundColor: '#fff !important',
boxShadow: '0px 1px 1px rgb(0 0 0 / 25%)',
padding: '0',
minWidth: '20px',
minHeight: '20px',
borderRadius: '100%',
position: 'absolute',
right: '4px',
top: '4px',
'&:hover': {
backgroundColor: 'red !important'
}
}}
onClick={() => {
removeImage(idx);
}}
>
<Close />
</Button>
</MDBox>
);
})}
</MDBox>
</MDBox>
</>
);
}
export default ImageUpload;
ImageUpload.propTypes = {
images: PropTypes.array,
heading: PropTypes.string,
multiple: PropTypes.bool,
accept: PropTypes.string,
setImages: PropTypes.func
};

View File

@@ -1,33 +1,43 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import Pagination from '@mui/material/Pagination';
import Stack from '@mui/material/Stack';
import { PaginationItem } from '@mui/material';
import NextIcon from 'assets/images/NextIcon';
import PreviousIcon from 'assets/images/PreviousIcon';
function TablePagination() {
function TablePagination({ count, page, setPage }) {
return (
<Stack spacing={2}>
<Pagination
count={10}
shape="rounded"
color="primary"
size="large"
<Pagination
count={count}
page={page}
shape="rounded"
color="primary"
size="large"
renderItem={(item) => (
<PaginationItem
components={{ previous: PreviousIcon, next: NextIcon }}
{...item}
sx={{
minWidth:'20px',
fontWeight:'500',
minWidth: '20px',
fontWeight: '500',
height: '30px'
}}
/>
)}
onChange={(event, value) => {
setPage(value);
}}
/>
</Stack>
);
}
TablePagination.propTypes = {
page: PropTypes.number,
count: PropTypes.number,
setPage: PropTypes.any
};
export default TablePagination;

View File

@@ -11,7 +11,8 @@ export default {
ADD_NEW_BAY: '/bay',
ADD_NEW_LEVEL: '/level',
ADD_NEW_SUBLEVEL: '/sublevel',
ADD_PRODUCT: '/item/',
ADD_ITEM: '/item/',
EDIT_ITEM: '/item/',
ADD_INVENTORY: '/inventory',
GET_INVENTORY: '/inventory/all?page=0&perPage=50',
GET_INVENTORY_TYPES: '/inventory/types',

View File

@@ -1,3 +1,4 @@
/* eslint-disable indent */
/* eslint-disable complexity */
import * as React from 'react';
import { Grid, TextField, Box, FormHelperText, TextareaAutosize } from '@mui/material';
@@ -7,7 +8,7 @@ import DashboardLayout from 'layouts/DashboardLayout';
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
import ImageUpload from 'components/ImageUpload';
import ImageUploadMultiple from 'components/ImageUploadMultiple';
import MDButton from 'components/Button';
import Dialog from '@mui/material/Dialog';
import DialogContent from '@mui/material/DialogContent';
@@ -16,13 +17,15 @@ import { useFormik } from 'formik';
import schema from 'services/ValidationServices';
import MDInput from 'components/MDInput';
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';
import ItemActions from 'redux/ItemRedux';
import { useNavigate } from 'react-router-dom';
import { ItemSelectors } from 'redux/ItemRedux';
const useStyles = makeStyles({
labelSize: {
@@ -48,11 +51,68 @@ const useStyles = makeStyles({
function AddNewItem() {
const classes = useStyles();
const { widgetName, inventoryId } = useParams();
const { widgetName, inventoryId, itemId } = useParams();
const getInitialFormValues = (data) => {
return data && data._id === itemId
? {
commonName: data.commonName,
formalName: data.formalName,
description: data.description,
manufacturer: data.manufacturer,
size: data.size,
color: data.color,
type: data.type,
unitOfMaterial: data.unitOfMaterial,
unitCost: data.unitCost,
packageCount: data.packageCount,
countPerPallet: data.countPerPallet,
countPerPalletPackage: data.countPerPalletPackage,
primaryWidgetFamilyId: data.widgetFamily.parent
? data.widgetFamily.parent
: data.widgetFamily._id,
secondaryWidgetFamilyId: data.widgetFamily.parent ? data.widgetFamily._id : '',
policiesMetadata: {
underStockLevelCount: data.policiesMetadata.underStockLevelCount,
overStockLevelCount: data.policiesMetadata.overStockLevelCount,
alertStockLevelCount: data.policiesMetadata.alertStockLevelCount,
reorderStockLevelCount: data.policiesMetadata.reorderStockLevelCount
},
images: data.images.map((img) => ({ ...img, src: img.url }))
}
: {
commonName: '',
formalName: '',
description: '',
manufacturer: '',
size: '',
color: '',
type: '',
unitOfMaterial: '',
unitCost: 0,
packageCount: 0,
countPerPallet: 0,
countPerPalletPackage: 0,
primaryWidgetFamilyId: '',
secondaryWidgetFamilyId: '',
policiesMetadata: {
underStockLevelCount: 0,
overStockLevelCount: 0,
alertStockLevelCount: 0,
reorderStockLevelCount: 0
},
images: []
};
};
const itemData = getInitialFormValues(useSelector(ItemSelectors.getFormItem(itemId)));
const dispatch = useDispatch();
const [Manufacturer, setManufacturer] = React.useState('');
const [open, setOpen] = React.useState(false);
const navigate = useNavigate();
const navigateTo = (path) => {
navigate(path);
};
React.useEffect(() => {
dispatch(
WidgetActions.widgetRequest({
@@ -61,74 +121,99 @@ function AddNewItem() {
method: 'get'
})
);
itemId &&
dispatch(
ItemActions.oneItemRequest({
loader: 'location-request',
slug: API.EDIT_ITEM,
method: 'get',
widgetName,
inventoryId,
itemId
})
);
}, []);
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: {
commonName: '',
formalName: '',
description: '',
manufacturer: '',
size: '',
color: '',
type: '',
unitOfMaterial: '',
unitCost: 0,
packageCount: 0,
countPerPallet: 0,
countPerPalletPackage: 0,
primaryWidgetFamilyId: '',
secondaryWidgetFamilyId: '',
policiesMetadata: {
underStockLevelCount: 0,
overStockLevelCount: 0,
alertStockLevelCount: 0,
reorderStockLevelCount: 0
},
images: []
},
enableReinitialize: true,
initialValues: itemData,
validationSchema: schema.addNewItem,
onSubmit: (values, onSubmitProps) => {
onSubmit: (values) => {
LOGGER.log('values', values);
dispatch(
ProductActions.addProductAction({
loader: 'loading-request',
slug: API.ADD_PRODUCT,
method: 'post',
data: {
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: [],
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();
itemId
? dispatch(
ItemActions.editItemRequest({
loader: 'loading-request',
slug: `${API.EDIT_ITEM}${itemId}`,
method: 'patch',
navigateTo,
data: {
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: [], // TBD
policiesMetadata: {
underStockLevelCount: values.policiesMetadata.underStockLevelCount,
overStockLevelCount: values.policiesMetadata.overStockLevelCount,
alertStockLevelCount: values.policiesMetadata.alertStockLevelCount,
reorderStockLevelCount: values.policiesMetadata.reorderStockLevelCount
},
widgetFamilyId:
values.secondaryWidgetFamilyId === ''
? values.primaryWidgetFamilyId
: values.secondaryWidgetFamilyId,
images: values.images
}
})
)
: dispatch(
ItemActions.addItemRequest({
loader: 'loading-request',
slug: API.ADD_ITEM,
method: 'post',
navigateTo,
data: {
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: [], // TBD
policiesMetadata: {
underStockLevelCount: values.policiesMetadata.underStockLevelCount,
overStockLevelCount: values.policiesMetadata.overStockLevelCount,
alertStockLevelCount: values.policiesMetadata.alertStockLevelCount,
reorderStockLevelCount: values.policiesMetadata.reorderStockLevelCount
},
widgetFamilyId:
values.secondaryWidgetFamilyId === ''
? values.primaryWidgetFamilyId
: values.secondaryWidgetFamilyId,
images: values.images
}
})
);
}
});
@@ -409,14 +494,27 @@ function AddNewItem() {
</Grid>
</Grid>
<Box>
<ImageUpload
heading="Upload Product Image"
accept="image/*"
images={formik.values.images}
setImages={(images) => {
formik.setFieldValue('images', images);
}}
/>
{itemId ? (
<ImageUploadMultiple
multiple
heading="Upload Product Image"
accept="image/*"
images={formik.values.images}
setImages={(images) => {
formik.setFieldValue('images', images);
}}
/>
) : (
<ImageUploadMultiple
multiple
heading="Upload Product Image"
accept="image/*"
images={formik.values.images}
setImages={(images) => {
formik.setFieldValue('images', images);
}}
/>
)}
</Box>
<Box
sx={{

View File

@@ -19,7 +19,7 @@ import DashboardLayout from 'layouts/DashboardLayout';
import ImageUploadSingle from 'components/ImageUploadSingle';
import MDButton from 'components/Button';
import { useFormik } from 'formik';
import schema from 'services/ValidationServices';
// import schema from 'services/ValidationServices';
import MDInput from 'components/MDInput';
import WarehouseActions from 'redux/WarehouseRedux';
import { getChildLocationType } from 'utils/nestedTableTools';
@@ -35,6 +35,8 @@ import { API } from 'constant';
import NestedDataTable from 'components/NestedTable';
import Breadcrumbs from 'components/Breadcrumbs';
import { WarehouseSelectors } from 'redux/WarehouseRedux';
import { InventorySelectors } from 'redux/InventoryRedux';
import InventoryActions from 'redux/InventoryRedux';
const bottomButtonStyling = {
width: '100%',
@@ -313,12 +315,22 @@ const WarehouseNestedDetails = () => {
);
};
const inventoryTypes = ['Perishable', 'Material', 'Product', 'Inventory', 'Fleet'];
function EditWarehouseDetails() {
const { warehouseId } = useParams();
const warehouseData = useSelector(WarehouseSelectors.getWarehouseDetailById(warehouseId));
const inventoryTypes = useSelector(InventorySelectors.getInventoryDetail);
React.useEffect(() => {
dispatch(
InventoryActions.getInventoryAction({
loader: 'loading-request',
slug: API.GET_INVENTORY,
method: 'get'
})
);
}, []);
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
@@ -334,12 +346,12 @@ function EditWarehouseDetails() {
const formik = useFormik({
initialValues: {
warehousename: warehouseData.name,
address: warehouseData.address,
inventorytype: [],
specs: warehouseData.specs,
image: [{ src: warehouseData.image_url }]
address: warehouseData.address || '',
preferredInventories: warehouseData.preferredInventories || [],
specs: warehouseData.specs || '',
image: warehouseData.image_url ? [{ src: warehouseData.image_url }] : []
},
validationSchema: schema.warehouseForm,
// validationSchema: schema.warehouseForm,
onSubmit: (values) => {
dispatch(
WarehouseActions.editWarehouseAction({
@@ -350,6 +362,7 @@ function EditWarehouseDetails() {
name: values.warehousename,
address: values.address,
specs: values.specs,
preferredInventories: values.preferredInventories,
image: values.image
}
})
@@ -446,40 +459,45 @@ function EditWarehouseDetails() {
<Select
multiple
select
disabled
fullWidth
variant="outlined"
name="inventorytype"
name="preferredInventories"
input={<OutlinedInput />}
value={formik.values.inventorytype}
error={formik.touched.inventorytype && Boolean(formik.errors.inventorytype)}
helperText={formik.touched.inventorytype && formik.errors.inventorytype}
value={formik.values.preferredInventories}
error={
formik.touched.preferredInventories &&
Boolean(formik.errors.preferredInventories)
}
helperText={
formik.touched.preferredInventories && formik.errors.preferredInventories
}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value) => (
<Chip key={value} label={value} />
<Chip
key={value}
label={inventoryTypes.find((x) => x._id === value)?.name || 'unknown'}
/>
))}
</Box>
)}
MenuProps={MenuProps}
onChange={(event) => {
const {
target: { value }
} = event;
onChange={(e) => {
const value = e.target.value;
formik.setFieldValue(
'inventorytype',
'preferredInventories',
// On autofill we get a stringified value.
typeof value === 'string' ? value.split(',') : value
);
}}
>
<MenuItem key={''} value={''}>
None Selected
</MenuItem>
{inventoryTypes.map((name) => (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
))}
{inventoryTypes &&
inventoryTypes.map((inventory) => (
<MenuItem key={inventory._id} value={inventory._id}>
{inventory.name}
</MenuItem>
))}
</Select>
</Box>
<Box component="div" sx={{ marginBottom: '15px' }}>

View File

@@ -10,41 +10,54 @@ import { useParams } from 'react-router-dom';
import LOGGER from 'services/Logger';
import { ItemSelectors } from 'redux/ItemRedux';
import EnhancedTable from 'components/EnhancedTable';
import { useNavigate } from 'react-router-dom';
const tHeads = [
{ key: 'name', name: '' },
{ key: 'commonName', name: 'Common Name' },
{ key: 'formalName', name: 'Formal Name' },
{ key: 'description', name: 'Description' },
{ key: 'manufacturer', name: 'Manufacturer' }
{ key: 'manufacturer', name: 'Manufacturer' },
{ key: 'size', name: 'size' },
{ key: 'color', name: 'color' },
{ key: 'type', name: 'type' },
{ key: 'unitOfMaterial', name: 'unitOfMaterial' },
{ key: 'unitCost', name: 'unitCost' },
{ key: 'packageCount', name: 'packageCount' },
{ key: 'countPerPallet', name: 'countPerPallet' },
{ key: 'countPerPalletPackage', name: 'countPerPalletPackage' },
{ key: 'totalQuantity', name: 'totalQuantity' },
{ key: 'reservedQuantity', name: 'reservedQuantity' },
{ key: 'availableQuantity', name: 'availableQuantity' }
];
function ItemListing() {
const dispatch = useDispatch();
const { widgetName, inventoryId } = useParams();
const [page /*, setPage*/] = React.useState(0);
const [perPage /*, setPerPage*/] = React.useState(10);
const [page, setPage] = React.useState(1);
const [perPage, setPerPage] = React.useState(10);
LOGGER.log({ widgetName, inventoryId });
const data = useSelector(ItemSelectors.getItemsByInventoryId(inventoryId));
const navigate = useNavigate();
const navigateTo = (path) => {
navigate(path);
};
React.useEffect(
() => {
dispatch(
ItemActions.itemRequest({
loader: 'loading-request',
slug: API.GET_ITEMS_BY_INVENTORY,
method: 'get',
page,
perPage,
inventoryId
})
);
},
[
/* page, perPage */
]
);
const data = useSelector(ItemSelectors.getItems);
const count = useSelector(ItemSelectors.getItemsCount);
React.useEffect(() => {
dispatch(
ItemActions.itemRequest({
loader: 'loading-request',
slug: API.GET_ITEMS_BY_INVENTORY,
method: 'get',
page: page - 1,
perPage,
inventoryId
})
);
}, [page, perPage]);
return (
<DashboardLayout>
@@ -60,7 +73,18 @@ function ItemListing() {
<MDBox px={2} py={3}>
List of {widgetName}s{/* <pre>{JSON.stringify(data, null, 4)}</pre> */}
<EnhancedTable data={data} tHeads={tHeads} />
<EnhancedTable
count={count}
page={page}
setPage={setPage}
perPage={perPage}
setPerPage={setPerPage}
editHandler={(id) => {
navigateTo(`/setup/inventory/browse/${widgetName}/${inventoryId}/edit/${id}`);
}}
data={data}
tHeads={tHeads}
/>
</MDBox>
</DashboardLayout>
);

View File

@@ -1,23 +1,35 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { Box, Grid, MenuItem, OutlinedInput, Chip, Select } from '@mui/material';
import DashboardNavbar from 'components/DashboardNavbar';
import DashboardLayout from 'layouts/DashboardLayout';
import ImageUploadSingle from 'components/ImageUploadSingle';
import MDButton from 'components/Button';
import { useFormik } from 'formik';
import schema from 'services/ValidationServices';
// import schema from 'services/ValidationServices';
import MDInput from 'components/MDInput';
import WarehouseActions from 'redux/WarehouseRedux';
import { API } from 'constant';
import Breadcrumbs from 'components/Breadcrumbs';
import { useNavigate } from 'react-router-dom';
const inventoryTypes = ['Perishable', 'Material', 'Product', 'Inventory', 'Fleet'];
import { InventorySelectors } from 'redux/InventoryRedux';
import InventoryActions from 'redux/InventoryRedux';
function NewWarehouseDetails() {
const dispatch = useDispatch();
const inventoryTypes = useSelector(InventorySelectors.getInventoryDetail);
React.useEffect(() => {
dispatch(
InventoryActions.getInventoryAction({
loader: 'loading-request',
slug: API.GET_INVENTORY,
method: 'get'
})
);
}, []);
const navigate = useNavigate();
const navigateTo = (id) => {
@@ -38,11 +50,11 @@ function NewWarehouseDetails() {
initialValues: {
warehousename: '',
address: '',
inventorytype: [],
preferredInventories: [],
specs: '',
image: []
},
validationSchema: schema.warehouseForm,
// validationSchema: schema.warehouseForm,
onSubmit: (values) => {
dispatch(
WarehouseActions.createWarehouseAction({
@@ -53,6 +65,7 @@ function NewWarehouseDetails() {
data: {
name: values.warehousename,
address: values.address,
preferredInventories: values.preferredInventories,
specs: values.specs,
image: values.image,
company_id: '61cea5fd028432700a7f8601'
@@ -153,25 +166,31 @@ function NewWarehouseDetails() {
select
fullWidth
variant="outlined"
name="inventorytype"
name="preferredInventories"
input={<OutlinedInput />}
value={formik.values.inventorytype}
error={formik.touched.inventorytype && Boolean(formik.errors.inventorytype)}
helperText={formik.touched.inventorytype && formik.errors.inventorytype}
value={formik.values.preferredInventories}
error={
formik.touched.preferredInventories &&
Boolean(formik.errors.preferredInventories)
}
helperText={
formik.touched.preferredInventories && formik.errors.preferredInventories
}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value) => (
<Chip key={value} label={value} />
<Chip
key={value}
label={inventoryTypes.find((x) => x._id === value)?.name || 'unknown'}
/>
))}
</Box>
)}
MenuProps={MenuProps}
onChange={(event) => {
const {
target: { value }
} = event;
onChange={(e) => {
const value = e.target.value;
formik.setFieldValue(
'inventorytype',
'preferredInventories',
// On autofill we get a stringified value.
typeof value === 'string' ? value.split(',') : value
);
@@ -180,11 +199,12 @@ function NewWarehouseDetails() {
<MenuItem key={''} value={''}>
None Selected
</MenuItem>
{inventoryTypes.map((name) => (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
))}
{inventoryTypes &&
inventoryTypes.map((inventory) => (
<MenuItem key={inventory._id} value={inventory._id}>
{inventory.name}
</MenuItem>
))}
</Select>
</Box>
<Box component="div" sx={{ marginBottom: '15px' }}>
@@ -233,7 +253,14 @@ function NewWarehouseDetails() {
columnGap: '20px'
}}
>
<MDButton size="medium" color="error" variant="outlined">
<MDButton
size="medium"
color="error"
variant="outlined"
onClick={() => {
navigate('/setup/warehouse');
}}
>
CANCEL
</MDButton>
<MDButton size="medium" color="primary" variant="outlined" type="submit">

View File

@@ -7,10 +7,13 @@ import { getFetchingValue, getErrorValue } from '../services/Utils';
const { Types, Creators } = createActions({
itemRequest: ['payload'],
itemSuccess: ['data'],
itemFailure: ['error'],
addItemRequest: ['payload'],
addItemSuccess: ['data'],
editItemRequest: ['payload'],
editItemSuccess: ['data'],
itemFailure: ['error'],
logout: null
oneItemRequest: ['payload'],
oneItemSuccess: ['data']
});
export const ItemTypes = Types;
@@ -20,6 +23,8 @@ export default ItemActions;
/* ------------- Initial State ------------- */
export const INITIAL_STATE = Immutable({
list: [],
item: null,
count: 0,
fetching: [],
error: {}
});
@@ -27,9 +32,9 @@ export const INITIAL_STATE = Immutable({
/* ------------- Selectors ------------- */
export const ItemSelectors = {
getItems: (state) => state.items.list,
getItemById: (id) => (state) => state.items.list.find((x) => x._id === id),
getItemsByInventoryId: (id) => (state) =>
state.items.list.filter((x) => x.widgetFamily?.inventory === id)
getFormItem: (itemId) => (state) => state.items.item?._id === itemId ? state.items.item : null,
getItemsCount: (state) => state.items.count,
getItemById: (id) => (state) => state.items.list.find((x) => x._id === id)
};
/* ------------- Reducers ------------- */
@@ -39,49 +44,51 @@ export const onItemRequest = (state, { payload }) =>
error: getErrorValue(state?.error, payload?.loader)
});
export const onItemSuccess = (state, { data }) =>
state.merge({
fetching: getFetchingValue(state.fetching, data?.loader),
error: getErrorValue(state?.error, data?.loader),
list: data.items,
count: data.count
});
export const onOneItemRequest = (state, { payload }) =>
state.merge({
fetching: _.uniq([...state.fetching, payload?.loader]),
error: getErrorValue(state?.error, payload?.loader)
});
export const onOneItemSuccess = (state, { data }) =>
state.merge({
fetching: getFetchingValue(state.fetching, data?.loader),
error: getErrorValue(state?.error, data?.loader),
item: data.item
});
export const onAddItemRequest = (state, { payload }) =>
state.merge({
fetching: _.uniq([...state.fetching, payload?.loader]),
error: getErrorValue(state?.error, payload?.loader)
});
export const onAddItemSuccess = (state, { data }) =>
state.merge({
fetching: getFetchingValue(state.fetching, data?.loader),
error: getErrorValue(state?.error, data?.loader),
item: null
});
export const onEditItemRequest = (state, { payload }) =>
state.merge({
fetching: _.uniq([...state.fetching, payload?.loader]),
error: getErrorValue(state?.error, payload?.loader)
});
const mergeItemStates = (stateData, items) => {
if (!items) return stateData; // undefined check
const idsInNewItems = items.map((x) => x._id);
const newState = stateData.filter((x) => !idsInNewItems.includes(x._id));
return [...newState, ...items];
};
export const onItemSuccess = (state, { data }) =>
state.merge({
fetching: getFetchingValue(state.fetching, data?.loader),
error: getErrorValue(state?.error, data?.loader),
list: mergeItemStates(state.list, data.items)
});
const mergeEditItemStates = (stateList, item, type) => {
if (!item) return stateList; // undefined check
if (type === 'add') {
return [...stateList, item];
} else if (type === 'edit') {
const newState = stateList.filter((x) => x._id !== item._id);
return [...newState, item];
// } else if (type === 'delete') {
// return stateList.filter((x) => x._id !== item._id);
} else {
return stateList;
}
};
export const onEditItemSuccess = (state, { data }) =>
state.merge({
fetching: getFetchingValue(state.fetching, data?.loader),
error: getErrorValue(state?.error, data?.loader),
list: mergeEditItemStates(state.list, data.item, data.type)
item: null
});
export const onItemFailure = (state, { error }) =>
@@ -93,8 +100,12 @@ export const onItemFailure = (state, { error }) =>
/* ------------- Hookup Reducers To Types ------------- */
export const itemReducer = createReducer(INITIAL_STATE, {
[Types.ITEM_REQUEST]: onItemRequest,
[Types.EDIT_ITEM_REQUEST]: onEditItemRequest,
[Types.ITEM_SUCCESS]: onItemSuccess,
[Types.ITEM_FAILURE]: onItemFailure,
[Types.ADD_ITEM_REQUEST]: onAddItemRequest,
[Types.ADD_ITEM_SUCCESS]: onAddItemSuccess,
[Types.EDIT_ITEM_REQUEST]: onEditItemRequest,
[Types.EDIT_ITEM_SUCCESS]: onEditItemSuccess,
[Types.ITEM_FAILURE]: onItemFailure
[Types.ONE_ITEM_REQUEST]: onOneItemRequest,
[Types.ONE_ITEM_SUCCESS]: onOneItemSuccess
});

View File

@@ -158,6 +158,14 @@ const protectedRoutes = [
route: '/setup/inventory/new-item/:widgetName/:inventoryId',
component: <AddNewItem />
},
{
name: 'Update Item',
key: 'udpate-item',
hide: true,
route: '/setup/inventory/browse/:widgetName/:inventoryId/edit/:itemId',
component: <AddNewItem />
},
// /setup/inventory/browse/${payload?.widgetName}/${payload?.inventoryId}/edit/${payload?.id}
{
name: 'View Items',
key: 'view-items',

View File

@@ -1,4 +1,5 @@
import { AuthorizedAPI } from 'config';
import { toast } from 'react-toastify';
import { call, put, takeEvery } from 'redux-saga/effects';
import ApiServices from 'services/API/ApiServices';
import ItemActions, { ItemTypes } from '../redux/ItemRedux';
@@ -14,7 +15,8 @@ export function* onRequestItem({ payload }) {
yield put(
ItemActions.itemSuccess({
loader: payload?.loader,
items: response?.data?.data,
items: response?.data?.data.result,
count: response?.data?.data.count,
page: payload?.page,
reset: !payload.page
})
@@ -29,22 +31,24 @@ export function* onRequestItem({ payload }) {
}
}
export function* onEditRequestItem({ payload }) {
export function* onRequestOneItem({ payload }) {
const response = yield call(
ApiServices[payload?.method],
AuthorizedAPI,
payload?.slug,
payload?.data
`${payload.slug}${payload?.itemId}`
);
if (response?.status === 200) {
yield put(
ItemActions.editItemSuccess({
ItemActions.oneItemSuccess({
loader: payload?.loader,
item: response?.data?.data,
type: payload?.type
item: response?.data?.data
})
);
// payload.navigateTo(
// `/setup/inventory/browse/${payload?.widgetName}/${payload?.inventoryId}/edit/${payload?.itemId}`
// );
} else {
toast('Failed to get item details');
yield put(
ItemActions.itemFailure({
loader: payload?.loader,
@@ -54,7 +58,98 @@ export function* onEditRequestItem({ payload }) {
}
}
function addImagesToFormData(formData, images) {
let imgIdx = 0;
let preImgIdx = 0;
images.forEach((image) => {
if (image.file) formData.append(`images[${imgIdx++}]`, image.file);
else formData.append(`imageIds[${preImgIdx++}]`, image._id);
});
}
function buildFormData(formData, data, parentKey) {
if (data && typeof data === 'object' && !(data instanceof Date) && !(data instanceof File)) {
Object.keys(data).forEach((key) => {
if (key !== 'images')
buildFormData(formData, data[key], parentKey ? `${parentKey}[${key}]` : key);
else addImagesToFormData(formData, data['images']);
});
} else {
// eslint-disable-next-line eqeqeq
const value = data == null ? '' : data;
formData.append(parentKey, value);
}
}
const createFormData = (data) => {
const formData = new FormData();
buildFormData(formData, data);
return formData;
};
export function* onAddRequestItem({ payload }) {
const response = yield call(
ApiServices[payload?.method],
AuthorizedAPI,
payload?.slug,
createFormData(payload?.data)
);
if (response?.status === 200) {
toast(`Added item: ${payload.data.commonName}`);
// payload.navigateTo(
// `/setup/inventory/browse/${payload?.widgetName}/${payload?.inventoryId}/edit/${response?.data?.data?._id}`
// );
payload.navigateTo('/setup/inventory');
yield put(
ItemActions.addItemSuccess({
loader: payload?.loader,
item: response?.data?.data
})
);
} else {
toast('Failed to add item');
yield put(
ItemActions.itemFailure({
loader: payload?.loader,
error: response?.data
})
);
}
}
export function* onEditRequestItem({ payload }) {
const response = yield call(
ApiServices[payload?.method],
AuthorizedAPI,
payload?.slug,
createFormData(payload?.data)
);
if (response?.status === 200) {
toast(`Successfully edited item: ${payload.data.commonName}`);
payload.navigateTo('/setup/inventory');
yield put(
ItemActions.addItemSuccess({
loader: payload?.loader,
item: response?.data?.data
})
);
} else {
toast('Failed to edit item');
yield put(
ItemActions.itemFailure({
loader: payload?.loader,
error: response?.data
})
);
}
}
export default [
takeEvery(ItemTypes.ITEM_REQUEST, onRequestItem),
takeEvery(ItemTypes.ONE_ITEM_REQUEST, onRequestOneItem),
takeEvery(ItemTypes.ADD_ITEM_REQUEST, onAddRequestItem),
takeEvery(ItemTypes.EDIT_ITEM_REQUEST, onEditRequestItem)
];

View File

@@ -35,6 +35,10 @@ const makeFormData = (data) => {
if (data.address) formData.append('address', data.address);
if (data.specs) formData.append('specs', data.specs);
if (data.company_id) formData.append('company_id', data.company_id);
if (data.preferredInventories)
data.preferredInventories.forEach((prefInv, idx) => {
formData.append(`preferredInventories[${idx}]`, prefInv);
});
if (data.image[0].file) formData.append('image', data.image[0].file);
return formData;
};
@@ -47,11 +51,15 @@ export function* onRequestCreateWarehouse({ payload }) {
makeFormData(payload?.data)
);
if (response?.status === 200 && response?.data?.message) {
const warehouse = response?.data?.message;
toast('Warehouse created successfully');
yield put(
WarehouseActions.createWarehouseSuccess({
loader: payload?.loader,
createdWarehouse: response?.data?.message
createdWarehouse: {
...warehouse,
preferredInventories: warehouse.preferredInventories.map((z) => z._id)
}
})
);
payload.navigateTo(response?.data?.message?._id);
@@ -73,12 +81,16 @@ export function* onRequestEditWarehouse({ payload }) {
payload?.slug,
makeFormData(payload?.data)
);
if (response?.status === 200) {
if (response?.status === 200 && response?.data?.data) {
toast('Warehouse edited successfully');
const warehouse = response?.data?.data;
yield put(
WarehouseActions.editWarehouseSuccess({
loader: payload?.loader,
editedWarehouse: response?.data?.data
editedWarehouse: {
...warehouse,
preferredInventories: warehouse.preferredInventories.map((z) => z._id)
}
})
);
} else {

View File

@@ -22,10 +22,11 @@ const schema = {
warehouseForm: Yup.object({
warehousename: Yup.string('Enter warehouse name').required('warehouse name is required'),
address: Yup.string('Enter address').required('address is required'),
inventorytype: Yup.array('Enter inventory Type')
preferredInventories: Yup.array('Enter inventory Type')
.of(Yup.string())
.required('inventory Type is required'),
attributes: Yup.string('Enter other attributes')
specs: Yup.string('Enter other attributes'),
image: Yup.array()
}),
addNewItem: Yup.object({