Compare commits

..

No commits in common. "a5f8e7bc7f8ef5a9755d7a02a6f202936d802cb6" and "b9f2eb10b4ef14646046e8978a6274b7bfc84532" have entirely different histories.

12 changed files with 123 additions and 231 deletions

View File

@ -492,11 +492,9 @@ async function validateStep(step) {
} else if (step === 1) {
const email = document.getElementById('email').value;
const companyName = document.getElementById('company-name').value;
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirmPassword').value;
if (!validateEmail(email) && !companyName && !password && !confirmPassword) {
if (!validateEmail(email) && !companyName) {
isValid = false;
showError('Fields are Required', null, step);
showError('Both Fields are Required', null, step);
let requiredFields1 = form.querySelectorAll('[required]');
requiredFields1.forEach(field => {
if (!field.value.trim()) {
@ -524,21 +522,6 @@ async function validateStep(step) {
return isValid;
}
if (password.length < 8) {
showError('Password must be at least 8 characters long', 'password', step);
isValid = false;
}
if (password !== confirmPassword) {
document.getElementById('password-match-error').style.display = 'block';
document.getElementById('confirmPassword').classList.add('field-error');
showError('Passwords do not match', 'confirmPassword', step);
isValid = false;
} else {
document.getElementById('password-match-error').style.display = 'none';
document.getElementById('confirmPassword').classList.remove('field-error');
}
// Validate other required fields
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
@ -629,7 +612,6 @@ function saveStepData(step) {
case 1:
formData.userDetails.name = document.getElementById('company-name').value;
formData.userDetails.email = document.getElementById('email').value;
formData.userDetails.password = document.getElementById('password').value;
break;
case 2:
formData.userDetails.address = document.getElementById('address').value;
@ -1973,51 +1955,3 @@ function preventInvalidAccountHolderChars(event) {
event.preventDefault();
return false;
}
// Function to generate a strong password
function generatePassword() {
const length = 12;
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
let password = "";
// Ensure at least one of each required character type
password += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[Math.floor(Math.random() * 26)];
password += "abcdefghijklmnopqrstuvwxyz"[Math.floor(Math.random() * 26)];
password += "0123456789"[Math.floor(Math.random() * 10)];
password += "!@#$%^&*"[Math.floor(Math.random() * 8)];
// Fill the rest randomly
for (let i = password.length; i < length; i++) {
password += charset.charAt(Math.floor(Math.random() * charset.length));
}
// Shuffle the password
password = password.split('').sort(() => 0.5 - Math.random()).join('');
// Set the password field
document.getElementById('password').value = password;
document.getElementById('confirmPassword').value = password;
// Trigger input events to update UI
document.getElementById('password').dispatchEvent(new Event('input'));
document.getElementById('confirmPassword').dispatchEvent(new Event('input'));
}
// Function to toggle password visibility
function togglePasswordVisibility() {
const passwordField = document.getElementById('password');
const confirmField = document.getElementById('confirmPassword');
const toggleButton = document.querySelector('.password-toggle');
if (passwordField.type === 'password') {
passwordField.type = 'text';
confirmField.type = 'text';
toggleButton.textContent = 'Hide Password';
} else {
passwordField.type = 'password';
confirmField.type = 'password';
toggleButton.textContent = 'Show Password';
}
}

View File

@ -1,6 +1,5 @@
import { VAModel, LoggedModel, ClientModel, ProjectModel } from "../../../models/vaModel.js";
import jwt from "jsonwebtoken";
import getDayNumber from "../../../utils/getDayNumber.js";
// Getting client dashboard data - all VAs, projects, and active VAs
async function client_dashboard(req, res) {
@ -315,11 +314,9 @@ async function client_dashboard(req, res) {
};
});
const dayNumber = getDayNumber(logTime);
// Calculate dashboard statistics
const totalHoursLogged = clientProjects.reduce((sum, p) => sum + (p.totalLoggedHours || 0), 0);
const totalDedicatedHours = clientProjects.reduce((sum, p) => sum + (p.dedicatedHours[dayNumber] || 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);

View File

@ -1,6 +1,5 @@
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);
@ -113,15 +112,13 @@ async function client_dashboard_projects(req, res) {
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",
dedicatedHours: formatHoursToReadable(project.dedicatedHours) || "0h",
status: project.status || '',
assignedVADetails: validVADetails
};

View File

@ -25,10 +25,10 @@ async function va_register_client(req, res) {
try {
const payload = req.body || {};
const { userDetails } = payload;
if (!userDetails || !userDetails.email || !userDetails.name || !userDetails.password) {
if (!userDetails || !userDetails.email || !userDetails.name) {
return res.status(400).json({
success: false,
message: "Missing required fields: name, email, and password are required"
message: "Missing required fields: name and email are required"
});
}

View File

@ -1,6 +1,7 @@
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { ClientModel, VAModel } from "../../models/vaModel.js";
import { va_timesheet_adjustment_function } from "../va/vaTimesheetAdjustment/vaTimesheetAdjustment.controller.js";
// Login VA
async function va_login_client(req, res) {

View File

@ -4,99 +4,99 @@ import { VAModel, LoggedModel, WorkTrackModel } from "../../../models/vaModel.js
import authFetch from "../../../utils/authFetch.js";
// function to close open work tracks and create logout
// async function closePreviousSession(vaId, lastLog) {
// try {
// const currentTime = new Date();
// // Find all open work tracks for this VA and project (where to is not set or totalTime is 0)
// const openWorkTracks = await WorkTrackModel.find({
// vaId,
// logId: lastLog._id,
// $or: [
// { to: { $exists: false } },
// { totalTime: 0 },
// { to: null }
// ]
// });
async function closePreviousSession(vaId, lastLog) {
try {
const currentTime = new Date();
// Find all open work tracks for this VA and project (where to is not set or totalTime is 0)
const openWorkTracks = await WorkTrackModel.find({
vaId,
logId: lastLog._id,
$or: [
{ to: { $exists: false } },
{ totalTime: 0 },
{ to: null }
]
});
// console.log(`Found ${openWorkTracks.length} open work tracks to close`);
// // Close all open work tracks
// for (const workTrack of openWorkTracks) {
// const fromTime = new Date(workTrack.from);
// const totalTimeMs = currentTime - fromTime;
// Close all open work tracks
for (const workTrack of openWorkTracks) {
const fromTime = new Date(workTrack.from);
const totalTimeMs = currentTime - fromTime;
// // Update the work track with end time and total time
// workTrack.to = currentTime;
// workTrack.totalTime = totalTimeMs;
// await workTrack.save();
// Update the work track with end time and total time
workTrack.to = currentTime;
workTrack.totalTime = totalTimeMs;
await workTrack.save();
// // console.log(`Closed work track ${workTrack._id} with duration: ${totalTimeMs}ms`);
// }
// console.log(`Closed work track ${workTrack._id} with duration: ${totalTimeMs}ms`);
}
// // Create a logout log entry
// const logoutLog = await LoggedModel.create({
// loggedType: "LOGOUT",
// vaId,
// vaEmail: lastLog.vaEmail, // Use email from previous log
// projectId: lastLog.projectId,
// time: currentTime // Use same timestamp for consistent logging
// });
// Create a logout log entry
const logoutLog = await LoggedModel.create({
loggedType: "LOGOUT",
vaId,
vaEmail: lastLog.vaEmail, // Use email from previous log
projectId: lastLog.projectId,
time: currentTime // Use same timestamp for consistent logging
});
// // console.log(`Created logout log: ${logoutLog._id}`);
// try {
// const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/va-project-log`;
// const sfDataSync = {
// "_id": logoutLog._id,
// "loggedType": "LOGOUT",
// "vaId": vaId,
// "vaEmail": lastLog.vaEmail,
// "projectId": lastLog.projectId,
// "time": logoutLog.time,
// }
// console.log(`Created logout log: ${logoutLog._id}`);
try {
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/va-project-log`;
const sfDataSync = {
"_id": logoutLog._id,
"loggedType": "LOGOUT",
"vaId": vaId,
"vaEmail": lastLog.vaEmail,
"projectId": lastLog.projectId,
"time": logoutLog.time,
}
// const response = await authFetch(salesforceUrl, {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// // Add any required headers for Salesforce authentication
// // 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN
// },
// body: JSON.stringify(sfDataSync)
// });
const response = await authFetch(salesforceUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// Add any required headers for Salesforce authentication
// 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN
},
body: JSON.stringify(sfDataSync)
});
// if (!response.ok) {
// const errorText = await response.text();
// throw new Error(`Salesforce API responded with status ${response.status}: ${errorText}`);
// }
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Salesforce API responded with status ${response.status}: ${errorText}`);
}
// const result = await response.json();
// // console.log("✅ Successfully synced VA data to Salesforce:", result);
const result = await response.json();
// console.log("✅ Successfully synced VA data to Salesforce:", result);
return {
closedWorkTracks: openWorkTracks.length,
logoutLogId: logoutLog._id,
SyncSalesforce: result
};
} catch (error) {
console.error("Failed to sync with Salesforce:", error.message);
console.log(error)
// Create a custom error to distinguish Salesforce errors
const salesforceError = new Error(`Salesforce sync failed: ${error.message}`);
salesforceError.isSalesforceError = true;
throw salesforceError;
}
// return {
// closedWorkTracks: openWorkTracks.length,
// logoutLogId: logoutLog._id,
// SyncSalesforce: result
// };
// } catch (error) {
// console.error("Failed to sync with Salesforce:", error.message);
// console.log(error)
// // Create a custom error to distinguish Salesforce errors
// const salesforceError = new Error(`Salesforce sync failed: ${error.message}`);
// salesforceError.isSalesforceError = true;
// throw salesforceError;
// }
// // return {
// // closedWorkTracks: openWorkTracks.length,
// // logoutLogId: logoutLog._id,
// // };
// } catch (error) {
// console.error("Error closing previous session:", error);
// throw error;
// }
// }
} catch (error) {
console.error("Error closing previous session:", error);
throw error;
}
}
// Login VA
async function va_login_callback(req, res) {
@ -132,17 +132,17 @@ async function va_login_callback(req, res) {
// also on main login when user try to login into app then logout project and workModel track then allow login on same timestamp
// Check if VA has any active sessions (LOGIN without LOGOUT)
// const activeSessions = await LoggedModel.find({
// vaId: user._id
// }).sort({ time: -1 });
const activeSessions = await LoggedModel.find({
vaId: user._id
}).sort({ time: -1 });
// let closeResult = [];
let closeResult = [];
// if (activeSessions.length > 0) {
// // Get the latest login to check if there's no corresponding logout
// const latestLog = activeSessions[0];
// closeResult = await closePreviousSession(user._id, latestLog);
// }
if (activeSessions.length > 0) {
// Get the latest login to check if there's no corresponding logout
const latestLog = activeSessions[0];
closeResult = await closePreviousSession(user._id, latestLog);
}
// Generate JWT accessToken with 24h expiry
const accessToken = jwt.sign(
@ -172,23 +172,23 @@ async function va_login_callback(req, res) {
let responseData = [];
// if (closeResult) {
// responseData = {
// success: true,
// message: "Login Successfull",
// accessToken,
// refreshToken,
// user: {
// id: user._id,
// name: user.userDetails.firstName,
// email: user.userDetails.email,
// },
// previousSession: {
// closedWorkTracks: closeResult.closedWorkTracks,
// logoutLogId: closeResult.logoutLogId
// }
// }
// } else {
if (closeResult) {
responseData = {
success: true,
message: "Login Successfull",
accessToken,
refreshToken,
user: {
id: user._id,
name: user.userDetails.firstName,
email: user.userDetails.email,
},
previousSession: {
closedWorkTracks: closeResult.closedWorkTracks,
logoutLogId: closeResult.logoutLogId
}
}
} else {
responseData = {
success: true,
message: "Login Successfull",
@ -200,7 +200,7 @@ async function va_login_callback(req, res) {
email: user.userDetails.email,
}
}
// }
}
// Return accessToken and user info
return res.status(200).json(responseData);

View File

@ -1,6 +1,5 @@
import { LoggedModel, WorkTrackModel, ProjectModel, VAModel } from "../../../models/vaModel.js";
import authFetch from "../../../utils/authFetch.js";
import getDayNumber from "../../../utils/getDayNumber.js";
// Helper function to get start and end of day in UTC
function getDayRange(date = new Date()) {
@ -430,13 +429,11 @@ async function va_project_log_callback(req, res) {
const totalTodayHours = totalTodayMs / (1000 * 60 * 60);
const dayNumber = getDayNumber(logTime);
// Check if daily hours are already completed
if (project.dedicatedHours[dayNumber] > 0 && totalTodayHours >= project.dedicatedHours[dayNumber]) {
if (project.dedicatedHours > 0 && totalTodayHours >= project.dedicatedHours) {
return res.status(400).json({
success: false,
message: `Cannot login - daily hours already completed (${formatHoursToReadable(totalTodayHours)} / ${formatHoursToReadable(project.dedicatedHours[dayNumber])})`,
message: `Cannot login - daily hours already completed (${formatHoursToReadable(totalTodayHours)} / ${formatHoursToReadable(project.dedicatedHours)})`,
// warnings: scheduleCheck.warnings
});
}

View File

@ -1,6 +1,5 @@
import jwt from "jsonwebtoken";
import { ProjectModel, VAModel, LoggedModel } from "../../../models/vaModel.js";
import getDayNumber from "../../../utils/getDayNumber.js";
function formatHMS(ms) {
const totalSeconds = Math.floor(ms / 1000);
@ -111,15 +110,13 @@ async function va_projects_log_list_function(req, res) {
const lastLog = logs[logs.length - 1];
const activityStatus = lastLog?.loggedType === "LOGIN" ? "ACTIVE" : "AWAY";
const dayNumber = getDayNumber();
finalReport.push({
projectId: project._id,
projectName: project.name,
activityStatus,
totalWorkTime: formatHMS(totalWorkMs),
totalWorkMs,
dedicatedHours: formatHoursToReadable(project?.dedicatedHours[dayNumber]) || "0h",
dedicatedHours: formatHoursToReadable(project?.dedicatedHours),
totalLogs: logs.length,
status: project?.status
});

View File

@ -1,6 +1,5 @@
import jwt from "jsonwebtoken";
import { VAModel, ProjectModel, LoggedModel } from "../../../models/vaModel.js";
import getDayNumber from "../../../utils/getDayNumber.js";
async function va_timesheet_summary_function(req, res) {
try {
@ -163,10 +162,8 @@ async function va_timesheet_summary_function(req, res) {
}
};
const dayNumber = getDayNumber();
// Calculate dedicated hours and pending hours
const dedicatedHours = project.dedicatedHours[dayNumber] || 0;
const dedicatedHours = project.dedicatedHours || 0;
const totalWorkedHours = totalWorkSeconds / 3600; // Convert to hours
const pendingHours = Math.max(0, dedicatedHours - totalWorkedHours);

View File

@ -18,7 +18,7 @@ class VaClientService {
}
// Sanitize and prepare data
const clientData = await this.sanitizeClientData(data);
const clientData = this.sanitizeClientData(data);
const response = await this.clientRepository.create(clientData);
if(response) {
@ -263,17 +263,13 @@ class VaClientService {
}
// Helper method to sanitize data
async sanitizeClientData(payload) {
sanitizeClientData(payload) {
const { userDetails } = payload;
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(userDetails.password, saltRounds);
return {
userDetails: {
name: userDetails.name,
email: userDetails.email.toLowerCase(),
password: hashedPassword,
address: userDetails.address || null,
city: userDetails.city || null,
state: userDetails.state || null,

View File

@ -1,6 +0,0 @@
function getDayNumber(date = new Date()) {
const today = new Date(date);
return today.getUTCDay(); // getDay() already returns 0-6 for Sunday-Saturday
}
export default getDayNumber;

View File

@ -111,24 +111,6 @@
<label for="email" class="input-label">Email *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="password" id="password" class="input-field" placeholder=" " required autocomplete="new-password">
<label for="password" class="input-label">Create Password *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="password" id="confirmPassword" class="input-field" placeholder=" " required autocomplete="new-password">
<label for="confirmPassword" class="input-label">Confirm Password *</label>
</div>
<div class="invalid-feedback" id="password-match-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">Passwords do not match</div>
<div class="password-actions">
<button type="button" class="password-toggle" onclick="togglePasswordVisibility()">Show Password</button>
<button type="button" class="password-generate" onclick="generatePassword()">Generate Strong Password</button>
</div>
</div>
<div class="form-actions">
<div></div> <!-- Empty div for spacing -->
<button type="button" class="btn btn-primary" onclick="handleNextStep(2)">Continue</button>
@ -260,7 +242,7 @@
<div class="error-message" id="step-4-error-message"></div>
</div>
<p style="margin-bottom: 20px; color: #6b7280; font-size: 14px;">
We will use the details below in the agreement that we will send to your prior to starting the project.
We will use the details below in the agreement that we will send to your prior to entering the project.
</p>
<form id="signatory-form">
<div class="form-group">
@ -340,7 +322,7 @@
</div>
</div>
<div class="payment-option" onclick="selectPaymentOption('bank')">
<h4>Bank Account (ACH)</h4>
<h4>Bank Account (ACR)</h4>
<p>Direct bank transfer</p>
</div>
<div id="bank-details" class="payment-details">