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" }, projectsWorked: { $addToSet: "$projectId" }, // Get all login logs for this VA allLogs: { $push: "$$ROOT" } } }, { $match: { "lastLog.loggedType": "LOGIN" } } ]); const activeVaIds = agg.map((a) => a._id).filter(Boolean); // Get active VA details const activeVAs = await VAModel.find({ _id: { $in: activeVaIds }, clientIds: clientID }) .select('userDetails.firstName userDetails.lastName userDetails.email userDetails.phone status createdAt projectIds') .lean() .exec(); // 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, projectsWorked: a.projectsWorked || [] }; 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 using the same logic as va_log_work_report_callback 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]; // YYYY-MM-DD 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; let lastProjectId = 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; lastProjectId = log.projectId?.toString() || null; } else if (log.loggedType === "LOGOUT" && lastLogin) { const diffSec = (time - lastLogin) / 1000; dayWorkSeconds += diffSec; // reset after each pair lastLogin = null; lastProjectId = null; } } totalWorkSeconds += dayWorkSeconds; } totalWorkedTimeByVa[vaId] = { totalSeconds: totalWorkSeconds, formatted: formatTime(totalWorkSeconds) }; } // Calculate session duration for currently active VAs const currentTime = new Date(); // Format all assigned VAs 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; // Calculate current session duration if still logged in let currentSessionDuration = null; let isCurrentlyActive = false; let currentSessionFormatted = null; let currentProjectDetails = null; if (lastLog && lastLog.loggedType === "LOGIN") { const loginTime = new Date(lastLog.time); currentSessionDuration = Math.floor((currentTime - loginTime) / 1000); currentSessionFormatted = formatTime(currentSessionDuration); 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'; return { id: v._id, name: fullName, email: v.userDetails?.email, phone: v.userDetails?.phone, status: v.status || 'active', isCurrentlyActive: isCurrentlyActive, lastLoginTime: lastLog?.time || null, lastActivity: activity.lastActivity, currentSessionDuration: currentSessionDuration, currentSessionFormatted: currentSessionFormatted, totalWorkedTime: workedTime.formatted, totalWorkedSeconds: workedTime.totalSeconds, totalLogins: activity.totalLogins || 0, lastProject: currentProjectDetails, lastProjectId: lastLog?.projectId || null, projectsWorked: activity.projectsWorked || [], projectsAssigned: v.projectIds?.length || 0, vaSince: v.createdAt }; }); // Format active VAs (currently working) const activeVAsFormatted = activeVAs.map((v) => { const activity = activityByVa[String(v._id)] || {}; const workedTime = totalWorkedTimeByVa[String(v._id)] || { totalSeconds: 0, formatted: '00:00:00' }; const lastLog = activity.lastLog; let currentSessionDuration = null; let currentSessionFormatted = null; let currentProjectDetails = null; if (lastLog && lastLog.loggedType === "LOGIN") { const loginTime = new Date(lastLog.time); currentSessionDuration = Math.floor((currentTime - loginTime) / 1000); currentSessionFormatted = formatTime(currentSessionDuration); // 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'; return { id: v._id, name: fullName, email: v.userDetails?.email, phone: v.userDetails?.phone, status: v.status || 'active', lastLoginTime: lastLog?.time || null, currentSessionDuration: currentSessionDuration, currentSessionFormatted: currentSessionFormatted, totalWorkedTime: workedTime.formatted, totalWorkedSeconds: workedTime.totalSeconds, totalLogins: activity.totalLogins || 0, currentProject: currentProjectDetails, currentProjectId: lastLog?.projectId || null, projectsAssigned: v.projectIds?.length || 0, vaSince: v.createdAt }; }); // Format projects with additional data const projectsFormatted = clientProjects.map((p) => { // Find VAs currently working on this project const activeVAsOnProject = allVAsFormatted.filter(va => va.isCurrentlyActive && va.lastProjectId && va.lastProjectId.toString() === p._id.toString() ); // Find all VAs assigned to this project const assignedVAs = allAssignedVAs.filter(va => va.projectIds && va.projectIds.some(projectId => projectId.toString() === p._id.toString() ) ); return { id: p._id, name: p.name, hourlyRate: p.hourlyRate, totalLoggedHours: p.totalLoggedHours || 0, dedicatedHours: p.dedicatedHours || 0, vaAssigned: assignedVAs.length, vaCurrentlyWorking: activeVAsOnProject.length, activeVAs: activeVAsOnProject.map(va => ({ id: va.id, name: va.name, currentSession: va.currentSessionFormatted, lastLoginTime: va.lastLoginTime })), createdAt: p.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); // Calculate projects with active workers const projectsWithActiveWorkers = projectsFormatted.filter(p => p.vaCurrentlyWorking > 0).length; return res.status(200).json({ success: true, data: { // All assigned VAs with their status allVAs: { active: currentlyActiveVAs, inactive: inactiveVAs, total: allVAsFormatted }, // Currently working VAs (active session) activeVAs: activeVAsFormatted, // All client projects projects: projectsFormatted }, summary: { totalVAs: allAssignedVAs.length, activeVAs: currentlyActiveVAs.length, inactiveVAs: inactiveVAs.length, totalProjects: clientProjects.length, projectsWithActiveWorkers: projectsWithActiveWorkers, totalHoursLogged: totalHoursLogged, totalDedicatedHours: totalDedicatedHours, totalWorkedTime: formatTime(totalWorkedTimeAllVAs), utilizationRate: totalDedicatedHours > 0 ? ((totalHoursLogged / totalDedicatedHours) * 100).toFixed(2) + '%' : '0%', lastUpdated: currentTime.toISOString() }, counts: { vas: { total: allAssignedVAs.length, active: currentlyActiveVAs.length, inactive: inactiveVAs.length }, projects: clientProjects.length, activeSessions: activeVAsFormatted.length, projectsWithActivity: projectsWithActiveWorkers } }); } 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 (same as in va_log_work_report_callback) 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 };