diff --git a/src/components/Employee/EmpDashboard.jsx b/src/components/Employee/EmpDashboard.jsx index fee8ea3d..e3866cab 100644 --- a/src/components/Employee/EmpDashboard.jsx +++ b/src/components/Employee/EmpDashboard.jsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from "react"; import EmpOverview from "./EmpOverview"; import { useProjectsAllocationByEmployee } from "../../hooks/useProjects"; +import EmpReportingManager from "./EmpReportingManager"; const EmpDashboard = ({ profile }) => { const { @@ -16,6 +17,7 @@ const EmpDashboard = ({ profile }) => { {" "} +
@@ -80,6 +82,10 @@ const EmpDashboard = ({ profile }) => {
+
+ {" "} + +
); diff --git a/src/components/Employee/EmpReportingManager.jsx b/src/components/Employee/EmpReportingManager.jsx new file mode 100644 index 00000000..61d33c37 --- /dev/null +++ b/src/components/Employee/EmpReportingManager.jsx @@ -0,0 +1,89 @@ +import React, { useState } from "react"; +import { useOrganizationHierarchy } from "../../hooks/useEmployees"; +import GlobalModel from "../common/GlobalModel"; +import ManageReporting from "./ManageReporting"; + +const EmpReportingManager = ({ employeeId, employee }) => { + const { data, isLoading } = useOrganizationHierarchy(employeeId); + const [showManageReportingModal, setShowManageReportingModal] = useState(false); + + if (isLoading) return Loading...; + + const primary = data?.find((item) => item.isPrimary); + const secondary = data?.filter((item) => !item.isPrimary); + + // Create comma-separated string for secondary managers + const secondaryNames = secondary + ?.map((item) => `${item.reportTo?.firstName || ""} ${item.reportTo?.lastName || ""}`.trim()) + .join(", "); + + return ( +
+
+
+
+
+ Update Reporting Manager + +
+ + + {/* Primary Reporting Manager */} +
+ + + Primary Reporting Manager + + : + + {primary?.reportTo?.firstName || NA}{" "} + {primary?.reportTo?.lastName || ""} + +
+ + {/* Secondary Reporting Manager (comma-separated) */} + {secondary?.length > 0 && ( +
+ + + Secondary Reporting Manager + + : + + {secondaryNames || NA} + +
+ )} + + {/* Open Modal Button */} +
+ +
+
+
+
+ + {/* ManageReporting Modal */} + {showManageReportingModal && ( + setShowManageReportingModal(false)} + > + setShowManageReportingModal(false)} + /> + + + )} +
+ ); +}; + +export default EmpReportingManager; diff --git a/src/components/Employee/EmployeeSchema.jsx b/src/components/Employee/EmployeeSchema.jsx index ba540ef4..b8c9f37a 100644 --- a/src/components/Employee/EmployeeSchema.jsx +++ b/src/components/Employee/EmployeeSchema.jsx @@ -3,8 +3,8 @@ import { z } from "zod" const mobileNumberRegex = /^[0-9]\d{9}$/; -export const employeeSchema = - z.object({ +export const employeeSchema = + z.object({ firstName: z.string().min(1, { message: "First Name is required" }), middleName: z.string().optional(), lastName: z.string().min(1, { message: "Last Name is required" }), @@ -90,35 +90,46 @@ export const employeeSchema = .min(1, { message: "Phone Number is required" }) .regex(mobileNumberRegex, { message: "Invalid phone number " }), jobRoleId: z.string().min(1, { message: "Role is required" }), - organizationId:z.string().min(1,{message:"Organization is required"}), - hasApplicationAccess:z.boolean().default(false), + organizationId: z.string().min(1, { message: "Organization is required" }), + hasApplicationAccess: z.boolean().default(false), }).refine((data) => { - if (data.hasApplicationAccess) { - return data.email && data.email.trim() !== ""; - } - return true; -}, { - message: "Email is required when employee has access", - path: ["email"], + if (data.hasApplicationAccess) { + return data.email && data.email.trim() !== ""; + } + return true; + }, { + message: "Email is required when employee has access", + path: ["email"], + }); + + +export const defatEmployeeObj = { + firstName: "", + middleName: "", + lastName: "", + email: "", + currentAddress: "", + birthDate: "", + joiningDate: "", + emergencyPhoneNumber: "", + emergencyContactPerson: "", + aadharNumber: "", + gender: "", + panNumber: "", + permanentAddress: "", + phoneNumber: "", + jobRoleId: null, + organizationId: "", + hasApplicationAccess: false +} + +export const ManageReportingSchema = z.object({ + primaryNotifyTo: z.array(z.string()).min(1, "Primary Reporting Manager is required"), + secondaryNotifyTo: z.array(z.string()).optional(), }); - -export const defatEmployeeObj = { - firstName: "", - middleName: "", - lastName: "", - email: "", - currentAddress: "", - birthDate: "", - joiningDate: "", - emergencyPhoneNumber: "", - emergencyContactPerson: "", - aadharNumber: "", - gender: "", - panNumber: "", - permanentAddress: "", - phoneNumber: "", - jobRoleId: null, - organizationId:"", - hasApplicationAccess:false - } \ No newline at end of file +export const defaultManageReporting = { + primaryNotifyTo: [], + secondaryNotifyTo: [], +}; + diff --git a/src/components/Employee/ManageReporting.jsx b/src/components/Employee/ManageReporting.jsx new file mode 100644 index 00000000..e2ebd9a3 --- /dev/null +++ b/src/components/Employee/ManageReporting.jsx @@ -0,0 +1,157 @@ +import React, { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import Label from "../common/Label"; +import PmsEmployeeInputTag from "../common/PmsEmployeeInputTag"; +import { useManageEmployeeHierarchy, useOrganizationHierarchy } from "../../hooks/useEmployees"; +import { ManageReportingSchema, defaultManageReporting } from "./EmployeeSchema"; + +const ManageReporting = ({ onClosed, employee, employeeId }) => { + const { + handleSubmit, + control, + reset, + formState: { errors }, + watch, + } = useForm({ + resolver: zodResolver(ManageReportingSchema), + defaultValues: defaultManageReporting, + }); + + const { data, isLoading } = useOrganizationHierarchy(employeeId); + + // mutation hook + const { mutate: manageHierarchy, isPending } = useManageEmployeeHierarchy( + employeeId, + onClosed + ); + + const primaryValue = watch("primaryNotifyTo"); + const secondaryValue = watch("secondaryNotifyTo"); + + // Prefill hierarchy data + useEffect(() => { + if (data && Array.isArray(data)) { + const primary = data.find((r) => r.isPrimary); + const secondary = data.filter((r) => !r.isPrimary); + + reset({ + primaryNotifyTo: primary ? [primary.reportTo.id] : [], + secondaryNotifyTo: secondary.map((r) => r.reportTo.id), + }); + } + }, [data, reset]); + + const handleClose = () => { + reset(defaultManageReporting); + onClosed(); + }; + + const onSubmit = (formData) => { + // Build set of currently selected IDs + const selectedIds = new Set([ + ...(formData.primaryNotifyTo || []), + ...(formData.secondaryNotifyTo || []), + ]); + + // Build payload including previous assignments, setting isActive true/false accordingly + const payload = (data || []).map((item) => ({ + reportToId: item.reportTo.id, + isPrimary: item.isPrimary, + isActive: selectedIds.has(item.reportTo.id), + })); + + // Add any new IDs that were not previously assigned + if (formData.primaryNotifyTo?.length) { + const primaryId = formData.primaryNotifyTo[0]; + if (!data?.some((d) => d.reportTo.id === primaryId)) { + payload.push({ + reportToId: primaryId, + isPrimary: true, + isActive: true, + }); + } + } + + if (formData.secondaryNotifyTo?.length) { + formData.secondaryNotifyTo.forEach((id) => { + if (!data?.some((d) => d.reportTo.id === id)) { + payload.push({ + reportToId: id, + isPrimary: false, + isActive: true, + }); + } + }); + } + + manageHierarchy(payload); + }; + + return ( +
+
+
+ Update Reporting Manager ( + {`${employee.firstName || ""} ${employee.middleName || ""} ${employee.lastName || ""}`.trim()} + ) +
+ + {/* Primary */} +
+ + 0 ? "" : "Select primary report-to"} + forAll={true} + disabled={primaryValue?.length > 0} + /> + {errors.primaryNotifyTo && ( +
+ {errors.primaryNotifyTo.message} +
+ )} +
+ + + {/* Secondary */} +
+ + +
+ + +
+ + + +
+
+
+ ); +}; + +export default ManageReporting; \ No newline at end of file diff --git a/src/components/common/PmsEmployeeInputTag.jsx b/src/components/common/PmsEmployeeInputTag.jsx index 8cad7aac..996cbc0d 100644 --- a/src/components/common/PmsEmployeeInputTag.jsx +++ b/src/components/common/PmsEmployeeInputTag.jsx @@ -11,6 +11,7 @@ const PmsEmployeeInputTag = ({ projectId, forAll, isApplicationUser = false, + disabled }) => { const { field: { value = [], onChange }, @@ -187,7 +188,7 @@ const PmsEmployeeInputTag = ({ + )} diff --git a/src/repositories/EmployeeRepository.jsx b/src/repositories/EmployeeRepository.jsx index d257825b..da51038c 100644 --- a/src/repositories/EmployeeRepository.jsx +++ b/src/repositories/EmployeeRepository.jsx @@ -10,18 +10,20 @@ const EmployeeRepository = { updateEmployee: (id, data) => api.put(`/users/${id}`, data), // deleteEmployee: ( id ) => api.delete( `/users/${ id }` ), getEmployeeProfile: (id) => api.get(`/api/employee/profile/get/${id}`), - deleteEmployee: (id,active) => api.delete(`/api/employee/${id}?active=${active}`), - getEmployeeName: (projectId, search,allEmployee) => { - const params = new URLSearchParams(); + deleteEmployee: (id, active) => api.delete(`/api/employee/${id}?active=${active}`), + getEmployeeName: (projectId, search, allEmployee) => { + const params = new URLSearchParams(); - if (projectId) params.append("projectId", projectId); - if (search) params.append("searchString", search); - if(allEmployee) params.append("allEmployee",allEmployee) + if (projectId) params.append("projectId", projectId); + if (search) params.append("searchString", search); + if (allEmployee) params.append("allEmployee", allEmployee) - const query = params.toString(); - return api.get(`/api/Employee/basic${query ? `?${query}` : ""}`); -} + const query = params.toString(); + return api.get(`/api/Employee/basic${query ? `?${query}` : ""}`); + }, + getOrganizaionHierarchy: (employeeId) => api.get(`/api/organization/hierarchy/list/${employeeId}`), + manageOrganizationHierarchy: (employeeId, data) => api.post(`/api/organization/hierarchy/manage/${employeeId}`, data), }; export default EmployeeRepository;