From 6baa2896c2f336654d9401c25a14581e321bb6d5 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Mon, 15 Sep 2025 16:27:06 +0530 Subject: [PATCH 001/120] initially setup --- src/components/common/Modal.jsx | 70 ++++++++++++++ src/hooks/useOrganization.js | 0 src/pages/Organization/OrganizationModal.jsx | 0 src/pages/Organization/OrganizationPage.jsx | 0 src/slices/apiSlice/attedanceLogsSlice.js | 93 ------------------- src/slices/apiSlice/attendanceAllSlice.js | 38 -------- .../apiSlice/employeeAttendanceSlice.js | 56 ----------- 7 files changed, 70 insertions(+), 187 deletions(-) create mode 100644 src/components/common/Modal.jsx create mode 100644 src/hooks/useOrganization.js create mode 100644 src/pages/Organization/OrganizationModal.jsx create mode 100644 src/pages/Organization/OrganizationPage.jsx delete mode 100644 src/slices/apiSlice/attedanceLogsSlice.js delete mode 100644 src/slices/apiSlice/attendanceAllSlice.js delete mode 100644 src/slices/apiSlice/employeeAttendanceSlice.js diff --git a/src/components/common/Modal.jsx b/src/components/common/Modal.jsx new file mode 100644 index 00000000..67866c50 --- /dev/null +++ b/src/components/common/Modal.jsx @@ -0,0 +1,70 @@ +import XIcon from "@/assets/XIcons"; +import { useCallback } from "react"; +import Button from "./Button"; + +const Modal = ({ + isOpen, + onClose, + onSubmit, + title, + body, + footer, + actionLabel, + disabled, +}) => { + const handleClose = useCallback(() => { + if (disabled) return; + onClose(); + }, [disabled, onClose]); + + const handleSubmit = useCallback(() => { + if (disabled) return; + onSubmit(); + }, [disabled, onSubmit]); + + if (!isOpen) return null; + + return ( +
+
+
+ {/* Header */} +
+
{title}
+ +
+ + {/* Body */} +
{body}
+ + {/* Footer */} +
+
+
+
+
+ ); +}; + +export default Modal; diff --git a/src/hooks/useOrganization.js b/src/hooks/useOrganization.js new file mode 100644 index 00000000..e69de29b diff --git a/src/pages/Organization/OrganizationModal.jsx b/src/pages/Organization/OrganizationModal.jsx new file mode 100644 index 00000000..e69de29b diff --git a/src/pages/Organization/OrganizationPage.jsx b/src/pages/Organization/OrganizationPage.jsx new file mode 100644 index 00000000..e69de29b diff --git a/src/slices/apiSlice/attedanceLogsSlice.js b/src/slices/apiSlice/attedanceLogsSlice.js deleted file mode 100644 index 249d5445..00000000 --- a/src/slices/apiSlice/attedanceLogsSlice.js +++ /dev/null @@ -1,93 +0,0 @@ -import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; -import AttendanceRepository from '../../repositories/AttendanceRepository'; -import {clearCacheKey} from '../apiDataManager'; - -// Fetch attendance data -export const fetchAttendanceData = createAsyncThunk( - 'attendanceLogs/fetchAttendanceData', - async ( {projectId, fromDate, toDate}, thunkAPI ) => - { - try { - const response = await AttendanceRepository.getAttendanceFilteredByDate(projectId, fromDate, toDate); - return response?.data?.filter((log) => log.checkInTime !== null && log.activity !== 0); - } catch (error) { - return thunkAPI.rejectWithValue(error.message); - } - } -); - - -export const markAttendance = createAsyncThunk( - 'attendanceLogs/markAttendance', // Updated action type prefix - async ( formData, thunkAPI ) => - - { - try { - let newRecordAttendance = { - Id: formData.id || null, - comment: formData.description, - employeeID: formData.employeeId, - projectID: formData.projectId, - date: new Date().toISOString(), - markTime: formData.markTime, - latitude: formData.latitude.toString(), - longitude: formData.longitude.toString(), - action: formData.action, - image: null, - }; - - const response = await AttendanceRepository.markAttendance( newRecordAttendance ); - return response.data; - } catch ( error ) - { - const message = error?.response?.data?.message || error.message || "Error Occured During Api Call"; - return thunkAPI.rejectWithValue(message); - } - } -); - -// Attendance Logs Slice -const attendanceLogsSlice = createSlice({ - name: 'attendanceLogs', // Updated slice name - initialState: { - data: [], - loading: false, - error: null, - }, - reducers: { - setAttendanceData: (state, action) => { - state.data = action.payload; - }, - }, - extraReducers: (builder) => { - builder - // Fetch attendance data - .addCase(fetchAttendanceData.pending, (state) => { - state.loading = true; - }) - .addCase(fetchAttendanceData.fulfilled, (state, action) => { - state.loading = false; - state.data = action.payload; - }) - .addCase(fetchAttendanceData.rejected, (state, action) => { - state.loading = false; - state.error = action.payload; - }) - - // Mark attendance - log attenace data - .addCase(markAttendance.fulfilled, (state, action) => { - const updatedRecord = action.payload; - const index = state.data.findIndex(item => item.id === updatedRecord.id); - - if (index !== -1) { - state.data[index] = { ...state.data[index], ...updatedRecord }; - } else { - state.data.push(updatedRecord); - } - }); - - }, -}); - -export const { setAttendanceData } = attendanceLogsSlice.actions; -export default attendanceLogsSlice.reducer; diff --git a/src/slices/apiSlice/attendanceAllSlice.js b/src/slices/apiSlice/attendanceAllSlice.js deleted file mode 100644 index 1e9acdd2..00000000 --- a/src/slices/apiSlice/attendanceAllSlice.js +++ /dev/null @@ -1,38 +0,0 @@ -import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; -import AttendanceRepository from '../../repositories/AttendanceRepository'; -import {clearCacheKey} from '../apiDataManager'; - -export const markCurrentAttendance = createAsyncThunk( - 'attendanceCurrentDate/markAttendance', - async ( formData, {getState, dispatch, rejectWithValue} ) => - { - - const { projectId } = getState().localVariables - try - { - - // Create the new attendance record - const newRecordAttendance = { - Id: formData.id || null, - comment: formData.description, - employeeID: formData.employeeId, - projectId: projectId, - date: new Date().toISOString(), - markTime: formData.markTime, - latitude: formData.latitude.toString(), - longitude: formData.longitude.toString(), - action: formData.action, - image: null, - }; - - const response = await AttendanceRepository.markAttendance(newRecordAttendance); - const markedAttendance = response.data - clearCacheKey("AttendanceLogs") - return markedAttendance; - - } catch (error) { - console.error('Error marking attendance:', error); - return rejectWithValue(error.message); // Reject with error message - } - } - ); \ No newline at end of file diff --git a/src/slices/apiSlice/employeeAttendanceSlice.js b/src/slices/apiSlice/employeeAttendanceSlice.js deleted file mode 100644 index 290883c7..00000000 --- a/src/slices/apiSlice/employeeAttendanceSlice.js +++ /dev/null @@ -1,56 +0,0 @@ -import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; -import AttendanceRepository from '../../repositories/AttendanceRepository'; -import { markAttendance } from './attedanceLogsSlice'; - -export const fetchEmployeeAttendanceData = createAsyncThunk( - 'employeeAttendance/fetchEmployeeAttendanceData', - async ( {employeeId, fromDate, toDate}, thunkAPI ) => - { - try { - const response = await AttendanceRepository.getAttendanceByEmployee( employeeId, fromDate, toDate ); - // return response?.data?.filter((log) => log.checkInTime !== null && log.activity !== 0); - return response.data - } catch (error) { - return thunkAPI.rejectWithValue(error.message); - } - } -); - - -const employeeAttendancesSlice = createSlice({ - name: 'employeeAttendance', // Updated slice name - initialState: { - data: [], - loading: false, - error: null, - }, - reducers: { - setEmployeeAttendanceData: (state, action) => { - state.data = action.payload; - }, - }, - extraReducers: (builder) => { - builder - // Fetch attendance data - .addCase(fetchEmployeeAttendanceData.pending, (state) => { - state.loading = true; - }) - .addCase(fetchEmployeeAttendanceData.fulfilled, (state, action) => { - state.loading = false; - state.data = action.payload; - }) - - - .addCase(fetchEmployeeAttendanceData.rejected, (state, action) => { - state.loading = false; - state.error = action.payload; - }) - - - - - }, - }); - - export const { setEmployeeAttendanceData } = employeeAttendancesSlice.actions; - export default employeeAttendancesSlice.reducer; \ No newline at end of file From e0c7eee1fd65e4795906d0e23bf370dcb9a84e82 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Mon, 15 Sep 2025 18:25:12 +0530 Subject: [PATCH 002/120] removed unused files --- src/components/Activities/AttendcesLogs.jsx | 1 - src/components/Activities/CheckCheckOutForm.jsx | 1 - .../Activities/RegularizationActions.jsx | 2 -- src/components/Employee/EmpAttendance.jsx | 1 - src/slices/localVariablesSlice.jsx | 16 +++++++++++++++- src/store/store.jsx | 5 +---- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/components/Activities/AttendcesLogs.jsx b/src/components/Activities/AttendcesLogs.jsx index faf4bdc5..9aced215 100644 --- a/src/components/Activities/AttendcesLogs.jsx +++ b/src/components/Activities/AttendcesLogs.jsx @@ -4,7 +4,6 @@ import Avatar from "../common/Avatar"; import { convertShortTime } from "../../utils/dateUtils"; import RenderAttendanceStatus from "./RenderAttendanceStatus"; import { useSelector, useDispatch } from "react-redux"; -import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice"; import DateRangePicker from "../common/DateRangePicker"; import { clearCacheKey, getCachedData, useSelectedProject } from "../../slices/apiDataManager"; import eventBus from "../../services/eventBus"; diff --git a/src/components/Activities/CheckCheckOutForm.jsx b/src/components/Activities/CheckCheckOutForm.jsx index ca47dd8c..40eb0a75 100644 --- a/src/components/Activities/CheckCheckOutForm.jsx +++ b/src/components/Activities/CheckCheckOutForm.jsx @@ -5,7 +5,6 @@ import { zodResolver } from "@hookform/resolvers/zod"; import TimePicker from "../common/TimePicker"; import { usePositionTracker } from "../../hooks/usePositionTracker"; import { useDispatch, useSelector } from "react-redux"; -import { markAttendance } from "../../slices/apiSlice/attedanceLogsSlice"; import showToast from "../../services/toastService"; import { checkIfCurrentDate } from "../../utils/dateUtils"; import { useMarkAttendance } from "../../hooks/useAttendance"; diff --git a/src/components/Activities/RegularizationActions.jsx b/src/components/Activities/RegularizationActions.jsx index 1fcb3485..4e94c685 100644 --- a/src/components/Activities/RegularizationActions.jsx +++ b/src/components/Activities/RegularizationActions.jsx @@ -1,9 +1,7 @@ import React, { act, useEffect, useState } from 'react' import useAttendanceStatus, { ACTIONS } from '../../hooks/useAttendanceStatus'; -// import AttendanceRepository from '../../repositories/AttendanceRepository'; import { useDispatch, useSelector } from 'react-redux'; import { usePositionTracker } from '../../hooks/usePositionTracker'; -import {markCurrentAttendance} from '../../slices/apiSlice/attendanceAllSlice'; import {cacheData, getCachedData, useSelectedProject} from '../../slices/apiDataManager'; import showToast from '../../services/toastService'; import { useMarkAttendance } from '../../hooks/useAttendance'; diff --git a/src/components/Employee/EmpAttendance.jsx b/src/components/Employee/EmpAttendance.jsx index c441b826..548471d8 100644 --- a/src/components/Employee/EmpAttendance.jsx +++ b/src/components/Employee/EmpAttendance.jsx @@ -2,7 +2,6 @@ import React, { useState, useEffect } from "react"; import moment from "moment"; import DateRangePicker, { DateRangePicker1 } from "../common/DateRangePicker"; import { useDispatch, useSelector } from "react-redux"; -import { fetchEmployeeAttendanceData } from "../../slices/apiSlice/employeeAttendanceSlice"; import usePagination from "../../hooks/usePagination"; import Avatar from "../common/Avatar"; import { convertShortTime } from "../../utils/dateUtils"; diff --git a/src/slices/localVariablesSlice.jsx b/src/slices/localVariablesSlice.jsx index 0b0a8405..5cc86af1 100644 --- a/src/slices/localVariablesSlice.jsx +++ b/src/slices/localVariablesSlice.jsx @@ -10,7 +10,11 @@ const localVariablesSlice = createSlice({ endDate: null, }, projectId: null, - reload:false + reload:false, + + OrganizationModal:{ + isOpen:false + } }, reducers: { @@ -32,6 +36,16 @@ const localVariablesSlice = createSlice({ setDefaultDateRange: (state, action) => { state.defaultDateRange = action.payload; }, + + openOrgModal: (state) => { + state.OrganizationModal.isOpen = true; + }, + closeOrgModal: (state) => { + state.OrganizationModal.isOpen = false; + }, + toggleOrgModal: (state) => { + state.OrganizationModal.isOpen = !state.OrganizationModal.isOpen; + }, }, }); diff --git a/src/store/store.jsx b/src/store/store.jsx index e1d9c03a..fca9836c 100644 --- a/src/store/store.jsx +++ b/src/store/store.jsx @@ -2,15 +2,12 @@ import { configureStore } from "@reduxjs/toolkit"; import apiCacheReducer from "../slices/apiCacheSlice"; import globalVariablesReducer from "../slices/globalVariablesSlice"; import localVariableRducer from "../slices/localVariablesSlice" -import attendanceReducer from "../slices/apiSlice/attedanceLogsSlice" -import employeeAttendanceReducer from "../slices/apiSlice/employeeAttendanceSlice" export const store = configureStore({ reducer: { apiCache: apiCacheReducer, globalVariables: globalVariablesReducer, localVariables:localVariableRducer, - attendanceLogs: attendanceReducer, - employeeAttendance: employeeAttendanceReducer, + }, }); From d944d3a389c9d1548d1cbed45e0836cabc3981b9 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Mon, 15 Sep 2025 19:56:45 +0530 Subject: [PATCH 003/120] setup organization modal, it seprated form another becuase this modal can open anywhere at one hook --- src/App.tsx | 2 + src/ModalContext.jsx | 112 ------------------ src/ModalProvider.jsx | 14 +++ .../Organization/ManageOrganization.jsx | 76 ++++++++++++ .../Organization/OrganizationSchema.js | 32 +++++ src/components/common/Modal.jsx | 37 ++---- src/hooks/useOrganization.js | 16 +++ src/main.tsx | 5 - src/pages/Organization/OrganizationModal.jsx | 0 src/pages/Organization/OrganizationPage.jsx | 43 +++++++ src/router/AppRoutes.jsx | 5 +- src/slices/localVariablesSlice.jsx | 2 +- 12 files changed, 199 insertions(+), 145 deletions(-) delete mode 100644 src/ModalContext.jsx create mode 100644 src/ModalProvider.jsx create mode 100644 src/components/Organization/ManageOrganization.jsx create mode 100644 src/components/Organization/OrganizationSchema.js delete mode 100644 src/pages/Organization/OrganizationModal.jsx diff --git a/src/App.tsx b/src/App.tsx index be7a72ba..db452672 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import { ToastContainer } from "react-toastify"; import { QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { queryClient } from "./layouts/AuthLayout"; +import ModalProvider from "./ModalProvider"; @@ -11,6 +12,7 @@ const App = () => { return (
+ diff --git a/src/ModalContext.jsx b/src/ModalContext.jsx deleted file mode 100644 index 72607465..00000000 --- a/src/ModalContext.jsx +++ /dev/null @@ -1,112 +0,0 @@ -import React, { - createContext, - useContext, - useState, - useEffect, - useRef, -} from "react"; - -const ModalContext = createContext(); - -export const useModal = () => useContext(ModalContext); - -// ModalProvider to manage modal state and expose functionality to the rest of the app -export const ModalProvider = ({ children }) => { - const [isOpen, setIsOpen] = useState(false); - const [modalContent, setModalContent] = useState(null); - const [onSubmit, setOnSubmit] = useState(null); - const [modalSize, setModalSize] = useState("lg"); - - // Ref to track the modal content element - const modalRef = useRef(null); - - const openModal = (content, onSubmitCallback, size = "lg") => { - setModalContent(content); // Set modal content dynamically - setOnSubmit(() => onSubmitCallback); // Set the submit handler dynamically - setIsOpen(true); // Open the modal - setModalSize(size); // Set the modal size - }; - - // Function to close the modal - const closeModal = () => { - setIsOpen(false); // Close the modal - setModalContent(null); // Clear modal content - setOnSubmit(null); // Clear the submit callback - setModalSize("lg"); // Reset modal size - }; - - useEffect(() => { - const handleEscape = (event) => { - if (event.key === "Escape") { - closeModal(); - } - }; - document.addEventListener("keydown", handleEscape); - return () => { - document.removeEventListener("keydown", handleEscape); - }; - }, []); - - useEffect(() => { - const handleClickOutside = (event) => { - if (modalRef.current && !modalRef.current.contains(event.target)) { - closeModal(); - } - }; - document.addEventListener("mousedown", handleClickOutside); - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, []); - - return ( - - {children} - - {isOpen && ( -
-
-
{modalContent}
-
-
- )} -
- ); -}; - -const overlayStyles = { - position: "fixed", - top: 0, - left: 0, - right: 0, - bottom: 0, - backgroundColor: "rgba(0, 0, 0, 0.5)", - display: "flex", - justifyContent: "center", - alignItems: "center", - zIndex: 1050, -}; - -const modalStyles = { - backgroundColor: "white", - padding: "20px", - borderRadius: "5px", - boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)", - width: "90%", - maxWidth: "800px", -}; diff --git a/src/ModalProvider.jsx b/src/ModalProvider.jsx new file mode 100644 index 00000000..f098ed0f --- /dev/null +++ b/src/ModalProvider.jsx @@ -0,0 +1,14 @@ +import React from 'react' +import Modal from './components/common/Modal' +import ManageOrganization from './components/Organization/ManageOrganization' + +const ModalProvider = () => { + return ( + <> + + + + ) +} + +export default ModalProvider \ No newline at end of file diff --git a/src/components/Organization/ManageOrganization.jsx b/src/components/Organization/ManageOrganization.jsx new file mode 100644 index 00000000..748f85f7 --- /dev/null +++ b/src/components/Organization/ManageOrganization.jsx @@ -0,0 +1,76 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import React from "react"; +import { FormProvider, useForm } from "react-hook-form"; +import { + defaultOrganizationValues, + organizationSchema, +} from "./OrganizationSchema"; +import Modal from "../common/Modal"; +import { useOrganization } from "../../hooks/useDirectory"; +import { useOrganizationModal } from "../../hooks/useOrganization"; +import Label from "../common/Label"; + +const ManageOrganization = () => { + const orgModal = useOrganizationModal(); + const method = useForm({ + resolver: zodResolver(organizationSchema), + defaultValues: defaultOrganizationValues, + }); + + const { handleSubmit, watch, register } = method; + + const onSubmit = () => {}; + + const contentBody = ( + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + {errors.description && ( +

{errors.description.message}

+ )} +
+ +
+ + +
+
+ ); +}; + +export default ManageServices; diff --git a/src/components/master/Services/ServicesSchema.js b/src/components/master/Services/ServicesSchema.js new file mode 100644 index 00000000..ceaa8bf5 --- /dev/null +++ b/src/components/master/Services/ServicesSchema.js @@ -0,0 +1,9 @@ +import { z } from "zod"; + +const schema = z.object({ + name: z.string().min(1, { message: "Service 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 a31a7c27..cf93ca6c 100644 --- a/src/hooks/masterHook/useMaster.js +++ b/src/hooks/masterHook/useMaster.js @@ -236,6 +236,8 @@ const fetchMasterData = async (masterType) => { return (await MasterRespository.getJobRole()).data; case "Activity": return (await MasterRespository.getActivites()).data; + case "Services": + return (await MasterRespository.getService()).data; case "Work Category": return (await MasterRespository.getWorkCategory()).data; case "Contact Category": @@ -672,6 +674,7 @@ export const useCreatePaymentMode = (onSuccessCallback)=>{ } }) } + export const useUpdatePaymentMode = (onSuccessCallback)=>{ const queryClient = useQueryClient(); @@ -694,6 +697,81 @@ export const useUpdatePaymentMode = (onSuccessCallback)=>{ }) } + +// Services------------------------------- + +// export const useCreateService = (onSuccessCallback) => { +// const queryClient = useQueryClient(); + +// return useMutation({ +// mutationFn: async (payload) => { +// const resp = await MasterRespository.createService(payload); +// return resp.data; // full API response +// }, +// onSuccess: (data) => { +// // Invalidate & refetch service list +// queryClient.invalidateQueries({ queryKey: ["masterData", "Services"] }); + +// showToast(data?.message || "Service added successfully", "success"); + +// if (onSuccessCallback) onSuccessCallback(data?.data); // pass back new service object +// }, +// onError: (error) => { +// showToast(error.message || "Something went wrong", "error"); +// }, +// }); +// }; + +export const useCreateService = (onSuccessCallback) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (payload) => { + debugger; + const resp = await MasterRespository.createService(payload); + debugger; + return resp.data; + }, + onSuccess: (data) => { + debugger; + queryClient.invalidateQueries({ queryKey: ["masterData", "Services"] }); + + showToast(data?.message || "Service added successfully", "success"); + + if (onSuccessCallback) onSuccessCallback(data?.data); + }, + onError: (error) => { + debugger; + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; + + +export const useUpdateService = (onSuccessCallback) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({ id, payload }) => { + const response = await MasterRespository.updateService(id, payload); + return response; // full response since it already has { success, message, data } + }, + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Services"], + }); + + showToast(data.message || "Service updated successfully.", "success"); + + if (onSuccessCallback) onSuccessCallback(data); + }, + onError: (error) => { + showToast(error?.message || "Something went wrong", "error"); + }, + }); +}; + + // -------------------Expense Status---------------------------------- export const useCreateExpenseStatus =(onSuccessCallback)=>{ const queryClient = useQueryClient(); diff --git a/src/pages/master/MasterPage.jsx b/src/pages/master/MasterPage.jsx index 1640f2ba..b187efd1 100644 --- a/src/pages/master/MasterPage.jsx +++ b/src/pages/master/MasterPage.jsx @@ -91,7 +91,7 @@ const MasterPage = () => { {modalConfig && ( api.get("api/master/activities"), createActivity: (data) => api.post("api/master/activity", data), + +//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}`), + updateActivity: (id, data) => api.post(`api/master/activity/edit/${id}`, data), getIndustries: () => api.get("api/master/industries"), From af5519fd6005e98cc43d085fa889e299b58163e8 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 19 Sep 2025 14:53:40 +0530 Subject: [PATCH 024/120] Adding Dropdown and Organization Column in Teams --- src/components/Project/Teams.jsx | 65 +++++++++++++++++++------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/src/components/Project/Teams.jsx b/src/components/Project/Teams.jsx index 3a8d78cf..e39a4b13 100644 --- a/src/components/Project/Teams.jsx +++ b/src/components/Project/Teams.jsx @@ -21,8 +21,6 @@ import { import { useSelectedProject } from "../../slices/apiDataManager"; const Teams = () => { - // const {projectId} = useParams() - // const projectId = useSelector((store)=>store.localVariables.projectId) const projectId = useSelectedProject(); const dispatch = useDispatch(); @@ -136,7 +134,6 @@ const Teams = () => { useEffect(() => { if (projectEmployees) { setEmployees(projectEmployees); - //setFilteredEmployees(projectEmployees?.filter((emp) => emp.isActive)); const filtered = projectEmployees.filter((emp) => emp.isActive); setFilteredEmployees(filtered); } @@ -179,13 +176,6 @@ const Teams = () => { const handleFilterEmployee = (e) => { const filterValue = e.target.value; - // if (filterValue === "true") { - // setActiveEmployee(true); - // setFilteredEmployees(employees.filter((emp) => emp.isActive)); - // } else { - // setFilteredEmployees(employees.filter((emp) => !emp.isActive)); - // setActiveEmployee(false); - // } setActiveEmployee(filterValue === "true"); setSearchTerm(""); }; @@ -274,6 +264,27 @@ const Teams = () => { onChange={handleSearch} />
+ {/* Services-Dropdown */} + +
+ +
{
+ + +
+ + {item.organizationName || "N/A"} + - {" "} - {moment(item.allocationDate).format( - "DD-MMM-YYYY" - )}{" "} + {moment(item.allocationDate).format("DD-MMM-YYYY")} {!activeEmployee && ( {item.reAllocationDate - ? moment(item.reAllocationDate).format( - "DD-MMM-YYYY" - ) + ? moment(item.reAllocationDate).format("DD-MMM-YYYY") : "Present"} )} @@ -373,7 +385,7 @@ const Teams = () => { - {item.isActive && ( + {item.isActive ? ( + ) : ( + Not in project )} - {!item.isActive && Not in project} ))} + )} {!employeeLodaing && filteredEmployees.length === 0 && (
From ea219b717696cf618678abaa53789f73ea1f074d Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 19 Sep 2025 15:03:25 +0530 Subject: [PATCH 025/120] Adding Services Dropdown in Infrastructure. --- src/components/Project/ProjectInfra.jsx | 124 ++++++++++++++---------- 1 file changed, 72 insertions(+), 52 deletions(-) diff --git a/src/components/Project/ProjectInfra.jsx b/src/components/Project/ProjectInfra.jsx index 38c05567..c99b08c9 100644 --- a/src/components/Project/ProjectInfra.jsx +++ b/src/components/Project/ProjectInfra.jsx @@ -21,18 +21,17 @@ import { 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 { useParams } from "react-router-dom"; import GlobalModel from "../common/GlobalModel"; -const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) => -{ +const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => { // const projectId = useSelector((store)=>store.localVariables.projectId) const projectId = useSelectedProject(); const reloadedData = useSelector((store) => store.localVariables.reload); - const [ expandedBuildings, setExpandedBuildings ] = useState( [] ); - const {projectInfra,isLoading,error} = useProjectInfra(projectId) + const [expandedBuildings, setExpandedBuildings] = useState([]); + const { projectInfra, isLoading, error } = useProjectInfra(projectId) const { projects_Details, refetch, loading } = useProjectDetails(data?.id); - const [ project, setProject ] = useState( projects_Details ); + const [project, setProject] = useState(projects_Details); const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA); const ManageTask = useHasUserPermission(MANAGE_TASK) const [showModalFloor, setshowModalFloor] = useState(false); @@ -58,29 +57,29 @@ const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) => return ( <> - {showModalBuilding && setshowModalBuilding( false )}> + {showModalBuilding && setshowModalBuilding(false)}> setshowModalBuilding( false )} + onClose={() => setshowModalBuilding(false)} /> - } - {showModalFloor && setshowModalFloor(false)}> + } + {showModalFloor && setshowModalFloor(false)}> setshowModalFloor(false)} - /> + project={projectInfra} + onClose={() => setshowModalFloor(false)} + /> } - {showModalWorkArea && setshowModalWorkArea(false)} > - setshowModalWorkArea(false)} - /> + {showModalWorkArea && setshowModalWorkArea(false)} > + setshowModalWorkArea(false)} + /> } - {showModalTask && ( setshowModalTask(false)}> + {showModalTask && ( setshowModalTask(false)}> setshowModalTask(false)} - /> + project={projectInfra} + onClose={() => setshowModalTask(false)} + /> )}
@@ -88,38 +87,59 @@ const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) =>
- {ManageInfra && (<> - - - )} - + +
+ + {/* Buttons Section (aligned to right) */} +
+ {ManageInfra && ( + <> + + + )} + {(ManageTask || ManageInfra) && ( +
+
+
+
+ ))} +
+ + {orgModal.orgData && ( +

+ Don't have required organization, Please find using{" "} + setStep(2)} + > + SPRID + +

+ )}
- {/* ======== org list ======*/} - {/*
-
setStep(2)} - > - - Sample Organization 1 -
-
*/} +
+ {!projectOrganizations && ( + + )} -
+
+
+ + + ))} -
+
+ - +
+
+ +
+ {Organization.address} +
+
+
+ +
+
+ +
    + {masterService.data && + masterService.data?.map((serv) => ( +
  • + + {serv.name} +
  • + ))} +
+
+ +
+ + +
)} @@ -309,48 +469,3 @@ const ManageOrganization = ({ }; export default ManageOrganization; - -//
-//
-//
-// -//
- -//
-//
-//
-//
Icing sweet gummies
-// 15 minutes -//
-// In Meeting -//
-//
-//
-// -//
-//
-//
-//
- -// {/* Icon item */} -//
-//
-// -//
- -//
-//
-//
-//
Icing sweet gummies
-// 15 minutes -//
-// In Meeting -//
-//
-//
-// -//
-//
-//
-//
-//
diff --git a/src/components/Organization/ManageOrganization1.jsx b/src/components/Organization/ManageOrganization1.jsx new file mode 100644 index 00000000..4ba25c4f --- /dev/null +++ b/src/components/Organization/ManageOrganization1.jsx @@ -0,0 +1,193 @@ +const ManageOrganization1 = ({ + projectOrganizations = [], + organizationId = null, +}) => { + const [step, setStep] = useState(1); // default = scenario decision + const orgModal = useOrganizationModal(); + const { data: services, isLoading } = useServices(); + + const method = useForm({ + resolver: zodResolver(organizationSchema), + defaultValues: defaultOrganizationValues, + }); + + const { + handleSubmit, + register, + reset, + formState: { errors }, + } = method; + + const { mutate: CreateOrganization, isPending } = useCreateOrganization( + () => { + reset(defaultOrganizationValues); + orgModal.onClose(); + setStep(1); // reset to first step + } + ); + + // 🔹 Decide first step when modal opens + useEffect(() => { + if (orgModal.isOpen) { + if (organizationId) { + setStep(3); // update flow → show org details directly + } else if (projectOrganizations && projectOrganizations.length > 0) { + setStep(1); // Scenario 1 → from current tenant list + } else { + setStep(2); // Scenario 2 → search with SPRID + } + } + }, [orgModal.isOpen, organizationId, projectOrganizations]); + + const onSubmit = (OrgPayload) => { + CreateOrganization(OrgPayload); + }; + + const RenderTitle = useMemo(() => { + if (organizationId) return "Update Organization"; + if (step === 1) return "Add Organization"; // current tenant + if (step === 2) return "Find Organization"; // search with SPRID + if (step === 3) return "Organization Details"; + if (step === 4) return "Create Organization"; + return "Manage Organization"; + }, [step, organizationId]); + + const contentBody = ( +
+ {/* ---------- STEP 1: From Current Tenant Organizations ---------- */} + {step === 1 && ( +
+
+ {projectOrganizations.map((org, idx) => ( +
setStep(3)} + > + + {org} +
+ ))} +
+ +
+ + +
+
+ )} + + {/* ---------- STEP 2: Search by Service Provider ID ---------- */} + {step === 2 && ( +
+
+ + +
+ + {/* Example SPR results */} +
+
setStep(3)} + > + + Sample Organization (SPRID) +
+
+ +
+ + +
+
+ )} + + {/* ---------- STEP 3: Organization Details ---------- */} + {step === 3 && ( +
+

+ Show organization details here (from SPR or tenant list). User + selects services and clicks Add. +

+
+ +
    +
  • + + Service 1 +
  • +
  • + + Service 2 +
  • +
+
+
+ + +
+
+ )} + + {/* ---------- STEP 4: Create New Organization ---------- */} + {step === 4 && ( + +
+ {/* same form as your code, unchanged */} + {/* ... */} +
+
+ )} +
+ ); + + return ( + + ); +}; + +export default ManageOrganization; \ No newline at end of file diff --git a/src/components/Project/ProjectOrganizations.jsx b/src/components/Project/ProjectOrganizations.jsx index c7093542..fd30568e 100644 --- a/src/components/Project/ProjectOrganizations.jsx +++ b/src/components/Project/ProjectOrganizations.jsx @@ -7,7 +7,8 @@ const ProjectOrganizations = () => { const selectedProject = useSelectedProject() return (
-
+
+
+

diff --git a/src/hooks/useOrganization.js b/src/hooks/useOrganization.js index 2fa6ea00..34e249ff 100644 --- a/src/hooks/useOrganization.js +++ b/src/hooks/useOrganization.js @@ -24,6 +24,15 @@ export const useOrganizationModal = () => { }; }; +export const useOrganizationBySPRID =(sprid)=>{ + return useQuery({ + queryKey:["organization by",sprid], + queryFn:async()=>await OrganizationRepository.getOrganizationBySPRID(sprid), + enabled:!!sprid + }) +} + + export const useOrganizationsList = ( pageSize, pageNumber, diff --git a/src/hooks/useProjects.js b/src/hooks/useProjects.js index 5650f0fa..b411ef33 100644 --- a/src/hooks/useProjects.js +++ b/src/hooks/useProjects.js @@ -268,6 +268,18 @@ export const useProjectLevelEmployeePermission = (employeeId, projectId) => { }); }; + +export const useProjectAssignedServices =(projectId)=>{ + return useQuery({ + queryKey: ["projectAssignedServices", projectId], + queryFn: async () => { + const resp = await ProjectRepository.getProjectAssignedServices(projectId); + return resp.data; + }, + enabled:!!projectId, + }); +} + // -- -------------Mutation------------------------------- export const useCreateProject = ({ onSuccessCallback }) => { diff --git a/src/pages/Directory/ContactsPage.jsx b/src/pages/Directory/ContactsPage.jsx index 40759abe..670b3ef9 100644 --- a/src/pages/Directory/ContactsPage.jsx +++ b/src/pages/Directory/ContactsPage.jsx @@ -98,7 +98,7 @@ const ContactsPage = ({ projectId, searchText, onExport }) => { Pagination={ } diff --git a/src/repositories/OrganizationRespository.jsx b/src/repositories/OrganizationRespository.jsx index ccde9714..36f41006 100644 --- a/src/repositories/OrganizationRespository.jsx +++ b/src/repositories/OrganizationRespository.jsx @@ -10,6 +10,8 @@ const OrganizationRepository = { ); }, + getOrganizationBySPRID :()=>api.get(`/api/Organization/list?sprid=${sprid}`), + assignOrganizationToProject:(data)=>api.post(`/api/Organization/assign/project`,data) }; diff --git a/src/repositories/ProjectRepository.jsx b/src/repositories/ProjectRepository.jsx index fc1c648d..af5c6e67 100644 --- a/src/repositories/ProjectRepository.jsx +++ b/src/repositories/ProjectRepository.jsx @@ -45,7 +45,12 @@ const ProjectRepository = { 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}`) + getAllProjectLevelPermission:(projectId)=>api.get(`/api/Project/get/all/project-level-permission/${projectId}`), + + + // Services + + getProjectAssignedServices:(projectId)=>api.get(`/api/Project/get/assigned/services/${projectId}`) }; export const TasksRepository = { From e154bac64a0ddadb36fc1377af6d8d1a5b726dc1 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 19 Sep 2025 15:54:29 +0530 Subject: [PATCH 027/120] Calling api for services dropdwon. --- src/components/Project/ProjectInfra.jsx | 31 +++++++++++++------------ 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/components/Project/ProjectInfra.jsx b/src/components/Project/ProjectInfra.jsx index c99b08c9..38821a6a 100644 --- a/src/components/Project/ProjectInfra.jsx +++ b/src/components/Project/ProjectInfra.jsx @@ -17,7 +17,7 @@ import { getCachedData, useSelectedProject, } from "../../slices/apiDataManager"; -import { useProjectDetails, useProjectInfra } from "../../hooks/useProjects"; +import { useProjectAssignedServices, useProjectDetails, useProjectInfra } from "../../hooks/useProjects"; import { useDispatch, useSelector } from "react-redux"; import { refreshData } from "../../slices/localVariablesSlice"; import eventBus from "../../services/eventBus"; @@ -39,18 +39,17 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => { 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); + }; useEffect(() => { setProject(projectInfra); }, [data, projects_Details]); - // useEffect(() => { - // if (reloadedData) { - // refetch(); - // dispatch(refreshData(false)); - // } - // }, [reloadedData]); - const signalRHandler = (response) => { setProject(response); } @@ -96,17 +95,19 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => { aria-controls="DataTables_Table_0" className="form-select form-select-sm" aria-label="Select Service" - defaultValue="Fire-Fitting" - // onChange={handleServiceChange} + value={selectedService} + onChange={handleServiceChange} > - - - - + + {servicesLoading && } + {assignedServices?.map((service) => ( + + ))}

- {/* Buttons Section (aligned to right) */}
{ManageInfra && ( From 7d17422681e73d55e1720203bcd569ce5a733470 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 19 Sep 2025 16:02:36 +0530 Subject: [PATCH 028/120] Calling Api for Services dropdown. --- src/components/Project/Teams.jsx | 50 ++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/components/Project/Teams.jsx b/src/components/Project/Teams.jsx index e39a4b13..fcac139e 100644 --- a/src/components/Project/Teams.jsx +++ b/src/components/Project/Teams.jsx @@ -17,6 +17,7 @@ import eventBus from "../../services/eventBus"; import { useEmployeesByProjectAllocated, useManageProjectAllocation, + useProjectAssignedServices, } from "../../hooks/useProjects"; import { useSelectedProject } from "../../slices/apiDataManager"; @@ -35,6 +36,13 @@ const Teams = () => { const [activeEmployee, setActiveEmployee] = useState(true); const [deleteEmployee, setDeleteEmplyee] = useState(null); const [searchTerm, setSearchTerm] = useState(""); // State for search term + const { data: assignedServices, isLoading: servicesLoading } = useProjectAssignedServices(projectId); + + const [selectedService, setSelectedService] = useState(""); + + const handleServiceChange = (e) => { + setSelectedService(e.target.value); + }; const navigate = useNavigate(); @@ -266,25 +274,29 @@ const Teams = () => {
{/* Services-Dropdown */} -
- -
+
+ +
Date: Fri, 19 Sep 2025 16:13:09 +0530 Subject: [PATCH 029/120] Adding Dropdown in Create Task Popup. --- .../Project/Infrastructure/TaskModel.jsx | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/components/Project/Infrastructure/TaskModel.jsx b/src/components/Project/Infrastructure/TaskModel.jsx index f0bb5c35..e7c1438d 100644 --- a/src/components/Project/Infrastructure/TaskModel.jsx +++ b/src/components/Project/Infrastructure/TaskModel.jsx @@ -6,9 +6,10 @@ import { useActivitiesMaster, useWorkCategoriesMaster, } from "../../../hooks/masterHook/useMaster"; -import { useManageTask } from "../../../hooks/useProjects"; +import { useManageTask, useProjectAssignedServices } from "../../../hooks/useProjects"; import showToast from "../../../services/toastService"; import Label from "../../common/Label"; +import { useSelectedProject } from "../../../slices/apiDataManager"; const taskSchema = z.object({ buildingID: z.string().min(1, "Building is required"), @@ -37,6 +38,15 @@ const TaskModel = ({ project, onSubmit, onClose }) => { const { activities, loading: activityLoading } = useActivitiesMaster(); const { categories, categoryLoading } = useWorkCategoriesMaster(); + const projectId = useSelectedProject(); + + const { data: assignedServices, isLoading: servicesLoading } = useProjectAssignedServices(projectId); + + const [selectedService, setSelectedService] = useState(""); + + const handleServiceChange = (e) => { + setSelectedService(e.target.value); + }; const { register, handleSubmit, @@ -96,10 +106,12 @@ const TaskModel = ({ project, onSubmit, onClose }) => { }, [categories]); const onSubmitForm = async (data) => { - const payload = [data]; - CreateTask({payload:payload,buildingId: data.buildingID, + const payload = [data]; + CreateTask({ + payload: payload, buildingId: data.buildingID, floorId: data.floorId, - workAreaId: data.workAreaId, PreviousPlannedWork:0,previousCompletedWork:0}); + workAreaId: data.workAreaId, PreviousPlannedWork: 0, previousCompletedWork: 0 + }); }; return ( @@ -107,6 +119,27 @@ const TaskModel = ({ project, onSubmit, onClose }) => {
Manage Task
+
+ + + {errors.buildingID && ( +

{errors.buildingID.message}

+ )} +
+ {servicesLoading && } + {assignedServices?.map((service) => ( + + ))} + +
+ + {/* 🔹 InfraPlanning only when project is selected */} {selectedProject ? ( - + ) : (
Please Select Project
)} From e9d8b6daea9f1b0cdf4ad976f4d5f3f7f4ad768d Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 19 Sep 2025 16:43:44 +0530 Subject: [PATCH 031/120] Adding Dropdown and API call in Daily Progress Report. --- src/pages/Activities/DailyTask.jsx | 58 +++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/src/pages/Activities/DailyTask.jsx b/src/pages/Activities/DailyTask.jsx index fc51f756..87bc52e2 100644 --- a/src/pages/Activities/DailyTask.jsx +++ b/src/pages/Activities/DailyTask.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useMemo, useState } from "react"; import { useDispatch } from "react-redux"; import { useTaskList } from "../../hooks/useTasks"; -import { useProjectName } from "../../hooks/useProjects"; +import { useProjectAssignedServices, useProjectName } from "../../hooks/useProjects"; import { setProjectId } from "../../slices/localVariablesSlice"; import Breadcrumb from "../../components/common/Breadcrumb"; import DateRangePicker from "../../components/common/DateRangePicker"; @@ -24,6 +24,14 @@ const DailyTask = () => { 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: [], @@ -38,7 +46,6 @@ const DailyTask = () => { dateRange?.endDate || null ); - // Ensure project is set useEffect(() => { if (!selectedProject && projectNames.length > 0) { debugger @@ -46,7 +53,6 @@ const DailyTask = () => { } }, [selectedProject, projectNames, dispatch]); - // 🔹 Reset filters when project changes useEffect(() => { setFilters({ selectedBuilding: "", @@ -55,7 +61,6 @@ const DailyTask = () => { }); }, [selectedProject]); - // Memoized filtering const filteredTasks = useMemo(() => { if (!TaskList) return []; return TaskList.filter((task) => { @@ -69,7 +74,6 @@ const DailyTask = () => { }); }, [TaskList, filters]); - // Memoized dates const groupedTasks = useMemo(() => { const groups = {}; filteredTasks.forEach((task) => { @@ -82,13 +86,11 @@ const DailyTask = () => { .map((date) => ({ date, tasks: groups[date] })); }, [filteredTasks]); - // --- Modal State const [modal, setModal] = useState({ type: null, data: null }); const openModal = (type, data = null) => setModal({ type, data }); const closeModal = () => setModal({ type: null, data: null }); - // --- Render helpers const renderTeamMembers = (task, refIndex) => (
{ return ( <> - {/* --- Modals --- */} {modal.type === "report" && ( @@ -165,7 +166,7 @@ const DailyTask = () => {
{!selectedProject && (
Please Select Project
)} {/* --- Filters --- */} -
+ {/*
{ currentSelectedActivities={filters.selectedActivities} selectedProject={selectedProject} /> +
*/} + +
+ +
+ + +
+ +
+ +
+ + {/* --- Table --- */}
From 164b82e1c79f0a3372e3a858dad71960bf785649 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 19 Sep 2025 17:00:02 +0530 Subject: [PATCH 032/120] Add an Organization column in the Attendance grid across all tabs. --- src/components/Activities/Attendance.jsx | 10 +++++++++- src/components/Activities/AttendcesLogs.jsx | 5 +++-- src/components/Activities/Regularization.jsx | 8 ++++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/components/Activities/Attendance.jsx b/src/components/Activities/Attendance.jsx index 6d751b12..3bd83f9b 100644 --- a/src/components/Activities/Attendance.jsx +++ b/src/components/Activities/Attendance.jsx @@ -142,6 +142,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => { + + + - @@ -221,6 +228,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
Name RoleOrganization Check-In @@ -190,6 +191,8 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => { {item.jobRoleName}{item.organizationName || "--"} {item.checkInTime ? convertShortTime(item.checkInTime) @@ -213,7 +216,11 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => { ))} {!attendance && (
+ No employees assigned to the project!
+ {!loading && finalFilteredData.length > ITEMS_PER_PAGE && (
*/} ))} + ) : (
Date: Fri, 19 Sep 2025 19:13:02 +0530 Subject: [PATCH 033/120] Adding condition if single or no project assigned then dropdown is not shown --- src/components/Project/Teams.jsx | 87 ++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/src/components/Project/Teams.jsx b/src/components/Project/Teams.jsx index fcac139e..afde352a 100644 --- a/src/components/Project/Teams.jsx +++ b/src/components/Project/Teams.jsx @@ -37,6 +37,7 @@ const Teams = () => { const [deleteEmployee, setDeleteEmplyee] = useState(null); const [searchTerm, setSearchTerm] = useState(""); // State for search term const { data: assignedServices, isLoading: servicesLoading } = useProjectAssignedServices(projectId); + const handleToggleActive = e => setActiveEmployee(e.target.checked); const [selectedService, setSelectedService] = useState(""); @@ -262,22 +263,10 @@ const Teams = () => {
-
- -
- {/* Services-Dropdown */} - -
+
+ {servicesLoading ? ( + Loading... + ) : assignedServices?.length > 1 ? ( -
-
-
-
- + {assignedServices?.length === 1 + ? assignedServices[0].name + : "No service available"} +
+ )}
- +
+ +
+ +
+
+
+ + +
{/* Buttons Section (aligned to right) */}
From 00d6774e06c95111b3e1710db62d19d02d4c679f Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 19 Sep 2025 19:26:46 +0530 Subject: [PATCH 035/120] Change the position of Services in Create Task popup in Infrastructure. --- .../Project/Infrastructure/TaskModel.jsx | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/components/Project/Infrastructure/TaskModel.jsx b/src/components/Project/Infrastructure/TaskModel.jsx index e7c1438d..9c65fa6f 100644 --- a/src/components/Project/Infrastructure/TaskModel.jsx +++ b/src/components/Project/Infrastructure/TaskModel.jsx @@ -119,27 +119,6 @@ const TaskModel = ({ project, onSubmit, onClose }) => {
Manage Task
-
- - - {errors.buildingID && ( -

{errors.buildingID.message}

- )} -
+ {servicesLoading && } + {assignedServices?.map((service) => ( + + ))} + + {errors.buildingID && ( +

{errors.buildingID.message}

+ )} +
+ )} + + {/* Activity Selection */} {selectedWorkArea && (
@@ -225,6 +231,7 @@ const TaskModel = ({ project, onSubmit, onClose }) => {
)} + {selectedWorkArea && (
From 27b62c858df737cb29933ff26f404576d294d6b8 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 19 Sep 2025 19:50:50 +0530 Subject: [PATCH 036/120] Change the position of Datepicker and Dropdown box. --- src/pages/Activities/DailyTask.jsx | 86 +++++++++++++++++------------- 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/src/pages/Activities/DailyTask.jsx b/src/pages/Activities/DailyTask.jsx index 87bc52e2..b935b0a6 100644 --- a/src/pages/Activities/DailyTask.jsx +++ b/src/pages/Activities/DailyTask.jsx @@ -165,28 +165,48 @@ const DailyTask = () => {
{!selectedProject && (
Please Select Project
)} - {/* --- Filters --- */} - {/*
- - -
*/}
-
- + {/* --- Left: Service Dropdown + Filter Icon --- */} +
+
+ {servicesLoading ? ( + Loading... + ) : assignedServices?.length > 1 ? ( + + ) : ( +
+ {assignedServices?.length === 1 + ? assignedServices[0].name + : "No service available"} +
+ )} +
+ { />
-
- + {/* --- Right: DateRangePicker --- */} +
+
- - - {/* --- Table --- */}
@@ -288,4 +298,4 @@ const DailyTask = () => { ); }; -export default DailyTask; +export default DailyTask; \ No newline at end of file From 9223f7a1768ea61544224d76bce87212d2965fc7 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 19 Sep 2025 20:03:36 +0530 Subject: [PATCH 037/120] Adding Card in Daily Progress Report. --- src/components/Activities/InfraPlanning.jsx | 4 +- src/components/Project/ProjectInfra.jsx | 2 +- src/pages/Activities/TaskPlannng.jsx | 70 ++++++++++++++------- 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/src/components/Activities/InfraPlanning.jsx b/src/components/Activities/InfraPlanning.jsx index dc237c63..0e9c9590 100644 --- a/src/components/Activities/InfraPlanning.jsx +++ b/src/components/Activities/InfraPlanning.jsx @@ -55,7 +55,7 @@ const InfraPlanning = () => { if (isFetched && (!projectInfra || projectInfra.length === 0)) { return ( -
+

No Result Found

); @@ -63,11 +63,9 @@ const InfraPlanning = () => { return (
-
-
diff --git a/src/components/Project/ProjectInfra.jsx b/src/components/Project/ProjectInfra.jsx index 38c05567..a0a137cf 100644 --- a/src/components/Project/ProjectInfra.jsx +++ b/src/components/Project/ProjectInfra.jsx @@ -129,7 +129,7 @@ const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) =>
{isLoading &&

Loading....

} {projectInfra && projectInfra?.length > 0 && ( - { const selectedProject = useSelectedProject(); const dispatch = useDispatch(); @@ -35,30 +36,53 @@ const TaskPlannng = () => { ]} /> - {/* 🔹 Service Dropdown */} -
- -
+
+
+ {/* Service Dropdown */} +
+ {assignedServices?.length > 1 ? ( + + ) : ( +
+ {assignedServices?.length === 1 + ? assignedServices[0].name + : "No service available"} +
+ )} +
- {/* 🔹 InfraPlanning only when project is selected */} - {selectedProject ? ( - - ) : ( -
Please Select Project
- )} + + {/* Infra Planning Component */} + {selectedProject ? ( + + ) : ( +
Please Select Project
+ )} +
+
); }; From 005fdb3490df5af1e81c0e9e83be4d8e31ba1893 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Fri, 19 Sep 2025 23:46:06 +0530 Subject: [PATCH 038/120] added assigned org to project --- src/ModalProvider.jsx | 11 +- src/components/Organization/AssignOrg.jsx | 211 +++++++++++++++++- src/components/Organization/ManagOrg.jsx | 142 ++++++++++++ .../Organization/ManageOrganization.jsx | 118 +--------- src/components/Organization/OrgPicker.jsx | 110 +++++++++ src/components/Organization/OrgPicker2.jsx | 145 ++++++++++++ .../Organization/OrganizationModal.jsx | 130 +++++++++++ .../Organization/OrganizationSchema.js | 25 +++ .../Organization/OrganizationSkeleton.jsx | 39 ++++ .../Project/ProjectOrganizations.jsx | 4 +- src/hooks/masterHook/useMaster.js | 14 +- src/hooks/useOrganization.js | 66 +++++- src/pages/Organization/OrganizationPage.jsx | 5 +- src/repositories/MastersRepository.jsx | 5 +- src/repositories/OrganizationRespository.jsx | 2 +- src/slices/localVariablesSlice.jsx | 33 ++- 16 files changed, 910 insertions(+), 150 deletions(-) create mode 100644 src/components/Organization/ManagOrg.jsx create mode 100644 src/components/Organization/OrgPicker.jsx create mode 100644 src/components/Organization/OrgPicker2.jsx create mode 100644 src/components/Organization/OrganizationModal.jsx create mode 100644 src/components/Organization/OrganizationSkeleton.jsx diff --git a/src/ModalProvider.jsx b/src/ModalProvider.jsx index a68465f7..d0d72a6e 100644 --- a/src/ModalProvider.jsx +++ b/src/ModalProvider.jsx @@ -1,11 +1,12 @@ -import React from 'react' -import ManageOrganization from './components/Organization/ManageOrganization' +import React, { useEffect } from 'react' +// import ManageOrganization from './components/Organization/ManageOrganization' import { useOrganizationModal } from './hooks/useOrganization'; +import OrganizationModal from './components/Organization/OrganizationModal'; const ModalProvider = () => { - const { isOpen } = useOrganizationModal(); - - return <>{isOpen && }; + const { isOpen,onClose } = useOrganizationModal(); + + return <>{isOpen && }; }; diff --git a/src/components/Organization/AssignOrg.jsx b/src/components/Organization/AssignOrg.jsx index 0fde3168..c773bc88 100644 --- a/src/components/Organization/AssignOrg.jsx +++ b/src/components/Organization/AssignOrg.jsx @@ -1,9 +1,208 @@ -import React from 'react' +import React, { useMemo, useState } from "react"; +import { useProjectAssignedServices } from "../../hooks/useProjects"; +import { useSelectedProject } from "../../slices/apiDataManager"; +import { + useOrganizationType, + useServices, +} from "../../hooks/masterHook/useMaster"; +import Label from "../common/Label"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { assignedOrgToProject } from "./OrganizationSchema"; +import { + useAssignOrgToProject, + useOrganizationModal, +} from "../../hooks/useOrganization"; -const AssignOrg = () => { +const AssignOrg = ({ setStep }) => { + const { isOpen, orgData, startStep, onOpen, onClose, prevStep } = + useOrganizationModal(); + const selectedProject = useSelectedProject(); + const { data: masterService, isLoading: isMasterserviceLoading } = + useServices(); + const { data: projectServices, isLoading } = + useProjectAssignedServices(selectedProject); + const { data: orgType, isLoading: orgLoading } = useOrganizationType(); + + const { mutate: AssignToProject, isPending } = useAssignOrgToProject( + () => {} + ); + + const mergedServices = useMemo(() => { + if (!masterService || !projectServices) return []; + + const combined = [...masterService?.data, ...projectServices]; + + const unique = combined.filter( + (item, index, self) => index === self.findIndex((s) => s.id === item.id) + ); + + return unique; + }, [masterService, projectServices]); + + const { + register, + setValue, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(assignedOrgToProject), + }); + + const onSubmit = (formData) => { + const payload = { + ...formData, + projectId: selectedProject, + organizationId: orgData.id, + parentOrganizationId: null, + }; + AssignToProject(payload); + }; + const handleEdit = () => { + onOpen({ startStep: 4 , orgData:orgData}); + }; + if (isMasterserviceLoading || isLoading) + return
Loading....
; return ( -
AssignOrg
- ) -} +
+
+
+
{orgData.name}
+
+ +
+
+
-export default AssignOrg \ No newline at end of file +
+
+ +
{orgData.name}
+
+
+
+
+ +
{orgData.contactNumber}
+
+
+
+
+ +
{orgData.email}
+
+
+
+
+ +
{orgData.sprid}
+
+
+ +
+
+ +
{orgData.address}
+
+
+ +
+
+
+ + + {errors.organizationTypeId && ( + + {errors.organizationTypeId.message} + + )} +
+ +
+ + {mergedServices?.map((service) => ( +
+ + +
+ ))} + {errors.serviceIds && ( +
+ {errors.serviceIds.message} +
+ )} +
+ + {/* Buttons */} +
+ + +
+ +
+
+ ); +}; + +export default AssignOrg; diff --git a/src/components/Organization/ManagOrg.jsx b/src/components/Organization/ManagOrg.jsx new file mode 100644 index 00000000..a4da2b48 --- /dev/null +++ b/src/components/Organization/ManagOrg.jsx @@ -0,0 +1,142 @@ +import React, { useEffect } from "react"; +import { FormProvider, useForm } from "react-hook-form"; +import { + useCreateOrganization, + useOrganizationModal, + useUpdateOrganization, +} from "../../hooks/useOrganization"; +import { + defaultOrganizationValues, + organizationSchema, +} from "./OrganizationSchema"; +import Label from "../common/Label"; +import { useGlobalServices } from "../../hooks/masterHook/useMaster"; +import { zodResolver } from "@hookform/resolvers/zod"; +import SelectMultiple from "../common/SelectMultiple"; + +const ManagOrg = () => { + const { data: service, isLoading } = useGlobalServices(); + const { isOpen, orgData, startStep, onOpen, onClose, prevStep } = + useOrganizationModal(); + + const method = useForm({ + resolver: zodResolver(organizationSchema), + defaultValues: defaultOrganizationValues, + }); + + const { + handleSubmit, + register, + reset, + formState: { errors }, + } = method; + + // Create & Update mutations + const { mutate: createOrganization, isPending: isCreating } = useCreateOrganization(() => { + reset(defaultOrganizationValues); + onOpen({ startStep: 1 }); + onClose(); + }); + + const { mutate: updateOrganization, isPending: isUpdating } = useUpdateOrganization(() => { + reset(defaultOrganizationValues); + onOpen({ startStep: 1 }); + onClose(); + }); + + // Prefill form if editing + useEffect(() => { + if (orgData) { + console.log(orgData) + reset({ + name: orgData.name || "", + contactPerson: orgData.contactPerson || "", + contactNumber: orgData.contactNumber || "", + email: orgData.email || "", + serviceIds: orgData.services?.map(s => s.id) || [], + address: orgData.address || "", + }); + } + }, [orgData, reset]); + + const onSubmit = (payload) => { + if (orgData?.id) { + updateOrganization({ id: orgData.id, ...payload }); + } else { + createOrganization(payload); + } + }; + + return ( + +
+
+ + + {errors.name && {errors.name.message}} +
+ +
+ + + {errors.contactPerson && {errors.contactPerson.message}} +
+ +
+ + + {errors.contactNumber && {errors.contactNumber.message}} +
+ +
+ + + {errors.email && {errors.email.message}} +
+ +
+ + {errors.serviceIds && {errors.serviceIds.message}} +
+ +
+ + + + {errors.description && ( +

{errors.description.message}

+ )} +
+ +
+ + +
+ + ); +}; + +export default ManageGroup; \ No newline at end of file diff --git a/src/components/master/Services/ServicesGroups.jsx b/src/components/master/Services/ServicesGroups.jsx new file mode 100644 index 00000000..a554ea6a --- /dev/null +++ b/src/components/master/Services/ServicesGroups.jsx @@ -0,0 +1,133 @@ +import { useState } from "react"; +import { + useActivitiesByGroups, + useGroups, +} from "../../../hooks/masterHook/useMaster"; +import ManageGroup from "./ManageGroup"; + +const ServiceGroups = ({ service }) => { + const [openService, setOpenService] = useState(true); + const [activeGroupId, setActiveGroupId] = useState(null); // track selected group + const [isManageGroup,setManageGroup] = useState({isOpen:false,group:true}) + + const { data: groups, isLoading } = useGroups(service?.id); + const { data: activities, isLoading: actLoading } = + useActivitiesByGroups(activeGroupId); + + if (isLoading) return
Loading groups...
; + + return ( +
+

Manage Service

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

{service.name}

+ setOpenService(!openService)} + className="text-end cursor-pointer" + data-bs-toggle="collapse" + data-bs-target="#accordionOne" + aria-expanded={openService} + aria-controls="accordionOne" + > + + +
+ + {/* Groups Section */} +
+
+
+ {groups?.data?.map((group) => { + const isOpen = activeGroupId === group.id; + + return ( +
+
+

{group.name}

+
+ +
+ setManageGroup({isOpen:true,group:null})}> + + +
+ setActiveGroupId(isOpen ? null : 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}`} + > + +
+
+ {isManageGroup.isOpen ? setManageGroup({isOpen:false,group:null})}/> : ( +
+
+ {isOpen && actLoading &&

Loading activities...

} + {isOpen && activities?.data?.length > 0 ? ( +
    + {activities.data.map((activity) => ( +
    +
  • + {activity.name} +
  • {" "} +
    + + + +
    +
    + ))} +
+ ) : ( + 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..7846fa77 100644 --- a/src/hooks/masterHook/useMaster.js +++ b/src/hooks/masterHook/useMaster.js @@ -1,52 +1,75 @@ -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({ + queryFn: ["groups", serviceId], + queryFn: async () => await MasterRespository.getActivityGrops(serviceId), + }) +}; +export const useActivitiesByGroups = (groupId) => { + return useQuery({ + queryFn: ["activties", groupId], + queryFn: async () => await MasterRespository.getActivityGrops(groupId), + }) +}; +export const useGlobalServices = () => { return useQuery({ - queryKey:["MasterMenu"], - queryFn:async()=> { + 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 +94,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 +141,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 +163,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 +186,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 +209,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 +234,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 +259,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 +276,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 +321,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 +336,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 +391,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 +403,315 @@ 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) => -{ +export const useCreateActivity = (onSuccessCallback) => { const queryClient = useQueryClient(); - return useMutation( { - mutationFn: async ( payload ) => - { + 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) + 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"); - } - }) -} + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +}; -export const useUpdateActivity = (onSuccessCallback) => -{ - const queryClient = useQueryClient(); +export const useUpdateActivity = (onSuccessCallback) => { + const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ( {id, payload} ) => - { - const response = await MasterRespository.updateActivity(id,payload); + 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 +742,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; + debugger; showToast(error.message || "Something went wrong", "error"); }, }); }; - export const useUpdateService = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -783,154 +785,169 @@ export const useUpdateService = (onSuccessCallback) => { }); }; +export const useCreateActivityGroup =()=>{ + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (payload) => { + const response = await MasterRespository.createActivityGroup(payload) + return response; + }, + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ + queryKey: ["masterData", "Services"], + }); + + showToast(data.message || "Activity Group created successfully.", "success"); + + if (onSuccessCallback) onSuccessCallback(data); + }, + onError: (error) => { + showToast(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 +962,10 @@ 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 +}; 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" && ( + + )} +
)} @@ -410,7 +431,9 @@ const Teams = () => { className="spinner-border spinner-border-sm text-primary" role="status" > - Loading... + + Loading... + ) : ( @@ -424,7 +447,6 @@ const Teams = () => { ))}
{ + onClick={() => { dispatch(setProjectId(projectInfo.id)) navigate(`/projects/details`) }} @@ -168,7 +169,7 @@ const ProjectListView = ({ projectData, recall }) => { navigate(`/projects/details`)} + onClick={handleViewProject} > View details From d87dae47990bd7484122608ac9f457d8732a5ec5 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Tue, 23 Sep 2025 16:35:48 +0530 Subject: [PATCH 068/120] In ExpensePanel add new Toggle button. --- .../Expenses/ExpenseFilterPanel.jsx | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/components/Expenses/ExpenseFilterPanel.jsx b/src/components/Expenses/ExpenseFilterPanel.jsx index 5e415821..f93f66ba 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"; @@ -17,7 +17,7 @@ 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 { data, isLoading, isError, error, isFetching, isFetched } = useExpenseFilter(); const groupByList = useMemo(() => { return [ @@ -27,11 +27,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 +39,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,7 +77,7 @@ 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 ( <> @@ -86,18 +85,29 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
- -
- + +
+ +
-
{
- + -
- +// {/* ✅ 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/master/CreateActivity.jsx b/src/components/master/CreateActivity.jsx index d707593d..1db2bb79 100644 --- a/src/components/master/CreateActivity.jsx +++ b/src/components/master/CreateActivity.jsx @@ -24,7 +24,7 @@ const schema = z.object({ .optional(), }); -const CreateActivity = ({ onClose }) => { +const CreateActivity = ({ activity = null, whichGroup = null, close }) => { const maxDescriptionLength = 255; const { mutate: createActivity, isPending: isLoading } = useCreateActivity(() => onClose?.()); @@ -86,25 +86,19 @@ const CreateActivity = ({ onClose }) => { const onSubmit = (formData) => { createActivity(formData); }; - // const onSubmit = (data) => { - // setIsLoading(true); - - // MasterRespository.createActivity(data) - // .then( ( resp ) => - // { - - // const cachedData = getCachedData("Activity"); - // const updatedData = [ ...cachedData, resp?.data ]; - // cacheData("Activity", updatedData); - // showToast("Activity Successfully Added.", "success"); - // setIsLoading(false); - // handleClose() - // }) - // .catch((error) => { - // showToast(error.message, "error"); - // setIsLoading(false); - // }); - // }; + + useEffect(()=>{ + if (activity) { + reset({ + activityName: activity.activityName || '', + unitOfMeasurement: activity.unitOfMeasurement || '', + checkList: activity.checkList?.map((check) => ({ + description: check.description || '', + isMandatory: check.isMandatory || false, + })) || [{ description: '', isMandatory: false }], + }); + } + },[activity,reset]) const handleClose = useCallback(() => { reset(); onClose(); diff --git a/src/components/master/EditActivity.jsx b/src/components/master/EditActivity.jsx index 43816f4c..15e85255 100644 --- a/src/components/master/EditActivity.jsx +++ b/src/components/master/EditActivity.jsx @@ -24,7 +24,7 @@ const schema = z.object({ }); -const UpdateActivity = ({ activityData, onClose }) => { +const UpdateActivity = ({ activity = null, whichService = null, close }) => { const { mutate: updateActivity, isPending: isLoading } = useUpdateActivity(() => onClose?.()); const { @@ -40,10 +40,10 @@ const UpdateActivity = ({ activityData, onClose }) => { } = useForm({ resolver: zodResolver(schema), defaultValues: { - id: activityData?.id, - activityName: activityData?.activityName, - unitOfMeasurement: activityData?.unitOfMeasurement, - checkList: activityData?.checkLists || [], + id: activity?.id, + activityName: activity?.activityName, + unitOfMeasurement: activity?.unitOfMeasurement, + checkList: activity?.checkLists || [], }, }); @@ -53,15 +53,15 @@ const UpdateActivity = ({ activityData, onClose }) => { }); useEffect(() => { - if (activityData) { + if (activity) { reset({ - id: activityData.id, - activityName: activityData.activityName, - unitOfMeasurement: activityData.unitOfMeasurement, - checkList: activityData.checkLists || [], + id: activity.id, + activityName: activity.activityName, + unitOfMeasurement: activity.unitOfMeasurement, + checkList: activity.checkLists || [], }); } - }, [activityData, reset]); + }, [activity, reset]); const addChecklistItem = () => { const values = getValues("checkList"); @@ -91,36 +91,10 @@ const UpdateActivity = ({ activityData, onClose }) => { }; const onSubmit = (formData) => { - const payload = { ...formData, id: activityData.id }; - updateActivity({ id: activityData.id, payload }); + const payload = { ...formData, id: activity.id }; + updateActivity({ id: activity.id, payload }); }; - // const onSubmit = async(data) => { - // setIsLoading(true); - // const Activity = {...data, id:activityData.id} - // try - // { - // const response = await MasterRespository.updateActivity( activityData?.id, Activity ); - // const updatedActivity = response.data; - // const cachedData = getCachedData("Activity") - - // if (cachedData) { - // const updatedActivities = cachedData.map((activity) => - // activity.id === updatedActivity.id ? { ...activity, ...updatedActivity } : activity - // ); - - // cacheData( "Activity", updatedActivities ); - // onClose() - // } - // setIsLoading( false ) - // showToast("Activity Successfully Updated", "success"); - // } catch ( err ) - // { - // setIsLoading( false ) - - // showToast("error.message", "error"); - // } - // }; useEffect(() => { const tooltipTriggerList = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]')); @@ -129,7 +103,6 @@ const UpdateActivity = ({ activityData, onClose }) => { return ( - {/*
Update Activity
*/}
{/* Activity Name */}
diff --git a/src/components/master/Services/ManageActivity.jsx b/src/components/master/Services/ManageActivity.jsx new file mode 100644 index 00000000..e3d0fa2c --- /dev/null +++ b/src/components/master/Services/ManageActivity.jsx @@ -0,0 +1,274 @@ +import React, { useState, useEffect, useCallback } from "react"; +import { useFieldArray, useForm } from "react-hook-form"; +import { z } from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; + +import { + useCreateActivity, + useUpdateActivity, +} from "../../../hooks/masterHook/useMaster"; +import Label from "../../common/Label"; + +const schema = z.object({ + activityName: z.string().min(1, { message: "Activity Name is required" }), + unitOfMeasurement: z + .string() + .min(1, { message: "Unit of Measurement is required" }), + checkList: z + .array( + z.object({ + description: z + .string() + .min(1, { message: "descriptionlist item cannot be empty" }), + isMandatory: z.boolean().default(false), + id: z.any().default(null), + }) + ) + .optional(), +}); + +const ManageActivity = ({ activity = null, whichGroup = null, close }) => { + const maxDescriptionLength = 255; + const { mutate: createActivity, isPending: isLoading } = useCreateActivity( + () => close?.() + ); + const { mutate: UpdateActivity, isPending: isUpdating } = useUpdateActivity( + () => close?.() + ); + + const { + register, + handleSubmit, + control, + setValue, + clearErrors, + setError, + getValues, + reset, + formState: { errors }, + } = useForm({ + resolver: zodResolver(schema), + defaultValues: { + activityName: "", + unitOfMeasurement: "", + checkList: [], + }, + }); + + const { + fields: checkListItems, + append, + remove, + } = useFieldArray({ + control, + name: "checkList", + }); + + const addChecklistItem = useCallback(() => { + const values = getValues("checkList"); + const lastIndex = checkListItems.length - 1; + + if ( + checkListItems.length > 0 && + (!values?.[lastIndex] || values[lastIndex].description.trim() === "") + ) { + setError(`checkList.${lastIndex}.description`, { + type: "manual", + message: "Please fill this checklist item before adding another.", + }); + return; + } + + clearErrors(`checkList.${lastIndex}.description`); + append({ id: null, description: "", isMandatory: false }); + }, [checkListItems, getValues, append, setError, clearErrors]); + + const removeChecklistItem = useCallback( + (index) => { + remove(index); + }, + [remove] + ); + + const handleChecklistChange = useCallback( + (index, value) => { + setValue(`checkList.${index}`, value); + }, + [setValue] + ); + + const onSubmit = (formData) => { + let payload = { + ...formData, + activityGroupId: whichGroup, + }; + if (activity) { + UpdateActivity({ id: activity.id, payload: payload }); + } else { + createActivity(payload); + } + }; + + useEffect(() => { + if (activity) { + reset({ + activityName: activity.activityName || "", + unitOfMeasurement: activity.unitOfMeasurement || "", + checkList: activity.checkLists?.map((check) => ({ + id: check.id || null, // Use the ID provided in the checklist + description: check.description || "", + isMandatory: check.isMandatory || false, + })) || [{ description: "", isMandatory: false }], // Default to an empty checklist item + }); + } + }, [activity, reset]); + const handleClose = useCallback(() => { + reset(); + close(); + }, [reset, close]); + + useEffect(() => { + const tooltipTriggerList = Array.from( + document.querySelectorAll('[data-bs-toggle="tooltip"]') + ); + tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el)); + }, []); + let isPending = isLoading || isUpdating; + return ( + + {/*
Create Activity
*/} +
+
+ + + {errors.activityName && ( +

{errors.activityName.message}

+ )} +
+ +
+ + + {errors.unitOfMeasurement && ( +

{errors.unitOfMeasurement.message}

+ )} +
+ +
+

+ {checkListItems.length > 0 ? "Check List" : "Add Check List"} +

+ {checkListItems.length > 0 && ( + + + + + + + + + + {checkListItems.map((item, index) => ( + + + + + + ))} + +
+ Name + + Is Mandatory + Action
+ + + handleChecklistChange(index, e.target.value) + } + /> + {errors.checkList?.[index]?.description && ( + + {errors.checkList[index]?.description?.message} + + )} + + + + +
+ )} + +
+ +
+ + +
+
+ + ); +}; + +export default ManageActivity; diff --git a/src/components/master/Services/ManageGroup.jsx b/src/components/master/Services/ManageGroup.jsx index 1a31f2f7..56ec49f5 100644 --- a/src/components/master/Services/ManageGroup.jsx +++ b/src/components/master/Services/ManageGroup.jsx @@ -1,24 +1,57 @@ import { useForm } from "react-hook-form"; -import { useCreateActivityGroup } from "../../../hooks/masterHook/useMaster"; +import { + useCreateActivityGroup, + useUpdateActivityGroup, +} from "../../../hooks/masterHook/useMaster"; import { zodResolver } from "@hookform/resolvers/zod"; import { ActivityGroupSchema } from "./ServicesSchema"; import Label from "../../common/Label"; +import { useEffect } from "react"; -const ManageGroup = ({ group = null, close }) => { +const ManageGroup = ({ group = null, whichService = null, close }) => { const { register, handleSubmit, + reset, formState: { errors }, } = useForm({ resolver: zodResolver(ActivityGroupSchema), defaultValues: { name: "", description: "" }, }); - const { mutate: createGroup, isPending } = useCreateActivityGroup(); + const { mutate: createGroup, isPending: isCreating } = useCreateActivityGroup( + () => close() + ); + const { mutate: UpdateGroup, isPending: isUpdating } = useUpdateActivityGroup( + () => close() + ); - const onSubmit = (payload) => { - console.log(payload); - // createGroup + useEffect(() => { + if (group) { + reset({ + name: group.name || " ", + description: group.description || "", + }); + } + }, [group, reset]); + const onSubmit = (formdata) => { + if (group) { + let payload = { + ...formdata, + serviceId: whichService, + id: group.id, + }; + UpdateGroup({ id: group.id, payload: payload }); + } else { + let payload = { + ...formdata, + serviceId: whichService, + }; + + createGroup(payload); + } }; + + let isPending = isCreating || isUpdating; return (
@@ -28,9 +61,13 @@ const ManageGroup = ({ group = null, close }) => { - {errors.name &&

{errors.name.message}

} + {errors.name && ( +

{errors.name.message}

+ )}
diff --git a/src/hooks/masterHook/useMaster.js b/src/hooks/masterHook/useMaster.js index 7846fa77..9afee253 100644 --- a/src/hooks/masterHook/useMaster.js +++ b/src/hooks/masterHook/useMaster.js @@ -18,16 +18,20 @@ export const useServices = () => { }; export const useGroups = (serviceId) => { - return useQuery({ - queryFn: ["groups", serviceId], + return useQuery({ + queryKey: ["groups", serviceId], queryFn: async () => await MasterRespository.getActivityGrops(serviceId), - }) + enabled: !!serviceId, + }); }; export const useActivitiesByGroups = (groupId) => { - return useQuery({ - queryFn: ["activties", groupId], - queryFn: async () => await MasterRespository.getActivityGrops(groupId), - }) + return useQuery({ + queryKey: ["activties", groupId], + queryFn: async () => await MasterRespository.getActivitesByGroup(groupId), + + enabled: !!groupId, + + }); }; export const useGlobalServices = () => { return useQuery({ @@ -46,8 +50,6 @@ export const useMasterMenu = () => { }); }; - - export const useActivitiesMaster = () => { const { data: activities = [], @@ -446,47 +448,7 @@ export const useUpdateApplicationRole = (onSuccessCallback) => { }); }; -// 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) => { @@ -785,20 +747,23 @@ export const useUpdateService = (onSuccessCallback) => { }); }; -export const useCreateActivityGroup =()=>{ - const queryClient = useQueryClient(); +export const useCreateActivityGroup = (onSuccessCallback) => { + const queryClient = useQueryClient(); return useMutation({ mutationFn: async (payload) => { - const response = await MasterRespository.createActivityGroup(payload) + const response = await MasterRespository.createActivityGroup(payload); return response; }, onSuccess: (data, variables) => { queryClient.invalidateQueries({ - queryKey: ["masterData", "Services"], + queryKey: ["groups"], }); - showToast(data.message || "Activity Group created successfully.", "success"); + showToast( + data.message || "Activity Group created successfully.", + "success" + ); if (onSuccessCallback) onSuccessCallback(data); }, @@ -806,7 +771,73 @@ export const useCreateActivityGroup =()=>{ showToast(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 || "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.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.message || "Something went wrong", "error"); + }, + }); +}; // -------------------Expense Status---------------------------------- export const useCreateExpenseStatus = (onSuccessCallback) => { @@ -969,3 +1000,48 @@ export const useDeleteMasterItem = () => { }, }); }; + + +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/pages/master/MasterPage.jsx b/src/pages/master/MasterPage.jsx index b187efd1..323e3442 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,18 @@ const MasterPage = () => { ); }; + + const handleDeleteServiceItem =()=>{ + if(!isDeleletingServiceItem.ItemId) return + if(isDeleletingServiceItem.whichItem == "activiy"){ + 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 +114,7 @@ const MasterPage = () => { ); return ( - <> + {modalConfig && ( { /> )} + { onSubmit={handleDeleteSubmit} onClose={() => setDeleteData(null)} /> + + setDeleletingServiceItem({isOpen:false,ItemId:null,whichItem:null})} + />
{
- + ); }; diff --git a/src/repositories/MastersRepository.jsx b/src/repositories/MastersRepository.jsx index 00778dc1..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,15 +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"), - getActivityGrops:(serviceId)=>api.get(`/api/Master/activity-group/list?serviceId=${serviceId}`), - createActivityGroup:(data)=>api.post(`/api/Master/activity-group/create`), - getActivitesByGroup: (serviceId) => api.get(`api/master/activities/activityGroupId=${activityGroupId}`), + 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"), }; From 4e315aafcf4b5710f937862dc73b1cc0443899a4 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Wed, 24 Sep 2025 12:49:00 +0530 Subject: [PATCH 070/120] added small ui changed like table b-padding, button size and fixd delete activity and group --- src/components/Organization/AssignOrg.jsx | 2 +- .../Organization/OrgPickerFromSPId.jsx | 15 +++-- .../Organization/OrgPickerfromTenant.jsx | 57 ++++++++++++------- .../Organization/OrganizationModal.jsx | 2 +- .../Organization/OrganizationsList.jsx | 2 +- .../ProjectAssignedOrgs.jsx | 18 ++++-- .../Project/ProjectOrganizations.jsx | 4 +- .../master/Services/ManageActivity.jsx | 51 ++++++++--------- .../master/Services/ManageGroup.jsx | 2 +- .../master/Services/ServicesGroups.jsx | 12 ++-- src/hooks/masterHook/useMaster.js | 19 ++++--- src/pages/Organization/OrganizationPage.jsx | 2 +- src/pages/master/MasterPage.jsx | 3 +- 13 files changed, 106 insertions(+), 83 deletions(-) diff --git a/src/components/Organization/AssignOrg.jsx b/src/components/Organization/AssignOrg.jsx index 270fac0d..c460de9b 100644 --- a/src/components/Organization/AssignOrg.jsx +++ b/src/components/Organization/AssignOrg.jsx @@ -248,7 +248,7 @@ const AssignOrg = ({ setStep }) => { ? "Please wait..." : flowType === "default" ? "Assign Organization" - : "Add"} + : "Assign Project"}
diff --git a/src/components/Organization/OrgPickerFromSPId.jsx b/src/components/Organization/OrgPickerFromSPId.jsx index e0bdced0..0a46778c 100644 --- a/src/components/Organization/OrgPickerFromSPId.jsx +++ b/src/components/Organization/OrgPickerFromSPId.jsx @@ -84,10 +84,7 @@ const OrgPickerFromSPId = ({ title, placeholder }) => { height={50} /> -
onOpen({ startStep: 3, orgData: org })} - > +
{org.name}
{ Address:
{org.address}
+
+ {" "} + +
))} diff --git a/src/components/Organization/OrgPickerfromTenant.jsx b/src/components/Organization/OrgPickerfromTenant.jsx index 5471df2b..7d39fa60 100644 --- a/src/components/Organization/OrgPickerfromTenant.jsx +++ b/src/components/Organization/OrgPickerfromTenant.jsx @@ -85,7 +85,7 @@ const OrgPickerfromTenant = ({ title }) => { {isLoading ? (
Loading....
) : data && data?.data?.length > 0 ? ( -
+
@@ -99,28 +99,41 @@ const OrgPickerfromTenant = ({ title }) => { - - {Array.isArray(data.data) && data.data.length > 0 - ? data.data.map((row, i) => ( - - {contactList.map((col) => ( - - ))} - - - )) - : null} -
- {col.getValue(row)} - - -
+ +
+ + + {Array.isArray(data.data) && data.data.length > 0 + ? data.data.map((row, i) => ( + + {contactList.map((col) => ( + + ))} + + + )) + : null} + +
+ {col.getValue(row)} + +
+ +
+
+
) : null}
diff --git a/src/components/Organization/OrganizationModal.jsx b/src/components/Organization/OrganizationModal.jsx index da742427..7361c41f 100644 --- a/src/components/Organization/OrganizationModal.jsx +++ b/src/components/Organization/OrganizationModal.jsx @@ -60,7 +60,7 @@ const OrganizationModal = () => { if (startStep === 1) { return orgData && orgData !== null ? "Add Organization" - : "Choose Organization1"; + : "Choose Organization"; } if (startStep === 2) { diff --git a/src/components/Organization/OrganizationsList.jsx b/src/components/Organization/OrganizationsList.jsx index d5243e87..b7ea28e5 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 ( -
+
diff --git a/src/components/Project/ProjectOrganization/ProjectAssignedOrgs.jsx b/src/components/Project/ProjectOrganization/ProjectAssignedOrgs.jsx index 4625a30b..53a2365a 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) => ( +
+ N/A +
+ ), + align: "text-start", }, { key: "sprid", @@ -61,7 +71,7 @@ const ProjectAssignedOrgs = () => {
- {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 = () => { ) : ( {Array.isArray(data.data) && data.data.length > 0 ? data.data.map((row, i) => ( - - {contactList.map((col) => ( - - ))} - + {contactList.map((col) => ( + - - )) + ))} + + + )) : null}
{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 ( -
+
+ ):(

Not Yet Added

)} +
-
diff --git a/src/components/master/Services/ManageGroup.jsx b/src/components/master/Services/ManageGroup.jsx index 56ec49f5..04342777 100644 --- a/src/components/master/Services/ManageGroup.jsx +++ b/src/components/master/Services/ManageGroup.jsx @@ -53,7 +53,7 @@ const ManageGroup = ({ group = null, whichService = null, close }) => { let isPending = isCreating || isUpdating; return ( - +
- {col.getValue(row)} - -
- -
+
+ {col.getValue(row)}
+
+ + onOpen({ startStep: 3, orgData: row }) + } + > + + +
+
From 57edd92dce40be645d14e8e32625fc94ae9e8ca3 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Thu, 25 Sep 2025 12:36:55 +0530 Subject: [PATCH 078/120] fixed blocking of dailyprogress by using pagination --- .../TaskReportFilterPanel.jsx | 11 + .../DailyProgressRport/TaskReportList.jsx | 292 ++++++++++++++++++ .../TaskRepprtListSkeleton.jsx | 62 ++++ src/components/common/HoverPopup.jsx | 65 ++++ src/hooks/useTasks.js | 23 +- .../DailyProgrssReport.jsx | 114 +++++++ .../authentication/TenantSelectionPage.jsx | 2 +- src/repositories/AuthRepository.jsx | 2 +- src/repositories/TaskRepository.jsx | 24 +- src/router/AppRoutes.jsx | 3 +- 10 files changed, 569 insertions(+), 29 deletions(-) create mode 100644 src/components/DailyProgressRport/TaskReportFilterPanel.jsx create mode 100644 src/components/DailyProgressRport/TaskReportList.jsx create mode 100644 src/components/DailyProgressRport/TaskRepprtListSkeleton.jsx create mode 100644 src/components/common/HoverPopup.jsx create mode 100644 src/pages/DailyProgressReport/DailyProgrssReport.jsx diff --git a/src/components/DailyProgressRport/TaskReportFilterPanel.jsx b/src/components/DailyProgressRport/TaskReportFilterPanel.jsx new file mode 100644 index 00000000..0216a7e7 --- /dev/null +++ b/src/components/DailyProgressRport/TaskReportFilterPanel.jsx @@ -0,0 +1,11 @@ +import React from 'react' + +const TaskReportFilterPanel = () => { + return ( +
+ +
+ ) +} + +export default TaskReportFilterPanel diff --git a/src/components/DailyProgressRport/TaskReportList.jsx b/src/components/DailyProgressRport/TaskReportList.jsx new file mode 100644 index 00000000..254b1ddf --- /dev/null +++ b/src/components/DailyProgressRport/TaskReportList.jsx @@ -0,0 +1,292 @@ +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 } = useDailyProgrssContext(); + const selectedProject = useSelectedProject(); + const { projectNames } = useProjectName(); + + const { data, isLoading, isError, error } = useTaskList( + selectedProject, + ITEMS_PER_PAGE, + currentPage, + service + ); + +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) => ( + + + + + + + + + ))} + + ))} + +
ActivityTotal Pending + + 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/common/HoverPopup.jsx b/src/components/common/HoverPopup.jsx new file mode 100644 index 00000000..ddf0a062 --- /dev/null +++ b/src/components/common/HoverPopup.jsx @@ -0,0 +1,65 @@ +import React, { useEffect, useRef, useState } from "react"; + +const HoverPopup = ({ title, content, children }) => { + const [visible, setVisible] = useState(false); + const triggerRef = useRef(null); + const popupRef = useRef(null); + + // Toggle on hover or click + const handleMouseEnter = () => setVisible(true); + const handleClick = () => setVisible((prev) => !prev); + + // Hide on outside click + useEffect(() => { + const handleDocumentClick = (e) => { + if ( + !popupRef.current?.contains(e.target) && + !triggerRef.current?.contains(e.target) + ) { + setVisible(false); + } + }; + + if (visible) { + document.addEventListener("click", handleDocumentClick); + } + + return () => { + document.removeEventListener("click", handleDocumentClick); + }; + }, [visible]); + + return ( +
+ {children} + + {visible && ( +
+ {title &&
{title}
} +
{content}
+
+ )} +
+ ); +}; + +export default HoverPopup; + 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/DailyProgressReport/DailyProgrssReport.jsx b/src/pages/DailyProgressReport/DailyProgrssReport.jsx new file mode 100644 index 00000000..72b360b4 --- /dev/null +++ b/src/pages/DailyProgressReport/DailyProgrssReport.jsx @@ -0,0 +1,114 @@ +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 { 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 contextObj = { + service, + openModal, + closeModal, + }; + + 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/authentication/TenantSelectionPage.jsx b/src/pages/authentication/TenantSelectionPage.jsx index 349c054c..22b72292 100644 --- a/src/pages/authentication/TenantSelectionPage.jsx +++ b/src/pages/authentication/TenantSelectionPage.jsx @@ -23,7 +23,7 @@ const TenantSelectionPage = () => { useEffect(() => { if (localStorage.getItem("ctnt")) { - navigate("/dashboard"); + chooseTenant(localStorage.getItem("ctnt")) } }, [navigate]); 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/TaskRepository.jsx b/src/repositories/TaskRepository.jsx index 40ad8750..9afc47a0 100644 --- a/src/repositories/TaskRepository.jsx +++ b/src/repositories/TaskRepository.jsx @@ -1,25 +1,25 @@ 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}`; } 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 70052cad..9c30e768 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -52,6 +52,7 @@ 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"; const router = createBrowserRouter( [ { @@ -89,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: }, From 92b1531b75ed383d762d88e0b3ea141403e567dc Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Thu, 25 Sep 2025 14:11:04 +0530 Subject: [PATCH 079/120] daily task planning filtering according to service --- .../Project/Infrastructure/Floor.jsx | 3 +- .../Project/Infrastructure/InfraTable.jsx | 3 +- .../Project/Infrastructure/WorkArea.jsx | 13 +- src/components/Project/ProjectInfra.jsx | 134 +++++--- src/hooks/useProjects.js | 10 + src/pages/Activities/DailyTask.jsx | 289 ------------------ src/pages/Activities/TaskPlannng.jsx | 90 +++--- .../DailyProgrssReport.jsx | 4 +- src/router/AppRoutes.jsx | 1 - src/slices/globalVariablesSlice.jsx | 6 +- 10 files changed, 151 insertions(+), 402 deletions(-) delete mode 100644 src/pages/Activities/DailyTask.jsx diff --git a/src/components/Project/Infrastructure/Floor.jsx b/src/components/Project/Infrastructure/Floor.jsx index 0f7f00f7..2ee5d84b 100644 --- a/src/components/Project/Infrastructure/Floor.jsx +++ b/src/components/Project/Infrastructure/Floor.jsx @@ -1,6 +1,6 @@ import React from "react"; import WorkArea from "./WorkArea"; -const Floor = ({ floor, workAreas, forBuilding,serviceId }) => { +const Floor = ({ floor, workAreas, forBuilding }) => { return ( {workAreas && workAreas.length > 0 ? ( @@ -10,7 +10,6 @@ const Floor = ({ floor, workAreas, forBuilding,serviceId }) => { key={workArea.id} workArea={workArea} floor={floor} - serviceId={serviceId} /> )) ) : ( diff --git a/src/components/Project/Infrastructure/InfraTable.jsx b/src/components/Project/Infrastructure/InfraTable.jsx index 3805857f..cb6b6cb0 100644 --- a/src/components/Project/Infrastructure/InfraTable.jsx +++ b/src/components/Project/Infrastructure/InfraTable.jsx @@ -11,7 +11,7 @@ import { getCachedData, } from "../../../slices/apiDataManager"; -const InfraTable = ({ buildings, projectId, serviceId }) => { +const InfraTable = ({ buildings, projectId }) => { const [projectBuilding, setProjectBuilding] = useState([]); const [expandedBuildings, setExpandedBuildings] = useState([]); const [showFloorModal, setShowFloorModal] = useState(false); @@ -90,7 +90,6 @@ const InfraTable = ({ buildings, projectId, serviceId }) => { forBuilding={building} floor={floor} workAreas={floor.workAreas} - serviceId={serviceId} /> )) ) : ( diff --git a/src/components/Project/Infrastructure/WorkArea.jsx b/src/components/Project/Infrastructure/WorkArea.jsx index b6fc3e35..c38af26d 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"; @@ -15,9 +15,11 @@ import { import { useParams } from "react-router-dom"; import ProgressBar from "../../common/ProgressBar"; 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, diff --git a/src/components/Project/ProjectInfra.jsx b/src/components/Project/ProjectInfra.jsx index cd7fafbc..29a0b283 100644 --- a/src/components/Project/ProjectInfra.jsx +++ b/src/components/Project/ProjectInfra.jsx @@ -17,33 +17,37 @@ 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); 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 { data: assignedServices, isLoading: servicesLoading } = + useProjectAssignedServices(projectId); - const [selectedService, setSelectedService] = useState(""); - const handleServiceChange = (e) => { - setSelectedService(e.target.value); - }; useEffect(() => { setProject(projectInfra); @@ -51,34 +55,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 +116,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 ? ( - ) : ( -
{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..e3e74f9c 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 index 72b360b4..79147d5a 100644 --- a/src/pages/DailyProgressReport/DailyProgrssReport.jsx +++ b/src/pages/DailyProgressReport/DailyProgrssReport.jsx @@ -83,7 +83,7 @@ const DailyProgrssReport = () => {
{
)} + {/* Activity Group (Organization) Selection */} + {selectedService && ( +
+ + +
+ )} + {/* Activity Selection */} - {selectedWorkArea && ( + {selectedOrg && (
+ {servicesLoading && } {assignedServices?.map((service) => ( ))} - {errors.buildingID && ( -

{errors.buildingID.message}

- )}
)} @@ -225,43 +239,36 @@ const TaskModel = ({ project, onSubmit, onClose }) => { + {errors.activityGroupId &&

{errors.activityGroupId.message}

}
)} + {/* Activity Selection */} - {selectedOrg && ( + {selectedGroup && (
- - {activityData.map((a) => ( - + {activitiesLoading && } + {activities?.map((a) => ( + ))} - {errors.activityID && ( -

{errors.activityID.message}

- )} + {errors.activityID &&

{errors.activityID.message}

}
)} - - {selectedWorkArea && (
From d2b10495bd45fd4904cfa40bac19952bb6172179 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Thu, 25 Sep 2025 11:19:36 +0530 Subject: [PATCH 082/120] Added Services Column in Edit activity modal --- .../Infrastructure/EditActivityModal.jsx | 79 +++++++++++-------- .../Project/Infrastructure/TaskModel.jsx | 9 ++- 2 files changed, 52 insertions(+), 36 deletions(-) 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 && } From 1cd3bf6c7f6bdec25eb5ae407899f394fde4bbfc Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Thu, 25 Sep 2025 14:29:02 +0530 Subject: [PATCH 083/120] updated assigned services poject whenver assigned new service --- src/hooks/useOrganization.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/hooks/useOrganization.js b/src/hooks/useOrganization.js index 2d77438b..dea19874 100644 --- a/src/hooks/useOrganization.js +++ b/src/hooks/useOrganization.js @@ -103,8 +103,12 @@ 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(); From 1d218056ac802515c1d830e4664a011f1c584284 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 24 Sep 2025 15:19:39 +0530 Subject: [PATCH 084/120] Incorrect button sequence in Tenant Edit form --- src/components/Tenant/EditProfile.jsx | 30 +++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) 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.name && ( +

{errors.name.message}

+ )} +
+
+ + - {errors.description && ( -

{errors.description.message}

- )} -
+ {errors.description && ( +

{errors.description.message}

+ )} +
-
- - +
+ + +
); From 2dbf08e3307566f1bf73d7e129154092663da19b Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 26 Sep 2025 15:02:03 +0530 Subject: [PATCH 097/120] Removing create and Edit activity component. --- src/components/master/CreateActivity.jsx | 235 ----------------------- src/components/master/EditActivity.jsx | 231 ---------------------- 2 files changed, 466 deletions(-) delete mode 100644 src/components/master/CreateActivity.jsx delete mode 100644 src/components/master/EditActivity.jsx diff --git a/src/components/master/CreateActivity.jsx b/src/components/master/CreateActivity.jsx deleted file mode 100644 index 1db2bb79..00000000 --- a/src/components/master/CreateActivity.jsx +++ /dev/null @@ -1,235 +0,0 @@ -import React, { useState, useEffect, useCallback } from "react"; -import { useFieldArray, useForm } from "react-hook-form"; -import { z } from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; - -import { MasterRespository } from "../../repositories/MastersRepository"; -import { clearApiCacheKey } from "../../slices/apiCacheSlice"; -import { getCachedData, cacheData } from "../../slices/apiDataManager"; -import showToast from "../../services/toastService"; -import { useCreateActivity } from "../../hooks/masterHook/useMaster"; -import Label from "../common/Label"; - -const schema = z.object({ - activityName: z.string().min(1, { message: "Activity Name is required" }), - unitOfMeasurement: z.string().min(1, { message: "Unit of Measurement is required" }), - checkList: z - .array( - z.object({ - description: z.string().min(1, { message: "descriptionlist item cannot be empty" }), - isMandatory: z.boolean().default(false), - id: z.any().default(null), - }) - ) - .optional(), -}); - -const CreateActivity = ({ activity = null, whichGroup = null, close }) => { - const maxDescriptionLength = 255; - const { mutate: createActivity, isPending: isLoading } = useCreateActivity(() => onClose?.()); - - const { - register, - handleSubmit, - control, - setValue, - clearErrors, - setError, - getValues, - reset, - formState: { errors }, - } = useForm({ - resolver: zodResolver(schema), - defaultValues: { - activityName: "", - unitOfMeasurement: "", - checkList: [], - }, - }); - - const { - fields: checkListItems, - append, - remove, - } = useFieldArray({ - control, - name: "checkList", - }); - - const addChecklistItem = useCallback(() => { - const values = getValues("checkList"); - const lastIndex = checkListItems.length - 1; - - if ( - checkListItems.length > 0 && - (!values?.[lastIndex] || values[lastIndex].description.trim() === "") - ) { - setError(`checkList.${lastIndex}.description`, { - type: "manual", - message: "Please fill this checklist item before adding another.", - }); - return; - } - - clearErrors(`checkList.${lastIndex}.description`); - append({ id: null, description: "", isMandatory: false }); - }, [checkListItems, getValues, append, setError, clearErrors]); - - const removeChecklistItem = useCallback((index) => { - remove(index); - }, [remove]); - - const handleChecklistChange = useCallback((index, value) => { - setValue(`checkList.${index}`, value); - }, [setValue]); - - const onSubmit = (formData) => { - createActivity(formData); - }; - - useEffect(()=>{ - if (activity) { - reset({ - activityName: activity.activityName || '', - unitOfMeasurement: activity.unitOfMeasurement || '', - checkList: activity.checkList?.map((check) => ({ - description: check.description || '', - isMandatory: check.isMandatory || false, - })) || [{ description: '', isMandatory: false }], - }); - } - },[activity,reset]) - const handleClose = useCallback(() => { - reset(); - onClose(); - }, [reset, onClose]); - - useEffect(() => { - const tooltipTriggerList = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]')); - tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el)); - }, []); - - return ( -
- {/*
Create Activity
*/} -
-
- - - {errors.activityName && ( -

{errors.activityName.message}

- )} -
- -
- - - {errors.unitOfMeasurement && ( -

{errors.unitOfMeasurement.message}

- )} -
- -
-

{checkListItems.length > 0 ? "Check List" : "Add Check List"}

- {checkListItems.length > 0 && ( - - - - - - - - - - {checkListItems.map((item, index) => ( - - - - - - ))} - -
- Name - - Is Mandatory - Action
- - - handleChecklistChange(index, e.target.value) - } - /> - {errors.checkList?.[index]?.description && ( - - {errors.checkList[index]?.description?.message} - - )} - - - - -
- )} - -
- -
- - - -
-
-
- ); -}; - -export default CreateActivity; diff --git a/src/components/master/EditActivity.jsx b/src/components/master/EditActivity.jsx deleted file mode 100644 index 15e85255..00000000 --- a/src/components/master/EditActivity.jsx +++ /dev/null @@ -1,231 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { useForm, useFieldArray } from "react-hook-form"; -import { z } from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { MasterRespository } from "../../repositories/MastersRepository"; -import showToast from "../../services/toastService"; -import { getCachedData, cacheData } from "../../slices/apiDataManager"; -import { useUpdateActivity } from "../../hooks/masterHook/useMaster"; -import Label from "../common/Label"; - - -const schema = z.object({ - activityName: z.string().min(1, { message: "Activity name is required" }), - unitOfMeasurement: z.string().min(1, { message: "Measurement is required" }), - checkList: z - .array( - z.object({ - id: z.any().default(null), - description: z.string().min(1, { message: "Checklist item cannot be empty" }), - isMandatory: z.boolean().default(false), - }) - ) - .optional(), -}); - - -const UpdateActivity = ({ activity = null, whichService = null, close }) => { - const { mutate: updateActivity, isPending: isLoading } = useUpdateActivity(() => onClose?.()); - - const { - register, - handleSubmit, - control, - setValue, - reset, - setError, - clearErrors, - getValues, - formState: { errors }, - } = useForm({ - resolver: zodResolver(schema), - defaultValues: { - id: activity?.id, - activityName: activity?.activityName, - unitOfMeasurement: activity?.unitOfMeasurement, - checkList: activity?.checkLists || [], - }, - }); - - const { fields: checkListItems, append, remove } = useFieldArray({ - control, - name: "checkList", - }); - - useEffect(() => { - if (activity) { - reset({ - id: activity.id, - activityName: activity.activityName, - unitOfMeasurement: activity.unitOfMeasurement, - checkList: activity.checkLists || [], - }); - } - }, [activity, reset]); - - const addChecklistItem = () => { - const values = getValues("checkList"); - const lastIndex = checkListItems.length - 1; - - if ( - checkListItems.length > 0 && - (!values?.[lastIndex] || values[lastIndex].description.trim() === "") - ) { - setError(`checkList.${lastIndex}.description`, { - type: "manual", - message: "Please fill this checklist item before adding another.", - }); - return; - } - - clearErrors(`checkList.${lastIndex}.description`); - append({ id: null, description: "", isMandatory: false }); - }; - - const removeChecklistItem = (index) => { - remove(index); - }; - - const handleChecklistChange = (index, value) => { - setValue(`checkList.${index}`, value); - }; - - const onSubmit = (formData) => { - const payload = { ...formData, id: activity.id }; - updateActivity({ id: activity.id, payload }); - }; - - - useEffect(() => { - const tooltipTriggerList = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]')); - tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el)); - }, []); - - return ( -
-
- {/* Activity Name */} -
- - - {errors.activityName && ( -
{errors.activityName.message}
- )} -
- - {/* Unit of Measurement */} -
- - - {errors.unitOfMeasurement && ( -
{errors.unitOfMeasurement.message}
- )} -
- - {/* Checklist */} -
-

{checkListItems.length > 0 ? "Check List" : "Add Check List"}

- {checkListItems.length > 0 && ( - - - - - - - - - - {checkListItems.map((item, index) => ( - - - - - - ))} - -
- Name - - Is Mandatory - Action
- - - handleChecklistChange(index, e.target.value) - } - /> - {errors.checkList?.[index]?.description && ( - - {errors.checkList[index]?.description?.message} - - )} - - - - -
- )} - - - -
- - {/* Submit / Cancel */} -
- - - -
-
-
- ); -}; - -export default UpdateActivity; \ No newline at end of file From 1a3890e8372f7266c196e807c34bc96edd7954c9 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 26 Sep 2025 15:34:08 +0530 Subject: [PATCH 098/120] Removing Create and Activity from MasterModal.jsx file. --- src/components/master/MasterModal.jsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/master/MasterModal.jsx b/src/components/master/MasterModal.jsx index 2c71ea71..38e1d591 100644 --- a/src/components/master/MasterModal.jsx +++ b/src/components/master/MasterModal.jsx @@ -3,8 +3,6 @@ import CreateRole from "./CreateRole"; import EditRole from "./EditRole"; import CreateJobRole from "./CreateJobRole"; import EditJobRole from "./EditJobRole"; -import CreateActivity from "./CreateActivity"; -import EditActivity from "./EditActivity"; import CreateWorkCategory from "./CreateWorkCategory"; import EditWorkCategory from "./EditWorkCategory"; import CreateCategory from "./CreateContactCategory"; @@ -35,9 +33,7 @@ const MasterModal = ({ modaldata, closeModal }) => { ), "Job Role": , - "Edit-Job Role": , - "Activity": , - "Edit-Activity": , + "Edit-Job Role": , "Work Category": , "Edit-Work Category": , "Contact Category": , From 1fb8eb9ef15a5c44ae7a9bc586f951ffe4103cb0 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 26 Sep 2025 16:21:37 +0530 Subject: [PATCH 099/120] Create Task popup will open at all the time while submitting. --- src/components/Project/Infrastructure/TaskModel.jsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/Project/Infrastructure/TaskModel.jsx b/src/components/Project/Infrastructure/TaskModel.jsx index efa2006f..4bbf1555 100644 --- a/src/components/Project/Infrastructure/TaskModel.jsx +++ b/src/components/Project/Infrastructure/TaskModel.jsx @@ -112,7 +112,7 @@ const TaskModel = ({ project, onSubmit, onClose }) => { const { mutate: CreateTask, isPending } = useManageTask({ onSuccessCallback: (response) => { showToast(response?.message, "success"); - onClose?.(); + // onClose?.(); }, }); useEffect(() => { @@ -356,9 +356,10 @@ const TaskModel = ({ project, onSubmit, onClose }) => {
From 3fddb686d3cafe79db4fa66d996c02cb5a6f6238 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Sat, 27 Sep 2025 14:58:39 +0530 Subject: [PATCH 100/120] change get employee at assign task to employee --- src/components/Project/AssignTask.jsx | 339 ++++++++++++++----------- src/hooks/useProjects.js | 8 + src/repositories/ProjectRepository.jsx | 13 + 3 files changed, 207 insertions(+), 153 deletions(-) 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/hooks/useProjects.js b/src/hooks/useProjects.js index eeac3c31..bdbd01c6 100644 --- a/src/hooks/useProjects.js +++ b/src/hooks/useProjects.js @@ -308,6 +308,14 @@ 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 }) => { diff --git a/src/repositories/ProjectRepository.jsx b/src/repositories/ProjectRepository.jsx index 42dde36a..b1b0727f 100644 --- a/src/repositories/ProjectRepository.jsx +++ b/src/repositories/ProjectRepository.jsx @@ -82,6 +82,19 @@ const ProjectRepository = { 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 = { From 3233043cf21bee04eeadc63fedfbadd65790570a Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Fri, 26 Sep 2025 11:38:06 +0530 Subject: [PATCH 101/120] initial setup for assign emp to project --- .../Project/Team/TeamAssignToProject.jsx | 42 ++++++++ .../Project/Team/TeamEmployeeList.jsx | 12 +++ src/components/Project/{ => Team}/Teams.jsx | 98 ++++++++++++------- src/hooks/useProjectAccess.js | 15 +-- src/pages/project/ProjectDetails.jsx | 2 +- 5 files changed, 124 insertions(+), 45 deletions(-) create mode 100644 src/components/Project/Team/TeamAssignToProject.jsx create mode 100644 src/components/Project/Team/TeamEmployeeList.jsx rename src/components/Project/{ => Team}/Teams.jsx (85%) diff --git a/src/components/Project/Team/TeamAssignToProject.jsx b/src/components/Project/Team/TeamAssignToProject.jsx new file mode 100644 index 00000000..102b0be7 --- /dev/null +++ b/src/components/Project/Team/TeamAssignToProject.jsx @@ -0,0 +1,42 @@ +import React from "react"; + +const TeamAssignToProject = () => { + return ( +
+

Assign Employee To Project

+
+
+ + +
+ +
+ + +
+
+
+ + +
+ +
+ +
+
+ ); +}; + +export default TeamAssignToProject; diff --git a/src/components/Project/Team/TeamEmployeeList.jsx b/src/components/Project/Team/TeamEmployeeList.jsx new file mode 100644 index 00000000..da62ac91 --- /dev/null +++ b/src/components/Project/Team/TeamEmployeeList.jsx @@ -0,0 +1,12 @@ +import React from 'react' + +const TeamEmployeeList = ({serviceId,organizationId}) => { + const {} = use + return ( +
+ +
+ ) +} + +export default TeamEmployeeList diff --git a/src/components/Project/Teams.jsx b/src/components/Project/Team/Teams.jsx similarity index 85% rename from src/components/Project/Teams.jsx rename to src/components/Project/Team/Teams.jsx index 3e31c0cb..f684347a 100644 --- a/src/components/Project/Teams.jsx +++ b/src/components/Project/Team/Teams.jsx @@ -1,30 +1,32 @@ import React, { useState, useEffect, useCallback } from "react"; -import MapUsers from "./MapUsers"; +import MapUsers from "../MapUsers"; import { Link, NavLink, useNavigate, useParams } from "react-router-dom"; -import showToast from "../../services/toastService"; -import Avatar from "../common/Avatar"; +import showToast from "../../../services/toastService"; +import Avatar from "../../common/Avatar"; import moment from "moment"; -import ProjectRepository from "../../repositories/ProjectRepository"; +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 { 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"; +} from "../../../hooks/useProjects"; +import { useSelectedProject } from "../../../slices/apiDataManager"; +import GlobalModel from "../../common/GlobalModel"; +import TeamAssignToProject from "./TeamAssignToProject"; const Teams = () => { const projectId = useSelectedProject(); const dispatch = useDispatch(); - + const [AssigTeam,setAssignTeam] = useState(false) const { data, loading } = useMaster(); const [isModalOpen, setIsModelOpen] = useState(false); const [error, setError] = useState(""); @@ -38,9 +40,9 @@ const Teams = () => { 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 { data: assignedServices, isLoading: servicesLoading } = + useProjectAssignedServices(projectId); + const handleToggleActive = (e) => setActiveEmployee(e.target.checked); const handleServiceChange = (e) => { setSelectedService(e.target.value); @@ -167,7 +169,8 @@ const Teams = () => { const lowercasedSearchTerm = searchTerm.toLowerCase(); const searchedAndFiltered = statusFiltered.filter((item) => { - const fullName = `${item.firstName} ${item.middleName} ${item.lastName}`.toLowerCase(); + 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(); @@ -183,7 +186,6 @@ const Teams = () => { setFilteredEmployees(searchedAndFiltered); }, [employees, activeEmployee, searchTerm, getRole]); - useEffect(() => { filterAndSearchEmployees(); }, [employees, activeEmployee, searchTerm, filterAndSearchEmployees]); @@ -252,6 +254,13 @@ const Teams = () => { >
+ + {AssigTeam && ( + setAssignTeam(false)}> + + + )} + {IsDeleteModal && ( {
- {!servicesLoading && assignedServices?.length > 0 && ( - assignedServices.length > 1 ? ( + {!servicesLoading && + assignedServices?.length > 0 && + (assignedServices.length > 1 ? (
+ {HasAssignUserPermission && ( + + className={`link-button btn-primary btn-sm `} + // data-bs-toggle="modal" + // data-bs-target="#user-model" + onClick={()=>setAssignTeam(true)} + > + + Assign Employee + + )}
@@ -387,7 +406,9 @@ const Teams = () => { {!activeEmployee && (
{item.reAllocationDate - ? moment(item.reAllocationDate).format("DD-MMM-YYYY") + ? moment(item.reAllocationDate).format( + "DD-MMM-YYYY" + ) : "Present"}
- )} {!employeeLodaing && filteredEmployees.length === 0 && (
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/pages/project/ProjectDetails.jsx b/src/pages/project/ProjectDetails.jsx index 7503f6ce..8d2f8277 100644 --- a/src/pages/project/ProjectDetails.jsx +++ b/src/pages/project/ProjectDetails.jsx @@ -5,7 +5,7 @@ import { useNavigate } from "react-router-dom"; import ProjectOverview from "../../components/Project/ProjectOverview"; import AboutProject from "../../components/Project/AboutProject"; import ProjectNav from "../../components/Project/ProjectNav"; -import Teams from "../../components/Project/Teams"; +import Teams from "../../components/Project/Team/Teams"; import ProjectInfra from "../../components/Project/ProjectInfra"; import Loader from "../../components/common/Loader"; import WorkPlan from "../../components/Project/WorkPlan"; From 2ef1fcfd1db611f9545fe98ed5525aea38778af3 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Sat, 27 Sep 2025 01:15:07 +0530 Subject: [PATCH 102/120] successfullly assigned employe to project --- public/assets/vendor/css/core.css | 4 + .../Project/Team/TeamAssignToProject.jsx | 94 ++-- .../Project/Team/TeamEmployeeList.jsx | 256 ++++++++++- src/components/Project/Team/Teams.jsx | 432 ++++++------------ src/hooks/useOrganization.js | 25 +- src/hooks/useProjects.js | 12 +- src/repositories/OrganizationRespository.jsx | 28 +- src/repositories/ProjectRepository.jsx | 22 +- 8 files changed, 516 insertions(+), 357 deletions(-) diff --git a/public/assets/vendor/css/core.css b/public/assets/vendor/css/core.css index 9be75669..5acc33c5 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/Project/Team/TeamAssignToProject.jsx b/src/components/Project/Team/TeamAssignToProject.jsx index 102b0be7..0bab7756 100644 --- a/src/components/Project/Team/TeamAssignToProject.jsx +++ b/src/components/Project/Team/TeamAssignToProject.jsx @@ -1,40 +1,78 @@ -import React from "react"; +import React, { useState } from "react"; +import TeamEmployeeList from "./TeamEmployeeList"; +import { useOrganization } from "../../../hooks/useDirectory"; +import { useOrganizationsList } from "../../../hooks/useOrganization"; +import { useProjectAssignedOrganizations } from "../../../hooks/useProjects"; +import { useSelectedProject } from "../../../slices/apiDataManager"; -const TeamAssignToProject = () => { +const TeamAssignToProject = ({ closeModal }) => { + const [searchText, setSearchText] = useState(""); + const [selectedOrg, setSelectedOrg] = useState(null); + const project = useSelectedProject(); + const { data, isLoading, isError, error } = + useProjectAssignedOrganizations(project); return (

Assign Employee To Project

-
-
- - -
-
- - +
+
+
+ {isLoading ? ( + + ) : data?.length === 0 ? ( +

No organizations found

+ ) : ( + <> + + + + )} +
+
+
+
+ + setSearchText(e.target.value)} + /> +
-
- - +
- -
- -
); }; diff --git a/src/components/Project/Team/TeamEmployeeList.jsx b/src/components/Project/Team/TeamEmployeeList.jsx index da62ac91..920d1f6f 100644 --- a/src/components/Project/Team/TeamEmployeeList.jsx +++ b/src/components/Project/Team/TeamEmployeeList.jsx @@ -1,12 +1,250 @@ -import React from 'react' +import React, { useState, useEffect } from "react"; +import Avatar from "../../common/Avatar"; +import { useDebounce } from "../../../utils/appUtils"; +import { useSelectedProject } from "../../../slices/apiDataManager"; +import { useOrganizationEmployees } from "../../../hooks/useOrganization"; +import { + useEmployeesByProjectAllocated, + useManageProjectAllocation, +} from "../../../hooks/useProjects"; +import useMaster, { useServices } from "../../../hooks/masterHook/useMaster"; +import showToast from "../../../services/toastService"; -const TeamEmployeeList = ({serviceId,organizationId}) => { - const {} = use - return ( -
- -
- ) +const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => { + const selectedProject = useSelectedProject(); + const debounceSearchTerm = useDebounce(searchTerm, 500); + + const { + data: employeesData = [], + isLoading, + isError, + error, + } = useOrganizationEmployees( + selectedProject, + organizationId, + debounceSearchTerm + ); + + const { projectEmployees, loading: employeeLodaing } = + useEmployeesByProjectAllocated(selectedProject, null); + + const { data: jobRoles } = useMaster(); + const { data: services } = useServices(); + + const [employees, setEmployees] = useState([]); + + const { mutate: handleAssignEmployee, isPending } = + useManageProjectAllocation({ + onSuccessCallback: () => { + closeModal(); + }, + onErrorCallback: () => { + closeModal(); + }, + }); + + useEffect(() => { + if (employeesData?.data?.length > 0) { + const available = employeesData.data.filter((emp) => { + const projEmp = projectEmployees.find((pe) => pe.employeeId === emp.id); + return !projEmp || projEmp.isActive === false; + }); + + setEmployees( + available.map((emp) => ({ + ...emp, + isChecked: false, + jobRole: emp?.jobRoleId || null, + serviceId: "", + errors: {}, + })) + ); + } + }, [employeesData, projectEmployees, organizationId]); + + const handleCheckboxChange = (index) => { + setEmployees((prev) => { + const newArr = [...prev]; + newArr[index].isChecked = !newArr[index].isChecked; + newArr[index].errors = {}; + return newArr; + }); + }; + + const handleSelectChange = (index, field, value) => { + setEmployees((prev) => { + const newArr = [...prev]; + newArr[index][field] = value; + newArr[index].errors[field] = ""; + return newArr; + }); + }; + + const onSubmit = () => { + const checkedEmployees = employees.filter((emp) => emp.isChecked); + + setEmployees((prev) => prev.map((emp) => ({ ...emp, errors: {} }))); + + if (checkedEmployees.length === 0) { + showToast("Select at least one employee", "info"); + return; + } + + let hasError = false; + const newEmployees = employees.map((emp) => { + const empErrors = {}; + if (emp.isChecked) { + if (!emp.jobRole) { + empErrors.jobRole = "Job role is required"; + hasError = true; + } + if (!emp.serviceId) { + empErrors.serviceId = "Service is required"; + hasError = true; + } + } + return { ...emp, errors: empErrors }; + }); + + setEmployees(newEmployees); + + if (hasError) return; // stop submit if validation fails + + const payload = checkedEmployees.map((emp) => ({ + employeeId: emp.id, + jobRoleId: emp.jobRole, + serviceId: emp.serviceId, + projectId: selectedProject, + status: true, + })); + + handleAssignEmployee({ payload }); + + setEmployees((prev) => + prev.map((emp) => ({ + ...emp, + isChecked: false, + jobRole: "", + serviceId: "", + errors: {}, + })) + ); + }; + +if (isLoading) { + return (

Loading employees...

) ; } -export default TeamEmployeeList +if (isError) { + return ( +
+ {error?.status === 400 ? ( +

Enter employee you want to find.

+ ) : ( +

Something went wrong. Please try again later.

+ )} +
+ + ); +} + +if (employees.length === 0) { + return(

No available employees to assign.

) ; +} + + + return ( +
+ + + + + + + + + + + {employees.map((emp, index) => ( + + + + + + + ))} + +
EmployeeServiceJob RoleSelect
+
+ + + {emp.firstName} {emp.lastName} + +
+
+ + {emp.errors.serviceId && ( +
{emp.errors.serviceId}
+ )} +
+ + {emp.errors.jobRole && ( +
{emp.errors.jobRole}
+ )} +
+ handleCheckboxChange(index)} + /> +
+
+ + +
+
+ ); +}; + +export default TeamEmployeeList; diff --git a/src/components/Project/Team/Teams.jsx b/src/components/Project/Team/Teams.jsx index f684347a..0d5d501d 100644 --- a/src/components/Project/Team/Teams.jsx +++ b/src/components/Project/Team/Teams.jsx @@ -24,24 +24,20 @@ import GlobalModel from "../../common/GlobalModel"; import TeamAssignToProject from "./TeamAssignToProject"; const Teams = () => { - const projectId = useSelectedProject(); + const selectedProject = useSelectedProject(); const dispatch = useDispatch(); - const [AssigTeam,setAssignTeam] = useState(false) - const { data, loading } = useMaster(); - const [isModalOpen, setIsModelOpen] = useState(false); - const [error, setError] = useState(""); - const [empJobRoles, setEmpJobRoles] = useState(null); + const [AssigTeam, setAssignTeam] = useState(false); const [employees, setEmployees] = useState([]); const [filteredEmployees, setFilteredEmployees] = useState([]); - const [removingEmployeeId, setRemovingEmployeeId] = useState(null); - const [assignedLoading, setAssignedLoading] = useState(false); - const [activeEmployee, setActiveEmployee] = useState(true); + 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(projectId); + useProjectAssignedServices(selectedProject); + const {data:empJobRoles,loading} = useMaster() const handleToggleActive = (e) => setActiveEmployee(e.target.checked); const handleServiceChange = (e) => { @@ -56,7 +52,7 @@ const Teams = () => { projectEmployees, loading: employeeLodaing, refetch, - } = useEmployeesByProjectAllocated(projectId, selectedService); + } = useEmployeesByProjectAllocated(selectedProject, selectedService,null,activeEmployee); const { mutate: submitAllocations, isPending, @@ -65,7 +61,6 @@ const Teams = () => { } = useManageProjectAllocation({ onSuccessCallback: () => { setRemovingEmployeeId(null); - setAssignedLoading(false); setDeleteEmplyee(null); closeDeleteModal(); }, @@ -74,44 +69,20 @@ const Teams = () => { }, }); - const removeAllocation = (item) => { - setRemovingEmployeeId(item.id); + const handleDelete = (employee) => { + let payload = [ + { + employeeId: employee.employeeId, + jobRoleId: employee.jobRoleId, + projectId: selectedProject, + serviceId: selectedService, + status: false, + }, + ]; - submitAllocations({ - items: [ - { - empID: item.employeeId, - jobRoleId: item.jobRoleId, - projectId: projectId, - status: false, - }, - ], - added: false, - }); + submitAllocations({payload:payload}); }; - - 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) => { + const getJobRole = (jobRoleId) => { if (loading) return "Loading..."; if (!Array.isArray(empJobRoles)) return "Unassigned"; if (!jobRoleId) return "Unassigned"; @@ -119,165 +90,47 @@ const Teams = () => { const role = empJobRoles.find((b) => b.id == jobRoleId); return role ? role.name : "Unassigned"; }; - const openModel = () => { - setIsModelOpen(true); - }; + // const employeeHandler = useCallback( + // (msg) => { + // if (filteredEmployees.some((emp) => emp.employeeId == msg.employeeId)) { + // refetch(); + // } + // }, + // [filteredEmployees, refetch] + // ); - 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]); + // useEffect(() => { + // eventBus.on("employee", employeeHandler); + // return () => eventBus.off("employee", employeeHandler); + // }, [employeeHandler]); return ( <> - - - {AssigTeam && ( - setAssignTeam(false)}> - + setAssignTeam(false)} + > + setAssignTeam(false)} /> )} - {IsDeleteModal && ( - removeAllocation(deleteEmployee)} - onClose={closeDeleteModal} - loading={isPending} - /> - )} + handleDelete(selectedEmployee)} + onClose={() => setSelectedEmployee(null)} + />
-
-
+
+ {/*
{!servicesLoading && assignedServices?.length > 0 && (assignedServices.length > 1 ? ( @@ -306,9 +159,9 @@ const Teams = () => { ) : (
{assignedServices[0].name}
))} -
+
*/}
-
+
{ {activeEmployee ? "Active Employees" : "Inactive Employees"}
-
- x`` +
{HasAssignUserPermission && (
{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" + {projectEmployees && projectEmployees.length > 0 && ( + + + + + + + + + + + + + + {projectEmployees && + projectEmployees.map((emp) => ( + + - )} - - - - ))} - -
+
Name
+
ServicesOrganizationAssigned DateRelease DateProject RoleActions
+ - - {getRole(item.jobRoleId)} - - - {item.isActive ? ( - - ) : ( - Not in project - )} -
- )} - {!employeeLodaing && filteredEmployees.length === 0 && ( + + {emp.firstName} + {emp.lastName} + + + + +
{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"}
- )} + )} */}
diff --git a/src/hooks/useOrganization.js b/src/hooks/useOrganization.js index dea19874..d6c93693 100644 --- a/src/hooks/useOrganization.js +++ b/src/hooks/useOrganization.js @@ -35,6 +35,8 @@ export const useOrganizationModal = () => { }; }; +// ================================Query============================================================= + export const useOrganizationBySPRID = (sprid) => { return useQuery({ queryKey: ["organization by", sprid], @@ -76,6 +78,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,11 +124,11 @@ export const useAssignOrgToProject = (onSuccessCallback) => { mutationFn: async (payload) => await OrganizationRepository.assignOrganizationToProject(payload), onSuccess: (_, variables) => { - const {projectId} = variables + const { projectId } = variables; useClient.invalidateQueries({ queryKey: ["projectAssignedOrganiztions"], }); - useClient.invalidateQueries({ + useClient.invalidateQueries({ queryKey: ["projectAssignedOrganization", projectId], }); showToast("Organization successfully", "success"); diff --git a/src/hooks/useProjects.js b/src/hooks/useProjects.js index bdbd01c6..9225b0c4 100644 --- a/src/hooks/useProjects.js +++ b/src/hooks/useProjects.js @@ -54,7 +54,7 @@ export const useProjects = () => { export const useEmployeesByProjectAllocated = ( projectId, serviceId, - organizationId, + organizationId,emloyeeeStatus ) => { const { data = [], @@ -62,12 +62,12 @@ 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, + projectId, serviceId, organizationId, - serviceId + emloyeeeStatus ); return res?.data || res; }, @@ -413,8 +413,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) => { diff --git a/src/repositories/OrganizationRespository.jsx b/src/repositories/OrganizationRespository.jsx index 4b4ad1b0..28bbf3ce 100644 --- a/src/repositories/OrganizationRespository.jsx +++ b/src/repositories/OrganizationRespository.jsx @@ -10,11 +10,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 b1b0727f..43a1ee66 100644 --- a/src/repositories/ProjectRepository.jsx +++ b/src/repositories/ProjectRepository.jsx @@ -5,19 +5,21 @@ 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) => api.get(`/api/Project/employees/get/${projectId}`), From 265c74f07992f881803db4afe5c6031d70db51cf Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Sat, 27 Sep 2025 10:12:34 +0530 Subject: [PATCH 103/120] added filter and sorted employee list - Team --- src/components/Project/Team/Teams.jsx | 313 ++++++++++++++------------ 1 file changed, 174 insertions(+), 139 deletions(-) diff --git a/src/components/Project/Team/Teams.jsx b/src/components/Project/Team/Teams.jsx index 0d5d501d..e9802765 100644 --- a/src/components/Project/Team/Teams.jsx +++ b/src/components/Project/Team/Teams.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback, useMemo } from "react"; import MapUsers from "../MapUsers"; import { Link, NavLink, useNavigate, useParams } from "react-router-dom"; @@ -28,16 +28,15 @@ const Teams = () => { const dispatch = useDispatch(); const [AssigTeam, setAssignTeam] = useState(false); const [employees, setEmployees] = useState([]); - const [filteredEmployees, setFilteredEmployees] = 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 [activeEmployee, setActiveEmployee] = useState(false); const { data: assignedServices, isLoading: servicesLoading } = useProjectAssignedServices(selectedProject); - const {data:empJobRoles,loading} = useMaster() + const { data: empJobRoles, loading } = useMaster(); const handleToggleActive = (e) => setActiveEmployee(e.target.checked); const handleServiceChange = (e) => { @@ -52,7 +51,12 @@ const Teams = () => { projectEmployees, loading: employeeLodaing, refetch, - } = useEmployeesByProjectAllocated(selectedProject, selectedService,null,activeEmployee); + } = useEmployeesByProjectAllocated( + selectedProject, + selectedService, + null, + activeEmployee + ); const { mutate: submitAllocations, isPending, @@ -80,9 +84,9 @@ const Teams = () => { }, ]; - submitAllocations({payload:payload}); + submitAllocations({ payload: payload }); }; - const getJobRole = (jobRoleId) => { + const getJobRole = (jobRoleId) => { if (loading) return "Loading..."; if (!Array.isArray(empJobRoles)) return "Unassigned"; if (!jobRoleId) return "Unassigned"; @@ -90,19 +94,36 @@ const Teams = () => { const role = empJobRoles.find((b) => b.id == jobRoleId); return role ? role.name : "Unassigned"; }; - // 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]); + 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 ( <> @@ -112,7 +133,7 @@ const Teams = () => { isOpen={AssigTeam} closeModal={() => setAssignTeam(false)} > - setAssignTeam(false)} /> + setAssignTeam(false)} /> )} @@ -128,79 +149,85 @@ const Teams = () => {
-
-
- {/*
- {!servicesLoading && - assignedServices?.length > 0 && - (assignedServices.length > 1 ? ( - - ) : ( -
{assignedServices[0].name}
- ))} -
*/} +
+
+
+
+ {!servicesLoading && ( + <> + {(!assignedServices || assignedServices.length === 0) && ( + + Not Service Assigned + + )} + + {assignedServices?.length === 1 && ( + + {assignedServices[0].name} + + )} + + {assignedServices?.length > 1 && ( + + )} + + )} +
+ +
+ + +
+
-
-
- - -
-
- -
+ +
+ + {HasAssignUserPermission && ( )}
+
{employeeLodaing &&

Loading..

} {projectEmployees && projectEmployees.length > 0 && ( @@ -213,72 +240,80 @@ const Teams = () => { Services Organization Assigned Date - Release Date + {activeEmployee && Release Date} Project Role Actions - {projectEmployees && - projectEmployees.map((emp) => ( - - -
- - - + - {emp.serviceName || "N/A"} - {emp.organizationName || "N/A"} + {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 + + {moment(emp.allocationDate).format("DD-MMM-YYYY")} + + {activeEmployee && ( + + {emp.reAllocationDate + ? moment(emp.reAllocationDate).format( + "DD-MMM-YYYY" + ) + : "Present"} + )} - - - ))} + + + {getJobRole(emp.jobRoleId)} + + + + {emp.isActive ? ( + + ) : ( + Not in project + )} + + + ))} )} From ca8a41bb63cdb13c6845afe1b7b42201b8b4b953 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Sat, 27 Sep 2025 11:21:41 +0530 Subject: [PATCH 104/120] added style classes for footer --- public/assets/vendor/css/core.css | 2 +- .../Project/Team/TeamAssignToProject.jsx | 2 +- .../Project/Team/TeamEmployeeList.jsx | 10 +++---- src/components/Project/Team/Teams.jsx | 30 +++++++++---------- src/hooks/useProjects.js | 2 +- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/public/assets/vendor/css/core.css b/public/assets/vendor/css/core.css index 5acc33c5..48163eb5 100644 --- a/public/assets/vendor/css/core.css +++ b/public/assets/vendor/css/core.css @@ -18613,7 +18613,7 @@ li:not(:first-child) .dropdown-item, min-height: 70vh !important; } -.modal-min-h-{ +.modal-min-h{ min-height: 60vh !important; } diff --git a/src/components/Project/Team/TeamAssignToProject.jsx b/src/components/Project/Team/TeamAssignToProject.jsx index 0bab7756..3a1bb856 100644 --- a/src/components/Project/Team/TeamAssignToProject.jsx +++ b/src/components/Project/Team/TeamAssignToProject.jsx @@ -66,7 +66,7 @@ const TeamAssignToProject = ({ closeModal }) => {
-
+
{ status: true, })); - handleAssignEmployee({ payload }); + handleAssignEmployee({ payload,actionType:"assign"} ); setEmployees((prev) => prev.map((emp) => ({ @@ -154,9 +154,9 @@ if (employees.length === 0) { return ( -
- - +
+
+ @@ -231,7 +231,7 @@ if (employees.length === 0) { ))}
Employee Service
-
+
-
+
{employeeLodaing &&

Loading..

} {projectEmployees && projectEmployees.length > 0 && ( @@ -317,13 +315,15 @@ const Teams = () => {
)} - {/* {!employeeLodaing && filteredEmployees.length === 0 && ( -
- {activeEmployee - ? "No active employees assigned to the project" - : "No inactive employees assigned to the project"} + {!employeeLodaing && filteredEmployees.length === 0 && ( +
+

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

- )} */} + )}
diff --git a/src/hooks/useProjects.js b/src/hooks/useProjects.js index 9225b0c4..a0116a62 100644 --- a/src/hooks/useProjects.js +++ b/src/hooks/useProjects.js @@ -422,7 +422,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"); From ae66cb370555118753b18d59d772883003e3268c Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Thu, 25 Sep 2025 16:12:44 +0530 Subject: [PATCH 105/120] Change the label in Assign Organization. --- src/components/Organization/AssignOrg.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Organization/AssignOrg.jsx b/src/components/Organization/AssignOrg.jsx index c460de9b..e30a417c 100644 --- a/src/components/Organization/AssignOrg.jsx +++ b/src/components/Organization/AssignOrg.jsx @@ -248,7 +248,7 @@ const AssignOrg = ({ setStep }) => { ? "Please wait..." : flowType === "default" ? "Assign Organization" - : "Assign Project"} + : "Assign to Project"}
From ddfe09b570975680d5e99c7329a6cc5496e2876e Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Sat, 27 Sep 2025 14:44:43 +0530 Subject: [PATCH 106/120] =?UTF-8?q?Tenant=20Creation=20=E2=80=93=20Logo=20?= =?UTF-8?q?removed=20when=20navigating=20back=20from=20second=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Tenant/LogoUpload.jsx | 16 ++++++++++------ src/components/Tenant/OrganizationInfo.jsx | 9 ++++++++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/components/Tenant/LogoUpload.jsx b/src/components/Tenant/LogoUpload.jsx index aa6fbc12..9da2f998 100644 --- a/src/components/Tenant/LogoUpload.jsx +++ b/src/components/Tenant/LogoUpload.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { useFormContext } from "react-hook-form"; const toBase64 = (file) => @@ -10,11 +10,15 @@ const toBase64 = (file) => }); export const LogoUpload = ({ preview, setPreview, fileName, setFileName }) => { - const { - register, - setValue, - formState: { errors }, - } = useFormContext(); + const { register, setValue, watch, formState: { errors } } = useFormContext(); + const logoImage = watch("logoImage"); + + // Sync preview when the form value changes + useEffect(() => { + if (logoImage && !preview) { + setPreview(logoImage); // Use base64 as preview + } + }, [logoImage, preview, setPreview]); const handleUpload = async (e) => { const file = e.target.files?.[0]; diff --git a/src/components/Tenant/OrganizationInfo.jsx b/src/components/Tenant/OrganizationInfo.jsx index b12c55b2..1e166423 100644 --- a/src/components/Tenant/OrganizationInfo.jsx +++ b/src/components/Tenant/OrganizationInfo.jsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { useFormContext, Controller } from "react-hook-form"; import Label from "../common/Label"; import DatePicker from "../common/DatePicker"; @@ -57,6 +57,13 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => { } }; + useEffect(() => { + const logoImage = getValues("logoImage"); + if (logoImage) { + setLogoPreview(logoImage); + setLogoName("Uploaded Logo"); + } +}, [getValues]); return ( From 0d6708619f9a5404e5978c449784a8232f935a7a Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Sat, 27 Sep 2025 14:31:46 +0530 Subject: [PATCH 107/120] =?UTF-8?q?Switch=20Tenant=20&=20Goto=20workspace?= =?UTF-8?q?=20option=20label=20should=20be=20=E2=80=9CSwitch=20Workspace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Layout/Header.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Layout/Header.jsx b/src/components/Layout/Header.jsx index c02d40db..50280bb1 100644 --- a/src/components/Layout/Header.jsx +++ b/src/components/Layout/Header.jsx @@ -401,7 +401,7 @@ const Header = () => { className="dropdown-item cusor-pointer" > - Workspace + Switch Workspace
  • From a873ace1095e00fac3f1c0bf5d3129206c8d5064 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Sat, 27 Sep 2025 14:24:05 +0530 Subject: [PATCH 108/120] =?UTF-8?q?Tenant=20Creation=20=E2=80=93=20Error?= =?UTF-8?q?=20banner=20for=20plan=20selection/currency=20should=20not=20be?= =?UTF-8?q?=20shown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Tenant/SubScription.jsx | 2 +- src/components/Tenant/TenantSchema.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Tenant/SubScription.jsx b/src/components/Tenant/SubScription.jsx index f63fd911..adb97c70 100644 --- a/src/components/Tenant/SubScription.jsx +++ b/src/components/Tenant/SubScription.jsx @@ -224,7 +224,7 @@ const SubScription = ({ onSubmitSubScription, onNext }) => {
  • {Object.keys(errors).length > 0 && ( -