From c85d21ad0c90b69aa395c8a1e08c8ba7b3b121af Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Tue, 5 Aug 2025 23:53:06 +0530 Subject: [PATCH 01/50] set data format - dd-mm-yy --- src/utils/dateUtils.jsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/utils/dateUtils.jsx b/src/utils/dateUtils.jsx index d788f19c..a9c5f31d 100644 --- a/src/utils/dateUtils.jsx +++ b/src/utils/dateUtils.jsx @@ -67,9 +67,13 @@ export const formatNumber = (num) => { if (num == null || isNaN(num)) return "NA"; return Number.isInteger(num) ? num : num.toFixed(2); }; -export const formatUTCToLocalTime = (datetime) =>{ - return moment.utc(datetime).local().format("DD MMMM, YYYY [at] hh:mm A"); -} + + +export const formatUTCToLocalTime = (datetime, timeRequired = false) => { + return timeRequired + ? moment.utc(datetime).local().format("DD MMM YYYY hh:mm A") + : moment.utc(datetime).local().format("DD MMM YYYY"); +}; export const getCompletionPercentage = (completedWork, plannedWork)=> { if (!plannedWork || plannedWork === 0) return 0; From cb009c77a4fbd124ef005c08d6e749ef3d32c157 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Tue, 5 Aug 2025 23:54:22 +0530 Subject: [PATCH 02/50] customize date picker, flexible can set max and min date and also handle react-hook-form --- src/components/common/DatePicker.jsx | 92 +++++++++---- src/components/common/DateRangePicker.jsx | 158 +++++++++++++++++++--- 2 files changed, 200 insertions(+), 50 deletions(-) diff --git a/src/components/common/DatePicker.jsx b/src/components/common/DatePicker.jsx index 728b14fb..9645fddb 100644 --- a/src/components/common/DatePicker.jsx +++ b/src/components/common/DatePicker.jsx @@ -1,39 +1,71 @@ -import React, { useEffect, useRef } from "react"; +import { useEffect, useRef } from "react"; +import { useController } from "react-hook-form"; -const DatePicker = ({ onDateChange }) => { + +const DatePicker = ({ + name, + control, + placeholder = "DD-MM-YYYY", + className = "", + allowText = false, + maxDate=new Date(), + minDate, + ...rest +}) => { const inputRef = useRef(null); - useEffect(() => { - const fp = flatpickr(inputRef.current, { - dateFormat: "Y-m-d", - defaultDate: new Date(), - onChange: (selectedDates, dateStr) => { - if (onDateChange) { - onDateChange(dateStr); // Pass selected date to parent - } - } - }); + const { + field: { onChange, value, ref } + } = useController({ + name, + control + }); - return () => { - // Cleanup flatpickr instance - fp.destroy(); - }; - }, [onDateChange]); + useEffect(() => { + if (inputRef.current) { + flatpickr(inputRef.current, { + dateFormat: "d-m-Y", + allowInput: allowText, + defaultDate: value + ? flatpickr.parseDate(value, "Y-m-d") + : null, + maxDate:maxDate, + minDate:new Date(minDate?.split("T")[0]) ?? undefined, + onChange: function (selectedDates, dateStr) { + onChange(dateStr); + }, + ...rest + }); + } + }, [inputRef]); return ( -
-
- {/* */} - -
+
+ { + inputRef.current = el; + ref(el); + }} + readOnly={!allowText} + autoComplete="off" + /> + + { + if (inputRef.current && inputRef.current._flatpickr) { + inputRef.current._flatpickr.open(); + } + }} + > + +
); }; diff --git a/src/components/common/DateRangePicker.jsx b/src/components/common/DateRangePicker.jsx index e7239c53..1e95ef09 100644 --- a/src/components/common/DateRangePicker.jsx +++ b/src/components/common/DateRangePicker.jsx @@ -1,8 +1,10 @@ import React, { useEffect, useRef } from "react"; - +import { useController, useFormContext, useWatch } from "react-hook-form"; const DateRangePicker = ({ + md, + sm, onRangeChange, - DateDifference = 7, + DateDifference = 7, endDateMode = "yesterday", }) => { const inputRef = useRef(null); @@ -10,33 +12,34 @@ const DateRangePicker = ({ useEffect(() => { const endDate = new Date(); if (endDateMode === "yesterday") { - endDate.setDate(endDate.getDate() - 1); + endDate.setDate(endDate.getDate() - 1); } endDate.setHours(0, 0, 0, 0); - const startDate = new Date(endDate); + const startDate = new Date(endDate); startDate.setDate(endDate.getDate() - (DateDifference - 1)); startDate.setHours(0, 0, 0, 0); const fp = flatpickr(inputRef.current, { mode: "range", - dateFormat: "Y-m-d", - altInput: true, - altFormat: "d-m-Y", - defaultDate: [startDate, endDate], - static: true, + dateFormat: "Y-m-d", + altInput: true, + altFormat: "d-m-Y", + defaultDate: [startDate, endDate], + static: false, + // appendTo: document.body, clickOpens: true, - maxDate: endDate, // ✅ Disable future dates + maxDate: endDate, onChange: (selectedDates, dateStr) => { - const [startDateString, endDateString] = dateStr.split(" to "); + const [startDateString, endDateString] = dateStr.split(" To "); onRangeChange?.({ startDate: startDateString, endDate: endDateString }); }, }); onRangeChange?.({ - startDate: startDate.toLocaleDateString("en-CA"), - endDate: endDate.toLocaleDateString("en-CA"), + startDate: startDate.toLocaleDateString("en-CA"), + endDate: endDate.toLocaleDateString("en-CA"), }); return () => { @@ -45,14 +48,129 @@ const DateRangePicker = ({ }, [onRangeChange, DateDifference, endDateMode]); return ( - +
+ + + +
); }; export default DateRangePicker; + + + + + +export const DateRangePicker1 = ({ + startField = "startDate", + endField = "endDate", + placeholder = "Select date range", + className = "", + allowText = false, + resetSignal, // <- NEW prop + ...rest +}) => { + const inputRef = useRef(null); + const { control, setValue, getValues } = useFormContext(); + + const { + field: { ref }, + } = useController({ name: startField, control }); + + const applyDefaultDates = () => { + const today = new Date(); + const past = new Date(); + past.setDate(today.getDate() - 6); + + const format = (d) => flatpickr.formatDate(d, "d-m-Y"); + const formattedStart = format(past); + const formattedEnd = format(today); + + setValue(startField, formattedStart, { shouldValidate: true }); + setValue(endField, formattedEnd, { shouldValidate: true }); + + if (inputRef.current?._flatpickr) { + inputRef.current._flatpickr.setDate([past, today]); + } + }; + + useEffect(() => { + if (!inputRef.current || inputRef.current._flatpickr) return; + + const instance = flatpickr(inputRef.current, { + mode: "range", + dateFormat: "d-m-Y", + allowInput: allowText, + onChange: (selectedDates) => { + if (selectedDates.length === 2) { + const [start, end] = selectedDates; + const format = (d) => flatpickr.formatDate(d, "d-m-Y"); + setValue(startField, format(start), { shouldValidate: true }); + setValue(endField, format(end), { shouldValidate: true }); + } else { + setValue(startField, "", { shouldValidate: true }); + setValue(endField, "", { shouldValidate: true }); + } + }, + ...rest, + }); + + // Apply default if empty + const currentStart = getValues(startField); + const currentEnd = getValues(endField); + if (!currentStart && !currentEnd) { + applyDefaultDates(); + } else if (currentStart && currentEnd) { + instance.setDate([ + flatpickr.parseDate(currentStart, "d-m-Y"), + flatpickr.parseDate(currentEnd, "d-m-Y"), + ]); + } + + return () => instance.destroy(); + }, []); + + // Reapply default range on resetSignal change + useEffect(() => { + if (resetSignal !== undefined) { + applyDefaultDates(); + } + }, [resetSignal]); + + const start = getValues(startField); + const end = getValues(endField); + const formattedValue = start && end ? `${start} To ${end}` : ""; + + return ( +
+ { + inputRef.current = el; + ref(el); + }} + readOnly={!allowText} + autoComplete="off" + /> + inputRef.current?._flatpickr?.open()} + > + + +
+ ); +}; From 8a77a609c69dbced0f282a7ed9bb0fc79ff3b3ad Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Tue, 5 Aug 2025 23:55:05 +0530 Subject: [PATCH 03/50] added Expense releated permissions id --- src/utils/constants.jsx | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/utils/constants.jsx b/src/utils/constants.jsx index a8dc1c2d..c1765b9f 100644 --- a/src/utils/constants.jsx +++ b/src/utils/constants.jsx @@ -41,5 +41,28 @@ export const DIRECTORY_MANAGER = "62668630-13ce-4f52-a0f0-db38af2230c5" export const DIRECTORY_USER = "0f919170-92d4-4337-abd3-49b66fc871bb" +export const VIEW_SELF_EXPENSE = "385be49f-8fde-440e-bdbc-3dffeb8dd116" + +export const VIEW_ALL_EXPNESE = "01e06444-9ca7-4df4-b900-8c3fa051b92f"; + +export const CREATE_EXEPENSE = "0f57885d-bcb2-4711-ac95-d841ace6d5a7"; + +export const REVIEW_EXPENSE = "1f4bda08-1873-449a-bb66-3e8222bd871b"; + +export const APPROVE_EXPENSE = "eaafdd76-8aac-45f9-a530-315589c6deca"; + + +export const PROCESS_EXPENSE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11" + +export const EXPENSE_MANAGE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11" + +export const EXPENSE_REJECTEDBY = ["d1ee5eec-24b6-4364-8673-a8f859c60729","965eda62-7907-4963-b4a1-657fb0b2724b"] + +export const EXPENSE_DRAFT = "297e0d8f-f668-41b5-bfea-e03b354251c8" +// -------------------Application Role------------------------------ + +// 1 - Expense Manage +export const EXPENSE_MANAGEMENT = "a4e25142-449b-4334-a6e5-22f70e4732d7" + export const BASE_URL = process.env.VITE_BASE_URL; -// export const BASE_URL = "https://api.marcoaiot.com"; \ No newline at end of file +// export const BASE_URL = "https://api.marcoaiot.com"; From eac8bd685c3bba64f976305d3767976966457586 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Tue, 5 Aug 2025 23:56:07 +0530 Subject: [PATCH 04/50] handle properly refresh token calls --- src/utils/axiosClient.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/axiosClient.jsx b/src/utils/axiosClient.jsx index 4b7f34e2..9c4bd3ae 100644 --- a/src/utils/axiosClient.jsx +++ b/src/utils/axiosClient.jsx @@ -44,7 +44,7 @@ axiosClient.interceptors.response.use( const originalRequest = error.config; // Skip retry for public requests or already retried ones - if (!originalRequest || originalRequest._retry || originalRequest.authRequired === false) { + if (!originalRequest && originalRequest._retry || originalRequest.authRequired === false) { return Promise.reject(error); } From a8d5b7415765fd9960da831fb180fbe90ceba577 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Tue, 5 Aug 2025 23:56:52 +0530 Subject: [PATCH 05/50] created app utils --- src/utils/appUtils.js | 49 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/utils/appUtils.js diff --git a/src/utils/appUtils.js b/src/utils/appUtils.js new file mode 100644 index 00000000..94d5b09a --- /dev/null +++ b/src/utils/appUtils.js @@ -0,0 +1,49 @@ +import { useEffect, useState } from "react"; + +export const formatFileSize=(bytes)=> { + if (bytes < 1024) return bytes + " B"; + else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB"; + else return (bytes / (1024 * 1024)).toFixed(2) + " MB"; +} +export const AppColorconfig = { + colors: { + primary: '#696cff', + secondary: '#8592a3', + success: '#71dd37', + info: '#03c3ec', + warning: '#ffab00', + danger: '#ff3e1d', + dark: '#233446', + black: '#000', + white: '#fff', + cardColor: '#fff', + bodyBg: '#f5f5f9', + bodyColor: '#697a8d', + headingColor: '#566a7f', + textMuted: '#a1acb8', + borderColor: '#eceef1' + } +}; +export const getColorNameFromHex = (hex) => { + const normalizedHex = hex?.replace(/'/g, '').toLowerCase(); + const colors = AppColorconfig.colors; + + for (const [name, value] of Object.entries(colors)) { + if (value.toLowerCase() === normalizedHex) { + return name; + } + } + + return null; // +}; + +export const useDebounce = (value, delay = 500) => { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const timer = setTimeout(() => setDebouncedValue(value), delay); + return () => clearTimeout(timer); + }, [value, delay]); + + return debouncedValue; +}; \ No newline at end of file From 549f11818bf3abc8b14aa8ae35588a7013a88028 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Tue, 5 Aug 2025 23:57:09 +0530 Subject: [PATCH 06/50] added Expense key --- src/services/signalRService.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/signalRService.js b/src/services/signalRService.js index dc686b8a..3d9d40b1 100644 --- a/src/services/signalRService.js +++ b/src/services/signalRService.js @@ -52,7 +52,9 @@ export function startSignalR(loggedUser) { } eventBus.emit("attendance_log", data); } - + if(data.keyword == "Expanse"){ + queryClient.invalidateQueries({queryKey:["Expenses"]}) + } // if create or update project if ( data.keyword == "Create_Project" || From b9a75efd5caf446198f97be5d9f0a0893277f3bf Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Tue, 5 Aug 2025 23:57:30 +0530 Subject: [PATCH 07/50] created Expense route --- src/router/AppRoutes.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index 5ce5b6d4..d89191d8 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -38,6 +38,7 @@ import LegalInfoCard from "../pages/TermsAndConditions/LegalInfoCard"; import ProtectedRoute from "./ProtectedRoute"; import Directory from "../pages/Directory/Directory"; import LoginWithOtp from "../pages/authentication/LoginWithOtp"; +import ExpensePage from "../pages/Expense/ExpensePage"; const router = createBrowserRouter( [ @@ -76,6 +77,7 @@ const router = createBrowserRouter( { path: "/activities/task", element: }, { path: "/activities/reports", element: }, { path: "/gallary", element: }, + { path: "/expenses", element: }, { path: "/masters", element: }, { path: "/help/support", element: }, { path: "/help/docs", element: }, From d580ca0a15d9ff75e24a3bd4f84b77a030d851c2 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Tue, 5 Aug 2025 23:58:31 +0530 Subject: [PATCH 08/50] added expense releated master - Expense Type, status and paymentModa --- src/hooks/masterHook/useMaster.js | 417 +++++++++++++------------ src/repositories/MastersRepository.jsx | 91 +++--- 2 files changed, 266 insertions(+), 242 deletions(-) diff --git a/src/hooks/masterHook/useMaster.js b/src/hooks/masterHook/useMaster.js index 20caff62..15dc3305 100644 --- a/src/hooks/masterHook/useMaster.js +++ b/src/hooks/masterHook/useMaster.js @@ -12,213 +12,6 @@ import showToast from "../../services/toastService"; -// const useMaster = () => { - -// const selectedMaster = useSelector((store)=>store.localVariables.selectedMaster); -// const [data, setData] = useState([]); -// const [loading, setLoading] = useState(true); -// const [error, setError] = useState(""); -// useEffect(() => { -// const fetchData = async () => { -// if (!selectedMaster) return; -// setLoading(true); -// try { -// const cachedData = getCachedData(selectedMaster); -// if (cachedData) { - -// setData(cachedData); - -// } else { -// let response; -// switch (selectedMaster) { -// case "Application Role": -// response = await MasterRespository.getRoles(); -// response = response.data; -// break; -// case "Job Role": -// response = await MasterRespository.getJobRole(); -// response = response.data -// break; -// case "Activity": -// response = await MasterRespository.getActivites(); -// response = response.data -// break; -// case "Work Category": -// response = await MasterRespository.getWorkCategory(); -// response = response.data -// break; -// case "Contact Category": -// response = await MasterRespository.getContactCategory(); -// response = response.data -// break; -// case "Contact Tag": -// response = await MasterRespository.getContactTag(); -// response = response.data -// break; -// case "Status": -// response = [{description: null,featurePermission: null,id: "02dd4761-363c-49ed-8851-3d2489a3e98d",status:"status 1"},{description: null,featurePermission: null,id: "03dy9761-363c-49ed-8851-3d2489a3e98d",status:"status 2"},{description: null,featurePermission: null,id: "03dy7761-263c-49ed-8851-3d2489a3e98d",status:"Status 3"}]; -// break; -// default: -// response = []; -// } - -// if (response) { -// setData(response); -// cacheData(selectedMaster, response); -// } -// } -// } catch (err) { -// setError("Failed to fetch data."); -// } finally { -// setLoading(false); -// } -// }; - -// if ( selectedMaster ) -// { - -// fetchData(); -// } - -// }, [selectedMaster]); - - - -// return { data, loading, error } -// }; - - - - -// export const useActivitiesMaster = () => -// { -// const [ activities, setActivites ] = useState( [] ) -// const [ loading, setloading ] = useState( false ); -// const [ error, setError ] = useState() -// const fetchActivities =async () => { -// setloading(true); -// try { -// const response = await MasterRespository.getActivites(); -// setActivites(response.data); -// cacheData( "ActivityMaster", response.data ); -// setloading(false); -// } catch (err) { -// setError(err); -// setloading(false); -// } -// } -// useEffect( () => -// { -// const cacheddata = getCachedData( "ActivityMaster" ); -// if ( !cacheddata ) -// { -// fetchActivities() -// } else -// { -// setActivites(cacheddata); -// } -// }, [] ) - -// return {activities,loading,error} -// } - -// export const useWorkCategoriesMaster = () => -// { -// const [ categories, setCategories ] = useState( [] ) -// const [ categoryLoading, setloading ] = useState( false ); -// const [ categoryError, setError ] = useState( "" ) - -// const fetchCategories =async () => { -// const cacheddata = getCachedData("Work Category"); - -// if (!cacheddata) { -// setloading(true); -// try { -// const response = await MasterRespository.getWorkCategory(); -// setCategories(response.data); -// cacheData("Work Category", response.data); -// } catch (err) { -// setError(err); -// console.log(err); -// } finally { -// setloading(false); -// } -// } else { -// setCategories(cacheddata); -// } -// } -// useEffect( () => -// { -// fetchCategories() -// }, [] ) - -// return {categories,categoryLoading,categoryError} -// } - -// export const useContactCategory = () => -// { -// const [ contactCategory, setContactCategory ] = useState( [] ) -// const [ loading, setLoading ] = useState( false ) -// const [ Error, setError ] = useState() - -// const fetchConatctCategory = async() => -// { -// const cache_Category = getCachedData( "Contact Category" ); -// if ( !cache_Category ) -// { -// try -// { -// let resp = await MasterRespository.getContactCategory(); -// setContactCategory( resp.data ); -// cacheData("Contact Category",resp.data) -// } catch ( error ) -// { -// setError(error) -// } -// } else -// { -// setContactCategory(cache_Category) -// } -// } - -// useEffect( () => -// { -// fetchConatctCategory() -// }, [] ) -// return { contactCategory,loading,Error} -// } -// export const useContactTags = () => { -// const [contactTags, setContactTags] = useState([]); -// const [loading, setLoading] = useState(false); -// const [error, setError] = useState(null); - -// useEffect(() => { -// const fetchContactTag = async () => { -// const cache_Tags = getCachedData("Contact Tag"); - -// if (!cache_Tags) { -// setLoading(true); -// try { -// const resp = await MasterRespository.getContactTag(); -// setContactTags(resp.data); -// cacheData("Contact Tag", resp.data); -// } catch (err) { -// setError(err); -// } finally { -// setLoading(false); -// } -// } else { -// setContactTags(cache_Tags); -// } -// }; - -// fetchContactTag(); -// }, []); - -// return { contactTags, loading, error }; -// }; - -// Separate matser------------- export const useActivitiesMaster = () => { @@ -300,6 +93,76 @@ export const useContactTags = () => { return { contactTags, loading, error }; }; + +export const useExpenseType =()=>{ +const { + data: ExpenseTypes = [], + isLoading: loading, + error, + } = useQuery({ + queryKey: ["Expense Type"], + queryFn: async () => { + const res = await MasterRespository.getExpenseType() + return res.data; + }, + onError: (error) => { + showToast( + error?.response?.data?.message || + error.message || + "Failed to fetch Expense Type", + "error" + ); + }, + }); + + return { ExpenseTypes, loading, error }; +} +export const usePaymentMode =()=>{ +const { + data: PaymentModes = [], + isLoading: loading, + error, + } = useQuery({ + queryKey: ["Payment Mode"], + queryFn: async () => { + const res = await MasterRespository.getPaymentMode() + return res.data; + }, + onError: (error) => { + showToast( + error?.response?.data?.message || + error.message || + "Failed to fetch Payment Mode", + "error" + ); + }, + }); + + return { PaymentModes, loading, error }; +} +export const useExpenseStatus =()=>{ +const { + data: ExpenseStatus = [], + isLoading: loading, + error, + } = useQuery({ + queryKey: ["Expense Status"], + queryFn: async () => { + const res = await MasterRespository.getExpenseStatus() + return res.data; + }, + onError: (error) => { + showToast( + error?.response?.data?.message || + error.message || + "Failed to fetch Expense Status", + "error" + ); + }, + }); + + return { ExpenseStatus, loading, error }; +} // ===Application Masters Query================================================= const fetchMasterData = async (masterType) => { @@ -316,6 +179,12 @@ const fetchMasterData = async (masterType) => { return (await MasterRespository.getContactCategory()).data; case "Contact Tag": return (await MasterRespository.getContactTag()).data; + case "Expense Type": + return (await MasterRespository.getExpenseType()).data; + case "Payment Mode": + return (await MasterRespository.getPaymentMode()).data; + case "Expense Status": + return (await MasterRespository.getExpenseStatus()).data; case "Status": return [ { @@ -666,6 +535,140 @@ export const useUpdateContactTag = (onSuccessCallback) => }); } +// ----------------------Expense Type------------------ +export const useCreateExpenseType = (onSuccessCallback)=>{ + const queryClient = useQueryClient(); + + return useMutation( { + mutationFn: async ( payload ) => + { + const resp = await MasterRespository.createExpenseType(payload); + return resp.data; + }, + onSuccess: ( data ) => + { + queryClient.invalidateQueries( {queryKey:[ "masterData", "Expense Type" ]} ) + showToast( "Expense Type added successfully", "success" ); + if(onSuccessCallback) onSuccessCallback(data) + }, + onError: ( error ) => + { + showToast(error.message || "Something went wrong", "error"); + } + }) +} +export const useUpdateExpenseType = (onSuccessCallback) => +{ + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ( {id, payload} ) => + { + const response = await MasterRespository.updateExpenseType(id,payload); + return response.data; + }, + onSuccess: (data, variables) => { + + queryClient.invalidateQueries({ + queryKey: ["masterData", "Expense Type"], + }); + showToast("Expense Type updated successfully.", "success"); + + if (onSuccessCallback) onSuccessCallback(data); + }, + onError: (error) => { + showToast(error.message || "Something went wrong", "error"); + }, + }); +} + +// -----------------Payment Mode ------------- + +export const useCreatePaymentMode = (onSuccessCallback)=>{ + const queryClient = useQueryClient(); + + return useMutation( { + mutationFn: async ( payload ) => + { + const resp = await MasterRespository.createPaymentMode(payload); + return resp.data; + }, + onSuccess: ( data ) => + { + queryClient.invalidateQueries( {queryKey:[ "masterData", "Payment Mode" ]} ) + showToast( "Payment Mode added successfully", "success" ); + if(onSuccessCallback) onSuccessCallback(data) + }, + onError: ( error ) => + { + showToast(error.message || "Something went wrong", "error"); + } + }) +} +export const useUpdatePaymentMode = (onSuccessCallback)=>{ + const queryClient = useQueryClient(); + + return useMutation( { + mutationFn: async ( {id,payload} ) => + { + const resp = await MasterRespository.updatePaymentMode(id,payload); + return resp.data; + }, + onSuccess: ( data ) => + { + queryClient.invalidateQueries( {queryKey:[ "masterData", "Payment Mode" ]} ) + showToast( "Payment Mode Updated successfully", "success" ); + if(onSuccessCallback) onSuccessCallback(data) + }, + onError: ( error ) => + { + showToast(error.message || "Something went wrong", "error"); + } + }) +} +// -------------------Expense Status---------------------------------- +export const useCreateExpenseStatus =(onSuccessCallback)=>{ + const queryClient = useQueryClient(); + + return useMutation( { + mutationFn: async ( payload ) => + { + const resp = await MasterRespository.createExpenseStatus(payload); + return resp.data; + }, + onSuccess: ( data ) => + { + queryClient.invalidateQueries( {queryKey:[ "masterData", "Expense Status" ]} ) + showToast( "Expense Status added successfully", "success" ); + if(onSuccessCallback) onSuccessCallback(data) + }, + onError: ( error ) => + { + showToast(error.message || "Something went wrong", "error"); + } + }) +} +export const useUpdateExpenseStatus = (onSuccessCallback)=>{ + const queryClient = useQueryClient(); + + return useMutation( { + mutationFn: async ( {id,payload} ) => + { + const resp = await MasterRespository.updateExepnseStatus(id,payload); + return resp.data; + }, + onSuccess: ( data ) => + { + queryClient.invalidateQueries( {queryKey:[ "masterData", "Expense Status" ]} ) + showToast( "Expense Status Updated successfully", "success" ); + if(onSuccessCallback) onSuccessCallback(data) + }, + onError: ( error ) => + { + showToast(error.message || "Something went wrong", "error"); + } + }) +} // -Delete Master -------- export const useDeleteMasterItem = () => { const queryClient = useQueryClient(); diff --git a/src/repositories/MastersRepository.jsx b/src/repositories/MastersRepository.jsx index aebfc157..6f3fb89b 100644 --- a/src/repositories/MastersRepository.jsx +++ b/src/repositories/MastersRepository.jsx @@ -12,50 +12,71 @@ export const RolesRepository = { createRoles: (data) => api.post("/users", data), updateRoles: (id, data) => api.put(`/users/${id}`, data), deleteRoles: (id) => api.delete(`/users/${id}`), - - getEmployeeRoles:(id)=>api.get(`/api/employee/roles/${id}`), - createEmployeeRoles:(data)=>api.post("/api/roles/assign-roles",data) + getEmployeeRoles: (id) => api.get(`/api/employee/roles/${id}`), + createEmployeeRoles: (data) => api.post("/api/roles/assign-roles", data), }; - export const MasterRespository = { getRoles: () => api.get("/api/roles"), createRole: (data) => api.post("/api/roles", data), - updateRoles:(id,data) => api.put(`/api/roles/${id}`,data), - getFeatures: () => api.get( `/api/feature` ), + updateRoles: (id, data) => api.put(`/api/roles/${id}`, data), + getFeatures: () => api.get(`/api/feature`), + createJobRole: (data) => api.post("api/roles/jobrole", data), + getJobRole: () => api.get("/api/roles/jobrole"), + updateJobRole: (id, data) => api.put(`/api/roles/jobrole/${id}`, data), - createJobRole:(data)=>api.post('api/roles/jobrole',data), - getJobRole :()=>api.get("/api/roles/jobrole"), - updateJobRole: ( id, data ) => api.put( `/api/roles/jobrole/${ id }`, data ), + getActivites: () => api.get("api/master/activities"), + createActivity: (data) => api.post("api/master/activity", data), + updateActivity: (id, data) => + api.post(`api/master/activity/edit/${id}`, data), + getIndustries: () => api.get("api/master/industries"), - - getActivites: () => api.get( 'api/master/activities' ), - createActivity: (data) => api.post( 'api/master/activity',data ), - updateActivity:(id,data) =>api.post(`api/master/activity/edit/${id}`,data), - getIndustries: () => api.get( 'api/master/industries' ), - // delete - "Job Role": ( id ) => api.delete( `/api/roles/jobrole/${ id }` ), - "Activity": ( id ) => api.delete( `/api/master/activity/delete/${ id }` ), - "Application Role":(id)=>api.delete(`/api/roles/${id}`), - "Work Category": ( id ) => api.delete( `api/master/work-category/${ id }` ), - "Contact Category": ( id ) => api.delete( `/api/master/contact-category/${id}` ), - "Contact Tag" :(id)=>api.delete(`/api/master/contact-tag/${id}`), + "Job Role": (id) => api.delete(`/api/roles/jobrole/${id}`), + Activity: (id) => api.delete(`/api/master/activity/delete/${id}`), + "Application Role": (id) => api.delete(`/api/roles/${id}`), + "Work Category": (id) => api.delete(`api/master/work-category/${id}`), + "Contact Category": (id) => api.delete(`/api/master/contact-category/${id}`), + "Contact Tag": (id) => api.delete(`/api/master/contact-tag/${id}`), + "Expense Type": (id, isActive) => + api.delete(`/api/Master/expenses-type/delete/${id}`, (isActive = false)), + "Payment Mode": (id, isActive) => + api.delete(`/api/Master/payment-mode/delete/${id}`, (isActive = false)), + "Expense Status": (id, isActive) => + api.delete(`/api/Master/expenses-status/delete/${id}`, (isActive = false)), - getWorkCategory:() => api.get(`/api/master/work-categories`), - createWorkCategory: (data) => api.post(`/api/master/work-category`,data), - updateWorkCategory: ( id, data ) => api.post( `/api/master/work-category/edit/${ id }`, data ), - - getContactCategory: () => api.get( `/api/master/contact-categories` ), - createContactCategory: (data ) => api.post( `/api/master/contact-category`, data ), - updateContactCategory: ( id, data ) => api.post( `/api/master/contact-category/edit/${ id }`, data ), - - getContactTag: () => api.get( `/api/master/contact-tags` ), - createContactTag: (data ) => api.post( `/api/master/contact-tag`, data ), - updateContactTag: ( id, data ) => api.post( `/api/master/contact-tag/edit/${ id }`, data ), + getWorkCategory: () => api.get(`/api/master/work-categories`), + createWorkCategory: (data) => api.post(`/api/master/work-category`, data), + updateWorkCategory: (id, data) => + api.post(`/api/master/work-category/edit/${id}`, data), - getAuditStatus:()=>api.get('/api/Master/work-status') - -} \ No newline at end of file + getContactCategory: () => api.get(`/api/master/contact-categories`), + createContactCategory: (data) => + api.post(`/api/master/contact-category`, data), + updateContactCategory: (id, data) => + api.post(`/api/master/contact-category/edit/${id}`, data), + + getContactTag: () => api.get(`/api/master/contact-tags`), + createContactTag: (data) => api.post(`/api/master/contact-tag`, data), + updateContactTag: (id, data) => + api.post(`/api/master/contact-tag/edit/${id}`, data), + + getAuditStatus: () => api.get("/api/Master/work-status"), + + getExpenseType: () => api.get("/api/Master/expenses-types"), + createExpenseType: (data) => api.post("/api/Master/expenses-type", data), + updateExpenseType: (id, data) => + api.put(`/api/Master/expenses-type/edit/${id}`, data), + + getPaymentMode: () => api.get("/api/Master/payment-modes"), + createPaymentMode: (data) => api.post(`/api/Master/payment-mode`, data), + updatePaymentMode: (id, data) => + api.put(`/api/Master/payment-mode/edit/${id}`, data), + + getExpenseStatus: () => api.get("/api/Master/expenses-status"), + createExpenseStatus: (data) => api.post("/api/Master/expenses-status", data), + updateExepnseStatus: (id, data) => + api.put(`/api/Master/expenses-status/edit/${id}`, data), +}; From ba4cf6ad8607f5975ae6f1e32308b211cdb353e7 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Tue, 5 Aug 2025 23:59:27 +0530 Subject: [PATCH 09/50] set up right side bar for filter globally --- src/components/common/GlobalOffcanvas.jsx | 52 ++++++++++++++++++++++ src/components/common/OffcanvasTrigger.jsx | 26 +++++++++++ src/layouts/HomeLayout.jsx | 4 ++ 3 files changed, 82 insertions(+) create mode 100644 src/components/common/GlobalOffcanvas.jsx create mode 100644 src/components/common/OffcanvasTrigger.jsx diff --git a/src/components/common/GlobalOffcanvas.jsx b/src/components/common/GlobalOffcanvas.jsx new file mode 100644 index 00000000..bcca92bb --- /dev/null +++ b/src/components/common/GlobalOffcanvas.jsx @@ -0,0 +1,52 @@ +import React, { useEffect, useRef } from "react"; +import { useFab } from "../../Context/FabContext"; + +const GlobalOffcanvas = () => { + const { offcanvas, setIsOffcanvasOpen } = useFab(); + const offcanvasRef = useRef(); + + useEffect(() => { + const el = offcanvasRef.current; + const bsOffcanvas = new bootstrap.Offcanvas(el); + + const handleShow = () => setIsOffcanvasOpen(true); + const handleHide = () => setIsOffcanvasOpen(false); + + el.addEventListener("show.bs.offcanvas", handleShow); + el.addEventListener("hidden.bs.offcanvas", handleHide); + + return () => { + el.removeEventListener("show.bs.offcanvas", handleShow); + el.removeEventListener("hidden.bs.offcanvas", handleHide); + }; + }, []); + + return ( +
+
+
+ {offcanvas.title} +
+ +
+
+ {offcanvas.content ||

No content

} +
+
+ ); +}; + +export default GlobalOffcanvas; diff --git a/src/components/common/OffcanvasTrigger.jsx b/src/components/common/OffcanvasTrigger.jsx new file mode 100644 index 00000000..3c74fb14 --- /dev/null +++ b/src/components/common/OffcanvasTrigger.jsx @@ -0,0 +1,26 @@ +import { createPortal } from "react-dom"; +import { useFab } from "../../Context/FabContext"; + +const OffcanvasTrigger = () => { + const { openOffcanvas, offcanvas, showTrigger } = useFab(); + + if (!showTrigger || !offcanvas.content) return null; + + const btn = ( + openOffcanvas(offcanvas.title, offcanvas.content)} + role="button" + style={{ + top: "25%", + right: "0%", + cursor: "pointer", + zIndex: 1056, + }} + /> + ); + + return createPortal(btn, document.body); +}; + +export default OffcanvasTrigger; diff --git a/src/layouts/HomeLayout.jsx b/src/layouts/HomeLayout.jsx index 34639ee0..d43de172 100644 --- a/src/layouts/HomeLayout.jsx +++ b/src/layouts/HomeLayout.jsx @@ -7,6 +7,8 @@ import Footer from "../components/Layout/Footer"; import FloatingMenu from "../components/common/FloatingMenu"; import { FabProvider } from "../Context/FabContext"; import { useSelector } from "react-redux"; +import OffcanvasTrigger from "../components/common/OffcanvasTrigger"; +import GlobalOffcanvas from "../components/common/GlobalOffcanvas"; const HomeLayout = () => { const loggedUser = useSelector((store) => store.globalVariables.loginUser); @@ -34,9 +36,11 @@ const HomeLayout = () => {
+
+ ); From 71376c770691d386536503e956ef81c624a0012e Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Wed, 6 Aug 2025 00:00:43 +0530 Subject: [PATCH 10/50] handle modals of expense --- src/components/master/MasterModal.jsx | 156 ++++++++++---------------- 1 file changed, 58 insertions(+), 98 deletions(-) diff --git a/src/components/master/MasterModal.jsx b/src/components/master/MasterModal.jsx index 52e22114..0a472727 100644 --- a/src/components/master/MasterModal.jsx +++ b/src/components/master/MasterModal.jsx @@ -1,5 +1,4 @@ import React, { useState, useEffect } from "react"; - import CreateRole from "./CreateRole"; import DeleteMaster from "./DeleteMaster"; import EditRole from "./EditRole"; @@ -18,67 +17,45 @@ import CreateContactTag from "./CreateContactTag"; import EditContactCategory from "./EditContactCategory"; import EditContactTag from "./EditContactTag"; import { useDeleteMasterItem } from "../../hooks/masterHook/useMaster"; +import ManageExpenseType from "./ManageExpenseType"; +import ManagePaymentMode from "./ManagePaymentMode"; +import ManageExpenseStatus from "./ManageExpenseStatus"; + const MasterModal = ({ modaldata, closeModal }) => { const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const { mutate: deleteMasterItem, isPending } = useDeleteMasterItem(); - // const handleSelectedMasterDeleted = async () => - // { - // debugger - // const deleteFn = MasterRespository[modaldata.masterType]; - // if (!deleteFn) { - // showToast(`No delete strategy defined for master type`,"error"); - // return false; - // } - // try - // { - // const response = await deleteFn( modaldata?.item?.id ); - // const selected_cachedData = getCachedData( modaldata?.masterType ); - // const updated_master = selected_cachedData?.filter(item => item.id !== modaldata?.item.id); - // cacheData( modaldata?.masterType, updated_master ) - - // showToast(`${modaldata?.masterType} is deleted successfully`, "success"); - // handleCloseDeleteModal() - - // } catch ( error ) - // { - // const message = error.response.data.message || error.message || "Error occured api during call" - // showToast(message, "success"); - // } - // } - const handleSelectedMasterDeleted = () => { - if (!modaldata?.masterType || !modaldata?.item?.id) { + const { masterType, item, validateFn } = modaldata || {}; + if (!masterType || !item?.id) { showToast("Missing master type or item", "error"); return; } + deleteMasterItem( - { - masterType: modaldata.masterType, - item: modaldata.item, - validateFn: modaldata.validateFn, // optional - }, - { - onSuccess: () => { - handleCloseDeleteModal(); - }, - } + { masterType, item, validateFn }, + { onSuccess: handleCloseDeleteModal } ); }; + const handleCloseDeleteModal = () => { + setIsDeleteModalOpen(false); + closeModal(); + }; + useEffect(() => { if (modaldata?.modalType === "delete") { setIsDeleteModalOpen(true); } }, [modaldata]); - const handleCloseDeleteModal = () => { - setIsDeleteModalOpen(false); + if (!modaldata?.modalType) { closeModal(); - }; + return null; + } - if (modaldata?.modalType === "delete" && isDeleteModalOpen) { + if (modaldata.modalType === "delete" && isDeleteModalOpen) { return (
{
); } + + const renderModalContent = () => { + const { modalType, item, masterType } = modaldata; + + const modalComponents = { + "Application Role": , + "Edit-Application Role": , + "Job Role": , + "Edit-Job Role": , + "Activity": , + "Edit-Activity": , + "Work Category": , + "Edit-Work Category": , + "Contact Category": , + "Edit-Contact Category": , + "Contact Tag": , + "Edit-Contact Tag": , + "Expense Type":, + "Edit-Expense Type":, + "Payment Mode":, + "Edit-Payment Mode":, + "Expense Status":, + "Edit-Expense Status": + }; + + return modalComponents[modalType] || null; + }; + + const isLargeModal = ["Application Role", "Edit-Application Role"].includes( + modaldata.modalType + ); + return (