dev-jaanvi #1

Open
jaanvi wants to merge 155 commits from dev-jaanvi into main
19 changed files with 1726 additions and 336 deletions
Showing only changes of commit 90f44b5183 - Show all commits

1
package-lock.json generated
View file

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

View file

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

View file

@ -1,6 +1,7 @@
import { BrowserRouter as Router } from "react-router-dom"; import { BrowserRouter as Router } from "react-router-dom";
import AppRouter from "./router"; import AppRouter from "./router";
function App() { function App() {
return ( return (
<Router> <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; 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 { adminList, deleteAdmin } from "../../redux/slices/adminSlice";
import { vehicleList, deleteVehicle } from "../../redux/slices/VehicleSlice"; import { vehicleList, deleteVehicle } from "../../redux/slices/VehicleSlice";
import { deleteManager, managerList } from "../../redux/slices/managerSlice";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { import {
Box, Box,
@ -33,6 +34,7 @@ import TuneIcon from "@mui/icons-material/Tune";
import { import {
CustomIconButton, CustomIconButton,
} from "../AddUserModel/styled.css.tsx"; } from "../AddUserModel/styled.css.tsx";
import ManagerViewModal from "../Modals/ViewManagerModal";
// Styled components for customization // Styled components for customization
const StyledTableCell = styled(TableCell)(({ theme }) => ({ const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: { [`&.${tableCellClasses.head}`]: {
@ -78,6 +80,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> = ({
@ -106,10 +109,21 @@ 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);
@ -181,7 +195,6 @@ const filteredRows = rows.filter(
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);
@ -457,6 +470,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"
@ -489,6 +503,16 @@ const filteredRows = rows.filter(
id={selectedRow?.id} id={selectedRow?.id}
/> />
)} )}
{viewModal && tableType === "manager" && (
<ManagerViewModal
handleView={() =>
handleViewButton(selectedRow?.id)
}
open={viewModal}
setViewModal={setViewModal}
id={selectedRow?.id}
/>
)}
<Button <Button
variant="text" variant="text"
@ -533,7 +557,6 @@ const filteredRows = rows.filter(
justifyContent: "flex-start", justifyContent: "flex-start",
py: 0, py: 0,
fontWeight: "bold", fontWeight: "bold",
}} }}
> >
Delete 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, email: string,
phone: string, phone: string,
registeredAddress: string, registeredAddress: string,
password: string imageUrl: string
) => void; ) => void;
editRow: any; editRow: any;
} }

View file

@ -1,14 +1,24 @@
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux'; import { CircularProgress, Box, Typography } from '@mui/material';
// import styled from './Loading.style';
function LoadingComponent() { function LoadingComponent() {
// const { isdarktheme } = useSelector((state) => ({ return (
// isdarktheme: state?.authReducer?.isDarkTheme, <Box
// })); sx={{
display: 'flex',
return (<h1>Loading</h1>); 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 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,30 @@ 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
},
userRole === "admin" && {
text: "Vehicles",
icon: <ManageAccountsOutlinedIcon />,
url: "/panel/vehicles", // 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 +67,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}

View file

@ -53,7 +53,7 @@ export default function DeleteModal({
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "flex-end", // Aligns the close icon to the right justifyContent: "flex-end", // Aligns the close icon to the right
marginTop: -3.5 marginTop: -3.5,
}} }}
> >
<CloseIcon /> <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}> <Grid item xs={6}>
<Typography variant="body1"> <Typography variant="body1">
<strong>Phone:</strong> <strong>Phone:</strong>
<Typography variant="body2">{selectedAdmin.phone}</Typography> <Typography variant="body2">
{selectedAdmin.phone}
</Typography>
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<Typography variant="body1"> <Typography variant="body1">
<strong>Email:</strong> <strong>Email:</strong>
<Typography variant="body2">{selectedAdmin.email}</Typography> <Typography variant="body2">
{selectedAdmin.email}
</Typography>
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<Typography variant="body1"> <Typography variant="body1">
<strong>Address:</strong> <strong>Address:</strong>
<Typography variant="body2">{selectedAdmin.registeredAddress ?? "N/A"}</Typography> <Typography variant="body2">
{selectedAdmin.registeredAddress ?? "N/A"}
</Typography>
</Typography> </Typography>
</Grid> </Grid>
</Grid> </Grid>

View file

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

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 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={