Inventory

This commit is contained in:
Sibunnayak 2024-08-14 18:35:43 +05:30
parent 6963e9d43c
commit 911fcf2785
10 changed files with 615 additions and 35 deletions

View File

@ -110,6 +110,13 @@ const _nav = [
to: "/leaves/today", to: "/leaves/today",
group: "Leaves", group: "Leaves",
}, },
{
component: CNavItem,
name: "Inventory Data",
icon: <CIcon icon={cilAddressBook} customClassName="nav-icon" />,
to: "/inventory",
group: "Inventory",
},
// { // {
// component: CNavGroup, // component: CNavGroup,
// name: "Orders", // name: "Orders",

View File

@ -143,6 +143,8 @@ import RetailDistributor from "./views/RetailDistributors/RetailDistributor";
import SingleRetailDistributor from "./views/RetailDistributors/SingleRetailDistributor"; import SingleRetailDistributor from "./views/RetailDistributors/SingleRetailDistributor";
import AddMultipleProduct from "./views/Products/AddMultipleProducts"; import AddMultipleProduct from "./views/Products/AddMultipleProducts";
import AddMultiplePd from "./views/PrincipalDistributors/AddMultiplePD"; import AddMultiplePd from "./views/PrincipalDistributors/AddMultiplePD";
import Inventory from "./views/Inventory/Inventory";
import SingleInventory from "./views/Inventory/SingleInventory";
const routes = [ const routes = [
//dashboard //dashboard
@ -367,6 +369,19 @@ const routes = [
element: AddMultiplePd, element: AddMultiplePd,
navName: "PrincipalDistributor", navName: "PrincipalDistributor",
}, },
//Inventory
{
path: "/inventory",
name: "Inventory",
element: Inventory,
navName: "Inventory",
},
{
path: "/inventory/view/:id",
name: "Inventory",
element: SingleInventory,
navName: "Inventory",
},
//------------------ End customers Route------------------------- //------------------ End customers Route-------------------------
// { // {

View File

@ -0,0 +1,370 @@
import React, { useState, useEffect, useRef, useCallback } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
import { isAutheticated } from "src/auth";
import swal from "sweetalert";
import debounce from "lodash.debounce";
const Inventory = () => {
const token = isAutheticated();
const [loading, setLoading] = useState(false);
const [inventoryData, setInventoryData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const nameRef = useRef();
const startDateRef = useRef();
const endDateRef = useRef();
const [currentPage, setCurrentPage] = useState(1);
const [itemPerPage, setItemPerPage] = useState(10);
const [totalData, setTotalData] = useState(0);
const getInventoryData = async () => {
setLoading(true);
try {
const response = await axios.get(`api/inventory/all`, {
headers: {
Authorization: `Bearer ${token}`,
},
params: {
page: currentPage,
show: itemPerPage,
startDate: startDateRef.current?.value || "",
endDate: endDateRef.current?.value || "",
},
});
const transformedData =
response.data?.inventories?.map((entry) => ({
id: entry._id,
tradeName:
entry.addedForData?.shippingAddress?.tradeName ||
entry.addedForData?.trade_name ||
"N/A",
designation: entry.addedFor === "PrincipalDistributor" ? "PD" : "RD",
products: entry.products.map((product) => ({
SKU: product.SKU,
ProductName: product.ProductName,
Sale: product.Sale,
Inventory: product.Inventory,
})),
createdAt: entry.createdAt,
updatedAt: entry.updatedAt,
})) || [];
setInventoryData(transformedData);
setTotalData(response.data?.total_data || 0);
// Apply the filter after data is fetched
filterData(transformedData);
} catch (err) {
const msg = err?.response?.data?.msg || "Something went wrong!";
swal({
title: "Error",
text: msg,
icon: "error",
button: "Retry",
dangerMode: true,
});
} finally {
setLoading(false);
}
};
const filterData = (data) => {
const tradeName = nameRef.current?.value || "";
const filtered = data.filter((entry) =>
entry.tradeName.toLowerCase().includes(tradeName.toLowerCase())
);
setFilteredData(filtered);
};
const debouncedSearch = useCallback(
debounce(() => {
setCurrentPage(1);
getInventoryData();
}, 500),
[]
);
const handleSearchChange = () => {
debouncedSearch();
};
useEffect(() => {
getInventoryData();
}, [itemPerPage, currentPage]);
return (
<div className="main-content">
<div className="page-content">
<div className="container-fluid">
<div className="row">
<div className="col-12">
<div
className="
page-title-box
d-flex
align-items-center
justify-content-between
"
>
<div style={{ fontSize: "22px" }} className="fw-bold">
Inventory List
</div>
</div>
</div>
</div>
<div className="row">
<div className="col-lg-12">
<div className="card">
<div className="card-body">
<div className="row ml-0 mr-0 mb-10">
<div className="col-lg-1">
<div className="dataTables_length">
<label className="w-100">
Show
<select
onChange={(e) => {
setItemPerPage(e.target.value);
setCurrentPage(1);
}}
className="form-control"
disabled={loading}
>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
entries
</label>
</div>
</div>
<div className="col-lg-3">
<label>Trade Name:</label>
<input
type="text"
placeholder="Trade name"
className="form-control"
ref={nameRef}
onChange={handleSearchChange}
disabled={loading}
/>
</div>
<div className="col-lg-3">
<label>Start Date:</label>
<input
type="date"
className="form-control"
ref={startDateRef}
onChange={handleSearchChange}
disabled={loading}
/>
</div>
<div className="col-lg-3">
<label>End Date:</label>
<input
type="date"
className="form-control"
ref={endDateRef}
onChange={handleSearchChange}
disabled={loading}
/>
</div>
</div>
<div className="table-responsive table-shoot mt-3">
<table
className="table table-centered table-nowrap"
style={{ border: "1px solid" }}
>
<thead
className="thead-light"
style={{ background: "#ecdddd" }}
>
<tr>
<th className="text-start">ID</th>
<th className="text-start">Date</th>
<th className="text-start">Time</th>
<th className="text-start">Trade Name</th>
<th className="text-start">PD/RD</th>
<th className="text-start">Product SKU</th>
<th className="text-start">Product Name</th>
<th className="text-start">Sale</th>
<th className="text-start">Inventory</th>
<th className="text-start">Actions</th>
</tr>
</thead>
<tbody>
{loading ? (
<tr>
<td className="text-center" colSpan="10">
Loading...
</td>
</tr>
) : filteredData.length > 0 ? (
filteredData.map((entry, i) =>
entry.products.map((product, j) => (
<tr key={`${i}-${j}`}>
<td className="text-start">{entry.id}</td>
<td className="text-start">
{new Date(entry.createdAt).toLocaleString(
"en-IN",
{
month: "short",
day: "numeric",
year: "numeric",
}
)}
</td>
<td className="text-start">
{new Date(entry.createdAt).toLocaleString(
"en-IN",
{
hour: "numeric",
minute: "numeric",
hour12: true,
}
)}
</td>
<td className="text-start">
{entry.tradeName}
</td>
<td className="text-start">
{entry.designation}
</td>
<td className="text-start">{product.SKU}</td>
<td className="text-start">
{product.ProductName}
</td>
<td className="text-start">{product.Sale}</td>
<td className="text-center">
{product.Inventory}
</td>
<td className="text-start">
<Link
to={`/inventory/view/${entry.id}`}
state={{ product }}
>
<button
style={{
color: "white",
marginRight: "1rem",
}}
type="button"
className="
btn btn-primary btn-sm
waves-effect waves-light
btn-table
mx-1
mt-1
"
>
View
</button>
</Link>
</td>
</tr>
))
)
) : (
!loading &&
filteredData.length === 0 && (
<tr className="text-center">
<td colSpan="10">
<h5>No Data Available...</h5>
</td>
</tr>
)
)}
</tbody>
</table>
</div>
<div className="row mt-20">
<div className="col-sm-12 col-md-6 mb-20">
<div
className="dataTables_info"
id="datatable_info"
role="status"
aria-live="polite"
>
Showing {Math.min(itemPerPage * currentPage, totalData)} of {totalData} entries
</div>
</div>
<div className="col-sm-12 col-md-6 text-md-end">
<div
className="dataTables_paginate paging_simple_numbers"
id="datatable_paginate"
>
<ul className="pagination pagination-rounded">
<li
className={`paginate_button page-item previous ${currentPage === 1 ? "disabled" : ""
}`}
>
<a
href="#"
className="page-link"
onClick={() => {
if (currentPage > 1) {
setCurrentPage((prev) => prev - 1);
}
}}
>
Previous
</a>
</li>
{[...Array(Math.ceil(totalData / itemPerPage))].map(
(_, index) => (
<li
key={index}
className={`paginate_button page-item ${index + 1 === currentPage ? "active" : ""
}`}
>
<a
href="#"
className="page-link"
onClick={() => setCurrentPage(index + 1)}
>
{index + 1}
</a>
</li>
)
)}
<li
className={`paginate_button page-item next ${currentPage === Math.ceil(totalData / itemPerPage) ? "disabled" : ""
}`}
>
<a
href="#"
className="page-link"
onClick={() => {
if (currentPage < Math.ceil(totalData / itemPerPage)) {
setCurrentPage((prev) => prev + 1);
}
}}
>
Next
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default Inventory;

View File

@ -0,0 +1,178 @@
import React, { useState, useEffect } from "react";
import axios from "axios";
import {
Box,
Typography,
Grid,
Paper,
IconButton,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper as MuiPaper,
} from "@mui/material";
import { useParams, useNavigate } from "react-router-dom";
import { isAutheticated } from "../../auth";
import CancelIcon from "@mui/icons-material/Cancel"; // Add this import
const SingleInventory = () => {
const { id } = useParams();
const [inventoryDetails, setInventoryDetails] = useState(null);
const token = isAutheticated();
const navigate = useNavigate();
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get(`api/inventory/${id}`, {
headers: {
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
});
setInventoryDetails(response.data);
// console.log("Inventory Details: ", response.data);
} catch (error) {
console.error("Error fetching data: ", error);
}
};
fetchData();
}, [id]);
const handleCancel = () => {
navigate("/inventory");
};
if (!inventoryDetails) {
return <Typography>Loading...</Typography>;
}
return (
<Box sx={{ p: 3 }}>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
mb: 3,
}}
>
<Typography variant="h4">Inventory Details</Typography>
<IconButton sx={{ color: "red" }} onClick={handleCancel}>
<CancelIcon />
</IconButton>
</Box>
<Paper sx={{ p: 2, mb: 3 }}>
<Typography variant="h5" gutterBottom>
Inventory Data
</Typography>
<Grid container spacing={2}>
<Grid item xs={12}>
<Typography>
<strong>Timestamp:</strong>{" "}
{new Date(inventoryDetails.createdAt).toLocaleString("en-IN", {
weekday: "short",
month: "short",
day: "numeric",
year: "numeric",
hour: "numeric",
minute: "numeric",
hour12: true,
})}
</Typography>
</Grid>
</Grid>
</Paper>
<Paper sx={{ p: 2, mb: 3 }}>
<Typography variant="h6" gutterBottom>
Product Details
</Typography>
<TableContainer component={MuiPaper}>
<Table>
<TableHead >
<TableRow>
<TableCell><strong>Product Name</strong></TableCell>
<TableCell><strong>SKU</strong></TableCell>
<TableCell><strong>Sale</strong></TableCell>
<TableCell><strong>Inventory</strong></TableCell>
</TableRow>
</TableHead>
<TableBody>
{inventoryDetails.products.map((product, index) => (
<TableRow key={index}>
<TableCell>{product.ProductName}</TableCell>
<TableCell>{product.SKU}</TableCell>
<TableCell>{product.Sale}</TableCell>
<TableCell>{product.Inventory}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Paper>
<Paper sx={{ p: 2, mb: 3 }}>
<Typography variant="h6" gutterBottom>
Data Added For
</Typography>
<Grid container spacing={2}>
<Grid item xs={6}>
<Typography>
<strong>PD or RD:</strong>{" "}
{inventoryDetails.addedFor}
</Typography>
<Typography>
<strong>Name:</strong> {inventoryDetails.addedForData.name}
</Typography>
<Typography>
<strong>Mobile Number:</strong>{" "}
{inventoryDetails.addedForData.phone || inventoryDetails.addedForData.mobile_number}
</Typography>
<Typography>
<strong>Email:</strong> {inventoryDetails.addedForData.email||'N/A'}
</Typography>
<Typography>
<strong>Trade Name:</strong>{" "}
{inventoryDetails.addedForData.shippingAddress?.tradeName||inventoryDetails.addedForData.trade_name}
</Typography>
</Grid>
</Grid>
</Paper>
<Paper sx={{ p: 2, mb: 3 }}>
<Typography variant="h6" gutterBottom>
Data Entered By
</Typography>
<Grid container spacing={2}>
<Grid item xs={6}>
<Typography>
<strong>Designation:</strong> {inventoryDetails.userType}
</Typography>
<Typography>
<strong>Name:</strong> {inventoryDetails.user?.name}
</Typography>
<Typography>
<strong>ID:</strong> {inventoryDetails.user?.uniqueId}
</Typography>
<Typography>
<strong>Email:</strong> {inventoryDetails.user?.email}
</Typography>
<Typography>
<strong>Mobile Number:</strong>{" "}
{inventoryDetails.user?.mobileNumber}
</Typography>
</Grid>
</Grid>
</Paper>
</Box>
);
};
export default SingleInventory;

View File

@ -20,6 +20,7 @@ const AddPrincipalDistributor = () => {
const token = isAutheticated(); const token = isAutheticated();
const [user, setUser] = useState({ const [user, setUser] = useState({
PD_ID: "",
name: "", name: "",
email: "", email: "",
phone: "", phone: "",
@ -89,6 +90,7 @@ const AddPrincipalDistributor = () => {
try { try {
// Validate input fields // Validate input fields
if ( if (
!user.PD_ID ||
!user.name || !user.name ||
!user.email || !user.email ||
!user.phone || !user.phone ||
@ -167,7 +169,20 @@ const AddPrincipalDistributor = () => {
Basic Information Basic Information
</Typography> </Typography>
<Grid container spacing={2} sx={{ mb: 2 }}> <Grid container spacing={2} sx={{ mb: 2 }}>
<Grid item xs={12} sm={4}> <Grid item xs={6}>
<TextField
id="PD_ID"
required
type="text"
fullWidth
name="PD_ID"
value={user.PD_ID}
label="Principal Distributor ID"
variant="outlined"
onChange={handleInputChange}
/>
</Grid>
<Grid item xs={6}>
<TextField <TextField
id="name" id="name"
required required
@ -180,7 +195,7 @@ const AddPrincipalDistributor = () => {
onChange={handleInputChange} onChange={handleInputChange}
/> />
</Grid> </Grid>
<Grid item xs={12} sm={4}> <Grid item xs={6}>
<TextField <TextField
id="email" id="email"
required required
@ -193,7 +208,7 @@ const AddPrincipalDistributor = () => {
onChange={handleInputChange} onChange={handleInputChange}
/> />
</Grid> </Grid>
<Grid item xs={12} sm={4}> <Grid item xs={6}>
<TextField <TextField
id="phone" id="phone"
required required
@ -228,7 +243,7 @@ const AddPrincipalDistributor = () => {
<Grid item xs={12} sm={4}> <Grid item xs={12} sm={4}>
<TextField <TextField
id="tradeName" id="tradeName"
// required required
type="text" type="text"
fullWidth fullWidth
name="tradeName" name="tradeName"
@ -241,7 +256,7 @@ const AddPrincipalDistributor = () => {
<Grid item xs={12} sm={4}> <Grid item xs={12} sm={4}>
<TextField <TextField
id="gstNumber" id="gstNumber"
// required required
type="text" type="text"
fullWidth fullWidth
name="gstNumber" name="gstNumber"

View File

@ -17,6 +17,7 @@ const AddProduct = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [data, setData] = useState({ const [data, setData] = useState({
SKU:"",
name: "", name: "",
category: "", category: "",
description: "", description: "",

View File

@ -89,6 +89,19 @@ const ProductDetails = (props) => {
{loading ? "Loading" : "Save Details"} {loading ? "Loading" : "Save Details"}
</button> </button>
</div> </div>
<div className="mb-3">
<label htmlFor="product" className="form-label">
Product SKU*
</label>
<input
type="text"
className="form-control"
id="SKU"
value={data.SKU}
maxLength="50"
onChange={(e) => handleChange(e)}
/>
</div>
<div className="mb-3"> <div className="mb-3">
<label htmlFor="product" className="form-label"> <label htmlFor="product" className="form-label">
Product Name* Product Name*

View File

@ -289,6 +289,7 @@ const Products = () => {
> >
<tr> <tr>
<th className="text-start">Image</th> <th className="text-start">Image</th>
<th className="text-start">SKU</th>
<th className="text-start">Product</th> <th className="text-start">Product</th>
<th className="text-start">Category</th> <th className="text-start">Category</th>
@ -332,6 +333,7 @@ const Products = () => {
</div> </div>
)} )}
</th> </th>
<td className="text-start">{product.SKU}</td>
<td className="text-start">{product.name}</td> <td className="text-start">{product.name}</td>
<td className="text-start"> <td className="text-start">
{product.category?.categoryName !== "" {product.category?.categoryName !== ""

View File

@ -66,6 +66,10 @@ const ViewProduct = () => {
<div className="table-responsive table-shoot"> <div className="table-responsive table-shoot">
<table className="table table-centered table-nowrap mb-0"> <table className="table table-centered table-nowrap mb-0">
<thead className="thead-light"> <thead className="thead-light">
<tr>
<th>SKU</th>
<td>{productData?.SKU}</td>
</tr>
<tr> <tr>
<th>Name</th> <th>Name</th>
<td>{productData?.name}</td> <td>{productData?.name}</td>

View File

@ -26,35 +26,7 @@ const SingleRetailDistributor = () => {
}, },
}); });
setRetailerDetails(response.data); setRetailerDetails(response.data);
// console.log('Retailer Details: ', response.data);
// // Dummy data
// const dummyData = {
// _id: '66a8bb8392d90216331b1f73',
// name: 'roshan garg',
// trade_name: 'abc',
// address: 'abc btm',
// state: 'KA',
// city: 'Bengaluru',
// district: 'benga',
// pincode: '124515',
// mobile_number: '8516913819',
// principal_distributer: { name: 'Distributor Name' },
// pan_number: '123456454',
// pan_img: { url: 'https://res.cloudinary.com/dslvetard/image/upload/v1722334072/KYC/pan/u3a08xjvrpovfzruedeq.png' },
// aadhar_number: '123123123123',
// aadhar_img: { url: 'https://res.cloudinary.com/dslvetard/image/upload/v1722334074/KYC/aadhar/ep64tuufileifysol4dx.png' },
// gst_number: '121212',
// gst_img: { url: 'https://res.cloudinary.com/dslvetard/image/upload/v1722334076/KYC/gst/jqy8uqa6ejntwhc7mq86.png' },
// pesticide_license_img: { url: 'https://res.cloudinary.com/dslvetard/image/upload/v1722334077/KYC/pesticide_license/iyznci3iibp50pug8e8p.png' },
// fertilizer_license_img: { url: 'https://res.cloudinary.com/dslvetard/image/upload/v1722334080/KYC/fertilizer_license/jnj1r8rzwsbkclarbbch.png' },
// selfie_entrance_img: { url: 'https://res.cloudinary.com/dslvetard/image/upload/v1722334082/KYC/selfie_entrance/weri7zhyioi7lqwv3dvg.png' },
// status: 'new',
// addedBy: { name: 'Added By', uniqueId: 'E123' },
// notes: [],
// createdAt: '2024-01-01T00:00:00Z',
// updatedAt: '2024-01-01T00:00:00Z',
// };
// setRetailerDetails(dummyData);
} catch (error) { } catch (error) {
console.error('Error fetching data: ', error); console.error('Error fetching data: ', error);
} }
@ -209,7 +181,10 @@ const SingleRetailDistributor = () => {
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={6}> <Grid item xs={6}>
<Typography> <Typography>
<strong>Designation:</strong> {retailerDetails.addedBy.name} <strong>Designation:</strong> {retailerDetails.userType}
</Typography>
<Typography>
<strong>Name:</strong> {retailerDetails.addedBy.name}
</Typography> </Typography>
<Typography> <Typography>
<strong>ID:</strong> {retailerDetails.addedBy.uniqueId} <strong>ID:</strong> {retailerDetails.addedBy.uniqueId}