Changes in Dashboard dropdown adding All Projects Selection.

This commit is contained in:
Kartik sharma 2025-07-15 15:00:49 +05:30
parent 4bbdcd0db0
commit 05530842b7
6 changed files with 186 additions and 229 deletions

View File

@ -1,3 +1,4 @@
import getGreetingMessage from "../../utils/greetingHandler";
import {
cacheData,
@ -26,6 +27,8 @@ const Header = () => {
const { data, loading } = useMaster();
const navigate = useNavigate();
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
const isDashboard = location.pathname === "/dashboard";
// const isDirectoryPath = location.pathname === "/directory";
const getRole = (roles, joRoleId) => {
if (!Array.isArray(roles)) return "User";
@ -99,7 +102,7 @@ const Header = () => {
if (
projectNames &&
projectNames.length > 0 &&
selectedProject === undefined &&
selectedProject === undefined &&
!getCachedData("hasReceived")
) {
dispatch(setProjectId(null)); // Set to null for "All Projects"
@ -108,7 +111,7 @@ const Header = () => {
/** Check if current page is project details page or directory page */
const isProjectPath = /^\/projects\/[a-f0-9-]{36}$/.test(location.pathname);
// const isProjectPath = /^\/projects\/[a-f0-9-]{36}$/.test(location.pathname);
const isDirectoryPath = /^\/directory$/.test(location.pathname);
const handler = useCallback(
@ -172,69 +175,61 @@ const Header = () => {
id="navbar-collapse"
>
{/* Project Selection Dropdown */}
{projectNames && ( // Ensure projectNames is not null before rendering dropdown
{projectNames && !isDirectoryPath && (
<div className="align-items-center">
{(!isProjectPath && !isDirectoryPath) && (
<>
<i className="rounded-circle bx bx-building-house bx-sm-lg bx-md me-2"></i>
<div className="btn-group">
<button
className={`btn btn-sm-sm btn-xl ${
projectNames.length > 0 ? "dropdown-toggle" : ""
} px-1`}
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
{displayText}
</button>
<i className="rounded-circle bx bx-building-house bx-sm-lg bx-md me-2"></i>
<div className="btn-group">
<button
className={`btn btn-sm-sm btn-xl ${projectNames.length > 0 ? "dropdown-toggle" : ""
} px-1`}
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
{displayText}
</button>
{/* Render dropdown menu only if there are projects or the "All Projects" option is relevant */}
{projectNames.length > 0 && (
<ul
className="dropdown-menu"
style={{ overflow: "auto", maxHeight: "300px" }}
>
{/* "All Projects" option */}
<li>
{projectNames.length > 0 && (
<ul
className="dropdown-menu"
style={{ overflow: "auto", maxHeight: "300px" }}
>
{/* Show "All Projects" only on dashboard */}
{isDashboard && (
<li>
<button
className="dropdown-item"
onClick={() => dispatch(setProjectId(null))}
>
All Projects
</button>
</li>
)}
{[...projectNames]
.sort((a, b) => a?.name?.localeCompare(b.name))
.map((project) => (
<li key={project?.id}>
<button
className="dropdown-item"
onClick={() => dispatch(setProjectId(null))} // Set projectId to null for "All Projects"
onClick={() => dispatch(setProjectId(project?.id))}
>
All Projects
{project?.name}
{project?.shortName && (
<span className="text-primary fw-semibold ms-1">
({project?.shortName})
</span>
)}
</button>
</li>
{[...projectNames]
.sort((a, b) => a?.name?.localeCompare(b.name))
.map((project) => (
<li key={project?.id}>
<button
className="dropdown-item"
onClick={() =>
dispatch(setProjectId(project?.id))
}
>
{project?.name}{" "}
{project?.shortName ? (
<span className="text-primary fw-semibold ">
({project?.shortName})
</span>
) : (
""
)}
</button>
</li>
))}
</ul>
)}
</div>
</>
)}
))}
</ul>
)}
</div>
</div>
)}
{/* Display project name on project details or directory pages */}
{isProjectPath && (<span className="fs-5 align-items-center"><i className="rounded-circle bx bx-building-house bx-sm-lg bx-md me-2"></i>{displayText}</span>)}
{/* { (<span className="fs-5 align-items-center"><i className="rounded-circle bx bx-building-house bx-sm-lg bx-md me-2"></i>{displayText}</span>)} */}
{/* User Profile and Shortcuts */}
<ul className="navbar-nav flex-row align-items-center ms-md-auto">

View File

@ -2,48 +2,53 @@ import React, { useEffect, useState } from "react";
import moment from "moment";
import { getProjectStatusName } from "../../utils/projectStatus";
import {useProjectDetails, useUpdateProject} from "../../hooks/useProjects";
import {useParams} from "react-router-dom";
import { useSelector } from "react-redux"; // Import useSelector
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
import {MANAGE_PROJECT} from "../../utils/constants";
import GlobalModel from "../common/GlobalModel";
import ManageProjectInfo from "./ManageProjectInfo";
import {useQueryClient} from "@tanstack/react-query";
const AboutProject = () =>
{
const [ IsOpenModal, setIsOpenModal ] = useState( false )
const {mutate: UpdateProjectDetails, isPending} = useUpdateProject( {
onSuccessCallback: () =>
{
setIsOpenModal(false)
const AboutProject = () => {
const [IsOpenModal, setIsOpenModal] = useState(false);
const {mutate: UpdateProjectDetails, isPending} = useUpdateProject({
onSuccessCallback: () => {
setIsOpenModal(false);
}
} )
const ClientQuery = useQueryClient()
const {projectId} = useParams();
});
const ClientQuery = useQueryClient();
// *** MODIFIED LINE: Get projectId from Redux store using useSelector ***
const projectId = useSelector((store) => store.localVariables.projectId);
const manageProject = useHasUserPermission(MANAGE_PROJECT);
const {projects_Details, isLoading, error,refetch} = useProjectDetails( projectId )
const handleFormSubmit = ( updatedProject ) =>
{
if ( projects_Details?.id )
{
UpdateProjectDetails({ projectId: projects_Details?.id,updatedData: updatedProject,
} );
refetch()
}
};
const {projects_Details, isLoading, error,refetch} = useProjectDetails( projectId ); // Pass projectId from useSelector
const handleFormSubmit = ( updatedProject ) => {
if ( projects_Details?.id ) {
UpdateProjectDetails({ projectId: projects_Details?.id,updatedData: updatedProject });
// The refetch here might be redundant or could be handled by react-query's invalidateQueries
// if UpdateProjectDetails properly invalidates the 'projectDetails' query key.
// If refetch is still needed, consider adding a delay or using onSuccess of UpdateProjectDetails.
// For now, keeping it as is based on your original code.
refetch();
}
};
return (
<>
{IsOpenModal && (
<GlobalModel isOpen={IsOpenModal} closeModal={()=>setIsOpenModal(false)}>
<GlobalModel isOpen={IsOpenModal} closeModal={()=>setIsOpenModal(false)}>
<ManageProjectInfo
project={projects_Details}
handleSubmitForm={handleFormSubmit}
onClose={() => setIsOpenModal( false )}
isPending={isPending}
/>
</GlobalModel>
)}
</GlobalModel>
)}
{projects_Details && (
<>
<>
<div className="card mb-6">
<div className="card-header text-start">
<h6 className="card-action-title mb-0">
@ -126,10 +131,9 @@ const AboutProject = () =>
</>
)}
{isLoading && <span>loading...</span>}
</>
);
};
export default AboutProject;
export default AboutProject;

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState } from "react";
import moment from "moment";
import { formatNumber, getCompletionPercentage, getDateDifferenceInDays } from "../../utils/dateUtils";
import { formatNumber, getDateDifferenceInDays } from "../../utils/dateUtils";
import { useNavigate } from "react-router-dom";
import { useProjectDetails, useUpdateProject } from "../../hooks/useProjects";
import ManageProjectInfo from "./ManageProjectInfo";
@ -57,7 +57,7 @@ const ProjectCard = ({ projectData, recall }) => {
const handleClose = () => setShowModal(false);
const handleViewProject = () => {
navigate(`/projects/${projectData.id}`);
navigate(`/projects/details`);
};
const handleFormSubmit = (updatedProject) => {
@ -87,19 +87,18 @@ const ProjectCard = ({ projectData, recall }) => {
<div className={`card cursor-pointer ${isPending ? "bg-light opacity-50 pointer-events-none" : ""}`}>
<div className="card-header pb-4">
<div className="d-flex align-items-start">
<div className="d-flex align-items-center ">
<div className="d-flex align-items-center">
<div className="avatar me-4">
<i
className="rounded-circle bx bx-building-house"
style={{ fontSize: "xx-large" }}
></i>
</div>
<div className="me-2 text-wrap ">
<div className="me-2">
<h5
className="mb-0 stretched-link text-heading text-start text-truncate"
onClick={handleViewProject}
style={{ maxWidth: "180px", display: "inline-block" }}
>
className="mb-0 stretched-link text-heading text-start"
onClick={handleViewProject}
>
{projectInfo.shortName
? projectInfo.shortName
: projectInfo.name}
@ -228,7 +227,12 @@ const ProjectCard = ({ projectData, recall }) => {
Task: {formatNumber(projectInfo.completedWork)} / {formatNumber(projectInfo.plannedWork)}
</small>
<small className="text-body">
{getCompletionPercentage(projectInfo.completedWork, projectInfo.plannedWork)}
{Math.floor(
getProgressInNumber(
projectInfo.plannedWork,
projectInfo.completedWork
)
) || 0}{" "}
% Completed
</small>
</div>
@ -270,4 +274,4 @@ const ProjectCard = ({ projectData, recall }) => {
);
};
export default ProjectCard;
export default ProjectCard;

View File

@ -1,4 +1,4 @@
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux"; // Import useSelector
import React, { useState, useEffect, useCallback } from "react";
import ActivityTimeline from "../../components/Project/ActivityTimeline";
@ -31,157 +31,111 @@ import eventBus from "../../services/eventBus";
import ProjectProgressChart from "../../components/Dashboard/ProjectProgressChart";
const ProjectDetails = () => {
let { projectId } = useParams();
const {
projects_Details,
loading: projectLoading,
error: ProjectError,
refetch
} = useProjectDetails(projectId);
// const { projectId } = useParams(); // REMOVE THIS LINE
const dispatch = useDispatch();
const [project, setProject] = useState(null);
// const [projectDetails, setProjectDetails] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
// const fetchData = async () => {
// const project_cache = getCachedData("projectInfo");
// if (!project_cache || project_cache?.projectId !== projectId) {
// ProjectRepository.getProjectByprojectId(projectId)
// .then((response) => {
// setProjectDetails(response.data);
// setProject(response.data);
// cacheData("projectInfo", { projectId, data: response.data });
// setLoading(false);
// })
// .catch((error) => {
// console.error(error);
// setError("Failed to fetch data.");
// setLoading(false);
// });
// } else {
// setProjectDetails(project_cache.data);
// setProject(project_cache.data);
// setLoading(false);
// }
// };
// GET projectId FROM REDUX STORE
const projectId = useSelector((store) => store.localVariables.projectId);
const {
projects_Details,
loading: projectLoading,
error: projectError,
refetch,
} = useProjectDetails(projectId);
const [activePill, setActivePill] = useState("profile");
const handlePillClick = (pillKey) => {
setActivePill(pillKey);
};
const handleDataChange = (data) => {
fetchData();
};
const renderContent = () => {
if (projectLoading) return <Loader></Loader>;
switch (activePill) {
case "profile": {
return (
<div className="row">
<div className="col-xl-4 col-lg-5 col-md-5 mt-5">
<AboutProject ></AboutProject>
</div>
<div className="col-xl-4 col-lg-5 col-md-5 mt-5">
<ProjectOverview project={projectId} />
</div>
</div>
);
}
case "teams": {
return (
<div className="row">
<div className="col-lg-12 col-xl-12">
{/* Teams */}
<Teams ></Teams>
{/* Teams */}
</div>
</div>
);
break;
}
case "infra": {
return (
<ProjectInfra
data={projects_Details}
onDataChange={handleDataChange}
></ProjectInfra>
);
break;
}
case "workplan": {
return (
<WorkPlan
data={projects_Details}
onDataChange={handleDataChange}
></WorkPlan>
);
break;
}
case "directory": {
return (
<div className="row">
<Directory IsPage={false} prefernceContacts={projects_Details.id} />
</div>
);
}
default:
return <ComingSoonPage></ComingSoonPage>;
}
};
useEffect(() => {
dispatch(setProjectId(projectId));
}, [projects_Details, projectId]);
// REMOVE THIS useEffect AS projectId IS NOW FROM REDUX
// useEffect(() => {
// if (projectId) dispatch(setProjectId(projectId));
// }, [projectId, dispatch]);
const handler = useCallback(
(msg) => {
if (msg.keyword === "Update_Project" && projects_Details.id === msg.response.id) {
refetch()
if (msg.keyword === "Update_Project" && projects_Details?.id === msg.response.id) {
refetch();
}
},
[projects_Details, handleDataChange]
[projects_Details, refetch]
);
useEffect(() => {
eventBus.on("project", handler);
return () => eventBus.off("project", handler);
}, [handler]);
const handlePillClick = (pillKey) => {
setActivePill(pillKey);
};
const renderContent = () => {
if (projectLoading || !projects_Details) return <Loader />;
switch (activePill) {
case "profile":
return (
<>
<div className="row">
<div className="col-lg-4 col-md-5 mt-5">
<AboutProject></AboutProject>
<ProjectOverview project={projectId} />
</div>
<div className="col-lg-8 col-md-7 mt-5">
<ProjectProgressChart ShowAllProject="false" DefaultRange="1M" />
</div>
</div>
</>
);
case "teams":
return (
<div className="row">
<div className="col-lg-12">
<Teams />
</div>
</div>
);
case "infra":
return (
<ProjectInfra data={projects_Details} onDataChange={refetch} />
);
case "workplan":
return (
<WorkPlan data={projects_Details} onDataChange={refetch} />
);
case "directory":
return (
<div className="row mt-2">
<Directory IsPage={false} prefernceContacts={projects_Details.id} />
</div>
);
default:
return <ComingSoonPage />;
}
};
return (
<>
{}
<div className="container-fluid">
<Breadcrumb
data={[
{ label: "Home", link: "/dashboard" },
{ label: "Projects", link: "/projects" },
{ label: `${project?.name ? project?.name : ""}`, link: null },
]}
></Breadcrumb>
<div className="container-fluid">
<Breadcrumb
data={[
{ label: "Home", link: "/dashboard" },
{ label: "Projects", link: "/projects" },
{ label: projects_Details?.name || "Project", link: null },
]}
/>
<div className="row">
{projectLoading && <p>Loading....</p>}
{/* {!projectLoading && project && (
<ProjectBanner project_data={project}></ProjectBanner>
)} */}
<ProjectNav
onPillClick={handlePillClick}
activePill={activePill}
></ProjectNav>
</div>
<div className="row"></div>
{renderContent()}
<div className="row">
<ProjectNav onPillClick={handlePillClick} activePill={activePill} />
</div>
</>
{renderContent()}
</div>
);
};
export default ProjectDetails;
export default ProjectDetails;

View File

@ -62,7 +62,7 @@ const ProjectListView = ({ projectData, recall }) => {
const handleClose = () => setShowModal(false);
const handleViewProject = () => {
navigate(`/projects/${projectData.id}`);
navigate(`/projects/details`);
};
const handleFormSubmit = (updatedProject) => {
@ -89,7 +89,7 @@ const ProjectListView = ({ projectData, recall }) => {
<td className="text-start" colSpan={5}>
<span
className="text-primary cursor-pointer"
onClick={() => navigate(`/projects/${projectInfo.id}`)}
onClick={() => navigate(`/projects/details`)}
>
{projectInfo.shortName
? `${projectInfo.name} (${projectInfo.shortName})`
@ -162,7 +162,7 @@ const ProjectListView = ({ projectData, recall }) => {
<a
aria-label="click to View details"
className="dropdown-item"
onClick={() => navigate(`/projects/${projectInfo.id}`)}
onClick={() => navigate(`/projects/details`)}
>
<i className="bx bx-detail me-2"></i>
<span className="align-left">View details</span>
@ -193,4 +193,4 @@ const ProjectListView = ({ projectData, recall }) => {
);
};
export default ProjectListView;
export default ProjectListView;

View File

@ -63,7 +63,7 @@ const router = createBrowserRouter(
{ path: "/", element: <Dashboard /> },
{ path: "/dashboard", element: <Dashboard /> },
{ path: "/projects", element: <ProjectList /> },
{ path: "/projects/:projectId", element: <ProjectDetails /> },
{ path: "/projects/details", element: <ProjectDetails /> },
{ path: "/project/manage/:projectId", element: <ManageProject /> },
{ path: "/employees", element: <EmployeeList /> },
{ path: "/employee/:employeeId", element: <EmployeeProfile /> },