418 lines
11 KiB
TypeScript
418 lines
11 KiB
TypeScript
import { Controller, useForm } from "react-hook-form";
|
|
import {
|
|
Box,
|
|
Button,
|
|
Typography,
|
|
Modal,
|
|
IconButton,
|
|
InputAdornment,
|
|
Select,
|
|
MenuItem,
|
|
FormControl,
|
|
} from "@mui/material";
|
|
import CloseIcon from "@mui/icons-material/Close";
|
|
import { Visibility, VisibilityOff } from "@mui/icons-material";
|
|
import { useDispatch, useSelector } from "react-redux";
|
|
import { addManager, managerList } from "../../redux/slices/managerSlice.ts";
|
|
import { CustomIconButton, CustomTextField } from "../AddUserModal/styled.css";
|
|
import React, { useEffect, useState } from "react";
|
|
import { RootState } from "../../redux/reducers.ts";
|
|
import { stationList } from "../../redux/slices/stationSlice.ts";
|
|
import { autofillFix } from "../../shared-theme/customizations/autoFill.tsx";
|
|
|
|
export default function AddManagerModal({ open, handleClose }) {
|
|
const dispatch = useDispatch();
|
|
const {
|
|
control,
|
|
register,
|
|
handleSubmit,
|
|
formState: { errors },
|
|
reset,
|
|
clearErrors,
|
|
} = useForm({
|
|
mode: "onChange", // Trigger validation on change for real-time feedback
|
|
});
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
const stations = useSelector(
|
|
(state: RootState) => state?.stationReducer.stations
|
|
);
|
|
|
|
useEffect(() => {
|
|
dispatch(stationList());
|
|
}, [dispatch]);
|
|
|
|
// Handle form submission
|
|
const onSubmit = async (data: any) => {
|
|
const selectedStation = stations.find(
|
|
(station) => station.name === data.stationName
|
|
);
|
|
const managerData = {
|
|
name: data.name,
|
|
email: data.email,
|
|
phone: data.phone,
|
|
password: data.password,
|
|
stationId: selectedStation?.id,
|
|
};
|
|
|
|
try {
|
|
await dispatch(addManager(managerData));
|
|
dispatch(managerList());
|
|
clearErrors(); // Clear errors on successful submission
|
|
reset(); // Reset form fields on successful submission
|
|
handleClose(); // Close modal
|
|
} catch (error) {
|
|
console.error("Error adding manager:", error);
|
|
}
|
|
};
|
|
|
|
// Handle modal close, clearing errors and resetting form
|
|
const handleModalClose = () => {
|
|
clearErrors(); // Clear errors when closing via close icon
|
|
reset(); // Reset form fields when closing via close icon
|
|
handleClose(); // Close modal
|
|
};
|
|
|
|
const togglePasswordVisibility = (e: React.MouseEvent) => {
|
|
e.preventDefault();
|
|
setShowPassword((prev) => !prev);
|
|
};
|
|
|
|
return (
|
|
<Modal
|
|
open={open}
|
|
onClose={(e, reason) => {
|
|
if (reason === "backdropClick") {
|
|
return; // Prevent closing on backdrop click
|
|
}
|
|
handleModalClose();
|
|
}}
|
|
aria-labelledby="add-manager-modal"
|
|
>
|
|
<Box
|
|
sx={{
|
|
position: "absolute",
|
|
top: "50%",
|
|
left: "50%",
|
|
transform: "translate(-50%, -50%)",
|
|
width: 400,
|
|
bgcolor: "#000000",
|
|
boxShadow: 24,
|
|
p: 3,
|
|
borderRadius: 2,
|
|
}}
|
|
>
|
|
{/* Header */}
|
|
<Box
|
|
sx={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
}}
|
|
>
|
|
<Typography
|
|
variant="h6"
|
|
fontWeight={600}
|
|
fontSize={"16px"}
|
|
color="#D0E1E9"
|
|
>
|
|
Add Manager
|
|
</Typography>
|
|
<CustomIconButton onClick={handleModalClose}>
|
|
<CloseIcon />
|
|
</CustomIconButton>
|
|
</Box>
|
|
|
|
{/* Horizontal Line */}
|
|
<Box sx={{ borderBottom: "1px solid #ddd", my: 2 }} />
|
|
|
|
{/* Form */}
|
|
<form onSubmit={handleSubmit(onSubmit)}>
|
|
{/* Manager Name */}
|
|
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
|
|
<Box sx={{ flex: 1, ...autofillFix }}>
|
|
<Typography
|
|
variant="body2"
|
|
fontWeight={500}
|
|
color="#D0E1E9"
|
|
>
|
|
Manager Name
|
|
</Typography>
|
|
<CustomTextField
|
|
fullWidth
|
|
placeholder="Enter Manager Name"
|
|
size="small"
|
|
sx={{ marginTop: 1 }}
|
|
error={!!errors.name}
|
|
helperText={
|
|
errors.name ? errors.name.message : ""
|
|
}
|
|
{...register("name", {
|
|
required: "Manager Name is required",
|
|
minLength: {
|
|
value: 3,
|
|
message:
|
|
"Minimum 3 characters required",
|
|
},
|
|
maxLength: {
|
|
value: 30,
|
|
message:
|
|
"Maximum 30 characters allowed",
|
|
},
|
|
pattern: {
|
|
value: /^[A-Za-z\s]+$/,
|
|
message:
|
|
"Manager Name must only contain letters and spaces",
|
|
},
|
|
})}
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Station Dropdown */}
|
|
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
|
|
<Box sx={{ flex: 1 }}>
|
|
<Typography
|
|
variant="body2"
|
|
fontWeight={500}
|
|
color="#D0E1E9"
|
|
>
|
|
Select Station
|
|
</Typography>
|
|
<FormControl
|
|
fullWidth
|
|
size="small"
|
|
error={!!errors.stationName}
|
|
>
|
|
<Controller
|
|
name="stationName"
|
|
control={control}
|
|
rules={{
|
|
required: "Station Name is required",
|
|
}}
|
|
render={({ field }) => (
|
|
<Select
|
|
{...field}
|
|
onChange={(e) =>
|
|
field.onChange(e.target.value)
|
|
} // Update form state
|
|
value={field.value || ""} // Ensure controlled value
|
|
sx={{ marginTop: 1 }}
|
|
displayEmpty
|
|
renderValue={(selected) => {
|
|
if (!selected) {
|
|
return (
|
|
<Typography color="text.secondary">
|
|
Choose Station
|
|
</Typography>
|
|
);
|
|
}
|
|
return selected;
|
|
}}
|
|
MenuProps={{
|
|
PaperProps: {
|
|
style: {
|
|
maxHeight: 224,
|
|
},
|
|
},
|
|
}}
|
|
>
|
|
{Array.isArray(stations) &&
|
|
stations.length > 0 ? (
|
|
stations.map((station) => (
|
|
<MenuItem
|
|
key={station.id}
|
|
value={station.name}
|
|
>
|
|
{station.name}
|
|
</MenuItem>
|
|
))
|
|
) : (
|
|
<MenuItem disabled>
|
|
No stations available
|
|
</MenuItem>
|
|
)}
|
|
</Select>
|
|
)}
|
|
/>
|
|
{errors.stationName && (
|
|
<Typography
|
|
color="error"
|
|
variant="body2"
|
|
sx={{ mt: 0.5 }}
|
|
>
|
|
{errors.stationName.message}
|
|
</Typography>
|
|
)}
|
|
</FormControl>
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Email and Password */}
|
|
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
|
|
{/* Email */}
|
|
<Box sx={{ flex: 1, ...autofillFix }}>
|
|
<Typography
|
|
variant="body2"
|
|
fontWeight={500}
|
|
color="#D0E1E9"
|
|
>
|
|
Email
|
|
</Typography>
|
|
<CustomTextField
|
|
fullWidth
|
|
placeholder="Enter Manager Email"
|
|
size="small"
|
|
sx={{ marginTop: 1 }}
|
|
autoComplete="new-email"
|
|
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:
|
|
"Please enter a valid email address (e.g., example@domain.com).",
|
|
},
|
|
})}
|
|
/>
|
|
</Box>
|
|
|
|
{/* Password */}
|
|
<Box sx={{ flex: 1, ...autofillFix }}>
|
|
<Typography
|
|
variant="body2"
|
|
fontWeight={500}
|
|
color="#D0E1E9"
|
|
>
|
|
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
|
|
sx={{ marginTop: 1 }}
|
|
autoComplete="new-password"
|
|
placeholder="Enter Password"
|
|
type={
|
|
showPassword ? "text" : "password"
|
|
}
|
|
fullWidth
|
|
color={
|
|
errors.password
|
|
? "error"
|
|
: "primary"
|
|
}
|
|
size="small"
|
|
slotProps={{
|
|
input: {
|
|
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>
|
|
|
|
{/* Phone Number */}
|
|
<Box sx={{ display: "flex", gap: 2, mb: 2 }}>
|
|
<Box sx={{ flex: 1, ...autofillFix }}>
|
|
<Typography
|
|
variant="body2"
|
|
fontWeight={500}
|
|
color="#D0E1E9"
|
|
>
|
|
Phone Number
|
|
</Typography>
|
|
<CustomTextField
|
|
fullWidth
|
|
placeholder="Enter Phone Number"
|
|
size="small"
|
|
sx={{ marginTop: 1 }}
|
|
error={!!errors.phone}
|
|
helperText={
|
|
errors.phone ? errors.phone.message : ""
|
|
}
|
|
{...register("phone", {
|
|
required: "Phone Number is required",
|
|
validate: (value) => {
|
|
if (!/^[0-9]*$/.test(value)) {
|
|
return "Only numbers are allowed";
|
|
}
|
|
if (value.length < 6) {
|
|
return "Phone number must be at least 6 digits";
|
|
}
|
|
if (value.length > 14) {
|
|
return "Phone number must be at most 14 digits";
|
|
}
|
|
return true;
|
|
},
|
|
})}
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Submit Button */}
|
|
<Box
|
|
sx={{
|
|
display: "flex",
|
|
justifyContent: "flex-end",
|
|
mt: 3,
|
|
}}
|
|
>
|
|
<Button
|
|
type="submit"
|
|
sx={{
|
|
backgroundColor: "#D0E1E9",
|
|
color: "#000000",
|
|
borderRadius: "8px",
|
|
fontSize: "16px",
|
|
width: "125px",
|
|
"&:hover": { backgroundColor: "#DFECF1" },
|
|
}}
|
|
>
|
|
Add Manager
|
|
</Button>
|
|
</Box>
|
|
</form>
|
|
</Box>
|
|
</Modal>
|
|
);
|
|
}
|