From 68eb8539876abf935c2fad0ee2218748f6c6073e Mon Sep 17 00:00:00 2001 From: JaiPandey425 Date: Thu, 11 Dec 2025 19:44:58 +0530 Subject: [PATCH] change the tabs logs data login when stop send logs tabs data then logout --- src/electron/ipc/ipcHandler.ts | 61 ++-- src/ui/hooks/useActiveAppTracker.tsx | 424 +++++++++++++-------------- src/ui/layout/DashboardLayout.tsx | 281 ++++++++---------- src/ui/store/index.tsx | 2 + src/ui/store/logsSlice.tsx | 182 ++++++++++++ types.d.ts | 27 +- 6 files changed, 552 insertions(+), 425 deletions(-) create mode 100644 src/ui/store/logsSlice.tsx diff --git a/src/electron/ipc/ipcHandler.ts b/src/electron/ipc/ipcHandler.ts index d88f698..06eb15a 100644 --- a/src/electron/ipc/ipcHandler.ts +++ b/src/electron/ipc/ipcHandler.ts @@ -15,17 +15,17 @@ export const registerIPCHandlers = (mainWindow: BrowserWindow) => { ipcMain.handle("GET_BASE_URL", () => { - return getBaseURL(); -}); + return getBaseURL(); + }); -// UPDATE Base URL -ipcMain.handle("UPDATE_BASE_URL", (event, url: string) => { - setBaseURL(url); - return { - updated: true, - baseURL: url - }; -}); + // UPDATE Base URL + ipcMain.handle("UPDATE_BASE_URL", (event, url: string) => { + setBaseURL(url); + return { + updated: true, + baseURL: url + }; + }); ipcMain.handle("get-token", async (_, key: "ACCESS_TOKEN" | "REFRESH_TOKEN") => { return await getToken(key); @@ -168,39 +168,30 @@ ipcMain.handle("stop-project-tracking", async (event, payload) => { // tabs opening and closing when and tracking start ipcMain.handle("tracking-tabs-opening-and-closing", async (event, payload) => { try { - console.log("🟦 IPC tracking-tabs-opening-and-closing:", payload); + console.log("🟦 IPC received payload:", payload); - let response; - if (!payload.id) { - // POST new log (new tab opened) - response = await api.post("/log/project/track", { - logId: payload.logId, - projectId: payload.projectId, - title: payload.title, - appName: payload.appName, - }); - console.log(" New log created:", response); - } else { - // PATCH existing log (same tab, update) - response = await api.patch("/log/project/track", { - id: payload.id, - logId: payload.logId, - projectId: payload.projectId, - title: payload.title, - appName: payload.appName, - }); - console.log("Log updated:", response); - } + const response = await api.post("/log/project/track", payload); - return response; + console.log("🟩 Log track API success:", response.data); + + return { + success: true, + data: response.data, + }; } catch (error) { - console.error("Error syncing project tab tracking:", error); - throw error; + console.error("❌ Log track API error:", error); + + return { + success: false, + message: "Something went wrong", + error, + }; } }); + ipcMain.handle("get-project-logs", async (event, { projectID, fromDate, toDate }) => { try { console.log("πŸ“₯ get-project-logs called with:", { projectID, fromDate, toDate }); diff --git a/src/ui/hooks/useActiveAppTracker.tsx b/src/ui/hooks/useActiveAppTracker.tsx index 3099d19..8d24af1 100644 --- a/src/ui/hooks/useActiveAppTracker.tsx +++ b/src/ui/hooks/useActiveAppTracker.tsx @@ -1,6 +1,14 @@ +// hooks/useActiveAppTracker.tsx import { useEffect, useRef, useCallback } from "react"; -import { useDispatch } from "react-redux"; -import { setActiveWindow, WindowInfo } from "../store/windowSlice"; +import { useDispatch, useSelector } from "react-redux"; +import { RootState } from "../store"; +import { WindowInfo } from "../store/windowSlice"; +import { + initializeProjectLogs, + updateWindowLog, + clearProjectLogs, + debugLogsState, +} from "../store/logsSlice"; interface DailyHours { todayTotal: string; @@ -16,273 +24,253 @@ export const useActiveAppTracker = ( onLimitExceeded?: () => void ) => { const dispatch = useDispatch(); + const { logsByProject } = useSelector((state: RootState) => state.logs); const lastActiveRef = useRef(null); - const lastLogIdRef = useRef(null); const lastTitleRef = useRef(null); - const intervalIdRef = useRef(null); - const finalizeTrackingRef = useRef<(() => Promise) | null>(null); - const limitCheckedRef = useRef(false); - const isStoppingRef = useRef(false); // βœ… Flag to prevent new requests + const lastAppNameRef = useRef(null); + const intervalIdRef = useRef | null>(null); + const limitCheckedRef = useRef(false); + const isStoppingRef = useRef(false); + const checkCountRef = useRef(0); - const canSend = !!(activeProjectLogId && projectId && !isStoppingRef.current); + // Initialize logs when tracking starts + useEffect(() => { + if (isTracking && activeProjectLogId && projectId) { + // Initialize fresh logs for this tracking session + dispatch( + initializeProjectLogs({ + projectId, + logId: activeProjectLogId, + }) + ); + console.log(` Logs initialized for project: ${projectName} (${projectId})`); - // βœ… Parse time string to minutes - const parseTimeToMinutes = (timeStr: string): number => { - const hourMatch = timeStr.match(/(\d+)h/); - const minMatch = timeStr.match(/(\d+)m/); - - const hours = hourMatch ? parseInt(hourMatch[1]) : 0; - const minutes = minMatch ? parseInt(minMatch[1]) : 0; - - return hours * 60 + minutes; - }; - - // βœ… Check if daily limit is exceeded - const checkDailyLimit = (dailyHours?: DailyHours): boolean => { - if (!dailyHours) return false; - - const todayTotalMins = parseTimeToMinutes(dailyHours.todayTotal); - const dailyLimitMins = parseTimeToMinutes(dailyHours.dailyLimit); - const remainingMins = parseTimeToMinutes(dailyHours.remaining); - - console.log("⏰ Daily Limit Check:", { - todayTotal: `${todayTotalMins} mins`, - dailyLimit: `${dailyLimitMins} mins`, - remaining: `${remainingMins} mins`, - exceeded: remainingMins <= 0 - }); - - if (remainingMins <= 0) { - return true; + // Reset refs + lastActiveRef.current = null; + lastTitleRef.current = null; + lastAppNameRef.current = null; + checkCountRef.current = 0; + isStoppingRef.current = false; } + }, [isTracking, activeProjectLogId, projectId, projectName, dispatch]); - return false; - }; - - // βœ… Send activity log (POST for new, PATCH for update) - const sendActivityLog = async ( - projectLogId: string, - projectId: string, - title: string, - 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 required IDs"); - return null; - } - - try { - const payload: { - id?: string; - logId: string; - projectId: string; - title: string; - appName: string; - } = { - logId: projectLogId, - projectId, - title, - appName, - }; - - if (!isNew && lastLogIdRef.current) { - payload.id = lastLogIdRef.current; - } - - console.log(isNew ? "➑️ POST new log" : "✏️ PATCH update log", payload); - - const res = await window.electron.trackingTabsOpeningClosing(payload); - - console.log("πŸ“Š Activity log response:", res); - - // Store the log ID for future updates - if (isNew && 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) { - const limitExceeded = checkDailyLimit(res.dailyHours); - - if (limitExceeded && !limitCheckedRef.current) { - limitCheckedRef.current = true; - console.warn("🚨 Daily limit exceeded!"); - - if (onLimitExceeded) { - setTimeout(() => { - onLimitExceeded(); - }, 500); - } - } - } - - return res; - } catch (error) { - console.error("❌ Activity log error:", error); - throw error; - } - }; - - // βœ… Stop the interval immediately - const stopInterval = useCallback(() => { - console.log("πŸ›‘ Stopping activity tracking interval..."); + // Stop tracking intervals + const stopIntervals = useCallback(() => { 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 () => { - 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; + // Debug function to check logs state + const debugCurrentLogs = useCallback(() => { + if (projectId) { + dispatch(debugLogsState({ projectId })); } + }, [projectId, dispatch]); + + // Finalize and send all logs when stopping tracking + const finalizeAndSendLogs = useCallback( + async (vaId: string): Promise => { + // console.log("=".repeat(60)); + console.log(" FINALIZING AND SENDING ALL LOGS"); + // console.log("=".repeat(60)); + + // Set stopping flag + isStoppingRef.current = true; + + // Stop intervals immediately + stopIntervals(); + + if (!activeProjectLogId || !projectId) { + console.log("⚠️ Skipping β€” no active project"); + return false; + } + + const projectLogs = logsByProject[projectId]; + if (!projectLogs || projectLogs.logs.length === 0) { + console.log("ℹ️ No logs found for project", projectId); + return true; + } - // βœ… 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: ${lastTitleRef.current}`); - console.log(` β€’ App: ${lastActiveRef.current.owner.name}`); - console.log(` β€’ Log ID: ${lastLogIdRef.current}`); - console.log(` β€’ Project: ${projectId}`); + const nowUTC = new Date().toISOString(); - const result = await sendActivityLog( - activeProjectLogId, - projectId, - lastTitleRef.current, - lastActiveRef.current.owner.name || "Unknown", - false // PATCH (update existing log) - ); + // Finalize the last log entry + const allLogs = [...projectLogs.logs]; + // if (allLogs.length > 0) { + // const lastLog = allLogs[allLogs.length - 1]; + // const endTime = new Date(nowUTC); + // const startTime = new Date(lastLog.from); + // lastLog.to = nowUTC; + // lastLog.totalTime = endTime.getTime() - startTime.getTime(); + // console.log(`πŸ›‘ Finalized last window: ${lastLog.title} (${lastLog.totalTime}ms)`); + // } - console.log("βœ… Final PATCH sent successfully!"); + // Filter out logs with very short duration (less than 1 second) + const validLogs = allLogs.filter((log) => { + const duration = new Date(log.to).getTime() - new Date(log.from).getTime(); + return duration >= 1000; // Minimum 1 second + }); - if (result?.dailyHours) { - console.log("πŸ“ˆ Final Daily Hours:"); - console.log(` β€’ Today: ${result.dailyHours.todayTotal}`); - console.log(` β€’ Limit: ${result.dailyHours.dailyLimit}`); - console.log(` β€’ Remaining: ${result.dailyHours.remaining}`); + console.log(`πŸ“Š Total logs: ${allLogs.length}, Valid logs (β‰₯1s): ${validLogs.length}`); + + if (validLogs.length === 0) { + console.log("ℹ️ No valid logs to send"); + dispatch(clearProjectLogs({ projectId })); + return true; } + // Prepare payload with all valid logs + const payload = { + vaId: vaId || "", + projectId, + logId: activeProjectLogId, + logs: allLogs.map((log) => ({ + title: log.title, + appName: log.appName, + url: log.url ?? "", + from: log.from, + to: log.to, + totalTime: log.totalTime, + })), + }; + + console.log("πŸ“€ Payload to send:", payload); + console.log("πŸ“Š Sending logs summary:", { + projectId, + projectName, + logCount: validLogs.length, + totalTime: validLogs.reduce((sum, log) => sum + log.totalTime, 0), + firstLog: validLogs[0] + ? { + title: validLogs[0].title.substring(0, 40), + appName: validLogs[0].appName, + duration: validLogs[0].totalTime, + from: new Date(validLogs[0].from).toLocaleTimeString(), + to: new Date(validLogs[0].to).toLocaleTimeString(), + } + : null, + lastLog: validLogs[validLogs.length - 1] + ? { + title: validLogs[validLogs.length - 1].title.substring(0, 40), + appName: validLogs[validLogs.length - 1].appName, + duration: validLogs[validLogs.length - 1].totalTime, + from: new Date(validLogs[validLogs.length - 1].from).toLocaleTimeString(), + to: new Date(validLogs[validLogs.length - 1].to).toLocaleTimeString(), + } + : null, + }); + + let res = window.electron.trackingTabsOpeningClosing(payload) + + console.log("Tracking response:", res); + dispatch(clearProjectLogs({ projectId })); + + // Cleanup refs + lastActiveRef.current = null; + lastTitleRef.current = null; + lastAppNameRef.current = null; + limitCheckedRef.current = false; + + console.log("βœ… Logs sent and cleared successfully"); + console.log("=".repeat(60)); + + return true; } catch (error) { - console.error("❌ Final PATCH failed:", error); - throw error; + const errorMsg = error instanceof Error ? error.message : "Unknown error"; + console.error("❌ Error sending logs:", error); + console.log("=".repeat(60)); + return false; } - } else { - console.log("ℹ️ No active window to finalize"); - } + }, + [ + activeProjectLogId, + projectId, + logsByProject, + stopIntervals, + dispatch, + ] + ); - // βœ… STEP 4: Clean up all refs - console.log("🧹 Cleaning up tracking references..."); - lastActiveRef.current = null; - lastTitleRef.current = null; - lastLogIdRef.current = null; - limitCheckedRef.current = false; - console.log("βœ… Cleanup complete"); - console.log("=".repeat(60)); - }, [activeProjectLogId, projectId, stopInterval]); - - // βœ… Update ref whenever sendFinalPatch changes + // Main tracking logic - runs every 5 seconds useEffect(() => { - finalizeTrackingRef.current = sendFinalPatch; - }, [sendFinalPatch]); - - // βœ… Main tracking interval effect - useEffect(() => { - // 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 β€” cleaning up"); - stopInterval(); + if (!isTracking || !projectId || !activeProjectLogId) { + console.log("⏸️ Tracking inactive β€” stopping interval"); + stopIntervals(); return; } - console.log(`🟒 Starting active tracker for: ${projectName}`); + console.log(`πŸš€ Starting tracker for: ${projectName}`); - const fetchActiveApp = async () => { - // βœ… Double-check stopping flag before each fetch + const checkActiveWindow = async () => { 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; - const title = details.title; - const appName = details.owner.name; - const prevTitle = lastTitleRef.current; - const hasChanged = !prevTitle || prevTitle !== title; + if (!details || !details.id || !details.owner?.name) { + return; + } - if (hasChanged) { - // PATCH previous window before creating new log - if (lastLogIdRef.current && prevTitle) { - console.log("πŸ”„ Updating previous window:", prevTitle); - await sendActivityLog(activeProjectLogId!, projectId!, prevTitle, appName, false); - } + const currentTitle = details.title; + const currentAppName = details.owner.name; - // POST new window log - console.log("πŸ†• New window detected:", title); - await sendActivityLog(activeProjectLogId!, projectId!, title, appName, true); + checkCountRef.current += 1; - // Update refs and Redux - lastActiveRef.current = details; - lastTitleRef.current = title; - dispatch(setActiveWindow({ window: details, projectId, projectName })); - } else { - console.log("⏸️ Same window, skipping"); + // Update Redux with current window activity + dispatch( + updateWindowLog({ + projectId, + title: currentTitle, + appName: currentAppName, + url: null, + }) + ); + + // Update refs + lastActiveRef.current = details; + lastTitleRef.current = currentTitle; + lastAppNameRef.current = currentAppName; + + // Debug every 10 checks (every 50 seconds) + if (checkCountRef.current % 10 === 0) { + debugCurrentLogs(); } } catch (error) { - console.error("❌ Failed to fetch active app:", error); + console.error("❌ Failed to check active window:", error); } }; - // Initial fetch - fetchActiveApp(); + // Initial check + checkActiveWindow(); // Set up interval (every 5 seconds) - intervalIdRef.current = setInterval(fetchActiveApp, 5000); + intervalIdRef.current = setInterval(checkActiveWindow, 5000); - // Cleanup on unmount + // Cleanup return () => { - console.log("🧹 Cleaning up tracker interval"); - stopInterval(); + console.log("🧹 Cleaning up tracker"); + stopIntervals(); }; - }, [isTracking, activeProjectLogId, projectId, projectName, dispatch, stopInterval]); + }, [ + isTracking, + activeProjectLogId, + projectId, + projectName, + stopIntervals, + dispatch, + debugCurrentLogs, + ]); return { - finalizeTracking: finalizeTrackingRef.current, - stopInterval, + finalizeAndSendLogs, + stopIntervals, setIsStopping: (value: boolean) => { isStoppingRef.current = value; - console.log(`πŸ”’ Stopping flag manually set to: ${value}`); - } + }, + debugCurrentLogs, }; }; \ No newline at end of file diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index c071a1c..0a64f69 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -39,6 +39,7 @@ import { canStartTracking } from "../utils/projectLimits"; import { useTheme } from "../helper/ThemeToggle"; import LogoutModal from "../modal/LogoutModal"; import { parseTimeStringToSeconds } from "../utils/timeParser"; +import { clearAllLogs, clearProjectLogs } from "../store/logsSlice"; interface User { id: string; @@ -100,6 +101,8 @@ const DashboardLayout = () => { const { projectsData, projectSummary, selectedProject, projectTrackingMap } = useSelector((state: any) => state.projects); + const { logsByProject } = useSelector((state: RootState) => state.logs); + const tracking = selectedProject ? projectTrackingMap[selectedProject.projectId] : null; @@ -173,7 +176,7 @@ const DashboardLayout = () => { }, [selectedProject]); // Initialize useActiveAppTracker and store finalizeTracking in ref - const { finalizeTracking, setIsStopping: setIsStoppingInTracker } = useActiveAppTracker( + const { finalizeAndSendLogs, setIsStopping: setIsStoppingInTracker } = useActiveAppTracker( isTracking, activeProjectLogId, selectedProject?.projectId ?? null, @@ -182,189 +185,131 @@ const DashboardLayout = () => { ); // Define handleStopTracking FIRST - const handleStopTracking = useCallback(async () => { - if (loadingStop || loadingStart) { - console.log("⏸️ Already processing, skipping..."); - return; - } + const handleStopTracking = useCallback(async () => { + if (loadingStop || loadingStart) { + return; + } - if (!selectedProject) { - console.warn("⚠️ No selected project to stop"); - return; - } + if (!selectedProject) { + 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`); + console.log("πŸ›‘ STOP TRACKING INITIATED"); - // Reset alert flag when stopping - limitAlertShownRef.current = false; + // Set loading state + setIsStopping(true); + setLoadingStop(true); - const currentStopTime = new Date().toISOString(); - const projectId = selectedProject.projectId; + const projectId = selectedProject.projectId; + const vaId = projectSummary.vaId || ""; - // βœ… CRITICAL: Set stopping flag IMMEDIATELY to block all new tab tracking - console.log("πŸ”’ Setting stopping flag..."); - if (setIsStoppingInTracker) { - setIsStoppingInTracker(true); - } + // STEP 1: Set stopping flag in tracker + if (setIsStoppingInTracker) { + setIsStoppingInTracker(true); + } - setIsStopping(true); - setLoadingStop(true); - handleSocketDisconnect(); + // STEP 2: Disconnect socket + 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 3: Clear local timer + if (timerRef.current) { + clearInterval(timerRef.current); + timerRef.current = null; + } - // 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..."); + // STEP 4: Finalize and send logs + console.log("πŸ“€ STEP 4: Finalizing and sending logs..."); + let logsSent = false; + if (finalizeAndSendLogs) { 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); + logsSent = await finalizeAndSendLogs(vaId); // Pass vaId here + console.log(logsSent ? "βœ… Logs sent successfully" : "⚠️ Logs not sent"); } catch (error) { - console.error("❌ LOGOUT failed:", error); + console.error("❌ Error sending logs:", error); } + } - // STEP 4: Update Redux tracking state - console.log("πŸ”„ STEP 4: Updating Redux state..."); - dispatch( - stopTrackingReducer({ - projectId, - }) - ); + // STEP 5: Send LOGOUT to backend + console.log("πŸšͺ STEP 5: Sending LOGOUT request..."); + try { + const logoutPayload = { + vaId: vaId, + projectId: projectId, + type: "LOGOUT", + time: new Date().toISOString() + }; - // 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"); + await window.electron.stopProjectTracking(logoutPayload); + console.log("βœ… LOGOUT successful"); + } catch (error) { + console.error("❌ LOGOUT failed:", error); + } - // Allow React to process the state update - await new Promise(resolve => setTimeout(resolve, 50)); + // STEP 6: Update Redux tracking state + dispatch(stopTrackingReducer({ projectId })); - // 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); + // STEP 7: Refresh project list + try { + const projectListRes = await window.electron.getProjectList(); + if (projectListRes?.success && Array.isArray(projectListRes.data)) { + dispatch(setProjectsDataSlice(projectListRes.data)); + + 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 || "", + })); - if (projectListRes?.success && Array.isArray(projectListRes.data)) { - // Update projects data - dispatch(setProjectsDataSlice(projectListRes.data)); + // Update selected project + const updatedProject = projectListRes.data.find( + (p: Project) => p.projectId === projectId + ); - // 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"); - } + if (updatedProject) { + dispatch(setSelectedProjectSlice(updatedProject)); } - - - - } 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 - ]); + } catch (error) { + console.error("❌ Error refreshing project list:", error); + } + + // STEP 8: Reset logs for this project + if (logsSent && projectId) { + dispatch(clearProjectLogs({ projectId })); + } + + // STEP 9: Restore window + await window.electron.toggleCloseBtn(true); + await window.electron.resizeWindow(500, 600); + toggleWindowSize.current = true; + + // STEP 10: Final cleanup + setLoadingStop(false); + setIsStopping(false); + + if (setIsStoppingInTracker) { + setIsStoppingInTracker(false); + } + + console.log("πŸŽ‰ TRACKING STOPPED SUCCESSFULLY"); +}, [ + selectedProject, + projectSummary, + dispatch, + loadingStop, + loadingStart, + setIsStoppingInTracker, + finalizeAndSendLogs, +]); // Update the ref whenever finalizeTracking changes - useEffect(() => { - stopTrackingRef.current = finalizeTracking; - }, [finalizeTracking]); + // useEffect(() => { + // stopTrackingRef.current = finalizeTracking; + // }, [finalizeTracking]); const socketRef = useRef(null); const intervalRef = useRef(null); @@ -404,6 +349,7 @@ const DashboardLayout = () => { const handleLogout = async () => { try { + dispatch(clearAllLogs()); const res = await window.electron.logout(); if (res) { dispatch(clearUser()); @@ -459,6 +405,11 @@ const DashboardLayout = () => { return; } + // Reset logs for this project if they exist + if (logsByProject[project.projectId]) { + dispatch(clearProjectLogs({ projectId: project.projectId })); + } + // CRITICAL FIX: Get the current accumulated time BEFORE starting timer const currentAccumulatedTime = selectedData?.accumulatedTime || parseTimeStringToSeconds(project.totalWorkTime); @@ -927,4 +878,6 @@ const DashboardLayout = () => { ); }; -export default DashboardLayout; \ No newline at end of file +export default DashboardLayout; + + diff --git a/src/ui/store/index.tsx b/src/ui/store/index.tsx index 18dc804..d7af0c6 100644 --- a/src/ui/store/index.tsx +++ b/src/ui/store/index.tsx @@ -4,6 +4,7 @@ import windowReducer from "./windowSlice"; import projectsReducer from "./projectSlice"; import userReducer from "./userSlice"; import profileReducer from "./profileSlice"; +import logsReducer from './logsSlice'; export const store = configureStore({ reducer: { @@ -11,6 +12,7 @@ export const store = configureStore({ projects: projectsReducer, user: userReducer, profile: profileReducer, + logs: logsReducer, }, }); diff --git a/src/ui/store/logsSlice.tsx b/src/ui/store/logsSlice.tsx new file mode 100644 index 0000000..3bec6f9 --- /dev/null +++ b/src/ui/store/logsSlice.tsx @@ -0,0 +1,182 @@ +// store/logsSlice.ts +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; + +export interface LogEntry { + id: string; // Unique ID for each log entry + title: string; + appName: string; + url: string | null; + from: string; // UTC ISO string + to: string; // UTC ISO string + totalTime: number; // milliseconds +} + +export interface ProjectLogs { + projectId: string; + logId: string | null; + logs: LogEntry[]; // All collected logs during tracking + trackingStartTime: string | null; + lastWindowTitle: string | null; // Track last window title + lastWindowApp: string | null; // Track last window app +} + +interface LogsState { + logsByProject: Record; +} + +const initialState: LogsState = { + logsByProject: {}, +}; + +const logsSlice = createSlice({ + name: "logs", + initialState, + reducers: { + // Initialize logs for a project + initializeProjectLogs: ( + state, + action: PayloadAction<{ + projectId: string; + logId: string; + }> + ) => { + const { projectId, logId } = action.payload; + const nowUTC = new Date().toISOString(); + + // Always create fresh logs for new tracking session + state.logsByProject[projectId] = { + projectId, + logId, + logs: [], // Start with empty logs + trackingStartTime: nowUTC, + lastWindowTitle: null, + lastWindowApp: null, + }; + + console.log(`πŸ“ Initialized logs for project ${projectId} with logId: ${logId}`); + }, + + // Add or update window log + updateWindowLog: ( + state, + action: PayloadAction<{ + projectId: string; + title: string; + appName: string; + url?: string | null; + }> + ) => { + const { projectId, title, appName, url = null } = action.payload; + const projectLogs = state.logsByProject[projectId]; + + if (!projectLogs) { + console.warn(`Cannot update log: Project ${projectId} not initialized`); + return; + } + + const nowUTC = new Date().toISOString(); + const isSameWindow = + projectLogs.lastWindowTitle === title && + projectLogs.lastWindowApp === appName; + + // If same window as before, update the last log's end time + if (isSameWindow && projectLogs.logs.length > 0) { + const lastLog = projectLogs.logs[projectLogs.logs.length - 1]; + const startTime = new Date(lastLog.from); + const endTime = new Date(nowUTC); + lastLog.to = nowUTC; + lastLog.totalTime = endTime.getTime() - startTime.getTime(); + // console.log(`⏱️ Updated existing log: ${title} (${lastLog.totalTime}ms)`); + } + // If new window or first window + else { + // If there was a previous window, ensure it's properly ended + if (projectLogs.lastWindowTitle && projectLogs.logs.length > 0) { + const lastLog = projectLogs.logs[projectLogs.logs.length - 1]; + const endTime = new Date(nowUTC); + const startTime = new Date(lastLog.from); + lastLog.to = nowUTC; + lastLog.totalTime = endTime.getTime() - startTime.getTime(); + console.log( + `βœ… Finalized previous window: ${lastLog.title} (${lastLog.totalTime}ms)` + ); + } + + // Create new log entry + const newLog: LogEntry = { + id: `log_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, + title, + appName, + url, + from: nowUTC, + to: nowUTC, // Will be updated continuously + totalTime: 0, // Starts at 0 + }; + + projectLogs.logs.push(newLog); + console.log(`πŸ“ Started new window: ${title} on ${appName}`); + } + + // Update last window tracking + projectLogs.lastWindowTitle = title; + projectLogs.lastWindowApp = appName; + }, + + // Clear project logs + clearProjectLogs: ( + state, + action: PayloadAction<{ + projectId: string; + }> + ) => { + const { projectId } = action.payload; + delete state.logsByProject[projectId]; + console.log(`🧹 Cleared logs for project ${projectId}`); + }, + + // Clear all logs + clearAllLogs: (state) => { + state.logsByProject = {}; + console.log("🧹 Cleared all logs"); + }, + + // Debug: Get logs state + debugLogsState: ( + state, + action: PayloadAction<{ + projectId: string; + }> + ) => { + const { projectId } = action.payload; + const projectLogs = state.logsByProject[projectId]; + + if (!projectLogs) { + console.log(`πŸ“‹ No logs found for project ${projectId}`); + return; + } + + console.log(`πŸ“‹ Logs State for ${projectId}:`, { + totalLogs: projectLogs.logs.length, + lastWindow: projectLogs.lastWindowTitle, + logs: projectLogs.logs.map((log) => ({ + title: + log.title.substring(0, 40) + (log.title.length > 40 ? "..." : ""), + appName: log.appName, + from: new Date(log.from).toLocaleTimeString(), + to: new Date(log.to).toLocaleTimeString(), + duration: log.totalTime, + })), + }); + }, + }, +}); + +export const { + initializeProjectLogs, + updateWindowLog, + clearProjectLogs, + clearAllLogs, + debugLogsState, +} = logsSlice.actions; + +export default logsSlice.reducer; \ No newline at end of file diff --git a/types.d.ts b/types.d.ts index d080cd6..9464e15 100644 --- a/types.d.ts +++ b/types.d.ts @@ -142,6 +142,24 @@ interface RegionResponse { } +interface TrackingLog { + title: string; + appName: string; + url: string; + from: string; + to: string; + totalTime: number; +} + +interface TrackingPayload { + vaId: string; + projectId: string; + logId: string; + logs: TrackingLog[]; +} + + + interface Window { electron: { login: (credentials: { email: string; password: string }) => Promise; @@ -171,14 +189,7 @@ interface Window { type: string; time: string }) => Promise; - trackingTabsOpeningClosing: (data: { - id?: string; - logId: string; - projectId: string; - title: string; - appName: string; - }) => Promise; - + trackingTabsOpeningClosing: (data: TrackingPayload) => Promise; getProjectLogs: ( projectId: string, fromDate?: string,