Modify project landing page
This commit is contained in:
parent
475f7b564e
commit
5bb27b6106
10
index.html
10
index.html
@ -1,11 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" lang="en"
|
||||
class="light-style layout-navbar-fixed layout-menu-fixed layout-compact"
|
||||
dir="ltr"
|
||||
data-theme="theme-default"
|
||||
data-assets-path="/assets/"
|
||||
data-template="vertical-menu-template"
|
||||
data-style="light">
|
||||
<html lang="en" lang="en" class="light-style layout-navbar-fixed layout-menu-fixed layout-compact" dir="ltr"
|
||||
data-theme="theme-default" data-assets-path="/assets/" data-template="vertical-menu-template" data-style="light">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@ -30,6 +25,7 @@
|
||||
<!-- Core CSS -->
|
||||
<link rel="stylesheet" href="/assets/vendor/css/core.css" class="template-customizer-core-css" />
|
||||
<link rel="stylesheet" href="/assets/vendor/css/theme-default.css" class="template-customizer-theme-css" />
|
||||
<link rel="stylesheet" href="/assets/css/core-extend.css" />
|
||||
<link rel="stylesheet" href="/assets/css/default.css" />
|
||||
|
||||
<link rel="stylesheet" href="/assets/vendor/libs/perfect-scrollbar/perfect-scrollbar.css" />
|
||||
|
12
public/assets/css/core-extend.css
Normal file
12
public/assets/css/core-extend.css
Normal file
@ -0,0 +1,12 @@
|
||||
:root,
|
||||
[data-bs-theme="light"] {
|
||||
--bs-nav-link-font-size: 0.7375rem;
|
||||
}
|
||||
|
||||
.nav {
|
||||
--bs-nav-link-font-size: 0.7375rem;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 0.5rem var(--bs-card-cap-padding-x);
|
||||
}
|
@ -4,12 +4,15 @@ import { useProjects } from "../../hooks/useProjects";
|
||||
import { useDashboard_Data } from "../../hooks/useDashboard_Data";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const ProjectProgressChart = () => {
|
||||
const ProjectProgressChart = ({
|
||||
ShowAllProject = true,
|
||||
DefaultRange = "15D",
|
||||
}) => {
|
||||
const selectedProject = useSelector(
|
||||
(store) => store.localVariables.projectId
|
||||
);
|
||||
const { projects } = useProjects();
|
||||
const [range, setRange] = useState("15D");
|
||||
const [range, setRange] = useState(DefaultRange);
|
||||
const [showAllEmployees, setShowAllEmployees] = useState(false);
|
||||
|
||||
const getDaysFromRange = (range) => {
|
||||
@ -69,7 +72,7 @@ const ProjectProgressChart = () => {
|
||||
);
|
||||
const lineChartCategoriesDates = sortedDashboardData.map((d) =>
|
||||
new Date(d.date).toLocaleDateString("en-US", {
|
||||
weekday:"short",
|
||||
weekday: "short",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
@ -82,7 +85,7 @@ const ProjectProgressChart = () => {
|
||||
: selectedProjectData?.name;
|
||||
|
||||
return (
|
||||
<div className="card h-100">
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="d-flex flex-wrap justify-content-between align-items-start mb-2">
|
||||
{/* Left: Title */}
|
||||
@ -93,22 +96,24 @@ const ProjectProgressChart = () => {
|
||||
|
||||
{/* Right: Checkbox and Project Name */}
|
||||
<div className="d-flex flex-column align-items-start align-items-md-end text-start text-md-end mt-1 mt-md-0">
|
||||
<div className="form-check form-switch mb-1 d-flex align-items-center">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id="showAllEmployees"
|
||||
checked={showAllEmployees}
|
||||
onChange={(e) => setShowAllEmployees(e.target.checked)}
|
||||
/>
|
||||
<label
|
||||
className="form-check-label ms-2"
|
||||
htmlFor="showAllEmployees"
|
||||
>
|
||||
All Projects
|
||||
</label>
|
||||
</div>
|
||||
{ShowAllProject == true && (
|
||||
<div className="form-check form-switch mb-1 d-flex align-items-center">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id="showAllEmployees"
|
||||
checked={showAllEmployees}
|
||||
onChange={(e) => setShowAllEmployees(e.target.checked)}
|
||||
/>
|
||||
<label
|
||||
className="form-check-label ms-2"
|
||||
htmlFor="showAllEmployees"
|
||||
>
|
||||
All Projects
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
{!showAllEmployees && selectedProjectName && (
|
||||
<p className="text-muted mb-0 small">
|
||||
<span className="card-subtitle">{selectedProjectName}</span>
|
||||
|
@ -19,6 +19,10 @@ const NoteCardDirectory = ({
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [isActivProcess, setActiveProcessing] = useState(false);
|
||||
|
||||
// State to manage hover status
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
const handleUpdateNote = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@ -127,6 +131,8 @@ const NoteCardDirectory = ({
|
||||
background: `${noteItem.isActive ? "" : "#f8f6f6"}`,
|
||||
}}
|
||||
key={noteItem.id}
|
||||
onMouseEnter={() => setIsHovered(true)} // Set hover state to true on mouse enter
|
||||
onMouseLeave={() => setIsHovered(false)} // Set hover state to false on mouse leave
|
||||
>
|
||||
<div className="d-flex justify-content-between align-items-center mb-1">
|
||||
<div className="d-flex align-items-center">
|
||||
@ -149,11 +155,15 @@ const NoteCardDirectory = ({
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className={`absolute top-4 right-4 flex space-x-2 transition-opacity duration-300 ${
|
||||
isHovered ? "opacity-100" : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
{noteItem.isActive ? (
|
||||
<>
|
||||
<i
|
||||
className="bx bxs-edit bx-sm me-1 text-primary cursor-pointer"
|
||||
className="p-2 bg-blue-500 text-white rounded-full shadow-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-75"
|
||||
onClick={() => setEditing(true)}
|
||||
></i>
|
||||
|
||||
|
@ -1,23 +1,96 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import moment from "moment";
|
||||
import { getProjectStatusName } from "../../utils/projectStatus";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { MANAGE_PROJECT } from "../../utils/constants";
|
||||
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
||||
import ManageProjectInfo from "./ManageProjectInfo";
|
||||
|
||||
const AboutProject = ({ data }) => {
|
||||
const [CurrentProject, setCurrentProject] = useState(data);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentProject(data);
|
||||
}, [data]);
|
||||
|
||||
const manageProject = useHasUserPermission(MANAGE_PROJECT);
|
||||
|
||||
const handleShow = () => setShowModal(true);
|
||||
const handleClose = () => setShowModal(false);
|
||||
const handleFormSubmit = (updatedProject, setLoading) => {
|
||||
if (CurrentProject?.id) {
|
||||
ProjectRepository.updateProject(CurrentProject.id, updatedProject)
|
||||
.then((response) => {
|
||||
const updatedProjectData = {
|
||||
...CurrentProject,
|
||||
...response.data,
|
||||
building: CurrentProject.building,
|
||||
};
|
||||
setCurrentProject(updatedProject);
|
||||
|
||||
cacheData(`projectinfo-${CurrentProject.id}`, updatedProjectData);
|
||||
const projects_list = getCachedData("projectslist");
|
||||
if (projects_list) {
|
||||
const updatedProjectsList = projects_list.map((project) =>
|
||||
project.id === CurrentProject.id
|
||||
? {
|
||||
...project,
|
||||
...response.data,
|
||||
// tenant:project.tenant
|
||||
}
|
||||
: project
|
||||
);
|
||||
|
||||
cacheData("projectslist", updatedProjectsList);
|
||||
}
|
||||
|
||||
showToast("Project updated successfully.", "success");
|
||||
setLoading(false);
|
||||
setShowModal(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
showToast(error.message, "error");
|
||||
});
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`modal fade ${showModal ? "show" : ""}`}
|
||||
tabIndex="-1"
|
||||
role="dialog"
|
||||
style={{ display: showModal ? "block" : "none" }}
|
||||
aria-hidden={!showModal}
|
||||
>
|
||||
<ManageProjectInfo
|
||||
project={CurrentProject}
|
||||
handleSubmitForm={handleFormSubmit}
|
||||
onClose={handleClose}
|
||||
></ManageProjectInfo>
|
||||
</div>
|
||||
{data && (
|
||||
<div className="card mb-6">
|
||||
<div class="card-header text-start">
|
||||
<h6 class="card-action-title mb-0">
|
||||
{" "}
|
||||
<i className="fa fa-building rounded-circle text-primary"></i>
|
||||
<span className="ms-2">Project Profile</span>
|
||||
</h6>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<small className="card-text text-uppercase text-muted small">
|
||||
Profile
|
||||
</small>
|
||||
<ul className="list-unstyled my-3">
|
||||
<li className="d-flex align-items-center mb-4">
|
||||
<ul className="list-unstyled my-3 ps-2">
|
||||
<li className="d-flex align-items-center mb-3">
|
||||
<i className="bx bx-cog"></i>
|
||||
<span className="fw-medium mx-2">Name:</span>{" "}
|
||||
<span>{data.name}</span>
|
||||
</li>
|
||||
<li className="d-flex align-items-center mb-3">
|
||||
<i className="bx bx-fingerprint"></i>
|
||||
<span className="fw-medium mx-2">Nick Name:</span>{" "}
|
||||
<span> {data.shortName} </span>
|
||||
</li>
|
||||
<li className="d-flex align-items-center mb-3">
|
||||
<i className="bx bx-check"></i>
|
||||
<span className="fw-medium mx-2">Start Date:</span>{" "}
|
||||
<span>
|
||||
@ -26,7 +99,7 @@ const AboutProject = ({ data }) => {
|
||||
: "N/A"}
|
||||
</span>
|
||||
</li>
|
||||
<li className="d-flex align-items-center mb-4">
|
||||
<li className="d-flex align-items-center mb-3">
|
||||
<i className="bx bx-stop-circle"></i>{" "}
|
||||
<span className="fw-medium mx-2">End Date:</span>{" "}
|
||||
<span>
|
||||
@ -35,17 +108,17 @@ const AboutProject = ({ data }) => {
|
||||
: "N/A"}
|
||||
</span>
|
||||
</li>
|
||||
<li className="d-flex align-items-center mb-2">
|
||||
<li className="d-flex align-items-center mb-3">
|
||||
<i className="bx bx-trophy"></i>
|
||||
<span className="fw-medium mx-2">Status:</span>{" "}
|
||||
<span>{getProjectStatusName(data.projectStatusId)}</span>
|
||||
</li>
|
||||
<li className="d-flex align-items-center mb-4">
|
||||
<li className="d-flex align-items-center mb-3">
|
||||
<i className="bx bx-user"></i>
|
||||
<span className="fw-medium mx-2">Contact:</span>{" "}
|
||||
<span>{data.contactPerson}</span>
|
||||
</li>
|
||||
<li className="d-flex flex-column align-items-start mb-4">
|
||||
<li className="d-flex flex-column align-items-start mb-3">
|
||||
<div className="d-flex align-items-center">
|
||||
<i className="bx bx-flag"></i>
|
||||
<span className="fw-medium mx-2">Address:</span>
|
||||
@ -57,6 +130,22 @@ const AboutProject = ({ data }) => {
|
||||
<div className="ms-4 text-start">{data.projectAddress}</div>
|
||||
)}
|
||||
</li>
|
||||
|
||||
<li className="d-flex justify-content-center mb-3">
|
||||
{manageProject && (
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-sm btn-primary ${
|
||||
!manageProject && "d-none"
|
||||
}`}
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#edit-project-modal"
|
||||
onClick={handleShow}
|
||||
>
|
||||
Modify Details
|
||||
</button>
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,33 +1,124 @@
|
||||
import React from "react";
|
||||
import {useEmployeesByProjectAllocated, useProjects} from "../../hooks/useProjects";
|
||||
import {
|
||||
useEmployeesByProjectAllocated,
|
||||
useProjects,
|
||||
} from "../../hooks/useProjects";
|
||||
|
||||
const ProjectOverview = ({ project }) => {
|
||||
const { projects } = useProjects();
|
||||
const getProgress = (planned, completed) => {
|
||||
return (completed * 100) / planned + "%";
|
||||
};
|
||||
const getProgressInNumber = (planned, completed) => {
|
||||
var number = (completed * 100) / planned;
|
||||
return FormattedNumber(number);
|
||||
};
|
||||
|
||||
const project_detail = projects.find((pro) => pro.id == project);
|
||||
|
||||
// Utility function to check if a number has a decimal part
|
||||
const hasDecimal = (num) => {
|
||||
// Convert to string and check for a decimal point
|
||||
// Or, check if the number is not equal to its integer part
|
||||
return num % 1 !== 0;
|
||||
};
|
||||
|
||||
// FormattedNumber component to display numbers with conditional decimal places
|
||||
function FormattedNumber(value, locale = "en-US") {
|
||||
// Ensure the value is a number
|
||||
const numericValue = parseFloat(value);
|
||||
|
||||
// Handle non-numeric values gracefully
|
||||
if (isNaN(numericValue)) {
|
||||
return <span>Invalid Number</span>;
|
||||
}
|
||||
|
||||
let options = {};
|
||||
|
||||
// Determine formatting options based on whether the number has a decimal part
|
||||
if (hasDecimal(numericValue)) {
|
||||
// If it has a decimal, format to exactly two decimal places
|
||||
options = {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
};
|
||||
} else {
|
||||
// If it's a whole number, format to zero decimal places
|
||||
options = {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Use Intl.NumberFormat for robust and locale-aware formatting
|
||||
const formattedString = new Intl.NumberFormat(locale, options).format(
|
||||
numericValue
|
||||
);
|
||||
|
||||
return formattedString;
|
||||
}
|
||||
|
||||
const ProjectOverview = ({project}) =>
|
||||
{
|
||||
const {projects} = useProjects()
|
||||
const project_detail = projects.find( ( pro ) => pro.id == project )
|
||||
return (
|
||||
<div className="card mb-6">
|
||||
<div className="card-header text-start">
|
||||
<h6 className="card-action-title mb-0">
|
||||
{" "}
|
||||
<i className="fa fa-line-chart rounded-circle text-primary"></i>
|
||||
<span className="ms-2">Project Statistics</span>
|
||||
</h6>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<small className="card-text text-uppercase text-muted small">
|
||||
Overview
|
||||
</small>
|
||||
<ul className="list-unstyled mb-0 mt-3 pt-1">
|
||||
<li className="d-flex align-items-center mb-4">
|
||||
<li className="d-flex align-items-center mb-3">
|
||||
<i className="bx bx-check"></i>
|
||||
<span className="fw-medium mx-2">Task Planned:</span>{" "}
|
||||
<span>{project_detail?.plannedWork
|
||||
}</span>
|
||||
<span>{project_detail?.plannedWork}</span>
|
||||
</li>
|
||||
<li className="d-flex align-items-center mb-4">
|
||||
<li className="d-flex align-items-center mb-3">
|
||||
<i className="bx bx-star"></i>
|
||||
<span className="fw-medium mx-2">Task Completed:</span>{" "}
|
||||
<span>{project_detail?.completedWork }</span>
|
||||
<span>{project_detail?.completedWork}</span>
|
||||
</li>
|
||||
<li className="d-flex align-items-center">
|
||||
<li className="d-flex align-items-center mb-3">
|
||||
<i className="bx bx-user"></i>
|
||||
<span className="fw-medium mx-2">Current team Size:</span>{" "}
|
||||
<span>{project_detail?.teamSize}</span>
|
||||
</li>
|
||||
<li className=" mb-3">
|
||||
{project_detail && (
|
||||
<>
|
||||
<div className="d-flex text-end mb-2 mt-5">
|
||||
<small className="text-body text-muted ">
|
||||
{Math.floor(
|
||||
getProgressInNumber(
|
||||
project_detail.plannedWork,
|
||||
project_detail.completedWork
|
||||
)
|
||||
) || 0}{" "}
|
||||
% Completed
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="progress mb-4 rounded"
|
||||
style={{ height: "8px" }}
|
||||
>
|
||||
<div
|
||||
className="progress-bar rounded"
|
||||
role="progressbar"
|
||||
style={{
|
||||
width: getProgress(
|
||||
project_detail.plannedWork,
|
||||
project_detail.completedWork
|
||||
),
|
||||
}}
|
||||
aria-valuenow={project_detail.completedWork}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax={project_detail.plannedWork}
|
||||
></div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -11,7 +11,11 @@ import ProjectInfra from "../../components/Project/ProjectInfra";
|
||||
import Loader from "../../components/common/Loader";
|
||||
import WorkPlan from "../../components/Project/WorkPlan";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import { cacheData, clearCacheKey, getCachedData } from "../../slices/apiDataManager";
|
||||
import {
|
||||
cacheData,
|
||||
clearCacheKey,
|
||||
getCachedData,
|
||||
} from "../../slices/apiDataManager";
|
||||
import ProjectRepository from "../../repositories/ProjectRepository";
|
||||
import { ActivityeRepository } from "../../repositories/MastersRepository";
|
||||
import "./ProjectDetails.css";
|
||||
@ -24,6 +28,7 @@ import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||
import Directory from "../Directory/Directory";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import ProjectProgressChart from "../../components/Dashboard/ProjectProgressChart";
|
||||
|
||||
const ProjectDetails = () => {
|
||||
let { projectId } = useParams();
|
||||
@ -75,18 +80,31 @@ const ProjectDetails = () => {
|
||||
switch (activePill) {
|
||||
case "profile": {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-xl-4 col-lg-5 col-md-5 mt-5">
|
||||
{/* About User */}
|
||||
<AboutProject data={projectDetails}></AboutProject>
|
||||
{/* About User */}
|
||||
<>
|
||||
<div className="row">
|
||||
<div className="col-lg-4 col-md-5 mt-5">
|
||||
{/* About User */}
|
||||
<AboutProject data={projectDetails}></AboutProject>
|
||||
<ProjectOverview project={projectId} />
|
||||
{/* About User */}
|
||||
</div>
|
||||
<div className="col-lg-8 col-md-5 mt-5">
|
||||
{/* Profile Overview */}
|
||||
<ProjectProgressChart
|
||||
ShowAllProject="false"
|
||||
DefaultRange="1M"
|
||||
/>
|
||||
{/* Profile Overview */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-xl-4 col-lg-5 col-md-5 mt-5">
|
||||
{/* Profile Overview */}
|
||||
<ProjectOverview project={projectId} />
|
||||
{/* Profile Overview */}
|
||||
<div className="row">
|
||||
<div className="col-xl-4 col-lg-5 col-md-5 ">
|
||||
{/* Profile Overview */}
|
||||
{/* <ProjectOverview project={projectId} /> */}
|
||||
{/* Profile Overview */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "teams": {
|
||||
@ -141,22 +159,22 @@ const ProjectDetails = () => {
|
||||
const handler = useCallback(
|
||||
(msg) => {
|
||||
if (msg.keyword === "Update_Project" && project.id === msg.response.id) {
|
||||
clearCacheKey("projectInfo")
|
||||
clearCacheKey("projectInfo");
|
||||
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);
|
||||
});
|
||||
.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);
|
||||
});
|
||||
}
|
||||
},
|
||||
[project,handleDataChange]
|
||||
[project, handleDataChange]
|
||||
);
|
||||
useEffect(() => {
|
||||
eventBus.on("project", handler);
|
||||
@ -171,15 +189,15 @@ const ProjectDetails = () => {
|
||||
data={[
|
||||
{ label: "Home", link: "/dashboard" },
|
||||
{ label: "Projects", link: "/projects" },
|
||||
{ label: "Details", link: null },
|
||||
{ label: `${project?.name ? project?.name : ""}`, link: null },
|
||||
]}
|
||||
></Breadcrumb>
|
||||
|
||||
<div className="row">
|
||||
{projectLoading && <p>Loading....</p>}
|
||||
{!projectLoading && project && (
|
||||
{/* {!projectLoading && project && (
|
||||
<ProjectBanner project_data={project}></ProjectBanner>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
<ProjectNav
|
||||
onPillClick={handlePillClick}
|
||||
|
Loading…
x
Reference in New Issue
Block a user