import { VAModel, LoggedModel, ClientModel, ProjectModel } from "../../../models/vaModel.js"; import jwt from "jsonwebtoken"; // Getting client dashboard data - all VAs, projects, and active VAs async function client_dashboard(req, res) { try { const accessToken = req.headers.token; if (!accessToken) { return res.status(401).json({ success: false, message: "Missing token" }); } const decoded = jwt.verify(accessToken, process.env.JWT_SECRET_ACCESS_TOKEN); if (!decoded || !decoded.email) { return res.status(401).json({ success: false, message: "Invalid token" }); } const clientEmail = decoded.email; const clientUser = await ClientModel.findOne({ "userDetails.email": clientEmail }); if (!clientUser) { return res.status(404).json({ success: false, message: "Client not found" }); } const clientID = clientUser._id; // Get all VA IDs and Project IDs assigned to this client const clientVaIds = clientUser.vaIds || []; const clientProjectIds = clientUser.projectIds || []; // Get all VAs assigned to this client (with full details) const allAssignedVAs = await VAModel.find({ _id: { $in: clientVaIds } }) .select('userDetails.firstName userDetails.lastName userDetails.email userDetails.phone status createdAt clientIds projectIds') .lean() .exec(); // Get All Projects of client const clientProjects = await ProjectModel.find({ _id: { $in: clientProjectIds } }) .select('name hourlyRate totalLoggedHours vaAssigned dedicatedHours createdAt clientId') .lean() .exec(); // Create project map for easy lookup const projectMap = clientProjects.reduce((acc, project) => { acc[project._id.toString()] = project; return acc; }, {}); // Find the latest log entry for each VA that belongs to this client and their projects const agg = await LoggedModel.aggregate([ { $match: { vaId: { $in: clientVaIds }, projectId: { $in: clientProjectIds } } }, { $sort: { time: -1 } }, { $group: { _id: "$vaId", lastLog: { $first: "$$ROOT" }, totalLogins: { $sum: { $cond: [{ $eq: ["$loggedType", "LOGIN"] }, 1, 0] } }, lastActivity: { $first: "$time" } } }, { $match: { "lastLog.loggedType": "LOGIN" } } ]); const activeVaIds = agg.map((a) => a._id).filter(Boolean); // Create mapping for last log and activity data const activityByVa = agg.reduce((acc, a) => { acc[String(a._id)] = { lastLog: a.lastLog, totalLogins: a.totalLogins, lastActivity: a.lastActivity }; return acc; }, {}); // Calculate total worked time for each VA const totalWorkedTimeByVa = {}; // Get all logs for client's VAs to calculate total worked time const allLogs = await LoggedModel.find({ vaId: { $in: clientVaIds }, projectId: { $in: clientProjectIds } }) .sort({ time: 1 }) .lean(); // Group logs by VA const logsByVa = {}; allLogs.forEach(log => { const vaId = log.vaId.toString(); if (!logsByVa[vaId]) { logsByVa[vaId] = []; } logsByVa[vaId].push(log); }); // Calculate total worked time for each VA for (const [vaId, logs] of Object.entries(logsByVa)) { let totalWorkSeconds = 0; // Group logs by date (YYYY-MM-DD format) const logsByDate = {}; logs.forEach(log => { const date = new Date(log.time).toISOString().split('T')[0]; if (!logsByDate[date]) { logsByDate[date] = []; } logsByDate[date].push(log); }); // Process each day separately for (const [date, dayLogs] of Object.entries(logsByDate)) { // Check if this day has any adjustment logs const hasAdjustmentLogs = dayLogs.some(log => log.isAdjustment === true); let dayWorkSeconds = 0; let lastLogin = null; // If day has adjustment logs, only process adjustment logs // If no adjustment logs, process all logs for that day const logsToProcess = hasAdjustmentLogs ? dayLogs.filter(log => log.isAdjustment === true) : dayLogs; // Sort logs by time for proper pairing logsToProcess.sort((a, b) => new Date(a.time) - new Date(b.time)); // Calculate work duration for this day for (const log of logsToProcess) { const time = new Date(log.time); if (log.loggedType === "LOGIN") { lastLogin = time; } else if (log.loggedType === "LOGOUT" && lastLogin) { const diffSec = (time - lastLogin) / 1000; dayWorkSeconds += diffSec; lastLogin = null; } } totalWorkSeconds += dayWorkSeconds; } totalWorkedTimeByVa[vaId] = { totalSeconds: totalWorkSeconds, formatted: formatTime(totalWorkSeconds) }; } // NEW: Calculate project-wise work time for each VA const projectWorkTimeByVa = {}; for (const [vaId, logs] of Object.entries(logsByVa)) { const projectWorkTime = {}; // Group logs by project const logsByProject = {}; logs.forEach(log => { const projectId = log.projectId?.toString(); if (projectId) { if (!logsByProject[projectId]) { logsByProject[projectId] = []; } logsByProject[projectId].push(log); } }); // Calculate work time per project for (const [projectId, projectLogs] of Object.entries(logsByProject)) { let projectWorkSeconds = 0; // Group logs by date const logsByDate = {}; projectLogs.forEach(log => { const date = new Date(log.time).toISOString().split('T')[0]; if (!logsByDate[date]) { logsByDate[date] = []; } logsByDate[date].push(log); }); // Process each day separately for (const [date, dayLogs] of Object.entries(logsByDate)) { const hasAdjustmentLogs = dayLogs.some(log => log.isAdjustment === true); let dayWorkSeconds = 0; let lastLogin = null; const logsToProcess = hasAdjustmentLogs ? dayLogs.filter(log => log.isAdjustment === true) : dayLogs; logsToProcess.sort((a, b) => new Date(a.time) - new Date(b.time)); for (const log of logsToProcess) { const time = new Date(log.time); if (log.loggedType === "LOGIN") { lastLogin = time; } else if (log.loggedType === "LOGOUT" && lastLogin) { const diffSec = (time - lastLogin) / 1000; dayWorkSeconds += diffSec; lastLogin = null; } } projectWorkSeconds += dayWorkSeconds; } projectWorkTime[projectId] = { totalSeconds: projectWorkSeconds, formatted: formatTime(projectWorkSeconds), projectName: projectMap[projectId]?.name || 'Unknown Project' }; } projectWorkTimeByVa[vaId] = projectWorkTime; } // Calculate session duration for currently active VAs const currentTime = new Date(); // Format all assigned VAs - Only include data needed for frontend const allVAsFormatted = allAssignedVAs.map((v) => { const activity = activityByVa[String(v._id)] || {}; const workedTime = totalWorkedTimeByVa[String(v._id)] || { totalSeconds: 0, formatted: '00:00:00' }; const lastLog = activity.lastLog; const projectWorkTime = projectWorkTimeByVa[String(v._id)] || {}; // Calculate current session duration if still logged in let currentSessionDuration = null; let isCurrentlyActive = false; let currentProjectDetails = null; if (lastLog && lastLog.loggedType === "LOGIN") { const loginTime = new Date(lastLog.time); currentSessionDuration = Math.floor((currentTime - loginTime) / 1000); isCurrentlyActive = true; // Consider VA inactive if last login was more than 24 hours ago const hoursSinceLastActivity = (currentTime - loginTime) / (1000 * 60 * 60); if (hoursSinceLastActivity > 24) { isCurrentlyActive = false; } // Get current project details if (lastLog.projectId && projectMap[lastLog.projectId.toString()]) { currentProjectDetails = { id: lastLog.projectId, name: projectMap[lastLog.projectId.toString()].name }; } } // Combine first and last name const fullName = `${v.userDetails?.firstName || ''} ${v.userDetails?.lastName || ''}`.trim() || 'Unknown'; // Filter: Only include projects that belong to this client const clientProjectsAssigned = (v.projectIds || []).filter(projectId => clientProjectIds.some(clientProjectId => clientProjectId.toString() === projectId.toString() ) ); // Format project work time for popup const projectsWithWorkTime = clientProjectsAssigned.map(projectId => { const projectIdStr = projectId.toString(); const workData = projectWorkTime[projectIdStr] || { totalSeconds: 0, formatted: '00:00:00' }; const project = projectMap[projectIdStr]; return { id: projectId, name: project?.name || 'Unknown Project', totalWorkedTime: workData.formatted, totalWorkedSeconds: workData.totalSeconds, isCurrentlyWorking: currentProjectDetails?.id?.toString() === projectIdStr, hourlyRate: project?.hourlyRate || 0 }; }); return { id: v._id, name: fullName, email: v.userDetails?.email, phone: v.userDetails?.phone, status: v.status || 'active', isCurrentlyActive: isCurrentlyActive, lastLoginTime: lastLog?.time || null, currentSessionDuration: currentSessionDuration, totalWorkedTime: workedTime.formatted, totalWorkedSeconds: workedTime.totalSeconds, lastProject: currentProjectDetails, lastProjectId: lastLog?.projectId || null, projectsAssigned: clientProjectsAssigned.length, // Detailed project information for popup projects: projectsWithWorkTime, vaSince: v.createdAt }; }); // Calculate dashboard statistics const totalHoursLogged = clientProjects.reduce((sum, p) => sum + (p.totalLoggedHours || 0), 0); const totalDedicatedHours = clientProjects.reduce((sum, p) => sum + (p.dedicatedHours || 0), 0); const currentlyActiveVAs = allVAsFormatted.filter(va => va.isCurrentlyActive); const inactiveVAs = allVAsFormatted.filter(va => !va.isCurrentlyActive); // Calculate total worked time across all VAs const totalWorkedTimeAllVAs = allVAsFormatted.reduce((total, va) => total + (va.totalWorkedSeconds || 0), 0); return res.status(200).json({ success: true, data: { // Only send data that frontend actually uses allVAs: allVAsFormatted }, summary: { totalVAs: allAssignedVAs.length, activeVAs: currentlyActiveVAs.length, inactiveVAs: inactiveVAs.length, totalProjects: clientProjects.length, totalHoursLogged: totalHoursLogged, totalDedicatedHours: totalDedicatedHours, totalWorkedTime: formatTime(totalWorkedTimeAllVAs), utilizationRate: totalDedicatedHours > 0 ? ((totalHoursLogged / totalDedicatedHours) * 100).toFixed(2) + '%' : '0%' } }); } catch (error) { console.error("Client dashboard error:", error); return res.status(500).json({ success: false, message: "Internal Server Error", error: error.message }); } } // Helper function to format time function formatTime(seconds) { const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.floor(seconds % 60); return `${h.toString().padStart(2, "0")}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`; } export { client_dashboard };