diff --git a/src/components/Project/AboutProject.jsx b/src/components/Project/AboutProject.jsx index afd54971..438de6be 100644 --- a/src/components/Project/AboutProject.jsx +++ b/src/components/Project/AboutProject.jsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import moment from "moment"; -import { ProjectStatus } from "../../utils/projectStatus"; +import { getProjectStatusName } from "../../utils/projectStatus"; const AboutProject = ({ data }) => { const [CurrentProject, setCurrentProject] = useState(data); @@ -34,7 +34,7 @@ const AboutProject = ({ data }) => {
  • Status:{" "} - {ProjectStatus(data.projectStatusId)} + {getProjectStatusName(data.projectStatusId)}
  • diff --git a/src/components/Project/ProjectCard.jsx b/src/components/Project/ProjectCard.jsx index bd0a0fca..a6a44d32 100644 --- a/src/components/Project/ProjectCard.jsx +++ b/src/components/Project/ProjectCard.jsx @@ -8,278 +8,270 @@ import ProjectRepository from "../../repositories/ProjectRepository"; import { cacheData, getCachedData } from "../../slices/apiDataManager"; import showToast from "../../services/toastService"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; -import { MANAGE_PROJECT } from "../../utils/constants"; - - +import {MANAGE_PROJECT} from "../../utils/constants"; +import { getProjectStatusColor,getProjectStatusName } from "../../utils/projectStatus"; const ProjectCard = ({ projectData }) => { - const [projectInfo, setProjectInfo] = useState(projectData); - const [projectDetails, setProjectDetails] = useState(null); - const [showModal, setShowModal] = useState(false); - const navigate = useNavigate(); - const ManageProject = useHasUserPermission(MANAGE_PROJECT); + const [projectInfo, setProjectInfo] = useState(projectData); + const [projectDetails, setProjectDetails] = useState(null); + const [showModal, setShowModal] = useState(false); + const navigate = useNavigate(); + const ManageProject = useHasUserPermission(MANAGE_PROJECT); - const handleShow = async () => { - try { - const response = await ProjectRepository.getProjectByprojectId(projectInfo.id); - setProjectDetails(response.data); - setShowModal(true); - } catch (error) { - showToast("Failed to load project details", "error"); - } - }; + const handleShow = async () => { + try { + const response = await ProjectRepository.getProjectByprojectId( + projectInfo.id + ); + setProjectDetails(response.data); + setShowModal(true); + } catch (error) { + showToast("Failed to load project details", "error"); + } + }; - const getProgress = (planned, completed) => { - return (completed * 100) / planned + "%"; - }; - const getProgressInNumber = (planned, completed) => { - return (completed * 100) / planned; - }; + const getProgress = (planned, completed) => { + return (completed * 100) / planned + "%"; + }; + const getProgressInNumber = (planned, completed) => { + return (completed * 100) / planned; + }; - const handleClose = () => setShowModal(false); - - const getProjectStatusName = (statusId) => { - switch (statusId) { - case 1: - return "Active"; - case 2: - return "On Hold"; - // case 3: - // return "Suspended"; - case 3: - return "Inactive"; - case 4: - return "Completed"; - } - }; - - const getProjectStatusColor = (statusId) => { - switch (statusId) { - case 1: - return "bg-label-success"; - case 2: - return "bg-label-warning"; - case 3: - return "bg-label-info"; - case 4: - return "bg-label-secondary"; - case 5: - return "bg-label-dark"; - } - }; - - const handleViewProject = () => { - navigate(`/projects/${projectData.id}`); - }; + const handleClose = () => setShowModal(false); - const handleFormSubmit = (updatedProject) => { - if (projectInfo?.id) { - ProjectRepository.updateProject(projectInfo.id, updatedProject) - .then((response) => { - const updatedProjectData = { - ...projectInfo, - ...response.data, - building: projectDetails?.building, - }; + const handleViewProject = () => { + navigate(`/projects/${projectData.id}`); + }; - setProjectInfo(updatedProject); + const handleFormSubmit = (updatedProject) => { + if (projectInfo?.id) { + ProjectRepository.updateProject(projectInfo.id, updatedProject) + .then((response) => { + const updatedProjectData = { + ...projectInfo, + ...response.data, + building: projectDetails?.building, + }; - if (getCachedData(`projectinfo-${projectInfo.id}`)) { - cacheData(`projectinfo-${projectInfo.id}`, updatedProjectData); - } + setProjectInfo(updatedProject); - const projects_list = getCachedData("projectslist"); - if (projects_list) { - const updatedProjectsList = projects_list.map((project) => - project.id === projectInfo.id - ? { ...project, ...response.data, tenant: project.tenant } - : project - ); - cacheData("projectslist", updatedProjectsList); - } + if (getCachedData(`projectinfo-${projectInfo.id}`)) { + cacheData(`projectinfo-${projectInfo.id}`, updatedProjectData); + } - showToast("Project updated successfully.", "success"); - setShowModal(false); - }) - .catch((error) => { - showToast(error.message, "error"); - }); - } - }; + const projects_list = getCachedData("projectslist"); + if (projects_list) { + const updatedProjectsList = projects_list.map((project) => + project.id === projectInfo.id + ? { ...project, ...response.data, tenant: project.tenant } + : project + ); + cacheData("projectslist", updatedProjectsList); + } - return ( - <> - {showModal && projectDetails && ( -
    - + showToast("Project updated successfully.", "success"); + setShowModal(false); + }) + .catch((error) => { + showToast(error.message, "error"); + }); + } + }; + + return ( + <> + {showModal && projectDetails && ( +
    + +
    + )} + +
    +
    +
    +
    +
    +
    +
    - )} - -
    -
    -
    -
    -
    -
    - -
    -
    -
    - - {projectInfo.name} - -
    -
    - Client: - {projectInfo.contactPerson} -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -

    - Start Date: - {projectInfo.startDate - ? moment(projectInfo.startDate).format("DD-MMM-YYYY") - : "NA"} -

    -

    - Deadline: - - {projectInfo.endDate - ? moment(projectInfo.endDate).format("DD-MMM-YYYY") - : "NA"} -

    -

    {projectInfo.projectAddress}

    - -
    - -
    -
    -
    -
    -

    - - {getProjectStatusName(projectInfo.projectStatusId)} - -

    {" "} - {getDateDifferenceInDays(projectInfo.endDate, Date()) >= 0 && - ( - {projectInfo.endDate && - getDateDifferenceInDays(projectInfo.endDate, Date())}{" "} - Days left - ) } - {getDateDifferenceInDays(projectInfo.endDate, Date()) < 0 && - ( - {projectInfo.endDate && - getDateDifferenceInDays(projectInfo.endDate, Date())}{" "} - Days overdue - )} - -
    -
    - Task: {projectInfo.completedWork} / {projectInfo.plannedWork} - {Math.floor(getProgressInNumber(projectInfo.plannedWork, projectInfo.completedWork)) || 0} % Completed -
    -
    -
    -
    - -
    +
    +
    + + {projectInfo.name} + +
    +
    + Client: + {projectInfo.contactPerson} +
    +
    +
    +
    + + +
    +
    - - ); +
    +
    +
    +
    +

    + Start Date: + {projectInfo.startDate + ? moment(projectInfo.startDate).format("DD-MMM-YYYY") + : "NA"} +

    +

    + Deadline: + + {projectInfo.endDate + ? moment(projectInfo.endDate).format("DD-MMM-YYYY") + : "NA"} +

    +

    {projectInfo.projectAddress}

    +
    +
    +
    +
    +
    +

    + + {getProjectStatusName(projectInfo.projectStatusId)} + +

    {" "} + {getDateDifferenceInDays(projectInfo.endDate, Date()) >= 0 && ( + + {projectInfo.endDate && + getDateDifferenceInDays(projectInfo.endDate, Date())}{" "} + Days left + + )} + {getDateDifferenceInDays(projectInfo.endDate, Date()) < 0 && ( + + {projectInfo.endDate && + getDateDifferenceInDays(projectInfo.endDate, Date())}{" "} + Days overdue + + )} +
    +
    + + Task: {projectInfo.completedWork} / {projectInfo.plannedWork} + + + {Math.floor( + getProgressInNumber( + projectInfo.plannedWork, + projectInfo.completedWork + ) + ) || 0}{" "} + % Completed + +
    +
    +
    +
    + +
    +
    +
    + + ); }; export default ProjectCard; diff --git a/src/components/common/ProgressBar.jsx b/src/components/common/ProgressBar.jsx index 1a29a343..947c6a00 100644 --- a/src/components/common/ProgressBar.jsx +++ b/src/components/common/ProgressBar.jsx @@ -1,30 +1,37 @@ import React from "react"; -const ProgressBar = ( {completeValue, totalValue} ) => -{ - - - const getProgress = (complete, total) => { - return (total * 100) / complete + "%"; +const ProgressBar = ({ + plannedWork = 100, + completedWork = 0, + height = "8px", + className = "mb-4", + rounded = true, +}) => { + const getProgress = (planned, completed) => { + if (!planned || planned === 0) return "0%"; + return `${Math.min((completed / planned) * 100, 100).toFixed(2)}%`; }; - return ( -
    -
    + + const progressStyle = { + width: getProgress(plannedWork, completedWork), + }; + + return ( +
    -
    - ); }; export default ProgressBar; + diff --git a/src/pages/employee/EmployeeList.jsx b/src/pages/employee/EmployeeList.jsx index 2e0f3bb2..d28a00c2 100644 --- a/src/pages/employee/EmployeeList.jsx +++ b/src/pages/employee/EmployeeList.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect,useRef } from "react"; +import React, { useState, useEffect, useRef } from "react"; import moment from "moment"; import { Link, NavLink, useNavigate } from "react-router-dom"; import Avatar from "../../components/common/Avatar"; @@ -11,7 +11,12 @@ import { hasUserPermission } from "../../utils/authUtils"; import { MANAGE_EMPLOYEES } from "../../utils/constants"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import SuspendEmp from "../../components/Employee/SuspendEmp"; -import {exportToCSV,exportToExcel,printTable,exportToPDF} from "../../utils/tableExportUtils"; +import { + exportToCSV, + exportToExcel, + printTable, + exportToPDF, +} from "../../utils/tableExportUtils"; const EmployeeList = () => { const { profile: loginUser } = useProfile(); @@ -105,7 +110,7 @@ const EmployeeList = () => { const tableRef = useRef(null); const handleExport = (type) => { if (!currentItems || currentItems.length === 0) return; - + switch (type) { case "csv": exportToCSV(currentItems, "employees"); @@ -114,17 +119,16 @@ const EmployeeList = () => { exportToExcel(currentItems, "employees"); break; case "pdf": - exportToPDF(currentItems, "employees"); // Pass the employeeList directly + exportToPDF(currentItems, "employees"); // Pass the employeeList directly break; case "print": - printTable(tableRef.current); + printTable(tableRef.current); break; default: break; } }; - - + return ( <> {isCreateModalOpen && ( @@ -163,32 +167,36 @@ const EmployeeList = () => { className="dataTables_length text-start" id="DataTables_Table_0_length" > - +
    @@ -223,22 +231,39 @@ const EmployeeList = () => {
    diff --git a/src/pages/project/ProjectList.jsx b/src/pages/project/ProjectList.jsx index 1f5fd1e5..def125ce 100644 --- a/src/pages/project/ProjectList.jsx +++ b/src/pages/project/ProjectList.jsx @@ -6,86 +6,74 @@ import ProjectRepository from "../../repositories/ProjectRepository"; import { useProjects } from "../../hooks/useProjects"; import { useDispatch } from "react-redux"; import showToast from "../../services/toastService"; -import { getCachedData, cacheData} from "../../slices/apiDataManager"; -import {useHasUserPermission} from "../../hooks/useHasUserPermission" +import { getCachedData, cacheData } from "../../slices/apiDataManager"; +import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useProfile } from "../../hooks/useProfile"; -import {MANAGE_PROJECT} from "../../utils/constants"; +import { MANAGE_PROJECT } from "../../utils/constants"; +import ProjectListView from "./ProjectListView"; -const ProjectList = () => -{ - - - const {profile: loginUser} = useProfile(); +const ProjectList = () => { + const { profile: loginUser } = useProfile(); + const [listView, setListView] = useState(true); const [showModal, setShowModal] = useState(false); - const {projects, loading, error, refetch} = useProjects(); - const [refresh, setRefresh] = useState(false); - const [ projectList, setProjectList ] = useState( [] ); - const HasManageProjectPermission = useHasUserPermission( MANAGE_PROJECT ) - const[HasManageProject,setHasManageProject] = useState(HasManageProjectPermission) + const { projects, loading, error, refetch } = useProjects(); + const [projectList, setProjectList] = useState([]); + const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT); + const [HasManageProject, setHasManageProject] = useState( + HasManageProjectPermission + ); const dispatch = useDispatch(); + const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage] = useState(6); + const [searchTerm, setSearchTerm] = useState(""); + const [selectedStatuses, setSelectedStatuses] = useState([1, 2, 3, 4]); - - const handleShow = () => setShowModal(true); - const handleClose = () => setShowModal( false ); - - + const handleShow = () => setShowModal(true); + const handleClose = () => setShowModal(false); useEffect(() => { if (!loading && Array.isArray(projects)) { - // Step 1: Group projects by statusId const grouped = {}; - projects.forEach((project) => { const statusId = project.projectStatusId; - if (!grouped[statusId]) { - grouped[statusId] = []; - } + if (!grouped[statusId]) grouped[statusId] = []; grouped[statusId].push(project); }); - - // Step 2: Sort each group by name + const sortedGrouped = Object.keys(grouped) - .sort() // sort group keys (status IDs) + .sort() .flatMap((statusId) => grouped[statusId].sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()) ) ); - - setProjectList(sortedGrouped); // final sorted flat list + + setProjectList(sortedGrouped); } - - }, [ projects, loginUser?.projects, loading ] ); + }, [projects, loginUser?.projects, loading]); useEffect(() => { if (loginUser) { - setHasManageProject(HasManageProjectPermission); + setHasManageProject(HasManageProjectPermission); } else { - setHasManageProject(false); } + setHasManageProject(false); + } }, [loginUser, HasManageProjectPermission]); - - const handleSubmitForm = (newProject) => { ProjectRepository.manageProject(newProject) - .then( ( response ) => - { - - const cachedProjects_list = getCachedData( "projectslist" ) || []; - - const updated_Projects_list = [ ...cachedProjects_list, response.data ]; - - cacheData("projectslist", updated_Projects_list); - setProjectList((prevProjectList) => [...prevProjectList, response.data]); - + .then((response) => { + const cachedProjects = getCachedData("projectslist") || []; + const updatedProjects = [...cachedProjects, response.data]; + cacheData("projectslist", updatedProjects); + setProjectList((prev) => [...prev, response.data]); showToast("Project Created successfully.", "success"); - setShowModal(false) + setShowModal(false); }) .catch((error) => { - closeModal(); showToast(error.message, "error"); + setShowModal(false); }); }; @@ -93,58 +81,122 @@ const ProjectList = () => if (!projects || projects.length === 0) { refetch(); } - setRefresh((prev) => !prev); }; + const handleStatusChange = (statusId) => { + setCurrentPage(1); + setSelectedStatuses((prev) => + prev.includes(statusId) + ? prev.filter((id) => id !== statusId) + : [...prev, statusId] + ); + }; + + const handleStatusFilterFromChild = (statusesFromChild) => { + setSelectedStatuses(statusesFromChild); + }; + + const filteredProjects = projectList.filter((project) => { + const matchesStatus = selectedStatuses.includes(project.projectStatusId); + const matchesSearch = project.name + .toLowerCase() + .includes(searchTerm.toLowerCase()); + return matchesStatus && matchesSearch; + }); + const indexOfLastItem = currentPage * itemsPerPage; const indexOfFirstItem = indexOfLastItem - itemsPerPage; - const currentItems = Array.isArray(projectList) - ? projectList.slice(indexOfFirstItem, indexOfLastItem) - : []; - - const paginate = (pageNumber) => setCurrentPage(pageNumber); - const totalPages = Array.isArray(projectList) - ? Math.ceil(projectList.length / itemsPerPage) - : 0; + const currentItems = filteredProjects.slice( + indexOfFirstItem, + indexOfLastItem + ); + const totalPages = Math.ceil(filteredProjects.length / itemsPerPage); + + useEffect(() => { + const tooltipTriggerList = Array.from( + document.querySelectorAll('[data-bs-toggle="tooltip"]') + ); + tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el)); + }, []); - - return ( <>
    - -
    - + + +
    + /> -
    -
    - {" "} +
    +
    +
    + { + setSearchTerm(e.target.value); + setCurrentPage(1); + }} + /> +
    + +
    + + + +
    +
    + +
    - {((error && !loading) || !projects) && ( -

    - There was an error loading the projects. Please try again. -

    + {loading &&

    Loading...

    } + {!loading && filteredProjects.length === 0 && !listView && ( +

    No projects found.

    )} - {(!projects || projects.length === 0 || projectList.length == 0) && - !loading && - error && ( -
    - -
    - )} -
    - {loading &&

    Loading...

    } - - - {Array.isArray(currentItems) && loginUser?.projects && ( - currentItems - .filter((item) => loginUser.projects.includes(String(item.id))) - .map((item) => ( - - )) -)} + {listView ? ( +
    + + + + + + + + + + + + + + + {currentItems.length === 0 ? ( + + + + ) : ( + currentItems.map((project) => ( + + )) + )} + +
    + Project Name + Project MangerSTART DATEDEADLINETaskProgress +
    + +
      + {[ + { id: 1, label: "Active" }, + { id: 2, label: "On Hold" }, + { id: 3, label: "Inactive" }, + { id: 4, label: "Completed" }, + ].map(({ id, label }) => ( +
    • +
      + handleStatusChange(id)} + /> + +
      +
    • + ))} +
    +
    +
    + Action +
    + No projects found +
    +
    + ) : ( + currentItems.map((project) => ( + + )) + )}
    - {/* Pagination */} - {!loading && ( - - )} + + {!loading && totalPages > 1 && ( + + )}
    ); diff --git a/src/pages/project/ProjectListView.jsx b/src/pages/project/ProjectListView.jsx new file mode 100644 index 00000000..428291f6 --- /dev/null +++ b/src/pages/project/ProjectListView.jsx @@ -0,0 +1,199 @@ +import React, { useState, useEffect } from "react"; +import moment from "moment"; +import { useProjects } from "../../hooks/useProjects"; +import { + getProjectStatusName, + getProjectStatusColor, +} from "../../utils/projectStatus"; +import ProgressBar from "../../components/common/ProgressBar"; +import { useNavigate } from "react-router-dom"; +import ManageProject from "../../components/Project/ManageProject"; +import ProjectRepository from "../../repositories/ProjectRepository"; +import { MANAGE_PROJECT } from "../../utils/constants"; +import { useHasUserPermission } from "../../hooks/useHasUserPermission"; +import ManageProjectInfo from "../../components/Project/ManageProjectInfo"; +import showToast from "../../services/toastService"; +import { getCachedData, cacheData } from "../../slices/apiDataManager"; + +const ProjectListView = ({ projectData }) => { + const [projectInfo, setProjectInfo] = useState(projectData); + const [projectDetails, setProjectDetails] = useState(null); + const [showModal, setShowModal] = useState(false); + const navigate = useNavigate(); + const ManageProject = useHasUserPermission(MANAGE_PROJECT); + + const handleShow = async () => { + try { + const response = await ProjectRepository.getProjectByprojectId( + projectInfo.id + ); + setProjectDetails(response.data); + setShowModal(true); + } catch (error) { + showToast("Failed to load project details", "error"); + } + }; + + const getProgress = (planned, completed) => { + return (completed * 100) / planned + "%"; + }; + const getProgressInNumber = (planned, completed) => { + return (completed * 100) / planned; + }; + + const handleClose = () => setShowModal(false); + + + const handleViewProject = () => { + navigate(`/projects/${projectData.id}`); + }; + + const handleFormSubmit = (updatedProject) => { + if (projectInfo?.id) { + ProjectRepository.updateProject(projectInfo.id, updatedProject) + .then((response) => { + const updatedProjectData = { + ...projectInfo, + ...response.data, + building: projectDetails?.building, + }; + + setProjectInfo( updatedProjectData ); + + if (getCachedData(`projectinfo-${projectInfo.id}`)) { + cacheData(`projectinfo-${projectInfo.id}`, updatedProjectData); + } + + const projects_list = getCachedData("projectslist"); + if (projects_list) { + const updatedProjectsList = projects_list.map((project) => + project.id === projectInfo.id + ? { ...project, ...response.data, tenant: project.tenant } + : project + ); + cacheData("projectslist", updatedProjectsList); + } + showToast("Project updated successfully.", "success"); + setShowModal(false); + }) + .catch((error) => { + showToast(error.message, "error"); + }); + } + }; + return ( + <> + {showModal && projectDetails && ( +
    + +
    + )} + + + + navigate(`/projects/${projectInfo.id}`)} + > + {projectInfo.name} + + + {projectInfo.contactPerson} + + + {projectInfo.startDate + ? moment(projectInfo.startDate).format("DD-MMM-YYYY") + : "NA"} + + + + {projectInfo.endDate + ? moment(projectInfo.endDate).format("DD-MMM-YYYY") + : "NA"} + + {projectInfo.plannedWork} + + + + + +

    + + {getProjectStatusName(projectInfo.projectStatusId)} + +

    + + + +
    + + +
    + + + + ); +}; + +export default ProjectListView; diff --git a/src/utils/projectStatus.js b/src/utils/projectStatus.js index 771c6294..81ab08af 100644 --- a/src/utils/projectStatus.js +++ b/src/utils/projectStatus.js @@ -1,25 +1,32 @@ -export const ProjectStatus =(statusId)=>{ - switch (statusId) { - case 1: - return "Active" - break; - case 2: - return "On Hold" - break; - // case 3: - // return "Suspended" - // break; - case 3: - return "Inactive" - break; - case 4: - return "Completed" - break; - default: - break; - } -} +export const getProjectStatusName = (statusId) => { + switch (statusId) { + case 1: + return "Active"; + case 2: + return "On Hold"; + // case 3: + // return "Suspended"; + case 3: + return "Inactive"; + case 4: + return "Completed"; + } +}; +export const getProjectStatusColor = (statusId) => { + switch (statusId) { + case 1: + return "bg-label-success"; + case 2: + return "bg-label-warning"; + case 3: + return "bg-label-info"; + case 4: + return "bg-label-secondary"; + case 5: + return "bg-label-dark"; + } +}; // for different color for each user export function hashString(str) {