Merge pull request 'Vehicle & Managers API integeration' (#21) from frontend/apiIntegration into develop

Reviewed-on: DigiMantra/digiev_frontend#21
This commit is contained in:
Mohit kalshan 2025-03-12 05:37:10 +00:00
commit 7793c4b031
28 changed files with 3673 additions and 1925 deletions

View file

@ -1,17 +1,20 @@
import React, { useEffect } from "react"; import React, { useEffect, useState } from "react";
import { import {
Box, Box,
Button, Button,
Dialog, Typography,
DialogActions, Modal,
DialogContent, InputAdornment,
DialogTitle,
IconButton,
TextField,
} from "@mui/material"; } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import Visibility from "@mui/icons-material/Visibility";
import VisibilityOff from "@mui/icons-material/VisibilityOff";
import { useForm, Controller } from "react-hook-form"; import { useForm, Controller } from "react-hook-form";
import { Visibility, VisibilityOff } from "@mui/icons-material"; import {
CustomIconButton,
CustomTextField,
} from "../AddUserModel/styled.css.tsx";
//By Jaanvi : Edit Model :: 11-feb-25 //By Jaanvi : Edit Model :: 11-feb-25
interface AddEditCategoryModalProps { interface AddEditCategoryModalProps {
@ -44,6 +47,7 @@ const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({
handleUpdate, handleUpdate,
editRow, editRow,
}) => { }) => {
const [showPassword, setShowPassword] = React.useState(false);
const { const {
control, control,
handleSubmit, handleSubmit,
@ -59,7 +63,7 @@ const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({
password: "", password: "",
}, },
}); });
const onSubmit = (data: FormData) => { const onSubmit = (data: FormData) => {
if (editRow) { if (editRow) {
handleUpdate( handleUpdate(
@ -77,6 +81,10 @@ const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({
reset(); reset();
}; };
const togglePasswordVisibility = () => {
setShowPassword((prev) => !prev);
};
useEffect(() => { useEffect(() => {
if (editRow) { if (editRow) {
setValue("name", editRow.name); setValue("name", editRow.name);
@ -88,210 +96,327 @@ const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({
} }
}, [editRow, setValue, reset]); }, [editRow, setValue, reset]);
const [showPassword, setShowPassword] = React.useState(false);
return ( return (
<Dialog <Modal
open={open} open={open}
onClose={handleClose} onClose={handleClose}
maxWidth="md" aria-labelledby="add-edit-category-modal"
fullWidth
PaperProps={{
component: "form",
onSubmit: handleSubmit(onSubmit),
}}
> >
<DialogTitle <Box
sx={{ sx={{
display: "flex", position: "absolute",
alignItems: "center", top: "50%",
justifyContent: "space-between", left: "50%",
transform: "translate(-50%, -50%)",
width: 600,
bgcolor: "background.paper",
boxShadow: 24,
p: 3,
borderRadius: 2,
}} }}
> >
{editRow ? "Edit Admin" : "Add Admin"} {/* Header */}
<Box <Box
onClick={handleClose}
sx={{ sx={{
cursor: "pointer",
display: "flex", display: "flex",
justifyContent: "space-between",
alignItems: "center", alignItems: "center",
}} }}
> >
<CloseIcon /> <Typography variant="h6" fontWeight={600}>
{editRow ? "Edit Admin" : "Add Admin"}
</Typography>
<CustomIconButton onClick={handleClose}>
<CloseIcon />
</CustomIconButton>
</Box> </Box>
</DialogTitle>
<DialogContent> {/* Horizontal Line */}
<Controller <Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
name="name"
control={control}
rules={{
required: "Admin Name is required",
minLength: {
value: 3,
message: "Minimum 3 characters required",
},
maxLength: {
value: 30,
message: "Maximum 30 characters allowed",
},
}}
render={({ field }) => (
<TextField
{...field}
autoFocus
required
margin="dense"
label="Admin Name"
type="text"
fullWidth
variant="standard"
error={!!errors.name}
helperText={errors.name?.message}
/>
)}
/>
<Controller {/* Form */}
name="email" <form onSubmit={handleSubmit(onSubmit)}>
control={control} {/* Input Fields */}
rules={{ <Box
required: "Email is required", sx={{
pattern: { display: "flex",
value: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, flexDirection: "column",
message: "Invalid email address", gap: 2,
},
}}
render={({ field }) => (
<TextField
{...field}
required
margin="dense"
label="Email"
type="email"
fullWidth
variant="standard"
error={!!errors.email}
helperText={errors.email?.message}
/>
)}
/>
{!editRow && (
<Controller
name="password"
control={control}
rules={{
required: "password is required",
}} }}
render={({ field }) => ( >
<> {/* First Row - Two Inputs */}
<Box sx={{position:"relative" }}> <Box sx={{ display: "flex", gap: 2 }}>
<TextField <Box
{...field} sx={{
required display: "flex",
margin="dense" flexDirection: "column",
label="Password" width: "50%",
type={showPassword ? "text" : "password"} }}
id="password" >
autoComplete="current-password" <Typography variant="body2" fontWeight={500}>
autoFocus Admin Name
fullWidth </Typography>
variant="standard" <Controller
error={!!errors.password} name="name"
helperText={errors.password?.message} control={control}
/> rules={{
<IconButton required: "Admin Name is required",
sx={{ minLength: {
position: "absolute", value: 3,
top: "60%", message:
right: "10px", "Minimum 3 characters required",
background: "none", },
borderColor: "transparent", maxLength: {
transform: "translateY(-50%)", value: 30,
"&:hover": { message:
backgroundColor: "transparent", "Maximum 30 characters allowed",
borderColor: "transparent",
}, },
}} }}
onClick={() => render={({ field }) => (
setShowPassword((prev) => !prev) <CustomTextField
} {...field}
> required
{showPassword ? ( placeholder="Enter Admin Name"
<VisibilityOff /> fullWidth
) : ( size="small"
<Visibility /> error={!!errors.name}
helperText={errors.name?.message}
/>
)} )}
</IconButton> />
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "50%",
}}
>
<Typography variant="body2" fontWeight={500}>
Email
</Typography>
<Controller
name="email"
control={control}
rules={{
required: "Email is required",
pattern: {
value: /\S+@\S+\.\S+/,
message:
"Please enter a valid email address.",
},
}}
render={({ field }) => (
<CustomTextField
{...field}
error={!!errors.email}
helperText={errors.email?.message}
id="email"
type="email"
placeholder="Email"
required
fullWidth
color={
errors.email
? "error"
: "primary"
}
/>
)}
/>
</Box>
</Box>
{/* Second Row - Two Inputs */}
<Box sx={{ display: "flex", gap: 2 }}>
{!editRow && (
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "50%",
}}
>
<Typography
variant="body2"
fontWeight={500}
>
Password
</Typography>
<Controller
name="password"
control={control}
rules={{
required: "Password is required",
minLength: {
value: 6,
message:
"Password must be at least 6 characters long.",
},
pattern: {
value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{6,}$/,
message:
"Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character.",
},
}}
render={({ field }) => (
<CustomTextField
{...field}
required
placeholder="Enter Password"
type={
showPassword
? "text"
: "password"
}
id="password"
fullWidth
color={
errors.password
? "error"
: "primary"
}
size="small"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<CustomIconButton
aria-label="toggle password visibility"
onClick={
togglePasswordVisibility
}
edge="end"
>
{showPassword ? (
<VisibilityOff />
) : (
<Visibility />
)}
</CustomIconButton>
</InputAdornment>
),
}}
error={!!errors.password}
helperText={
errors.password?.message
}
/>
)}
/>
</Box> </Box>
</> )}
)} <Box
/> sx={{
)} display: "flex",
<Controller flexDirection: "column",
name="phone" width: "50%",
control={control} }}
rules={{ >
required: "Phone number is required", <Typography variant="body2" fontWeight={500}>
pattern: { Phone Number
value: /^[0-9]*$/, </Typography>
message: "Only numbers are allowed", <Controller
}, name="phone"
minLength: { control={control}
value: 6, rules={{
message: "Phone number must be exactly 6 digits", required: "Phone number is required",
}, pattern: {
maxLength: { value: /^[0-9]*$/,
value: 14, message: "Only numbers are allowed",
message: "Phone number must be exactly 14 digits", },
}, minLength: {
}} value: 6,
render={({ field }) => ( message:
<TextField "Phone number must be at least 6 digits",
{...field} },
required maxLength: {
margin="dense" value: 14,
label="Phone Number" message:
type="tel" "Phone number must be at most 14 digits",
fullWidth },
variant="standard" }}
error={!!errors.phone} render={({ field }) => (
helperText={errors.phone?.message} <CustomTextField
/> {...field}
)} required
/> placeholder="Enter Phone Number"
size="small"
error={!!errors.phone}
helperText={errors.phone?.message}
/>
)}
/>
</Box>
</Box>
<Controller {/* Third Row - One Input */}
name="registeredAddress" <Box
control={control} sx={{
rules={{ display: "flex",
required: "Address is required", flexDirection: "column",
maxLength: { width: "50%",
value: 100, }}
message: "Address cannot exceed 100 characters", >
}, <Typography variant="body2" fontWeight={500}>
}} Address
render={({ field }) => ( </Typography>
<TextField <Controller
{...field} name="registeredAddress"
required control={control}
margin="dense" rules={{
label="Address" required: "Address is required",
type="text" maxLength: {
fullWidth value: 100,
variant="standard" message:
error={!!errors.registeredAddress} "Address cannot exceed 100 characters",
helperText={errors.registeredAddress?.message} },
/> }}
)} render={({ field }) => (
/> <CustomTextField
</DialogContent> {...field}
required
placeholder="Enter Address"
fullWidth
size="small"
error={!!errors.registeredAddress}
helperText={
errors.registeredAddress?.message
}
/>
)}
/>
</Box>
</Box>
<DialogActions> {/* Submit Button */}
<Button onClick={handleClose}>Cancel</Button> <Box
<Button type="submit">{editRow ? "Update" : "Create"}</Button> sx={{
</DialogActions> display: "flex",
</Dialog> justifyContent: "flex-end",
width: "100%",
mt: 3,
}}
>
<Button
type="submit"
sx={{
backgroundColor: "#52ACDF",
color: "white",
borderRadius: "8px",
width: "117px",
"&:hover": { backgroundColor: "#439BC1" },
}}
>
{editRow ? "Update Admin" : "Add Admin"}
</Button>
</Box>
</form>
</Box>
</Modal>
); );
}; };

View file

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

View file

@ -1,17 +1,27 @@
import React, { useEffect } from "react"; import React, { useEffect, useState } from "react";
import { import {
Box, Box,
Button, Button,
Dialog, Typography,
DialogActions,
DialogContent,
DialogTitle,
TextField, TextField,
Modal,
IconButton,
InputAdornment,
styled,
} from "@mui/material"; } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import Visibility from "@mui/icons-material/Visibility";
import VisibilityOff from "@mui/icons-material/VisibilityOff";
import { useForm, Controller } from "react-hook-form"; import { useForm, Controller } from "react-hook-form";
import { CustomIconButton, CustomTextField } from "./styled.css.tsx";
//By Jaanvi : Edit Model :: 11-feb-25
interface FormData {
name: string;
email: string;
password: string;
phone: string;
}
interface AddUserModalProps { interface AddUserModalProps {
open: boolean; open: boolean;
handleClose: () => void; handleClose: () => void;
@ -20,52 +30,55 @@ interface AddUserModalProps {
id: string, id: string,
name: string, name: string,
email: string, email: string,
password: string,
phone: string, phone: string,
password: string
) => void; ) => void;
editRow: any; editRow: any;
} }
interface FormData {
name: string;
email: string;
phone: string;
password: string;
}
const AddUserModal: React.FC<AddUserModalProps> = ({ const AddUserModal: React.FC<AddUserModalProps> = ({
open, open,
handleClose, handleClose,
handleCreate, handleCreate,
handleUpdate,
editRow, editRow,
}) => { }) => {
const [showPassword, setShowPassword] = useState(false);
const { const {
control, control,
handleSubmit, handleSubmit,
formState: { errors }, formState: { errors },
setValue,
reset, reset,
} = useForm<FormData>({ } = useForm<FormData>({
defaultValues: { defaultValues: {
name: "", name: "",
email: "", email: "",
password: "",
phone: "", phone: "",
password: "",
}, },
}); });
// useEffect(() => {
// if (editRow) {
// setValue("name", editRow.name);
// setValue("email", editRow.email);
// setValue("password", editRow.password);
// setValue("phone", editRow.phone);
// } else {
// reset();
// }
// }, [editRow, setValue, reset]);
const onSubmit = (data: FormData) => { const onSubmit = (data: FormData) => {
if (editRow) { if (editRow) {
handleUpdate( handleUpdate(
editRow.id, editRow.id,
data.name, data.name,
data.email, data.email,
data.phone, data.password,
data.password data.phone
); );
} else { } else {
handleCreate(data); handleCreate(data);
} }
@ -73,156 +86,287 @@ const AddUserModal: React.FC<AddUserModalProps> = ({
reset(); reset();
}; };
const togglePasswordVisibility = () => {
setShowPassword((prev) => !prev);
};
return ( return (
<Dialog <Modal
open={open} open={open}
onClose={handleClose} onClose={handleClose}
maxWidth="md" aria-labelledby="add-user-modal"
fullWidth
PaperProps={{
component: "form",
onSubmit: handleSubmit(onSubmit),
}}
> >
<DialogTitle <Box
sx={{ sx={{
display: "flex", position: "absolute",
alignItems: "center", top: "50%",
justifyContent: "space-between", left: "50%",
transform: "translate(-50%, -50%)",
width: 600, // Adjusted the width for a wider modal
bgcolor: "background.paper",
boxShadow: 24,
p: 3,
borderRadius: 2,
}} }}
> >
{editRow ? "Edit User" : "Add User"} {/* Header */}
<Box <Box
onClick={handleClose}
sx={{ sx={{
cursor: "pointer",
display: "flex", display: "flex",
justifyContent: "space-between",
alignItems: "center", alignItems: "center",
}} }}
> >
<CloseIcon /> <Typography variant="h6" fontWeight={600}>
{editRow ? "Edit User" : "Add User"}
</Typography>
<CustomIconButton onClick={handleClose}>
<CloseIcon />
</CustomIconButton>
</Box> </Box>
</DialogTitle>
<DialogContent> {/* Horizontal Line */}
<Controller <Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
name="name"
control={control}
rules={{
required: "Admin Name is required",
minLength: {
value: 3,
message: "Minimum 3 characters required",
},
maxLength: {
value: 30,
message: "Maximum 30 characters allowed",
},
}}
render={({ field }) => (
<TextField
{...field}
autoFocus
required
margin="dense"
label="User Name"
type="text"
fullWidth
variant="standard"
error={!!errors.name}
helperText={errors.name?.message}
/>
)}
/>
<Controller {/* Form */}
name="email" <form onSubmit={handleSubmit(onSubmit)}>
control={control} {/* Input Fields */}
rules={{ <Box
required: "Email is required", sx={{
pattern: { display: "flex",
value: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, flexDirection: "column",
message: "Invalid email address", gap: 2,
}, }}
}} >
render={({ field }) => ( {/* First Row - Two Inputs */}
<TextField <Box sx={{ display: "flex", gap: 2 }}>
{...field} <Box
required sx={{
margin="dense" display: "flex",
label="Email" flexDirection: "column",
type="email" width: "100%",
fullWidth }}
variant="standard" >
error={!!errors.email} <Typography variant="body2" fontWeight={500}>
helperText={errors.email?.message} User Name
/> </Typography>
)} <Controller
/> name="name"
<Controller control={control}
name="password" rules={{
control={control} required: "User Name is required",
rules={{ minLength: {
required: "password is required", value: 3,
}} message:
render={({ field }) => ( "Minimum 3 characters required",
<TextField },
{...field} maxLength: {
required value: 30,
margin="dense" message:
label="Password" "Maximum 30 characters allowed",
type="password" },
fullWidth }}
variant="standard" render={({ field }) => (
error={!!errors.password} <CustomTextField
helperText={errors.password?.message} {...field}
/> required
)} placeholder="Enter User Name"
/> fullWidth
<Controller size="small"
name="phone" error={!!errors.name}
control={control} helperText={errors.name?.message}
rules={{ />
required: "Phone number is required", )}
pattern: { />
value: /^[0-9]*$/, </Box>
message: "Only numbers are allowed",
},
minLength: {
value: 6,
message: "Phone number must be exactly 6 digits",
},
maxLength: {
value: 14,
message: "Phone number must be exactly 14 digits",
},
}}
render={({ field }) => (
<TextField
{...field}
required
margin="dense"
label="Phone Number"
type="tel"
fullWidth
variant="standard"
error={!!errors.phone}
helperText={errors.phone?.message}
/>
)}
/>
</DialogContent>
<DialogActions> <Box
<Button onClick={handleClose}>Cancel</Button> sx={{
<Button type="submit">Create</Button> display: "flex",
</DialogActions> flexDirection: "column",
</Dialog> width: "100%",
}}
>
<Typography variant="body2" fontWeight={500}>
Email
</Typography>
<Controller
name="email"
control={control}
rules={{
required: "Email is required",
pattern: {
value: /\S+@\S+\.\S+/,
message:
"Please enter a valid email address.",
},
}}
render={({ field }) => (
<CustomTextField
{...field}
error={!!errors.email}
helperText={errors.email?.message}
id="email"
type="email"
placeholder="Email"
required
fullWidth
color={
errors.email
? "error"
: "primary"
}
/>
)}
/>
</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}>
Password
</Typography>
<Controller
name="password"
control={control}
rules={{
required: "Password is required",
minLength: {
value: 6,
message:
"Password must be at least 6 characters long.",
},
pattern: {
value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{6,}$/,
message:
"Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character.",
},
}}
render={({ field }) => (
<CustomTextField
{...field}
required
placeholder="Enter Password"
type={
showPassword
? "text"
: "password"
}
id="password"
fullWidth
color={
errors.password
? "error"
: "primary"
}
size="small"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<CustomIconButton
aria-label="toggle password visibility"
onClick={
togglePasswordVisibility
}
edge="end"
>
{showPassword ? (
<VisibilityOff />
) : (
<Visibility />
)}
</CustomIconButton>
</InputAdornment>
),
}}
error={!!errors.password}
helperText={
errors.password?.message
}
/>
)}
/>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
}}
>
<Typography variant="body2" fontWeight={500}>
Phone Number
</Typography>
<Controller
name="phone"
control={control}
rules={{
required: "Phone number is required",
pattern: {
value: /^[0-9]*$/,
message: "Only numbers are allowed",
},
minLength: {
value: 6,
message:
"Phone number must be at least 6 digits",
},
maxLength: {
value: 14,
message:
"Phone number must be at most 14 digits",
},
}}
render={({ field }) => (
<CustomTextField
{...field}
required
placeholder="Enter Phone Number"
size="small"
error={!!errors.phone}
helperText={errors.phone?.message}
/>
)}
/>
</Box>
</Box>
</Box>
{/* Submit Button */}
<Box
sx={{
display: "flex",
justifyContent: "flex-end",
width: "100%",
mt: 3,
}}
>
<Button
type="submit"
sx={{
backgroundColor: "#52ACDF",
color: "white",
borderRadius: "8px",
width: "117px",
"&:hover": { backgroundColor: "#439BC1" },
}}
>
{editRow ? "Update User" : "Add User"}
</Button>
</Box>
</form>
</Box>
</Modal>
); );
}; };
export default AddUserModal; export default AddUserModal;
function handleUpdate(id: any, name: string, email: string, phone: string, password: string) {
throw new Error("Function not implemented.");
}

View file

@ -0,0 +1,26 @@
import { styled } from "@mui/material/styles";
import { IconButton, TextField } from "@mui/material";
export const CustomIconButton = styled(IconButton)({
backgroundColor: "transparent",
"&:hover": {
backgroundColor: "#272727",
},
"*:where([data-mui-color-scheme='dark']) &": {
backgroundColor: "transparent",
border: "none",
},
});
// Custom TextField with different placeholder color
export const CustomTextField = styled(TextField)({
"& .MuiInputBase-input::placeholder": {
color: "#D9D8D8",
opacity: 1,
},
"& .MuiInputBase-root.Mui-focused .MuiInputBase-input::placeholder": {
color: "darkgray",
},
});

View file

@ -0,0 +1,252 @@
import { useForm } from "react-hook-form";
import {
Box,
Button,
Typography,
Modal,
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { CustomIconButton, CustomTextField } from "../AddUserModel/styled.css.tsx";
export default function AddVehicleModal({
open,
handleClose,
handleAddVehicle,
}) {
const {
register,
handleSubmit,
formState: { errors },
reset,
} = useForm();
const onSubmit = (data: any) => {
handleAddVehicle(data); // Add vehicle to table
handleClose(); // Close modal after adding
reset();
};
return (
<Modal
open={open}
onClose={handleClose}
aria-labelledby="add-vehicle-modal"
>
<Box
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "background.paper",
boxShadow: 24,
p: 3,
borderRadius: 2,
}}
>
{/* Header */}
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Typography variant="h6" fontWeight={600}>
Add Vehicle
</Typography>
<CustomIconButton onClick={handleClose}>
<CloseIcon />
</CustomIconButton>
</Box>
{/* Horizontal Line */}
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
{/* Form */}
<form onSubmit={handleSubmit(onSubmit)}>
{/* 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}>
Vehicle Name
</Typography>
<CustomTextField
fullWidth
placeholder="Enter Vehicle Name"
size="small"
error={!!errors.name}
helperText={
errors.name ? errors.name.message : ""
}
{...register("name", {
required: "Vehicle Name is required",
minLength: {
value: 2,
message:
"Vehicle Name must be at least 2 characters long",
},
})}
/>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
}}
>
<Typography variant="body2" fontWeight={500}>
Company
</Typography>
<CustomTextField
fullWidth
placeholder="Enter Company Name"
size="small"
error={!!errors.company}
helperText={
errors.company
? errors.company.message
: ""
}
{...register("company", {
required: "Company is required",
minLength: {
value: 5,
message:
"Company must be at least 5 characters long",
},
})}
/>
</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}>
Model Name
</Typography>
<CustomTextField
fullWidth
placeholder="Enter Model Name"
size="small"
error={!!errors.modelName}
helperText={
errors.modelName
? errors.modelName.message
: ""
}
{...register("modelName", {
required: "Model Name is required",
})}
/>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
}}
>
<Typography variant="body2" fontWeight={500}>
Charge Type
</Typography>
<CustomTextField
fullWidth
placeholder="Enter Charge Type"
size="small"
error={!!errors.chargeType}
helperText={
errors.chargeType
? errors.chargeType.message
: ""
}
{...register("chargeType", {
required: "Charge Type is required",
})}
/>
</Box>
</Box>
{/* Third Row - Image URL */}
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
}}
>
<Typography variant="body2" fontWeight={500}>
Image URL
</Typography>
<CustomTextField
fullWidth
placeholder="Enter Image URL"
size="small"
error={!!errors.imageUrl}
helperText={
errors.imageUrl
? errors.imageUrl.message
: ""
}
{...register("imageUrl", {
required: "Image URL is required",
pattern: {
value: /^(https?:\/\/)?((([a-zA-Z\d]([a-zA-Z\d-]*[a-zA-Z\d])*)\.)+[a-zA-Z]{2,}|((\d{1,3}\.){3}\d{1,3}))(:\d+)?(\/[-a-zA-Z\d%_.~+]*)*(\?[;&a-zA-Z\d%_.~+=-]*)?(#[-a-zA-Z\d_]*)?$/,
message: "Please enter a valid URL",
},
})}
/>
</Box>
</Box>
{/* Submit Button */}
<Box
sx={{
display: "flex",
justifyContent: "flex-end",
mt: 3,
}}
>
<Button
type="submit"
sx={{
backgroundColor: "#52ACDF",
color: "white",
borderRadius: "8px",
width: "117px",
"&:hover": { backgroundColor: "#439BC1" },
}}
>
Add Vehicle
</Button>
</Box>
</form>
</Box>
</Modal>
);
}

View file

@ -9,14 +9,16 @@ import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow"; import TableRow from "@mui/material/TableRow";
import Paper, { paperClasses } from "@mui/material/Paper"; import Paper, { paperClasses } from "@mui/material/Paper";
import { adminList, deleteAdmin } from "../../redux/slices/adminSlice"; import { adminList, deleteAdmin } from "../../redux/slices/adminSlice";
import { deleteManager } from "../../redux/slices/managerSlice"; import { vehicleList, deleteVehicle } from "../../redux/slices/VehicleSlice";
import { deleteManager, managerList } from "../../redux/slices/managerSlice";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { import {
Box, Box,
Button, Button,
IconButton,
InputAdornment, InputAdornment,
Menu, Menu,
IconButton,
Pagination, Pagination,
TextField, TextField,
Typography, Typography,
@ -25,9 +27,14 @@ 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";
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 {
CustomIconButton,
} from "../AddUserModel/styled.css.tsx";
import ManagerViewModal from "../Modals/ViewManagerModal";
// Styled components for customization // Styled components for customization
const StyledTableCell = styled(TableCell)(({ theme }) => ({ const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: { [`&.${tableCellClasses.head}`]: {
@ -73,7 +80,7 @@ interface CustomTableProps {
handleStatusToggle: (id: string, currentStatus: number) => void; handleStatusToggle: (id: string, currentStatus: number) => void;
tableType: string; // Adding tableType prop to change header text dynamically tableType: string; // Adding tableType prop to change header text dynamically
handleClickOpen: () => void; handleClickOpen: () => void;
handleDeleteButton: (id: string | number | undefined) => void; //handleDeleteButton: (id: string | number | undefined) => void;
} }
const CustomTable: React.FC<CustomTableProps> = ({ const CustomTable: React.FC<CustomTableProps> = ({
@ -97,6 +104,8 @@ 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) => {
@ -107,18 +116,13 @@ const CustomTable: React.FC<CustomTableProps> = ({
}; };
const handleViewButton = (id: string | undefined) => { // const handleViewButton = (id: string | undefined) => {
if (!id) { // if (!id) {
console.error("ID not found for viewing."); // console.error("ID not found for viewing.");
return; // return;
} // }
setViewModal(true); // setViewModal(true);
}; // };
const handleClose = () => { const handleClose = () => {
@ -132,16 +136,58 @@ const CustomTable: React.FC<CustomTableProps> = ({
return false; return false;
}; };
const handleDeleteButton = (id: string | undefined) => {
if (!id) {
console.error("ID not found for viewing."); const handleDeleteButton = (id: string | undefined) => {
return; if (!id) console.error("ID not found", id);
switch (tableType) {
case "admin":
dispatch(deleteAdmin(id || ""));
break;
case "vehicle":
dispatch(deleteVehicle(id || ""));
break;
case "manager":
dispatch(deleteManager(id || ""));
break;
default:
console.error("Unknown table type:", tableType);
return;
} }
setViewModal(true); setDeleteModal(false); // Close the modal only after deletion
handleClose();
}; };
const handleViewButton = (id: string | undefined) => {
if (!id) console.error("ID not found", id);
switch (tableType) {
case "admin":
dispatch(adminList());
break;
case "vehicle":
dispatch(vehicleList());
break;
case "manager":
dispatch(managerList());
break;
default:
console.error("Unknown table type:", tableType);
return;
}
dispatch(adminList());
setViewModal(false);
};
const handleToggleStatus = () => {
if (selectedRow) {
// Toggle the opposite of current status
const newStatus = selectedRow.statusValue === 1 ? 0 : 1;
handleStatusToggle(selectedRow.id, newStatus);
}
handleClose();
};
const filteredRows = rows.filter( const filteredRows = rows.filter(
(row) => (row) =>
(row.name && (row.name &&
@ -217,11 +263,15 @@ const filteredRows = rows.filter(
borderColor: "#52ACDF", borderColor: "#52ACDF",
}, },
}, },
"& .MuiInputBase-input::placeholder": {
color: "#D9D8D8",
opacity: 1,
},
}} }}
InputProps={{ InputProps={{
startAdornment: ( startAdornment: (
<InputAdornment position="start"> <InputAdornment position="start">
<SearchIcon sx={{ color: "#272727" }} /> <SearchIcon sx={{ color: "#52ACDF" }} />
</InputAdornment> </InputAdornment>
), ),
}} }}
@ -252,8 +302,8 @@ const filteredRows = rows.filter(
? "Role" ? "Role"
: tableType === "user" : tableType === "user"
? "User" ? "User"
: tableType === "managers" : tableType === "manager"
? "Managers" ? "Manager"
: tableType === "vehicle" : tableType === "vehicle"
? "Vehicle" ? "Vehicle"
: "Item"} : "Item"}
@ -330,7 +380,7 @@ const filteredRows = rows.filter(
) : column.id !== "action" ? ( ) : column.id !== "action" ? (
row[column.id] row[column.id]
) : ( ) : (
<IconButton <CustomIconButton
onClick={(e) => { onClick={(e) => {
handleClick(e, row); handleClick(e, row);
setRowData(row); // Store the selected row setRowData(row); // Store the selected row
@ -340,20 +390,13 @@ const filteredRows = rows.filter(
minWidth: 0, minWidth: 0,
width: "auto", width: "auto",
height: "auto", height: "auto",
backgroundColor:
"transparent",
color: "#FFFFFF", color: "#FFFFFF",
border: "none",
"&:hover": {
backgroundColor:
"transparent",
},
}} }}
> >
<MoreHorizRoundedIcon <MoreHorizRoundedIcon
sx={{ fontSize: "24px" }} sx={{ fontSize: "24px" }}
/> />
</IconButton> </CustomIconButton>
)} )}
</StyledTableCell> </StyledTableCell>
))} ))}
@ -411,6 +454,9 @@ const filteredRows = rows.filter(
[`& .${paperClasses.root}`]: { [`& .${paperClasses.root}`]: {
padding: 0, padding: 0,
}, },
"& .MuiList-root": {
background: "#272727", // Remove any divider under menu items
},
}} }}
> >
<Box <Box
@ -424,7 +470,7 @@ const filteredRows = rows.filter(
variant="text" variant="text"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
// setSelectedRow(row); // setSelectedRow(row);
setViewModal(true); setViewModal(true);
}} }}
color="primary" color="primary"
@ -437,16 +483,36 @@ const filteredRows = rows.filter(
> >
View View
</Button> </Button>
{viewModal && ( {viewModal && tableType === "admin" && (
<ViewModal <ViewModal
handleView={() => handleViewButton(selectedRow?.id)} handleView={() =>
open={viewModal} handleViewButton(selectedRow?.id)
setViewModal={setViewModal} }
id={selectedRow?.id} open={viewModal}
tableType={tableType} setViewModal={setViewModal}
/> id={selectedRow?.id}
)} />
)}
{viewModal && tableType === "vehicle" && (
<VehicleViewModal
handleView={() =>
handleViewButton(selectedRow?.id)
}
open={viewModal}
setViewModal={setViewModal}
id={selectedRow?.id}
/>
)}
{viewModal && tableType === "manager" && (
<ManagerViewModal
handleView={() =>
handleViewButton(selectedRow?.id)
}
open={viewModal}
setViewModal={setViewModal}
id={selectedRow?.id}
/>
)}
<Button <Button
variant="text" variant="text"
@ -485,14 +551,12 @@ const filteredRows = rows.filter(
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setDeleteModal(true); setDeleteModal(true);
}} }}
color="error" color="error"
sx={{ sx={{
justifyContent: "flex-start", justifyContent: "flex-start",
py: 0, py: 0,
fontWeight: "bold", fontWeight: "bold",
color: "red",
}} }}
> >
Delete Delete
@ -503,18 +567,16 @@ const filteredRows = rows.filter(
{/* Modals */} {/* Modals */}
{deleteModal && ( {deleteModal && (
<DeleteModal <DeleteModal
handleDelete={() => handleDeleteButton(selectedRow?.id)} handleDelete={() => handleDeleteButton(selectedRow?.id)}
open={deleteModal} open={deleteModal}
setDeleteModal={setDeleteModal} setDeleteModal={setDeleteModal}
id={selectedRow?.id} id={selectedRow?.id}
tableType={tableType} />
/> )}
)}
</Box> </Box>
); );
}; };
export default CustomTable; export default CustomTable;

View file

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

View file

@ -0,0 +1,310 @@
import React, { useEffect } from "react";
import {
Box,
Button,
Typography,
Modal,
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { useForm, Controller } from "react-hook-form";
import { updateVehicle } from "../../redux/slices/VehicleSlice";
import {
CustomIconButton,
CustomTextField,
} from "../AddUserModel/styled.css.tsx";
interface EditVehicleModalProps {
open: boolean;
handleClose: () => void;
handleUpdate: (
id: string,
name: string,
email: string,
phone: string,
registeredAddress: string,
imageUrl: string
) => void;
editRow: any;
}
interface FormData {
name: string;
company: string;
modelName: string;
chargeType: string;
imageUrl: string;
}
const EditVehicleModal: React.FC<EditVehicleModalProps> = ({
open,
handleClose,
handleUpdate,
editRow,
}) => {
const {
control,
handleSubmit,
formState: { errors },
setValue,
reset,
} = useForm<FormData>({
defaultValues: {
name: "",
company: "",
modelName: "",
chargeType: "",
imageUrl: "",
},
});
// Set values if editRow is provided
useEffect(() => {
if (editRow) {
setValue("name", editRow.name);
setValue("company", editRow.company);
setValue("modelName", editRow.modelName);
setValue("chargeType", editRow.chargeType);
setValue("imageUrl", editRow.imageUrl);
} else {
reset();
}
}, [editRow, setValue, reset]);
const onSubmit = (data: FormData) => {
if (editRow) {
handleUpdate(
editRow.id,
data.name,
data.company,
data.modelName,
data.chargeType,
data.imageUrl
);
}
handleClose(); // Close the modal
reset(); // Reset the form fields
};
return (
<Modal
open={open}
onClose={handleClose}
aria-labelledby="edit-vehicle-modal"
>
<Box
component="form"
onSubmit={handleSubmit(onSubmit)}
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "background.paper",
boxShadow: 24,
p: 3,
borderRadius: 2,
}}
>
{/* Header */}
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Typography variant="h6" fontWeight={600}>
Edit Vehicle
</Typography>
<CustomIconButton onClick={handleClose}>
<CloseIcon />
</CustomIconButton>
</Box>
{/* Horizontal Line */}
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
{/* Input Fields */}
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
{/* 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}
>
Vehicle Name
</Typography>
<Controller
name="name"
control={control}
rules={{ required: "Vehicle Name is required" }}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
placeholder="Enter Vehicle 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}
>
Company
</Typography>
<Controller
name="company"
control={control}
rules={{ required: "Company is required" }}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
placeholder="Enter Company Name"
size="small"
error={!!errors.company}
helperText={errors.company?.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}
>
Model Name
</Typography>
<Controller
name="modelName"
control={control}
rules={{ required: "Model Name is required" }}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
placeholder="Enter Model Name"
size="small"
error={!!errors.modelName}
helperText={errors.modelName?.message}
/>
)}
/>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
}}
>
<Typography
variant="body2"
fontWeight={500}
mb={0.5}
>
Charge Type
</Typography>
<Controller
name="chargeType"
control={control}
rules={{ required: "Charge Type is required" }}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
placeholder="Enter Charge Type"
size="small"
error={!!errors.chargeType}
helperText={errors.chargeType?.message}
/>
)}
/>
</Box>
</Box>
{/* Third Row - Image URL Input */}
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
}}
>
<Typography variant="body2" fontWeight={500} mb={0.5}>
Image URL
</Typography>
<Controller
name="imageUrl"
control={control}
rules={{ required: "Image URL is required" }}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
placeholder="Enter Image URL"
size="small"
error={!!errors.imageUrl}
helperText={errors.imageUrl?.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" },
}}
>
Update Vehicle
</Button>
</Box>
</Box>
</Modal>
);
};
export default EditVehicleModal;

View file

@ -49,6 +49,11 @@ export default function MenuContent({ hidden }: PropType) {
icon: <ManageAccountsOutlinedIcon />, icon: <ManageAccountsOutlinedIcon />,
url: "/panel/manager-list", // Placeholder for now url: "/panel/manager-list", // Placeholder for now
}, },
userRole === "admin" && {
text: "Vehicles",
icon: <ManageAccountsOutlinedIcon />,
url: "/panel/vehicles", // Placeholder for now
},
]; ];
const filteredMenuItems = baseMenuItems.filter(Boolean); const filteredMenuItems = baseMenuItems.filter(Boolean);

View file

@ -1,108 +1,98 @@
import { Box, Modal, Typography, Divider, Button } from "@mui/material"; import { Box, Button, Modal, Typography } from "@mui/material";
import { useEffect, useState } from "react"; import CloseIcon from "@mui/icons-material/Close";
import { useSelector, useDispatch } from "react-redux";
import { RootState, AppDispatch } from "../../../redux/store/store";
import { deleteManager, fetchManagerList } from "../../../redux/slices/managerSlice";
type User = {
id: number;
name: string;
email?: string;
phone: string;
registeredAddress?: string;
};
type Props = { type Props = {
open: boolean; open: boolean;
setDeleteModal: (value: boolean) => void; setDeleteModal: Function;
id?: number; handleDelete: (id: string | undefined) => void;
tableType?: "admin" | "manager"; id?: string | undefined;
}; };
const style = { const style = {
position: "absolute", position: "absolute",
top: "50%", top: "50%",
left: "50%", left: "50%",
transform: "translate(-50%, -50%)", transform: "translate(-50%, -50%)",
width: 400, width: 330,
bgcolor: "background.paper", bgcolor: "background.paper",
borderRadius: 2, borderRadius: 1.5,
boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.15)", boxShadow: 24,
p: 4, p: 3,
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 2,
}; };
export default function DeleteModal({ open, setDeleteModal, id, tableType }: Props) { const btnStyle = { py: 1, px: 5, width: "50%", textTransform: "capitalize" };
const dispatch = useDispatch<AppDispatch>();
const adminList = useSelector((state: RootState) => state.adminReducer.admins) || [];
const managerList = useSelector((state: RootState) => state.managerReducer.managers) || [];
const [selectedItem, setSelectedItem] = useState<User | null>(null); export default function DeleteModal({
const [loading, setLoading] = useState(false); open,
setDeleteModal,
handleDelete,
id,
}: Props) {
// console.log("DeleteModal opened with ID:", id)
useEffect(() => { return (
if (!id || !tableType) return; <Modal
open={open}
const dataList: User[] = tableType === "admin" ? adminList : managerList; aria-labelledby="modal-modal-title"
const item = dataList.find((user) => user.id === id); aria-describedby="modal-modal-description"
setSelectedItem(item || null); >
}, [id, tableType, adminList, managerList]); <Box sx={style}>
<Typography
id="modal-modal-title"
variant="h6"
component="h2"
align="center"
const handleDelete = async () => { sx={{ flexGrow: 1 }} // This ensures the title takes up available space
if (!id || !tableType) { >
return; Delete Record
} </Typography>
<Box
setLoading(true); onClick={() => setDeleteModal(false)}
try { sx={{
let deleteResult; cursor: "pointer",
if (tableType === "managers") { display: "flex",
deleteResult = await dispatch(deleteManager(id)).unwrap(); alignItems: "center",
} justifyContent: "flex-end", // Aligns the close icon to the right
marginTop: -3.5,
if (deleteResult) { }}
await dispatch(fetchManagerList()); // Refresh list only if deletion is successful >
} <CloseIcon />
} catch (error) { </Box>
console.error("❌ Error while deleting:", error); <Typography
} finally { id="modal-modal-description"
setLoading(false); sx={{ mt: 2 }}
setDeleteModal(false); align="center"
} >
}; Are you sure you want to delete this record?
</Typography>
<Box
return ( sx={{
<Modal open={open} onClose={() => setDeleteModal(false)} aria-labelledby="modal-title"> display: "flex",
<Box sx={style}> justifyContent: "space-between",
<Typography variant="h5" fontWeight="bold" sx={{ width: "100%", textAlign: "center" }}> mt: 4,
Delete {tableType ? tableType.charAt(0).toUpperCase() + tableType.slice(1) : "User"} gap: 2,
</Typography> }}
>
<Divider sx={{ width: "100%" }} /> <Button
variant="contained"
{selectedItem ? ( color="error"
<Typography align="center"> type="button"
Are you sure you want to delete <b>{selectedItem.name}</b>? sx={btnStyle}
</Typography> onClick={() => setDeleteModal(false)}
) : ( >
<Typography align="center">No {tableType} found with this ID</Typography> Cancel
)} </Button>
<Button
<Box sx={{ display: "flex", gap: 2 }}> variant="contained"
<Button variant="contained" color="error" onClick={handleDelete} disabled={loading}> type="button"
{loading ? "Deleting..." : "Delete"} color="primary"
</Button> sx={btnStyle}
<Button variant="outlined" onClick={() => setDeleteModal(false)}>Cancel</Button> onClick={() => handleDelete(id || "")}
</Box> >
</Box> Delete
</Modal> </Button>
); </Box>
</Box>
</Modal>
);
} }

View file

@ -0,0 +1,133 @@
import React, { useEffect, useState } from "react";
import { Box, Modal, Typography, Divider, Grid } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { useSelector } from "react-redux";
import { RootState } from "../../../redux/reducers";
type Props = {
open: boolean;
setViewModal: Function;
handleView: (id: string | undefined) => void;
id?: number | undefined;
};
const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "background.paper",
borderRadius: 2,
boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.15)",
p: 4,
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 2,
};
export default function VehicleViewModal({ open, setViewModal, id }: Props) {
const { vehicles } = useSelector(
(state: RootState) => state.vehicleReducer
);
const [selectedVehicle, setSelectedVehicle] = useState<any>(null);
useEffect(() => {
if (id) {
const vehicle = vehicles.find((vehicle) => vehicle.id === id);
setSelectedVehicle(vehicle || null);
}
}, [id, vehicles]);
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" }}>
{selectedVehicle?.name || "Vehicle"}'s Details
</Box>
<Box
onClick={() => setViewModal(false)}
sx={{
cursor: "pointer",
display: "flex",
alignItems: "center",
}}
>
<CloseIcon />
</Box>
</Box>
</Typography>
<Divider sx={{ width: "100%" }} />
{selectedVehicle ? (
<Grid container spacing={2} sx={{ width: "80%" }}>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Name:</strong>{" "}
<Typography variant="body2">
{selectedVehicle.name}
</Typography>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Company:</strong>{" "}
<Typography variant="body2">
{selectedVehicle.company}
</Typography>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Model Name:</strong>
<Typography variant="body2">
{selectedVehicle.modelName}
</Typography>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Charge Type:</strong>
<Typography variant="body2">
{selectedVehicle.chargeType}
</Typography>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Image URL:</strong>
<Typography variant="body2">
{selectedVehicle.imageUrl ?? "N/A"}
</Typography>
</Typography>
</Grid>
</Grid>
) : (
<Typography align="center">
No vehicle found with this ID
</Typography>
)}
</Box>
</Modal>
);
}

View file

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

View file

@ -1,95 +1,123 @@
import { Box, Modal, Typography, Divider } from "@mui/material"; import { Box, Button, Modal, Typography, Divider, Grid } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { RootState } from "../../../redux/store/store"; import { RootState } from "../../../redux/store/store";
import { useSelector } from "react-redux";
type User = { import { useEffect, useState } from "react";
id: number; import CloseIcon from "@mui/icons-material/Close";
name: string;
email?: string;
phone: string;
registeredAddress?: string;
action?: any;
};
type Props = { type Props = {
open: boolean; open: boolean;
setViewModal: (value: boolean) => void; setViewModal: Function;
id?: number; id?: string;
tableType?: "admin" | "manager";
}; };
const style = { const style = {
position: "absolute", position: "absolute",
top: "50%", top: "50%",
left: "50%", left: "50%",
transform: "translate(-50%, -50%)", transform: "translate(-50%, -50%)",
width: 400, width: 400,
bgcolor: "background.paper", bgcolor: "background.paper",
borderRadius: 2, borderRadius: 2,
boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.15)", boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.15)",
p: 4, p: 4,
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
alignItems: "center", alignItems: "center",
gap: 2, gap: 2,
}; };
export default function ViewModal({ open, setViewModal, id, tableType }: Props) { export default function ViewModal({ open, setViewModal, id }: Props) {
const adminList = useSelector((state: RootState) => state.adminReducer.admins) || []; const { admins } = useSelector((state: RootState) => state.adminReducer);
const managerList = useSelector((state: RootState) => state.managerReducer.managers) || []; const [selectedAdmin, setSelectedAdmin] = useState<any>(null);
const [selectedItem, setSelectedItem] = useState<User | null>(null); useEffect(() => {
if (id) {
const admin = admins.find((admin) => admin.id === id);
setSelectedAdmin(admin);
}
}, [id, admins]);
useEffect(() => { return (
if (!id || !tableType) 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" }}>
{selectedAdmin?.name || "Admin"}'s Details
</Box>
<Box
onClick={() => setViewModal(false)}
sx={{
cursor: "pointer",
display: "flex",
alignItems: "center",
}}
>
<CloseIcon />
</Box>
</Box>
</Typography>
const dataList: User[] = tableType === "admin" ? adminList : managerList; <Divider sx={{ width: "100%" }} />
const item = dataList.find((user) => user.id === id);
if (item) { {selectedAdmin ? (
setSelectedItem(item); // ✅ Updating selected item properly <Grid container spacing={2} sx={{ width: "80%" }}>
} <Grid item xs={6}>
}, [id, tableType, adminList, managerList]); <Typography variant="body1">
<strong>Name:</strong>
<Typography variant="body2">
{selectedAdmin.name}
</Typography>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Phone:</strong>
<Typography variant="body2">
{selectedAdmin.phone}
</Typography>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Email:</strong>
<Typography variant="body2">
{selectedAdmin.email}
</Typography>
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body1">
<strong>Address:</strong>
const formattedType = tableType ? tableType.charAt(0).toUpperCase() + tableType.slice(1) : "User"; <Typography variant="body2">
{selectedAdmin.registeredAddress ?? "N/A"}
return ( </Typography>
<Modal open={open} onClose={() => setViewModal(false)} aria-labelledby="modal-title"> </Typography>
<Box sx={style}> </Grid>
<Typography variant="h5" fontWeight="bold" sx={{ width: "100%" }}> </Grid>
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}> ) : (
<Box sx={{ flex: 1, textAlign: "center" }}> <Typography align="center">
{selectedItem?.name ? `${selectedItem.name}'s Details` : `${formattedType} Details`} No admin found with this ID
</Box> </Typography>
<Box onClick={() => setViewModal(false)} sx={{ cursor: "pointer", display: "flex" }}> )}
<CloseIcon /> </Box>
</Box> </Modal>
</Box> );
</Typography>
<Divider sx={{ width: "100%" }} />
{selectedItem ? (
<Box sx={{ width: "80%", textAlign: "left", display: "flex", flexDirection: "column", gap: 1.5 }}>
<Typography variant="body1">
<b>Name:</b> {selectedItem.name || "N/A"}
</Typography>
<Typography variant="body1">
<b>Email:</b> {selectedItem.email || "N/A"}
</Typography>
<Typography variant="body1">
<b>Phone:</b> {selectedItem.phone || "N/A"}
</Typography>
<Typography variant="body1">
<b>Address:</b> {selectedItem.registeredAddress || "N/A"}
</Typography>
</Box>
) : (
<Typography align="center">No {formattedType} found with this ID</Typography>
)}
</Box>
</Modal>
);
} }

View file

@ -25,7 +25,7 @@ export default function OptionsMenu({ avatar }: { avatar?: boolean }) {
return ( return (
<React.Fragment> <React.Fragment>
<ExpandMoreIcon onClick={handleClick} /> <ExpandMoreIcon onClick={handleClick} sx={{ cursor: "pointer" }} />
<Menu <Menu
anchorEl={anchorEl} anchorEl={anchorEl}
id="top-menu" id="top-menu"
@ -39,6 +39,10 @@ export default function OptionsMenu({ avatar }: { avatar?: boolean }) {
overflow: "visible", overflow: "visible",
filter: "drop-shadow(0px 2px 8px rgba(0,0,0,0.32))", filter: "drop-shadow(0px 2px 8px rgba(0,0,0,0.32))",
mt: 1.5, mt: 1.5,
"& .MuiList-root": {
background: "#272727", // Remove any divider under menu items
},
"& .MuiMenuItem-root": { "& .MuiMenuItem-root": {
borderBottom: "none", // Remove any divider under menu items borderBottom: "none", // Remove any divider under menu items
}, },

View file

@ -1,5 +1,5 @@
import axios from "axios"; import axios from "axios";
// import { useHistory } from "react-router-dom";
const http = axios.create({ const http = axios.create({
baseURL: process.env.REACT_APP_BACKEND_URL, baseURL: process.env.REACT_APP_BACKEND_URL,
}); });
@ -11,5 +11,18 @@ http.interceptors.request.use((config) => {
return config; return config;
}); });
http.interceptors.response.use(
(response) => response,
(error) => {
if (error.response && error.response.status === 401) {
window.location.href = "/login";
// const history = useHistory();
// history.push("/login");
}
return Promise.reject(error);
}
);
export default http; export default http;

View file

@ -11,9 +11,8 @@ import {
Typography, Typography,
Box, Box,
Grid, Grid,
FormControlLabel,
Button, Button,
TextField, FormControlLabel,
Snackbar, Snackbar,
} from "@mui/material"; } from "@mui/material";
import { useNavigate } from "react-router-dom"; // Import useNavigate import { useNavigate } from "react-router-dom"; // Import useNavigate
@ -21,6 +20,9 @@ import { useDispatch, useSelector } from "react-redux";
import { createRole } from "../../redux/slices/roleSlice"; // Import the createRole action import { createRole } from "../../redux/slices/roleSlice"; // Import the createRole action
import { AppDispatch, RootState } from "../../redux/store/store"; // Assuming this is the path to your store file import { AppDispatch, RootState } from "../../redux/store/store"; // Assuming this is the path to your store file
import { toast } from "sonner"; import { toast } from "sonner";
import {
CustomTextField,
} from "../../components/AddUserModel/styled.css.tsx";
// Define the data structure for permission // Define the data structure for permission
interface Permission { interface Permission {
module: string; module: string;
@ -155,7 +157,7 @@ const AddEditRolePage: React.FC = () => {
{/* Role Name Input */} {/* Role Name Input */}
<Box sx={{ mb: 2 }}> <Box sx={{ mb: 2 }}>
<label>Role Name</label> <label>Role Name</label>
<TextField <CustomTextField
placeholder="Enter role name" placeholder="Enter role name"
value={roleName} value={roleName}
onChange={handleRoleNameChange} onChange={handleRoleNameChange}
@ -256,10 +258,15 @@ const AddEditRolePage: React.FC = () => {
{/* Submit Button */} {/* Submit Button */}
<Box sx={{ mt: 2, display: "flex", justifyContent: "flex-end" }}> <Box sx={{ mt: 2, display: "flex", justifyContent: "flex-end" }}>
<Button <Button
variant="contained"
color="primary"
onClick={handleSubmit} onClick={handleSubmit}
disabled={loading} disabled={loading}
sx={{
backgroundColor: "#52ACDF",
color: "white",
borderRadius: "8px",
width: "117px",
"&:hover": { backgroundColor: "#439BC1" },
}}
> >
{loading ? "Saving..." : "Save Role"} {loading ? "Saving..." : "Save Role"}
</Button> </Button>

View file

@ -1,150 +0,0 @@
import React, { useState } from "react";
import { Box, Button, Typography, Modal, IconButton, TextField } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { useDispatch } from "react-redux";
import { toast } from "sonner";
import { addManager } from "../../redux/slices/managerSlice";
interface AddManagerModalProps {
open: boolean;
handleClose: () => void;
}
const AddManagerModal: React.FC<AddManagerModalProps> = ({ open, handleClose }) => {
// State for input fields
const [name, setName] = useState("");
const [stationLocation, setStationLocation] = useState("");
const [phone, setPhone] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const roleId = 5; // Fixed role ID
const roleName = "Peon"; // Required role name
const dispatch = useDispatch();
// Function to validate form inputs
const validateInputs = () => {
if (!name || !stationLocation || !phone || !email || !password) {
toast.error("All fields are required.");
return false;
}
const phoneRegex = /^[0-9]{6,14}$/;
if (!phoneRegex.test(phone)) {
toast.error("Phone number must be between 6 to 14 digits.");
return false;
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
toast.error("Enter a valid email address.");
return false;
}
if (password.length < 6) {
toast.error("Password must be at least 6 characters long.");
return false;
}
return true;
};
// Handle form submission
const handleSubmit = async () => {
if (!validateInputs()) return;
const managerData = {
name,
registeredAddress: stationLocation,
phone,
email,
password,
roleId,
roleName, // ✅ Ensure roleName is correctly included
};
try {
const response = await dispatch(addManager(managerData)).unwrap();
// ✅ Ensure response contains expected data
if (!response || !response.id) {
throw new Error("Invalid response from server. ID is missing.");
}
// ✅ Show success message from API if available, fallback if not
toast.success(response.message || "Manager added successfully!");
resetForm();
handleClose();
} catch (error: any) {
console.error("API Error:", error); // ✅ Log error for debugging
// ✅ Handle both API errors and unexpected errors
toast.error(
error?.response?.data?.message || error.message || "Failed to add manager"
);
}
};
// Function to reset form fields
const resetForm = () => {
setName("");
setStationLocation("");
setPhone("");
setEmail("");
setPassword("");
};
return (
<Modal open={open} onClose={handleClose} aria-labelledby="add-manager-modal">
<Box
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "background.paper",
boxShadow: 24,
p: 3,
borderRadius: 2,
}}
>
{/* Modal Header */}
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<Typography variant="h6" fontWeight={600}>
Add Manager
</Typography>
<IconButton onClick={handleClose}>
<CloseIcon />
</IconButton>
</Box>
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
{/* Input Fields */}
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
<TextField label="Manager Name" fullWidth size="small" value={name} onChange={(e) => setName(e.target.value)} />
<TextField label="Station Location" fullWidth size="small" value={stationLocation} onChange={(e) => setStationLocation(e.target.value)} />
<TextField label="Phone Number" fullWidth size="small" value={phone} onChange={(e) => setPhone(e.target.value)} />
<TextField label="Email" fullWidth size="small" value={email} onChange={(e) => setEmail(e.target.value)} />
<TextField label="Password" type="password" fullWidth size="small" value={password} onChange={(e) => setPassword(e.target.value)} />
</Box>
{/* Submit Button */}
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 3 }}>
<Button variant="contained" onClick={handleSubmit}>
Add Manager
</Button>
</Box>
</Box>
</Modal>
);
};
export default AddManagerModal;

View file

@ -14,44 +14,49 @@ interface ForgotPasswordProps {
export default function ForgotPassword({ open, handleClose }: ForgotPasswordProps) { export default function ForgotPassword({ open, handleClose }: ForgotPasswordProps) {
return ( return (
<Dialog <Dialog
open={open} open={open}
onClose={handleClose} onClose={handleClose}
PaperProps={{ PaperProps={{
component: 'form', component: "form",
onSubmit: (event: React.FormEvent<HTMLFormElement>) => { onSubmit: (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
handleClose(); handleClose();
}, },
sx: { backgroundImage: 'none' }, sx: { backgroundImage: "none" },
}} }}
> >
<DialogTitle>Reset password</DialogTitle> <DialogTitle>Reset password</DialogTitle>
<DialogContent <DialogContent
sx={{ display: 'flex', flexDirection: 'column', gap: 2, width: '100%' }} sx={{
> display: "flex",
<DialogContentText> flexDirection: "column",
Enter your account&apos;s email address, and we&apos;ll send you a link to gap: 2,
reset your password. width: "100%",
</DialogContentText> }}
<OutlinedInput >
autoFocus <DialogContentText>
required Enter your account&apos;s email address, and we&apos;ll send
margin="dense" you a link to reset your password.
id="email" </DialogContentText>
name="email" <OutlinedInput
label="Email address" autoFocus
placeholder="Email address" required
type="email" margin="dense"
fullWidth id="email"
/> name="email"
</DialogContent> label="Email address"
<DialogActions sx={{ pb: 3, px: 3 }}> placeholder="Email address"
<Button onClick={handleClose}>Cancel</Button> type="email"
<Button variant="contained" type="submit"> fullWidth
Continue />
</Button> </DialogContent>
</DialogActions> <DialogActions sx={{ pb: 3, px: 3 }}>
</Dialog> <Button onClick={handleClose}>Cancel</Button>
<Button variant="contained" type="submit">
Continue
</Button>
</DialogActions>
</Dialog>
); );
} }

View file

@ -50,15 +50,15 @@ export default function Login(props: { disableCustomTheme?: boolean }) {
}; };
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}>

View file

@ -1,191 +0,0 @@
import React, { useEffect } from "react";
import {
Box,
Button,
Typography,
TextField,
Modal,
IconButton,
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { useForm, Controller } from "react-hook-form";
interface EditModalProps {
open: boolean;
handleClose: () => void;
handleCreate: (data: FormData) => void;
handleUpdate: (id: string, data: FormData) => void;
editRow: any | null;
}
interface FormData {
managerName: string;
stationName: string;
stationLocation: string;
phoneNumber: string;
}
const EditModal: React.FC<EditModalProps> = ({
open,
handleClose,
handleCreate,
handleUpdate,
editRow,
}) => {
const {
control,
handleSubmit,
formState: { errors },
setValue,
reset,
} = useForm<FormData>({
defaultValues: {
managerName: "",
stationName: "",
stationLocation: "",
phoneNumber: "",
},
});
// Populate form fields when `editRow` changes
useEffect(() => {
if (editRow) {
setValue("managerName", editRow.name || "");
setValue("stationName", editRow.stationName || "");
setValue("stationLocation", editRow.registeredAddress || "");
setValue("phoneNumber", editRow.phone || "");
} else {
reset({ // ✅ Ensure default values are reset when adding a new manager
managerName: "",
stationName: "",
stationLocation: "",
phoneNumber: "",
});
}
}, [editRow, setValue, reset]);
const onSubmit = (data: FormData) => {
if (editRow) {
handleUpdate({
id: editRow.id,
managerName: data.managerName,
stationLocation: data.stationLocation,
phoneNumber: data.phoneNumber
});
} else {
handleCreate(data);
}
handleClose();
reset();
};
return (
<Modal open={open} onClose={handleClose} aria-labelledby="edit-manager-modal">
<Box
component="form"
onSubmit={handleSubmit(onSubmit)}
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "background.paper",
boxShadow: 24,
p: 3,
borderRadius: 2,
}}
>
{/* Header */}
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<Typography variant="h6" fontWeight={600}>
{editRow ? "Edit Manager" : "Add Manager"}
</Typography>
<IconButton onClick={handleClose}>
<CloseIcon />
</IconButton>
</Box>
{/* Horizontal Line */}
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
{/* Input Fields */}
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
<Box sx={{ display: "flex", gap: 2 }}>
{/* Manager Name */}
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>Manager Name</Typography>
<Controller
name="managerName"
control={control}
rules={{ required: "Manager Name is required" }}
render={({ field }) => (
<TextField {...field} fullWidth placeholder="Enter Manager Name" size="small" error={!!errors.managerName} helperText={errors.managerName?.message} />
)}
/>
</Box>
{/* Station Name */}
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>Station Name</Typography>
<Controller
name="stationName"
control={control}
rules={{ required: "Station Name is required" }}
render={({ field }) => (
<TextField {...field} fullWidth placeholder="Enter Station Name" size="small" error={!!errors.stationName} helperText={errors.stationName?.message} />
)}
/>
</Box>
</Box>
<Box sx={{ display: "flex", gap: 2 }}>
{/* Station Location */}
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>Station Location</Typography>
<Controller
name="stationLocation"
control={control}
rules={{ required: "Station Location is required" }}
render={({ field }) => (
<TextField {...field} fullWidth placeholder="Enter Station Location" size="small" error={!!errors.stationLocation} helperText={errors.stationLocation?.message} />
)}
/>
</Box>
{/* Phone Number */}
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
<Typography variant="body2" fontWeight={500} mb={0.5}>Phone Number</Typography>
<Controller
name="phoneNumber"
control={control}
rules={{
required: "Phone number is required",
pattern: {
value: /^[0-9]+$/,
message: "Only numbers are allowed",
},
}}
render={({ field }) => (
<TextField {...field} fullWidth placeholder="Enter Phone Number" size="small" error={!!errors.phoneNumber} helperText={errors.phoneNumber?.message} />
)}
/>
</Box>
</Box>
</Box>
{/* Submit Button */}
<Box sx={{ display: "flex", justifyContent: "flex-end", mt: 3 }}>
<Button variant="contained" type="submit">
{editRow ? "Update" : "Create"}
</Button>
</Box>
</Box>
</Modal>
);
};
export default EditModal;

View file

@ -1,240 +1,157 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Box, Button, Typography, TextField, InputAdornment, IconButton } from "@mui/material"; import {
Box,
Button,
Typography,
TextField,
InputAdornment,
IconButton,
} from "@mui/material";
import SearchIcon from "@mui/icons-material/Search"; import SearchIcon from "@mui/icons-material/Search";
import EqualizerIcon from "@mui/icons-material/Tune"; import EqualizerIcon from "@mui/icons-material/Tune";
import CustomTable, { Column } from "../../components/CustomTable"; import CustomTable, { Column } from "../../components/CustomTable";
import AddManagerModal from "../../pages/AddManagerModal"; import AddManagerModal from "../../components/AddManagerModal";
import EditUserModal from "../EditUserModal";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { fetchManagerList, addManager,updateManager,deleteManager } from "../../redux/slices/managerSlice"; import DeleteModal from "../../components/Modals/DeleteModal";
import {
managerList,
addManager,
updateManager,
deleteManager,
} from "../../redux/slices/managerSlice";
import { RootState, AppDispatch } from "../../redux/store/store"; import { RootState, AppDispatch } from "../../redux/store/store";
import { useForm } from "react-hook-form";
import EditManagerModal from "../../components/EditManagerModal";
export default function ManagerList() { export default function ManagerList() {
const dispatch = useDispatch<AppDispatch>(); const [addModalOpen, setAddModalOpen] = useState<boolean>(false);
const { managers, isLoading } = useSelector((state: RootState) => state.managerReducer); const [editModalOpen, setEditModalOpen] = useState<boolean>(false);
const [editRow, setEditRow] = useState<any>(null);
const { reset } = useForm();
const [search, setSearch] = useState(""); const [deleteModal, setDeleteModal] = useState<boolean>(false);
const [addButtonModal, setAddButtonModal] = useState(false); const [viewModal, setViewModal] = useState<boolean>(false);
const [modalOpen, setModalOpen] = useState(false); const [rowData, setRowData] = useState<any | null>(null);
const [rowData, setRowData] = useState(null); const [searchTerm, setSearchTerm] = useState("");
const [viewModal, setViewModal] = useState(false); const dispatch = useDispatch<AppDispatch>();
const [deleteModal, setDeleteModal] = useState(false); const managers = useSelector(
(state: RootState) => state.managerReducer.managers
);
useEffect(() => { useEffect(() => {
dispatch(fetchManagerList()); // Fetch data when component mounts dispatch(managerList());
}, [dispatch]); }, [dispatch]);
const handleClickOpen = () => {
setRowData(null); // Reset row data when opening for new admin
setAddModalOpen(true);
};
// Function to handle adding a manager const handleCloseModal = () => {
const handleAddManager = async (newManager: { setAddModalOpen(false);
name: string; setEditModalOpen(false);
registeredAddress: string; setRowData(null);
phone: string; reset();
email: string; };
password: string;
}) => { const handleAddManager = async (data: {
await dispatch(addManager(newManager)); name: string;
dispatch(fetchManagerList()); // Refresh list after adding email: string;
handleCloseModal(); // Close modal after adding phone: string;
}; registeredAddress: string;
}) => {
try {
await dispatch(addManager(data)); // Dispatch action to add manager
await dispatch(managerList()); // Fetch the updated list
handleCloseModal(); // Close the modal
} catch (error) {
console.error("Error adding manager", error);
}
};
const handleUpdate = async (
id: number,
name: string,
email: string,
phone: string,
registeredAddress: string
) => {
try {
// Creating the managerData object to match the expected payload structure
const managerData = {
name,
email,
phone,
registeredAddress,
};
// Dispatching the updateManager action with the correct payload structure
await dispatch(updateManager({ id, managerData }));
await dispatch(managerList()); // Refresh the manager list after updating
handleCloseModal(); // Close the modal after updating
} catch (error) {
console.error("Update failed", error);
}
};
// Remove 'stationName' from columns
const categoryColumns: Column[] = [
{ id: "srno", label: "Sr No" },
{ id: "name", label: "Name" },
{ id: "email", label: "Email" },
{ id: "phone", label: "Phone" },
{ id: "registeredAddress", label: "Station Location" },
// Function to handle updating a { id: "action", label: "Action", align: "center" },
const handleUpdateManager = async (updatedManager) => { ];
if (!updatedManager || typeof updatedManager !== "object") {
return;
}
if (!rowData) {
return;
}
const managerId = rowData.id;
if (!updatedManager.managerName || !updatedManager.stationLocation || !updatedManager.phoneNumber) {
return;
}
try {
const response = await dispatch(updateManager({
id: managerId,
name: updatedManager.managerName,
registeredAddress: updatedManager.stationLocation,
phone: updatedManager.phoneNumber,
}));
if (response?.payload?.statusCode === 200) {
await dispatch(fetchManagerList()); // ✅ Refresh list after update
}
handleCloseModal();
} catch (error) {
console.error("❌ Update failed:", error);
}
};
// Function to handle deleting a manager
// Update rows to remove 'stationName'
const categoryRows = managers?.map(
(
manager: {
id: number;
name: string;
email: string;
phone: string;
registeredAddress: string;
},
index: number
) => ({
id: manager?.id,
srno: index + 1,
name: manager?.name,
email: manager?.email,
phone: manager.phone ?? "NA",
registeredAddress: manager?.registeredAddress ?? "NA",
})
);
const handleDeleteManager = async () => { return (
if (!rowData?.id) { <>
console.error("❌ No manager ID found for deletion!"); <CustomTable
return; columns={categoryColumns}
} rows={categoryRows}
setDeleteModal={setDeleteModal}
try { deleteModal={deleteModal}
setViewModal={setViewModal}
const response = await dispatch(deleteManager(rowData.id)); viewModal={viewModal}
setRowData={setRowData}
if (response?.payload) { setModalOpen={() => setEditModalOpen(true)}
tableType="manager"
dispatch(fetchManagerList()); // Refresh list after deletion handleClickOpen={handleClickOpen}
} else { />
console.error("❌ Deletion failed!", response); <AddManagerModal
} open={addModalOpen}
} catch (error) { handleClose={handleCloseModal}
console.error("❌ Error deleting manager:", error); handleAddManager={handleAddManager}
} />
<EditManagerModal
setDeleteModal(false); // Close delete modal open={editModalOpen}
handleClose={handleCloseModal}
handleCloseModal(); handleUpdate={handleUpdate}
}; editRow={rowData}
/>
</>
);
// Function to handle search input change
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearch(event.target.value);
};
// Open the Add Manager Modal
const handleClickOpen = () => {
setRowData(null);
setAddButtonModal(true);
};
// Close all modals
const handleCloseModal = () => {
setAddButtonModal(false);
setModalOpen(false);
setRowData(null);
};
// Table columns definition
const managerColumns: Column[] = [
{ id: "name", label: "Manager Name" },
{ id: "registeredAddress", label: "Station Location" },
{ id: "phone", label: "Phone Number" },
{ id: "action", label: "Action", align: "center" },
];
// Filtered manager list based on search input
const filteredManagers = managers.filter((manager) =>
Object.values(manager).some((value) =>
typeof value === "string" && value.toLowerCase().includes(search.toLowerCase())
)
);
return (
<>
{/* Header Section */}
<Box sx={{ width: "100%", display: "flex", flexDirection: "column", gap: 2, mt: 2 }}>
<Typography component="h2" variant="h6" sx={{ fontWeight: 600 }}>
Managers
</Typography>
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<TextField
variant="outlined"
size="small"
placeholder="Search"
value={search}
onChange={handleSearchChange}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
<Button variant="contained" size="medium" onClick={handleClickOpen}>
Add Manager
</Button>
<IconButton>
<EqualizerIcon />
</IconButton>
</Box>
</Box>
</Box>
{/* Table Section */}
{isLoading ? (
<Typography>Loading managers...</Typography>
) : (
<CustomTable
columns={managerColumns}
rows={filteredManagers.map((manager) => ({
id: manager.id,
name: manager.name,
registeredAddress: manager.registeredAddress,
phone: manager.phone,
action: (
<Box sx={{ display: "flex", gap: 1 }}>
<Button onClick={() => { setRowData(manager); setModalOpen(true); }}>Edit</Button>
<Button onClick={() => { setRowData(manager); setViewModal(true); }}>View</Button>
<Button color="error" onClick={() => { setRowData(manager); setDeleteModal(true); }}>
Delete
</Button>
</Box>
),
}))}
tableType="managers"
setRowData={setRowData}
setModalOpen={setModalOpen}
setViewModal={setViewModal}
viewModal={viewModal}
setDeleteModal={setDeleteModal}
deleteModal={deleteModal}
handleDeleteButton={handleDeleteManager}
/>
)}
{/* Modals */}
<EditUserModal
open={modalOpen}
handleClose={handleCloseModal}
editRow={rowData}
handleUpdate={handleUpdateManager} // Pass function
/>
<AddManagerModal
open={addButtonModal}
handleClose={handleCloseModal}
handleAddManager={(data) =>
handleAddManager({
name: data.managerName,
registeredAddress: data.stationLocation,
phone: data.phoneNumber,
email: data.email,
password: data.password,
})
}
/>
</>
);
} }

View file

@ -12,55 +12,18 @@ import {
updateVehicle, updateVehicle,
vehicleList, vehicleList,
} from "../../redux/slices/VehicleSlice"; } from "../../redux/slices/VehicleSlice";
import SearchIcon from "@mui/icons-material/Search"; import AddVehicleModal from "../../components/AddVehicleModal";
const categoryRows = [ import EditVehicleModal from "../../components/EditVehicleModal";
{
srno: 1,
id: 1, // Using a number for 'id'
name: "Tesla Model S",
brand: "Tesla",
imageUrl:
"https://example.com/https://imgd-ct.aeplcdn.com/1056x660/n/cw/ec/93821/model-s-exterior-front-view.jpeg?q=80.jpg",
},
{
srno: 2,
id: 2,
name: "BMW X5",
brand: "BMW",
imageUrl: "https://example.com/bmw_x5.jpg",
},
{
srno: 3,
id: 3,
name: "Audi A6",
brand: "Audi",
imageUrl: "https://example.com/audi_a6.jpg",
},
{
srno: 4,
id: 4,
name: "Mercedes-Benz S-Class",
brand: "Mercedes-Benz",
imageUrl: "https://example.com/mercedes_s_class.jpg",
},
{
srno: 5,
id: 5,
name: "Ford Mustang",
brand: "Ford",
imageUrl: "https://example.com/ford_mustang.jpg",
},
];
export default function VehicleList() { export default function VehicleList() {
const [modalOpen, setModalOpen] = useState<boolean>(false); const [addModalOpen, setAddModalOpen] = useState<boolean>(false);
const [editModalOpen, setEditModalOpen] = useState<boolean>(false);
const [editRow, setEditRow] = useState<any>(null); const [editRow, setEditRow] = useState<any>(null);
const { reset } = useForm(); const { reset } = useForm();
const [deleteModal, setDeleteModal] = React.useState<boolean>(false); const [deleteModal, setDeleteModal] = useState<boolean>(false);
const [viewModal, setViewModal] = React.useState<boolean>(false); const [viewModal, setViewModal] = useState<boolean>(false);
const [rowData, setRowData] = React.useState<any | null>(null); const [rowData, setRowData] = useState<any | null>(null);
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const dispatch = useDispatch<AppDispatch>(); const dispatch = useDispatch<AppDispatch>();
const vehicles = useSelector( const vehicles = useSelector(
@ -73,32 +36,38 @@ export default function VehicleList() {
const handleClickOpen = () => { const handleClickOpen = () => {
setRowData(null); // Reset row data when opening for new admin setRowData(null); // Reset row data when opening for new admin
setModalOpen(true); setAddModalOpen(true);
}; };
const handleCloseModal = () => { const handleCloseModal = () => {
setModalOpen(false); setAddModalOpen(false);
setEditModalOpen(false);
setRowData(null); setRowData(null);
reset(); reset();
}; };
const handleCreate = async (data: { const handleAddVehicle = async (data: {
name: string; vehicleName: string;
brand: string; company: string;
modelName: string;
chargeType: string;
imageUrl: string; imageUrl: string;
}) => { }) => {
try { try {
await dispatch(addVehicle(data)); await dispatch(addVehicle(data)); // Dispatch action to add vehicle
await dispatch(vehicleList()); await dispatch(vehicleList()); // Fetch the updated list
handleCloseModal(); handleCloseModal(); // Close the modal
} catch (error) { } catch (error) {
console.error("Creation failed", error); console.error("Error adding vehicle", error);
} }
}; };
const handleUpdate = async ( const handleUpdate = async (
id: number, id: number,
name: string, name: string,
brand: string, company: string,
modelName: string,
chargeType: string,
imageUrl: string imageUrl: string
) => { ) => {
try { try {
@ -106,53 +75,70 @@ export default function VehicleList() {
updateVehicle({ updateVehicle({
id, id,
name, name,
brand, company,
modelName,
chargeType,
imageUrl, imageUrl,
}) })
); );
await dispatch(vehicleList()); await dispatch(vehicleList());
handleCloseModal();
} catch (error) { } catch (error) {
console.error("Update failed", error); console.error("Update failed", error);
} }
}; };
const categoryColumns: Column[] = [ const categoryColumns: Column[] = [
{ id: "srno", label: "Sr No" }, { id: "srno", label: "Sr No" },
{ id: "name", label: "Vehicle Name" }, { id: "name", label: "Vehicle Name" },
{ id: "brand", label: "Brand" }, { id: "company", label: "Company" },
{ id: "modelName", label: "Model Name" },
{ id: "chargeType", label: "Charge Type" },
{ id: "imageUrl", label: "Image" }, { id: "imageUrl", label: "Image" },
{ id: "action", label: "Action", align: "center" }, { id: "action", label: "Action", align: "center" },
]; ];
const filteredVehicles = vehicles?.filter( const filteredVehicles = vehicles?.filter(
(vehicle) => (vehicle) =>
vehicle.name.toLowerCase().includes(searchTerm.toLowerCase()) || vehicle.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
vehicle.brand.toLowerCase().includes(searchTerm.toLowerCase()) vehicle.company?.toLowerCase().includes(searchTerm.toLowerCase()) ||
vehicle.modelName
?.toLowerCase()
.includes(searchTerm.toLowerCase()) ||
vehicle.chargeType
?.toLowerCase()
.includes(searchTerm.toLowerCase()) ||
vehicle.imageUrl?.toLowerCase().includes(searchTerm.toLowerCase())
); );
// const categoryRows = filteredVehicles?.length const categoryRows = filteredVehicles?.length
// ? filteredVehicles?.map( ? filteredVehicles?.map(
// ( (
// vehicle: { vehicle: {
// id: number; id: number;
// name: string; name: string;
// brand: string; company: string;
// imageUrl: string; modelName: string;
// }, chargeType: string;
// index: number imageUrl: string;
// ) => ({ },
// id: vehicle?.id, index: number
// srno: index + 1, ) => ({
// name: vehicle?.name, id: vehicle?.id,
// brand: vehicle?.brand, srno: index + 1,
// imageUrl: vehicle?.imageUrl, name: vehicle?.name,
// }) company: vehicle?.company,
// ) modelName: vehicle?.modelName,
// : []; chargeType: vehicle?.chargeType,
imageUrl: vehicle?.imageUrl,
})
)
: [];
return ( return (
<> <>
<CustomTable <CustomTable
columns={categoryColumns} columns={categoryColumns}
rows={categoryRows} rows={categoryRows}
@ -161,20 +147,22 @@ export default function VehicleList() {
setViewModal={setViewModal} setViewModal={setViewModal}
viewModal={viewModal} viewModal={viewModal}
setRowData={setRowData} setRowData={setRowData}
setModalOpen={setModalOpen} setModalOpen={() => setEditModalOpen(true)}
tableType="vehicle" tableType="vehicle"
handleClickOpen={handleClickOpen} handleClickOpen={handleClickOpen}
/> />
{/* <AddEditCategoryModal <AddVehicleModal
open={modalOpen} open={addModalOpen}
handleClose={handleCloseModal} handleClose={handleCloseModal}
editRow={rowData} handleAddVehicle={handleAddVehicle}
/> />
<DeleteModal <EditVehicleModal
open={deleteModal} open={editModalOpen}
setDeleteModal={setDeleteModal} handleClose={handleCloseModal}
handleDelete={handleDelete} handleUpdate={handleUpdate}
/> */} editRow={rowData}
/>
</> </>
); );
} }

View file

@ -3,14 +3,15 @@ import http from "../../lib/https";
import { toast } from "sonner"; import { toast } from "sonner";
interface Vehicle { interface Vehicle {
id:number; id: number;
name:string; name: string;
brand:string; company: string;
imageUrl:string; modelName: string;
chargeType: string;
imageUrl: string;
} }
interface VehicleState { interface VehicleState {
vehicles:Vehicle[]; vehicles: Vehicle[];
loading: boolean; loading: boolean;
error: string | null; error: string | null;
} }
@ -20,39 +21,42 @@ const initialState: VehicleState = {
error: null, error: null,
}; };
export const vehicleList = createAsyncThunk<Vehicle, void, { rejectValue: string }>( export const vehicleList = createAsyncThunk<
"fetchVehicles", Vehicle,
async (_, { rejectWithValue }) => { void,
try { { rejectValue: string }
const token = localStorage?.getItem("authToken"); >("fetchVehicles", async (_, { rejectWithValue }) => {
if (!token) throw new Error("No token found"); try {
const token = localStorage?.getItem("authToken");
if (!token) throw new Error("No token found");
const response = await http.get("/"); const response = await http.get("/get-vehicles");
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;
} catch (error: any) { } catch (error: any) {
toast.error("Error Fetching Profile" + error); toast.error("Error Fetching Profile" + error);
return rejectWithValue( return rejectWithValue(
error?.response?.data?.message || "An error occurred" error?.response?.data?.message || "An error occurred"
); );
}
} }
); });
//Add Vehicle //Add Vehicle
export const addVehicle = createAsyncThunk< export const addVehicle = createAsyncThunk<
Vehicle, Vehicle,
{ {
name: string; name: string;
brand: string; company: string;
modelName: string;
chargeType: string;
imageUrl: string; imageUrl: string;
}, },
{ rejectValue: string } { rejectValue: string }
>("/AddVehicle", async (data, { rejectWithValue }) => { >("/AddVehicle", async (data, { rejectWithValue }) => {
try { try {
const response = await http.post("/", data); const response = await http.post("create-vehicle", data);
return response.data; return response.data;
} catch (error: any) { } catch (error: any) {
return rejectWithValue( return rejectWithValue(
@ -61,13 +65,15 @@ export const addVehicle = createAsyncThunk<
} }
}); });
// Update Vehicle details // Update Vehicle details
export const updateVehicle = createAsyncThunk( export const updateVehicle = createAsyncThunk(
"updateVehicle", "updateVehicle",
async ({ id, ...vehicleData }: Vehicle, { rejectWithValue }) => { async ({ id, ...vehicleData }: Vehicle, { rejectWithValue }) => {
try { try {
const response = await http.put(`/${id}`, vehicleData); const response = await http.patch(
`${id}/update-vehicle`,
vehicleData
);
toast.success("Vehicle Deatils updated successfully"); toast.success("Vehicle Deatils updated successfully");
return response?.data; return response?.data;
} catch (error: any) { } catch (error: any) {
@ -78,6 +84,23 @@ export const updateVehicle = createAsyncThunk(
} }
} }
); );
export const deleteVehicle = createAsyncThunk<
string,
string,
{ rejectValue: string }
>("deleteVehicle", async (id, { rejectWithValue }) => {
try {
const response = await http.delete(`/${id}/delete-vehicle`);
toast.success(response.data?.message);
return id;
} catch (error: any) {
toast.error("Error deleting the vehicle" + error);
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
}
});
const vehicleSlice = createSlice({ const vehicleSlice = createSlice({
name: "vehicle", name: "vehicle",
initialState, initialState,
@ -125,6 +148,18 @@ const vehicleSlice = createSlice({
}) })
.addCase(updateVehicle.rejected, (state) => { .addCase(updateVehicle.rejected, (state) => {
state.loading = false; state.loading = false;
})
.addCase(deleteVehicle.pending, (state) => {
state.loading = true;
})
.addCase(deleteVehicle.fulfilled, (state, action) => {
state.loading = false;
state.vehicles = state.vehicles.filter(
(vehicle) => String(vehicle.id) !== String(action.payload)
);
})
.addCase(deleteVehicle.rejected, (state) => {
state.loading = false;
}); });
}, },
}); });

View file

@ -90,11 +90,8 @@ const initialState: AuthState = {
isAuthenticated: false, isAuthenticated: false,
isLoading: false, isLoading: false,
// error: null, // error: null,
//Eknoor singh
//date:- 12-Feb-2025
//initial state of token set to null
token: null, token: null,
role: null, // New field for role role: null,
}; };
const authSlice = createSlice({ const authSlice = createSlice({
@ -144,14 +141,7 @@ const authSlice = createSlice({
} }
); );
// created by Jaanvi and Eknoor
//AdminList
//Eknoor singh
//date:- 12-Feb-2025
//Reducers for fetching profiles has been implemente
}, },
}); });
// export const { logout } = authSlice.actions;
export default authSlice.reducer; export default authSlice.reducer;

View file

@ -2,188 +2,179 @@ import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
import http from "../../lib/https"; import http from "../../lib/https";
import { toast } from "sonner"; import { toast } from "sonner";
// Define TypeScript types // Define the Manager interface based on the payload
interface Manager { interface Manager {
id: string; id: number;
name: string; name: string;
email: string; email: string;
phone?: string; phone: string;
role: string; registeredAddress: string;
roleId: number;
} }
interface ManagerState { interface ManagerState {
managers: Manager[]; managers: Manager[];
loading: boolean; loading: boolean;
error: string | null; error: string | null;
} }
// Initial state // Initial state
const initialState: ManagerState = { const initialState: ManagerState = {
managers: [], managers: [],
loading: false, loading: false,
error: null, error: null,
}; };
// Fetch Manager List // Fetch Manager List (Async Thunk)
export const fetchManagerList = createAsyncThunk<Manager[], void, { rejectValue: string }>( export const managerList = createAsyncThunk<
"fetchManagers", Manager[],
async (_, { rejectWithValue }) => { void,
try { { rejectValue: string }
const response = await http.get("manager-list"); >("fetchManagers", async (_, { rejectWithValue }) => {
try {
if (!response.data?.data) throw new Error("Invalid API response"); const response = await http.get("manager-list");
if (!response.data?.data) throw new Error("Invalid API response");
return response.data.data;
} catch (error: any) {
toast.error("Error Fetching Managers: " + error.message);
return rejectWithValue(
error?.response?.data?.message || "An error occurred"
);
}
});
return response.data.data; // Create Manager (Async Thunk)
} catch (error: any) { export const addManager = createAsyncThunk<
toast.error("Error Fetching Managers: " + error); Manager,
return rejectWithValue( Manager,
error?.response?.data?.message || "An error occurred" { rejectValue: string }
); >("addManager", async (data, { rejectWithValue }) => {
} try {
} const response = await http.post("create-manager", data);
); toast.success("Manager created successfully");
return response.data?.data;
// Create Manager } catch (error: any) {
export const addManager = createAsyncThunk( toast.error("Error creating manager: " + error.message);
"addManager", return rejectWithValue(
async (data, { rejectWithValue }) => { error.response?.data?.message || "An error occurred"
try { );
}
const response = await http.post("create-manager", data); });
toast.success("Manager created successfully");
// ✅ Ensure the response contains the expected data
if (!response.data || !response.data.data || !response.data.data.id) {
console.error("❌ ERROR: Missing manager ID in response", response.data);
throw new Error("Invalid API response: Missing manager ID");
}
return response.data.data;
} catch (error: any) {
console.error("❌ API Error:", error?.response?.data || error);
return rejectWithValue(error?.response?.data?.message || "An error occurred");
}
}
);
// Update Manager (Async Thunk)
export const updateManager = createAsyncThunk< export const updateManager = createAsyncThunk<
Manager, Manager,
{ id: string; name?: string; email?: string; phone?: string; role?: string; registeredAddress?: string }, { id: number; managerData: Manager },
{ rejectValue: string } { rejectValue: string }
>( >("updateManager", async ({ id, managerData }, { rejectWithValue }) => {
"updateManager", if (!id) {
async ({ id, ...managerData }, { rejectWithValue }) => { return rejectWithValue("Manager ID is required.");
try { }
try {
const response = await http.put(`/${id}/update-manager`, managerData);
toast.success("Manager updated successfully");
return response?.data;
} catch (error: any) {
toast.error("Error updating manager: " + error.message);
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
}
});
const response = await http.put(`${id}/update-manager`, managerData); // Delete Manager (Async Thunk)
toast.success("Manager updated successfully!");
return response.data.data; // ✅ Extracting correct response data
} catch (error: any) {
toast.error("Error updating manager: " + error);
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
}
}
);
// Delete Manager
export const deleteManager = createAsyncThunk< export const deleteManager = createAsyncThunk<
string, string,
string, string,
{ rejectValue: string } { rejectValue: string }
>( >("deleteManager", async (id, { rejectWithValue }) => {
"deleteManager", try {
async (id, { rejectWithValue }) => { await http.delete(`/${id}/delete-manager`);
try { toast.success("Manager deleted successfully!");
await http.delete(`/${id}/delete-manager`); return id;
toast.success("Manager deleted successfully!"); } catch (error: any) {
return id; // Return the ID of the deleted manager toast.error("Error deleting manager: " + error.message);
} catch (error: any) { return rejectWithValue(
toast.error("Error deleting manager: " + error); error.response?.data?.message || "An error occurred"
return rejectWithValue( );
error.response?.data?.message || "An error occurred" }
); });
}
}
);
// Create Slice // Create Slice
const managerSlice = createSlice({ const managerSlice = createSlice({
name: "fetchManagers", name: "maanger",
initialState, initialState,
reducers: {}, reducers: {},
extraReducers: (builder) => { extraReducers: (builder) => {
builder builder
// Fetch Managers // Fetch Managers
.addCase(fetchManagerList.pending, (state) => { .addCase(managerList.pending, (state) => {
state.loading = true; state.loading = true;
state.error = null; state.error = null;
}) })
.addCase( .addCase(
fetchManagerList.fulfilled, managerList.fulfilled,
(state, action: PayloadAction<Manager[]>) => { (state, action: PayloadAction<Manager[]>) => {
state.loading = false; state.loading = false;
state.managers = action.payload; state.managers = action.payload;
} }
) )
.addCase(fetchManagerList.rejected, (state, action) => { .addCase(managerList.rejected, (state, action) => {
state.loading = false; state.loading = false;
state.error = action.payload || "Failed to fetch managers"; state.error = action.payload || "Failed to fetch managers";
}) })
// Add Manager // Add Manager
.addCase(addManager.pending, (state) => { .addCase(addManager.pending, (state) => {
state.loading = true; state.loading = true;
}) })
.addCase(addManager.fulfilled, (state, action: PayloadAction<Manager>) => { .addCase(
state.loading = false; addManager.fulfilled,
state.managers.push(action.payload); (state, action: PayloadAction<Manager>) => {
}) state.loading = false;
.addCase(addManager.rejected, (state, action) => { state.managers.push(action.payload);
state.loading = false; }
}) )
.addCase(addManager.rejected, (state, action) => {
state.loading = false;
state.error = action.payload || "Failed to add manager";
})
// Update Manager // Update Manager
.addCase(updateManager.pending, (state) => { .addCase(updateManager.pending, (state) => {
state.loading = true; state.loading = true;
}) })
.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(m => m.id === updatedManager.id); // const index = state.managers.findIndex(
if (index !== -1) { // (manager) => manager.id === updatedManager.id
state.managers[index] = { ...state.managers[index], ...updatedManager }; // 🔥 Merge updated fields // );
} // if (index !== -1) {
}) // state.managers[index] = updatedManager; // Update the manager in the state
// }
.addCase(updateManager.rejected, (state) => { })
state.loading = false; .addCase(updateManager.rejected, (state, action) => {
}) state.loading = false;
state.error = action.payload || "Failed to update manager";
})
// Delete Manager // Delete Manager
// Delete Manager .addCase(deleteManager.pending, (state) => {
.addCase(deleteManager.pending, (state) => { state.loading = true;
state.loading = true; })
}) .addCase(deleteManager.fulfilled, (state, action) => {
.addCase(deleteManager.fulfilled, (state, action) => { state.loading = false;
state.loading = false; state.managers = state.managers.filter(
state.managers = state.managers.filter(manager => manager.id !== action.payload); (manager) => manager.id !== action.payload
}) );
.addCase(deleteManager.rejected, (state) => { })
state.loading = false; .addCase(deleteManager.rejected, (state, action) => {
}); state.loading = false;
state.error = action.payload || "Failed to delete manager";
});
}, },
}); });
export default managerSlice.reducer; export default managerSlice.reducer;

View file

@ -14,6 +14,7 @@ export const dataDisplayCustomizations = {
padding: '8px', padding: '8px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: 0, gap: 0,
}, },
}, },

View file

@ -385,7 +385,7 @@ export const inputsCustomizations = {
color: (theme.vars || theme).palette.text.primary, color: (theme.vars || theme).palette.text.primary,
borderRadius: (theme.vars || theme).shape.borderRadius, borderRadius: (theme.vars || theme).shape.borderRadius,
border: `1px solid ${(theme.vars || theme).palette.divider}`, border: `1px solid ${(theme.vars || theme).palette.divider}`,
backgroundColor: (theme.vars || theme).palette.background.default, backgroundColor: "#272727",
transition: 'border 120ms ease-in', transition: 'border 120ms ease-in',
'&:hover': { '&:hover': {
borderColor: gray[400], borderColor: gray[400],

View file

@ -1,445 +1,459 @@
import * as React from 'react'; import * as React from "react";
import { alpha, Theme, Components } from '@mui/material/styles'; import { alpha, Theme, Components } from "@mui/material/styles";
import { outlinedInputClasses } from '@mui/material/OutlinedInput'; import { outlinedInputClasses } from "@mui/material/OutlinedInput";
import { svgIconClasses } from '@mui/material/SvgIcon'; import { svgIconClasses } from "@mui/material/SvgIcon";
import { toggleButtonGroupClasses } from '@mui/material/ToggleButtonGroup'; import { toggleButtonGroupClasses } from "@mui/material/ToggleButtonGroup";
import { toggleButtonClasses } from '@mui/material/ToggleButton'; import { toggleButtonClasses } from "@mui/material/ToggleButton";
import CheckBoxOutlineBlankRoundedIcon from '@mui/icons-material/CheckBoxOutlineBlankRounded'; import CheckBoxOutlineBlankRoundedIcon from "@mui/icons-material/CheckBoxOutlineBlankRounded";
import CheckRoundedIcon from '@mui/icons-material/CheckRounded'; import CheckRoundedIcon from "@mui/icons-material/CheckRounded";
import RemoveRoundedIcon from '@mui/icons-material/RemoveRounded'; import RemoveRoundedIcon from "@mui/icons-material/RemoveRounded";
import { gray, brand } from '../themePrimitives'; import { gray, brand } from "../themePrimitives";
/* eslint-disable import/prefer-default-export */ /* eslint-disable import/prefer-default-export */
export const inputsCustomizations: Components<Theme> = { export const inputsCustomizations: Components<Theme> = {
MuiButtonBase: { MuiButtonBase: {
defaultProps: { defaultProps: {
disableTouchRipple: true, disableTouchRipple: true,
disableRipple: true, disableRipple: true,
}, },
styleOverrides: { styleOverrides: {
root: ({ theme }) => ({ root: ({ theme }) => ({
boxSizing: 'border-box', boxSizing: "border-box",
transition: 'all 100ms ease-in', transition: "all 100ms ease-in",
'&:focus-visible': { "&:focus-visible": {
outline: `3px solid ${alpha(theme.palette.primary.main, 0.5)}`, outline: `3px solid ${alpha(
outlineOffset: '2px', theme.palette.primary.main,
}, 0.5
}), )}`,
}, outlineOffset: "2px",
}, },
MuiButton: { }),
styleOverrides: { },
root: ({ theme }) => ({ },
boxShadow: 'none', MuiButton: {
borderRadius: (theme.vars || theme).shape.borderRadius, styleOverrides: {
textTransform: 'none', root: ({ theme }) => ({
variants: [ boxShadow: "none",
{ borderRadius: (theme.vars || theme).shape.borderRadius,
props: { textTransform: "none",
size: 'small', variants: [
}, {
style: { props: {
height: '2.25rem', size: "small",
padding: '8px 12px', },
}, style: {
}, height: "2.25rem",
{ padding: "8px 12px",
props: { },
size: 'medium', },
}, {
style: { props: {
height: '2.5rem', // 40px size: "medium",
}, },
}, style: {
{ height: "2.5rem", // 40px
props: { },
color: 'primary', },
variant: 'contained', {
}, props: {
style: { color: "primary",
color: 'white', variant: "contained",
backgroundColor: gray[900], },
backgroundImage: `linear-gradient(to bottom, ${gray[700]}, ${gray[800]})`, style: {
boxShadow: `inset 0 1px 0 ${gray[600]}, inset 0 -1px 0 1px hsl(220, 0%, 0%)`, color: "white",
border: `1px solid ${gray[700]}`, backgroundColor: gray[900],
'&:hover': { backgroundImage: `linear-gradient(to bottom, ${gray[700]}, ${gray[800]})`,
backgroundImage: 'none', boxShadow: `inset 0 1px 0 ${gray[600]}, inset 0 -1px 0 1px hsl(220, 0%, 0%)`,
backgroundColor: gray[700], border: `1px solid ${gray[700]}`,
boxShadow: 'none', "&:hover": {
}, backgroundImage: "none",
'&:active': { backgroundColor: gray[700],
backgroundColor: gray[800], boxShadow: "none",
}, },
...theme.applyStyles('dark', { "&:active": {
color: 'black', backgroundColor: gray[800],
backgroundColor: gray[50], },
backgroundImage: `linear-gradient(to bottom, ${gray[100]}, ${gray[50]})`, ...theme.applyStyles("dark", {
boxShadow: 'inset 0 -1px 0 hsl(220, 30%, 80%)', color: "black",
border: `1px solid ${gray[50]}`, backgroundColor: gray[50],
'&:hover': { backgroundImage: `linear-gradient(to bottom, ${gray[100]}, ${gray[50]})`,
backgroundImage: 'none', boxShadow: "inset 0 -1px 0 hsl(220, 30%, 80%)",
backgroundColor: gray[300], border: `1px solid ${gray[50]}`,
boxShadow: 'none', "&:hover": {
}, backgroundImage: "none",
'&:active': { backgroundColor: gray[300],
backgroundColor: gray[400], boxShadow: "none",
}, },
}), "&:active": {
}, backgroundColor: gray[400],
}, },
{ }),
props: { },
color: 'secondary', },
variant: 'contained', {
}, props: {
style: { color: "secondary",
color: 'white', variant: "contained",
backgroundColor: brand[300], },
backgroundImage: `linear-gradient(to bottom, ${alpha(brand[400], 0.8)}, ${brand[500]})`, style: {
boxShadow: `inset 0 2px 0 ${alpha(brand[200], 0.2)}, inset 0 -2px 0 ${alpha(brand[700], 0.4)}`, color: "white",
border: `1px solid ${brand[500]}`, backgroundColor: brand[300],
'&:hover': { backgroundImage: `linear-gradient(to bottom, ${alpha(
backgroundColor: brand[700], brand[400],
boxShadow: 'none', 0.8
}, )}, ${brand[500]})`,
'&:active': { boxShadow: `inset 0 2px 0 ${alpha(
backgroundColor: brand[700], brand[200],
backgroundImage: 'none', 0.2
}, )}, inset 0 -2px 0 ${alpha(brand[700], 0.4)}`,
}, border: `1px solid ${brand[500]}`,
}, "&:hover": {
{ backgroundColor: brand[700],
props: { boxShadow: "none",
variant: 'outlined', },
}, "&:active": {
style: { backgroundColor: brand[700],
color: (theme.vars || theme).palette.text.primary, backgroundImage: "none",
border: '1px solid', },
borderColor: gray[200], },
backgroundColor: alpha(gray[50], 0.3), },
'&:hover': { {
backgroundColor: gray[100], props: {
borderColor: gray[300], variant: "outlined",
}, },
'&:active': { style: {
backgroundColor: gray[200], color: (theme.vars || theme).palette.text.primary,
}, border: "1px solid",
...theme.applyStyles('dark', { borderColor: gray[200],
backgroundColor: gray[800], backgroundColor: alpha(gray[50], 0.3),
borderColor: gray[700], "&:hover": {
backgroundColor: gray[100],
borderColor: gray[300],
},
"&:active": {
backgroundColor: gray[200],
},
...theme.applyStyles("dark", {
backgroundColor: gray[800],
borderColor: gray[700],
'&:hover': { "&:hover": {
backgroundColor: gray[900], backgroundColor: gray[900],
borderColor: gray[600], borderColor: gray[600],
}, },
'&:active': { "&:active": {
backgroundColor: gray[900], backgroundColor: gray[900],
}, },
}), }),
}, },
}, },
{ {
props: { props: {
color: 'secondary', color: "secondary",
variant: 'outlined', variant: "outlined",
}, },
style: { style: {
color: brand[700], color: brand[700],
border: '1px solid', border: "1px solid",
borderColor: brand[200], borderColor: brand[200],
backgroundColor: brand[50], backgroundColor: brand[50],
'&:hover': { "&:hover": {
backgroundColor: brand[100], backgroundColor: brand[100],
borderColor: brand[400], borderColor: brand[400],
}, },
'&:active': { "&:active": {
backgroundColor: alpha(brand[200], 0.7), backgroundColor: alpha(brand[200], 0.7),
}, },
...theme.applyStyles('dark', { ...theme.applyStyles("dark", {
color: brand[50], color: brand[50],
border: '1px solid', border: "1px solid",
borderColor: brand[900], borderColor: brand[900],
backgroundColor: alpha(brand[900], 0.3), backgroundColor: alpha(brand[900], 0.3),
'&:hover': { "&:hover": {
borderColor: brand[700], borderColor: brand[700],
backgroundColor: alpha(brand[900], 0.6), backgroundColor: alpha(brand[900], 0.6),
}, },
'&:active': { "&:active": {
backgroundColor: alpha(brand[900], 0.5), backgroundColor: alpha(brand[900], 0.5),
}, },
}), }),
}, },
}, },
{ {
props: { props: {
variant: 'text', variant: "text",
}, },
style: { style: {
color: gray[600], color: gray[600],
'&:hover': { "&:hover": {
backgroundColor: gray[100], backgroundColor: gray[100],
}, },
'&:active': { "&:active": {
backgroundColor: gray[200], backgroundColor: gray[200],
}, },
...theme.applyStyles('dark', { ...theme.applyStyles("dark", {
color: gray[50], color: gray[50],
'&:hover': { "&:hover": {
backgroundColor: gray[700], backgroundColor: gray[700],
}, },
'&:active': { "&:active": {
backgroundColor: alpha(gray[700], 0.7), backgroundColor: alpha(gray[700], 0.7),
}, },
}), }),
}, },
}, },
{ {
props: { props: {
color: 'secondary', color: "secondary",
variant: 'text', variant: "text",
}, },
style: { style: {
color: brand[700], color: brand[700],
'&:hover': { "&:hover": {
backgroundColor: alpha(brand[100], 0.5), backgroundColor: alpha(brand[100], 0.5),
}, },
'&:active': { "&:active": {
backgroundColor: alpha(brand[200], 0.7), backgroundColor: alpha(brand[200], 0.7),
}, },
...theme.applyStyles('dark', { ...theme.applyStyles("dark", {
color: brand[100], color: brand[100],
'&:hover': { "&:hover": {
backgroundColor: alpha(brand[900], 0.5), backgroundColor: alpha(brand[900], 0.5),
}, },
'&:active': { "&:active": {
backgroundColor: alpha(brand[900], 0.3), backgroundColor: alpha(brand[900], 0.3),
}, },
}), }),
}, },
}, },
], ],
}), }),
}, },
}, },
MuiIconButton: { MuiIconButton: {
styleOverrides: { styleOverrides: {
root: ({ theme }) => ({ root: ({ theme }) => ({
boxShadow: 'none', boxShadow: "none",
borderRadius: (theme.vars || theme).shape.borderRadius, borderRadius: (theme.vars || theme).shape.borderRadius,
textTransform: 'none', textTransform: "none",
fontWeight: theme.typography.fontWeightMedium, fontWeight: theme.typography.fontWeightMedium,
letterSpacing: 0, letterSpacing: 0,
color: (theme.vars || theme).palette.text.primary, color: (theme.vars || theme).palette.text.primary,
border: '1px solid ', border: "1px solid ",
borderColor: gray[200], borderColor: gray[200],
backgroundColor: alpha(gray[50], 0.3), backgroundColor: alpha(gray[50], 0.3),
'&:hover': { "&:hover": {
backgroundColor: gray[100], backgroundColor: gray[100],
borderColor: gray[300], borderColor: gray[300],
}, },
'&:active': { "&:active": {
backgroundColor: gray[200], backgroundColor: gray[200],
}, },
...theme.applyStyles('dark', { ...theme.applyStyles("dark", {
backgroundColor: gray[800], backgroundColor: gray[800],
borderColor: gray[700], borderColor: gray[700],
'&:hover': { "&:hover": {
backgroundColor: gray[900], backgroundColor: gray[900],
borderColor: gray[600], borderColor: gray[600],
}, },
'&:active': { "&:active": {
backgroundColor: gray[900], backgroundColor: gray[900],
}, },
}), }),
variants: [ variants: [
{ {
props: { props: {
size: 'small', size: "small",
}, },
style: { style: {
width: '2.25rem', width: "2.25rem",
height: '2.25rem', height: "2.25rem",
padding: '0.25rem', padding: "0.25rem",
[`& .${svgIconClasses.root}`]: { fontSize: '1rem' }, [`& .${svgIconClasses.root}`]: { fontSize: "1rem" },
}, },
}, },
{ {
props: { props: {
size: 'medium', size: "medium",
}, },
style: { style: {
width: '2.5rem', width: "2.5rem",
height: '2.5rem', height: "2.5rem",
}, },
}, },
], ],
}), }),
}, },
}, },
MuiToggleButtonGroup: { MuiToggleButtonGroup: {
styleOverrides: { styleOverrides: {
root: ({ theme }) => ({ root: ({ theme }) => ({
borderRadius: '10px', borderRadius: "10px",
boxShadow: `0 4px 16px ${alpha(gray[400], 0.2)}`, boxShadow: `0 4px 16px ${alpha(gray[400], 0.2)}`,
[`& .${toggleButtonGroupClasses.selected}`]: { [`& .${toggleButtonGroupClasses.selected}`]: {
color: brand[500], color: brand[500],
}, },
...theme.applyStyles('dark', { ...theme.applyStyles("dark", {
[`& .${toggleButtonGroupClasses.selected}`]: { [`& .${toggleButtonGroupClasses.selected}`]: {
color: '#fff', color: "#fff",
}, },
boxShadow: `0 4px 16px ${alpha(brand[700], 0.5)}`, boxShadow: `0 4px 16px ${alpha(brand[700], 0.5)}`,
}), }),
}), }),
}, },
}, },
MuiToggleButton: { MuiToggleButton: {
styleOverrides: { styleOverrides: {
root: ({ theme }) => ({ root: ({ theme }) => ({
padding: '12px 16px', padding: "12px 16px",
textTransform: 'none', textTransform: "none",
borderRadius: '10px', borderRadius: "10px",
fontWeight: 500, fontWeight: 500,
...theme.applyStyles('dark', { ...theme.applyStyles("dark", {
color: gray[400], color: gray[400],
boxShadow: '0 4px 16px rgba(0, 0, 0, 0.5)', boxShadow: "0 4px 16px rgba(0, 0, 0, 0.5)",
[`&.${toggleButtonClasses.selected}`]: { [`&.${toggleButtonClasses.selected}`]: {
color: brand[300], color: brand[300],
}, },
}), }),
}), }),
}, },
}, },
MuiCheckbox: { MuiCheckbox: {
defaultProps: { defaultProps: {
disableRipple: true, disableRipple: true,
icon: ( icon: (
<CheckBoxOutlineBlankRoundedIcon sx={{ color: 'hsla(210, 0%, 0%, 0.0)' }} /> <CheckBoxOutlineBlankRoundedIcon
), sx={{ color: "hsla(210, 0%, 0%, 0.0)" }}
checkedIcon: <CheckRoundedIcon sx={{ height: 14, width: 14 }} />, />
indeterminateIcon: <RemoveRoundedIcon sx={{ height: 14, width: 14 }} />, ),
}, checkedIcon: <CheckRoundedIcon sx={{ height: 14, width: 14 }} />,
styleOverrides: { indeterminateIcon: (
root: ({ theme }) => ({ <RemoveRoundedIcon sx={{ height: 14, width: 14 }} />
margin: 10, ),
height: 16, },
width: 16, styleOverrides: {
borderRadius: 5, root: ({ theme }) => ({
border: '1px solid ', margin: 10,
borderColor: alpha(gray[300], 0.8), height: 16,
boxShadow: '0 0 0 1.5px hsla(210, 0%, 0%, 0.04) inset', width: 16,
backgroundColor: alpha(gray[100], 0.4), borderRadius: 5,
transition: 'border-color, background-color, 120ms ease-in', border: "1px solid ",
'&:hover': { borderColor: alpha(gray[300], 0.8),
borderColor: brand[300], boxShadow: "0 0 0 1.5px hsla(210, 0%, 0%, 0.04) inset",
}, backgroundColor: alpha(gray[100], 0.4),
'&.Mui-focusVisible': { transition: "border-color, background-color, 120ms ease-in",
outline: `3px solid ${alpha(brand[500], 0.5)}`, "&:hover": {
outlineOffset: '2px', borderColor: brand[300],
borderColor: brand[400], },
}, "&.Mui-focusVisible": {
'&.Mui-checked': { outline: `3px solid ${alpha(brand[500], 0.5)}`,
color: 'white', outlineOffset: "2px",
backgroundColor: brand[500], borderColor: brand[400],
borderColor: brand[500], },
boxShadow: `none`, "&.Mui-checked": {
'&:hover': { color: "white",
backgroundColor: brand[600], backgroundColor: brand[500],
}, borderColor: brand[500],
}, boxShadow: `none`,
...theme.applyStyles('dark', { "&:hover": {
borderColor: alpha(gray[700], 0.8), backgroundColor: brand[600],
boxShadow: '0 0 0 1.5px hsl(210, 0%, 0%) inset', },
backgroundColor: alpha(gray[900], 0.8), },
'&:hover': { ...theme.applyStyles("dark", {
borderColor: brand[300], borderColor: alpha(gray[700], 0.8),
}, boxShadow: "0 0 0 1.5px hsl(210, 0%, 0%) inset",
'&.Mui-focusVisible': { backgroundColor: alpha(gray[900], 0.8),
borderColor: brand[400], "&:hover": {
outline: `3px solid ${alpha(brand[500], 0.5)}`, borderColor: brand[300],
outlineOffset: '2px', },
}, "&.Mui-focusVisible": {
}), borderColor: brand[400],
}), outline: `3px solid ${alpha(brand[500], 0.5)}`,
}, outlineOffset: "2px",
}, },
MuiInputBase: { }),
styleOverrides: { }),
root: { },
border: 'none', },
}, MuiInputBase: {
input: { styleOverrides: {
'&::placeholder': { root: {
opacity: 0.7, border: "none",
color: gray[500], },
}, input: {
}, "&::placeholder": {
}, opacity: 0.7,
}, color: gray[500],
MuiOutlinedInput: { },
styleOverrides: { },
input: { },
padding: 0, },
}, MuiOutlinedInput: {
root: ({ theme }) => ({ styleOverrides: {
padding: '8px 12px', input: {
color: (theme.vars || theme).palette.text.primary, padding: 0,
borderRadius: (theme.vars || theme).shape.borderRadius, },
border: `1px solid ${(theme.vars || theme).palette.divider}`, root: ({ theme }) => ({
backgroundColor: (theme.vars || theme).palette.background.default, padding: "8px 12px",
transition: 'border 120ms ease-in', color: (theme.vars || theme).palette.text.primary,
'&:hover': { borderRadius: (theme.vars || theme).shape.borderRadius,
borderColor: gray[400], border: `1px solid ${(theme.vars || theme).palette.divider}`,
}, backgroundColor: (theme.vars || theme).palette.background
[`&.${outlinedInputClasses.focused}`]: { .default,
outline: `3px solid ${alpha(brand[500], 0.5)}`, transition: "border 120ms ease-in",
borderColor: brand[400], "&:hover": {
}, borderColor: gray[400],
...theme.applyStyles('dark', { },
'&:hover': { [`&.${outlinedInputClasses.focused}`]: {
borderColor: gray[500], outline: `3px solid ${alpha(brand[500], 0.5)}`,
}, borderColor: brand[400],
}), },
variants: [ ...theme.applyStyles("dark", {
{ "&:hover": {
props: { borderColor: gray[500],
size: 'small', },
}, }),
style: { variants: [
height: '2.25rem', {
}, props: {
}, size: "small",
{ },
props: { style: {
size: 'medium', height: "2.25rem",
}, },
style: { },
height: '2.5rem', {
}, props: {
}, size: "medium",
], },
}), style: {
notchedOutline: { height: "2.5rem",
border: 'none', },
}, },
}, ],
}, }),
MuiInputAdornment: { notchedOutline: {
styleOverrides: { border: "none",
root: ({ theme }) => ({ },
color: (theme.vars || theme).palette.grey[500], },
...theme.applyStyles('dark', { },
color: (theme.vars || theme).palette.grey[400], MuiInputAdornment: {
}), styleOverrides: {
}), root: ({ theme }) => ({
}, color: (theme.vars || theme).palette.grey[500],
}, ...theme.applyStyles("dark", {
MuiFormLabel: { color: (theme.vars || theme).palette.grey[400],
styleOverrides: { }),
root: ({ theme }) => ({ }),
typography: theme.typography.caption, },
marginBottom: 8, },
}), MuiFormLabel: {
}, styleOverrides: {
}, root: ({ theme }) => ({
typography: theme.typography.caption,
marginBottom: 8,
}),
},
},
}; };