dev-jaanvi #1
|
@ -9,6 +9,7 @@ import TableHead from "@mui/material/TableHead";
|
||||||
import TableRow from "@mui/material/TableRow";
|
import TableRow from "@mui/material/TableRow";
|
||||||
import Paper, { paperClasses } from "@mui/material/Paper";
|
import Paper, { paperClasses } from "@mui/material/Paper";
|
||||||
import { adminList, deleteAdmin } from "../../redux/slices/adminSlice";
|
import { adminList, deleteAdmin } from "../../redux/slices/adminSlice";
|
||||||
|
import { deleteManager } from "../../redux/slices/managerSlice";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
|
@ -72,6 +73,7 @@ interface CustomTableProps {
|
||||||
handleStatusToggle: (id: string, currentStatus: number) => void;
|
handleStatusToggle: (id: string, currentStatus: number) => void;
|
||||||
tableType: string; // Adding tableType prop to change header text dynamically
|
tableType: string; // Adding tableType prop to change header text dynamically
|
||||||
handleClickOpen: () => void;
|
handleClickOpen: () => void;
|
||||||
|
handleDeleteButton: (id: string | number | undefined) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomTable: React.FC<CustomTableProps> = ({
|
const CustomTable: React.FC<CustomTableProps> = ({
|
||||||
|
@ -98,10 +100,26 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
||||||
const open = Boolean(anchorEl);
|
const open = Boolean(anchorEl);
|
||||||
|
|
||||||
const handleClick = (event: React.MouseEvent<HTMLElement>, row: Row) => {
|
const handleClick = (event: React.MouseEvent<HTMLElement>, row: Row) => {
|
||||||
|
|
||||||
setAnchorEl(event.currentTarget);
|
setAnchorEl(event.currentTarget);
|
||||||
setSelectedRow(row); // Ensure the row data is set
|
setSelectedRow(row);
|
||||||
setRowData(row);
|
setRowData(row);
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
const handleViewButton = (id: string | undefined) => {
|
||||||
|
if (!id) {
|
||||||
|
console.error("ID not found for viewing.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setViewModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
|
@ -115,37 +133,22 @@ const CustomTable: React.FC<CustomTableProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteButton = (id: string | undefined) => {
|
const handleDeleteButton = (id: string | undefined) => {
|
||||||
if (!id) console.error("ID not found", id);
|
if (!id) {
|
||||||
|
console.error("ID not found for viewing.");
|
||||||
dispatch(deleteAdmin(id || ""));
|
return;
|
||||||
setDeleteModal(false); // Close the modal only after deletion
|
|
||||||
handleClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleViewButton = (id: string | undefined) => {
|
|
||||||
if (!id) console.error("ID not found", id);
|
|
||||||
|
|
||||||
dispatch(adminList());
|
|
||||||
setViewModal(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleToggleStatus = () => {
|
|
||||||
if (selectedRow) {
|
|
||||||
// Toggle the opposite of current status
|
|
||||||
const newStatus = selectedRow.statusValue === 1 ? 0 : 1;
|
|
||||||
handleStatusToggle(selectedRow.id, newStatus);
|
|
||||||
}
|
}
|
||||||
handleClose();
|
setViewModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const filteredRows = rows.filter(
|
const filteredRows = rows.filter(
|
||||||
(row) =>
|
(row) =>
|
||||||
(row.name &&
|
(row.name &&
|
||||||
row.name.toLowerCase().includes(searchQuery.toLowerCase())) ||
|
row.name.toLowerCase().includes(searchQuery.toLowerCase())) ||
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
const indexOfLastRow = currentPage * usersPerPage;
|
const indexOfLastRow = currentPage * usersPerPage;
|
||||||
const indexOfFirstRow = indexOfLastRow - usersPerPage;
|
const indexOfFirstRow = indexOfLastRow - usersPerPage;
|
||||||
const currentRows = filteredRows.slice(indexOfFirstRow, indexOfLastRow);
|
const currentRows = filteredRows.slice(indexOfFirstRow, indexOfLastRow);
|
||||||
|
@ -249,8 +252,8 @@ const filteredRows = rows.filter(
|
||||||
? "Role"
|
? "Role"
|
||||||
: tableType === "user"
|
: tableType === "user"
|
||||||
? "User"
|
? "User"
|
||||||
: tableType === "manager"
|
: tableType === "managers"
|
||||||
? "Manager"
|
? "Managers"
|
||||||
: tableType === "vehicle"
|
: tableType === "vehicle"
|
||||||
? "Vehicle"
|
? "Vehicle"
|
||||||
: "Item"}
|
: "Item"}
|
||||||
|
@ -421,6 +424,7 @@ const filteredRows = rows.filter(
|
||||||
variant="text"
|
variant="text"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
// setSelectedRow(row);
|
||||||
setViewModal(true);
|
setViewModal(true);
|
||||||
}}
|
}}
|
||||||
color="primary"
|
color="primary"
|
||||||
|
@ -434,15 +438,15 @@ const filteredRows = rows.filter(
|
||||||
View
|
View
|
||||||
</Button>
|
</Button>
|
||||||
{viewModal && (
|
{viewModal && (
|
||||||
<ViewModal
|
<ViewModal
|
||||||
handleView={() =>
|
handleView={() => handleViewButton(selectedRow?.id)}
|
||||||
handleViewButton(selectedRow?.id)
|
open={viewModal}
|
||||||
}
|
setViewModal={setViewModal}
|
||||||
open={viewModal}
|
id={selectedRow?.id}
|
||||||
setViewModal={setViewModal}
|
tableType={tableType}
|
||||||
id={selectedRow?.id}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="text"
|
variant="text"
|
||||||
|
@ -481,6 +485,7 @@ const filteredRows = rows.filter(
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setDeleteModal(true);
|
setDeleteModal(true);
|
||||||
|
|
||||||
}}
|
}}
|
||||||
color="error"
|
color="error"
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -498,13 +503,16 @@ const filteredRows = rows.filter(
|
||||||
|
|
||||||
{/* Modals */}
|
{/* Modals */}
|
||||||
{deleteModal && (
|
{deleteModal && (
|
||||||
<DeleteModal
|
<DeleteModal
|
||||||
handleDelete={() => handleDeleteButton(selectedRow?.id)}
|
handleDelete={() => handleDeleteButton(selectedRow?.id)}
|
||||||
open={deleteModal}
|
open={deleteModal}
|
||||||
setDeleteModal={setDeleteModal}
|
setDeleteModal={setDeleteModal}
|
||||||
id={selectedRow?.id}
|
id={selectedRow?.id}
|
||||||
/>
|
tableType={tableType}
|
||||||
)}
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,10 +13,6 @@ import { RootState } from "../../redux/store/store";
|
||||||
import DashboardOutlinedIcon from "@mui/icons-material/DashboardOutlined";
|
import DashboardOutlinedIcon from "@mui/icons-material/DashboardOutlined";
|
||||||
import ManageAccountsOutlinedIcon from "@mui/icons-material/ManageAccountsOutlined";
|
import ManageAccountsOutlinedIcon from "@mui/icons-material/ManageAccountsOutlined";
|
||||||
|
|
||||||
//Eknoor singh and Jaanvi
|
|
||||||
//date:- 12-Feb-2025
|
|
||||||
//Made a different variable for super admin to access all the details.
|
|
||||||
|
|
||||||
type PropType = {
|
type PropType = {
|
||||||
hidden: boolean;
|
hidden: boolean;
|
||||||
};
|
};
|
||||||
|
@ -26,6 +22,7 @@ export default function MenuContent({ hidden }: PropType) {
|
||||||
const userRole = useSelector(
|
const userRole = useSelector(
|
||||||
(state: RootState) => state.profileReducer.user?.userType
|
(state: RootState) => state.profileReducer.user?.userType
|
||||||
);
|
);
|
||||||
|
|
||||||
const baseMenuItems = [
|
const baseMenuItems = [
|
||||||
{
|
{
|
||||||
text: "Dashboard",
|
text: "Dashboard",
|
||||||
|
@ -37,24 +34,25 @@ export default function MenuContent({ hidden }: PropType) {
|
||||||
icon: <AnalyticsRoundedIcon />,
|
icon: <AnalyticsRoundedIcon />,
|
||||||
url: "/panel/admin-list",
|
url: "/panel/admin-list",
|
||||||
},
|
},
|
||||||
userRole === "admin" && {
|
|
||||||
text: "Users",
|
|
||||||
icon: <AnalyticsRoundedIcon />,
|
|
||||||
url: "/panel/user-list",
|
|
||||||
},
|
|
||||||
userRole === "superadmin" && {
|
userRole === "superadmin" && {
|
||||||
text: "Roles",
|
text: "Roles",
|
||||||
icon: <AnalyticsRoundedIcon />,
|
icon: <AnalyticsRoundedIcon />,
|
||||||
url: "/panel/role-list",
|
url: "/panel/role-list",
|
||||||
},
|
},
|
||||||
userRole === "admin" && {
|
userRole === "admin" && {
|
||||||
text: "Vehicles",
|
text: "Users",
|
||||||
icon: <AnalyticsRoundedIcon />,
|
icon: <AnalyticsRoundedIcon />,
|
||||||
url: "/panel/vehicle-list",
|
url: "/panel/user-list",
|
||||||
|
},
|
||||||
|
userRole === "admin" && {
|
||||||
|
text: "Managers",
|
||||||
|
icon: <ManageAccountsOutlinedIcon />,
|
||||||
|
url: "/panel/manager-list", // Placeholder for now
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const filteredMenuItems = baseMenuItems.filter(Boolean);
|
const filteredMenuItems = baseMenuItems.filter(Boolean);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack sx={{ flexGrow: 1, p: 1, justifyContent: "space-between" }}>
|
<Stack sx={{ flexGrow: 1, p: 1, justifyContent: "space-between" }}>
|
||||||
<List dense>
|
<List dense>
|
||||||
|
@ -64,7 +62,6 @@ export default function MenuContent({ hidden }: PropType) {
|
||||||
disablePadding
|
disablePadding
|
||||||
sx={{ display: "block", py: 1 }}
|
sx={{ display: "block", py: 1 }}
|
||||||
>
|
>
|
||||||
{/* Wrap ListItemButton with Link to enable routing */}
|
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
component={Link}
|
component={Link}
|
||||||
to={item.url}
|
to={item.url}
|
||||||
|
|
|
@ -1,98 +1,108 @@
|
||||||
import { Box, Button, Modal, Typography } from "@mui/material";
|
import { Box, Modal, Typography, Divider, Button } from "@mui/material";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
import { RootState, AppDispatch } from "../../../redux/store/store";
|
||||||
|
import { deleteManager, fetchManagerList } from "../../../redux/slices/managerSlice";
|
||||||
|
|
||||||
|
type User = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
email?: string;
|
||||||
|
phone: string;
|
||||||
|
registeredAddress?: string;
|
||||||
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setDeleteModal: Function;
|
setDeleteModal: (value: boolean) => void;
|
||||||
handleDelete: (id: string | undefined) => void;
|
id?: number;
|
||||||
id?: string | undefined;
|
tableType?: "admin" | "manager";
|
||||||
};
|
};
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "50%",
|
top: "50%",
|
||||||
left: "50%",
|
left: "50%",
|
||||||
transform: "translate(-50%, -50%)",
|
transform: "translate(-50%, -50%)",
|
||||||
width: 330,
|
width: 400,
|
||||||
bgcolor: "background.paper",
|
bgcolor: "background.paper",
|
||||||
borderRadius: 1.5,
|
borderRadius: 2,
|
||||||
boxShadow: 24,
|
boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.15)",
|
||||||
p: 3,
|
p: 4,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
const btnStyle = { py: 1, px: 5, width: "50%", textTransform: "capitalize" };
|
export default function DeleteModal({ open, setDeleteModal, id, tableType }: Props) {
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
const adminList = useSelector((state: RootState) => state.adminReducer.admins) || [];
|
||||||
|
const managerList = useSelector((state: RootState) => state.managerReducer.managers) || [];
|
||||||
|
|
||||||
export default function DeleteModal({
|
const [selectedItem, setSelectedItem] = useState<User | null>(null);
|
||||||
open,
|
const [loading, setLoading] = useState(false);
|
||||||
setDeleteModal,
|
|
||||||
handleDelete,
|
|
||||||
id,
|
|
||||||
}: Props) {
|
|
||||||
// console.log("DeleteModal opened with ID:", id)
|
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<Modal
|
if (!id || !tableType) return;
|
||||||
open={open}
|
|
||||||
aria-labelledby="modal-modal-title"
|
const dataList: User[] = tableType === "admin" ? adminList : managerList;
|
||||||
aria-describedby="modal-modal-description"
|
const item = dataList.find((user) => user.id === id);
|
||||||
>
|
setSelectedItem(item || null);
|
||||||
<Box sx={style}>
|
}, [id, tableType, adminList, managerList]);
|
||||||
<Typography
|
|
||||||
id="modal-modal-title"
|
|
||||||
variant="h6"
|
|
||||||
component="h2"
|
|
||||||
align="center"
|
|
||||||
sx={{ flexGrow: 1 }} // This ensures the title takes up available space
|
const handleDelete = async () => {
|
||||||
>
|
if (!id || !tableType) {
|
||||||
Delete Record
|
return;
|
||||||
</Typography>
|
}
|
||||||
<Box
|
|
||||||
onClick={() => setDeleteModal(false)}
|
setLoading(true);
|
||||||
sx={{
|
try {
|
||||||
cursor: "pointer",
|
let deleteResult;
|
||||||
display: "flex",
|
if (tableType === "managers") {
|
||||||
alignItems: "center",
|
deleteResult = await dispatch(deleteManager(id)).unwrap();
|
||||||
justifyContent: "flex-end", // Aligns the close icon to the right
|
}
|
||||||
marginTop: -3.5
|
|
||||||
}}
|
if (deleteResult) {
|
||||||
>
|
await dispatch(fetchManagerList()); // Refresh list only if deletion is successful
|
||||||
<CloseIcon />
|
}
|
||||||
</Box>
|
} catch (error) {
|
||||||
<Typography
|
console.error("❌ Error while deleting:", error);
|
||||||
id="modal-modal-description"
|
} finally {
|
||||||
sx={{ mt: 2 }}
|
setLoading(false);
|
||||||
align="center"
|
setDeleteModal(false);
|
||||||
>
|
}
|
||||||
Are you sure you want to delete this record?
|
};
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
return (
|
||||||
display: "flex",
|
<Modal open={open} onClose={() => setDeleteModal(false)} aria-labelledby="modal-title">
|
||||||
justifyContent: "space-between",
|
<Box sx={style}>
|
||||||
mt: 4,
|
<Typography variant="h5" fontWeight="bold" sx={{ width: "100%", textAlign: "center" }}>
|
||||||
gap: 2,
|
Delete {tableType ? tableType.charAt(0).toUpperCase() + tableType.slice(1) : "User"}
|
||||||
}}
|
</Typography>
|
||||||
>
|
|
||||||
<Button
|
<Divider sx={{ width: "100%" }} />
|
||||||
variant="contained"
|
|
||||||
color="error"
|
{selectedItem ? (
|
||||||
type="button"
|
<Typography align="center">
|
||||||
sx={btnStyle}
|
Are you sure you want to delete <b>{selectedItem.name}</b>?
|
||||||
onClick={() => setDeleteModal(false)}
|
</Typography>
|
||||||
>
|
) : (
|
||||||
Cancel
|
<Typography align="center">No {tableType} found with this ID</Typography>
|
||||||
</Button>
|
)}
|
||||||
<Button
|
|
||||||
variant="contained"
|
<Box sx={{ display: "flex", gap: 2 }}>
|
||||||
type="button"
|
<Button variant="contained" color="error" onClick={handleDelete} disabled={loading}>
|
||||||
color="primary"
|
{loading ? "Deleting..." : "Delete"}
|
||||||
sx={btnStyle}
|
</Button>
|
||||||
onClick={() => handleDelete(id || "")}
|
<Button variant="outlined" onClick={() => setDeleteModal(false)}>Cancel</Button>
|
||||||
>
|
</Box>
|
||||||
Delete
|
</Box>
|
||||||
</Button>
|
</Modal>
|
||||||
</Box>
|
);
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
import { Box, Button, Modal, Typography } from "@mui/material";
|
|
||||||
import { AppDispatch, RootState } from "../../../redux/store/store";
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
open: boolean;
|
|
||||||
setViewModal: Function;
|
|
||||||
handleView: (id: string | undefined) => void;
|
|
||||||
id?: string | undefined;
|
|
||||||
};
|
|
||||||
;
|
|
||||||
|
|
||||||
const style = {
|
|
||||||
position: "absolute",
|
|
||||||
top: "50%",
|
|
||||||
left: "50%",
|
|
||||||
transform: "translate(-50%, -50%)",
|
|
||||||
width: 330,
|
|
||||||
bgcolor: "background.paper",
|
|
||||||
borderRadius: 1.5,
|
|
||||||
boxShadow: 24,
|
|
||||||
p: 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
const btnStyle = { py: 1, px: 5, width: "50%", textTransform: "capitalize", alignItems: "center" };
|
|
||||||
|
|
||||||
export default function ViewModal({
|
|
||||||
open,
|
|
||||||
setViewModal,
|
|
||||||
id, // Selected user's ID
|
|
||||||
}: Props) {
|
|
||||||
const { admins } = useSelector((state: RootState) => state.adminReducer);
|
|
||||||
const [selectedAdmin, setSelectedAdmin] = useState<any>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (id) {
|
|
||||||
const admin = admins.find((admin) => admin.id === id);
|
|
||||||
setSelectedAdmin(admin || null);
|
|
||||||
}
|
|
||||||
}, [id, admins]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
open={open}
|
|
||||||
aria-labelledby="modal-modal-title"
|
|
||||||
aria-describedby="modal-modal-description"
|
|
||||||
>
|
|
||||||
<Box sx={style}>
|
|
||||||
<Typography
|
|
||||||
id="modal-modal-title"
|
|
||||||
variant="h6"
|
|
||||||
component="h2"
|
|
||||||
align="center"
|
|
||||||
>
|
|
||||||
Details of {selectedAdmin?.name}
|
|
||||||
</Typography>
|
|
||||||
{selectedAdmin ? (
|
|
||||||
<>
|
|
||||||
<Typography align="center">Name: {selectedAdmin?.name}</Typography>
|
|
||||||
<Typography align="center">Email: {selectedAdmin?.email}</Typography>
|
|
||||||
<Typography align="center">Phone: {selectedAdmin?.phone}</Typography>
|
|
||||||
<Typography align="center">Address: {selectedAdmin?.registeredAddress}</Typography>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Typography align="center">No admin found with this ID</Typography>
|
|
||||||
)}
|
|
||||||
<Box sx={{ display: "flex", justifyContent: "space-between", mt: 4, gap: 2 }}>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
color="error"
|
|
||||||
type="button"
|
|
||||||
sx={btnStyle}
|
|
||||||
onClick={() => setViewModal(false)}
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,126 +1,95 @@
|
||||||
import { Box, Button, Modal, Typography, Divider } from "@mui/material";
|
import { Box, Modal, Typography, Divider } from "@mui/material";
|
||||||
import { RootState } from "../../../redux/store/store";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../../redux/store/store";
|
||||||
|
|
||||||
|
type User = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
email?: string;
|
||||||
|
phone: string;
|
||||||
|
registeredAddress?: string;
|
||||||
|
action?: any;
|
||||||
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setViewModal: Function;
|
setViewModal: (value: boolean) => void;
|
||||||
id?: string;
|
id?: number;
|
||||||
|
tableType?: "admin" | "manager";
|
||||||
};
|
};
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "50%",
|
top: "50%",
|
||||||
left: "50%",
|
left: "50%",
|
||||||
transform: "translate(-50%, -50%)",
|
transform: "translate(-50%, -50%)",
|
||||||
width: 400,
|
width: 400,
|
||||||
bgcolor: "background.paper",
|
bgcolor: "background.paper",
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.15)",
|
boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.15)",
|
||||||
p: 4,
|
p: 4,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: 2,
|
gap: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
const btnStyle = {
|
export default function ViewModal({ open, setViewModal, id, tableType }: Props) {
|
||||||
mt: 2,
|
const adminList = useSelector((state: RootState) => state.adminReducer.admins) || [];
|
||||||
px: 5,
|
const managerList = useSelector((state: RootState) => state.managerReducer.managers) || [];
|
||||||
py: 1.2,
|
|
||||||
width: "100%",
|
|
||||||
textTransform: "capitalize",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ViewModal({ open, setViewModal, id }: Props) {
|
const [selectedItem, setSelectedItem] = useState<User | null>(null);
|
||||||
const { admins } = useSelector((state: RootState) => state.adminReducer);
|
|
||||||
const [selectedAdmin, setSelectedAdmin] = useState<any>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
if (!id || !tableType) return;
|
||||||
const admin = admins.find((admin) => admin.id === id);
|
|
||||||
setSelectedAdmin(admin);
|
|
||||||
}
|
|
||||||
}, [id, admins]);
|
|
||||||
|
|
||||||
return (
|
const dataList: User[] = tableType === "admin" ? adminList : managerList;
|
||||||
<Modal
|
const item = dataList.find((user) => user.id === id);
|
||||||
open={open}
|
|
||||||
aria-labelledby="modal-title"
|
|
||||||
aria-describedby="modal-description"
|
|
||||||
>
|
|
||||||
<Box sx={style}>
|
|
||||||
<Typography
|
|
||||||
id="modal-title"
|
|
||||||
variant="h5"
|
|
||||||
fontWeight="bold"
|
|
||||||
sx={{ width: "100%" }}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ flex: 1, textAlign: "center" }}>
|
|
||||||
{selectedAdmin?.name}'s Details
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
onClick={() => setViewModal(false)}
|
|
||||||
sx={{
|
|
||||||
cursor: "pointer",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CloseIcon />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Divider sx={{ width: "100%" }} />
|
if (item) {
|
||||||
|
setSelectedItem(item); // ✅ Updating selected item properly
|
||||||
|
}
|
||||||
|
}, [id, tableType, adminList, managerList]);
|
||||||
|
|
||||||
{selectedAdmin ? (
|
const formattedType = tableType ? tableType.charAt(0).toUpperCase() + tableType.slice(1) : "User";
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: "80%",
|
|
||||||
textAlign: "left",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: 1.5,
|
|
||||||
whiteSpace: "pre-wrap", // the text wraps properly
|
|
||||||
wordBreak: "break-word",
|
|
||||||
overflowWrap: "break-word",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="body1">
|
|
||||||
<b>Name:</b> {selectedAdmin.name}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body1">
|
|
||||||
<strong>Email:</strong> {selectedAdmin.email}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body1">
|
|
||||||
<strong>Phone:</strong> {selectedAdmin.phone}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body1">
|
|
||||||
<strong>Address:</strong>{" "}
|
|
||||||
{selectedAdmin.registeredAddress ?? "N/A"}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
<Typography align="center">
|
|
||||||
No admin found with this ID
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* <Button variant="contained" color="error" sx={btnStyle} onClick={() => setViewModal(false)}>
|
return (
|
||||||
Close
|
<Modal open={open} onClose={() => setViewModal(false)} aria-labelledby="modal-title">
|
||||||
</Button> */}
|
<Box sx={style}>
|
||||||
</Box>
|
<Typography variant="h5" fontWeight="bold" sx={{ width: "100%" }}>
|
||||||
</Modal>
|
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||||
);
|
<Box sx={{ flex: 1, textAlign: "center" }}>
|
||||||
|
{selectedItem?.name ? `${selectedItem.name}'s Details` : `${formattedType} Details`}
|
||||||
|
</Box>
|
||||||
|
<Box onClick={() => setViewModal(false)} sx={{ cursor: "pointer", display: "flex" }}>
|
||||||
|
<CloseIcon />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Divider sx={{ width: "100%" }} />
|
||||||
|
|
||||||
|
{selectedItem ? (
|
||||||
|
<Box sx={{ width: "80%", textAlign: "left", display: "flex", flexDirection: "column", gap: 1.5 }}>
|
||||||
|
<Typography variant="body1">
|
||||||
|
<b>Name:</b> {selectedItem.name || "N/A"}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1">
|
||||||
|
<b>Email:</b> {selectedItem.email || "N/A"}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1">
|
||||||
|
<b>Phone:</b> {selectedItem.phone || "N/A"}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1">
|
||||||
|
<b>Address:</b> {selectedItem.registeredAddress || "N/A"}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Typography align="center">No {formattedType} found with this ID</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
150
src/pages/AddManagerModal/index.tsx
Normal file
150
src/pages/AddManagerModal/index.tsx
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { Box, Button, Typography, Modal, IconButton, TextField } from "@mui/material";
|
||||||
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { addManager } from "../../redux/slices/managerSlice";
|
||||||
|
|
||||||
|
interface AddManagerModalProps {
|
||||||
|
open: boolean;
|
||||||
|
handleClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddManagerModal: React.FC<AddManagerModalProps> = ({ open, handleClose }) => {
|
||||||
|
// State for input fields
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [stationLocation, setStationLocation] = useState("");
|
||||||
|
const [phone, setPhone] = useState("");
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
|
||||||
|
const roleId = 5; // Fixed role ID
|
||||||
|
const roleName = "Peon"; // Required role name
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
// Function to validate form inputs
|
||||||
|
const validateInputs = () => {
|
||||||
|
if (!name || !stationLocation || !phone || !email || !password) {
|
||||||
|
toast.error("All fields are required.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const phoneRegex = /^[0-9]{6,14}$/;
|
||||||
|
if (!phoneRegex.test(phone)) {
|
||||||
|
toast.error("Phone number must be between 6 to 14 digits.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
if (!emailRegex.test(email)) {
|
||||||
|
toast.error("Enter a valid email address.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length < 6) {
|
||||||
|
toast.error("Password must be at least 6 characters long.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle form submission
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!validateInputs()) return;
|
||||||
|
|
||||||
|
const managerData = {
|
||||||
|
name,
|
||||||
|
registeredAddress: stationLocation,
|
||||||
|
phone,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
roleId,
|
||||||
|
roleName, // ✅ Ensure roleName is correctly included
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await dispatch(addManager(managerData)).unwrap();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ✅ Ensure response contains expected data
|
||||||
|
if (!response || !response.id) {
|
||||||
|
throw new Error("Invalid response from server. ID is missing.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Show success message from API if available, fallback if not
|
||||||
|
toast.success(response.message || "Manager added successfully!");
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
handleClose();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("API Error:", error); // ✅ Log error for debugging
|
||||||
|
|
||||||
|
// ✅ Handle both API errors and unexpected errors
|
||||||
|
toast.error(
|
||||||
|
error?.response?.data?.message || error.message || "Failed to add manager"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Function to reset form fields
|
||||||
|
const resetForm = () => {
|
||||||
|
setName("");
|
||||||
|
setStationLocation("");
|
||||||
|
setPhone("");
|
||||||
|
setEmail("");
|
||||||
|
setPassword("");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={handleClose} aria-labelledby="add-manager-modal">
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
width: 400,
|
||||||
|
bgcolor: "background.paper",
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 3,
|
||||||
|
borderRadius: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Modal Header */}
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||||
|
<Typography variant="h6" fontWeight={600}>
|
||||||
|
Add Manager
|
||||||
|
</Typography>
|
||||||
|
<IconButton onClick={handleClose}>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
|
||||||
|
|
||||||
|
{/* Input Fields */}
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
|
||||||
|
<TextField label="Manager Name" fullWidth size="small" value={name} onChange={(e) => setName(e.target.value)} />
|
||||||
|
<TextField label="Station Location" fullWidth size="small" value={stationLocation} onChange={(e) => setStationLocation(e.target.value)} />
|
||||||
|
<TextField label="Phone Number" fullWidth size="small" value={phone} onChange={(e) => setPhone(e.target.value)} />
|
||||||
|
<TextField label="Email" fullWidth size="small" value={email} onChange={(e) => setEmail(e.target.value)} />
|
||||||
|
<TextField label="Password" type="password" fullWidth size="small" value={password} onChange={(e) => setPassword(e.target.value)} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 3 }}>
|
||||||
|
<Button variant="contained" onClick={handleSubmit}>
|
||||||
|
Add Manager
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddManagerModal;
|
191
src/pages/EditUserModal/index.tsx
Normal file
191
src/pages/EditUserModal/index.tsx
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
TextField,
|
||||||
|
Modal,
|
||||||
|
IconButton,
|
||||||
|
} from "@mui/material";
|
||||||
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
import { useForm, Controller } from "react-hook-form";
|
||||||
|
|
||||||
|
interface EditModalProps {
|
||||||
|
open: boolean;
|
||||||
|
handleClose: () => void;
|
||||||
|
handleCreate: (data: FormData) => void;
|
||||||
|
handleUpdate: (id: string, data: FormData) => void;
|
||||||
|
editRow: any | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FormData {
|
||||||
|
managerName: string;
|
||||||
|
stationName: string;
|
||||||
|
stationLocation: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditModal: React.FC<EditModalProps> = ({
|
||||||
|
open,
|
||||||
|
handleClose,
|
||||||
|
handleCreate,
|
||||||
|
handleUpdate,
|
||||||
|
editRow,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
setValue,
|
||||||
|
reset,
|
||||||
|
} = useForm<FormData>({
|
||||||
|
defaultValues: {
|
||||||
|
managerName: "",
|
||||||
|
stationName: "",
|
||||||
|
stationLocation: "",
|
||||||
|
phoneNumber: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Populate form fields when `editRow` changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (editRow) {
|
||||||
|
setValue("managerName", editRow.name || "");
|
||||||
|
setValue("stationName", editRow.stationName || "");
|
||||||
|
setValue("stationLocation", editRow.registeredAddress || "");
|
||||||
|
setValue("phoneNumber", editRow.phone || "");
|
||||||
|
} else {
|
||||||
|
reset({ // ✅ Ensure default values are reset when adding a new manager
|
||||||
|
managerName: "",
|
||||||
|
stationName: "",
|
||||||
|
stationLocation: "",
|
||||||
|
phoneNumber: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [editRow, setValue, reset]);
|
||||||
|
|
||||||
|
|
||||||
|
const onSubmit = (data: FormData) => {
|
||||||
|
if (editRow) {
|
||||||
|
handleUpdate({
|
||||||
|
id: editRow.id,
|
||||||
|
managerName: data.managerName,
|
||||||
|
stationLocation: data.stationLocation,
|
||||||
|
phoneNumber: data.phoneNumber
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
handleCreate(data);
|
||||||
|
}
|
||||||
|
handleClose();
|
||||||
|
reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={handleClose} aria-labelledby="edit-manager-modal">
|
||||||
|
<Box
|
||||||
|
component="form"
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
width: 400,
|
||||||
|
bgcolor: "background.paper",
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 3,
|
||||||
|
borderRadius: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||||
|
<Typography variant="h6" fontWeight={600}>
|
||||||
|
{editRow ? "Edit Manager" : "Add Manager"}
|
||||||
|
</Typography>
|
||||||
|
<IconButton onClick={handleClose}>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Horizontal Line */}
|
||||||
|
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
|
||||||
|
|
||||||
|
{/* Input Fields */}
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
|
||||||
|
<Box sx={{ display: "flex", gap: 2 }}>
|
||||||
|
{/* Manager Name */}
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||||
|
<Typography variant="body2" fontWeight={500} mb={0.5}>Manager Name</Typography>
|
||||||
|
<Controller
|
||||||
|
name="managerName"
|
||||||
|
control={control}
|
||||||
|
rules={{ required: "Manager Name is required" }}
|
||||||
|
render={({ field }) => (
|
||||||
|
<TextField {...field} fullWidth placeholder="Enter Manager Name" size="small" error={!!errors.managerName} helperText={errors.managerName?.message} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Station Name */}
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||||
|
<Typography variant="body2" fontWeight={500} mb={0.5}>Station Name</Typography>
|
||||||
|
<Controller
|
||||||
|
name="stationName"
|
||||||
|
control={control}
|
||||||
|
rules={{ required: "Station Name is required" }}
|
||||||
|
render={({ field }) => (
|
||||||
|
<TextField {...field} fullWidth placeholder="Enter Station Name" size="small" error={!!errors.stationName} helperText={errors.stationName?.message} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: "flex", gap: 2 }}>
|
||||||
|
{/* Station Location */}
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||||
|
<Typography variant="body2" fontWeight={500} mb={0.5}>Station Location</Typography>
|
||||||
|
<Controller
|
||||||
|
name="stationLocation"
|
||||||
|
control={control}
|
||||||
|
rules={{ required: "Station Location is required" }}
|
||||||
|
render={({ field }) => (
|
||||||
|
<TextField {...field} fullWidth placeholder="Enter Station Location" size="small" error={!!errors.stationLocation} helperText={errors.stationLocation?.message} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Phone Number */}
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||||
|
<Typography variant="body2" fontWeight={500} mb={0.5}>Phone Number</Typography>
|
||||||
|
<Controller
|
||||||
|
name="phoneNumber"
|
||||||
|
control={control}
|
||||||
|
rules={{
|
||||||
|
required: "Phone number is required",
|
||||||
|
pattern: {
|
||||||
|
value: /^[0-9]+$/,
|
||||||
|
message: "Only numbers are allowed",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
render={({ field }) => (
|
||||||
|
<TextField {...field} fullWidth placeholder="Enter Phone Number" size="small" error={!!errors.phoneNumber} helperText={errors.phoneNumber?.message} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 3 }}>
|
||||||
|
<Button variant="contained" type="submit">
|
||||||
|
{editRow ? "Update" : "Create"}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditModal;
|
240
src/pages/ManagerList/index.tsx
Normal file
240
src/pages/ManagerList/index.tsx
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Box, Button, Typography, TextField, InputAdornment, IconButton } from "@mui/material";
|
||||||
|
import SearchIcon from "@mui/icons-material/Search";
|
||||||
|
import EqualizerIcon from "@mui/icons-material/Tune";
|
||||||
|
import CustomTable, { Column } from "../../components/CustomTable";
|
||||||
|
import AddManagerModal from "../../pages/AddManagerModal";
|
||||||
|
import EditUserModal from "../EditUserModal";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { fetchManagerList, addManager,updateManager,deleteManager } from "../../redux/slices/managerSlice";
|
||||||
|
import { RootState, AppDispatch } from "../../redux/store/store";
|
||||||
|
|
||||||
|
|
||||||
|
export default function ManagerList() {
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
const { managers, isLoading } = useSelector((state: RootState) => state.managerReducer);
|
||||||
|
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const [addButtonModal, setAddButtonModal] = useState(false);
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
const [rowData, setRowData] = useState(null);
|
||||||
|
const [viewModal, setViewModal] = useState(false);
|
||||||
|
const [deleteModal, setDeleteModal] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(fetchManagerList()); // Fetch data when component mounts
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Function to handle adding a manager
|
||||||
|
const handleAddManager = async (newManager: {
|
||||||
|
name: string;
|
||||||
|
registeredAddress: string;
|
||||||
|
phone: string;
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
}) => {
|
||||||
|
await dispatch(addManager(newManager));
|
||||||
|
dispatch(fetchManagerList()); // Refresh list after adding
|
||||||
|
handleCloseModal(); // Close modal after adding
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Function to handle updating a
|
||||||
|
const handleUpdateManager = async (updatedManager) => {
|
||||||
|
if (!updatedManager || typeof updatedManager !== "object") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rowData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const managerId = rowData.id;
|
||||||
|
|
||||||
|
if (!updatedManager.managerName || !updatedManager.stationLocation || !updatedManager.phoneNumber) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await dispatch(updateManager({
|
||||||
|
id: managerId,
|
||||||
|
name: updatedManager.managerName,
|
||||||
|
registeredAddress: updatedManager.stationLocation,
|
||||||
|
phone: updatedManager.phoneNumber,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (response?.payload?.statusCode === 200) {
|
||||||
|
await dispatch(fetchManagerList()); // ✅ Refresh list after update
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCloseModal();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Update failed:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Function to handle deleting a manager
|
||||||
|
|
||||||
|
|
||||||
|
const handleDeleteManager = async () => {
|
||||||
|
if (!rowData?.id) {
|
||||||
|
console.error("❌ No manager ID found for deletion!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const response = await dispatch(deleteManager(rowData.id));
|
||||||
|
|
||||||
|
if (response?.payload) {
|
||||||
|
|
||||||
|
dispatch(fetchManagerList()); // Refresh list after deletion
|
||||||
|
} else {
|
||||||
|
console.error("❌ Deletion failed!", response);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Error deleting manager:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
setDeleteModal(false); // Close delete modal
|
||||||
|
|
||||||
|
handleCloseModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Function to handle search input change
|
||||||
|
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setSearch(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open the Add Manager Modal
|
||||||
|
const handleClickOpen = () => {
|
||||||
|
setRowData(null);
|
||||||
|
setAddButtonModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Close all modals
|
||||||
|
const handleCloseModal = () => {
|
||||||
|
setAddButtonModal(false);
|
||||||
|
setModalOpen(false);
|
||||||
|
setRowData(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Table columns definition
|
||||||
|
const managerColumns: Column[] = [
|
||||||
|
{ id: "name", label: "Manager Name" },
|
||||||
|
{ id: "registeredAddress", label: "Station Location" },
|
||||||
|
{ id: "phone", label: "Phone Number" },
|
||||||
|
{ id: "action", label: "Action", align: "center" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Filtered manager list based on search input
|
||||||
|
const filteredManagers = managers.filter((manager) =>
|
||||||
|
Object.values(manager).some((value) =>
|
||||||
|
typeof value === "string" && value.toLowerCase().includes(search.toLowerCase())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Header Section */}
|
||||||
|
<Box sx={{ width: "100%", display: "flex", flexDirection: "column", gap: 2, mt: 2 }}>
|
||||||
|
<Typography component="h2" variant="h6" sx={{ fontWeight: 600 }}>
|
||||||
|
Managers
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||||
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
placeholder="Search"
|
||||||
|
value={search}
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<SearchIcon />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||||
|
<Button variant="contained" size="medium" onClick={handleClickOpen}>
|
||||||
|
Add Manager
|
||||||
|
</Button>
|
||||||
|
<IconButton>
|
||||||
|
<EqualizerIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Table Section */}
|
||||||
|
{isLoading ? (
|
||||||
|
<Typography>Loading managers...</Typography>
|
||||||
|
) : (
|
||||||
|
<CustomTable
|
||||||
|
columns={managerColumns}
|
||||||
|
rows={filteredManagers.map((manager) => ({
|
||||||
|
id: manager.id,
|
||||||
|
name: manager.name,
|
||||||
|
registeredAddress: manager.registeredAddress,
|
||||||
|
phone: manager.phone,
|
||||||
|
action: (
|
||||||
|
<Box sx={{ display: "flex", gap: 1 }}>
|
||||||
|
<Button onClick={() => { setRowData(manager); setModalOpen(true); }}>Edit</Button>
|
||||||
|
<Button onClick={() => { setRowData(manager); setViewModal(true); }}>View</Button>
|
||||||
|
<Button color="error" onClick={() => { setRowData(manager); setDeleteModal(true); }}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
|
||||||
|
}))}
|
||||||
|
tableType="managers"
|
||||||
|
setRowData={setRowData}
|
||||||
|
setModalOpen={setModalOpen}
|
||||||
|
setViewModal={setViewModal}
|
||||||
|
viewModal={viewModal}
|
||||||
|
setDeleteModal={setDeleteModal}
|
||||||
|
deleteModal={deleteModal}
|
||||||
|
handleDeleteButton={handleDeleteManager}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Modals */}
|
||||||
|
<EditUserModal
|
||||||
|
open={modalOpen}
|
||||||
|
handleClose={handleCloseModal}
|
||||||
|
editRow={rowData}
|
||||||
|
handleUpdate={handleUpdateManager} // Pass function
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<AddManagerModal
|
||||||
|
open={addButtonModal}
|
||||||
|
handleClose={handleCloseModal}
|
||||||
|
handleAddManager={(data) =>
|
||||||
|
handleAddManager({
|
||||||
|
name: data.managerName,
|
||||||
|
registeredAddress: data.stationLocation,
|
||||||
|
phone: data.phoneNumber,
|
||||||
|
email: data.email,
|
||||||
|
password: data.password,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import profileReducer from "./slices/profileSlice";
|
||||||
import userReducer from "./slices/userSlice.ts";
|
import userReducer from "./slices/userSlice.ts";
|
||||||
import roleReducer from "./slices/roleSlice.ts";
|
import roleReducer from "./slices/roleSlice.ts";
|
||||||
import vehicleReducer from "./slices/VehicleSlice.ts";
|
import vehicleReducer from "./slices/VehicleSlice.ts";
|
||||||
|
import managerReducer from "../redux/slices/managerSlice.ts";
|
||||||
|
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
|
@ -14,7 +15,9 @@ const rootReducer = combineReducers({
|
||||||
profileReducer,
|
profileReducer,
|
||||||
userReducer,
|
userReducer,
|
||||||
roleReducer,
|
roleReducer,
|
||||||
vehicleReducer
|
vehicleReducer,
|
||||||
|
managerReducer,
|
||||||
|
// Add other reducers here...
|
||||||
});
|
});
|
||||||
|
|
||||||
export type RootState = ReturnType<typeof rootReducer>;
|
export type RootState = ReturnType<typeof rootReducer>;
|
||||||
|
|
189
src/redux/slices/managerSlice.ts
Normal file
189
src/redux/slices/managerSlice.ts
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
import http from "../../lib/https";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
// Define TypeScript types
|
||||||
|
interface Manager {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
phone?: string;
|
||||||
|
role: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ManagerState {
|
||||||
|
managers: Manager[];
|
||||||
|
loading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial state
|
||||||
|
const initialState: ManagerState = {
|
||||||
|
managers: [],
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch Manager List
|
||||||
|
export const fetchManagerList = createAsyncThunk<Manager[], void, { rejectValue: string }>(
|
||||||
|
"fetchManagers",
|
||||||
|
async (_, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await http.get("manager-list");
|
||||||
|
|
||||||
|
if (!response.data?.data) throw new Error("Invalid API response");
|
||||||
|
|
||||||
|
return response.data.data;
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.error("Error Fetching Managers: " + error);
|
||||||
|
return rejectWithValue(
|
||||||
|
error?.response?.data?.message || "An error occurred"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create Manager
|
||||||
|
export const addManager = createAsyncThunk(
|
||||||
|
"addManager",
|
||||||
|
async (data, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const response = await http.post("create-manager", data);
|
||||||
|
toast.success("Manager created successfully");
|
||||||
|
|
||||||
|
// ✅ Ensure the response contains the expected data
|
||||||
|
if (!response.data || !response.data.data || !response.data.data.id) {
|
||||||
|
console.error("❌ ERROR: Missing manager ID in response", response.data);
|
||||||
|
throw new Error("Invalid API response: Missing manager ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data.data;
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("❌ API Error:", error?.response?.data || error);
|
||||||
|
return rejectWithValue(error?.response?.data?.message || "An error occurred");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateManager = createAsyncThunk<
|
||||||
|
Manager,
|
||||||
|
{ id: string; name?: string; email?: string; phone?: string; role?: string; registeredAddress?: string },
|
||||||
|
{ rejectValue: string }
|
||||||
|
>(
|
||||||
|
"updateManager",
|
||||||
|
async ({ id, ...managerData }, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
|
||||||
|
const response = await http.put(`${id}/update-manager`, managerData);
|
||||||
|
|
||||||
|
|
||||||
|
toast.success("Manager updated successfully!");
|
||||||
|
return response.data.data; // ✅ Extracting correct response data
|
||||||
|
} catch (error: any) {
|
||||||
|
|
||||||
|
toast.error("Error updating manager: " + error);
|
||||||
|
return rejectWithValue(
|
||||||
|
error.response?.data?.message || "An error occurred"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Delete Manager
|
||||||
|
export const deleteManager = createAsyncThunk<
|
||||||
|
string,
|
||||||
|
string,
|
||||||
|
{ rejectValue: string }
|
||||||
|
>(
|
||||||
|
"deleteManager",
|
||||||
|
async (id, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
await http.delete(`/${id}/delete-manager`);
|
||||||
|
toast.success("Manager deleted successfully!");
|
||||||
|
return id; // Return the ID of the deleted manager
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.error("Error deleting manager: " + error);
|
||||||
|
return rejectWithValue(
|
||||||
|
error.response?.data?.message || "An error occurred"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Create Slice
|
||||||
|
const managerSlice = createSlice({
|
||||||
|
name: "fetchManagers",
|
||||||
|
initialState,
|
||||||
|
reducers: {},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder
|
||||||
|
// Fetch Managers
|
||||||
|
.addCase(fetchManagerList.pending, (state) => {
|
||||||
|
state.loading = true;
|
||||||
|
state.error = null;
|
||||||
|
})
|
||||||
|
.addCase(
|
||||||
|
fetchManagerList.fulfilled,
|
||||||
|
(state, action: PayloadAction<Manager[]>) => {
|
||||||
|
state.loading = false;
|
||||||
|
state.managers = action.payload;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.addCase(fetchManagerList.rejected, (state, action) => {
|
||||||
|
state.loading = false;
|
||||||
|
state.error = action.payload || "Failed to fetch managers";
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add Manager
|
||||||
|
.addCase(addManager.pending, (state) => {
|
||||||
|
state.loading = true;
|
||||||
|
})
|
||||||
|
.addCase(addManager.fulfilled, (state, action: PayloadAction<Manager>) => {
|
||||||
|
state.loading = false;
|
||||||
|
state.managers.push(action.payload);
|
||||||
|
})
|
||||||
|
.addCase(addManager.rejected, (state, action) => {
|
||||||
|
state.loading = false;
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update Manager
|
||||||
|
.addCase(updateManager.pending, (state) => {
|
||||||
|
state.loading = true;
|
||||||
|
})
|
||||||
|
.addCase(updateManager.fulfilled, (state, action) => {
|
||||||
|
state.loading = false;
|
||||||
|
const updatedManager = action.payload;
|
||||||
|
const index = state.managers.findIndex(m => m.id === updatedManager.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
state.managers[index] = { ...state.managers[index], ...updatedManager }; // 🔥 Merge updated fields
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.addCase(updateManager.rejected, (state) => {
|
||||||
|
state.loading = false;
|
||||||
|
})
|
||||||
|
|
||||||
|
// Delete Manager
|
||||||
|
// Delete Manager
|
||||||
|
.addCase(deleteManager.pending, (state) => {
|
||||||
|
state.loading = true;
|
||||||
|
})
|
||||||
|
.addCase(deleteManager.fulfilled, (state, action) => {
|
||||||
|
state.loading = false;
|
||||||
|
state.managers = state.managers.filter(manager => manager.id !== action.payload);
|
||||||
|
})
|
||||||
|
.addCase(deleteManager.rejected, (state) => {
|
||||||
|
state.loading = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default managerSlice.reducer;
|
|
@ -16,6 +16,8 @@ const ProfilePage = lazy(() => import("./pages/ProfilePage"));
|
||||||
const NotFoundPage = lazy(() => import("./pages/NotFound"));
|
const NotFoundPage = lazy(() => import("./pages/NotFound"));
|
||||||
const UserList = lazy(() => import("./pages/UserList"));
|
const UserList = lazy(() => import("./pages/UserList"));
|
||||||
const PermissionsTable = lazy(() => import("./pages/PermissionTable"));
|
const PermissionsTable = lazy(() => import("./pages/PermissionTable"));
|
||||||
|
const ManagerList = lazy(() => import("./pages/ManagerList"));
|
||||||
|
|
||||||
|
|
||||||
interface ProtectedRouteProps {
|
interface ProtectedRouteProps {
|
||||||
caps: string[];
|
caps: string[];
|
||||||
|
@ -87,6 +89,18 @@ export default function AppRouter() {
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="manager-list"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute
|
||||||
|
caps={[]}
|
||||||
|
component={<ManagerList />}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="role-list"
|
path="role-list"
|
||||||
element={
|
element={
|
||||||
|
|
Loading…
Reference in a new issue