diff --git a/public/assets/img/orglogo.png b/public/assets/img/orglogo.png new file mode 100644 index 00000000..51ce9299 Binary files /dev/null and b/public/assets/img/orglogo.png differ diff --git a/public/assets/vendor/css/core.css b/public/assets/vendor/css/core.css index 9be75669..48163eb5 100644 --- a/public/assets/vendor/css/core.css +++ b/public/assets/vendor/css/core.css @@ -18613,6 +18613,10 @@ li:not(:first-child) .dropdown-item, min-height: 70vh !important; } +.modal-min-h{ + min-height: 60vh !important; +} + .flex-fill { flex: 1 1 auto !important; } diff --git a/src/components/Activities/InfraPlanning.jsx b/src/components/Activities/InfraPlanning.jsx index 0e9c9590..5dce91c4 100644 --- a/src/components/Activities/InfraPlanning.jsx +++ b/src/components/Activities/InfraPlanning.jsx @@ -1,4 +1,3 @@ - import React, { useState, useEffect } from "react"; import "../../components/Project/ProjectInfra.css"; import BuildingModel from "../Project/Infrastructure/BuildingModel"; @@ -8,25 +7,33 @@ import WorkAreaModel from "../Project/Infrastructure/WorkAreaModel"; import TaskModel from "../Project/Infrastructure/TaskModel"; import ProjectRepository from "../../repositories/ProjectRepository"; import Breadcrumb from "../../components/common/Breadcrumb"; -import {useProjectDetails, useProjectInfra, useProjects} from "../../hooks/useProjects"; -import {useHasUserPermission} from "../../hooks/useHasUserPermission"; -import {APPROVE_TASK, ASSIGN_REPORT_TASK, MANAGE_PROJECT_INFRA} from "../../utils/constants"; -import {useDispatch, useSelector} from "react-redux"; -import {useProfile} from "../../hooks/useProfile"; -import {refreshData, setProjectId} from "../../slices/localVariablesSlice"; +import { + useCurrentService, + useProjectDetails, + useProjectInfra, + useProjects, +} from "../../hooks/useProjects"; +import { useHasUserPermission } from "../../hooks/useHasUserPermission"; +import { + APPROVE_TASK, + ASSIGN_REPORT_TASK, + MANAGE_PROJECT_INFRA, +} from "../../utils/constants"; +import { useDispatch, useSelector } from "react-redux"; +import { useProfile } from "../../hooks/useProfile"; +import { refreshData, setProjectId } from "../../slices/localVariablesSlice"; import InfraTable from "../Project/Infrastructure/InfraTable"; import { useSelectedProject } from "../../slices/apiDataManager"; import Loader from "../common/Loader"; - - - const InfraPlanning = () => { const { profile: LoggedUser, refetch: fetchData } = useProfile(); const dispatch = useDispatch(); const selectedProject = useSelectedProject(); + const selectedService = useCurrentService(); - const { projectInfra, isLoading, isError, error, isFetched } = useProjectInfra(selectedProject); + const { projectInfra, isLoading, isError, error, isFetched } = + useProjectInfra(selectedProject, selectedService || "" ); const canManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA); const canApproveTask = useHasUserPermission(APPROVE_TASK); @@ -63,9 +70,9 @@ const InfraPlanning = () => { return (
-
-
- +
+
+
@@ -73,4 +80,3 @@ const InfraPlanning = () => { }; export default InfraPlanning; - diff --git a/src/components/DailyProgressRport/TaskReportFilterPanel.jsx b/src/components/DailyProgressRport/TaskReportFilterPanel.jsx new file mode 100644 index 00000000..003339b4 --- /dev/null +++ b/src/components/DailyProgressRport/TaskReportFilterPanel.jsx @@ -0,0 +1,96 @@ +import React, { useState } from "react"; +import { useCurrentService, useProjectInfra } from "../../hooks/useProjects"; +import { useSelectedProject } from "../../slices/apiDataManager"; +import { FormProvider, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { + TaskReportDefaultValue, + TaskReportFilterSchema, +} from "./TaskRportScheam"; +import { DateRangePicker1 } from "../common/DateRangePicker"; +import SelectMultiple from "../common/SelectMultiple"; +import { localToUtc } from "../../utils/appUtils"; + +const TaskReportFilterPanel = ({ handleFilter }) => { + const [resetKey, setResetKey] = useState(0); + const selectedProjec = useSelectedProject(); + const selectedService = useCurrentService(); + const { projectInfra, isLoading, error, isFetched } = useProjectInfra( + selectedProjec, + selectedService + ); + const methods = useForm({ + resolver: zodResolver(TaskReportFilterSchema), + defaultValues: TaskReportDefaultValue, + }); + + const { + register, + reset, + handleSubmit, + formState: { errors }, + } = methods; + + const onSubmit = (formData) => { + console.log(formData) + const filterPayload = { + startDate:localToUtc(formData.startDate), + endDate:localToUtc(formData.endDate) + + } + handleFilter(filterPayload); + }; + + const onClear =()=>{ + setResetKey((prev) => prev + 1); + handleFilter(TaskReportDefaultValue) + reset(TaskReportDefaultValue) + } + + return ( + +
+
+ + +
+ + {/*
+ +
+
+ +
*/} + +
+ + +
+
+
+ ); +}; + +export default TaskReportFilterPanel; diff --git a/src/components/DailyProgressRport/TaskReportList.jsx b/src/components/DailyProgressRport/TaskReportList.jsx new file mode 100644 index 00000000..27323b51 --- /dev/null +++ b/src/components/DailyProgressRport/TaskReportList.jsx @@ -0,0 +1,301 @@ +import React, { useState, useEffect, useMemo } from "react"; +import { useTaskList } from "../../hooks/useTasks"; +import { useSelectedProject } from "../../slices/apiDataManager"; +import { useProjectName } from "../../hooks/useProjects"; +import DailyProgrssReport, { + useDailyProgrssContext, +} from "../../pages/DailyProgressReport/DailyProgrssReport"; +import { useDispatch } from "react-redux"; +import { setProjectId } from "../../slices/localVariablesSlice"; +import { + APPROVE_TASK, + ASSIGN_REPORT_TASK, + ITEMS_PER_PAGE, +} from "../../utils/constants"; +import { formatNumber, formatUTCToLocalTime } from "../../utils/dateUtils"; +import { useHasUserPermission } from "../../hooks/useHasUserPermission"; +import Pagination from "../common/Pagination"; +import { TaskReportListSkeleton } from "./TaskRepprtListSkeleton"; +import HoverPopup from "../common/HoverPopup"; + +const TaskReportList = () => { + const [currentPage, setCurrentPage] = useState(1); + const [filters, setFilters] = useState({ + selectedBuilding: "", + selectedFloors: [], + selectedActivities: [], + }); + const dispatch = useDispatch(); + const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK); + const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK); + + const { service, openModal, closeModal,filter } = useDailyProgrssContext(); + const selectedProject = useSelectedProject(); + const { projectNames } = useProjectName(); + + const { data, isLoading, isError, error } = useTaskList( + selectedProject, + ITEMS_PER_PAGE, + currentPage, + service,filter + ); + + const ProgrssReportColumn = [ + { + key: "activity", + label: "Activity", + getValue: (task) => task.workItem.activityMaster?.activityName || "N/A", + align: "text-start", + }, + { + key: "assigned", + label: "Total Assigned", + getValue: (task) => task.plannedTask ?? "N/A", + align: "text-start", + }, + { + key: "completed", + label: "Completed", + getValue: (task) => task.completedTask ?? "N/A", + align: "text-start", + }, + { + key: "assignAt", + label: "Assign Date", + getValue: (task) => + task.assignmentDate ? formatUTCToLocalTime(task.assignmentDate) : "N/A", + align: "text-start", + }, + { + key: "team", + label: "Team", + getValue: (task) => + task.teamMembers?.map((m) => `${m.firstName} ${m.lastName}`).join(", ") || + "N/A", + align: "text-start", + }, + ]; + + const paginate = (page) => { + if (page >= 1 && page <= (data?.totalPages ?? 1)) { + setCurrentPage(page); + } + }; + + useEffect(() => { + if (!selectedProject && projectNames.length > 0) { + dispatch(setProjectId(projectNames[0].id)); + } + }, [selectedProject, projectNames, dispatch]); + + useEffect(() => { + setFilters({ + selectedBuilding: "", + selectedFloors: [], + selectedActivities: [], + }); + }, [selectedProject]); + + // Filter and Group wise data + + const filteredTasks = useMemo(() => { + if (!data?.data) return []; + return data?.data.filter((task) => { + const { selectedBuilding, selectedFloors, selectedActivities } = filters; + + if ( + selectedBuilding && + task?.workItem?.workArea?.floor?.building?.name !== selectedBuilding + ) + return false; + if ( + selectedFloors.length > 0 && + !selectedFloors.includes(task?.workItem?.workArea?.floor?.floorName) + ) + return false; + if ( + selectedActivities.length > 0 && + !selectedActivities.includes( + task?.workItem?.activityMaster?.activityName + ) + ) + return false; + + return true; + }); + }, [data?.data, filters, currentPage]); + + const groupedTasks = useMemo(() => { + const groups = {}; + filteredTasks.forEach((task) => { + const date = task.assignmentDate.split("T")[0]; + if (!groups[date]) groups[date] = []; + groups[date].push(task); + }); + return Object.keys(groups) + .sort((a, b) => new Date(b) - new Date(a)) + .map((date) => ({ date, tasks: groups[date] })); + }, [filteredTasks, paginate, currentPage, selectedProject]); + + const renderTeamMembers = (task, refIndex) => ( +
+ ${task.teamMembers + .map( + (m) => ` +
+
+ + ${m?.firstName?.charAt(0) || ""}${m?.lastName?.charAt(0) || "" + } + +
+ ${m.firstName} ${m.lastName} +
` + ) + .join("")} +
+ `} + > + {task.teamMembers.slice(0, 3).map((m) => ( +
+ + {m?.firstName.slice(0, 1)} + +
+ ))} + {task.teamMembers.length > 3 && ( +
+ + +{task.teamMembers.length - 3} + +
+ )} +
+ ); + + if (isLoading) return ; + if (isError) return
Loading....
; + return ( +
+ + + + + + + + + + + + + {groupedTasks.length === 0 && ( + + + + )} + + {groupedTasks.map(({ date, tasks }) => ( + + + + + {tasks.map((task, idx) => ( + + + + + + + + + ))} + + ))} + +
Activity + + Total Pending{" "} + This shows the total pending tasks for each activity on that date.

} + > + +
+
+
+ + Reported/Planned{" "} + This shows the reported versus planned tasks for each activity on that date.

} + > + +
+
+
Assign DateTeamActions
+ No reports available +
+ {formatUTCToLocalTime(date)} +
+
+ {task.workItem.activityMaster?.activityName || "No Activity Name"} +
+
+ {task.workItem.workArea?.floor?.building?.name} ›{" "} + {task.workItem.workArea?.floor?.floorName} ›{" "} + {task.workItem.workArea?.areaName} +
+
+ {formatNumber(task.workItem.plannedWork)} + {`${formatNumber(task.completedTask)} / ${formatNumber(task.plannedTask)}`}{formatUTCToLocalTime(task.assignmentDate)}{renderTeamMembers(task, idx)} +
+ {ReportTaskRights && !task.reportedDate && ( + + )} + {ApprovedTaskRights && task.reportedDate && !task.approvedBy && ( + + )} + +
+
+ {data?.data?.length > 0 && ( + + )} +
+ ); +}; + +export default TaskReportList; diff --git a/src/components/DailyProgressRport/TaskRepprtListSkeleton.jsx b/src/components/DailyProgressRport/TaskRepprtListSkeleton.jsx new file mode 100644 index 00000000..5580e924 --- /dev/null +++ b/src/components/DailyProgressRport/TaskRepprtListSkeleton.jsx @@ -0,0 +1,62 @@ + +const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => ( +
+); +export const TaskReportListSkeleton = () => { + const skeletonRows = 8; // Number of placeholder rows + + return ( +
+ + + + + + + + + + + + + {[...Array(skeletonRows)].map((_, idx) => ( + + + + + + + + + ))} + +
ActivityAssignedCompletedAssign OnTeamActions
+ + + + + + + + + +
+ {[...Array(3)].map((_, i) => ( + + ))} +
+
+
+ + +
+
+
+ ); +}; diff --git a/src/components/DailyProgressRport/TaskRportScheam.jsx b/src/components/DailyProgressRport/TaskRportScheam.jsx new file mode 100644 index 00000000..11a79e6f --- /dev/null +++ b/src/components/DailyProgressRport/TaskRportScheam.jsx @@ -0,0 +1,15 @@ +import { z } from "zod"; + +export const TaskReportFilterSchema = z.object({ + // buildingIds: z.array(z.string()).optional(), + // floorIds: z.array(z.string()).optional(), + startDate: z.string().optional(), + endDate: z.string().optional(), +}); + +export const TaskReportDefaultValue = { + // buildingIds:[], + // floorIds:[], + startDate:null, + endDate:null +} \ No newline at end of file diff --git a/src/components/Dashboard/Activity.jsx b/src/components/Dashboard/Activity.jsx index a1f9c5fc..78483ed5 100644 --- a/src/components/Dashboard/Activity.jsx +++ b/src/components/Dashboard/Activity.jsx @@ -1,194 +1,194 @@ -import React, { useState, useEffect } from "react"; -import LineChart from "../Charts/LineChart"; -import { useProjects } from "../../hooks/useProjects"; -import { useDashboard_ActivityData } from "../../hooks/useDashboard_Data"; -import ApexChart from "../Charts/Circlechart"; +// import React, { useState, useEffect } from "react"; +// import LineChart from "../Charts/LineChart"; +// import { useProjects } from "../../hooks/useProjects"; +// import { useDashboard_ActivityData } from "../../hooks/useDashboard_Data"; +// import ApexChart from "../Charts/Circlechart"; -const LOCAL_STORAGE_PROJECT_KEY = "selectedActivityProjectId"; +// const LOCAL_STORAGE_PROJECT_KEY = "selectedActivityProjectId"; -const Activity = () => { - const { projects } = useProjects(); - const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD - const [selectedDate, setSelectedDate] = useState(today); - const storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY); - const initialProjectId = storedProjectId || "all"; - const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId); - const [displayedProjectName, setDisplayedProjectName] = useState("Select Project"); - const [activeTab, setActiveTab] = useState("all"); +// const Activity = () => { +// const { projects } = useProjects(); +// const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD +// const [selectedDate, setSelectedDate] = useState(today); +// const storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY); +// const initialProjectId = storedProjectId || "all"; +// const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId); +// const [displayedProjectName, setDisplayedProjectName] = useState("Select Project"); +// const [activeTab, setActiveTab] = useState("all"); - const { dashboard_Activitydata: ActivityData, isLoading, error: isError } = - useDashboard_ActivityData(selectedDate, selectedProjectId); +// const { dashboard_Activitydata: ActivityData, isLoading, error: isError } = +// useDashboard_ActivityData(selectedDate, selectedProjectId); - useEffect(() => { - if (selectedProjectId === "all") { - setDisplayedProjectName("All Projects"); - } else if (projects) { - const foundProject = projects.find((p) => p.id === selectedProjectId); - setDisplayedProjectName(foundProject ? foundProject.name : "Select Project"); - } else { - setDisplayedProjectName("Select Project"); - } +// useEffect(() => { +// if (selectedProjectId === "all") { +// setDisplayedProjectName("All Projects"); +// } else if (projects) { +// const foundProject = projects.find((p) => p.id === selectedProjectId); +// setDisplayedProjectName(foundProject ? foundProject.name : "Select Project"); +// } else { +// setDisplayedProjectName("Select Project"); +// } - localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId); - }, [selectedProjectId, projects]); +// localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId); +// }, [selectedProjectId, projects]); - const handleProjectSelect = (projectId) => { - setSelectedProjectId(projectId); - }; +// const handleProjectSelect = (projectId) => { +// setSelectedProjectId(projectId); +// }; - const handleDateChange = (e) => { - setSelectedDate(e.target.value); - }; +// const handleDateChange = (e) => { +// setSelectedDate(e.target.value); +// }; - return ( -
-
-
-
-
Activity
-

Activity Progress Chart

-
+// return ( +//
+//
+//
+//
+//
Activity
+//

Activity Progress Chart

+//
-
- -
    -
  • - -
  • - {projects?.map((project) => ( -
  • - -
  • - ))} -
-
-
-
+//
+// +//
    +//
  • +// +//
  • +// {projects?.map((project) => ( +//
  • +// +//
  • +// ))} +//
+//
+//
+//
- {/* ✅ Date Picker Aligned Left with Padding */} -
-
- -
-
+// {/* ✅ Date Picker Aligned Left with Padding */} +//
+//
+// +//
+//
- {/* Tabs */} -
    -
  • - -
  • -
  • - -
  • -
+// {/* Tabs */} +//
    +//
  • +// +//
  • +//
  • +// +//
  • +//
-
- {activeTab === "all" && ( -
-
- {isLoading ? ( -

Loading activity data...

- ) : isError ? ( -

No data available.

- ) : ( - ActivityData && ( - <> -
- Allocated Task -
-

- {ActivityData.totalCompletedWork?.toLocaleString()}/ - {ActivityData.totalPlannedWork?.toLocaleString()} -

- Completed / Assigned -
- -
- - ) - )} -
+//
+// {activeTab === "all" && ( +//
+//
+// {isLoading ? ( +//

Loading activity data...

+// ) : isError ? ( +//

No data available.

+// ) : ( +// ActivityData && ( +// <> +//
+// Allocated Task +//
+//

+// {ActivityData.totalCompletedWork?.toLocaleString()}/ +// {ActivityData.totalPlannedWork?.toLocaleString()} +//

+// Completed / Assigned +//
+// +//
+// +// ) +// )} +//
-
- {!isLoading && !isError && ActivityData && ( - <> -
- Activities -
-

- {ActivityData.totalCompletedWork?.toLocaleString()}/ - {ActivityData.totalPlannedWork?.toLocaleString()} -

- Pending / Assigned -
- -
- - )} -
-
- )} +//
+// {!isLoading && !isError && ActivityData && ( +// <> +//
+// Activities +//
+//

+// {ActivityData.totalCompletedWork?.toLocaleString()}/ +// {ActivityData.totalPlannedWork?.toLocaleString()} +//

+// Pending / Assigned +//
+// +//
+// +// )} +//
+//
+// )} - {activeTab === "logs" && ( -
- - - - - - - - - {[{ - activity: "Code Review / Remote", - assignedToday: 3, - completed: 2 - }].map((log, index) => ( - - - - - ))} - -
Activity / LocationAssigned / Completed
{log.activity}{log.assignedToday} / {log.completed}
-
- )} -
-
- ); -}; +// {activeTab === "logs" && ( +//
+// +// +// +// +// +// +// +// +// {[{ +// activity: "Code Review / Remote", +// assignedToday: 3, +// completed: 2 +// }].map((log, index) => ( +// +// +// +// +// ))} +// +//
Activity / LocationAssigned / Completed
{log.activity}{log.assignedToday} / {log.completed}
+//
+// )} +//
+//
+// ); +// }; -export default Activity; +// export default Activity; diff --git a/src/components/Directory/ManageBucket1.jsx b/src/components/Directory/ManageBucket1.jsx index 8455765e..e222ed75 100644 --- a/src/components/Directory/ManageBucket1.jsx +++ b/src/components/Directory/ManageBucket1.jsx @@ -23,7 +23,6 @@ const ManageBucket1 = () => { const handleClose = () => { setAction(null); setSelectedBucket(null); - setDeleteId(null); }; const { mutate: createBucket, isPending: creating } = useCreateBucket(() => { handleClose(); @@ -49,23 +48,26 @@ const ManageBucket1 = () => {

Manage Buckets

- {action == "create" ? ( + {action ? ( <> - { - setAction(null); - setSelectedBucket(null); - }} - isPending={creating || updating} - /> - {action === "edit" && selectedBucket && ( - + {action && ( +
+ { + setAction(null); + setSelectedBucket(null); + }} + isPending={creating || updating} + /> + {action === "edit" && ( + )} +
)} ) : ( @@ -91,11 +93,19 @@ const ManageBucket1 = () => { buckets={data} loading={isLoading} searchTerm={searchTerm} - onDelete={(id) => setDeleteBucket({isOpen:true,bucketId:id})} + onDelete={(id) => setDeleteBucket({ isOpen: true, bucketId: id })} + onEdit={(b) => { + setAction("edit") + setSelectedBucket(b) + }} /> )} + ); }; export default ManageBucket1; + + + diff --git a/src/components/Documents/DocumentFilterPanel.jsx b/src/components/Documents/DocumentFilterPanel.jsx index c305b9bb..15a2cbf1 100644 --- a/src/components/Documents/DocumentFilterPanel.jsx +++ b/src/components/Documents/DocumentFilterPanel.jsx @@ -96,7 +96,7 @@ const DocumentFilterPanel = ({ entityTypeId, onApply }) => { placeholder="DD-MM-YYYY To DD-MM-YYYY" startField="startDate" endField="endDate" - defaultRange={true} + defaultRange={false} resetSignal={resetKey} maxDate={new Date()} /> diff --git a/src/components/Documents/Documents.jsx b/src/components/Documents/Documents.jsx index b1dbe493..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 */}
@@ -149,30 +149,11 @@ const Documents = ({ Document_Entity, Entity }) => {
- {/* Actions */}
- {/* { - setSearchText(""); - setFilter(DocumentFilterDefaultValues); - refetchFn && refetchFn(); - }} - > - Refresh - - */} - {(isSelf || canUploadDocument) && ( )}
diff --git a/src/components/Employee/DemoTable.jsx b/src/components/Employee/DemoTable.jsx deleted file mode 100644 index 1d645cc3..00000000 --- a/src/components/Employee/DemoTable.jsx +++ /dev/null @@ -1,172 +0,0 @@ -import React from "react"; - -const DemoTable = () => { - return ( -
-
-
-
- - - - - - - - - - - - - - -
idNameEmailDateSalaryStatusAction
-
-
-
-
-
- New Record -
- -
-
-
-
- -
- - - - -
-
-
- -
- - - - -
-
-
- -
- - - - -
-
- You can use letters, numbers & periods -
-
-
- -
- - - - -
-
-
- -
- - - - -
-
-
- - -
-
-
-
- -
- -
- -
-
- -
-
- ); -}; - -export default DemoTable; diff --git a/src/components/Employee/EmployeeList.jsx b/src/components/Employee/EmployeeList.jsx deleted file mode 100644 index 5a79c6ce..00000000 --- a/src/components/Employee/EmployeeList.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -const EmployeeList = () => { - return
EmployeeList
; -}; - -export default EmployeeList; \ No newline at end of file diff --git a/src/components/Employee/EmployeeSchema.jsx b/src/components/Employee/EmployeeSchema.jsx new file mode 100644 index 00000000..ba540ef4 --- /dev/null +++ b/src/components/Employee/EmployeeSchema.jsx @@ -0,0 +1,124 @@ +import { z } from "zod" + + +const mobileNumberRegex = /^[0-9]\d{9}$/; + +export const employeeSchema = + z.object({ + firstName: z.string().min(1, { message: "First Name is required" }), + middleName: z.string().optional(), + lastName: z.string().min(1, { message: "Last Name is required" }), + email: z + .string() + .max(80, "Email cannot exceed 80 characters") + .optional() + .refine((val) => !val || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val), { + message: "Invalid email format", + }) + .refine( + (val) => { + if (!val) return true; + const [local, domain] = val.split("@"); + return ( + val.length <= 320 && local?.length <= 64 && domain?.length <= 255 + ); + }, + { + message: "Email local or domain part is too long", + } + ), + currentAddress: z + .string() + .min(1, { message: "Current Address is required" }) + .max(500, { message: "Address cannot exceed 500 characters" }), + birthDate: z + .string() + .min(1, { message: "Birth Date is required" }) + .refine( + (date, ctx) => { + return new Date(date) <= new Date(); + }, + { + message: "Birth date cannot be in the future", + } + ), + joiningDate: z + .string() + .min(1, { message: "Joining Date is required" }) + .refine( + (date, ctx) => { + return new Date(date) <= new Date(); + }, + { + message: "Joining date cannot be in the future", + } + ), + emergencyPhoneNumber: z + .string() + .min(1, { message: "Phone Number is required" }) + .regex(mobileNumberRegex, { message: "Invalid phone number " }), + emergencyContactPerson: z + .string() + .min(1, { message: "Emergency Contact Person is required" }) + .regex(/^[A-Za-z\s]+$/, { + message: "Emergency Contact Person must contain only letters", + }), + aadharNumber: z + .string() + .optional() + .refine((val) => !val || /^\d{12}$/.test(val), { + message: "Aadhar card must be exactly 12 digits long", + }), + gender: z + .string() + .min(1, { message: "Gender is required" }) + .refine((val) => val !== "Select Gender", { + message: "Please select a gender", + }), + panNumber: z + .string() + .optional() + .refine((val) => !val || /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/.test(val), { + message: "Invalid PAN number", + }), + permanentAddress: z + .string() + .min(1, { message: "Permanent Address is required" }) + .max(500, { message: "Address cannot exceed 500 characters" }), + phoneNumber: z + .string() + .min(1, { message: "Phone Number is required" }) + .regex(mobileNumberRegex, { message: "Invalid phone number " }), + jobRoleId: z.string().min(1, { message: "Role is required" }), + organizationId:z.string().min(1,{message:"Organization is required"}), + hasApplicationAccess:z.boolean().default(false), + }).refine((data) => { + if (data.hasApplicationAccess) { + return data.email && data.email.trim() !== ""; + } + return true; +}, { + message: "Email is required when employee has access", + path: ["email"], +}); + + +export const defatEmployeeObj = { + firstName: "", + middleName: "", + lastName: "", + email: "", + currentAddress: "", + birthDate: "", + joiningDate: "", + emergencyPhoneNumber: "", + emergencyContactPerson: "", + aadharNumber: "", + gender: "", + panNumber: "", + permanentAddress: "", + phoneNumber: "", + jobRoleId: null, + organizationId:"", + hasApplicationAccess:false + } \ No newline at end of file diff --git a/src/components/Employee/ManageEmployee.jsx b/src/components/Employee/ManageEmployee.jsx index 81a4c4d2..f57ca994 100644 --- a/src/components/Employee/ManageEmployee.jsx +++ b/src/components/Employee/ManageEmployee.jsx @@ -1,36 +1,37 @@ import React, { useEffect, useState } from "react"; -import showToast from "../../services/toastService"; -import EmployeeRepository from "../../repositories/EmployeeRepository"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; + import useMaster from "../../hooks/masterHook/useMaster"; import { useDispatch } from "react-redux"; import { changeMaster } from "../../slices/localVariablesSlice"; import { Link, useNavigate, useParams } from "react-router-dom"; import { formatDate } from "../../utils/dateUtils"; -import { useEmployeeProfile, useUpdateEmployee } from "../../hooks/useEmployees"; import { - cacheData, - clearCacheKey, - getCachedData, -} from "../../slices/apiDataManager"; -import { clearApiCacheKey } from "../../slices/apiCacheSlice"; -import { useMutation } from "@tanstack/react-query"; + useEmployeeProfile, + useUpdateEmployee, +} from "../../hooks/useEmployees"; + import Label from "../common/Label"; import DatePicker from "../common/DatePicker"; - -const mobileNumberRegex = /^[0-9]\d{9}$/; +import { defatEmployeeObj, employeeSchema } from "./EmployeeSchema"; +import { useOrganizationsList } from "../../hooks/useOrganization"; +import { ITEMS_PER_PAGE } from "../../utils/constants"; const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { const dispatch = useDispatch(); const { mutate: updateEmployee, isPending } = useUpdateEmployee(); - + const { + data: organzationList, + isLoading, + isError, + error: EempError, + } = useOrganizationsList(ITEMS_PER_PAGE, 1, true); const { employee, error, loading: empLoading, - refetch + refetch, } = useEmployeeProfile(employeeId); useEffect(() => { @@ -38,6 +39,7 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { }, [employeeId]); const [disabledEmail, setDisabledEmail] = useState(false); + const { data: job_role, loading } = useMaster(); const [isloading, setLoading] = useState(false); const navigation = useNavigate(); @@ -45,98 +47,9 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { const [currentAddressLength, setCurrentAddressLength] = useState(0); const [permanentAddressLength, setPermanentAddressLength] = useState(0); - const userSchema = z.object({ - ...(employeeId ? { id: z.string().optional() } : {}), - firstName: z.string().min(1, { message: "First Name is required" }), - middleName: z.string().optional(), - lastName: z.string().min(1, { message: "Last Name is required" }), - email: z - .string() - .max(80, "Email cannot exceed 80 characters") - .optional() - .refine((val) => !val || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val), { - message: "Invalid email format", - }) - .refine( - (val) => { - if (!val) return true; - const [local, domain] = val.split("@"); - return ( - val.length <= 320 && local?.length <= 64 && domain?.length <= 255 - ); - }, - { - message: "Email local or domain part is too long", - } - ), - currentAddress: z - .string() - .min(1, { message: "Current Address is required" }) - .max(500, { message: "Address cannot exceed 500 characters" }), - birthDate: z - .string() - .min(1, { message: "Birth Date is required" }) - .refine( - (date, ctx) => { - return new Date(date) <= new Date(); - }, - { - message: "Birth date cannot be in the future", - } - ), - joiningDate: z - .string() - .min(1, { message: "Joining Date is required" }) - .refine( - (date, ctx) => { - return new Date(date) <= new Date(); - }, - { - message: "Joining date cannot be in the future", - } - ), - emergencyPhoneNumber: z - .string() - .min(1, { message: "Phone Number is required" }) - .regex(mobileNumberRegex, { message: "Invalid phone number " }), - emergencyContactPerson: z - .string() - .min(1, { message: "Emergency Contact Person is required" }) - .regex(/^[A-Za-z\s]+$/, { - message: "Emergency Contact Person must contain only letters", - }), - aadharNumber: z - .string() - .optional() - .refine((val) => !val || /^\d{12}$/.test(val), { - message: "Aadhar card must be exactly 12 digits long", - }), - gender: z - .string() - .min(1, { message: "Gender is required" }) - .refine((val) => val !== "Select Gender", { - message: "Please select a gender", - }), - panNumber: z - .string() - .optional() - .refine((val) => !val || /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/.test(val), { - message: "Invalid PAN number", - }), - permanentAddress: z - .string() - .min(1, { message: "Permanent Address is required" }) - .max(500, { message: "Address cannot exceed 500 characters" }), - phoneNumber: z - .string() - .min(1, { message: "Phone Number is required" }) - .regex(mobileNumberRegex, { message: "Invalid phone number " }), - jobRoleId: z.string().min(1, { message: "Role is required" }), - }); - useEffect(() => { - refetch() - }, []) + refetch(); + }, []); const { register, @@ -147,25 +60,8 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { reset, getValues, } = useForm({ - resolver: zodResolver(userSchema), - defaultValues: { - id: currentEmployee?.id || null, - firstName: currentEmployee?.firstName || "", - middleName: currentEmployee?.middleName || "", - lastName: currentEmployee?.lastName || "", - email: currentEmployee?.email || "", - currentAddress: currentEmployee?.currentAddress || "", - birthDate: formatDate(currentEmployee?.birthDate) || "", - joiningDate: formatDate(currentEmployee?.joiningDate) || "", - emergencyPhoneNumber: currentEmployee?.emergencyPhoneNumber || "", - emergencyContactPerson: currentEmployee?.emergencyContactPerson || "", - aadharNumber: currentEmployee?.aadharNumber || "", - gender: currentEmployee?.gender || "", - panNumber: currentEmployee?.panNumber || "", - permanentAddress: currentEmployee?.permanentAddress || "", - phoneNumber: currentEmployee?.phoneNumber || "", - jobRoleId: currentEmployee?.jobRoleId.toString() || null, - }, + resolver: zodResolver(employeeSchema), + defaultValues: defatEmployeeObj, mode: "onChange", }); @@ -176,7 +72,13 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { data.email = null; } - updateEmployee({ ...data, IsAllEmployee }, { + const payload = { ...data, IsAllEmployee }; + + if (employeeId) { + payload.id = employeeId; + } + + updateEmployee(payload, { onSuccess: () => { reset(); onClosed(); @@ -184,7 +86,6 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { }); }; - useEffect(() => { if (!loading && !error && employee) { setCurrentEmployee(employee); @@ -195,37 +96,47 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { reset( currentEmployee ? { - id: currentEmployee.id || null, - firstName: currentEmployee.firstName || "", - middleName: currentEmployee.middleName || "", - lastName: currentEmployee.lastName || "", - email: currentEmployee.email || "", - currentAddress: currentEmployee.currentAddress || "", - birthDate: formatDate(currentEmployee.birthDate) || "", - joiningDate: formatDate(currentEmployee.joiningDate) || "", - emergencyPhoneNumber: currentEmployee.emergencyPhoneNumber || "", - emergencyContactPerson: - currentEmployee.emergencyContactPerson || "", - aadharNumber: currentEmployee.aadharNumber || "", - gender: currentEmployee.gender || "", - panNumber: currentEmployee.panNumber || "", - permanentAddress: currentEmployee.permanentAddress || "", - phoneNumber: currentEmployee.phoneNumber || "", - jobRoleId: currentEmployee.jobRoleId?.toString() || "", - } + id: currentEmployee.id || null, + firstName: currentEmployee.firstName || "", + middleName: currentEmployee.middleName || "", + lastName: currentEmployee.lastName || "", + email: currentEmployee.email || "", + currentAddress: currentEmployee.currentAddress || "", + birthDate: formatDate(currentEmployee.birthDate) || "", + joiningDate: formatDate(currentEmployee.joiningDate) || "", + emergencyPhoneNumber: currentEmployee.emergencyPhoneNumber || "", + emergencyContactPerson: + currentEmployee.emergencyContactPerson || "", + aadharNumber: currentEmployee.aadharNumber || "", + gender: currentEmployee.gender || "", + panNumber: currentEmployee.panNumber || "", + permanentAddress: currentEmployee.permanentAddress || "", + phoneNumber: currentEmployee.phoneNumber || "", + jobRoleId: currentEmployee.jobRoleId?.toString() || "", + organizationId: currentEmployee.organizationId || "", + hasApplicationAccess: currentEmployee.hasApplicationAccess || false, + } : {} ); setCurrentAddressLength(currentEmployee?.currentAddress?.length || 0); setPermanentAddressLength(currentEmployee?.permanentAddress?.length || 0); }, [currentEmployee, reset]); + const hasAccessAplication = watch("hasApplicationAccess"); return ( <>
-

{employee ? "Update Employee" : "Create Employee"}

+
+

+ {" "} + {employee ? "Update Employee" : "Create Employee"} +

{" "} +
- + { }} /> {errors.firstName && ( -
+
{errors.firstName.message}
)} @@ -267,14 +181,18 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { }} /> {errors.middleName && ( -
+
{errors.middleName.message}
)}
-
- + { }} /> {errors.lastName && ( -
+
{errors.lastName.message}
)}
-
-
Email
+ { )}
- + {
- +
- - {" "} - {500 - currentAddressLength} characters left - + {500 - currentAddressLength} characters left
{errors.currentAddress && (
{ }} >
- - {500 - permanentAddressLength} characters left - + {500 - permanentAddressLength} characters left
{errors.permanentAddress && (
{ )}
+ + {/* -------------- */} +
+
+ +
+ +
+ {errors.organizationId && ( +
+ {errors.organizationId.message} +
+ )} +
+ +
+ +
+
+ + {/* --------------- */}
{" "}
@@ -488,7 +469,9 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => {
- +
-
-
- )} -
- -
- ); }; diff --git a/src/components/Expenses/ExpenseFilterPanel.jsx b/src/components/Expenses/ExpenseFilterPanel.jsx index 5e415821..250f5de4 100644 --- a/src/components/Expenses/ExpenseFilterPanel.jsx +++ b/src/components/Expenses/ExpenseFilterPanel.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState,useMemo } from "react"; +import React, { useEffect, useState, useMemo } from "react"; import { FormProvider, useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { defaultFilter, SearchSchema } from "./ExpenseSchema"; @@ -16,8 +16,11 @@ import { ExpenseFilterSkeleton } from "./ExpenseSkeleton"; import { useLocation } from "react-router-dom"; const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { - const selectedProjectId = useSelector((store) => store.localVariables.projectId); - const { data, isLoading,isError,error,isFetching , isFetched} = useExpenseFilter(); + const selectedProjectId = useSelector( + (store) => store.localVariables.projectId + ); + const { data, isLoading, isError, error, isFetching, isFetched } = + useExpenseFilter(); const groupByList = useMemo(() => { return [ @@ -27,11 +30,10 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { { id: "project", name: "Project" }, { id: "paymentMode", name: "Payment Mode" }, { id: "expensesType", name: "Expense Type" }, - { id: "createdAt", name: "Submitted Date" } + { id: "createdAt", name: "Submitted Date" }, ].sort((a, b) => a.name.localeCompare(b.name)); }, []); - const [selectedGroup, setSelectedGroup] = useState(groupByList[0]); const [resetKey, setResetKey] = useState(0); @@ -40,7 +42,7 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { defaultValues: defaultFilter, }); - const { control, register, handleSubmit, reset, watch } = methods; + const { control, handleSubmit, reset, setValue, watch } = methods; const isTransactionDate = watch("isTransactionDate"); const closePanel = () => { @@ -78,28 +80,37 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { }, [location]); if (isLoading || isFetching) return ; - if(isError && isFetched) return
Something went wrong Here- {error.message}
+ if (isError && isFetched) + return
Something went wrong Here- {error.message}
; return ( <> -
- -
- + +
+ +
-
- + {
- + { />
+ {/* ---- Organization list ---- */} {isLoading ? (
Loading....
) : data && data?.data?.length > 0 ? ( -
+
@@ -99,28 +101,41 @@ const OrgPickerfromTenant = ({ title }) => { - - {Array.isArray(data.data) && data.data.length > 0 - ? data.data.map((row, i) => ( +
+ +
+ + + {Array.isArray(data.data) && data.data.length > 0 + ? data.data.map((row, i) => ( {contactList.map((col) => ( ))} - )) - : null} - -
{col.getValue(row)} - + +
+ + onOpen({ startStep: 3, orgData: row }) + } + > + + +
+ : null} + + +
) : null}
diff --git a/src/components/Organization/OrganizationModal.jsx b/src/components/Organization/OrganizationModal.jsx index da742427..5afef904 100644 --- a/src/components/Organization/OrganizationModal.jsx +++ b/src/components/Organization/OrganizationModal.jsx @@ -19,6 +19,7 @@ import AssignOrg from "./AssignOrg"; import ManagOrg from "./ManagOrg"; import OrgPickerFromSPId from "./OrgPickerFromSPId"; import OrgPickerfromTenant from "./OrgPickerfromTenant"; +import ViewOrganization from "./ViewOrganization"; const OrganizationModal = () => { const { isOpen, orgData, startStep, onOpen, onClose, onToggle } = @@ -53,14 +54,14 @@ const OrganizationModal = () => { }; const RenderTitle = useMemo(() => { - if (orgData) { + if (orgData && startStep === 3 ) { return "Assign Organization"; } if (startStep === 1) { return orgData && orgData !== null ? "Add Organization" - : "Choose Organization1"; + : "Choose Organization"; } if (startStep === 2) { @@ -70,8 +71,11 @@ const OrganizationModal = () => { if (startStep === 3) { return "Assign Organization"; } + if(startStep === 5){ + return "Organization Details" + } - return "Manage Organization"; + return `${orgData ? "Update":"Create"} Organization`; }, [startStep, orgData]); const contentBody = ( @@ -94,6 +98,9 @@ const OrganizationModal = () => { {/* ---------- STEP 3: Add New Organization ---------- */} {startStep === 4 && } + + {/* ---------- STEP 3: View Organization ---------- */} + {startStep === 5 && }
); diff --git a/src/components/Organization/OrganizationSchema.js b/src/components/Organization/OrganizationSchema.js index e8b24daa..91c24d76 100644 --- a/src/components/Organization/OrganizationSchema.js +++ b/src/components/Organization/OrganizationSchema.js @@ -19,7 +19,7 @@ export const organizationSchema = z.object({ .email("Invalid email address"), serviceIds: z .array(z.string()) - .min(1, { message: "Please insert service id" }), + .min(1, { message: "Service isrequired" }), }); export const defaultOrganizationValues = { diff --git a/src/components/Organization/OrganizationSkeleton.jsx b/src/components/Organization/OrganizationSkeleton.jsx index bdb5feff..841815dc 100644 --- a/src/components/Organization/OrganizationSkeleton.jsx +++ b/src/components/Organization/OrganizationSkeleton.jsx @@ -42,3 +42,82 @@ export const OrgCardSkeleton = () => {
); }; + + +export const OrgDetailsSkeleton = () => { + return ( +
+ {/* Header */} +
+
+ {/* Logo + Name */} +
+ + +
+ + {/* Status Badge */} + +
+
+ + {/* Section Title */} +
+ +
+ + {/* Contact Person */} +
+
+ + +
+
+ + {/* Contact Number */} +
+
+ + +
+
+ + {/* Email */} +
+
+ + +
+
+ + {/* SPRID */} +
+
+ + +
+
+ + {/* Employees */} +
+
+ + +
+
+ + {/* Address */} +
+
+ + +
+
+ + {/* Section Title 2 */} +
+ +
+
+ ); +}; diff --git a/src/components/Organization/OrganizationsList.jsx b/src/components/Organization/OrganizationsList.jsx index d5243e87..6e170a2f 100644 --- a/src/components/Organization/OrganizationsList.jsx +++ b/src/components/Organization/OrganizationsList.jsx @@ -93,7 +93,7 @@ const OrganizationsList = ({searchText}) => { if (isError) return
{error?.message || "Something went wrong"}
; return ( -
+
@@ -129,7 +129,7 @@ const OrganizationsList = ({searchText}) => { ))}
- + onOpen({startStep:5,orgData:org.id,flowType:"view"})}> onOpen({startStep:4,orgData:org,flowType:"edit"})}>
diff --git a/src/components/Organization/ViewOrganization.jsx b/src/components/Organization/ViewOrganization.jsx new file mode 100644 index 00000000..74c898b1 --- /dev/null +++ b/src/components/Organization/ViewOrganization.jsx @@ -0,0 +1,103 @@ +import React from "react"; +import { useOrganization } from "../../hooks/useOrganization"; +import { OrgDetailsSkeleton } from "./OrganizationSkeleton"; + +const VieworgDataanization = ({ orgId }) => { + const { data, isLoading, isError, error } = useOrganization(orgId); + if (isLoading) return ; + if (isError) return
{error.message}
; + return ( +
+ {/* Header */} +
+
+
+ logo

{data?.data?.name}

+
+
+ {data?.data.isActive ? "Active":"In-Active"} +
+
+
+
Organization Info
+ {/* Contact Info */} +
+
+ +
{data?.data?.contactPerson}
+
+
+
+
+ +
{data?.data?.contactNumber}
+
+
+
+
+ +
{data?.data?.email}
+
+
+
+
+ +
{data?.data?.sprid}
+
+
+ +
+
+ +
{data?.data?.activeEmployeeCount}
+
+
+
+
+ +
{data?.data?.address}
+
+
+
Projects And Services
+
+ ) +}; + +export default VieworgDataanization; diff --git a/src/components/Project/AssignTask.jsx b/src/components/Project/AssignTask.jsx index 0f755176..8c8aab22 100644 --- a/src/components/Project/AssignTask.jsx +++ b/src/components/Project/AssignTask.jsx @@ -1,24 +1,25 @@ import React, { useState, useEffect, useRef, useCallback } from "react"; import { useDispatch, useSelector } from "react-redux"; import { changeMaster } from "../../slices/localVariablesSlice"; -import useMaster from "../../hooks/masterHook/useMaster"; +import useMaster, { useServices } from "../../hooks/masterHook/useMaster"; import { useForm, Controller } from "react-hook-form"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; -import { clearCacheKey, getCachedData } from "../../slices/apiDataManager"; +import { useSelectedProject } from "../../slices/apiDataManager"; import { useEmployeesAllOrByProjectId } from "../../hooks/useEmployees"; import { TasksRepository } from "../../repositories/ProjectRepository"; import showToast from "../../services/toastService"; -import { useProjectDetails } from "../../hooks/useProjects"; +import { + useEmployeeForTaskAssign, + useProjectAssignedOrganizations, + useProjectDetails, +} from "../../hooks/useProjects"; import eventBus from "../../services/eventBus"; import { useCreateTask } from "../../hooks/useTasks"; import Label from "../common/Label"; -const AssignTask = ({ assignData, onClose, setAssigned }) => { - const maxPlanned = - assignData?.workItem?.plannedWork - assignData?.workItem?.completedWork; - - const schema = z.object({ +const TaskSchema = (maxPlanned) => { + return z.object({ selectedEmployees: z .array(z.string()) .min(1, { message: "At least one employee must be selected" }), @@ -37,20 +38,26 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => { }) ), }); +}; + +const AssignTask = ({ assignData, onClose, setAssigned }) => { + const planned = assignData?.workItem?.plannedWork || 0; + const completed = assignData?.workItem?.completedWork || 0; + const maxPlanned = planned - completed; const [isHelpVisibleTarget, setIsHelpVisibleTarget] = useState(false); const helpPopupRefTarget = useRef(null); const [isHelpVisible, setIsHelpVisible] = useState(false); + const [selectedService, setSelectedService] = useState(null); + const [selectedOrganization, setSelectedOrganization] = useState(null); + const { mutate: assignTask, isPending: isSubmitting } = useCreateTask({ - onSuccessCallback: () => { - closedModel(); - }, + onSuccessCallback: closedModel, }); const dropdownRef = useRef(null); const [open, setOpen] = useState(false); - // Close dropdown on outside click useEffect(() => { const handleClickOutside = (event) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { @@ -63,48 +70,47 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => { const infoRef = useRef(null); const infoRef1 = useRef(null); - - // State for search term const [searchTerm, setSearchTerm] = useState(""); useEffect(() => { if (typeof bootstrap !== "undefined") { - if (infoRef.current) { + infoRef.current && new bootstrap.Popover(infoRef.current, { trigger: "focus", placement: "right", html: true, content: `
Total Pending tasks of the Activity
`, }); - } - if (infoRef1.current) { + infoRef1.current && new bootstrap.Popover(infoRef1.current, { trigger: "focus", placement: "right", html: true, content: `
Target task for today
`, }); - } } else { console.warn("Bootstrap is not available. Popovers might not function."); } }, []); - const selectedProject = useSelector( - (store) => store.localVariables.projectId - ); - const { - employees, - loading: employeeLoading, - recallEmployeeData, - } = useEmployeesAllOrByProjectId(false, selectedProject, false); - const dispatch = useDispatch(); - const { loading } = useMaster(); - const { data: jobRoleData } = useMaster(); - // Changed to an array to hold multiple selected roles + const selectedProject = useSelectedProject(); + const { data: serviceList, isLoading: isServiceLoading } = useServices(); + const { data: organizationList, isLoading: isOrgLoading } = + useProjectAssignedOrganizations(selectedProject); + const { data: employees, isLoading: isEmployeeLoading } = + useEmployeeForTaskAssign( + selectedProject, + selectedService, + selectedOrganization + ); + + const dispatch = useDispatch(); + const { loading, data: jobRoleData } = useMaster(); + const [selectedRoles, setSelectedRoles] = useState(["all"]); const [displayedSelection, setDisplayedSelection] = useState(""); + const { handleSubmit, control, @@ -114,133 +120,98 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => { reset, trigger, } = useForm({ - defaultValues: { - selectedEmployees: [], - description: "", - plannedTask: "", - }, - resolver: zodResolver(schema), + defaultValues: { selectedEmployees: [], description: "", plannedTask: "" }, + resolver: zodResolver(TaskSchema(maxPlanned)), }); const handleCheckboxChange = (event, user) => { - const isChecked = event.target.checked; - let updatedSelectedEmployees = watch("selectedEmployees") || []; + const updatedSelectedEmployees = event.target.checked + ? [...(watch("selectedEmployees") || []), user.id].filter( + (v, i, a) => a.indexOf(v) === i + ) + : (watch("selectedEmployees") || []).filter((id) => id !== user.id); - if (isChecked) { - if (!updatedSelectedEmployees.includes(user.id)) { - updatedSelectedEmployees = [...updatedSelectedEmployees, user.id]; - } - } else { - updatedSelectedEmployees = updatedSelectedEmployees?.filter( - (id) => id !== user.id - ); - } setValue("selectedEmployees", updatedSelectedEmployees); trigger("selectedEmployees"); }; useEffect(() => { dispatch(changeMaster("Job Role")); - // Initial state should reflect "All Roles" selected setSelectedRoles(["all"]); }, [dispatch]); - // Modified handleRoleChange to handle multiple selections const handleRoleChange = (event, roleId) => { - // If 'all' is selected, clear other selections - if (roleId === "all") { - setSelectedRoles(["all"]); - } else { - setSelectedRoles((prevSelectedRoles) => { - // If "all" was previously selected, remove it - const newRoles = prevSelectedRoles.filter((role) => role !== "all"); - if (newRoles.includes(roleId)) { - // If role is already selected, unselect it - return newRoles.filter((id) => id !== roleId); - } else { - // If role is not selected, add it - return [...newRoles, roleId]; - } - }); - } + setSelectedRoles((prev) => { + if (roleId === "all") return ["all"]; + const newRoles = prev.filter((r) => r !== "all"); + return newRoles.includes(roleId) + ? newRoles.filter((r) => r !== roleId) + : [...newRoles, roleId]; + }); }; useEffect(() => { - // Update displayedSelection based on selectedRoles if (selectedRoles.includes("all")) { setDisplayedSelection("All Roles"); } else if (selectedRoles.length > 0) { - const selectedRoleNames = selectedRoles.map(roleId => { - const role = jobRoleData?.find(r => String(r.id) === roleId); - return role ? role.name : ''; - }).filter(Boolean); // Filter out empty strings for roles not found - setDisplayedSelection(selectedRoleNames.join(', ')); - } else { - setDisplayedSelection("Select Roles"); - } + setDisplayedSelection( + selectedRoles + .map((id) => jobRoleData?.find((r) => String(r.id) === id)?.name) + .filter(Boolean) + .join(", ") + ); + } else setDisplayedSelection("Select Roles"); }, [selectedRoles, jobRoleData]); + const handleSearchChange = (e) => setSearchTerm(e.target.value); - const handleSearchChange = (event) => { - setSearchTerm(event.target.value); - }; - - // Filter employees first by role, then by search term AND job role name - const filteredEmployees = employees?.filter((emp) => { + const filteredEmployees = employees?.data?.filter((emp) => { const matchesRole = - selectedRoles.includes("all") || selectedRoles.includes(String(emp.jobRoleId)); - // Convert both first and last names and job role name to lowercase for case-insensitive matching - const fullName = `${emp.firstName} ${emp.lastName}`.toLowerCase(); - - const jobRoleName = jobRoleData?.find((role) => role.id === emp.jobRoleId)?.name?.toLowerCase() || ""; - + selectedRoles.includes("all") || + selectedRoles.includes(String(emp.jobRoleId)); const searchLower = searchTerm.toLowerCase(); - // Check if the full name OR job role name includes the search term - const matchesSearch = fullName.includes(searchLower) || jobRoleName.includes(searchLower); - return matchesRole && matchesSearch; + const fullName = `${emp.firstName} ${emp.lastName}`.toLowerCase(); + const jobRoleName = + jobRoleData + ?.find((role) => role.id === emp.jobRoleId) + ?.name?.toLowerCase() || ""; + return ( + matchesRole && + (fullName.includes(searchLower) || jobRoleName.includes(searchLower)) + ); }); - // Determine unique job role IDs from the filtered employees (for dropdown options) - const uniqueJobRoleIdsInFilteredEmployees = new Set( - employees?.map(emp => emp.jobRoleId).filter(Boolean) + const jobRolesForDropdown = jobRoleData?.filter((role) => + new Set(employees?.data?.map((emp) => emp.jobRoleId).filter(Boolean)).has( + role.id + ) ); - // Filter jobRoleData to only include roles present in the uniqueJobRoleIdsInFilteredEmployees - const jobRolesForDropdown = jobRoleData?.filter(role => - uniqueJobRoleIdsInFilteredEmployees.has(role.id) - ); - - // Calculate the count of selected roles for display const selectedRolesCount = selectedRoles.includes("all") - ? 0 // "All Roles" doesn't contribute to a specific count + ? 0 : selectedRoles.length; const onSubmit = (data) => { - const selectedEmployeeIds = data.selectedEmployees; - - const taskTeamWithDetails = selectedEmployeeIds - ?.map((empId) => empId) - ?.filter(Boolean); - const formattedData = { - taskTeam: taskTeamWithDetails, - plannedTask: data.plannedTask, - description: data.description, - assignmentDate: new Date().toISOString(), - workItemId: assignData?.workItem.id, - }; assignTask({ - payload: formattedData, + payload: { + taskTeam: data.selectedEmployees.filter(Boolean), + plannedTask: data.plannedTask, + description: data.description, + assignmentDate: new Date().toISOString(), + workItemId: assignData?.workItem.id, + }, workAreaId: assignData?.workArea?.id, }); }; - const closedModel = () => { + function closedModel() { reset(); onClose(); - }; + } + return (
-

Assign Task

+

Assign Task

@@ -252,7 +223,7 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => { assignData?.workArea?.areaName, assignData?.workItem?.activityMaster?.activityName, ] - .filter(Boolean) // Filter out any undefined/null values + .filter(Boolean) .map((item, index, array) => ( {item} @@ -268,20 +239,64 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {

-
+
+
+
+ +
+
+ +
+
@@ -381,19 +403,19 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => { > {selectedRoles?.length > 0 && (
- {employeeLoading ? ( + {isEmployeeLoading ? (

Loading employees...

) : filteredEmployees?.length > 0 ? ( - filteredEmployees.map((emp) => { + filteredEmployees.map((emp,index) => { const jobRole = jobRoleData?.find( (role) => role?.id === emp?.jobRoleId ); return (
@@ -441,7 +463,7 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => { ) : (

- No employees found for the selected role. + No employees found for the selected filter.

)} @@ -456,12 +478,14 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => { {watch("selectedEmployees")?.length > 0 && (
- {watch("selectedEmployees")?.map((empId) => { - const emp = employees.find((emp) => emp.id === empId); + {watch("selectedEmployees")?.map((empId,ind) => { + const emp = employees?.data?.find( + (emp) => emp.id === empId + ); return ( emp && ( {emp.firstName} {emp.lastName} @@ -506,7 +530,12 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => { {assignData?.workItem?.plannedWork - assignData?.workItem?.completedWork} {" "} - {assignData?.workItem?.activityMaster?.unitOfMeasurement} + + { + assignData?.workItem?.activityMaster + ?.unitOfMeasurement + } +
@@ -537,8 +566,15 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => { className="form-control form-control-sm" {...field} /> - - {assignData?.workItem?.activityMaster?.unitOfMeasurement} + + + { + assignData?.workItem?.activityMaster + ?.unitOfMeasurement + } +
)} @@ -546,19 +582,17 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
{errors.plannedTask && ( -
{errors.plannedTask.message}
+
+ {errors.plannedTask.message} +
)}
- - {/* */} - { {isSubmitting ? "Please Wait" : "Submit"}
-
diff --git a/src/components/Project/Infrastructure/EditActivityModal.jsx b/src/components/Project/Infrastructure/EditActivityModal.jsx index c3d12648..e57b6a30 100644 --- a/src/components/Project/Infrastructure/EditActivityModal.jsx +++ b/src/components/Project/Infrastructure/EditActivityModal.jsx @@ -32,9 +32,8 @@ const EditActivityModal = ({ building, floor, onClose, -} ) => -{ - +}) => { + const { activities, loading: loadingActivities } = useActivitiesMaster(); const { categories, loading: loadingCategories } = useWorkCategoriesMaster(); const [selectedActivity, setSelectedActivity] = useState(null); @@ -57,13 +56,12 @@ const EditActivityModal = ({ comment: "", }, }); -const { mutate: UpdateTask, isPending } = useManageTask({ - onSuccessCallback: (response) => - { - showToast( response?.message, "success" ) - onClose() - } -} ); + const { mutate: UpdateTask, isPending } = useManageTask({ + onSuccessCallback: (response) => { + showToast(response?.message, "success") + onClose() + } + }); @@ -82,34 +80,33 @@ const { mutate: UpdateTask, isPending } = useManageTask({ [categories] ); -useEffect(() => { - if (!workItem) return; - console.log(workItem) - reset({ - activityID: String( - workItem?.workItem?.activityId || workItem?.activityMaster?.id - ), - workCategoryId: String( - workItem?.workItem?.workCategoryId || workItem?.workCategoryMaster?.id - ), - plannedWork: - workItem?.workItem?.plannedWork || workItem?.plannedWork || 0, - completedWork: - workItem?.workItem?.completedWork || workItem?.completedWork || 0, - comment: workItem?.workItem?.description || workItem?.description || "", - }); -}, [workItem?.id,selectedActivity]); + useEffect(() => { + if (!workItem) return; + console.log(workItem) + reset({ + activityID: String( + workItem?.workItem?.activityId || workItem?.activityMaster?.id + ), + workCategoryId: String( + workItem?.workItem?.workCategoryId || workItem?.workCategoryMaster?.id + ), + plannedWork: + workItem?.workItem?.plannedWork || workItem?.plannedWork || 0, + completedWork: + workItem?.workItem?.completedWork || workItem?.completedWork || 0, + comment: workItem?.workItem?.description || workItem?.description || "", + }); + }, [workItem?.id, selectedActivity]); useEffect(() => { const selected = activities?.find((a) => a.id === activityID); - setSelectedActivity( selected || null ); + setSelectedActivity(selected || null); }, [activityID, activities]); - const onSubmitForm = (data) => - { - const payload = { + const onSubmitForm = (data) => { + const payload = { ...data, id: workItem?.workItem?.id ?? workItem?.id, buildingID: building?.id, @@ -125,9 +122,9 @@ useEffect(() => { buildingId: building?.id, floorId: floor?.id, workAreaId: workArea?.id, - previousCompletedWork:completedTask + previousCompletedWork: completedTask }); - } + } return (
@@ -162,14 +159,26 @@ useEffect(() => { disabled />
+
+ + +
+
{ + handleServiceChange(e); + setValue("serviceId", e.target.value); + }} > + {servicesLoading && } {assignedServices?.map((service) => ( ))} - {errors.buildingID && ( -

{errors.buildingID.message}

- )}
)} - {/* Activity Selection */} - {selectedWorkArea && ( + {/* Activity Group (Organization) Selection */} + {selectedService && (
- + - {errors.activityID && ( -

{errors.activityID.message}

- )} + {errors.activityGroupId &&

{errors.activityGroupId.message}

}
)} + {/* Activity Selection */} + {selectedGroup && ( +
+ + + {errors.activityID &&

{errors.activityID.message}

} +
+ )} + {selectedWorkArea && (
@@ -312,9 +356,10 @@ const TaskModel = ({ project, onSubmit, onClose }) => {
diff --git a/src/components/Project/Infrastructure/WorkArea.jsx b/src/components/Project/Infrastructure/WorkArea.jsx index b6fc3e35..f4b868d2 100644 --- a/src/components/Project/Infrastructure/WorkArea.jsx +++ b/src/components/Project/Infrastructure/WorkArea.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import WorkItem from "./WorkItem"; -import { useProjectDetails, useProjectTasks } from "../../../hooks/useProjects"; -import { cacheData } from "../../../slices/apiDataManager"; +import { useCurrentService, useProjectDetails, useProjectTasks } from "../../../hooks/useProjects"; +import { cacheData, useSelectedProject } from "../../../slices/apiDataManager"; import { useDispatch, useSelector } from "react-redux"; import { refreshData } from "../../../slices/localVariablesSlice"; import ProjectRepository from "../../../repositories/ProjectRepository"; @@ -14,10 +14,12 @@ import { } from "../../../utils/constants"; import { useParams } from "react-router-dom"; import ProgressBar from "../../common/ProgressBar"; -import {formatNumber} from "../../../utils/dateUtils"; +import { formatNumber } from "../../../utils/dateUtils"; +import { useServices } from "../../../hooks/masterHook/useMaster"; -const WorkArea = ({ workArea, floor, forBuilding,serviceId = null }) => { - const selectedProject = useSelector((store) => store.localVariables.projectId); +const WorkArea = ({ workArea, floor, forBuilding }) => { + const selectedProject = useSelectedProject() + const selectedService = useCurrentService() const { projects_Details, loading } = useProjectDetails(selectedProject); const [IsExpandedArea, setIsExpandedArea] = useState(false); const dispatch = useDispatch(); @@ -25,8 +27,7 @@ const WorkArea = ({ workArea, floor, forBuilding,serviceId = null }) => { const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA); const ManageAndAssignTak = useHasUserPermission(ASSIGN_REPORT_TASK); - - const { ProjectTaskList, isLoading } = useProjectTasks(workArea.id,serviceId, IsExpandedArea); + const { ProjectTaskList, isLoading } = useProjectTasks(workArea.id, selectedService, IsExpandedArea); const [workAreaStatus, setWorkAreaStatus] = useState({ completed: 0, @@ -81,9 +82,8 @@ const WorkArea = ({ workArea, floor, forBuilding,serviceId = null }) => { aria-controls={`collapse-${workArea.id}`} > {
-
- -
+
+ +

@@ -120,12 +120,12 @@ const WorkArea = ({ workArea, floor, forBuilding,serviceId = null }) => { className="accordion-collapse collapse" aria-labelledby={`heading-${workArea.id}`} > -
- {isLoading || ProjectTaskList === undefined ? ( +
+ {isLoading || ProjectTaskList === undefined ? (
Loading activities...
- ) : ProjectTaskList?.length === 0 ? ( + ) : ProjectTaskList?.length === 0 ? (
No activities available for this work area.
- ):ProjectTaskList?.length > 0 ? ( + ) : ProjectTaskList?.length > 0 ? ( @@ -151,9 +151,9 @@ const WorkArea = ({ workArea, floor, forBuilding,serviceId = null }) => { - {ProjectTaskList.map((workItem,index) => ( + {ProjectTaskList.map((workItem, index) => ( { : "Work Area created Successfully", "success" ); - reset({ id: "0", buildingId: "0", areaName: "", floorId: "0" }); - // onClose?.(); + setValue("id", "0"); + setValue("areaName", ""); }, }); @@ -194,7 +194,7 @@ const WorkAreaModel = ({ project, onSubmit, onClose }) => { - + ); diff --git a/src/components/Project/ManageProject.jsx b/src/components/Project/ManageProject.jsx index 7bcef356..a14def21 100644 --- a/src/components/Project/ManageProject.jsx +++ b/src/components/Project/ManageProject.jsx @@ -77,13 +77,7 @@ const ManageProject = () => { )} - {isProjectCreated && ( -
-

Additional Content

-

Now that the project is created, you can access this content.

- {/* Add more content here */} -
- )} + ); }; diff --git a/src/components/Project/ManageProjectInfo.jsx b/src/components/Project/ManageProjectInfo.jsx index 5d56c171..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,54 +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" }) - }) - .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, @@ -72,80 +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: String(project?.projectStatusId || "00000000-0000-0000-0000-000000000000"), - projectStatusId: project?.projectStatusId && project.projectStatusId !== DEFAULT_EMPTY_STATUS_ID - - ? String(project.projectStatusId) - - : ACTIVE_STATUS_ID, - }, + 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"}
-
+
- - {employeeLoading && allocationEmployeesData.length === 0 && ( - - - - )} - - {!employeeLoading && - allocationEmployeesData.length === 0 && - filteredData.length === 0 && ( - - - - )} - - {!employeeLoading && - allocationEmployeesData.length > 0 && - filteredData.length === 0 && ( - - - - )} - - {(filteredData.length > 0 || - allocationEmployeesData.length > 0) && - filteredData.map((emp) => ( - - ))} - -
Loading..
All employee assigned to Project.
No matching employees found.
-
-
- - - {(filteredData.length > 0 || allocationEmployeesData.length > 0) && ( - - )} -
- -
-
- - ); -}; - -export default MapUsers; diff --git a/src/components/Project/ProjectCard.jsx b/src/components/Project/ProjectCard.jsx index eded7324..ca5d88de 100644 --- a/src/components/Project/ProjectCard.jsx +++ b/src/components/Project/ProjectCard.jsx @@ -14,41 +14,15 @@ import { getProjectStatusName, } from "../../utils/projectStatus"; import GlobalModel from "../common/GlobalModel"; - import { useDispatch } from "react-redux"; +import { useDispatch } from "react-redux"; import { setProjectId } from "../../slices/localVariablesSlice"; +import { useProjectContext } from "../../pages/project/ProjectPage"; -const ProjectCard = ({ projectData, recall }) => { - const [ projectInfo, setProjectInfo ] = useState( projectData ); - const { projects_Details, loading, error, refetch } = useProjectDetails( - projectInfo?.id,false - ); - const [showModal, setShowModal] = useState(false); - const dispatch = useDispatch() +const ProjectCard = ({ project }) => { + const dispatch = useDispatch(); const navigate = useNavigate(); const ManageProject = useHasUserPermission(MANAGE_PROJECT); - const { - mutate: updateProject, - isPending, - isSuccess, - isError, - } = useUpdateProject({ - onSuccessCallback: () => { - setShowModal(false); - }, - }) - - useEffect(()=>{ - setProjectInfo(projectData); - }, [ projectData ] ) - - const handleShow = async () => { - try { - const { data } = await refetch(); - setShowModal(true); - } catch (err) { - showToast("Failed to load project details", "error"); - } - }; + const { setMangeProject } = useProjectContext(); const getProgress = (planned, completed) => { return (completed * 100) / planned + "%"; @@ -60,35 +34,18 @@ const ProjectCard = ({ projectData, recall }) => { const handleClose = () => setShowModal(false); const handleViewProject = () => { - dispatch(setProjectId(projectInfo.id)) + dispatch(setProjectId(project.id)); navigate(`/projects/details`); }; - - const handleFormSubmit = (updatedProject) => { - if (projectInfo?.id) { - updateProject({ - projectId: projectInfo.id, - updatedData: updatedProject, - }); - } -}; + const handleViewActivities = () => { + dispatch(setProjectId(project.id)); + navigate(`/activities/records?project=${project.id}`); + }; return ( <> - - {showModal && projects_Details && ( - - - - )} -
-
+
@@ -102,13 +59,11 @@ const ProjectCard = ({ projectData, recall }) => {
- {projectInfo.shortName - ? projectInfo.shortName - : projectInfo.name} + > + {project?.shortName ? project?.shortName : project?.name}
- {projectInfo.shortName ? projectInfo.name : ""} + {project.shortName ? project.name : ""}
@@ -120,23 +75,14 @@ const ProjectCard = ({ projectData, recall }) => { data-bs-toggle="dropdown" aria-expanded="false" > - {loading ? ( -
- Loading... -
- ) : ( - - )} +
@@ -205,36 +150,37 @@ const ProjectCard = ({ projectData, recall }) => { - {getProjectStatusName(projectInfo.projectStatusId)} + {getProjectStatusName(project.projectStatusId)}

{" "} - {getDateDifferenceInDays(projectInfo.endDate, Date()) >= 0 && ( + {getDateDifferenceInDays(project.endDate, Date()) >= 0 && ( - {projectInfo.endDate && - getDateDifferenceInDays(projectInfo.endDate, Date())}{" "} + {project.endDate && + getDateDifferenceInDays(project.endDate, Date())}{" "} Days left )} - {getDateDifferenceInDays(projectInfo.endDate, Date()) < 0 && ( + {getDateDifferenceInDays(project.endDate, Date()) < 0 && ( - {projectInfo.endDate && - getDateDifferenceInDays(projectInfo.endDate, Date())}{" "} + {project.endDate && + getDateDifferenceInDays(project.endDate, Date())}{" "} Days overdue )}
- Task: {formatNumber(projectInfo.completedWork)} / {formatNumber(projectInfo.plannedWork)} + Task: {formatNumber(project.completedWork)} /{" "} + {formatNumber(project.plannedWork)} {Math.floor( getProgressInNumber( - projectInfo.plannedWork, - projectInfo.completedWork + project.plannedWork, + project.completedWork ) ) || 0}{" "} % Completed @@ -246,22 +192,20 @@ const ProjectCard = ({ projectData, recall }) => { role="progressbar" style={{ width: getProgress( - projectInfo.plannedWork, - projectInfo.completedWork + project.plannedWork, + project.completedWork ), }} - aria-valuenow={projectInfo.completedWork} + aria-valuenow={project.completedWork} aria-valuemin="0" - aria-valuemax={projectInfo.plannedWork} + aria-valuemax={project.plannedWork} >
- {/*
-
*/}
@@ -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/ProjectInfra.jsx b/src/components/Project/ProjectInfra.jsx index cd7fafbc..643f6106 100644 --- a/src/components/Project/ProjectInfra.jsx +++ b/src/components/Project/ProjectInfra.jsx @@ -17,33 +17,39 @@ import { getCachedData, useSelectedProject, } from "../../slices/apiDataManager"; -import { useProjectAssignedServices, useProjectDetails, useProjectInfra } from "../../hooks/useProjects"; +import { + useCurrentService, + useProjectAssignedServices, + useProjectDetails, + useProjectInfra, +} from "../../hooks/useProjects"; import { useDispatch, useSelector } from "react-redux"; import { refreshData } from "../../slices/localVariablesSlice"; import eventBus from "../../services/eventBus"; import { useParams } from "react-router-dom"; import GlobalModel from "../common/GlobalModel"; +import { setService } from "../../slices/globalVariablesSlice"; const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => { const projectId = useSelectedProject(); + const selectedService = useCurrentService(); const reloadedData = useSelector((store) => store.localVariables.reload); const [expandedBuildings, setExpandedBuildings] = useState([]); - const { projectInfra, isLoading, error } = useProjectInfra(projectId) + const { projectInfra, isLoading, error } = useProjectInfra( + projectId, + selectedService + ); const { projects_Details, refetch, loading } = useProjectDetails(data?.id); const [project, setProject] = useState(projects_Details); const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA); - const ManageTask = useHasUserPermission(MANAGE_TASK) + const ManageTask = useHasUserPermission(MANAGE_TASK); const [showModalFloor, setshowModalFloor] = useState(false); const [showModalWorkArea, setshowModalWorkArea] = useState(false); const [showModalTask, setshowModalTask] = useState(false); const [showModalBuilding, setshowModalBuilding] = useState(false); const dispatch = useDispatch(); - const { data: assignedServices, isLoading: servicesLoading } = useProjectAssignedServices(projectId); - - const [selectedService, setSelectedService] = useState(""); - const handleServiceChange = (e) => { - setSelectedService(e.target.value); - }; + const { data: assignedServices, isLoading: servicesLoading } = + useProjectAssignedServices(projectId); useEffect(() => { setProject(projectInfra); @@ -51,34 +57,58 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => { const signalRHandler = (response) => { setProject(response); - } + }; return ( <> - {showModalBuilding && setshowModalBuilding(false)}> - setshowModalBuilding(false)} - /> - } - {showModalFloor && setshowModalFloor(false)}> - setshowModalFloor(false)} - /> - } - {showModalWorkArea && setshowModalWorkArea(false)} > - setshowModalWorkArea(false)} - /> - } - {showModalTask && ( setshowModalTask(false)}> - setshowModalTask(false)} - /> - )} + {showModalBuilding && ( + setshowModalBuilding(false)} + > + setshowModalBuilding(false)} + /> + + )} + {showModalFloor && ( + setshowModalFloor(false)} + > + setshowModalFloor(false)} + /> + + )} + {showModalWorkArea && ( + setshowModalWorkArea(false)} + > + setshowModalWorkArea(false)} + /> + + )} + {showModalTask && ( + setshowModalTask(false)} + > + setshowModalTask(false)} + /> + + )}
@@ -88,8 +118,9 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => { className="dataTables_length text-start py-2 px-6 col-md-4 col-12" id="DataTables_Table_0_length" > - {!servicesLoading && assignedServices?.length > 0 && ( - assignedServices.length > 1 ? ( + {!servicesLoading && + assignedServices?.length > 0 && + (assignedServices.length > 1 ? (
+ + ))} + +
+ ), + 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/ProjectNav.jsx b/src/components/Project/ProjectNav.jsx index e2ecd455..d8b2e8ed 100644 --- a/src/components/Project/ProjectNav.jsx +++ b/src/components/Project/ProjectNav.jsx @@ -41,8 +41,8 @@ const ProjectNav = ({ onPillClick, activePill }) => { hidden: !(DirAdmin || DireManager || DirUser), }, { key: "documents", icon: "bx bx-folder-open", label: "Documents",hidden:!(isViewDocuments || isModifyDocument || isUploadDocument) }, + { key: "organization", icon: "bx bx-buildings", label: "Organization"}, { key: "setting", icon: "bx bxs-cog", label: "Setting",hidden:!isManageTeam }, - { key: "organization", icon: "bx bx-buildings", label: "Organization"}, ]; return (
diff --git a/src/components/Project/ProjectOrganization/ProjectAssignedOrgs.jsx b/src/components/Project/ProjectOrganization/ProjectAssignedOrgs.jsx index 4625a30b..97f54cf0 100644 --- a/src/components/Project/ProjectOrganization/ProjectAssignedOrgs.jsx +++ b/src/components/Project/ProjectOrganization/ProjectAssignedOrgs.jsx @@ -7,7 +7,7 @@ const ProjectAssignedOrgs = () => { const { data, isLoading, isError, error } = useProjectAssignedOrganizations(selectedProject); - const contactList = [ + const orgList = [ { key: "name", label: "Organization Name", @@ -23,6 +23,16 @@ const ProjectAssignedOrgs = () => {
), align: "text-start", + }, + { + key: "service", + label: "Service Name", + getValue: (org) => ( +
+ {org?.service?.name} +
+ ), + align: "text-start", }, { key: "sprid", @@ -57,11 +67,11 @@ const ProjectAssignedOrgs = () => { return (
-
+
- {contactList.map((col) => ( + {orgList.map((col) => ( @@ -72,7 +82,7 @@ const ProjectAssignedOrgs = () => { {Array.isArray(data) && data.length > 0 ? ( data.map((row, i) => ( - {contactList.map((col) => ( + {orgList.map((col) => ( @@ -82,7 +92,7 @@ const ProjectAssignedOrgs = () => { ) : ( - - - - - - - - - - - - - ); -}; - -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/repositories/AuthRepository.jsx b/src/repositories/AuthRepository.jsx index cf3f6e31..700020fd 100644 --- a/src/repositories/AuthRepository.jsx +++ b/src/repositories/AuthRepository.jsx @@ -2,7 +2,7 @@ import { api } from "../utils/axiosClient"; const AuthRepository = { // Public routes (no auth token required) - login: (data) => api.postPublic("/api/auth/login/v1", data), + login: (data) => api.postPublic("/api/auth/login", data), refreshToken: (data) => api.postPublic("/api/auth/refresh-token", data), forgotPassword: (data) => api.postPublic("/api/auth/forgot-password", data), resetPassword: (data) => api.postPublic("/api/auth/reset-password", data), diff --git a/src/repositories/MastersRepository.jsx b/src/repositories/MastersRepository.jsx index a69d3562..3e9fac8f 100644 --- a/src/repositories/MastersRepository.jsx +++ b/src/repositories/MastersRepository.jsx @@ -32,11 +32,11 @@ export const MasterRespository = { getActivites: () => api.get("api/master/activities"), createActivity: (data) => api.post("api/master/activity", data), -//Services + //Services getService: () => api.get("api/master/service/list"), createService: (data) => api.post("api/master/service/create", data), updateService: (id, data) => api.put(`api/master/service/edit/${id}`, data), - "Services": (id) => api.delete(`/api/master/service/delete/${id}`), + Services: (id) => api.delete(`/api/master/service/delete/${id}`), updateActivity: (id, data) => api.post(`api/master/activity/edit/${id}`, data), @@ -114,10 +114,20 @@ export const MasterRespository = { updateDocumentType: (id, data) => api.put(`/api/Master/document-type/edit/${id}`, data), + getGlobalServices: () => api.get("/api/Master/global-service/list"), + getMasterServices: () => api.get("/api/Master/service/list"), + getActivityGrops: (serviceId) => + api.get(`/api/Master/activity-group/list?serviceId=${serviceId}`), + createActivityGroup: (data) => + api.post(`/api/Master/activity-group/create`, data), + updateActivityGrop: (serviceId, data) => + api.put(`/api/Master/activity-group/edit/${serviceId}`, data), + getActivitesByGroup: (activityGroupId) => + api.get(`api/master/activities?activityGroupId=${activityGroupId}`), + deleteActivityGroup:(id)=>api.delete(`/api/Master/activity-group/delete/${id}`), - getGlobalServices:()=>api.get("/api/Master/global-service/list"), - getMasterServices:()=>api.get("/api/Master/service/list"), + deleteActivity:(id)=>api.delete(`/api/Master/activity/delete/${id}`), - getOrganizationType:()=>api.get('/api/Master/organization-type/list') + getOrganizationType: () => api.get("/api/Master/organization-type/list"), }; diff --git a/src/repositories/OrganizationRespository.jsx b/src/repositories/OrganizationRespository.jsx index 4b4ad1b0..722423a9 100644 --- a/src/repositories/OrganizationRespository.jsx +++ b/src/repositories/OrganizationRespository.jsx @@ -2,6 +2,8 @@ import { api } from "../utils/axiosClient"; const OrganizationRepository = { createOrganization: (data) => api.post("/api/Organization/create", data), + updateOrganizaion:(id,data)=>api.put(`/api/Organization/edit/${id}`,data), + getOrganizaion:(id)=>api.get(`/api/Organization/details/${id}`), getOrganizationList: (pageSize, pageNumber, active, sprid, searchString) => { return api.get( `/api/Organization/list?pageSize=${pageSize}&pageNumber=${pageNumber}&active=${active}&${ @@ -10,11 +12,33 @@ const OrganizationRepository = { ); }, - getOrganizationBySPRID :(sprid)=>api.get(`/api/Organization/list?sprid=${sprid}`), + getOrganizationBySPRID: (sprid) => + api.get(`/api/Organization/list?sprid=${sprid}`), - assignOrganizationToProject:(data)=>api.post(`/api/Organization/assign/project`,data), + assignOrganizationToProject: (data) => + api.post(`/api/Organization/assign/project`, data), - assignOrganizationToTenanat:(organizationId)=>api.post(`/api/Organization/assign/tenant/${organizationId}`) + assignOrganizationToTenanat: (organizationId) => + api.post(`/api/Organization/assign/tenant/${organizationId}`), + + getOrganizationEmployees: (projectId, organizationId, searchString) => { + let url = `/api/Employee/list/organizations/${projectId}`; + const queryParams = []; + + if (organizationId) { + queryParams.push(`organizationId=${organizationId}`); + } + + if (searchString) { + queryParams.push(`searchString=${encodeURIComponent(searchString)}`); + } + + if (queryParams.length > 0) { + url += `?${queryParams.join("&")}`; + } + + return api.get(url); + }, }; export default OrganizationRepository; diff --git a/src/repositories/ProjectRepository.jsx b/src/repositories/ProjectRepository.jsx index 8f5ece39..43a1ee66 100644 --- a/src/repositories/ProjectRepository.jsx +++ b/src/repositories/ProjectRepository.jsx @@ -5,19 +5,20 @@ const ProjectRepository = { getProjectByprojectId: (projetid) => api.get(`/api/project/details/${projetid}`), - getProjectAllocation: (projectId, organizationId, serviceId) => { - let url = `/api/project/allocation/${projectId}`; + getProjectAllocation: (projectId, serviceId, organizationId, employeeStatus) => { + let url = `/api/project/allocation/${projectId}`; - const params = []; - if (organizationId) params.push(`organizationId=${organizationId}`); - if (serviceId) params.push(`serviceId=${serviceId}`); + const params = []; + if (organizationId) params.push(`organizationId=${organizationId}`); + if (serviceId) params.push(`serviceId=${serviceId}`); + if (employeeStatus !== undefined) params.push(`includeInactive=${employeeStatus}`); - if (params.length > 0) { - url += `?${params.join("&")}`; - } + if (params.length > 0) { + url += `?${params.join("&")}`; + } - return api.get(url); - }, + return api.get(url); +}, getEmployeesByProject: (projectId) => @@ -42,7 +43,14 @@ const ProjectRepository = { projectNameList: () => api.get("/api/project/list/basic"), getProjectDetails: (id) => api.get(`/api/project/details/${id}`), - getProjectInfraByproject: (id) => api.get(`/api/project/infra-details/${id}`), + getProjectInfraByproject: (projectId, serviceId) => { + let url = `/api/project/infra-details/${projectId}`; + + if (serviceId) { + url + `?serviceId=${serviceId}`; + } + return api.get(url); + }, getProjectTasksByWorkArea: (workAreaId, serviceId) => { let url = `/api/project/tasks/${workAreaId}`; if (serviceId) { @@ -58,16 +66,37 @@ const ProjectRepository = { // Permission Managment for Employee at Project Level - getProjectLevelEmployeeList: (projectId) => api.get(`/api/Project/get/proejct-level/employees/${projectId}`), - getProjectLevelModules: () => api.get(`/api/Project/get/proejct-level/modules`), - getProjectLevelEmployeePermissions: (employeeId, projectId) => api.get(`/api/Project/get/project-level-permission/employee/${employeeId}/project/${projectId}`), - updateProjectLevelEmployeePermission: (data) => api.post(`/api/Project/assign/project-level-permission`, data), - getAllProjectLevelPermission: (projectId) => api.get(`/api/Project/get/all/project-level-permission/${projectId}`), - + getProjectLevelEmployeeList: (projectId) => + api.get(`/api/Project/get/proejct-level/employees/${projectId}`), + getProjectLevelModules: () => + api.get(`/api/Project/get/proejct-level/modules`), + getProjectLevelEmployeePermissions: (employeeId, projectId) => + api.get( + `/api/Project/get/project-level-permission/employee/${employeeId}/project/${projectId}` + ), + updateProjectLevelEmployeePermission: (data) => + api.post(`/api/Project/assign/project-level-permission`, data), + getAllProjectLevelPermission: (projectId) => + api.get(`/api/Project/get/all/project-level-permission/${projectId}`), // Services - getProjectAssignedServices: (projectId) => api.get(`/api/Project/get/assigned/services/${projectId}`), - getProjectAssignedOrganizations: (projectId) => api.get(`/api/Project/get/assigned/organization/${projectId}`) + getProjectAssignedServices: (projectId) => + api.get(`/api/Project/get/assigned/services/${projectId}`), + getProjectAssignedOrganizations: (projectId) => + api.get(`/api/Project/get/assigned/organization/${projectId}`), + + getEmployeeForTaskAssign: (projectId, serviceId, organizationId) => { + let url = `/api/Project/get/task/team/${projectId}`; + + const params = []; + if (serviceId) params.push(`serviceId=${serviceId}`); + if (organizationId) params.push(`organizationId=${organizationId}`); + + if (params.length > 0) { + url += `?${params.join("&")}`; + } + return api.get(url); + }, }; export const TasksRepository = { diff --git a/src/repositories/TaskRepository.jsx b/src/repositories/TaskRepository.jsx index 40ad8750..33883452 100644 --- a/src/repositories/TaskRepository.jsx +++ b/src/repositories/TaskRepository.jsx @@ -1,25 +1,26 @@ import { api } from "../utils/axiosClient"; export const TasksRepository = { - getTaskList: (id, fromdate = null, todate = null) => { - let url = `api/task/list?projectId=${id}`; + getTaskList: (projectId, pageSize, pageNumber, serviceId, filter) => { + const payloadJsonString = JSON.stringify(filter); + let url = `api/task/list?projectId=${projectId}&pageSize=${pageSize}&pageNumber=${pageNumber}`; - if (fromdate) { - url += `&dateFrom=${fromdate}`; + if (serviceId) { + url += `&serviceId=${serviceId}`; } - - if (todate) { - url += `&dateTo=${todate}`; + if (filter && Object.keys(filter).length > 0) { + const payloadJsonString = encodeURIComponent(JSON.stringify(filter)); + url += `&filter=${payloadJsonString}`; } + debugger return api.get(url); }, - getTaskById:(id)=>api.get(`/api/task/get/${id}`), + getTaskById: (id) => api.get(`/api/task/get/${id}`), reportTask: (data) => api.post("api/task/report", data), - taskComments: ( data ) => api.post( "api/task/comment", data ), - auditTask: ( data ) => api.post( '/api/task/approve', data ), - - assignTask:(data) =>api.post('/api/task/assign',data) + taskComments: (data) => api.post("api/task/comment", data), + auditTask: (data) => api.post("/api/task/approve", data), + assignTask: (data) => api.post("/api/task/assign", data), }; diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index e8be179e..1b02e874 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"; @@ -21,7 +20,6 @@ import EmployeeList from "../pages/employee/EmployeeList"; import EmployeeProfile from "../pages/employee/EmployeeProfile"; import Inventory from "../pages/project/Inventory"; import AttendancePage from "../pages/Activities/AttendancePage"; -import DailyTask from "../pages/Activities/DailyTask"; import TaskPlannng from "../pages/Activities/TaskPlannng"; import Reports from "../pages/reports/Reports"; import MasterPage from "../pages/master/MasterPage"; @@ -53,6 +51,8 @@ import CreateTenant from "../pages/Tenant/CreateTenant"; 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( [ { @@ -80,7 +80,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: }, @@ -90,7 +90,7 @@ const router = createBrowserRouter( { path: "/directory", element: }, { path: "/inventory", element: }, { path: "/activities/attendance", element: }, - { path: "/activities/records/:projectId?", element: }, + { path: "/activities/records/:projectId?", element: }, { path: "/activities/task", element: }, { path: "/activities/reports", element: }, { path: "/gallary", element: }, diff --git a/src/router/ProtectedRoute.jsx b/src/router/ProtectedRoute.jsx index 68dd847f..d174652c 100644 --- a/src/router/ProtectedRoute.jsx +++ b/src/router/ProtectedRoute.jsx @@ -2,13 +2,69 @@ import React, { useState, useEffect } from "react"; import { Navigate, Outlet } from "react-router-dom"; import { jwtDecode } from "jwt-decode"; import AuthRepository from "../repositories/AuthRepository"; +import { removeSession } from "../utils/authUtils"; + +const isTokenExpired = (token) => { + if (!token) return true; + try { + const { exp } = jwtDecode(token); + return exp * 1000 < Date.now(); + } catch { + return true; + } +}; + +const validateToken = async () => { + const token = + localStorage.getItem("jwtToken") || + sessionStorage.getItem("jwtToken"); + + const refreshTokenStored = + localStorage.getItem("refreshToken") || + sessionStorage.getItem("refreshToken"); + + if (!refreshTokenStored){ + console.log("no refrh tokem"); + removeSession() + return false + }; + + if (isTokenExpired(token)) { + return await attemptTokenRefresh(refreshTokenStored); + } + + return true; +}; + +const attemptTokenRefresh = async (storedRefreshToken) => { + try { + const currentToken = + localStorage.getItem("jwtToken") || + sessionStorage.getItem("jwtToken"); + + const response = await AuthRepository.refreshToken({ + token: currentToken, + refreshToken: storedRefreshToken, + }); + + const { token: newToken, refreshToken: newRefreshToken } = response.data; + + if (localStorage.getItem("jwtToken")) { + localStorage.setItem("jwtToken", newToken); + localStorage.setItem("refreshToken", newRefreshToken); + } else { + sessionStorage.setItem("jwtToken", newToken); + sessionStorage.setItem("refreshToken", newRefreshToken); + } + + return true; + } catch (error) { + console.error("Token refresh failed:", error); + return false; + } +}; const ProtectedRoute = () => { - // const isAuthenticated = localStorage.getItem("jwtToken"); // Example authentication check - // // const isAuthenticated = true; - // isTokenValid(); - // return isAuthenticated ? : - const [isAuthenticated, setIsAuthenticated] = useState(null); useEffect(() => { @@ -21,80 +77,10 @@ const ProtectedRoute = () => { }, []); if (isAuthenticated === null) { - return
Loading...
; // Show a loader while checking + return
Loading...
; } return isAuthenticated ? : ; }; -// Function to check if the token is expired -const isTokenExpired = (token) => { - if (!token) return true; - try { - const { exp } = jwtDecode(token); - return exp * 1000 < Date.now(); // Check if expired - } catch (error) { - return true; // If decoding fails, treat as expired - } -}; - -// Function to validate and refresh the token if expired -export const validateToken = async () => { - const token = localStorage.getItem("jwtToken"); - const refreshTokenStored = localStorage.getItem("refreshToken"); - // If refresh token is absent, cannot proceed - if (!refreshTokenStored) { - console.warn("No refresh token available. Redirecting to login."); - return false; - } - - // If access token expired, try to refresh - if (isTokenExpired(token)) { - return await attemptTokenRefresh(refreshTokenStored); - } - return true; -}; - -// Attempt to refresh the access token -const attemptTokenRefresh = async (storedRefreshToken) => { - try { - const response = await AuthRepository.refreshToken({ - token: localStorage.getItem("jwtToken"), - refreshToken: storedRefreshToken, - }); - - localStorage.setItem("jwtToken", response.data.token); - localStorage.setItem("refreshToken", response.data.refreshToken); - return true; - // api - // .post("/api/auth/refresh-token", { - // token: localStorage.getItem("jwtToken"), - // refreshToken: refreshToken, - // }) - // .then((data) => { - // localStorage.setItem("jwtToken", response.data.token); - // localStorage.setItem("refreshToken", response.data.refreshToken); - // return true; - // }) - // .catch((error) => { - // console.error("Token refresh failed:", error); - // }); - - // const refreshToken = localStorage.getItem("refreshToken"); - // const response = await axiosClient.post(`/api/auth/refresh-token`, { - // token: localStorage.getItem("jwtToken"), - // refreshToken: refreshToken, - // }); - - // if (response.status === 200) { - // localStorage.setItem("jwtToken", response.data.token); - // localStorage.setItem("refreshToken", response.data.refreshToken); - // return true; - // } - } catch (error) { - console.error("Token refresh failed:", error); - return false; - } -}; - export default ProtectedRoute; diff --git a/src/slices/globalVariablesSlice.jsx b/src/slices/globalVariablesSlice.jsx index 8e656000..cd861ec7 100644 --- a/src/slices/globalVariablesSlice.jsx +++ b/src/slices/globalVariablesSlice.jsx @@ -5,6 +5,7 @@ const globalVariablesSlice = createSlice({ initialState: { loginUser: null, currentTenant: null, + selectedServiceId : null }, reducers: { setGlobalVariable: (state, action) => { @@ -17,9 +18,12 @@ const globalVariablesSlice = createSlice({ setCurrentTenant: (state, action) => { state.currentTenant = action.payload; }, + setService: (state, action) => { + state.selectedServiceId = action.payload; + }, }, }); -export const { setGlobalVariable, setLoginUserPermmisions, setCurrentTenant } = +export const { setGlobalVariable, setLoginUserPermmisions, setCurrentTenant, setService} = globalVariablesSlice.actions; export default globalVariablesSlice.reducer; diff --git a/src/utils/appUtils.js b/src/utils/appUtils.js index 8ee3b42e..6f652db9 100644 --- a/src/utils/appUtils.js +++ b/src/utils/appUtils.js @@ -1,31 +1,31 @@ import { useEffect, useState } from "react"; import { format, parseISO } from "date-fns"; -export const formatFileSize=(bytes)=> { +export const formatFileSize = (bytes) => { if (bytes < 1024) return bytes + " B"; else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB"; else return (bytes / (1024 * 1024)).toFixed(2) + " MB"; -} +}; export const AppColorconfig = { colors: { - primary: '#696cff', - secondary: '#8592a3', - success: '#71dd37', - info: '#03c3ec', - warning: '#ffab00', - danger: '#ff3e1d', - dark: '#233446', - black: '#000', - white: '#fff', - cardColor: '#fff', - bodyBg: '#f5f5f9', - bodyColor: '#697a8d', - headingColor: '#566a7f', - textMuted: '#a1acb8', - borderColor: '#eceef1' - } + primary: "#696cff", + secondary: "#8592a3", + success: "#71dd37", + info: "#03c3ec", + warning: "#ffab00", + danger: "#ff3e1d", + dark: "#233446", + black: "#000", + white: "#fff", + cardColor: "#fff", + bodyBg: "#f5f5f9", + bodyColor: "#697a8d", + headingColor: "#566a7f", + textMuted: "#a1acb8", + borderColor: "#eceef1", + }, }; export const getColorNameFromHex = (hex) => { - const normalizedHex = hex?.replace(/'/g, '').toLowerCase(); + const normalizedHex = hex?.replace(/'/g, "").toLowerCase(); const colors = AppColorconfig.colors; for (const [name, value] of Object.entries(colors)) { @@ -62,21 +62,19 @@ export const getIconByFileType = (type = "") => { return "bx bx-file"; }; - export const normalizeAllowedContentTypes = (allowedContentType) => { if (!allowedContentType) return []; if (Array.isArray(allowedContentType)) return allowedContentType; - if (typeof allowedContentType === "string") return allowedContentType.split(","); + if (typeof allowedContentType === "string") + return allowedContentType.split(","); return []; }; - export function localToUtc(localDateString) { if (!localDateString || localDateString.trim() === "") return null; - const [day, month, year] = localDateString.split("-").map(Number); - if (!day || !month || !year) return null; + const [day, month, year] = localDateString.split("-"); + const date = new Date(`${year}-${month}-${day}T00:00:00`); - const date = new Date(Date.UTC(year, month - 1, day)); - return date.toISOString(); + return isNaN(date.getTime()) ? null : date.toISOString(); } diff --git a/src/utils/authUtils.js b/src/utils/authUtils.js index e69de29b..8e266583 100644 --- a/src/utils/authUtils.js +++ b/src/utils/authUtils.js @@ -0,0 +1,7 @@ +export const removeSession = () => { + localStorage.removeItem("jwtToken"); + localStorage.removeItem("refreshToken"); + sessionStorage.removeItem("jwtToken"); + sessionStorage.removeItem("refreshToken"); + localStorage.removeItem("ctnt"); +}; \ No newline at end of file diff --git a/src/utils/axiosClient.jsx b/src/utils/axiosClient.jsx index 62e9b3bb..bf26500e 100644 --- a/src/utils/axiosClient.jsx +++ b/src/utils/axiosClient.jsx @@ -16,14 +16,14 @@ export const axiosClient = axios.create({ // Auto retry failed requests (e.g., network issues) axiosRetry(axiosClient, { retries: 3 }); - // Request Interceptor — Add Bearer token if required axiosClient.interceptors.request.use( async (config) => { const requiresAuth = config.authRequired !== false; // default to true if (requiresAuth) { - const token = localStorage.getItem("jwtToken"); + const token = + localStorage.getItem("jwtToken") || sessionStorage.getItem("jwtToken"); if (token) { config.headers["Authorization"] = `Bearer ${token}`; config._retry = true; @@ -72,7 +72,7 @@ axiosClient.interceptors.response.use( if (status === 401 && !isRefreshRequest) { originalRequest._retry = true; - const refreshToken = localStorage.getItem("refreshToken"); + const refreshToken = localStorage.getItem("refreshToken") || sessionStorage.getItem("refreshToken"); if ( !refreshToken || @@ -87,15 +87,20 @@ axiosClient.interceptors.response.use( try { // Refresh token call const res = await axiosClient.post("/api/Auth/refresh-token", { - token: localStorage.getItem("jwtToken"), + token: localStorage.getItem("jwtToken") || sessionStorage.getItem("jwtToken"), refreshToken, }); const { token, refreshToken: newRefreshToken } = res.data.data; // Save updated tokens - localStorage.setItem("jwtToken", token); - localStorage.setItem("refreshToken", newRefreshToken); + if (localStorage.getItem("jwtToken")) { + localStorage.setItem("jwtToken", token); + localStorage.setItem("refreshToken", newRefreshToken); + } else { + sessionStorage.setItem("jwtToken", token); + sessionStorage.setItem("refreshToken", newRefreshToken); + } startSignalR(); // Set Authorization header diff --git a/src/utils/constants.jsx b/src/utils/constants.jsx index 1af801ca..62c686c9 100644 --- a/src/utils/constants.jsx +++ b/src/utils/constants.jsx @@ -1,54 +1,53 @@ -export const THRESH_HOLD = 48; // hours +export const THRESH_HOLD = 48; // hours export const DURATION_TIME = 10; // minutes export const ITEMS_PER_PAGE = 20; -export const OTP_EXPIRY_SECONDS = 300 // OTP time +export const OTP_EXPIRY_SECONDS = 300; // OTP time export const MANAGE_MASTER = "588a8824-f924-4955-82d8-fc51956cf323"; -export const VIEW_MASTER = "5ffbafe0-7ab0-48b1-bb50-c1bf76b65f9d" +export const VIEW_MASTER = "5ffbafe0-7ab0-48b1-bb50-c1bf76b65f9d"; -export const MANAGE_PROJECT = "172fc9b6-755b-4f62-ab26-55c34a330614" +export const MANAGE_PROJECT = "172fc9b6-755b-4f62-ab26-55c34a330614"; -export const VIEW_PROJECTS = "6ea44136-987e-44ba-9e5d-1cf8f5837ebc" +export const VIEW_PROJECTS = "6ea44136-987e-44ba-9e5d-1cf8f5837ebc"; -export const MANAGE_EMPLOYEES = "a97d366a-c2bb-448d-be93-402bd2324566" +export const MANAGE_EMPLOYEES = "a97d366a-c2bb-448d-be93-402bd2324566"; -export const VIEW_ALL_EMPLOYEES = "60611762-7f8a-4fb5-b53f-b1139918796b" +export const VIEW_ALL_EMPLOYEES = "60611762-7f8a-4fb5-b53f-b1139918796b"; -export const VIEW_TEAM_MEMBERS = "b82d2b7e-0d52-45f3-997b-c008ea460e7f" +export const VIEW_TEAM_MEMBERS = "b82d2b7e-0d52-45f3-997b-c008ea460e7f"; -export const MANAGE_TEAM = "b94802ce-0689-4643-9e1d-11c86950c35b" +export const MANAGE_TEAM = "b94802ce-0689-4643-9e1d-11c86950c35b"; -export const MANAGE_PROJECT_INFRA = "cf2825ad-453b-46aa-91d9-27c124d63373" +export const MANAGE_PROJECT_INFRA = "cf2825ad-453b-46aa-91d9-27c124d63373"; -export const VIEW_PROJECT_INFRA = "8d7cc6e3-9147-41f7-aaa7-fa507e450bd4" - -export const REGULARIZE_ATTENDANCE ="57802c4a-00aa-4a1f-a048-fd2f70dd44b6" -export const TEAM_ATTENDANCE = "915e6bff-65f6-4e3f-aea8-3fd217d3ea9e" -export const SELF_ATTENDANCE = "ccb0589f-712b-43de-92ed-5b6088e7dc4e" +export const VIEW_PROJECT_INFRA = "8d7cc6e3-9147-41f7-aaa7-fa507e450bd4"; +export const REGULARIZE_ATTENDANCE = "57802c4a-00aa-4a1f-a048-fd2f70dd44b6"; +export const TEAM_ATTENDANCE = "915e6bff-65f6-4e3f-aea8-3fd217d3ea9e"; +export const SELF_ATTENDANCE = "ccb0589f-712b-43de-92ed-5b6088e7dc4e"; export const ASSIGN_TO_PROJECT = "b94802ce-0689-4643-9e1d-11c86950c35b"; export const INFRASTRUCTURE = "9666de86-d7c7-4d3d-acaa-fcd6d6b81f3c"; -export const MANAGE_TASK = "08752f33-3b29-4816-b76b-ea8a968ed3c5" +export const MANAGE_TASK = "08752f33-3b29-4816-b76b-ea8a968ed3c5"; -export const APPROVE_TASK = "db4e40c5-2ba9-4b6d-b8a6-a16a250ff99c" +export const APPROVE_TASK = "db4e40c5-2ba9-4b6d-b8a6-a16a250ff99c"; -export const VIEW_TASK = "9fcc5f87-25e3-4846-90ac-67a71ab92e3c" +export const VIEW_TASK = "9fcc5f87-25e3-4846-90ac-67a71ab92e3c"; -export const ASSIGN_REPORT_TASK = "6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2" +export const ASSIGN_REPORT_TASK = "6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2"; // ------------------------Directory------------------------------------- -export const DIRECTORY_ADMIN = "4286a13b-bb40-4879-8c6d-18e9e393beda" +export const DIRECTORY_ADMIN = "4286a13b-bb40-4879-8c6d-18e9e393beda"; -export const DIRECTORY_MANAGER = "62668630-13ce-4f52-a0f0-db38af2230c5" +export const DIRECTORY_MANAGER = "62668630-13ce-4f52-a0f0-db38af2230c5"; -export const DIRECTORY_USER = "0f919170-92d4-4337-abd3-49b66fc871bb" +export const DIRECTORY_USER = "0f919170-92d4-4337-abd3-49b66fc871bb"; // -----------------------Expense---------------------------------------- -export const VIEW_SELF_EXPENSE = "385be49f-8fde-440e-bdbc-3dffeb8dd116" +export const VIEW_SELF_EXPENSE = "385be49f-8fde-440e-bdbc-3dffeb8dd116"; export const VIEW_ALL_EXPNESE = "01e06444-9ca7-4df4-b900-8c3fa051b92f"; @@ -58,19 +57,22 @@ export const REVIEW_EXPENSE = "1f4bda08-1873-449a-bb66-3e8222bd871b"; export const APPROVE_EXPENSE = "eaafdd76-8aac-45f9-a530-315589c6deca"; -export const PROCESS_EXPENSE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11" +export const PROCESS_EXPENSE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11"; -export const EXPENSE_MANAGE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11" +export const EXPENSE_MANAGE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11"; -export const EXPENSE_REJECTEDBY = ["d1ee5eec-24b6-4364-8673-a8f859c60729","965eda62-7907-4963-b4a1-657fb0b2724b"] +export const EXPENSE_REJECTEDBY = [ + "d1ee5eec-24b6-4364-8673-a8f859c60729", + "965eda62-7907-4963-b4a1-657fb0b2724b", +]; -export const EXPENSE_DRAFT = "297e0d8f-f668-41b5-bfea-e03b354251c8" +export const EXPENSE_DRAFT = "297e0d8f-f668-41b5-bfea-e03b354251c8"; // ----------------------------Tenant------------------------- -export const SUPPER_TENANT = "d032cb1a-3f30-462c-bef0-7ace73a71c0b" -export const MANAGE_TENANTS = "00e20637-ce8d-4417-bec4-9b31b5e65092" -export const VIEW_TENANTS = "647145c6-2108-4c98-aab4-178602236e55" -export const ActiveTenant = "297e0d8f-f668-41b5-bfea-e03b354251c8" +export const SUPPER_TENANT = "d032cb1a-3f30-462c-bef0-7ace73a71c0b"; +export const MANAGE_TENANTS = "00e20637-ce8d-4417-bec4-9b31b5e65092"; +export const VIEW_TENANTS = "647145c6-2108-4c98-aab4-178602236e55"; +export const ActiveTenant = "297e0d8f-f668-41b5-bfea-e03b354251c8"; // ---------------------Documents--------------------------------- export const VIEW_DOCUMENT = "71189504-f1c8-4ca5-8db6-810497be2854"; @@ -81,43 +83,64 @@ export const DOWNLOAD_DOCUMENT = "404373d0-860f-490e-a575-1c086ffbce1d"; export const VERIFY_DOCUMENT = "13a1f30f-38d1-41bf-8e7a-b75189aab8e0"; // -------------------Application Role------------------------------ -// 1 - Expense Manage -export const EXPENSE_MANAGEMENT = "a4e25142-449b-4334-a6e5-22f70e4732d7" +// 1 - Expense Manage +export const EXPENSE_MANAGEMENT = "a4e25142-449b-4334-a6e5-22f70e4732d7"; export const TENANT_STATUS = [ - {id:"62b05792-5115-4f99-8ff5-e8374859b191",name:"Active"}, - {id:"c0b5def8-087e-4235-b3a4-8e2f0ed91b94",name:"In Active"}, - {id:"35d7840a-164a-448b-95e6-efb2ec84a751",name:"Supspended"} -] + { id: "62b05792-5115-4f99-8ff5-e8374859b191", name: "Active" }, + { id: "c0b5def8-087e-4235-b3a4-8e2f0ed91b94", name: "In Active" }, + { id: "35d7840a-164a-448b-95e6-efb2ec84a751", name: "Supspended" }, +]; export const DOCUMENTS_ENTITIES = { - ProjectEntity : "c8fe7115-aa27-43bc-99f4-7b05fabe436e", - EmployeeEntity:"dbb9555a-7a0c-40f2-a9ed-f0463f1ceed7", -} + ProjectEntity: "c8fe7115-aa27-43bc-99f4-7b05fabe436e", + EmployeeEntity: "dbb9555a-7a0c-40f2-a9ed-f0463f1ceed7", +}; - -export const CONSTANT_TEXT = { - -} +export const CONSTANT_TEXT = {}; export const SUBSCRIPTION_PLAN_FREQUENCIES = { 0: "Monthly", - 1:"Quarterly", - 2:"Half-Yearly", - 3:"Yearly" -} + 1: "Quarterly", + 2: "Half-Yearly", + 3: "Yearly", +}; export const reference = [ - { val: "google", name: "Google" }, - { val: "frineds", name: "Friends" }, - { val: "advertisement", name: "Advertisement" }, - { val: "root tenant", name: "Root Tenant" }, - ]; -export const orgSize = [ - { val: "1-50", name: "1-50" }, - { val: "51-100", name: "51-100" }, - { val: "101-500", name: "101-500" }, - { val: "500+", name: "500+" }, + { val: "google", name: "Google" }, + { val: "frineds", name: "Friends" }, + { val: "advertisement", name: "Advertisement" }, + { val: "root tenant", name: "Root Tenant" }, ]; +export const orgSize = [ + { val: "1-50", name: "1-50" }, + { val: "51-100", name: "51-100" }, + { val: "101-500", name: "101-500" }, + { val: "500+", name: "500+" }, +]; + +export const PROJECT_STATUS = [ + { + 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", + }, +]; +export const DEFAULT_EMPTY_STATUS_ID = "00000000-0000-0000-0000-000000000000"; export const BASE_URL = process.env.VITE_BASE_URL;
{col.label}
{col.getValue(row)}
Not Assigned yet diff --git a/src/components/Project/ProjectOrganizations.jsx b/src/components/Project/ProjectOrganizations.jsx index d18a9375..b5a91fa6 100644 --- a/src/components/Project/ProjectOrganizations.jsx +++ b/src/components/Project/ProjectOrganizations.jsx @@ -7,12 +7,12 @@ const ProjectOrganizations = () => { const { onOpen, startStep, flowType } = useOrganizationModal(); const selectedProject = useSelectedProject(); return ( -
+
+ +
+
+ ); +}; + +export default TeamEmployeeList; diff --git a/src/components/Project/Team/Teams.jsx b/src/components/Project/Team/Teams.jsx new file mode 100644 index 00000000..1f58971c --- /dev/null +++ b/src/components/Project/Team/Teams.jsx @@ -0,0 +1,333 @@ +import React, { useState, useEffect, useCallback, useMemo } from "react"; +import { Link, NavLink, useNavigate, useParams } from "react-router-dom"; + +import showToast from "../../../services/toastService"; +import Avatar from "../../common/Avatar"; +import moment from "moment"; + +import ProjectRepository from "../../../repositories/ProjectRepository"; +import { useDispatch, useSelector } from "react-redux"; +import { changeMaster } from "../../../slices/localVariablesSlice"; +import useMaster from "../../../hooks/masterHook/useMaster"; +import { useHasUserPermission } from "../../../hooks/useHasUserPermission"; +import { ASSIGN_TO_PROJECT } from "../../../utils/constants"; +import ConfirmModal from "../../common/ConfirmModal"; +import eventBus from "../../../services/eventBus"; +import { + useEmployeesByProjectAllocated, + useManageProjectAllocation, + useProjectAssignedServices, +} from "../../../hooks/useProjects"; +import { useSelectedProject } from "../../../slices/apiDataManager"; +import GlobalModel from "../../common/GlobalModel"; +import TeamAssignToProject from "./TeamAssignToProject"; + +const Teams = () => { + const selectedProject = useSelectedProject(); + const dispatch = useDispatch(); + const [AssigTeam, setAssignTeam] = useState(false); + const [employees, setEmployees] = useState([]); + const [selectedEmployee, setSelectedEmployee] = useState(null); + const [deleteEmployee, setDeleteEmplyee] = useState(null); + const [searchTerm, setSearchTerm] = useState(""); // State for search term + const [selectedService, setSelectedService] = useState(null); + const [activeEmployee, setActiveEmployee] = useState(false); + + const { data: assignedServices, isLoading: servicesLoading } = + useProjectAssignedServices(selectedProject); + const { data: empJobRoles, loading } = useMaster(); + const handleToggleActive = (e) => setActiveEmployee(e.target.checked); + + const handleServiceChange = (e) => { + setSelectedService(e.target.value); + }; + + const navigate = useNavigate(); + + const HasAssignUserPermission = useHasUserPermission(ASSIGN_TO_PROJECT); + const [IsDeleteModal, setIsDeleteModal] = useState(false); + const { + projectEmployees, + loading: employeeLodaing, + refetch, + } = useEmployeesByProjectAllocated( + selectedProject, + selectedService, + null, + activeEmployee + ); + const { + mutate: submitAllocations, + isPending, + isSuccess, + isError, + } = useManageProjectAllocation({ + onSuccessCallback: () => { + setSelectedEmployee(null); + }, + onErrorCallback: () => { + setSelectedEmployee(null); + }, + }); + + const handleDelete = (employee) => { + let payload = [ + { + employeeId: employee?.employeeId, + jobRoleId: employee?.jobRoleId, + projectId: selectedProject, + serviceId: employee?.serviceId, + status: false, + }, + ]; + + submitAllocations({ payload: payload, actionType: "remove" }); + }; + const getJobRole = (jobRoleId) => { + if (loading) return "Loading..."; + if (!Array.isArray(empJobRoles)) return "Unassigned"; + if (!jobRoleId) return "Unassigned"; + + const role = empJobRoles.find((b) => b.id == jobRoleId); + return role ? role.name : "Unassigned"; + }; + + const filteredEmployees = useMemo(() => { + if (!projectEmployees || !searchTerm?.trim()) return projectEmployees; + + const lower = searchTerm.toLowerCase(); + + return projectEmployees?.filter((emp) => { + const fullName = `${emp.firstName ?? ""} ${ + emp.lastName ?? "" + }`.toLowerCase(); + + const joberole = getJobRole(emp?.jobRoleId)?.toLowerCase(); + + return fullName?.includes(lower) || joberole?.includes(lower); + }); + }, [projectEmployees, searchTerm]); + const handleSearch = (e) => setSearchTerm(e.target.value); + const employeeHandler = useCallback( + (msg) => { + if (filteredEmployees.some((emp) => emp.employeeId == msg.employeeId)) { + refetch(); + } + }, + [filteredEmployees, refetch] + ); + + useEffect(() => { + eventBus.on("employee", employeeHandler); + return () => eventBus.off("employee", employeeHandler); + }, [employeeHandler]); + + return ( + <> + {AssigTeam && ( + setAssignTeam(false)} + > + setAssignTeam(false)} /> + + )} + + handleDelete(selectedEmployee)} + onClose={() => setSelectedEmployee(null)} + /> + +
+
+
+
+
+
+ {!servicesLoading && ( + <> + {(!assignedServices || assignedServices.length === 0) && ( + + Not Service Assigned + + )} + + {assignedServices?.length === 1 && ( + + {assignedServices[0].name} + + )} + + {assignedServices?.length > 1 && ( + + )} + + )} +
+ +
+ + +
+
+
+ +
+ + + {HasAssignUserPermission && ( + + )} +
+
+ +
+ {employeeLodaing &&

Loading..

} + {projectEmployees && projectEmployees.length > 0 && ( + + + + + + + + {activeEmployee && } + + + + + + {filteredEmployees && + filteredEmployees + .sort((a, b) => + (a.firstName || "").localeCompare(b.firstName || "") + ) + .map((emp) => ( + + + + + + + + {activeEmployee && ( + + )} + + + + ))} + +
+
Name
+
ServicesOrganizationAssigned DateRelease DateProject RoleActions
+ + {emp.serviceName || "N/A"}{emp.organizationName || "N/A"} + {moment(emp.allocationDate).format("DD-MMM-YYYY")} + + {emp.reAllocationDate + ? moment(emp.reAllocationDate).format( + "DD-MMM-YYYY" + ) + : "Present"} + + + {getJobRole(emp.jobRoleId)} + + + {emp.isActive ? ( + + ) : ( + Not in project + )} +
+ )} + {!employeeLodaing && filteredEmployees.length === 0 && ( +
+

+ {!activeEmployee + ? "No active employees assigned to the project" + : "No inactive employees assigned to the project"} +

+
+ )} +
+
+
+ + ); +}; + +export default Teams; diff --git a/src/components/Project/Teams.jsx b/src/components/Project/Teams.jsx deleted file mode 100644 index 1fe7838e..00000000 --- a/src/components/Project/Teams.jsx +++ /dev/null @@ -1,443 +0,0 @@ -import React, { useState, useEffect, useCallback } from "react"; -import MapUsers from "./MapUsers"; -import { Link, NavLink, useNavigate, useParams } from "react-router-dom"; - -import showToast from "../../services/toastService"; -import Avatar from "../common/Avatar"; -import moment from "moment"; - -import ProjectRepository from "../../repositories/ProjectRepository"; -import { useDispatch, useSelector } from "react-redux"; -import { changeMaster } from "../../slices/localVariablesSlice"; -import useMaster from "../../hooks/masterHook/useMaster"; -import { useHasUserPermission } from "../../hooks/useHasUserPermission"; -import { ASSIGN_TO_PROJECT } from "../../utils/constants"; -import ConfirmModal from "../common/ConfirmModal"; -import eventBus from "../../services/eventBus"; -import { - useEmployeesByProjectAllocated, - useManageProjectAllocation, - useProjectAssignedServices, -} from "../../hooks/useProjects"; -import { useSelectedProject } from "../../slices/apiDataManager"; - -const Teams = () => { - const projectId = useSelectedProject(); - const dispatch = useDispatch(); - - const { data, loading } = useMaster(); - const [isModalOpen, setIsModelOpen] = useState(false); - const [error, setError] = useState(""); - const [empJobRoles, setEmpJobRoles] = useState(null); - const [employees, setEmployees] = useState([]); - const [filteredEmployees, setFilteredEmployees] = useState([]); - const [removingEmployeeId, setRemovingEmployeeId] = useState(null); - const [assignedLoading, setAssignedLoading] = useState(false); - const [activeEmployee, setActiveEmployee] = useState(true); - const [deleteEmployee, setDeleteEmplyee] = useState(null); - const [searchTerm, setSearchTerm] = useState(""); // State for search term - const [selectedService, setSelectedService] = useState(null); - - const { data: assignedServices, isLoading: servicesLoading } = useProjectAssignedServices(projectId); - const handleToggleActive = e => setActiveEmployee(e.target.checked); - - - const handleServiceChange = (e) => { - setSelectedService(e.target.value); - }; - - const navigate = useNavigate(); - - const HasAssignUserPermission = useHasUserPermission(ASSIGN_TO_PROJECT); - const [IsDeleteModal, setIsDeleteModal] = useState(false); - const { - projectEmployees, - loading: employeeLodaing, - refetch, - } = useEmployeesByProjectAllocated(projectId, selectedService); - const { - mutate: submitAllocations, - isPending, - isSuccess, - isError, - } = useManageProjectAllocation({ - onSuccessCallback: () => { - setRemovingEmployeeId(null); - setAssignedLoading(false); - setDeleteEmplyee(null); - closeDeleteModal(); - }, - onErrorCallback: () => { - closeDeleteModal(); - }, - }); - - const removeAllocation = (item) => { - setRemovingEmployeeId(item.id); - - submitAllocations({ - items: [ - { - empID: item.employeeId, - jobRoleId: item.jobRoleId, - projectId: projectId, - status: false, - }, - ], - added: false, - }); - }; - - const handleEmpAlicationFormSubmit = (allocaionObj) => { - let items = allocaionObj.map((item) => { - return { - empID: item.empID, - jobRoleId: item.jobRoleId, - projectId: projectId, - status: true, - }; - }); - - submitAllocations({ items, added: true }); - - setActiveEmployee(true); - setFilteredEmployees(employees.filter((emp) => emp.isActive)); - - const dropdown = document.querySelector( - 'select[name="DataTables_Table_0_length"]' - ); - if (dropdown) dropdown.value = "true"; - }; - - const getRole = (jobRoleId) => { - if (loading) return "Loading..."; - if (!Array.isArray(empJobRoles)) return "Unassigned"; - if (!jobRoleId) return "Unassigned"; - - const role = empJobRoles.find((b) => b.id == jobRoleId); - return role ? role.name : "Unassigned"; - }; - const openModel = () => { - setIsModelOpen(true); - }; - - const onModelClose = () => { - setIsModelOpen(false); - const modalElement = document.getElementById("user-model"); - if (modalElement) { - modalElement.classList.remove("show"); - modalElement.style.display = "none"; - document.body.classList.remove("modal-open"); - document.querySelector(".modal-backdrop").remove(); - } - const modalBackdropElement = document.querySelector(".modal-backdrop"); - if (modalBackdropElement) { - modalBackdropElement.remove(); - } - document.body.style.overflow = "auto"; - }; - - useEffect(() => { - dispatch(changeMaster("Job Role")); - }, [dispatch]); - - useEffect(() => { - if (projectEmployees) { - setEmployees(projectEmployees); - const filtered = projectEmployees.filter((emp) => emp.isActive); - setFilteredEmployees(filtered); - } - }, [projectEmployees, employeeLodaing]); - - useEffect(() => { - if (data) { - setEmpJobRoles(data); - } - }, [data]); - const filterAndSearchEmployees = useCallback(() => { - const statusFiltered = employees.filter((emp) => - activeEmployee ? emp.isActive : !emp.isActive - ); - - if (searchTerm === "") { - setFilteredEmployees(statusFiltered); - return; - } - - const lowercasedSearchTerm = searchTerm.toLowerCase(); - - const searchedAndFiltered = statusFiltered.filter((item) => { - const fullName = `${item.firstName} ${item.middleName} ${item.lastName}`.toLowerCase(); - const roleName = getRole(item.jobRoleId).toLowerCase(); - const orgName = (item.organizationName || "").toLowerCase(); - const serviceName = (item.serviceName || "").toLowerCase(); - - return ( - fullName.includes(lowercasedSearchTerm) || - roleName.includes(lowercasedSearchTerm) || - orgName.includes(lowercasedSearchTerm) || - serviceName.includes(lowercasedSearchTerm) - ); - }); - - setFilteredEmployees(searchedAndFiltered); - }, [employees, activeEmployee, searchTerm, getRole]); - - - useEffect(() => { - filterAndSearchEmployees(); - }, [employees, activeEmployee, searchTerm, filterAndSearchEmployees]); - - const handleFilterEmployee = (e) => { - const filterValue = e.target.value; - setActiveEmployee(filterValue === "true"); - setSearchTerm(""); - }; - - const handleSearch = (e) => { - setSearchTerm(e.target.value); - }; - - const deleteModalOpen = (item) => { - setDeleteEmplyee(item); - setIsDeleteModal(true); - }; - const closeDeleteModal = () => setIsDeleteModal(false); - - const handler = useCallback( - (msg) => { - if (msg.projectIds.some((item) => item === projectId)) { - refetch(); - } - }, - [projectId, refetch] - ); - - useEffect(() => { - eventBus.on("assign_project_all", handler); - return () => eventBus.off("assign_project_all", handler); - }, [handler]); - - const employeeHandler = useCallback( - (msg) => { - if (filteredEmployees.some((item) => item.employeeId == msg.employeeId)) { - refetch(); - } - }, - [filteredEmployees, refetch] - ); - - useEffect(() => { - eventBus.on("employee", employeeHandler); - return () => eventBus.off("employee", employeeHandler); - }, [employeeHandler]); - - return ( - <> - - - {IsDeleteModal && ( - removeAllocation(deleteEmployee)} - onClose={closeDeleteModal} - loading={isPending} - /> - )} - -
-
-
-
-
- {!servicesLoading && assignedServices?.length > 0 && ( - assignedServices.length > 1 ? ( - - ) : ( -
{assignedServices[0].name}
- ) - )} -
-
-
-
- - -
-
- -
- - -
-
-
- {employeeLodaing &&

Loading..

} - {!employeeLodaing && - filteredEmployees && - filteredEmployees.length > 0 && ( - - - - - - - - {!activeEmployee && } - - - - - - {filteredEmployees && - filteredEmployees.map((item) => ( - - - - - - - - {!activeEmployee && ( - - )} - - - - ))} - -
-
Name
-
ServicesOrganizationAssigned DateRelease DateProject RoleActions
- - {item.serviceName || "N/A"}{item.organizationName || "N/A"} - {moment(item.allocationDate).format("DD-MMM-YYYY")} - - {item.reAllocationDate - ? moment(item.reAllocationDate).format("DD-MMM-YYYY") - : "Present"} - - - {getRole(item.jobRoleId)} - - - {item.isActive ? ( - - ) : ( - Not in project - )} -
- - )} - {!employeeLodaing && filteredEmployees.length === 0 && ( -
- {activeEmployee - ? "No active employees assigned to the project" - : "No inactive employees assigned to the project"} -
- )} -
-
-
- - ); -}; - -export default Teams; diff --git a/src/components/Tenant/EditProfile.jsx b/src/components/Tenant/EditProfile.jsx index 8e355737..41a99f27 100644 --- a/src/components/Tenant/EditProfile.jsx +++ b/src/components/Tenant/EditProfile.jsx @@ -72,15 +72,15 @@ const EditProfile = ({ TenantId, onClose }) => { return (
-
Edit Tenant
+
Edit Tenant
-
+
{errors.firstName &&
{errors.firstName.message}
}
-
+
{errors.lastName &&
{errors.lastName.message}
} @@ -88,32 +88,32 @@ const EditProfile = ({ TenantId, onClose }) => { -
+
{errors.contactNumber &&
{errors.contactNumber.message}
}
-
+
{errors.domainName &&
{errors.domainName.message}
}
-
+
{errors.taxId &&
{errors.taxId.message}
}
-
+
{errors.officeNumber &&
{errors.officeNumber.message}
}
-
+
{reference.map((org) => ( @@ -134,7 +134,7 @@ const EditProfile = ({ TenantId, onClose }) => { {errors.reference &&
{errors.reference.message}
}
-
+
@@ -154,19 +154,19 @@ const EditProfile = ({ TenantId, onClose }) => { )}
-
+
+ + {errors.description && ( +

{errors.description.message}

+ )} +
+ +
+ + +
+
+ + ); +}; + +export default ManageGroup; diff --git a/src/components/master/Services/ServicesGroups.jsx b/src/components/master/Services/ServicesGroups.jsx new file mode 100644 index 00000000..b4262351 --- /dev/null +++ b/src/components/master/Services/ServicesGroups.jsx @@ -0,0 +1,263 @@ +import { useState } from "react"; +import { + useActivitiesByGroups, + useGroups, +} from "../../../hooks/masterHook/useMaster"; +import ManageGroup from "./ManageGroup"; +import ManageActivity from "./ManageActivity"; +import { useMasterContext } from "../../../pages/master/MasterPage"; +const ServiceGroups = ({ service }) => { + const [openService, setOpenService] = useState(true); + const [activeGroupId, setActiveGroupId] = useState(null); // track selected group + const {setDeleletingServiceItem} =useMasterContext() + const [isManageGroup, setManageGroup] = useState({ + isOpen: false, + group: null, + serviceId: null, + }); + const [isManageActivity, setManageActivity] = useState({ + isOpen: false, + activity: null, // activity is either a single activity for editing or null for creating new activity + groupId: null, // groupId for managing activities in specific groups + }); + + // Fetch groups and activities data + const { data: groups, isLoading } = useGroups(service?.id); // Fetch groups for the service + const { data: activities, isLoading: actLoading } = + useActivitiesByGroups(activeGroupId); // Fetch activities based on activeGroupId + + if (isLoading) return
Loading groups...
; // Show loading state while groups are being fetched + + // Handle toggling of group to view activities + const toggleGroup = (groupId) => { + setActiveGroupId((prev) => (prev === groupId ? null : groupId)); // If the same group is clicked again, close it + }; + + return ( + +
+ +

Manage Service

+
+
+ {/* Service Header */} +
+

{service.name}

+ +
+ + {/* Groups Section */} +
+ {/* Show ManageGroup for creating a new group */} + {isManageGroup.isOpen && isManageGroup.group === null ? ( + + setManageGroup({ + isOpen: false, + group: null, + serviceId: service.id, + }) + } + /> + ) : ( +
+
+ + {groups?.data?.map((group) => { + const isOpen = activeGroupId === group.id; + + return ( +
+
+ {/* Show group toggle button only if ManageGroup is not open */} +
+ {!isManageGroup.isOpen && ( + toggleGroup(group.id)} + className="text-end cursor-pointer" + data-bs-toggle="collapse" + data-bs-target={`#accordionGroup${group.id}`} + aria-expanded={isOpen} + aria-controls={`accordionGroup${group.id}`} + > + + + )} +

{group.name}

+
+
+
+ {/* Create New Activity */} + { + setManageActivity({ + isOpen: true, + activity: null, // Indicating new activity creation + groupId: group.id, // Set the groupId for the new activity + }); + }} + /> + {/* Edit Group */} + + setManageGroup({ + isOpen: true, + group: group, // Group selected for Editing + serviceId: service.id, + }) + } + /> + {/* Delete Group */} + setDeleletingServiceItem({isOpen:true,ItemId:group.id,whichItem:"group"})} /> +
+
+
+ + {/* Only show ManageGroup for the specific group if it's open */} + {isManageGroup.isOpen && + isManageGroup.group?.id === group.id ? ( + + setManageGroup({ + isOpen: false, + group: null, + serviceId: null, + }) + } + /> + ) : isManageActivity.isOpen && + isManageActivity.groupId === group.id ? ( + { + setManageActivity({ + isOpen: false, + activity: null, + groupId: null, + }); + }} + /> + ) : ( +
+
+ {isOpen && actLoading && ( +

+ Loading activities... +

+ )} + + {isOpen && activities?.data?.length > 0 ? ( +
+ {/* Header Row */} +
+ + Activity Name + + + Unit of Measurement + + + Action + +
+ + {/* Map through activities */} + {activities.data.map((activity) => ( +
+ {/* Activity Name Column */} +
+ {activity.activityName} +
+ + {/* Unit of Measurement Column */} +
+ {activity.unitOfMeasurement} +
+ + {/* Action Column */} +
+ {/* Edit Activity */} + { + setManageActivity({ + isOpen: true, + activity: activity, // Pass the specific activity for editing + groupId: group.id, // Set groupId for the specific activity + }); + }} + /> + {/* Delete Activity */} + setDeleletingServiceItem({isOpen:true,ItemId:activity.id,whichItem:"activity"})} + /> +
+
+ ))} +
+ ) : ( + isOpen && ( +

+ No activities found +

+ ) + )} +
+
+ )} +
+ ); + })} +
+ )} +
+
+
+
+ ); +}; + +export default ServiceGroups; diff --git a/src/components/master/Services/ServicesSchema.js b/src/components/master/Services/ServicesSchema.js index ceaa8bf5..72f28e50 100644 --- a/src/components/master/Services/ServicesSchema.js +++ b/src/components/master/Services/ServicesSchema.js @@ -6,4 +6,12 @@ const schema = z.object({ .string() .min(1, { message: "Description is required" }) .max(255, { message: "Description cannot exceed 255 characters" }), +}); + +export const ActivityGroupSchema = z.object({ + name: z.string().min(1, { message: "Group Name is required" }), + description: z + .string() + .min(1, { message: "Description is required" }) + .max(255, { message: "Description cannot exceed 255 characters" }), }); \ No newline at end of file diff --git a/src/hooks/masterHook/useMaster.js b/src/hooks/masterHook/useMaster.js index 6e589efe..5b31f79a 100644 --- a/src/hooks/masterHook/useMaster.js +++ b/src/hooks/masterHook/useMaster.js @@ -1,52 +1,77 @@ -import {useState,useEffect, useCallback} from "react" +import { useState, useEffect, useCallback } from "react"; import { MasterRespository } from "../../repositories/MastersRepository"; -import { cacheData,getCachedData } from "../../slices/apiDataManager"; +import { cacheData, getCachedData } from "../../slices/apiDataManager"; import { useSelector } from "react-redux"; -import {useMutation, useQueries, useQuery, useQueryClient} from "@tanstack/react-query"; +import { + useMutation, + useQueries, + useQuery, + useQueryClient, +} from "@tanstack/react-query"; import showToast from "../../services/toastService"; - - - - -export const useServices = ()=>{ +export const useServices = () => { return useQuery({ - queryKey:["services"], - queryFn:async()=> await MasterRespository.getMasterServices() - }) -} -export const useGlobalServices = ()=>{ - return useQuery({ - queryKey:["globalServices"], - queryFn:async()=> await MasterRespository.getGlobalServices() - }) -} + queryKey: ["services"], + queryFn: async () => await MasterRespository.getMasterServices(), + }); +}; -export const useMasterMenu = ()=>{ +export const useGroups = (serviceId) => { return useQuery({ - queryKey:["MasterMenu"], - queryFn:async()=> { + queryKey: ["groups", serviceId], + queryFn: async () => await MasterRespository.getActivityGrops(serviceId), + enabled: !!serviceId, + }); +}; +export const useActivitiesByGroups = (groupId) => { + return useQuery({ + queryKey: ["activties", groupId], + queryFn: async () => await MasterRespository.getActivitesByGroup(groupId), + + enabled: !!groupId, + + }); +}; +export const useGlobalServices = () => { + return useQuery({ + queryKey: ["globalServices"], + queryFn: async () => await MasterRespository.getGlobalServices(), + }); +}; + +export const useMasterMenu = () => { + return useQuery({ + queryKey: ["MasterMenu"], + queryFn: async () => { const resp = await MasterRespository.getMasterMenus(); - return resp.data - } - }) -} + return resp.data; + }, + }); +}; -export const useActivitiesMaster = () => -{ - const { data:activities=[],isLoading:loading,error} = useQuery( { - queryKey: [ "ActivityMaster" ], - queryFn: async() => - { +export const useActivitiesMaster = () => { + const { + data: activities = [], + isLoading: loading, + error, + } = useQuery({ + queryKey: ["ActivityMaster"], + queryFn: async () => { const response = await MasterRespository.getActivites(); - return response.data + return response.data; }, - onError: (error) => { - showToast(error?.response?.data?.message || error.message || "Failed to fetch ActivityMasters", "error"); + onError: (error) => { + showToast( + error?.response?.data?.message || + error.message || + "Failed to fetch ActivityMasters", + "error" + ); }, - } ) - return {activities,loading,error} -} + }); + return { activities, loading, error }; +}; export const useWorkCategoriesMaster = () => { const { data: categories = [], @@ -71,22 +96,28 @@ export const useWorkCategoriesMaster = () => { return { categories, categoryLoading, categoryError }; }; - -export const useContactCategory = () => -{ - const {data:contactCategory=[],isLoading:loading,error:Error } = useQuery( { - queryKey: [ "Contact Category" ], - queryFn: async () => - { +export const useContactCategory = () => { + const { + data: contactCategory = [], + isLoading: loading, + error: Error, + } = useQuery({ + queryKey: ["Contact Category"], + queryFn: async () => { let resp = await MasterRespository.getContactCategory(); - return resp.data + return resp.data; }, - onError: (error) => { - showToast(error?.response?.data?.message || error.message || "Failed to fetch Contact categories", "error"); + onError: (error) => { + showToast( + error?.response?.data?.message || + error.message || + "Failed to fetch Contact categories", + "error" + ); }, - } ) - return { contactCategory,loading,Error} -} + }); + return { contactCategory, loading, Error }; +}; export const useContactTags = () => { const { @@ -112,16 +143,15 @@ export const useContactTags = () => { return { contactTags, loading, error }; }; - -export const useExpenseType =()=>{ -const { +export const useExpenseType = () => { + const { data: ExpenseTypes = [], isLoading: loading, error, } = useQuery({ queryKey: ["Expense Type"], queryFn: async () => { - const res = await MasterRespository.getExpenseType() + const res = await MasterRespository.getExpenseType(); return res.data; }, onError: (error) => { @@ -135,16 +165,16 @@ const { }); return { ExpenseTypes, loading, error }; -} -export const usePaymentMode =()=>{ -const { +}; +export const usePaymentMode = () => { + const { data: PaymentModes = [], isLoading: loading, error, } = useQuery({ queryKey: ["Payment Mode"], queryFn: async () => { - const res = await MasterRespository.getPaymentMode() + const res = await MasterRespository.getPaymentMode(); return res.data; }, onError: (error) => { @@ -158,16 +188,16 @@ const { }); return { PaymentModes, loading, error }; -} -export const useExpenseStatus =()=>{ -const { +}; +export const useExpenseStatus = () => { + const { data: ExpenseStatus = [], isLoading: loading, error, } = useQuery({ queryKey: ["Expense Status"], queryFn: async () => { - const res = await MasterRespository.getExpenseStatus() + const res = await MasterRespository.getExpenseStatus(); return res.data; }, onError: (error) => { @@ -181,20 +211,20 @@ const { }); return { ExpenseStatus, loading, error }; -} -export const useDocumentTypes =(category)=>{ -const { +}; +export const useDocumentTypes = (category) => { + const { data: DocumentTypes = [], error, isError, - isLoading + isLoading, } = useQuery({ - queryKey: ["Document Type",category], + queryKey: ["Document Type", category], queryFn: async () => { - const res = await MasterRespository.getDocumentTypes(category) + const res = await MasterRespository.getDocumentTypes(category); return res.data; }, - enabled:!!category, + enabled: !!category, onError: (error) => { showToast( error?.response?.data?.message || @@ -206,20 +236,20 @@ const { }); return { DocumentTypes, isError, isLoading, error }; -} -export const useDocumentCategories =(EntityType)=>{ -const { +}; +export const useDocumentCategories = (EntityType) => { + const { data: DocumentCategories = [], error, isError, - isLoading + isLoading, } = useQuery({ - queryKey: ["Document Category",EntityType], + queryKey: ["Document Category", EntityType], queryFn: async () => { - const res = await MasterRespository.getDocumentCategories(EntityType) + const res = await MasterRespository.getDocumentCategories(EntityType); return res.data; }, - enabled:!!EntityType, + enabled: !!EntityType, onError: (error) => { showToast( error?.response?.data?.message || @@ -231,13 +261,13 @@ const { }); return { DocumentCategories, isError, isLoading, error }; -} -export const useOrganizationType =()=>{ +}; +export const useOrganizationType = () => { return useQuery({ - queryKey:["orgType"], - queryFn:async()=>await MasterRespository.getOrganizationType() - }) -} + queryKey: ["orgType"], + queryFn: async () => await MasterRespository.getOrganizationType(), + }); +}; // ===Application Masters Query================================================= const fetchMasterData = async (masterType) => { @@ -248,7 +278,7 @@ const fetchMasterData = async (masterType) => { return (await MasterRespository.getJobRole()).data; case "Activity": return (await MasterRespository.getActivites()).data; - case "Services": + case "Services": return (await MasterRespository.getService()).data; case "Work Category": return (await MasterRespository.getWorkCategory()).data; @@ -293,8 +323,13 @@ const fetchMasterData = async (masterType) => { }; const useMaster = () => { - const selectedMaster = useSelector((store) => store.localVariables.selectedMaster); - const queryFn = useCallback(() => fetchMasterData(selectedMaster), [selectedMaster]); + const selectedMaster = useSelector( + (store) => store.localVariables.selectedMaster + ); + const queryFn = useCallback( + () => fetchMasterData(selectedMaster), + [selectedMaster] + ); const { data = [], isLoading, @@ -303,11 +338,16 @@ const useMaster = () => { queryKey: ["masterData", selectedMaster], queryFn, enabled: !!selectedMaster, - staleTime: 1000 * 60 * 10, + staleTime: 1000 * 60 * 10, refetchOnWindowFocus: false, onError: (error) => { - showToast(error?.response?.data?.message || error.message || `Failed to fetch ${selectedMaster} Maseter`, "error"); - }, + showToast( + error?.response?.data?.message || + error.message || + `Failed to fetch ${selectedMaster} Maseter`, + "error" + ); + }, }); return { data, loading: isLoading, error }; @@ -353,7 +393,7 @@ export const useCreateJobRole = (onSuccessCallback) => { onSuccess: (data) => { showToast("JobRole added successfully.", "success"); - queryClient.invalidateQueries({queryKey:["masterData", "Job Role"]}); + queryClient.invalidateQueries({ queryKey: ["masterData", "Job Role"] }); if (onSuccessCallback) onSuccessCallback(data); }, @@ -365,350 +405,275 @@ export const useCreateJobRole = (onSuccessCallback) => { // Application Role------------------------------------------- -export const useCreateApplicationRole = (onSuccessCallback) => -{ +export const useCreateApplicationRole = (onSuccessCallback) => { const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { - const resp = await MasterRespository.createRole( payload ); + return useMutation({ + mutationFn: async (payload) => { + const resp = await MasterRespository.createRole(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Application Role" ]} ) - showToast( "Application Role added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Application Role"], + }); + showToast("Application Role added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; -export const useUpdateApplicationRole = (onSuccessCallback) => -{ - const queryClient = useQueryClient(); +export const useUpdateApplicationRole = (onSuccessCallback) => { + const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ( {id, payload} ) => - { - const response = await MasterRespository.updateRoles(id,payload); + mutationFn: async ({ id, payload }) => { + const response = await MasterRespository.updateRoles(id, payload); return response.data; }, onSuccess: (data, variables) => { - queryClient.invalidateQueries({ queryKey: ["masterData", "Application Role"], }); showToast("Application Role updated successfully.", "success"); - + if (onSuccessCallback) onSuccessCallback(data); }, onError: (error) => { showToast(error.message || "Something went wrong", "error"); }, }); -} +}; -// Activity------------------------------ -export const useCreateActivity = (onSuccessCallback) => -{ - const queryClient = useQueryClient(); - - return useMutation( { - mutationFn: async ( payload ) => - { - const resp = await MasterRespository.createActivity(payload); - return resp.data; - }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Activity" ]} ) - showToast( "Activity added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) - }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} - -export const useUpdateActivity = (onSuccessCallback) => -{ - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async ( {id, payload} ) => - { - const response = await MasterRespository.updateActivity(id,payload); - return response.data; - }, - onSuccess: (data, variables) => { - - queryClient.invalidateQueries({ - queryKey: ["masterData", "Activity"], - }); - showToast("Activity updated successfully.", "success"); - - if (onSuccessCallback) onSuccessCallback(data); - }, - onError: (error) => { - showToast(error.message || "Something went wrong", "error"); - }, - }); -} //-----Create work Category------------------------------- -export const useCreateWorkCategory = (onSuccessCallback) => -{ +export const useCreateWorkCategory = (onSuccessCallback) => { const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { + return useMutation({ + mutationFn: async (payload) => { const resp = await MasterRespository.createWorkCategory(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries({queryKey: [ "masterData", "Work Category" ]} ) - showToast( "Work Category added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Work Category"], + }); + showToast("Work Category added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; -export const useUpdateWorkCategory = (onSuccessCallback) => -{ - const queryClient = useQueryClient(); +export const useUpdateWorkCategory = (onSuccessCallback) => { + const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ( {id, payload} ) => - { - const response = await MasterRespository.updateWorkCategory(id,payload); + mutationFn: async ({ id, payload }) => { + const response = await MasterRespository.updateWorkCategory(id, payload); return response.data; }, onSuccess: (data, variables) => { - queryClient.invalidateQueries({ queryKey: ["masterData", "Work Category"], }); showToast("Work Category updated successfully.", "success"); - + if (onSuccessCallback) onSuccessCallback(data); }, onError: (error) => { showToast(error.message || "Something went wrong", "error"); }, }); -} +}; //-- Contact Category--------------------------- - -export const useCreateContactCategory = (onSuccessCallback) => -{ +export const useCreateContactCategory = (onSuccessCallback) => { const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { + return useMutation({ + mutationFn: async (payload) => { const resp = await MasterRespository.createContactCategory(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Contact Category" ]} ) - showToast( "Contact Category added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Contact Category"], + }); + showToast("Contact Category added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; - -export const useUpdateContactCategory = (onSuccessCallback) => -{ - const queryClient = useQueryClient(); +export const useUpdateContactCategory = (onSuccessCallback) => { + const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ( {id, payload} ) => - { - const response = await MasterRespository.updateContactCategory(id,payload); + mutationFn: async ({ id, payload }) => { + const response = await MasterRespository.updateContactCategory( + id, + payload + ); return response.data; }, onSuccess: (data, variables) => { - queryClient.invalidateQueries({ queryKey: ["masterData", "Contact Category"], }); showToast("Contact Category updated successfully.", "success"); - + if (onSuccessCallback) onSuccessCallback(data); }, onError: (error) => { showToast(error.message || "Something went wrong", "error"); }, }); -} +}; // ---------Contact Tag------------------- -export const useCreateContactTag = (onSuccessCallback) => -{ +export const useCreateContactTag = (onSuccessCallback) => { const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { + return useMutation({ + mutationFn: async (payload) => { const resp = await MasterRespository.createContactTag(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Contact Tag" ]} ) - showToast( "Contact Tag added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Contact Tag"], + }); + showToast("Contact Tag added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; - -export const useUpdateContactTag = (onSuccessCallback) => -{ - const queryClient = useQueryClient(); +export const useUpdateContactTag = (onSuccessCallback) => { + const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ( {id, payload} ) => - { - debugger - const response = await MasterRespository.updateContactTag(id,payload); + mutationFn: async ({ id, payload }) => { + debugger; + const response = await MasterRespository.updateContactTag(id, payload); return response.data; }, onSuccess: (data, variables) => { - queryClient.invalidateQueries({ queryKey: ["masterData", "Contact Tag"], }); showToast("Contact Tag updated successfully.", "success"); - + if (onSuccessCallback) onSuccessCallback(data); }, onError: (error) => { showToast(error.message || "Something went wrong", "error"); }, }); -} +}; // ----------------------Expense Type------------------ -export const useCreateExpenseType = (onSuccessCallback)=>{ - const queryClient = useQueryClient(); +export const useCreateExpenseType = (onSuccessCallback) => { + const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { + return useMutation({ + mutationFn: async (payload) => { const resp = await MasterRespository.createExpenseType(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Expense Type" ]} ) - showToast( "Expense Type added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Expense Type"], + }); + showToast("Expense Type added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} -export const useUpdateExpenseType = (onSuccessCallback) => -{ - const queryClient = useQueryClient(); + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; +export const useUpdateExpenseType = (onSuccessCallback) => { + const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ( {id, payload} ) => - { - const response = await MasterRespository.updateExpenseType(id,payload); + mutationFn: async ({ id, payload }) => { + const response = await MasterRespository.updateExpenseType(id, payload); return response.data; }, onSuccess: (data, variables) => { - queryClient.invalidateQueries({ queryKey: ["masterData", "Expense Type"], }); showToast("Expense Type updated successfully.", "success"); - + if (onSuccessCallback) onSuccessCallback(data); }, onError: (error) => { showToast(error.message || "Something went wrong", "error"); }, }); -} +}; // -----------------Payment Mode ------------- -export const useCreatePaymentMode = (onSuccessCallback)=>{ - const queryClient = useQueryClient(); +export const useCreatePaymentMode = (onSuccessCallback) => { + const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { + return useMutation({ + mutationFn: async (payload) => { const resp = await MasterRespository.createPaymentMode(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Payment Mode" ]} ) - showToast( "Payment Mode added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Payment Mode"], + }); + showToast("Payment Mode added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; -export const useUpdatePaymentMode = (onSuccessCallback)=>{ - const queryClient = useQueryClient(); +export const useUpdatePaymentMode = (onSuccessCallback) => { + const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( {id,payload} ) => - { - const resp = await MasterRespository.updatePaymentMode(id,payload); + return useMutation({ + mutationFn: async ({ id, payload }) => { + const resp = await MasterRespository.updatePaymentMode(id, payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Payment Mode" ]} ) - showToast( "Payment Mode Updated successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Payment Mode"], + }); + showToast("Payment Mode Updated successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} - + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; // Services------------------------------- @@ -739,27 +704,26 @@ export const useCreateService = (onSuccessCallback) => { return useMutation({ mutationFn: async (payload) => { - debugger; + debugger; const resp = await MasterRespository.createService(payload); - debugger; + debugger; return resp.data; }, onSuccess: (data) => { - debugger; + debugger; queryClient.invalidateQueries({ queryKey: ["masterData", "Services"] }); showToast(data?.message || "Service added successfully", "success"); - if (onSuccessCallback) onSuccessCallback(data?.data); + if (onSuccessCallback) onSuccessCallback(data?.data); }, onError: (error) => { - debugger; - showToast(error.message || "Something went wrong", "error"); + debugger; + showToast( error?.response?.data?.message || error?.message || "Something went wrong", "error"); }, }); }; - export const useUpdateService = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -778,159 +742,244 @@ export const useUpdateService = (onSuccessCallback) => { if (onSuccessCallback) onSuccessCallback(data); }, onError: (error) => { - showToast(error?.message || "Something went wrong", "error"); + showToast(error?.response?.data?.message || error?.message || "Something went wrong", "error"); }, }); }; +export const useCreateActivityGroup = (onSuccessCallback) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (payload) => { + const response = await MasterRespository.createActivityGroup(payload); + return response; + }, + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ + queryKey: ["groups"], + }); + debugger + showToast( data?.message || + data?.response?.data?.message || "Activity Group created successfully.", + "success" + ); + + if (onSuccessCallback) onSuccessCallback(data); + }, + onError: (error) => { + showToast(error?.response?.data?.message || error?.message || "Something went wrong", "error"); + }, + }); +}; +export const useUpdateActivityGroup = (onSuccessCallback) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({id,payload}) => { + const response = await MasterRespository.updateActivityGrop(id,payload); + return response; + }, + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ + queryKey: ["groups"], + }); + + showToast( + data?.message || + data?.response?.data?.message|| "Activity Group Updated successfully.", + "success" + ); + + if (onSuccessCallback) onSuccessCallback(data); + }, + onError: (error) => { + showToast(error?.message || "Something went wrong", "error"); + }, + }); +}; +// Activity------------------------------ +export const useCreateActivity = (onSuccessCallback) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (payload) => { + const resp = await MasterRespository.createActivity(payload); + return resp.data; + }, + onSuccess: (data) => { + queryClient.invalidateQueries({ queryKey: ["activties"] }); + showToast("Activity added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); + }, + onError: (error) => { + showToast(error?.response?.data?.message || error?.message || "Something went wrong", "error"); + }, + }); +}; + +export const useUpdateActivity = (onSuccessCallback) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({ id, payload }) => { + const response = await MasterRespository.updateActivity(id, payload); + return response.data; + }, + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ + queryKey: ["activties"], + }); + showToast("Activity updated successfully.", "success"); + + if (onSuccessCallback) onSuccessCallback(data); + }, + onError: (error) => { + showToast(error?.response?.data?.message || error?.message || "Something went wrong", "error"); + }, + }); +}; // -------------------Expense Status---------------------------------- -export const useCreateExpenseStatus =(onSuccessCallback)=>{ - const queryClient = useQueryClient(); +export const useCreateExpenseStatus = (onSuccessCallback) => { + const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { + return useMutation({ + mutationFn: async (payload) => { const resp = await MasterRespository.createExpenseStatus(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Expense Status" ]} ) - showToast( "Expense Status added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Expense Status"], + }); + showToast("Expense Status added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} -export const useUpdateExpenseStatus = (onSuccessCallback)=>{ - const queryClient = useQueryClient(); + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; +export const useUpdateExpenseStatus = (onSuccessCallback) => { + const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( {id,payload} ) => - { - const resp = await MasterRespository.updateExepnseStatus(id,payload); + return useMutation({ + mutationFn: async ({ id, payload }) => { + const resp = await MasterRespository.updateExepnseStatus(id, payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Expense Status" ]} ) - showToast( "Expense Status Updated successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Expense Status"], + }); + showToast("Expense Status Updated successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} - - + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; // --------------------Document-Category-------------------------------- -export const useCreateDocumentCatgory =(onSuccessCallback)=>{ - const queryClient = useQueryClient(); +export const useCreateDocumentCatgory = (onSuccessCallback) => { + const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { + return useMutation({ + mutationFn: async (payload) => { const resp = await MasterRespository.createDocumenyCategory(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Document Category" ]} ) - queryClient.invalidateQueries( {queryKey:[ "Document Category" ]} ) - showToast( "Document Category added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Document Category"], + }); + queryClient.invalidateQueries({ queryKey: ["Document Category"] }); + showToast("Document Category added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; -export const useUpdateDocumentCategory =(onSuccessCallback)=>{ - const queryClient = useQueryClient(); +export const useUpdateDocumentCategory = (onSuccessCallback) => { + const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( {id,payload} ) => - { - const resp = await MasterRespository.updateDocumentCategory(id,payload); + return useMutation({ + mutationFn: async ({ id, payload }) => { + const resp = await MasterRespository.updateDocumentCategory(id, payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Document Category" ]} ) - queryClient.invalidateQueries( {queryKey:[ "Document Category" ]} ) - showToast( "Document Category Updated successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Document Category"], + }); + queryClient.invalidateQueries({ queryKey: ["Document Category"] }); + showToast("Document Category Updated successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; // ------------------------------Document-Type----------------------------------- -export const useCreateDocumentType =(onSuccessCallback)=>{ - const queryClient = useQueryClient(); +export const useCreateDocumentType = (onSuccessCallback) => { + const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { + return useMutation({ + mutationFn: async (payload) => { const resp = await MasterRespository.createDocumentType(payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Document Type" ]} ) - showToast( "Document Type added successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Document Type"], + }); + showToast("Document Type added successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; -export const useUpdateDocumentType =(onSuccessCallback)=>{ - const queryClient = useQueryClient(); +export const useUpdateDocumentType = (onSuccessCallback) => { + const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( {id,payload} ) => - { - const resp = await MasterRespository.updateDocumentType(id,payload); + return useMutation({ + mutationFn: async ({ id, payload }) => { + const resp = await MasterRespository.updateDocumentType(id, payload); return resp.data; }, - onSuccess: ( data ) => - { - queryClient.invalidateQueries( {queryKey:[ "masterData", "Document Type" ]} ) - showToast( "Document Type Updated successfully", "success" ); - if(onSuccessCallback) onSuccessCallback(data) + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Document Type"], + }); + showToast("Document Type Updated successfully", "success"); + if (onSuccessCallback) onSuccessCallback(data); }, - onError: ( error ) => - { - showToast(error.message || "Something went wrong", "error"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; // -Delete Master -------- export const useDeleteMasterItem = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ( {masterType, item} ) => - { + mutationFn: async ({ masterType, item }) => { const deleteFn = MasterRespository[masterType]; if (!deleteFn) { - throw new Error(`No delete strategy defined for master type: ${masterType}`); + throw new Error( + `No delete strategy defined for master type: ${masterType}` + ); } await deleteFn(item.id); @@ -945,8 +994,55 @@ export const useDeleteMasterItem = () => { onError: (error) => { const message = - error?.response?.data?.message || error?.message || "Error occurred during deletion"; + error?.response?.data?.message || + error?.message || + "Error occurred during deletion"; showToast(message, "error"); }, }); -}; \ No newline at end of file +}; + + +export const useDeleteServiceGroup =()=>{ + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (id)=>await MasterRespository.deleteActivityGroup(id), + onSuccess: ({_,variable}) => { + + queryClient.invalidateQueries({ queryKey: ["groups"] }); + + showToast(`Group deleted successfully.`, "success"); + }, + + onError: (error) => { + const message = + error?.response?.data?.message || + error?.message || + "Error occurred during deletion"; + showToast(message, "error"); + }, + }); +} +export const useDeleteActivity =()=>{ + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (id)=>await MasterRespository.deleteActivity(id), + onSuccess: ({_,variable}) => { + + + queryClient.invalidateQueries({ queryKey: ["activties"] }); + + showToast(`Acivity deleted successfully.`, "success"); + }, + + onError: (error) => { + const message = + error?.response?.data?.message || + error?.message || + "Error occurred during deletion"; + showToast(message, "error"); + }, + }); +} diff --git a/src/hooks/useAuth.jsx b/src/hooks/useAuth.jsx index 8f158197..8e5b8de2 100644 --- a/src/hooks/useAuth.jsx +++ b/src/hooks/useAuth.jsx @@ -12,6 +12,7 @@ import { closeAuthModal, openAuthModal, } from "../slices/localVariablesSlice.jsx"; +import { removeSession } from "../utils/authUtils.js"; export const useTenants = () => { return useQuery({ @@ -28,18 +29,24 @@ export const useSelectTenant = (onSuccessCallBack) => { const res = await AuthRepository.selectTenant(tenantId); return res.data; }, - + onSuccess: (data) => { - localStorage.setItem("jwtToken", data.token); - localStorage.setItem("refreshToken", data.refreshToken); + if (localStorage.getItem("jwtToken")) { + localStorage.setItem("jwtToken", data.token); + localStorage.setItem("refreshToken", data.refreshToken); + } else { + sessionStorage.setItem("jwtToken", data.token); + sessionStorage.setItem("refreshToken", data.refreshToken); + } + if (onSuccessCallBack) onSuccessCallBack(); }, onError: (error) => { showToast(error.message || "Error while creating project", "error"); localStorage.removeItem("jwtToken"); - localStorage.removeItem("refreshToken") - localStorage.removeItem("ctnt") + localStorage.removeItem("refreshToken"); + localStorage.removeItem("ctnt"); }, }); }; @@ -55,29 +62,26 @@ export const useAuthModal = () => { }; }; -export const useLogout = ()=>{ + +export const useLogout = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async () => { - let payload = {refreshToken: localStorage.getItem("refreshToken")} - return await AuthRepository.logout(payload); + let payload = { refreshToken: localStorage.getItem("refreshToken") || sessionStorage.getItem("refreshToken") }; + return await AuthRepository.logout(payload); }, - + onSuccess: (data) => { - localStorage.removeItem("jwtToken"); - localStorage.removeItem("refreshToken"); - localStorage.removeItem("ctnt"); - localStorage.clear(); - window.location.href = "/auth/login"; + removeSession() + + window.location.href = "/auth/login"; if (onSuccessCallBack) onSuccessCallBack(); }, onError: (error) => { showToast(error.message || "Error while creating project", "error"); - localStorage.removeItem("jwtToken"); - localStorage.removeItem("refreshToken") - localStorage.removeItem("ctnt") + removeSession() }, }); -} \ No newline at end of file +}; diff --git a/src/hooks/useDirectory.js b/src/hooks/useDirectory.js index 28869f9a..a1fd716b 100644 --- a/src/hooks/useDirectory.js +++ b/src/hooks/useDirectory.js @@ -399,11 +399,13 @@ export const useUpdateBucket = (onSuccessCallBack) => { mutationFn: async ({ bucketId, BucketPayload }) => await DirectoryRepository.UpdateBuckets(bucketId, BucketPayload), onSuccess: (_, variables) => { + debugger; queryClient.invalidateQueries({ queryKey: ["bucketList"] }); showToast("Bucket updated successfully", "success"); if (onSuccessCallBack) onSuccessCallBack(); }, onError: (error) => { + debugger; showToast( error?.response?.data?.message || "Something went wrong. Please try again later.", diff --git a/src/hooks/useEmployees.js b/src/hooks/useEmployees.js index 58a12e5a..477b18f7 100644 --- a/src/hooks/useEmployees.js +++ b/src/hooks/useEmployees.js @@ -221,7 +221,7 @@ export const useUpdateEmployee = () => { mutationFn: (employeeData) => EmployeeRepository.manageEmployee(employeeData), onSuccess: (_, variables) => { - const id = variables.id || variables.employeeId; + const id = variables?.id || variables?.employeeId; const isAllEmployee = variables.IsAllEmployee; // Cache invalidation diff --git a/src/hooks/useOrganization.js b/src/hooks/useOrganization.js index 2d77438b..e99d8ccb 100644 --- a/src/hooks/useOrganization.js +++ b/src/hooks/useOrganization.js @@ -35,6 +35,18 @@ export const useOrganizationModal = () => { }; }; +// ================================Query============================================================= + +export const useOrganization=(id)=>{ +return useQuery({ + queryKey:["organization",id], + queryFn:async()=> { + const resp = await await OrganizationRepository.getOrganizaion(id); + return resp.data + }, + enabled:!!id +}) +} export const useOrganizationBySPRID = (sprid) => { return useQuery({ queryKey: ["organization by", sprid], @@ -76,6 +88,25 @@ export const useOrganizationsList = ( }); }; +export const useOrganizationEmployees = ( + projectId, + organizationId, + searchString +) => { + return useQuery({ + queryKey: ["OrgEmployees", projectId, organizationId, searchString], + queryFn: async () => + await OrganizationRepository.getOrganizationEmployees( + projectId, + organizationId, + searchString + ), + enabled: !!projectId , + }); +}; + +// =================================Mutation======================================================== + export const useCreateOrganization = (onSuccessCallback) => { const useClient = useQueryClient(); return useMutation({ @@ -103,9 +134,13 @@ export const useAssignOrgToProject = (onSuccessCallback) => { mutationFn: async (payload) => await OrganizationRepository.assignOrganizationToProject(payload), onSuccess: (_, variables) => { + const { projectId } = variables; useClient.invalidateQueries({ queryKey: ["projectAssignedOrganiztions"], }); + useClient.invalidateQueries({ + queryKey: ["projectAssignedOrganization", projectId], + }); showToast("Organization successfully", "success"); if (onSuccessCallback) onSuccessCallback(); }, @@ -139,14 +174,14 @@ export const useAssignOrgToTenant = (onSuccessCallback) => { }, }); }; -export const useUpdateOrganization = () => { +export const useUpdateOrganization = (onSuccessCallback) => { const useClient = useQueryClient(); return useMutation({ - mutationFn: async (payload) => - await OrganizationRepository.assignOrganizationToProject(payload), + mutationFn: async ({orgId,payload}) => + await OrganizationRepository.updateOrganizaion(orgId,payload), onSuccess: (_, variables) => { - // useClient.invalidateQueries({ queryKey: ["organizationList"] }); - showToast("Organization successfully", "success"); + useClient.invalidateQueries({ queryKey: ["organizationList"] }); + showToast("Organization Updated successfully", "success"); if (onSuccessCallback) onSuccessCallback(); }, onError: (error) => { diff --git a/src/hooks/useProjectAccess.js b/src/hooks/useProjectAccess.js index a3ff27ac..d932b157 100644 --- a/src/hooks/useProjectAccess.js +++ b/src/hooks/useProjectAccess.js @@ -6,21 +6,24 @@ import { VIEW_PROJECTS } from "../utils/constants"; import showToast from "../services/toastService"; export const useProjectAccess = (projectId) => { + const navigate = useNavigate(); + const { data: projectPermissions, isLoading, isFetched } = useAllProjectLevelPermissions(projectId); - const canView = useHasUserPermission(VIEW_PROJECTS); - const navigate = useNavigate(); + const canView = useHasUserPermission(VIEW_PROJECTS); + + const loading = isLoading || !isFetched; useEffect(() => { - if (projectId && isFetched && !isLoading && !canView) { + if (projectId && !loading && !canView) { showToast("You don't have permission to view project details", "warning"); navigate("/projects"); } - }, [projectId, isFetched, isLoading, canView, navigate]); + }, [projectId, loading, canView, navigate]); return { canView, - loading: isLoading || !isFetched, + loading, }; -}; +}; \ No newline at end of file diff --git a/src/hooks/useProjects.js b/src/hooks/useProjects.js index 2c7e0f61..28c65a0b 100644 --- a/src/hooks/useProjects.js +++ b/src/hooks/useProjects.js @@ -14,17 +14,15 @@ import { } from "@tanstack/react-query"; import showToast from "../services/toastService"; +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(); @@ -32,19 +30,13 @@ export const useProjects = () => { }, enabled: !!loggedUser, }); - - return { - projects, - loading, - error, - refetch, - }; }; export const useEmployeesByProjectAllocated = ( projectId, serviceId, - organizationId, + organizationId, + emloyeeeStatus ) => { const { data = [], @@ -52,16 +44,23 @@ export const useEmployeesByProjectAllocated = ( refetch, error, } = useQuery({ - queryKey: ["empListByProjectAllocated", projectId, serviceId,organizationId], + queryKey: [ + "empListByProjectAllocated", + projectId, + serviceId, + organizationId, + emloyeeeStatus, + ], queryFn: async () => { const res = await ProjectRepository.getProjectAllocation( projectId, + serviceId, organizationId, - serviceId + emloyeeeStatus ); return res?.data || res; }, - enabled: !!projectId, + enabled: !!projectId, onError: (error) => { showToast( error.message || "Error while fetching project allocated employees", @@ -180,17 +179,20 @@ export const useProjectName = () => { }; }; -export const useProjectInfra = (projectId) => { +export const useProjectInfra = (projectId, serviceId) => { const { data: projectInfra, isLoading, error, isFetched, } = useQuery({ - queryKey: ["ProjectInfra", projectId], + queryKey: ["ProjectInfra", projectId, serviceId], queryFn: async () => { if (!projectId) return null; - const res = await ProjectRepository.getProjectInfraByproject(projectId); + const res = await ProjectRepository.getProjectInfraByproject( + projectId, + serviceId + ); return res.data; }, enabled: !!projectId, @@ -202,11 +204,18 @@ export const useProjectInfra = (projectId) => { 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 @@ -298,14 +307,30 @@ 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 + ), + }); +}; + // -- -------------Mutation------------------------------- 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) => { @@ -331,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) => { @@ -395,8 +420,8 @@ export const useManageProjectAllocation = ({ const queryClient = useQueryClient(); const { mutate, isPending, isSuccess, isError } = useMutation({ - mutationFn: async ({ items }) => { - const response = await ProjectRepository.manageProjectAllocation(items); + mutationFn: async ({ payload }) => { + const response = await ProjectRepository.manageProjectAllocation(payload); return response.data; }, onSuccess: (data, variables, context) => { @@ -404,7 +429,7 @@ export const useManageProjectAllocation = ({ queryKey: ["empListByProjectAllocated"], }); queryClient.removeQueries({ queryKey: ["projectEmployees"] }); - if (variables?.added) { + if (variables.actionType === "assign") { showToast("Employee Assigned Successfully", "success"); } else { showToast("Removed Employee Successfully", "success"); diff --git a/src/hooks/useTasks.js b/src/hooks/useTasks.js index b793ced4..5073c115 100644 --- a/src/hooks/useTasks.js +++ b/src/hooks/useTasks.js @@ -8,30 +8,25 @@ import { useSelector } from "react-redux"; // ---------Query--------------------------------- -export const useTaskList = (projectId, dateFrom, toDate) => { - const enabled = !!projectId && !!dateFrom && !!toDate; +export const useTaskList = (projectId, pageSize, pageNumber, serviceId, filter) => { - const { - data: TaskList = [], - isLoading: loading, - error, - refetch, - } = useQuery({ - queryKey: ["taskList", projectId, dateFrom, toDate], + return useQuery({ + queryKey: ["taskList", projectId, pageSize, pageNumber, serviceId, filter], queryFn: async () => { const response = await TasksRepository.getTaskList( projectId, - dateFrom, - toDate + pageSize, + pageNumber, + serviceId, + filter ); return response.data; }, - enabled, + enabled:!!projectId }); - - return { TaskList, loading, error, refetch }; }; + export const useTaskById = (TaskId) => { const { data: Task = null, diff --git a/src/pages/Activities/DailyTask.jsx b/src/pages/Activities/DailyTask.jsx deleted file mode 100644 index 908c7ea9..00000000 --- a/src/pages/Activities/DailyTask.jsx +++ /dev/null @@ -1,289 +0,0 @@ -import React, { useEffect, useMemo, useState } from "react"; -import { useDispatch } from "react-redux"; -import { useTaskList } from "../../hooks/useTasks"; -import { useProjectAssignedServices, useProjectName } from "../../hooks/useProjects"; -import { setProjectId } from "../../slices/localVariablesSlice"; -import Breadcrumb from "../../components/common/Breadcrumb"; -import DateRangePicker from "../../components/common/DateRangePicker"; -import FilterIcon from "../../components/common/FilterIcon"; -import GlobalModel from "../../components/common/GlobalModel"; -import ReportTask from "../../components/Activities/ReportTask"; -import ReportTaskComments from "../../components/Activities/ReportTaskComments"; -import SubTask from "../../components/Activities/SubTask"; -import { formatNumber, formatUTCToLocalTime } from "../../utils/dateUtils"; -import { useHasUserPermission } from "../../hooks/useHasUserPermission"; -import { APPROVE_TASK, ASSIGN_REPORT_TASK } from "../../utils/constants"; -import { useSelectedProject } from "../../slices/apiDataManager"; -import moment from "moment"; -import Loader from "../../components/common/Loader"; - -const DailyTask = () => { - const dispatch = useDispatch(); - const selectedProject = useSelectedProject(); - const { projectNames } = useProjectName(); - const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK); - const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK); - - const { data: assignedServices, isLoading: servicesLoading } = useProjectAssignedServices(selectedProject); - - const [selectedService, setSelectedService] = useState(""); - - const handleServiceChange = (e) => { - setSelectedService(e.target.value); - }; - - const [filters, setFilters] = useState({ - selectedBuilding: "", - selectedFloors: [], - selectedActivities: [], - }); - - const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" }); - - const { TaskList, loading: taskLoading } = useTaskList( - selectedProject || null, - dateRange?.startDate || null, - dateRange?.endDate || null - ); - - useEffect(() => { - if (!selectedProject && projectNames.length > 0) { - debugger - dispatch(setProjectId(projectNames[0].id)); - } - }, [selectedProject, projectNames, dispatch]); - - useEffect(() => { - setFilters({ - selectedBuilding: "", - selectedFloors: [], - selectedActivities: [], - }); - }, [selectedProject]); - - const filteredTasks = useMemo(() => { - if (!TaskList) return []; - return TaskList.filter((task) => { - const { selectedBuilding, selectedFloors, selectedActivities } = filters; - - if (selectedBuilding && task?.workItem?.workArea?.floor?.building?.name !== selectedBuilding) return false; - if (selectedFloors.length > 0 && !selectedFloors.includes(task?.workItem?.workArea?.floor?.floorName)) return false; - if (selectedActivities.length > 0 && !selectedActivities.includes(task?.workItem?.activityMaster?.activityName)) return false; - - return true; - }); - }, [TaskList, filters]); - - const groupedTasks = useMemo(() => { - const groups = {}; - filteredTasks.forEach((task) => { - const date = task.assignmentDate.split("T")[0]; - if (!groups[date]) groups[date] = []; - groups[date].push(task); - }); - return Object.keys(groups) - .sort((a, b) => new Date(b) - new Date(a)) - .map((date) => ({ date, tasks: groups[date] })); - }, [filteredTasks]); - - const [modal, setModal] = useState({ type: null, data: null }); - - const openModal = (type, data = null) => setModal({ type, data }); - const closeModal = () => setModal({ type: null, data: null }); - - const renderTeamMembers = (task, refIndex) => ( -
- ${task.teamMembers - .map( - (m) => ` -
-
- - ${m?.firstName?.charAt(0) || ""}${m?.lastName?.charAt(0) || ""} - -
- ${m.firstName} ${m.lastName} -
` - ) - .join("")} -
- `} - > - {task.teamMembers.slice(0, 3).map((m) => ( -
- - {m?.firstName.slice(0, 1)} - -
- ))} - {task.teamMembers.length > 3 && ( -
- +{task.teamMembers.length - 3} -
- )} -
- ); - - return ( - <> - {modal.type === "report" && ( - - - - )} - {modal.type === "comments" && ( - - { - if (isSubTask) openModal("subtask", modal.data.task); - else closeModal(); - }} - closeModal={closeModal} - /> - - )} - {modal.type === "subtask" && ( - - - - )} - -
- - -
-
- {!selectedProject && (
Please Select Project
)} - -
- {/* --- Left: Service Dropdown + Filter Icon --- */} -
-
- {!servicesLoading && assignedServices?.length > 0 && ( - assignedServices.length > 1 ? ( - - ) : ( -
{assignedServices[0].name}
- ) - )} -
-
- - {/* --- Right: DateRangePicker --- */} -
- - -
- -
- {/* --- Table --- */} -
- - - - - - - - - - - - - {taskLoading && ( - - - - )} - {!taskLoading && groupedTasks.length === 0 && ( - - - - )} - {!taskLoading && - groupedTasks.map(({ date, tasks }) => ( - - - - - {tasks.map((task, idx) => ( - - - - - - - - - ))} - - ))} - -
ActivityAssignedCompletedAssign OnTeamActions
- -
- No reports available for the selected date range. -
{formatUTCToLocalTime(date)}
-
{task.workItem.activityMaster?.activityName || "No Activity Name"}
-
- {task.workItem.workArea?.floor?.building?.name} › {task.workItem.workArea?.floor?.floorName} › {task.workItem.workArea?.areaName} -
-
{formatNumber(task.plannedTask)} / {formatNumber(task.workItem.plannedWork - task.workItem.completedWork)}{task.completedTask}{formatUTCToLocalTime(task.assignmentDate)}{renderTeamMembers(task, idx)} -
- {ReportTaskRights && !task.reportedDate && ( - - )} - {ApprovedTaskRights && task.reportedDate && !task.approvedBy && ( - - )} - -
-
-
-
-
-
- - ); -}; -export default DailyTask; \ No newline at end of file diff --git a/src/pages/Activities/TaskPlannng.jsx b/src/pages/Activities/TaskPlannng.jsx index c9356dfd..8728240f 100644 --- a/src/pages/Activities/TaskPlannng.jsx +++ b/src/pages/Activities/TaskPlannng.jsx @@ -1,32 +1,34 @@ import React, { useEffect, useState } from "react"; import Breadcrumb from "../../components/common/Breadcrumb"; import InfraPlanning from "../../components/Activities/InfraPlanning"; -import { useProjectName } from "../../hooks/useProjects"; +import { useCurrentService, useProjectName } from "../../hooks/useProjects"; import { useDispatch } from "react-redux"; import { setProjectId } from "../../slices/localVariablesSlice"; import { useSelectedProject } from "../../slices/apiDataManager"; import { useProjectAssignedServices } from "../../hooks/useProjects"; +import { setService } from "../../slices/globalVariablesSlice"; -const TaskPlannng = () => { +const TaskPlanning = () => { const selectedProject = useSelectedProject(); + const selectedService = useCurrentService(); const dispatch = useDispatch(); + const { projectNames = [], loading: projectLoading } = useProjectName(); - // Service dropdown state - const { data: assignedServices, isLoading: servicesLoading } = + const { data, isLoading: servicesLoading } = useProjectAssignedServices(selectedProject); - const [selectedService, setSelectedService] = useState(""); + // Set default project if none selected useEffect(() => { - if (!selectedProject && projectNames?.length > 0) { + if (!selectedProject && projectNames.length > 0) { dispatch(setProjectId(projectNames[0]?.id)); } }, [projectNames, selectedProject, dispatch]); - const handleServiceChange = (e) => { - setSelectedService(e.target.value); - }; - + // Loading state + if (projectLoading) { + return
Loading...
; + } return (
{ ]} /> -
-
- {/* Service Dropdown */} - -
- {!servicesLoading && assignedServices?.length > 0 && ( - assignedServices.length > 1 ? ( - - ) : ( -
{assignedServices[0].name}
- ) - )} -
- - - {/* Infra Planning Component */} - {selectedProject ? ( - +
+
+ {data?.length === 0 ? ( +

Service not assigned

) : ( -
Please Select Project
+ )}
+ + {/* Planning Component */} + {selectedProject ? ( + + ) : ( +
Please select a project
+ )}
); }; -export default TaskPlannng; +export default TaskPlanning; diff --git a/src/pages/DailyProgressReport/DailyProgrssReport.jsx b/src/pages/DailyProgressReport/DailyProgrssReport.jsx new file mode 100644 index 00000000..96d9de6c --- /dev/null +++ b/src/pages/DailyProgressReport/DailyProgrssReport.jsx @@ -0,0 +1,119 @@ +import React, { createContext, useContext, useEffect, useState } from "react"; +import Breadcrumb from "../../components/common/Breadcrumb"; +import { useServices } from "../../hooks/masterHook/useMaster"; +import TaskReportList from "../../components/DailyProgressRport/TaskReportList"; +import GlobalModel from "../../components/common/GlobalModel"; +import ReportTaskComments from "../../components/Activities/ReportTaskComments"; +import ReportTask from "../../components/Activities/ReportTask"; +import TaskReportFilterPanel from "../../components/DailyProgressRport/TaskReportFilterPanel"; +import { useFab } from "../../Context/FabContext"; + +const DailyProgrssContext = createContext(); +export const useDailyProgrssContext = () => { + const context = useContext(DailyProgrssContext); + if (!context) { + throw new Error( + "useDailyTaskContext must be used within a DailyTaskProvider" + ); + } + return context; +}; + +const DailyProgrssReport = () => { + const [service, setService] = useState(""); + const [filter,setFilter] = useState('') + const { setOffcanvasContent, setShowTrigger } = useFab(); + const { data, isLoading, isError, error } = useServices(); + + const [modal, setModal] = useState({ type: null, data: null }); + + const openModal = (type, data = null) => setModal({ type, data }); + const closeModal = () => setModal({ type: null, data: null }); + + const contextDispatcher = { + service, + openModal, + closeModal, + filter, + }; + + const handleFilter = (filterObj)=>{ + setFilter(filterObj) + } + + useEffect(() => { + setShowTrigger(true); + setOffcanvasContent("Report Filter", ); + + return () => { + setShowTrigger(false); + setOffcanvasContent("", null); + }; + }, []); + return ( +
+ + {modal.type === "report" && ( + + + + )} + {modal.type === "comments" && ( + + { + if (isSubTask) openModal("subtask", modal.data.task); + else closeModal(); + }} + closeModal={closeModal} + /> + + )} + {modal.type === "subtask" && ( + + + + )} + + + +
+
+ +
+
+ +
+
+
+
+ ); +}; + +export default DailyProgrssReport; diff --git a/src/pages/Expense/ExpensePage.jsx b/src/pages/Expense/ExpensePage.jsx index 02af6286..5d2ef806 100644 --- a/src/pages/Expense/ExpensePage.jsx +++ b/src/pages/Expense/ExpensePage.jsx @@ -131,9 +131,8 @@ const ExpensePage = () => {
{IsCreatedAble && ( )}
diff --git a/src/pages/Organization/OrganizationPage.jsx b/src/pages/Organization/OrganizationPage.jsx index e010a76d..21a28263 100644 --- a/src/pages/Organization/OrganizationPage.jsx +++ b/src/pages/Organization/OrganizationPage.jsx @@ -4,9 +4,9 @@ import { useOrganizationModal } from "../../hooks/useOrganization"; import OrganizationsList from "../../components/Organization/OrganizationsList"; const OrganizationPage = () => { - const { isOpen, orgData, startStep, onOpen, flowType } = + const { isOpen, orgData, startStep, onOpen, flowType } = useOrganizationModal(); - const [searchText,setSearchText] = useState("") + const [searchText, setSearchText] = useState("") return (
@@ -21,7 +21,7 @@ const OrganizationPage = () => { setSearchText(e.target.value)} + onChange={(e) => setSearchText(e.target.value)} className="form-control form-control-sm w-auto" placeholder="Search Organization" aria-describedby="search-label" @@ -29,20 +29,22 @@ const OrganizationPage = () => {
-
+
- +
); diff --git a/src/pages/Tenant/TenantPage.jsx b/src/pages/Tenant/TenantPage.jsx index 640c8fc2..694ff557 100644 --- a/src/pages/Tenant/TenantPage.jsx +++ b/src/pages/Tenant/TenantPage.jsx @@ -127,38 +127,27 @@ const TenantPage = () => {
{/* Search */} -
+
setSearchText(e.target.value)} - className="form-control form-control" placeholder="Search Tenant" />
{/* Actions */} -
- refetchFn && refetchFn()} - > - Refresh{" "} - - - +
diff --git a/src/pages/authentication/LoginPage.jsx b/src/pages/authentication/LoginPage.jsx index 363de91f..6be7cb2b 100644 --- a/src/pages/authentication/LoginPage.jsx +++ b/src/pages/authentication/LoginPage.jsx @@ -16,13 +16,13 @@ const LoginPage = () => { const loginSchema = IsLoginWithOTP ? z.object({ - username: z.string().trim().email({ message: "Valid email required" }), - }) + username: z.string().trim().email({ message: "Valid email required" }), + }) : z.object({ - username: z.string().trim().email({ message: "Valid email required" }), - password: z.string().trim().min(1, { message: "Password required" }), - rememberMe: z.boolean(), - }); + username: z.string().trim().email({ message: "Valid email required" }), + password: z.string().trim().min(1, { message: "Password required" }), + rememberMe: z.boolean(), + }); const { register, @@ -41,8 +41,13 @@ const LoginPage = () => { password: data.password, }; const response = await AuthRepository.login(userCredential); - localStorage.setItem("jwtToken", response.data.token); - localStorage.setItem("refreshToken", response.data.refreshToken); + if (data.rememberMe) { + localStorage.setItem("jwtToken", response.data.token); + localStorage.setItem("refreshToken", response.data.refreshToken); + } else { + sessionStorage.setItem("jwtToken", response.data.token); + sessionStorage.setItem("refreshToken", response.data.refreshToken); + } setLoading(false); navigate("/auth/switch/org"); } else { @@ -69,6 +74,16 @@ const LoginPage = () => { } }, [IsLoginWithOTP]); + useEffect(() => { + const token = + localStorage.getItem("jwtToken") || + sessionStorage.getItem("jwtToken"); + + if (token) { + navigate("/dashboard", { replace: true }); + } +}, []); + return (
@@ -106,36 +121,6 @@ const LoginPage = () => { {/* Password */} {!IsLoginWithOTP && ( <> - {/*
- -
- - setHidepass(!hidepass)} - > - - -
- {errors.password && ( -
- {errors.password.message} -
- )} -
*/} -
- {/* ✅ Error message */} {errors.password && ( -
+
{errors.password.message}
)}
- {/* Remember Me + Forgot Password */}
@@ -209,8 +196,8 @@ const LoginPage = () => { {loading ? "Please Wait..." : IsLoginWithOTP - ? "Send OTP" - : "Sign In"} + ? "Send OTP" + : "Sign In"} {/* Login With OTP Button */} @@ -254,4 +241,4 @@ const LoginPage = () => { ); }; -export default LoginPage; \ No newline at end of file +export default LoginPage; diff --git a/src/pages/authentication/LoginWithOtp.jsx b/src/pages/authentication/LoginWithOtp.jsx index 8b07f8fb..a1af0e40 100644 --- a/src/pages/authentication/LoginWithOtp.jsx +++ b/src/pages/authentication/LoginWithOtp.jsx @@ -52,7 +52,7 @@ const LoginWithOtp = () => { setLoading(false); localStorage.removeItem("otpUsername"); localStorage.removeItem("otpSentTime"); - navigate("/dashboard"); + navigate("/auth/switch/org"); } catch (err) { showToast("Invalid or expired OTP.", "error"); diff --git a/src/pages/authentication/MainForgetPage.jsx b/src/pages/authentication/MainForgetPage.jsx index caae1016..ea6bdd4c 100644 --- a/src/pages/authentication/MainForgetPage.jsx +++ b/src/pages/authentication/MainForgetPage.jsx @@ -7,7 +7,7 @@ const MainForgetPage = () => { <>
-
+
{ <>
-
+
{ chooseTenant(tenantId); }; - const {mutate:handleLogout,isPending:isLogouting} = useLogout(()=>{}) + const {mutate:handleLogout,isPending:isLogouting} = useLogout() - // useEffect(() => { - // if (localStorage.getItem("ctnt")) { - // navigate("/dashboard"); - // } - // }, [navigate]); + useEffect(() => { + if (localStorage.getItem("ctnt")) { + chooseTenant(localStorage.getItem("ctnt")) + } + }, [navigate]); useEffect(() => { if (!isLoading && data?.data?.length === 1) { diff --git a/src/pages/employee/EmployeeList.jsx b/src/pages/employee/EmployeeList.jsx index b0f10a75..fc77ba1c 100644 --- a/src/pages/employee/EmployeeList.jsx +++ b/src/pages/employee/EmployeeList.jsx @@ -751,7 +751,6 @@ const EmployeeList = () => {
) : ( - //
diff --git a/src/pages/master/MasterPage.jsx b/src/pages/master/MasterPage.jsx index b187efd1..a206b913 100644 --- a/src/pages/master/MasterPage.jsx +++ b/src/pages/master/MasterPage.jsx @@ -1,4 +1,4 @@ -import React, { useState, useMemo, useEffect } from "react"; +import React, { useState, useMemo, useEffect, createContext, useContext } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useQueryClient } from "@tanstack/react-query"; import Breadcrumb from "../../components/common/Breadcrumb"; @@ -6,7 +6,9 @@ import MasterModal from "../../components/master/MasterModal"; import ConfirmModal from "../../components/common/ConfirmModal"; import MasterTable from "./MasterTable"; import useMaster, { + useDeleteActivity, useDeleteMasterItem, + useDeleteServiceGroup, useMasterMenu, } from "../../hooks/masterHook/useMaster"; import { changeMaster } from "../../slices/localVariablesSlice"; @@ -14,6 +16,16 @@ import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { MANAGE_MASTER } from "../../utils/constants"; import GlobalModel from "../../components/common/GlobalModel"; + +export const MasterContext = createContext(); +export const useMasterContext = () => { + const context = useContext(MasterContext); + if (!context) { + throw new Error("useMasterContext must be used within an MasterProvider"); + } + return context; +}; + const MasterPage = () => { const dispatch = useDispatch(); const queryClient = useQueryClient(); @@ -34,6 +46,9 @@ const MasterPage = () => { isError: isMasterError, } = useMaster(); const { mutate: DeleteMaster, isPending: isDeleting } = useDeleteMasterItem(); + const [isDeleletingServiceItem,setDeleletingServiceItem] = useState({isOpen:false,ItemId:null,whichItem:null}) + const {mutate:DeleteSericeGroup,isPending:deletingGroup} =useDeleteServiceGroup() + const {mutate:DeleteAcivity,isPending:deletingActivity} = useDeleteActivity() const [modalConfig, setModalConfig] = useState(null); const [deleteData, setDeleteData] = useState(null); @@ -73,6 +88,19 @@ const MasterPage = () => { ); }; + + const handleDeleteServiceItem =()=>{ + if(!isDeleletingServiceItem.ItemId) return + debugger + if(isDeleletingServiceItem.whichItem === "activity"){ + DeleteAcivity(isDeleletingServiceItem.ItemId,{onSuccess:()=>setDeleletingServiceItem({isOpen:false,ItemId:null,whichItem:null})}) + }else{ + DeleteSericeGroup(isDeleletingServiceItem.ItemId,{onSuccess:()=>setDeleletingServiceItem({isOpen:false,ItemId:null,whichItem:null})}) + } + + + } + if (menuErrorFlag || isMasterError) return (
@@ -87,7 +115,7 @@ const MasterPage = () => { ); return ( - <> + {modalConfig && ( { /> )} + { onSubmit={handleDeleteSubmit} onClose={() => setDeleteData(null)} /> + + setDeleletingServiceItem({isOpen:false,ItemId:null,whichItem:null})} + />
{
- + ); }; diff --git a/src/pages/master/MasterTable.jsx b/src/pages/master/MasterTable.jsx index b2285842..8facfdb7 100644 --- a/src/pages/master/MasterTable.jsx +++ b/src/pages/master/MasterTable.jsx @@ -158,6 +158,21 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => { ) : ( <> + {selectedMaster === "Services" && ( + + )} + - -
- -
- -
    - {[ - { - 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 ad6d4fcb..00000000 --- a/src/pages/project/ProjectListView.jsx +++ /dev/null @@ -1,202 +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 = () => { - navigate(`/projects/details`); - }; - - 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)} - -

-
-
- - -
-