Merge branch 'Service_Project_Managment' of https://git.marcoaiot.com/admin/marco.pms.web into Service_Project_Managment
This commit is contained in:
commit
9b7988d6d8
@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 = [
|
||||
|
||||
@ -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">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user