342 lines
12 KiB
JavaScript
342 lines
12 KiB
JavaScript
import { useCallback, useEffect, useState, useMemo } from "react";
|
|
import getGreetingMessage from "../../utils/greetingHandler";
|
|
import {
|
|
cacheData,
|
|
clearAllCache,
|
|
getCachedData,
|
|
useSelectedProject,
|
|
} from "../../slices/apiDataManager";
|
|
import AuthRepository from "../../repositories/AuthRepository";
|
|
import { useDispatch, useSelector } from "react-redux";
|
|
import { changeMaster, setProjectId } from "../../slices/localVariablesSlice";
|
|
import useMaster from "../../hooks/masterHook/useMaster";
|
|
import { useProfile } from "../../hooks/useProfile";
|
|
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
|
import Avatar from "../../components/common/Avatar";
|
|
import { useChangePassword } from "../Context/ChangePasswordContext";
|
|
import { useProjects } from "../../hooks/useProjects";
|
|
import { useProjectName } from "../../hooks/useProjects";
|
|
import eventBus from "../../services/eventBus";
|
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
|
import { ALLOW_PROJECTSTATUS_ID, MANAGE_PROJECT, UUID_REGEX } from "../../utils/constants";
|
|
import { useAuthModal, useLogout } from "../../hooks/useAuth";
|
|
|
|
const Header = () => {
|
|
const { profile } = useProfile();
|
|
const { data: masterData } = useMaster();
|
|
const location = useLocation();
|
|
const dispatch = useDispatch();
|
|
const navigate = useNavigate();
|
|
|
|
const { mutate: logout, isPending: logouting } = useLogout();
|
|
const { onOpen } = useAuthModal();
|
|
const { openChangePassword } = useChangePassword();
|
|
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
|
|
|
|
const pathname = location.pathname;
|
|
|
|
// ======= MEMO CHECKS =======
|
|
|
|
const isDashboardPath = pathname === "/" || pathname === "/dashboard";
|
|
const isProjectPath = pathname === "/projects";
|
|
const isDirectory = pathname === "/directory";
|
|
const isEmployeeList = pathname === "/employees";
|
|
const isEmployeeProfile = UUID_REGEX.test(pathname);
|
|
const isMasters = pathname === "/masters";
|
|
const isExpensePath = pathname.startsWith("/expenses");
|
|
|
|
const hideDropPaths =
|
|
isDirectory || isEmployeeList || isMasters || isEmployeeProfile || isExpensePath;
|
|
|
|
const showProjectDropdown = !hideDropPaths;
|
|
|
|
// ===== Project Names & Selected Project =====
|
|
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
|
const selectedProject = useSelectedProject();
|
|
|
|
const projectsForDropdown = useMemo(
|
|
() =>
|
|
isDashboardPath
|
|
? projectNames
|
|
: projectNames?.filter((project) =>
|
|
ALLOW_PROJECTSTATUS_ID.includes(project.projectStatusId)
|
|
),
|
|
[projectNames, isDashboardPath]
|
|
);
|
|
|
|
const currentProjectDisplayName = useMemo(() => {
|
|
if (projectLoading) return "Loading...";
|
|
if (!projectNames?.length) return "No Projects Assigned";
|
|
if (projectNames.length === 1) return projectNames[0].name;
|
|
if (selectedProject === null) return "All Projects";
|
|
const selectedObj = projectNames.find((p) => p.id === selectedProject);
|
|
return selectedObj
|
|
? selectedObj.name
|
|
: projectNames[0]?.name || "No Projects Assigned";
|
|
}, [projectLoading, projectNames, selectedProject]);
|
|
|
|
// ===== Role Helper =====
|
|
const getRole = (roles, joRoleId) => {
|
|
if (!Array.isArray(roles)) return "User";
|
|
return roles.find((r) => r.id === joRoleId)?.name || "User";
|
|
};
|
|
|
|
// ===== Navigate to Profile =====
|
|
const handleProfilePage = () =>
|
|
navigate(`/employee/${profile?.employeeInfo?.id}`);
|
|
|
|
// ===== Set default project on load =====
|
|
useEffect(() => {
|
|
if (
|
|
projectNames?.length &&
|
|
selectedProject === undefined &&
|
|
!getCachedData("hasReceived")
|
|
) {
|
|
if (projectNames.length === 1) {
|
|
dispatch(setProjectId(projectNames[0].id || null));
|
|
} else {
|
|
if (isDashboardPath) {
|
|
dispatch(setProjectId(null));
|
|
} else {
|
|
const firstAllowed = projectNames.find((project) =>
|
|
ALLOW_PROJECTSTATUS_ID.includes(project.projectStatusId)
|
|
);
|
|
dispatch(setProjectId(firstAllowed?.id || null));
|
|
}
|
|
}
|
|
}
|
|
}, [projectNames, selectedProject, dispatch, isDashboardPath]);
|
|
|
|
// ===== Event Handlers =====
|
|
const handler = useCallback(
|
|
async (data) => {
|
|
if (!HasManageProjectPermission) {
|
|
await fetchData();
|
|
if (data.projectIds?.includes(selectedProject)) {
|
|
cacheData("hasReceived", false);
|
|
}
|
|
}
|
|
},
|
|
[fetchData, selectedProject, HasManageProjectPermission]
|
|
);
|
|
|
|
const newProjectHandler = useCallback(
|
|
async (msg) => {
|
|
if (
|
|
msg.keyword === "Create_Project" ||
|
|
projectNames?.some((p) => p.id === msg.response?.id)
|
|
) {
|
|
await fetchData();
|
|
cacheData("hasReceived", false);
|
|
}
|
|
},
|
|
[projectNames, fetchData]
|
|
);
|
|
|
|
useEffect(() => {
|
|
dispatch(changeMaster("Job Role"));
|
|
}, [dispatch]);
|
|
|
|
useEffect(() => {
|
|
eventBus.on("assign_project_one", handler);
|
|
eventBus.on("project", newProjectHandler);
|
|
|
|
return () => {
|
|
eventBus.off("assign_project_one", handler);
|
|
eventBus.off("project", newProjectHandler);
|
|
};
|
|
}, [handler, newProjectHandler]);
|
|
|
|
// ===== Project Change =====
|
|
const handleProjectChange = (projectId) => {
|
|
dispatch(setProjectId(projectId));
|
|
if (isProjectPath && projectId !== null) {
|
|
navigate("/projects/details");
|
|
}
|
|
};
|
|
|
|
const shouldShowDropdown = projectNames && projectNames.length > 1;
|
|
|
|
return (
|
|
<nav
|
|
className="layout-navbar container-fluid mb-3 navbar navbar-expand-xl navbar-detached align-items-center bg-navbar-theme"
|
|
id="layout-navbar"
|
|
>
|
|
<div className="layout-menu-toggle navbar-nav align-items-xl-center me-3 me-xl-0 d-xl-none">
|
|
<a
|
|
aria-label="toggle for sidebar"
|
|
className="nav-item nav-link px-0 me-xl-4"
|
|
>
|
|
<i className="bx bx-menu bx-sm"></i>
|
|
</a>
|
|
</div>
|
|
<div
|
|
className="navbar-nav-right d-flex align-items-center justify-content-between"
|
|
id="navbar-collapse"
|
|
>
|
|
{showProjectDropdown && (
|
|
<div className="align-items-center">
|
|
<i className="rounded-circle bx bx-building-house bx-sm-lg bx-md me-2"></i>
|
|
<div className="btn-group">
|
|
{shouldShowDropdown ? (
|
|
<button
|
|
className={`btn btn-sm-sm btn-xl dropdown-toggle px-1`}
|
|
type="button"
|
|
data-bs-toggle="dropdown"
|
|
aria-expanded="false"
|
|
>
|
|
{currentProjectDisplayName}
|
|
</button>
|
|
) : (
|
|
<span className="btn btn-sm-sm btn-xl px-1">
|
|
{currentProjectDisplayName}
|
|
</span>
|
|
)}
|
|
|
|
{shouldShowDropdown &&
|
|
projectsForDropdown &&
|
|
projectsForDropdown.length > 0 && (
|
|
<ul
|
|
className="dropdown-menu"
|
|
style={{ overflow: "auto", maxHeight: "300px" }}
|
|
>
|
|
|
|
<li>
|
|
<button
|
|
className="dropdown-item"
|
|
onClick={() => handleProjectChange(null)}
|
|
>All Project</button>
|
|
</li>
|
|
|
|
{[...projectsForDropdown]
|
|
.sort((a, b) => a?.name?.localeCompare(b.name))
|
|
.map((project) => (
|
|
<li key={project?.id}>
|
|
<button
|
|
className="dropdown-item"
|
|
onClick={() => handleProjectChange(project?.id)}
|
|
>
|
|
{project?.name}
|
|
{project?.shortName && (
|
|
<span className="text-primary fw-semibold ms-1">
|
|
({project?.shortName})
|
|
</span>
|
|
)}
|
|
</button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<ul className="navbar-nav flex-row align-items-center ms-md-auto">
|
|
{/* {HasManageProjectPermission && ( */}
|
|
|
|
{/* )} */}
|
|
<li className="nav-item navbar-dropdown dropdown-user dropdown">
|
|
<a
|
|
aria-label="dropdown profile avatar"
|
|
className="nav-link dropdown-toggle hide-arrow"
|
|
href="#"
|
|
data-bs-toggle="dropdown"
|
|
>
|
|
<div className="avatar avatar-online">
|
|
<Avatar
|
|
firstName={`${profile?.employeeInfo?.firstName}`}
|
|
lastName={`${profile?.employeeInfo?.lastName}`}
|
|
/>
|
|
</div>
|
|
</a>
|
|
<ul className="dropdown-menu dropdown-menu-end">
|
|
<li onClick={handleProfilePage}>
|
|
<a aria-label="go to profile" className="dropdown-item">
|
|
<div className="d-flex">
|
|
<div className="flex-shrink-0 me-3">
|
|
<div className="avatar avatar-online">
|
|
<Avatar
|
|
firstName={`${profile?.employeeInfo?.firstName}`}
|
|
lastName={`${profile?.employeeInfo?.lastName}`}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="flex-grow-1">
|
|
<span className="fw-medium d-block">
|
|
{profile?.employeeInfo?.firstName}
|
|
</span>
|
|
<small className="text-muted">
|
|
{getRole(masterData, profile?.employeeInfo?.joRoleId)}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<div className="dropdown-divider"></div>
|
|
</li>
|
|
{/* <li onClick={() => onOpen()}>
|
|
{" "}
|
|
<a className="dropdown-item cusor-pointer">
|
|
<i className="bx bx-transfer-alt me-2"></i>
|
|
<span className="align-middle">Switch Workspace</span>
|
|
</a>
|
|
</li> */}
|
|
<li onClick={handleProfilePage}>
|
|
<a
|
|
aria-label="go to profile"
|
|
className="dropdown-item cusor-pointer"
|
|
>
|
|
<i className="bx bx-user me-2"></i>
|
|
<span className="align-middle">My Profile</span>
|
|
</a>
|
|
</li>
|
|
<li onClick={handleProfilePage}>
|
|
<a
|
|
aria-label="go to setting "
|
|
className="dropdown-item cusor-pointer"
|
|
>
|
|
<i className="bx bx-cog me-2"></i>
|
|
<span className="align-middle">Settings</span>
|
|
</a>
|
|
</li>
|
|
<li onClick={openChangePassword}>
|
|
{" "}
|
|
<a
|
|
aria-label="go to profile"
|
|
className="dropdown-item cusor-pointer"
|
|
>
|
|
<i className="bx bx-lock-alt me-2"></i>
|
|
<span className="align-middle">Change Password</span>
|
|
</a>
|
|
</li>
|
|
|
|
<li>
|
|
<div className="dropdown-divider"></div>
|
|
</li>
|
|
<li>
|
|
<a
|
|
aria-label="click to log out"
|
|
className="dropdown-item cusor-pointer"
|
|
onClick={() => logout()}
|
|
>
|
|
{logouting ? (
|
|
"Please Wait"
|
|
) : (
|
|
<>
|
|
{" "}
|
|
<i className="bx bx-log-out me-2"></i>
|
|
<span className="align-middle">SignOut</span>
|
|
</>
|
|
)}
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</nav>
|
|
);
|
|
};
|
|
export default Header;
|