diff --git a/resources/wireguard/windows/wg0.conf b/resources/wireguard/windows/wg0.conf index fe4bde9..c5a6248 100644 --- a/resources/wireguard/windows/wg0.conf +++ b/resources/wireguard/windows/wg0.conf @@ -6,6 +6,7 @@ DNS = 1.1.1.1 [Peer] PublicKey = jG+7H5thEiNjxY1XqI1R9oyMAMTIegLwE3tZkS1Rj2U= +AllowedIPs = 10.0.0.0/24 Endpoint = 35.95.209.161:51820 AllowedIPs = 10.0.0.0/24 -PersistentKeepalive = 25 +PersistentKeepalive = 25 \ No newline at end of file diff --git a/src/electron/api/regionCheck.ts b/src/electron/api/regionCheck.ts index 5a5912b..719fec6 100644 --- a/src/electron/api/regionCheck.ts +++ b/src/electron/api/regionCheck.ts @@ -8,7 +8,7 @@ const httpsAgent = new https.Agent({ rejectUnauthorized: false }); export const checkRegion = async () => { const baseURL = await getBaseURL(); - console.log("checkRegion@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", baseURL) + console.log("Lin 11 BaseURL CHECK@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" ,baseURL) try { diff --git a/src/electron/ipc/ipcHandler.ts b/src/electron/ipc/ipcHandler.ts index 06eb15a..f13f3d4 100644 --- a/src/electron/ipc/ipcHandler.ts +++ b/src/electron/ipc/ipcHandler.ts @@ -172,7 +172,7 @@ ipcMain.handle("tracking-tabs-opening-and-closing", async (event, payload) => { const response = await api.post("/log/project/track", payload); - console.log("๐ŸŸฉ Log track API success:", response.data); + console.log("๐ŸŸฉ Log track API success:", response); return { success: true, diff --git a/src/ui/hooks/useActiveAppTracker.tsx b/src/ui/hooks/useActiveAppTracker.tsx index 8d24af1..2993c5e 100644 --- a/src/ui/hooks/useActiveAppTracker.tsx +++ b/src/ui/hooks/useActiveAppTracker.tsx @@ -70,128 +70,98 @@ export const useActiveAppTracker = ( }, [projectId, dispatch]); // Finalize and send all logs when stopping tracking - const finalizeAndSendLogs = useCallback( + // hooks/useActiveAppTracker.tsx - finalizeAndSendLogs function +const finalizeAndSendLogs = useCallback( async (vaId: string): Promise => { - // console.log("=".repeat(60)); - console.log(" FINALIZING AND SENDING ALL LOGS"); - // console.log("=".repeat(60)); + console.log(" FINALIZING AND SENDING ALL LOGS"); - // Set stopping flag - isStoppingRef.current = true; + // Set stopping flag + isStoppingRef.current = true; - // Stop intervals immediately - stopIntervals(); + // 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; - } - - try { - const nowUTC = new Date().toISOString(); - - // 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)`); - // } - - // 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 - }); - - 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; + if (!activeProjectLogId || !projectId) { + console.log("โš ๏ธ Skipping โ€” no active project"); + return false; } - // 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, - })), - }; + const projectLogs = logsByProject[projectId]; + if (!projectLogs || projectLogs.logs.length === 0) { + console.log("โ„น๏ธ No logs found for project", projectId); + return true; + } - 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, - }); + try { + // Create a DEEP copy of the logs + const allLogs = JSON.parse(JSON.stringify(projectLogs.logs)); - let res = window.electron.trackingTabsOpeningClosing(payload) + // Filter out logs with very short duration (less than 1000ms = 1 second) + const validLogs = allLogs.filter((log:any) => { + return log.totalTime >= 1000; // Minimum 1 second (1000ms) + }); - console.log("Tracking response:", res); - dispatch(clearProjectLogs({ projectId })); + console.log(`๐Ÿ“Š Total logs: ${allLogs.length}, Valid logs (โ‰ฅ1000ms): ${validLogs.length}`); - // Cleanup refs - lastActiveRef.current = null; - lastTitleRef.current = null; - lastAppNameRef.current = null; - limitCheckedRef.current = false; + if (validLogs.length === 0) { + console.log("โ„น๏ธ No valid logs to send"); + dispatch(clearProjectLogs({ projectId })); + return true; + } - console.log("โœ… Logs sent and cleared successfully"); - console.log("=".repeat(60)); + // Prepare payload with all valid logs - send directly as stored + const payload = { + vaId: vaId || "", + projectId, + logId: activeProjectLogId, + logs: validLogs, // Already formatted correctly + }; - return true; - } catch (error) { - const errorMsg = error instanceof Error ? error.message : "Unknown error"; - console.error("โŒ Error sending logs:", error); - console.log("=".repeat(60)); - return false; - } + console.log("๐Ÿ“ค Payload to send:", payload); + // console.log("๐Ÿ“Š Sending logs summary:", { + // projectId, + // projectName, + // logCount: validLogs.length, + // totalTimeMs: validLogs.reduce((sum, log) => sum + log.totalTime, 0), + // totalTimeSec: validLogs.reduce((sum, log) => sum + Math.round(log.totalTime / 1000), 0), + // sampleLog: validLogs[0] ? { + // title: validLogs[0].title.substring(0, 40), + // from: validLogs[0].from, + // to: validLogs[0].to, + // durationMs: validLogs[0].totalTime, + // } : 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) { + const errorMsg = error instanceof Error ? error.message : "Unknown error"; + console.error("โŒ Error sending logs:", error); + console.log("=".repeat(60)); + return false; + } }, [ - activeProjectLogId, - projectId, - logsByProject, - stopIntervals, - dispatch, + activeProjectLogId, + projectId, + logsByProject, + stopIntervals, + dispatch, ] - ); +); // Main tracking logic - runs every 5 seconds useEffect(() => { diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index a98de0c..2c45e1e 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -101,7 +101,7 @@ const DashboardLayout = () => { const { projectsData, projectSummary, selectedProject, projectTrackingMap } = useSelector((state: any) => state.projects); - const { logsByProject } = useSelector((state: RootState) => state.logs); + const { logsByProject } = useSelector((state: RootState) => state.logs); const tracking = selectedProject ? projectTrackingMap[selectedProject.projectId] @@ -176,7 +176,7 @@ const DashboardLayout = () => { }, [selectedProject]); // Initialize useActiveAppTracker and store finalizeTracking in ref - const { finalizeAndSendLogs, setIsStopping: setIsStoppingInTracker } = useActiveAppTracker( + const { finalizeAndSendLogs, setIsStopping: setIsStoppingInTracker } = useActiveAppTracker( isTracking, activeProjectLogId, selectedProject?.projectId ?? null, @@ -185,126 +185,177 @@ const DashboardLayout = () => { ); // Define handleStopTracking FIRST - const handleStopTracking = useCallback(async () => { - if (loadingStop || loadingStart) { - return; - } - - if (!selectedProject) { - return; - } - - console.log("๐Ÿ›‘ STOP TRACKING INITIATED"); - - // Set loading state - setIsStopping(true); - setLoadingStop(true); - - const projectId = selectedProject.projectId; - const vaId = projectSummary.vaId || ""; - - // STEP 1: Set stopping flag in tracker - if (setIsStoppingInTracker) { - setIsStoppingInTracker(true); - } - - // STEP 2: Disconnect socket - handleSocketDisconnect(); - - // STEP 3: Clear local timer - if (timerRef.current) { - clearInterval(timerRef.current); - timerRef.current = null; - } - - // STEP 4: Finalize and send logs - console.log("๐Ÿ“ค STEP 4: Finalizing and sending logs..."); - let logsSent = false; - if (finalizeAndSendLogs) { - try { - logsSent = await finalizeAndSendLogs(vaId); // Pass vaId here - console.log(logsSent ? "โœ… Logs sent successfully" : "โš ๏ธ Logs not sent"); - } catch (error) { - console.error("โŒ Error sending logs:", error); + const handleStopTracking = useCallback(async () => { + if (loadingStop || loadingStart) { + return; } - } - // 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() - }; + if (!selectedProject) { + return; + } - await window.electron.stopProjectTracking(logoutPayload); - console.log("โœ… LOGOUT successful"); - } catch (error) { - console.error("โŒ LOGOUT failed:", error); - } + console.log("๐Ÿ›‘ STOP TRACKING INITIATED"); - // STEP 6: Update Redux tracking state - dispatch(stopTrackingReducer({ projectId })); + // Reset alert flag when stopping + limitAlertShownRef.current = false; - // 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 || "", - })); - // Update selected project - const updatedProject = projectListRes.data.find( - (p: Project) => p.projectId === projectId - ); + const currentStopTime = new Date().toISOString(); + const projectId = selectedProject.projectId; + const vaId = projectSummary.vaId || ""; - if (updatedProject) { - dispatch(setSelectedProjectSlice(updatedProject)); + // STEP 1: Set stopping flag in tracker + if (setIsStoppingInTracker) { + setIsStoppingInTracker(true); + } + + // Set loading state + setIsStopping(true); + setLoadingStop(true); + + // STEP 2: Disconnect socket + handleSocketDisconnect(); + + // STEP 3: Clear local timer + if (timerRef.current) { + clearInterval(timerRef.current); + timerRef.current = null; + } + + // STEP 4: Finalize and send logs + console.log("๐Ÿ“ค STEP 4: Finalizing and sending logs..."); + let logsSent = false; + if (finalizeAndSendLogs) { + try { + logsSent = await finalizeAndSendLogs(vaId); // Pass vaId here + console.log(logsSent ? "โœ… Logs sent successfully" : "โš ๏ธ Logs not sent"); + } catch (error) { + console.error("โŒ Error sending logs:", error); } } - } catch (error) { - console.error("โŒ Error refreshing project list:", error); - } - // STEP 8: Reset logs for this project - if (logsSent && projectId) { - dispatch(clearProjectLogs({ projectId })); - } + // STEP 5: Send LOGOUT to backend + console.log("๐Ÿšช STEP 5: Sending LOGOUT request..."); + try { + const logoutPayload = { + vaId: vaId, + logId: activeProjectLogId, + projectId: projectId, + type: "LOGOUT", + time: currentStopTime + }; - // STEP 9: Restore window - await window.electron.toggleCloseBtn(true); - await window.electron.resizeWindow(500, 600); - toggleWindowSize.current = true; + await window.electron.stopProjectTracking(logoutPayload); + console.log("โœ… LOGOUT successful"); + } catch (error) { + console.error("โŒ LOGOUT failed:", error); + } - // STEP 10: Final cleanup - setLoadingStop(false); - setIsStopping(false); - - if (setIsStoppingInTracker) { - setIsStoppingInTracker(false); - } + // STEP 6: Update Redux tracking state + dispatch(stopTrackingReducer({ projectId })); - console.log("๐ŸŽ‰ TRACKING STOPPED SUCCESSFULLY"); -}, [ - selectedProject, - projectSummary, - dispatch, - loadingStop, - loadingStart, - setIsStoppingInTracker, - finalizeAndSendLogs, -]); + await window.electron.toggleCloseBtn(true); + await window.electron.resizeWindow(500, 600); + toggleWindowSize.current = true; + + // Allow React to process the state update + await new Promise(resolve => setTimeout(resolve, 50)); + + // STEP 7: Refresh 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)); + } + + // STEP 8: Reset logs for this project + if (logsSent && projectId) { + dispatch(clearProjectLogs({ projectId })); + } + + console.log("๐ŸŽ‰ TRACKING STOPPED SUCCESSFULLY"); + }, [ + selectedProject, + projectSummary, + dispatch, + loadingStop, + loadingStart, + setIsStoppingInTracker, + finalizeAndSendLogs, + ]); // Update the ref whenever finalizeTracking changes // useEffect(() => { @@ -406,9 +457,9 @@ const DashboardLayout = () => { } // Reset logs for this project if they exist - if (logsByProject[project.projectId]) { - dispatch(clearProjectLogs({ projectId: project.projectId })); - } + 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); diff --git a/src/ui/store/logsSlice.tsx b/src/ui/store/logsSlice.tsx index 3bec6f9..d8c00fd 100644 --- a/src/ui/store/logsSlice.tsx +++ b/src/ui/store/logsSlice.tsx @@ -2,181 +2,239 @@ 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 + id: string; // Unique ID for each log entry + title: string; + appName: string; + url: string | null; + from: string; // UTC ISO string with .000 milliseconds + to: string; // UTC ISO string with .000 milliseconds + 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 + 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; + logsByProject: Record; } const initialState: LogsState = { - logsByProject: {}, + logsByProject: {}, +}; + +// Helper function to get timestamp with .000 milliseconds +const getTimestampWithZeroMs = () => { + const now = new Date(); + // Format: YYYY-MM-DDTHH:mm:ss.000Z + const isoString = now.toISOString(); + // Replace milliseconds with .000 + return isoString.replace(/\.\d{3}Z$/, '.000Z'); +}; + +// Helper function to format any date string to have .000 milliseconds +const formatToZeroMs = (dateString: string): string => { + if (dateString.endsWith('.000Z')) { + return dateString; // Already formatted + } + const date = new Date(dateString); + const isoString = date.toISOString(); + return isoString.replace(/\.\d{3}Z$/, '.000Z'); }; 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(); + name: "logs", + initialState, + reducers: { + // Initialize logs for a project + initializeProjectLogs: ( + state, + action: PayloadAction<{ + projectId: string; + logId: string; + }> + ) => { + const { projectId, logId } = action.payload; + const nowUTC = getTimestampWithZeroMs(); - // Always create fresh logs for new tracking session - state.logsByProject[projectId] = { - projectId, - logId, - logs: [], // Start with empty logs - trackingStartTime: nowUTC, - lastWindowTitle: null, - lastWindowApp: null, - }; + // 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}`); + 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 = getTimestampWithZeroMs(); + 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]; + // Format the existing from time to ensure it has .000 milliseconds + lastLog.from = formatToZeroMs(lastLog.from); + lastLog.to = nowUTC; + + // Calculate total time in milliseconds + const startTime = new Date(lastLog.from).getTime(); + const endTime = new Date(lastLog.to).getTime(); + lastLog.totalTime = endTime - startTime; + + // 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]; + // Format the existing from time to ensure it has .000 milliseconds + lastLog.from = formatToZeroMs(lastLog.from); + lastLog.to = nowUTC; + + // Calculate total time in milliseconds + const startTime = new Date(lastLog.from).getTime(); + const endTime = new Date(lastLog.to).getTime(); + lastLog.totalTime = endTime - startTime; + + console.log( + `โœ… Finalized previous window: ${lastLog.title} (${lastLog.totalTime}ms)` + ); + } + + // Create new log entry with .000 milliseconds + 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 (in milliseconds) + }; + + 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: log.from, + to: log.to, + durationMs: log.totalTime, + durationSec: Math.round(log.totalTime / 1000), + })), + }); + }, + + // Optional: Add a reducer to fix all timestamps in existing logs + fixAllTimestamps: ( + state, + action: PayloadAction<{ + projectId: string; + }> + ) => { + const { projectId } = action.payload; + const projectLogs = state.logsByProject[projectId]; + + if (!projectLogs) { + console.warn(`Cannot fix timestamps: Project ${projectId} not found`); + return; + } + + // Fix all timestamps in existing logs + projectLogs.logs.forEach(log => { + log.from = formatToZeroMs(log.from); + log.to = formatToZeroMs(log.to); + // Recalculate totalTime based on formatted timestamps + const startTime = new Date(log.from).getTime(); + const endTime = new Date(log.to).getTime(); + log.totalTime = endTime - startTime; + }); + + console.log(`๐Ÿ”ง Fixed timestamps for project ${projectId}`); + }, }, - - // 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, + initializeProjectLogs, + updateWindowLog, + clearProjectLogs, + clearAllLogs, + debugLogsState, + fixAllTimestamps, } = logsSlice.actions; export default logsSlice.reducer; \ No newline at end of file diff --git a/types.d.ts b/types.d.ts index 9464e15..bf8f151 100644 --- a/types.d.ts +++ b/types.d.ts @@ -185,6 +185,7 @@ interface Window { }) => Promise; stopProjectTracking: (data: { vaId: string; + logId: string, projectId: string; type: string; time: string