240 lines
8.3 KiB
TypeScript
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; |