176 lines
6.3 KiB
JavaScript
176 lines
6.3 KiB
JavaScript
import { ClientModel, ProjectModel, VAModel, LoggedModel } from "../../../models/vaModel.js";
|
|
import jwt from "jsonwebtoken";
|
|
import getDayNumber from "../../../utils/getDayNumber.js";
|
|
|
|
function formatHoursToReadable(hours) {
|
|
const wholeHours = Math.floor(hours);
|
|
const decimalMinutes = (hours - wholeHours) * 60;
|
|
const wholeMinutes = Math.round(decimalMinutes);
|
|
|
|
if (wholeHours > 0 && wholeMinutes > 0) {
|
|
return `${wholeHours}h ${wholeMinutes}m`;
|
|
} else if (wholeHours > 0) {
|
|
return `${wholeHours}h`;
|
|
} else {
|
|
return `${wholeMinutes}m`;
|
|
}
|
|
}
|
|
|
|
// Getting client dashboard data - projects with assigned VA details
|
|
async function client_dashboard_projects(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;
|
|
|
|
// STEP 1: Get client ID from token
|
|
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;
|
|
|
|
// STEP 2: Find all projects of this client
|
|
const clientProjects = await ProjectModel.find({
|
|
clientId: clientId
|
|
})
|
|
.select('name hourlyRate vaAssigned dedicatedHours status')
|
|
.lean()
|
|
.exec();
|
|
|
|
// Get all VA IDs from all projects for activity check
|
|
const allProjectVaIds = [];
|
|
clientProjects.forEach(project => {
|
|
if (project.vaAssigned && project.vaAssigned.length > 0) {
|
|
allProjectVaIds.push(...project.vaAssigned);
|
|
}
|
|
});
|
|
|
|
// STEP 3: Get all VAs assigned to these projects
|
|
const allAssignedVAs = await VAModel.find({
|
|
_id: { $in: allProjectVaIds.vaId }
|
|
})
|
|
.select('userDetails.firstName userDetails.lastName userDetails.email status')
|
|
.lean()
|
|
.exec();
|
|
|
|
// Create a map for quick VA lookup
|
|
const vaMap = allAssignedVAs.reduce((acc, va) => {
|
|
acc[va._id.toString()] = va;
|
|
return acc;
|
|
}, {});
|
|
|
|
// STEP 4: Check VA activity per project
|
|
const currentTime = new Date();
|
|
|
|
// Format projects with assigned VA details
|
|
const projectsFormatted = await Promise.all(clientProjects.map(async (project) => {
|
|
const assignedVAIds = project.vaAssigned || [];
|
|
|
|
// Get VA details and check activity for each assigned VA
|
|
const assignedVADetails = await Promise.all(assignedVAIds.map(async (vaId) => {
|
|
const va = vaMap[vaId.toString()];
|
|
if (!va) return null;
|
|
|
|
// Check if this VA is currently active on THIS specific project
|
|
const latestLog = await LoggedModel.findOne({
|
|
vaId: vaId,
|
|
projectId: project._id,
|
|
loggedType: "LOGIN"
|
|
}).sort({ time: -1 }).lean();
|
|
|
|
let isActive = false;
|
|
if (latestLog) {
|
|
const loginTime = new Date(latestLog.time);
|
|
const hoursSinceLastActivity = (currentTime - loginTime) / (1000 * 60 * 60);
|
|
if (hoursSinceLastActivity <= 24) {
|
|
isActive = true;
|
|
}
|
|
}
|
|
|
|
return {
|
|
id: va._id,
|
|
firstName: va.userDetails?.firstName || '',
|
|
lastName: va.userDetails?.lastName || '',
|
|
email: va.userDetails?.email || '',
|
|
status: va.status || '',
|
|
isActive: isActive
|
|
};
|
|
}));
|
|
|
|
// Filter out null entries and count active VAs
|
|
const validVADetails = assignedVADetails.filter(Boolean);
|
|
const activeVAsCount = validVADetails.filter(va => va.isActive).length;
|
|
|
|
const dayNumber = getDayNumber();
|
|
|
|
return {
|
|
id: project._id,
|
|
name: project.name,
|
|
hourlyRate: project.hourlyRate,
|
|
vaAssigned: validVADetails.length,
|
|
activeVAs: activeVAsCount,
|
|
dedicatedHours: formatHoursToReadable(project.dedicatedHours[dayNumber]) || "0h",
|
|
status: project.status || '',
|
|
assignedVADetails: validVADetails
|
|
};
|
|
}));
|
|
|
|
// Calculate summary counts
|
|
const totalProjects = projectsFormatted.length;
|
|
|
|
// Count total unique VAs across all projects
|
|
const allVAsAcrossProjects = new Set();
|
|
projectsFormatted.forEach(project => {
|
|
project.assignedVADetails.forEach(va => {
|
|
allVAsAcrossProjects.add(va.id);
|
|
});
|
|
});
|
|
|
|
// Count total active VAs (across all projects)
|
|
const totalActiveVAs = new Set();
|
|
projectsFormatted.forEach(project => {
|
|
project.assignedVADetails.forEach(va => {
|
|
if (va.isActive) {
|
|
totalActiveVAs.add(va.id);
|
|
}
|
|
});
|
|
});
|
|
|
|
const projectsWithActiveVAs = projectsFormatted.filter(project => project.activeVAs > 0).length;
|
|
|
|
return res.status(200).json({
|
|
success: true,
|
|
data: {
|
|
projects: projectsFormatted
|
|
},
|
|
counts: {
|
|
totalProjects: totalProjects,
|
|
totalVAs: allVAsAcrossProjects.size,
|
|
activeVAs: totalActiveVAs.size,
|
|
projectsWithActiveVAs: projectsWithActiveVAs
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error("Client dashboard projects error:", error);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: "Internal Server Error",
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
export { client_dashboard_projects }; |