Fixed API issues
This commit is contained in:
parent
7d73299822
commit
9f086d55ba
|
|
@ -4,7 +4,7 @@ import { VAModel } from "../../../models/vaModel.js";
|
||||||
// Register a new VA
|
// Register a new VA
|
||||||
async function va_register(req, res) {
|
async function va_register(req, res) {
|
||||||
try {
|
try {
|
||||||
const payload = req.body ? JSON.parse(req.body) : {};
|
const payload = req.body || {};
|
||||||
const { userDetails } = payload;
|
const { userDetails } = payload;
|
||||||
|
|
||||||
if (!userDetails || !userDetails.email || !userDetails.firstName || !userDetails.password) {
|
if (!userDetails || !userDetails.email || !userDetails.firstName || !userDetails.password) {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
import { ProjectModel, VAModel, LoggedModel } from "../../../models/vaModel.js";
|
import { ProjectModel, VAModel, LoggedModel } from "../../../models/vaModel.js";
|
||||||
|
|
||||||
function formatHMS(ms) {
|
function formatHMS(ms) {
|
||||||
|
|
@ -11,50 +12,63 @@ function formatHMS(ms) {
|
||||||
async function va_projects_log_list_function(req, res) {
|
async function va_projects_log_list_function(req, res) {
|
||||||
try {
|
try {
|
||||||
const { projectId, from, to } = req.params;
|
const { projectId, from, to } = req.params;
|
||||||
|
const accessToken = req.headers.token;
|
||||||
|
|
||||||
if (!projectId) {
|
if (!accessToken) {
|
||||||
return res.status(400).json({ success: false, message: "Missing projectId" });
|
return res.status(401).json({ success: false, message: "Missing token" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 📅 Date filtering
|
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 vaEmail = decoded.email;
|
||||||
|
const vaUser = await VAModel.findOne({ "userDetails.email": vaEmail });
|
||||||
|
if (!vaUser) {
|
||||||
|
return res.status(404).json({ success: false, message: "VA not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const vaId = vaUser._id;
|
||||||
|
|
||||||
|
// Date filtering
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const fromDate = from && from !== "undefined" ? new Date(from) : new Date(now.setHours(0, 0, 0, 0));
|
const fromDate = from && from !== "undefined" ? new Date(from) : new Date(now.setHours(0, 0, 0, 0));
|
||||||
const toDate = to && to !== "undefined" ? new Date(to) : new Date();
|
const toDate = to && to !== "undefined" ? new Date(to) : new Date();
|
||||||
toDate.setHours(23, 59, 59, 999);
|
toDate.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
// 🧩 Fetch Project
|
let projectList = [];
|
||||||
const project = await ProjectModel.findById(projectId).populate("vaAssigned", {
|
if (projectId) {
|
||||||
"userDetails.firstName": 1,
|
const project = await ProjectModel.findById(projectId);
|
||||||
"userDetails.lastName": 1,
|
if (!project) {
|
||||||
"userDetails.email": 1,
|
return res.status(404).json({ success: false, message: "Project not found" });
|
||||||
status: 1
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (!project) {
|
// Check if VA is assigned to this project
|
||||||
return res.status(404).json({ success: false, message: "Project not found" });
|
const isAssigned = project.vaAssigned.some(id => id.toString() === vaId.toString());
|
||||||
|
if (!isAssigned) {
|
||||||
|
return res.status(403).json({ success: false, message: "VA not assigned to this project" });
|
||||||
|
}
|
||||||
|
|
||||||
|
projectList = [project];
|
||||||
|
} else {
|
||||||
|
projectList = await ProjectModel.find({ vaAssigned: vaId });
|
||||||
}
|
}
|
||||||
|
|
||||||
const vaList = project.vaAssigned || [];
|
if (!projectList.length) {
|
||||||
if (!vaList.length) {
|
return res.status(200).json({ success: true, message: "No assigned projects found", data: [] });
|
||||||
return res.status(200).json({ success: true, message: "No VAs assigned to this project", data: [] });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let totalProjectWorkMs = 0;
|
const finalReport = [];
|
||||||
let activeCount = 0;
|
|
||||||
|
|
||||||
const vaReport = [];
|
for (const project of projectList) {
|
||||||
|
|
||||||
// 🔁 Loop through each VA assigned to the project
|
|
||||||
for (const va of vaList) {
|
|
||||||
const vaId = va._id;
|
|
||||||
const vaEmail = va.userDetails?.email;
|
|
||||||
|
|
||||||
// 🔎 Get all login/logout logs for this VA in the date range
|
|
||||||
const logs = await LoggedModel.find({
|
const logs = await LoggedModel.find({
|
||||||
vaId,
|
vaId,
|
||||||
projectId,
|
projectId: project._id,
|
||||||
time: { $gte: fromDate, $lte: toDate }
|
time: { $gte: fromDate, $lte: toDate }
|
||||||
}).sort({ time: 1 }).lean();
|
})
|
||||||
|
.sort({ time: 1 })
|
||||||
|
.lean();
|
||||||
|
|
||||||
let totalWorkMs = 0;
|
let totalWorkMs = 0;
|
||||||
let currentLogin = null;
|
let currentLogin = null;
|
||||||
|
|
@ -68,46 +82,40 @@ async function va_projects_log_list_function(req, res) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if still logged in (no logout yet) — count up to now
|
// still logged in → count till now
|
||||||
if (currentLogin) {
|
if (currentLogin) {
|
||||||
totalWorkMs += new Date() - new Date(currentLogin.time);
|
totalWorkMs += new Date() - new Date(currentLogin.time);
|
||||||
}
|
}
|
||||||
|
|
||||||
totalProjectWorkMs += totalWorkMs;
|
|
||||||
|
|
||||||
// 🟢 Determine Active / Away status
|
|
||||||
const lastLog = logs[logs.length - 1];
|
const lastLog = logs[logs.length - 1];
|
||||||
let activityStatus = "AWAY";
|
const activityStatus = lastLog?.loggedType === "LOGIN" ? "ACTIVE" : "AWAY";
|
||||||
if (lastLog?.loggedType === "LOGIN") {
|
|
||||||
activityStatus = "ACTIVE";
|
|
||||||
activeCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
vaReport.push({
|
finalReport.push({
|
||||||
vaId,
|
projectId: project._id,
|
||||||
name: `${va.userDetails?.firstName || ""} ${va.userDetails?.lastName || ""}`.trim(),
|
projectName: project.name,
|
||||||
email: vaEmail,
|
|
||||||
status: va.status,
|
|
||||||
activityStatus,
|
activityStatus,
|
||||||
totalWorkTime: formatHMS(totalWorkMs),
|
totalWorkTime: formatHMS(totalWorkMs),
|
||||||
totalWorkMs,
|
totalWorkMs,
|
||||||
|
totalLogs: logs.length
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 📊 Prepare Summary
|
// Summary for current VA
|
||||||
const totalVAs = vaReport.length;
|
const totalProjects = finalReport.length;
|
||||||
const awayCount = totalVAs - activeCount;
|
const activeProjects = finalReport.filter(r => r.activityStatus === "ACTIVE").length;
|
||||||
|
const awayProjects = totalProjects - activeProjects;
|
||||||
|
const totalMs = finalReport.reduce((acc, r) => acc + r.totalWorkMs, 0);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
projectId,
|
vaId,
|
||||||
projectName: project.name,
|
vaName: `${vaUser.userDetails.firstName} ${vaUser.userDetails.lastName}`,
|
||||||
totalVAs,
|
totalProjects,
|
||||||
activeVAs: activeCount,
|
activeProjects,
|
||||||
awayVAs: awayCount,
|
awayProjects,
|
||||||
totalProjectWorkTime: formatHMS(totalProjectWorkMs),
|
totalWorkTime: formatHMS(totalMs),
|
||||||
totalProjectWorkMs,
|
totalWorkMs: totalMs,
|
||||||
data: vaReport
|
data: finalReport
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
||||||
|
|
@ -1,89 +1,109 @@
|
||||||
import { VAModel, LoggedModel } from "../../models/vaModel.js";
|
import { VAModel, ProjectModel, LoggedModel } from "../../models/vaModel.js";
|
||||||
|
|
||||||
// Getting VA details by username
|
// Getting VA details by username
|
||||||
async function va_username_callback(req, res) {
|
async function va_username_callback(req, res) {
|
||||||
try {
|
try {
|
||||||
const { userName } = req.params;
|
const { userName } = req.params;
|
||||||
if (!userName) {
|
if (!userName) {
|
||||||
return res.status(400).json({ success: false, message: "Missing userName param" });
|
return res.status(400).json({ success: false, message: "Missing username" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find VA by common identifier fields (id, email, username, first/last name)
|
// Find VA by firstName or lastName or email (depending on how you store username)
|
||||||
const va = await VAModel.findOne({
|
const vaUser = await VAModel.findOne({
|
||||||
$or: [
|
$or: [
|
||||||
{ _id: userName },
|
|
||||||
{ "userDetails.email": userName },
|
{ "userDetails.email": userName },
|
||||||
{ "userDetails.userName": userName },
|
|
||||||
{ "userDetails.firstName": userName },
|
{ "userDetails.firstName": userName },
|
||||||
{ "userDetails.lastName": userName },
|
{ "userDetails.lastName": userName },
|
||||||
{ "userDetails.fullName": userName }
|
],
|
||||||
]
|
})
|
||||||
}).lean().exec();
|
.populate("projectIds", "name totalLoggedHours")
|
||||||
|
.lean();
|
||||||
|
|
||||||
if (!va) {
|
if (!vaUser) {
|
||||||
return res.status(404).json({ success: false, message: "VA not found" });
|
return res.status(404).json({ success: false, message: "VA not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Latest log entry to determine active state
|
const vaId = vaUser._id;
|
||||||
const lastLog = await LoggedModel.findOne({ vaId: va._id }).sort({ time: -1 }).lean().exec();
|
|
||||||
const active = lastLog ? String(lastLog.loggedType).toUpperCase() === "LOGIN" : false;
|
|
||||||
|
|
||||||
// Fetch all logs for this VA (ordered ascending) to compute logged hours
|
// Get all logs for this VA
|
||||||
const logs = await LoggedModel.find({ vaId: va._id }).sort({ time: 1 }).lean().exec();
|
const logs = await LoggedModel.find({ vaId }).sort({ time: 1 }).populate("projectId", "name").lean();
|
||||||
|
|
||||||
let totalSeconds = 0;
|
if (!logs.length) {
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: "No logs found for this VA",
|
||||||
|
data: {
|
||||||
|
va: vaUser,
|
||||||
|
totalWorkTime: "00h 00m 00s",
|
||||||
|
totalWorkMs: 0,
|
||||||
|
projects: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (logs.length) {
|
// Compute time spent on each project
|
||||||
// If logs have an explicit duration field, sum it
|
let totalWorkMs = 0;
|
||||||
if (logs[0].duration !== undefined) {
|
const projectWork = {};
|
||||||
totalSeconds = logs.reduce((sum, l) => sum + (Number(l.duration) || 0), 0);
|
|
||||||
} else {
|
|
||||||
// Otherwise compute from LOGIN/LOGOUT pairs
|
|
||||||
let lastLogin = null;
|
|
||||||
for (const l of logs) {
|
|
||||||
const t = l.time ? new Date(l.time) : null;
|
|
||||||
const type = String(l.loggedType || "").toUpperCase();
|
|
||||||
|
|
||||||
if (type === "LOGIN" && t) {
|
let currentLogin = null;
|
||||||
lastLogin = t;
|
let currentProject = null;
|
||||||
} else if (type === "LOGOUT" && t && lastLogin) {
|
|
||||||
const diff = (t.getTime() - lastLogin.getTime()) / 1000;
|
for (const log of logs) {
|
||||||
if (diff > 0) totalSeconds += diff;
|
const time = new Date(log.time);
|
||||||
lastLogin = null;
|
if (log.loggedType === "LOGIN") {
|
||||||
}
|
currentLogin = time;
|
||||||
}
|
currentProject = log.projectId?._id?.toString() || null;
|
||||||
// If currently logged in, add time since last login
|
} else if (log.loggedType === "LOGOUT" && currentLogin) {
|
||||||
if (lastLogin) {
|
const diff = time - currentLogin;
|
||||||
totalSeconds += (Date.now() - lastLogin.getTime()) / 1000;
|
totalWorkMs += diff;
|
||||||
|
if (currentProject) {
|
||||||
|
projectWork[currentProject] = (projectWork[currentProject] || 0) + diff;
|
||||||
}
|
}
|
||||||
|
currentLogin = null;
|
||||||
|
currentProject = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hours = Math.floor(totalSeconds / 3600);
|
// Format helper
|
||||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
const formatHMS = (ms) => {
|
||||||
const seconds = Math.floor(totalSeconds % 60);
|
const totalSeconds = Math.floor(ms / 1000);
|
||||||
const totalHours = `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
const h = Math.floor(totalSeconds / 3600);
|
||||||
|
const m = Math.floor((totalSeconds % 3600) / 60);
|
||||||
|
const s = totalSeconds % 60;
|
||||||
|
return `${h}h ${m}m ${s}s`;
|
||||||
|
};
|
||||||
|
|
||||||
// Project / client info (fall back to empty array)
|
// Build per-project breakdown
|
||||||
const projects = va.clientIds || va.projects || [];
|
const projects = await Promise.all(
|
||||||
|
Object.entries(projectWork).map(async ([projectId, ms]) => {
|
||||||
const name = `${va.userDetails?.firstName || ""}`.trim() + (va.userDetails?.lastName ? ` ${va.userDetails.lastName}` : "");
|
const project = await ProjectModel.findById(projectId).lean();
|
||||||
const email = va.userDetails?.email || null;
|
return {
|
||||||
|
projectId,
|
||||||
|
projectName: project?.name || "Unknown Project",
|
||||||
|
totalWorkMs: ms,
|
||||||
|
totalWorkTime: formatHMS(ms),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Final Response
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
va: {
|
||||||
id: va._id,
|
id: vaUser._id,
|
||||||
name: name || null,
|
name: `${vaUser.userDetails.firstName || ""} ${vaUser.userDetails.lastName || ""}`.trim(),
|
||||||
email,
|
email: vaUser.userDetails.email,
|
||||||
active,
|
status: vaUser.status,
|
||||||
totalHours,
|
schedule: vaUser.schedule,
|
||||||
lastLog: lastLog || null,
|
tracking: vaUser.tracking,
|
||||||
projects
|
totalAssignedProjects: vaUser.projectIds?.length || 0,
|
||||||
}
|
},
|
||||||
|
totalWorkMs,
|
||||||
|
totalWorkTime: formatHMS(totalWorkMs),
|
||||||
|
projects,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("va_username_callback error:", error);
|
console.error("Error in va_username_callback:", error);
|
||||||
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,10 @@ vaRoutes.get("/profile", authValidator, va_profile_callback); // Get VA Profile
|
||||||
vaRoutes.patch("/profile", authValidator, va_profile_update_callback); // Update VA Profile
|
vaRoutes.patch("/profile", authValidator, va_profile_update_callback); // Update VA Profile
|
||||||
|
|
||||||
vaRoutes.get("/:userName", va_username_callback); // VA Username Callback
|
vaRoutes.get("/:userName", va_username_callback); // VA Username Callback
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Example: GET /va/search?name=john&email=@example.com&from=2025-01-01&to=2025-01-31
|
// Example: GET /va/search?name=john&email=@example.com&from=2025-01-01&to=2025-01-31
|
||||||
vaRoutes.get("/search", va_filter_search);
|
vaRoutes.get("/search", va_filter_search);
|
||||||
|
|
||||||
|
|
@ -42,12 +46,16 @@ vaRoutes.get("/clients/:clientId/projects/:projectId", va_clients_projects_callb
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Getting Project List assigned to VA should be ACTIVE and add total logged hours added by VA
|
|
||||||
vaRoutes.get("/list/project/:projectId/:from?/:to?", va_projects_log_list_function);
|
|
||||||
// GET Windows Tab Logged Data for VA IN Between Login and LOGOUT
|
|
||||||
vaRoutes.get("/report/project/:projectId/:from?/:to?", va_projects_report_function);
|
|
||||||
|
|
||||||
|
|
||||||
|
// GET Windows Tab Logged Data for VA IN Between Login and LOGOUT
|
||||||
|
vaRoutes.get("/report/project/:projectId/:from?/:to?", authValidator, va_projects_report_function);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Getting Project List assigned to VA should be ACTIVE and add total logged hours added by VA
|
||||||
|
vaRoutes.get("/list/projects/:projectId?/:from?/:to?", authValidator, va_projects_log_list_function);
|
||||||
|
|
||||||
vaRoutes.post("/log/project", authValidator, va_project_log_callback); // VA Logged IN / Logged Out in project
|
vaRoutes.post("/log/project", authValidator, va_project_log_callback); // VA Logged IN / Logged Out in project
|
||||||
|
|
||||||
vaRoutes.post("/log/project/track", authValidator, va_log_track_work_callback); // Track Window Tabs Logs Data
|
vaRoutes.post("/log/project/track", authValidator, va_log_track_work_callback); // Track Window Tabs Logs Data
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue