diff --git a/public/assets/vendor/css/core.css b/public/assets/vendor/css/core.css index d7f7cfcb..aca2dc05 100644 --- a/public/assets/vendor/css/core.css +++ b/public/assets/vendor/css/core.css @@ -836,7 +836,7 @@ progress { } .row { - --bs-gutter-x: 1.625rem; + --bs-gutter-x: 0.500rem; --bs-gutter-y: 0; display: flex; flex-wrap: wrap; @@ -8966,10 +8966,8 @@ a:not([href]):hover { } /* Autofill input bg and text color issue on different OS and browsers */ -input:-webkit-autofill, input:-webkit-autofill:hover, input:-webkit-autofill:focus, -textarea:-webkit-autofill, textarea:-webkit-autofill:hover, textarea:-webkit-autofill:focus, select:-webkit-autofill, @@ -8978,6 +8976,15 @@ select:-webkit-autofill:focus, input:-internal-autofill-selected { background-clip: text !important; } +input:-webkit-autofill, +textarea:-webkit-autofill, +select:-webkit-autofill { + -webkit-box-shadow: 0 0 0px 1000px white inset !important; + box-shadow: 0 0 0px 1000px white inset !important; + -webkit-text-fill-color: #000 !important; + caret-color: #000 !important; + transition: background-color 5000s ease-in-out 0s; +} h1, .h1 { diff --git a/src/assets/vendor/css/core.css b/src/assets/vendor/css/core.css index 625896b1..70f4fb09 100644 --- a/src/assets/vendor/css/core.css +++ b/src/assets/vendor/css/core.css @@ -2436,7 +2436,7 @@ progress { } .table-responsive { - overflow-x: auto; + /* overflow-x: auto; */ -webkit-overflow-scrolling: touch; } diff --git a/src/components/Activities/AttendcesLogs.jsx b/src/components/Activities/AttendcesLogs.jsx index 32c46617..505c7c61 100644 --- a/src/components/Activities/AttendcesLogs.jsx +++ b/src/components/Activities/AttendcesLogs.jsx @@ -234,7 +234,7 @@ const AttendanceLog = ({
{data && data.length > 0 && ( @@ -332,7 +332,7 @@ const AttendanceLog = ({
)} {!loading && !isRefreshing && data.length === 0 && ( - No employee logs + No employee logs )} {/* {error && !loading && !isRefreshing && ( diff --git a/src/components/Activities/SubTask.jsx b/src/components/Activities/SubTask.jsx index 43a3c049..4014673b 100644 --- a/src/components/Activities/SubTask.jsx +++ b/src/components/Activities/SubTask.jsx @@ -9,7 +9,7 @@ import { import showToast from "../../services/toastService"; import ProjectRepository from "../../repositories/ProjectRepository"; import { useTaskById } from "../../hooks/useTasks"; -import {useManageTask} from "../../hooks/useProjects"; +import { useManageTask } from "../../hooks/useProjects"; const subTaskSchema = z.object({ activityId: z.string().min(1, "Activity is required"), @@ -37,14 +37,13 @@ const SubTask = ({ activity, onClose }) => { }); const selectedActivityId = watch("activityId"); const selectedActivity = activities?.find((a) => a.id === selectedActivityId); - const {mutate:createSubTask,isPending } = useManageTask( { - onSuccessCallback: () => - { - showToast("Sub Task Created Successfully","success") + const { mutate: createSubTask, isPending } = useManageTask({ + onSuccessCallback: () => { + showToast("Sub Task Created Successfully", "success"); reset(); onClose(); - } - } ) + }, + }); useEffect(() => { setCategoryData(categories); @@ -73,7 +72,7 @@ const SubTask = ({ activity, onClose }) => { }; const onSubmitForm = async (formData) => { - let payload = { + let data = { workAreaID: Task.workItem.workAreaId, workCategoryId: formData.workCategoryId, activityID: formData.activityId, @@ -82,8 +81,19 @@ const SubTask = ({ activity, onClose }) => { parentTaskId: activity?.id, comment: formData.comment, }; - - createSubTask([payload]) + + const payload = [data]; + let buildingId = activity.workItem.workArea.floor.building.id; + let floorId = activity.workItem.workArea.floor.id; + let workAreaId = activity.workItem.workArea.id; + createSubTask({ + payload: payload, + buildingId: buildingId, + floorId: floorId, + workAreaId: workAreaId, + PreviousPlannedWork:0, + previousCompletedWork:0 + }); }; return (
@@ -147,15 +157,15 @@ const SubTask = ({ activity, onClose }) => { disabled > + {loading ? "Loading..." : "-- Select Activity --"} + - {!loading && - activities?.map((activity) => ( - - ))} + {!loading && + activities?.map((activity) => ( + + ))} {errors.activityId && (
{errors.activityId.message}
diff --git a/src/components/Charts/LineChart.jsx b/src/components/Charts/LineChart.jsx index 1dc1b1ac..6eb4840c 100644 --- a/src/components/Charts/LineChart.jsx +++ b/src/components/Charts/LineChart.jsx @@ -117,7 +117,7 @@ const LineChart = ({ return ( -
+
{ - const { projectsCardData } = useDashboardProjectsCardData(); - const { teamsCardData } = useDashboardTeamsCardData(); - const { tasksCardData } = useDashboardTasksCardData(); +const { projectsCardData } = useDashboardProjectsCardData(); +const { teamsCardData } = useDashboardTeamsCardData(); +const { tasksCardData } = useDashboardTasksCardData(); - return ( -
-
- {/* Projects Card */} -
- -
+// Get the selected project ID from Redux store +const selectedProjectId = useSelector( +(store) => store.localVariables.projectId +); - {/* Teams Card */} -
- -
+// Determine if "All Projects" is selected +// selectedProjectId will be null when "All Projects" is chosen +const isAllProjectsSelected = selectedProjectId === null; - {/* Tasks Card */} -
- -
+return ( +
+
- {/* Bar Chart (Project Completion) */} -
- -
+{isAllProjectsSelected && ( +
+ +
+)} - {/* Line Chart (Project Progress) */} -
- -
- {/*
- -
*/} -
-
- ); +
+ +
+ +
+ +
+ + +{isAllProjectsSelected && ( +
+ +
+)} + +{! isAllProjectsSelected && ( +
+ +
+)} +
+ +
+ + +
+
+); }; export default Dashboard; \ No newline at end of file diff --git a/src/components/Dashboard/ProjectProgressChart.jsx b/src/components/Dashboard/ProjectProgressChart.jsx index 20e0191a..f80bb0d5 100644 --- a/src/components/Dashboard/ProjectProgressChart.jsx +++ b/src/components/Dashboard/ProjectProgressChart.jsx @@ -93,33 +93,6 @@ const ProjectProgressChart = ({
Project Progress

Progress Overview by Project

- - {/* Right: Checkbox and Project Name */} -
- {ShowAllProject == true && ( -
- setShowAllEmployees(e.target.checked)} - /> - -
- )} - {!showAllEmployees && selectedProjectName && ( -

- {selectedProjectName} -

- )} -
{/* Row 2: Time Range Buttons */} diff --git a/src/components/Layout/Header.jsx b/src/components/Layout/Header.jsx index 9c1b4b17..476c73d0 100644 --- a/src/components/Layout/Header.jsx +++ b/src/components/Layout/Header.jsx @@ -1,3 +1,4 @@ + import getGreetingMessage from "../../utils/greetingHandler"; import { cacheData, @@ -20,26 +21,28 @@ import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { MANAGE_PROJECT } from "../../utils/constants"; const Header = () => { - const {profile} = useProfile(); + const { profile } = useProfile(); const location = useLocation(); const dispatch = useDispatch(); const { data, loading } = useMaster(); const navigate = useNavigate(); const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT); + const isDirectoryPath = /^\/directory$/.test(location.pathname); + const isDashboard = /^\/dashboard$/.test(location.pathname); const getRole = (roles, joRoleId) => { if (!Array.isArray(roles)) return "User"; let role = roles.find((role) => role.id === joRoleId); return role ? role.name : "User"; }; + const handleLogout = (e) => { - e.preventDefault(); // Prevent default anchor behavior (e.g., page reload) + e.preventDefault(); logout(); }; const logout = async () => { try { - // Notify server about the logout (optional) let data = { refreshToken: localStorage.getItem("refreshToken"), }; @@ -54,6 +57,7 @@ const Header = () => { window.location.href = "/auth/login"; }) .catch((error) => { + // Even if logout API fails, clear local storage and redirect localStorage.removeItem("jwtToken"); localStorage.removeItem("refreshToken"); localStorage.removeItem("user"); @@ -71,98 +75,93 @@ const Header = () => { const handleProfilePage = () => { navigate(`/employee/${profile?.employeeInfo?.id}?for=attendance`); }; - // const { projects, loading: projectLoading } = useProjects(); + const { projectNames, loading: projectLoading, fetchData } = useProjectName(); const selectedProject = useSelector( (store) => store.localVariables.projectId ); - const selectedProjectName = projectNames?.find( - (p) => p?.id === selectedProject - )?.name; - - let displayText = ""; - if (selectedProjectName) { - displayText = selectedProjectName; - } else if (projectLoading && selectedProject) { - displayText = selectedProject; + // Determine the display text for the project dropdown + let displayText = "All Projects"; + if (selectedProject === null) { + displayText = "All Projects"; + } else if (selectedProject) { + const selectedProjectObj = projectNames?.find( + (p) => p?.id === selectedProject + ); + // Fallback to selectedProject ID if name not found during loading or mismatch + displayText = selectedProjectObj ? selectedProjectObj.name : selectedProject; } else if (projectLoading) { displayText = "Loading..."; } const { openChangePassword } = useChangePassword(); + useEffect(() => { if ( projectNames && - selectedProject !== " " && + projectNames.length > 0 && + selectedProject === undefined && !getCachedData("hasReceived") ) { - dispatch(setProjectId(projectNames[0]?.id)); + if(isDashboard){ + dispatch(setProjectId(null)); + }else{ + dispatch(setProjectId(projectNames[0]?.id)); + } } - }, [projectNames]); + }, [projectNames, selectedProject, dispatch]); + + + /** Check if current page is project details page or directory page */ + // const isProjectPath = /^\/projects\/[a-f0-9-]{36}$/.test(location.pathname); - /** Check if current page id project details page */ - const isProjectPath = /^\/projects\/[a-f0-9-]{36}$/.test(location.pathname) - const isDirectoryPath = /^\/directory$/.test(location.pathname); const handler = useCallback( async (data) => { if (!HasManageProjectPermission) { await fetchData(); const projectExist = data.projectIds.some( - (item) => item == selectedProject + (item) => item === selectedProject ); if (projectExist) { cacheData("hasReceived", false); } } }, - [fetchData,projectNames,selectedProject] + [fetchData, selectedProject, HasManageProjectPermission] ); - // useEffect(() => { - // eventBus.on("assign_project_one", handler); - // return () => eventBus.off("assign_project_one", handler); - // }, [handler]); - const newProjectHandler = useCallback( async (msg) => { - if (HasManageProjectPermission && msg.keyword === "Create_Project") { await fetchData(); - } else if (projectNames.some((item) => item.id == msg.response.id)) { - console.log((projectNames.some((item) => item.id == msg.response.id))) + } else if (projectNames?.some((item) => item.id === msg.response.id)) { await fetchData(); } cacheData("hasReceived", false); }, - [HasManageProjectPermission,projectNames] + [HasManageProjectPermission, projectNames, fetchData] ); - // useEffect(() => { - // eventBus.on("project", newProjectHandler); - // return () => eventBus.off("project", newProjectHandler); - // }, [handler]); - - useDispatch( () => - { - dispatch(changeMaster("Job Role")) -},[]) useEffect(() => { - eventBus.on("assign_project_one", handler); - eventBus.on("project", newProjectHandler); + dispatch(changeMaster("Job Role")); + }, [dispatch]); - return () => { - eventBus.off("assign_project_one", handler); - eventBus.off("project", newProjectHandler); - }; -}, [handler, newProjectHandler]); + useEffect(() => { + eventBus.on("assign_project_one", handler); + eventBus.on("project", newProjectHandler); + return () => { + eventBus.off("assign_project_one", handler); + eventBus.off("project", newProjectHandler); + }; + }, [handler, newProjectHandler]); return (
@@ -336,321 +331,12 @@ const Header = () => { Daily Work Log - Daily Work Allocations
- - {/*
  • - - -
  • */}
  • { Settings
  • - {/*
  • - - - - - Billing - - - 4 - - - -
  • */}
  • {" "} - {/* Use the function from the context */} { @@ -757,4 +426,4 @@ const Header = () => { ); }; -export default Header; +export default Header; \ No newline at end of file diff --git a/src/components/Project/AboutProject.jsx b/src/components/Project/AboutProject.jsx index 30c42e03..e5196eac 100644 --- a/src/components/Project/AboutProject.jsx +++ b/src/components/Project/AboutProject.jsx @@ -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 && ( - setIsOpenModal(false)}> + setIsOpenModal(false)}> setIsOpenModal( false )} isPending={isPending} /> - - )} + + )} {projects_Details && ( - <> + <>
    @@ -126,10 +131,9 @@ const AboutProject = () => )} - {isLoading && loading...} ); }; -export default AboutProject; +export default AboutProject; \ No newline at end of file diff --git a/src/components/Project/Infrastructure/EditActivityModal.jsx b/src/components/Project/Infrastructure/EditActivityModal.jsx index 36b9e98c..14b64b53 100644 --- a/src/components/Project/Infrastructure/EditActivityModal.jsx +++ b/src/components/Project/Infrastructure/EditActivityModal.jsx @@ -116,8 +116,17 @@ useEffect(() => { floorId: floor?.id, workAreaId: workArea?.id, }; - let plannedTask = workItem?.workItem?.plannedWork || workItem?.plannedWork || 0 - UpdateTask({payload:[payload],PreviousPlannedWork:plannedTask}) + let plannedTask = + workItem?.workItem?.plannedWork || workItem?.plannedWork || 0; + let completedTask = workItem?.workItem?.completedWork || workItem?.completedWork || 0 + UpdateTask({ + payload: [payload], + PreviousPlannedWork: plannedTask, + buildingId: building?.id, + floorId: floor?.id, + workAreaId: workArea?.id, + previousCompletedWork:completedTask + }); } return (
    diff --git a/src/components/Project/Infrastructure/TaskModel.jsx b/src/components/Project/Infrastructure/TaskModel.jsx index 24365622..4de3c215 100644 --- a/src/components/Project/Infrastructure/TaskModel.jsx +++ b/src/components/Project/Infrastructure/TaskModel.jsx @@ -69,10 +69,9 @@ const TaskModel = ({ project, onSubmit, onClose }) => { const selectedCategory = categoryData?.find((c) => c.id === watchCategoryId); const { mutate: CreateTask, isPending } = useManageTask({ - onSuccessCallback: ( response ) => - { - showToast( response?.message, "success" ) - onClose?.() + onSuccessCallback: (response) => { + showToast(response?.message, "success"); + onClose?.(); }, }); useEffect(() => { @@ -96,8 +95,10 @@ const TaskModel = ({ project, onSubmit, onClose }) => { }, [categories]); const onSubmitForm = async (data) => { - const payload = [data]; - CreateTask({payload,PreviousPlannedWork:0}); + const payload = [data]; + CreateTask({payload:payload,buildingId: data.buildingID, + floorId: data.floorId, + workAreaId: data.workAreaId, PreviousPlannedWork:0,previousCompletedWork:0}); }; return ( diff --git a/src/components/Project/Infrastructure/WorkItem.jsx b/src/components/Project/Infrastructure/WorkItem.jsx index 1116991c..629f9e39 100644 --- a/src/components/Project/Infrastructure/WorkItem.jsx +++ b/src/components/Project/Infrastructure/WorkItem.jsx @@ -10,7 +10,10 @@ import { } from "../../../utils/constants"; import ConfirmModal from "../../common/ConfirmModal"; import ProjectRepository from "../../../repositories/ProjectRepository"; -import { useDeleteProjectTask, useProjectDetails } from "../../../hooks/useProjects"; +import { + useDeleteProjectTask, + useProjectDetails, +} from "../../../hooks/useProjects"; import showToast from "../../../services/toastService"; import { cacheData, @@ -19,7 +22,7 @@ import { } from "../../../slices/apiDataManager"; import { refreshData } from "../../../slices/localVariablesSlice"; import GlobalModel from "../../common/GlobalModel"; -import {useDeleteMasterItem} from "../../../hooks/masterHook/useMaster"; +import { useDeleteMasterItem } from "../../../hooks/masterHook/useMaster"; const WorkItem = ({ workItem, @@ -28,7 +31,9 @@ const WorkItem = ({ forWorkArea, deleteHandleTask, }) => { - const { projectId } = useParams(); + // const { projectId } = useParams(); +const isTaskPlanning = /^\/activities\/task$/.test(location.pathname); + const [itemName, setItemName] = useState(""); const [NewWorkItem, setNewWorkItem] = useState(); const [isModalOpen, setIsModalOpen] = useState(false); @@ -40,19 +45,18 @@ const WorkItem = ({ const [loadingDelete, setLoadingDelete] = useState(false); const project = getCachedData("projectInfo"); - const openModal = () => setIsModalOpen(true); - const closeModal = () => setIsModalOpen( false ); - - const showModalDelete = () => setShowModal2(true); + const closeModal = () => setIsModalOpen(false); + + const showModalDelete = () => setShowModal2(true); const closeModalDelete = () => setShowModal2(false); const getProgress = (planned, completed) => { return (completed * 100) / planned + "%"; }; - - const {mutate:DeleteTask,isPending } = useDeleteProjectTask(() => { - closeModalDelete?.(); - }); + + const { mutate: DeleteTask, isPending } = useDeleteProjectTask(() => { + closeModalDelete?.(); + }); const handleAssignTask = () => { setItemName(""); @@ -61,15 +65,15 @@ const WorkItem = ({ setNewWorkItem(workItem); }, [workItem]); - const refreshWorkItem = (plannedTask) =>{ + const refreshWorkItem = (plannedTask) => { if (workItem) { - const updated = { - ...workItem, - todaysAssigned: (workItem.todaysAssigned || 0) + plannedTask, - }; - setNewWorkItem(updated); - } - } + const updated = { + ...workItem, + todaysAssigned: (workItem.todaysAssigned || 0) + plannedTask, + }; + setNewWorkItem(updated); + } + }; let assigndata = { building: forBuilding, floor: forFloor, @@ -85,15 +89,17 @@ const WorkItem = ({ tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el)); }, []); - - - - - const handleSubmit = async () => { let WorkItemId = workItem.workItemId || workItem.id; - DeleteTask({workItemId:WorkItemId,workAreaId:forWorkArea?.id}) - + debugger + DeleteTask({ + workItemId: WorkItemId, + workAreaId: forWorkArea?.id, + completedTask: workItem?.completedWork, + plannedTask: workItem?.plannedWork, + buildingId: forBuilding?.id, + floorId: forFloor?.id, + }); }; const PlannedWork = @@ -104,18 +110,26 @@ const WorkItem = ({ <> {isModalOpen && ( - + )} {showModal && ( - setShowModal(false)}> + setShowModal(false)} + > setShowModal(false)} + onClose={() => setShowModal(false)} /> )} @@ -167,7 +181,6 @@ const WorkItem = ({ : "NA"} - {hasWorkItem @@ -195,9 +208,7 @@ const WorkItem = ({ {hasWorkItem ? `${ - NewWorkItem?.todaysAssigned ?? - workItem?.todaysAssigned ?? - "0" + NewWorkItem?.todaysAssigned ?? workItem?.todaysAssigned ?? "0" }` : "NA"} @@ -235,7 +246,7 @@ const WorkItem = ({ {/* Desktop (md and up): inline icons */}
    - {!projectId && + {isTaskPlanning && ManageAndAssignTak && PlannedWork !== CompletedWork && ( setShowModal(true)} + onClick={() => setShowModal(true)} role="button" >
    -
    -
      -
    • - - Task Planned:{" "} - {formatNumber(project_detail?.plannedWork)} -
    • -
    • - - Task Completed:{" "} - {formatNumber(project_detail?.completedWork)} -
    • -
    • - - Current team Size:{" "} - {project_detail?.teamSize} -
    • -
    • - {project_detail && ( - <> -
      - - {/* {Math.floor( - getProgressInNumber( - - project_detail.completedWork - ) - ) || 0}{" "} */} - { - (formatNumber(project_detail.plannedWork), - "/", - formatNumber(project_detail.completedWork)) - } - % Completed - -
      - {/*
      -
      -
      */} - - - )} -
    • -
    +
    +
      +
    • +
      + {/* Centered Chart */} +
      +
      + +
      +
      + + {/* Info Section */} +
      +
      + {/* Tasks Planned */} +
      +
      + + + +
      +
      + Tasks Planned +
      + {FormattedNumber(current_project?.plannedWork)} +
      +
      +
      + + {/* Tasks Completed */} +
      +
      + + + +
      +
      + Tasks Completed +
      + {FormattedNumber(current_project?.completedWork)} +
      +
      +
      + + {/* Team Size */} +
      +
      + + + +
      +
      + Current Team Size +
      + {FormattedNumber(current_project?.teamSize)} +
      +
      +
      +
      +
      +
    • +
    +
    +
    ); }; -export default ProjectOverview; +export default ProjectOverview; \ No newline at end of file diff --git a/src/components/Project/Teams.jsx b/src/components/Project/Teams.jsx index 263f3f41..25e3eda8 100644 --- a/src/components/Project/Teams.jsx +++ b/src/components/Project/Teams.jsx @@ -7,7 +7,7 @@ import Avatar from "../common/Avatar"; import moment from "moment"; import ProjectRepository from "../../repositories/ProjectRepository"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { changeMaster } from "../../slices/localVariablesSlice"; import useMaster from "../../hooks/masterHook/useMaster"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; @@ -18,7 +18,8 @@ import {useEmployeesByProjectAllocated, useManageProjectAllocation} from "../../ const Teams = () => { - const {projectId} = useParams() + // const {projectId} = useParams() + const projectId = useSelector((store)=>store.localVariables.projectId) const dispatch = useDispatch(); const { data, loading } = useMaster(); diff --git a/src/components/common/DateRangePicker.jsx b/src/components/common/DateRangePicker.jsx index a417a413..e7239c53 100644 --- a/src/components/common/DateRangePicker.jsx +++ b/src/components/common/DateRangePicker.jsx @@ -26,7 +26,8 @@ const DateRangePicker = ({ altFormat: "d-m-Y", defaultDate: [startDate, endDate], static: true, - clickOpens: true, + clickOpens: true, + maxDate: endDate, // ✅ Disable future dates onChange: (selectedDates, dateStr) => { const [startDateString, endDateString] = dateStr.split(" to "); onRangeChange?.({ startDate: startDateString, endDate: endDateString }); @@ -54,4 +55,4 @@ const DateRangePicker = ({ ); }; -export default DateRangePicker; \ No newline at end of file +export default DateRangePicker; diff --git a/src/hooks/useEmployees.js b/src/hooks/useEmployees.js index 4839d36c..3978ac81 100644 --- a/src/hooks/useEmployees.js +++ b/src/hooks/useEmployees.js @@ -120,11 +120,11 @@ export const useEmployeesAllOrByProjectId = (projectId, showInactive) => { : ['projectEmployees', projectId]; const queryFn = async () => { - if (isAllEmployees) { + if (isAllEmployees) { const res = await EmployeeRepository.getAllEmployeeList(showInactive); return res.data; } else { - const res = await ProjectRepository.getEmployeesByProject(projectId); + const res = await EmployeeRepository.getEmployeeListByproject(projectId); return res.data; } }; diff --git a/src/hooks/useImageGallery.js b/src/hooks/useImageGallery.js new file mode 100644 index 00000000..7e8e5334 --- /dev/null +++ b/src/hooks/useImageGallery.js @@ -0,0 +1,85 @@ +import { useState, useCallback } from "react"; +// import { ImageGalleryAPI } from "../repositories/ImageGalleyRepository"; +import { ImageGalleryAPI } from "../repositories/ImageGalleryAPI"; + +const PAGE_SIZE = 10; + +const useImageGallery = (selectedProjectId) => { + const [images, setImages] = useState([]); + const [allImagesData, setAllImagesData] = useState([]); + const [pageNumber, setPageNumber] = useState(1); + const [hasMore, setHasMore] = useState(true); + const [loading, setLoading] = useState(false); + const [loadingMore, setLoadingMore] = useState(false); + + const fetchImages = useCallback(async (page = 1, filters = {}, reset = false) => { + if (!selectedProjectId) return; + + try { + if (page === 1) { + setLoading(true); + } else { + setLoadingMore(true); + } + + const res = await ImageGalleryAPI.ImagesGet( + selectedProjectId, + filters, + page, + PAGE_SIZE + ); + + const newBatches = res.data || []; + const receivedCount = newBatches.length; + + setImages((prev) => { + if (page === 1 || reset) return newBatches; + const uniqueNew = newBatches.filter( + (batch) => !prev.some((b) => b.batchId === batch.batchId) + ); + return [...prev, ...uniqueNew]; + }); + + setAllImagesData((prev) => { + if (page === 1 || reset) return newBatches; + const uniqueAll = newBatches.filter( + (batch) => !prev.some((b) => b.batchId === batch.batchId) + ); + return [...prev, ...uniqueAll]; + }); + + setHasMore(receivedCount === PAGE_SIZE); + } catch (error) { + console.error("Error fetching images:", error); + if (page === 1) { + setImages([]); + setAllImagesData([]); + } + setHasMore(false); + } finally { + setLoading(false); + setLoadingMore(false); + } + }, [selectedProjectId]); + + const resetGallery = useCallback(() => { + setImages([]); + setAllImagesData([]); + setPageNumber(1); + setHasMore(true); + }, []); + + return { + images, + allImagesData, + pageNumber, + setPageNumber, + hasMore, + loading, + loadingMore, + fetchImages, + resetGallery, + }; +}; + +export default useImageGallery; \ No newline at end of file diff --git a/src/hooks/usePagination.js b/src/hooks/usePagination.js index ffbbcf60..aca2d0d5 100644 --- a/src/hooks/usePagination.js +++ b/src/hooks/usePagination.js @@ -1,12 +1,31 @@ -import { useState, useMemo } from "react"; +import { useState, useMemo,useEffect } from "react"; const usePagination = (data, itemsPerPage) => { const [currentPage, setCurrentPage] = useState(1); - const totalPages = Math.ceil(data?.length / itemsPerPage); + + // const totalPages = Math.ceil(data?.length / itemsPerPage); + +// add this new line + const totalPages = useMemo(() => { + return Math.ceil((data?.length || 0) / itemsPerPage); + }, [data?.length, itemsPerPage]); + + useEffect(() => { + if (currentPage > totalPages && totalPages > 0) { + setCurrentPage(1); + } else if (!data || data.length === 0) { + setCurrentPage(1); + } else if (currentPage === 0 && totalPages > 0) { + setCurrentPage(1); + } + }, [data, totalPages, currentPage]); const currentItems = useMemo(() => { const startIndex = (currentPage - 1) * itemsPerPage; - return data?.slice(startIndex, startIndex + itemsPerPage); + + // return data?.slice(startIndex, startIndex + itemsPerPage); + return data?.slice(startIndex, startIndex + itemsPerPage) || []; + }, [data, currentPage, itemsPerPage]); const paginate = (pageNumber) => { diff --git a/src/hooks/useProjects.js b/src/hooks/useProjects.js index aace5f74..cc6e94f7 100644 --- a/src/hooks/useProjects.js +++ b/src/hooks/useProjects.js @@ -311,14 +311,72 @@ export const useManageTask = ({ onSuccessCallback }) => { const selectedProject = useSelector( (store) => store.localVariables.projectId ); - return useMutation({ - mutationFn: async ({ payload, PreviousPlannedWork }) => - await ProjectRepository.manageProjectTasks(payload), + mutationFn: async ({ + payload, + PreviousPlannedWork, + previousCompletedWork, + buildingId, + floorId, + workAreaId, + }) => await ProjectRepository.manageProjectTasks(payload), onSuccess: (data, variables) => { - const { PreviousPlannedWork } = variables; + const { + workAreaId, + buildingId, + floorId, + PreviousPlannedWork, + previousCompletedWork, + } = variables; queryClient.invalidateQueries({ queryKey: ["WorkItems"] }); const getPlannedDelta = (previous, current) => current - previous; + queryClient.setQueryData(["ProjectInfra", selectedProject], (oldData) => { + if (!oldData) return oldData; + const { workAreaId, floorId, buildingId } = variables; + const updatedData = JSON.parse(JSON.stringify(oldData)); + return updatedData.map((building) => { + if (building.id !== buildingId) return building; + return { + ...building, + floors: building.floors.map((floor) => { + if (floor.id !== floorId) return floor; + + return { + ...floor, + workAreas: floor.workAreas.map((area) => { + if (area.id !== workAreaId) return area; + + const previousPlanned = PreviousPlannedWork ?? 0; + const currentPlanned = + data?.data[0]?.workItem.plannedWork ?? 0; + + const plannedWorkDelta = getPlannedDelta( + previousPlanned, + currentPlanned + ); + const updatedPlannedWork = + (area.plannedWork ?? 0) + plannedWorkDelta; + const previousCompleted = previousCompletedWork; + + const currentCompleted = + data?.data[0]?.workItem.completedWork ?? 0; + const completedWorkDelta = getPlannedDelta( + previousCompleted, + currentCompleted + ); + const updatedCompletedWork = + (area.completedWork ?? 0) + completedWorkDelta; + return { + ...area, + plannedWork: updatedPlannedWork, + completedWork: updatedCompletedWork, + }; + }), + }; + }), + }; + }); + }); queryClient.setQueryData(["ProjectsList"], (projects) => { if (!projects) return projects; @@ -326,20 +384,30 @@ export const useManageTask = ({ onSuccessCallback }) => { return projects.map((project) => { if (project.id !== selectedProject) return project; - const previous = PreviousPlannedWork ?? 0; - const current = data?.data[0]?.workItem.plannedWork ?? 0; + const previousPlanned = PreviousPlannedWork ?? 0; + const currentPlanned = data?.data[0]?.workItem.plannedWork ?? 0; - const plannedWorkDelta = getPlannedDelta(previous, current); + const plannedWorkDelta = getPlannedDelta( + previousPlanned, + currentPlanned + ); const updatedPlannedWork = (project.plannedWork ?? 0) + plannedWorkDelta; - + const previousCompleted = previousCompletedWork; + const currentCompleted = data?.data[0]?.workItem.completedWork ?? 0; + const completedWorkDelta = getPlannedDelta( + previousCompleted, + currentCompleted + ); + const updatedCompletedWork = + (project.completedWork ?? 0) + completedWorkDelta; return { ...project, plannedWork: updatedPlannedWork, + completedWork: updatedCompletedWork, }; }); }); - if (onSuccessCallback) onSuccessCallback(data); }, onError: (error) => { @@ -354,16 +422,73 @@ export const useManageTask = ({ onSuccessCallback }) => { export const useDeleteProjectTask = (onSuccessCallback) => { const queryClient = useQueryClient(); - + const selectedProject = useSelector( + (store) => store.localVariables.projectId + ); return useMutation({ - mutationFn: async ({ workItemId, workAreaId }) => { + mutationFn: async ({ + workItemId, + workAreaId, + completedTask, + plannedTask, + }) => { return await ProjectRepository.deleteProjectTask(workItemId); }, onSuccess: (_, variables) => { - showToast("Task deleted successfully", "success"); + const { completedTask, plannedTask, workAreaId, buildingId, floorId } = + variables; + queryClient.invalidateQueries({ queryKey: ["WorkItems", variables.workAreaId], }); + + queryClient.setQueryData(["ProjectInfra", selectedProject], (oldData) => { + if (!oldData) return oldData; + + const { workAreaId, floorId, buildingId, plannedTask, completedTask } = + variables; + + const updatedData = JSON.parse(JSON.stringify(oldData)); + + return updatedData.map((building) => { + if (building.id !== buildingId) return building; + + return { + ...building, + floors: building.floors.map((floor) => { + if (floor.id !== floorId) return floor; + + return { + ...floor, + workAreas: floor.workAreas.map((area) => { + if (area.id !== workAreaId) return area; + + return { + ...area, + plannedWork: (area.plannedWork ?? 0) - plannedTask, + completedWork: (area.completedWork ?? 0) - completedTask, + }; + }), + }; + }), + }; + }); + }); + + queryClient.setQueryData(["ProjectsList"], (projects) => { + if (!projects) return projects; + + return projects.map((project) => { + if (project.id !== selectedProject) return project; + + return { + ...project, + plannedWork: (project.plannedWork ?? 0) - plannedTask, + completedWork: (project.completedWork ?? 0) - completedTask, + }; + }); + }); + showToast("Task deleted successfully", "success"); if (onSuccessCallback) onSuccessCallback(); }, onError: (error) => { diff --git a/src/hooks/useTasks.js b/src/hooks/useTasks.js index 7b70201c..b793ced4 100644 --- a/src/hooks/useTasks.js +++ b/src/hooks/useTasks.js @@ -78,17 +78,17 @@ export const useAuditStatus = () => { return { status, loading, error, refetch }; }; -// ---------====================Mutation================------------------ +// -----------------------Mutation------------------------ const toDate = new Date().toISOString().split("T")[0]; const dateFrom = new Date(Date.now() - 6 * 24 * 60 * 60 * 1000) .toISOString() .split("T")[0]; export const useReportTask = ({ onSuccessCallback, onErrorCallback } = {}) => { + const queryClient = useQueryClient(); const selectedProject = useSelector( (store) => store.localVariables.projectId ); - const queryClient = useQueryClient(); const { mutate, isPending, isSuccess, isError, error } = useMutation({ mutationFn: async ({ reportData, workAreaId }) => { return await TasksRepository.reportTask(reportData); @@ -138,7 +138,6 @@ export const useReportTask = ({ onSuccessCallback, onErrorCallback } = {}) => { }; }); }); - showToast("Task Reported Successfully.", "success"); if (onSuccessCallback) onSuccessCallback(data); }, diff --git a/src/pages/Activities/AttendancePage.jsx b/src/pages/Activities/AttendancePage.jsx index 4ffcebc1..f4e6c58c 100644 --- a/src/pages/Activities/AttendancePage.jsx +++ b/src/pages/Activities/AttendancePage.jsx @@ -21,13 +21,14 @@ import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { REGULARIZE_ATTENDANCE } from "../../utils/constants"; import eventBus from "../../services/eventBus"; import AttendanceRepository from "../../repositories/AttendanceRepository"; +import { useProjectName } from "../../hooks/useProjects"; const AttendancePage = () => { const [activeTab, setActiveTab] = useState("all"); const [ShowPending, setShowPending] = useState(false); const loginUser = getCachedProfileData(); var selectedProject = useSelector((store) => store.localVariables.projectId); - // const { projects, loading: projectLoading } = useProjects(); + const dispatch = useDispatch() const { attendance, loading: attLoading, @@ -38,7 +39,8 @@ const AttendancePage = () => { const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [modelConfig, setModelConfig] = useState(); const DoRegularized = useHasUserPermission(REGULARIZE_ATTENDANCE); - const dispatch = useDispatch(); + const { projectNames, loading: projectLoading, fetchData } = useProjectName(); + const [formData, setFormData] = useState({ markTime: "", @@ -130,7 +132,13 @@ const AttendancePage = () => { const handleToggle = (event) => { setShowOnlyCheckout(event.target.checked); }; + useEffect(() => { + if(selectedProject == null){ + dispatch(setProjectId(projectNames[0]?.id)); + } + },[]) + useEffect(() => { if (modelConfig !== null) { openModel(); @@ -140,18 +148,7 @@ const AttendancePage = () => { setAttendances(attendance); }, [attendance]); - // useEffect(() => { - // if (selectedProject === 1 || selectedProject === undefined) { - // dispatch(setProjectId(loginUser?.projects[0])); - // } - // }, [selectedProject, loginUser?.projects]); - // Filter attendance data based on the toggle - // const filteredAttendance = showOnlyCheckout - // ? attendances?.filter( - // (att) => att?.checkOutTime !== null && att?.checkInTime !== null - // ) - // : attendances; const filteredAttendance = ShowPending ? attendances?.filter( (att) => att?.checkInTime !== null && att?.checkOutTime === null @@ -233,7 +230,7 @@ const AttendancePage = () => {
  • - {task.reportedDate && ( + } + {(ApprovedTaskRights && task.reportedDate ) && ( )} - {type !== "dateRange" && - selectedFilters[type] && - selectedFilters[type].length > 0 && ( - - )} - - {collapsedFilters[type] ? '+' : '-'} - + {type !== "dateRange" && selectedFilters[type]?.length > 0 && ( + + )} + {collapsedFilters[type] ? '+' : '-'}
  • {!collapsedFilters[type] && ( @@ -406,31 +331,22 @@ const ImageGallery = () => {
    ) : ( - items.map((item) => { - const itemId = item[0]; - const itemName = item[1]; - const isChecked = selectedFilters[type].some( - (selectedItem) => selectedItem[0] === itemId - ); - - return ( - - ); - }) + items.map(([itemId, itemName]) => ( + + )) )} )} @@ -438,40 +354,25 @@ const ImageGallery = () => { ); return ( -
    - +
    +
    {loading && pageNumber === 1 ? ( -
    -
    -
    +
    ) : images.length > 0 ? ( images.map((batch) => { const firstDoc = batch.documents[0]; - const userName = `${firstDoc?.uploadedBy?.firstName || ""} ${firstDoc?.uploadedBy?.lastName || "" - }`.trim(); - const date = formatUTCToLocalTime(firstDoc?.uploadedAt) - - - + const userName = `${firstDoc?.uploadedBy?.firstName || ""} ${firstDoc?.uploadedBy?.lastName || ""}`.trim(); + const date = formatUTCToLocalTime(firstDoc?.uploadedAt); const showScrollButtons = batch.documents.length > SCROLL_THRESHOLD; return ( @@ -479,29 +380,15 @@ const ImageGallery = () => {
    - +
    - - {userName} - - - {date} - + {userName} + {date}
    -
    -
    - {batch.buildingName} > {batch.floorName} >{" "} - {batch.workAreaName || "Unknown"} >{" "} - {batch.activityName} -
    +
    {batch.buildingName} > {batch.floorName} > {batch.workAreaName || "Unknown"} > {batch.activityName}
    {batch.workCategoryName && (
    @@ -511,107 +398,53 @@ const ImageGallery = () => { )}
    -
    - {showScrollButtons && ( - - )} -
    (imageGroupRefs.current[batch.batchId] = el)} - > + {showScrollButtons && } +
    (imageGroupRefs.current[batch.batchId] = el)}> {batch.documents.map((doc, idx) => { const hoverDate = moment(doc.uploadedAt).format("DD-MM-YYYY"); const hoverTime = moment(doc.uploadedAt).format("hh:mm A"); return ( -
    - openModal() - } - onMouseEnter={() => setHoveredImage(doc)} - onMouseLeave={() => setHoveredImage(null)} - > +
    openModal()} onMouseEnter={() => setHoveredImage(doc)} onMouseLeave={() => setHoveredImage(null)}>
    {`Image
    {hoveredImage === doc && (
    -

    - Date: {hoverDate} -

    -

    - Time: {hoverTime} -

    -

    - Activity: {batch.activityName} -

    +

    Date: {hoverDate}

    +

    Time: {hoverTime}

    +

    Activity: {batch.activityName}

    )}
    ); })}
    - {showScrollButtons && ( - - )} + {showScrollButtons && }
    ); }) ) : ( - !loading &&

    - No images match the selected filters. -

    + !loading &&

    No images match the selected filters.

    )} -
    {loadingMore && hasMore &&
    } - {!hasMore && !loading && images.length > 0 && ( -

    You've reached the end of the images.

    - )} + {!hasMore && !loading && images.length > 0 &&

    You've reached the end of the images.

    }
    -
    +
    -
    - Filters -
    - +
    Filters
    +
    - - -
    + + +
    {renderFilterCategory("Date Range", [], "dateRange")} {renderFilterCategory("Building", buildings, "building")} @@ -620,12 +453,10 @@ const ImageGallery = () => { {renderFilterCategory("Activity", activities, "activity")} {renderFilterCategory("Uploaded By (User)", uploadedByUsers, "uploadedBy")} {renderFilterCategory("Work Category", workCategories, "workCategory")} - -
    ); }; -export default ImageGallery; \ No newline at end of file +export default ImageGallery; diff --git a/src/pages/Gallary/ImagePop.css b/src/pages/Gallary/ImagePop.css index dfdb549c..367758f6 100644 --- a/src/pages/Gallary/ImagePop.css +++ b/src/pages/Gallary/ImagePop.css @@ -1,103 +1,149 @@ +/* Image Modal Overlay */ .image-modal-overlay { position: fixed; top: 0; left: 0; - z-index: 9999; /* High z-index to ensure it's on top */ + z-index: 9999; width: 100%; height: 100%; - background-color: rgba(0, 0, 0, 0.85); /* Dark semi-transparent background */ + background-color: rgba(0, 0, 0, 0.85); display: flex; justify-content: center; align-items: center; } +/* Main Modal Content Box */ .image-modal-content { background: #fff; padding: 24px; - max-width: 90%; /* Responsive max-width */ - max-height: 100%; /* Responsive max-height */ + max-width: 50%; + max-height: 95vh; /* Limits the modal's height to 95% of viewport height */ border-radius: 12px; position: relative; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); text-align: center; - display: flex; /* Use flexbox for internal layout */ + display: flex; flex-direction: column; align-items: center; justify-content: center; + overflow-y: auto; /* Enables vertical scrolling */ + + /* --- HIDE SCROLLBAR FOR MAIN MODAL CONTENT --- */ + /* For Webkit browsers (Chrome, Safari, Edge) */ + &::-webkit-scrollbar { + width: 0px; /* Hide vertical scrollbar */ + height: 0px; /* Hide horizontal scrollbar, though unlikely needed here */ + } + /* For Firefox */ + scrollbar-width: none; /* Hide scrollbar in Firefox */ + /* For Internet Explorer and Edge (legacy) */ + -ms-overflow-style: none; + /* --- END HIDE SCROLLBAR --- */ } +/* Image Styles */ .modal-image { max-width: 100%; - max-height: 70vh; /* Limits image height to 70% of viewport height */ + max-height: 70vh; + width: auto; border-radius: 10px; - object-fit: contain; /* Ensures the whole image is visible without cropping */ + object-fit: contain; margin-bottom: 20px; - flex-shrink: 0; /* Prevent image from shrinking if content is too large */ + flex-shrink: 0; } -.image-details { +/* Scrollable Container for Text Details */ +.image-details-scroll-container { + width: 100%; + flex-grow: 1; + max-height: calc(95vh - 70vh - (24px * 2) - 20px); /* Approximate calculation for text area height */ + overflow-y: auto; /* Enables vertical scrolling for details */ text-align: left; + padding-right: 5px; /* Add some padding so text doesn't touch the hidden scrollbar area */ + + /* --- HIDE SCROLLBAR FOR TEXT DETAILS SECTION --- */ + /* For Webkit browsers (Chrome, Safari, Edge) */ + &::-webkit-scrollbar { + width: 0px; /* Hide vertical scrollbar */ + height: 0px; /* Hide horizontal scrollbar */ + } + /* For Firefox */ + scrollbar-width: none; /* Hide scrollbar in Firefox */ + /* For Internet Explorer and Edge (legacy) */ + -ms-overflow-style: none; + /* --- END HIDE SCROLLBAR --- */ +} + +/* Image Details Section (inside the scroll container) */ +.image-details { color: #444; font-size: 14px; line-height: 1.4; margin: 0; padding: 0; - width: 100%; /* Ensure details section takes full width */ + width: 100%; + text-align: left; } .image-details p { - margin: 4px 0; /* Reduce vertical space between lines in details */ + margin: 4px 0; + white-space: normal; + word-wrap: break-word; + overflow-wrap: break-word; + text-overflow: initial; + text-align: left; } +/* Close Button */ .close-button { position: absolute; - top: 1px; /* Position relative to the modal content */ + top: 1px; right: 8px; font-size: 30px; background: none; border: none; - color: black; /* White color for visibility on dark overlay */ + color: black; cursor: pointer; padding: 0; line-height: 1; - z-index: 10000; /* Ensure it's above everything else */ + z-index: 10000; } -/* Styles for the navigation buttons */ +/* Navigation Buttons */ .nav-button { position: absolute; - top: 50%; /* Vertically center them */ - transform: translateY(-50%); /* Adjust for perfect vertical centering */ - background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent background */ + top: 50%; + transform: translateY(-50%); + background-color: rgba(0, 0, 0, 0.5); color: white; border: none; padding: 10px 15px; font-size: 30px; cursor: pointer; - z-index: 1000; /* Ensure buttons are above the image */ - border-radius: 50%; /* Make them circular */ + z-index: 1000; + border-radius: 50%; width: 50px; height: 50px; display: flex; align-items: center; justify-content: center; - transition: background-color 0.3s ease; /* Smooth hover effect */ + transition: background-color 0.3s ease; } .nav-button:hover { - background-color: rgba(0, 0, 0, 0.8); /* Darker on hover */ + background-color: rgba(0, 0, 0, 0.8); } .nav-button.prev-button { - left: 0px; /* Position left arrow */ + left: 0px; } .nav-button.next-button { - right: 0px; /* Position right arrow */ + right: 0px; } -/* Style for disabled buttons (optional) */ +/* Disabled Button Style */ .nav-button:disabled { opacity: 0.5; cursor: not-allowed; -} +} \ No newline at end of file diff --git a/src/pages/authentication/ChangePassword.jsx b/src/pages/authentication/ChangePassword.jsx index 5191d6c3..a501c5eb 100644 --- a/src/pages/authentication/ChangePassword.jsx +++ b/src/pages/authentication/ChangePassword.jsx @@ -69,22 +69,17 @@ const ChangePasswordPage = ({ onClose }) => { className="modal d-flex align-items-center justify-content-center show" tabIndex="-1" role="dialog" - style={{ display: "flex", backgroundColor: "rgba(0,0,0,0.5)" }} + style={{ backgroundColor: "rgba(0,0,0,0.5)" }} >
    -
    - {" "} +
    -
    - -
    - {/* Close Button */}
    Change Password

    @@ -119,7 +114,6 @@ const ChangePasswordPage = ({ onClose }) => { )}

    - {/*
    */}
    @@ -180,14 +174,14 @@ const ChangePasswordPage = ({ onClose }) => {
    )}
    - {/*
    */} -
    + +
    Your password must have at least 8 characters and include a lower - case latter, an uppercase letter, a number, and a special + case letter, an uppercase letter, a number, and a special character.
    {/* Action Buttons */} -
    +
    diff --git a/src/pages/employee/AttendancesEmployeeRecords.jsx b/src/pages/employee/AttendancesEmployeeRecords.jsx index 1908ef21..bac57a09 100644 --- a/src/pages/employee/AttendancesEmployeeRecords.jsx +++ b/src/pages/employee/AttendancesEmployeeRecords.jsx @@ -85,7 +85,7 @@ const AttendancesEmployeeRecords = ({ employee }) => { const currentDate = new Date().toLocaleDateString("en-CA"); const { currentPage, totalPages, currentItems, paginate } = usePagination( sortedFinalList, - 10 + 20 ); useEffect(() => { @@ -141,13 +141,12 @@ const AttendancesEmployeeRecords = ({ employee }) => { id="DataTables_Table_0_length" >
    - +
    setIsRefreshing(!isRefreshing)} @@ -224,7 +223,7 @@ const AttendancesEmployeeRecords = ({ employee }) => { )}
    - {!loading && data.length > 5 && ( + {!loading && sortedFinalList.length > 20 && (
    - - {/* Add Employee Button */} - {Manage_Employee && ( - - )} -
    -
    - - - - - - - - - - - - - - - - {loading && ( - - - - )} - {/* Conditional messages for no data or no search results */} - {!loading && displayData?.length === 0 && searchText && !showAllEmployees ? ( - - - - ) : null} - {!loading && displayData?.length === 0 && (!searchText || showAllEmployees) ? ( - - + Export + + + + + {/* Add Employee Button */} + {Manage_Employee && ( + + )} + + + +
    -
    Name
    -
    -
    Email
    -
    -
    Contact
    -
    -
    Role
    -
    - Joining Date - - Status - - Actions -
    -

    Loading...

    -
    - - '{searchText}' employee not found - {" "} -
    +
    + + + + + + + + + + - ) : null} - - {/* Render current items */} - {currentItems && !loading && currentItems.map((item) => ( - - - - - - - + + {loading && ( + + - + )} + {/* Conditional messages for no data or no search results */} + {!loading && + displayData?.length === 0 && + searchText && + !showAllEmployees ? ( + + - {Manage_Employee && ( - + + + ) : null} + + {/* Render current items */} + {currentItems && + !loading && + currentItems.map((item) => ( + + - )} - - ))} - -
    +
    Name
    +
    +
    Email
    +
    +
    Contact
    +
    +
    Role
    +
    + Joining Date + + Status + + Actions +
    - - - {item.email ? ( - - - {item.email} - - ) : ( - - NA - - )} - - - - {item.phoneNumber} - - - - - {item.jobRole || "Not Assign Yet"} - - - {moment(item.joiningDate)?.format("DD-MMM-YYYY")} +
    +

    Loading...

    - {showInactive ? ( - - Inactive - - ) : ( - - Active - - )} +
    + + '{searchText}' employee not found + {" "} -
    - -
    -
    + No Data Found +
    +
    + + {item.email ? ( + + + {item.email} + + ) : ( + + NA + + )} + + + + + {item.phoneNumber} + + + + + + {item.jobRole || "Not Assign Yet"} + + -
    + + {moment(item.joiningDate)?.format("DD-MMM-YYYY")} + + + {showInactive ? ( + + Inactive + + ) : ( + + Active + + )} + + {Manage_Employee && ( + +
    + +
    + + + {!item.isSystem && ( + <> + + + + )} +
    +
    + + )} + + ))} + + - {/* Pagination */} - {!loading && displayData.length > ITEMS_PER_PAGE && ( - + )} +
    -
    + ) : ( +
    +
    + +

    + Access Denied: You don't have permission to perform this action. + ! +

    +
    +
    + )}
    ); }; -export default EmployeeList; \ No newline at end of file +export default EmployeeList; diff --git a/src/pages/project/ProjectDetails.jsx b/src/pages/project/ProjectDetails.jsx index f34586aa..ff1be7bb 100644 --- a/src/pages/project/ProjectDetails.jsx +++ b/src/pages/project/ProjectDetails.jsx @@ -1,11 +1,9 @@ -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"; import ProjectOverview from "../../components/Project/ProjectOverview"; import AboutProject from "../../components/Project/AboutProject"; import ProjectNav from "../../components/Project/ProjectNav"; -import ProjectBanner from "../../components/Project/ProjectBanner"; import Teams from "../../components/Project/Teams"; import ProjectInfra from "../../components/Project/ProjectInfra"; import Loader from "../../components/common/Loader"; @@ -16,172 +14,116 @@ import { clearCacheKey, getCachedData, } from "../../slices/apiDataManager"; -import ProjectRepository from "../../repositories/ProjectRepository"; -import { ActivityeRepository } from "../../repositories/MastersRepository"; import "./ProjectDetails.css"; import { - useEmployeesByProjectAllocated, useProjectDetails, } from "../../hooks/useProjects"; -import { useDispatch } from "react-redux"; -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(); - const { - projects_Details, - loading: projectLoading, - error: ProjectError, - refetch - } = useProjectDetails(projectId); - 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); - // } - // }; + + 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 ; - switch (activePill) { - case "profile": { - return ( -
    -
    - -
    -
    - -
    -
    - ); - } - case "teams": { - return ( -
    -
    - {/* Teams */} - - {/* Teams */} -
    -
    - ); - break; - } - case "infra": { - return ( - - ); - break; - } - case "workplan": { - return ( - - ); - break; - } - case "directory": { - return ( -
    - -
    - ); - } - - default: - return ; - } - }; - - useEffect(() => { - dispatch(setProjectId(projectId)); - - }, [projects_Details, projectId]); 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 ; + + switch (activePill) { + case "profile": + return ( + <> +
    +
    + + +
    +
    + +
    +
    + + ); + + case "teams": + return ( +
    +
    + +
    +
    + ); + + case "infra": + return ( + + ); + + case "workplan": + return ( + + ); + + case "directory": + return ( +
    + +
    + ); + + default: + return ; + } + }; + return ( - <> - {} -
    - +
    + -
    - {projectLoading &&

    Loading....

    } - {/* {!projectLoading && project && ( - - )} */} - - -
    - -
    - - {renderContent()} +
    +
    - + + {renderContent()} +
    ); }; -export default ProjectDetails; +export default ProjectDetails; \ No newline at end of file diff --git a/src/pages/project/ProjectList.jsx b/src/pages/project/ProjectList.jsx index 7733a60a..75b8f1f4 100644 --- a/src/pages/project/ProjectList.jsx +++ b/src/pages/project/ProjectList.jsx @@ -5,11 +5,6 @@ import Breadcrumb from "../../components/common/Breadcrumb"; import ProjectRepository from "../../repositories/ProjectRepository"; import { useProjects, useCreateProject } from "../../hooks/useProjects"; import showToast from "../../services/toastService"; -// import { -// getCachedData, -// cacheData, -// clearCacheKey, -// } from "../../slices/apiDataManager"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useProfile } from "../../hooks/useProfile"; import { ITEMS_PER_PAGE, MANAGE_PROJECT } from "../../utils/constants"; @@ -20,11 +15,15 @@ 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([]); @@ -75,6 +74,10 @@ const ProjectList = () => { }; useEffect(() => { + if(selectedProject == null){ + dispatch(setProjectId(projects[0]?.id)); + } + if (!loading && projects) { sortingProject(projects); } diff --git a/src/pages/project/ProjectListView.jsx b/src/pages/project/ProjectListView.jsx index 9deead71..a9ffc60d 100644 --- a/src/pages/project/ProjectListView.jsx +++ b/src/pages/project/ProjectListView.jsx @@ -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 }) => { navigate(`/projects/${projectInfo.id}`)} + onClick={() => navigate(`/projects/details`)} > {projectInfo.shortName ? `${projectInfo.name} (${projectInfo.shortName})` @@ -162,7 +162,7 @@ const ProjectListView = ({ projectData, recall }) => { navigate(`/projects/${projectInfo.id}`)} + onClick={() => navigate(`/projects/details`)} > View details @@ -193,4 +193,4 @@ const ProjectListView = ({ projectData, recall }) => { ); }; -export default ProjectListView; +export default ProjectListView; \ No newline at end of file diff --git a/src/pages/Gallary/ImageGalleryAPI.jsx b/src/repositories/ImageGalleryAPI.jsx similarity index 72% rename from src/pages/Gallary/ImageGalleryAPI.jsx rename to src/repositories/ImageGalleryAPI.jsx index 9ca09bfe..5f75dbed 100644 --- a/src/pages/Gallary/ImageGalleryAPI.jsx +++ b/src/repositories/ImageGalleryAPI.jsx @@ -1,9 +1,8 @@ -import { api } from "../../utils/axiosClient"; +import { api } from "../utils/axiosClient"; export const ImageGalleryAPI = { ImagesGet: (projectId, filter, pageNumber, pageSize) => { const payloadJsonString = JSON.stringify(filter); - // Corrected API endpoint with pagination parameters return api.get(`/api/image/images/${projectId}?filter=${payloadJsonString}&pageNumber=${pageNumber}&pageSize=${pageSize}`); }, -}; \ No newline at end of file +}; diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index 7c364ca9..5ce5b6d4 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -63,7 +63,7 @@ const router = createBrowserRouter( { path: "/", element: }, { path: "/dashboard", element: }, { path: "/projects", element: }, - { path: "/projects/:projectId", element: }, + { path: "/projects/details", element: }, { path: "/project/manage/:projectId", element: }, { path: "/employees", element: }, { path: "/employee/:employeeId", element: }, diff --git a/src/slices/localVariablesSlice.jsx b/src/slices/localVariablesSlice.jsx index ae0f104c..9bd787c6 100644 --- a/src/slices/localVariablesSlice.jsx +++ b/src/slices/localVariablesSlice.jsx @@ -5,7 +5,7 @@ const localVariablesSlice = createSlice({ initialState: { selectedMaster:"Application Role", regularizationCount:0, - projectId: "", + projectId: null, reload:false }, diff --git a/src/utils/constants.jsx b/src/utils/constants.jsx index fca560af..a8dc1c2d 100644 --- a/src/utils/constants.jsx +++ b/src/utils/constants.jsx @@ -11,6 +11,11 @@ export const VIEW_PROJECTS = "6ea44136-987e-44ba-9e5d-1cf8f5837ebc" export const MANAGE_EMPLOYEES = "a97d366a-c2bb-448d-be93-402bd2324566" +export const VIEW_ALL_EMPLOYEES = "60611762-7f8a-4fb5-b53f-b1139918796b" + +export const VIEW_TEAM_MEMBERS = "b82d2b7e-0d52-45f3-997b-c008ea460e7f" + + export const MANAGE_PROJECT_INFRA = "cf2825ad-453b-46aa-91d9-27c124d63373" export const VIEW_PROJECT_INFRA = "8d7cc6e3-9147-41f7-aaa7-fa507e450bd4" @@ -24,6 +29,8 @@ export const INFRASTRUCTURE = "9666de86-d7c7-4d3d-acaa-fcd6d6b81f3c"; export const MANAGE_TASK = "08752f33-3b29-4816-b76b-ea8a968ed3c5" +export const APPROVE_TASK = "db4e40c5-2ba9-4b6d-b8a6-a16a250ff99c" + export const VIEW_TASK = "9fcc5f87-25e3-4846-90ac-67a71ab92e3c" export const ASSIGN_REPORT_TASK = "6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2" diff --git a/src/utils/dateUtils.jsx b/src/utils/dateUtils.jsx index 0105d4aa..4d6643b6 100644 --- a/src/utils/dateUtils.jsx +++ b/src/utils/dateUtils.jsx @@ -69,4 +69,13 @@ export const formatNumber = (num) => { }; export const formatUTCToLocalTime = (datetime) =>{ return moment.utc(datetime).local().format("MMMM DD, YYYY [at] hh:mm A"); +} + +export const getCompletionPercentage = (completedWork, plannedWork)=> { + if (!plannedWork || plannedWork === 0) return 0; + + const percentage = (completedWork / plannedWork) * 100; + const clamped = Math.min(Math.max(percentage, 0), 100); + + return clamped.toFixed(2); } \ No newline at end of file