handleStop tracking fixed time
This commit is contained in:
parent
68eb853987
commit
c03c89d613
|
|
@ -1,10 +1,10 @@
|
||||||
[Interface]
|
[Interface]
|
||||||
PrivateKey = CEzCd25ufxbOjvRhMqLfkcdP/OziG5i6OkXCfMZrI1I=
|
PrivateKey = CEzCd25ufxbOjvRhMqLfkcdP/OziG5i6OkXCfMZrI1I=
|
||||||
Address = 10.0.0.2/32
|
Address = 10.0.0.2/24
|
||||||
DNS = 1.1.1.1
|
DNS = 1.1.1.1
|
||||||
|
|
||||||
[Peer]
|
[Peer]
|
||||||
PublicKey = jG+7H5thEiNjxY1XqI1R9oyMAMTIegLwE3tZkS1Rj2U=
|
PublicKey = jG+7H5thEiNjxY1XqI1R9oyMAMTIegLwE3tZkS1Rj2U=
|
||||||
|
AllowedIPs = 10.0.0.0/24
|
||||||
Endpoint = 35.95.209.161:51820
|
Endpoint = 35.95.209.161:51820
|
||||||
AllowedIPs = 10.0.0.0/32
|
PersistentKeepalive = 25
|
||||||
PersistentKeepalive = 25
|
|
||||||
|
|
@ -8,7 +8,7 @@ const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
||||||
export const checkRegion = async () => {
|
export const checkRegion = async () => {
|
||||||
const baseURL = await getBaseURL();
|
const baseURL = await getBaseURL();
|
||||||
|
|
||||||
console.log("checkRegion@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" ,baseURL)
|
console.log("Lin 11 BaseURL CHECK@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" ,baseURL)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ ipcMain.handle("tracking-tabs-opening-and-closing", async (event, payload) => {
|
||||||
|
|
||||||
const response = await api.post("/log/project/track", 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 {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
|
||||||
|
|
@ -70,128 +70,98 @@ export const useActiveAppTracker = (
|
||||||
}, [projectId, dispatch]);
|
}, [projectId, dispatch]);
|
||||||
|
|
||||||
// Finalize and send all logs when stopping tracking
|
// Finalize and send all logs when stopping tracking
|
||||||
const finalizeAndSendLogs = useCallback(
|
// hooks/useActiveAppTracker.tsx - finalizeAndSendLogs function
|
||||||
|
const finalizeAndSendLogs = useCallback(
|
||||||
async (vaId: string): Promise<boolean> => {
|
async (vaId: string): Promise<boolean> => {
|
||||||
// console.log("=".repeat(60));
|
console.log(" FINALIZING AND SENDING ALL LOGS");
|
||||||
console.log(" FINALIZING AND SENDING ALL LOGS");
|
|
||||||
// console.log("=".repeat(60));
|
|
||||||
|
|
||||||
// Set stopping flag
|
// Set stopping flag
|
||||||
isStoppingRef.current = true;
|
isStoppingRef.current = true;
|
||||||
|
|
||||||
// Stop intervals immediately
|
// Stop intervals immediately
|
||||||
stopIntervals();
|
stopIntervals();
|
||||||
|
|
||||||
if (!activeProjectLogId || !projectId) {
|
if (!activeProjectLogId || !projectId) {
|
||||||
console.log("⚠️ Skipping — no active project");
|
console.log("⚠️ Skipping — no active project");
|
||||||
return false;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare payload with all valid logs
|
const projectLogs = logsByProject[projectId];
|
||||||
const payload = {
|
if (!projectLogs || projectLogs.logs.length === 0) {
|
||||||
vaId: vaId || "",
|
console.log("ℹ️ No logs found for project", projectId);
|
||||||
projectId,
|
return true;
|
||||||
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);
|
try {
|
||||||
console.log("📊 Sending logs summary:", {
|
// Create a DEEP copy of the logs
|
||||||
projectId,
|
const allLogs = JSON.parse(JSON.stringify(projectLogs.logs));
|
||||||
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)
|
// 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);
|
console.log(`📊 Total logs: ${allLogs.length}, Valid logs (≥1000ms): ${validLogs.length}`);
|
||||||
dispatch(clearProjectLogs({ projectId }));
|
|
||||||
|
|
||||||
// Cleanup refs
|
if (validLogs.length === 0) {
|
||||||
lastActiveRef.current = null;
|
console.log("ℹ️ No valid logs to send");
|
||||||
lastTitleRef.current = null;
|
dispatch(clearProjectLogs({ projectId }));
|
||||||
lastAppNameRef.current = null;
|
return true;
|
||||||
limitCheckedRef.current = false;
|
}
|
||||||
|
|
||||||
console.log("✅ Logs sent and cleared successfully");
|
// Prepare payload with all valid logs - send directly as stored
|
||||||
console.log("=".repeat(60));
|
const payload = {
|
||||||
|
vaId: vaId || "",
|
||||||
|
projectId,
|
||||||
|
logId: activeProjectLogId,
|
||||||
|
logs: validLogs, // Already formatted correctly
|
||||||
|
};
|
||||||
|
|
||||||
return true;
|
console.log("📤 Payload to send:", payload);
|
||||||
} catch (error) {
|
// console.log("📊 Sending logs summary:", {
|
||||||
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
// projectId,
|
||||||
console.error("❌ Error sending logs:", error);
|
// projectName,
|
||||||
console.log("=".repeat(60));
|
// logCount: validLogs.length,
|
||||||
return false;
|
// 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,
|
activeProjectLogId,
|
||||||
projectId,
|
projectId,
|
||||||
logsByProject,
|
logsByProject,
|
||||||
stopIntervals,
|
stopIntervals,
|
||||||
dispatch,
|
dispatch,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Main tracking logic - runs every 5 seconds
|
// Main tracking logic - runs every 5 seconds
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ const DashboardLayout = () => {
|
||||||
const { projectsData, projectSummary, selectedProject, projectTrackingMap } =
|
const { projectsData, projectSummary, selectedProject, projectTrackingMap } =
|
||||||
useSelector((state: any) => state.projects);
|
useSelector((state: any) => state.projects);
|
||||||
|
|
||||||
const { logsByProject } = useSelector((state: RootState) => state.logs);
|
const { logsByProject } = useSelector((state: RootState) => state.logs);
|
||||||
|
|
||||||
const tracking = selectedProject
|
const tracking = selectedProject
|
||||||
? projectTrackingMap[selectedProject.projectId]
|
? projectTrackingMap[selectedProject.projectId]
|
||||||
|
|
@ -176,7 +176,7 @@ const DashboardLayout = () => {
|
||||||
}, [selectedProject]);
|
}, [selectedProject]);
|
||||||
|
|
||||||
// Initialize useActiveAppTracker and store finalizeTracking in ref
|
// Initialize useActiveAppTracker and store finalizeTracking in ref
|
||||||
const { finalizeAndSendLogs, setIsStopping: setIsStoppingInTracker } = useActiveAppTracker(
|
const { finalizeAndSendLogs, setIsStopping: setIsStoppingInTracker } = useActiveAppTracker(
|
||||||
isTracking,
|
isTracking,
|
||||||
activeProjectLogId,
|
activeProjectLogId,
|
||||||
selectedProject?.projectId ?? null,
|
selectedProject?.projectId ?? null,
|
||||||
|
|
@ -185,126 +185,177 @@ const DashboardLayout = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Define handleStopTracking FIRST
|
// Define handleStopTracking FIRST
|
||||||
const handleStopTracking = useCallback(async () => {
|
const handleStopTracking = useCallback(async () => {
|
||||||
if (loadingStop || loadingStart) {
|
if (loadingStop || loadingStart) {
|
||||||
return;
|
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);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// STEP 5: Send LOGOUT to backend
|
if (!selectedProject) {
|
||||||
console.log("🚪 STEP 5: Sending LOGOUT request...");
|
return;
|
||||||
try {
|
}
|
||||||
const logoutPayload = {
|
|
||||||
vaId: vaId,
|
|
||||||
projectId: projectId,
|
|
||||||
type: "LOGOUT",
|
|
||||||
time: new Date().toISOString()
|
|
||||||
};
|
|
||||||
|
|
||||||
await window.electron.stopProjectTracking(logoutPayload);
|
console.log("🛑 STOP TRACKING INITIATED");
|
||||||
console.log("✅ LOGOUT successful");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ LOGOUT failed:", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// STEP 6: Update Redux tracking state
|
// Reset alert flag when stopping
|
||||||
dispatch(stopTrackingReducer({ projectId }));
|
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 currentStopTime = new Date().toISOString();
|
||||||
const updatedProject = projectListRes.data.find(
|
const projectId = selectedProject.projectId;
|
||||||
(p: Project) => p.projectId === projectId
|
const vaId = projectSummary.vaId || "";
|
||||||
);
|
|
||||||
|
|
||||||
if (updatedProject) {
|
// STEP 1: Set stopping flag in tracker
|
||||||
dispatch(setSelectedProjectSlice(updatedProject));
|
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
|
// STEP 5: Send LOGOUT to backend
|
||||||
if (logsSent && projectId) {
|
console.log("🚪 STEP 5: Sending LOGOUT request...");
|
||||||
dispatch(clearProjectLogs({ projectId }));
|
try {
|
||||||
}
|
const logoutPayload = {
|
||||||
|
vaId: vaId,
|
||||||
|
logId: activeProjectLogId,
|
||||||
|
projectId: projectId,
|
||||||
|
type: "LOGOUT",
|
||||||
|
time: currentStopTime
|
||||||
|
};
|
||||||
|
|
||||||
// STEP 9: Restore window
|
await window.electron.stopProjectTracking(logoutPayload);
|
||||||
await window.electron.toggleCloseBtn(true);
|
console.log("✅ LOGOUT successful");
|
||||||
await window.electron.resizeWindow(500, 600);
|
} catch (error) {
|
||||||
toggleWindowSize.current = true;
|
console.error("❌ LOGOUT failed:", error);
|
||||||
|
}
|
||||||
|
|
||||||
// STEP 10: Final cleanup
|
// STEP 6: Update Redux tracking state
|
||||||
setLoadingStop(false);
|
dispatch(stopTrackingReducer({ projectId }));
|
||||||
setIsStopping(false);
|
|
||||||
|
|
||||||
if (setIsStoppingInTracker) {
|
|
||||||
setIsStoppingInTracker(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("🎉 TRACKING STOPPED SUCCESSFULLY");
|
await window.electron.toggleCloseBtn(true);
|
||||||
}, [
|
await window.electron.resizeWindow(500, 600);
|
||||||
selectedProject,
|
toggleWindowSize.current = true;
|
||||||
projectSummary,
|
|
||||||
dispatch,
|
// Allow React to process the state update
|
||||||
loadingStop,
|
await new Promise(resolve => setTimeout(resolve, 50));
|
||||||
loadingStart,
|
|
||||||
setIsStoppingInTracker,
|
// STEP 7: Refresh project list
|
||||||
finalizeAndSendLogs,
|
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
|
// Update the ref whenever finalizeTracking changes
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
|
|
@ -406,9 +457,9 @@ const DashboardLayout = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset logs for this project if they exist
|
// Reset logs for this project if they exist
|
||||||
if (logsByProject[project.projectId]) {
|
if (logsByProject[project.projectId]) {
|
||||||
dispatch(clearProjectLogs({ projectId: project.projectId }));
|
dispatch(clearProjectLogs({ projectId: project.projectId }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// CRITICAL FIX: Get the current accumulated time BEFORE starting timer
|
// CRITICAL FIX: Get the current accumulated time BEFORE starting timer
|
||||||
const currentAccumulatedTime = selectedData?.accumulatedTime || parseTimeStringToSeconds(project.totalWorkTime);
|
const currentAccumulatedTime = selectedData?.accumulatedTime || parseTimeStringToSeconds(project.totalWorkTime);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ interface Props {
|
||||||
|
|
||||||
const VpnGuard: React.FC<Props> = ({ children }) => {
|
const VpnGuard: React.FC<Props> = ({ children }) => {
|
||||||
|
|
||||||
const VPN_API = "http://10.0.0.2:3000";
|
const VPN_API = "http://10.0.0.1:3000";
|
||||||
const DEFAULT_API = "https://quality-friendly-goldfish.ngrok-free.app"
|
const DEFAULT_API = "https://quality-friendly-goldfish.ngrok-free.app"
|
||||||
|
|
||||||
const [checking, setChecking] = useState(true);
|
const [checking, setChecking] = useState(true);
|
||||||
|
|
|
||||||
|
|
@ -2,181 +2,239 @@
|
||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
export interface LogEntry {
|
export interface LogEntry {
|
||||||
id: string; // Unique ID for each log entry
|
id: string; // Unique ID for each log entry
|
||||||
title: string;
|
title: string;
|
||||||
appName: string;
|
appName: string;
|
||||||
url: string | null;
|
url: string | null;
|
||||||
from: string; // UTC ISO string
|
from: string; // UTC ISO string with .000 milliseconds
|
||||||
to: string; // UTC ISO string
|
to: string; // UTC ISO string with .000 milliseconds
|
||||||
totalTime: number; // milliseconds
|
totalTime: number; // milliseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProjectLogs {
|
export interface ProjectLogs {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
logId: string | null;
|
logId: string | null;
|
||||||
logs: LogEntry[]; // All collected logs during tracking
|
logs: LogEntry[]; // All collected logs during tracking
|
||||||
trackingStartTime: string | null;
|
trackingStartTime: string | null;
|
||||||
lastWindowTitle: string | null; // Track last window title
|
lastWindowTitle: string | null; // Track last window title
|
||||||
lastWindowApp: string | null; // Track last window app
|
lastWindowApp: string | null; // Track last window app
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LogsState {
|
interface LogsState {
|
||||||
logsByProject: Record<string, ProjectLogs>;
|
logsByProject: Record<string, ProjectLogs>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: LogsState = {
|
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({
|
const logsSlice = createSlice({
|
||||||
name: "logs",
|
name: "logs",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
// Initialize logs for a project
|
// Initialize logs for a project
|
||||||
initializeProjectLogs: (
|
initializeProjectLogs: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{
|
action: PayloadAction<{
|
||||||
projectId: string;
|
projectId: string;
|
||||||
logId: string;
|
logId: string;
|
||||||
}>
|
}>
|
||||||
) => {
|
) => {
|
||||||
const { projectId, logId } = action.payload;
|
const { projectId, logId } = action.payload;
|
||||||
const nowUTC = new Date().toISOString();
|
const nowUTC = getTimestampWithZeroMs();
|
||||||
|
|
||||||
// Always create fresh logs for new tracking session
|
// Always create fresh logs for new tracking session
|
||||||
state.logsByProject[projectId] = {
|
state.logsByProject[projectId] = {
|
||||||
projectId,
|
projectId,
|
||||||
logId,
|
logId,
|
||||||
logs: [], // Start with empty logs
|
logs: [], // Start with empty logs
|
||||||
trackingStartTime: nowUTC,
|
trackingStartTime: nowUTC,
|
||||||
lastWindowTitle: null,
|
lastWindowTitle: null,
|
||||||
lastWindowApp: 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 {
|
export const {
|
||||||
initializeProjectLogs,
|
initializeProjectLogs,
|
||||||
updateWindowLog,
|
updateWindowLog,
|
||||||
clearProjectLogs,
|
clearProjectLogs,
|
||||||
clearAllLogs,
|
clearAllLogs,
|
||||||
debugLogsState,
|
debugLogsState,
|
||||||
|
fixAllTimestamps,
|
||||||
} = logsSlice.actions;
|
} = logsSlice.actions;
|
||||||
|
|
||||||
export default logsSlice.reducer;
|
export default logsSlice.reducer;
|
||||||
|
|
@ -185,6 +185,7 @@ interface Window {
|
||||||
}) => Promise<any>;
|
}) => Promise<any>;
|
||||||
stopProjectTracking: (data: {
|
stopProjectTracking: (data: {
|
||||||
vaId: string;
|
vaId: string;
|
||||||
|
logId: string,
|
||||||
projectId: string;
|
projectId: string;
|
||||||
type: string;
|
type: string;
|
||||||
time: string
|
time: string
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue