diff --git a/src/electron/api/axiosClient.ts b/src/electron/api/axiosClient.ts index ca851b2..18d5805 100644 --- a/src/electron/api/axiosClient.ts +++ b/src/electron/api/axiosClient.ts @@ -6,13 +6,13 @@ import chalk from "chalk"; const TOKEN_KEY = 'ACCESS_TOKEN'; const api: AxiosInstance = axios.create({ - baseURL: 'https://wired-terminally-muskrat.ngrok-free.app/api/v1/va', // ๐Ÿ” your backend + baseURL: 'https://dreadingly-unfighting-pandora.ngrok-free.dev/api/v1/va', // ๐Ÿ” your backend // timeout: 10000, }); -console.log("Creating Axios Instance", axios.VERSION); -console.log("Axios .get reference:", api.get); -console.log("Axios .request reference:", api.request); +// 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( @@ -22,14 +22,9 @@ api.interceptors.request.use( if (token) { config.headers['token'] = token; - config.headers['ngrok-skip-browser-warning'] = 98098; + config.headers['ngrok-skip-browser-warning'] = 'true'; } - // 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/ipc/ipcHandler.ts b/src/electron/ipc/ipcHandler.ts index 33f0d6e..dbb2c31 100644 --- a/src/electron/ipc/ipcHandler.ts +++ b/src/electron/ipc/ipcHandler.ts @@ -53,15 +53,152 @@ export const registerIPCHandlers = (mainWindow: BrowserWindow) => { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.setClosable(shouldEnable); } - }); + }); - ipcMain.handle('resize-window', (event, width: number, height: number) => { - const mainWindow = getMainWindow() - console.log("toggle-close-btn Called", mainWindow) - // if (mainWindow && !mainWindow.isDestroyed()) { + + +ipcMain.handle("resize-window", (event, width: number, height: number) => { + const mainWindow = getMainWindow(); + if (mainWindow && !mainWindow.isDestroyed()) { + console.log(`๐Ÿ“ Resizing window to ${width}x${height}`); + mainWindow.setResizable(true); // temporarily enable resizing mainWindow.setSize(width, height); - // } - }) + mainWindow.setResizable(false); // disable again if you want to lock it + } +}); + + + // โœ… Start background polling when main window is ready +}; +// startWindowPolling(mainWindow); + + + +ipcMain.handle("get-project-list", async () => { + // console.log("๐Ÿ“ก get-project-list called"); + try { + const res = await api.get("/list/projects"); + + // 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); + return { + success: false, + message: error?.message || String(error) || "Unknown error" + }; + } +}); + + +// When project tracking start +ipcMain.handle("start-project-tracking", async (event, payload) => { + try { + console.log("๐Ÿš€ start-project-tracking called with:", payload); + + const response = await api.post("/log/project", payload); + + console.log(" Tracking start synced:", response); + return response; + } catch (error) { + console.error(" Error syncing project start:", error); + throw error; + } +}); + + +// When project tracking stop +ipcMain.handle("stop-project-tracking", async (event, payload) => { + try { + console.log("stop-project-tracking called with:", payload); + + const response = await api.post("/log/project", payload); + + console.log(" Tracking stop synced:", response); + return response; + } catch (error) { + console.error(" Error syncing project start:", error); + throw error; + } +}); + +// tabs opening and closing when and tracking start +ipcMain.handle("tracking-tabs-opening-and-closing", async (event, payload) => { + try { + console.log("๐ŸŸฆ IPC tracking-tabs-opening-and-closing:", payload); + + let response; + if (!payload.id) { + // ๐ŸŸข POST new log (new tab opened) + response = await api.post("/log/project/track", { + logId: payload.logId, + projectId: payload.projectId, + title: payload.title, + appName: payload.appName, + }); + console.log("โœ… New log created:", response.data); + } else { + // ๐ŸŸก PATCH existing log (same tab, update) + response = await api.patch("/log/project/track", { + id: payload.id, + logId: payload.logId, + projectId: payload.projectId, + title: payload.title, + appName: payload.appName, + }); + console.log("๐ŸŸจ Log updated:", response.data); + } + + return response.data; + } catch (error) { + console.error("โŒ Error syncing project tab tracking:", error); + throw error; + } +}); + + +// ipcMain.handle("get-project-logs", async (event, projectID) => { +// try { +// console.log("get-project-logs",projectID) +// const response = await api.get(`/report/project/${projectID}`); +// console.log("response-logs 164" , response) + +// return response; +// } catch (error) { +// console.error("Error fetching project logs:", error); +// throw error; +// } +// }); + + +ipcMain.handle("get-project-logs", async (event, { projectID, fromDate, toDate }) => { + try { + console.log("๐Ÿ“ฅ get-project-logs called with:", { projectID, fromDate, toDate }); + + let url = `/report/project/${projectID}`; + + console.log("url" , url) + + // ๐Ÿง  Append query params only if dates are provided + if (fromDate && toDate) { + const params = new URLSearchParams({ + from: fromDate, + to: toDate, + }); + url += `?${params.toString()}`; + } + + const response = await api.get(url); + console.log("โœ… response-logs:", response); + + return response; + } catch (error) { + console.error("โŒ Error fetching project logs:", error); + throw error; + } +}); }; diff --git a/src/electron/main.ts b/src/electron/main.ts index e9d20ab..626f3ae 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -22,7 +22,7 @@ app.on('ready', async () => { width: 500, // fixed width height: 600, // fixed height - resizable: true, // prevents resizing + resizable: false, // prevents resizing fullscreenable: false, // disables fullscreen toggle (F11 or double-click title bar) minimizable: false, // disables minimize button maximizable: false, // prevents maximize diff --git a/src/electron/preload.cts b/src/electron/preload.cts index 6aebf39..f6ce23a 100644 --- a/src/electron/preload.cts +++ b/src/electron/preload.cts @@ -10,10 +10,23 @@ contextBridge.exposeInMainWorld('electron', { getActiveAppDetails: () => ipcRenderer.invoke('get-active-app'), toggleCloseBtn: (shouldEnable: boolean) => ipcRenderer.invoke('toggle-close-btn', shouldEnable), resizeWindow: (width: number, height: number) => ipcRenderer.invoke('resize-window', width, height), + getProjectList: () => ipcRenderer.invoke("get-project-list"), getRunningApps: () => ipcRenderer.invoke('get-running-apps'), captureAppScreenShot: (appTitle: string) : Promise => ipcRenderer.invoke('capture-specific-window', appTitle), 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 }), + } satisfies Window['electron']); // function ipcInvoke( diff --git a/src/electron/utils/utilityFunctions.ts b/src/electron/utils/utilityFunctions.ts index 3ef1b6e..ab0ed15 100644 --- a/src/electron/utils/utilityFunctions.ts +++ b/src/electron/utils/utilityFunctions.ts @@ -57,7 +57,7 @@ export const handleLogin = async (credentials: { email: string, password: string try { console.log("โžก๏ธ Calling API..."); const response = await axios.post( - 'https://wired-terminally-muskrat.ngrok-free.app/api/v1/va/login', + 'https://dreadingly-unfighting-pandora.ngrok-free.dev/api/v1/va/login', { userDetails: { ...credentials } }, { headers: { "ngrok-skip-browser-warning": 69420 } } ); diff --git a/src/ui/hooks/useActiveAppTracker.tsx b/src/ui/hooks/useActiveAppTracker.tsx index e2568b3..d726935 100644 --- a/src/ui/hooks/useActiveAppTracker.tsx +++ b/src/ui/hooks/useActiveAppTracker.tsx @@ -1,51 +1,141 @@ -// src/hooks/useActiveAppTracker.ts -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useCallback } from "react"; import { useDispatch } from "react-redux"; import { setActiveWindow, WindowInfo } from "../store/windowSlice"; export const useActiveAppTracker = ( isTracking: boolean, - projectId: string, + activeProjectLogId: string | null, + projectId: string | null, projectName: string ) => { const dispatch = useDispatch(); const lastActiveRef = useRef(null); + const lastLogIdRef = useRef(null); + const lastTitleRef = useRef(null); + const intervalIdRef = useRef(null); + const finalizeTrackingRef = useRef<(() => Promise) | null>(null); - useEffect(() => { - if (!isTracking || !projectId) { - console.log(" Tracking not active or project not selected"); + // โœ… Common guard to check if we can send + const canSend = !!(activeProjectLogId && projectId); + + const sendActivityLog = async ( + projectLogId: string, + projectId: string, + title: string, + appName: string, + isNew: boolean + ) => { + if (!canSend) { + console.log("๐Ÿšซ Skipping API โ€” missing activeProjectLogId or projectId"); + return null; + } + + try { + const payload = { + id: !isNew ? lastLogIdRef.current : undefined, + logId: projectLogId, + projectId, + title, + appName, + }; + + 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; + return res; + } catch (error) { + console.error("โŒ Activity log error:", error); + throw error; + } + }; + + // โœ… Final PATCH before stopping + const sendFinalPatch = useCallback(async () => { + if (!canSend) { + console.log("๐Ÿšซ Skipping final PATCH โ€” no active project log ID"); return; } - console.log(` Tracking started for project: ${projectName}`); + console.log("๐Ÿ›‘ Sending final PATCH before stopping..."); + if (lastLogIdRef.current && lastTitleRef.current && lastActiveRef.current) { + try { + await sendActivityLog( + activeProjectLogId!, + projectId!, + lastTitleRef.current, + lastActiveRef.current.owner.name || "Unknown", + false + ); + console.log("โœ… Final PATCH sent for", lastTitleRef.current); + } catch (error) { + console.error("โŒ Final PATCH failed:", error); + } + } else { + console.log("โ„น๏ธ No active log to finalize"); + } + + lastActiveRef.current = null; + lastTitleRef.current = null; + lastLogIdRef.current = null; + }, [activeProjectLogId, projectId, canSend]); + + useEffect(() => { + finalizeTrackingRef.current = sendFinalPatch; + }, [sendFinalPatch]); + + useEffect(() => { + // ๐Ÿšซ If not tracking OR missing IDs, skip everything + if (!isTracking || !canSend) { + console.log("๐ŸŸก Tracking inactive or missing IDs โ€” skipping tracker setup"); + return; + } + + console.log(`๐ŸŸข Starting active tracker for: ${projectName}`); const fetchActiveApp = async () => { try { const details = (await window.electron.getActiveAppDetails()) as WindowInfo; if (!details || !details.id) return; - const prev = lastActiveRef.current; - const hasChanged = - !prev || - prev.title !== details.title || - prev.owner.processId !== details.owner.processId; + const title = details.title; + const appName = details.owner.name; + const prevTitle = lastTitleRef.current; + const hasChanged = !prevTitle || prevTitle !== title; if (hasChanged) { - console.log("๐ŸชŸ Window changed:", details.title); - dispatch(setActiveWindow({ window: details, projectId, projectName })); + if (lastLogIdRef.current && prevTitle) { + console.log("๐Ÿ”„ Patching previous log:", prevTitle); + await sendActivityLog(activeProjectLogId!, projectId!, prevTitle, appName, false); + } + + console.log("๐Ÿ†• New title detected โ†’ creating POST:", title); + await sendActivityLog(activeProjectLogId!, projectId!, title, appName, true); + lastActiveRef.current = details; + lastTitleRef.current = title; + dispatch(setActiveWindow({ window: details, projectId, projectName })); + } else { + console.log("โธ Same window, skipping log"); } } catch (error) { - console.error("โŒ Failed to fetch active app:", error); + console.error("Failed to fetch active app:", error); } }; fetchActiveApp(); - const intervalId = setInterval(fetchActiveApp, 5000); + intervalIdRef.current = setInterval(fetchActiveApp, 5000); return () => { - console.log("๐Ÿ›‘ Stopping active window tracker"); - clearInterval(intervalId); + console.log("๐Ÿงน Cleaning up tracker interval"); + if (intervalIdRef.current) { + clearInterval(intervalIdRef.current); + intervalIdRef.current = null; + } }; - }, [isTracking, dispatch, projectId, projectName]); + }, [isTracking, activeProjectLogId]); + + return { + finalizeTracking: finalizeTrackingRef.current, + }; }; diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index 563b546..c8cb145 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -27,18 +27,23 @@ import { ChevronUp, } from "lucide-react"; import { useActiveAppTracker } from "../hooks/useActiveAppTracker"; -import { useSelector } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { RootState } from "../store"; +import { setProjectsDataSlice, setProjectSummarySlice, setSelectedProjectSlice } from "../store/projectSlice"; interface User { email: string; password: string; } +// Updated Project interface to match the JSON structure interface Project { - id: number; + projectId: string; projectName: string; - clientName: string; + activityStatus: "ACTIVE" | "AWAY"; + totalWorkTime: string; + totalWorkMs: number; + totalLogs: number; } interface ProjectTrackingData { @@ -49,12 +54,12 @@ interface ProjectTrackingData { isPaused: boolean; pauseStartTime: Date | null; totalPausedTime: number; - accumulatedTime: number; // NEW: Stores time from previous sessions + accumulatedTime: number; } -interface TrackingState { +export interface TrackingState { selectedProject: Project | null; - projectTrackingMap: Map; + projectTrackingMap: Map; isTracking: boolean; isPaused: boolean; elapsedTime: number; @@ -63,7 +68,7 @@ interface TrackingState { handleStartTracking: (project: Project) => void; handlePauseResume: () => void; handleStopTracking: () => void; - handleProjectSelect: (id: number) => void; + handleProjectSelect: (projectId: string) => void; projectsData: Project[]; } @@ -73,16 +78,24 @@ interface DashboardLayoutProps { } const DashboardLayout: React.FC = ({ setUser }) => { - const [isSidebarOpen, setSidebarOpen] = useState(true); const [isMobile, setIsMobile] = useState(window.innerWidth < 768); const navigate = useNavigate(); const location = useLocation(); + const dispatch = useDispatch(); const { windowLogs } = useSelector((state: RootState) => state.window); console.log("๐Ÿงพ windowLogs in component:", windowLogs); + const menuItems = [ + { label: "Dashboard", path: "/dashboard", icon: , exact: true }, + // { label: "Projects", path: "/dashboard/projects", icon: }, + { label: "Timesheet", path: "/dashboard/timesheet", icon: }, + { label: "PTO", path: "/dashboard/leave-adjustments", icon: }, + { label: "Settings", path: "/dashboard/settings", icon: }, + { label: "Help & Support", path: "/dashboard/help-support", icon: }, + ]; // Tracking states const [selectedProject, setSelectedProject] = useState(null); @@ -92,32 +105,114 @@ const DashboardLayout: React.FC = ({ setUser }) => { const [startTime, setStartTime] = useState(null); const [endTime, setEndTime] = useState(null); const timerRef = useRef | null>(null); + const [projectSummary, setProjectSummary] = useState({ + totalProjects: 0, + activeProjects: 0, + awayProjects: 0, + totalWorkTime: "0h 0m 0s", + totalWorkMs: 0, + vaName: "", + vaId: "", + }); + const [activeProjectLogId, setActiveProjectLogId] = useState("") - useActiveAppTracker(isTracking, selectedProject?.id, selectedProject?.projectName); + const { finalizeTracking } = useActiveAppTracker( + isTracking, + activeProjectLogId, + selectedProject?.projectId, + selectedProject?.projectName + ); const socketRef = useRef(null); const intervalRef = useRef(null); const [isConnected, setIsConnected] = useState(false); // const [seclectedScreen, setSelectedScreen] = useState(null) - const [projectsData, setProjectsData] = useState([ - { id: 1, projectName: "CRM Redesign", clientName: "Salesforce" }, - { id: 2, projectName: "AI Assistant", clientName: "OpenAI" }, - { id: 3, projectName: "Marketing Dashboard", clientName: "Google" }, - ]); + const [projectsData, setProjectsData] = useState([]); + + // fetch the project list of VA + useEffect(() => { + const fetchProjects = async () => { + try { + console.log("๐Ÿ“ก Fetching projects..."); + const res = await window.electron.getProjectList(); + console.log(" Project list response:", res); + + if (res?.success && Array.isArray(res.data)) { + // Store project list + setProjectsData(res.data); + + // Store project summary info + setProjectSummary({ + totalProjects: res.totalProjects || 0, + activeProjects: res.activeProjects || 0, + awayProjects: res.awayProjects || 0, + totalWorkTime: res.totalWorkTime || "0h 0m 0s", + totalWorkMs: res.totalWorkMs || 0, + vaName: res.vaName || "", + vaId: res.vaId || "", + }); + + // Store data redux store + dispatch(setProjectsDataSlice(res.data)); + + dispatch( + setProjectSummarySlice({ + totalProjects: res.totalProjects || 0, + activeProjects: res.activeProjects || 0, + awayProjects: res.awayProjects || 0, + totalWorkTime: res.totalWorkTime || "0h 0m 0s", + totalWorkMs: res.totalWorkMs || 0, + vaName: res.vaName || "", + vaId: res.vaId || "", + }) + ); + + // Auto-select first project + if (res.data.length > 0 && !selectedProject) { + const firstProject = res.data[0]; + setSelectedProject(firstProject); + dispatch(setSelectedProjectSlice(firstProject)); + + // Initialize tracking map for that project + setProjectTrackingMap((prev) => { + const updated = new Map(prev); + if (!updated.has(firstProject.projectId)) { + updated.set(firstProject.projectId, { + elapsedTime: 0, + startTime: null, + endTime: null, + isTracking: false, + isPaused: false, + pauseStartTime: null, + totalPausedTime: 0, + accumulatedTime: 0, + }); + } + return updated; + }); + } + } else { + console.warn("โš ๏ธ Unexpected response:", res); + } + } catch (err) { + console.error("โŒ Error fetching projects:", err); + } + }; + + fetchProjects(); + }, []); const [projectTrackingMap, setProjectTrackingMap] = useState< - Map + Map >(new Map()); const handleLogout = async () => { try { - const res = await window.electron.logout() - if (res && res.success) { - localStorage.removeItem("user"); - setUser(null); - navigate("/login"); - } + const res = await window.electron.logout(); + localStorage.removeItem("user"); + setUser(null); + navigate("/login"); } catch (error) { console.error("Logout error:", error); } @@ -194,20 +289,10 @@ const DashboardLayout: React.FC = ({ setUser }) => { } }; - - const menuItems = [ - { label: "Dashboard", path: "/dashboard", icon: , exact: true }, - { label: "Projects", path: "/dashboard/projects", icon: }, - { label: "PTO", path: "/dashboard/leave-adjustments", icon: }, - { label: "Timesheet", path: "/dashboard/timesheet", icon: }, - { label: "Settings", path: "/dashboard/settings", icon: }, - { 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); + const projectData = projectTrackingMap.get(selectedProject.projectId); if (projectData) { setIsTracking(projectData.isTracking); setIsPaused(projectData.isPaused); @@ -218,13 +303,13 @@ const DashboardLayout: React.FC = ({ setUser }) => { } }, [selectedProject, projectTrackingMap]); - // Timer effect - calculates elapsed time based on timestamps + accumulated time + // 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); + const current = updated.get(selectedProject.projectId); if (current && current.isTracking && !current.isPaused && current.startTime) { const now = Date.now(); @@ -236,9 +321,8 @@ const DashboardLayout: React.FC = ({ setUser }) => { (current.accumulatedTime || 0) + currentSessionTime - totalPausedTime; current.elapsedTime = calculatedElapsed; - updated.set(selectedProject.id, { ...current }); + updated.set(selectedProject.projectId, { ...current }); - // Update global elapsedTime in sync setElapsedTime(calculatedElapsed); } return updated; @@ -259,18 +343,19 @@ const DashboardLayout: React.FC = ({ setUser }) => { }; }, [isTracking, isPaused, selectedProject]); - // Start tracking - const handleStartTracking = (project: Project) => { + // Start tracking + const handleStartTracking = async (project: Project) => { if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; } const now = new Date(); - window.electron.toggleCloseBtn(false) + window.electron.toggleCloseBtn(false); + setProjectTrackingMap((prev) => { const updated = new Map(prev); - const current = updated.get(project.id) || { + const current = updated.get(project.projectId) || { elapsedTime: 0, startTime: null, endTime: null, @@ -281,29 +366,42 @@ const DashboardLayout: React.FC = ({ setUser }) => { accumulatedTime: 0, }; - // 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); + current.totalPausedTime = 0; + updated.set(project.projectId, current); - // Update global tracking states setIsTracking(true); setIsPaused(false); setStartTime(now); - setElapsedTime(current.accumulatedTime); // Start from accumulated time + setElapsedTime(current.accumulatedTime); return updated; }); + + try { + const payload = { + vaId: projectSummary.vaId, + projectId: project.projectId, + type: "LOGIN", + }; + + // console.log("๐Ÿ“ก Sending start tracking payload:", payload); + const res = await window.electron.startProjectTracking(payload); + console.log(" Synced start tracking:", res); + setActiveProjectLogId(res?.logId) + } catch (error) { + console.error(" Failed to sync project start:", error); + } }; - // Pause/Resume tracking with pause duration tracking + // Pause/Resume tracking with pause duration tracking const handlePauseResume = () => { if (!selectedProject) return; - const currentProjectData = projectTrackingMap.get(selectedProject.id); + const currentProjectData = projectTrackingMap.get(selectedProject.projectId); if (!currentProjectData || !currentProjectData.isTracking) return; const now = new Date(); @@ -311,15 +409,13 @@ const DashboardLayout: React.FC = ({ setUser }) => { setProjectTrackingMap((prev) => { const updated = new Map(prev); - const current = updated.get(selectedProject.id); + const current = updated.get(selectedProject.projectId); if (!current) return updated; if (newPausedState) { - // Starting pause current.isPaused = true; current.pauseStartTime = now; } else { - // Resuming from pause current.isPaused = false; if (current.pauseStartTime) { const pauseDuration = Math.floor( @@ -330,46 +426,77 @@ const DashboardLayout: React.FC = ({ setUser }) => { } } - updated.set(selectedProject.id, { ...current }); + updated.set(selectedProject.projectId, { ...current }); setIsPaused(newPausedState); return updated; }); }; - // Stop tracking - PRESERVES elapsed time - const handleStopTracking = () => { + // Stop tracking - PRESERVES elapsed time + const handleStopTracking = async () => { if (!selectedProject) return; + console.log("๐Ÿ›‘ Stop tracking initiated..."); + + // STEP 1: Send final PATCH first + if (finalizeTracking) { + try { + await finalizeTracking(); + console.log(" Final tracking data sent"); + } catch (error) { + console.error("โŒ Error sending final tracking data:", error); + } + } + + // STEP 2: Clear timer if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; } + // STEP 3: Send LOGOUT request + try { + const payload = { + vaId: projectSummary.vaId, + projectId: selectedProject.projectId, + type: "LOGOUT", + }; + + console.log("๐Ÿ“ก Sending stop tracking payload:", payload); + const res = await window.electron.stopProjectTracking(payload); + console.log(" Synced stop tracking:", res); + } catch (error) { + console.error("โŒ Failed to sync project stop:", error); + } + + // STEP 4: Update UI and state const now = new Date(); - window.electron.toggleCloseBtn(true) + await window.electron.toggleCloseBtn(true); + await window.electron.resizeWindow(500, 600); + toggleWindowSize.current = true; + setProjectTrackingMap((prev) => { const updated = new Map(prev); - const current = updated.get(selectedProject.id); + const current = updated.get(selectedProject.projectId); if (!current) return updated; 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 }); + current.startTime = null; + updated.set(selectedProject.projectId, { ...current }); - // Update global states - keep elapsed time setIsTracking(false); setIsPaused(false); setEndTime(now); - setElapsedTime(current.elapsedTime); // Keep the elapsed time + setElapsedTime(current.elapsedTime); return updated; }); - console.log("Tracking stopped. Time preserved:", elapsedTime); + console.log(" Tracking stopped successfully. Time preserved:", elapsedTime); }; // Cleanup timer on unmount @@ -381,15 +508,15 @@ const DashboardLayout: React.FC = ({ setUser }) => { }; }, []); - // Handle project selection - const handleProjectSelect = (id: number) => { + // Handle project selection + const handleProjectSelect = (projectId: string) => { if (isTracking) return; - const project = projectsData.find((p) => p.id === id); + const project = projectsData.find((p) => p.projectId === projectId); if (project) { setSelectedProject(project); - const projectData = projectTrackingMap.get(id); + const projectData = projectTrackingMap.get(projectId); if (projectData) { setElapsedTime(projectData.elapsedTime); setStartTime(projectData.startTime); @@ -428,14 +555,15 @@ const DashboardLayout: React.FC = ({ setUser }) => { return `${hrs}h ${mins}m ${secs}s`; }; - const toggleWindowSize = useRef(true); // true -> Open, false -> Close + const toggleWindowSize = useRef(true); return ( <> {/* Header */} -
- {/* Left Section */} - {!isConnected ? ( + {isTracking && +
+ {/* Left Section */} + {!isConnected ? ( )}
- {selectedProject ? ( - isTracking ? ( - + {selectedProject ? ( + isTracking ? ( + + ) : ( + + ) ) : ( - ) - ) : ( - - )} + )} - {selectedProject ? ( - - {selectedProject.projectName} - - ) : ( - - No Project Selected - - )} -
- - {/* Right Section */} -
- {/* Timer */} -
- {formatTime(elapsedTime || 0)} + {selectedProject ? ( + + {selectedProject.projectName} + + ) : ( + + No Project Selected + + )}
- {/* Alert Icon */} - - - + {/* Right Section */} +
+ {/* Timer */} +
+ {formatTime(elapsedTime || 0)} +
- {/* Dropdown */} - -
-
- - {/* Layout Body */} -
- {/* Sidebar - Always visible */} -
+
+ } + + {/* Layout Body */} +
+ {/* Sidebar */} + - {/* Main Content */} + {/* Main Content */}
diff --git a/src/ui/pages/Dashboard.tsx b/src/ui/pages/Dashboard.tsx index 1c13481..a54f291 100644 --- a/src/ui/pages/Dashboard.tsx +++ b/src/ui/pages/Dashboard.tsx @@ -2,11 +2,14 @@ import React, { useEffect, useRef, useState } from "react"; import { Clock, TrendingUp, Play, Square, ProjectorIcon, Pause } from "lucide-react"; import { useOutletContext } from "react-router-dom"; -// ----- Interfaces ----- +// Updated Project interface to match JSON structure interface Project { - id: number; + projectId: string; projectName: string; - clientName: string; + activityStatus: "ACTIVE" | "AWAY"; + totalWorkTime: string; + totalWorkMs: number; + totalLogs: number; } interface ProjectTrackingData { @@ -19,7 +22,7 @@ interface ProjectTrackingData { interface TrackingState { selectedProject: Project | null; - projectTrackingMap: Map; + projectTrackingMap: Map; isTracking: boolean; isPaused: boolean; elapsedTime: number; @@ -28,11 +31,11 @@ interface TrackingState { handleStartTracking: (project: Project) => void; handlePauseResume: () => void; handleStopTracking: () => void; - handleProjectSelect: (id: number) => void; + handleProjectSelect: (projectId: string) => void; projectsData: Project[]; } -// ----- Helper ----- +// Helper function const formatTime = (seconds: number) => { const hrs = Math.floor(seconds / 3600); const mins = Math.floor((seconds % 3600) / 60); @@ -40,7 +43,7 @@ const formatTime = (seconds: number) => { return `${hrs}h ${mins}m ${secs}s`; }; -// ----- Widget ----- +// Widget Component const Widget: React.FC<{ title: string; value: string | number; @@ -71,7 +74,7 @@ const Widget: React.FC<{
); -// ----- Dashboard ----- +// Dashboard Component const Dashboard: React.FC = () => { const { selectedProject, @@ -96,25 +99,9 @@ const Dashboard: React.FC = () => { // const [runningApps, setRunningApps] = useState([]); const selectedData = selectedProject - ? projectTrackingMap.get(selectedProject.id) + ? projectTrackingMap.get(selectedProject.projectId) : null; - - // const getRunningApps = async () => { - // const res = await window.electron.getRunningApps() - // const appsList: AppsListInt[] = Array.isArray(res) ? res : []; - // setRunningApps(appsList) - // } - - // useEffect(() => { - // setInterval(() => { - // getRunningApps(); - // }, 5000) - // }, []) - - - - return (
{/* Header */} @@ -157,7 +144,7 @@ const Dashboard: React.FC = () => { {/* Widgets */} -
+ {/*
{ icon={} color="warning" /> -
+
*/} {/* Tracking Section */}
-
+ {/*
Tracking -
+
*/}
@@ -226,11 +213,18 @@ const Dashboard: React.FC = () => {
- Client:{" "} - {selectedProject.clientName} + Status:{" "} + + {selectedProject.activityStatus} +
- Status:{" "} + Tracking:{" "} { {/* Projects Table */}

Projects

-
- - - - - - - - - - - {projectsData.map((project) => { - const isCurrentlySelected = selectedProject?.id === project.id; - // โœ… FIX: Disable ALL projects when ANY project is tracking - // Only enable selection when not tracking - const isDisabled = isTracking && !isCurrentlySelected; - - return ( - - - - - +
+ {projectsData.length > 0 ? ( +
+
#ProjectClientAction
{project.id}{project.projectName}{project.clientName} - -
+ + + + + + - ); - })} - -
#NameLogs HoursAction
+ + + {projectsData.map((project, index) => { + const isCurrentlySelected = selectedProject?.projectId === project.projectId; + const isDisabled = isTracking && !isCurrentlySelected; + + return ( + + {index + 1} + {project.projectName} + {project.totalWorkTime} + + + + + ); + })} + + +
+ ) : ( +
+

No projects found.

+
+ )}
+
); diff --git a/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx b/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx index 18c1859..4a48cc9 100644 --- a/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx +++ b/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx @@ -1,10 +1,11 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import AdjustmentModal from './AdjustmentModal'; +import { RootState } from '../../store'; +import { useSelector } from 'react-redux'; interface LeaveFormData { fromDate: string; toDate: string; - description: string; } interface AdjustmentEntry { @@ -29,16 +30,18 @@ const data: LogEntry[] = [ { id: 5, date: "2025-11-05", hours: 5.5, task: "Code Review", status: "Completed" }, ]; -type TabType = 'pto' | 'sick' | 'adjustment'; +type TabType = 'create_and_adjust_timesheet' | 'sick' | 'adjustment'; const LeaveAndAdjustments: React.FC = () => { - const [activeTab, setActiveTab] = useState('pto'); + const [activeTab, setActiveTab] = useState('create_and_adjust_timesheet'); + const projects = useSelector((state: RootState) => state.projects.projectsData); + + console.log("LeaveAndAdjustments", projects) // PTO State - const [ptoLeave, setPtoLeave] = useState({ + const [ptoLeave, setPtoLeave] = useState({ fromDate: '', toDate: '', - description: '' }); const [ptoErrors, setPtoErrors] = useState>({}); @@ -46,9 +49,10 @@ const LeaveAndAdjustments: React.FC = () => { const [sickLeave, setSickLeave] = useState({ fromDate: '', toDate: '', - description: '' }); const [sickErrors, setSickErrors] = useState>({}); + const [selectedProjectId, setSelectedProjectId] = useState("") + const [selectedDate, setSelectedDate] = useState("") // Adjustment State const [selectedPeriod, setSelectedPeriod] = useState('week'); @@ -83,13 +87,6 @@ const LeaveAndAdjustments: React.FC = () => { return errors; }; - // PTO Handlers - const handlePtoChange = (field: keyof LeaveFormData, value: string) => { - setPtoLeave(prev => ({ ...prev, [field]: value })); - if (ptoErrors[field]) { - setPtoErrors(prev => ({ ...prev, [field]: undefined })); - } - }; const handlePtoSubmit = async () => { const errors = validateLeaveForm(ptoLeave); @@ -162,12 +159,57 @@ const LeaveAndAdjustments: React.FC = () => { handleCloseModal(); }; + const handleProjectSelect = (projectId: string) => { + console.log("handleProjectSelect", projectId) + setSelectedProjectId(projectId) + } + + const handleSelectedDate = (value: string) => { + // Ensure value stays as "YYYY-MM-DD" + setSelectedDate(value); +}; + + + + + + const fetchLogs = async () => { + const res = await window.electron.getProjectLogs( + selectedProjectId, + selectedDate || undefined, + selectedDate || undefined, + ); + + if (res.success) { + console.log("โœ… Logs: fetchLogs", res); + + // Safely extract time if from/to exist + const fromTime = res.from ? new Date(res.from).toISOString().slice(11, 16) : ""; + const toTime = res.to ? new Date(res.to).toISOString().slice(11, 16) : ""; + + setPtoLeave({ + fromDate: fromTime, + toDate: toTime, + }); + } else { + console.warn("โš ๏ธ Failed to fetch project logs"); + } + }; + + + useEffect(() => { + if (selectedProjectId && selectedDate) { + fetchLogs() + } + }, [selectedProjectId, selectedDate]) + + return ( -
+
{/* Header */}
-

Leave & Adjustments

+

Adjust Timesheet

{/* Main Container */} @@ -175,8 +217,8 @@ const LeaveAndAdjustments: React.FC = () => { {/* Tabs Navigation */}
- - + */}
{/* Tab Content */} -
+
{/* PTO Tab */} - {activeTab === 'pto' && ( + {activeTab === 'create_and_adjust_timesheet' && (
-
+ {/*

Paid Time Off Request

-
+
*/}
-
+
+ + + + {ptoErrors.fromDate && ( + + )} +
+ +
+ handlePtoChange('fromDate', e.target.value)} - className={`input input-bordered w-full focus:input-primary transition-all ${ptoErrors.fromDate ? 'input-error' : ''}`} + value={selectedDate || ""} + onChange={(e) => handleSelectedDate(e.target.value)} + className="input input-bordered input-sm w-full focus:input-primary transition-all" + /> +
+ + + +
+ + handlePtoChange('fromDate', e.target.value)} + className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ptoErrors.fromDate ? 'input-error' : ''}`} /> {ptoErrors.fromDate && (