scoketUpdate
This commit is contained in:
parent
36bdc24b54
commit
e9a2ace3db
308
src/server.js
308
src/server.js
|
|
@ -196,236 +196,14 @@ import { ObjectId } from 'mongodb/lib/bson.js';
|
||||||
|
|
||||||
const vaSocketRespectToClient = new Map();
|
const vaSocketRespectToClient = new Map();
|
||||||
|
|
||||||
io.use(async (socket, next) => {
|
|
||||||
try {
|
|
||||||
const token = socket.handshake.auth?.token;
|
|
||||||
const projectId = socket.handshake.auth?.projectId;
|
|
||||||
const userType = socket.handshake.auth?.userType;
|
|
||||||
|
|
||||||
console.log("🔐 Received token:", token);
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
return next(new Error("Not Authorized: Missing Token"));
|
|
||||||
}
|
|
||||||
|
|
||||||
const decoded = jwt.verify(token, process.env.JWT_SECRET_ACCESS_TOKEN);
|
|
||||||
|
|
||||||
if (!decoded?.email) {
|
|
||||||
return next(new Error("Invalid token payload"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------- 1⃣ Check if VA ---------------------- */
|
|
||||||
let vaUser = null;
|
|
||||||
let clientData = null;
|
|
||||||
|
|
||||||
if (userType === "va") {
|
|
||||||
vaUser = await VAModel.findOne({ "userDetails.email": decoded.email });
|
|
||||||
clientData = await ProjectModel.findById(projectId).populate("clientId");
|
|
||||||
} else if (userType === "client") {
|
|
||||||
clientData = await ProjectModel.findById(projectId).populate("clientId");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clientData == null) return next(new Error("Invalid Project ID"));
|
|
||||||
const vaProjClientId = clientData.clientId._id;
|
|
||||||
|
|
||||||
if (vaUser && vaUser.accessToken === token) {
|
|
||||||
socket.data.role = "va";
|
|
||||||
socket.data.vaId = vaUser._id.toString();
|
|
||||||
socket.data.vaEmail = decoded.email;
|
|
||||||
|
|
||||||
const a = vaProjClientId.toString();
|
|
||||||
const b = clientData.clientId._id.toString();
|
|
||||||
const c = JSON.stringify(clientData);
|
|
||||||
|
|
||||||
console.log(a, b, c, "vaProjClientId vs clientData.clientId");
|
|
||||||
|
|
||||||
vaSocketRespectToClient.set(vaProjClientId.toString(), socket.id)
|
|
||||||
|
|
||||||
console.log("✅ Authenticated VA:", vaUser.userDetails.email);
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------- 2⃣ Check if Client ---------------------- */
|
|
||||||
// const clientUser = await ClientModel.findOne({ "userDetails.email": decoded.email });
|
|
||||||
|
|
||||||
// if (clientUser) {
|
|
||||||
// socket.data.role = "client";
|
|
||||||
// socket.data.clientId = clientUser._id.toString();
|
|
||||||
|
|
||||||
// // Get all VA IDs assigned to this client
|
|
||||||
// socket.data.allowedVAs = clientUser.vaIds.map((va) => va.toString());
|
|
||||||
|
|
||||||
// console.log("✅ Authenticated Client:", clientUser.userDetails.email);
|
|
||||||
// return next();
|
|
||||||
// }
|
|
||||||
const clientUser = clientData.clientId;
|
|
||||||
|
|
||||||
if (clientUser) {
|
|
||||||
socket.data.role = "client";
|
|
||||||
socket.data.clientId = clientUser._id.toString();
|
|
||||||
|
|
||||||
// Get all VA IDs assigned to this client
|
|
||||||
socket.data.allowedVAs = clientData.vaAssigned.map((va) => va.toString());
|
|
||||||
|
|
||||||
// console.log("✅ Authenticated Client:", clientUser.userDetails.email);
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
return next(new Error("Not Authorized: Invalid User"));
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ Socket Auth Error:", error.message);
|
|
||||||
next(new Error("Authentication failed: " + error.message));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
io.on("connection", (socket) => {
|
|
||||||
console.log("User connected:", socket.id);
|
|
||||||
|
|
||||||
// Register role and ID (client or VA)
|
|
||||||
socket.on("register", async ({ role, id, projectId = null, clientId = null }) => {
|
|
||||||
if (role === "va") {
|
|
||||||
socket.data.role = "va";
|
|
||||||
socket.data.vaId = id;
|
|
||||||
console.log(`VA registered: ${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role === "client") {
|
|
||||||
socket.data.role = "client";
|
|
||||||
socket.data.vaId = id;
|
|
||||||
socket.data.projectId = projectId;
|
|
||||||
socket.data.clientId = null
|
|
||||||
|
|
||||||
if (!projectId || !clientId) {
|
|
||||||
console.log("projectId is missing in register payload");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.data.clientId = clientId;
|
|
||||||
|
|
||||||
// 🧩 Get allowed VAs for this client
|
|
||||||
const client = await ClientModel.findById(clientId)
|
|
||||||
.populate("projectIds") // to access their projects
|
|
||||||
.populate("vaIds"); // optional if you track directly
|
|
||||||
|
|
||||||
// Collect all VA IDs from their projects
|
|
||||||
const projectVAIds = new Set();
|
|
||||||
for (const project of client.projectIds) {
|
|
||||||
const populated = await ProjectModel.findById(project._id).populate("vaAssigned");
|
|
||||||
populated.vaAssigned.forEach((va) => projectVAIds.add(va._id.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store allowed VA IDs in socket memory
|
|
||||||
socket.data.allowedVAs = Array.from(projectVAIds);
|
|
||||||
console.log(`Client ${id} can view VAs:`, socket.data.allowedVAs);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// When VA sends live image
|
|
||||||
socket.on("sendImage", async ({ token, image, title }) => {
|
|
||||||
try {
|
|
||||||
// Verify token
|
|
||||||
const decoded = jwt.verify(token, process.env.JWT_SECRET_ACCESS_TOKEN);
|
|
||||||
const vaEmail = decoded.email;
|
|
||||||
|
|
||||||
// Find VA in DB
|
|
||||||
const vaUser = await VAModel.findOne({ "userDetails.email": vaEmail });
|
|
||||||
if (!vaUser) {
|
|
||||||
console.log("❌ Unauthorized VA");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const vaId = vaUser._id.toString();
|
|
||||||
console.log("🟢 Received image from VA:", vaId);
|
|
||||||
|
|
||||||
// Broadcast image only to allowed clients
|
|
||||||
for (let [_, clientSocket] of io.sockets.sockets) {
|
|
||||||
if (
|
|
||||||
clientSocket.data.role === "client" &&
|
|
||||||
clientSocket.data.allowedVAs?.includes(vaId)
|
|
||||||
) {
|
|
||||||
clientSocket.emit("receiveImage", image, title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("❌ Token verification failed:", err.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("selectApp", ({ appTitle, clientId }) => {
|
|
||||||
console.log(`VA selected app: ${appTitle}`);
|
|
||||||
|
|
||||||
const sssrrrr = vaSocketRespectToClient.get(clientId);
|
|
||||||
console.log(vaSocketRespectToClient.get(clientId), sssrrrr, "vaSocketRespectToClient[clientId]")
|
|
||||||
// You can forward this info to the respective Electron client
|
|
||||||
socket.to(vaSocketRespectToClient.get(clientId)).emit("appToCapture", { appTitle });
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("getVisibleApps", async ({ token, apps }) => {
|
|
||||||
try {
|
|
||||||
// Verify token
|
|
||||||
const decoded = jwt.verify(token, process.env.JWT_SECRET_ACCESS_TOKEN);
|
|
||||||
const vaEmail = decoded.email;
|
|
||||||
|
|
||||||
// Find VA in DB
|
|
||||||
const vaUser = await VAModel.findOne({ "userDetails.email": vaEmail });
|
|
||||||
if (!vaUser) {
|
|
||||||
console.log("❌ Unauthorized VA");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const vaId = vaUser._id.toString();
|
|
||||||
console.log("🟢 Received Apps from VA:", vaId);
|
|
||||||
|
|
||||||
// Broadcast image only to allowed clients
|
|
||||||
for (let [_, clientSocket] of io.sockets.sockets) {
|
|
||||||
if (
|
|
||||||
clientSocket.data.role === "client" &&
|
|
||||||
clientSocket.data.allowedVAs?.includes(vaId)
|
|
||||||
) {
|
|
||||||
clientSocket.emit("receiveVisibleApps", apps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("❌ Token verification failed:", err.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
|
||||||
console.log('a user Disconnected');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// io.on('connection', (socket) => {
|
|
||||||
|
|
||||||
// socket.on("sendImage", (base64String) => {
|
|
||||||
// io.emit("receiveImage", base64String);
|
|
||||||
// });
|
|
||||||
|
|
||||||
|
|
||||||
// socket.on('disconnect', () => {
|
|
||||||
// console.log('a user Disconnected');
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
app.get("/client/login", (req, res) => {
|
|
||||||
res.render("client_login")
|
|
||||||
})
|
|
||||||
app.get("/client/forgot-password", (req, res) => {
|
|
||||||
res.render("forgot_password")
|
|
||||||
})
|
|
||||||
|
|
||||||
// Handling Socket
|
// Handling Socket
|
||||||
io.use(async (socket, next) => {
|
io.use(async (socket, next) => {
|
||||||
try {
|
try {
|
||||||
const token = socket.handshake.auth?.token;
|
const token = socket.handshake.auth?.token;
|
||||||
const projectId = socket.handshake.auth?.projectId;
|
const projectId = socket.handshake.auth?.projectId;
|
||||||
const userType = socket.handshake.auth?.userType;
|
const userType = socket.handshake.auth?.userType;
|
||||||
|
const vaId = socket.handshake.auth?.vaId;
|
||||||
|
|
||||||
|
|
||||||
console.log("🔐 Received token:", token);
|
console.log("🔐 Received token:", token);
|
||||||
|
|
||||||
|
|
@ -464,7 +242,7 @@ io.use(async (socket, next) => {
|
||||||
|
|
||||||
console.log(a, b, c, "vaProjClientId vs clientData.clientId");
|
console.log(a, b, c, "vaProjClientId vs clientData.clientId");
|
||||||
|
|
||||||
vaSocketRespectToClient.set(`${vaProjClientId.toString()}-${vaUser._id.toString()}`, socket.id)
|
vaSocketRespectToClient.set(`${vaProjClientId.toString()}-${vaUser._id.toString()}-${projectId.toString()}-va`, socket.id)
|
||||||
|
|
||||||
console.log("✅ Authenticated VA:", vaUser.userDetails.email);
|
console.log("✅ Authenticated VA:", vaUser.userDetails.email);
|
||||||
return next();
|
return next();
|
||||||
|
|
@ -491,6 +269,7 @@ io.use(async (socket, next) => {
|
||||||
|
|
||||||
// Get all VA IDs assigned to this client
|
// Get all VA IDs assigned to this client
|
||||||
socket.data.allowedVAs = clientData.vaAssigned.map((va) => va.toString());
|
socket.data.allowedVAs = clientData.vaAssigned.map((va) => va.toString());
|
||||||
|
vaSocketRespectToClient.set(`${vaProjClientId.toString()}-${vaId.toString()}-${projectId.toString()}-client`, socket.id)
|
||||||
|
|
||||||
// console.log("✅ Authenticated Client:", clientUser.userDetails.email);
|
// console.log("✅ Authenticated Client:", clientUser.userDetails.email);
|
||||||
return next();
|
return next();
|
||||||
|
|
@ -503,8 +282,6 @@ io.use(async (socket, next) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
io.on("connection", (socket) => {
|
io.on("connection", (socket) => {
|
||||||
console.log("User connected:", socket.id);
|
console.log("User connected:", socket.id);
|
||||||
|
|
||||||
|
|
@ -539,7 +316,7 @@ io.on("connection", (socket) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// When VA sends live image
|
// When VA sends live image
|
||||||
socket.on("sendImage", async ({ token, image, title }) => {
|
socket.on("sendImage", async ({ token, image, title, projectId }) => {
|
||||||
try {
|
try {
|
||||||
// Verify token
|
// Verify token
|
||||||
const decoded = jwt.verify(token, process.env.JWT_SECRET_ACCESS_TOKEN);
|
const decoded = jwt.verify(token, process.env.JWT_SECRET_ACCESS_TOKEN);
|
||||||
|
|
@ -552,30 +329,42 @@ io.on("connection", (socket) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const project = await ProjectModel.findById(projectId);
|
||||||
|
if (!project) {
|
||||||
|
console.log("❌ Invalid Project ID");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientId = project.clientId.toString();
|
||||||
const vaId = vaUser._id.toString();
|
const vaId = vaUser._id.toString();
|
||||||
|
const clientVaUniqueId = `${clientId}-${vaId}-${projectId}-client`;
|
||||||
|
const socketId = vaSocketRespectToClient.get(clientVaUniqueId);
|
||||||
|
|
||||||
console.log("🟢 Received image from VA:", vaId);
|
console.log("🟢 Received image from VA:", vaId);
|
||||||
|
|
||||||
// Broadcast image only to allowed clients
|
// Broadcast image only to allowed clients
|
||||||
for (let [_, clientSocket] of io.sockets.sockets) {
|
// for (let [_, clientSocket] of io.sockets.sockets) {
|
||||||
if (
|
// if (
|
||||||
clientSocket.data.role === "client" &&
|
// clientSocket.data.role === "client" &&
|
||||||
clientSocket.data.allowedVAs?.includes(vaId)
|
// clientSocket.data.allowedVAs?.includes(vaId)
|
||||||
) {
|
// ) {
|
||||||
clientSocket.emit("receiveImage", image, title);
|
// clientSocket.emit("receiveImage", image, title);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
// Can be like clientId-projectId-vaId to uniquely identify
|
||||||
|
socket.to(socketId).emit("receiveImage", image, title);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("❌ Token verification failed:", err.message);
|
console.error("❌ Token verification failed:", err.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("selectApp", ({ appTitle, clientId }) => {
|
socket.on("selectApp", ({ appTitle, clientId, vaId, projectId }) => {
|
||||||
console.log(`VA selected app: ${appTitle}`);
|
console.log(`VA selected app: ${appTitle}`);
|
||||||
|
|
||||||
const sssrrrr = vaSocketRespectToClient.get(`${clientId}-${socket.data.vaId}`);
|
console.log(vaSocketRespectToClient.get(`${clientId}-${vaId}-${projectId}-va`), "vaSocketRespectToClient[clientId]")
|
||||||
console.log(vaSocketRespectToClient.get(`${clientId}-${socket.data.vaId}`), sssrrrr, "vaSocketRespectToClient[clientId]")
|
|
||||||
// You can forward this info to the respective Electron client
|
// You can forward this info to the respective Electron client
|
||||||
socket.to(vaSocketRespectToClient.get(`${clientId}-${socket.data.vaId}`)).emit("appToCapture", { appTitle });
|
socket.to(vaSocketRespectToClient.get(`${clientId}-${vaId}-${projectId}-va`)).emit("appToCapture", { appTitle });
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("getVisibleApps", async ({ token, apps }) => {
|
socket.on("getVisibleApps", async ({ token, apps }) => {
|
||||||
|
|
@ -600,7 +389,7 @@ io.on("connection", (socket) => {
|
||||||
clientSocket.data.role === "client" &&
|
clientSocket.data.role === "client" &&
|
||||||
clientSocket.data.allowedVAs?.includes(vaId)
|
clientSocket.data.allowedVAs?.includes(vaId)
|
||||||
) {
|
) {
|
||||||
clientSocket.emit("receiveVisibleApps", apps);
|
clientSocket.emit("receiveVisibleApps", {apps, vaId});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -614,37 +403,12 @@ io.on("connection", (socket) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get("/client/login", (req, res) => {
|
||||||
|
res.render("client_login")
|
||||||
|
})
|
||||||
// io.on('connection', (socket) => {
|
app.get("/client/forgot-password", (req, res) => {
|
||||||
|
res.render("forgot_password")
|
||||||
// socket.on("sendImage", (base64String) => {
|
})
|
||||||
// io.emit("receiveImage", base64String);
|
|
||||||
// });
|
|
||||||
|
|
||||||
|
|
||||||
// socket.on('disconnect', () => {
|
|
||||||
// console.log('a user Disconnected');
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Express App Listening
|
|
||||||
// app.listen(PORT, () => {
|
|
||||||
// mongoose
|
|
||||||
// .connect(
|
|
||||||
// process.env.MONGO_URI,
|
|
||||||
// )
|
|
||||||
// .then(() => console.log("mongodb connected"))
|
|
||||||
// .catch((err) => {
|
|
||||||
// console.log(err.message);
|
|
||||||
// process.exit(1);
|
|
||||||
// });
|
|
||||||
// console.log(`Express started on http://localhost:${PORT}`);
|
|
||||||
// });
|
|
||||||
|
|
||||||
|
|
||||||
app.use((req, res) => {
|
app.use((req, res) => {
|
||||||
res.status(404).render("not_found", {
|
res.status(404).render("not_found", {
|
||||||
|
|
|
||||||
|
|
@ -148,8 +148,7 @@
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Virtual Assistant</th>
|
<th>Virtual Assistant</th>
|
||||||
<th>Projects Assigned</th>
|
<th>Projects Assigned</th>
|
||||||
<th>Working from</th>
|
<th>Worked Time</th>
|
||||||
<th>Total Worked</th>
|
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
@ -222,7 +221,7 @@
|
||||||
|
|
||||||
// Format time function
|
// Format time function
|
||||||
function formatTime(seconds) {
|
function formatTime(seconds) {
|
||||||
if (!seconds) return '';
|
if (!seconds) return 'Not working';
|
||||||
|
|
||||||
const hours = Math.floor(seconds / 3600);
|
const hours = Math.floor(seconds / 3600);
|
||||||
const minutes = Math.floor((seconds % 3600) / 60);
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
|
@ -231,8 +230,6 @@
|
||||||
return `${hours}h ${minutes}m`;
|
return `${hours}h ${minutes}m`;
|
||||||
} else if (minutes > 0) {
|
} else if (minutes > 0) {
|
||||||
return `${minutes}m`;
|
return `${minutes}m`;
|
||||||
} else if (seconds > 0) {
|
|
||||||
return `${seconds}s`;
|
|
||||||
} else {
|
} else {
|
||||||
return 'Just started';
|
return 'Just started';
|
||||||
}
|
}
|
||||||
|
|
@ -243,7 +240,6 @@
|
||||||
const statusClass = va.isCurrentlyActive ? 'active' : 'inactive';
|
const statusClass = va.isCurrentlyActive ? 'active' : 'inactive';
|
||||||
const statusText = va.isCurrentlyActive ? 'Active' : 'Inactive';
|
const statusText = va.isCurrentlyActive ? 'Active' : 'Inactive';
|
||||||
const workedTime = formatTime(va.currentSessionDuration);
|
const workedTime = formatTime(va.currentSessionDuration);
|
||||||
const totalWorkedTime = formatTime(va.totalWorkedSeconds);
|
|
||||||
const projectsAssigned = va.projectsAssigned || 0;
|
const projectsAssigned = va.projectsAssigned || 0;
|
||||||
const viewLink = va.isCurrentlyActive ? `<a href="/live/${va.lastProjectId}/${va.id}" class="view-link" data-va-id="${va.id}" target="_blank">
|
const viewLink = va.isCurrentlyActive ? `<a href="/live/${va.lastProjectId}/${va.id}" class="view-link" data-va-id="${va.id}" target="_blank">
|
||||||
<i class="fas fa-eye"></i>
|
<i class="fas fa-eye"></i>
|
||||||
|
|
@ -251,7 +247,7 @@
|
||||||
</a>` : '';
|
</a>` : '';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<tr data-vaid="${va.id}">
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<div class="status-indicator">
|
<div class="status-indicator">
|
||||||
<div class="status-dot ${statusClass}"></div>
|
<div class="status-dot ${statusClass}"></div>
|
||||||
|
|
@ -260,7 +256,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="va-info">
|
<div class="va-info">
|
||||||
<div class="va-name">${va.name}</div>
|
<div class="va-name">${va.firstName} ${va.lastName}</div>
|
||||||
<div class="va-email">${va.email || 'N/A'}</div>
|
<div class="va-email">${va.email || 'N/A'}</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -270,9 +266,6 @@
|
||||||
<td>
|
<td>
|
||||||
<span class="worked-time">${workedTime}</span>
|
<span class="worked-time">${workedTime}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
|
||||||
<span class="worked-time">${totalWorkedTime}</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
${viewLink}
|
${viewLink}
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -310,34 +303,15 @@
|
||||||
vaTableBody.style.display = '';
|
vaTableBody.style.display = '';
|
||||||
vaEmptyState.style.display = 'none';
|
vaEmptyState.style.display = 'none';
|
||||||
|
|
||||||
// Add event listeners to table rows
|
// Add event listeners to view links
|
||||||
document.querySelectorAll('tr[data-vaid]').forEach(row => {
|
document.querySelectorAll('.view-link').forEach(link => {
|
||||||
row.addEventListener('click', async function(e) {
|
link.addEventListener('click', function(e) {
|
||||||
// Don't trigger if click is on view link
|
e.preventDefault();
|
||||||
if (e.target.closest('.view-link')) {
|
const vaId = this.getAttribute('data-va-id');
|
||||||
return;
|
// You can update this link as needed
|
||||||
}
|
window.location.href = `/client/virtual-assistants/${vaId}`;
|
||||||
|
|
||||||
const vaId = this.getAttribute('data-vaid');
|
|
||||||
const vaData = allVAs.find(va => va.id === vaId);
|
|
||||||
|
|
||||||
if (vaData) {
|
|
||||||
// Show loading state
|
|
||||||
const originalContent = this.innerHTML;
|
|
||||||
this.innerHTML = '<td colspan="6" style="text-align: center;"><i class="fas fa-spinner fa-spin"></i> Loading...</td>';
|
|
||||||
|
|
||||||
// Fetch additional VA details from /test API
|
|
||||||
const projectData = await fetchVADetails(vaId);
|
|
||||||
|
|
||||||
// Restore original content
|
|
||||||
this.innerHTML = originalContent;
|
|
||||||
|
|
||||||
// Show popup with VA details
|
|
||||||
showVAPopup(projectData);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
vaTableBody.innerHTML = '';
|
vaTableBody.innerHTML = '';
|
||||||
vaTableBody.style.display = 'none';
|
vaTableBody.style.display = 'none';
|
||||||
|
|
@ -377,127 +351,6 @@
|
||||||
// Optional: Auto-refresh data every 30 seconds
|
// Optional: Auto-refresh data every 30 seconds
|
||||||
setInterval(loadDashboardData, 30000);
|
setInterval(loadDashboardData, 30000);
|
||||||
|
|
||||||
function createPopup(vaData) {
|
|
||||||
// Extract project details from the API response
|
|
||||||
// The API response is the project array, so use it directly
|
|
||||||
const projectDetails = Array.isArray(vaData) ? vaData : (vaData.projects || []);
|
|
||||||
|
|
||||||
// Create project details HTML
|
|
||||||
const projectsHTML = projectDetails.length > 0
|
|
||||||
? projectDetails.map(project => `
|
|
||||||
<div class="project-item">
|
|
||||||
<div class="project-header">
|
|
||||||
<span class="project-name">${project.projectName || 'Unknown Project'}</span>
|
|
||||||
<span class="project-time">${project.totalTime || '00:00:00'}</span>
|
|
||||||
</div>
|
|
||||||
<div class="project-id">ID: ${project.projectId || 'N/A'}</div>
|
|
||||||
</div>
|
|
||||||
`).join('')
|
|
||||||
: '<div class="no-projects">No project data available</div>';
|
|
||||||
|
|
||||||
return `
|
|
||||||
<div class="popup-overlay" id="vaPopup">
|
|
||||||
<div class="popup-content">
|
|
||||||
<div class="popup-header">
|
|
||||||
<h3>Project Work Details</h3>
|
|
||||||
<button class="popup-close" id="closePopup">
|
|
||||||
<i class="fas fa-times"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="popup-body">
|
|
||||||
<!-- Project Count -->
|
|
||||||
<div class="project-summary">
|
|
||||||
<div class="total-projects">
|
|
||||||
<i class="fas fa-folder-open"></i>
|
|
||||||
<span>Total Projects: ${projectDetails.length}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Project Details List -->
|
|
||||||
<div class="projects-list">
|
|
||||||
${projectsHTML}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="popup-footer">
|
|
||||||
<button class="btn btn-secondary" id="closePopupBtn">
|
|
||||||
<i class="fas fa-times"></i> Close
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show popup with VA details
|
|
||||||
function showVAPopup(projectData) {
|
|
||||||
console.log('Project Data:', projectData);
|
|
||||||
|
|
||||||
// Remove existing popup if any
|
|
||||||
const existingPopup = document.getElementById('vaPopup');
|
|
||||||
if (existingPopup) {
|
|
||||||
existingPopup.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and show new popup with only project data
|
|
||||||
const popupHTML = createPopup(projectData);
|
|
||||||
document.body.insertAdjacentHTML('beforeend', popupHTML);
|
|
||||||
|
|
||||||
// Add event listeners for closing popup
|
|
||||||
document.getElementById('closePopup').addEventListener('click', closePopup);
|
|
||||||
document.getElementById('closePopupBtn').addEventListener('click', closePopup);
|
|
||||||
|
|
||||||
// Close popup when clicking outside
|
|
||||||
document.getElementById('vaPopup').addEventListener('click', function(e) {
|
|
||||||
if (e.target === this) {
|
|
||||||
closePopup();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close popup with Escape key
|
|
||||||
document.addEventListener('keydown', function(e) {
|
|
||||||
if (e.key === 'Escape') {
|
|
||||||
closePopup();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch VA details for popup - now returns only project data
|
|
||||||
async function fetchVADetails(vaId) {
|
|
||||||
try {
|
|
||||||
const response = await fetchWithAuth(`${API_BASE}/dashboard/va-report`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({ vaId: vaId }),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response) return;
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
console.log('API Response:', result);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
// Return the projects array directly
|
|
||||||
return result.projects || [];
|
|
||||||
} else {
|
|
||||||
console.error('Failed to fetch project details:', result.message);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching project details:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close popup function
|
|
||||||
function closePopup() {
|
|
||||||
const popup = document.getElementById('vaPopup');
|
|
||||||
if (popup) {
|
|
||||||
popup.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -189,13 +189,15 @@
|
||||||
<script>
|
<script>
|
||||||
const vaId = "<%= vaId %>";
|
const vaId = "<%= vaId %>";
|
||||||
const clientId = "<%= clientId %>";
|
const clientId = "<%= clientId %>";
|
||||||
|
const projectId = "<%= projectId %>";
|
||||||
const token = localStorage.getItem("knoptpq");
|
const token = localStorage.getItem("knoptpq");
|
||||||
|
|
||||||
const socket = io({
|
const socket = io({
|
||||||
auth: {
|
auth: {
|
||||||
token: token,
|
token: token,
|
||||||
userType: "client",
|
userType: "client",
|
||||||
projectId: "<%= projectId %>"
|
projectId: projectId,
|
||||||
|
vaId: vaId
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -229,7 +231,9 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Receive visible apps */
|
/* Receive visible apps */
|
||||||
socket.on("receiveVisibleApps", (apps) => {
|
socket.on("receiveVisibleApps", ({apps, vaId: senderVaId}) => {
|
||||||
|
if (vaId !== senderVaId) return;
|
||||||
|
|
||||||
appsList.innerHTML = "";
|
appsList.innerHTML = "";
|
||||||
|
|
||||||
if (!apps?.length) {
|
if (!apps?.length) {
|
||||||
|
|
@ -252,7 +256,7 @@
|
||||||
placeholder.style.display = "none";
|
placeholder.style.display = "none";
|
||||||
liveImage.style.display = "none";
|
liveImage.style.display = "none";
|
||||||
|
|
||||||
socket.emit("selectApp", { appTitle: app.name, clientId, vaId });
|
socket.emit("selectApp", { appTitle: app.name, clientId, vaId, projectId });
|
||||||
|
|
||||||
// Close sidebar on mobile
|
// Close sidebar on mobile
|
||||||
sidebar.classList.remove("open");
|
sidebar.classList.remove("open");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue