marco.pms.web/src/pages/project/ProjectPage.jsx

255 lines
8.2 KiB
JavaScript

import React, { createContext, useContext, useEffect, useState } from "react";
import Breadcrumb from "../../components/common/Breadcrumb";
import {
ITEMS_PER_PAGE,
MANAGE_PROJECT,
PROJECT_STATUS,
} from "../../utils/constants";
import ProjectListView from "../../components/Project/ProjectListView";
import GlobalModel from "../../components/common/GlobalModel";
import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
import ProjectCardView from "../../components/Project/ProjectCardView";
import usePagination from "../../hooks/usePagination";
import { useProjects } from "../../hooks/useProjects";
import Loader from "../../components/common/Loader";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
const ProjectContext = createContext();
export const useProjectContext = () => {
const context = useContext(ProjectContext);
if (!context) {
throw new Error("useProjectContext must be used within an ProjectProvider");
}
return context;
};
const ProjectPage = () => {
const [manageProject, setMangeProject] = useState({
isOpen: false,
Project: null,
});
const [projectList, setProjectList] = useState([]);
const [listView, setListView] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
const HasManageProject = useHasUserPermission(MANAGE_PROJECT);
const [selectedStatuses, setSelectedStatuses] = useState(
PROJECT_STATUS.map((s) => s.id)
);
const { data, isLoading, isError, error } = useProjects();
const contextDispatcher = {
setMangeProject,
};
const filteredProjects = projectList.filter((project) => {
const matchesStatus = selectedStatuses.includes(project.projectStatusId);
const matchesSearch = project.name
.toLowerCase()
.includes(searchTerm.toLowerCase());
return matchesStatus && matchesSearch;
});
const totalPages = Math.ceil(filteredProjects.length / ITEMS_PER_PAGE);
const { currentItems, currentPage, paginate, setCurrentPage } = usePagination(
filteredProjects,
ITEMS_PER_PAGE
);
const handleStatusChange = (statusId) => {
setCurrentPage(1);
setSelectedStatuses((prev) =>
prev.includes(statusId)
? prev.filter((id) => id !== statusId)
: [...prev, statusId]
);
};
const sortingProject = (projects) => {
if (!isLoading && Array.isArray(projects)) {
const grouped = {};
projects.forEach((project) => {
const statusId = project.projectStatusId;
if (!grouped[statusId]) grouped[statusId] = [];
grouped[statusId].push(project);
});
const sortedGrouped = selectedStatuses
.filter((statusId) => grouped[statusId])
.flatMap((statusId) =>
grouped[statusId].sort((a, b) =>
a.name.toLowerCase().localeCompare(b.name.toLowerCase())
)
);
setProjectList((prev) => {
const isSame = JSON.stringify(prev) === JSON.stringify(sortedGrouped);
return isSame ? prev : sortedGrouped;
});
}
};
useEffect(() => {
if (!isLoading && data) {
sortingProject(data);
}
}, [data, isLoading, selectedStatuses]);
if (isLoading)
return (
<div className="page-min-h">
<Loader />
</div>
);
if (isError)
return (
<div className="page-min-h d-flex justify-content-center align-items-center">
<p>{error.message}</p>
</div>
);
return (
<ProjectContext.Provider value={contextDispatcher}>
<div className="container-fluid">
<Breadcrumb
data={[
{ label: "Home", link: "/dashboard" },
{ label: "Projects", link: null },
]}
/>
<div className="card cursor-pointer mb-5">
<div className="card-body p-2 pb-1">
<div className="d-flex flex-wrap justify-content-between align-items-start">
<div className="d-flex flex-wrap align-items-start">
<div className="flex-grow-1 me-2 mb-2">
<input
type="search"
className="form-control form-control-sm"
placeholder="Search projects..."
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
setCurrentPage(1);
}}
/>
</div>
<div className="d-flex gap-2 mb-2">
<button
type="button"
className={`btn btn-sm p-1 ${
!listView ? "btn-primary" : "btn-outline-primary"
}`}
onClick={() => setListView(false)}
data-bs-toggle="tooltip"
data-bs-custom-class="tooltip"
title="Card View"
>
<i className="bx bx-grid-alt fs-5"></i>
</button>
<button
type="button"
className={`btn btn-sm p-1 ${
listView ? "btn-primary" : "btn-outline-primary"
}`}
onClick={() => setListView(true)}
data-bs-toggle="tooltip"
data-bs-custom-class="tooltip"
title="List View"
>
<i className="bx bx-list-ul fs-5"></i>
</button>
</div>
<div className="dropdown mt-1">
<a
className="dropdown-toggle hide-arrow cursor-pointer p-1 mt-3 "
data-bs-toggle="dropdown"
aria-expanded="false"
data-bs-custom-class="tooltip"
title="Filter"
>
<i className="bx bx-slider-alt ms-1"></i>
</a>
<ul className="dropdown-menu p-2 text-capitalize">
{PROJECT_STATUS.map(({ id, label }) => (
<li key={id}>
<div className="form-check">
<input
className="form-check-input "
type="checkbox"
checked={selectedStatuses.includes(id)}
onChange={() => handleStatusChange(id)}
/>
<label className="form-check-label">{label}</label>
</div>
</li>
))}
</ul>
</div>
</div>
<div>
{/* {HasManageProject && ( */}
<button
className="btn btn-sm btn-primary"
type="button"
onClick={() =>
setMangeProject({ isOpen: true, Project: null })
}
>
<i className="bx bx-plus-circle me-2"></i>
<span className="d-none d-md-inline-block">
Add New Project
</span>
</button>
{/* )} */}
</div>
</div>
</div>
</div>
{/* Project Render here */}
{listView ? (
<ProjectListView
currentItems={currentItems}
selectedStatuses={selectedStatuses}
handleStatusChange={handleStatusChange}
setCurrentPage={setCurrentPage}
totalPages={totalPages}
isLoading={isLoading}
/>
) : (
<ProjectCardView
currentItems={currentItems}
setCurrentPage={setCurrentPage}
totalPages={totalPages}
/>
)}
{/* ------------------ */}
{/* Project Manage UPdate or create */}
{manageProject.isOpen && (
<GlobalModel
isOpen={manageProject.isOpen}
closeModal={() => setMangeProject({ isOpen: false, Project: null })}
>
<ManageProjectInfo
project={manageProject.Project}
onClose={() => setMangeProject({ isOpen: false, Project: null })}
/>
</GlobalModel>
)}
</div>
</ProjectContext.Provider>
);
};
export default ProjectPage;