diff --git a/e2e/example.spec.js b/e2e/example.spec.js new file mode 100644 index 0000000..f3c2971 --- /dev/null +++ b/e2e/example.spec.js @@ -0,0 +1,132 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); + return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var test_1 = require("@playwright/test"); +var electronApp; +var mainPage; +function waitForPreloadScript() { + return __awaiter(this, void 0, void 0, function () { + var _this = this; + return __generator(this, function (_a) { + return [2 /*return*/, new Promise(function (resolve) { + var interval = setInterval(function () { return __awaiter(_this, void 0, void 0, function () { + var electronBridge; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, mainPage.evaluate(function () { + return window.electron; + })]; + case 1: + electronBridge = _a.sent(); + if (electronBridge) { + clearInterval(interval); + resolve(true); + } + return [2 /*return*/]; + } + }); + }); }, 100); + })]; + }); + }); +} +test_1.test.beforeEach(function () { return __awaiter(void 0, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, test_1._electron.launch({ + args: ['.'], + env: { NODE_ENV: 'development' }, + })]; + case 1: + electronApp = _a.sent(); + return [4 /*yield*/, electronApp.firstWindow()]; + case 2: + mainPage = _a.sent(); + return [4 /*yield*/, waitForPreloadScript()]; + case 3: + _a.sent(); + return [2 /*return*/]; + } + }); +}); }); +test_1.test.afterEach(function () { return __awaiter(void 0, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, electronApp.close()]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); +}); }); +(0, test_1.test)('custom frame should minimize the mainWindow', function () { return __awaiter(void 0, void 0, void 0, function () { + var isMinimized; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, mainPage.click('#minimize')]; + case 1: + _a.sent(); + return [4 /*yield*/, electronApp.evaluate(function (electron) { + return electron.BrowserWindow.getAllWindows()[0].isMinimized(); + })]; + case 2: + isMinimized = _a.sent(); + (0, test_1.expect)(isMinimized).toBeTruthy(); + return [2 /*return*/]; + } + }); +}); }); +(0, test_1.test)('should create a custom menu', function () { return __awaiter(void 0, void 0, void 0, function () { + var menu; + var _a, _b; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: return [4 /*yield*/, electronApp.evaluate(function (electron) { + return electron.Menu.getApplicationMenu(); + })]; + case 1: + menu = _c.sent(); + (0, test_1.expect)(menu).not.toBeNull(); + (0, test_1.expect)(menu === null || menu === void 0 ? void 0 : menu.items).toHaveLength(2); + (0, test_1.expect)((_a = menu === null || menu === void 0 ? void 0 : menu.items[0].submenu) === null || _a === void 0 ? void 0 : _a.items).toHaveLength(2); + (0, test_1.expect)((_b = menu === null || menu === void 0 ? void 0 : menu.items[1].submenu) === null || _b === void 0 ? void 0 : _b.items).toHaveLength(3); + (0, test_1.expect)(menu === null || menu === void 0 ? void 0 : menu.items[1].label).toBe('View'); + return [2 /*return*/]; + } + }); +}); }); diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..39d7c81 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,73 @@ +"use strict"; +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var test_1 = require("@playwright/test"); +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); +/** + * See https://playwright.dev/docs/test-configuration. + */ +exports.default = (0, test_1.defineConfig)({ + testDir: './e2e', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: __assign({}, test_1.devices['Desktop Chrome']), + }, + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run dev:react', + url: 'http://localhost:5123', + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/src/electron/ipc/ipcHandler.ts b/src/electron/ipc/ipcHandler.ts index 585c759..71e511f 100644 --- a/src/electron/ipc/ipcHandler.ts +++ b/src/electron/ipc/ipcHandler.ts @@ -81,7 +81,7 @@ ipcMain.handle("get-project-list", async () => { // console.log(" API /list/project response:", res); - // βœ… Return only serializable data + // Return only serializable data return JSON.parse(JSON.stringify(res)); } catch (error: any) { console.error(" get-project-list error:", error); @@ -179,7 +179,7 @@ ipcMain.handle("get-project-logs", async (event, { projectID, fromDate, toDate } let url = `/report/project/${projectID}`; - console.log("url" , url) + // 🧠 Append query params only if dates are provided if (fromDate && toDate) { @@ -190,16 +190,33 @@ ipcMain.handle("get-project-logs", async (event, { projectID, fromDate, toDate } url += `?${params.toString()}`; } + console.log("url" , url) + const response = await api.get(url); console.log("βœ… response-logs:", response); - return response; + return { success: true, data: response.data }; } catch (error) { console.error("❌ Error fetching project logs:", error); throw error; } }); +ipcMain.handle("forgot-password", async (event, email: string) => { + try { + const response = await api.post("/forgot-password", { email }); + console.log(response , "Line response") + if (response) { + return { success: true, message: response.data?.message }; + } + } catch (error: any) { + console.error("Forgot password error:", error.response?.data || error.message); + return { + success: false, + message: error.response?.data?.message || "Something went wrong", + }; + } +}); ipcMain.handle('get-running-apps', async () => { return await getRunningAppsList() }); diff --git a/src/electron/main.ts b/src/electron/main.ts index 626f3ae..5d3a0ce 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -97,6 +97,8 @@ function handleCloseEvents(mainWindow: BrowserWindow) { }); } + + export function getMainWindow() { return mainWindow; } \ No newline at end of file diff --git a/src/electron/preload.cjs b/src/electron/preload.cjs new file mode 100644 index 0000000..786cf92 --- /dev/null +++ b/src/electron/preload.cjs @@ -0,0 +1,43 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var electron_1 = require("electron"); +console.log("setting PreLoad"); +electron_1.contextBridge.exposeInMainWorld('electron', { + login: function (credentials) { return electron_1.ipcRenderer.invoke('login-request', credentials); }, + logout: function () { return electron_1.ipcRenderer.invoke('logout-request'); }, + forgotPassword: function (email) { return electron_1.ipcRenderer.invoke("forgot-password", email); }, + getToken: function (TOKEN_KEY) { return electron_1.ipcRenderer.invoke('get-token', TOKEN_KEY); }, + uploadScreenshot: function () { return electron_1.ipcRenderer.invoke('upload-screenshot'); }, + getActiveAppDetails: function () { return electron_1.ipcRenderer.invoke('get-active-app'); }, + toggleCloseBtn: function (shouldEnable) { return electron_1.ipcRenderer.invoke('toggle-close-btn', shouldEnable); }, + resizeWindow: function (width, height) { return electron_1.ipcRenderer.invoke('resize-window', width, height); }, + getProjectList: function () { return electron_1.ipcRenderer.invoke("get-project-list"); }, + onWindowUpdate: function (callback) { + return electron_1.ipcRenderer.on("window:updated", function (_, data) { return callback(data); }); + }, + startProjectTracking: function (data) { return electron_1.ipcRenderer.invoke("start-project-tracking", data); }, + stopProjectTracking: function (data) { return electron_1.ipcRenderer.invoke("stop-project-tracking", data); }, + trackingTabsOpeningClosing: function (data) { + return electron_1.ipcRenderer.invoke("tracking-tabs-opening-and-closing", data); + }, + getProjectLogs: function (projectID, fromDate, toDate) { return electron_1.ipcRenderer.invoke("get-project-logs", { projectID: projectID, fromDate: fromDate, toDate: toDate }); }, +}); +// function ipcInvoke( +// key: Key +// ): Promise { +// return ipcRenderer.invoke(key); +// } +// function ipcOn( +// key: Key, +// callback: (payload: EventPayloadMapping[Key]) => void +// ) { +// const cb = (_: Electron.IpcRendererEvent, payload: any) => callback(payload); +// ipcRenderer.on(key, cb); +// return () => ipcRenderer.off(key, cb); +// } +// function ipcSend( +// key: Key, +// payload: EventPayloadMapping[Key] +// ) { +// ipcRenderer.send(key, payload); +// } diff --git a/src/electron/preload.cts b/src/electron/preload.cts index f6ce23a..881d0aa 100644 --- a/src/electron/preload.cts +++ b/src/electron/preload.cts @@ -5,6 +5,7 @@ console.log("setting PreLoad") contextBridge.exposeInMainWorld('electron', { login: (credentials: { email: string, password: string }) => ipcRenderer.invoke('login-request', credentials), logout: () => ipcRenderer.invoke('logout-request'), + forgotPassword: (email: string) => ipcRenderer.invoke("forgot-password", email), getToken: (TOKEN_KEY: "ACCESS_TOKEN" | "REFRESH_TOKEN") => ipcRenderer.invoke('get-token', TOKEN_KEY), uploadScreenshot: () => ipcRenderer.invoke('upload-screenshot'), getActiveAppDetails: () => ipcRenderer.invoke('get-active-app'), diff --git a/src/ui/App.tsx b/src/ui/App.tsx index c5f0a6e..7a66012 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -1,46 +1,38 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect } from "react"; import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom"; +import { useDispatch, useSelector } from "react-redux"; +import { AppDispatch, RootState } from "./store"; +import { loadUserFromStorage } from "./store/userSlice"; + import Login from "./pages/Login"; import Dashboard from "./pages/Dashboard"; import Projects from "./pages/Projects/Projects"; import DashboardLayout from "./layout/DashboardLayout"; -import ProtectedRoute from "./routes/ProtectedRoute" +import ProtectedRoute from "./routes/ProtectedRoute"; import PublicRoute from "./routes/PublicRoute"; -import { ThemeToggle } from "./helper/ThemeToggle" +import { ThemeToggle } from "./helper/ThemeToggle"; import LeaveAndAdjustments from "./pages/LeaveAndAdjustments/LeaveAndAdjustments"; import Timesheets from "./pages/Timesheets/Timesheets"; import Settings from "./pages/Settings/Settings"; import HelpSupport from "./pages/HelpSupport/HelpSupport"; -interface User { - email: string; - password: string; -} - - const App: React.FC = () => { const theme = ThemeToggle(); - const [user, setUser] = useState(null); + const dispatch = useDispatch(); + // βœ… Get user directly from Redux + const user = useSelector((state: RootState) => state.user.user); - // Apply theme useEffect(() => { document.documentElement.setAttribute("data-theme", theme); }, [theme]); - // Load user from localStorage + // βœ… Load user from localStorage into Redux when app starts useEffect(() => { - const storedUser = localStorage.getItem("user"); - if (storedUser) { - try { - setUser(JSON.parse(storedUser)); - } catch (err) { - console.error("Invalid user in localStorage", err); - } - } - }, []); + dispatch(loadUserFromStorage()); + }, [dispatch]); - + console.log("App.tsx β†’ user:", user); return ( @@ -50,7 +42,7 @@ const App: React.FC = () => { path="/login" element={ - + } /> @@ -60,7 +52,7 @@ const App: React.FC = () => { path="/dashboard" element={ - + } > @@ -82,4 +74,4 @@ const App: React.FC = () => { ); }; -export default App; \ No newline at end of file +export default App; diff --git a/src/ui/hooks/useActiveAppTracker.tsx b/src/ui/hooks/useActiveAppTracker.tsx index d726935..0727117 100644 --- a/src/ui/hooks/useActiveAppTracker.tsx +++ b/src/ui/hooks/useActiveAppTracker.tsx @@ -6,13 +6,13 @@ export const useActiveAppTracker = ( isTracking: boolean, activeProjectLogId: string | null, projectId: string | null, - projectName: string + projectName: string | null ) => { const dispatch = useDispatch(); const lastActiveRef = useRef(null); const lastLogIdRef = useRef(null); const lastTitleRef = useRef(null); - const intervalIdRef = useRef(null); + const intervalIdRef = useRef(null); const finalizeTrackingRef = useRef<(() => Promise) | null>(null); // βœ… Common guard to check if we can send @@ -31,15 +31,27 @@ export const useActiveAppTracker = ( } try { - const payload = { - id: !isNew ? lastLogIdRef.current : undefined, + const payload: { + id?: string; + logId: string; + projectId: string; + title: string; + appName: string; + } = { logId: projectLogId, projectId, title, appName, }; + // βœ… only attach `id` if it’s non-null and not undefined + if (!isNew && lastLogIdRef.current) { + payload.id = lastLogIdRef.current; + } + + console.log(isNew ? "➑️ POST new log" : "✏️ PATCH update log", payload); + const res = await window.electron.trackingTabsOpeningClosing(payload); if (isNew && res?._id) lastLogIdRef.current = res._id; diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index 41f5e40..d7f1b91 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -30,10 +30,17 @@ import { useActiveAppTracker } from "../hooks/useActiveAppTracker"; import { useDispatch, useSelector } from "react-redux"; import { RootState } from "../store"; import { setProjectsDataSlice, setProjectSummarySlice, setSelectedProjectSlice } from "../store/projectSlice"; +import { clearUser } from "../store/userSlice"; + +// interface User { +// email: string; +// password: string; +// } interface User { + id: string; + name: string; email: string; - password: string; } // Updated Project interface to match the JSON structure @@ -72,12 +79,7 @@ export interface TrackingState { projectsData: Project[]; } -interface DashboardLayoutProps { - user: User | null; - setUser: React.Dispatch>; -} - -const DashboardLayout: React.FC = ({ setUser }) => { +const DashboardLayout = () => { const [isSidebarOpen, setSidebarOpen] = useState(true); const [isMobile, setIsMobile] = useState(window.innerWidth < 768); const navigate = useNavigate(); @@ -119,8 +121,8 @@ const DashboardLayout: React.FC = ({ setUser }) => { const { finalizeTracking } = useActiveAppTracker( isTracking, activeProjectLogId, - selectedProject?.projectId, - selectedProject?.projectName + selectedProject?.projectId ?? null, + selectedProject?.projectName ?? null ); const socketRef = useRef(null); @@ -174,23 +176,28 @@ const DashboardLayout: React.FC = ({ setUser }) => { setSelectedProject(firstProject); dispatch(setSelectedProjectSlice(firstProject)); + // Convert totalWorkMs to seconds for elapsedTime + const initialElapsedTime = Math.floor(firstProject.totalWorkMs / 1000); + // Initialize tracking map for that project setProjectTrackingMap((prev) => { const updated = new Map(prev); if (!updated.has(firstProject.projectId)) { updated.set(firstProject.projectId, { - elapsedTime: 0, + elapsedTime:initialElapsedTime, startTime: null, endTime: null, isTracking: false, isPaused: false, pauseStartTime: null, totalPausedTime: 0, - accumulatedTime: 0, + accumulatedTime: initialElapsedTime, }); } return updated; }); + // Set the initial elapsed time in the component state + setElapsedTime(initialElapsedTime); } } else { console.warn("⚠️ Unexpected response:", res); @@ -209,12 +216,89 @@ const DashboardLayout: React.FC = ({ setUser }) => { const handleLogout = async () => { try { - const res = await window.electron.logout(); - localStorage.removeItem("user"); - setUser(null); + const res = await window.electron.logout(); + if (res) { + dispatch(clearUser()); // βœ… Removes from Redux + localStorage navigate("/login"); - } catch (error) { - console.error("Logout error:", error); + } + } catch (error) { + console.error("Logout error:", error); + } + + }; + + // Connect and start sending + const handleConnect = async () => { + + const token = await window.electron.getToken("ACCESS_TOKEN"); + + if (socketRef.current) { + console.warn("Already connected"); + return; + } + if(!selectedProject?.projectId) return + + console.log(selectedProject?.projectId, "SelectedProject ID") + socketRef.current = io("https://wired-terminally-muskrat.ngrok-free.app", { + transports: ["websocket"], + secure: true, + auth: { + token: token, + projectId: selectedProject?.projectId, + userType: "va" + }, + }); + + socketRef.current.on("connect", () => { + console.log("🟒 Connected:", socketRef.current?.id); + setIsConnected(true); + + // Start sending every 5 seconds + intervalRef.current = setInterval(async () => { + const apps = await window.electron.getRunningApps() + socketRef.current?.emit("getVisibleApps", { token: await token, apps }); + + // const base64String = await window.electron.captureAppScreenShot("Screen 1"); + // if (base64String) { + // socketRef.current?.emit("sendImage", {token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY5MGIzMjhkMDkxMDFmNzRjZjUyNGMwNyIsImVtYWlsIjoiYmhhZ3dhbi5yQGhpY2dsb2JhbHNvbHV0aW9ucy5jb20iLCJuYW1lIjoiUm9oaXQiLCJpYXQiOjE3NjI4NTgxOTMsImV4cCI6MTc2Mjk0NDU5M30.UlHy3RU9W5LOid7smG7WzyNT7dnqhQT4qqzRrN1axmU", image: base64String, title: "Screen 1"}); + // } + }, 5000); + }); + + socketRef.current.on("appToCapture", async ({ appTitle }) => { + console.log("πŸ“Έ Capture requested for:", appTitle); + + const base64String = await window.electron.captureAppScreenShot(appTitle); + if (base64String && socketRef.current) { + socketRef.current.emit("sendImage", { + token: token, + image: base64String, + title: appTitle + }); + } + }); + + socketRef.current.on("disconnect", () => { + console.log("πŸ”΄ Disconnected"); + setIsConnected(false); + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + }); + }; + + const handleDisconnect = () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + + if (socketRef.current) { + socketRef.current.disconnect(); + socketRef.current = null; + setIsConnected(false); + console.log("πŸ”Œ Disconnected manually"); } }; @@ -514,28 +598,52 @@ const DashboardLayout: React.FC = ({ setUser }) => { // Handle project selection const handleProjectSelect = (projectId: string) => { - if (isTracking) return; + if (isTracking) return; // prevent switching while tracking - const project = projectsData.find((p) => p.projectId === projectId); - if (project) { - setSelectedProject(project); + const project = projectsData.find((p) => p.projectId === projectId); + if (!project) return; - const projectData = projectTrackingMap.get(projectId); - if (projectData) { - setElapsedTime(projectData.elapsedTime); - setStartTime(projectData.startTime); - setEndTime(projectData.endTime); - setIsTracking(projectData.isTracking); - setIsPaused(projectData.isPaused); - } else { - setElapsedTime(0); - setStartTime(null); - setEndTime(null); - setIsTracking(false); - setIsPaused(false); - } + setSelectedProject(project); + dispatch(setSelectedProjectSlice(project)); + + const existingData = projectTrackingMap.get(projectId); + + setProjectTrackingMap((prev) => { + const updated = new Map(prev); + + // If project is not yet tracked, initialize it with totalWorkMs + if (!existingData) { + const initialElapsedTime = Math.floor((project.totalWorkMs || 0) / 1000); + updated.set(projectId, { + elapsedTime: initialElapsedTime, + startTime: null, + endTime: null, + isTracking: false, + isPaused: false, + pauseStartTime: null, + totalPausedTime: 0, + accumulatedTime: initialElapsedTime, + }); + + // Update component-level state + setElapsedTime(initialElapsedTime); + setStartTime(null); + setEndTime(null); + setIsTracking(false); + setIsPaused(false); + } else { + // If already tracked, restore its state + setElapsedTime(existingData.elapsedTime); + setStartTime(existingData.startTime); + setEndTime(existingData.endTime); + setIsTracking(existingData.isTracking); + setIsPaused(existingData.isPaused); } - }; + + return updated; + }); +}; + const trackingState: TrackingState = { selectedProject, @@ -654,11 +762,11 @@ const DashboardLayout: React.FC = ({ setUser }) => { } {/* Layout Body */} -
+
{/* Sidebar */}