diff --git a/src/components/Breadcrumbs/index.js b/src/components/Breadcrumbs/index.js index a424bc3..8975b24 100644 --- a/src/components/Breadcrumbs/index.js +++ b/src/components/Breadcrumbs/index.js @@ -44,6 +44,7 @@ const buildBreadcrumbs = (route, light) => { fontWeight="regular" textTransform="capitalize" color={light ? 'white' : 'dark'} + key={el} sx={{ lineHeight: 0 }} > {el.name} diff --git a/src/components/EnhancedTable/index.js b/src/components/EnhancedTable/index.js index 8842e2a..2df1dd2 100644 --- a/src/components/EnhancedTable/index.js +++ b/src/components/EnhancedTable/index.js @@ -1,12 +1,7 @@ import * as React from 'react'; import Box from '@mui/material/Box'; import PropTypes from 'prop-types'; -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 { styled } from '@mui/material/styles'; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; @@ -15,10 +10,8 @@ import TableContainer from '@mui/material/TableContainer'; import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import Paper from '@mui/material/Paper'; -// import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; -// import IconButton from '@mui/material/IconButton'; -// import Collapse from '@mui/material/Collapse'; import TablePagination from 'components/TablePagination'; +import { Dialog, DialogActions, MenuItem, Select } from '@mui/material'; const StyledTableCell = styled(TableCell)(({ theme }) => ({ [`&.${tableCellClasses.head}`]: { @@ -47,16 +40,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 ( *': { borderBottom: 'unset' } }}> { + editHandler(rowData._id); + }} > EDIT @@ -82,15 +78,24 @@ function Row({ tHeads, rowData }) { ); } -function EnhancedTable({ data, tHeads }) { - // const [anchorEl, setAnchorEl] = React.useState(false); - // const open = Boolean(anchorEl); - // const handleClick = (event) => { - // setAnchorEl(event.currentTarget); - // }; - // const handleClose = () => { - // setAnchorEl(null); - // }; +function EnhancedTable({ + count, + page, + setPage, + perPage, + setPerPage, + data, + tHeads, + editHandler, + filtersControl, + resetFilters +}) { + const [filtersOpen, setFiltersOpen] = React.useState(false); + + const handleFiltersClose = () => { + setFiltersOpen(false); + }; + return ( <> - - - + {/* */} { + setFiltersOpen(true); + }} > - Sorting + Filter - + {filtersControl} + + Reset Filters + { + setFiltersOpen(false); + }} + > + Close + + + + ) : null} + {/* {}} > Dashboard - - */} + {/* Profile My account Logout - + */} {/* Table-row- */} @@ -174,7 +198,14 @@ function EnhancedTable({ data, tHeads }) { {data && - data.map((rowData) => )} + data.map((rowData) => ( + + ))} @@ -265,10 +296,29 @@ function EnhancedTable({ data, tHeads }) { */} {/*---- pagination- */} - - + + Per page:{' '} + + + + + + + [{(page - 1) * perPage + 1} to {perPage * page > count ? count : perPage * page} of{' '} + {count}] - [1 to 10 of 92] @@ -276,8 +326,16 @@ 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, + filtersControl: PropTypes.any, + resetFilters: PropTypes.any }; export default EnhancedTable; diff --git a/src/components/ImageUpload/index.js b/src/components/ImageUpload/index.js index 4cbea83..08102f5 100644 --- a/src/components/ImageUpload/index.js +++ b/src/components/ImageUpload/index.js @@ -39,6 +39,7 @@ function ImageUpload({ heading, accept, multiple, images, setImages, type, pageI const addNewImageToImages = (image) => { setImages([...images, image]); }; + const addImage = (e) => { addNewImage(type, pageId, e.target.files[0], addNewImageToImages); setImages([ @@ -46,8 +47,9 @@ function ImageUpload({ heading, accept, multiple, images, setImages, type, pageI { src: URL.createObjectURL(e.target.files[0]), file: e.target.files[0] } ]); }; + const removeImage = (index) => { - deleteImage(type, pageId, images._id); + multiple && deleteImage(type, pageId, images._id); setImages(images.filter((_val, idx) => idx !== index)); }; diff --git a/src/components/ImageUploadMultiple/index.js b/src/components/ImageUploadMultiple/index.js new file mode 100644 index 0000000..ee4a82f --- /dev/null +++ b/src/components/ImageUploadMultiple/index.js @@ -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 ( + <> + + + + + {!multiple && images.length ? null : } + + {!multiple && images.length ? 'Cannot add more images' : heading} + + + + {/* -----------img-preview----------- */} + + {images && + images.map((item, idx) => { + return ( + + placeholder + + + ); + })} + + + + ); +} + +export default ImageUpload; +ImageUpload.propTypes = { + images: PropTypes.array, + heading: PropTypes.string, + multiple: PropTypes.bool, + accept: PropTypes.string, + setImages: PropTypes.func +}; diff --git a/src/components/ImageUploadSingle/index.js b/src/components/ImageUploadSingle/index.js new file mode 100644 index 0000000..d9bf008 --- /dev/null +++ b/src/components/ImageUploadSingle/index.js @@ -0,0 +1,119 @@ +/* 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 ImageUploadSingle({ heading, accept, multiple, images, setImages }) { + return ( + <> + + {!images.length ? ( + + { + setImages([ + { src: URL.createObjectURL(e.target.files[0]), file: e.target.files[0] } + ]); + }} + /> + + {!multiple && images.length ? null : } + + {!multiple && images.length ? 'Cannot add more images' : heading} + + + + ) : ( + + + + + )} + + + ); +} + +ImageUploadSingle.propTypes = { + images: PropTypes.array, + heading: PropTypes.string, + multiple: PropTypes.bool, + accept: PropTypes.string, + setImages: PropTypes.func +}; + +export default ImageUploadSingle; diff --git a/src/components/TablePagination/index.js b/src/components/TablePagination/index.js index 9981eaf..8669415 100644 --- a/src/components/TablePagination/index.js +++ b/src/components/TablePagination/index.js @@ -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 ( - ( )} + onChange={(event, value) => { + setPage(value); + }} /> - ); } +TablePagination.propTypes = { + page: PropTypes.number, + count: PropTypes.number, + setPage: PropTypes.any +}; + export default TablePagination; diff --git a/src/components/TileBasic/index.js b/src/components/TileBasic/index.js index 77e6ab4..40949b6 100644 --- a/src/components/TileBasic/index.js +++ b/src/components/TileBasic/index.js @@ -67,13 +67,30 @@ export default function TileBasic({ tiles }) { {tiles && tiles.map((item) => ( - <> - 4 ? 4 : 6}> - {item.disabled ? ( + 4 ? 4 : 6}> + {item.disabled ? ( + white.main, + padding: '32px 40px' + }} + > + {item.icon} + {item.name} + + ) : ( + white.main, @@ -83,28 +100,9 @@ export default function TileBasic({ tiles }) { {item.icon} {item.name} - ) : ( - - white.main, - padding: '32px 40px' - }} - > - {item.icon} - {item.name} - - - )} - - + + )} + ))} diff --git a/src/constant/Endpoints.js b/src/constant/Endpoints.js index dbda3e6..6eb5dcf 100644 --- a/src/constant/Endpoints.js +++ b/src/constant/Endpoints.js @@ -16,7 +16,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', diff --git a/src/pages/addNewProduct/index.js b/src/pages/addNewProduct/index.js index 3256b78..34991d1 100644 --- a/src/pages/addNewProduct/index.js +++ b/src/pages/addNewProduct/index.js @@ -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() { - { - formik.setFieldValue('images', images); - }} - /> + {itemId ? ( + { + formik.setFieldValue('images', images); + }} + /> + ) : ( + { + formik.setFieldValue('images', images); + }} + /> + )} { ); }; -const inventoryTypes = ['Perishable', 'Material', 'Product', 'Inventory', 'Fleet']; - function EditWarehouseDetails() { const { warehouseId } = useParams(); const warehouseData = useSelector(WarehouseSelectors.getWarehouseDetailById(warehouseId)); - const [open, setOpen] = useState(false); + 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 = { @@ -336,13 +346,13 @@ function EditWarehouseDetails() { const formik = useFormik({ initialValues: { warehousename: warehouseData.name, - address: warehouseData.address, - inventorytype: [], - attributes: '', - images: warehouseData.images + address: warehouseData.address || '', + preferredInventories: warehouseData.preferredInventories || [], + specs: warehouseData.specs || '', + image: warehouseData.image_url ? [{ src: warehouseData.image_url }] : [] }, - validationSchema: schema.warehouseForm, - onSubmit: (values, onSubmitProps) => { + // validationSchema: schema.warehouseForm, + onSubmit: (values) => { dispatch( WarehouseActions.editWarehouseAction({ loader: 'loading-request', @@ -351,21 +361,14 @@ function EditWarehouseDetails() { data: { name: values.warehousename, address: values.address, - specs: '', - company_id: '' + specs: values.specs, + preferredInventories: values.preferredInventories, + image: values.image } }) ); - onSubmitProps.resetForm(); - setOpen(true); } }); - const handleClose = (event, reason) => { - if (reason === 'clickaway') { - return; - } - setOpen(false); - }; return ( <> @@ -456,40 +459,45 @@ function EditWarehouseDetails() { @@ -508,23 +516,22 @@ function EditWarehouseDetails() { fullWidth type="text" variant="outlined" - name="attributes" - value={formik.values.attributes} - error={formik.touched.attributes && Boolean(formik.errors.attributes)} - helperText={formik.touched.attributes && formik.errors.attributes} + name="specs" + value={formik.values.specs} + error={formik.touched.specs && Boolean(formik.errors.specs)} + helperText={formik.touched.specs && formik.errors.specs} onChange={formik.handleChange} /> - { - formik.setFieldValue('images', images); + formik.setFieldValue('image', images); }} /> @@ -558,7 +565,6 @@ function EditWarehouseDetails() { - ); } diff --git a/src/pages/inventory/index.js b/src/pages/inventory/index.js index 3791aa9..01b3ab7 100644 --- a/src/pages/inventory/index.js +++ b/src/pages/inventory/index.js @@ -3,7 +3,6 @@ import DashboardNavbar from 'components/DashboardNavbar'; import DashboardLayout from 'layouts/DashboardLayout'; 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'; @@ -76,6 +75,10 @@ function InventoryScreen() { const navigate = useNavigate(); const { inventoryId } = useParams(); + const navigateTo = () => { + navigate('/setup/inventory'); + }; + const currentInventoryData = useSelector(InventorySelectors.getInventoryDetailById(inventoryId)); LOGGER.log({ currentInventoryData }); // const [inventoryAllData, setInventoryAllData] = useState([]); @@ -102,8 +105,7 @@ function InventoryScreen() { replenishment: currentInventoryData.policies.replenishment, preferredLocations: false, // TODO: change later when implemented on BE inventory_process: currentInventoryData.policies.inventory_process - }, - image: [{ src: currentInventoryData.image_url }] + } } : { name: '', @@ -114,8 +116,7 @@ function InventoryScreen() { replenishment: false, preferredLocations: false, inventory_process: 'CCR' - }, - image: [] + } }, validationSchema: schema.addInventory, onSubmit: (values) => { @@ -126,9 +127,10 @@ function InventoryScreen() { loader: 'loading-request', slug: `${API.ADD_INVENTORY}/${inventoryId}`, method: 'patch', + navigateTo, data: { ...values, - image: values.image[0]?.file || null + icon_slug: 'testslug' } }) ) @@ -137,14 +139,13 @@ function InventoryScreen() { loader: 'loading-request', slug: API.ADD_INVENTORY, method: 'post', + navigateTo, data: { ...values, - image: values.image[0]?.file || null + icon_slug: 'testslug' } }) ); - // navigate to edit inventory page - // onSubmitProps.resetForm(); } }); @@ -257,14 +258,7 @@ function InventoryScreen() { - { - formik.setFieldValue('image', images); - }} - /> + icon slugs selector { + 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); + + const [pFam, setPFam] = React.useState(''); + const [sFam, setSFam] = React.useState(''); + const primaryFamilies = useSelector(WidgetSelectors.getWidgetFamiliesByInventoryId(inventoryId)); + const secondaryFamilies = useSelector(WidgetSelectors.getWidgetsByParentId(pFam)); + + React.useEffect(() => { + dispatch( + ItemActions.itemRequest({ + loader: 'loading-request', + slug: API.GET_ITEMS_BY_INVENTORY, + method: 'get', + page: page - 1, + perPage, + inventoryId, + family: sFam || pFam || null + }) + ); + }, [page, perPage, pFam, sFam]); + + React.useEffect(() => { + dispatch( + WidgetActions.widgetRequest({ + loader: 'location-request', + slug: `${API.GET_WIDGET_FAMILY_BY_INVENTORY}${inventoryId}`, + method: 'get' + }) + ); + }, []); return ( @@ -60,7 +90,89 @@ function ItemListing() { List of {widgetName}s{/*
{JSON.stringify(data, null, 4)}
*/} - + { + navigateTo(`/setup/inventory/browse/${widgetName}/${inventoryId}/edit/${id}`); + }} + resetFilters={() => { + setPFam(''); + setSFam(''); + }} + filtersControl={ + <> + Add filters + + Filter by family + + + + + } + data={ + data + ? data.map((item) => { + return { + name: item._id, + commonName: item.commonName, + formalName: item.formalName, + description: item.description, + manufacturer: item.manufacturer, + size: item.size, + color: item.color, + type: item.type, + unitOfMaterial: item.unitOfMaterial, + unitCost: item.unitCost, + packageCount: item.packageCount, + countPerPallet: item.countPerPallet, + countPerPalletPackage: item.countPerPalletPackage + }; + }) + : [] + } + tHeads={tHeads} + />
); diff --git a/src/pages/newWarehouseDetails/index.js b/src/pages/newWarehouseDetails/index.js index 8f4e3c2..4692061 100644 --- a/src/pages/newWarehouseDetails/index.js +++ b/src/pages/newWarehouseDetails/index.js @@ -1,23 +1,40 @@ -import React, { useState } from 'react'; -import { useDispatch } from 'react-redux'; +import React from 'react'; +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 ImageUpload from 'components/ImageUpload'; +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 SnackBar from 'components/SnackBar'; import Breadcrumbs from 'components/Breadcrumbs'; -const inventoryTypes = ['Perishable', 'Material', 'Product', 'Inventory', 'Fleet']; +import { useNavigate } from 'react-router-dom'; +import { InventorySelectors } from 'redux/InventoryRedux'; +import InventoryActions from 'redux/InventoryRedux'; function NewWarehouseDetails() { const dispatch = useDispatch(); - const [open, setOpen] = useState(false); + 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) => { + navigate(`/setup/warehouse/edit-warehouse/${id}`); + }; const ITEM_HEIGHT = 48; const ITEM_PADDING_TOP = 8; @@ -33,37 +50,31 @@ function NewWarehouseDetails() { initialValues: { warehousename: '', address: '', - inventorytype: [], - attributes: '', - images: [] + preferredInventories: [], + specs: '', + image: [] }, - validationSchema: schema.warehouseForm, - onSubmit: (values, onSubmitProps) => { + // validationSchema: schema.warehouseForm, + onSubmit: (values) => { dispatch( WarehouseActions.createWarehouseAction({ loader: 'loading-request', slug: API.CREATE_WAREHOUSE, method: 'post', + navigateTo: navigateTo, data: { name: values.warehousename, address: values.address, - specs: '', + preferredInventories: values.preferredInventories, + specs: values.specs, + image: values.image, company_id: '61cea5fd028432700a7f8601' } }) ); - onSubmitProps.resetForm(); - setOpen(true); } }); - const handleClose = (event, reason) => { - if (reason === 'clickaway') { - return; - } - setOpen(false); - }; - return ( <> @@ -155,25 +166,31 @@ function NewWarehouseDetails() { select fullWidth variant="outlined" - name="inventorytype" + name="preferredInventories" input={} - 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) => ( {selected.map((value) => ( - + x._id === value)?.name || 'unknown'} + /> ))} )} 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 ); @@ -182,11 +199,12 @@ function NewWarehouseDetails() { None Selected - {inventoryTypes.map((name) => ( - - {name} - - ))} + {inventoryTypes && + inventoryTypes.map((inventory) => ( + + {inventory.name} + + ))} @@ -205,23 +223,23 @@ function NewWarehouseDetails() { fullWidth type="text" variant="outlined" - name="attributes" - value={formik.values.attributes} - error={formik.touched.attributes && Boolean(formik.errors.attributes)} - helperText={formik.touched.attributes && formik.errors.attributes} + name="specs" + value={formik.values.specs} + error={formik.touched.specs && Boolean(formik.errors.specs)} + helperText={formik.touched.specs && formik.errors.specs} onChange={formik.handleChange} /> - { - formik.setFieldValue('images', images); + images={formik.values.image} + setImages={(image) => { + formik.setFieldValue('image', image); }} /> @@ -235,7 +253,14 @@ function NewWarehouseDetails() { columnGap: '20px' }} > - + { + navigate('/setup/warehouse'); + }} + > CANCEL @@ -249,11 +274,6 @@ function NewWarehouseDetails() { - ); } diff --git a/src/redux/ItemRedux.js b/src/redux/ItemRedux.js index 9d52a6a..f7fe178 100644 --- a/src/redux/ItemRedux.js +++ b/src/redux/ItemRedux.js @@ -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 }); diff --git a/src/redux/WarehouseRedux.js b/src/redux/WarehouseRedux.js index fa4da55..3a3b98b 100644 --- a/src/redux/WarehouseRedux.js +++ b/src/redux/WarehouseRedux.js @@ -26,12 +26,8 @@ export default WarehouseActions; export const INITIAL_STATE = Immutable({ warehouseDetail: [], error: {}, - - createWarehouse: [], createWarehouseLoading: false, createWarehouseError: {}, - - editWarehouse: [], editWarehouseLoading: false, editWarehouseError: {} }); @@ -75,7 +71,7 @@ export const onCreateWarehouseSuccess = (state, { data }) => state.merge({ fetching: getFetchingValue(state.fetching, data?.loader), error: getErrorValue(state?.error, data?.loader), - createWarehouse: data.createWarehouse + warehouseDetail: [...state.warehouseDetail, data.createdWarehouse] }); export const onCreateWarehouseFailure = (state, { error }) => @@ -94,7 +90,10 @@ export const onEditWarehouseSuccess = (state, { data }) => state.merge({ fetching: getFetchingValue(state.fetching, data?.loader), error: getErrorValue(state?.error, data?.loader), - editWarehouse: data.editWarehouse + warehouseDetail: [ + ...state.warehouseDetail.filter((x) => x._id !== data?.editedWarehouse?._id), + data.editedWarehouse + ] }); export const onEditWarehouseFailure = (state, { error }) => diff --git a/src/routes/index.js b/src/routes/index.js index 458d2c6..a5c35d7 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -117,6 +117,14 @@ const protectedRoutes = [ route: '/setup/inventory/new-item/:widgetName/:inventoryId', component: }, + { + name: 'Update Item', + key: 'udpate-item', + hide: true, + route: '/setup/inventory/browse/:widgetName/:inventoryId/edit/:itemId', + component: + }, + // /setup/inventory/browse/${payload?.widgetName}/${payload?.inventoryId}/edit/${payload?.id} { name: 'View Items', key: 'view-items', diff --git a/src/sagas/Inventory.js b/src/sagas/Inventory.js index a25bb2d..744a70b 100644 --- a/src/sagas/Inventory.js +++ b/src/sagas/Inventory.js @@ -55,35 +55,24 @@ export function* onRequestGetInventoryTypesData({ payload }) { } } -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) + payload?.data ); if (response?.status === 200) { + toast('New inventory added'); yield put( InventoryActions.addInventorySuccess({ loader: payload?.loader, newInventory: response?.data?.data?.inventoryData }) ); + payload.navigateTo(); } else { + toast('Failed to add inventory'); payload.onFailedAddInventoryData(response.data.error); yield put( InventoryActions.addInventoryFailure({ @@ -109,8 +98,9 @@ export function* onRequestUpdateInventoryData({ payload }) { updateInventory: response?.data?.data }) ); + payload.navigateTo(); } else { - payload.onFailedUpdateInventoryData(response.data.error); + toast('Failed to update inventory'); yield put( InventoryActions.updateInventoryFailure({ loader: payload?.loader, diff --git a/src/sagas/Item.js b/src/sagas/Item.js index e09426b..604b46c 100644 --- a/src/sagas/Item.js +++ b/src/sagas/Item.js @@ -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'; @@ -7,14 +8,17 @@ export function* onRequestItem({ payload }) { const response = yield call( ApiServices[payload?.method], AuthorizedAPI, - `${payload?.slug}${payload?.inventoryId}&page=${payload?.page}&perPage=${payload?.perPage}`, + `${payload?.slug}${payload?.inventoryId}&page=${payload?.page}&perPage=${payload?.perPage}${ + payload?.family ? '&family=' + payload?.family : '' + }`, payload?.data ); if (response?.status === 200) { 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 +33,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 +60,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) ]; diff --git a/src/sagas/Warehouse.js b/src/sagas/Warehouse.js index 2d440e5..b01484c 100644 --- a/src/sagas/Warehouse.js +++ b/src/sagas/Warehouse.js @@ -2,6 +2,7 @@ import { AuthorizedAPI } from 'config'; import { takeLatest, call, put } from 'redux-saga/effects'; import WarehouseActions, { WarehouseTypes } from '../redux/WarehouseRedux'; import ApiServices from 'services/API/ApiServices'; +import { toast } from 'react-toastify'; export function* onRequestWarehouseData({ payload }) { const response = yield call( @@ -28,22 +29,42 @@ export function* onRequestWarehouseData({ payload }) { } } +const makeFormData = (data) => { + const formData = new FormData(); + if (data.name) formData.append('name', data.name); + 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; +}; + export function* onRequestCreateWarehouse({ payload }) { const response = yield call( ApiServices[payload?.method], AuthorizedAPI, payload?.slug, - payload?.data + makeFormData(payload?.data) ); - if (response?.status === 200) { + if (response?.status === 200 && response?.data?.message) { + const warehouse = response?.data?.message; + toast('Warehouse created successfully'); yield put( WarehouseActions.createWarehouseSuccess({ loader: payload?.loader, - createWarehouse: response?.data?.data + createdWarehouse: { + ...warehouse, + preferredInventories: warehouse.preferredInventories.map((z) => z._id) + } }) ); + payload.navigateTo(response?.data?.message?._id); } else { - payload.onFailedCreateWarehouse(response.data.error); + toast('Failed to create warehouse'); yield put( WarehouseActions.createWarehouseFailure({ loader: payload?.loader, @@ -58,17 +79,22 @@ export function* onRequestEditWarehouse({ payload }) { ApiServices[payload?.method], AuthorizedAPI, payload?.slug, - payload?.data + 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, - createWarehouse: response?.data?.data + editedWarehouse: { + ...warehouse, + preferredInventories: warehouse.preferredInventories.map((z) => z._id) + } }) ); } else { - payload.onFailedEditWarehouse(response.data.error); + toast('Failed to edit warehouse'); yield put( WarehouseActions.editWarehouseFailure({ loader: payload?.loader, diff --git a/src/services/ValidationServices.js b/src/services/ValidationServices.js index 15ec3f1..efac783 100644 --- a/src/services/ValidationServices.js +++ b/src/services/ValidationServices.js @@ -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({