marco.pms.web/src/components/Employee/ManageEmployee.jsx
2025-04-20 08:33:59 +00:00

661 lines
24 KiB
JavaScript

import React, { useEffect, useState } from "react";
import showToast from "../../services/toastService";
import EmployeeRepository from "../../repositories/EmployeeRepository";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import useMaster from "../../hooks/masterHook/useMaster";
import { useDispatch } from "react-redux";
import { changeMaster } from "../../slices/localVariablesSlice";
import { Link, useNavigate, useParams } from "react-router-dom";
import { formatDate } from "../../utils/dateUtils";
import { useEmployeeProfile } from "../../hooks/useEmployees";
import { clearCacheKey, getCachedData } from "../../slices/apiDataManager";
import { clearApiCacheKey } from "../../slices/apiCacheSlice";
const mobileNumberRegex = /^[7-9]\d{9}$/;
const ManageEmployee = () => {
const dispatch = useDispatch();
const { employeeId } = useParams();
const {
employee,
error,
loading: empLoading,
} = useEmployeeProfile( employeeId );
dispatch( changeMaster( "Job Role" ) );
const [disabledEmail, setDisabledEmail] = useState(false);
const { data: job_role, loading } = useMaster();
const [isloading, setLoading] = useState(false);
const navigation = useNavigate();
const [currentEmployee, setCurrentEmployee] = useState();
const [currentAddressLength, setCurrentAddressLength] = useState(0);
const [permanentAddressLength, setPermanentAddressLength] = useState(0);
const userSchema = z.object({
...(employeeId ? { Id: z.number().optional() } : {}),
FirstName: z.string().min(1, { message: "First Name is required" }),
MiddleName: z.string().optional(),
LastName: z.string().min(1, { message: "Last Name is required" }),
Email: z
.string().max(80,"Email cannot exceed 80 characters")
.optional()
.refine((val) => !val || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val), {
message: "Invalid email format",
})
.refine(
(val) => {
if (!val) return true;
const [local, domain] = val.split("@");
return (
val.length <= 320 && local?.length <= 64 && domain?.length <= 255
);
},
{
message: "Email local or domain part is too long",
}
),
CurrentAddress: z
.string()
.min(1, { message: "Current Address is required" })
.max(500, { message: "Address cannot exceed 500 characters" }),
BirthDate: z
.string()
.min(1, { message: "Birth Date is required" })
.refine(
(date, ctx) => {
return new Date(date) <= new Date();
},
{
message: "Birth date cannot be in the future",
}
),
JoiningDate: z
.string()
.min(1, { message: "Joining Date is required" })
.refine(
(date, ctx) => {
return new Date(date) <= new Date();
},
{
message: "Joining date cannot be in the future",
}
),
EmergencyPhoneNumber: z
.string()
.min(1, { message: "Phone Number is required" })
.regex(mobileNumberRegex, { message: "Invalid phone number " }),
EmergencyContactPerson: z
.string()
.min(1, { message: "Emergency Contact Person is required" })
.regex(/^[A-Za-z\s]+$/, {
message: "Emergency Contact Person must contain only letters",
}),
AadharNumber: z
.string()
.regex(/^\d{12}$/, "Aadhar card must be exactly 12 digits long")
.nonempty("Aadhar card is required"),
Gender: z
.string()
.min(1, { message: "Gender is required" })
.refine((val) => val !== "Select Gender", {
message: "Please select a gender",
}),
PanNumber: z
.string()
.optional()
.refine((val) => !val || /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/.test(val), {
message: "Invalid PAN number",
}),
PermanentAddress: z
.string()
.min(1, { message: "Permanent Address is required" })
.max(500, { message: "Address cannot exceed 500 characters" }),
PhoneNumber: z
.string()
.min(1, { message: "Phone Number is required" })
.regex(mobileNumberRegex, { message: "Invalid phone number " }),
JobRoleId: z.string().min(1, { message: "Role is required" }),
});
const {
register,
control,
handleSubmit,
watch,
formState: { errors },
reset,
getValues,
} = useForm({
resolver: zodResolver(userSchema),
defaultValues: {
Id: currentEmployee?.id || "",
FirstName: currentEmployee?.firstName || "",
MiddleName: currentEmployee?.middleName || "",
LastName: currentEmployee?.lastName || "",
Email: currentEmployee?.email || "",
CurrentAddress: currentEmployee?.currentAddress || "",
BirthDate: formatDate(currentEmployee?.birthDate) || "",
JoiningDate: formatDate(currentEmployee?.joiningDate) || "",
EmergencyPhoneNumber: currentEmployee?.emergencyPhoneNumber || "",
EmergencyContactPerson: currentEmployee?.emergencyContactPerson || "",
AadharNumber: currentEmployee?.aadharNumber || "",
Gender: currentEmployee?.gender || "",
PanNumber: currentEmployee?.panNumber || "",
PermanentAddress: currentEmployee?.permanentAddress || "",
PhoneNumber: currentEmployee?.phoneNumber || "",
JobRoleId: currentEmployee?.jobRoleId || "",
},
mode: "onChange",
});
const AadharNumberValue = watch( "AadharNumber" ) || "";
const onSubmit = (data) => {
setLoading(true);
const formData = getValues();
const formDataToSend = new FormData();
if (!employeeId) {
delete formData["Id"];
}
for (const key in formData) {
formDataToSend.append(key, formData[key]);
// if (key === "Documents") {
// formData[key]?.forEach((file, index) => {
// formDataToSend.append(`Documents`, file);
// });
// } else if (key === "Photo" && formData[key]) {
// formDataToSend.append("Photo", formData[key]);
// } else{
// formDataToSend.append(key, formData[key]);
// }
}
EmployeeRepository.manageEmployee(formDataToSend)
.then((response) => {
showToast("Employee details updated successfully.", "success");
clearCacheKey("employeeListByProject");
clearCacheKey( "allEmployeeList" );
clearCacheKey("employeeProfile")
setLoading( false );
reset()
navigation("/employees");
})
.catch((error) => {
showToast(error.message, "error");
setLoading(false);
});
};
useEffect(() => {
if (!loading && !error && employee) {
setCurrentEmployee(employee);
}
}, [loading, error, employee]);
dispatch(changeMaster("Job Role"));
useEffect(() => {
reset(
currentEmployee
? {
Id: currentEmployee.id || "",
FirstName: currentEmployee.firstName || "",
MiddleName: currentEmployee.middleName || "",
LastName: currentEmployee.lastName || "",
Email: currentEmployee.email || "",
CurrentAddress: currentEmployee.currentAddress || "",
BirthDate: formatDate(currentEmployee.birthDate) || "",
JoiningDate: formatDate(currentEmployee.joiningDate) || "",
EmergencyPhoneNumber: currentEmployee.emergencyPhoneNumber || "",
EmergencyContactPerson:
currentEmployee.emergencyContactPerson || "",
AadharNumber: currentEmployee.aadharNumber || "",
Gender: currentEmployee.gender || "",
PanNumber: currentEmployee.panNumber || "",
PermanentAddress: currentEmployee.permanentAddress || "",
PhoneNumber: currentEmployee.phoneNumber || "",
JobRoleId: currentEmployee.jobRoleId?.toString() || "",
}
: {} // Empty object resets the form
);
setCurrentAddressLength(currentEmployee?.currentAddress?.length || 0);
setPermanentAddressLength(currentEmployee?.permanentAddress?.length || 0);
}, [currentEmployee, reset]);
return (
<>
<div className="row">
<div className="col-xxl">
<div className="card mb-4">
<div className="card-header d-flex align-items-center justify-content-between">
<h6 className="mb-0">
{employee ? "Update Employee" : "Create Employee"}
</h6>
<span
className="cursor-pointer fs-6"
data-htm="true"
data-bs-toggle="tooltip"
data-bs-offset="0,6"
data-bs-placement="top"
data-bs-html="true"
title="Move Back"
onClick={() => navigation("/employees")}
>
<i className="bx bxs-chevron-left"></i> Back
</span>
</div>
<div className="card-body">
{(!currentEmployee && empLoading) && (
<p>Loading Employee Data...</p>
)}
<form onSubmit={handleSubmit(onSubmit)}>
<div className="row mb-3">
<div className="col-sm-4">
{" "}
<div className="form-text text-start">First Name</div>
<input
type="text"
name="FirstName"
{...register("FirstName")}
className="form-control form-control-sm"
id="FirstName"
placeholder="First Name"
/>
{errors.FirstName && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.FirstName.message}
</div>
)}
</div>{" "}
<div className="col-sm-4">
<div className="form-text text-start">Middle Name</div>
<input
type="text"
{...register("MiddleName")}
className="form-control form-control-sm"
id="MiddleName"
placeholder="Middle Name"
/>
{errors.MiddleName && (
<div
className="danger-text text-start "
style={{ fontSize: "12px" }}
>
{errors.MiddleName.message}
</div>
)}
</div>
<div className="col-sm-4">
<div className="form-text text-start">Last Name</div>
<input
type="text"
{...register("LastName")}
className="form-control form-control-sm"
id="LastName"
placeholder="Last Name"
/>
{errors.LastName && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.LastName.message}
</div>
)}
</div>
</div>
<div className="row mb-3">
<div className="col-sm-6">
<div className="form-text text-start">Email</div>
<input
type="email"
id="Email"
{...register("Email")}
className="form-control form-control-sm"
placeholder="example@domain.com"
maxLength={80}
aria-describedby="Email"
disabled={!!currentEmployee?.email}
/>
{errors.Email && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.Email.message}
</div>
)}
</div>
<div className="col-sm-6">
<div className="form-text text-start">Phone Number</div>
<input
type="text"
keyboardType="numeric"
id="PhoneNumber"
{...register("PhoneNumber")}
className="form-control form-control-sm"
placeholder="Phone Number"
inputMode="numeric"
maxLength={10}
/>
{errors.PhoneNumber && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.PhoneNumber.message}
</div>
)}
</div>
</div>
<div className="row mb-3"></div>
<div className="row mb-3">
<div className="col-sm-4">
<div className="form-text text-start">Gender</div>
<div className="input-group input-group-merge ">
<select
className="form-select form-select-sm "
{...register("Gender")}
id="Gender"
aria-label=""
>
<option disabled value="">
Select Gender
</option>
<option value="Male">Male </option>
<option value="Female">Female</option>
<option value="Other">Other</option>
</select>
</div>
{errors.Gender && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.Gender.message}
</div>
)}
</div>
<div className="col-sm-4">
<div className="form-text text-start">Birth Date</div>
<div className="input-group input-group-merge ">
<input
className="form-control form-control-sm"
type="date"
{...register("BirthDate")}
id="BirthDate"
/>
</div>
{errors.BirthDate && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.BirthDate.message}
</div>
)}
</div>
<div className="col-sm-4">
<div className="form-text text-start">Joining Date</div>
<div className="input-group input-group-merge ">
<input
className="form-control form-control-sm"
type="date"
{...register("JoiningDate")}
id="JoiningDate"
/>
</div>
{errors.JoiningDate && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.JoiningDate.message}
</div>
)}
</div>
</div>
<div className="row mb-3">
<div className="col-sm-6">
<div className="form-text text-start">Current Address</div>
<textarea
id="CurrentAddress"
className="form-control form-control-sm"
placeholder="Current Address"
aria-label="Current Address"
aria-describedby="basic-icon-default-message2"
{...register( "CurrentAddress" )}
maxLength={500}
onChange={(e) => {
setCurrentAddressLength(e.target.value.length);
// let react-hook-form still handle it
register("CurrentAddress").onChange(e);
}}
></textarea>
<div className="text-end muted">
<small> {500 - currentAddressLength} characters left</small>
</div>
{errors.CurrentAddress && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.CurrentAddress.message}
</div>
)}
</div>
<div className="col-sm-6">
<div className="form-text text-start">
Permanent Address
</div>
<textarea
id="PermanentAddress"
className="form-control form-control-sm"
placeholder="Permanent Address"
aria-label="Permanent Address"
aria-describedby="basic-icon-default-message2"
{...register( "PermanentAddress" )}
maxLength={500}
onChange={(e) => {
setPermanentAddressLength(e.target.value.length);
register("PermanentAddress").onChange(e);
}}
></textarea>
<div className="text-end muted">
<small>{500 - permanentAddressLength} characters left</small>
</div>
{errors.PermanentAddress && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.PermanentAddress.message}
</div>
)}
</div>
</div>
<div className="row mb-3">
{" "}
<div className="divider">
<div className="divider-text">Other Information</div>
</div>
</div>
<div className="row mb-3">
<div className="col-sm-4">
<div className="form-text text-start">Role</div>
<div className="input-group input-group-merge ">
<select
className="form-select form-select-sm"
{...register("JobRoleId")}
id="JobRoleId"
aria-label=""
>
<option disabled value="">
Select Role
</option>
{job_role?.map((item) => (
<option value={item?.id} key={item.id}>
{item?.name}{" "}
</option>
))}
</select>
</div>
{errors.JobRoleId && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.JobRoleId.message}
</div>
)}
</div>
<div className="col-sm-4">
<div className="form-text text-start">
Emergency Contact Person
</div>
<input
type="text"
{...register("EmergencyContactPerson")}
className="form-control form-control-sm"
id="EmergencyContactPerson"
maxLength={50}
placeholder="Contact Person"
/>
{errors.EmergencyContactPerson && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.EmergencyContactPerson.message}
</div>
)}
</div>
<div className="col-sm-4">
<div className="form-text text-start">
Emergency Phone Number
</div>
<input
type="text"
{...register("EmergencyPhoneNumber")}
className="form-control form-control-sm phone-mask"
id="EmergencyPhoneNumber"
placeholder="Phone Number"
inputMode="numeric"
maxLength={10}
/>
{errors.EmergencyPhoneNumber && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.EmergencyPhoneNumber.message}
</div>
)}
</div>
</div>
<div className="row mb-3">
<div className="col-sm-6">
<div className="form-text text-start">AADHAR Number</div>
<input
type="text"
{...register("AadharNumber")}
className="form-control form-control-sm"
id="AadharNumber"
placeholder="AADHAR Number"
maxLength={12}
inputMode="numeric"
/>
{errors.AadharNumber && (
<div className="danger-text text-start">
{errors.AadharNumber.message}
</div>
)}
</div>
<div className="col-sm-6">
<div className="form-text text-start">PAN Number</div>
<input
type="text"
{...register("PanNumber")}
className="form-control form-control-sm"
id="PanNumber"
placeholder="PAN Number"
maxLength={10}
/>
{errors.PanNumber && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.PanNumber.message}
</div>
)}
</div>
</div>
{employeeId && (
<div className="row mb-3 d-none">
<div className="col-sm-12">
<input type="number" name="id" {...register("Id")} />
</div>
</div>
)}
<div className="row justify-content-start">
<div className="col-sm-12">
<button
aria-label="manage employee"
type="submit"
className="btn btn-sm btn-primary"
disabled={isloading}
>
{isloading
? "Please Wait..."
: employeeId
? "Update"
: "Create"}
</button>
<button
aria-label="manage employee"
type="reset"
className="btn btn-sm btn-primary ms-2"
>
Clear
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</>
);
};
export default ManageEmployee;