From 9822ae91ece024b16ecd75dafd00d2f09a7f1772 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Tue, 18 Nov 2025 15:56:16 +0530 Subject: [PATCH 01/41] Adding AdvancePayment a list view. --- .../AdvancePayment/AdvancePaymentList.jsx | 17 +++++++---- .../AdvancePayment/AdvancePaymentList1.jsx | 16 ++++++++++ src/hooks/useExpense.js | 9 ++++++ .../AdvancePayment/AdvancePaymentPage.jsx | 10 ++++--- .../AdvancePayment/AdvancePaymentPage1.jsx | 29 +++++++++++++++++++ src/repositories/ExpsenseRepository.jsx | 8 +++-- src/router/AppRoutes.jsx | 3 +- 7 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 src/components/AdvancePayment/AdvancePaymentList1.jsx create mode 100644 src/pages/AdvancePayment/AdvancePaymentPage1.jsx diff --git a/src/components/AdvancePayment/AdvancePaymentList.jsx b/src/components/AdvancePayment/AdvancePaymentList.jsx index e9113b48..5aef0de7 100644 --- a/src/components/AdvancePayment/AdvancePaymentList.jsx +++ b/src/components/AdvancePayment/AdvancePaymentList.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo } from "react"; -import { useExpenseTransactions } from "../../hooks/useExpense"; +import { useExpenseAllTransactionsList, useExpenseTransactions } from "../../hooks/useExpense"; import Error from "../common/Error"; import { formatUTCToLocalTime } from "../../utils/dateUtils"; import Loader, { SpinnerLoader } from "../common/Loader"; @@ -11,11 +11,16 @@ import { employee } from "../../data/masters"; import { useAdvancePaymentContext } from "../../pages/AdvancePayment/AdvancePaymentPage"; import { formatFigure } from "../../utils/appUtils"; -const AdvancePaymentList = ({ employeeId }) => { - const { setBalance } = useAdvancePaymentContext(); +const AdvancePaymentList = ({ employeeId, searchString }) => { + const { setBalance } = useAdvancePaymentContext(); const { data, isError, isLoading, error, isFetching } = useExpenseTransactions(employeeId, { enabled: !!employeeId }); + const { data: getAllData } = + useExpenseAllTransactionsList(searchString ?? ""); + + console.log("Kartik", getAllData) + const records = Array.isArray(data) ? data : []; let currentBalance = 0; @@ -85,7 +90,7 @@ const AdvancePaymentList = ({ employeeId }) => { key: "date", label: ( <> - Date + Date ), align: "text-start", @@ -154,8 +159,8 @@ const AdvancePaymentList = ({ employeeId }) => { - {Array.isArray(data) && data.length > 0 ? ( - data.map((row) => ( + {Array.isArray(getAllData) && getAllData.length > 0 ? ( + getAllData.map((row) => ( {columns.map((col) => ( diff --git a/src/components/AdvancePayment/AdvancePaymentList1.jsx b/src/components/AdvancePayment/AdvancePaymentList1.jsx new file mode 100644 index 00000000..9e5fb3e5 --- /dev/null +++ b/src/components/AdvancePayment/AdvancePaymentList1.jsx @@ -0,0 +1,16 @@ +import React from 'react' +import { useExpenseAllTransactionsList } from '../../hooks/useExpense'; + +const AdvancePaymentList1 = ({searchString}) => { + const { data, isError, isLoading, error, isFetching } = + useExpenseAllTransactionsList(); + return ( +
+
+ +
+
+ ) +} + +export default AdvancePaymentList1 diff --git a/src/hooks/useExpense.js b/src/hooks/useExpense.js index bc5c1da4..539f0afd 100644 --- a/src/hooks/useExpense.js +++ b/src/hooks/useExpense.js @@ -438,6 +438,15 @@ export const useExpenseTransactions = (employeeId)=>{ keepPreviousData:true, }) } +export const useExpenseAllTransactionsList = (searchString) => { + return useQuery({ + queryKey: ["transaction", searchString], + queryFn: async () => { + const resp = await ExpenseRepository.getAllTranctionList(searchString); + return resp.data; + }, + }); +}; //#endregion // ---------------------------Put Post Recurring Expense--------------------------------------- diff --git a/src/pages/AdvancePayment/AdvancePaymentPage.jsx b/src/pages/AdvancePayment/AdvancePaymentPage.jsx index 9af4a6c9..70913ba8 100644 --- a/src/pages/AdvancePayment/AdvancePaymentPage.jsx +++ b/src/pages/AdvancePayment/AdvancePaymentPage.jsx @@ -29,10 +29,13 @@ const AdvancePaymentPage = () => { const { control, reset, watch } = useForm({ defaultValues: { employeeId: "", + searchString: "", }, }); const selectedEmployeeId = watch("employeeId"); + const searchString = watch("searchString"); + useEffect(() => { const selectedEmpoyee = sessionStorage.getItem("transaction-empId"); reset({ @@ -68,9 +71,8 @@ const AdvancePaymentPage = () => { <> 0 ? "text-success" : "text-danger" - } fs-5 fw-bold ms-1`} + className={`${balance > 0 ? "text-success" : "text-danger" + } fs-5 fw-bold ms-1`} > {balance > 0 ? ( @@ -88,7 +90,7 @@ const AdvancePaymentPage = () => { )} - + diff --git a/src/pages/AdvancePayment/AdvancePaymentPage1.jsx b/src/pages/AdvancePayment/AdvancePaymentPage1.jsx new file mode 100644 index 00000000..1d1b457a --- /dev/null +++ b/src/pages/AdvancePayment/AdvancePaymentPage1.jsx @@ -0,0 +1,29 @@ +import React from 'react' +import Breadcrumb from '../../components/common/Breadcrumb' +import AdvancePaymentList1 from '../../components/AdvancePayment/AdvancePaymentList1' +import { useForm } from 'react-hook-form'; + +const AdvancePaymentPage1 = () => { + const { control, reset, watch } = useForm({ + defaultValues: { + searchString: "", + }, + }); + const searchString = watch("searchString"); + return ( +
+ +
+ +
+
+ ) +} + +export default AdvancePaymentPage1 diff --git a/src/repositories/ExpsenseRepository.jsx b/src/repositories/ExpsenseRepository.jsx index 5e30bf76..6be424b0 100644 --- a/src/repositories/ExpsenseRepository.jsx +++ b/src/repositories/ExpsenseRepository.jsx @@ -44,13 +44,13 @@ const ExpenseRepository = { DeletePaymentRequest: () => api.get("delete here come"), CreatePaymentRequestExpense: (data) => api.post("/api/Expense/payment-request/expense/create", data), - GetPayee:()=>api.get('/api/Expense/payment-request/payee'), + GetPayee: () => api.get('/api/Expense/payment-request/payee'), //#endregion //#region Recurring Expense - GetRecurringExpenseList:(pageSize, pageNumber, filter,isActive, searchString) => { + GetRecurringExpenseList: (pageSize, pageNumber, filter, isActive, searchString) => { const payloadJsonString = JSON.stringify(filter); return api.get( `/api/expense/get/recurring-payment/list?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&isActive=${isActive}&searchString=${searchString}` @@ -70,9 +70,11 @@ const ExpenseRepository = { //#region Advance Payment GetTranctionList: (employeeId) => api.get(`/api/Expense/get/transactions/${employeeId}`), + getAllTranctionList: (searchString) => + api.get(`/api/Expense/get/advance-payment/employee/list?searchString=${searchString}`), //#endregion - + }; export default ExpenseRepository; diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index 1c95ec64..619b5465 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -61,6 +61,7 @@ import RecurringExpensePage from "../pages/RecurringExpense/RecurringExpensePage import AdvancePaymentPage from "../pages/AdvancePayment/AdvancePaymentPage"; import ServiceProjectDetail from "../pages/ServiceProject/ServiceProjectDetail"; import ManageJob from "../components/ServiceProject/ManageJob"; +import AdvancePaymentPage1 from "../pages/AdvancePayment/AdvancePaymentPage1"; const router = createBrowserRouter( [ { @@ -116,7 +117,7 @@ const router = createBrowserRouter( { path: "/expenses", element: }, { path: "/payment-request", element: }, { path: "/recurring-payment", element: }, - { path: "/advance-payment", element: }, + { path: "/advance-payment", element: }, { path: "/collection", element: }, { path: "/masters", element: }, { path: "/tenants", element: }, From 5b91c13b857ab74157ba8f46a1da177e0ef579aa Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 19 Nov 2025 09:46:41 +0530 Subject: [PATCH 02/41] adding list view in advance payment list. --- .../AdvancePayment/AdvancePaymentList1.jsx | 105 +++++++++++++++++- .../RecurringExpense/RecurringExpenseList.jsx | 2 +- .../AdvancePayment/AdvancePaymentPage1.jsx | 21 +++- 3 files changed, 119 insertions(+), 9 deletions(-) diff --git a/src/components/AdvancePayment/AdvancePaymentList1.jsx b/src/components/AdvancePayment/AdvancePaymentList1.jsx index 9e5fb3e5..eb35942c 100644 --- a/src/components/AdvancePayment/AdvancePaymentList1.jsx +++ b/src/components/AdvancePayment/AdvancePaymentList1.jsx @@ -1,15 +1,108 @@ import React from 'react' import { useExpenseAllTransactionsList } from '../../hooks/useExpense'; -const AdvancePaymentList1 = ({searchString}) => { +const AdvancePaymentList1 = ({ searchString }) => { const { data, isError, isLoading, error, isFetching } = - useExpenseAllTransactionsList(); - return ( -
-
+ useExpenseAllTransactionsList(searchString); + console.log("Kartik", data) + const recurringExpenseColumns = [ + { + key: "date", + label: ( + <> + Date + + ), + align: "text-start", + }, + { key: "description", label: "Description", align: "text-start" }, + { + key: "credit", + label: ( + <> + Credit + + ), + align: "text-end", + }, + { + key: "debit", + label: ( + <> + Debit + + ), + align: "text-end", + }, + + { + key: "balance", + label: ( + <> + Balance + + ), + align: "text-end fw-bold", + }, + + ]; + return ( +
+
+ {/* {Array.isArray(data) && data.length > 0 && ( */} + + + + {recurringExpenseColumns.map((col) => ( + + ))} + + + + + {data?.length > 0 ? ( + data?.map((recurringExpense) => ( + + {recurringExpenseColumns.map((col) => ( + + ))} + + + )) + ) : ( + + + + )} + +
+ {col.label} +
+ {col?.customRender + ? col?.customRender(recurringExpense) + : col?.getValue(recurringExpense)} +
+ {/* )} */} + {/* {!data || + data.length === 0 + && ( +
+ {isError ? (

{error.message}

) : (

No Recurring Expense Found

)} +
+ )} */} +
-
) } diff --git a/src/components/RecurringExpense/RecurringExpenseList.jsx b/src/components/RecurringExpense/RecurringExpenseList.jsx index 338a3f38..a4c45fba 100644 --- a/src/components/RecurringExpense/RecurringExpenseList.jsx +++ b/src/components/RecurringExpense/RecurringExpenseList.jsx @@ -166,7 +166,7 @@ const RecurringExpenseList = ({ search, filterStatuses }) => { } ); }; - +console.log("Tanish",filteredData) return ( <> {IsDeleteModalOpen && ( diff --git a/src/pages/AdvancePayment/AdvancePaymentPage1.jsx b/src/pages/AdvancePayment/AdvancePaymentPage1.jsx index 1d1b457a..0a35dd44 100644 --- a/src/pages/AdvancePayment/AdvancePaymentPage1.jsx +++ b/src/pages/AdvancePayment/AdvancePaymentPage1.jsx @@ -2,6 +2,7 @@ import React from 'react' import Breadcrumb from '../../components/common/Breadcrumb' import AdvancePaymentList1 from '../../components/AdvancePayment/AdvancePaymentList1' import { useForm } from 'react-hook-form'; +import EmployeeSearchInput from '../../components/common/EmployeeSearchInput'; const AdvancePaymentPage1 = () => { const { control, reset, watch } = useForm({ @@ -19,9 +20,25 @@ const AdvancePaymentPage1 = () => { { label: "Advance Payment" }, ]} /> -
- +
+
+
+
+ +
+
+ +
+ + +
) } From 3fe533c8e30822534a345f7a64b9dd94ccf4f9ed Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Wed, 19 Nov 2025 10:42:12 +0530 Subject: [PATCH 03/41] addd serach poject field both projects --- .../common/Forms/InputFieldSuggesstion.jsx | 56 ------------ .../common/Forms/InputSuggesstionField.jsx | 90 +++++++++++++++++++ .../common/Forms/SelectFieldServerSide.jsx | 42 +++++---- src/hooks/useProjects.js | 30 +++++-- src/repositories/ProjectRepository.jsx | 23 +++-- 5 files changed, 155 insertions(+), 86 deletions(-) delete mode 100644 src/components/common/Forms/InputFieldSuggesstion.jsx create mode 100644 src/components/common/Forms/InputSuggesstionField.jsx diff --git a/src/components/common/Forms/InputFieldSuggesstion.jsx b/src/components/common/Forms/InputFieldSuggesstion.jsx deleted file mode 100644 index 881eb16c..00000000 --- a/src/components/common/Forms/InputFieldSuggesstion.jsx +++ /dev/null @@ -1,56 +0,0 @@ -import React from 'react' - -const InputFieldSuggesstion = () => { - return ( -
- setTimeout(() => setShowSuggestions(false), 150)} - onFocus={() => { - if (value) setShowSuggestions(true); - }} - disabled={disabled} - /> - {showSuggestions && filteredList.length > 0 && ( -
    - {filteredList.map((org) => ( -
  • handleSelectSuggestion(org)} - onMouseEnter={(e) => - (e.currentTarget.style.backgroundColor = "#f8f9fa") - } - onMouseLeave={(e) => - (e.currentTarget.style.backgroundColor = "transparent") - } - > - {org} -
  • - ))} -
- )} - - {error && {error}} -
- ) -} - -export default InputFieldSuggesstion diff --git a/src/components/common/Forms/InputSuggesstionField.jsx b/src/components/common/Forms/InputSuggesstionField.jsx new file mode 100644 index 00000000..9ef02e9d --- /dev/null +++ b/src/components/common/Forms/InputSuggesstionField.jsx @@ -0,0 +1,90 @@ +import React, { useEffect, useRef, useState } from "react"; +import Label from "../Label"; + +const InputSuggessionField = ({ + organizationList = [], + value, + onChange, + error, + disabled=false +}) => { + const [open, setOpen] = useState(false); + const dropdownRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setOpen(false); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + const selectedOption = options.find((opt) => opt[valueKey] === value); + + const displayText = selectedOption ? selectedOption[labelKey] : placeholder; + + const handleSelect = (option) => { + onChange(option[valueKey]); + setOpen(false); + }; + + const toggleDropdown = () => setOpen((prev) => !prev); + + return ( +
+ {label && ( + + )} + + + + {open && !isLoading && ( +
    + {options.map((option, i) => ( +
  • + +
  • + ))} +
+ )} +
+ ); +}; + +export default InputSuggessionField; diff --git a/src/components/common/Forms/SelectFieldServerSide.jsx b/src/components/common/Forms/SelectFieldServerSide.jsx index f7aa70eb..6ff6eb90 100644 --- a/src/components/common/Forms/SelectFieldServerSide.jsx +++ b/src/components/common/Forms/SelectFieldServerSide.jsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from "react"; import Label from "../Label"; import { useDebounce } from "../../../utils/appUtils"; import { useEmployeesName } from "../../../hooks/useEmployees"; +import { useProjectBothName } from "../../../hooks/useProjects"; const SelectEmployeeServerSide = ({ label = "Select", @@ -154,7 +155,9 @@ const SelectEmployeeServerSide = ({ )} {!isLoading && options.length === 0 && ( -
  • No results found
  • +
  • + No results found +
  • )} {!isLoading && @@ -183,24 +186,29 @@ const SelectEmployeeServerSide = ({ }; export default SelectEmployeeServerSide; - -export const SelectProjectField = ()=>{ - const [searchText, setSearchText] = useState(""); +export const SelectProjectField = ({ + label = "Select", + placeholder = "Select Project", + required = false, + value = null, + onChange, + valueKey = "id", + isFullObject = false, + isMultiple = false, + isAllProject = false, +}) => { + const [searchText, setSearchText] = useState(""); const debounce = useDebounce(searchText, 300); - const { data, isLoading } = useEmployeesName( - projectId, - debounce, - isAllEmployee - ); + const { data, isLoading } = useProjectBothName(debounce); - const options = data?.data ?? []; + const options = data ?? []; const [open, setOpen] = useState(false); const dropdownRef = useRef(null); - const getDisplayName = (emp) => { - if (!emp) return ""; - return `${emp.firstName || ""} ${emp.lastName || ""}`.trim(); + const getDisplayName = (project) => { + if (!project) return ""; + return `${project.name || ""}`.trim(); }; /** ----------------------------- @@ -304,7 +312,7 @@ export const SelectProjectField = ()=>{ top: "100%", left: 0, zIndex: 1050, - marginTop: "4px", + marginTop: "2px", borderRadius: "0.375rem", overflow: "hidden", }} @@ -324,7 +332,9 @@ export const SelectProjectField = ()=>{ )} {!isLoading && options.length === 0 && ( -
  • No results found
  • +
  • + No results found +
  • )} {!isLoading && @@ -350,4 +360,4 @@ export const SelectProjectField = ()=>{ )}
    ); -}; \ No newline at end of file +}; diff --git a/src/hooks/useProjects.js b/src/hooks/useProjects.js index fcb715b6..25caf14b 100644 --- a/src/hooks/useProjects.js +++ b/src/hooks/useProjects.js @@ -20,12 +20,15 @@ export const useCurrentService = () => { // ------------------------------Query------------------- -export const useProjects = (pageSize,pageNumber) => { +export const useProjects = (pageSize, pageNumber) => { const loggedUser = useSelector((store) => store.globalVariables.loginUser); return useQuery({ - queryKey: ["ProjectsList",pageSize,pageNumber], + queryKey: ["ProjectsList", pageSize, pageNumber], queryFn: async () => { - const response = await ProjectRepository.getProjectList(pageSize,pageNumber); + const response = await ProjectRepository.getProjectList( + pageSize, + pageNumber + ); return response?.data; }, enabled: !!loggedUser, @@ -153,7 +156,7 @@ export const useProjectsAllocationByEmployee = (employeeId) => { return { projectList, loading: isLoading, error, refetch }; }; -export const useProjectName = (provideAll=false) => { +export const useProjectName = (provideAll = false) => { const { data = [], isLoading, @@ -161,7 +164,7 @@ export const useProjectName = (provideAll=false) => { refetch, isError, } = useQuery({ - queryKey: ["basicProjectNameList",provideAll], + queryKey: ["basicProjectNameList", provideAll], queryFn: async () => { const res = await ProjectRepository.projectNameList(provideAll); return res.data || res; @@ -179,6 +182,19 @@ export const useProjectName = (provideAll=false) => { }; }; +export const useProjectBothName = (searchString) => { + return useQuery({ + queryKey: ["basic_bothProject", searchString], + queryFn: async () => { + const res = await ProjectRepository.projectNameListAll(searchString); + return res.data || res; + }, + onError: (error) => { + showToast(error.message || "Error while Fetching project Name", "error"); + }, + }); +}; + export const useProjectInfra = (projectId, serviceId) => { const { data: projectInfra, @@ -337,7 +353,7 @@ export const useEmployeeForTaskAssign = ( // -- -------------Mutation------------------------------- -export const useCreateProject = ( onSuccessCallback ) => { +export const useCreateProject = (onSuccessCallback) => { const queryClient = useQueryClient(); return useMutation({ @@ -368,7 +384,7 @@ export const useCreateProject = ( onSuccessCallback ) => { }); }; -export const useUpdateProject = ( onSuccessCallback ) => { +export const useUpdateProject = (onSuccessCallback) => { const queryClient = useQueryClient(); const { mutate, isPending, isSuccess, isError } = useMutation({ mutationFn: async ({ projectId, payload }) => { diff --git a/src/repositories/ProjectRepository.jsx b/src/repositories/ProjectRepository.jsx index 1220d085..86e59955 100644 --- a/src/repositories/ProjectRepository.jsx +++ b/src/repositories/ProjectRepository.jsx @@ -1,17 +1,24 @@ import { api } from "../utils/axiosClient"; const ProjectRepository = { - getProjectList: (pageSize,pageNumber) => api.get(`/api/project/list?pageSize=${pageSize}&pageNumber=${pageNumber}`), + getProjectList: (pageSize, pageNumber) => + api.get(`/api/project/list?pageSize=${pageSize}&pageNumber=${pageNumber}`), getProjectByprojectId: (projetid) => api.get(`/api/project/details/${projetid}`), - getProjectAllocation: (projectId, serviceId, organizationId, employeeStatus) => { + 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}`); - if (employeeStatus !== undefined) params.push(`includeInactive=${employeeStatus}`); + if (employeeStatus !== undefined) + params.push(`includeInactive=${employeeStatus}`); if (params.length > 0) { url += `?${params.join("&")}`; @@ -20,7 +27,6 @@ const ProjectRepository = { return api.get(url); }, - getEmployeesByProject: (projectId) => api.get(`/api/Project/employees/get/${projectId}`), @@ -40,10 +46,13 @@ const ProjectRepository = { api.get(`/api/project/allocation-histery/${id}`), updateProjectsByEmployee: (id, data) => api.post(`/api/project/assign-projects/${id}`, data), - projectNameList: (provideAll) => api.get(`/api/project/list/basic?provideAll=${provideAll}`), + projectNameList: (provideAll) => + api.get(`/api/project/list/basic?provideAll=${provideAll}`), + projectNameListAll: (searchString) => + api.get(`/api/project/list/basic/all?searchString=${searchString}`), getProjectDetails: (id) => api.get(`/api/project/details/${id}`), - + getProjectInfraByproject: (projectId, serviceId) => { let url = `/api/project/infra-details/${projectId}`; if (serviceId) { @@ -85,7 +94,7 @@ const ProjectRepository = { api.get(`/api/Project/get/assigned/services/${projectId}`), getProjectAssignedOrganizations: (projectId) => api.get(`/api/Project/get/assigned/organization/${projectId}`), - getProjectAssignedOrganizationsName: (projectId) => + getProjectAssignedOrganizationsName: (projectId) => api.get(`/api/Project/get/assigned/organization/dropdown/${projectId}`), getEmployeeForTaskAssign: (projectId, serviceId, organizationId) => { From 10e54637d56e9263ed4c73c74fbc2a9f69838765 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 19 Nov 2025 10:47:11 +0530 Subject: [PATCH 04/41] Adding list view in Advance payment list. --- .../AdvancePayment/AdvancePaymentList1.jsx | 255 +++++++++++------- 1 file changed, 159 insertions(+), 96 deletions(-) diff --git a/src/components/AdvancePayment/AdvancePaymentList1.jsx b/src/components/AdvancePayment/AdvancePaymentList1.jsx index eb35942c..2269f23f 100644 --- a/src/components/AdvancePayment/AdvancePaymentList1.jsx +++ b/src/components/AdvancePayment/AdvancePaymentList1.jsx @@ -1,109 +1,172 @@ +// import React from 'react' +// import { useExpenseAllTransactionsList } from '../../hooks/useExpense'; + +// const AdvancePaymentList1 = ({ searchString }) => { +// const { data, isError, isLoading, error, isFetching } = +// useExpenseAllTransactionsList(searchString); +// console.log("Kartik", data) + +// const recurringExpenseColumns = [ +// { +// key: "date", +// label: ( +// <> +// Date +// +// ), +// align: "text-start", +// }, +// { key: "description", label: "Description", align: "text-start" }, +// { +// key: "credit", +// label: ( +// <> +// Credit +// +// ), +// align: "text-end", +// }, +// { +// key: "debit", +// label: ( +// <> +// Debit +// +// ), +// align: "text-end", +// }, + +// { +// key: "balance", +// label: ( +// <> +// Balance +// +// ), +// align: "text-end fw-bold", +// }, + +// ]; +// return ( +//
    +//
    +// {/* {Array.isArray(data) && data.length > 0 && ( */} +// +// +// +// {recurringExpenseColumns.map((col) => ( +// +// ))} +// +// + +// +// {data?.length > 0 ? ( +// data?.map((recurringExpense) => ( +// +// {recurringExpenseColumns.map((col) => ( +// +// ))} + +// +// )) +// ) : ( +// +// +// +// )} +// +//
    +// {col.label} +//
    +// {col?.customRender +// ? col?.customRender(recurringExpense) +// : col?.getValue(recurringExpense)} +//
    +// {/* )} */} +// {/* {!data || +// data.length === 0 +// && ( +//
    +// {isError ? (

    {error.message}

    ) : (

    No Recurring Expense Found

    )} +//
    +// )} */} +//
    +//
    +// ) +// } + +// export default AdvancePaymentList1 + import React from 'react' import { useExpenseAllTransactionsList } from '../../hooks/useExpense'; const AdvancePaymentList1 = ({ searchString }) => { - const { data, isError, isLoading, error, isFetching } = + + const { data, isError, isLoading, error } = useExpenseAllTransactionsList(searchString); - console.log("Kartik", data) - const recurringExpenseColumns = [ - { - key: "date", - label: ( - <> - Date - - ), - align: "text-start", - }, - { key: "description", label: "Description", align: "text-start" }, - { - key: "credit", - label: ( - <> - Credit - - ), - align: "text-end", - }, - { - key: "debit", - label: ( - <> - Debit - - ), - align: "text-end", - }, - - { - key: "balance", - label: ( - <> - Balance - - ), - align: "text-end fw-bold", - }, + const rows = data; + const columns = [ + { key: "employee", label: "Employee Name", align: "text-start", getValue: (r) => r.firstName + " " + r.lastName }, + { key: "jobRoleName", label: "Job Role", align: "text-start", getValue: (r) => r.jobRoleName }, + { key: "balanceAmount", label: "Balance (₹)", align: "text-end", getValue: (r) => r.balanceAmount }, ]; - return ( -
    -
    - {/* {Array.isArray(data) && data.length > 0 && ( */} - - - - {recurringExpenseColumns.map((col) => ( - - ))} - - - - {data?.length > 0 ? ( - data?.map((recurringExpense) => ( - - {recurringExpenseColumns.map((col) => ( - - ))} - - - )) - ) : ( - - - - )} - -
    - {col.label} -
    - {col?.customRender - ? col?.customRender(recurringExpense) - : col?.getValue(recurringExpense)} -
    - {/* )} */} - {/* {!data || - data.length === 0 - && ( -
    - {isError ? (

    {error.message}

    ) : (

    No Recurring Expense Found

    )} -
    - )} */} -
    + if (isLoading) return

    Loading...

    ; + if (isError) return

    {error.message}

    ; + + return ( +
    +
    + + + + + {columns.map((col) => ( + + ))} + + + + + {rows.length > 0 ? ( + rows.map((row) => ( + + {columns.map((col) => ( + + ))} + + )) + ) : ( + + + + )} + +
    + {col.label} +
    + {col.getValue(row)} +
    + No Employees Found +
    +
    +
    ) } -export default AdvancePaymentList1 +export default AdvancePaymentList1; + From 54420c70d933fbb45784cccc53505c785d0c7866 Mon Sep 17 00:00:00 2001 From: Vikas Nale Date: Wed, 19 Nov 2025 12:15:08 +0530 Subject: [PATCH 05/41] change landing page logo to marco from ON --- public/img/brand/marco-250x250.png | Bin 0 -> 43649 bytes src/pages/Home/LandingPage.jsx | 20 ++++++++++---------- 2 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 public/img/brand/marco-250x250.png diff --git a/public/img/brand/marco-250x250.png b/public/img/brand/marco-250x250.png new file mode 100644 index 0000000000000000000000000000000000000000..ca14f30360be45095d721b2c8505102664004f4d GIT binary patch literal 43649 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ച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 literal 0 HcmV?d00001 diff --git a/src/pages/Home/LandingPage.jsx b/src/pages/Home/LandingPage.jsx index b72b1523..4062d3ba 100644 --- a/src/pages/Home/LandingPage.jsx +++ b/src/pages/Home/LandingPage.jsx @@ -20,7 +20,7 @@ const LandingPage = () => {
    @@ -401,15 +401,15 @@ const LandingPage = () => {

    Our Mission
    {" "}

    - At - , our mission is to empower organizations to manage their field - operations effortlessly — helping teams stay organized, - accountable, and productive, no matter where they are. We aim to - eliminate manual processes, data silos, and communication gaps - that often slow down projects and increase operational costs.{" "} -
    What We Do We provide a comprehensive suite of tools - designed to handle every critical aspect of field management — - from workforce tracking to expense control and reporting. With + At , our mission is to empower organizations + to manage their field operations effortlessly — helping teams stay + organized, accountable, and productive, no matter where they are. + We aim to eliminate manual processes, data silos, and + communication gaps that often slow down projects and increase + operational costs.
    What We Do We provide a comprehensive + suite of tools designed to handle every critical aspect of field + management — from workforce tracking to expense control and + reporting. With , you can:

      From a3be63b74f2376f8dd6a28a98236c3906b690ada Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 19 Nov 2025 12:22:38 +0530 Subject: [PATCH 06/41] Implementing api for Advance payment all data. --- .../AdvancePayment/AdvancePaymentList.jsx | 10 +- .../AdvancePayment/AdvancePaymentList1.jsx | 164 +++++------------- src/components/Layout/Header.jsx | 5 +- .../AdvancePayment/AdvancePaymentPage.jsx | 40 +++-- .../AdvancePayment/AdvancePaymentPage1.jsx | 18 +- src/router/AppRoutes.jsx | 1 + 6 files changed, 80 insertions(+), 158 deletions(-) diff --git a/src/components/AdvancePayment/AdvancePaymentList.jsx b/src/components/AdvancePayment/AdvancePaymentList.jsx index 5aef0de7..c6e9d004 100644 --- a/src/components/AdvancePayment/AdvancePaymentList.jsx +++ b/src/components/AdvancePayment/AdvancePaymentList.jsx @@ -15,12 +15,6 @@ const AdvancePaymentList = ({ employeeId, searchString }) => { const { setBalance } = useAdvancePaymentContext(); const { data, isError, isLoading, error, isFetching } = useExpenseTransactions(employeeId, { enabled: !!employeeId }); - - const { data: getAllData } = - useExpenseAllTransactionsList(searchString ?? ""); - - console.log("Kartik", getAllData) - const records = Array.isArray(data) ? data : []; let currentBalance = 0; @@ -159,8 +153,8 @@ const AdvancePaymentList = ({ employeeId, searchString }) => { - {Array.isArray(getAllData) && getAllData.length > 0 ? ( - getAllData.map((row) => ( + {Array.isArray(data) && data.length > 0 ? ( + data.map((row) => ( {columns.map((col) => ( diff --git a/src/components/AdvancePayment/AdvancePaymentList1.jsx b/src/components/AdvancePayment/AdvancePaymentList1.jsx index 2269f23f..e17cd1b1 100644 --- a/src/components/AdvancePayment/AdvancePaymentList1.jsx +++ b/src/components/AdvancePayment/AdvancePaymentList1.jsx @@ -1,127 +1,56 @@ -// import React from 'react' -// import { useExpenseAllTransactionsList } from '../../hooks/useExpense'; - -// const AdvancePaymentList1 = ({ searchString }) => { -// const { data, isError, isLoading, error, isFetching } = -// useExpenseAllTransactionsList(searchString); -// console.log("Kartik", data) - -// const recurringExpenseColumns = [ -// { -// key: "date", -// label: ( -// <> -// Date -// -// ), -// align: "text-start", -// }, -// { key: "description", label: "Description", align: "text-start" }, -// { -// key: "credit", -// label: ( -// <> -// Credit -// -// ), -// align: "text-end", -// }, -// { -// key: "debit", -// label: ( -// <> -// Debit -// -// ), -// align: "text-end", -// }, - -// { -// key: "balance", -// label: ( -// <> -// Balance -// -// ), -// align: "text-end fw-bold", -// }, - -// ]; -// return ( -//
      -//
      -// {/* {Array.isArray(data) && data.length > 0 && ( */} -// -// -// -// {recurringExpenseColumns.map((col) => ( -// -// ))} -// -// - -// -// {data?.length > 0 ? ( -// data?.map((recurringExpense) => ( -// -// {recurringExpenseColumns.map((col) => ( -// -// ))} - -// -// )) -// ) : ( -// -// -// -// )} -// -//
      -// {col.label} -//
      -// {col?.customRender -// ? col?.customRender(recurringExpense) -// : col?.getValue(recurringExpense)} -//
      -// {/* )} */} -// {/* {!data || -// data.length === 0 -// && ( -//
      -// {isError ? (

      {error.message}

      ) : (

      No Recurring Expense Found

      )} -//
      -// )} */} -//
      -//
      -// ) -// } - -// export default AdvancePaymentList1 - import React from 'react' +import Avatar from "../../components/common/Avatar"; // <-- ADD THIS import { useExpenseAllTransactionsList } from '../../hooks/useExpense'; +import { useNavigate } from 'react-router-dom'; +import { formatFigure } from '../../utils/appUtils'; const AdvancePaymentList1 = ({ searchString }) => { const { data, isError, isLoading, error } = useExpenseAllTransactionsList(searchString); - const rows = data; + const rows = data || []; + const navigate = useNavigate(); const columns = [ - { key: "employee", label: "Employee Name", align: "text-start", getValue: (r) => r.firstName + " " + r.lastName }, - { key: "jobRoleName", label: "Job Role", align: "text-start", getValue: (r) => r.jobRoleName }, - { key: "balanceAmount", label: "Balance (₹)", align: "text-end", getValue: (r) => r.balanceAmount }, + { + key: "employee", + label: "Employee Name", + align: "text-start", + customRender: (r) => ( +
      navigate(`/advance-payment/${r.id}`)} + style={{ cursor: "pointer" }}> + + + + {r.firstName} {r.lastName} + +
      + ), + }, + { + key: "jobRoleName", + label: "Job Role", + align: "text-start", + customRender: (r) => ( + + {r.jobRoleName} + + ), + }, + { + key: "balanceAmount", + label: "Balance (₹)", + align: "text-end", + customRender: (r) => ( + + {formatFigure(r.balanceAmount, { + // type: "currency", + currency: "INR", + })} + + ), + }, ]; if (isLoading) return

      Loading...

      ; @@ -130,7 +59,6 @@ const AdvancePaymentList1 = ({ searchString }) => { return (
      - @@ -148,7 +76,9 @@ const AdvancePaymentList1 = ({ searchString }) => { {columns.map((col) => ( ))} @@ -162,11 +92,9 @@ const AdvancePaymentList1 = ({ searchString }) => { )}
      - {col.getValue(row)} + {col.customRender + ? col.customRender(row) + : col.getValue(row)}
      -
      ) } export default AdvancePaymentList1; - diff --git a/src/components/Layout/Header.jsx b/src/components/Layout/Header.jsx index 0437539e..0dca90e5 100644 --- a/src/components/Layout/Header.jsx +++ b/src/components/Layout/Header.jsx @@ -50,8 +50,11 @@ const Header = () => { const isRecurringExpense = /^\/recurring-payment$/.test(pathname); const isAdvancePayment = /^\/advance-payment$/.test(pathname); const isServiceProjectPage = /^\/service-projects\/[0-9a-fA-F-]{36}$/.test(pathname); + const isAdvancePayment1 = + /^\/advance-payment(\/[0-9a-fA-F-]{36})?$/.test(pathname); - return !(isDirectoryPath || isProfilePage || isExpensePage || isPaymentRequest || isRecurringExpense || isAdvancePayment ||isServiceProjectPage); + + return !(isDirectoryPath || isProfilePage || isExpensePage || isPaymentRequest || isRecurringExpense || isAdvancePayment ||isServiceProjectPage || isAdvancePayment1); }; const allowedProjectStatusIds = [ "603e994b-a27f-4e5d-a251-f3d69b0498ba", diff --git a/src/pages/AdvancePayment/AdvancePaymentPage.jsx b/src/pages/AdvancePayment/AdvancePaymentPage.jsx index 70913ba8..f95f199a 100644 --- a/src/pages/AdvancePayment/AdvancePaymentPage.jsx +++ b/src/pages/AdvancePayment/AdvancePaymentPage.jsx @@ -13,6 +13,8 @@ import Label from "../../components/common/Label"; import AdvancePaymentList from "../../components/AdvancePayment/AdvancePaymentList"; import { employee } from "../../data/masters"; import { formatFigure } from "../../utils/appUtils"; +import { useParams } from "react-router-dom"; +import { useExpenseTransactions } from "../../hooks/useExpense"; export const AdvancePaymentContext = createContext(); export const useAdvancePaymentContext = () => { @@ -25,15 +27,30 @@ export const useAdvancePaymentContext = () => { return context; }; const AdvancePaymentPage = () => { + const { employeeId } = useParams(); + + const { data: transactionData } = useExpenseTransactions(employeeId, { + enabled: !!employeeId + }); + + const employeeName = useMemo(() => { + if (Array.isArray(transactionData) && transactionData.length > 0) { + const emp = transactionData[0].employee; + if (emp) return `${emp.firstName} ${emp.lastName}`; + } + return ""; + }, [transactionData]); + const [balance, setBalance] = useState(null); const { control, reset, watch } = useForm({ defaultValues: { - employeeId: "", + employeeId: employeeId || "", searchString: "", }, }); - const selectedEmployeeId = watch("employeeId"); + const selectedEmployeeId = employeeId || watch("employeeId"); + const searchString = watch("searchString"); useEffect(() => { @@ -50,22 +67,13 @@ const AdvancePaymentPage = () => { data={[ { label: "Home", link: "/dashboard" }, { label: "Finance", link: "/advance-payment" }, - { label: "Advance Payment" }, - ]} + { label: "Advance Payment", link: "/advance-payment" }, + employeeName && { label: employeeName, link: "" }, + ].filter(Boolean)} /> +
      -
      -
      -
      - -
      -
      +
      {balance ? ( <> diff --git a/src/pages/AdvancePayment/AdvancePaymentPage1.jsx b/src/pages/AdvancePayment/AdvancePaymentPage1.jsx index 0a35dd44..4ba3721b 100644 --- a/src/pages/AdvancePayment/AdvancePaymentPage1.jsx +++ b/src/pages/AdvancePayment/AdvancePaymentPage1.jsx @@ -11,6 +11,7 @@ const AdvancePaymentPage1 = () => { }, }); const searchString = watch("searchString"); + return (
      { ]} />
      -
      -
      -
      - -
      -
      +
      -
      - - +
      ) diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index 619b5465..c5d37aa3 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -118,6 +118,7 @@ const router = createBrowserRouter( { path: "/payment-request", element: }, { path: "/recurring-payment", element: }, { path: "/advance-payment", element: }, + { path: "/advance-payment/:employeeId", element: }, { path: "/collection", element: }, { path: "/masters", element: }, { path: "/tenants", element: }, From 25003d912ed66d3d2e3ebd46522e2bd0205e4ab4 Mon Sep 17 00:00:00 2001 From: Vikas Nale Date: Wed, 19 Nov 2025 12:35:13 +0530 Subject: [PATCH 07/41] add green color class for onfieldwork.com --- public/assets/vendor/css/core.css | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/public/assets/vendor/css/core.css b/public/assets/vendor/css/core.css index f440443e..e1d620fe 100644 --- a/public/assets/vendor/css/core.css +++ b/public/assets/vendor/css/core.css @@ -32560,9 +32560,7 @@ body:not(.modal-open) .layout-content-navbar .layout-navbar { .bg-blue { background-color:var(--bs-blue) } -.text-blue{ - color:var(--bs-blue) -} + .bg-indigo { background-color:var(--bs-indigo) } @@ -32574,4 +32572,10 @@ body:not(.modal-open) .layout-content-navbar .layout-navbar { } .text-red{ color:var(--bs-red) +} +.text-blue{ + color:var(--bs-blue) +} +.text-green{ + color:var(--bs-green) } \ No newline at end of file From 7773b7a43bb74e385158d5ade48f7527cc21cdbf Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 19 Nov 2025 12:59:57 +0530 Subject: [PATCH 08/41] Adding Status and UI implementation in Collection. --- src/components/collections/CollectionList.jsx | 13 +++++- src/components/collections/ViewCollection.jsx | 16 +++----- src/pages/collections/CollectionPage.jsx | 41 ++++++++++--------- 3 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/components/collections/CollectionList.jsx b/src/components/collections/CollectionList.jsx index bb94130e..339828ed 100644 --- a/src/components/collections/CollectionList.jsx +++ b/src/components/collections/CollectionList.jsx @@ -27,7 +27,7 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => { const selectedProject = useSelectedProject(); const searchDebounce = useDebounce(searchString, 500); - + const { data, isLoading, isError, error } = useCollections( selectedProject, searchDebounce, @@ -40,7 +40,6 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => { ); const { setProcessedPayment, setAddPayment, setViewCollection } = useCollectionContext(); - const paginate = (page) => { if (page >= 1 && page <= (data?.totalPages ?? 1)) { setCurrentPage(page); @@ -129,6 +128,16 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => { ), align: "text-end", }, + { + key: "status", + label: "Status", + getValue: (col) => ( + + {col?.isActive ? "Active" : "Inactive"} + + ), + align: "text-center", + }, { key: "balance", label: "Balance", diff --git a/src/components/collections/ViewCollection.jsx b/src/components/collections/ViewCollection.jsx index 77bc78fe..fdf563b9 100644 --- a/src/components/collections/ViewCollection.jsx +++ b/src/components/collections/ViewCollection.jsx @@ -25,7 +25,6 @@ const ViewCollection = ({ onClose }) => { if (isLoading) return ; if (isError) return
      {error.message}
      ; - return (

      Collection Details

      @@ -43,9 +42,8 @@ const ViewCollection = ({ onClose }) => {
      {" "} {data?.isActive ? "Active" : "Inactive"} @@ -214,9 +212,8 @@ const ViewCollection = ({ onClose }) => {
      • +
      {/* Right side: Search + Add Button */}
      + + + Date: Wed, 19 Nov 2025 14:26:12 +0530 Subject: [PATCH 09/41] Changing the position of Status in Collection list view. --- src/components/collections/CollectionList.jsx | 21 ++++++++++--------- src/pages/collections/CollectionPage.jsx | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/components/collections/CollectionList.jsx b/src/components/collections/CollectionList.jsx index 339828ed..ad1ff5c0 100644 --- a/src/components/collections/CollectionList.jsx +++ b/src/components/collections/CollectionList.jsx @@ -112,6 +112,16 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => { ), align: "text-center", }, + { + key: "status", + label: "Status", + getValue: (col) => ( + + {col?.isActive ? "Active" : "Inactive"} + + ), + align: "text-center", + }, { key: "amount", label: "Total Amount", @@ -128,16 +138,7 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => { ), align: "text-end", }, - { - key: "status", - label: "Status", - getValue: (col) => ( - - {col?.isActive ? "Active" : "Inactive"} - - ), - align: "text-center", - }, + { key: "balance", label: "Balance", diff --git a/src/pages/collections/CollectionPage.jsx b/src/pages/collections/CollectionPage.jsx index 8f68641d..5469be62 100644 --- a/src/pages/collections/CollectionPage.jsx +++ b/src/pages/collections/CollectionPage.jsx @@ -126,7 +126,7 @@ const CollectionPage = () => { }`} onClick={() => setShowPending(false)} > - All + Show All + + +
      + + + + +
      + ) +} + +export default ManageBranch diff --git a/src/components/ServiceProject/ServiceBranch.jsx b/src/components/ServiceProject/ServiceBranch.jsx new file mode 100644 index 00000000..7722bb19 --- /dev/null +++ b/src/components/ServiceProject/ServiceBranch.jsx @@ -0,0 +1,170 @@ +import React, { useState } from 'react' +import GlobalModel from '../common/GlobalModel'; +import ManageBranch from './ManageBranch'; + +const ServiceBranch = () => { + + const [ManageServiceBranch, setManageServiceBranch] = useState({ + IsOpen: null, + branchId: null, + }); + + const ServiceBranch = [ + { + key: "branchName", + label: "Branch Name", + align: "text-start", + getValue: (e) => e?.payee || "N/A", + }, + ]; + return ( +
      +
      +
      +
      + +
      +
      + + +
      + {/* {Array.isArray(filteredData) && filteredData.length > 0 && ( */} + + + + {ServiceBranch.map((col) => ( + + ))} + + + + + {/* + {filteredData?.length > 0 ? ( + filteredData?.map((recurringExpense) => ( + + {recurringExpenseColumns.map((col) => ( + + ))} + + + )) + ) : ( + + + + )} + */} +
      + {col.label} + Action
      + {col?.customRender + ? col?.customRender(recurringExpense) + : col?.getValue(recurringExpense)} + +
      + + setViewRecurring({ + recurringId: recurringExpense?.id, + view: true, + }) + } + > + +
      + +
        +
      • + setManageServiceBranch({ + IsOpen: true, + RecurringId: recurringExpense?.id, + }) + } + > + + + Modify + +
      • + +
      • { + setIsDeleteModalOpen(true); + setDeletingId(recurringExpense.id); + }} + > + + + Delete + +
      • +
      +
      +
      +
      + {/* )} */} + {/* {!filteredData || + filteredData.length === 0 + && ( +
      + {isError ? (

      {error.message}

      ) : (

      No Recurring Expense Found

      )} +
      + )} */} +
      + + {ManageServiceBranch.IsOpen && ( + + setManageServiceBranch({ IsOpen: null, branchId: null }) + } + > + + setManageServiceBranch({ IsOpen: null, branchId: null }) + } + // requestToEdit={ManageServiceBranch.branchId} + /> + + )} +
      +
      + ) +} + +export default ServiceBranch diff --git a/src/components/ServiceProject/ServiceProjectProfile.jsx b/src/components/ServiceProject/ServiceProjectProfile.jsx index 8e131852..05a05fd3 100644 --- a/src/components/ServiceProject/ServiceProjectProfile.jsx +++ b/src/components/ServiceProject/ServiceProjectProfile.jsx @@ -5,6 +5,7 @@ import { formatUTCToLocalTime } from "../../utils/dateUtils"; import ManageServiceProject from "./ManageServiceProject"; import GlobalModel from "../common/GlobalModel"; import { SpinnerLoader } from "../common/Loader"; +import ServiceBranch from "./ServiceBranch"; const ServiceProjectProfile = () => { const { projectId } = useParams(); @@ -114,6 +115,10 @@ const ServiceProjectProfile = () => {
      +
      + +
      +
      diff --git a/src/repositories/ServiceProjectRepository.jsx b/src/repositories/ServiceProjectRepository.jsx index 94ef3c8e..1da42fd2 100644 --- a/src/repositories/ServiceProjectRepository.jsx +++ b/src/repositories/ServiceProjectRepository.jsx @@ -35,4 +35,13 @@ export const ServiceProjectRepository = { api.patch(`/api/ServiceProject/job/edit/${id}`, patchData, { "Content-Type": "application/json-patch+json", }), + + //Branch API + getServiceBranch: (jobTicketId, pageSize, pageNumber) => + api.get( + `/api/serviceproject/branch/list?jobTicketId=${jobTicketId}&pageSize=${pageSize}&pageNumber=${pageNumber}` + ), + }; + + From 15978b2ac7308e33dba8aa1f87dee4e5cd1f4191 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Wed, 19 Nov 2025 16:35:53 +0530 Subject: [PATCH 14/41] fixed useClient implmention --- src/hooks/useServiceProject.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/hooks/useServiceProject.jsx b/src/hooks/useServiceProject.jsx index 95283992..b0b98719 100644 --- a/src/hooks/useServiceProject.jsx +++ b/src/hooks/useServiceProject.jsx @@ -317,7 +317,7 @@ export const useBranches = ( export const useBranch = (id)=>{ return useQuery({ queryKey:["branch",id], - queryFn:()=>{ + queryFn:async()=>{ const resp = await ServiceProjectRepository.GetBranchDetail(id); return resp.data ?? resp; }, @@ -326,8 +326,9 @@ export const useBranch = (id)=>{ } export const useCreateBranche =()=>{ + const queryClient = useQueryClient(); return useMutation({ - mutationFn:(payload)=> await ServiceProjectRepository.CreateBranch(payload), + mutationFn:async(payload)=> await ServiceProjectRepository.CreateBranch(payload), onSuccess: (data, variables) => { queryClient.invalidateQueries({ queryKey: ["branches"], @@ -347,6 +348,7 @@ export const useCreateBranche =()=>{ } export const useUpdateBranch=()=>{ + const queryClient = useQueryClient(); return useMutation({ mutationFn:async({id,payload})=> await ServiceProjectRepository.UpdateBranch(id,payload), onSuccess: (_,variables) => { @@ -367,6 +369,7 @@ export const useUpdateBranch=()=>{ } export const useDeleteBranch=()=>{ + const queryClient = useQueryClient(); return useMutation({ mutationFn:async(id)=> await ServiceProjectRepository.DeleteBranch(id), onSuccess: (_,variables) => { From df24b18a27b24b4a46f9c30061af04ef251105d1 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 19 Nov 2025 16:36:32 +0530 Subject: [PATCH 15/41] At the time of hit in cancel form will be closed. --- src/components/ServiceProject/ManageBranch.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ServiceProject/ManageBranch.jsx b/src/components/ServiceProject/ManageBranch.jsx index 51c8f7f6..2eae8c49 100644 --- a/src/components/ServiceProject/ManageBranch.jsx +++ b/src/components/ServiceProject/ManageBranch.jsx @@ -5,7 +5,7 @@ import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import Label from '../common/Label'; -const ManageBranch = ({ BranchToEdit = null }) => { +const ManageBranch = ({ closeModal,BranchToEdit = null }) => { const schema = BranchSchema(); const { From 50269aead221f3533d18f2e113ecf1a85d0c623d Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Wed, 19 Nov 2025 18:28:54 +0530 Subject: [PATCH 16/41] added selectFiled for branch --- src/components/ServiceProject/ManageJob.jsx | 20 +- .../ServiceProject/ServiceProjectSchema.jsx | 3 + .../common/Forms/SelectFieldServerSide.jsx | 179 ++++++++++++++++++ src/hooks/useServiceProject.jsx | 2 + src/repositories/ServiceProjectRepository.jsx | 4 +- 5 files changed, 204 insertions(+), 4 deletions(-) diff --git a/src/components/ServiceProject/ManageJob.jsx b/src/components/ServiceProject/ManageJob.jsx index c0d9223e..97cd1a97 100644 --- a/src/components/ServiceProject/ManageJob.jsx +++ b/src/components/ServiceProject/ManageJob.jsx @@ -4,6 +4,7 @@ import Label from "../common/Label"; import { zodResolver } from "@hookform/resolvers/zod"; import { defaultJobValue, jobSchema } from "./ServiceProjectSchema"; import { + useBranches, useCreateServiceProjectJob, useJobTags, useServiceProjectJobDetails, @@ -25,6 +26,7 @@ import { useParams } from "react-router-dom"; import { useDispatch } from "react-redux"; import { useJobStatus } from "../../hooks/masterHook/useMaster"; import { useServiceProjectJobContext } from "./Jobs"; +import { SelectFieldSearch } from "../common/Forms/SelectFieldServerSide"; const ManageJob = ({ Job }) => { const { setManageJob, setSelectedJob } = useServiceProjectJobContext(); @@ -39,7 +41,7 @@ const ManageJob = ({ Job }) => { control, watch, handleSubmit, - reset, + reset,setValue, formState: { errors }, } = methods; @@ -199,7 +201,7 @@ const ManageJob = ({ Job }) => { />
    - + { name="tags" label="Tag" placeholder="Enter Tag" + + /> +
    +
    + setValue("branchId", val)} + valueKey="id" + labelKey="branchName" + hookParams={[projectId,true,10,1]} + useFetchHook={useBranches} + isMultiple={false} />
    diff --git a/src/components/ServiceProject/ServiceProjectSchema.jsx b/src/components/ServiceProject/ServiceProjectSchema.jsx index 5a5727b7..9a14605c 100644 --- a/src/components/ServiceProject/ServiceProjectSchema.jsx +++ b/src/components/ServiceProject/ServiceProjectSchema.jsx @@ -70,6 +70,8 @@ export const jobSchema = z.object({ tags: z.array(TagSchema).optional().default([]), statusId: z.string().optional().nullable(), + + branchId: z.string().optional().nullable(), }); const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB @@ -109,6 +111,7 @@ export const defaultJobValue = { startDate: null, dueDate: null, tags: [], + branchId: null, }; //#endregion diff --git a/src/components/common/Forms/SelectFieldServerSide.jsx b/src/components/common/Forms/SelectFieldServerSide.jsx index 6ff6eb90..de0265af 100644 --- a/src/components/common/Forms/SelectFieldServerSide.jsx +++ b/src/components/common/Forms/SelectFieldServerSide.jsx @@ -361,3 +361,182 @@ export const SelectProjectField = ({
    ); }; + + + +export const SelectFieldSearch = ({ + label = "Select", + placeholder = "Select ", + required = false, + value = null, + onChange, + valueKey = "id", + labelKey = "name", + + isFullObject = false, + isMultiple = false, + hookParams, + useFetchHook, +}) => { + const [searchText, setSearchText] = useState(""); + const debounce = useDebounce(searchText, 300); + + const { data, isLoading } = useFetchHook(...hookParams,debounce); + const options = data?.data ?? []; + const [open, setOpen] = useState(false); + const dropdownRef = useRef(null); + + const getDisplayName = (entity) => { + if (!entity) return ""; + return `${entity[labelKey] || ""}`.trim(); + }; + + /** ----------------------------- + * SELECTED OPTION (SINGLE) + * ----------------------------- */ + let selectedSingle = null; + + if (!isMultiple) { + if (isFullObject && value) selectedSingle = value; + else if (!isFullObject && value) + selectedSingle = options.find((o) => o[valueKey] === value); + } + + /** ----------------------------- + * SELECTED OPTION (MULTIPLE) + * ----------------------------- */ + let selectedList = []; + if (isMultiple && Array.isArray(value)) { + if (isFullObject) selectedList = value; + else { + selectedList = options.filter((opt) => value.includes(opt[valueKey])); + } + } + + /** Main button label */ + const displayText = !isMultiple + ? getDisplayName(selectedSingle) || placeholder + : selectedList.length > 0 + ? selectedList.map((e) => getDisplayName(e)).join(", ") + : placeholder; + + /** ----------------------------- + * HANDLE OUTSIDE CLICK + * ----------------------------- */ + useEffect(() => { + const handleClickOutside = (e) => { + if (dropdownRef.current && !dropdownRef.current.contains(e.target)) { + setOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + /** ----------------------------- + * HANDLE SELECT + * ----------------------------- */ + const handleSelect = (option) => { + if (!isMultiple) { + // SINGLE SELECT + if (isFullObject) onChange(option); + else onChange(option[valueKey]); + } else { + // MULTIPLE SELECT + let updated = []; + + const exists = selectedList.some((e) => e[valueKey] === option[valueKey]); + + if (exists) { + // remove + updated = selectedList.filter((e) => e[valueKey] !== option[valueKey]); + } else { + // add + updated = [...selectedList, option]; + } + + if (isFullObject) onChange(updated); + else onChange(updated.map((x) => x[valueKey])); + } + }; + + return ( +
    + {label && ( + + )} + + {/* MAIN BUTTON */} + + + {/* DROPDOWN */} + {open && ( +
      +
      + setSearchText(e.target.value)} + className="form-control form-control-sm" + placeholder="Search..." + /> +
      + + {isLoading && ( +
    • Loading...
    • + )} + + {!isLoading && options.length === 0 && ( +
    • + No results found +
    • + )} + + {!isLoading && + options.map((option) => { + const isActive = isMultiple + ? selectedList.some((x) => x[valueKey] === option[valueKey]) + : selectedSingle && + selectedSingle[valueKey] === option[valueKey]; + + return ( +
    • + +
    • + ); + })} +
    + )} +
    + ); +}; diff --git a/src/hooks/useServiceProject.jsx b/src/hooks/useServiceProject.jsx index b0b98719..ab536e2b 100644 --- a/src/hooks/useServiceProject.jsx +++ b/src/hooks/useServiceProject.jsx @@ -308,12 +308,14 @@ export const useBranches = ( pageNumber, searchString ); + console.log(resp) return resp.data; }, enabled: !!projectId, }); }; + export const useBranch = (id)=>{ return useQuery({ queryKey:["branch",id], diff --git a/src/repositories/ServiceProjectRepository.jsx b/src/repositories/ServiceProjectRepository.jsx index b61505cd..339d5212 100644 --- a/src/repositories/ServiceProjectRepository.jsx +++ b/src/repositories/ServiceProjectRepository.jsx @@ -45,8 +45,8 @@ export const ServiceProjectRepository = { UpdateBranch: (id, data) => api.put("/api/ServiceProject/branch/edit/${id}", data), GetBranchList: (projectId, isActive, pageSize, pageNumber, searchString) => { - api.get( - `/api/ServiceProject/branch/list/${projectId}&isActive=${isActive}&pageSize=${pageSize}&pageNumber=${pageNumber}&searchString=${searchString}` + return api.get( + `/api/ServiceProject/branch/list/${projectId}?isActive=${isActive}&pageSize=${pageSize}&pageNumber=${pageNumber}&searchString=${searchString}` ); }, GetBranchDetail: (id) => api.get(`/api/ServiceProject/branch/details/${id}`), From 8a5d6158c2c5b27649fc7b6e851aba525d091bdb Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 19 Nov 2025 18:31:17 +0530 Subject: [PATCH 17/41] Implementing Creating API. --- .../ServiceProject/ManageBranch.jsx | 120 +++++++------- .../ServiceProject/ServiceBranch.jsx | 155 ++++++++---------- src/hooks/useServiceProject.jsx | 83 +++++----- src/repositories/ServiceProjectRepository.jsx | 8 +- 4 files changed, 181 insertions(+), 185 deletions(-) diff --git a/src/components/ServiceProject/ManageBranch.jsx b/src/components/ServiceProject/ManageBranch.jsx index 2eae8c49..70257710 100644 --- a/src/components/ServiceProject/ManageBranch.jsx +++ b/src/components/ServiceProject/ManageBranch.jsx @@ -1,12 +1,16 @@ -import React from 'react' +import React, { useEffect } from 'react' import { useProjectName } from '../../hooks/useProjects'; import { BranchSchema, defaultBranches } from './BranchSchema'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import Label from '../common/Label'; +import { useCreateBranch, useServiceProjects, useUpdateBranch } from '../../hooks/useServiceProject'; +import { useAppForm } from '../../hooks/appHooks/useAppForm'; +import { useParams } from 'react-router-dom'; -const ManageBranch = ({ closeModal,BranchToEdit = null }) => { - +const ManageBranch = ({ closeModal, BranchToEdit = null }) => { + const { data } = {} + const { projectId } = useParams(); const schema = BranchSchema(); const { register, @@ -16,23 +20,50 @@ const ManageBranch = ({ closeModal,BranchToEdit = null }) => { setValue, reset, formState: { errors }, - } = useForm({ + } = useAppForm({ resolver: zodResolver(schema), - defaultValues: defaultBranches, + defaultValues: { + ...defaultBranches, + projectId: projectId || "" + }, }); - const { - projectNames, - loading: projectLoading, - error, - isError: isProjectError, - } = useProjectName(); - const handleClose = () => { reset(); closeModal(); }; + useEffect(() => { + if (BranchToEdit && data) { + reset({ + branchName: data.branchName || "", + projectId: data.project?.id || projectId || "", + contactInformation: data.contactInformation || "", + address: data.address || "", + branchType: data.branchType || "", + googleMapUrl: data.googleMapUrl || "", + }); + } + }, [data, reset]); + + const { mutate: CreateServiceBranch, isPending: createPending } = + useCreateBranch(() => { + handleClose(); + }); + const { mutate: ServiceBranchUpdate, isPending } = + useUpdateBranch(() => handleClose()); + + const onSubmit = (fromdata) => { + let payload = { + ...fromdata, projectId, + }; + if (BranchToEdit) { + const editPayload = { ...payload, id: data.id }; + ServiceBranchUpdate({ id: data.id, payload: editPayload }); + } else { + CreateServiceBranch(payload); + } + }; return (
    @@ -41,50 +72,24 @@ const ManageBranch = ({ closeModal,BranchToEdit = null }) => { ? "Update Branch" : "Create Branch"} -
    +
    +
    -
    - -
    - - - {errors.branchName && ( - {errors.branchName.message} - )} -
    -
    - -
    +
    + +
    +
    -
    - - -
    +
    + + +
    +
    -
    - - - )) - ) : ( - - + + + setManageServiceBranch({ + IsOpen: true, + branchId: branch.id, + }) + } + > + - )} - */} + )) + ) : ( + + + No Branch Found + + + )} + */} + + {/* )} */} {/* {!filteredData || @@ -158,10 +139,16 @@ const ServiceBranch = () => { closeModal={() => setManageServiceBranch({ IsOpen: null, branchId: null }) } - // requestToEdit={ManageServiceBranch.branchId} + // requestToEdit={ManageServiceBranch.branchId} /> )} + + {/* */}
    ) diff --git a/src/hooks/useServiceProject.jsx b/src/hooks/useServiceProject.jsx index b0b98719..3dfd4115 100644 --- a/src/hooks/useServiceProject.jsx +++ b/src/hooks/useServiceProject.jsx @@ -59,8 +59,8 @@ export const useCreateServiceProject = (onSuccessCallback) => { onError: (error) => { showToast( error?.response?.data?.message || - error.message || - "Failed to delete task", + error.message || + "Failed to delete task", "error" ); }, @@ -84,8 +84,8 @@ export const useUpdateServiceProject = (onSuccessCallback) => { onError: (error) => { showToast( error?.response?.data?.message || - error.message || - "Failed to update project", + error.message || + "Failed to update project", "error" ); }, @@ -110,8 +110,8 @@ export const useActiveInActiveServiceProject = (onSuccessCallback) => { onError: (error) => { showToast( error?.response?.data?.message || - error.message || - "Failed to update project", + error.message || + "Failed to update project", "error" ); }, @@ -138,8 +138,8 @@ export const useAllocationServiceProjectTeam = (onSuccessCallback) => { onError: (error) => { showToast( error?.response?.data?.message || - error.message || - "Failed to update project", + error.message || + "Failed to update project", "error" ); }, @@ -223,8 +223,8 @@ export const useAddCommentJob = (onSuccessCallback) => { onError: (error) => { showToast( error?.response?.data?.message || - error.message || - "Failed to update project", + error.message || + "Failed to update project", "error" ); }, @@ -247,8 +247,8 @@ export const useCreateServiceProjectJob = (onSuccessCallback) => { onError: (error) => { showToast( error?.response?.data?.message || - error.message || - "Failed to update project", + error.message || + "Failed to update project", "error" ); }, @@ -273,8 +273,8 @@ export const useUpdateServiceProjectJob = (onSuccessCallback) => { onError: (error) => { showToast( error?.response?.data?.message || - error.message || - "Failed to update project", + error.message || + "Failed to update project", "error" ); }, @@ -314,65 +314,64 @@ export const useBranches = ( }); }; -export const useBranch = (id)=>{ +export const useBranch = (id) => { return useQuery({ - queryKey:["branch",id], - queryFn:async()=>{ + queryKey: ["branch", id], + queryFn: async () => { const resp = await ServiceProjectRepository.GetBranchDetail(id); return resp.data ?? resp; }, - enabled:!!id + enabled: !!id }) } -export const useCreateBranche =()=>{ +export const useCreateBranch = (onSuccessCallBack) => { const queryClient = useQueryClient(); return useMutation({ - mutationFn:async(payload)=> await ServiceProjectRepository.CreateBranch(payload), - onSuccess: (data, variables) => { - queryClient.invalidateQueries({ - queryKey: ["branches"], - }); - if (onSuccessCallback) onSuccessCallback(); - showToast("Branch created successfully", "success"); + mutationFn: async (payload) => { + await ServiceProjectRepository.CreateBranch(payload); + }, + + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ queryKey: ["branches"] }); + showToast("Branches Created Successfully", "success"); + if (onSuccessCallBack) onSuccessCallBack(); }, onError: (error) => { showToast( - error?.response?.data?.message || - error.message || - "Failed to create branch", + error.message || "Something went wrong please try again !", "error" ); }, - }) -} + }); +}; -export const useUpdateBranch=()=>{ +export const useUpdateBranch = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn:async({id,payload})=> await ServiceProjectRepository.UpdateBranch(id,payload), - onSuccess: (_,variables) => { + mutationFn: async ({ id, payload }) => await ServiceProjectRepository.UpdateBranch(id, payload), + onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: ["branches"] }); - queryClient.invalidateQueries({ queryKey: ["branch",variables.id] }); + queryClient.invalidateQueries({ queryKey: ["branch", variables.id] }); if (onSuccessCallback) onSuccessCallback(); showToast("Branch Updated successfully", "success"); }, onError: (error) => { showToast( error?.response?.data?.message || - error.message || - "Failed to update branch", + error.message || + "Failed to update branch", "error" ); }, }) } -export const useDeleteBranch=()=>{ +export const useDeleteBranch = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn:async(id)=> await ServiceProjectRepository.DeleteBranch(id), - onSuccess: (_,variables) => { + mutationFn: async (id) => await ServiceProjectRepository.DeleteBranch(id), + onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: ["branches"] }); if (onSuccessCallback) onSuccessCallback(); showToast("Branch Deleted successfully", "success"); @@ -380,8 +379,8 @@ export const useDeleteBranch=()=>{ onError: (error) => { showToast( error?.response?.data?.message || - error.message || - "Failed to delete branch", + error.message || + "Failed to delete branch", "error" ); }, diff --git a/src/repositories/ServiceProjectRepository.jsx b/src/repositories/ServiceProjectRepository.jsx index b61505cd..e838a04b 100644 --- a/src/repositories/ServiceProjectRepository.jsx +++ b/src/repositories/ServiceProjectRepository.jsx @@ -41,16 +41,16 @@ export const ServiceProjectRepository = { //#endregion //#region Project Branch - CreateBranch: (data) => api.post(`/api/ServiceProject/create`, data), + CreateBranch: (data) => api.post(`/api/ServiceProject/branch/create`, data), UpdateBranch: (id, data) => api.put("/api/ServiceProject/branch/edit/${id}", data), GetBranchList: (projectId, isActive, pageSize, pageNumber, searchString) => { api.get( - `/api/ServiceProject/branch/list/${projectId}&isActive=${isActive}&pageSize=${pageSize}&pageNumber=${pageNumber}&searchString=${searchString}` + `/api/ServiceProject/branch/list/${projectId}/?isActive=${isActive}&pageSize=${pageSize}&pageNumber=${pageNumber}&searchString=${searchString}` ); - }, + }, + GetBranchDetail: (id) => api.get(`/api/ServiceProject/branch/details/${id}`), DeleteBranch: (id) => api.delete(`/api/ServiceProject/branch/delete/${id}`), }; - From 7e4dffff34ea1d2b01aefb28f029440b02e5e703 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 19 Nov 2025 18:47:30 +0530 Subject: [PATCH 18/41] Implementing Get api. --- .../ServiceProject/ServiceBranch.jsx | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/components/ServiceProject/ServiceBranch.jsx b/src/components/ServiceProject/ServiceBranch.jsx index 982b4ab4..fc0bf1ac 100644 --- a/src/components/ServiceProject/ServiceBranch.jsx +++ b/src/components/ServiceProject/ServiceBranch.jsx @@ -22,12 +22,11 @@ const ServiceBranch = () => { const { data, isLoading, isError, error } = useBranches( projectId, - debouncedSearch || null, - null, + true, + ITEMS_PER_PAGE, currentPage, - ITEMS_PER_PAGE + debouncedSearch, ); - const paginate = (page) => { if (page >= 1 && page <= (data?.totalPages ?? 1)) { setCurrentPage(page); @@ -68,30 +67,38 @@ const ServiceBranch = () => {
    {/* {Array.isArray(filteredData) && filteredData.length > 0 && ( */} - +
    {ServiceBranch.map((col) => ( - ))} - - {/* + {isLoading ? ( + ) : isError ? ( + + + ) : data?.data?.length > 0 ? ( data.data.map((branch) => ( - - - + + {ServiceBranch.map((col) => ( + + ))} )) @@ -112,9 +119,7 @@ const ServiceBranch = () => { )} - */} - - +
    + {col.label} Action
    Loading...
    + {error?.message || "Error loading branches"} +
    {branch.branchName}
    + {col.getValue(branch)} + { branchId: branch.id, }) } - > + />
    {/* )} */} {/* {!filteredData || From e7fddd41c2fb0f1606ecf6578ba7ee089cf47be2 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Wed, 19 Nov 2025 21:28:01 +0530 Subject: [PATCH 19/41] added branch for projects --- .../PaymentRequest/ActionPaymentRequest.jsx | 1 - .../Infrastructure/EditActivityModal.jsx | 1 - src/components/ServiceProject/BranchSchema.js | 43 ---- .../ServiceProject/ManageBranch.jsx | 186 ------------------ src/components/ServiceProject/ManageJob.jsx | 11 +- .../ServiceProject/ManageJobTicket.jsx | 10 +- .../ServiceProject/ServiceBranch.jsx | 162 --------------- .../ServiceProject/ServiceProfile.jsx | 95 +++++++++ .../ServiceProjectBranch/ManageBranch.jsx | 183 +++++++++++++++++ .../ServiceProjectBranch/ServiceBranch.jsx | 174 ++++++++++++++++ .../ServiceProject/ServiceProjectProfile.jsx | 108 ++-------- .../ServiceProject/ServiceProjectSchema.jsx | 55 ++++++ src/components/Tenant/SubScriptionHistory.jsx | 3 +- src/components/Tenant/TenantForm.jsx | 2 - .../common/Forms/SelectFieldServerSide.jsx | 40 +++- .../common/GlobalModal/CommentEditor.jsx | 1 - src/hooks/useProjects.js | 1 - src/hooks/useServiceProject.jsx | 1 - 18 files changed, 571 insertions(+), 506 deletions(-) delete mode 100644 src/components/ServiceProject/BranchSchema.js delete mode 100644 src/components/ServiceProject/ManageBranch.jsx delete mode 100644 src/components/ServiceProject/ServiceBranch.jsx create mode 100644 src/components/ServiceProject/ServiceProfile.jsx create mode 100644 src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx create mode 100644 src/components/ServiceProject/ServiceProjectBranch/ServiceBranch.jsx diff --git a/src/components/PaymentRequest/ActionPaymentRequest.jsx b/src/components/PaymentRequest/ActionPaymentRequest.jsx index 5f3cc1d9..52222b55 100644 --- a/src/components/PaymentRequest/ActionPaymentRequest.jsx +++ b/src/components/PaymentRequest/ActionPaymentRequest.jsx @@ -40,7 +40,6 @@ const ActionPaymentRequest = ({ requestId }) => { error: PaymentModeError, } = usePaymentMode(); - console.log("Kartik", data) const IsReview = useHasUserPermission(REVIEW_EXPENSE); const [imageLoaded, setImageLoaded] = useState({}); diff --git a/src/components/Project/Infrastructure/EditActivityModal.jsx b/src/components/Project/Infrastructure/EditActivityModal.jsx index e57b6a30..14e66020 100644 --- a/src/components/Project/Infrastructure/EditActivityModal.jsx +++ b/src/components/Project/Infrastructure/EditActivityModal.jsx @@ -82,7 +82,6 @@ const EditActivityModal = ({ useEffect(() => { if (!workItem) return; - console.log(workItem) reset({ activityID: String( workItem?.workItem?.activityId || workItem?.activityMaster?.id diff --git a/src/components/ServiceProject/BranchSchema.js b/src/components/ServiceProject/BranchSchema.js deleted file mode 100644 index 346654d3..00000000 --- a/src/components/ServiceProject/BranchSchema.js +++ /dev/null @@ -1,43 +0,0 @@ -import { z } from "zod"; - -export const BranchSchema = () => - z.object({ - projectId: z - .string() - .trim() - .min(1, { message: "Project is required" }), - - branchName: z - .string() - .trim() - .min(1, { message: "Branch Name is required" }), - - contactInformation: z - .string() - .trim() - .min(1, { message: "Contact Information is required" }), - - address: z - .string() - .trim() - .min(1, { message: "Address is required" }), - - branchType: z - .string() - .trim() - .min(1, { message: "Branch Type is required" }), - - googleMapUrl: z - .string() - .trim() - .url({ message: "Enter a valid Google Map URL" }), - }); - -export const defaultBranches = { - branchName: "", - projectId: "", - contactInformation: "", - address: "", - branchType: "", - googleMapUrl: "", -}; \ No newline at end of file diff --git a/src/components/ServiceProject/ManageBranch.jsx b/src/components/ServiceProject/ManageBranch.jsx deleted file mode 100644 index 70257710..00000000 --- a/src/components/ServiceProject/ManageBranch.jsx +++ /dev/null @@ -1,186 +0,0 @@ -import React, { useEffect } from 'react' -import { useProjectName } from '../../hooks/useProjects'; -import { BranchSchema, defaultBranches } from './BranchSchema'; -import { useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; -import Label from '../common/Label'; -import { useCreateBranch, useServiceProjects, useUpdateBranch } from '../../hooks/useServiceProject'; -import { useAppForm } from '../../hooks/appHooks/useAppForm'; -import { useParams } from 'react-router-dom'; - -const ManageBranch = ({ closeModal, BranchToEdit = null }) => { - const { data } = {} - const { projectId } = useParams(); - const schema = BranchSchema(); - const { - register, - control, - watch, - handleSubmit, - setValue, - reset, - formState: { errors }, - } = useAppForm({ - resolver: zodResolver(schema), - defaultValues: { - ...defaultBranches, - projectId: projectId || "" - }, - }); - - const handleClose = () => { - reset(); - closeModal(); - }; - - useEffect(() => { - if (BranchToEdit && data) { - reset({ - branchName: data.branchName || "", - projectId: data.project?.id || projectId || "", - contactInformation: data.contactInformation || "", - address: data.address || "", - branchType: data.branchType || "", - googleMapUrl: data.googleMapUrl || "", - }); - } - }, [data, reset]); - - const { mutate: CreateServiceBranch, isPending: createPending } = - useCreateBranch(() => { - handleClose(); - }); - const { mutate: ServiceBranchUpdate, isPending } = - useUpdateBranch(() => handleClose()); - - const onSubmit = (fromdata) => { - let payload = { - ...fromdata, projectId, - }; - if (BranchToEdit) { - const editPayload = { ...payload, id: data.id }; - ServiceBranchUpdate({ id: data.id, payload: editPayload }); - } else { - CreateServiceBranch(payload); - } - }; - - return ( -
    -
    - {BranchToEdit - ? "Update Branch" - : "Create Branch"} -
    - -
    - -
    - - - {errors.branchName && ( - {errors.branchName.message} - )} -
    -
    - - - {errors.contactInformation && ( - {errors.contactInformation.message} - )} -
    -
    - -
    - - -
    - - - {errors.address && ( - {errors.address.message} - )} -
    -
    - - - {errors.branchType && ( - {errors.branchType.message} - )} -
    -
    - - -
    - - -
    - - - {errors.googleMapUrl && ( - {errors.googleMapUrl.message} - )} -
    -
    - -
    - - - -
    - - - - -
    - ) -} - -export default ManageBranch diff --git a/src/components/ServiceProject/ManageJob.jsx b/src/components/ServiceProject/ManageJob.jsx index 97cd1a97..6ec96e3b 100644 --- a/src/components/ServiceProject/ManageJob.jsx +++ b/src/components/ServiceProject/ManageJob.jsx @@ -41,7 +41,8 @@ const ManageJob = ({ Job }) => { control, watch, handleSubmit, - reset,setValue, + reset, + setValue, formState: { errors }, } = methods; @@ -164,6 +165,7 @@ const ManageJob = ({ Job }) => { dueDate: JobData.dueDate ?? null, tags: JobData.tags ?? [], statusId: JobData.status.id, + branchId : JobData?.projectBranch?.id }); }, [JobData, Job, projectId]); return ( @@ -201,7 +203,7 @@ const ManageJob = ({ Job }) => { />
    - + { name="tags" label="Tag" placeholder="Enter Tag" - />
    setValue("branchId", val)} valueKey="id" labelKey="branchName" - hookParams={[projectId,true,10,1]} + hookParams={[projectId, true, 10, 1]} useFetchHook={useBranches} isMultiple={false} + disabled={Job} />
    diff --git a/src/components/ServiceProject/ManageJobTicket.jsx b/src/components/ServiceProject/ManageJobTicket.jsx index 9dbf0b9f..c589509c 100644 --- a/src/components/ServiceProject/ManageJobTicket.jsx +++ b/src/components/ServiceProject/ManageJobTicket.jsx @@ -124,10 +124,16 @@ const ManageJobTicket = ({ Job }) => { ); })()}
    +
    + + {" "} + Branch Name : + +
    - Created By + Created By
    { {data?.assignees?.length > 0 && (
    - Assigned To + Assigned To
    diff --git a/src/components/ServiceProject/ServiceBranch.jsx b/src/components/ServiceProject/ServiceBranch.jsx deleted file mode 100644 index fc0bf1ac..00000000 --- a/src/components/ServiceProject/ServiceBranch.jsx +++ /dev/null @@ -1,162 +0,0 @@ -import React, { useState } from 'react' -import GlobalModel from '../common/GlobalModel'; -import ManageBranch from './ManageBranch'; -import { Pagination } from 'swiper/modules'; -import { useBranches } from '../../hooks/useServiceProject'; -import { ITEMS_PER_PAGE } from '../../utils/constants'; -import { useDebounce } from '../../utils/appUtils'; -import { useParams } from 'react-router-dom'; - -const ServiceBranch = () => { - - const [ManageServiceBranch, setManageServiceBranch] = useState({ - IsOpen: null, - branchId: null, - }); - - const { projectId } = useParams(); - - const [search, setSearch] = useState(""); - const [currentPage, setCurrentPage] = useState(1); - const debouncedSearch = useDebounce(search, 500); - - const { data, isLoading, isError, error } = useBranches( - projectId, - true, - ITEMS_PER_PAGE, - currentPage, - debouncedSearch, - ); - const paginate = (page) => { - if (page >= 1 && page <= (data?.totalPages ?? 1)) { - setCurrentPage(page); - } - }; - - const ServiceBranch = [ - { - key: "branchName", - label: "Branch Name", - align: "text-start", - getValue: (e) => e?.branchName || "N/A", - }, - ]; - return ( -
    -
    -
    -
    - -
    -
    - - -
    - {/* {Array.isArray(filteredData) && filteredData.length > 0 && ( */} - - - - {ServiceBranch.map((col) => ( - - ))} - - - - - {isLoading ? ( - - - - ) : isError ? ( - - - - ) : data?.data?.length > 0 ? ( - data.data.map((branch) => ( - - {ServiceBranch.map((col) => ( - - ))} - - - )) - ) : ( - - - - )} - -
    - {col.label} - Action
    - Loading... -
    - {error?.message || "Error loading branches"} -
    - {col.getValue(branch)} - - - setManageServiceBranch({ - IsOpen: true, - branchId: branch.id, - }) - } - /> -
    - No Branch Found -
    - {/* )} */} - {/* {!filteredData || - filteredData.length === 0 - && ( -
    - {isError ? (

    {error.message}

    ) : (

    No Recurring Expense Found

    )} -
    - )} */} -
    - - {ManageServiceBranch.IsOpen && ( - - setManageServiceBranch({ IsOpen: null, branchId: null }) - } - > - - setManageServiceBranch({ IsOpen: null, branchId: null }) - } - // requestToEdit={ManageServiceBranch.branchId} - /> - - )} - - {/* */} -
    -
    - ) -} - -export default ServiceBranch diff --git a/src/components/ServiceProject/ServiceProfile.jsx b/src/components/ServiceProject/ServiceProfile.jsx new file mode 100644 index 00000000..30864106 --- /dev/null +++ b/src/components/ServiceProject/ServiceProfile.jsx @@ -0,0 +1,95 @@ +import React from 'react' +import { formatUTCToLocalTime } from '../../utils/dateUtils' + +const ServiceProfile = ({data,setIsOpenModal}) => { + return ( +
    +
    +
    + {" "} + + Project Profile +
    +
    +
    +
      + +
    • +
      + + Name: +
      + +
      + {data.name} +
      +
    • +
    • +
      + + Nick Name: +
      + {data.shortName} +
    • +
    • +
      + + Assign Date: +
      + + {data.assignedDate ? formatUTCToLocalTime(data.assignedDate) : "NA"} + +
    • + +
    • +
      + + Status: +
      + {data?.status.status} +
    • +
    • +
      + + Contact: +
      + {data.contactName} +
    • +
    • + {/* Label section with icon */} +
      + + Address: +
      + + {/* Content section that wraps nicely */} +
      + {data.address} +
      +
    • + + + +
    • + + + + + +
    • +
    + +
    +
    + ) +} + +export default ServiceProfile diff --git a/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx b/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx new file mode 100644 index 00000000..34cc0530 --- /dev/null +++ b/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx @@ -0,0 +1,183 @@ +import React, { useEffect } from "react"; +import { useProjectName } from "../../../hooks/useProjects"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import Label from "../../common/Label"; +import { + useCreateBranch, + useServiceProjects, + useUpdateBranch, +} from "../../../hooks/useServiceProject"; +import { useAppForm } from "../../../hooks/appHooks/useAppForm"; +import { useParams } from "react-router-dom"; +import { BranchSchema, defaultBranches } from "../ServiceProjectSchema"; + +const ManageBranch = ({ closeModal, BranchToEdit = null }) => { + const { data } = {}; + const { projectId } = useParams(); + const schema = BranchSchema(); + const { + register, + control, + watch, + handleSubmit, + setValue, + reset, + formState: { errors }, + } = useAppForm({ + resolver: zodResolver(schema), + defaultValues: { + ...defaultBranches, + projectId: projectId || "", + }, + }); + + const handleClose = () => { + reset(); + closeModal(); + }; + + useEffect(() => { + if (BranchToEdit && data) { + reset({ + branchName: data.branchName || "", + projectId: data.project?.id || projectId || "", + contactInformation: data.contactInformation || "", + address: data.address || "", + branchType: data.branchType || "", + googleMapUrl: data.googleMapUrl || "", + }); + } + }, [data, reset]); + + const { mutate: CreateServiceBranch, isPending: createPending } = + useCreateBranch(() => { + handleClose(); + }); + const { mutate: ServiceBranchUpdate, isPending } = useUpdateBranch(() => + handleClose() + ); + + const onSubmit = (fromdata) => { + let payload = { + ...fromdata, + projectId, + }; + if (BranchToEdit) { + const editPayload = { ...payload, id: data.id }; + ServiceBranchUpdate({ id: data.id, payload: editPayload }); + } else { + CreateServiceBranch(payload); + } + }; + + return ( +
    +
    + {BranchToEdit ? "Update Branch" : "Create Branch"} +
    +
    +
    +
    + + + {errors.branchName && ( + {errors.branchName.message} + )} +
    +
    + + + {errors.contactInformation && ( + + {errors.contactInformation.message} + + )} +
    +
    + +
    + +
    + + + {errors.branchType && ( + {errors.branchType.message} + )} +
    +
    + + + {errors.googleMapUrl && ( + + {errors.googleMapUrl.message} + + )} +
    +
    + +
    + +
    + + + + {errors?.comment && ( + {errors?.comment?.message} + )} +
    +
    + {/*
    + {files?.length > 0 && ( + + )} +
    */} +
    +
    document.getElementById("attachments").click()} + className="cursor-pointer" + style={{ whiteSpace: "nowrap" }} + > + { + onFileChange(e); + e.target.value = ""; + }} + /> + + Add Attachment +
    + + +
    +
    +
    + ); +}; + +export default UpdateJobComment; diff --git a/src/components/ServiceProject/ServiceProjectSeketon.jsx b/src/components/ServiceProject/ServiceProjectSeketon.jsx new file mode 100644 index 00000000..b4b3a93e --- /dev/null +++ b/src/components/ServiceProject/ServiceProjectSeketon.jsx @@ -0,0 +1,138 @@ +import React from "react"; + +const SkeletonLine = ({ height = 18, width = "100%", className = "" }) => ( +
    +); + +export const BranchDetailsSkeleton = () => { + return ( +
    +
    + +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + + +
    +
    +
    + ); +}; + +export const JobDetailsSkeleton = () => { + return ( +
    +
    + {/* Title */} + + + {/* Job ID + Status */} +
    + +
    + + +
    +
    + + {/* Description */} + + + {/* Created Date */} +
    + +
    + + {/* Start / Due Date */} +
    + + +
    + + {/* Branch Name */} +
    + + +
    + + {/* Created By */} +
    +
    + +
    + +
    + {/* Avatar */} + +
    +
    + + {/* Assigned To */} +
    +
    + +
    +
    + + {/* Tabs */} +
    +
    + + + +
    + + +
    +
    +
    + ); +}; diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index 1c95ec64..3ef8131b 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -60,7 +60,7 @@ import PaymentRequestPage from "../pages/PaymentRequest/PaymentRequestPage"; import RecurringExpensePage from "../pages/RecurringExpense/RecurringExpensePage"; import AdvancePaymentPage from "../pages/AdvancePayment/AdvancePaymentPage"; import ServiceProjectDetail from "../pages/ServiceProject/ServiceProjectDetail"; -import ManageJob from "../components/ServiceProject/ManageJob"; +import ManageJob from "../components/ServiceProject/ServiceProjectJob/ManageJob"; const router = createBrowserRouter( [ { From 5b86a2f64f5c27b1d721cf4db88b40ea74906b0a Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Thu, 20 Nov 2025 09:50:49 +0530 Subject: [PATCH 22/41] removed console error --- src/components/Layout/Sidebar.jsx | 10 ++-- .../ServiceProjectJob/ManageJobTicket.jsx | 59 +------------------ 2 files changed, 8 insertions(+), 61 deletions(-) diff --git a/src/components/Layout/Sidebar.jsx b/src/components/Layout/Sidebar.jsx index 1f4b63b8..4fa7cd9f 100644 --- a/src/components/Layout/Sidebar.jsx +++ b/src/components/Layout/Sidebar.jsx @@ -25,8 +25,7 @@ const Sidebar = () => { /> */} - @@ -35,12 +34,13 @@ const Sidebar = () => { OnField Work .com - + - + + - +
    diff --git a/src/components/ServiceProject/ServiceProjectJob/ManageJobTicket.jsx b/src/components/ServiceProject/ServiceProjectJob/ManageJobTicket.jsx index 1041d93c..12428c77 100644 --- a/src/components/ServiceProject/ServiceProjectJob/ManageJobTicket.jsx +++ b/src/components/ServiceProject/ServiceProjectJob/ManageJobTicket.jsx @@ -145,62 +145,9 @@ const ManageJobTicket = ({ Job }) => {
    )} -
    -
    -
    - Created By -
    -
    - {" "} -
    -

    {`${data?.createdBy?.firstName} ${data?.createdBy?.lastName}`}

    - - ({data?.createdBy?.jobRoleName}) - -
    -
    -
    - - {data?.assignees?.length > 0 && ( -
    -
    - Assigned To -
    - -
    -
    - {data?.assignees?.map((emp) => ( -
    -
    - - -
    - - {emp.firstName} {emp.lastName} - - - {emp.jobRoleName} - -
    -
    -
    - ))} -
    -
    -
    - )} -
    +
    + People +
    From dd716187da267263557fac390a222807ce6e58bf Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Thu, 20 Nov 2025 09:51:23 +0530 Subject: [PATCH 23/41] Implementing edit api for branches. --- .../ServiceProjectBranch/ManageBranch.jsx | 17 +- .../ServiceProjectBranch/ServiceBranch.jsx | 320 ++++++++++++------ src/hooks/useServiceProject.jsx | 51 +-- src/repositories/ServiceProjectRepository.jsx | 10 +- 4 files changed, 255 insertions(+), 143 deletions(-) diff --git a/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx b/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx index 34cc0530..11bcdafd 100644 --- a/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx +++ b/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx @@ -4,6 +4,7 @@ import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import Label from "../../common/Label"; import { + useBranchDetails, useCreateBranch, useServiceProjects, useUpdateBranch, @@ -13,7 +14,13 @@ import { useParams } from "react-router-dom"; import { BranchSchema, defaultBranches } from "../ServiceProjectSchema"; const ManageBranch = ({ closeModal, BranchToEdit = null }) => { - const { data } = {}; + const { + data, + isLoading, + isError, + error: requestError, + } = useBranchDetails(BranchToEdit); + const { projectId } = useParams(); const schema = BranchSchema(); const { @@ -112,7 +119,7 @@ const ManageBranch = ({ closeModal, BranchToEdit = null }) => {
    - +
    -
    +
    @@ -146,7 +153,7 @@ const ManageBranch = ({ closeModal, BranchToEdit = null }) => {
    - +
    diff --git a/src/components/ServiceProject/ServiceProjectBranch/ServiceBranch.jsx b/src/components/ServiceProject/ServiceProjectBranch/ServiceBranch.jsx index 0cae102a..e168778b 100644 --- a/src/components/ServiceProject/ServiceProjectBranch/ServiceBranch.jsx +++ b/src/components/ServiceProject/ServiceProjectBranch/ServiceBranch.jsx @@ -1,19 +1,24 @@ import React, { useState } from "react"; import GlobalModel from "../../common/GlobalModel"; import ManageBranch from "./ManageBranch"; -import { useBranches } from "../../../hooks/useServiceProject"; +import { useBranches, useDeleteBranch } from "../../../hooks/useServiceProject"; import { ITEMS_PER_PAGE } from "../../../utils/constants"; import { useDebounce } from "../../../utils/appUtils"; import { useParams } from "react-router-dom"; import Pagination from "../../common/Pagination"; +import ConfirmModal from "../../common/ConfirmModal"; +import { SpinnerLoader } from "../../common/Loader"; const ServiceBranch = () => { const { projectId } = useParams(); - + const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [showInactive, setShowInactive] = useState(false); const [manageState, setManageState] = useState({ IsOpen: false, branchId: null, }); + const { mutate: DeleteBranch, isPending } = useDeleteBranch(); + const [deletingId, setDeletingId] = useState(null); const [search, setSearch] = useState(""); const [currentPage, setCurrentPage] = useState(1); @@ -21,7 +26,8 @@ const ServiceBranch = () => { const { data, isLoading, isError, error } = useBranches( projectId, - true, + // true, + !showInactive, ITEMS_PER_PAGE - 10, currentPage, debouncedSearch @@ -42,132 +48,222 @@ const ServiceBranch = () => { }, ]; + const handleDelete = (id) => { + setDeletingId(id); + DeleteBranch( + { id, isActive: false }, + { + onSettled: () => { + setDeletingId(null); + setIsDeleteModalOpen(false); + }, + } + ); + }; return ( -
    -
    - {/* Header Section */} -
    -
    -
    - - Branches -
    + <> + {IsDeleteModalOpen && ( + setIsDeleteModalOpen(false)} + loading={isPending} + paramData={deletingId} + /> + )} +
    +
    + {/* Header Section */} +
    +
    +
    + + Branch +
    +
    + + {/* Flex container for toggle + button */} +
    +
    + + {/* Toggle Switch */} +
    + setShowInactive(!showInactive)} + /> + +
    + + {/* Add Branch Button */} + + +
    +
    +
    -
    - -
    -
    - -
    - - - - {columns.map((col) => ( - - ))} - - - - - - {isLoading && ( +
    +
    - {col.label} - Action
    + - + {columns.map((col) => ( + + ))} + - )} + - {isError && ( - - - - )} - - {!isLoading && - !isError && - data?.data?.length > 0 && - data.data.map((branch) => ( - - {columns.map((col) => ( - - ))} - - - ))} - - {!isLoading && - !isError && - (!data?.data || data.data.length === 0) && ( + + {isLoading && ( )} - -
    - Loading... - + {col.label} + Action
    - {error?.message || "Error loading branches"} -
    - {col.getValue(branch)} - - - setManageState({ - IsOpen: true, - branchId: branch.id, - }) - } - /> -
    - No Branch Found +
    + +
    - {data?.data?.length > 0 && ( - + + {isError && ( + + + {error?.message || "Error loading branches"} + + + )} + + {!isLoading && + !isError && + data?.data?.length > 0 && + data.data.map((branch) => ( + + {columns.map((col) => ( + + {col.getValue(branch)} + + ))} + +
    + + +
      + {/* Modify */} +
    • + setManageState({ + IsOpen: true, + branchId: branch.id, + }) + } + > + + + Modify + +
    • + + {/* Delete */} +
    • { + setIsDeleteModalOpen(true); + setDeletingId(branch.id); + }} + > + + + Delete + +
    • +
    +
    + + + + ))} + + {!isLoading && + !isError && + (!data?.data || data.data.length === 0) && ( + + + No Branch Found + + + )} + + + + {data?.data?.length > 0 && ( + + )} +
    + + {manageState.IsOpen && ( + setManageState({ IsOpen: false, branchId: null })} + > + + setManageState({ IsOpen: false, branchId: null }) + } + /> + + )}
    - - {manageState.IsOpen && ( - setManageState({ IsOpen: false, branchId: null })} - > - - setManageState({ IsOpen: false, branchId: null }) - } - /> - - )}
    -
    + ); }; diff --git a/src/hooks/useServiceProject.jsx b/src/hooks/useServiceProject.jsx index 3278fa2a..93817800 100644 --- a/src/hooks/useServiceProject.jsx +++ b/src/hooks/useServiceProject.jsx @@ -315,12 +315,12 @@ export const useBranches = ( }; -export const useBranch = (id)=>{ +export const useBranchDetails = (id) => { return useQuery({ queryKey: ["branch", id], queryFn: async () => { const resp = await ServiceProjectRepository.GetBranchDetail(id); - return resp.data ?? resp; + return resp.data; }, enabled: !!id }) @@ -346,37 +346,42 @@ export const useCreateBranch = (onSuccessCallBack) => { }, }); }; - -export const useUpdateBranch = () => { +export const useUpdateBranch = (onSuccessCallBack) => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ id, payload }) => await ServiceProjectRepository.UpdateBranch(id, payload), + mutationFn: async ({ id, payload }) => + await ServiceProjectRepository.UpdateBranch(id, payload), + onSuccess: (_, variables) => { + // remove old single-branch cache + queryClient.removeQueries({ queryKey: ["branch", variables.id] }); + + // refresh list queryClient.invalidateQueries({ queryKey: ["branches"] }); - queryClient.invalidateQueries({ queryKey: ["branch", variables.id] }); - if (onSuccessCallback) onSuccessCallback(); - showToast("Branch Updated successfully", "success"); + + showToast("Branch updated successfully", "success"); + onSuccessCallBack?.(); }, - onError: (error) => { - showToast( - error?.response?.data?.message || - error.message || - "Failed to update branch", - "error" - ); + + onError: () => { + showToast("Something went wrong. Please try again later.", "error"); }, - }) -} + }); +}; + export const useDeleteBranch = () => { const queryClient = useQueryClient(); + return useMutation({ - mutationFn: async (id) => await ServiceProjectRepository.DeleteBranch(id), - onSuccess: (_, variables) => { + mutationFn: async ({ id, isActive}) => + await ServiceProjectRepository.DeleteBranch(id, isActive), + + onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["branches"] }); - if (onSuccessCallback) onSuccessCallback(); - showToast("Branch Deleted successfully", "success"); + showToast("Branch deleted successfully", "success"); }, + onError: (error) => { showToast( error?.response?.data?.message || @@ -385,5 +390,5 @@ export const useDeleteBranch = () => { "error" ); }, - }) -} \ No newline at end of file + }); +}; diff --git a/src/repositories/ServiceProjectRepository.jsx b/src/repositories/ServiceProjectRepository.jsx index 1fa29fa9..23fe8b38 100644 --- a/src/repositories/ServiceProjectRepository.jsx +++ b/src/repositories/ServiceProjectRepository.jsx @@ -1,3 +1,4 @@ +import { isAction } from "@reduxjs/toolkit"; import { api } from "../utils/axiosClient"; export const ServiceProjectRepository = { @@ -43,14 +44,17 @@ export const ServiceProjectRepository = { //#region Project Branch CreateBranch: (data) => api.post(`/api/ServiceProject/branch/create`, data), UpdateBranch: (id, data) => - api.put("/api/ServiceProject/branch/edit/${id}", data), + api.put(`/api/ServiceProject/branch/edit/${id}`, data), + GetBranchList: (projectId, isActive, pageSize, pageNumber, searchString) => { return api.get( `/api/ServiceProject/branch/list/${projectId}?isActive=${isActive}&pageSize=${pageSize}&pageNumber=${pageNumber}&searchString=${searchString}` ); - }, + }, GetBranchDetail: (id) => api.get(`/api/ServiceProject/branch/details/${id}`), - DeleteBranch: (id) => api.delete(`/api/ServiceProject/branch/delete/${id}`), + DeleteBranch: (id, isActive = false) => + api.delete(`/api/ServiceProject/branch/delete/${id}?isActive=${isActive}`), + }; From 195a0c83bb8a0fea2bb634cb6efea183703821d6 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Thu, 20 Nov 2025 10:00:06 +0530 Subject: [PATCH 24/41] Correction in api for details. --- .../ServiceProject/ServiceProjectBranch/BranchDetails.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ServiceProject/ServiceProjectBranch/BranchDetails.jsx b/src/components/ServiceProject/ServiceProjectBranch/BranchDetails.jsx index 66bd700c..2aefa4ad 100644 --- a/src/components/ServiceProject/ServiceProjectBranch/BranchDetails.jsx +++ b/src/components/ServiceProject/ServiceProjectBranch/BranchDetails.jsx @@ -1,5 +1,5 @@ import React, { useState } from "react"; -import { useBranch } from "../../../hooks/useServiceProject"; +import { useBranchDetails } from "../../../hooks/useServiceProject"; import { SpinnerLoader } from "../../common/Loader"; import Error from "../../common/Error"; import { BranchDetailsSkeleton } from "../ServiceProjectSeketon"; @@ -7,7 +7,7 @@ import { BranchDetailsSkeleton } from "../ServiceProjectSeketon"; const BranchDetails = ({ branch }) => { const [copied, setCopied] = useState(false); - const { data, isLoading, isError, error } = useBranch(branch); + const { data, isLoading, isError, error } = useBranchDetails(branch); const googleMapUrl = data?.googleMapUrl || data?.locationLink; From d3c006279c7614ec0f1f377b08e3b61805682071 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Thu, 20 Nov 2025 10:29:42 +0530 Subject: [PATCH 25/41] Increasing the size of Service details page. --- .../ServiceProject/ServiceProjectBranch/ServiceBranch.jsx | 4 +--- src/components/ServiceProject/ServiceProjectProfile.jsx | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/ServiceProject/ServiceProjectBranch/ServiceBranch.jsx b/src/components/ServiceProject/ServiceProjectBranch/ServiceBranch.jsx index e168778b..0655247f 100644 --- a/src/components/ServiceProject/ServiceProjectBranch/ServiceBranch.jsx +++ b/src/components/ServiceProject/ServiceProjectBranch/ServiceBranch.jsx @@ -117,10 +117,8 @@ const ServiceBranch = () => { Add Branch -
    -
    @@ -170,7 +168,7 @@ const ServiceBranch = () => { !isError && data?.data?.length > 0 && data.data.map((branch) => ( - + {columns.map((col) => ( {col.getValue(branch)} diff --git a/src/components/ServiceProject/ServiceProjectProfile.jsx b/src/components/ServiceProject/ServiceProjectProfile.jsx index 2a04be99..6ef359ff 100644 --- a/src/components/ServiceProject/ServiceProjectProfile.jsx +++ b/src/components/ServiceProject/ServiceProjectProfile.jsx @@ -33,11 +33,11 @@ const ServiceProjectProfile = () => { )}
    -
    +
    -
    +
    From d167c57ab01c5fc7210a002ec54b0dde85acad6c Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Thu, 20 Nov 2025 12:34:45 +0530 Subject: [PATCH 26/41] Implementing the Update api for branches. --- .../ServiceProjectBranch/ManageBranch.jsx | 221 +++++++++++++++--- .../ServiceProject/ServiceProjectSchema.jsx | 8 +- 2 files changed, 196 insertions(+), 33 deletions(-) diff --git a/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx b/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx index 11bcdafd..720fa08f 100644 --- a/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx +++ b/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx @@ -21,6 +21,15 @@ const ManageBranch = ({ closeModal, BranchToEdit = null }) => { error: requestError, } = useBranchDetails(BranchToEdit); + const [contacts, setContacts] = React.useState([ + { + contactPerson: "", + designation: "", + contactEmails: [""], + contactNumbers: [""] + } + ]); + const { projectId } = useParams(); const schema = BranchSchema(); const { @@ -49,14 +58,22 @@ const ManageBranch = ({ closeModal, BranchToEdit = null }) => { reset({ branchName: data.branchName || "", projectId: data.project?.id || projectId || "", - contactInformation: data.contactInformation || "", address: data.address || "", branchType: data.branchType || "", googleMapUrl: data.googleMapUrl || "", }); + + if (data.contactInformation) { + try { + setContacts(JSON.parse(data.contactInformation)); + } catch { + setContacts([]); + } + } } }, [data, reset]); + const { mutate: CreateServiceBranch, isPending: createPending } = useCreateBranch(() => { handleClose(); @@ -65,19 +82,23 @@ const ManageBranch = ({ closeModal, BranchToEdit = null }) => { handleClose() ); - const onSubmit = (fromdata) => { + const onSubmit = (formdata) => { let payload = { - ...fromdata, + ...data, + ...formdata, projectId, + contactInformation: JSON.stringify(contacts), // ← important }; + if (BranchToEdit) { - const editPayload = { ...payload, id: data.id }; - ServiceBranchUpdate({ id: data.id, payload: editPayload }); + ServiceBranchUpdate({ id: data.id, payload }); } else { CreateServiceBranch(payload); } }; + + return (
    @@ -100,26 +121,6 @@ const ManageBranch = ({ closeModal, BranchToEdit = null }) => { {errors.branchName.message} )}
    -
    - - - {errors.contactInformation && ( - - {errors.contactInformation.message} - - )} -
    -
    - -
    -
    +
    + +
    + +
    -
    +
    +
    + + + {contacts.map((item, index) => ( +
    + + {/* Contact Person + Designation */} +
    +
    + { + const list = [...contacts]; + list[index].contactPerson = e.target.value; + setContacts(list); + }} + /> +
    + +
    + { + const list = [...contacts]; + list[index].designation = e.target.value; + setContacts(list); + }} + /> +
    + + {/* Remove entire contact */} +
    + + setContacts(contacts.filter((_, i) => i !== index)) + } + > +
    +
    + + {/* Numbers Section */} + + + {item.contactNumbers.map((num, numIndex) => ( +
    + + { + const value = e.target.value.replace(/\D/g, ""); // remove non-digit characters + const list = [...contacts]; + list[index].contactNumbers[numIndex] = value; + setContacts(list); + }} + /> + + {/* Show PLUS only on last row */} + {numIndex === item.contactNumbers.length - 1 ? ( + { + const list = [...contacts]; + list[index].contactNumbers.push(""); + setContacts(list); + }} + > + ) : ( + { + const list = [...contacts]; + list[index].contactNumbers.splice(numIndex, 1); + setContacts(list); + }} + > + )} + +
    + ))} + +
    + + {/* Emails Section */} + + + {item.contactEmails.map((email, emailIndex) => ( +
    + + { + const list = [...contacts]; + list[index].contactEmails[emailIndex] = e.target.value; + setContacts(list); + }} + /> + + {/* Show PLUS only on the last row */} + {emailIndex === item.contactEmails.length - 1 ? ( + { + const list = [...contacts]; + list[index].contactEmails.push(""); + setContacts(list); + }} + > + ) : ( + { + const list = [...contacts]; + list[index].contactEmails.splice(emailIndex, 1); + setContacts(list); + }} + > + )} + +
    + ))} + + +
    + ))} + + + + +
    +
    + +
    diff --git a/src/components/ServiceProject/ServiceProjectSchema.jsx b/src/components/ServiceProject/ServiceProjectSchema.jsx index dc8a6713..589f1497 100644 --- a/src/components/ServiceProject/ServiceProjectSchema.jsx +++ b/src/components/ServiceProject/ServiceProjectSchema.jsx @@ -138,11 +138,7 @@ export const BranchSchema = () => .trim() .min(1, { message: "Branch Name is required" }), - contactInformation: z - .string() - .trim() - .min(1, { message: "Contact Information is required" }), - + contactInformation: z.string().optional(), address: z .string() .trim() @@ -155,8 +151,6 @@ export const BranchSchema = () => googleMapUrl: z .string() - .trim() - .url({ message: "Enter a valid Google Map URL" }), }); export const defaultBranches = { From 047e563505632736383e0955b3d59d83fa9e22a6 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Thu, 20 Nov 2025 12:49:56 +0530 Subject: [PATCH 27/41] added mutiple contact person inside branch --- .../Dashboard/ProjectCompletionChart.jsx | 14 +- .../ServiceProjectBranch/BranchDetails.jsx | 71 ++---- .../ServiceProjectBranch/ServiceBranch.jsx | 76 +++---- .../ServiceProjectJob/JobComments.jsx | 2 +- .../ServiceProjectJob/ManageJobTicket.jsx | 100 +++++++-- src/components/common/HoverPopup.jsx | 38 +++- src/hooks/useDashboard_Data.jsx | 206 +++++------------- src/repositories/GlobalRepository.jsx | 2 + src/repositories/ProjectRepository.jsx | 1 + 9 files changed, 238 insertions(+), 272 deletions(-) diff --git a/src/components/Dashboard/ProjectCompletionChart.jsx b/src/components/Dashboard/ProjectCompletionChart.jsx index b8c656b8..f1f63936 100644 --- a/src/components/Dashboard/ProjectCompletionChart.jsx +++ b/src/components/Dashboard/ProjectCompletionChart.jsx @@ -2,15 +2,19 @@ import React, { useState } from "react"; import HorizontalBarChart from "../Charts/HorizontalBarChart"; import { useProjects } from "../../hooks/useProjects"; import { ITEMS_PER_PAGE } from "../../utils/constants"; +import { useProjectCompletionStatus } from "../../hooks/useDashboard_Data"; const ProjectCompletionChart = () => { - const [currentPage, setCurrentPage] = useState(1); - const { data: projects, isLoading: loading, isError, error } = useProjects(50,currentPage); - // Bar chart logic - const projectNames = projects?.data?.map((p) => p.name) || []; + const { + data: projects, + isLoading: loading, + isError, + error, + } = useProjectCompletionStatus(); + const projectNames = projects?.map((p) => p.name) || []; const projectProgress = - projects?.data?.map((p) => { + projects?.map((p) => { const completed = p.completedWork || 0; const planned = p.plannedWork || 1; const percent = planned ? (completed / planned) * 100 : 0; diff --git a/src/components/ServiceProject/ServiceProjectBranch/BranchDetails.jsx b/src/components/ServiceProject/ServiceProjectBranch/BranchDetails.jsx index 2aefa4ad..87d59b8c 100644 --- a/src/components/ServiceProject/ServiceProjectBranch/BranchDetails.jsx +++ b/src/components/ServiceProject/ServiceProjectBranch/BranchDetails.jsx @@ -27,59 +27,34 @@ const BranchDetails = ({ branch }) => {
    ); return ( -
    -
    - - Branch Details - -
    -
    -
    Name:
    -
    {data?.branchName}
    -
    +<> +
    + + Branch Details + +
    -
    -
    Type:
    -
    {data?.branchType}
    -
    -
    -
    Contact No:
    -
    {data?.contactInformation}
    -
    +
    +
    Contact No:
    +
    {data?.contactInformation}
    +
    -
    -
    Address:
    -
    {data?.address}
    -
    +
    +
    Type:
    +
    {data?.branchType}
    +
    - {googleMapUrl && ( -
    -
    Map:
    +
    +
    Email:
    +
    {data?.email}
    +
    -
    - - Open in Google Maps - +
    +
    Address:
    +
    {data?.address}
    +
    + - - - {copied && Copied!} -
    -
    - )} -
    ); }; diff --git a/src/components/ServiceProject/ServiceProjectBranch/ServiceBranch.jsx b/src/components/ServiceProject/ServiceProjectBranch/ServiceBranch.jsx index 0655247f..cfa35f17 100644 --- a/src/components/ServiceProject/ServiceProjectBranch/ServiceBranch.jsx +++ b/src/components/ServiceProject/ServiceProjectBranch/ServiceBranch.jsx @@ -26,7 +26,6 @@ const ServiceBranch = () => { const { data, isLoading, isError, error } = useBranches( projectId, - // true, !showInactive, ITEMS_PER_PAGE - 10, currentPage, @@ -78,45 +77,47 @@ const ServiceBranch = () => {
    {/* Header Section */}
    -
    +
    - Branch + Branchs
    {/* Flex container for toggle + button */} -
    -
    - - {/* Toggle Switch */} -
    - setShowInactive(!showInactive)} - /> - -
    - - {/* Add Branch Button */} - +
    +
    + +
    + setShowInactive(!showInactive)} + /> + +
    +
    + +
    @@ -152,7 +153,6 @@ const ServiceBranch = () => { )} - {isError && ( {
    - ))} @@ -247,7 +246,9 @@ const ServiceBranch = () => { setManageState({ IsOpen: false, branchId: null })} + closeModal={() => + setManageState({ IsOpen: false, branchId: null }) + } > { setManageState({ IsOpen: false, branchId: null }) } /> - )}
    diff --git a/src/components/ServiceProject/ServiceProjectJob/JobComments.jsx b/src/components/ServiceProject/ServiceProjectJob/JobComments.jsx index 197aa729..4e1dfb9d 100644 --- a/src/components/ServiceProject/ServiceProjectJob/JobComments.jsx +++ b/src/components/ServiceProject/ServiceProjectJob/JobComments.jsx @@ -150,7 +150,7 @@ const JobComments = ({ data }) => { type="submit" disabled={!watch("comment")?.trim() || isPending} > - Submit + Send
    diff --git a/src/components/ServiceProject/ServiceProjectJob/ManageJobTicket.jsx b/src/components/ServiceProject/ServiceProjectJob/ManageJobTicket.jsx index 12428c77..fd5c69da 100644 --- a/src/components/ServiceProject/ServiceProjectJob/ManageJobTicket.jsx +++ b/src/components/ServiceProject/ServiceProjectJob/ManageJobTicket.jsx @@ -47,7 +47,11 @@ const ManageJobTicket = ({ Job }) => {
    ); return ( -
    +
    {data?.title}
    @@ -56,7 +60,7 @@ const ManageJobTicket = ({ Job }) => { {data?.jobTicketUId || "N/A"}

    -
    +
    {data?.status?.displayName} @@ -65,6 +69,7 @@ const ManageJobTicket = ({ Job }) => { id="STATUS_CHANEG" Mode="click" className="" + align="right" content={ {

    {data?.description || "N/A"}

    -
    -
    +
    +
    {" "} Created Date : {formatUTCToLocalTime(data?.createdAt, true)}
    +
    + {data?.tags?.map((tag, ind) => ( + + {tag?.name} + + ))} +
    - + Start Date :{" "} {formatUTCToLocalTime(data?.startDate)} {" "} {" "} - + Due on :{" "} {formatUTCToLocalTime(data?.startDate)} @@ -117,7 +132,7 @@ const ManageJobTicket = ({ Job }) => { const { days, color } = daysLeft(data?.startDate, data?.dueDate); return ( - Days Left: + Days Left: {days !== null ? `${days} days` : "N/A"} @@ -125,29 +140,80 @@ const ManageJobTicket = ({ Job }) => { ); })()}
    - {data?.projectBranch && ( -
    - - Branch Name : + {/* {data?.projectBranch && ( +
    + + Branch Name: } > - + {data?.projectBranch?.branchName}
    - )} + )} */} -
    - People -
    +
    +

    + Peoples +

    + + {/* Created By */} +
    +

    Created By

    + +
    + +
    +

    + {data?.createdBy?.firstName} {data?.createdBy?.lastName} +

    + + {data?.createdBy?.jobRoleName} + +
    +
    +
    + + {/* Assigned To */} +
    +

    Assigned To

    + +
    +
    + {data?.assignees?.map((emp) => ( +
    + + +
    + + {emp.firstName} {emp.lastName} + + + {emp.jobRoleName} + +
    +
    + ))} +
    +
    +
    +
    diff --git a/src/components/common/HoverPopup.jsx b/src/components/common/HoverPopup.jsx index 2093c25a..89716984 100644 --- a/src/components/common/HoverPopup.jsx +++ b/src/components/common/HoverPopup.jsx @@ -1,6 +1,10 @@ import React, { useEffect, useRef } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { closePopup, openPopup, togglePopup } from "../../slices/localVariablesSlice"; +import { + closePopup, + openPopup, + togglePopup, +} from "../../slices/localVariablesSlice"; /** * align: "auto" | "left" | "right" @@ -63,7 +67,8 @@ const HoverPopup = ({ const popup = popupRef.current; // choose boundary: provided boundaryRef or nearest positioned parent (popup.parentElement) - const boundaryEl = (boundaryRef && boundaryRef.current) || popup.parentElement; + const boundaryEl = + (boundaryRef && boundaryRef.current) || popup.parentElement; if (!boundaryEl) return; const boundaryRect = boundaryEl.getBoundingClientRect(); @@ -75,15 +80,12 @@ const HoverPopup = ({ popup.style.transform = ""; popup.style.top = ""; - // default: place below trigger and center horizontally relative to parent - // We'll use absolute positioning with respect to the positioned parent container. - // Ensure popup is positioned using left/right in parent's coordinate system. - // Compute desired left (centered under trigger) const popupRect = popup.getBoundingClientRect(); const parentRect = boundaryRect; // alias // Convert trigger center to parent coordinates - const triggerCenterX = triggerRect.left + triggerRect.width / 2 - parentRect.left; + const triggerCenterX = + triggerRect.left + triggerRect.width / 2 - parentRect.left; // preferred left so popup center aligns to trigger center: const preferredLeft = triggerCenterX - popupRect.width / 2; @@ -111,10 +113,19 @@ const HoverPopup = ({ setRight(0); return; } + if (align === "center") { + popup.style.left = "50%"; + popup.style.right = "auto"; + popup.style.transform = "translateX(-50%)"; + return; + } // align === "auto": try preferred centered position, but flip fully if overflow // clamp preferredLeft to boundaries so it doesn't render partially outside - const leftIfCentered = Math.max(0, Math.min(preferredLeft, parentRect.width - popupRect.width)); + const leftIfCentered = Math.max( + 0, + Math.min(preferredLeft, parentRect.width - popupRect.width) + ); // if centered fits, use it if (leftIfCentered === preferredLeft) { @@ -142,8 +153,17 @@ const HoverPopup = ({ }, [visible, align, boundaryRef]); return ( -
    +
    { const [dashboard_data, setDashboard_Data] = useState([]); const [isLineChartLoading, setLoading] = useState(false); @@ -18,11 +17,13 @@ export const useDashboard_Data = ({ days, FromDate, projectId }) => { try { const payload = { days, - FromDate: FromDate || '', + FromDate: FromDate || "", projectId: projectId || null, }; - const response = await GlobalRepository.getDashboardProgressionData(payload); + const response = await GlobalRepository.getDashboardProgressionData( + payload + ); setDashboard_Data(response.data); } catch (err) { setError("Failed to fetch dashboard data."); @@ -38,123 +39,6 @@ export const useDashboard_Data = ({ days, FromDate, projectId }) => { return { dashboard_data, loading: isLineChartLoading, error }; }; - -// export const useDashboard_AttendanceData = (date, projectId) => { -// const [dashboard_Attendancedata, setDashboard_AttendanceData] = useState([]); -// const [isLineChartLoading, setLoading] = useState(false); -// const [error, setError] = useState(""); - -// useEffect(() => { -// const fetchData = async () => { -// setLoading(true); -// setError(""); - -// try { -// const response = await GlobalRepository.getDashboardAttendanceData(date, projectId); // date in 2nd param -// setDashboard_AttendanceData(response.data); -// } catch (err) { -// setError("Failed to fetch dashboard data."); -// console.error(err); -// } finally { -// setLoading(false); -// } -// }; - -// if (date && projectId !== null) { -// fetchData(); -// } -// }, [date, projectId]); - -// return { dashboard_Attendancedata, isLineChartLoading: isLineChartLoading, error }; -// }; - - -// 🔹 Dashboard Projects Card Data Hook -// export const useDashboardProjectsCardData = () => { -// const [projectsCardData, setProjectsData] = useState([]); -// const [loading, setLoading] = useState(false); -// const [error, setError] = useState(""); - -// useEffect(() => { -// const fetchProjectsData = async () => { -// setLoading(true); -// setError(""); - -// try { -// const response = await GlobalRepository.getDashboardProjectsCardData(); -// setProjectsData(response.data); -// } catch (err) { -// setError("Failed to fetch projects card data."); -// console.error(err); -// } finally { -// setLoading(false); -// } -// }; - -// fetchProjectsData(); -// }, []); - -// return { projectsCardData, loading, error }; -// }; - -// 🔹 Dashboard Teams Card Data Hook -// export const useDashboardTeamsCardData = (projectId) => { -// const [teamsCardData, setTeamsData] = useState({}); -// const [loading, setLoading] = useState(false); -// const [error, setError] = useState(""); - -// useEffect(() => { -// const fetchTeamsData = async () => { -// setLoading(true); -// setError(""); - -// try { -// const response = await GlobalRepository.getDashboardTeamsCardData(projectId); -// setTeamsData(response.data || {}); -// } catch (err) { -// setError("Failed to fetch teams card data."); -// console.error("Error fetching teams card data:", err); -// setTeamsData({}); -// } finally { -// setLoading(false); -// } -// }; - -// fetchTeamsData(); -// }, [projectId]); - -// return { teamsCardData, loading, error }; -// }; - -// export const useDashboardTasksCardData = (projectId) => { -// const [tasksCardData, setTasksData] = useState({}); -// const [loading, setLoading] = useState(false); -// const [error, setError] = useState(""); - -// useEffect(() => { -// const fetchTasksData = async () => { -// setLoading(true); -// setError(""); - -// try { -// const response = await GlobalRepository.getDashboardTasksCardData(projectId); -// setTasksData(response.data); -// } catch (err) { -// setError("Failed to fetch tasks card data."); -// console.error(err); -// setTasksData({}); -// } finally { -// setLoading(false); -// } -// }; - -// fetchTasksData(); -// }, [projectId]); - -// return { tasksCardData, loading, error }; -// }; - - export const useAttendanceOverviewData = (projectId, days) => { const [attendanceOverviewData, setAttendanceOverviewData] = useState([]); const [loading, setLoading] = useState(false); @@ -167,7 +51,10 @@ export const useAttendanceOverviewData = (projectId, days) => { setError(""); try { - const response = await GlobalRepository.getAttendanceOverview(projectId, days); + const response = await GlobalRepository.getAttendanceOverview( + projectId, + days + ); setAttendanceOverviewData(response.data); } catch (err) { setError("Failed to fetch attendance overview data."); @@ -182,7 +69,6 @@ export const useAttendanceOverviewData = (projectId, days) => { return { attendanceOverviewData, loading, error }; }; - // -------------------Query---------------------------- // export const useDashboard_Data = (days, FromDate, projectId)=>{ @@ -199,39 +85,47 @@ export const useAttendanceOverviewData = (projectId, days) => { // } // }) // } - +export const useProjectCompletionStatus = () => { + return useQuery({ + queryKey: ["projectCompletionStatus"], + queryFn: async () => { + const resp = await await GlobalRepository.getProjectCompletionStatus(); + return resp.data; + }, + }); +}; export const useDashboard_AttendanceData = (date, projectId) => { return useQuery({ queryKey: ["dashboardAttendances", date, projectId], queryFn: async () => { - - const resp = await await GlobalRepository.getDashboardAttendanceData(date, projectId) + const resp = await await GlobalRepository.getDashboardAttendanceData( + date, + projectId + ); return resp.data; - } - }) -} + }, + }); +}; export const useDashboardTeamsCardData = (projectId) => { return useQuery({ queryKey: ["dashboardTeams", projectId], queryFn: async () => { - - const resp = await GlobalRepository.getDashboardTeamsCardData(projectId) + const resp = await GlobalRepository.getDashboardTeamsCardData(projectId); return resp.data; - } - }) -} + }, + }); +}; export const useDashboardTasksCardData = (projectId) => { return useQuery({ queryKey: ["dashboardTasks", projectId], queryFn: async () => { - - const resp = await GlobalRepository.getDashboardTasksCardData(projectId) + const resp = await GlobalRepository.getDashboardTasksCardData(projectId); return resp.data; - } - }) -} + }, + }); +}; // export const useAttendanceOverviewData = (projectId, days) => { // return useQuery({ // queryKey:["dashboardAttendanceOverView",projectId], @@ -247,29 +141,30 @@ export const useDashboardProjectsCardData = () => { return useQuery({ queryKey: ["dashboardProjects"], queryFn: async () => { - const resp = await GlobalRepository.getDashboardProjectsCardData(); return resp.data; - } - }) -} + }, + }); +}; export const useExpenseAnalysis = (projectId, startDate, endDate) => { const hasBothDates = !!startDate && !!endDate; const noDatesSelected = !startDate && !endDate; - const shouldFetch = - noDatesSelected || - hasBothDates; + const shouldFetch = noDatesSelected || hasBothDates; return useQuery({ queryKey: ["expenseAnalysis", projectId, startDate, endDate], queryFn: async () => { - const resp = await GlobalRepository.getExpenseData(projectId, startDate, endDate); + const resp = await GlobalRepository.getExpenseData( + projectId, + startDate, + endDate + ); return resp.data; }, enabled: shouldFetch, - refetchOnWindowFocus: true, // refetch when you come back - refetchOnMount: "always", // always refetch on remount + refetchOnWindowFocus: true, // refetch when you come back + refetchOnMount: "always", // always refetch on remount staleTime: 0, }); }; @@ -280,17 +175,20 @@ export const useExpenseStatus = (projectId) => { queryFn: async () => { const resp = await GlobalRepository.getExpenseStatus(projectId); return resp.data; - } - }) -} + }, + }); +}; export const useExpenseDataByProject = (projectId, categoryId, months) => { return useQuery({ queryKey: ["expenseByProject", projectId, categoryId, months], queryFn: async () => { - const resp = await GlobalRepository.getExpenseDataByProject(projectId, categoryId, months); + const resp = await GlobalRepository.getExpenseDataByProject( + projectId, + categoryId, + months + ); return resp.data; }, - }); -}; \ No newline at end of file +}; diff --git a/src/repositories/GlobalRepository.jsx b/src/repositories/GlobalRepository.jsx index ec90d25d..2516020a 100644 --- a/src/repositories/GlobalRepository.jsx +++ b/src/repositories/GlobalRepository.jsx @@ -18,6 +18,8 @@ const GlobalRepository = { return api.get(`/api/Dashboard/Progression?${params.toString()}`); }, + getProjectCompletionStatus:()=>api.get(`/api/Dashboard/project-completion-status`), + getDashboardAttendanceData: (date, projectId) => { diff --git a/src/repositories/ProjectRepository.jsx b/src/repositories/ProjectRepository.jsx index 86e59955..9894feae 100644 --- a/src/repositories/ProjectRepository.jsx +++ b/src/repositories/ProjectRepository.jsx @@ -1,6 +1,7 @@ import { api } from "../utils/axiosClient"; const ProjectRepository = { + getProjectList: (pageSize, pageNumber) => api.get(`/api/project/list?pageSize=${pageSize}&pageNumber=${pageNumber}`), getProjectByprojectId: (projetid) => From 604bb68dc2e6f10957af7457ff8fcaa82b4ea4a7 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 19 Nov 2025 11:23:38 +0530 Subject: [PATCH 28/41] Adding Billed To field in Manage Collection. --- .../collections/ManageCollection.jsx | 91 +++++++++++++------ .../collections/collectionSchema.jsx | 2 + 2 files changed, 65 insertions(+), 28 deletions(-) diff --git a/src/components/collections/ManageCollection.jsx b/src/components/collections/ManageCollection.jsx index 608a6c33..c0160b6b 100644 --- a/src/components/collections/ManageCollection.jsx +++ b/src/components/collections/ManageCollection.jsx @@ -16,10 +16,16 @@ import { import { formatFileSize, localToUtc } from "../../utils/appUtils"; import { useCollectionContext } from "../../pages/collections/CollectionPage"; import { formatDate } from "../../utils/dateUtils"; +import { SelectProjectField } from "../common/Forms/SelectFieldServerSide"; +import { ITEMS_PER_PAGE } from "../../utils/constants"; +import { useOrganizationsList } from "../../hooks/useOrganization"; const ManageCollection = ({ collectionId, onClose }) => { const { data, isError, isLoading, error } = useCollection(collectionId); const { projectNames, projectLoading } = useProjectName(true); + const { data: organization, isLoading: isLoadingOrganization } = + useOrganizationsList(ITEMS_PER_PAGE, 1, true); + const methods = useForm({ resolver: zodResolver(newCollection), defaultValues: defaultCollection, @@ -138,6 +144,7 @@ const ManageCollection = ({ collectionId, onClose }) => { taxAmount: data?.taxAmount, basicAmount: data?.basicAmount, description: data?.description, + billedToId: data?.billedToId, attachments: data.attachments ? data.attachments.map((doc) => ({ fileName: doc.fileName, @@ -163,36 +170,61 @@ const ManageCollection = ({ collectionId, onClose }) => {
    -
    - - +
    + + setValue("projectId", val, { + shouldDirty: true, + shouldValidate: true, + }) + } + /> {errors.projectId && ( - - {errors.projectId.message} - + {errors.projectId.message} )}
    + +
    + +
    + +
    + {errors?.clientId && ( + {errors.billedToId.message} + )} +
    +
    {errors.title && ( @@ -203,7 +235,7 @@ const ManageCollection = ({ collectionId, onClose }) => { {errors.invoiceId && ( @@ -216,7 +248,7 @@ const ManageCollection = ({ collectionId, onClose }) => { {errors.invoiceId && ( @@ -232,6 +264,7 @@ const ManageCollection = ({ collectionId, onClose }) => { name="invoiceDate" control={control} maxDate={new Date()} + size="md" /> {errors.invoiceDate && ( @@ -246,6 +279,7 @@ const ManageCollection = ({ collectionId, onClose }) => { name="exceptedPaymentDate" control={control} minDate={watch("invoiceDate")} + size="md" /> {errors.exceptedPaymentDate && ( @@ -260,6 +294,7 @@ const ManageCollection = ({ collectionId, onClose }) => { name="clientSubmitedDate" control={control} maxDate={new Date()} + size="md" /> {errors.exceptedPaymentDate && ( @@ -275,7 +310,7 @@ const ManageCollection = ({ collectionId, onClose }) => { { { diff --git a/src/components/collections/collectionSchema.jsx b/src/components/collections/collectionSchema.jsx index bb97fede..84ea806b 100644 --- a/src/components/collections/collectionSchema.jsx +++ b/src/components/collections/collectionSchema.jsx @@ -19,6 +19,7 @@ export const newCollection = z.object({ invoiceDate: z.string().min(1, { message: "Date is required" }), description: z.string().trim().optional(), clientSubmitedDate: z.string().min(1, { message: "Date is required" }), + billedToId: z.string().min(1, { message: "Date is required" }), exceptedPaymentDate: z.string().min(1, { message: "Date is required" }), invoiceNumber: z .string() @@ -75,6 +76,7 @@ export const defaultCollection = { taxAmount: "", basicAmount: "", description: "", + billedToId:"", attachments: [], }; From fa923d4c3ab4f48375dbd43819adfe9aab005f19 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Thu, 20 Nov 2025 14:39:54 +0530 Subject: [PATCH 29/41] added popup for branch view --- .../ServiceProjectBranch/BranchDetails.jsx | 93 ++++++++++++------- .../ServiceProjectBranch/ManageBranch.jsx | 68 +++++++------- .../ServiceProjectBranch/ServiceBranch.jsx | 41 ++++---- .../ServiceProjectJob/ManageJobTicket.jsx | 13 ++- src/components/common/HoverPopup.jsx | 75 ++++++++------- 5 files changed, 155 insertions(+), 135 deletions(-) diff --git a/src/components/ServiceProject/ServiceProjectBranch/BranchDetails.jsx b/src/components/ServiceProject/ServiceProjectBranch/BranchDetails.jsx index 87d59b8c..b7c441d6 100644 --- a/src/components/ServiceProject/ServiceProjectBranch/BranchDetails.jsx +++ b/src/components/ServiceProject/ServiceProjectBranch/BranchDetails.jsx @@ -6,56 +6,81 @@ import { BranchDetailsSkeleton } from "../ServiceProjectSeketon"; const BranchDetails = ({ branch }) => { const [copied, setCopied] = useState(false); - const { data, isLoading, isError, error } = useBranchDetails(branch); + if (isLoading) return ; + if (isError) return ; + + let contactInfo = []; + try { + contactInfo = JSON.parse(data?.contactInformation || "[]"); + } catch (e) {} + const googleMapUrl = data?.googleMapUrl || data?.locationLink; const handleCopy = async () => { if (!googleMapUrl) return; - await navigator.clipboard.writeText(googleMapUrl); setCopied(true); - - setTimeout(() => setCopied(false), 3000); + setTimeout(() => setCopied(false), 2000); }; - if (isLoading) return ; - if (isError) - return ( -
    - -
    - ); + return ( -<> -
    - - Branch Details - -
    +
    +
    + + Branch Details +
    -
    -
    Contact No:
    -
    {data?.contactInformation}
    -
    + -
    -
    Type:
    -
    {data?.branchType}
    -
    + -
    -
    Email:
    -
    {data?.email}
    -
    + -
    -
    Address:
    -
    {data?.address}
    -
    - + {/* Contact persons */} + {contactInfo.map((person, index) => ( +
    +
    {person.contactPerson}
    + + + +
    + ))} + {/* Map Link */} + {googleMapUrl && ( +
    + + View on Google Maps + + + +
    + )} +
    ); }; +const DetailRow = ({ label, value }) => ( +
    +
    + {label}: +
    +
    + {value || "N/A"} +
    +
    +); + export default BranchDetails; diff --git a/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx b/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx index 720fa08f..b78f8895 100644 --- a/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx +++ b/src/components/ServiceProject/ServiceProjectBranch/ManageBranch.jsx @@ -26,8 +26,8 @@ const ManageBranch = ({ closeModal, BranchToEdit = null }) => { contactPerson: "", designation: "", contactEmails: [""], - contactNumbers: [""] - } + contactNumbers: [""], + }, ]); const { projectId } = useParams(); @@ -73,7 +73,6 @@ const ManageBranch = ({ closeModal, BranchToEdit = null }) => { } }, [data, reset]); - const { mutate: CreateServiceBranch, isPending: createPending } = useCreateBranch(() => { handleClose(); @@ -97,8 +96,6 @@ const ManageBranch = ({ closeModal, BranchToEdit = null }) => { } }; - - return (
    @@ -138,8 +135,6 @@ const ManageBranch = ({ closeModal, BranchToEdit = null }) => {
    - -