661 lines
24 KiB
JavaScript
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;
|