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", // You can replace this with dynamic role if needed
roleName: "Manager", // Add a role name (you can replace this with a dynamic value 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,49 +106,42 @@ export default function AddManagerModal({ open, handleClose }: Props) {
{/* Form */} {/* Form */}
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
{/* First Row - Manager Name */} {/* Manager Name */}
<Box sx={{ display: "flex", gap: 2 }}> <Box
{/* Manager Name */} sx={{ display: "flex", flexDirection: "column", mb: 2 }}
<Box >
sx={{ <Typography variant="body2" fontWeight={500}>
display: "flex", Manager Name
flexDirection: "column", </Typography>
width: "100%", <CustomTextField
}} fullWidth
> placeholder="Enter Manager Name"
<Typography variant="body2" fontWeight={500}> size="small"
Manager Name error={!!errors.name}
</Typography> helperText={errors.name ? errors.name.message : ""}
<CustomTextField {...register("name", {
fullWidth required: "Manager Name is required",
placeholder="Enter Manager Name" minLength: {
size="small" value: 3,
error={!!errors.name} message: "Minimum 3 characters required",
helperText={ },
errors.name ? errors.name.message : "" maxLength: {
} value: 30,
{...register("name", { message: "Maximum 30 characters allowed",
required: "Manager Name is required", },
minLength: { pattern: {
value: 2, 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,91 +164,73 @@ export default function AddManagerModal({ open, handleClose }: Props) {
</Box> </Box>
{/* Password */} {/* Password */}
<Box sx={{ display: "flex", gap: 2 }}> <Box sx={{ flex: 1 }}>
<Box <Typography variant="body2" fontWeight={500}>
sx={{ Password
display: "flex", </Typography>
flexDirection: "column", <Controller
width: "100%", 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 }) => (
<Typography variant="body2" fontWeight={500}> <CustomTextField
Password {...field}
</Typography> required
<Controller placeholder="Enter Password"
name="password" type={
control={control} showPassword ? "text" : "password"
rules={{ }
required: "Password is required", fullWidth
minLength: { color={
value: 6, errors.password
message: ? "error"
"Password must be at least 6 characters long.", : "primary"
}, }
pattern: { size="small"
value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{6,}$/, // onMouseDown={togglePasswordVisibility}
message: InputProps={{
"Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character.", endAdornment: (
}, <InputAdornment position="end">
}} <CustomIconButton
render={({ field }) => ( aria-label="toggle password visibility"
<CustomTextField onClick={
{...field} togglePasswordVisibility
required }
placeholder="Enter Password" edge="end"
type={ >
showPassword {showPassword ? (
? "text" <VisibilityOff />
: "password" ) : (
} <Visibility />
id="password" )}
fullWidth </CustomIconButton>
color={ </InputAdornment>
errors.password ),
? "error" }}
: "primary" error={!!errors.password}
} helperText={errors.password?.message}
size="small" />
InputProps={{ )}
endAdornment: ( />
<InputAdornment position="end"> </Box>
<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> </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";
@ -18,12 +17,12 @@ import {
Button, Button,
InputAdornment, InputAdornment,
Menu, Menu,
IconButton, IconButton,
Pagination, Pagination,
TextField, TextField,
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import MoreHorizRoundedIcon from "@mui/icons-material/MoreHorizRounded"; import MoreHorizRoundedIcon from "@mui/icons-material/MoreHorizRounded";
import DeleteModal from "../Modals/DeleteModal"; import DeleteModal from "../Modals/DeleteModal";
import { AppDispatch } from "../../redux/store/store"; import { AppDispatch } from "../../redux/store/store";
import ViewModal from "../Modals/ViewModal"; import ViewModal from "../Modals/ViewModal";
@ -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) {
@ -123,7 +116,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;
@ -158,24 +151,27 @@ const CustomTable: React.FC<CustomTableProps> = ({
setDeleteModal(false); // Close the modal only after deletion setDeleteModal(false); // Close the modal only after deletion
handleClose(); handleClose();
}; };
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);
}; };
@ -188,12 +184,12 @@ const CustomTable: React.FC<CustomTableProps> = ({
handleClose(); handleClose();
}; };
const filteredRows = rows.filter( const filteredRows = rows.filter(
(row) => (row) =>
(row.name && (row.name &&
row.name.toLowerCase().includes(searchQuery.toLowerCase())) || row.name.toLowerCase().includes(searchQuery.toLowerCase())) ||
false false
); );
const indexOfLastRow = currentPage * usersPerPage; const indexOfLastRow = currentPage * usersPerPage;
const indexOfFirstRow = indexOfLastRow - usersPerPage; const indexOfFirstRow = indexOfLastRow - usersPerPage;
@ -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,24 +134,35 @@ 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={{
render={({ field }) => ( 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 }) => (
<CustomTextField <CustomTextField
{...field} {...field}
fullWidth fullWidth
@ -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,81 +1,112 @@
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;
setLogoutModal: Function; setLogoutModal: Function;
handlelogout: any; handlelogout: any;
}; };
const style = { const style = {
position: "absolute", position: "absolute",
top: "50%", top: "50%",
left: "50%", left: "50%",
transform: "translate(-50%, -50%)", transform: "translate(-50%, -50%)",
width: 330, width: 330,
bgcolor: "background.paper", bgcolor: "background.paper",
borderRadius: 1.5, borderRadius: 1.5,
boxShadow: 24, boxShadow: 24,
p: 3, p: 3,
}; };
const btnStyle = { py: 1, px: 5, width: "50%", textTransform: "capitalize" }; const btnStyle = { py: 1, px: 5, width: "50%", textTransform: "capitalize" };
export default function LogoutModal({ export default function LogoutModal({
open, open,
setLogoutModal, setLogoutModal,
handlelogout, handlelogout,
}: Props) { }: Props) {
return ( // Function to prevent closing the modal when clicking outside
<Modal const handleClose = (event: React.SyntheticEvent) => {
open={open} // Close only when clicking the close button, not the backdrop
aria-labelledby="modal-modal-title" setLogoutModal(false);
aria-describedby="modal-modal-description" };
>
<Box sx={style}> return (
<Typography id="modal-title" variant="h5" fontWeight="bold" sx={{ width: "100%" }}> <Modal
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%" }}> open={open}
<Box sx={{ flex: 1, textAlign: "center" }}>Logout</Box> onClose={(e, reason) =>
<Box onClick={() => setLogoutModal(false)} sx={{ cursor: "pointer", display: "flex", alignItems: "center" }}> reason !== "backdropClick" && setLogoutModal(false)
<CloseIcon /> } // Prevent closing on backdrop click
</Box> aria-labelledby="modal-modal-title"
</Box> aria-describedby="modal-modal-description"
</Typography> BackdropProps={{
<Typography onClick: (e) => e.stopPropagation(), // Stop propagation on backdrop click to prevent closing the modal
id="modal-modal-description" }}
sx={{ mt: 2 }} >
align="center" <Box sx={style}>
> <Typography
Are you sure you want to Logout? id="modal-title"
</Typography> variant="h5"
<Box fontWeight="bold"
sx={{ sx={{ width: "100%" }}
display: "flex", >
justifyContent: "space-between", <Box
mt: 4, sx={{
gap: 2, display: "flex",
}} alignItems: "center",
> justifyContent: "space-between",
<Button width: "100%",
variant="contained" }}
color="error" >
type="button" <Box sx={{ flex: 1, textAlign: "center" }}>Logout</Box>
sx={btnStyle} <Box
onClick={() => setLogoutModal(false)} onClick={() => setLogoutModal(false)}
> sx={{
Cancel cursor: "pointer",
</Button> display: "flex",
<Button alignItems: "center",
variant="contained" }}
type="button" >
color="primary" <CloseIcon />
sx={btnStyle} </Box>
onClick={() => handlelogout()} </Box>
> </Typography>
Logout <Typography
</Button> id="modal-modal-description"
</Box> sx={{ mt: 2 }}
</Box> align="center"
</Modal> >
); Are you sure you want to Logout?
} </Typography>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
mt: 4,
gap: 2,
}}
>
<Button
variant="contained"
color="error"
type="button"
sx={btnStyle}
onClick={() => setLogoutModal(false)}
>
Cancel
</Button>
<Button
variant="contained"
type="button"
color="primary"
sx={btnStyle}
onClick={() => handlelogout()}
>
Logout
</Button>
</Box>
</Box>
</Modal>
);
}

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,9 +32,8 @@ 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,
@ -48,17 +49,22 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
const handleClose = () => { const handleClose = () => {
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();
if (response?.data?.token) { if (response?.data?.token) {
router("/panel/dashboard"); router("/panel/dashboard");
}
} catch (error: any) {
console.log("Login failed:", error);
} }
} catch (error: any) { };
console.log("Login failed:", error);
}
};
return ( return (
<AppTheme {...props}> <AppTheme {...props}>
@ -80,21 +86,19 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
{/* Form Section */} {/* Form Section */}
<Grid <Grid
item item
xs={12} xs={12}
md={5} md={5}
sx={{ sx={{
backgroundColor: "black", backgroundColor: "black",
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
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,178 +134,251 @@ 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
<Typography component="h1"
component="h6" variant="h4"
variant="subtitle2" sx={{
sx={{ textAlign: "center", color: "white", fontFamily: "Gilroy", fontSize: "16px" }} textAlign: "center",
> color: "white",
Log in with your email and password fontFamily: "Gilroy",
</Typography> fontSize: "24px",
}}
>
Login
</Typography>
<Typography
component="h6"
variant="subtitle2"
sx={{
textAlign: "center",
color: "white",
fontFamily: "Gilroy",
fontSize: "16px",
}}
>
Log in with your email and password
</Typography>
{/* -------------------------------- Email Field ----------------- */}
<FormControl sx={{ width: "100%" }}>
<FormLabel
htmlFor="email"
sx={{
fontSize: {
xs: "0.9rem",
sm: "1rem",
},
color: "white",
fontFamily: "Gilroy, sans-serif",
}}
>
Email
</FormLabel>
<Controller
name="email"
control={control}
defaultValue=""
rules={{
required: "Email is required",
pattern: {
value: /\S+@\S+\.\S+/,
message:
"Please enter a valid email address.",
},
}}
render={({ field }) => (
<TextField
{...field}
error={!!errors.email}
helperText={
errors.email?.message
}
id="email"
type="email"
placeholder="Email"
autoComplete="email"
autoFocus
required
fullWidth
variant="outlined"
color={
errors.email
? "error"
: "primary"
}
InputProps={{
sx: {
height: "50px",
alignItems: "center",
backgroundColor:
"#1E1F1F",
fontFamily:
"Gilroy, sans-serif",
},
}}
sx={{
"& .MuiOutlinedInput-root":
{
backgroundColor:
"#1E1F1F",
borderRadius: "4px",
"& fieldset": {
borderColor:
"#4b5255",
},
"&:hover fieldset":
{
borderColor:
"#4b5255",
},
"&.Mui-focused fieldset":
{
borderColor:
"#4b5255",
},
},
"& input": {
color: "white",
fontSize: {
xs: "0.9rem",
sm: "1rem",
},
fontFamily:
"Gilroy, sans-serif",
},
"& .MuiInputBase-input::placeholder":
{
color: "white",
opacity: 1,
fontFamily:
"Gilroy, sans-serif",
},
}}
/>
)}
/>
</FormControl>
{/* -------------------------------- Email Field ----------------- */} {/* -------------------------------- Password Field ----------------- */}
<FormControl sx={{ width: "100%" }}> <FormControl sx={{ width: "100%" }}>
<FormLabel <FormLabel
htmlFor="email" htmlFor="password"
sx={{ sx={{
fontSize: { xs: "0.9rem", sm: "1rem" }, fontSize: {
color: "white", xs: "0.9rem",
fontFamily: "Gilroy, sans-serif", // ✅ Apply Gilroy font sm: "1rem",
}} },
> color: "white",
Email fontFamily: "Gilroy, sans-serif",
</FormLabel> }}
<Controller >
name="email" Password
control={control} </FormLabel>
defaultValue="" <Controller
rules={{ name="password"
required: "Email is required", control={control}
pattern: { defaultValue=""
value: /\S+@\S+\.\S+/, rules={{
message: "Please enter a valid email address.", required: "Password is required",
}, minLength: {
}} value: 6,
render={({ field }) => ( message:
<TextField "Password must be at least 6 characters long.",
{...field} },
error={!!errors.email} pattern: {
helperText={errors.email?.message} value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{6,}$/,
id="email" message:
type="email" "Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character.",
placeholder="Email" },
autoComplete="email" }}
autoFocus render={({ field }) => (
required <TextField
fullWidth {...field}
variant="outlined" error={!!errors.password}
color={errors.email ? "error" : "primary"} helperText={
InputProps={{ errors.password?.message
sx: { }
height: "50px", name="password"
alignItems: "center", placeholder="Password"
backgroundColor: "#1E1F1F", type={
fontFamily: "Gilroy, sans-serif", showPassword
}, ? "text"
}} : "password"
sx={{ }
"& .MuiOutlinedInput-root": { id="password"
backgroundColor: "#1E1F1F", autoComplete="current-password"
borderRadius: "4px", required
"& fieldset": { borderColor: "#4b5255" }, fullWidth
"&:hover fieldset": { borderColor: "#4b5255" }, variant="outlined"
"&.Mui-focused fieldset": { borderColor: "#4b5255" }, color={
}, errors.password
"& input": { ? "error"
color: "white", : "primary"
fontSize: { xs: "0.9rem", sm: "1rem" }, }
fontFamily: "Gilroy, sans-serif", onMouseDown={
}, togglePasswordVisibility
"& .MuiInputBase-input::placeholder": { }
color: "white", InputProps={{
opacity: 1, endAdornment: (
fontFamily: "Gilroy, sans-serif", <InputAdornment position="end">
}, <CustomIconButton
}} aria-label="toggle password visibility"
/> onClick={
)} togglePasswordVisibility
/> }
</FormControl> edge="end"
>
{showPassword ? (
{/* -------------------------------- Password Field ----------------- */} <VisibilityOff />
<FormControl sx={{ width: "100%" }}> ) : (
<FormLabel <Visibility />
htmlFor="password" )}
sx={{ </CustomIconButton>
fontSize: { xs: "0.9rem", sm: "1rem" }, </InputAdornment>
color: "white", ),
fontFamily: "Gilroy, sans-serif", }}
}} sx={{
> "& .MuiOutlinedInput-root":
Password {
</FormLabel> backgroundColor:
<Controller "#1E1F1F",
name="password" borderRadius: "4px",
control={control} "& fieldset": {
defaultValue="" borderColor:
rules={{ "#4b5255",
required: "Password is required", },
minLength: { "&:hover fieldset":
value: 6, {
message: "Password must be at least 6 characters long.", borderColor:
}, "#4b5255",
pattern: { },
value: "&.Mui-focused fieldset":
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{6,}$/, {
message: borderColor:
"Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character.", "#4b5255",
}, },
}} },
render={({ field }) => ( "& input": {
<TextField color: "white",
{...field} fontSize: {
error={!!errors.password} xs: "0.9rem",
helperText={errors.password?.message} sm: "1rem",
name="password" },
placeholder="Password" fontFamily:
type={showPassword ? "text" : "password"} "Gilroy, sans-serif",
id="password" },
autoComplete="current-password" "& .MuiInputBase-input::placeholder":
required {
fullWidth color: "white",
variant="outlined" opacity: 1,
color={errors.password ? "error" : "primary"} fontFamily:
InputProps={{ "Gilroy, sans-serif",
sx: { },
height: "50px", }}
fontFamily: "Gilroy, sans-serif", // Apply Gilroy font />
}, )}
/>
endAdornment: ( </FormControl>
<IconButton
onClick={() => setShowPassword((prev) => !prev)}
edge="end"
sx={{
color: "white",
padding: 0,
margin: 0,
backgroundColor: "transparent",
border: "none",
boxShadow: "none",
"&:hover": { backgroundColor: "transparent" },
"&:focus": { outline: "none", border: "none" },
}}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
),
}}
sx={{
"& .MuiOutlinedInput-root": {
backgroundColor: "#1E1F1F",
borderRadius: "4px",
"& fieldset": { borderColor: "#4b5255" },
"&:hover fieldset": { borderColor: "#4b5255" },
"&.Mui-focused fieldset": { borderColor: "#4b5255" },
},
"& input": {
color: "white",
fontSize: { xs: "0.9rem", sm: "1rem" },
fontFamily: "Gilroy, sans-serif",
},
"& .MuiInputBase-input::placeholder": {
color: "white",
opacity: 1,
fontFamily: "Gilroy, sans-serif",
},
}}
/>
)}
/>
</FormControl>
<Box <Box
sx={{ sx={{
@ -304,50 +389,52 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
flexWrap: "wrap", flexWrap: "wrap",
}} }}
> >
<FormControlLabel <FormControlLabel
control={ control={
<Checkbox <Checkbox
value="remember" value="remember"
sx={{ sx={{
width: 20, width: 20,
height: 20, height: 20,
fontFamily: "Gilroy, sans-serif", fontFamily:
border: "2px solid #4b5255", "Gilroy, sans-serif",
borderRadius: "4px", border: "2px solid #4b5255",
backgroundColor: "transparent", borderRadius: "4px",
"&:hover": { backgroundColor:
backgroundColor: "transparent", "transparent",
}, "&:hover": {
"&.Mui-checked": { backgroundColor:
backgroundColor: "transparent", "transparent",
borderColor: "#4b5255", },
"&:hover": { "&.Mui-checked": {
backgroundColor: "transparent", backgroundColor:
}, "transparent",
}, borderColor: "#4b5255",
}} "&:hover": {
/> backgroundColor:
"transparent",
} },
label="Remember me" },
/> }}
/>
<Link }
component="button" label="Remember me"
type="button" />
onClick={handleClickOpen}
variant="body2"
sx={{
alignSelf: "center",
fontFamily: "Gilroy, sans-serif",
color: "#01579b",
textDecoration: "none", // ✅ Removes underline
}}
>
Forgot password?
</Link>
<Link
component="button"
type="button"
onClick={handleClickOpen}
variant="body2"
sx={{
alignSelf: "center",
fontFamily: "Gilroy, sans-serif",
color: "#01579b",
textDecoration: "none", // ✅ Removes underline
}}
>
Forgot password?
</Link>
</Box> </Box>
<ForgotPassword <ForgotPassword
open={open} open={open}
@ -359,7 +446,7 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
disabled={!isValid} disabled={!isValid}
sx={{ sx={{
color: "white", color: "white",
fontFamily: "Gilroy, sans-serif", fontFamily: "Gilroy, sans-serif",
backgroundColor: "#52ACDF", backgroundColor: "#52ACDF",
"&:hover": { "&:hover": {
backgroundColor: "#52ACDF", backgroundColor: "#52ACDF",

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,56 +68,64 @@ 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({
name, id,
email, name,
phone, email,
registeredAddress, phone,
}; registeredAddress,
})
// Dispatching the updateManager action with the correct payload structure );
await dispatch(updateManager({ id, managerData })); await dispatch(managerList()); // Refresh the list after update
await dispatch(managerList()); // Refresh the manager list after updating handleCloseModal(); // Close modal after update
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: { manager.email?.toLowerCase().includes(searchTerm.toLowerCase()) ||
id: number; manager.phone?.toLowerCase().includes(searchTerm.toLowerCase()) ||
name: string; manager.registeredAddress
email: string; ?.toLowerCase()
phone: string; .includes(searchTerm.toLowerCase())
registeredAddress: string;
},
index: number
) => ({
id: manager?.id,
srno: index + 1,
name: manager?.name,
email: manager?.email,
phone: manager.phone ?? "NA",
registeredAddress: manager?.registeredAddress ?? "NA",
})
); );
const categoryRows = filteredManagers?.length
? filteredManagers?.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 ( return (
<> <>
<CustomTable <CustomTable

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 />}
/> />
} }