diff --git a/src/components/Documents/Documents.jsx b/src/components/Documents/Documents.jsx index 1b7f25f4..8210bf58 100644 --- a/src/components/Documents/Documents.jsx +++ b/src/components/Documents/Documents.jsx @@ -118,7 +118,7 @@ const Documents = ({ Document_Entity, Entity }) => { return (
-
+
{/* Search */}
diff --git a/src/components/Project/ManageProjectInfo.jsx b/src/components/Project/ManageProjectInfo.jsx index eaf7cacd..a9490675 100644 --- a/src/components/Project/ManageProjectInfo.jsx +++ b/src/components/Project/ManageProjectInfo.jsx @@ -1,11 +1,24 @@ import React, { useEffect, useState } from "react"; +import { projectSchema, projectDefault } from "./ProjectSchema"; import { useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; + import Label from "../common/Label"; import DatePicker from "../common/DatePicker"; +import { useCreateProject, useProjectDetails, useUpdateProject } from "../../hooks/useProjects"; -const currentDate = new Date().toLocaleDateString('en-CA'); +import { + DEFAULT_EMPTY_STATUS_ID, + ITEMS_PER_PAGE, + PROJECT_STATUS, +} from "../../utils/constants"; +import { + useOrganizationModal, + useOrganizationsList, +} from "../../hooks/useOrganization"; +import { localToUtc } from "../../utils/appUtils"; + +const currentDate = new Date().toLocaleDateString("en-CA"); const formatDate = (date) => { if (!date) { return currentDate; @@ -14,56 +27,23 @@ const formatDate = (date) => { if (isNaN(d.getTime())) { return currentDate; } - return d.toLocaleDateString('en-CA'); + return d.toLocaleDateString("en-CA"); }; -const ManageProjectInfo = ({ project, handleSubmitForm, onClose, isPending }) => { - const [CurrentProject, setCurrentProject] = useState(); +const ManageProjectInfo = ({ project, onClose }) => { const [addressLength, setAddressLength] = useState(0); const maxAddressLength = 500; + const { onOpen, startStep, flowType } = useOrganizationModal(); const ACTIVE_STATUS_ID = "b74da4c2-d07e-46f2-9919-e75e49b12731"; - const DEFAULT_EMPTY_STATUS_ID = "00000000-0000-0000-0000-000000000000"; - const projectSchema = z - .object({ - ...(project?.id ? { id: z.string().optional() } : {}), - name: z.string().min(1, { message: "Project Name is required" }), - shortName: z.string().optional(), - contactPerson: z - .string() - .min(1, { message: "Contact Person Name is required" }) - .regex(/^[A-Za-z\s]+$/, { - message: "Contact Person must contain only letters", - }), - projectAddress: z - .string() - .min(1, { message: "Address is required" }) - .max(500, "Address must not exceed 150 characters"), - startDate: z - .string() - .min(1, { message: "Start Date is required" }) - .default(currentDate), - endDate: z - .string() - .min(1, { message: "End Date is required" }) - .default(currentDate), - projectStatusId: z - .string() - .min(1, { message: "Status is required" }), - promoterId:z.string().min(1,{message:"Promoter is required"}), - pmcId:z.string().min(1,{message:"PMC is required"}) - }) - .refine( - (data) => { - const start = new Date(data.startDate); - const end = new Date(data.endDate); - return end >= start; - }, - { - path: ["endDate"], // attaches the error to the endDate field - message: "End Date must be greater than Start Date", - } - ); + const { projects_Details, loading } = useProjectDetails(project); + const { data, isLoading, isError, error } = useOrganizationsList( + ITEMS_PER_PAGE, + 1, + true + ); + const { mutate: UpdateProject, isPending } = useUpdateProject(() => {onClose?.()}); + const {mutate:CeateProject,isPending:isCreating}= useCreateProject(()=>onClose?.()) const { register, @@ -74,81 +54,67 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose, isPending }) => getValues, } = useForm({ resolver: zodResolver(projectSchema), - defaultValues: { - id: project?.id || "", - name: project?.name || "", - shortName: project?.shortName || "", - contactPerson: project?.contactPerson || "", - projectAddress: project?.projectAddress || "", - startDate: formatDate(project?.startDate) || currentDate, - endDate: formatDate(project?.endDate) || currentDate, - projectStatusId: project?.projectStatusId && project.projectStatusId !== DEFAULT_EMPTY_STATUS_ID - - ? String(project.projectStatusId) - - : ACTIVE_STATUS_ID, - promoterId:project.promoterId, - pmcId:project.pmcId - }, + defaultValues: projectDefault, mode: "onChange", }); useEffect(() => { - setCurrentProject(project); - reset( - project - ? { - id: project?.id || "", - name: project?.name || "", - shortName: project?.shortName || "", - contactPerson: project?.contactPerson || "", - projectAddress: project?.projectAddress || "", - startDate: formatDate(project?.startDate) || "", - endDate: formatDate(project?.endDate) || "", - projectStatusId: String(project?.projectStatus?.id) || "00000000-0000-0000-0000-000000000000", - } - : {} - ); - setAddressLength(project?.projectAddress?.length || 0); - }, [project, reset]); + if (project && projects_Details) + reset({ + name: projects_Details?.name || "", + shortName: projects_Details?.shortName || "", + contactPerson: projects_Details?.contactPerson || "", + projectAddress: projects_Details?.projectAddress || "", + startDate: formatDate(projects_Details?.startDate) || "", + endDate: formatDate(projects_Details?.endDate) || "", + projectStatusId: + String(projects_Details?.projectStatus?.id) || + DEFAULT_EMPTY_STATUS_IDF, + promoterId: projects_Details.promoter.id || "", + pmcId: projects_Details.pmc.id || "", + }); + setAddressLength(projects_Details?.projectAddress?.length || 0); + }, [project, projects_Details, reset]); - /** - - * Handles the form submission. - - * @param {object} updatedProject - The project data from the form. - - */ - - const onSubmitForm = (updatedProject) => { - - handleSubmitForm(updatedProject); + const onSubmitForm = (formData) => { + if (project) { + let payload = { + ...formData, + startDate: localToUtc(formData.startDate), + endDate: localToUtc(formData.endDate), + id: project, + }; + console.log(payload); + UpdateProject({ projectId: project, payload: payload }); + }else{ + let payload = { + ...formData, + startDate: localToUtc(formData.startDate), + endDate: localToUtc(formData.endDate), + }; + CeateProject(payload) + } }; const handleCancel = () => { - reset({ - id: project?.id || "", - name: project?.name || "", - shortName: project?.shortName || "", - contactPerson: project?.contactPerson || "", - projectAddress: project?.projectAddress || "", - startDate: formatDate(project?.startDate) || currentDate, - endDate: formatDate(project?.endDate) || currentDate, - projectStatusId: String(project?.projectStatus?.id || "00000000-0000-0000-0000-000000000000"), - }); + reset(projectDefault); onClose(); }; + const handleOrganizaioFinder = () => { + onClose(); + onOpen({ startStep: 2, flowType: "default" }); + }; + return ( -
-
-
- {project?.id ? "Edit Project" : "Create Project"} -
+
{project ? "Edit Project" : "Create Project"}
-
+
- {/*
-
*/}
@@ -278,4 +222,4 @@ const ProjectCard = ({ projectData, recall }) => { ); }; -export default ProjectCard; \ No newline at end of file +export default ProjectCard; diff --git a/src/components/Project/ProjectCardView.jsx b/src/components/Project/ProjectCardView.jsx new file mode 100644 index 00000000..1108120c --- /dev/null +++ b/src/components/Project/ProjectCardView.jsx @@ -0,0 +1,70 @@ +import React from 'react' +import { useProjects } from '../../hooks/useProjects' +import Loader from '../common/Loader' +import ProjectCard from './ProjectCard' + +const ProjectCardView = ({currentItems,setCurrentPage,totalPages }) => { + + + return ( + +
+ + { currentItems.length === 0 && ( +

No projects found.

+ )} + + {currentItems.map((project) => ( + + ))} + + + { totalPages > 1 && ( + + )} +
+ + ) +} + +export default ProjectCardView diff --git a/src/components/Project/ProjectListView.jsx b/src/components/Project/ProjectListView.jsx new file mode 100644 index 00000000..2e717163 --- /dev/null +++ b/src/components/Project/ProjectListView.jsx @@ -0,0 +1,280 @@ +import React, { useState } from "react"; +import { MANAGE_PROJECT, PROJECT_STATUS } from "../../utils/constants"; +import { useProjects } from "../../hooks/useProjects"; +import { formatNumber, formatUTCToLocalTime } from "../../utils/dateUtils"; +import ProgressBar from "../common/ProgressBar"; +import { + getProjectStatusColor, + getProjectStatusName, +} from "../../utils/projectStatus"; +import { useDispatch } from "react-redux"; +import { setProjectId } from "../../slices/localVariablesSlice"; +import { useNavigate } from "react-router-dom"; +import { useHasUserPermission } from "../../hooks/useHasUserPermission"; +import { useProjectContext } from "../../pages/project/ProjectPage"; +import usePagination from "../../hooks/usePagination"; + +const ProjectListView = ({ + currentItems, + selectedStatuses, + handleStatusChange, + setCurrentPage, + totalPages, + isLoading, +}) => { + const dispatch = useDispatch(); + const navigate = useNavigate(); + const { setMangeProject } = useProjectContext(); + // const { data, isLoading, isError, error } = useProjects(); + + // check Permissions + const canManageProject = useHasUserPermission(MANAGE_PROJECT); + + const projectColumns = [ + { + key: "projectName", + label: "Project Name", + className: "text-start py-3", + getValue: (p) => ( +
{ + dispatch(setProjectId(p.id)); + navigate(`/projects/details`); + }} + > + {p.shortName ? `${p.name} (${p.shortName})` : p.name} +
+ ), + }, + { + key: "contactPerson", + label: "Contact Person", + className: "text-start small", + getValue: (p) => `${p?.contactPerson ?? ""}`.trim() || "N/A", + }, + { + key: "startDate", + label: "Start Date", + className: "text-center small", + getValue: (p) => formatUTCToLocalTime(p?.startDate) || "N/A", + }, + { + key: "deadline", + label: "Deadline", + className: "text-center small", + getValue: (p) => formatUTCToLocalTime(p?.endDate) || "N/A", + }, + { + key: "task", + label: "Task", + colSpan: 2, + className: "text-center small", + getValue: (p) => formatNumber(p?.plannedWork) || "0", + }, + { + key: "progress", + label: "Progress", + className: "text-start small", + + getValue: (p) => ( + + ), + }, + { + key: "status", + label: "Status", + className: "text-center small", + isFilter: true, + customRender: (_, selectedStatuses, handleStatusChange) => ( +
+ +
    + {PROJECT_STATUS.map(({ id, label }) => ( +
  • +
    + handleStatusChange(id)} + /> + +
    +
  • + ))} +
+
+ ), + getValue: (p) => ( + + {getProjectStatusName(p.projectStatusId)} + + ), + }, + ]; + + const handleViewActivities = (project) => { + dispatch(setProjectId(project)); + navigate(`/activities/records?project=${project}`); + }; + + return ( +
+ + + + {projectColumns.map((col) => ( + + ))} + + + + + {currentItems?.map((project) => ( + + {projectColumns.map((col) => ( + + ))} + + + ))} + +
+ {col.label} + Action
+ {col.getValue + ? col.getValue(project) + : project[col.key] || "N/A"} + + +
+ + {isLoading && ( +
+ {" "} + {isLoading &&

Loading...

} + {!isLoading && filteredProjects.length === 0 && ( +

No projects found.

+ )} +
+ )} + {!isLoading && currentItems.length === 0 && ( +
+

No projects found.

+
+ )} + {!isLoading && totalPages > 1 && ( + + )} +
+ ); +}; + +export default ProjectListView; diff --git a/src/components/Project/ProjectModal.jsx b/src/components/Project/ProjectModal.jsx index b56720da..788ceabf 100644 --- a/src/components/Project/ProjectModal.jsx +++ b/src/components/Project/ProjectModal.jsx @@ -1,36 +1,35 @@ -import React from 'react' -import AssignRole from './AssignTask' +import React from "react"; +import AssignRole from "./AssignTask"; -const ProjectModal = ({modalConfig,closeModal}) => { - +const ProjectModal = ({ modalConfig, closeModal }) => { return ( - ) -} + className="modal fade" + id="project-modal" + tabindex="-1" + aria-hidden="true" + role="dialog" + > +
+
+
+ +
-export default ProjectModal \ No newline at end of file + {modalConfig?.type === "assignRole" && ( + + )} +
+
+
+
+ ); +}; + +export default ProjectModal; diff --git a/src/components/Project/ProjectOrganization/ProjectAssignedOrgs.jsx b/src/components/Project/ProjectOrganization/ProjectAssignedOrgs.jsx index 2aa2b046..97f54cf0 100644 --- a/src/components/Project/ProjectOrganization/ProjectAssignedOrgs.jsx +++ b/src/components/Project/ProjectOrganization/ProjectAssignedOrgs.jsx @@ -67,7 +67,7 @@ const ProjectAssignedOrgs = () => { return (
-
+
diff --git a/src/components/Project/ProjectOverview.jsx b/src/components/Project/ProjectOverview.jsx index f5cb219e..ee665144 100644 --- a/src/components/Project/ProjectOverview.jsx +++ b/src/components/Project/ProjectOverview.jsx @@ -10,9 +10,9 @@ import ReactApexChart from "react-apexcharts"; import Chart from "react-apexcharts"; const ProjectOverview = ({ project }) => { - const { projects } = useProjects(); + const { data } = useProjects(); const [current_project, setCurrentProject] = useState( - projects.find((pro) => pro.id == project) + data?.find((pro) => pro.id == project) ); const selectedProject = useSelector( @@ -154,7 +154,7 @@ const ProjectOverview = ({ project }) => { }, [current_project]); useEffect(() => { - setCurrentProject(projects.find((pro) => pro.id == selectedProject)); + setCurrentProject(data?.find((pro) => pro.id == selectedProject)); if (current_project) { let val = getProgressInPercentage( current_project.plannedWork, diff --git a/src/components/Project/ProjectSchema.jsx b/src/components/Project/ProjectSchema.jsx new file mode 100644 index 00000000..6caafe81 --- /dev/null +++ b/src/components/Project/ProjectSchema.jsx @@ -0,0 +1,59 @@ +import { z } from "zod"; +import { DEFAULT_EMPTY_STATUS_ID } from "../../utils/constants"; +const currentDate = new Date() + + +export const projectDefault = { + name: "", + shortName: "", + contactPerson: "", + projectAddress: "", + startDate: currentDate.toISOString().split("T")[0], + endDate: currentDate.toISOString().split("T")[0], + projectStatusId: DEFAULT_EMPTY_STATUS_ID, + promoterId: "", + pmcId: "", +}; + + +export const projectSchema = z + .object({ + name: z.string().min(1, { message: "Project Name is required" }), + shortName: z.string().optional(), + contactPerson: z + .string() + .min(1, { message: "Contact Person Name is required" }) + .regex(/^[A-Za-z\s]+$/, { + message: "Contact Person must contain only letters", + }), + projectAddress: z + .string() + .min(1, { message: "Address is required" }) + .max(500, "Address must not exceed 150 characters"), + startDate: z + .string() + .min(1, { message: "Start Date is required" }) + .default(projectDefault), + endDate: z + .string() + .min(1, { message: "End Date is required" }) + .default(projectDefault), + projectStatusId: z.string().min(1, { message: "Status is required" }), + promoterId: z.string().min(1, { message: "Promoter is required" }), + pmcId: z.string().min(1, { message: "PMC is required" }), + }) + .refine( + (data) => { + const start = new Date(data.startDate); + const end = new Date(data.endDate); + return end >= start; + }, + { + path: ["endDate"], + message: "End Date must be greater than Start Date", + } + ); + + + + diff --git a/src/components/Project/Team/Teams.jsx b/src/components/Project/Team/Teams.jsx index 81921bd9..473dfae6 100644 --- a/src/components/Project/Team/Teams.jsx +++ b/src/components/Project/Team/Teams.jsx @@ -1,5 +1,4 @@ import React, { useState, useEffect, useCallback, useMemo } from "react"; -import MapUsers from "../MapUsers"; import { Link, NavLink, useNavigate, useParams } from "react-router-dom"; import showToast from "../../../services/toastService"; diff --git a/src/hooks/useProjects.js b/src/hooks/useProjects.js index a0116a62..28c65a0b 100644 --- a/src/hooks/useProjects.js +++ b/src/hooks/useProjects.js @@ -14,27 +14,15 @@ import { } from "@tanstack/react-query"; import showToast from "../services/toastService"; - - -export const useCurrentService = ()=>{ - return useSelector((store)=>store.globalVariables.selectedServiceId) -} - - - - +export const useCurrentService = () => { + return useSelector((store) => store.globalVariables.selectedServiceId); +}; // ------------------------------Query------------------- export const useProjects = () => { const loggedUser = useSelector((store) => store.globalVariables.loginUser); - - const { - data: projects = [], - isLoading: loading, - error, - refetch, - } = useQuery({ + return useQuery({ queryKey: ["ProjectsList"], queryFn: async () => { const response = await ProjectRepository.getProjectList(); @@ -42,19 +30,13 @@ export const useProjects = () => { }, enabled: !!loggedUser, }); - - return { - projects, - loading, - error, - refetch, - }; }; export const useEmployeesByProjectAllocated = ( projectId, serviceId, - organizationId,emloyeeeStatus + organizationId, + emloyeeeStatus ) => { const { data = [], @@ -62,16 +44,23 @@ export const useEmployeesByProjectAllocated = ( refetch, error, } = useQuery({ - queryKey: ["empListByProjectAllocated", projectId, serviceId,organizationId,emloyeeeStatus], + queryKey: [ + "empListByProjectAllocated", + projectId, + serviceId, + organizationId, + emloyeeeStatus, + ], queryFn: async () => { const res = await ProjectRepository.getProjectAllocation( - projectId, serviceId, + projectId, + serviceId, organizationId, - emloyeeeStatus + emloyeeeStatus ); return res?.data || res; }, - enabled: !!projectId, + enabled: !!projectId, onError: (error) => { showToast( error.message || "Error while fetching project allocated employees", @@ -190,17 +179,20 @@ export const useProjectName = () => { }; }; -export const useProjectInfra = (projectId,serviceId) => { +export const useProjectInfra = (projectId, serviceId) => { const { data: projectInfra, isLoading, error, isFetched, } = useQuery({ - queryKey: ["ProjectInfra", projectId,serviceId], + queryKey: ["ProjectInfra", projectId, serviceId], queryFn: async () => { if (!projectId) return null; - const res = await ProjectRepository.getProjectInfraByproject(projectId,serviceId); + const res = await ProjectRepository.getProjectInfraByproject( + projectId, + serviceId + ); return res.data; }, enabled: !!projectId, @@ -212,11 +204,18 @@ export const useProjectInfra = (projectId,serviceId) => { return { projectInfra, isLoading, error, isFetched }; }; -export const useProjectTasks = (workAreaId, serviceId = null, isExpandedArea = false) => { +export const useProjectTasks = ( + workAreaId, + serviceId = null, + isExpandedArea = false +) => { const { data, isLoading, error } = useQuery({ queryKey: ["WorkItems", workAreaId, serviceId], queryFn: async () => { - const res = await ProjectRepository.getProjectTasksByWorkArea(workAreaId, serviceId); + const res = await ProjectRepository.getProjectTasksByWorkArea( + workAreaId, + serviceId + ); return res.data; // return actual task list }, enabled: !!workAreaId && isExpandedArea, // only fetch if workAreaId exists and area is expanded @@ -308,13 +307,21 @@ export const useProjectAssignedServices = (projectId) => { }); }; - -export const useEmployeeForTaskAssign = (projectId,serviceId,organizationId)=>{ - return useQuery({ - queryKey:["EmployeeForTaskAssign",projectId,serviceId,organizationId], - queryFn:async()=> await ProjectRepository.getEmployeeForTaskAssign(projectId,serviceId,organizationId) - }) -} +export const useEmployeeForTaskAssign = ( + projectId, + serviceId, + organizationId +) => { + return useQuery({ + queryKey: ["EmployeeForTaskAssign", projectId, serviceId, organizationId], + queryFn: async () => + await ProjectRepository.getEmployeeForTaskAssign( + projectId, + serviceId, + organizationId + ), + }); +}; // -- -------------Mutation------------------------------- @@ -322,8 +329,8 @@ export const useCreateProject = ({ onSuccessCallback }) => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async (newProject) => { - const res = await ProjectRepository.manageProject(newProject); + mutationFn: async (payload) => { + const res = await ProjectRepository.manageProject(payload); return res.data; }, onSuccess: (data) => { @@ -349,11 +356,11 @@ export const useCreateProject = ({ onSuccessCallback }) => { }); }; -export const useUpdateProject = ({ onSuccessCallback }) => { +export const useUpdateProject = ( onSuccessCallback ) => { const queryClient = useQueryClient(); const { mutate, isPending, isSuccess, isError } = useMutation({ - mutationFn: async ({ projectId, updatedData }) => { - return await ProjectRepository.updateProject(projectId, updatedData); + mutationFn: async ({ projectId, payload }) => { + return await ProjectRepository.updateProject(projectId, payload); }, onSuccess: (data, variables) => { @@ -413,7 +420,7 @@ export const useManageProjectAllocation = ({ const queryClient = useQueryClient(); const { mutate, isPending, isSuccess, isError } = useMutation({ - mutationFn: async ({payload}) => { + mutationFn: async ({ payload }) => { const response = await ProjectRepository.manageProjectAllocation(payload); return response.data; }, diff --git a/src/pages/project/ProjectList.jsx b/src/pages/project/ProjectList.jsx deleted file mode 100644 index b3b1ae7d..00000000 --- a/src/pages/project/ProjectList.jsx +++ /dev/null @@ -1,419 +0,0 @@ -import React, { useState, useEffect, useCallback } from "react"; -import ProjectCard from "../../components/Project/ProjectCard"; -import ManageProjectInfo from "../../components/Project/ManageProjectInfo"; -import Breadcrumb from "../../components/common/Breadcrumb"; -import ProjectRepository from "../../repositories/ProjectRepository"; -import { useProjects, useCreateProject } from "../../hooks/useProjects"; -import showToast from "../../services/toastService"; -import { useHasUserPermission } from "../../hooks/useHasUserPermission"; -import { useProfile } from "../../hooks/useProfile"; -import { ITEMS_PER_PAGE, MANAGE_PROJECT } from "../../utils/constants"; -import ProjectListView from "./ProjectListView"; -import eventBus from "../../services/eventBus"; -import { clearApiCacheKey } from "../../slices/apiCacheSlice"; -import { defaultCheckBoxAppearanceProvider } from "pdf-lib"; -import { useMutation } from "@tanstack/react-query"; -import usePagination from "../../hooks/usePagination"; -import GlobalModel from "../../components/common/GlobalModel"; -import { useDispatch, useSelector } from "react-redux"; -import { setProjectId } from "../../slices/localVariablesSlice"; - -const ProjectList = () => { - const { profile: loginUser } = useProfile(); - const [listView, setListView] = useState(false); - const [showModal, setShowModal] = useState(false); - const selectedProject = useSelector( - (store) => store.localVariables.projectId - ); - const dispatch = useDispatch(); - - const { projects, loading, error, refetch } = useProjects(); - const [projectList, setProjectList] = useState([]); - - const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT); - const [HasManageProject, setHasManageProject] = useState( - HasManageProjectPermission - ); - - const { mutate: createProject, isPending } = useCreateProject({ - onSuccessCallback: () => { - setShowModal(false); - }, - }); - - const [searchTerm, setSearchTerm] = useState(""); - const [selectedStatuses, setSelectedStatuses] = useState([ - "b74da4c2-d07e-46f2-9919-e75e49b12731", - "603e994b-a27f-4e5d-a251-f3d69b0498ba", - "ef1c356e-0fe0-42df-a5d3-8daee355492d", - "cdad86aa-8a56-4ff4-b633-9c629057dfef", - "33deaef9-9af1-4f2a-b443-681ea0d04f81", - ]); - - const handleShow = () => setShowModal(true); - const handleClose = () => setShowModal(false); - useEffect(() => { - dispatch(setProjectId(null)); - }, []); - const sortingProject = (projects) => { - if (!loading && 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 (!loading && projects) { - sortingProject(projects); - } - }, [projects, loading, selectedStatuses]); - - useEffect(() => { - setHasManageProject(loginUser ? HasManageProjectPermission : false); - }, [loginUser, HasManageProjectPermission]); - - const handleSubmitForm = (newProject) => { - createProject(newProject); - }; - - 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 totalPages = Math.ceil(filteredProjects.length / ITEMS_PER_PAGE); - - const { currentItems, currentPage, paginate, setCurrentPage } = usePagination( - filteredProjects, - ITEMS_PER_PAGE - ); - - useEffect(() => { - const tooltipTriggerList = Array.from( - document.querySelectorAll('[data-bs-toggle="tooltip"]') - ); - tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el)); - }, []); - - return ( - <> - {showModal && ( - - - - )} - -
- -
-
-
-
-
- { - setSearchTerm(e.target.value); - setCurrentPage(1); - }} - /> -
- -
- - -
- -
- -
    - {[ - { - id: "b74da4c2-d07e-46f2-9919-e75e49b12731", - label: "Active", - }, - { - id: "cdad86aa-8a56-4ff4-b633-9c629057dfef", - label: "In Progress", - }, - { - id: "603e994b-a27f-4e5d-a251-f3d69b0498ba", - label: "On Hold", - }, - { - id: "ef1c356e-0fe0-42df-a5d3-8daee355492d", - label: "Inactive", - }, - { - id: "33deaef9-9af1-4f2a-b443-681ea0d04f81", - label: "Completed", - }, - ].map(({ id, label }) => ( -
  • -
    - handleStatusChange(id)} - /> - -
    -
  • - ))} -
-
-
- -
- -
-
-
-
- {loading &&

Loading...

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

No projects found.

- )} - - {listView ? ( -
-
-
-
- - - - - - - - - - - - - - {currentItems.length === 0 ? ( - - - - ) : ( - currentItems.map((project) => ( - - )) - )} - -
- Project Name - Contact PersonSTART DATEDEADLINETaskProgress -
- -
    - {[ - { - id: "b74da4c2-d07e-46f2-9919-e75e49b12731", - label: "Active", - }, - { - id: "cdad86aa-8a56-4ff4-b633-9c629057dfef", - label: "In Progress", - }, - { - id: "603e994b-a27f-4e5d-a251-f3d69b0498ba", - label: "On Hold", - }, - { - id: "ef1c356e-0fe0-42df-a5d3-8daee355492d", - label: "Inactive", - }, - { - id: "33deaef9-9af1-4f2a-b443-681ea0d04f81", - label: "Completed", - }, - ].map(({ id, label }) => ( -
  • -
    - handleStatusChange(id)} - /> - -
    -
  • - ))} -
-
-
- Action -
- No projects found -
-
{" "} -
{" "} -
- ) : ( -
- {currentItems.map((project) => ( - - ))} -
- )} - - {!loading && totalPages > 1 && ( - - )} -
- - ); -}; - -export default ProjectList; diff --git a/src/pages/project/ProjectListView.jsx b/src/pages/project/ProjectListView.jsx deleted file mode 100644 index ad47c735..00000000 --- a/src/pages/project/ProjectListView.jsx +++ /dev/null @@ -1,206 +0,0 @@ -import React, { useState, useEffect } from "react"; -import moment from "moment"; -import { - useProjectDetails, - useProjects, - useUpdateProject, -} 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"; -import GlobalModel from "../../components/common/GlobalModel"; -import { formatNumber } from "../../utils/dateUtils"; -import { setProjectId } from "../../slices/localVariablesSlice"; -import { useDispatch } from "react-redux"; - -const ProjectListView = ({ projectData, recall }) => { - const [projectInfo, setProjectInfo] = useState(projectData); - const dispatch = useDispatch() - const { projects_Details, loading, error, refetch } = useProjectDetails( - projectInfo?.id, false - ); - const [showModal, setShowModal] = useState(false); - const navigate = useNavigate(); - const ManageProject = useHasUserPermission(MANAGE_PROJECT); - useEffect(() => { - setProjectInfo(projectData); - }, [projectData]); - const { - mutate: updateProject, - isPending, - isSuccess, - isError, - } = useUpdateProject({ - onSuccessCallback: () => { - setShowModal(false); - }, - }) - - const handleShow = async () => { - try { - const { data } = await refetch(); - setShowModal(true); - } catch (err) { - 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 = () => { - dispatch(setProjectId(projectInfo.id)) - navigate(`/projects/details`); - }; - - const handleViewActivities = () => { - dispatch(setProjectId(projectInfo.id)) - navigate(`/activities/records?project=${projectInfo.id}`); - }; - - const handleFormSubmit = (updatedProject) => { - if (projectInfo?.id) { - updateProject({ - projectId: projectInfo.id, - updatedData: updatedProject, - }); - } - }; - - return ( - <> - {showModal && projects_Details && ( - - )} - - - - { - dispatch(setProjectId(projectInfo.id)) - navigate(`/projects/details`) - }} - > - {projectInfo.shortName - ? `${projectInfo.name} (${projectInfo.shortName})` - : projectInfo.name} - - - {projectInfo.contactPerson} - - - {projectInfo.startDate - ? moment(projectInfo.startDate).format("DD-MMM-YYYY") - : "NA"} - - - - {projectInfo.endDate - ? moment(projectInfo.endDate).format("DD-MMM-YYYY") - : "NA"} - - {formatNumber(projectInfo.plannedWork)} - - - - - -

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

- - - -
- - -
- - - - ); -}; - -export default ProjectListView; \ No newline at end of file diff --git a/src/pages/project/ProjectPage.jsx b/src/pages/project/ProjectPage.jsx new file mode 100644 index 00000000..4fdccc30 --- /dev/null +++ b/src/pages/project/ProjectPage.jsx @@ -0,0 +1,235 @@ +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
+ if(isError) return

{error.message}

+ return ( + +
+ + +
+
+
+
+
+ { + setSearchTerm(e.target.value); + setCurrentPage(1); + }} + /> +
+ +
+ + +
+ +
+ +
    + {PROJECT_STATUS.map(({ id, label }) => ( +
  • +
    + handleStatusChange(id)} + /> + +
    +
  • + ))} +
+
+
+ +
+ {HasManageProject && ( )} +
+
+
+
+ + {/* Project Render here */} + {listView ? ( + + ) : ( + + )} + + {/* ------------------ */} + + {/* Project Manage UPdate or create */} + + {manageProject.isOpen && ( + setMangeProject({ isOpen: false, Project: null })} + > + setMangeProject({ isOpen: false, Project: null })} + /> + + )} +
+
+ ); +}; + +export default ProjectPage; diff --git a/src/pages/project/ProjectsPage.jsx b/src/pages/project/ProjectsPage.jsx deleted file mode 100644 index 3bb73175..00000000 --- a/src/pages/project/ProjectsPage.jsx +++ /dev/null @@ -1,105 +0,0 @@ -import React from "react"; -import Breadcrumb from "../../components/common/Breadcrumb"; -import { PROJECt_STATUS } from "../../utils/constants"; - -const ProjectsPage = () => { - return ( -
- - - -
-
-
-
-
- { - setSearchTerm(e.target.value); - setCurrentPage(1); - }} - /> -
- -
- - -
- -
- -
    - {PROJECt_STATUS.map(({ id, label }) => ( -
  • -
    - handleStatusChange(id)} - /> - -
    -
  • - ))} -
-
-
- -
- -
-
-
-
-
- ); -}; - -export default ProjectsPage; diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index 8083b9ba..823ebb3a 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -13,7 +13,6 @@ import ChangePasswordPage from "../pages/authentication/ChangePassword"; // Home & Protected Pages import Dashboard from "../components/Dashboard/Dashboard"; -import ProjectList from "../pages/project/ProjectList"; import ProjectDetails from "../pages/project/ProjectDetails"; import ManageProject from "../components/Project/ManageProject"; import EmployeeList from "../pages/employee/EmployeeList"; @@ -52,6 +51,7 @@ import OrganizationPage from "../pages/Organization/OrganizationPage"; import LandingPage from "../pages/Home/LandingPage"; import TenantSelectionPage from "../pages/authentication/TenantSelectionPage"; import DailyProgrssReport from "../pages/DailyProgressReport/DailyProgrssReport"; +import ProjectPage from "../pages/project/ProjectPage"; const router = createBrowserRouter( [ { @@ -79,7 +79,7 @@ const router = createBrowserRouter( element: , children: [ { path: "/dashboard", element: }, - { path: "/projects", element: }, + { path: "/projects", element: }, { path: "/projects/details", element: }, { path: "/project/manage/:projectId", element: }, { path: "/employees", element: }, diff --git a/src/utils/constants.jsx b/src/utils/constants.jsx index 5d17337a..62c686c9 100644 --- a/src/utils/constants.jsx +++ b/src/utils/constants.jsx @@ -118,7 +118,7 @@ export const orgSize = [ { val: "500+", name: "500+" }, ]; -export const PROJECt_STATUS = [ +export const PROJECT_STATUS = [ { id: "b74da4c2-d07e-46f2-9919-e75e49b12731", label: "Active", @@ -140,6 +140,7 @@ export const PROJECt_STATUS = [ label: "Completed", }, ]; +export const DEFAULT_EMPTY_STATUS_ID = "00000000-0000-0000-0000-000000000000"; export const BASE_URL = process.env.VITE_BASE_URL;