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

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

View File

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

View File

@ -5,6 +5,8 @@ import Label from "../common/Label";
import PmsEmployeeInputTag from "../common/PmsEmployeeInputTag";
import { useManageEmployeeHierarchy, useOrganizationHierarchy } from "../../hooks/useEmployees";
import { ManageReportingSchema, defaultManageReporting } from "./EmployeeSchema";
import Avatar from "../common/Avatar";
import { useNavigate } from "react-router-dom";
const ManageReporting = ({ onClosed, employee, employeeId }) => {
const {
@ -17,6 +19,7 @@ const ManageReporting = ({ onClosed, employee, employeeId }) => {
resolver: zodResolver(ManageReportingSchema),
defaultValues: defaultManageReporting,
});
const navigate = useNavigate();
const { data, isLoading } = useOrganizationHierarchy(employeeId);
@ -25,7 +28,6 @@ const ManageReporting = ({ onClosed, employee, employeeId }) => {
employeeId,
onClosed
);
const primaryValue = watch("primaryNotifyTo");
const secondaryValue = watch("secondaryNotifyTo");
@ -88,16 +90,48 @@ const ManageReporting = ({ onClosed, employee, employeeId }) => {
manageHierarchy(payload);
};
const handleClick = () => {
handleClose();
navigate(`/employee/${employee.id}`);
};
return (
<div>
<form onSubmit={handleSubmit(onSubmit)} className="p-sm-0 p-2">
<h5 className="m-0 py-1 mb-3">
Update Reporting Manager (
{`${employee.firstName || ""} ${employee.middleName || ""} ${employee.lastName || ""}`.trim()}
)
</h5>
<h5 className="m-0 py-1 mb-4">Reporting Manager</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">
<Label className="form-label" required>
Primary Reporting Manager
@ -105,7 +139,7 @@ const ManageReporting = ({ onClosed, employee, employeeId }) => {
<PmsEmployeeInputTag
control={control}
name="primaryNotifyTo"
placeholder={primaryValue?.length > 0 ? "" : "Select primary report-to"}
placeholder={primaryValue?.length > 0 ? "" : "Search and select primary manager"}
forAll={true}
disabled={primaryValue?.length > 0}
/>
@ -116,21 +150,18 @@ const ManageReporting = ({ onClosed, employee, employeeId }) => {
)}
</div>
{/* Secondary */}
{/* Secondary Reporting Manager */}
<div className="mb-4 text-start">
<Label className="form-label">
Secondary Reporting Manager
</Label>
<Label className="form-label">Secondary Reporting Manager</Label>
<PmsEmployeeInputTag
control={control}
name="secondaryNotifyTo"
placeholder="Select secondary report-to(s)"
placeholder="Search and add secondary managers"
forAll={true}
/>
</div>
{/* Buttons */}
<div className="d-flex justify-content-end gap-3 mt-3 mb-3">
<button
type="button"

View File

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

View File

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