diff --git a/package-lock.json b/package-lock.json index 263b7cf..016bdbf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@tailwindcss/vite": "^4.1.16", "axios": "^1.13.2", + "chalk": "^5.6.2", "electron-store": "^11.0.2", "get-windows": "^9.2.3", "lucide-react": "^0.552.0", @@ -288,6 +289,21 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/parser": { "version": "7.24.5", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", @@ -3832,17 +3848,15 @@ } }, "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", "engines": { - "node": ">=4" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/check-error": { @@ -5422,6 +5436,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -6319,6 +6334,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -8168,6 +8184,21 @@ "concat-map": "0.0.1" } }, + "node_modules/npm-run-all/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/npm-run-all/node_modules/cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -9863,6 +9894,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, diff --git a/package.json b/package.json index 8d674c0..dd4f5ca 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dependencies": { "@tailwindcss/vite": "^4.1.16", "axios": "^1.13.2", + "chalk": "^5.6.2", "electron-store": "^11.0.2", "get-windows": "^9.2.3", "lucide-react": "^0.552.0", diff --git a/src/electron/api/apiHandler.ts b/src/electron/api/apiHandler.ts index 41aaeef..50e321a 100644 --- a/src/electron/api/apiHandler.ts +++ b/src/electron/api/apiHandler.ts @@ -1,30 +1,30 @@ -// electron/ipc/apiHandler.ts -import { ipcMain } from 'electron'; -import api from '../api/axiosClient.js'; +// // electron/ipc/apiHandler.ts +// import { ipcMain } from 'electron'; +// import api from '../api/axiosClient.js'; -interface ApiRequestArgs { - method: 'get' | 'post' | 'put' | 'patch' | 'delete'; - url: string; - data?: any; - config?: Record; -} +// interface ApiRequestArgs { +// method: 'get' | 'post' | 'put' | 'patch' | 'delete'; +// url: string; +// data?: any; +// config?: Record; +// } -ipcMain.handle('api:request', async (_, args: ApiRequestArgs) => { - try { - const response = await api.request({ - method: args.method, - url: args.url, - data: args.data, - ...args.config, - }); +// ipcMain.handle('api:request', async (_, args: ApiRequestArgs) => { +// try { +// const response = await api.request({ +// method: args.method, +// url: args.url, +// data: args.data, +// ...args.config, +// }); - return { success: true, data: response }; - } catch (error: any) { - console.error('[API Handler Error]', error.message); - return { - success: false, - error: error.message, - status: error.response?.status, - }; - } -}); +// return { success: true, data: response }; +// } catch (error: any) { +// console.error('[API Handler Error]', error.message); +// return { +// success: false, +// error: error.message, +// status: error.response?.status, +// }; +// } +// }); diff --git a/src/electron/api/axiosClient.ts b/src/electron/api/axiosClient.ts index 240cd23..ca851b2 100644 --- a/src/electron/api/axiosClient.ts +++ b/src/electron/api/axiosClient.ts @@ -1,6 +1,7 @@ // electron/api/axiosClient.ts import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; import { getToken } from '../utils/tokenHelper.js'; +import chalk from "chalk"; const TOKEN_KEY = 'ACCESS_TOKEN'; @@ -9,6 +10,10 @@ const api: AxiosInstance = axios.create({ // timeout: 10000, }); +console.log("Creating Axios Instance", axios.VERSION); +console.log("Axios .get reference:", api.get); +console.log("Axios .request reference:", api.request); + // --- Request Interceptor --- api.interceptors.request.use( async (config: AxiosRequestConfig): Promise => { @@ -20,7 +25,11 @@ api.interceptors.request.use( config.headers['ngrok-skip-browser-warning'] = 98098; } - console.log(`[Axios] ${config.method?.toUpperCase()} ${config.url}`); + // Log method, URL, and headers + console.log( + chalk.red(`[Axios] ${config.method?.toUpperCase()} ${config.url}`) + ); + console.log(chalk.blue("Headers: "), config.headers); // <-- log headers return config; }, (error) => Promise.reject(error) diff --git a/src/electron/main.ts b/src/electron/main.ts index 4bb27fe..54b2d6c 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -1,27 +1,30 @@ -import { app, BrowserWindow } from 'electron'; +import { app, BrowserWindow, session } from 'electron'; import { ipcMainHandle, ipcMainOn, isDev } from './util.js'; import { getStaticData, pollResources } from './resourceManager.js'; import { getPreloadPath, getUIPath } from './pathResolver.js'; -import "./ipc/ipcHandler.js" +import "./ipc/ipcHandler.js"; app.on('ready', async () => { - console.log("ready") + console.log("ready"); + const mainWindow = new BrowserWindow({ + width: 550, // fixed width + height: 750, // fixed height + resizable: false, // prevents resizing + fullscreenable: false, // disables fullscreen toggle (F11 or double-click title bar) + maximizable: false, // prevents maximize + autoHideMenuBar: true, + frame: process.platform === 'darwin' ? false : true, webPreferences: { preload: getPreloadPath(), }, - - resizable: false, // disables resizing (no maximize) - minimizable: false, // disables minimize button - maximizable: false, // disables maximize button - closable: true, - alwaysOnTop: true, - // disables default system frame (dont do this if you want a proper working menu bar) - frame: process.platform === 'darwin' ? false : true, - autoHideMenuBar: true }); + session.defaultSession.webRequest.onBeforeRequest((details, callback) => { + console.log('🌐 Request:', details.method, details.url); + callback({ cancel: false }); + }); if (isDev()) { mainWindow.loadURL('http://localhost:5123'); @@ -32,39 +35,37 @@ app.on('ready', async () => { pollResources(mainWindow); - ipcMainHandle('getStaticData', () => { - return getStaticData(); + // IPC handlers + ipcMainHandle('getStaticData', () => getStaticData()); + + ipcMainOn('sendFrameAction', (payload) => { + switch (payload) { + case 'CLOSE': + mainWindow.close(); + break; + case 'MAXIMIZE': + // prevent maximize + break; + case 'MINIMIZE': + mainWindow.minimize(); + break; + } }); - // ipcMainOn('sendFrameAction', (payload) => { - // switch (payload) { - // case 'CLOSE': - // mainWindow.close(); - // break; - // case 'MAXIMIZE': - // mainWindow.maximize(); - // break; - // case 'MINIMIZE': - // mainWindow.minimize(); - // break; - // } - // }); - - // createTray(mainWindow); handleCloseEvents(mainWindow); - // createMenu(mainWindow); - // await takeScreenshot(); }); + + function handleCloseEvents(mainWindow: BrowserWindow) { let willClose = false; mainWindow.on('close', (e) => { - if (willClose) { - return; - } + if (willClose) return; + e.preventDefault(); mainWindow.hide(); + if (app.dock) { app.dock.hide(); } @@ -78,3 +79,5 @@ function handleCloseEvents(mainWindow: BrowserWindow) { willClose = false; }); } + + diff --git a/src/electron/preload.cts b/src/electron/preload.cts index b242939..232b28e 100644 --- a/src/electron/preload.cts +++ b/src/electron/preload.cts @@ -30,4 +30,4 @@ contextBridge.exposeInMainWorld('electron', { // payload: EventPayloadMapping[Key] // ) { // ipcRenderer.send(key, payload); -// } \ No newline at end of file +// } diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index a162a39..c6cf96e 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -18,6 +18,10 @@ import { SettingsIcon, TimerIcon, HelpCircleIcon, + Pause, + ShieldBan, + ChevronDown, + Play, } from "lucide-react"; interface User { @@ -37,6 +41,9 @@ interface ProjectTrackingData { endTime: Date | null; isTracking: boolean; isPaused: boolean; + pauseStartTime: Date | null; + totalPausedTime: number; + accumulatedTime: number; // ✅ NEW: Stores time from previous sessions } interface TrackingState { @@ -54,12 +61,9 @@ interface TrackingState { projectsData: Project[]; } - - interface DashboardLayoutProps { user: User | null; setUser: React.Dispatch>; - // trackingState: TrackingState; } const DashboardLayout: React.FC = ({ setUser }) => { @@ -68,7 +72,6 @@ const DashboardLayout: React.FC = ({ setUser }) => { const navigate = useNavigate(); const location = useLocation(); - // Tracking states const [selectedProject, setSelectedProject] = useState(null); const [isTracking, setIsTracking] = useState(false); @@ -88,18 +91,6 @@ const DashboardLayout: React.FC = ({ setUser }) => { Map >(new Map()); - useEffect(() => { - const handleResize = () => { - const mobile = window.innerWidth < 768; - setIsMobile(mobile); - setSidebarOpen(!mobile); - }; - - handleResize(); - window.addEventListener("resize", handleResize); - return () => window.removeEventListener("resize", handleResize); - }, []); - const handleLogout = async () => { try { const res = await window.electron.logout() @@ -109,7 +100,7 @@ const DashboardLayout: React.FC = ({ setUser }) => { navigate("/login"); } } catch (error) { - console.log(error) + console.error("Logout error:", error); } }; @@ -138,8 +129,7 @@ const DashboardLayout: React.FC = ({ setUser }) => { { label: "Help & Support", path: "/dashboard/help-support", icon: }, ]; - - // Sync global states with selected project tracking data + // ✅ Sync global states with selected project tracking data useEffect(() => { if (selectedProject) { const projectData = projectTrackingMap.get(selectedProject.id); @@ -153,14 +143,57 @@ const DashboardLayout: React.FC = ({ setUser }) => { } }, [selectedProject, projectTrackingMap]); - // Start tracking - const handleStartTracking = (project: Project) => { - // Clear any existing timer - if (timerRef.current) { - clearInterval(timerRef.current); + // ✅ Timer effect - calculates elapsed time based on timestamps + accumulated time + useEffect(() => { + if (isTracking && !isPaused && selectedProject) { + timerRef.current = setInterval(() => { + setProjectTrackingMap((prev) => { + const updated = new Map(prev); + const current = updated.get(selectedProject.id); + + if (current && current.isTracking && !current.isPaused && current.startTime) { + const now = Date.now(); + const startTimestamp = current.startTime.getTime(); + const currentSessionTime = Math.floor((now - startTimestamp) / 1000); + const totalPausedTime = current.totalPausedTime || 0; + + // ✅ FIX: Add accumulated time from previous sessions + const calculatedElapsed = + (current.accumulatedTime || 0) + currentSessionTime - totalPausedTime; + + current.elapsedTime = calculatedElapsed; + updated.set(selectedProject.id, { ...current }); + + // Update global elapsedTime in sync + setElapsedTime(calculatedElapsed); + } + return updated; + }); + }, 1000); + } else { + if (timerRef.current) { + clearInterval(timerRef.current); + timerRef.current = null; + } } - // Update project tracking map + return () => { + if (timerRef.current) { + clearInterval(timerRef.current); + timerRef.current = null; + } + }; + }, [isTracking, isPaused, selectedProject]); + + // ✅ Start tracking + const handleStartTracking = (project: Project) => { + if (timerRef.current) { + clearInterval(timerRef.current); + timerRef.current = null; + } + + const now = new Date(); + setProjectTrackingMap((prev) => { const updated = new Map(prev); const current = updated.get(project.id) || { @@ -169,46 +202,37 @@ const DashboardLayout: React.FC = ({ setUser }) => { endTime: null, isTracking: false, isPaused: false, + pauseStartTime: null, + totalPausedTime: 0, + accumulatedTime: 0, }; - const now = new Date(); + // ✅ Keep accumulated time from previous sessions current.startTime = now; current.isTracking = true; current.isPaused = false; + current.pauseStartTime = null; + current.totalPausedTime = 0; // Reset pause time for new session updated.set(project.id, current); + // Update global tracking states + setIsTracking(true); + setIsPaused(false); + setStartTime(now); + setElapsedTime(current.accumulatedTime); // Start from accumulated time + return updated; }); - - // Update global tracking states - setIsTracking(true); - setIsPaused(false); - setStartTime(new Date()); - - // Start the interval timer - timerRef.current = setInterval(() => { - setProjectTrackingMap((prev) => { - const updated = new Map(prev); - const current = updated.get(project.id); - - // Only increment when tracking AND not paused - if (current && current.isTracking && !current.isPaused) { - current.elapsedTime += 1; - updated.set(project.id, { ...current }); - } - return updated; - }); - }, 1000); }; - // Pause/Resume tracking + // ✅ Pause/Resume tracking with pause duration tracking const handlePauseResume = () => { if (!selectedProject) return; - // Get current state before toggling to avoid double-click issues const currentProjectData = projectTrackingMap.get(selectedProject.id); if (!currentProjectData || !currentProjectData.isTracking) return; + const now = new Date(); const newPausedState = !currentProjectData.isPaused; setProjectTrackingMap((prev) => { @@ -216,48 +240,63 @@ const DashboardLayout: React.FC = ({ setUser }) => { const current = updated.get(selectedProject.id); if (!current) return updated; - // Set the new pause state - current.isPaused = newPausedState; + if (newPausedState) { + // Starting pause + current.isPaused = true; + current.pauseStartTime = now; + } else { + // Resuming from pause + current.isPaused = false; + if (current.pauseStartTime) { + const pauseDuration = Math.floor( + (now.getTime() - current.pauseStartTime.getTime()) / 1000 + ); + current.totalPausedTime = (current.totalPausedTime || 0) + pauseDuration; + current.pauseStartTime = null; + } + } + updated.set(selectedProject.id, { ...current }); + setIsPaused(newPausedState); return updated; }); - - // Update global isPaused state immediately - setIsPaused(newPausedState); }; - // Stop tracking + // ✅ Stop tracking - PRESERVES elapsed time const handleStopTracking = () => { if (!selectedProject) return; - // Clear the timer if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; } - // Update project tracking map + const now = new Date(); + setProjectTrackingMap((prev) => { const updated = new Map(prev); const current = updated.get(selectedProject.id); if (!current) return updated; - const now = new Date(); + // ✅ FIX: Store current elapsed time as accumulated time + current.accumulatedTime = current.elapsedTime; current.isTracking = false; current.isPaused = false; current.endTime = now; + current.startTime = null; // Clear startTime for stopped state updated.set(selectedProject.id, { ...current }); + // Update global states - keep elapsed time + setIsTracking(false); + setIsPaused(false); + setEndTime(now); + setElapsedTime(current.elapsedTime); // ✅ Keep the elapsed time + return updated; }); - // Reset global tracking states to allow selecting another project - setIsTracking(false); - setIsPaused(false); - setEndTime(new Date()); - - console.log("projectTrackingMap", projectTrackingMap) + console.log("Tracking stopped. Time preserved:", elapsedTime); }; // Cleanup timer on unmount @@ -269,16 +308,14 @@ const DashboardLayout: React.FC = ({ setUser }) => { }; }, []); - // Handle project selection + // ✅ Handle project selection const handleProjectSelect = (id: number) => { - // Only allow project selection when not tracking if (isTracking) return; const project = projectsData.find((p) => p.id === id); if (project) { setSelectedProject(project); - // Load existing tracking data if available const projectData = projectTrackingMap.get(id); if (projectData) { setElapsedTime(projectData.elapsedTime); @@ -287,7 +324,6 @@ const DashboardLayout: React.FC = ({ setUser }) => { setIsTracking(projectData.isTracking); setIsPaused(projectData.isPaused); } else { - // Reset states for new project setElapsedTime(0); setStartTime(null); setEndTime(null); @@ -312,132 +348,129 @@ const DashboardLayout: React.FC = ({ setUser }) => { handleProjectSelect, }; + 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 ( -
- {/* Sidebar */} - + {/* Right Section */} +
+ {/* Timer */} +
+ {formatTime(elapsedTime || 0)} +
- {/* Mobile overlay */} - {isMobile && isSidebarOpen && ( -
- )} + {/* Alert Icon */} + + + - {/* Main Content */} -
- {/* Navbar */} - +
+ - {/* Page Content */} -
+ {/* Layout Body */} +
+ {/* Sidebar - Always visible */} + + + {/* Main Content */} +
-
+ ); }; -export default DashboardLayout; +export default DashboardLayout; \ No newline at end of file