613 lines
20 KiB
JavaScript
613 lines
20 KiB
JavaScript
import React, { useEffect, useState } from "react";
|
||
import { useForm } from "react-hook-form";
|
||
import { zodResolver } from "@hookform/resolvers/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,
|
||
useUpdateEmployee,
|
||
} from "../../hooks/useEmployees";
|
||
|
||
import Label from "../common/Label";
|
||
import DatePicker from "../common/DatePicker";
|
||
import { defatEmployeeObj, employeeSchema } from "./EmployeeSchema";
|
||
import { useOrganizationsList } from "../../hooks/useOrganization";
|
||
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||
|
||
const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => {
|
||
const dispatch = useDispatch();
|
||
const { mutate: updateEmployee, isPending } = useUpdateEmployee();
|
||
const {
|
||
data: organzationList,
|
||
isLoading,
|
||
isError,
|
||
error: EempError,
|
||
} = useOrganizationsList(ITEMS_PER_PAGE, 1, true);
|
||
const {
|
||
employee,
|
||
error,
|
||
loading: empLoading,
|
||
refetch,
|
||
} = 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);
|
||
|
||
useEffect(() => {
|
||
refetch();
|
||
}, []);
|
||
|
||
const {
|
||
register,
|
||
control,
|
||
handleSubmit,
|
||
watch,
|
||
formState: { errors },
|
||
reset,
|
||
getValues,
|
||
} = useForm({
|
||
resolver: zodResolver(employeeSchema),
|
||
defaultValues: defatEmployeeObj,
|
||
mode: "onChange",
|
||
});
|
||
|
||
const AadharNumberValue = watch("aadharNumber") || "";
|
||
|
||
const onSubmit = (data) => {
|
||
if (data.email === "") {
|
||
data.email = null;
|
||
}
|
||
|
||
const payload = { ...data, IsAllEmployee };
|
||
|
||
if (employeeId) {
|
||
payload.id = employeeId;
|
||
}
|
||
|
||
updateEmployee(payload, {
|
||
onSuccess: () => {
|
||
reset();
|
||
onClosed();
|
||
},
|
||
});
|
||
};
|
||
|
||
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() || "",
|
||
organizationId: currentEmployee.organizationId || "",
|
||
hasApplicationAccess: currentEmployee.hasApplicationAccess || false,
|
||
}
|
||
: {}
|
||
);
|
||
setCurrentAddressLength(currentEmployee?.currentAddress?.length || 0);
|
||
setPermanentAddressLength(currentEmployee?.permanentAddress?.length || 0);
|
||
}, [currentEmployee, reset]);
|
||
|
||
const hasAccessAplication = watch("hasApplicationAccess");
|
||
return (
|
||
<>
|
||
<form onSubmit={handleSubmit(onSubmit)} className="p-sm-0 p-2">
|
||
<div className="text-center">
|
||
<p className="fs-5 fw-semibold">
|
||
{" "}
|
||
{employee ? "Update Employee" : "Create Employee"}
|
||
</p>{" "}
|
||
</div>
|
||
<div className="row mb-3">
|
||
<div className="col-sm-4">
|
||
<Label className="form-text text-start" required>
|
||
First Name
|
||
</Label>
|
||
<input
|
||
type="text"
|
||
name="firstName"
|
||
{...register("firstName", {
|
||
required: "First name is required",
|
||
pattern: {
|
||
value: /^[A-Za-z\s]+$/, // Only letters and spaces
|
||
message: "Only letters are allowed",
|
||
},
|
||
})}
|
||
className="form-control form-control-sm"
|
||
id="firstName"
|
||
placeholder="First Name"
|
||
onInput={(e) => {
|
||
e.target.value = e.target.value.replace(/[^A-Za-z\s]/g, "");
|
||
}}
|
||
/>
|
||
{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", {
|
||
pattern: {
|
||
value: /^[A-Za-z\s]+$/, // Only letters and spaces
|
||
message: "Only letters are allowed",
|
||
},
|
||
})}
|
||
className="form-control form-control-sm"
|
||
id="middleName"
|
||
placeholder="Middle Name"
|
||
onInput={(e) => {
|
||
e.target.value = e.target.value.replace(/[^A-Za-z\s]/g, "");
|
||
}}
|
||
/>
|
||
{errors.middleName && (
|
||
<div
|
||
className="danger-text text-start "
|
||
style={{ fontSize: "12px" }}
|
||
>
|
||
{errors.middleName.message}
|
||
</div>
|
||
)}
|
||
</div>
|
||
<div className="col-sm-4">
|
||
<Label className="form-text text-start" required>
|
||
Last Name
|
||
</Label>
|
||
<input
|
||
type="text"
|
||
{...register("lastName", {
|
||
pattern: {
|
||
value: /^[A-Za-z\s]+$/, // Only letters and spaces
|
||
message: "Only letters are allowed",
|
||
},
|
||
})}
|
||
className="form-control form-control-sm"
|
||
id="lastName"
|
||
placeholder="Last Name"
|
||
onInput={(e) => {
|
||
e.target.value = e.target.value.replace(/[^A-Za-z\s]/g, "");
|
||
}}
|
||
/>
|
||
{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">
|
||
<Label
|
||
htmlFor="email"
|
||
className="text-start form-text"
|
||
required={hasAccessAplication}
|
||
>
|
||
Email
|
||
</Label>
|
||
<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">
|
||
<Label className="form-text text-start" required>
|
||
Phone Number
|
||
</Label>
|
||
<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">
|
||
<Label className="form-text text-start" required>
|
||
Gender
|
||
</Label>
|
||
|
||
<div className="input-group">
|
||
<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">
|
||
<Label className="form-text text-start" required>
|
||
Birth Date
|
||
</Label>
|
||
|
||
<div className="input-group">
|
||
<DatePicker
|
||
name="birthDate"
|
||
control={control} // from useForm()
|
||
placeholder="DD-MM-YYYY"
|
||
maxDate={new Date()} // restrict future dates (e.g., birth date must be today or past)
|
||
className="w-100"
|
||
/>
|
||
</div>
|
||
|
||
{errors.birthDate && (
|
||
<div
|
||
className="danger-text text-start"
|
||
style={{ fontSize: "12px" }}
|
||
>
|
||
{errors.birthDate.message}
|
||
</div>
|
||
)}
|
||
</div>
|
||
<div className="col-sm-4">
|
||
<Label className="form-text text-start" required>
|
||
Joining Date
|
||
</Label>
|
||
|
||
<div className="input-group">
|
||
<DatePicker
|
||
name="joiningDate"
|
||
control={control} // from useForm()
|
||
placeholder="DD-MM-YYYY"
|
||
// For joining date, we don’t block future dates
|
||
className="w-100"
|
||
/>
|
||
</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">
|
||
<Label className="form-text text-start" required>
|
||
Current Address
|
||
</Label>
|
||
|
||
<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);
|
||
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">
|
||
<Label className="form-text text-start" required>
|
||
Permanent Address
|
||
</Label>
|
||
|
||
<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="col-sm-6">
|
||
<Label className="form-text text-start" required>
|
||
Organization
|
||
</Label>
|
||
<div className="input-group">
|
||
<select
|
||
className="form-select form-select-sm"
|
||
{...register("organizationId")}
|
||
id="organizationId"
|
||
aria-label=""
|
||
>
|
||
<option disabled value="">
|
||
Select Organization
|
||
</option>
|
||
{organzationList?.data
|
||
.sort((a, b) => a?.name?.localeCompare(b?.name))
|
||
.map((item) => (
|
||
<option value={item?.id} key={item?.id}>
|
||
{item?.name}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
{errors.organizationId && (
|
||
<div
|
||
className="danger-text text-start justify-content-center"
|
||
style={{ fontSize: "12px" }}
|
||
>
|
||
{errors.organizationId.message}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="col-sm-6 d-flex align-items-center mt-2">
|
||
<label className="form-check-label d-flex align-items-center">
|
||
<input
|
||
type="checkbox"
|
||
className="form-check-input me-2"
|
||
{...register("hasApplicationAccess")}
|
||
/>
|
||
Has Application Access ?
|
||
</label>
|
||
</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">
|
||
<Label className="form-text text-start" required>
|
||
Official Designation
|
||
</Label>
|
||
<div className="input-group">
|
||
<select
|
||
className="form-select form-select-sm"
|
||
{...register("jobRoleId")}
|
||
id="jobRoleId"
|
||
aria-label=""
|
||
>
|
||
<option disabled value="">
|
||
Select Role
|
||
</option>
|
||
{[...job_role]
|
||
.sort((a, b) => a?.name?.localeCompare(b.name))
|
||
.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">
|
||
<Label className="form-text text-start" required>
|
||
Emergency Contact Person
|
||
</Label>
|
||
<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">
|
||
<Label className="form-text text-start" required>
|
||
Emergency Phone Number
|
||
</Label>
|
||
<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>
|
||
<div className="row text-end">
|
||
<div className="col-sm-12">
|
||
<button
|
||
aria-label="manage employee"
|
||
type="reset"
|
||
className="btn btn-sm btn-label-secondary me-2"
|
||
disabled={isPending}
|
||
>
|
||
Clear
|
||
</button>
|
||
<button
|
||
aria-label="manage employee"
|
||
type="submit"
|
||
className="btn btn-sm btn-primary"
|
||
disabled={isPending}
|
||
>
|
||
{isPending ? "Please Wait..." : employeeId ? "Update" : "Create"}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default ManageEmployee;
|