From af28c0b99cf8f1650ba419630d2361c714f2b8d9 Mon Sep 17 00:00:00 2001 From: bluestreamlds <85561356+bluestreamlds@users.noreply.github.com> Date: Tue, 1 Mar 2022 03:18:16 +0530 Subject: [PATCH] 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 Co-authored-by: Llewellyn Dsouza --- src/components/TileComponent/index.js | 14 +- src/components/TileComponent/styles.js | 17 + src/components/WidgetNestedDataTable/index.js | 329 +++++++++++++ src/constant/Endpoints.js | 7 +- src/pages/addNewProduct/index.js | 446 ++++++++---------- src/pages/inventory/index.js | 336 ++++++------- src/pages/setupInventory/index.js | 79 ++-- src/redux/InventoryRedux.js | 96 +++- src/redux/WidgetRedux.js | 101 ++++ src/redux/index.js | 4 +- src/routes/index.js | 19 +- src/sagas/Inventory.js | 102 +++- src/sagas/Widget.js | 58 +++ src/sagas/index.js | 2 + src/services/ValidationServices.js | 56 ++- 15 files changed, 1151 insertions(+), 515 deletions(-) create mode 100644 src/components/WidgetNestedDataTable/index.js create mode 100644 src/redux/WidgetRedux.js create mode 100644 src/sagas/Widget.js diff --git a/src/components/TileComponent/index.js b/src/components/TileComponent/index.js index 8bc10c8..e484a9f 100644 --- a/src/components/TileComponent/index.js +++ b/src/components/TileComponent/index.js @@ -34,24 +34,24 @@ export default function Tile({ data, children }) { - + Update {data.name} - + - Add New {data.name} + Add New {data.widgetname} - - + + Cycle Count - + - {data.name} List + {data.widgetname} List diff --git a/src/components/TileComponent/styles.js b/src/components/TileComponent/styles.js index 55d2b6b..e82720c 100644 --- a/src/components/TileComponent/styles.js +++ b/src/components/TileComponent/styles.js @@ -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' + } + } } }); diff --git a/src/components/WidgetNestedDataTable/index.js b/src/components/WidgetNestedDataTable/index.js new file mode 100644 index 0000000..3eafd98 --- /dev/null +++ b/src/components/WidgetNestedDataTable/index.js @@ -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 ( + { + setFormOpen(false); + }} + > + + {['family', 'subfamily'].includes(formType) ? 'Create' : 'Edit'} material{' '} + {['family', 'subfamily'].includes(formType) ? formType : null} + + + {/* Some more text if needed */} + + + + { + setFormOpen(false); + }} + > + Cancel + + Save + + + ); +} + +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 ( + <> + + + + setOpen(!open)} + > + {open ? : } + + { + setFormType(data); + setFormOpen(true); + }} + > + EDIT + + + { + setSelected(data); + }} + > + + {data.name} + + + { + // dispatch( + // WarehouseLocationsActions.deleteLocationRequest({ + // loader: 'location-request', + // slug: API.LOCATION_DELETE, + // method: 'post', + // data: { type: data.location, id: data.id } + // }) + // ); + }} + > + DELETE + + + + + {open && widgetChildren ? ( + + {/* Add headers here */} + {widgetChildren.map((data) => ( + + ))} + + ) : null} + + + ); +} + +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) => ( + + ))} + + + { + setFormType('family'); + setFormOpen(true); + }} + > + Add Family + + + + { + setFormType('subfamily'); + setFormOpen(true); + }} + > + Add SubFamily + + + + {/*
{JSON.stringify(selected, null, 4)}
*/} + + {formOpen && ( + + )} + + ); +} + +WidgetNestedDataTableContainer.propTypes = { + inventoryId: PropTypes.any +}; + +export default WidgetNestedDataTableContainer; diff --git a/src/constant/Endpoints.js b/src/constant/Endpoints.js index b904006..77ce016 100644 --- a/src/constant/Endpoints.js +++ b/src/constant/Endpoints.js @@ -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/' }; diff --git a/src/pages/addNewProduct/index.js b/src/pages/addNewProduct/index.js index f7ec80a..3256b78 100644 --- a/src/pages/addNewProduct/index.js +++ b/src/pages/addNewProduct/index.js @@ -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'}` } ]} /> @@ -135,16 +152,16 @@ function AddNewProduct() { - Warehouse name + {widgetName || 'Item'} name @@ -170,91 +187,46 @@ function AddNewProduct() { Manufacturer - - - - {formik.errors.manufacturer && - formik.touched.manufacturer && - formik.errors.manufacturer} - - + Type - - - - {formik.errors.type && formik.touched.type && formik.errors.type} - - + Unit of Material - - - - {formik.errors.unitofmaterial && - formik.touched.unitofmaterial && - formik.errors.unitofmaterial} - - + @@ -262,12 +234,12 @@ function AddNewProduct() { @@ -279,12 +251,12 @@ function AddNewProduct() { @@ -292,57 +264,31 @@ function AddNewProduct() { Size - - - - {formik.errors.size && formik.touched.size && formik.errors.size} - - + Color - - - - {formik.errors.color && formik.touched.color && formik.errors.color} - - + @@ -350,12 +296,12 @@ function AddNewProduct() { @@ -365,12 +311,12 @@ function AddNewProduct() { @@ -380,16 +326,16 @@ function AddNewProduct() { @@ -398,32 +344,36 @@ function AddNewProduct() { Product Family Association - + - {formik.errors.productfamilyassociation && - formik.touched.productfamilyassociation && - formik.errors.productfamilyassociation} + {formik.errors.primaryWidgetFamilyId && + formik.touched.primaryWidgetFamilyId && + formik.errors.primaryWidgetFamilyId} @@ -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} > - + None Selected - {inventoryTypes.map((name) => ( - - {name} - - ))} + {secondaryFamily && + secondaryFamily.map((fam) => ( + + {fam.name} + + ))} - {formik.errors.productfamilyassociation && - formik.touched.productfamilyassociation && - formik.errors.productfamilyassociation} + {formik.errors.secondaryWidgetFamilyId && + formik.touched.secondaryWidgetFamilyId && + formik.errors.secondaryWidgetFamilyId} @@ -459,7 +410,6 @@ function AddNewProduct() { { + setOpen(true); + }} > add custom fields @@ -508,12 +460,10 @@ function AddNewProduct() { @@ -523,12 +473,10 @@ function AddNewProduct() { @@ -538,12 +486,23 @@ function AddNewProduct() { + + + + Reorder + + @@ -558,10 +517,10 @@ function AddNewProduct() { }} > - cancel + Cancel - add ITem + Add {widgetName} @@ -584,7 +543,12 @@ function AddNewProduct() { padding: '10px 20px' }} > - + { + setOpen(false); + }} + > @@ -628,7 +592,9 @@ function AddNewProduct() { return Placeholder; } }} - onChange={handleChange} + onChange={(event) => { + setManufacturer(event.target.value); + }} > Ten Twenty @@ -648,7 +614,9 @@ function AddNewProduct() { return Placeholder; } }} - onChange={handleChange} + onChange={(event) => { + setManufacturer(event.target.value); + }} > Ten Twenty @@ -783,4 +751,4 @@ function AddNewProduct() { ); } -export default AddNewProduct; +export default AddNewItem; diff --git a/src/pages/inventory/index.js b/src/pages/inventory/index.js index 2214b44..6f8ec94 100644 --- a/src/pages/inventory/index.js +++ b/src/pages/inventory/index.js @@ -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( - 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 + 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: { + ...values, + image: values.image[0]?.file || null + } + }) + ); + // navigate to edit inventory page onSubmitProps.resetForm(); } }); @@ -187,67 +178,43 @@ function InventoryScreen() { - - - Inventory Type - - - - + Widget Name Policies - 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
- {stockBox.map((item) => ( - <> -
- {item.text} - -
- + {definedPolicies.map((item) => ( +
+ {item.text} + +
))} + + + Inventory Process + + +
{ - formik.setFieldValue('images', images); + formik.setFieldValue('image', images); }} /> - - - {dataLevel && - dataLevel.map((item, index) => ( - - - - ))} - - - {'add hierarchy level'} - - - - - - + { + navigate('/setup/inventory'); }} > - - Widget hierarchy - - - - {records && - records.map((item) => ( - - {item.level1} - {item.level2} - - ))} - - - - - - {'CANCEL'} - + {'SAVE'} @@ -346,6 +295,7 @@ function InventoryScreen() { + {inventoryId ? : null} ); diff --git a/src/pages/setupInventory/index.js b/src/pages/setupInventory/index.js index 0c29e29..09c0333 100644 --- a/src/pages/setupInventory/index.js +++ b/src/pages/setupInventory/index.js @@ -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 ; + case 'product': + return ; + case 'fleet': + return ; + case 'rawmaterial': + default: + return ; + } +} 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: - }, - { - name: 'Products', - path: { - update: '/', - addNew: '/setup/inventory/product/add-new-product', - cycleCount: '/', - list: '/' - }, - icon: - }, - { - name: 'Equipment', - path: { update: '/', addNew: '/', cycleCount: '/', list: '/' }, - icon: - }, - { - name: 'Fleet', - path: { update: '/', addNew: '/', cycleCount: '/', list: '/' }, - icon: + useEffect(() => { + if (inventoryData?.length) { + setInventoryAllData(inventoryData); } - ]; + }, [inventoryData]); + + useEffect(() => { + dispatch( + InventoryActions.getInventoryAction({ + loader: 'loading-request', + slug: API.GET_INVENTORY, + method: 'get' + }) + ); + }, []); + return ( @@ -51,20 +62,18 @@ function SetupInventory() { { name: 'Inventory' } ]} > - navigate('/setup/inventory/inventory-new')} - > + navigate('/setup/inventory/new')}> Create Inventory - {tiles && - tiles.map((tile) => ( - 4 ? 4 : 6} key={tile.name}> - {tile.icon} + {inventoryAllData && + inventoryAllData.map((tile) => ( + 4 ? 4 : 6} key={tile._id}> + + {getIconFromSlug(tile.icon_slug)} + ))} diff --git a/src/redux/InventoryRedux.js b/src/redux/InventoryRedux.js index 1dc19c8..f67e144 100644 --- a/src/redux/InventoryRedux.js +++ b/src/redux/InventoryRedux.js @@ -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 }); diff --git a/src/redux/WidgetRedux.js b/src/redux/WidgetRedux.js new file mode 100644 index 0000000..ab219da --- /dev/null +++ b/src/redux/WidgetRedux.js @@ -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 +}); diff --git a/src/redux/index.js b/src/redux/index.js index 025c49f..d23d878 100644 --- a/src/redux/index.js +++ b/src/redux/index.js @@ -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) => { diff --git a/src/routes/index.js b/src/routes/index.js index 0af896c..fd423fb 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -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: }, { - name: 'Add New Product', - key: 'add-new-product', + name: 'Inventory Definition', + key: 'inventory-update', hide: true, - route: '/setup/inventory/product/add-new-product', - component: + route: '/setup/inventory/update/:inventoryId', + component: + }, + { + name: 'Add New Item', + key: 'add-new-item', + hide: true, + route: '/setup/inventory/new-item/:widgetName/:inventoryId', + component: }, { name: 'Location Labeling', diff --git a/src/sagas/Inventory.js b/src/sagas/Inventory.js index fe1e20d..ae3d2cc 100644 --- a/src/sagas/Inventory.js +++ b/src/sagas/Inventory.js @@ -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) +]; diff --git a/src/sagas/Widget.js b/src/sagas/Widget.js new file mode 100644 index 0000000..30f9448 --- /dev/null +++ b/src/sagas/Widget.js @@ -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) +]; diff --git a/src/sagas/index.js b/src/sagas/index.js index 72a2c8c..f8ab1db 100644 --- a/src/sagas/index.js +++ b/src/sagas/index.js @@ -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]); } diff --git a/src/services/ValidationServices.js b/src/services/ValidationServices.js index 58576bb..5f1832f 100644 --- a/src/services/ValidationServices.js +++ b/src/services/ValidationServices.js @@ -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() }) };