656 lines
24 KiB
JavaScript
656 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 { cacheData, clearCacheKey, getCachedData } from "../../slices/apiDataManager";
|
|
import { clearApiCacheKey } from "../../slices/apiCacheSlice";
|
|
|
|
const mobileNumberRegex = /^[0-9]\d{9}$/;
|
|
|
|
const ManageEmployee = ({ employeeId, onClosed }) => {
|
|
const dispatch = useDispatch();
|
|
|
|
// const { employeeId } = useParams();
|
|
const {
|
|
employee,
|
|
error,
|
|
loading: empLoading,
|
|
} = useEmployeeProfile(employeeId);
|
|
|
|
useEffect(() => {
|
|
dispatch(changeMaster("Job Role"));
|
|
}, [employeeId]);
|
|
|
|
const [disabledEmail, setDisabledEmail] = useState(false);
|
|
const { data: job_role, loading } = useMaster();
|
|
const [isloading, setLoading] = useState(false);
|
|
const navigation = useNavigate();
|
|
const [currentEmployee, setCurrentEmployee] = useState(null);
|
|
const [currentAddressLength, setCurrentAddressLength] = useState(0);
|
|
const [permanentAddressLength, setPermanentAddressLength] = useState(0);
|
|
|
|
const userSchema = z.object({
|
|
...(employeeId ? { id: z.string().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()
|
|
.optional()
|
|
.refine((val) => !val || /^\d{12}$/.test(val), {
|
|
message: "Aadhar card must be exactly 12 digits long",
|
|
}),
|
|
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 || null,
|
|
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() || null,
|
|
},
|
|
mode: "onChange",
|
|
});
|
|
|
|
const AadharNumberValue = watch("aadharNumber") || "";
|
|
|
|
const onSubmit = (data) => {
|
|
setLoading(true);
|
|
if (data.email == "") {
|
|
data.email = null;
|
|
}
|
|
EmployeeRepository.manageEmployee(data)
|
|
.then((response) => {
|
|
cacheData("employeeProfileInfo", data);
|
|
showToast(
|
|
`Employee details ${
|
|
data.id == null ? "created" : "updated"
|
|
} successfully.`,
|
|
"success"
|
|
);
|
|
clearCacheKey("employeeListByProject");
|
|
clearCacheKey("allEmployeeList");
|
|
clearCacheKey("allInactiveEmployeeList");
|
|
clearCacheKey("employeeProfile");
|
|
|
|
|
|
setLoading(false);
|
|
reset();
|
|
// navigation("/employees");
|
|
onClosed();
|
|
})
|
|
.catch((error) => {
|
|
const message =
|
|
error?.response?.data?.message ||
|
|
error?.message ||
|
|
"Error occured during api calling";
|
|
showToast(message, "error");
|
|
setLoading(false);
|
|
});
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!loading && !error && employee) {
|
|
setCurrentEmployee(employee);
|
|
}
|
|
}, [loading, error, employee]);
|
|
|
|
useEffect(() => {
|
|
reset(
|
|
currentEmployee
|
|
? {
|
|
id: currentEmployee.id || null,
|
|
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-xl">
|
|
<div className="card">
|
|
<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"
|
|
onClick={() => onClosed()}
|
|
>
|
|
<i className="bx bx-x"></i>
|
|
</span>
|
|
</div>
|
|
<div className="card-body">
|
|
{!currentEmployee && empLoading && employeeId && (
|
|
<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 d-none">
|
|
<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 d-none">
|
|
<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="text" 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"
|
|
disabled={isloading}
|
|
>
|
|
Clear
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default ManageEmployee;
|