va-tracker/src/ui/store/logsSlice.tsx

240 lines
8.3 KiB
TypeScript

// 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<string, ProjectLogs>;
}
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;