import React, { useState, useEffect, useCallback } from "react"; import clsx from "clsx"; import { connect } from "react-redux"; import { history } from "Redux/store/store"; import ROUTES from "Constants/routes"; import { writeConfigRequest, useConfigInMainRequest, } from "secure-electron-store"; import axios from "axios"; import { makeStyles, withStyles } from "@material-ui/core/styles"; import { Button, Divider, Grid, Typography, Menu, MenuItem, Snackbar, Paper, TextField, InputAdornment, } from "@material-ui/core"; import { DataGrid, GridToolbarContainer, GridToolbarExport, } from "@material-ui/data-grid"; import CloseIcon from "@material-ui/icons/Close"; import SearchIcon from "@material-ui/icons/Search"; import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown"; import { openModal } from "Redux/modals/addItemModalSlice"; import { openEditModal } from "Redux/modals/editItemModalSlice"; import { openSaleModal } from "Redux/modals/addSaleModalSlice"; import { addItem, replaceItems, deleteItem } from "Redux/data/inventorySlice"; import { addSale } from "Redux/data/salesSlice"; import AddItemModal from "Components/modals/addItemModal"; import EditItemModal from "Components/modals/editItemModal"; import AddSaleModal from "Components/modals/addSaleModal"; const useStyles = makeStyles((theme) => ({ divider: { marginBottom: 24 }, section: { paddingLeft: 4, paddingRight: 4, marginBottom: 18 }, data: { height: 650, width: "100%", "& .datagrid.negative": { color: "#d50000" }, "& .datagrid.positive": { color: "#00c853", }, }, root: { width: "100%", "& > * + *": { marginTop: theme.spacing(2), }, }, paper: { padding: theme.spacing(2), color: theme.palette.text.secondary, display: "flex", alignItems: "center", marginBottom: theme.spacing(1), }, buttonSpacing: { marginRight: 12, }, })); const StyledMenu = withStyles({ paper: { border: "1px solid #d3d4d5", }, })((props) => ( <Menu elevation={0} getContentAnchorEl={null} anchorOrigin={{ vertical: "bottom", horizontal: "left", }} transformOrigin={{ vertical: "top", horizontal: "left", }} {...props} /> )); const initContextMenuState = { mouseX: null, mouseY: null, }; const headers = { "Access-Control-Allow-Origin": "http://localhost:40992", "Content-Type": "application/json", "Access-Control-Allow-Methods": "GET", "Access-Control-Allow-Headers": "Content-Type, Authorization", "X-Requested-With": "XMLHttpRequest", }; const Inventory = (props) => { useEffect(() => { window.api.store.send(useConfigInMainRequest); window.api.inventoryData("receiveInventoryData", (data) => { const imports = JSON.parse(data); let currIds = []; for (let i = 0; i < props.inventory.items.length; i++) { currIds.push(props.inventory.items[i].id); } let importsToAdd = imports.filter((im) => { return !currIds.includes(im.id); }); importsToAdd.map((im) => { props.addItem(im); }); window.api.store.send(writeConfigRequest, "inventoryItems", [ ...importsToAdd, ...props.inventory.items, ]); }); console.log("INVENTORYPAGE Props:", props); setInventoryRows(...inventoryRows, props.inventory.items); }, []); const Navigate = (url, item) => { history.push({ pathname: url, search: selectedItems[0], state: "inventory", }); }; const columns = [ { field: "name", headerName: "Product Name", width: 300 }, { field: "size", headerName: "Size", filterable: false, width: 120 }, { field: "sku", headerName: "SKU", width: 130, sortable: false, description: "Style ID", }, { field: "status", headerName: "Status", width: 125 }, { field: "unrealizedProfit", headerName: "Unrealized Profit", filterable: false, width: 165, valueGetter: (params) => `${getUnrealizedProfit(params) < 0 ? "-" : ""}` + `$` + `${Math.abs(getUnrealizedProfit(params)).toFixed(2)}`, cellClassName: (params) => clsx("datagrid", { negative: getUnrealizedProfit(params) < 0, positive: getUnrealizedProfit(params) > 0, }), }, { field: "totalCost", headerName: "Total Cost", filterable: false, description: "Cost + Tax + Shipping", width: 150, valueGetter: (params) => `$${getTotalCost(params).toFixed(2)} `, }, { field: "colorway", headerName: "Colorway", filterable: false, sortable: false, width: 160, }, { field: "store", headerName: "Place of purchase", sortable: false, filterable: false, width: 200, }, { field: "dateObtained", headerName: "Date Obtained", filterable: false, width: 170, }, { field: "dateAdded", headerName: "Date Added", filterable: false, width: 170, }, ]; const getTotalCost = (params) => { return ( parseFloat(params.row.cost) + parseFloat(params.row.costShipping) + parseFloat(params.row.costTax) ); }; const getUnrealizedProfit = (params) => { return ( parseFloat( togglePriceType == "ask" ? params.row.marketPrices.stockX.lowestAsk : params.row.marketPrices.stockX.highestBid ) - parseFloat(getTotalCost(params)) ); }; const [tableLoading, setTableLoading] = useState(true); const [inventoryRows, setInventoryRows] = useState([]); useEffect(() => { if (inventoryRows.length > 0) { setTableLoading(false); } }, [inventoryRows]); const [selectedItems, setSelectedItems] = useState([]); const [selectedItemsTotalCost, setSelectedItemsTotalCost] = useState(0); useEffect(() => { if (selectedItems.length > 0) { let totalCost = parseFloat(0); selectedItems.map((itemId) => { props.inventory.items.map((item) => { if (itemId == item.id) { totalCost += parseFloat(item.cost) + parseFloat(item.costTax) + parseFloat(item.costShipping); } }); }); setSelectedItemsTotalCost(totalCost.toFixed(2)); } else { setSelectedItemsTotalCost(0); } }, [selectedItems]); const deleteSelected = () => { const newItems = props.inventory.items.filter( (item) => !selectedItems.includes(item.id) ); props.replaceItems(newItems); window.api.store.send(writeConfigRequest, "inventoryItems", newItems); setSelectedItems([]); closeContextMenu(); }; const [filterProductInput, setFilterProductInput] = useState(""); const loadInventoryRows = (filter) => { const inventory = props.inventory.items; return new Promise((resolve) => { setTimeout(() => { if (!filter) { resolve(inventory); return; } resolve( inventory.filter( (row) => row.name.toLowerCase().indexOf(filter.toLowerCase()) > -1 ) ); }, Math.random() * 500 + 100); // simulate network latency }); }; useEffect(() => { let active = true; (async () => { setTableLoading(true); const newRows = await loadInventoryRows(filterProductInput); if (!active) { return; } setInventoryRows(newRows); setTableLoading(false); })(); return () => { active = false; }; }, [filterProductInput, props.inventory.items]); const [actionsAnchorEl, setActionsAnchorEl] = useState(null); const handleActionsMenuClick = (e) => { setActionsAnchorEl(e.currentTarget); }; const handleActionsMenuClose = () => { setActionsAnchorEl(null); }; const [filterStatusInput, setFilterStatusInput] = useState("all"); const [statusAnchorEl, setStatusAnchorEl] = useState(null); const handleStatusMenuClick = (e) => { setStatusAnchorEl(e.currentTarget); }; const handleStatusMenuClose = () => { setStatusAnchorEl(null); }; const loadStatusFilterRows = (filter) => { const inventory = props.inventory.items; return new Promise((resolve) => { setTimeout(() => { if (!filter) { resolve(inventory); return; } resolve( inventory.filter( (row) => row.status.toLowerCase() == filter.toLowerCase() ) ); }, Math.random() * 500 + 100); // simulate network latency }); }; useEffect(() => { let active = true; if (filterStatusInput == "all") { setInventoryRows(props.inventory.items); } else { (async () => { setTableLoading(true); const newRows = await loadStatusFilterRows(filterStatusInput); if (!active) { return; } setInventoryRows(newRows); setTableLoading(false); })(); } return () => { active = false; }; }, [filterStatusInput]); const [togglePriceType, setTogglePriceType] = useState("ask"); const [priceTypeAnchorEl, setPriceTypeAnchorEl] = useState(null); const handlePriceTypeMenuClick = (e) => { setPriceTypeAnchorEl(e.currentTarget); }; const handlePriceTypeMenuClose = () => { setPriceTypeAnchorEl(null); }; const [contextMenuVis, setContextMenuVis] = useState(initContextMenuState); const openContextMenu = (e) => { e.preventDefault(); setContextMenuVis({ mouseX: e.clientX - 2, mouseY: e.clientY - 4, }); }; const closeContextMenu = () => { setContextMenuVis(initContextMenuState); }; const styles = useStyles(); return ( <React.Fragment> <div className={styles.root}> <Grid container alignItems="center" direction="row"> <Grid item xs={6}> <Typography variant="h5">Inventory</Typography> <Typography variant="subtitle1"> Manage your inventory of sneakers, apparel, and collectibles. </Typography> </Grid> <Grid item container xs direction="row" justify="flex-end" alignItems="center" > <Button variant="contained" size="small" className={styles.buttonSpacing} onClick={() => { window.api.inventoryData("importInventoryData", (data) => { console.log(data); }); }} > Import </Button> <Button variant="contained" size="small" className={styles.buttonSpacing} onClick={() => { window.api.inventoryData( "exportInventoryData", props.inventory.items ); }} > Export </Button> <Button variant="contained" color="primary" className={styles.buttonSpacing} endIcon={<ArrowDropDownIcon />} onClick={handleActionsMenuClick} > Actions </Button> <StyledMenu anchorEl={actionsAnchorEl} keepMounted open={Boolean(actionsAnchorEl)} onClose={handleActionsMenuClose} > <MenuItem onClick={() => { Navigate(ROUTES.INFO); closeContextMenu(); }} disabled={selectedItems.length == 0} > Info </MenuItem> <MenuItem onClick={() => { closeContextMenu(); }} disabled={selectedItems.length == 0} > Mark Sold </MenuItem> <MenuItem onClick={() => { props.openEditModal(); closeContextMenu(); }} disabled={selectedItems.length == 0} > Edit </MenuItem> <MenuItem onClick={closeContextMenu} disabled={selectedItems.length == 0} > Duplicate </MenuItem> <MenuItem onClick={() => { deleteSelected(); closeContextMenu(); }} disabled={selectedItems.length == 0} > Delete </MenuItem> </StyledMenu> <Button variant="contained" color="primary" onClick={() => { props.openModal(); }} > Add Item </Button> </Grid> </Grid> <Divider className={styles.divider} /> <div className={styles.section}> <Grid container spacing={1} alignItems="center"> <Grid item xs={3} xl={2}> <TextField fullWidth placeholder="Filter by Product Name" value={filterProductInput} onChange={(e) => { setFilterProductInput(e.target.value); }} InputProps={{ startAdornment: ( <InputAdornment position="start"> <SearchIcon /> </InputAdornment> ), endAdornment: ( <InputAdornment position="end" style={{ cursor: "pointer" }} onClick={() => { setFilterProductInput(""); }} > <CloseIcon /> </InputAdornment> ), }} /> </Grid> <Grid item> <Button onClick={handleStatusMenuClick} variant="contained" color="primary" size="small" startIcon={<ArrowDropDownIcon />} > Status: {filterStatusInput} </Button> <StyledMenu anchorEl={statusAnchorEl} keepMounted open={Boolean(statusAnchorEl)} onClose={handleStatusMenuClose} > <MenuItem onClick={() => { setFilterStatusInput("all"); }} > All </MenuItem> <MenuItem onClick={() => { setFilterStatusInput("sitting"); }} > Sitting </MenuItem> <MenuItem onClick={() => { setFilterStatusInput("listed"); }} > Listed </MenuItem> <MenuItem onClick={() => { setFilterStatusInput("personal"); }} > Personal </MenuItem> </StyledMenu> </Grid> <Grid item> <Button onClick={handlePriceTypeMenuClick} variant="contained" color="primary" size="small" startIcon={<ArrowDropDownIcon />} > Price type: {togglePriceType} </Button> <StyledMenu anchorEl={priceTypeAnchorEl} keepMounted open={Boolean(priceTypeAnchorEl)} onClose={handlePriceTypeMenuClose} > <MenuItem onClick={() => { setTogglePriceType("ask"); handlePriceTypeMenuClose(); }} > Ask </MenuItem> <MenuItem onClick={() => { setTogglePriceType("bid"); handlePriceTypeMenuClose(); }} > Bid </MenuItem> </StyledMenu> </Grid> </Grid> </div> <div className={styles.data} onContextMenu={openContextMenu} style={{ cursor: "context-menu" }} > <DataGrid rows={inventoryRows} columns={columns} pageSize={10} checkboxSelection disableColumnMenu loading={tableLoading} onSelectionModelChange={(newSelection) => { setSelectedItems(newSelection.selectionModel); }} selectionModel={selectedItems} /> <Menu keepMounted open={contextMenuVis.mouseY !== null} onClose={closeContextMenu} anchorReference="anchorPosition" anchorPosition={ contextMenuVis.mouseY !== null && contextMenuVis.mouseX !== null ? { top: contextMenuVis.mouseY, left: contextMenuVis.mouseX } : undefined } > <MenuItem onClick={() => { Navigate(ROUTES.INFO); closeContextMenu(); }} disabled={selectedItems.length == 0} > Info </MenuItem> <MenuItem onClick={() => { props.openSaleModal(); closeContextMenu(); }} disabled={selectedItems.length == 0} > Mark as Sold </MenuItem> <MenuItem onClick={() => { props.openEditModal(); closeContextMenu(); }} disabled={selectedItems.length == 0} > Edit </MenuItem> <MenuItem onClick={closeContextMenu} disabled={selectedItems.length == 0} > Duplicate </MenuItem> <MenuItem onClick={() => { deleteSelected(); closeContextMenu(); }} disabled={selectedItems.length == 0} > Delete Selected </MenuItem> </Menu> </div> <Snackbar open={selectedItems.length > 0}> <Paper className={styles.paper}> <Grid container spacing={1} alignItems="center"> <Grid xs={4} item> <Typography variant="body2"> {selectedItems.length == 1 ? selectedItems.length + " item" : selectedItems.length + " items"}{" "} selected </Typography> </Grid> <Grid xs={4} item> <Typography variant="body2"> Total cost: ${selectedItemsTotalCost} </Typography> </Grid> <Grid xs={4} item> <Typography variant="body2"> {selectedItems.length == 1 ? selectedItems.length + " item" : selectedItems.length + " items"}{" "} selected </Typography> </Grid> </Grid> </Paper> </Snackbar> <AddItemModal /> <EditItemModal editItems={selectedItems} /> <AddSaleModal saleItems={selectedItems} /> </div> </React.Fragment> ); }; const mapStateToProps = (state, props) => ({ addItemModal: state.addItemModal, editItemModal: state.editItemModal, inventory: state.inventory, }); const mapDispatch = { openModal, addItem, replaceItems, openEditModal, addSale, openSaleModal, deleteItem, }; export default connect(mapStateToProps, mapDispatch)(Inventory);