PD mapped with TM and SC mapped with TM and product manual

This commit is contained in:
Sibunnayak 2024-08-30 16:35:50 +05:30
parent cb609c3372
commit 0a4c957274
8 changed files with 1922 additions and 2 deletions

View File

@ -68,6 +68,13 @@ const _nav = [
}, },
], ],
}, },
{
component: CNavItem,
name: "ProductManual",
icon: <CIcon icon={cilClipboard} customClassName="nav-icon" />,
to: "/product-manual",
group: "Product Management",
},
{ {
component: CNavItem, component: CNavItem,
name: "Principal Distributor", name: "Principal Distributor",

View File

@ -69,7 +69,11 @@ const ProtectedRoute = ({ element: Element }) => {
} }
} }
checkToken() checkToken();
const intervalId = setInterval(checkToken, 1 * 60 * 1000);
// Clear interval on component unmount
return () => clearInterval(intervalId);
}, [navigate]) }, [navigate])
return <Element /> return <Element />

View File

@ -33,7 +33,8 @@ import Products from "./views/Products/Products";
import AddProduct from "./views/Products/AddProduct"; import AddProduct from "./views/Products/AddProduct";
import EditProduct from "./views/Products/EditProduct"; import EditProduct from "./views/Products/EditProduct";
import ViewProduct from "./views/Products/ViewProduct"; import ViewProduct from "./views/Products/ViewProduct";
//product manual
import ProductManual from "./views/ProductManual/ProductManual";
//Order Management //Order Management
import NewOrders from "./views/orders/NewOrders.js"; import NewOrders from "./views/orders/NewOrders.js";
import ProcessingOrders from "./views/orders/ProcessingOrders.js"; import ProcessingOrders from "./views/orders/ProcessingOrders.js";
@ -142,6 +143,9 @@ 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 Inventory from "./views/Inventory/Inventory";
import SingleInventory from "./views/Inventory/SingleInventory"; import SingleInventory from "./views/Inventory/SingleInventory";
import ViewProductManual from "./views/ProductManual/SingleProductManual";
import ViewSalesCoOrdinatorTM from "./views/TerritoryManager/ViewSalesCoOrdinatorTM";
import ViewPrincipalDistributorTM from "./views/TerritoryManager/ViewPrincipalDistributorTM";
const routes = [ const routes = [
//dashboard //dashboard
@ -202,6 +206,18 @@ const routes = [
element: Brands, element: Brands,
navName: "Product Management", navName: "Product Management",
}, },
{
path: "/product-manual",
name: "Product Manual",
element: ProductManual,
navName: "Product Management",
},
{
path: "/product-manual/view/:id",
name: "Product Manual",
element: ViewProductManual,
navName: "Product Management",
},
//SalesCoOrdinator //SalesCoOrdinator
{ {
path: "/salescoordinator/edit/:id", path: "/salescoordinator/edit/:id",
@ -240,6 +256,18 @@ const routes = [
element: AddTerritoryManager, element: AddTerritoryManager,
navName: "TerritoryManagers", navName: "TerritoryManagers",
}, },
{
path: "/view/salescoordinator/:id",
name: "View SalesCoOrdinator",
element: ViewSalesCoOrdinatorTM,
navName: "TerritoryManagers",
},
{
path: "/view/principaldistributor/:id",
name: "View Principal Distributor",
element: ViewPrincipalDistributorTM,
navName: "TerritoryManagers",
},
// Attendence // Attendence
{ {
path: "/attendance/today", path: "/attendance/today",

View File

@ -0,0 +1,652 @@
import React, { useState, useEffect, useCallback, useRef } from "react";
import axios from "axios";
import { Link } from "react-router-dom";
import { isAutheticated } from "src/auth";
import {
Button,
Box,
IconButton,
Modal,
TextField,
Typography,
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { ClipLoader } from "react-spinners";
import swal from "sweetalert";
import { useNavigate } from "react-router-dom";
import debounce from "lodash/debounce";
const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 500,
bgcolor: "background.paper",
borderRadius: "0.5rem",
boxShadow: 24,
};
const ProductManual = () => {
const token = isAutheticated();
const navigate = useNavigate();
const [loading, setLoading] = useState(true);
const [updating, setUpdating] = useState(true);
const [saveLoding, setSaveLoading] = useState(true);
const [edit, setEdit] = useState(false);
const [title, setTitle] = useState("");
const [pdfFile, setPdfFile] = useState(null);
const [itemPerPage, setItemPerPage] = useState(10);
const [currentPage, setCurrentPage] = useState(1);
const [open, setOpen] = useState(false);
const [currentManual, setCurrentManual] = useState(null);
const [productManuals, setProductManuals] = useState([]);
const [fileError, setFileError] = useState("");
const [totalData, setTotalData] = useState(0);
const [success, setSuccess] = useState(true);
const titleRef = useRef();
// const handleOpen = (manual = null) => {
// setOpen(true);
// setCurrentManual(manual);
// if (manual) {
// setTitle(manual.title);
// setPdfFile(null);
// setEdit(true);
// } else {
// setTitle("");
// setPdfFile(null);
// setEdit(false);
// }
// };
const handleOpen = () => setOpen(true);
const handleClose = () => {
setOpen(false);
setEdit(false);
setTitle("");
setPdfFile(null);
setFileError("");
};
const handleEditClick = (manual) => {
setOpen(true);
setCurrentManual(manual);
setTitle(manual.title);
setPdfFile(null);
setEdit(true);
};
const getProductManuals = async () => {
try {
setLoading(true);
const response = await axios.get(`/api/productmanual`, {
headers: { Authorization: `Bearer ${token}` },
params: {
page: currentPage,
show: itemPerPage,
title: titleRef.current?.value || "",
},
});
if (response.status === 200) {
const { productManuals, total } = response.data;
setProductManuals(productManuals);
setTotalData(total);
setLoading(false);
}
} catch (error) {
console.error("Failed to fetch product manuals:", error);
setLoading(false);
}
};
useEffect(() => {
getProductManuals();
}, [itemPerPage, currentPage, success]);
const validateFile = (file) => {
return file && file.type === "application/pdf";
};
const handleSave = async () => {
if (!title || !pdfFile || !validateFile(pdfFile)) {
setFileError("Please upload a valid PDF file!");
return;
}
setFileError("");
setSaveLoading(false);
const formData = new FormData();
formData.append("title", title);
formData.append("pdfFile", pdfFile);
try {
await axios.post("/api/productmanual/create", formData, {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "multipart/form-data",
},
});
handleClose();
swal("Success", "New product manual added successfully!", "success");
getProductManuals();
} catch (error) {
swal("Error", "Failed to add product manual", "error");
} finally {
setSaveLoading(true);
}
};
const handleUpdate = async () => {
if (!title) {
setFileError("Please upload a valid PDF file!");
return;
}
setFileError("");
setUpdating(false);
const formData = new FormData();
formData.append("title", title);
formData.append("pdfFile", pdfFile);
try {
await axios.put(
`/api/productmanual/update/${currentManual._id}`,
formData,
{
headers: { Authorization: `Bearer ${token}` },
}
);
handleClose();
setSuccess((prev) => !prev);
swal(
"Success",
"The product manual was updated successfully!",
"success"
);
getProductManuals();
} catch (err) {
swal("Error", "Failed to update product manual", "error");
} finally {
setUpdating(true);
}
};
const handleDelete = async (_id) => {
swal({
title: "Are you sure?",
icon: "warning",
buttons: {
Yes: { text: "Yes", value: true },
Cancel: { text: "Cancel", value: "cancel" },
},
}).then(async (value) => {
if (value === true) {
try {
await axios.delete(`/api/productmanual/delete/${_id}`, {
headers: { Authorization: `Bearer ${token}` },
});
setSuccess((prev) => !prev);
swal(
"Success",
"The product manual was deleted successfully!",
"success"
);
getProductManuals();
} catch (err) {
swal("Error", "Failed to delete product manual", "error");
}
}
});
};
const debouncedSearch = useCallback(
debounce(() => {
setCurrentPage(1);
getProductManuals();
}, 500),
[]
);
const handleSearchChange = (e) => {
debouncedSearch();
};
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">
Product Manual
</div>
<div className="page-title-right">
<Button
variant="contained"
color="primary"
className="font-bold mb-2 capitalize mr-2"
onClick={() => handleOpen()}
>
Add New Product Manual
</Button>
</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>Title:</label>
<input
type="text"
placeholder="product name"
className="form-control"
ref={titleRef}
onChange={handleSearchChange}
disabled={loading}
/>
</div>
</div>
{/* Modal for Add/Edit Product Manual */}
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<Box
p={2}
display="flex"
alignItems="center"
justifyContent="space-between"
>
<Typography
id="modal-modal-title"
variant="h6"
component="h2"
flex={1}
fontWeight="bold"
textAlign="center"
>
{edit
? "Edit Product Manual"
: "Add New Product Manual"}
</Typography>
<IconButton onClick={handleClose} color="error">
<CloseIcon />
</IconButton>
</Box>
<Box p={2}>
<TextField
required
id="title"
label="Title"
variant="outlined"
margin="normal"
fullWidth
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<input
accept="application/pdf"
style={{ display: "none" }}
id="pdfFile"
type="file"
onChange={(e) => setPdfFile(e.target.files[0])}
/>
<label htmlFor="pdfFile">
<Button
variant="contained"
component="span"
className="font-bold mb-2 capitalize"
color="primary"
>
Upload PDF
</Button>
{pdfFile && (
<span style={{ marginLeft: "10px" }}>
{pdfFile.name}
</span>
)}
{fileError && (
<Typography color="error" variant="body2">
{fileError}
</Typography>
)}
</label>
</Box>
<Box
p={2}
display={"flex"}
justifyContent={"right"}
// width={"500px"}
>
{!edit && (
<button
style={{
color: "white",
marginRight: "1rem",
}}
onClick={() => handleSave()}
type="button"
className="
btn btn-primary btn-sm
waves-effect waves-light
btn-table
mx-1
mt-1
"
>
<ClipLoader loading={!saveLoding} size={18} />
{saveLoding && "Save"}
</button>
)}
{edit && (
<button
style={{
color: "white",
marginRight: "1rem",
}}
onClick={() => handleUpdate()}
type="button"
className="
btn btn-primary btn-sm
waves-effect waves-light
btn-table
mx-1
mt-1
"
>
<ClipLoader loading={!updating} size={18} />
{updating && "update"}
</button>
)}
<button
style={{
color: "black",
marginRight: "1rem",
background: "grey",
}}
onClick={() => setOpen(false)}
type="button"
className="
btn btn-sm
waves-effect waves-light
btn-table
mx-1
mt-1
"
>
Close
</button>
</Box>
</Box>
</Modal>
<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">Title</th>
<th className="text-start">File Name</th>
<th className="text-start">Updated Date</th>
<th className="text-start">Actions</th>
</tr>
</thead>
<tbody>
{loading ? (
<tr>
<td className="text-center" colSpan="6">
Loading...
</td>
</tr>
) : productManuals?.length > 0 ? (
productManuals?.map((manual, i) => {
return (
<tr key={i}>
<td className="text-start">{manual.title}</td>
<td className="text-start">
{manual.product_manual.filename}
</td>
<td className="text-start">
{new Date(manual.updatedAt).toLocaleString(
"en-IN",
{
weekday: "short",
month: "short",
day: "numeric",
year: "numeric",
hour: "numeric",
minute: "numeric",
hour12: true,
}
)}
</td>
<td className="text-start">
<Link
to={`/product-manual/view/${manual._id}`}
>
<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>
<button
style={{
color: "white",
marginRight: "1rem",
}}
type="button"
className="
btn btn-info btn-sm
waves-effect waves-light
btn-table
mt-1
mx-1
"
onClick={() => handleEditClick(manual)}
>
Edit
</button>
<Link
to={"#"}
style={{
marginRight: "1rem",
}}
>
<button
style={{ color: "white" }}
type="button"
className="
btn btn-danger btn-sm
waves-effect waves-light
btn-table
mt-1
mx-1
"
onClick={() => {
handleDelete(manual._id);
}}
>
Delete
</button>
</Link>
</td>
</tr>
);
})
) : (
!loading &&
productManuals?.length === 0 && (
<tr className="text-center">
<td colSpan="6">
<h5>No Product manual 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 {currentPage * itemPerPage - itemPerPage + 1} to{" "}
{Math.min(currentPage * itemPerPage, totalData)} of{" "}
{totalData} entries
</div>
</div>
<div className="col-sm-12 col-md-6">
<div className="d-flex">
<ul className="pagination ms-auto">
<li
className={
currentPage === 1
? "paginate_button page-item previous disabled"
: "paginate_button page-item previous"
}
>
<span
className="page-link"
style={{ cursor: "pointer" }}
onClick={() => setCurrentPage((prev) => prev - 1)}
disabled={loading}
>
Previous
</span>
</li>
{!(currentPage - 1 < 1) && (
<li className="paginate_button page-item">
<span
className="page-link"
style={{ cursor: "pointer" }}
onClick={(e) =>
setCurrentPage((prev) => prev - 1)
}
disabled={loading}
>
{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 >
totalData - 1
) && (
<li className="paginate_button page-item ">
<span
className="page-link"
style={{ cursor: "pointer" }}
onClick={() => {
setCurrentPage((prev) => prev + 1);
}}
disabled={loading}
>
{currentPage + 1}
</span>
</li>
)}
<li
className={
!(
(currentPage + 1) * itemPerPage - itemPerPage >
totalData - 1
)
? "paginate_button page-item next"
: "paginate_button page-item next disabled"
}
>
<span
className="page-link"
style={{ cursor: "pointer" }}
onClick={() => setCurrentPage((prev) => prev + 1)}
disabled={loading}
>
Next
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default ProductManual;

View File

@ -0,0 +1,107 @@
import React, { useEffect, useState } from "react";
import Button from "@material-ui/core/Button";
import { Link, useParams } from "react-router-dom";
import swal from "sweetalert";
import axios from "axios";
import { isAutheticated } from "src/auth";
const ViewProductManual = () => {
const [title, setTitle] = useState("");
const [image, setImage] = useState("");
const token = isAutheticated();
const { id } = useParams();
const getproductmanual = async () => {
try {
const res = await axios.get(`/api/productmanual/${id}`, {
headers: { Authorization: `Bearer ${token}` },
});
// console.log(res);
setTitle(res?.data?.productManual?.title);
setImage(res?.data?.productManual?.product_manual);
} catch (err) {
console.error(err);
swal({
title: "Error",
text: "Unable to fetch the blog",
icon: "error",
button: "Retry",
dangerMode: true,
});
}
};
useEffect(() => {
getproductmanual();
}, []);
return (
<div className="container">
<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">
View Product Manual
</div>
<div style={{ display: "flex", gap: "1rem" }}>
<h4 className="mb-0"></h4>
</div>
<div className="page-title-right">
<Link to="/product-manual">
<Button
variant="contained"
color="secondary"
style={{
fontWeight: "bold",
marginBottom: "1rem",
textTransform: "capitalize",
}}
>
Back
</Button>
</Link>
</div>
</div>
</div>
</div>
<div className="row">
<div className="col-lg-12 col-md-12 col-sm-12 my-1">
<div className="card h-100">
<div className="card-body px-5">
<h4
className="card-title"
style={{
fontWeight: "bold",
fontSize: "3rem",
marginBottom: "1rem",
textTransform: "capitalize",
}}
>
{title}
</h4>
<div className="mb-3">
{image && (
image.url.endsWith('.pdf') ? (
<iframe
src={image.url}
title="Product Manual"
style={{ width: "100%", height: "80vh", border: "none" }}
/>
) : (
<img
src={image.url}
alt="blog"
style={{ width: "100%", height: "50vh" }}
/>
)
)}
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default ViewProductManual;

View File

@ -274,6 +274,34 @@ const TerritoryManager = () => {
})} })}
</td> </td>
<td className="text-start"> <td className="text-start">
<Link
to={`/view/salescoordinator/${territorymanager._id}`}
>
<button
style={{
color: "white",
marginRight: "1rem",
}}
type="button"
className="btn btn-primary btn-sm waves-effect waves-light btn-table ml-2"
>
SC
</button>
</Link>
<Link
to={`/view/principaldistributor/${territorymanager._id}`}
>
<button
style={{
color: "white",
marginRight: "1rem",
}}
type="button"
className="btn btn-primary btn-sm waves-effect waves-light btn-table ml-2"
>
PD
</button>
</Link>
<Link <Link
to={`/territorymanager/edit/${territorymanager._id}`} to={`/territorymanager/edit/${territorymanager._id}`}
> >

View File

@ -0,0 +1,548 @@
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 ViewPrincipalDistributorTM = () => {
const token = isAutheticated();
const { id } = useParams();
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(true);
const [principaldistributorData, setprincipaldistributorData] = useState([]);
const [data, setData] = useState({});
const nameRef = useRef();
const mobileRef = useRef();
const pdnameRef = useRef();
const pdmobileRef = 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 [modalPrincipalDistributors, setmodalPrincipalDistributors] = 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 Principal Distributors data
const getTMsprincipaldistributorData = async () => {
setLoading(true);
axios
.get(`/api/v1/getbyTmId/${id}`, {
headers: {
Authorization: `Bearer ${token}`,
},
params: {
page: currentPage,
show: itemPerPage,
name: nameRef.current?.value,
mobileNumber: mobileRef.current?.value,
},
})
.then((res) => {
setprincipaldistributorData(res.data?.principaldistributor);
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(() => {
getTMsprincipaldistributorData();
}, [success, itemPerPage, currentPage]);
// Debounced search for Principal Distributors
const debouncedSearch = useCallback(
debounce(() => {
setCurrentPage(1);
getTMsprincipaldistributorData();
}, 500),
[currentPage, itemPerPage]
);
const handleSearchChange = useCallback(() => {
debouncedSearch();
}, [debouncedSearch]);
// Fetch Principal Distributors data for modal
const getprincipaldistributorData = async () => {
setLoading(true);
try {
const res = await axios.get(`/api/v1/admin/users`, {
headers: {
Authorization: `Bearer ${token}`,
},
params: {
page: modalcurrentPage,
show: modalitemPerPage,
name: pdnameRef.current?.value,
mobileNumber: pdmobileRef.current?.value,
},
});
setmodalPrincipalDistributors(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) {
getprincipaldistributorData();
}
}, [openModal, modalcurrentPage]);
// Debounced search for Principal Distributors in modal
const debouncedmodalSearch = useCallback(
debounce(() => {
setmodalCurrentPage(1);
getprincipaldistributorData();
}, 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 (modalPrincipalDistributors.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: "Principal 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 handleAddPrincipalDistributor = async (pdid) => {
try {
await axios.put(
`/api/v1/mappedtm/${pdid}`,
{ mappedby: id },
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
swal({
title: "Success",
text: "Principal 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 Principal 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 Principal Distributor</DialogTitle>
<DialogContent>
<div style={{ display: "flex", gap: "16px", marginBottom:"2rem",marginTop:"-1rem" }}>
<TextField
label="Principal Distributor Name"
placeholder="Principal Distributor name"
inputRef={pdnameRef}
onChange={handlemodalSearchChange}
disabled={loading}
style={{ flex: 1, marginRight: "16px" }}
/>
<TextField
style={{ flex: 1 }}
label="Mobile Number"
placeholder="Mobile Number"
inputRef={pdmobileRef}
onChange={handlemodalSearchChange}
disabled={loading}
/>
</div>
<div className="table-responsive table-shoot mt-3">
<table className="table table-centered table-nowrap">
<thead>
<tr>
<th>Id</th>
<th>SBU</th>
<th>Name</th>
<th>Mobile</th>
<th>Email</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{modalPrincipalDistributors.length > 0 ? (
modalPrincipalDistributors.map((PD) => (
<tr key={PD._id}>
<td>{PD.uniqueId}</td>
<td>{PD.SBU}</td>
<td>{PD.name}</td>
<td>{PD.phone}</td>
<td>{PD.email}</td>
<td>
<Button
variant="contained"
color="primary"
onClick={() =>
handleAddPrincipalDistributor(PD._id)
}
>
Add
</Button>
</td>
</tr>
))
) : (
<tr>
<td colSpan="6" className="text-center">
No Principal Distributor found!
</td>
</tr>
)}
</tbody>
</table>
</div>
<div className="d-flex justify-content-between">
<div>
Showing {modalPrincipalDistributors?.length} of{" "}
{modalTotalData} entries
</div>
<div>
<button
onClick={handlePreviousPage}
disabled={modalcurrentPage === 1 || loading}
className="btn btn-primary"
>
Previous
</button>
<button
onClick={handleNextPage}
disabled={
modalPrincipalDistributors?.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>Principal Distributor Name:</label>
<input
type="text"
placeholder="Principal Distributor 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>Unique Id </th>
<th className="text-start">SBU</th>
<th className="text-start">Name</th>
<th className="text-start">Mobile No.</th>
<th className="text-start">Email</th>
<th className="text-start">Action</th>
</tr>
</thead>
<tbody>
{loading ? (
<tr>
<td className="text-center" colSpan="6">
Loading...
</td>
</tr>
) : principaldistributorData?.length > 0 ? (
principaldistributorData?.map((PD, i) => {
return (
<tr key={i}>
<td className="text-start">{PD?.uniqueId}</td>
<td className="text-start">{PD?.SBU}</td>
<td className="text-start">{PD?.name}</td>
<td className="text-start">{PD?.phone}</td>
<td className="text-start">
{PD?.email ? (
PD?.email
) : (
<small className="m-0 text-secondary">
No Email Added!
</small>
)}
</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(PD._id)}
>
Delete
</button>
</td>
</tr>
);
})
) : (
<tr>
<td className="text-center" colSpan="6">
No Principal Distributor found!
</td>
</tr>
)}
</tbody>
</table>
</div>
<div className="d-flex justify-content-between">
<div>
Showing {principaldistributorData?.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={
principaldistributorData?.length < itemPerPage ||
loading
}
className="btn btn-primary ml-2"
>
Next
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default ViewPrincipalDistributorTM;

View File

@ -0,0 +1,546 @@
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 ViewSalesCoOrdinatorTM = () => {
const token = isAutheticated();
const { id } = useParams();
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(true);
const [salescoordinatorsData, setSalesCoOrdinatorsData] = useState([]);
const [data, setData] = useState({});
const nameRef = useRef();
const mobileRef = useRef();
const scnameRef = useRef();
const scmobileRef = 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 [modalSalesCoordinators, setModalSalesCoordinators] = 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 Sales Coordinators data
const getTMsSalesCoOrdinatorsData = async () => {
setLoading(true);
axios
.get(`/api/salescoordinator/getbyTmId/${id}`, {
headers: {
Authorization: `Bearer ${token}`,
},
params: {
page: currentPage,
show: itemPerPage,
name: nameRef.current?.value,
mobileNumber: mobileRef.current?.value,
},
})
.then((res) => {
setSalesCoOrdinatorsData(res.data?.salesCoOrinators);
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(() => {
getTMsSalesCoOrdinatorsData();
}, [success, itemPerPage, currentPage]);
// Debounced search for Sales Coordinators
const debouncedSearch = useCallback(
debounce(() => {
setCurrentPage(1);
getTMsSalesCoOrdinatorsData();
}, 500),
[currentPage, itemPerPage]
);
const handleSearchChange = useCallback(() => {
debouncedSearch();
}, [debouncedSearch]);
// Fetch Sales Coordinators data for modal
const getSalesCoOrdinatorsData = async () => {
setLoading(true);
try {
const res = await axios.get(`/api/salescoordinator/getAll`, {
headers: {
Authorization: `Bearer ${token}`,
},
params: {
page: modalcurrentPage,
show: modalitemPerPage,
name: scnameRef.current?.value,
mobileNumber: scmobileRef.current?.value,
},
});
setModalSalesCoordinators(res.data?.salesCoOrinators);
setModalTotalData(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(() => {
if (openModal) {
getSalesCoOrdinatorsData();
}
}, [openModal, modalcurrentPage]);
// Debounced search for Sales Coordinators in modal
const debouncedmodalSearch = useCallback(
debounce(() => {
setmodalCurrentPage(1);
getSalesCoOrdinatorsData();
}, 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 (modalPrincipalDistributors.length === modalitemPerPage) {
setmodalCurrentPage(modalcurrentPage + 1);
}
};
const handleDelete = (id) => {
swal({
title: "Are you sure?",
icon: "error",
buttons: {
Yes: { text: "Yes", value: true },
Cancel: { text: "Cancel", value: "cancel" },
},
}).then((value) => {
if (value === true) {
axios
.delete(`/api/salescoordinator/unmap/${id}`, {
headers: {
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${token}`,
},
})
.then((res) => {
swal({
title: "Deleted",
text: "SalesCoOrdinator 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 handleAddSalesCoordinator = async (scid) => {
try {
await axios.put(
`/api/salescoordinator/mappedtm/${scid}`,
{ mappedby: id },
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
swal({
title: "Success",
text: "Sales Coordinator 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 Sales Coordinator
</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} fullWidth>
<DialogTitle>Search and Add Sales Coordinator</DialogTitle>
<DialogContent>
<TextField
label="Sales Coordinator Name"
placeholder="Sales Coordinator name"
inputRef={scnameRef}
onChange={handlemodalSearchChange}
disabled={loading}
/>
<TextField
className="ml-4"
label="Mobile Number"
placeholder="Mobile Number"
inputRef={scmobileRef}
onChange={handlemodalSearchChange}
disabled={loading}
/>
<div className="table-responsive table-shoot mt-3">
<table className="table table-centered table-nowrap">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Mobile</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{modalSalesCoordinators.map((coordinator) => (
<tr key={coordinator._id}>
<td>{coordinator.uniqueId}</td>
<td>{coordinator.name}</td>
<td>{coordinator.mobileNumber}</td>
<td>
<Button
variant="contained"
color="primary"
onClick={() =>
handleAddSalesCoordinator(coordinator._id)
}
>
Add
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="d-flex justify-content-between">
<div>
Showing {modalSalesCoordinators?.length} of{" "}
{modalTotalData} entries
</div>
<div>
<button
onClick={handlePreviousPage}
disabled={modalcurrentPage === 1 || loading}
className="btn btn-primary"
>
Previous
</button>
<button
onClick={handleNextPage}
disabled={
modalSalesCoordinators?.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>Sales Coordinator Name:</label>
<input
type="text"
placeholder="Sales Coordinator 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>Unique Id </th>
<th className="text-start">Name</th>
<th className="text-start">Mobile No.</th>
<th className="text-start">Email</th>
<th className="text-start">Action</th>
</tr>
</thead>
<tbody>
{loading ? (
<tr>
<td className="text-center" colSpan="6">
Loading...
</td>
</tr>
) : salescoordinatorsData?.length > 0 ? (
salescoordinatorsData?.map((salescoordinator, i) => {
return (
<tr key={i}>
<td className="text-start">
{salescoordinator?.uniqueId}
</td>
<td className="text-start">
{salescoordinator?.name}
</td>
<td className="text-start">
{salescoordinator?.mobileNumber}
</td>
<td className="text-start">
{salescoordinator?.email ? (
salescoordinator?.email
) : (
<small className="m-0 text-secondary">
No Email Added!
</small>
)}
</td>
<td className="text-start">
{/* <Link
to={`/salescoordinator/edit/${salescoordinator._id}`}
>
<button
style={{
color: "white",
marginRight: "1rem",
}}
type="button"
className="btn btn-info btn-sm waves-effect waves-light btn-table ml-2"
>
Edit
</button>
</Link> */}
<button
type="button"
style={{ color: "white" }}
className="btn btn-danger btn-sm waves-effect waves-light btn-table ml-2"
onClick={() =>
handleDelete(salescoordinator._id)
}
>
Delete
</button>
</td>
</tr>
);
})
) : (
<tr>
<td className="text-center" colSpan="6">
No Sales Coordinator found!
</td>
</tr>
)}
</tbody>
</table>
</div>
<div className="d-flex justify-content-between">
<div>
Showing {salescoordinatorsData?.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={
salescoordinatorsData?.length < itemPerPage || loading
}
className="btn btn-primary ml-2"
>
Next
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default ViewSalesCoOrdinatorTM;