import React, { useState, useEffect, useRef } from "react"; import io, { Socket } from 'socket.io-client'; import { Outlet, NavLink, useNavigate, useLocation, } from "react-router-dom"; import { Bell, Menu, LogOut, User, Settings, Home, FolderKanban, X, CalendarDays, SettingsIcon, TimerIcon, HelpCircleIcon, Pause, ShieldBan, ChevronDown, Play, ChevronUp, } from "lucide-react"; 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; } // Updated Project interface to match the JSON structure interface Project { projectId: string; projectName: string; activityStatus: "ACTIVE" | "AWAY"; totalWorkTime: string; totalWorkMs: number; totalLogs: number; } interface ProjectTrackingData { elapsedTime: number; startTime: Date | null; endTime: Date | null; isTracking: boolean; isPaused: boolean; pauseStartTime: Date | null; totalPausedTime: number; accumulatedTime: number; } export interface TrackingState { selectedProject: Project | null; projectTrackingMap: Map; isTracking: boolean; isPaused: boolean; elapsedTime: number; startTime: Date | null; endTime: Date | null; handleStartTracking: (project: Project) => void; handlePauseResume: () => void; handleStopTracking: () => void; handleProjectSelect: (projectId: string) => void; projectsData: Project[]; } const DashboardLayout = () => { 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); const [isTracking, setIsTracking] = useState(false); const [isPaused, setIsPaused] = useState(false); const [elapsedTime, setElapsedTime] = useState(0); 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("") const { finalizeTracking } = useActiveAppTracker( isTracking, activeProjectLogId, selectedProject?.projectId ?? null, selectedProject?.projectName ?? null ); const socketRef = useRef(null); const intervalRef = useRef(null); const [isConnected, setIsConnected] = useState(false); // const [seclectedScreen, setSelectedScreen] = useState(null) 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)); // 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:initialElapsedTime, startTime: null, endTime: null, isTracking: false, isPaused: false, pauseStartTime: null, totalPausedTime: 0, accumulatedTime: initialElapsedTime, }); } return updated; }); // Set the initial elapsed time in the component state setElapsedTime(initialElapsedTime); } } else { console.warn("โš ๏ธ Unexpected response:", res); } } catch (err) { console.error("โŒ Error fetching projects:", err); } }; fetchProjects(); }, []); const [projectTrackingMap, setProjectTrackingMap] = useState< Map >(new Map()); const handleLogout = async () => { try { const res = await window.electron.logout(); if (res) { dispatch(clearUser()); // โœ… Removes from Redux + localStorage navigate("/login"); } } 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"); } }; // 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"); } }; // Sync global states with selected project tracking data useEffect(() => { if (selectedProject) { const projectData = projectTrackingMap.get(selectedProject.projectId); if (projectData) { setIsTracking(projectData.isTracking); setIsPaused(projectData.isPaused); setElapsedTime(projectData.elapsedTime); setStartTime(projectData.startTime); setEndTime(projectData.endTime); } } }, [selectedProject, projectTrackingMap]); // 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.projectId); 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; const calculatedElapsed = (current.accumulatedTime || 0) + currentSessionTime - totalPausedTime; current.elapsedTime = calculatedElapsed; updated.set(selectedProject.projectId, { ...current }); setElapsedTime(calculatedElapsed); } return updated; }); }, 1000); } else { if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; } } return () => { if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; } }; }, [isTracking, isPaused, selectedProject]); // Start tracking const handleStartTracking = async (project: Project) => { if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; } const now = new Date(); window.electron.toggleCloseBtn(false); setProjectTrackingMap((prev) => { const updated = new Map(prev); const current = updated.get(project.projectId) || { elapsedTime: 0, startTime: null, endTime: null, isTracking: false, isPaused: false, pauseStartTime: null, totalPausedTime: 0, accumulatedTime: 0, }; current.startTime = now; current.isTracking = true; current.isPaused = false; current.pauseStartTime = null; current.totalPausedTime = 0; updated.set(project.projectId, current); setIsTracking(true); setIsPaused(false); setStartTime(now); 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 const handlePauseResume = () => { if (!selectedProject) return; const currentProjectData = projectTrackingMap.get(selectedProject.projectId); if (!currentProjectData || !currentProjectData.isTracking) return; const now = new Date(); const newPausedState = !currentProjectData.isPaused; setProjectTrackingMap((prev) => { const updated = new Map(prev); const current = updated.get(selectedProject.projectId); if (!current) return updated; if (newPausedState) { current.isPaused = true; current.pauseStartTime = now; } else { 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.projectId, { ...current }); setIsPaused(newPausedState); return updated; }); }; // 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(); 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.projectId); if (!current) return updated; current.accumulatedTime = current.elapsedTime; current.isTracking = false; current.isPaused = false; current.endTime = now; current.startTime = null; updated.set(selectedProject.projectId, { ...current }); setIsTracking(false); setIsPaused(false); setEndTime(now); setElapsedTime(current.elapsedTime); return updated; }); console.log(" Tracking stopped successfully. Time preserved:", elapsedTime); }; // Cleanup timer on unmount useEffect(() => { return () => { if (timerRef.current) { clearInterval(timerRef.current); } }; }, []); // Handle project selection const handleProjectSelect = (projectId: string) => { if (isTracking) return; // prevent switching while tracking const project = projectsData.find((p) => p.projectId === projectId); if (!project) return; 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, projectTrackingMap, isTracking, isPaused, elapsedTime, startTime, endTime, handleStartTracking, handlePauseResume, handleStopTracking, projectsData, 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`; }; const toggleWindowSize = useRef(true); return ( <> {/* Header */} {isTracking &&
{/* Left Section */} {!isConnected ? ( ) : ( )}
{selectedProject ? ( isTracking ? ( ) : ( ) ) : ( )} {selectedProject ? ( {selectedProject.projectName} ) : ( No Project Selected )}
{/* Right Section */}
{/* Timer */}
{formatTime(elapsedTime || 0)}
{/* Alert Icon */} {/* Dropdown */}
} {/* Layout Body */}
{/* Sidebar */} {/* Main Content */}
); }; export default DashboardLayout;