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 {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
IconButton,
TextField,
Typography,
Modal,
InputAdornment,
} from "@mui/material";
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 { Visibility, VisibilityOff } from "@mui/icons-material";
import {
CustomIconButton,
CustomTextField,
} from "../AddUserModel/styled.css.tsx";
//By Jaanvi : Edit Model :: 11-feb-25
interface AddEditCategoryModalProps {
@ -44,6 +47,7 @@ const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({
handleUpdate,
editRow,
}) => {
const [showPassword, setShowPassword] = React.useState(false);
const {
control,
handleSubmit,
@ -59,7 +63,7 @@ const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({
password: "",
},
});
const onSubmit = (data: FormData) => {
if (editRow) {
handleUpdate(
@ -77,6 +81,10 @@ const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({
reset();
};
const togglePasswordVisibility = () => {
setShowPassword((prev) => !prev);
};
useEffect(() => {
if (editRow) {
setValue("name", editRow.name);
@ -88,210 +96,327 @@ const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({
}
}, [editRow, setValue, reset]);
const [showPassword, setShowPassword] = React.useState(false);
return (
<Dialog
<Modal
open={open}
onClose={handleClose}
maxWidth="md"
fullWidth
PaperProps={{
component: "form",
onSubmit: handleSubmit(onSubmit),
}}
aria-labelledby="add-edit-category-modal"
>
<DialogTitle
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 600,
bgcolor: "background.paper",
boxShadow: 24,
p: 3,
borderRadius: 2,
}}
>
{editRow ? "Edit Admin" : "Add Admin"}
{/* Header */}
<Box
onClick={handleClose}
sx={{
cursor: "pointer",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<CloseIcon />
<Typography variant="h6" fontWeight={600}>
{editRow ? "Edit Admin" : "Add Admin"}
</Typography>
<CustomIconButton onClick={handleClose}>
<CloseIcon />
</CustomIconButton>
</Box>
</DialogTitle>
<DialogContent>
<Controller
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}
/>
)}
/>
{/* Horizontal Line */}
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
<Controller
name="email"
control={control}
rules={{
required: "Email is required",
pattern: {
value: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
message: "Invalid email address",
},
}}
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",
{/* Form */}
<form onSubmit={handleSubmit(onSubmit)}>
{/* Input Fields */}
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: 2,
}}
render={({ field }) => (
<>
<Box sx={{position:"relative" }}>
<TextField
{...field}
required
margin="dense"
label="Password"
type={showPassword ? "text" : "password"}
id="password"
autoComplete="current-password"
autoFocus
fullWidth
variant="standard"
error={!!errors.password}
helperText={errors.password?.message}
/>
<IconButton
sx={{
position: "absolute",
top: "60%",
right: "10px",
background: "none",
borderColor: "transparent",
transform: "translateY(-50%)",
"&:hover": {
backgroundColor: "transparent",
borderColor: "transparent",
>
{/* First Row - Two Inputs */}
<Box sx={{ display: "flex", gap: 2 }}>
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "50%",
}}
>
<Typography variant="body2" fontWeight={500}>
Admin Name
</Typography>
<Controller
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",
},
}}
onClick={() =>
setShowPassword((prev) => !prev)
}
>
{showPassword ? (
<VisibilityOff />
) : (
<Visibility />
render={({ field }) => (
<CustomTextField
{...field}
required
placeholder="Enter Admin Name"
fullWidth
size="small"
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>
</>
)}
/>
)}
<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 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}
/>
)}
/>
)}
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "50%",
}}
>
<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>
<Controller
name="registeredAddress"
control={control}
rules={{
required: "Address is required",
maxLength: {
value: 100,
message: "Address cannot exceed 100 characters",
},
}}
render={({ field }) => (
<TextField
{...field}
required
margin="dense"
label="Address"
type="text"
fullWidth
variant="standard"
error={!!errors.registeredAddress}
helperText={errors.registeredAddress?.message}
/>
)}
/>
</DialogContent>
{/* Third Row - One Input */}
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "50%",
}}
>
<Typography variant="body2" fontWeight={500}>
Address
</Typography>
<Controller
name="registeredAddress"
control={control}
rules={{
required: "Address is required",
maxLength: {
value: 100,
message:
"Address cannot exceed 100 characters",
},
}}
render={({ field }) => (
<CustomTextField
{...field}
required
placeholder="Enter Address"
fullWidth
size="small"
error={!!errors.registeredAddress}
helperText={
errors.registeredAddress?.message
}
/>
)}
/>
</Box>
</Box>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button type="submit">{editRow ? "Update" : "Create"}</Button>
</DialogActions>
</Dialog>
{/* 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 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 {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Typography,
TextField,
Modal,
IconButton,
InputAdornment,
styled,
} from "@mui/material";
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 { 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 {
open: boolean;
handleClose: () => void;
@ -20,52 +30,55 @@ interface AddUserModalProps {
id: string,
name: string,
email: string,
password: string,
phone: string,
password: string
) => void;
editRow: any;
}
interface FormData {
name: string;
email: string;
phone: string;
password: string;
}
const AddUserModal: React.FC<AddUserModalProps> = ({
open,
handleClose,
handleCreate,
handleUpdate,
editRow,
}) => {
const [showPassword, setShowPassword] = useState(false);
const {
control,
handleSubmit,
formState: { errors },
setValue,
reset,
} = useForm<FormData>({
defaultValues: {
name: "",
email: "",
password: "",
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) => {
if (editRow) {
handleUpdate(
editRow.id,
data.name,
data.email,
data.phone,
data.password
data.password,
data.phone
);
} else {
handleCreate(data);
}
@ -73,156 +86,287 @@ const AddUserModal: React.FC<AddUserModalProps> = ({
reset();
};
const togglePasswordVisibility = () => {
setShowPassword((prev) => !prev);
};
return (
<Dialog
<Modal
open={open}
onClose={handleClose}
maxWidth="md"
fullWidth
PaperProps={{
component: "form",
onSubmit: handleSubmit(onSubmit),
}}
aria-labelledby="add-user-modal"
>
<DialogTitle
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
position: "absolute",
top: "50%",
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
onClick={handleClose}
sx={{
cursor: "pointer",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<CloseIcon />
<Typography variant="h6" fontWeight={600}>
{editRow ? "Edit User" : "Add User"}
</Typography>
<CustomIconButton onClick={handleClose}>
<CloseIcon />
</CustomIconButton>
</Box>
</DialogTitle>
<DialogContent>
<Controller
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}
/>
)}
/>
{/* Horizontal Line */}
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
<Controller
name="email"
control={control}
rules={{
required: "Email is required",
pattern: {
value: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
message: "Invalid email address",
},
}}
render={({ field }) => (
<TextField
{...field}
required
margin="dense"
label="Email"
type="email"
fullWidth
variant="standard"
error={!!errors.email}
helperText={errors.email?.message}
/>
)}
/>
<Controller
name="password"
control={control}
rules={{
required: "password is required",
}}
render={({ field }) => (
<TextField
{...field}
required
margin="dense"
label="Password"
type="password"
fullWidth
variant="standard"
error={!!errors.password}
helperText={errors.password?.message}
/>
)}
/>
<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 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>
{/* 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}>
User Name
</Typography>
<Controller
name="name"
control={control}
rules={{
required: "User Name is required",
minLength: {
value: 3,
message:
"Minimum 3 characters required",
},
maxLength: {
value: 30,
message:
"Maximum 30 characters allowed",
},
}}
render={({ field }) => (
<CustomTextField
{...field}
required
placeholder="Enter User Name"
fullWidth
size="small"
error={!!errors.name}
helperText={errors.name?.message}
/>
)}
/>
</Box>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button type="submit">Create</Button>
</DialogActions>
</Dialog>
<Box
sx={{
display: "flex",
flexDirection: "column",
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;
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 Paper, { paperClasses } from "@mui/material/Paper";
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 {
Box,
Button,
IconButton,
InputAdornment,
Menu,
IconButton,
Pagination,
TextField,
Typography,
@ -25,9 +27,14 @@ import MoreHorizRoundedIcon from "@mui/icons-material/MoreHorizRounded";
import DeleteModal from "../Modals/DeleteModal";
import { AppDispatch } from "../../redux/store/store";
import ViewModal from "../Modals/ViewModal";
import VehicleViewModal from "../Modals/VehicleViewModal";
import SearchIcon from "@mui/icons-material/Search";
import TuneIcon from "@mui/icons-material/Tune";
import {
CustomIconButton,
} from "../AddUserModel/styled.css.tsx";
import ManagerViewModal from "../Modals/ViewManagerModal";
// Styled components for customization
const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
@ -73,7 +80,7 @@ interface CustomTableProps {
handleStatusToggle: (id: string, currentStatus: number) => void;
tableType: string; // Adding tableType prop to change header text dynamically
handleClickOpen: () => void;
handleDeleteButton: (id: string | number | undefined) => void;
//handleDeleteButton: (id: string | number | undefined) => void;
}
const CustomTable: React.FC<CustomTableProps> = ({
@ -97,6 +104,8 @@ const CustomTable: React.FC<CustomTableProps> = ({
const [currentPage, setCurrentPage] = React.useState(1);
const usersPerPage = 10;
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>, row: Row) => {
@ -107,18 +116,13 @@ const CustomTable: React.FC<CustomTableProps> = ({
};
const handleViewButton = (id: string | undefined) => {
if (!id) {
console.error("ID not found for viewing.");
return;
}
setViewModal(true);
};
// const handleViewButton = (id: string | undefined) => {
// if (!id) {
// console.error("ID not found for viewing.");
// return;
// }
// setViewModal(true);
// };
const handleClose = () => {
@ -132,16 +136,58 @@ const CustomTable: React.FC<CustomTableProps> = ({
return false;
};
const handleDeleteButton = (id: string | undefined) => {
if (!id) {
console.error("ID not found for viewing.");
return;
const handleDeleteButton = (id: string | undefined) => {
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(
(row) =>
(row.name &&
@ -217,11 +263,15 @@ const filteredRows = rows.filter(
borderColor: "#52ACDF",
},
},
"& .MuiInputBase-input::placeholder": {
color: "#D9D8D8",
opacity: 1,
},
}}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon sx={{ color: "#272727" }} />
<SearchIcon sx={{ color: "#52ACDF" }} />
</InputAdornment>
),
}}
@ -252,8 +302,8 @@ const filteredRows = rows.filter(
? "Role"
: tableType === "user"
? "User"
: tableType === "managers"
? "Managers"
: tableType === "manager"
? "Manager"
: tableType === "vehicle"
? "Vehicle"
: "Item"}
@ -330,7 +380,7 @@ const filteredRows = rows.filter(
) : column.id !== "action" ? (
row[column.id]
) : (
<IconButton
<CustomIconButton
onClick={(e) => {
handleClick(e, row);
setRowData(row); // Store the selected row
@ -340,20 +390,13 @@ const filteredRows = rows.filter(
minWidth: 0,
width: "auto",
height: "auto",
backgroundColor:
"transparent",
color: "#FFFFFF",
border: "none",
"&:hover": {
backgroundColor:
"transparent",
},
}}
>
<MoreHorizRoundedIcon
sx={{ fontSize: "24px" }}
/>
</IconButton>
</CustomIconButton>
)}
</StyledTableCell>
))}
@ -411,6 +454,9 @@ const filteredRows = rows.filter(
[`& .${paperClasses.root}`]: {
padding: 0,
},
"& .MuiList-root": {
background: "#272727", // Remove any divider under menu items
},
}}
>
<Box
@ -424,7 +470,7 @@ const filteredRows = rows.filter(
variant="text"
onClick={(e) => {
e.stopPropagation();
// setSelectedRow(row);
// setSelectedRow(row);
setViewModal(true);
}}
color="primary"
@ -437,16 +483,36 @@ const filteredRows = rows.filter(
>
View
</Button>
{viewModal && (
<ViewModal
handleView={() => handleViewButton(selectedRow?.id)}
open={viewModal}
setViewModal={setViewModal}
id={selectedRow?.id}
tableType={tableType}
/>
)}
{viewModal && tableType === "admin" && (
<ViewModal
handleView={() =>
handleViewButton(selectedRow?.id)
}
open={viewModal}
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
variant="text"
@ -485,14 +551,12 @@ const filteredRows = rows.filter(
onClick={(e) => {
e.stopPropagation();
setDeleteModal(true);
}}
color="error"
sx={{
justifyContent: "flex-start",
py: 0,
fontWeight: "bold",
color: "red",
}}
>
Delete
@ -503,18 +567,16 @@ const filteredRows = rows.filter(
{/* Modals */}
{deleteModal && (
<DeleteModal
handleDelete={() => handleDeleteButton(selectedRow?.id)}
open={deleteModal}
setDeleteModal={setDeleteModal}
id={selectedRow?.id}
tableType={tableType}
/>
)}
<DeleteModal
handleDelete={() => handleDeleteButton(selectedRow?.id)}
open={deleteModal}
setDeleteModal={setDeleteModal}
id={selectedRow?.id}
/>
)}
</Box>
);
};
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 />,
url: "/panel/manager-list", // Placeholder for now
},
userRole === "admin" && {
text: "Vehicles",
icon: <ManageAccountsOutlinedIcon />,
url: "/panel/vehicles", // Placeholder for now
},
];
const filteredMenuItems = baseMenuItems.filter(Boolean);

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import axios from "axios";
// import { useHistory } from "react-router-dom";
const http = axios.create({
baseURL: process.env.REACT_APP_BACKEND_URL,
});
@ -11,5 +11,18 @@ http.interceptors.request.use((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;

View file

@ -11,9 +11,8 @@ import {
Typography,
Box,
Grid,
FormControlLabel,
Button,
TextField,
FormControlLabel,
Snackbar,
} from "@mui/material";
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 { AppDispatch, RootState } from "../../redux/store/store"; // Assuming this is the path to your store file
import { toast } from "sonner";
import {
CustomTextField,
} from "../../components/AddUserModel/styled.css.tsx";
// Define the data structure for permission
interface Permission {
module: string;
@ -155,7 +157,7 @@ const AddEditRolePage: React.FC = () => {
{/* Role Name Input */}
<Box sx={{ mb: 2 }}>
<label>Role Name</label>
<TextField
<CustomTextField
placeholder="Enter role name"
value={roleName}
onChange={handleRoleNameChange}
@ -256,10 +258,15 @@ const AddEditRolePage: React.FC = () => {
{/* Submit Button */}
<Box sx={{ mt: 2, display: "flex", justifyContent: "flex-end" }}>
<Button
variant="contained"
color="primary"
onClick={handleSubmit}
disabled={loading}
sx={{
backgroundColor: "#52ACDF",
color: "white",
borderRadius: "8px",
width: "117px",
"&:hover": { backgroundColor: "#439BC1" },
}}
>
{loading ? "Saving..." : "Save Role"}
</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) {
return (
<Dialog
open={open}
onClose={handleClose}
PaperProps={{
component: 'form',
onSubmit: (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
handleClose();
},
sx: { backgroundImage: 'none' },
}}
>
<DialogTitle>Reset password</DialogTitle>
<DialogContent
sx={{ display: 'flex', flexDirection: 'column', gap: 2, width: '100%' }}
>
<DialogContentText>
Enter your account&apos;s email address, and we&apos;ll send you a link to
reset your password.
</DialogContentText>
<OutlinedInput
autoFocus
required
margin="dense"
id="email"
name="email"
label="Email address"
placeholder="Email address"
type="email"
fullWidth
/>
</DialogContent>
<DialogActions sx={{ pb: 3, px: 3 }}>
<Button onClick={handleClose}>Cancel</Button>
<Button variant="contained" type="submit">
Continue
</Button>
</DialogActions>
</Dialog>
<Dialog
open={open}
onClose={handleClose}
PaperProps={{
component: "form",
onSubmit: (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
handleClose();
},
sx: { backgroundImage: "none" },
}}
>
<DialogTitle>Reset password</DialogTitle>
<DialogContent
sx={{
display: "flex",
flexDirection: "column",
gap: 2,
width: "100%",
}}
>
<DialogContentText>
Enter your account&apos;s email address, and we&apos;ll send
you a link to reset your password.
</DialogContentText>
<OutlinedInput
autoFocus
required
margin="dense"
id="email"
name="email"
label="Email address"
placeholder="Email address"
type="email"
fullWidth
/>
</DialogContent>
<DialogActions sx={{ pb: 3, px: 3 }}>
<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) => {
try {
const response = await dispatch(loginUser(data)).unwrap();
if (response?.data?.token) {
router("/panel/dashboard");
}
} catch (error: any) {
console.log("Login failed:", error);
try {
const response = await dispatch(loginUser(data)).unwrap();
if (response?.data?.token) {
router("/panel/dashboard");
}
};
} catch (error: any) {
console.log("Login failed:", error);
}
};
return (
<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 { 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 EqualizerIcon from "@mui/icons-material/Tune";
import CustomTable, { Column } from "../../components/CustomTable";
import AddManagerModal from "../../pages/AddManagerModal";
import EditUserModal from "../EditUserModal";
import AddManagerModal from "../../components/AddManagerModal";
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 { useForm } from "react-hook-form";
import EditManagerModal from "../../components/EditManagerModal";
export default function ManagerList() {
const dispatch = useDispatch<AppDispatch>();
const { managers, isLoading } = useSelector((state: RootState) => state.managerReducer);
const [addModalOpen, setAddModalOpen] = useState<boolean>(false);
const [editModalOpen, setEditModalOpen] = useState<boolean>(false);
const [editRow, setEditRow] = useState<any>(null);
const { reset } = useForm();
const [search, setSearch] = useState("");
const [addButtonModal, setAddButtonModal] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
const [rowData, setRowData] = useState(null);
const [viewModal, setViewModal] = useState(false);
const [deleteModal, setDeleteModal] = useState(false);
const [deleteModal, setDeleteModal] = useState<boolean>(false);
const [viewModal, setViewModal] = useState<boolean>(false);
const [rowData, setRowData] = useState<any | null>(null);
const [searchTerm, setSearchTerm] = useState("");
const dispatch = useDispatch<AppDispatch>();
const managers = useSelector(
(state: RootState) => state.managerReducer.managers
);
useEffect(() => {
dispatch(fetchManagerList()); // Fetch data when component mounts
}, [dispatch]);
useEffect(() => {
dispatch(managerList());
}, [dispatch]);
const handleClickOpen = () => {
setRowData(null); // Reset row data when opening for new admin
setAddModalOpen(true);
};
// Function to handle adding a manager
const handleAddManager = async (newManager: {
name: string;
registeredAddress: string;
phone: string;
email: string;
password: string;
}) => {
await dispatch(addManager(newManager));
dispatch(fetchManagerList()); // Refresh list after adding
handleCloseModal(); // Close modal after adding
};
const handleCloseModal = () => {
setAddModalOpen(false);
setEditModalOpen(false);
setRowData(null);
reset();
};
const handleAddManager = async (data: {
name: string;
email: string;
phone: string;
registeredAddress: string;
}) => {
try {
await dispatch(addManager(data)); // Dispatch action to add manager
await dispatch(managerList()); // Fetch the updated list
handleCloseModal(); // Close the modal
} catch (error) {
console.error("Error adding manager", error);
}
};
const handleUpdate = async (
id: number,
name: string,
email: string,
phone: string,
registeredAddress: string
) => {
try {
// Creating the managerData object to match the expected payload structure
const managerData = {
name,
email,
phone,
registeredAddress,
};
// Dispatching the updateManager action with the correct payload structure
await dispatch(updateManager({ id, managerData }));
await dispatch(managerList()); // Refresh the manager list after updating
handleCloseModal(); // Close the modal after updating
} catch (error) {
console.error("Update failed", error);
}
};
// Remove 'stationName' from columns
const categoryColumns: Column[] = [
{ id: "srno", label: "Sr No" },
{ id: "name", label: "Name" },
{ id: "email", label: "Email" },
{ id: "phone", label: "Phone" },
{ id: "registeredAddress", label: "Station Location" },
// Function to handle updating a
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
{ id: "action", label: "Action", align: "center" },
];
// Update rows to remove 'stationName'
const categoryRows = managers?.map(
(
manager: {
id: number;
name: string;
email: string;
phone: string;
registeredAddress: string;
},
index: number
) => ({
id: manager?.id,
srno: index + 1,
name: manager?.name,
email: manager?.email,
phone: manager.phone ?? "NA",
registeredAddress: manager?.registeredAddress ?? "NA",
})
);
const handleDeleteManager = async () => {
if (!rowData?.id) {
console.error("❌ No manager ID found for deletion!");
return;
}
try {
const response = await dispatch(deleteManager(rowData.id));
if (response?.payload) {
dispatch(fetchManagerList()); // Refresh list after deletion
} else {
console.error("❌ Deletion failed!", response);
}
} catch (error) {
console.error("❌ Error deleting manager:", error);
}
setDeleteModal(false); // Close delete modal
handleCloseModal();
};
// 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,
})
}
/>
</>
);
return (
<>
<CustomTable
columns={categoryColumns}
rows={categoryRows}
setDeleteModal={setDeleteModal}
deleteModal={deleteModal}
setViewModal={setViewModal}
viewModal={viewModal}
setRowData={setRowData}
setModalOpen={() => setEditModalOpen(true)}
tableType="manager"
handleClickOpen={handleClickOpen}
/>
<AddManagerModal
open={addModalOpen}
handleClose={handleCloseModal}
handleAddManager={handleAddManager}
/>
<EditManagerModal
open={editModalOpen}
handleClose={handleCloseModal}
handleUpdate={handleUpdate}
editRow={rowData}
/>
</>
);
}

View file

@ -12,55 +12,18 @@ import {
updateVehicle,
vehicleList,
} from "../../redux/slices/VehicleSlice";
import SearchIcon from "@mui/icons-material/Search";
const categoryRows = [
{
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",
},
];
import AddVehicleModal from "../../components/AddVehicleModal";
import EditVehicleModal from "../../components/EditVehicleModal";
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 { reset } = useForm();
const [deleteModal, setDeleteModal] = React.useState<boolean>(false);
const [viewModal, setViewModal] = React.useState<boolean>(false);
const [rowData, setRowData] = React.useState<any | null>(null);
const [deleteModal, setDeleteModal] = useState<boolean>(false);
const [viewModal, setViewModal] = useState<boolean>(false);
const [rowData, setRowData] = useState<any | null>(null);
const [searchTerm, setSearchTerm] = useState("");
const dispatch = useDispatch<AppDispatch>();
const vehicles = useSelector(
@ -73,32 +36,38 @@ export default function VehicleList() {
const handleClickOpen = () => {
setRowData(null); // Reset row data when opening for new admin
setModalOpen(true);
setAddModalOpen(true);
};
const handleCloseModal = () => {
setModalOpen(false);
setAddModalOpen(false);
setEditModalOpen(false);
setRowData(null);
reset();
};
const handleCreate = async (data: {
name: string;
brand: string;
const handleAddVehicle = async (data: {
vehicleName: string;
company: string;
modelName: string;
chargeType: string;
imageUrl: string;
}) => {
try {
await dispatch(addVehicle(data));
await dispatch(vehicleList());
handleCloseModal();
await dispatch(addVehicle(data)); // Dispatch action to add vehicle
await dispatch(vehicleList()); // Fetch the updated list
handleCloseModal(); // Close the modal
} catch (error) {
console.error("Creation failed", error);
console.error("Error adding vehicle", error);
}
};
const handleUpdate = async (
id: number,
name: string,
brand: string,
company: string,
modelName: string,
chargeType: string,
imageUrl: string
) => {
try {
@ -106,53 +75,70 @@ export default function VehicleList() {
updateVehicle({
id,
name,
brand,
company,
modelName,
chargeType,
imageUrl,
})
);
await dispatch(vehicleList());
handleCloseModal();
} catch (error) {
console.error("Update failed", error);
}
};
const categoryColumns: Column[] = [
{ id: "srno", label: "Sr No" },
{ 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: "action", label: "Action", align: "center" },
];
const filteredVehicles = vehicles?.filter(
(vehicle) =>
vehicle.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
vehicle.brand.toLowerCase().includes(searchTerm.toLowerCase())
vehicle.name?.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
// ? filteredVehicles?.map(
// (
// vehicle: {
// id: number;
// name: string;
// brand: string;
// imageUrl: string;
// },
// index: number
// ) => ({
// id: vehicle?.id,
// srno: index + 1,
// name: vehicle?.name,
// brand: vehicle?.brand,
// imageUrl: vehicle?.imageUrl,
// })
// )
// : [];
const categoryRows = filteredVehicles?.length
? filteredVehicles?.map(
(
vehicle: {
id: number;
name: string;
company: string;
modelName: string;
chargeType: string;
imageUrl: string;
},
index: number
) => ({
id: vehicle?.id,
srno: index + 1,
name: vehicle?.name,
company: vehicle?.company,
modelName: vehicle?.modelName,
chargeType: vehicle?.chargeType,
imageUrl: vehicle?.imageUrl,
})
)
: [];
return (
<>
<CustomTable
columns={categoryColumns}
rows={categoryRows}
@ -161,20 +147,22 @@ export default function VehicleList() {
setViewModal={setViewModal}
viewModal={viewModal}
setRowData={setRowData}
setModalOpen={setModalOpen}
setModalOpen={() => setEditModalOpen(true)}
tableType="vehicle"
handleClickOpen={handleClickOpen}
/>
{/* <AddEditCategoryModal
open={modalOpen}
handleClose={handleCloseModal}
editRow={rowData}
/>
<DeleteModal
open={deleteModal}
setDeleteModal={setDeleteModal}
handleDelete={handleDelete}
/> */}
<AddVehicleModal
open={addModalOpen}
handleClose={handleCloseModal}
handleAddVehicle={handleAddVehicle}
/>
<EditVehicleModal
open={editModalOpen}
handleClose={handleCloseModal}
handleUpdate={handleUpdate}
editRow={rowData}
/>
</>
);
}

View file

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

View file

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

View file

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

View file

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

View file

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