dev-jaanvi #1

Open
jaanvi wants to merge 155 commits from dev-jaanvi into main
17 changed files with 955 additions and 987 deletions
Showing only changes of commit 1e922f1ac5 - Show all commits

View file

@ -102,7 +102,12 @@ const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({
return ( return (
<Modal <Modal
open={open} open={open}
onClose={handleClose} onClose={(e, reason) => {
if (reason === "backdropClick") {
return;
}
handleClose(); // Close modal when clicking cross or cancel
}}
aria-labelledby="add-edit-category-modal" aria-labelledby="add-edit-category-modal"
> >
<Box <Box
@ -174,6 +179,11 @@ const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({
message: message:
"Maximum 30 characters allowed", "Maximum 30 characters allowed",
}, },
pattern: {
value: /^[A-Za-z\s]+$/, // Only letters and spaces are allowed
message:
"Admin Name must only contain letters and spaces",
},
}} }}
render={({ field }) => ( render={({ field }) => (
<CustomTextField <CustomTextField
@ -205,9 +215,9 @@ const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({
rules={{ rules={{
required: "Email is required", required: "Email is required",
pattern: { pattern: {
value: /\S+@\S+\.\S+/, value: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
message: message:
"Please enter a valid email address.", "Please enter a valid email address (e.g., example@domain.com).",
}, },
}} }}
render={({ field }) => ( render={({ field }) => (

View file

@ -1,22 +1,27 @@
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { Box, Button, Typography, Modal, IconButton, InputAdornment } from "@mui/material"; import {
Box,
Button,
Typography,
Modal,
IconButton,
InputAdornment,
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import { Visibility, VisibilityOff } from "@mui/icons-material"; import { Visibility, VisibilityOff } from "@mui/icons-material";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { addManager } from "../../redux/slices/managerSlice"; import { addManager, managerList } from "../../redux/slices/managerSlice";
import { import {
CustomIconButton, CustomIconButton,
CustomTextField, CustomTextField,
} from "../AddUserModel/styled.css.tsx"; } from "../AddUserModel/styled.css.tsx";
import React from "react"; import React, { useState, useRef } from "react";
type Props = { export default function AddManagerModal({
open: boolean; open,
handleClose: () => void; handleClose,
}; handleAddManager,
}) {
export default function AddManagerModal({ open, handleClose }: Props) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { const {
control, control,
@ -25,22 +30,24 @@ export default function AddManagerModal({ open, handleClose }: Props) {
formState: { errors }, formState: { errors },
reset, reset,
} = useForm(); } = useForm();
const [showPassword, setShowPassword] = React.useState(false); const [showPassword, setShowPassword] = useState(false);
// Handle form submission
const onSubmit = async (data: any) => { const onSubmit = async (data: any) => {
const managerData = { const managerData = {
name: data.name, name: data.name,
email: data.email, email: data.email,
phone: data.phone, phone: data.phone,
registeredAddress: data.registeredAddress, registeredAddress: data.registeredAddress,
roleId: data.role,
password: data.password, password: data.password,
roleName: "Manager", // Add a role name (you can replace this with a dynamic value if needed) roleName: "Manager", // You can replace this with dynamic role if needed
}; };
try { try {
// Dispatch the addManager action // Dispatch the addManager action
await dispatch(addManager(managerData)).unwrap(); await dispatch(addManager(managerData));
dispatch(managerList());
handleClose(); // Close modal after successful addition handleClose(); // Close modal after successful addition
reset(); // Reset form fields reset(); // Reset form fields
} catch (error) { } catch (error) {
@ -48,15 +55,21 @@ export default function AddManagerModal({ open, handleClose }: Props) {
} }
}; };
// Toggle password visibility
const togglePasswordVisibility = () => { const togglePasswordVisibility = (e: React.MouseEvent) => {
e.preventDefault(); // Prevent focus loss
setShowPassword((prev) => !prev); setShowPassword((prev) => !prev);
}; };
return ( return (
<Modal <Modal
open={open} open={open}
onClose={handleClose} onClose={(e, reason) => {
if (reason === "backdropClick") {
return;
}
handleClose(); // Close modal when clicking cross or cancel
}}
aria-labelledby="add-manager-modal" aria-labelledby="add-manager-modal"
> >
<Box <Box
@ -93,15 +106,9 @@ export default function AddManagerModal({ open, handleClose }: Props) {
{/* Form */} {/* Form */}
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
{/* First Row - Manager Name */}
<Box sx={{ display: "flex", gap: 2 }}>
{/* Manager Name */} {/* Manager Name */}
<Box <Box
sx={{ sx={{ display: "flex", flexDirection: "column", mb: 2 }}
display: "flex",
flexDirection: "column",
width: "100%",
}}
> >
<Typography variant="body2" fontWeight={500}> <Typography variant="body2" fontWeight={500}>
Manager Name Manager Name
@ -111,31 +118,30 @@ export default function AddManagerModal({ open, handleClose }: Props) {
placeholder="Enter Manager Name" placeholder="Enter Manager Name"
size="small" size="small"
error={!!errors.name} error={!!errors.name}
helperText={ helperText={errors.name ? errors.name.message : ""}
errors.name ? errors.name.message : ""
}
{...register("name", { {...register("name", {
required: "Manager Name is required", required: "Manager Name is required",
minLength: { minLength: {
value: 2, value: 3,
message: "Minimum 3 characters required",
},
maxLength: {
value: 30,
message: "Maximum 30 characters allowed",
},
pattern: {
value: /^[A-Za-z\s]+$/, // Only letters and spaces are allowed
message: message:
"Manager Name must be at least 2 characters long", "Manager Name must only contain letters and spaces",
}, },
})} })}
/> />
</Box> </Box>
</Box>
{/* Second Row - Email, Password */} {/* Email and Password */}
<Box sx={{ display: "flex", gap: 2 }}> <Box sx={{ display: "flex", gap: 2, mb: 2 }}>
{/* Email */} {/* Email */}
<Box <Box sx={{ flex: 1 }}>
sx={{
display: "flex",
flexDirection: "column",
width: "50%",
}}
>
<Typography variant="body2" fontWeight={500}> <Typography variant="body2" fontWeight={500}>
Email Email
</Typography> </Typography>
@ -158,14 +164,7 @@ export default function AddManagerModal({ open, handleClose }: Props) {
</Box> </Box>
{/* Password */} {/* Password */}
<Box sx={{ display: "flex", gap: 2 }}> <Box sx={{ flex: 1 }}>
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
}}
>
<Typography variant="body2" fontWeight={500}> <Typography variant="body2" fontWeight={500}>
Password Password
</Typography> </Typography>
@ -191,11 +190,8 @@ export default function AddManagerModal({ open, handleClose }: Props) {
required required
placeholder="Enter Password" placeholder="Enter Password"
type={ type={
showPassword showPassword ? "text" : "password"
? "text"
: "password"
} }
id="password"
fullWidth fullWidth
color={ color={
errors.password errors.password
@ -203,6 +199,7 @@ export default function AddManagerModal({ open, handleClose }: Props) {
: "primary" : "primary"
} }
size="small" size="small"
// onMouseDown={togglePasswordVisibility}
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
@ -223,26 +220,17 @@ export default function AddManagerModal({ open, handleClose }: Props) {
), ),
}} }}
error={!!errors.password} error={!!errors.password}
helperText={ helperText={errors.password?.message}
errors.password?.message
}
/> />
)} )}
/> />
</Box> </Box>
</Box> </Box>
</Box>
{/* Third Row - Phone Number, Registered Address */} {/* Phone and Registered Address */}
<Box sx={{ display: "flex", gap: 2 }}> <Box sx={{ display: "flex", gap: 2, mb: 2 }}>
{/* Phone Number */} {/* Phone */}
<Box <Box sx={{ flex: 1 }}>
sx={{
display: "flex",
flexDirection: "column",
width: "50%",
}}
>
<Typography variant="body2" fontWeight={500}> <Typography variant="body2" fontWeight={500}>
Phone Number Phone Number
</Typography> </Typography>
@ -266,13 +254,7 @@ export default function AddManagerModal({ open, handleClose }: Props) {
</Box> </Box>
{/* Registered Address */} {/* Registered Address */}
<Box <Box sx={{ flex: 1 }}>
sx={{
display: "flex",
flexDirection: "column",
width: "50%",
}}
>
<Typography variant="body2" fontWeight={500}> <Typography variant="body2" fontWeight={500}>
Registered Address Registered Address
</Typography> </Typography>

View file

@ -93,7 +93,12 @@ const AddUserModal: React.FC<AddUserModalProps> = ({
return ( return (
<Modal <Modal
open={open} open={open}
onClose={handleClose} onClose={(e, reason) => {
if (reason === "backdropClick") {
return;
}
handleClose(); // Close modal when clicking cross or cancel
}}
aria-labelledby="add-user-modal" aria-labelledby="add-user-modal"
> >
<Box <Box
@ -165,6 +170,11 @@ const AddUserModal: React.FC<AddUserModalProps> = ({
message: message:
"Maximum 30 characters allowed", "Maximum 30 characters allowed",
}, },
pattern: {
value: /^[A-Za-z\s]+$/, // Only letters and spaces are allowed
message:
"User Name must only contain letters and spaces",
},
}} }}
render={({ field }) => ( render={({ field }) => (
<CustomTextField <CustomTextField

View file

@ -28,7 +28,12 @@ export default function AddVehicleModal({
return ( return (
<Modal <Modal
open={open} open={open}
onClose={handleClose} onClose={(e, reason) => {
if (reason === "backdropClick") {
return;
}
handleClose(); // Close modal when clicking cross or cancel
}}
aria-labelledby="add-vehicle-modal" aria-labelledby="add-vehicle-modal"
> >
<Box <Box
@ -96,9 +101,19 @@ export default function AddVehicleModal({
{...register("name", { {...register("name", {
required: "Vehicle Name is required", required: "Vehicle Name is required",
minLength: { minLength: {
value: 2, value: 3,
message: message:
"Vehicle Name must be at least 2 characters long", "Minimum 3 characters required",
},
maxLength: {
value: 30,
message:
"Maximum 30 characters allowed",
},
pattern: {
value: /^[A-Za-z\s]+$/, // Only letters and spaces are allowed
message:
"Vehicle Name must only contain letters and spaces",
}, },
})} })}
/> />
@ -232,7 +247,6 @@ export default function AddVehicleModal({
}} }}
> >
<Button <Button
type="submit" type="submit"
sx={{ sx={{
backgroundColor: "#52ACDF", backgroundColor: "#52ACDF",

View file

@ -1,4 +1,3 @@
import * as React from "react"; import * as React from "react";
import { styled } from "@mui/material/styles"; import { styled } from "@mui/material/styles";
import Table from "@mui/material/Table"; import Table from "@mui/material/Table";
@ -31,10 +30,10 @@ import VehicleViewModal from "../Modals/VehicleViewModal";
import SearchIcon from "@mui/icons-material/Search"; import SearchIcon from "@mui/icons-material/Search";
import TuneIcon from "@mui/icons-material/Tune"; import TuneIcon from "@mui/icons-material/Tune";
import { import { CustomIconButton } from "../AddUserModel/styled.css.tsx";
CustomIconButton,
} from "../AddUserModel/styled.css.tsx";
import ManagerViewModal from "../Modals/ViewManagerModal"; import ManagerViewModal from "../Modals/ViewManagerModal";
import UserViewModal from "../Modals/UserViewModal/index.tsx";
import { deleteUser, userList } from "../../redux/slices/userSlice.ts";
// Styled components for customization // Styled components for customization
const StyledTableCell = styled(TableCell)(({ theme }) => ({ const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: { [`&.${tableCellClasses.head}`]: {
@ -95,7 +94,6 @@ const CustomTable: React.FC<CustomTableProps> = ({
handleStatusToggle, handleStatusToggle,
tableType, tableType,
handleClickOpen, handleClickOpen,
}) => { }) => {
const dispatch = useDispatch<AppDispatch>(); const dispatch = useDispatch<AppDispatch>();
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
@ -104,17 +102,12 @@ const CustomTable: React.FC<CustomTableProps> = ({
const [currentPage, setCurrentPage] = React.useState(1); const [currentPage, setCurrentPage] = React.useState(1);
const usersPerPage = 10; const usersPerPage = 10;
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); setSelectedRow(row);
setRowData(row); setRowData(row);
}; };
// const handleViewButton = (id: string | undefined) => { // const handleViewButton = (id: string | undefined) => {
// if (!id) { // if (!id) {
@ -124,7 +117,6 @@ const CustomTable: React.FC<CustomTableProps> = ({
// setViewModal(true); // setViewModal(true);
// }; // };
const handleClose = () => { const handleClose = () => {
setAnchorEl(null); setAnchorEl(null);
}; };
@ -136,8 +128,6 @@ const CustomTable: React.FC<CustomTableProps> = ({
return false; return false;
}; };
const handleDeleteButton = (id: string | undefined) => { const handleDeleteButton = (id: string | undefined) => {
if (!id) console.error("ID not found", id); if (!id) console.error("ID not found", id);
@ -151,6 +141,9 @@ const CustomTable: React.FC<CustomTableProps> = ({
case "manager": case "manager":
dispatch(deleteManager(id || "")); dispatch(deleteManager(id || ""));
break; break;
case "user":
dispatch(deleteUser(id || ""));
break;
default: default:
console.error("Unknown table type:", tableType); console.error("Unknown table type:", tableType);
return; return;
@ -161,21 +154,24 @@ const CustomTable: React.FC<CustomTableProps> = ({
const handleViewButton = (id: string | undefined) => { const handleViewButton = (id: string | undefined) => {
if (!id) console.error("ID not found", id); if (!id) console.error("ID not found", id);
switch (tableType) { // switch (tableType) {
case "admin": // case "admin":
dispatch(adminList()); // dispatch(adminList());
break; // break;
case "vehicle": // case "vehicle":
dispatch(vehicleList()); // dispatch(vehicleList());
break; // break;
case "manager": // case "manager":
dispatch(managerList()); // dispatch(managerList());
break; // break;
default: // case "user":
console.error("Unknown table type:", tableType); // dispatch(userList());
return; // break;
} // default:
dispatch(adminList()); // console.error("Unknown table type:", tableType);
// return;
// }
setViewModal(false); setViewModal(false);
}; };
@ -513,6 +509,16 @@ const filteredRows = rows.filter(
id={selectedRow?.id} id={selectedRow?.id}
/> />
)} )}
{viewModal && tableType === "user" && (
<UserViewModal
handleView={() =>
handleViewButton(selectedRow?.id)
}
open={viewModal}
setViewModal={setViewModal}
id={selectedRow?.id}
/>
)}
<Button <Button
variant="text" variant="text"
@ -579,4 +585,3 @@ const filteredRows = rows.filter(
}; };
export default CustomTable; export default CustomTable;

View file

@ -1,317 +1,3 @@
// 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 React, { useEffect, useState } from "react";
import { import {
Box, Box,
@ -323,7 +9,7 @@ import {
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import { useForm, Controller } from "react-hook-form"; import { useForm, Controller } from "react-hook-form";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { updateManager } from "../../redux/slices/managerSlice"; // Import the updateManager action import { managerList, updateManager } from "../../redux/slices/managerSlice"; // Import the updateManager action
import { import {
CustomIconButton, CustomIconButton,
CustomTextField, CustomTextField,
@ -379,9 +65,6 @@ const EditManagerModal: React.FC<EditManagerModalProps> = ({
const onSubmit = async (data: FormData) => { const onSubmit = async (data: FormData) => {
if (editRow) { if (editRow) {
setLoading(true); // Start loading state
// Dispatch the updateManager action from Redux
try { try {
await dispatch( await dispatch(
updateManager({ updateManager({
@ -394,7 +77,7 @@ const EditManagerModal: React.FC<EditManagerModalProps> = ({
}, },
}) })
).unwrap(); // Ensure that it throws an error if the update fails ).unwrap(); // Ensure that it throws an error if the update fails
dispatch(managerList());
handleClose(); // Close modal on success handleClose(); // Close modal on success
reset(); // Reset form fields after submit reset(); // Reset form fields after submit
} catch (error) { } catch (error) {
@ -409,7 +92,12 @@ const EditManagerModal: React.FC<EditManagerModalProps> = ({
return ( return (
<Modal <Modal
open={open} open={open}
onClose={handleClose} onClose={(e, reason) => {
if (reason === "backdropClick") {
return;
}
handleClose(); // Close modal when clicking cross or cancel
}}
aria-labelledby="edit-manager-modal" aria-labelledby="edit-manager-modal"
> >
<Box <Box
@ -446,23 +134,34 @@ const EditManagerModal: React.FC<EditManagerModalProps> = ({
{/* Horizontal Line */} {/* Horizontal Line */}
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} /> <Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
{/* Input Fields */} {/* Input Fields (2 inputs per row) */}
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}> <Box sx={{ display: "flex", flexWrap: "wrap", gap: 2 }}>
{/* Manager Name */} {/* Manager Name and Email in one row */}
<Box <Box sx={{ flex: "1 1 48%" }}>
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
}}
>
<Typography variant="body2" fontWeight={500} mb={0.5}> <Typography variant="body2" fontWeight={500} mb={0.5}>
Manager Name Manager Name
</Typography> </Typography>
<Controller <Controller
name="name" name="name"
control={control} control={control}
rules={{ required: "Manager Name is required" }} rules={{
required: "Manager Name is required",
minLength: {
value: 3,
message:
"Minimum 3 characters required",
},
maxLength: {
value: 30,
message:
"Maximum 30 characters allowed",
},
pattern: {
value: /^[A-Za-z\s]+$/, // Only letters and spaces are allowed
message:
"Manager Name must only contain letters and spaces",
},
}}
render={({ field }) => ( render={({ field }) => (
<CustomTextField <CustomTextField
{...field} {...field}
@ -471,19 +170,35 @@ const EditManagerModal: React.FC<EditManagerModalProps> = ({
size="small" size="small"
error={!!errors.name} error={!!errors.name}
helperText={errors.name?.message} helperText={errors.name?.message}
/> />
)} )}
/> />
</Box> </Box>
{/* Registered Address */} <Box sx={{ flex: "1 1 48%" }}>
<Box <Typography variant="body2" fontWeight={500} mb={0.5}>
sx={{ Email
display: "flex", </Typography>
flexDirection: "column", <Controller
width: "100%", 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>
{/* Registered Address and Phone Number in one row */}
<Box sx={{ flex: "1 1 48%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}> <Typography variant="body2" fontWeight={500} mb={0.5}>
Registered Address Registered Address
</Typography> </Typography>
@ -508,42 +223,7 @@ const EditManagerModal: React.FC<EditManagerModalProps> = ({
/> />
</Box> </Box>
{/* Email */} <Box sx={{ flex: "1 1 48%" }}>
<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}> <Typography variant="body2" fontWeight={500} mb={0.5}>
Phone Number Phone Number
</Typography> </Typography>

View file

@ -87,7 +87,12 @@ const EditVehicleModal: React.FC<EditVehicleModalProps> = ({
return ( return (
<Modal <Modal
open={open} open={open}
onClose={handleClose} onClose={(e, reason) => {
if (reason === "backdropClick") {
return;
}
handleClose(); // Close modal when clicking cross or cancel
}}
aria-labelledby="edit-vehicle-modal" aria-labelledby="edit-vehicle-modal"
> >
<Box <Box
@ -145,7 +150,24 @@ const EditVehicleModal: React.FC<EditVehicleModalProps> = ({
<Controller <Controller
name="name" name="name"
control={control} control={control}
rules={{ required: "Vehicle Name is required" }} rules={{
required: "Vehicle Name is required",
minLength: {
value: 3,
message:
"Minimum 3 characters required",
},
maxLength: {
value: 30,
message:
"Maximum 30 characters allowed",
},
pattern: {
value: /^[A-Za-z\s]+$/, // Only letters and spaces are allowed
message:
"Vehicle Name must only contain letters and spaces",
},
}}
render={({ field }) => ( render={({ field }) => (
<CustomTextField <CustomTextField
{...field} {...field}
@ -154,6 +176,7 @@ const EditVehicleModal: React.FC<EditVehicleModalProps> = ({
size="small" size="small"
error={!!errors.name} error={!!errors.name}
helperText={errors.name?.message} helperText={errors.name?.message}
/> />
)} )}
/> />

View file

@ -52,7 +52,7 @@ export default function MenuContent({ hidden }: PropType) {
userRole === "admin" && { userRole === "admin" && {
text: "Vehicles", text: "Vehicles",
icon: <ManageAccountsOutlinedIcon />, icon: <ManageAccountsOutlinedIcon />,
url: "/panel/vehicles", // Placeholder for now url: "/panel/vehicle-list", // Placeholder for now
}, },
]; ];

View file

@ -1,5 +1,5 @@
import { Box, Button, Modal, Typography } from "@mui/material"; import { Box, Button, Modal, Typography } from "@mui/material";
import CloseIcon from '@mui/icons-material/Close'; import CloseIcon from "@mui/icons-material/Close";
type Props = { type Props = {
open: boolean; open: boolean;
@ -26,17 +26,48 @@ export default function LogoutModal({
setLogoutModal, setLogoutModal,
handlelogout, handlelogout,
}: Props) { }: Props) {
// Function to prevent closing the modal when clicking outside
const handleClose = (event: React.SyntheticEvent) => {
// Close only when clicking the close button, not the backdrop
setLogoutModal(false);
};
return ( return (
<Modal <Modal
open={open} open={open}
onClose={(e, reason) =>
reason !== "backdropClick" && setLogoutModal(false)
} // Prevent closing on backdrop click
aria-labelledby="modal-modal-title" aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description" aria-describedby="modal-modal-description"
BackdropProps={{
onClick: (e) => e.stopPropagation(), // Stop propagation on backdrop click to prevent closing the modal
}}
> >
<Box sx={style}> <Box sx={style}>
<Typography id="modal-title" variant="h5" fontWeight="bold" sx={{ width: "100%" }}> <Typography
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%" }}> 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" }}>Logout</Box> <Box sx={{ flex: 1, textAlign: "center" }}>Logout</Box>
<Box onClick={() => setLogoutModal(false)} sx={{ cursor: "pointer", display: "flex", alignItems: "center" }}> <Box
onClick={() => setLogoutModal(false)}
sx={{
cursor: "pointer",
display: "flex",
alignItems: "center",
}}
>
<CloseIcon /> <CloseIcon />
</Box> </Box>
</Box> </Box>

View file

@ -0,0 +1,117 @@
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?: string | 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 UserViewModal({ open, setViewModal, id }: Props) {
const { users } = useSelector(
(state: RootState) => state.userReducer // Assuming users are stored in userReducer
);
const [selectedUser, setSelectedUser] = useState<any>(null);
useEffect(() => {
if (id) {
const user = users.find((user) => user.id === id);
setSelectedUser(user || null);
}
}, [id, users]);
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" }}>
{selectedUser?.name || "User"}'s Details
</Box>
<Box
onClick={() => setViewModal(false)}
sx={{
cursor: "pointer",
display: "flex",
alignItems: "center",
}}
>
<CloseIcon />
</Box>
</Box>
</Typography>
<Divider sx={{ width: "100%" }} />
{selectedUser ? (
<Grid container spacing={2} sx={{ width: "80%" }}>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Name:</strong>{" "}
<Typography variant="body2">
{selectedUser.name}
</Typography>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Email:</strong>{" "}
<Typography variant="body2">
{selectedUser.email}
</Typography>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Phone:</strong>{" "}
<Typography variant="body2">
{selectedUser.phone ?? "N/A"}
</Typography>
</Typography>
</Grid>
</Grid>
) : (
<Typography align="center">
No user found with this ID
</Typography>
)}
</Box>
</Modal>
);
}

View file

@ -9,9 +9,9 @@ import {
TextField, TextField,
Typography, Typography,
Grid, Grid,
IconButton,
Link, Link,
InputAdornment 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";
@ -22,7 +22,9 @@ import { toast } from "sonner";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { Visibility, VisibilityOff } from "@mui/icons-material"; import { Visibility, VisibilityOff } from "@mui/icons-material";
import { Card, SignInContainer } from "./styled.css.tsx"; import { Card, SignInContainer } from "./styled.css.tsx";
import {
CustomIconButton,
} from "../../../components/AddUserModel/styled.css.tsx";
interface ILoginForm { interface ILoginForm {
email: string; email: string;
password: string; password: string;
@ -30,7 +32,6 @@ 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 {
@ -49,6 +50,11 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
setOpen(false); setOpen(false);
}; };
const togglePasswordVisibility = (e: React.MouseEvent) => {
e.preventDefault(); // Prevent focus loss
setShowPassword((prev) => !prev);
};
const onSubmit: SubmitHandler<ILoginForm> = async (data: ILoginForm) => { const onSubmit: SubmitHandler<ILoginForm> = async (data: ILoginForm) => {
try { try {
const response = await dispatch(loginUser(data)).unwrap(); const response = await dispatch(loginUser(data)).unwrap();
@ -90,11 +96,9 @@ 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: "auto",
height: "auto", // ✅ Allows the height to adjust dynamically
}} }}
> >
<Typography <Typography
variant="h3" variant="h3"
sx={{ sx={{
@ -110,8 +114,16 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
Welcome Back! Welcome Back!
</Typography> </Typography>
<Card variant="outlined" sx={{ width: { xs: "100%", sm: "300px",lg:"408px" }, padding: "24px", borderRadius: "12px", backgroundColor: "#1E1E1E", border: "1px solid #4B5255" }}> <Card
variant="outlined"
sx={{
width: { xs: "100%", sm: "300px", lg: "408px" },
padding: "24px",
borderRadius: "12px",
backgroundColor: "#1E1E1E",
border: "1px solid #4B5255",
}}
>
<Box <Box
component="form" component="form"
onSubmit={handleSubmit(onSubmit)} onSubmit={handleSubmit(onSubmit)}
@ -122,24 +134,42 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
gap: 2, gap: 2,
}} }}
> >
<Typography component="h1" variant="h4" sx={{ textAlign: "center", color: "white", fontFamily: "Gilroy", fontSize: "24px" }}>Login</Typography> <Typography
component="h1"
variant="h4"
sx={{
textAlign: "center",
color: "white",
fontFamily: "Gilroy",
fontSize: "24px",
}}
>
Login
</Typography>
<Typography <Typography
component="h6" component="h6"
variant="subtitle2" variant="subtitle2"
sx={{ textAlign: "center", color: "white", fontFamily: "Gilroy", fontSize: "16px" }} sx={{
textAlign: "center",
color: "white",
fontFamily: "Gilroy",
fontSize: "16px",
}}
> >
Log in with your email and password Log in with your email and password
</Typography> </Typography>
{/* -------------------------------- Email Field ----------------- */} {/* -------------------------------- Email Field ----------------- */}
<FormControl sx={{ width: "100%" }}> <FormControl sx={{ width: "100%" }}>
<FormLabel <FormLabel
htmlFor="email" htmlFor="email"
sx={{ sx={{
fontSize: { xs: "0.9rem", sm: "1rem" }, fontSize: {
xs: "0.9rem",
sm: "1rem",
},
color: "white", color: "white",
fontFamily: "Gilroy, sans-serif", // ✅ Apply Gilroy font fontFamily: "Gilroy, sans-serif",
}} }}
> >
Email Email
@ -152,14 +182,17 @@ 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: "Please enter a valid email address.", message:
"Please enter a valid email address.",
}, },
}} }}
render={({ field }) => ( render={({ field }) => (
<TextField <TextField
{...field} {...field}
error={!!errors.email} error={!!errors.email}
helperText={errors.email?.message} helperText={
errors.email?.message
}
id="email" id="email"
type="email" type="email"
placeholder="Email" placeholder="Email"
@ -168,32 +201,57 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
required required
fullWidth fullWidth
variant="outlined" variant="outlined"
color={errors.email ? "error" : "primary"} color={
errors.email
? "error"
: "primary"
}
InputProps={{ InputProps={{
sx: { sx: {
height: "50px", height: "50px",
alignItems: "center", alignItems: "center",
backgroundColor: "#1E1F1F", backgroundColor:
fontFamily: "Gilroy, sans-serif", "#1E1F1F",
fontFamily:
"Gilroy, sans-serif",
}, },
}} }}
sx={{ sx={{
"& .MuiOutlinedInput-root": { "& .MuiOutlinedInput-root":
backgroundColor: "#1E1F1F", {
backgroundColor:
"#1E1F1F",
borderRadius: "4px", borderRadius: "4px",
"& fieldset": { borderColor: "#4b5255" }, "& fieldset": {
"&:hover fieldset": { borderColor: "#4b5255" }, borderColor:
"&.Mui-focused fieldset": { borderColor: "#4b5255" }, "#4b5255",
},
"&:hover fieldset":
{
borderColor:
"#4b5255",
},
"&.Mui-focused fieldset":
{
borderColor:
"#4b5255",
},
}, },
"& input": { "& input": {
color: "white", color: "white",
fontSize: { xs: "0.9rem", sm: "1rem" }, fontSize: {
fontFamily: "Gilroy, sans-serif", xs: "0.9rem",
sm: "1rem",
}, },
"& .MuiInputBase-input::placeholder": { fontFamily:
"Gilroy, sans-serif",
},
"& .MuiInputBase-input::placeholder":
{
color: "white", color: "white",
opacity: 1, opacity: 1,
fontFamily: "Gilroy, sans-serif", fontFamily:
"Gilroy, sans-serif",
}, },
}} }}
/> />
@ -201,13 +259,15 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
/> />
</FormControl> </FormControl>
{/* -------------------------------- Password Field ----------------- */} {/* -------------------------------- Password Field ----------------- */}
<FormControl sx={{ width: "100%" }}> <FormControl sx={{ width: "100%" }}>
<FormLabel <FormLabel
htmlFor="password" htmlFor="password"
sx={{ sx={{
fontSize: { xs: "0.9rem", sm: "1rem" }, fontSize: {
xs: "0.9rem",
sm: "1rem",
},
color: "white", color: "white",
fontFamily: "Gilroy, sans-serif", fontFamily: "Gilroy, sans-serif",
}} }}
@ -222,11 +282,11 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
required: "Password is required", required: "Password is required",
minLength: { minLength: {
value: 6, value: 6,
message: "Password must be at least 6 characters long.", message:
"Password must be at least 6 characters long.",
}, },
pattern: { pattern: {
value: value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{6,}$/,
/^(?=.*[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.",
}, },
@ -235,58 +295,84 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
<TextField <TextField
{...field} {...field}
error={!!errors.password} error={!!errors.password}
helperText={errors.password?.message} helperText={
errors.password?.message
}
name="password" name="password"
placeholder="Password" placeholder="Password"
type={showPassword ? "text" : "password"} type={
showPassword
? "text"
: "password"
}
id="password" id="password"
autoComplete="current-password" autoComplete="current-password"
required required
fullWidth fullWidth
variant="outlined" variant="outlined"
color={errors.password ? "error" : "primary"} color={
errors.password
? "error"
: "primary"
}
onMouseDown={
togglePasswordVisibility
}
InputProps={{ InputProps={{
sx: {
height: "50px",
fontFamily: "Gilroy, sans-serif", // Apply Gilroy font
},
endAdornment: ( endAdornment: (
<IconButton <InputAdornment position="end">
onClick={() => setShowPassword((prev) => !prev)} <CustomIconButton
aria-label="toggle password visibility"
onClick={
togglePasswordVisibility
}
edge="end" 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 />} {showPassword ? (
</IconButton> <VisibilityOff />
) : (
<Visibility />
)}
</CustomIconButton>
</InputAdornment>
), ),
}} }}
sx={{ sx={{
"& .MuiOutlinedInput-root": { "& .MuiOutlinedInput-root":
backgroundColor: "#1E1F1F", {
backgroundColor:
"#1E1F1F",
borderRadius: "4px", borderRadius: "4px",
"& fieldset": { borderColor: "#4b5255" }, "& fieldset": {
"&:hover fieldset": { borderColor: "#4b5255" }, borderColor:
"&.Mui-focused fieldset": { borderColor: "#4b5255" }, "#4b5255",
},
"&:hover fieldset":
{
borderColor:
"#4b5255",
},
"&.Mui-focused fieldset":
{
borderColor:
"#4b5255",
},
}, },
"& input": { "& input": {
color: "white", color: "white",
fontSize: { xs: "0.9rem", sm: "1rem" }, fontSize: {
fontFamily: "Gilroy, sans-serif", xs: "0.9rem",
sm: "1rem",
}, },
"& .MuiInputBase-input::placeholder": { fontFamily:
"Gilroy, sans-serif",
},
"& .MuiInputBase-input::placeholder":
{
color: "white", color: "white",
opacity: 1, opacity: 1,
fontFamily: "Gilroy, sans-serif", fontFamily:
"Gilroy, sans-serif",
}, },
}} }}
/> />
@ -294,7 +380,6 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
/> />
</FormControl> </FormControl>
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
@ -311,23 +396,27 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
sx={{ sx={{
width: 20, width: 20,
height: 20, height: 20,
fontFamily: "Gilroy, sans-serif", fontFamily:
"Gilroy, sans-serif",
border: "2px solid #4b5255", border: "2px solid #4b5255",
borderRadius: "4px", borderRadius: "4px",
backgroundColor: "transparent", backgroundColor:
"transparent",
"&:hover": { "&:hover": {
backgroundColor: "transparent", backgroundColor:
"transparent",
}, },
"&.Mui-checked": { "&.Mui-checked": {
backgroundColor: "transparent", backgroundColor:
"transparent",
borderColor: "#4b5255", borderColor: "#4b5255",
"&:hover": { "&:hover": {
backgroundColor: "transparent", backgroundColor:
"transparent",
}, },
}, },
}} }}
/> />
} }
label="Remember me" label="Remember me"
/> />
@ -342,12 +431,10 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
fontFamily: "Gilroy, sans-serif", fontFamily: "Gilroy, sans-serif",
color: "#01579b", color: "#01579b",
textDecoration: "none", // ✅ Removes underline textDecoration: "none", // ✅ Removes underline
}} }}
> >
Forgot password? Forgot password?
</Link> </Link>
</Box> </Box>
<ForgotPassword <ForgotPassword
open={open} open={open}

View file

@ -9,10 +9,9 @@ import {
datePickersCustomizations, datePickersCustomizations,
treeViewCustomizations, treeViewCustomizations,
} from "./theme/customizations"; } from "./theme/customizations";
import DashboardLayout from "../../layouts/DashboardLayout";
import AppTheme from "../../shared-theme/AppTheme"; import AppTheme from "../../shared-theme/AppTheme";
import MainGrid from "../../components/MainGrid"; import MainGrid from "../../components/MainGrid";
import AdminList from "../AdminList";
const xThemeComponents = { const xThemeComponents = {
...chartsCustomizations, ...chartsCustomizations,

View file

@ -1,27 +1,18 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { import { Box, Button, TextField, Typography } from "@mui/material";
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 CustomTable, { Column } from "../../components/CustomTable";
import AddManagerModal from "../../components/AddManagerModal"; import AddManagerModal from "../../components/AddManagerModal";
import EditManagerModal from "../../components/EditManagerModal";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import DeleteModal from "../../components/Modals/DeleteModal"; import { RootState, AppDispatch } from "../../redux/store/store";
import { import {
managerList, managerList,
addManager, addManager,
updateManager, updateManager,
deleteManager, deleteManager,
} from "../../redux/slices/managerSlice"; } from "../../redux/slices/managerSlice";
import { RootState, AppDispatch } from "../../redux/store/store";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import EditManagerModal from "../../components/EditManagerModal"; import DeleteModal from "../../components/Modals/DeleteModal";
export default function ManagerList() { export default function ManagerList() {
const [addModalOpen, setAddModalOpen] = useState<boolean>(false); const [addModalOpen, setAddModalOpen] = useState<boolean>(false);
@ -43,7 +34,7 @@ export default function ManagerList() {
}, [dispatch]); }, [dispatch]);
const handleClickOpen = () => { const handleClickOpen = () => {
setRowData(null); // Reset row data when opening for new admin setRowData(null); // Reset row data when opening for new manager
setAddModalOpen(true); setAddModalOpen(true);
}; };
@ -77,37 +68,44 @@ export default function ManagerList() {
registeredAddress: string registeredAddress: string
) => { ) => {
try { try {
// Creating the managerData object to match the expected payload structure await dispatch(
const managerData = { updateManager({
id,
name, name,
email, email,
phone, phone,
registeredAddress, registeredAddress,
}; })
);
// Dispatching the updateManager action with the correct payload structure await dispatch(managerList()); // Refresh the list after update
await dispatch(updateManager({ id, managerData })); handleCloseModal(); // Close modal after update
await dispatch(managerList()); // Refresh the manager list after updating
handleCloseModal(); // Close the modal after updating
} catch (error) { } catch (error) {
console.error("Update failed", error); console.error("Update failed", error);
} }
}; };
// Remove 'stationName' from columns
const categoryColumns: Column[] = [ const categoryColumns: Column[] = [
{ id: "srno", label: "Sr No" }, { id: "srno", label: "Sr No" },
{ id: "name", label: "Name" }, { id: "name", label: "Name" },
{ id: "email", label: "Email" }, { id: "email", label: "Email" },
{ id: "phone", label: "Phone" }, { id: "phone", label: "Phone" },
{ id: "registeredAddress", label: "Station Location" }, { id: "registeredAddress", label: "Station Location" },
{ id: "action", label: "Action", align: "center" }, { id: "action", label: "Action", align: "center" },
]; ];
// Update rows to remove 'stationName' const filteredManagers = managers?.filter(
const categoryRows = managers?.map( (manager) =>
manager.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
manager.email?.toLowerCase().includes(searchTerm.toLowerCase()) ||
manager.phone?.toLowerCase().includes(searchTerm.toLowerCase()) ||
manager.registeredAddress
?.toLowerCase()
.includes(searchTerm.toLowerCase())
);
const categoryRows = filteredManagers?.length
? filteredManagers?.map(
( (
manager: { manager: {
id: number; id: number;
@ -125,7 +123,8 @@ export default function ManagerList() {
phone: manager.phone ?? "NA", phone: manager.phone ?? "NA",
registeredAddress: manager?.registeredAddress ?? "NA", registeredAddress: manager?.registeredAddress ?? "NA",
}) })
); )
: [];
return ( return (
<> <>

View file

@ -1,9 +1,6 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Box, Button, TextField, Typography } from "@mui/material";
import AddEditCategoryModal from "../../components/AddEditCategoryModal";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import CustomTable, { Column } from "../../components/CustomTable"; import CustomTable, { Column } from "../../components/CustomTable";
import DeleteModal from "../../components/Modals/DeleteModal";
import { RootState } from "../../redux/reducers"; import { RootState } from "../../redux/reducers";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { AppDispatch } from "../../redux/store/store"; import { AppDispatch } from "../../redux/store/store";

View file

@ -33,6 +33,9 @@ export const managerList = createAsyncThunk<
{ rejectValue: string } { rejectValue: string }
>("fetchManagers", async (_, { rejectWithValue }) => { >("fetchManagers", async (_, { rejectWithValue }) => {
try { try {
const token = localStorage?.getItem("authToken");
if (!token) throw new Error("No token found");
const response = await http.get("manager-list"); const response = await http.get("manager-list");
if (!response.data?.data) throw new Error("Invalid API response"); if (!response.data?.data) throw new Error("Invalid API response");
return response.data.data; return response.data.data;
@ -46,7 +49,6 @@ export const managerList = createAsyncThunk<
// Create Manager (Async Thunk) // Create Manager (Async Thunk)
export const addManager = createAsyncThunk< export const addManager = createAsyncThunk<
Manager,
Manager, Manager,
{ rejectValue: string } { rejectValue: string }
>("addManager", async (data, { rejectWithValue }) => { >("addManager", async (data, { rejectWithValue }) => {
@ -103,7 +105,7 @@ export const deleteManager = createAsyncThunk<
// Create Slice // Create Slice
const managerSlice = createSlice({ const managerSlice = createSlice({
name: "maanger", name: "manager",
initialState, initialState,
reducers: {}, reducers: {},
extraReducers: (builder) => { extraReducers: (builder) => {
@ -147,13 +149,13 @@ const managerSlice = createSlice({
}) })
.addCase(updateManager.fulfilled, (state, action) => { .addCase(updateManager.fulfilled, (state, action) => {
state.loading = false; state.loading = false;
// const updatedManager = action.payload; const updatedManager = action.payload;
// const index = state.managers.findIndex( const index = state.managers.findIndex(
// (manager) => manager.id === updatedManager.id (manager) => manager.id === updatedManager.id
// ); );
// if (index !== -1) { if (index !== -1) {
// state.managers[index] = updatedManager; // Update the manager in the state state.managers[index] = updatedManager; // Update the manager in the state
// } }
}) })
.addCase(updateManager.rejected, (state, action) => { .addCase(updateManager.rejected, (state, action) => {
state.loading = false; state.loading = false;
@ -166,9 +168,6 @@ const managerSlice = createSlice({
}) })
.addCase(deleteManager.fulfilled, (state, action) => { .addCase(deleteManager.fulfilled, (state, action) => {
state.loading = false; state.loading = false;
state.managers = state.managers.filter(
(manager) => manager.id !== action.payload
);
}) })
.addCase(deleteManager.rejected, (state, action) => { .addCase(deleteManager.rejected, (state, action) => {
state.loading = false; state.loading = false;

View file

@ -56,7 +56,6 @@ export const createUser = createAsyncThunk<
name: string; name: string;
email: string; email: string;
phone: string; phone: string;
}, },
{ rejectValue: string } { rejectValue: string }
>("/CreateUser", async (data, { rejectWithValue }) => { >("/CreateUser", async (data, { rejectWithValue }) => {
@ -70,8 +69,6 @@ export const createUser = createAsyncThunk<
} }
}); });
export const updateUser = createAsyncThunk( export const updateUser = createAsyncThunk(
"updateUser", "updateUser",
async ({ id, ...userData }: User, { rejectWithValue }) => { async ({ id, ...userData }: User, { rejectWithValue }) => {
@ -87,6 +84,24 @@ export const updateUser = createAsyncThunk(
} }
} }
); );
export const deleteUser = createAsyncThunk<
string,
string,
{ rejectValue: string }
>("deleteUser", async (id, { rejectWithValue }) => {
try {
const response = await http.delete(`/${id}/delete-user`);
toast.success(response.data?.message);
return id;
} catch (error: any) {
toast.error("Error deleting the user" + error);
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
}
});
const userSlice = createSlice({ const userSlice = createSlice({
name: "fetchUsers", name: "fetchUsers",
initialState, initialState,
@ -133,6 +148,18 @@ const userSlice = createSlice({
}) })
.addCase(updateUser.rejected, (state) => { .addCase(updateUser.rejected, (state) => {
state.loading = false; state.loading = false;
})
.addCase(deleteUser.pending, (state) => {
state.loading = true;
})
.addCase(deleteUser.fulfilled, (state, action) => {
state.loading = false;
state.users = state.users.filter(
(user) => String(user.id) !== String(action.payload)
);
})
.addCase(deleteUser.rejected, (state) => {
state.loading = false;
}); });
}, },
}); });

View file

@ -2,30 +2,31 @@ import { Routes as BaseRoutes, Navigate, Route } from "react-router-dom";
import React, { lazy, Suspense } from "react"; import React, { lazy, Suspense } from "react";
import LoadingComponent from "./components/Loading"; import LoadingComponent from "./components/Loading";
import DashboardLayout from "./layouts/DashboardLayout"; import DashboardLayout from "./layouts/DashboardLayout";
import RoleList from "./pages/RoleList"; // import RoleList from "./pages/RoleList";
import AddEditRolePage from "./pages/AddEditRolePage"; // import AddEditRolePage from "./pages/AddEditRolePage";
import VehicleList from "./pages/VehicleList"; // import VehicleList from "./pages/VehicleList";
// Page imports // Page imports
const Login = lazy(() => import("./pages/Auth/Login")); const Login = lazy(() => import("./pages/Auth/Login"));
const SignUp = lazy(() => import("./pages/Auth/SignUp")); const SignUp = lazy(() => import("./pages/Auth/SignUp"));
const Dashboard = lazy(() => import("./pages/Dashboard")); const Dashboard = lazy(() => import("./pages/Dashboard"));
const Vehicles = lazy(() => import("./pages/VehicleList")); const VehicleList = lazy(() => import("./pages/VehicleList"));
const AdminList = lazy(() => import("./pages/AdminList")); const AdminList = lazy(() => import("./pages/AdminList"));
const ProfilePage = lazy(() => import("./pages/ProfilePage")); 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 AddEditRolePage = lazy(() => import("./pages/AddEditRolePage"));
const RoleList = lazy(() => import("./pages/RoleList"));
const ManagerList = lazy(() => import("./pages/ManagerList")); const ManagerList = lazy(() => import("./pages/ManagerList"));
interface ProtectedRouteProps { interface ProtectedRouteProps {
caps: string[]; // caps: string[];
component: React.ReactNode; component: React.ReactNode;
} }
// Protected Route Component // Protected Route Component
const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ caps, component }) => { const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ component }) => {
if (!localStorage.getItem("authToken")) { if (!localStorage.getItem("authToken")) {
return <Navigate to="/login" replace />; return <Navigate to="/login" replace />;
} }
@ -42,11 +43,6 @@ export default function AppRouter() {
{/* Auth Routes */} {/* Auth Routes */}
<Route path=""> <Route path="">
<Route
path=""
element={<Navigate to="/login" replace />}
index
/>
<Route path="login" element={<Login />} /> <Route path="login" element={<Login />} />
<Route path="signup" element={<SignUp />} /> <Route path="signup" element={<SignUp />} />
</Route> </Route>
@ -57,25 +53,17 @@ export default function AppRouter() {
path="dashboard" path="dashboard"
element={ element={
<ProtectedRoute <ProtectedRoute
caps={[]}
component={<Dashboard />} component={<Dashboard />}
/> />
} }
/> />
<Route
path="vehicles"
element={
<ProtectedRoute
caps={[]}
component={<Vehicles />}
/>
}
/>
<Route <Route
path="admin-list" path="admin-list"
element={ element={
<ProtectedRoute <ProtectedRoute
caps={[]}
component={<AdminList />} component={<AdminList />}
/> />
} }
@ -84,7 +72,7 @@ export default function AppRouter() {
path="user-list" path="user-list"
element={ element={
<ProtectedRoute <ProtectedRoute
caps={[]}
component={<UserList />} component={<UserList />}
/> />
} }
@ -94,7 +82,7 @@ export default function AppRouter() {
path="manager-list" path="manager-list"
element={ element={
<ProtectedRoute <ProtectedRoute
caps={[]}
component={<ManagerList />} component={<ManagerList />}
/> />
} }
@ -105,7 +93,7 @@ export default function AppRouter() {
path="role-list" path="role-list"
element={ element={
<ProtectedRoute <ProtectedRoute
caps={[]}
component={<RoleList />} component={<RoleList />}
/> />
} }
@ -114,7 +102,7 @@ export default function AppRouter() {
path="vehicle-list" path="vehicle-list"
element={ element={
<ProtectedRoute <ProtectedRoute
caps={[]}
component={<VehicleList />} component={<VehicleList />}
/> />
} }
@ -123,7 +111,7 @@ export default function AppRouter() {
path="permissions" path="permissions"
element={ element={
<ProtectedRoute <ProtectedRoute
caps={[]}
component={<AddEditRolePage />} component={<AddEditRolePage />}
/> />
} }
@ -132,7 +120,7 @@ export default function AppRouter() {
path="profile" path="profile"
element={ element={
<ProtectedRoute <ProtectedRoute
caps={[]}
component={<ProfilePage />} component={<ProfilePage />}
/> />
} }