// 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 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 } interface LogsState { logsByProject: Record; } const initialState: LogsState = { 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 = 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, }; 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}`); }, }, }); export const { initializeProjectLogs, updateWindowLog, clearProjectLogs, clearAllLogs, debugLogsState, fixAllTimestamps, } = logsSlice.actions; export default logsSlice.reducer;