Mapping PD/RD and search functionality

This commit is contained in:
Sibunnayak 2024-09-06 16:23:46 +05:30
parent 237861d6a8
commit 7ca86eeafa
7 changed files with 1300 additions and 252 deletions

View File

@ -146,6 +146,8 @@ import SingleInventory from "./views/Inventory/SingleInventory";
import ViewProductManual from "./views/ProductManual/SingleProductManual"; import ViewProductManual from "./views/ProductManual/SingleProductManual";
import ViewSalesCoOrdinatorTM from "./views/TerritoryManager/ViewSalesCoOrdinatorTM"; import ViewSalesCoOrdinatorTM from "./views/TerritoryManager/ViewSalesCoOrdinatorTM";
import ViewPrincipalDistributorTM from "./views/TerritoryManager/ViewPrincipalDistributorTM"; import ViewPrincipalDistributorTM from "./views/TerritoryManager/ViewPrincipalDistributorTM";
import ViewRetailDistributorTM from "./views/TerritoryManager/ViewRetailDistributor";
import AddRetailDistributor from "./views/RetailDistributors/addRetailDistributor";
const routes = [ const routes = [
//dashboard //dashboard
@ -268,6 +270,12 @@ const routes = [
element: ViewPrincipalDistributorTM, element: ViewPrincipalDistributorTM,
navName: "TerritoryManagers", navName: "TerritoryManagers",
}, },
{
path: "/view/retaildistributor/:id",
name: "View Retail Distributor",
element: ViewRetailDistributorTM,
navName: "TerritoryManagers",
},
// Attendence // Attendence
{ {
path: "/attendance/today", path: "/attendance/today",
@ -331,6 +339,12 @@ const routes = [
element: SingleRetailDistributor, element: SingleRetailDistributor,
navName: "RetailDistributor", navName: "RetailDistributor",
}, },
{
path: "/retaildistributor/add",
name: "Add Retail Distributor",
element: AddRetailDistributor,
navName: "RetailDistributor",
},
//----------------------- End Product Management Routes------------------------------------------------ //----------------------- End Product Management Routes------------------------------------------------
//Departure //Departure

View File

@ -1,54 +1,45 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useRef, useCallback } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import axios from "axios"; import axios from "axios";
import { isAutheticated } from "src/auth"; import { isAutheticated } from "src/auth";
import swal from "sweetalert"; import swal from "sweetalert";
import {
Box,
FormControl,
IconButton,
InputLabel,
MenuItem,
Select,
TextField,
} from "@mui/material";
import SearchIcon from "@mui/icons-material/Search";
import Fuse from "fuse.js";
import { Typography } from "@material-ui/core";
import OrderDetails from "./orderDetails"; import OrderDetails from "./orderDetails";
import debounce from "lodash.debounce";
const principalDistributor = () => { const principalDistributor = () => {
const token = isAutheticated(); const token = isAutheticated();
const [query, setQuery] = useState("");
const navigate = useNavigate(); const navigate = useNavigate();
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [loading1, setLoading1] = useState(true); const [loading1, setLoading1] = useState(true);
const [success, setSuccess] = useState(true); const [success, setSuccess] = useState(true);
const [users, setUsers] = useState([]); const [users, setUsers] = useState([]);
const [totalPages, setTotalPages] = useState(1);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [itemPerPage, setItemPerPage] = useState(10); const [itemPerPage, setItemPerPage] = useState(10);
const [showData, setShowData] = useState(users); const [totalData, setTotalData] = useState(0);
const nameRef = useRef();
const handleShowEntries = (e) => { const SBURef = useRef();
setCurrentPage(1);
setItemPerPage(e.target.value);
};
const getUsers = async () => { const getUsers = async () => {
axios setLoading(true);
try {
const res = await axios
.get(`/api/v1/admin/users`, { .get(`/api/v1/admin/users`, {
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}, },
}) params: {
.then((res) => { page: currentPage,
show: itemPerPage,
name: nameRef.current.value,
SBU: SBURef.current.value,
},
});
// console.log(res.data); // console.log(res.data);
setUsers(res.data.users); setUsers(res.data.users);
setLoading(false); setTotalData(res.data?.total_data);
}) setTotalPages(res.data?.total_pages);
.catch((error) => { }catch(error) {
swal({ swal({
title: error, title: error,
text: "please login to access the resource or refresh the page ", text: "please login to access the resource or refresh the page ",
@ -56,23 +47,26 @@ const principalDistributor = () => {
button: "Retry", button: "Retry",
dangerMode: true, dangerMode: true,
}); });
} finally {
setLoading(false); setLoading(false);
}); }
}; };
useEffect(() => { useEffect(() => {
getUsers(); getUsers();
}, [success]); }, [itemPerPage, currentPage]);
// console.log(users);
useEffect(() => { const debouncedSearch = useCallback(
const loadData = () => { debounce(() => {
const indexOfLastPost = currentPage * itemPerPage; setCurrentPage(1);
const indexOfFirstPost = indexOfLastPost - itemPerPage; getUsers();
setShowData(users.slice(indexOfFirstPost, indexOfLastPost)); }, 500),
[]
);
const handleSearchChange = () => {
debouncedSearch();
}; };
loadData();
}, [currentPage, itemPerPage, users]);
// console.log(users); // console.log(users);
// const handleDelete = (id) => { // const handleDelete = (id) => {
@ -165,19 +159,17 @@ const principalDistributor = () => {
<div className="card"> <div className="card">
<div className="card-body"> <div className="card-body">
<div className="row ml-0 mr-0 mb-10 "> <div className="row ml-0 mr-0 mb-10 ">
<div className="col-sm-12 col-md-12"> <div className="col-lg-1">
<div className="dataTables_length"> <div className="dataTables_length">
<label className="w-100"> <label className="w-100">
Show Show
<select <select
style={{ width: "10%" }} onChange={(e) => {
name="" setItemPerPage(Number(e.target.value));
onChange={(e) => handleShowEntries(e)} setCurrentPage(1); // Reset to first page when changing items per page
className=" }}
select-w className="form-control"
custom-select custom-select-sm disabled={loading}
form-control form-control-sm
"
> >
<option value="10">10</option> <option value="10">10</option>
<option value="25">25</option> <option value="25">25</option>
@ -188,6 +180,30 @@ const principalDistributor = () => {
</label> </label>
</div> </div>
</div> </div>
<div className="col-lg-3">
<label>Trade Name:</label>
<input
type="text"
name="searchTerm"
placeholder="Trade name"
className="form-control"
ref={nameRef}
onChange={handleSearchChange}
disabled={loading}
/>
</div>
<div className="col-lg-3">
<label>SBU:</label>
<input
type="text"
name="searchTerm"
placeholder="SBU"
className="form-control"
ref={SBURef}
onChange={handleSearchChange}
disabled={loading}
/>
</div>
</div> </div>
<div className="table-responsive table-shoot mt-3"> <div className="table-responsive table-shoot mt-3">
@ -213,25 +229,17 @@ const principalDistributor = () => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{!loading && showData.length === 0 && ( {loading ? (
<tr className="text-center"> <tr className="text-center">
<td colSpan="6"> <td colSpan="6">
<h5>No Data Available</h5> <h5>Loading....</h5>
</td> </td>
</tr> </tr>
)} ) : users.length > 0 ? (
{loading ? ( users.map((user, i) => (
<tr>
<td className="text-center" colSpan="6">
Loading...
</td>
</tr>
) : (
showData.map((user, i) => {
return (
<tr key={i}> <tr key={i}>
<td>{user.uniqueId}</td> <td>{user.uniqueId}</td>
<td>{user.SBU?user.SBU:"N/A"}</td> <td>{user.SBU ? user.SBU : "N/A"}</td>
<td className="text-start">{user.name}</td> <td className="text-start">{user.name}</td>
<td>{user.email}</td> <td>{user.email}</td>
<td className="text-start"> <td className="text-start">
@ -337,8 +345,13 @@ const principalDistributor = () => {
</Link> </Link>
</td> </td>
</tr> </tr>
); ))
}) ) : (
<tr>
<td colSpan="6" className="text-center">
<h5>No Principal Distributor found!</h5>
</td>
</tr>
)} )}
</tbody> </tbody>
</table> </table>
@ -352,88 +365,60 @@ const principalDistributor = () => {
role="status" role="status"
aria-live="polite" aria-live="polite"
> >
Showing {currentPage * itemPerPage - itemPerPage + 1} to{" "} Showing {users?.length} of {totalData} entries
{Math.min(currentPage * itemPerPage, users.length)} of{" "}
{users.length} entries
</div> </div>
</div> </div>
<div className="col-sm-12 col-md-6"> <div className="col-sm-12 col-md-6">
<div className="d-flex"> <div className="dataTables_paginate paging_simple_numbers">
<ul className="pagination ms-auto"> <ul className="pagination">
<li <li
className={ className={`paginate_button page-item previous ${
currentPage === 1 currentPage === 1 ? "disabled" : ""
? "paginate_button page-item previous disabled" }`}
: "paginate_button page-item previous"
}
> >
<span <a
className="page-link" className="page-link"
style={{ cursor: "pointer" }} onClick={() =>
onClick={() => setCurrentPage((prev) => prev - 1)} setCurrentPage((prev) => Math.max(prev - 1, 1))
}
aria-controls="datatable"
> >
Previous Previous
</span> </a>
</li> </li>
{Array.from({ length: totalPages }, (_, index) => (
{!(currentPage - 1 < 1) && (
<li className="paginate_button page-item">
<span
className="page-link"
style={{ cursor: "pointer" }}
onClick={(e) =>
setCurrentPage((prev) => prev - 1)
}
>
{currentPage - 1}
</span>
</li>
)}
<li className="paginate_button page-item active">
<span
className="page-link"
style={{ cursor: "pointer" }}
>
{currentPage}
</span>
</li>
{!(
(currentPage + 1) * itemPerPage - itemPerPage >
users.length - 1
) && (
<li className="paginate_button page-item ">
<span
className="page-link"
style={{ cursor: "pointer" }}
onClick={() => {
setCurrentPage((prev) => prev + 1);
}}
>
{currentPage + 1}
</span>
</li>
)}
<li <li
className={ key={index}
!( className={`paginate_button page-item ${
(currentPage + 1) * itemPerPage - itemPerPage > currentPage === index + 1 ? "active" : ""
users.length - 1 }`}
)
? "paginate_button page-item next"
: "paginate_button page-item next disabled"
}
> >
<span <a
className="page-link" className="page-link"
style={{ cursor: "pointer" }} onClick={() => setCurrentPage(index + 1)}
onClick={() => setCurrentPage((prev) => prev + 1)} aria-controls="datatable"
>
{index + 1}
</a>
</li>
))}
<li
className={`paginate_button page-item next ${
currentPage === totalPages ? "disabled" : ""
}`}
>
<a
className="page-link"
onClick={() =>
setCurrentPage((prev) =>
Math.min(prev + 1, totalPages)
)
}
aria-controls="datatable"
> >
Next Next
</span> </a>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -1,5 +1,6 @@
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback, useRef } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import axios from "axios"; import axios from "axios";
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import { isAutheticated } from "src/auth"; import { isAutheticated } from "src/auth";
@ -8,18 +9,17 @@ import debounce from "lodash.debounce";
const RetailDistributor = () => { const RetailDistributor = () => {
const token = isAutheticated(); const token = isAutheticated();
const Navigate = useNavigate();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [allRetailDistributorsData, setAllRetailDistributorsData] = useState( const [allRetailDistributorsData, setAllRetailDistributorsData] = useState(
[] []
); );
const [filteredRetailDistributorsData, setFilteredRetailDistributorsData] = const nameRef = useRef();
useState([]); const principalDistributorRef = useRef();
const [searchTerm, setSearchTerm] = useState(""); const [totalPages, setTotalPages] = useState(1);
const [searchPrincipalDistributor, setSearchPrincipalDistributor] =
useState("");
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [itemPerPage, setItemPerPage] = useState(10); const [itemPerPage, setItemPerPage] = useState(10);
const [totalData, setTotalData] = useState(0);
const getRetailDistributorsData = async () => { const getRetailDistributorsData = async () => {
setLoading(true); setLoading(true);
@ -28,9 +28,16 @@ const RetailDistributor = () => {
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}, },
params: {
page: currentPage,
show: itemPerPage,
name: nameRef.current.value,
principaldistributor: principalDistributorRef.current.value,
},
}); });
setAllRetailDistributorsData(res.data || []); setAllRetailDistributorsData(res.data?.kycs);
setFilteredRetailDistributorsData(res.data || []); // Initialize filtered data setTotalData(res.data?.total_data);
setTotalPages(res.data?.total_pages);
} catch (err) { } catch (err) {
const msg = err?.response?.data?.message || "Something went wrong!"; const msg = err?.response?.data?.message || "Something went wrong!";
swal({ swal({
@ -47,65 +54,17 @@ const RetailDistributor = () => {
useEffect(() => { useEffect(() => {
getRetailDistributorsData(); getRetailDistributorsData();
}, []); }, [ itemPerPage, currentPage]);
useEffect(() => { const debouncedSearch = useCallback(debounce(() => {
filterData(); setCurrentPage(1);
}, [searchTerm, searchPrincipalDistributor, allRetailDistributorsData]); getRetailDistributorsData();
}, 500), []);
const filterData = () => { const handleSearchChange = () => {
let filteredData = allRetailDistributorsData;
if (searchTerm) {
filteredData = filteredData.filter((item) =>
item.trade_name.toLowerCase().includes(searchTerm.toLowerCase())
);
}
if (searchPrincipalDistributor) {
filteredData = filteredData.filter((item) =>
item.principal_distributer?.name
?.toLowerCase()
.includes(searchPrincipalDistributor.toLowerCase())
);
}
setFilteredRetailDistributorsData(filteredData);
setCurrentPage(1); // Reset to first page when filtering
};
const debouncedSearch = useCallback(
debounce(() => {
filterData();
}, 500),
[searchTerm, searchPrincipalDistributor]
);
const handleSearchChange = (e) => {
const { name, value } = e.target;
if (name === "searchTerm") {
setSearchTerm(value);
} else if (name === "searchPrincipalDistributor") {
setSearchPrincipalDistributor(value);
}
debouncedSearch(); debouncedSearch();
}; };
const totalPages = Math.ceil(
filteredRetailDistributorsData.length / itemPerPage
);
const paginatedData = filteredRetailDistributorsData.slice(
(currentPage - 1) * itemPerPage,
currentPage * itemPerPage
);
const handleResetSearch = () => {
setSearchTerm("");
setSearchPrincipalDistributor("");
setFilteredRetailDistributorsData(allRetailDistributorsData); // Reset filtered data
setCurrentPage(1); // Reset to first page
};
return ( return (
<div className="main-content"> <div className="main-content">
<div className="page-content"> <div className="page-content">
@ -116,6 +75,18 @@ const RetailDistributor = () => {
<div style={{ fontSize: "22px" }} className="fw-bold"> <div style={{ fontSize: "22px" }} className="fw-bold">
Retail Distributors Retail Distributors
</div> </div>
<div className="page-title-right">
<Button
variant="contained"
color="primary"
className="font-bold mb-2 capitalize mr-2"
onClick={() => {
Navigate("/retaildistributor/add");
}}
>
Add Retail Distributor
</Button>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -153,9 +124,9 @@ const RetailDistributor = () => {
name="searchTerm" name="searchTerm"
placeholder="Trade name" placeholder="Trade name"
className="form-control" className="form-control"
ref={nameRef}
onChange={handleSearchChange} onChange={handleSearchChange}
disabled={loading} disabled={loading}
value={searchTerm}
/> />
</div> </div>
<div className="col-lg-3"> <div className="col-lg-3">
@ -167,18 +138,9 @@ const RetailDistributor = () => {
className="form-control" className="form-control"
onChange={handleSearchChange} onChange={handleSearchChange}
disabled={loading} disabled={loading}
value={searchPrincipalDistributor} ref={principalDistributorRef}
/> />
</div> </div>
<div className="col-lg-3">
<Button
variant="outlined"
color="primary"
onClick={handleResetSearch}
>
Reset Search
</Button>
</div>
</div> </div>
<div className="table-responsive table-shoot mt-3"> <div className="table-responsive table-shoot mt-3">
@ -205,8 +167,8 @@ const RetailDistributor = () => {
Loading... Loading...
</td> </td>
</tr> </tr>
) : paginatedData.length > 0 ? ( ) : allRetailDistributorsData.length > 0 ? (
paginatedData.map((retailDistributor) => ( allRetailDistributorsData.map((retailDistributor) => (
<tr key={retailDistributor._id}> <tr key={retailDistributor._id}>
<td className="text-start"> <td className="text-start">
{retailDistributor._id} {retailDistributor._id}
@ -272,12 +234,7 @@ const RetailDistributor = () => {
role="status" role="status"
aria-live="polite" aria-live="polite"
> >
Showing {currentPage * itemPerPage - itemPerPage + 1} to{" "} Showing {allRetailDistributorsData?.length} of {totalData} entries
{Math.min(
currentPage * itemPerPage,
filteredRetailDistributorsData.length
)}{" "}
of {filteredRetailDistributorsData.length} entries
</div> </div>
</div> </div>

View File

@ -98,7 +98,7 @@ const SingleRetailDistributor = () => {
<strong>Mobile Number:</strong> {retailerDetails.mobile_number} <strong>Mobile Number:</strong> {retailerDetails.mobile_number}
</Typography> </Typography>
<Typography> <Typography>
<strong>Mapped Principal Distributor:</strong> {retailerDetails.principal_distributer.name} <strong>Mapped Principal Distributor:</strong> {retailerDetails?.principal_distributer?.name || 'Not Mapped'}
</Typography> </Typography>
</Grid> </Grid>
</Grid> </Grid>
@ -181,13 +181,13 @@ const SingleRetailDistributor = () => {
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={6}> <Grid item xs={6}>
<Typography> <Typography>
<strong>Designation:</strong> {retailerDetails.userType} <strong>Designation:</strong> {retailerDetails?.userType|| 'Not Available'}
</Typography> </Typography>
<Typography> <Typography>
<strong>Name:</strong> {retailerDetails.addedBy.name} <strong>Name:</strong> {retailerDetails?.addedBy?.name || 'Not Available'}
</Typography> </Typography>
<Typography> <Typography>
<strong>ID:</strong> {retailerDetails.addedBy.uniqueId} <strong>ID:</strong> {retailerDetails?.addedBy?.uniqueId || 'Not Available'}
</Typography> </Typography>
</Grid> </Grid>
</Grid> </Grid>

View File

@ -0,0 +1,524 @@
import React, { useState, useEffect } from "react";
import {
TextField,
Button,
Card,
Grid,
Typography,
Autocomplete,
CircularProgress,
} from "@mui/material";
import { useNavigate } from "react-router-dom";
import { toast } from "react-hot-toast";
import axios from "axios";
import { isAutheticated } from "src/auth";
import { City, State } from "country-state-city";
const AddRetailDistributor = () => {
const navigate = useNavigate();
const token = isAutheticated();
const [user, setUser] = useState({
name: "",
trade_name: "",
address: "",
state: "",
city: "",
district: "",
pincode: "",
mobile_number: "",
pan_number: "",
aadhar_number: "",
gst_number: "",
});
const [files, setFiles] = useState({
selfieEntranceImg: null,
panImg: null,
aadharImg: null,
gstImg: null,
pesticideLicenseImg: null,
fertilizerLicenseImg: null,
});
const [loading, setLoading] = useState(false);
const [stateOptions, setStateOptions] = useState([]);
const [cityOptions, setCityOptions] = useState([]);
const [selectedState, setSelectedState] = useState(null);
const [selectedCity, setSelectedCity] = useState(null);
const [errors, setErrors] = useState({});
useEffect(() => {
const fetchStates = async () => {
const states = State.getStatesOfCountry("IN").map((state) => ({
label: state.name,
value: state.isoCode,
}));
setStateOptions(states);
};
fetchStates();
}, []);
useEffect(() => {
const fetchCities = async () => {
if (selectedState) {
const cities = City.getCitiesOfState("IN", selectedState.value).map(
(city) => ({
label: city.name,
value: city.name,
})
);
setCityOptions(cities);
}
};
fetchCities();
}, [selectedState]);
const handleInputChange = (e) => {
setUser({ ...user, [e.target.name]: e.target.value });
};
const handleStateChange = (event, newValue) => {
setSelectedState(newValue);
};
const handleCityChange = (event, newValue) => {
setSelectedCity(newValue);
};
const handleFileChange = (e) => {
const { name, files } = e.target;
if (files.length > 0) {
const file = files[0];
if (["image/png", "image/jpeg", "image/jpg"].includes(file.type)) {
setFiles((prev) => ({ ...prev, [name]: file }));
} else {
toast.error("Only PNG, JPG, and JPEG files are allowed.");
}
}
};
const handleFormSubmit = async (e) => {
e.preventDefault();
try {
// Validate input fields
if (
!user.name ||
!user.trade_name ||
!user.address ||
!user.mobile_number ||
!user.pan_number ||
!user.aadhar_number ||
!user.gst_number ||
!selectedState ||
!selectedCity
) {
setErrors({ message: "Fill all required fields!" });
return;
}
setLoading(true);
const formData = new FormData();
formData.append("name", user.name);
formData.append("trade_name", user.trade_name);
formData.append("address", user.address);
formData.append("state", selectedState.value);
formData.append("city", selectedCity.value);
formData.append("district", user.district);
formData.append("pincode", user.pincode);
formData.append("mobile_number", user.mobile_number);
formData.append("pan_number", user.pan_number);
formData.append("aadhar_number", user.aadhar_number);
formData.append("gst_number", user.gst_number);
// Ensure files are included only if they exist
Object.keys(files).forEach((key) => {
if (files[key]) {
formData.append(key, files[key]);
}
});
// Attempt to create distributor
const response = await axios.post("/api/kyc/create-admin/", formData, {
headers: {
"Content-Type": "multipart/form-data",
"Authorization": `Bearer ${token}`,
},
});
if (response.data.success) {
toast.success("Retail Distributor created successfully!");
navigate("/retail-distributor");
}
} catch (error) {
setLoading(false);
if (error.response && error.response.data) {
toast.error(error.response.data.message || "Something went wrong!");
} else {
toast.error("Something went wrong!");
}
}
};
const handleCancel = () => {
navigate("/retail-distributor");
};
return (
<div>
<Card
sx={{ padding: "1rem", marginBottom: "1rem", position: "relative" }}
>
<Button
variant="outlined"
color="secondary"
onClick={handleCancel}
sx={{ position: "absolute", top: "10px", right: "10px" }}
>
Cancel
</Button>
<Typography variant="h5" sx={{ mb: 3 }}>
Add Retail Distributor
</Typography>
<form onSubmit={handleFormSubmit}>
<Typography variant="h5" sx={{ mb: 2 }}>
Basic Information
</Typography>
<Grid container spacing={2} sx={{ mb: 2 }}>
{/* Name */}
<Grid item xs={12} className="d-flex align-items-center">
<Grid item xs={2}>
<Typography
variant="body1"
htmlFor="name"
className="form-label"
>
Name*
</Typography>
</Grid>
<Grid item xs={10}>
<TextField
id="name"
required
type="text"
fullWidth
name="name"
value={user.name}
variant="outlined"
onChange={handleInputChange}
/>
</Grid>
</Grid>
{/* Trade Name */}
<Grid item xs={12} className="d-flex align-items-center">
<Grid item xs={2}>
<Typography
variant="body1"
htmlFor="trade_name"
className="form-label"
>
Trade Name*
</Typography>
</Grid>
<Grid item xs={10}>
<TextField
id="trade_name"
required
type="text"
fullWidth
name="trade_name"
value={user.trade_name}
variant="outlined"
onChange={handleInputChange}
/>
</Grid>
</Grid>
{/* Address */}
<Grid item xs={12} className="d-flex align-items-center">
<Grid item xs={2}>
<Typography
variant="body1"
htmlFor="address"
className="form-label"
>
Address*
</Typography>
</Grid>
<Grid item xs={10}>
<TextField
id="address"
required
type="text"
fullWidth
name="address"
value={user.address}
variant="outlined"
onChange={handleInputChange}
/>
</Grid>
</Grid>
{/* Mobile Number */}
<Grid item xs={12} className="d-flex align-items-center">
<Grid item xs={2}>
<Typography
variant="body1"
htmlFor="mobile_number"
className="form-label"
>
Mobile Number*
</Typography>
</Grid>
<Grid item xs={10}>
<TextField
id="mobile_number"
required
type="text"
fullWidth
name="mobile_number"
value={user.mobile_number}
variant="outlined"
onChange={handleInputChange}
/>
</Grid>
</Grid>
{/* PAN Number */}
<Grid item xs={12} className="d-flex align-items-center">
<Grid item xs={2}>
<Typography
variant="body1"
htmlFor="pan_number"
className="form-label"
>
PAN Number*
</Typography>
</Grid>
<Grid item xs={10}>
<TextField
id="pan_number"
required
type="text"
fullWidth
name="pan_number"
value={user.pan_number}
variant="outlined"
onChange={handleInputChange}
/>
</Grid>
</Grid>
{/* Aadhar Number */}
<Grid item xs={12} className="d-flex align-items-center">
<Grid item xs={2}>
<Typography
variant="body1"
htmlFor="aadhar_number"
className="form-label"
>
Aadhar Number*
</Typography>
</Grid>
<Grid item xs={10}>
<TextField
id="aadhar_number"
required
type="text"
fullWidth
name="aadhar_number"
value={user.aadhar_number}
variant="outlined"
onChange={handleInputChange}
/>
</Grid>
</Grid>
{/* GST Number */}
<Grid item xs={12} className="d-flex align-items-center">
<Grid item xs={2}>
<Typography
variant="body1"
htmlFor="gst_number"
className="form-label"
>
GST Number*
</Typography>
</Grid>
<Grid item xs={10}>
<TextField
id="gst_number"
required
type="text"
fullWidth
name="gst_number"
value={user.gst_number}
variant="outlined"
onChange={handleInputChange}
/>
</Grid>
</Grid>
{/* State */}
<Grid item xs={12} className="d-flex align-items-center">
<Grid item xs={2}>
<Typography
variant="body1"
htmlFor="state"
className="form-label"
>
State*
</Typography>
</Grid>
<Grid item xs={10}>
<Autocomplete
options={stateOptions}
value={selectedState}
onChange={handleStateChange}
renderInput={(params) => (
<TextField
{...params}
id="state"
required
variant="outlined"
/>
)}
/>
</Grid>
</Grid>
{/* City */}
<Grid item xs={12} className="d-flex align-items-center">
<Grid item xs={2}>
<Typography
variant="body1"
htmlFor="city"
className="form-label"
>
City*
</Typography>
</Grid>
<Grid item xs={10}>
<Autocomplete
options={cityOptions}
value={selectedCity}
onChange={handleCityChange}
renderInput={(params) => (
<TextField
{...params}
id="city"
required
variant="outlined"
/>
)}
/>
</Grid>
</Grid>
{/* District */}
<Grid item xs={12} className="d-flex align-items-center">
<Grid item xs={2}>
<Typography
variant="body1"
htmlFor="district"
className="form-label"
>
District*
</Typography>
</Grid>
<Grid item xs={10}>
<TextField
id="district"
type="text"
fullWidth
name="district"
value={user.district}
variant="outlined"
onChange={handleInputChange}
/>
</Grid>
</Grid>
{/* Pincode */}
<Grid item xs={12} className="d-flex align-items-center">
<Grid item xs={2}>
<Typography
variant="body1"
htmlFor="pincode"
className="form-label"
>
Pincode*
</Typography>
</Grid>
<Grid item xs={10}>
<TextField
id="pincode"
type="text"
fullWidth
name="pincode"
value={user.pincode}
variant="outlined"
onChange={handleInputChange}
/>
</Grid>
</Grid>
{/* File Uploads */}
{[
{ label: "Selfie Entrance Image*", name: "selfieEntranceImg" },
{ label: "PAN Image*", name: "panImg" },
{ label: "Aadhar Image*", name: "aadharImg" },
{ label: "GST Image*", name: "gstImg" },
{ label: "Pesticide License Image*", name: "pesticideLicenseImg" },
{ label: "Fertilizer License Image*", name: "fertilizerLicenseImg" },
].map(({ label, name }) => (
<Grid item xs={12} className="d-flex align-items-center" key={name}>
<Grid item xs={2}>
<Typography
variant="body1"
htmlFor={name}
className="form-label"
>
{label}
</Typography>
</Grid>
<Grid item xs={10}>
<input
type="file"
id={name}
name={name}
onChange={handleFileChange}
accept=".png,.jpg,.jpeg"
/>
</Grid>
</Grid>
))}
{/* Submit Button */}
<Grid item xs={12} className="d-flex align-items-center">
<Grid item xs={2}></Grid>
<Grid item xs={10}>
<Button
type="submit"
variant="contained"
color="primary"
disabled={loading}
>
{loading ? (
<CircularProgress size={24} />
) : (
"Submit"
)}
</Button>
</Grid>
</Grid>
</Grid>
</form>
</Card>
</div>
);
};
export default AddRetailDistributor;

View File

@ -302,6 +302,20 @@ const TerritoryManager = () => {
PD PD
</button> </button>
</Link> </Link>
<Link
to={`/view/retaildistributor/${territorymanager._id}`}
>
<button
style={{
color: "white",
marginRight: "1rem",
}}
type="button"
className="btn btn-primary btn-sm waves-effect waves-light btn-table ml-2"
>
RD
</button>
</Link>
<Link <Link
to={`/territorymanager/edit/${territorymanager._id}`} to={`/territorymanager/edit/${territorymanager._id}`}
> >

View File

@ -0,0 +1,554 @@
import React, { useState, useEffect, useRef, useCallback } from "react";
import { Link, useParams } from "react-router-dom";
import axios from "axios";
import Button from "@material-ui/core/Button";
import { useNavigate } from "react-router-dom";
import { isAutheticated } from "src/auth";
import swal from "sweetalert";
import debounce from "lodash.debounce";
import Dialog from "@material-ui/core/Dialog";
import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogTitle from "@material-ui/core/DialogTitle";
import TextField from "@material-ui/core/TextField";
const ViewRetailDistributorTM = () => {
const token = isAutheticated();
const { id } = useParams();
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(true);
const [retaildistributorData, setretaildistributorData] = useState([]);
const [data, setData] = useState({});
const nameRef = useRef();
const mobileRef = useRef();
const rdnameRef = useRef();
const rdmobileRef = useRef();
const [currentPage, setCurrentPage] = useState(1);
const [itemPerPage, setItemPerPage] = useState(10);
const [modalcurrentPage, setmodalCurrentPage] = useState(1);
const modalitemPerPage = 10;
const [totalData, setTotalData] = useState(0);
const [openModal, setOpenModal] = useState(false);
const [modalRetailDistributors, setmodalRetailDistributors] = useState(
[]
);
const [modalTotalData, setModalTotalData] = useState(0);
// Fetch territory manager data
useEffect(() => {
axios
.get(`/api/territorymanager/getOne/${id}`, {
headers: { Authorization: `Bearer ${token}` },
})
.then((response) => setData(response.data.data))
.catch((error) => console.error("Error fetching TM data:", error));
}, [id, token]);
// Fetch Retail Distributors data
const getTMsretaildistributorData = async () => {
setLoading(true);
axios
.get(`/api/kyc/getAllapprovedbytmid/${id}`, {
headers: {
Authorization: `Bearer ${token}`,
},
params: {
page: currentPage,
show: itemPerPage,
tradename: nameRef.current?.value,
},
})
.then((res) => {
// console.log(res.data);
setretaildistributorData(res.data?.retaildistributor);
setTotalData(res.data?.total_data);
})
.catch((err) => {
const msg = err?.response?.data?.message || "Something went wrong!";
swal({
title: "Error",
text: msg,
icon: "error",
button: "Retry",
dangerMode: true,
});
})
.finally(() => setLoading(false));
};
useEffect(() => {
getTMsretaildistributorData();
}, [success, itemPerPage, currentPage]);
// Debounced search for Retail Distributors
const debouncedSearch = useCallback(
debounce(() => {
setCurrentPage(1);
getTMsretaildistributorData();
}, 500),
[currentPage, itemPerPage]
);
const handleSearchChange = useCallback(() => {
debouncedSearch();
}, [debouncedSearch]);
// Fetch Retail Distributors data for modal
const getretaildistributorData = async () => {
setLoading(true);
try {
const res = await axios.get(`/api/v1/admin/users`, {
headers: {
Authorization: `Bearer ${token}`,
},
params: {
page: modalcurrentPage,
show: modalitemPerPage,
name: rdnameRef.current?.value,
mobileNumber: rdmobileRef.current?.value,
},
});
setmodalRetailDistributors(res.data?.users);
setModalTotalData(res.data?.totalUsers);
} catch (err) {
const msg = err?.response?.data?.message || "Something went wrong!";
swal({
title: "Error",
text: msg,
icon: "error",
button: "Retry",
dangerMode: true,
});
} finally {
setLoading(false);
}
};
useEffect(() => {
if (openModal) {
getretaildistributorData();
}
}, [openModal, modalcurrentPage]);
// Debounced search for Retail Distributors in modal
const debouncedmodalSearch = useCallback(
debounce(() => {
setmodalCurrentPage(1);
getretaildistributorData();
}, 500),
[modalcurrentPage]
);
const handlemodalSearchChange = useCallback(() => {
debouncedmodalSearch();
}, [debouncedmodalSearch]);
const handleOpenModal = () => {
setOpenModal(true);
};
const handleCloseModal = () => {
setOpenModal(false);
};
const handlePreviousPage = () => {
if (modalcurrentPage > 1) {
setmodalCurrentPage(modalcurrentPage - 1);
}
};
const handleNextPage = () => {
if (modalRetailDistributors.length === modalitemPerPage) {
setmodalCurrentPage(modalcurrentPage + 1);
}
};
const handleDelete = (id) => {
swal({
title: "Are you sure?",
icon: "warning",
buttons: {
Yes: { text: "Yes", value: true },
Cancel: { text: "Cancel", value: "cancel" },
},
}).then((value) => {
if (value === true) {
axios
.patch(`/api/v1/unmap/${id}`, {}, { // Changed to PATCH and sent an empty body
headers: {
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${token}`,
},
})
.then((res) => {
swal({
title: "Deleted",
text: "Retail Distributor Unmapped successfully!",
icon: "success",
button: "ok",
});
setSuccess((prev) => !prev);
})
.catch((err) => {
let msg = err?.response?.data?.message
? err?.response?.data?.message
: "Something went wrong!";
swal({
title: "Warning",
text: msg,
icon: "error",
button: "Retry",
dangerMode: true,
});
});
}
});
};
const handleAddRetailDistributor = async (rdid) => {
try {
await axios.put(
`/api/v1/mappedtm/${rdid}`,
{ mappedby: id },
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
swal({
title: "Success",
text: "Retail Distributor added successfully!",
icon: "success",
button: "Ok",
});
setSuccess((prev) => !prev);
handleCloseModal(); // Close modal after adding
} catch (err) {
const msg = err?.response?.data?.message || "Something went wrong!";
swal({
title: "Error",
text: msg,
icon: "error",
button: "Retry",
dangerMode: true,
});
}
};
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 mb-1 align-items-center justify-content-between">
{/* Left Side with Information in Separate Columns */}
<div className="d-flex flex-column">
<div style={{ fontSize: "18px" }} className="fw-bold">
Unique ID: {data?.uniqueId}
</div>
<div style={{ fontSize: "18px" }} className="fw-bold">
Name: {data?.name}
</div>
<div style={{ fontSize: "18px" }} className="fw-bold">
Mobile Number: {data?.mobileNumber}
</div>
<div style={{ fontSize: "18px" }} className="fw-bold">
Mail: {data?.email}
</div>
</div>
{/* Right Side with the Button */}
<div className="page-title-right">
{/* <Button
variant="contained"
color="primary"
style={{
fontWeight: "bold",
marginBottom: "1rem",
textTransform: "capitalize",
}}
onClick={handleOpenModal}
>
Add Retail Distributor
</Button> */}
<Button
variant="contained"
color="secondary"
style={{
fontWeight: "bold",
marginBottom: "1rem",
marginLeft: "1rem",
textTransform: "capitalize",
backgroundColor: "#d32f2f", // Red color for danger
color: "#fff",
"&:hover": {
backgroundColor: "#b71c1c", // Darker red on hover
},
}}
onClick={() => navigate("/territorymanagers")}
>
Back
</Button>
</div>
</div>
{/* <Dialog
open={openModal}
onClose={handleCloseModal}
maxWidth="md"
fullWidth
>
<DialogTitle>Search and Add Retail Distributor</DialogTitle>
<DialogContent>
<div style={{ display: "flex", gap: "16px", marginBottom:"2rem",marginTop:"-1rem" }}>
<TextField
label="Retail Distributor Trade Name"
placeholder="Retail Distributor Trade name"
inputRef={rdnameRef}
onChange={handlemodalSearchChange}
disabled={loading}
style={{ flex: 1, marginRight: "16px" }}
/>
<TextField
style={{ flex: 1 }}
label="Mobile Number"
placeholder="Mobile Number"
inputRef={rdmobileRef}
onChange={handlemodalSearchChange}
disabled={loading}
/>
</div>
<div className="table-responsive table-shoot mt-3">
<table className="table table-centered table-nowrap">
<thead>
<tr>
<th>Trade Name</th>
<th>Mobile</th>
<th>Approved PD</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{modalRetailDistributors.length > 0 ? (
modalRetailDistributors.map((RD) => (
<tr key={RD._id}>
<td>{RD.trade_name}</td>
<td>{RD.mobile_number}</td>
<td>{RD.principal_distributer?.name}</td>
<td>
<Button
variant="contained"
color="primary"
onClick={() =>
handleAddRetailDistributor(RD._id)
}
>
Add
</Button>
</td>
</tr>
))
) : (
<tr>
<td colSpan="6" className="text-center">
No Retail Distributor found!
</td>
</tr>
)}
</tbody>
</table>
</div>
<div className="d-flex justify-content-between">
<div>
Showing {modalRetailDistributors?.length} of{" "}
{modalTotalData} entries
</div>
<div>
<button
onClick={handlePreviousPage}
disabled={modalcurrentPage === 1 || loading}
className="btn btn-primary"
>
Previous
</button>
<button
onClick={handleNextPage}
disabled={
modalRetailDistributors?.length <
modalitemPerPage || loading
}
className="btn btn-primary ml-2"
>
Next
</button>
</div>
</div>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseModal} color="secondary">
Cancel
</Button>
</DialogActions>
</Dialog> */}
</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="Retail Distributor Trade name"
className="form-control"
ref={nameRef}
onChange={handleSearchChange}
disabled={loading}
/>
</div>
{/* <div className="col-lg-3">
<label>Mobile Number:</label>
<input
type="text"
placeholder="Mobile Number"
className="form-control"
ref={mobileRef}
onChange={handleSearchChange}
disabled={loading}
/>
</div> */}
</div>
<div className="table-responsive table-shoot mt-3">
<table className="table table-centered table-nowrap">
<thead
className="thead-light"
style={{ background: "#ecdddd" }}
>
<tr>
<th className="text-start">ID</th>
<th className="text-start">Trade Name</th>
<th className="text-start">Approved PD</th>
<th className="text-start">Mobile</th>
<th className="text-start">Created On</th>
{/* <th className="text-start">Action</th> */}
</tr>
</thead>
<tbody>
{loading ? (
<tr>
<td className="text-center" colSpan="6">
Loading...
</td>
</tr>
) : retaildistributorData?.length > 0 ? (
retaildistributorData?.map((RD, i) => {
return (
<tr key={i}>
<td className="text-start">{RD?._id}</td>
<td className="text-start">{RD?.trade_name}</td>
<td className="text-start">{RD?.principal_distributer?.name}</td>
<td className="text-start">
{RD?.mobile_number ? (
RD?.mobile_number
) : (
<small className="m-0 text-secondary">
No Phone Added!
</small>
)}
</td>
<td className="text-start"> {new Date(
RD.updatedAt
).toLocaleString("en-IN", {
weekday: "short",
month: "short",
day: "numeric",
year: "numeric",
hour: "numeric",
minute: "numeric",
hour12: true,
})}</td>
{/* <td className="text-start">
<button
type="button"
style={{ color: "white" }}
className="btn btn-danger btn-sm waves-effect waves-light btn-table ml-2"
onClick={() => handleDelete(RD._id)}
>
Delete
</button>
</td> */}
</tr>
);
})
) : (
<tr>
<td className="text-center" colSpan="6">
No Retail Distributor found!
</td>
</tr>
)}
</tbody>
</table>
</div>
<div className="d-flex justify-content-between">
<div>
Showing {retaildistributorData?.length} of {totalData}{" "}
entries
</div>
<div>
<button
onClick={() => setCurrentPage(currentPage - 1)}
disabled={currentPage === 1 || loading}
className="btn btn-primary"
>
Previous
</button>
<button
onClick={() => setCurrentPage(currentPage + 1)}
disabled={
retaildistributorData?.length < itemPerPage ||
loading
}
className="btn btn-primary ml-2"
>
Next
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default ViewRetailDistributorTM;