From 44d790ababbd8e9f4201f12f23ef30f6e68ed01c Mon Sep 17 00:00:00 2001 From: JaiPandey425 Date: Fri, 14 Nov 2025 16:04:48 +0530 Subject: [PATCH 1/3] if any user start the tracking , not login other devices and the expire token then the logout --- src/electron/api/axiosClient.ts | 2 ++ src/electron/ipc/ipcHandler.ts | 25 ++++++++++++++++++++++--- src/electron/main.ts | 3 ++- src/electron/preload.cts | 24 ++++++++++++++++++------ src/electron/utils/utilityFunctions.ts | 4 +++- src/ui/App.tsx | 2 ++ src/ui/layout/DashboardLayout.tsx | 23 ++++++++++++++++++++++- types.d.ts | 3 ++- 8 files changed, 73 insertions(+), 13 deletions(-) diff --git a/src/electron/api/axiosClient.ts b/src/electron/api/axiosClient.ts index 18d5805..5fedd1c 100644 --- a/src/electron/api/axiosClient.ts +++ b/src/electron/api/axiosClient.ts @@ -2,6 +2,7 @@ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; import { getToken } from '../utils/tokenHelper.js'; import chalk from "chalk"; +import { sendTokenExpired } from '../ipc/ipcHandler.js'; const TOKEN_KEY = 'ACCESS_TOKEN'; @@ -37,6 +38,7 @@ api.interceptors.response.use( console.error('[Axios Error]', error.message); if (error.response?.status === 401) { console.warn('Token expired — trigger refresh logic if needed'); + sendTokenExpired(); } return Promise.reject(error); } diff --git a/src/electron/ipc/ipcHandler.ts b/src/electron/ipc/ipcHandler.ts index 6e76996..c97479c 100644 --- a/src/electron/ipc/ipcHandler.ts +++ b/src/electron/ipc/ipcHandler.ts @@ -4,6 +4,9 @@ import { getActiveApp, handleLogin, takeScreenshot, startWindowPolling } from ". import api from "../api/axiosClient.js"; import { getMainWindow } from '../main.js'; + + + export const registerIPCHandlers = (mainWindow: BrowserWindow) => { console.log("⚙️ Registering IPC Handlers..."); @@ -43,6 +46,10 @@ export const registerIPCHandlers = (mainWindow: BrowserWindow) => { return { success: false, message: "Logout failed" }; } }); + + + + ipcMain.handle("get-active-app", async () => { return await getActiveApp(); @@ -80,15 +87,16 @@ ipcMain.handle("get-project-list", async () => { try { const res = await api.get("/list/projects"); - // console.log(" API /list/project response:", res); + console.log(" API /list/project response:", res); // Return only serializable data return JSON.parse(JSON.stringify(res)); } catch (error: any) { - console.error(" get-project-list error:", error); + console.error(" get-project-list error:", error.status); return { success: false, - message: error?.message || String(error) || "Unknown error" + message: error?.message || String(error) || "Unknown error", + status:error?.status }; } }); @@ -264,3 +272,14 @@ ipcMain.handle("change-password", async (event, data) => { }; } }); + + +export const sendTokenExpired = () => { + console.log("⚠️ Sending token-expired event to renderer"); + + BrowserWindow.getAllWindows().forEach((win) => { + if (!win.isDestroyed()) { + win.webContents.send("token-expired"); + } + }); +}; diff --git a/src/electron/main.ts b/src/electron/main.ts index 4083518..739da33 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -94,4 +94,5 @@ function handleCloseEvents(mainWindow: BrowserWindow) { export function getMainWindow() { return mainWindow; -} \ No newline at end of file +} + diff --git a/src/electron/preload.cts b/src/electron/preload.cts index c885819..08a3d6d 100644 --- a/src/electron/preload.cts +++ b/src/electron/preload.cts @@ -15,22 +15,34 @@ contextBridge.exposeInMainWorld('electron', { onWindowUpdate: (callback) => ipcRenderer.on("window:updated", (_, data) => callback(data)), + startProjectTracking: (data) => ipcRenderer.invoke("start-project-tracking", data), stopProjectTracking: (data) => ipcRenderer.invoke("stop-project-tracking", data), trackingTabsOpeningClosing: (data) => ipcRenderer.invoke("tracking-tabs-opening-and-closing", data), getProjectLogs: ( - projectID: string, - fromDate?: string, - toDate?: string -) => ipcRenderer.invoke("get-project-logs", { projectID, fromDate, toDate }), + projectID: string, + fromDate?: string, + toDate?: string + ) => ipcRenderer.invoke("get-project-logs", { projectID, fromDate, toDate }), -getProfile: () => ipcRenderer.invoke("get-profile"), + getProfile: () => ipcRenderer.invoke("get-profile"), -changePassword: (data: { oldPassword: string; password: string; cPassword: string }) => + changePassword: (data: { oldPassword: string; password: string; cPassword: string }) => ipcRenderer.invoke("change-password", data), + onTokenExpired: (callback) => { + // Remove old listeners + ipcRenderer.removeAllListeners("token-expired"); + + // Add single-use listener + ipcRenderer.once("token-expired", () => { + console.log("⚠️ Token expired event received in PRELOAD (ONE TIME)"); + callback(); + }); + }, + } satisfies Window['electron']); // function ipcInvoke( diff --git a/src/electron/utils/utilityFunctions.ts b/src/electron/utils/utilityFunctions.ts index b285a3b..b2e3808 100644 --- a/src/electron/utils/utilityFunctions.ts +++ b/src/electron/utils/utilityFunctions.ts @@ -114,4 +114,6 @@ export const startWindowPolling = (win: BrowserWindow) => { console.error("❌ Polling error:", err); } }, 5000); -}; \ No newline at end of file +}; + + diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 7a66012..474ce9f 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -20,6 +20,8 @@ const App: React.FC = () => { const theme = ThemeToggle(); const dispatch = useDispatch(); + + // ✅ Get user directly from Redux const user = useSelector((state: RootState) => state.user.user); diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index e748606..ca9c8bb 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -126,6 +126,24 @@ const DashboardLayout = () => { const [projectsData, setProjectsData] = useState([]); + useEffect(() => { + if (!window.electron?.onTokenExpired) return; + + console.log("🔐 Token expiry listener registered"); + + window.electron.onTokenExpired(() => { + console.log("🔥 Token expired — logging out..."); + handleLogout(); + }); + + return () => { + console.log("🧹 Cleaning token-expired listener"); + }; + }, []); + + + + const fetchProfile = async () => { try { dispatch(setLoading(true)); @@ -210,10 +228,13 @@ const DashboardLayout = () => { // Set the initial elapsed time in the component state setElapsedTime(initialElapsedTime); } - fetchProfile() + fetchProfile() } else { console.warn("⚠️ Unexpected response:", res); + // if (res?.status) { + // // handleLogout() + // } } } catch (err) { console.error("❌ Error fetching projects:", err); diff --git a/types.d.ts b/types.d.ts index 5058b2b..25a3095 100644 --- a/types.d.ts +++ b/types.d.ts @@ -33,6 +33,7 @@ interface ProjectListResponse { totalWorkTime?: string; totalWorkMs?: number; data?: any[]; // you can replace `any[]` with a typed array later + status : number } interface TabActivity { @@ -142,7 +143,7 @@ interface Window { }) => Promise<{ success: boolean; message?: string; data?: any }>; - +onTokenExpired: (callback: () => void) => void; }; } From d6481a0c27b4223b78167c68afb8ef74ee0b7c64 Mon Sep 17 00:00:00 2001 From: JaiPandey425 Date: Fri, 14 Nov 2025 16:49:48 +0530 Subject: [PATCH 2/3] sec remove show min --- src/electron/ipc/ipcHandler.ts | 21 +++++++++ src/electron/preload.cts | 4 ++ src/ui/layout/DashboardLayout.tsx | 7 +-- src/ui/pages/Dashboard.tsx | 18 ++++++-- .../LeaveAndAdjustments.tsx | 46 +++++++++---------- src/ui/pages/Timesheets/Timesheets.tsx | 12 ++++- types.d.ts | 31 +++++++------ 7 files changed, 94 insertions(+), 45 deletions(-) diff --git a/src/electron/ipc/ipcHandler.ts b/src/electron/ipc/ipcHandler.ts index c97479c..8db0718 100644 --- a/src/electron/ipc/ipcHandler.ts +++ b/src/electron/ipc/ipcHandler.ts @@ -274,6 +274,27 @@ ipcMain.handle("change-password", async (event, data) => { }); +ipcMain.handle("get-project-summary", async (_, { projectId, date }) => { + try { + const url = `/timesheet/summary/${projectId}/${date}`; + console.log("📡 Fetching summary:", url); + + const res = await api.get(url); + return { + success: true, + data: res.data, + }; + } catch (error: any) { + console.error("❌ Error fetching summary:", error.message); + return { + success: false, + message: error.response?.data?.message || "Something went wrong", + }; + } +}); + + + export const sendTokenExpired = () => { console.log("⚠️ Sending token-expired event to renderer"); diff --git a/src/electron/preload.cts b/src/electron/preload.cts index 08a3d6d..c9d34d2 100644 --- a/src/electron/preload.cts +++ b/src/electron/preload.cts @@ -43,6 +43,10 @@ contextBridge.exposeInMainWorld('electron', { }); }, + + getProjectSummary: (projectId: string, date: string) => + ipcRenderer.invoke("get-project-summary", { projectId, date }), + } satisfies Window['electron']); // function ipcInvoke( diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index ca9c8bb..6763099 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -548,7 +548,7 @@ const DashboardLayout = () => { const hrs = Math.floor(seconds / 3600); const mins = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; - return `${hrs}h ${mins}m ${secs}s`; + return `${hrs}h ${mins}m`; }; const toggleWindowSize = useRef(true); @@ -651,13 +651,14 @@ const DashboardLayout = () => { ))} - + } {/* Main Content */} diff --git a/src/ui/pages/Dashboard.tsx b/src/ui/pages/Dashboard.tsx index 30b55b2..0bcd51f 100644 --- a/src/ui/pages/Dashboard.tsx +++ b/src/ui/pages/Dashboard.tsx @@ -41,7 +41,8 @@ const formatTime = (seconds: number) => { const hrs = Math.floor(seconds / 3600); const mins = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; - return `${hrs}h ${mins}m ${secs}s`; + // return `${hrs}h ${mins}m ${secs}s`; + return `${hrs}h ${mins}m `; }; // Widget Component @@ -95,6 +96,17 @@ const Dashboard: React.FC = () => { ? projectTrackingMap.get(selectedProject.projectId) : null; + const formatWorkTime = (time: string) => { + if (!time) return "0h 0m"; + + // Example input: "0h 2m 29s" + const parts = time.split(" "); + + // Always return first two parts: ["0h", "2m"] + return parts.slice(0, 2).join(" "); + }; + + return (
{/* Header */} @@ -263,7 +275,7 @@ const Dashboard: React.FC = () => { {index + 1} {project.projectName} - {project.totalWorkTime || "0h 0m 0s"} + {formatWorkTime(project.totalWorkTime)} - - {/* */} {/* Tab Content */} @@ -251,10 +295,6 @@ const LeaveAndAdjustments: React.FC = () => { {activeTab === 'create_and_adjust_timesheet' && (
- {/*
-

Paid Time Off Request

-
*/} -
@@ -277,7 +317,6 @@ const LeaveAndAdjustments: React.FC = () => { ))} - {ptoErrors.fromDate && (
- -
handlePtoChange('fromDate', e.target.value)} - className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ptoErrors.fromDate ? 'input-error' : ''}`} + type="text" + value={projectSummary ? `${projectSummary.totalWorkedHoursDecimal} hrs` : "0 hrs"} + readOnly + className="input input-bordered input-sm w-full bg-base-200" /> - {ptoErrors.fromDate && ( - - )}
@@ -320,15 +353,11 @@ const LeaveAndAdjustments: React.FC = () => { Current pending time handlePtoChange('toDate', e.target.value)} - className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ptoErrors.toDate ? 'input-error' : ''}`} + type="text" + value={projectSummary ? `${projectSummary.pendingHours} hrs` : "0 hrs"} + readOnly + className="input input-bordered input-sm w-full bg-base-200" /> - {ptoErrors.toDate && ( - - )}
@@ -337,11 +366,9 @@ const LeaveAndAdjustments: React.FC = () => { - setPtoLeave((prev) => ({ ...prev, fromDate: e.target.value })) - } - className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ptoErrors.fromDate ? 'input-error' : ''}`} + value={startTime} + onChange={(e) => setStartTime(e.target.value)} + className="input input-bordered input-sm w-full focus:input-primary transition-all" />
@@ -351,30 +378,22 @@ const LeaveAndAdjustments: React.FC = () => { - setPtoLeave((prev) => ({ ...prev, toDate: e.target.value })) - } - className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ptoErrors.toDate ? 'input-error' : ''}`} + value={endTime} + onChange={(e) => setEndTime(e.target.value)} + className="input input-bordered input-sm w-full focus:input-primary transition-all" />
-
handlePtoChange('toDate', e.target.value)} - className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ptoErrors.toDate ? 'input-error' : ''}`} + type="text" + placeholder="Calculated automatically" + readOnly + className="input input-bordered input-sm w-full bg-base-200" /> - {ptoErrors.toDate && ( - - )}
@@ -383,10 +402,7 @@ const LeaveAndAdjustments: React.FC = () => { Description / Reason