diff --git a/src/electron/api/axiosClient.ts b/src/electron/api/axiosClient.ts index b521203..d02ed8b 100644 --- a/src/electron/api/axiosClient.ts +++ b/src/electron/api/axiosClient.ts @@ -13,7 +13,7 @@ const httpsAgent = new https.Agent({ const api: AxiosInstance = axios.create({ // https://dreadingly-unfighting-pandora.ngrok-free.dev/ - baseURL: 'https://dreadingly-unfighting-pandora.ngrok-free.dev/api/v1/va', // πŸ” your backend + baseURL: 'https://daniela-nonprofessed-boastfully.ngrok-free.app/api/v1/va', // πŸ” your backend httpsAgent // timeout: 10000, }); @@ -42,7 +42,7 @@ api.interceptors.request.use( api.interceptors.response.use( (response: AxiosResponse) => response.data, async (error) => { - // console.error('[Axios Error]', error.message); + console.error('[Axios Error]', error.message); if (error.response?.status === 401 || 403) { console.warn('Token expired β€” trigger refresh logic if needed'); sendTokenExpired(); diff --git a/src/electron/ipc/ipcHandler.ts b/src/electron/ipc/ipcHandler.ts index 8d1b67f..dfa4558 100644 --- a/src/electron/ipc/ipcHandler.ts +++ b/src/electron/ipc/ipcHandler.ts @@ -405,11 +405,10 @@ ipcMain.handle("get-payslips", async (event, vaId: string) => { console.log("get-payslips", vaId) try { const response = await api.get(`/paySlip/${vaId}`); - - + console.log(response.data , "Line 409") return { success: true, - data: response.data?.data || [], + data: response.data || [], }; } catch (error: any) { let message = "Unknown error occurred"; @@ -439,10 +438,10 @@ ipcMain.handle("get-leave-list", async () => { const res = await api.get("/leaves/all"); console.log("/leaves/all", res.data); - return { - success: true, - data: res.data, - }; + return { + success: true, + data: res.data, + }; } catch (error: any) { let message = "Unknown error occurred"; @@ -468,10 +467,10 @@ ipcMain.handle("get-adjustments-list", async (event, vaId: string) => { const res = await api.get(`/timesheet/adjustments/list?vaId=${vaId}`); console.log("/timesheet/adjustments/list", res.data); - return { - success: true, - data: res.data, - }; + return { + success: true, + data: res.data, + }; } catch (error: any) { let message = "Unknown error occurred"; diff --git a/src/electron/utils/utilityFunctions.ts b/src/electron/utils/utilityFunctions.ts index c82ca6c..0a15c7d 100644 --- a/src/electron/utils/utilityFunctions.ts +++ b/src/electron/utils/utilityFunctions.ts @@ -68,7 +68,7 @@ export const handleLogin = async (credentials: { email: string, password: string try { console.log("➑️ Calling API..."); const response = await axios.post( - 'https://dreadingly-unfighting-pandora.ngrok-free.dev/api/v1/va/login', + 'https://daniela-nonprofessed-boastfully.ngrok-free.app/api/v1/va/login', { userDetails: { ...credentials } }, { headers: { "ngrok-skip-browser-warning": 69420 }, httpsAgent } ); diff --git a/src/ui/hooks/useActiveAppTracker.tsx b/src/ui/hooks/useActiveAppTracker.tsx index a7620d1..3099d19 100644 --- a/src/ui/hooks/useActiveAppTracker.tsx +++ b/src/ui/hooks/useActiveAppTracker.tsx @@ -8,14 +8,12 @@ interface DailyHours { remaining: string; } - - export const useActiveAppTracker = ( isTracking: boolean, activeProjectLogId: string | null, projectId: string | null, projectName: string | null, - onLimitExceeded?: () => void // Callback when limit is reached + onLimitExceeded?: () => void ) => { const dispatch = useDispatch(); const lastActiveRef = useRef(null); @@ -24,8 +22,9 @@ export const useActiveAppTracker = ( const intervalIdRef = useRef(null); const finalizeTrackingRef = useRef<(() => Promise) | null>(null); const limitCheckedRef = useRef(false); + const isStoppingRef = useRef(false); // βœ… Flag to prevent new requests - const canSend = !!(activeProjectLogId && projectId); + const canSend = !!(activeProjectLogId && projectId && !isStoppingRef.current); // βœ… Parse time string to minutes const parseTimeToMinutes = (timeStr: string): number => { @@ -53,7 +52,6 @@ export const useActiveAppTracker = ( exceeded: remainingMins <= 0 }); - // If remaining time is 0 or negative, limit is exceeded if (remainingMins <= 0) { return true; } @@ -61,6 +59,7 @@ export const useActiveAppTracker = ( return false; }; + // βœ… Send activity log (POST for new, PATCH for update) const sendActivityLog = async ( projectLogId: string, projectId: string, @@ -68,8 +67,14 @@ export const useActiveAppTracker = ( appName: string, isNew: boolean ) => { + // βœ… CRITICAL: Block all requests when stopping + if (isStoppingRef.current) { + console.log("🚫 Stopping in progress β€” blocking activity log"); + return null; + } + if (!canSend) { - console.log("🚫 Skipping API β€” missing activeProjectLogId or projectId"); + console.log("🚫 Skipping API β€” missing required IDs"); return null; } @@ -99,20 +104,18 @@ export const useActiveAppTracker = ( // Store the log ID for future updates if (isNew && res?.data?._id) { - console.log("res?.data?._id", res?.data?._id) lastLogIdRef.current = res.data._id; + console.log("πŸ’Ύ Stored log ID:", res.data._id); } // βœ… Check daily limit after every API call if (res?.dailyHours) { - console.log("res?.data?.dailyHours", res?.dailyHours) - const limitExceeded = checkDailyLimit(res?.dailyHours); + const limitExceeded = checkDailyLimit(res.dailyHours); if (limitExceeded && !limitCheckedRef.current) { limitCheckedRef.current = true; - console.warn("🚨 Daily limit exceeded! Stopping tracking..."); + console.warn("🚨 Daily limit exceeded!"); - // Trigger stop tracking callback if (onLimitExceeded) { setTimeout(() => { onLimitExceeded(); @@ -128,54 +131,69 @@ export const useActiveAppTracker = ( } }; - // βœ… Final PATCH before stopping + // βœ… Stop the interval immediately + const stopInterval = useCallback(() => { + console.log("πŸ›‘ Stopping activity tracking interval..."); + if (intervalIdRef.current) { + clearInterval(intervalIdRef.current); + intervalIdRef.current = null; + console.log("βœ… Interval cleared"); + } + }, []); + + // βœ… Send final PATCH before stopping (called by handleStopTracking) const sendFinalPatch = useCallback(async () => { - if (!canSend) { - console.log("🚫 Skipping final PATCH β€” no active project log ID"); + console.log("=".repeat(60)); + console.log("πŸ›‘ FINAL PATCH INITIATED"); + console.log("=".repeat(60)); + + // βœ… STEP 1: Set stopping flag IMMEDIATELY + isStoppingRef.current = true; + console.log("πŸ”’ Stopping flag set β€” no new requests allowed"); + + // βœ… STEP 2: Stop the interval IMMEDIATELY + stopInterval(); + + if (!activeProjectLogId || !projectId) { + console.log("⚠️ Skipping final PATCH β€” no active project log ID"); return; } - console.log("=".repeat(60)); - console.log("πŸ›‘ FINAL PATCH INITIATED - Recording last activity"); - console.log("=".repeat(60)); - + // βœ… STEP 3: Send ONE final PATCH for the last active window if (lastLogIdRef.current && lastTitleRef.current && lastActiveRef.current) { try { console.log("πŸ“Š Final Activity Details:"); - console.log(` β€’ Window Title: ${lastTitleRef.current}`); - console.log(` β€’ App Name: ${lastActiveRef.current.owner.name}`); + console.log(` β€’ Window: ${lastTitleRef.current}`); + console.log(` β€’ App: ${lastActiveRef.current.owner.name}`); console.log(` β€’ Log ID: ${lastLogIdRef.current}`); - console.log(` β€’ Project ID: ${projectId}`); + console.log(` β€’ Project: ${projectId}`); const result = await sendActivityLog( - activeProjectLogId!, - projectId!, + activeProjectLogId, + projectId, lastTitleRef.current, lastActiveRef.current.owner.name || "Unknown", - false // This is a PATCH (update), not a POST (new) + false // PATCH (update existing log) ); console.log("βœ… Final PATCH sent successfully!"); if (result?.dailyHours) { - console.log("πŸ“ˆ Final Daily Hours Summary:"); - console.log(` β€’ Today Total: ${result.dailyHours.todayTotal}`); - console.log(` β€’ Daily Limit: ${result.dailyHours.dailyLimit}`); + console.log("πŸ“ˆ Final Daily Hours:"); + console.log(` β€’ Today: ${result.dailyHours.todayTotal}`); + console.log(` β€’ Limit: ${result.dailyHours.dailyLimit}`); console.log(` β€’ Remaining: ${result.dailyHours.remaining}`); } } catch (error) { console.error("❌ Final PATCH failed:", error); - throw error; // Re-throw to let caller handle + throw error; } } else { console.log("ℹ️ No active window to finalize"); - console.log(" β€’ Last Log ID:", lastLogIdRef.current || "None"); - console.log(" β€’ Last Title:", lastTitleRef.current || "None"); - console.log(" β€’ Last Active Window:", lastActiveRef.current ? "Yes" : "No"); } - // Clean up all refs + // βœ… STEP 4: Clean up all refs console.log("🧹 Cleaning up tracking references..."); lastActiveRef.current = null; lastTitleRef.current = null; @@ -183,70 +201,88 @@ export const useActiveAppTracker = ( limitCheckedRef.current = false; console.log("βœ… Cleanup complete"); console.log("=".repeat(60)); - }, [activeProjectLogId, projectId, canSend]); + }, [activeProjectLogId, projectId, stopInterval]); + // βœ… Update ref whenever sendFinalPatch changes useEffect(() => { finalizeTrackingRef.current = sendFinalPatch; }, [sendFinalPatch]); + // βœ… Main tracking interval effect useEffect(() => { - // Reset limit check when tracking starts + // Reset flags when tracking starts if (isTracking) { limitCheckedRef.current = false; + isStoppingRef.current = false; + console.log("🟒 Tracking started β€” flags reset"); } if (!isTracking || !canSend) { - console.log("🟑 Tracking inactive or missing IDs β€” skipping tracker setup"); + console.log("🟑 Tracking inactive β€” cleaning up"); + stopInterval(); return; } console.log(`🟒 Starting active tracker for: ${projectName}`); const fetchActiveApp = async () => { + // βœ… Double-check stopping flag before each fetch + if (isStoppingRef.current) { + console.log("🚫 Stopping in progress β€” skipping fetch"); + return; + } + try { const details = (await window.electron.getActiveAppDetails()) as WindowInfo; if (!details || !details.id) return; - console.log("details" , details) - const title = details.title; const appName = details.owner.name; const prevTitle = lastTitleRef.current; const hasChanged = !prevTitle || prevTitle !== title; if (hasChanged) { + // PATCH previous window before creating new log if (lastLogIdRef.current && prevTitle) { - console.log("πŸ”„ Patching previous log:", prevTitle); + console.log("πŸ”„ Updating previous window:", prevTitle); await sendActivityLog(activeProjectLogId!, projectId!, prevTitle, appName, false); } - console.log("πŸ†• New title detected β†’ creating POST:", title); + // POST new window log + console.log("πŸ†• New window detected:", title); await sendActivityLog(activeProjectLogId!, projectId!, title, appName, true); + // Update refs and Redux lastActiveRef.current = details; lastTitleRef.current = title; dispatch(setActiveWindow({ window: details, projectId, projectName })); } else { - console.log("⏸ Same window, skipping log"); + console.log("⏸️ Same window, skipping"); } } catch (error) { - console.error("Failed to fetch active app:", error); + console.error("❌ Failed to fetch active app:", error); } }; + // Initial fetch fetchActiveApp(); + + // Set up interval (every 5 seconds) intervalIdRef.current = setInterval(fetchActiveApp, 5000); + // Cleanup on unmount return () => { console.log("🧹 Cleaning up tracker interval"); - if (intervalIdRef.current) { - clearInterval(intervalIdRef.current); - intervalIdRef.current = null; - } + stopInterval(); }; - }, [isTracking, activeProjectLogId]); + }, [isTracking, activeProjectLogId, projectId, projectName, dispatch, stopInterval]); return { finalizeTracking: finalizeTrackingRef.current, + stopInterval, + setIsStopping: (value: boolean) => { + isStoppingRef.current = value; + console.log(`πŸ”’ Stopping flag manually set to: ${value}`); + } }; }; \ No newline at end of file diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index 547fe9f..46d39d8 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -135,155 +135,10 @@ const DashboardLayout = () => { document.documentElement.setAttribute("data-theme", theme); }, [theme]); - // Create a ref to store the stop function so it can be called from the hook const stopTrackingRef = useRef<(() => Promise) | null>(null); const limitAlertShownRef = useRef(false); - // Define handleStopTracking FIRST - const handleStopTracking = useCallback(async () => { - if (loadingStop || loadingStart) return; - - if (!selectedProject) { - console.warn("No selected project to stop"); - return; - } - - // Reset alert flag when stopping - limitAlertShownRef.current = false; - - let currentStopTime = new Date().toISOString(); - const projectId = selectedProject.projectId; - - console.log(" Stop tracking initiated for:", selectedProject.projectName); - console.log(" Current elapsed time:", elapsedTime); - - setIsStopping(true); - setLoadingStop(true); - handleSocketDisconnect(); - - // STEP 1: Clear timer FIRST to prevent further client-side updates - if (timerRef.current) { - clearInterval(timerRef.current); - timerRef.current = null; - console.log("Timer cleared"); - } - - // STEP 2: Send final PATCH with last active window data - if (stopTrackingRef.current) { - try { - console.log(" Sending final PATCH to record last activity..."); - await stopTrackingRef.current(); - console.log(" Final tracking data sent successfully"); - } catch (error) { - console.error(" Error sending final tracking data:", error); - } - } - - // STEP 3: Send LOGOUT to backend - try { - const payload = { - vaId: projectSummary.vaId, - projectId: projectId, - type: "LOGOUT", - time: currentStopTime - }; - - console.log("πŸ“‘ Sending LOGOUT request:", payload); - const res = await window.electron.stopProjectTracking(payload); - console.log(" LOGOUT successful:", res); - } catch (error) { - console.error(" Failed to send LOGOUT:", error); - } - - // STEP 4: Update Redux tracking state to stop tracking - console.log("πŸ”„ Updating Redux state to stop tracking..."); - dispatch( - stopTrackingReducer({ - projectId, - }) - ); - - // Allow React to process the state update - await new Promise(resolve => setTimeout(resolve, 50)); - - // STEP 5: Refresh project list to get updated work hours from server - try { - console.log("πŸ”„ Refreshing project list..."); - const projectListRes = await window.electron.getProjectList(); - console.log("πŸ“Š Updated project list:", projectListRes); - - if (projectListRes?.success && Array.isArray(projectListRes.data)) { - // Update projects data - dispatch(setProjectsDataSlice(projectListRes.data)); - - // Update project summary - dispatch( - setProjectSummarySlice({ - totalProjects: projectListRes.totalProjects || 0, - activeProjects: projectListRes.activeProjects || 0, - awayProjects: projectListRes.awayProjects || 0, - totalWorkTime: projectListRes.totalWorkTime || "0h 0m 0s", - totalWorkMs: projectListRes.totalWorkMs || 0, - vaName: projectListRes.vaName || "", - vaId: projectListRes.vaId || "", - }) - ); - - // Update selected project with fresh data - const updatedProject = projectListRes.data.find( - (p: Project) => p.projectId === projectId - ); - - if (updatedProject) { - dispatch(setSelectedProjectSlice(updatedProject)); - - // CRITICAL FIX: Use server time instead of client elapsedTime - const serverElapsedTime = parseTimeStringToSeconds(updatedProject.totalWorkTime); - - console.log("πŸ”„ Syncing client time with server time:", { - serverTime: updatedProject.totalWorkTime, - serverElapsedSeconds: serverElapsedTime, - previousClientTime: elapsedTime, - difference: serverElapsedTime - elapsedTime - }); - - // Force update the tracking map with server time - dispatch( - updateProjectTracking({ - projectId: updatedProject.projectId, - data: { - elapsedTime: serverElapsedTime, - accumulatedTime: serverElapsedTime, - isTracking: false, - isPaused: false, - startTime: null, - endTime: Date.now(), - pauseStartTime: null, - totalPausedTime: 0 - } - }) - ); - - console.log("βœ… Selected project updated with server time"); - } - } - - // STEP 6: Update UI (window restore) - await window.electron.toggleCloseBtn(true); - await window.electron.resizeWindow(500, 600); - toggleWindowSize.current = true; - - } catch (error) { - console.error("❌ Error refreshing project list:", error); - } finally { - // Clear loading states - setLoadingStop(false); - setIsStopping(false); - console.log("πŸŽ‰ TRACKING STOPPED SUCCESSFULLY"); - } - }, [selectedProject, projectSummary, elapsedTime, dispatch]); - // NOW define handleLimitExceeded using the defined handleStopTracking const handleLimitExceeded = useCallback(async () => { console.log("🚨 LIMIT EXCEEDED - Showing alert (not stopping)"); @@ -316,7 +171,7 @@ const DashboardLayout = () => { }, [selectedProject]); // Initialize useActiveAppTracker and store finalizeTracking in ref - const { finalizeTracking } = useActiveAppTracker( + const { finalizeTracking, setIsStopping: setIsStoppingInTracker } = useActiveAppTracker( isTracking, activeProjectLogId, selectedProject?.projectId ?? null, @@ -324,6 +179,186 @@ const DashboardLayout = () => { handleLimitExceeded ); + // Define handleStopTracking FIRST + const handleStopTracking = useCallback(async () => { + if (loadingStop || loadingStart) { + console.log("⏸️ Already processing, skipping..."); + return; + } + + if (!selectedProject) { + console.warn("⚠️ No selected project to stop"); + return; + } + + console.log("=".repeat(70)); + console.log("πŸ›‘ STOP TRACKING INITIATED"); + console.log("=".repeat(70)); + console.log(`πŸ“¦ Project: ${selectedProject.projectName}`); + console.log(`⏱️ Elapsed Time: ${elapsedTime}s`); + + // Reset alert flag when stopping + limitAlertShownRef.current = false; + + const currentStopTime = new Date().toISOString(); + const projectId = selectedProject.projectId; + + // βœ… CRITICAL: Set stopping flag IMMEDIATELY to block all new tab tracking + console.log("πŸ”’ Setting stopping flag..."); + if (setIsStoppingInTracker) { + setIsStoppingInTracker(true); + } + + setIsStopping(true); + setLoadingStop(true); + handleSocketDisconnect(); + + // STEP 1: Clear local timer to prevent further client-side updates + console.log("⏹️ STEP 1: Clearing local timer..."); + if (timerRef.current) { + clearInterval(timerRef.current); + timerRef.current = null; + console.log("βœ… Timer cleared"); + } + + // STEP 2: Send final PATCH with last active window data + console.log("πŸ“€ STEP 2: Sending final activity tracking..."); + if (stopTrackingRef.current) { + try { + await stopTrackingRef.current(); // This calls sendFinalPatch() in the hook + console.log("βœ… Final activity tracking completed"); + } catch (error) { + console.error("❌ Error during final tracking:", error); + } + } else { + console.log("⚠️ No tracking ref available"); + } + + // STEP 3: Send LOGOUT to backend + console.log("πŸšͺ STEP 3: Sending LOGOUT request..."); + try { + const payload = { + vaId: projectSummary.vaId, + projectId: projectId, + type: "LOGOUT", + time: currentStopTime + }; + + console.log("πŸ“‘ Logout payload:", payload); + const res = await window.electron.stopProjectTracking(payload); + console.log("βœ… LOGOUT successful:", res); + } catch (error) { + console.error("❌ LOGOUT failed:", error); + } + + // STEP 4: Update Redux tracking state + console.log("πŸ”„ STEP 4: Updating Redux state..."); + dispatch( + stopTrackingReducer({ + projectId, + }) + ); + + // STEP 6: Update UI (restore window) + console.log("πŸͺŸ STEP 6: Restoring window..."); + await window.electron.toggleCloseBtn(true); + await window.electron.resizeWindow(500, 600); + toggleWindowSize.current = true; + console.log("βœ… Window restored"); + + // Allow React to process the state update + await new Promise(resolve => setTimeout(resolve, 50)); + + // STEP 5: Refresh project list to get updated work hours from server + console.log("πŸ“Š STEP 5: Refreshing project list..."); + try { + const projectListRes = await window.electron.getProjectList(); + console.log("βœ… Project list fetched:", projectListRes); + + if (projectListRes?.success && Array.isArray(projectListRes.data)) { + // Update projects data + dispatch(setProjectsDataSlice(projectListRes.data)); + + // Update project summary + dispatch( + setProjectSummarySlice({ + totalProjects: projectListRes.totalProjects || 0, + activeProjects: projectListRes.activeProjects || 0, + awayProjects: projectListRes.awayProjects || 0, + totalWorkTime: projectListRes.totalWorkTime || "0h 0m 0s", + totalWorkMs: projectListRes.totalWorkMs || 0, + vaName: projectListRes.vaName || "", + vaId: projectListRes.vaId || "", + }) + ); + + // Update selected project with fresh data from server + const updatedProject = projectListRes.data.find( + (p: Project) => p.projectId === projectId + ); + + if (updatedProject) { + dispatch(setSelectedProjectSlice(updatedProject)); + + // Sync client time with server time + const serverElapsedTime = parseTimeStringToSeconds(updatedProject.totalWorkTime); + + console.log("πŸ”„ Syncing with server time:", { + serverTime: updatedProject.totalWorkTime, + serverSeconds: serverElapsedTime, + clientSeconds: elapsedTime, + difference: serverElapsedTime - elapsedTime + }); + + // Force update the tracking map with server time + dispatch( + updateProjectTracking({ + projectId: updatedProject.projectId, + data: { + elapsedTime: serverElapsedTime, + accumulatedTime: serverElapsedTime, + isTracking: false, + isPaused: false, + startTime: null, + endTime: Date.now(), + pauseStartTime: null, + totalPausedTime: 0 + } + }) + ); + + console.log("βœ… Project synced with server"); + } + } + + + + } catch (error) { + console.error("❌ Error refreshing project list:", error); + } finally { + // Clear loading states + setLoadingStop(false); + setIsStopping(false); + + // βœ… Reset stopping flag for next session + if (setIsStoppingInTracker) { + setIsStoppingInTracker(false); + } + + console.log("=".repeat(70)); + console.log("πŸŽ‰ TRACKING STOPPED SUCCESSFULLY"); + console.log("=".repeat(70)); + } + }, [ + selectedProject, + projectSummary, + elapsedTime, + dispatch, + loadingStop, + loadingStart, + setIsStoppingInTracker + ]); + // Update the ref whenever finalizeTracking changes useEffect(() => { stopTrackingRef.current = finalizeTracking; @@ -362,7 +397,7 @@ const DashboardLayout = () => { return () => { console.log("🧹 Cleaning token-expired listener"); }; - }, [isTracking, handleStopTracking]); + }, [isTracking]); const handleLogout = async () => { @@ -585,7 +620,7 @@ const DashboardLayout = () => { // socketRef.current?.emit("getVisibleApps", { token: await token, apps }); // }); - socketRef.current.on("startSendAppsDetails", async ({projId}) => { + socketRef.current.on("startSendAppsDetails", async ({ projId }) => { if (projId !== selectedProject?.projectId) return; console.log("▢️ startSendAppsDetails received"); @@ -609,7 +644,7 @@ const DashboardLayout = () => { }, 5000); }); - socketRef.current.on("stopSendAppsDetails", async ({projId}) => { + socketRef.current.on("stopSendAppsDetails", async ({ projId }) => { if (projId !== selectedProject?.projectId) return; console.log("⏹ stopSendAppsDetails received"); diff --git a/src/ui/pages/PaySlips/PaysSlips.tsx b/src/ui/pages/PaySlips/PaysSlips.tsx index b54e3c9..d3d9ca5 100644 --- a/src/ui/pages/PaySlips/PaysSlips.tsx +++ b/src/ui/pages/PaySlips/PaysSlips.tsx @@ -36,6 +36,8 @@ const PaysSlips = () => { return; } + console.log(response , "getPayslips") + // Extract payrolls array from first index const payrolls = response[0]?.payrolls || [];