Merge branch 'Service_Project_Managment' of https://git.marcoaiot.com/admin/marco.pms.web into Service_Project_Managment

This commit is contained in:
pramod.mahajan 2025-11-13 19:50:41 +05:30
commit 9b7988d6d8
5 changed files with 143 additions and 101 deletions

View File

@ -83,9 +83,12 @@ const EmpDashboard = ({ profile }) => {
</div> </div>
</div> </div>
<div className="col-12 col-sm-6 pt-5"> <div className="col-12 col-sm-6 pt-5">
{" "} <EmpReportingManager
<EmpReportingManager employeeId={profile?.id}></EmpReportingManager> employeeId={profile?.id}
employee={profile}
/>
</div> </div>
</div> </div>
</> </>
); );

View File

@ -2,61 +2,59 @@ import React, { useState } from "react";
import { useOrganizationHierarchy } from "../../hooks/useEmployees"; import { useOrganizationHierarchy } from "../../hooks/useEmployees";
import GlobalModel from "../common/GlobalModel"; import GlobalModel from "../common/GlobalModel";
import ManageReporting from "./ManageReporting"; import ManageReporting from "./ManageReporting";
import Avatar from "../common/Avatar";
import { SpinnerLoader } from "../common/Loader";
const EmpReportingManager = ({ employeeId, employee }) => { const EmpReportingManager = ({ employeeId, employee }) => {
const { data, isLoading } = useOrganizationHierarchy(employeeId); const { data, isLoading } = useOrganizationHierarchy(employeeId);
const [showManageReportingModal, setShowManageReportingModal] = useState(false); const [showManageReportingModal, setShowManageReportingModal] = useState(false);
if (isLoading) return <span>Loading...</span>; if (isLoading)
return (
<div className="d-flex justify-content-center py-5">
<SpinnerLoader />
</div>
);
const primary = data?.find((item) => item.isPrimary); // Safe access to primary and secondary managers
const secondary = data?.filter((item) => !item.isPrimary); const primaryManager = data?.find((d) => d.isPrimary)?.reportTo;
const secondaryManagers = data?.filter((d) => !d.isPrimary).map((d) => d.reportTo) || [];
// Create comma-separated string for secondary managers
const secondaryNames = secondary
?.map((item) => `${item.reportTo?.firstName || ""} ${item.reportTo?.lastName || ""}`.trim())
.join(", ");
return ( return (
<div className="row"> <div className="row">
<div className="col-12 mb-4"> <div className="col-12 mb-4">
<div className="card"> <div className="card">
<div className="card-body"> <div className="card-body">
<h5 className="m-0 py-1 mb-3"> <small className="card-text text-uppercase text-body-secondary small d-block text-start mb-3">
Update Reporting Manager Reporting Manager
</small>
</h5> <div className="text-start">
{/* Primary Manager */}
<div className="row mb-2 align-items-start">
<div className="col-auto text-end pe-3"><i className="bx bx-user-circle me-1"></i>Primary Manager<span style={{ marginLeft: "50px" }}>:</span></div>
<div className="col text-wrap">
{primaryManager
? `${primaryManager.firstName || ""} ${primaryManager.lastName || ""}`
: "NA"}
</div>
</div>
{/* Secondary Managers */}
{/* Primary Reporting Manager */} {secondaryManagers?.length > 0 && (
<div className="d-flex align-items-start mb-3"> <div className="row mb-3 align-items-start">
<span className="d-flex"> <div className="col-auto text-end pe-3"><i className="bx bx-group me-1"></i>Secondary Managers<span style={{ marginLeft: "25px" }}>:</span></div>
<i className="bx bx-user bx-xs me-2 mt-1"></i> <div className="col text-wrap">
<span>Primary Reporting Manager</span> {secondaryManagers
</span> .map((m) => `${m.firstName || ""} ${m.lastName || ""}`)
<span style={{ marginLeft: "75px" }}>:</span> .join(", ")}
<span className="ms-5"> </div>
{primary?.reportTo?.firstName || <em>NA</em>}{" "} </div>
{primary?.reportTo?.lastName || ""} )}
</span>
</div> </div>
{/* Secondary Reporting Manager (comma-separated) */} {/* Manage Reporting Button */}
{secondary?.length > 0 && ( <div className="mt-5 text-end" >
<div className="d-flex align-items-start mb-3" style={{ textAlign: "left" }}>
<span className="d-flex">
<i className="bx bx-user bx-xs me-2 mt-1"></i>
<span>Secondary Reporting Manager</span>
</span>
<span style={{ marginLeft: "57px" }}>:</span>
<span className="ms-5" >
{secondaryNames || <em>NA</em>}
</span>
</div>
)}
{/* Open Modal Button */}
<div className="mt-3 text-end">
<button <button
className="btn btn-sm btn-primary" className="btn btn-sm btn-primary"
onClick={() => setShowManageReportingModal(true)} onClick={() => setShowManageReportingModal(true)}
@ -68,18 +66,17 @@ const EmpReportingManager = ({ employeeId, employee }) => {
</div> </div>
</div> </div>
{/* ManageReporting Modal */} {/* Manage Reporting Modal */}
{showManageReportingModal && ( {showManageReportingModal && (
<GlobalModel <GlobalModel
isOpen={showManageReportingModal} isOpen={showManageReportingModal}
closeModal={() => setShowManageReportingModal(false)} closeModal={() => setShowManageReportingModal(false)}
> >
<ManageReporting <ManageReporting
employee={employee}
employeeId={employeeId} employeeId={employeeId}
employee={primary?.employee || {}}
onClosed={() => setShowManageReportingModal(false)} onClosed={() => setShowManageReportingModal(false)}
/> />
</GlobalModel> </GlobalModel>
)} )}
</div> </div>
@ -87,3 +84,4 @@ const EmpReportingManager = ({ employeeId, employee }) => {
}; };
export default EmpReportingManager; export default EmpReportingManager;

View File

@ -5,6 +5,8 @@ import Label from "../common/Label";
import PmsEmployeeInputTag from "../common/PmsEmployeeInputTag"; import PmsEmployeeInputTag from "../common/PmsEmployeeInputTag";
import { useManageEmployeeHierarchy, useOrganizationHierarchy } from "../../hooks/useEmployees"; import { useManageEmployeeHierarchy, useOrganizationHierarchy } from "../../hooks/useEmployees";
import { ManageReportingSchema, defaultManageReporting } from "./EmployeeSchema"; import { ManageReportingSchema, defaultManageReporting } from "./EmployeeSchema";
import Avatar from "../common/Avatar";
import { useNavigate } from "react-router-dom";
const ManageReporting = ({ onClosed, employee, employeeId }) => { const ManageReporting = ({ onClosed, employee, employeeId }) => {
const { const {
@ -17,6 +19,7 @@ const ManageReporting = ({ onClosed, employee, employeeId }) => {
resolver: zodResolver(ManageReportingSchema), resolver: zodResolver(ManageReportingSchema),
defaultValues: defaultManageReporting, defaultValues: defaultManageReporting,
}); });
const navigate = useNavigate();
const { data, isLoading } = useOrganizationHierarchy(employeeId); const { data, isLoading } = useOrganizationHierarchy(employeeId);
@ -25,7 +28,6 @@ const ManageReporting = ({ onClosed, employee, employeeId }) => {
employeeId, employeeId,
onClosed onClosed
); );
const primaryValue = watch("primaryNotifyTo"); const primaryValue = watch("primaryNotifyTo");
const secondaryValue = watch("secondaryNotifyTo"); const secondaryValue = watch("secondaryNotifyTo");
@ -88,16 +90,48 @@ const ManageReporting = ({ onClosed, employee, employeeId }) => {
manageHierarchy(payload); manageHierarchy(payload);
}; };
const handleClick = () => {
handleClose();
navigate(`/employee/${employee.id}`);
};
return ( return (
<div> <div>
<form onSubmit={handleSubmit(onSubmit)} className="p-sm-0 p-2"> <form onSubmit={handleSubmit(onSubmit)} className="p-sm-0 p-2">
<h5 className="m-0 py-1 mb-3"> <h5 className="m-0 py-1 mb-4">Reporting Manager</h5>
Update Reporting Manager (
{`${employee.firstName || ""} ${employee.middleName || ""} ${employee.lastName || ""}`.trim()}
)
</h5>
{/* Primary */} {/* Employee Info */}
<div className="d-flex align-items-center justify-content-start mb-4 ps-0">
<div className="me-1 mb-1">
<Avatar size="sm" firstName={employee.firstName} lastName={employee.lastName} />
</div>
{/* Employee Name + Role */}
<div className="d-flex flex-column">
<div className="d-flex align-items-center gap-2">
<span className="fw-semibold text-dark fs-6 me-1 cursor-pointer" onClick={handleClick}>
{`${employee.firstName || ""} ${employee.middleName || ""} ${employee.lastName || ""}`.trim() || "Employee Name NA"}
</span>
{/* External Link Icon (Navigate to Employee Profile) */}
<i
className="bx bx-link-external text-primary"
style={{ fontSize: "1rem", cursor: "pointer" }}
title="View Profile"
onClick={handleClick}
></i>
</div>
<div className="text-start">
{employee.jobRole && (
<small className="text-muted">{employee.jobRole}</small>
)}
</div>
</div>
</div>
{/* Primary Reporting Manager */}
<div className="mb-4 text-start"> <div className="mb-4 text-start">
<Label className="form-label" required> <Label className="form-label" required>
Primary Reporting Manager Primary Reporting Manager
@ -105,7 +139,7 @@ const ManageReporting = ({ onClosed, employee, employeeId }) => {
<PmsEmployeeInputTag <PmsEmployeeInputTag
control={control} control={control}
name="primaryNotifyTo" name="primaryNotifyTo"
placeholder={primaryValue?.length > 0 ? "" : "Select primary report-to"} placeholder={primaryValue?.length > 0 ? "" : "Search and select primary manager"}
forAll={true} forAll={true}
disabled={primaryValue?.length > 0} disabled={primaryValue?.length > 0}
/> />
@ -116,21 +150,18 @@ const ManageReporting = ({ onClosed, employee, employeeId }) => {
)} )}
</div> </div>
{/* Secondary Reporting Manager */}
{/* Secondary */}
<div className="mb-4 text-start"> <div className="mb-4 text-start">
<Label className="form-label"> <Label className="form-label">Secondary Reporting Manager</Label>
Secondary Reporting Manager
</Label>
<PmsEmployeeInputTag <PmsEmployeeInputTag
control={control} control={control}
name="secondaryNotifyTo" name="secondaryNotifyTo"
placeholder="Select secondary report-to(s)" placeholder="Search and add secondary managers"
forAll={true} forAll={true}
/> />
</div> </div>
{/* Buttons */}
<div className="d-flex justify-content-end gap-3 mt-3 mb-3"> <div className="d-flex justify-content-end gap-3 mt-3 mb-3">
<button <button
type="button" type="button"

View File

@ -44,9 +44,8 @@ const PaymentRequestList = ({ filters, groupBy = "submittedBy", search }) => {
displayField = "Status"; displayField = "Status";
break; break;
case "submittedBy": case "submittedBy":
key = `${item?.createdBy?.firstName ?? ""} ${ key = `${item?.createdBy?.firstName ?? ""} ${item.createdBy?.lastName ?? ""
item.createdBy?.lastName ?? "" }`.trim();
}`.trim();
displayField = "Submitted By"; displayField = "Submitted By";
break; break;
case "project": case "project":
@ -93,40 +92,52 @@ const PaymentRequestList = ({ filters, groupBy = "submittedBy", search }) => {
align: "text-start", align: "text-start",
getValue: (e) => e.title || "N/A", getValue: (e) => e.title || "N/A",
}, },
// { key: "payee", label: "Payee", align: "text-start" }, // {
{ // key: "SubmittedBy",
key: "SubmittedBy", // label: "Submitted By",
label: "Submitted By", // align: "text-start",
align: "text-start", // getValue: (e) =>
getValue: (e) => // `${e.createdBy?.firstName ?? ""} ${
`${e.createdBy?.firstName ?? ""} ${ // e.createdBy?.lastName ?? ""
e.createdBy?.lastName ?? "" // }`.trim() || "N/A",
}`.trim() || "N/A", // customRender: (e) => (
customRender: (e) => ( // <div
<div // className="d-flex align-items-center cursor-pointer"
className="d-flex align-items-center cursor-pointer" // onClick={() => navigate(`/employee/${e.createdBy?.id}`)}
onClick={() => navigate(`/employee/${e.createdBy?.id}`)} // >
> // <Avatar
<Avatar // size="xs"
size="xs" // classAvatar="m-0"
classAvatar="m-0" // firstName={e.createdBy?.firstName}
firstName={e.createdBy?.firstName} // lastName={e.createdBy?.lastName}
lastName={e.createdBy?.lastName} // />
/> // <span className="text-truncate">
<span className="text-truncate"> // {`${e.createdBy?.firstName ?? ""} ${
{`${e.createdBy?.firstName ?? ""} ${ // e.createdBy?.lastName ?? ""
e.createdBy?.lastName ?? "" // }`.trim() || "N/A"}
}`.trim() || "N/A"} // </span>
</span> // </div>
</div> // ),
), // },
},
{ {
key: "createdAt", key: "createdAt",
label: "Submitted On", label: "Created At",
align: "text-start", align: "text-start",
getValue: (e) => formatUTCToLocalTime(e?.createdAt), getValue: (e) => formatUTCToLocalTime(e?.createdAt),
}, },
{
key: "payee",
label: "Payee",
align: "text-start",
getValue: (e) => e.payee || "N/A",
},
{
key: "dueDate",
label: "Due Date",
align: "text-start",
getValue: (e) => formatUTCToLocalTime(e?.dueDate),
},
{ {
key: "amount", key: "amount",
label: "Amount", label: "Amount",
@ -143,9 +154,8 @@ const PaymentRequestList = ({ filters, groupBy = "submittedBy", search }) => {
align: "text-center", align: "text-center",
getValue: (e) => ( getValue: (e) => (
<span <span
className={`badge bg-label-${ className={`badge bg-label-${getColorNameFromHex(e?.expenseStatus?.color) || "secondary"
getColorNameFromHex(e?.expenseStatus?.color) || "secondary" }`}
}`}
> >
{e?.expenseStatus?.name || "Unknown"} {e?.expenseStatus?.name || "Unknown"}
</span> </span>
@ -171,8 +181,8 @@ const PaymentRequestList = ({ filters, groupBy = "submittedBy", search }) => {
const header = [ const header = [
"Request ID", "Request ID",
"Request Title", "Request Title",
"Submitted By", "Created At",
"Submitted On", "Due Date",
"Amount", "Amount",
"Status", "Status",
"Action", "Action",
@ -181,10 +191,10 @@ const PaymentRequestList = ({ filters, groupBy = "submittedBy", search }) => {
const grouped = groupBy const grouped = groupBy
? Object.fromEntries( ? Object.fromEntries(
Object.entries(groupByField(data?.data ?? [], groupBy)).sort( Object.entries(groupByField(data?.data ?? [], groupBy)).sort(
([keyA], [keyB]) => keyA.localeCompare(keyB) ([keyA], [keyB]) => keyA.localeCompare(keyB)
)
) )
)
: { All: data?.data ?? [] }; : { All: data?.data ?? [] };
const IsGroupedByDate = [ const IsGroupedByDate = [

View File

@ -199,7 +199,7 @@ const ViewPaymentRequest = ({ requestId }) => {
<div className="row text-start"> <div className="row text-start">
<div className="col-6 mb-3"> <div className="col-6 mb-3">
<label className="form-label me-2 mb-0 fw-semibold text-start"> <label className="form-label me-2 mb-0 fw-semibold text-start">
Supplier: Payee:
</label> </label>
</div> </div>
<div className="col-6 mb-3"> <div className="col-6 mb-3">