Manager List API Integeration

This commit is contained in:
jaanvi 2025-03-11 18:09:26 +05:30
commit 90f44b5183
19 changed files with 1726 additions and 336 deletions

1
package-lock.json generated
View file

@ -15041,6 +15041,7 @@
},
"node_modules/typescript": {
"version": "5.7.3",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",

View file

@ -1,3 +1,12 @@
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden; /* Prevents scrolling */
}
.App {
text-align: center;
}

View file

@ -1,6 +1,7 @@
import { BrowserRouter as Router } from "react-router-dom";
import AppRouter from "./router";
function App() {
return (
<Router>

View file

@ -0,0 +1,321 @@
import { Controller, useForm } from "react-hook-form";
import { Box, Button, Typography, Modal, IconButton, InputAdornment } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { Visibility, VisibilityOff } from "@mui/icons-material";
import { useDispatch } from "react-redux";
import { addManager } from "../../redux/slices/managerSlice";
import {
CustomIconButton,
CustomTextField,
} from "../AddUserModel/styled.css.tsx";
import React from "react";
type Props = {
open: boolean;
handleClose: () => void;
};
export default function AddManagerModal({ open, handleClose }: Props) {
const dispatch = useDispatch();
const {
control,
register,
handleSubmit,
formState: { errors },
reset,
} = useForm();
const [showPassword, setShowPassword] = React.useState(false);
const onSubmit = async (data: any) => {
const managerData = {
name: data.name,
email: data.email,
phone: data.phone,
registeredAddress: data.registeredAddress,
roleId: data.role,
password: data.password,
roleName: "Manager", // Add a role name (you can replace this with a dynamic value if needed)
};
try {
// Dispatch the addManager action
await dispatch(addManager(managerData)).unwrap();
handleClose(); // Close modal after successful addition
reset(); // Reset form fields
} catch (error) {
console.error("Error adding manager:", error);
}
};
// Toggle password visibility
const togglePasswordVisibility = () => {
setShowPassword((prev) => !prev);
};
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,
}}
>
{/* Header */}
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Typography variant="h6" fontWeight={600}>
Add Manager
</Typography>
<CustomIconButton onClick={handleClose}>
<CloseIcon />
</CustomIconButton>
</Box>
{/* Horizontal Line */}
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
{/* Form */}
<form onSubmit={handleSubmit(onSubmit)}>
{/* First Row - Manager Name */}
<Box sx={{ display: "flex", gap: 2 }}>
{/* Manager Name */}
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
}}
>
<Typography variant="body2" fontWeight={500}>
Manager Name
</Typography>
<CustomTextField
fullWidth
placeholder="Enter Manager Name"
size="small"
error={!!errors.name}
helperText={
errors.name ? errors.name.message : ""
}
{...register("name", {
required: "Manager Name is required",
minLength: {
value: 2,
message:
"Manager Name must be at least 2 characters long",
},
})}
/>
</Box>
</Box>
{/* Second Row - Email, Password */}
<Box sx={{ display: "flex", gap: 2 }}>
{/* Email */}
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "50%",
}}
>
<Typography variant="body2" fontWeight={500}>
Email
</Typography>
<CustomTextField
fullWidth
placeholder="Enter Manager Email"
size="small"
error={!!errors.email}
helperText={
errors.email ? errors.email.message : ""
}
{...register("email", {
required: "Email is required",
pattern: {
value: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
message: "Invalid email address",
},
})}
/>
</Box>
{/* Password */}
<Box sx={{ display: "flex", gap: 2 }}>
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
}}
>
<Typography variant="body2" fontWeight={500}>
Password
</Typography>
<Controller
name="password"
control={control}
rules={{
required: "Password is required",
minLength: {
value: 6,
message:
"Password must be at least 6 characters long.",
},
pattern: {
value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{6,}$/,
message:
"Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character.",
},
}}
render={({ field }) => (
<CustomTextField
{...field}
required
placeholder="Enter Password"
type={
showPassword
? "text"
: "password"
}
id="password"
fullWidth
color={
errors.password
? "error"
: "primary"
}
size="small"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<CustomIconButton
aria-label="toggle password visibility"
onClick={
togglePasswordVisibility
}
edge="end"
>
{showPassword ? (
<VisibilityOff />
) : (
<Visibility />
)}
</CustomIconButton>
</InputAdornment>
),
}}
error={!!errors.password}
helperText={
errors.password?.message
}
/>
)}
/>
</Box>
</Box>
</Box>
{/* Third Row - Phone Number, Registered Address */}
<Box sx={{ display: "flex", gap: 2 }}>
{/* Phone Number */}
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "50%",
}}
>
<Typography variant="body2" fontWeight={500}>
Phone Number
</Typography>
<CustomTextField
fullWidth
placeholder="Enter Phone Number"
size="small"
error={!!errors.phone}
helperText={
errors.phone ? errors.phone.message : ""
}
{...register("phone", {
required: "Phone Number is required",
pattern: {
value: /^[0-9]{10}$/,
message:
"Phone number must be 10 digits",
},
})}
/>
</Box>
{/* Registered Address */}
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "50%",
}}
>
<Typography variant="body2" fontWeight={500}>
Registered Address
</Typography>
<CustomTextField
fullWidth
placeholder="Enter Registered Address"
size="small"
error={!!errors.registeredAddress}
helperText={
errors.registeredAddress
? errors.registeredAddress.message
: ""
}
{...register("registeredAddress", {
required: "Registered Address is required",
})}
/>
</Box>
</Box>
{/* Submit Button */}
<Box
sx={{
display: "flex",
justifyContent: "flex-end",
mt: 3,
}}
>
<Button
type="submit"
sx={{
backgroundColor: "#52ACDF",
color: "white",
borderRadius: "8px",
width: "117px",
"&:hover": { backgroundColor: "#439BC1" },
}}
>
Add Manager
</Button>
</Box>
</form>
</Box>
</Modal>
);
}

View file

@ -369,7 +369,4 @@ const AddUserModal: React.FC<AddUserModalProps> = ({
};
export default AddUserModal;
function setValue(arg0: string, name: any) {
throw new Error("Function not implemented.");
}

View file

@ -11,6 +11,7 @@ import Paper, { paperClasses } from "@mui/material/Paper";
import { adminList, deleteAdmin } from "../../redux/slices/adminSlice";
import { vehicleList, deleteVehicle } from "../../redux/slices/VehicleSlice";
import { deleteManager, managerList } from "../../redux/slices/managerSlice";
import { useDispatch } from "react-redux";
import {
Box,
@ -33,6 +34,7 @@ import TuneIcon from "@mui/icons-material/Tune";
import {
CustomIconButton,
} from "../AddUserModel/styled.css.tsx";
import ManagerViewModal from "../Modals/ViewManagerModal";
// Styled components for customization
const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
@ -78,6 +80,7 @@ interface CustomTableProps {
handleStatusToggle: (id: string, currentStatus: number) => void;
tableType: string; // Adding tableType prop to change header text dynamically
handleClickOpen: () => void;
//handleDeleteButton: (id: string | number | undefined) => void;
}
const CustomTable: React.FC<CustomTableProps> = ({
@ -106,10 +109,21 @@ const CustomTable: React.FC<CustomTableProps> = ({
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>, row: Row) => {
setAnchorEl(event.currentTarget);
setSelectedRow(row); // Ensure the row data is set
setSelectedRow(row);
setRowData(row);
};
// const handleViewButton = (id: string | undefined) => {
// if (!id) {
// console.error("ID not found for viewing.");
// return;
// }
// setViewModal(true);
// };
const handleClose = () => {
setAnchorEl(null);
@ -175,13 +189,12 @@ const CustomTable: React.FC<CustomTableProps> = ({
};
const filteredRows = rows.filter(
(row) =>
(row.name &&
row.name.toLowerCase().includes(searchQuery.toLowerCase())) ||
false
(row) =>
(row.name &&
row.name.toLowerCase().includes(searchQuery.toLowerCase())) ||
false
);
const indexOfLastRow = currentPage * usersPerPage;
const indexOfFirstRow = indexOfLastRow - usersPerPage;
const currentRows = filteredRows.slice(indexOfFirstRow, indexOfLastRow);
@ -457,6 +470,7 @@ const filteredRows = rows.filter(
variant="text"
onClick={(e) => {
e.stopPropagation();
// setSelectedRow(row);
setViewModal(true);
}}
color="primary"
@ -489,6 +503,16 @@ const filteredRows = rows.filter(
id={selectedRow?.id}
/>
)}
{viewModal && tableType === "manager" && (
<ManagerViewModal
handleView={() =>
handleViewButton(selectedRow?.id)
}
open={viewModal}
setViewModal={setViewModal}
id={selectedRow?.id}
/>
)}
<Button
variant="text"
@ -533,7 +557,6 @@ const filteredRows = rows.filter(
justifyContent: "flex-start",
py: 0,
fontWeight: "bold",
}}
>
Delete

View file

@ -0,0 +1,595 @@
// 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";
// import {
// CustomIconButton,
// CustomTextField,
// } from "../AddUserModel/styled.css.tsx";
// interface EditManagerModalProps {
// open: boolean;
// handleClose: () => void;
// handleUpdate: (
// id: number,
// name: string,
// email: string,
// stationName: string,
// registeredAddress: string,
// phone: string
// ) => void;
// editRow: any;
// }
// interface FormData {
// name: string;
// email: string;
// stationName: string;
// registeredAddress: string;
// phone: string;
// }
// const EditManagerModal: React.FC<EditManagerModalProps> = ({
// open,
// handleClose,
// handleUpdate,
// editRow,
// }) => {
// const {
// control,
// handleSubmit,
// formState: { errors },
// setValue,
// reset,
// } = useForm<FormData>({
// defaultValues: {
// name: "",
// stationName: "",
// email: "",
// registeredAddress: "",
// phone: "",
// },
// });
// // Populate form fields when `editRow` changes
// useEffect(() => {
// if (editRow) {
// setValue("name", editRow.name || "");
// setValue("stationName", editRow.stationName || "");
// setValue("email", editRow.email || "");
// setValue("registeredAddress", editRow.registeredAddress || "");
// setValue("phone", editRow.phone || "");
// } else {
// reset();
// }
// }, [editRow, setValue, reset]);
// const onSubmit = (data: FormData) => {
// if (editRow) {
// handleUpdate({
// id: editRow.id,
// name: data.name,
// stationName: data.stationName,
// email: data.email,
// registeredAddress: data.registeredAddress,
// phone: data.phone,
// });
// }
// 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 }}>
// {/* First Row - Two Inputs */}
// <Box sx={{ display: "flex", gap: 2 }}>
// <Box
// sx={{
// display: "flex",
// flexDirection: "column",
// width: "100%",
// }}
// >
// <Typography
// variant="body2"
// fontWeight={500}
// mb={0.5}
// >
// Manager Name
// </Typography>
// <Controller
// name="name"
// control={control}
// rules={{ required: "Manager Name is required" }}
// render={({ field }) => (
// <TextField
// {...field}
// fullWidth
// placeholder="Enter Manager Name"
// size="small"
// error={!!errors.name}
// helperText={errors.name?.message}
// />
// )}
// />
// </Box>
// <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>
// {/* Second Row - Two Inputs */}
// <Box sx={{ display: "flex", gap: 2 }}>
// <Box
// sx={{
// display: "flex",
// flexDirection: "column",
// width: "100%",
// }}
// >
// <Typography
// variant="body2"
// fontWeight={500}
// mb={0.5}
// >
// Registered Address
// </Typography>
// <Controller
// name="registeredAddress"
// control={control}
// rules={{ required: "Registered Address is required" }}
// render={({ field }) => (
// <TextField
// {...field}
// fullWidth
// placeholder="Enter Registered Address"
// size="small"
// error={!!errors.registeredAddress}
// helperText={errors.registeredAddress?.message}
// />
// )}
// />
// </Box>
// <Box
// sx={{
// display: "flex",
// flexDirection: "column",
// width: "100%",
// }}
// >
// <Typography
// variant="body2"
// fontWeight={500}
// mb={0.5}
// >
// Email
// </Typography>
// <Controller
// name="email"
// control={control}
// rules={{ required: "Email is required" }}
// render={({ field }) => (
// <TextField
// {...field}
// fullWidth
// placeholder="Enter Email"
// size="small"
// error={!!errors.email}
// helperText={errors.email?.message}
// />
// )}
// />
// </Box>
// </Box>
// {/* Third Row - Phone Number */}
// <Box
// sx={{
// display: "flex",
// flexDirection: "column",
// width: "100%",
// }}
// >
// <Typography variant="body2" fontWeight={500} mb={0.5}>
// Phone Number
// </Typography>
// <Controller
// name="phone"
// control={control}
// rules={{ required: "Phone number is required" }}
// render={({ field }) => (
// <TextField
// {...field}
// fullWidth
// placeholder="Enter Phone Number"
// size="small"
// error={!!errors.phone}
// helperText={errors.phone?.message}
// />
// )}
// />
// </Box>
// </Box>
// {/* Submit Button */}
// <Box
// sx={{ display: "flex", justifyContent: "flex-end", mt: 3 }}
// >
// <Button
// type="submit"
// sx={{
// backgroundColor: "#52ACDF",
// color: "white",
// borderRadius: "8px",
// width: "117px",
// "&:hover": { backgroundColor: "#439BC1" },
// }}
// >
// {editRow ? "Update" : "Create"}
// </Button>
// </Box>
// </Box>
// </Modal>
// );
// };
// export default EditManagerModal;
import React, { useEffect, useState } from "react";
import {
Box,
Button,
Typography,
Modal,
CircularProgress,
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { useForm, Controller } from "react-hook-form";
import { useDispatch } from "react-redux";
import { updateManager } from "../../redux/slices/managerSlice"; // Import the updateManager action
import {
CustomIconButton,
CustomTextField,
} from "../AddUserModel/styled.css.tsx"; // Custom styled components
interface EditManagerModalProps {
open: boolean;
handleClose: () => void;
editRow: any; // Manager data including id
}
interface FormData {
name: string;
email: string;
registeredAddress: string;
phone: string;
}
const EditManagerModal: React.FC<EditManagerModalProps> = ({
open,
handleClose,
editRow,
}) => {
const dispatch = useDispatch(); // Use dispatch to send Redux actions
const {
control,
handleSubmit,
formState: { errors },
setValue,
reset,
} = useForm<FormData>({
defaultValues: {
name: "",
email: "",
registeredAddress: "",
phone: "",
},
});
const [loading, setLoading] = useState(false);
// Set values if editRow is provided
useEffect(() => {
if (editRow) {
setValue("name", editRow.name);
setValue("email", editRow.email);
setValue("registeredAddress", editRow.registeredAddress);
setValue("phone", editRow.phone);
} else {
reset();
}
}, [editRow, setValue, reset]);
const onSubmit = async (data: FormData) => {
if (editRow) {
setLoading(true); // Start loading state
// Dispatch the updateManager action from Redux
try {
await dispatch(
updateManager({
id: editRow.id, // Manager ID
managerData: {
name: data.name,
email: data.email,
registeredAddress: data.registeredAddress,
phone: data.phone,
},
})
).unwrap(); // Ensure that it throws an error if the update fails
handleClose(); // Close modal on success
reset(); // Reset form fields after submit
} catch (error) {
console.error(error);
// You can handle the error here or show a toast
} finally {
setLoading(false); // Stop loading state
}
}
};
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}>
Edit Manager
</Typography>
<CustomIconButton onClick={handleClose}>
<CloseIcon />
</CustomIconButton>
</Box>
{/* Horizontal Line */}
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
{/* Input Fields */}
<Box sx={{ display: "flex", flexDirection: "column", 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="name"
control={control}
rules={{ required: "Manager Name is required" }}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
placeholder="Enter Manager Name"
size="small"
error={!!errors.name}
helperText={errors.name?.message}
/>
)}
/>
</Box>
{/* Registered Address */}
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
}}
>
<Typography variant="body2" fontWeight={500} mb={0.5}>
Registered Address
</Typography>
<Controller
name="registeredAddress"
control={control}
rules={{
required: "Registered Address is required",
}}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
placeholder="Enter Registered Address"
size="small"
error={!!errors.registeredAddress}
helperText={
errors.registeredAddress?.message
}
/>
)}
/>
</Box>
{/* Email */}
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
}}
>
<Typography variant="body2" fontWeight={500} mb={0.5}>
Email
</Typography>
<Controller
name="email"
control={control}
rules={{ required: "Email is required" }}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
placeholder="Enter Email"
size="small"
error={!!errors.email}
helperText={errors.email?.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="phone"
control={control}
rules={{ required: "Phone number is required" }}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
placeholder="Enter Phone Number"
size="small"
error={!!errors.phone}
helperText={errors.phone?.message}
/>
)}
/>
</Box>
</Box>
{/* Submit Button */}
<Box
sx={{ display: "flex", justifyContent: "flex-end", mt: 3 }}
>
<Button
type="submit"
sx={{
backgroundColor: "#52ACDF",
color: "white",
borderRadius: "8px",
width: "117px",
"&:hover": { backgroundColor: "#439BC1" },
}}
disabled={loading} // Disable the button during loading state
>
{loading ? (
<CircularProgress size={24} color="inherit" />
) : (
"Update Manager"
)}
</Button>
</Box>
</Box>
</Modal>
);
};
export default EditManagerModal;

View file

@ -21,7 +21,7 @@ interface EditVehicleModalProps {
email: string,
phone: string,
registeredAddress: string,
password: string
imageUrl: string
) => void;
editRow: any;
}

View file

@ -1,14 +1,24 @@
import React from 'react';
import { useSelector } from 'react-redux';
// import styled from './Loading.style';
import { CircularProgress, Box, Typography } from '@mui/material';
function LoadingComponent() {
// const { isdarktheme } = useSelector((state) => ({
// isdarktheme: state?.authReducer?.isDarkTheme,
// }));
return (<h1>Loading</h1>);
return (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
width: '100%',
flexDirection: 'column',
}}
>
<CircularProgress size={50} color="primary" />
<Typography variant="h6" sx={{ marginTop: 2 }}>
Loading...
</Typography>
</Box>
);
}
export default LoadingComponent;
export default LoadingComponent;

View file

@ -13,10 +13,6 @@ import { RootState } from "../../redux/store/store";
import DashboardOutlinedIcon from "@mui/icons-material/DashboardOutlined";
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 = {
hidden: boolean;
};
@ -26,6 +22,7 @@ export default function MenuContent({ hidden }: PropType) {
const userRole = useSelector(
(state: RootState) => state.profileReducer.user?.userType
);
const baseMenuItems = [
{
text: "Dashboard",
@ -37,24 +34,30 @@ export default function MenuContent({ hidden }: PropType) {
icon: <AnalyticsRoundedIcon />,
url: "/panel/admin-list",
},
userRole === "admin" && {
text: "Users",
icon: <AnalyticsRoundedIcon />,
url: "/panel/user-list",
},
userRole === "superadmin" && {
text: "Roles",
icon: <AnalyticsRoundedIcon />,
url: "/panel/role-list",
},
userRole === "admin" && {
text: "Vehicles",
text: "Users",
icon: <AnalyticsRoundedIcon />,
url: "/panel/vehicle-list",
url: "/panel/user-list",
},
userRole === "admin" && {
text: "Managers",
icon: <ManageAccountsOutlinedIcon />,
url: "/panel/manager-list", // Placeholder for now
},
userRole === "admin" && {
text: "Vehicles",
icon: <ManageAccountsOutlinedIcon />,
url: "/panel/vehicles", // Placeholder for now
},
];
const filteredMenuItems = baseMenuItems.filter(Boolean);
return (
<Stack sx={{ flexGrow: 1, p: 1, justifyContent: "space-between" }}>
<List dense>
@ -64,7 +67,6 @@ export default function MenuContent({ hidden }: PropType) {
disablePadding
sx={{ display: "block", py: 1 }}
>
{/* Wrap ListItemButton with Link to enable routing */}
<ListItemButton
component={Link}
to={item.url}
@ -97,4 +99,4 @@ export default function MenuContent({ hidden }: PropType) {
</List>
</Stack>
);
}
}

View file

@ -53,7 +53,7 @@ export default function DeleteModal({
display: "flex",
alignItems: "center",
justifyContent: "flex-end", // Aligns the close icon to the right
marginTop: -3.5
marginTop: -3.5,
}}
>
<CloseIcon />

View file

@ -0,0 +1,133 @@
import React, { useEffect, useState } from "react";
import { Box, Modal, Typography, Divider, Grid } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { useSelector } from "react-redux";
import { RootState } from "../../../redux/reducers";
type Props = {
open: boolean;
setViewModal: Function;
handleView: (id: string | undefined) => void;
id?: number | undefined;
};
const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "background.paper",
borderRadius: 2,
boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.15)",
p: 4,
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 2,
};
export default function ManagerViewModal({ open, setViewModal, id }: Props) {
const { managers } = useSelector(
(state: RootState) => state.managerReducer
);
const [selectedManager, setSelectedManager] = useState<any>(null);
useEffect(() => {
if (id) {
const manager = managers.find((manager) => manager.id === id);
setSelectedManager(manager || null);
}
}, [id, managers]);
return (
<Modal
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" }}>
{selectedManager?.name || "Manager"}'s Details
</Box>
<Box
onClick={() => setViewModal(false)}
sx={{
cursor: "pointer",
display: "flex",
alignItems: "center",
}}
>
<CloseIcon />
</Box>
</Box>
</Typography>
<Divider sx={{ width: "100%" }} />
{selectedManager ? (
<Grid container spacing={2} sx={{ width: "80%" }}>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Name:</strong>
<Typography variant="body2">
{selectedManager.name}
</Typography>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Email:</strong>
<Typography variant="body2">
{selectedManager.email}
</Typography>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Phone:</strong>
<Typography variant="body2">
{selectedManager.phone}
</Typography>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Registered Address:</strong>
<Typography variant="body2">
{selectedManager.registeredAddress}
</Typography>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Station Name:</strong>
<Typography variant="body2">
{selectedManager.stationName}
</Typography>
</Typography>
</Grid>
</Grid>
) : (
<Typography align="center">
No manager found with this ID
</Typography>
)}
</Box>
</Modal>
);
}

View file

@ -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>
);
}

View file

@ -89,20 +89,26 @@ export default function ViewModal({ open, setViewModal, id }: Props) {
<Grid item xs={6}>
<Typography variant="body1">
<strong>Phone:</strong>
<Typography variant="body2">{selectedAdmin.phone}</Typography>
<Typography variant="body2">
{selectedAdmin.phone}
</Typography>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Email:</strong>
<Typography variant="body2">{selectedAdmin.email}</Typography>
<Typography variant="body2">
{selectedAdmin.email}
</Typography>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Address:</strong>
<Typography variant="body2">{selectedAdmin.registeredAddress ?? "N/A"}</Typography>
<Typography variant="body2">
{selectedAdmin.registeredAddress ?? "N/A"}
</Typography>
</Typography>
</Grid>
</Grid>

View file

@ -11,6 +11,7 @@ import {
Grid,
IconButton,
Link,
InputAdornment
} from "@mui/material";
import { useForm, Controller, SubmitHandler } from "react-hook-form";
import { useDispatch } from "react-redux";
@ -29,7 +30,9 @@ interface ILoginForm {
export default function Login(props: { disableCustomTheme?: boolean }) {
const [open, setOpen] = React.useState(false);
const [isClicked, setIsClicked] = React.useState(false);
const [showPassword, setShowPassword] = React.useState(false);
const {
control,
handleSubmit,
@ -69,7 +72,7 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
md={7}
sx={{
background: `url('/mainPageLogo.png') center/cover no-repeat`,
height: { xs: "0%", sm: "0%", md: "100%" },
// height: { xs: "0%", sm: "50%", md: "100%" },
backgroundSize: "cover",
display: { xs: "none", md: "block" }, // Hide the image on xs and sm screens
}}
@ -77,19 +80,21 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
{/* Form Section */}
<Grid
item
xs={12}
md={5}
sx={{
backgroundColor: "black",
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
padding: { xs: "2rem", md: "3rem", lg: "3rem" },
height: "100%",
}}
>
item
xs={12}
md={5}
sx={{
backgroundColor: "black",
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
padding: { xs: "2rem", md: "3rem", lg: "3rem" },
height: "auto", // ✅ Allows the height to adjust dynamically
}}
>
<Typography
variant="h3"
sx={{
@ -105,13 +110,8 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
Welcome Back!
</Typography>
<Card
variant="outlined"
sx={{
maxWidth: "400px",
width: { xs: "80%", sm: "80%", md: "100%" },
}}
>
<Card variant="outlined" sx={{ width: { xs: "100%", sm: "300px",lg:"408px" }, padding: "24px", borderRadius: "12px", backgroundColor: "#1E1E1E", border: "1px solid #4B5255" }}>
<Box
component="form"
onSubmit={handleSubmit(onSubmit)}
@ -122,182 +122,178 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
gap: 2,
}}
>
<Typography
component="h1"
variant="h4"
sx={{
textAlign: "center",
color: "white",
fontSize: { xs: "1.5rem", sm: "2rem" },
}}
>
Login
</Typography>
<Typography
component="h6"
variant="subtitle2"
sx={{
textAlign: "center",
color: "white",
fontSize: { xs: "0.9rem", sm: "1rem" },
}}
>
Log in with your email and password
</Typography>
<Typography component="h1" variant="h4" sx={{ textAlign: "center", color: "white", fontFamily: "Gilroy", fontSize: "24px" }}>Login</Typography>
<Typography
component="h6"
variant="subtitle2"
sx={{ textAlign: "center", color: "white", fontFamily: "Gilroy", fontSize: "16px" }}
>
Log in with your email and password
</Typography>
<FormControl sx={{ width: "100%" }}>
<FormLabel
htmlFor="email"
sx={{
fontSize: {
xs: "0.9rem",
sm: "1rem",
},
color: "white",
}}
>
Email
</FormLabel>
<Controller
name="email"
control={control}
defaultValue=""
rules={{
required: "Email is required",
pattern: {
value: /\S+@\S+\.\S+/,
message:
"Please enter a valid email address.",
},
}}
render={({ field }) => (
<TextField
{...field}
error={!!errors.email}
helperText={
errors.email?.message
}
id="email"
type="email"
placeholder="Email"
autoComplete="email"
autoFocus
required
fullWidth
variant="outlined"
color={
errors.email
? "error"
: "primary"
}
sx={{
input: {
fontSize: {
xs: "0.9rem",
sm: "1rem",
},
},
}}
/>
)}
/>
</FormControl>
<FormControl sx={{ width: "100%" }}>
<FormLabel
htmlFor="password"
sx={{
fontSize: {
xs: "0.9rem",
sm: "1rem",
},
color: "white",
}}
>
Password
</FormLabel>
<Controller
name="password"
control={control}
defaultValue=""
rules={{
required: "Password is required",
minLength: {
value: 6,
message:
"Password must be at least 6 characters long.",
},
pattern: {
value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{6,}$/,
message:
"Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character.",
},
}}
render={({ field }) => (
<Box sx={{ position: "relative" }}>
<TextField
{...field}
error={!!errors.password}
helperText={
errors.password?.message
}
name="password"
placeholder="Password"
type={
showPassword
? "text"
: "password"
}
id="password"
autoComplete="current-password"
autoFocus
required
fullWidth
variant="outlined"
color={
errors.password
? "error"
: "primary"
}
sx={{
paddingRight: "40px",
height: "40px",
marginBottom: "8px",
}}
/>
<IconButton
sx={{
position: "absolute",
top: "50%",
right: "10px",
transform:
"translateY(-50%)",
background: "none",
borderColor:
"transparent",
"&:hover": {
backgroundColor:
"transparent",
borderColor:
"transparent",
},
}}
onClick={() =>
setShowPassword(
(prev) => !prev
)
}
>
{showPassword ? (
<VisibilityOff />
) : (
<Visibility />
)}
</IconButton>
</Box>
)}
/>
</FormControl>
{/* -------------------------------- Email Field ----------------- */}
<FormControl sx={{ width: "100%" }}>
<FormLabel
htmlFor="email"
sx={{
fontSize: { xs: "0.9rem", sm: "1rem" },
color: "white",
fontFamily: "Gilroy, sans-serif", // ✅ Apply Gilroy font
}}
>
Email
</FormLabel>
<Controller
name="email"
control={control}
defaultValue=""
rules={{
required: "Email is required",
pattern: {
value: /\S+@\S+\.\S+/,
message: "Please enter a valid email address.",
},
}}
render={({ field }) => (
<TextField
{...field}
error={!!errors.email}
helperText={errors.email?.message}
id="email"
type="email"
placeholder="Email"
autoComplete="email"
autoFocus
required
fullWidth
variant="outlined"
color={errors.email ? "error" : "primary"}
InputProps={{
sx: {
height: "50px",
alignItems: "center",
backgroundColor: "#1E1F1F",
fontFamily: "Gilroy, sans-serif",
},
}}
sx={{
"& .MuiOutlinedInput-root": {
backgroundColor: "#1E1F1F",
borderRadius: "4px",
"& fieldset": { borderColor: "#4b5255" },
"&:hover fieldset": { borderColor: "#4b5255" },
"&.Mui-focused fieldset": { borderColor: "#4b5255" },
},
"& input": {
color: "white",
fontSize: { xs: "0.9rem", sm: "1rem" },
fontFamily: "Gilroy, sans-serif",
},
"& .MuiInputBase-input::placeholder": {
color: "white",
opacity: 1,
fontFamily: "Gilroy, sans-serif",
},
}}
/>
)}
/>
</FormControl>
{/* -------------------------------- Password Field ----------------- */}
<FormControl sx={{ width: "100%" }}>
<FormLabel
htmlFor="password"
sx={{
fontSize: { xs: "0.9rem", sm: "1rem" },
color: "white",
fontFamily: "Gilroy, sans-serif",
}}
>
Password
</FormLabel>
<Controller
name="password"
control={control}
defaultValue=""
rules={{
required: "Password is required",
minLength: {
value: 6,
message: "Password must be at least 6 characters long.",
},
pattern: {
value:
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{6,}$/,
message:
"Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character.",
},
}}
render={({ field }) => (
<TextField
{...field}
error={!!errors.password}
helperText={errors.password?.message}
name="password"
placeholder="Password"
type={showPassword ? "text" : "password"}
id="password"
autoComplete="current-password"
required
fullWidth
variant="outlined"
color={errors.password ? "error" : "primary"}
InputProps={{
sx: {
height: "50px",
fontFamily: "Gilroy, sans-serif", // Apply Gilroy font
},
endAdornment: (
<IconButton
onClick={() => setShowPassword((prev) => !prev)}
edge="end"
sx={{
color: "white",
padding: 0,
margin: 0,
backgroundColor: "transparent",
border: "none",
boxShadow: "none",
"&:hover": { backgroundColor: "transparent" },
"&:focus": { outline: "none", border: "none" },
}}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
),
}}
sx={{
"& .MuiOutlinedInput-root": {
backgroundColor: "#1E1F1F",
borderRadius: "4px",
"& fieldset": { borderColor: "#4b5255" },
"&:hover fieldset": { borderColor: "#4b5255" },
"&.Mui-focused fieldset": { borderColor: "#4b5255" },
},
"& input": {
color: "white",
fontSize: { xs: "0.9rem", sm: "1rem" },
fontFamily: "Gilroy, sans-serif",
},
"& .MuiInputBase-input::placeholder": {
color: "white",
opacity: 1,
fontFamily: "Gilroy, sans-serif",
},
}}
/>
)}
/>
</FormControl>
<Box
sx={{
@ -308,27 +304,50 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
flexWrap: "wrap",
}}
>
<FormControlLabel
control={
<Checkbox
value="remember"
color="primary"
/>
}
label="Remember me"
/>
<Link
component="button"
type="button"
onClick={handleClickOpen}
variant="body2"
sx={{
alignSelf: "center",
color: "#01579b",
}}
>
Forgot password?
</Link>
<FormControlLabel
control={
<Checkbox
value="remember"
sx={{
width: 20,
height: 20,
fontFamily: "Gilroy, sans-serif",
border: "2px solid #4b5255",
borderRadius: "4px",
backgroundColor: "transparent",
"&:hover": {
backgroundColor: "transparent",
},
"&.Mui-checked": {
backgroundColor: "transparent",
borderColor: "#4b5255",
"&:hover": {
backgroundColor: "transparent",
},
},
}}
/>
}
label="Remember me"
/>
<Link
component="button"
type="button"
onClick={handleClickOpen}
variant="body2"
sx={{
alignSelf: "center",
fontFamily: "Gilroy, sans-serif",
color: "#01579b",
textDecoration: "none", // ✅ Removes underline
}}
>
Forgot password?
</Link>
</Box>
<ForgotPassword
open={open}
@ -340,6 +359,7 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
disabled={!isValid}
sx={{
color: "white",
fontFamily: "Gilroy, sans-serif",
backgroundColor: "#52ACDF",
"&:hover": {
backgroundColor: "#52ACDF",

View file

@ -0,0 +1,157 @@
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 "../../components/AddManagerModal";
import { useDispatch, useSelector } from "react-redux";
import DeleteModal from "../../components/Modals/DeleteModal";
import {
managerList,
addManager,
updateManager,
deleteManager,
} from "../../redux/slices/managerSlice";
import { RootState, AppDispatch } from "../../redux/store/store";
import { useForm } from "react-hook-form";
import EditManagerModal from "../../components/EditManagerModal";
export default function ManagerList() {
const [addModalOpen, setAddModalOpen] = useState<boolean>(false);
const [editModalOpen, setEditModalOpen] = useState<boolean>(false);
const [editRow, setEditRow] = useState<any>(null);
const { reset } = useForm();
const [deleteModal, setDeleteModal] = useState<boolean>(false);
const [viewModal, setViewModal] = useState<boolean>(false);
const [rowData, setRowData] = useState<any | null>(null);
const [searchTerm, setSearchTerm] = useState("");
const dispatch = useDispatch<AppDispatch>();
const managers = useSelector(
(state: RootState) => state.managerReducer.managers
);
useEffect(() => {
dispatch(managerList());
}, [dispatch]);
const handleClickOpen = () => {
setRowData(null); // Reset row data when opening for new admin
setAddModalOpen(true);
};
const handleCloseModal = () => {
setAddModalOpen(false);
setEditModalOpen(false);
setRowData(null);
reset();
};
const handleAddManager = async (data: {
name: string;
email: string;
phone: string;
registeredAddress: string;
}) => {
try {
await dispatch(addManager(data)); // Dispatch action to add manager
await dispatch(managerList()); // Fetch the updated list
handleCloseModal(); // Close the modal
} catch (error) {
console.error("Error adding manager", error);
}
};
const handleUpdate = async (
id: number,
name: string,
email: string,
phone: string,
registeredAddress: string
) => {
try {
// Creating the managerData object to match the expected payload structure
const managerData = {
name,
email,
phone,
registeredAddress,
};
// Dispatching the updateManager action with the correct payload structure
await dispatch(updateManager({ id, managerData }));
await dispatch(managerList()); // Refresh the manager list after updating
handleCloseModal(); // Close the modal after updating
} catch (error) {
console.error("Update failed", error);
}
};
// Remove 'stationName' from columns
const categoryColumns: Column[] = [
{ id: "srno", label: "Sr No" },
{ id: "name", label: "Name" },
{ id: "email", label: "Email" },
{ id: "phone", label: "Phone" },
{ id: "registeredAddress", label: "Station Location" },
{ id: "action", label: "Action", align: "center" },
];
// Update rows to remove 'stationName'
const categoryRows = managers?.map(
(
manager: {
id: number;
name: string;
email: string;
phone: string;
registeredAddress: string;
},
index: number
) => ({
id: manager?.id,
srno: index + 1,
name: manager?.name,
email: manager?.email,
phone: manager.phone ?? "NA",
registeredAddress: manager?.registeredAddress ?? "NA",
})
);
return (
<>
<CustomTable
columns={categoryColumns}
rows={categoryRows}
setDeleteModal={setDeleteModal}
deleteModal={deleteModal}
setViewModal={setViewModal}
viewModal={viewModal}
setRowData={setRowData}
setModalOpen={() => setEditModalOpen(true)}
tableType="manager"
handleClickOpen={handleClickOpen}
/>
<AddManagerModal
open={addModalOpen}
handleClose={handleCloseModal}
handleAddManager={handleAddManager}
/>
<EditManagerModal
open={editModalOpen}
handleClose={handleCloseModal}
handleUpdate={handleUpdate}
editRow={rowData}
/>
</>
);
}

View file

@ -6,6 +6,7 @@ import profileReducer from "./slices/profileSlice";
import userReducer from "./slices/userSlice.ts";
import roleReducer from "./slices/roleSlice.ts";
import vehicleReducer from "./slices/VehicleSlice.ts";
import managerReducer from "../redux/slices/managerSlice.ts";
const rootReducer = combineReducers({
@ -14,7 +15,9 @@ const rootReducer = combineReducers({
profileReducer,
userReducer,
roleReducer,
vehicleReducer
vehicleReducer,
managerReducer,
// Add other reducers here...
});
export type RootState = ReturnType<typeof rootReducer>;

View file

@ -0,0 +1,180 @@
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
import http from "../../lib/https";
import { toast } from "sonner";
// Define the Manager interface based on the payload
interface Manager {
id: number;
name: string;
email: string;
phone: string;
registeredAddress: string;
roleId: number;
}
interface ManagerState {
managers: Manager[];
loading: boolean;
error: string | null;
}
// Initial state
const initialState: ManagerState = {
managers: [],
loading: false,
error: null,
};
// Fetch Manager List (Async Thunk)
export const managerList = 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.message);
return rejectWithValue(
error?.response?.data?.message || "An error occurred"
);
}
});
// Create Manager (Async Thunk)
export const addManager = createAsyncThunk<
Manager,
Manager,
{ rejectValue: string }
>("addManager", async (data, { rejectWithValue }) => {
try {
const response = await http.post("create-manager", data);
toast.success("Manager created successfully");
return response.data?.data;
} catch (error: any) {
toast.error("Error creating manager: " + error.message);
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
}
});
// Update Manager (Async Thunk)
export const updateManager = createAsyncThunk<
Manager,
{ id: number; managerData: Manager },
{ rejectValue: string }
>("updateManager", async ({ id, managerData }, { rejectWithValue }) => {
if (!id) {
return rejectWithValue("Manager ID is required.");
}
try {
const response = await http.put(`/${id}/update-manager`, managerData);
toast.success("Manager updated successfully");
return response?.data;
} catch (error: any) {
toast.error("Error updating manager: " + error.message);
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
}
});
// Delete Manager (Async Thunk)
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;
} catch (error: any) {
toast.error("Error deleting manager: " + error.message);
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
}
});
// Create Slice
const managerSlice = createSlice({
name: "maanger",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
// Fetch Managers
.addCase(managerList.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(
managerList.fulfilled,
(state, action: PayloadAction<Manager[]>) => {
state.loading = false;
state.managers = action.payload;
}
)
.addCase(managerList.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;
state.error = action.payload || "Failed to add manager";
})
// 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(
// (manager) => manager.id === updatedManager.id
// );
// if (index !== -1) {
// state.managers[index] = updatedManager; // Update the manager in the state
// }
})
.addCase(updateManager.rejected, (state, action) => {
state.loading = false;
state.error = action.payload || "Failed to update 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, action) => {
state.loading = false;
state.error = action.payload || "Failed to delete manager";
});
},
});
export default managerSlice.reducer;

View file

@ -16,6 +16,8 @@ const ProfilePage = lazy(() => import("./pages/ProfilePage"));
const NotFoundPage = lazy(() => import("./pages/NotFound"));
const UserList = lazy(() => import("./pages/UserList"));
const PermissionsTable = lazy(() => import("./pages/PermissionTable"));
const ManagerList = lazy(() => import("./pages/ManagerList"));
interface ProtectedRouteProps {
caps: string[];
@ -87,6 +89,18 @@ export default function AppRouter() {
/>
}
/>
<Route
path="manager-list"
element={
<ProtectedRoute
caps={[]}
component={<ManagerList />}
/>
}
/>
<Route
path="role-list"
element={