va-tracker-middleware/public/js/pages/va-logs.js

301 lines
9.3 KiB
JavaScript

// API base URL
const API_BASE = '/api/v1/client';
function toggleAccordion(accordionId) {
const accordion = document.getElementById(accordionId);
accordion.classList.toggle('active');
// const allAccordions = document.querySelectorAll('.accordion');
// allAccordions.forEach(acc => {
// if (acc.id !== accordionId && acc.classList.contains('active')) {
// acc.classList.remove('active');
// }
// });
}
function formatTime(milliseconds) {
if (!milliseconds) return '0m';
const seconds = Math.floor(milliseconds / 1000);
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
if (hours > 0) {
return `${hours}h ${minutes}m ${seconds % 60}s`;
}
return `${minutes}m ${seconds % 60}s`;
}
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
}
function formatTimeOnly(dateString) {
const date = new Date(dateString);
return date.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
function calculateTotalIndividualLogsHours(workTrackLogs) {
let total = 0;
workTrackLogs.forEach(workTrack => {
if (workTrack.logs && Array.isArray(workTrack.logs)) {
workTrack.logs.forEach(log => {
total += log.totalTime || 0;
});
}
});
return total;
}
function countIndividualLogs(workTrackLogs) {
let count = 0;
workTrackLogs.forEach(workTrack => {
if (workTrack.logs && Array.isArray(workTrack.logs)) {
count += workTrack.logs.length;
}
});
return count;
}
function calculateTotalHours(logs) {
return logs.reduce((total, log) => total + (log.totalTime || 0), 0);
}
// Create project accordion
function createProjectAccordion(projectData, index) {
const workTrackLogs = projectData.workTrackLogs || [];
const totalHours = calculateTotalIndividualLogsHours(workTrackLogs);
const individualLogsCount = countIndividualLogs(workTrackLogs);
const projectName = projectData.projectDetails?.name || `Project ${index + 1}`;
const accordionId = `project-${index}`;
return `
<div class="accordion" id="${accordionId}">
<div class="accordion-header" onclick="toggleAccordion('${accordionId}')">
<div class="accordion-title">
${projectName}
<span class="logs-count">${individualLogsCount} activities</span>
</div>
<div style="display: flex; align-items: center; gap: 1rem;">
<div style="text-align: right;">
<div class="time-badge">${formatTime(totalHours)}</div>
</div>
<i class="fas fa-chevron-down accordion-icon"></i>
</div>
</div>
<div class="accordion-content">
${individualLogsCount > 0 ? createIndividualLogsTable(workTrackLogs) : createNoLogsMessage()}
</div>
</div>
`;
}
// Create table for individual log entries
function createIndividualLogsTable(workTrackLogs) {
// Collect all individual logs from all work tracks
const allIndividualLogs = [];
workTrackLogs.forEach(workTrack => {
if (workTrack.logs && Array.isArray(workTrack.logs)) {
workTrack.logs.forEach(log => {
allIndividualLogs.push(log);
});
}
});
return `
<div class="table-wrapper">
<table class="data-table">
<thead>
<tr>
<th>Title</th>
<th>App Name</th>
<th>From</th>
<th>To</th>
<th>Total Time</th>
</tr>
</thead>
<tbody>
${allIndividualLogs.map((log, index) => createIndividualLogRow(log, index)).join('')}
</tbody>
</table>
</div>
`;
}
// Create a single row for individual log entry
function createIndividualLogRow(log, index) {
const title = log.title || `Activity ${index + 1}`;
const appName = log.appName || 'Unknown App';
// Parse dates
const fromDate = log.from;
const toDate = log.to || log.from;
// Calculate duration
let duration = log.totalTime || 0;
if (!duration && fromDate && toDate) {
const from = new Date(fromDate);
const to = new Date(toDate);
duration = Math.floor((to - from) / 1000);
}
return `
<tr>
<td>
<strong>${title}</strong>
</td>
<td>${appName}</td>
<td>
<div class="datetime-cell">
<div class="date-text">${formatDate(fromDate)}</div>
<div class="time-text">${formatTimeOnly(fromDate)}</div>
</div>
</td>
<td>
<div class="datetime-cell">
<div class="date-text">${formatDate(toDate)}</div>
<div class="time-text">${formatTimeOnly(toDate)}</div>
</div>
</td>
<td>
<span class="time-badge">${formatTime(duration)}</span>
</td>
</tr>
`;
}
// Create no logs message
function createNoLogsMessage() {
return `
<div style="text-align: center; padding: 2rem; color: #64748b;">
<i class="fas fa-clipboard"></i>
<h4>No Activity Logs</h4>
<p>No detailed activity logs available for this project.</p>
</div>
`;
}
// Load VA logs
async function loadVALogs() {
const loadingState = document.getElementById('loadingState');
const emptyState = document.getElementById('emptyState');
const projectsContainer = document.getElementById('projectsContainer');
try {
// Get VA ID from URL
const urlParams = new URLSearchParams(window.location.search);
const vaId = urlParams.get('va');
if (!vaId) {
window.location.href = '/client/virtual-assistants';
return;
}
// Fetch VA logs data
const response = await fetchWithAuth(`${API_BASE}/vaLogsData/${vaId}`);
if (!response || !response.ok) {
throw new Error('Failed to load VA logs');
}
const data = await response.json();
if (!data.success) {
throw new Error(data.message || 'Failed to load VA logs');
}
// Hide loading state
loadingState.style.display = 'none';
const projectLogs = data.data?.projectLogs || [];
// Handle empty state
if (projectLogs.length === 0) {
emptyState.style.display = 'block';
projectsContainer.style.display = 'none';
return;
}
// Show projects container
projectsContainer.style.display = 'block';
emptyState.style.display = 'none';
// Create accordions for each project
let accordionsHTML = '';
projectLogs.forEach((project, index) => {
accordionsHTML += createProjectAccordion(project, index);
});
projectsContainer.innerHTML = accordionsHTML;
// Auto-open first accordion
if (projectLogs.length > 0) {
setTimeout(() => {
const firstAccordion = document.getElementById('project-0');
if (firstAccordion) {
firstAccordion.classList.add('active');
}
}, 100);
}
} catch (error) {
console.error("Error loading VA logs:", error);
loadingState.style.display = 'none';
projectsContainer.style.display = 'none';
// Show error in empty state
emptyState.innerHTML = `
<i class="fas fa-exclamation-triangle"></i>
<h3>Error Loading Data</h3>
<p>${error.message}</p>
<button onclick="loadVALogs()" class="btn btn-primary" style="margin-top: 1rem;">
<i class="fas fa-redo"></i> Retry
</button>
`;
emptyState.style.display = 'block';
}
}
document.addEventListener('DOMContentLoaded', function () {
// Mobile menu toggle
const menuToggle = document.querySelector('.mobile-menu-button');
const sidebar = document.querySelector('.dashboard-sidebar');
menuToggle.addEventListener('click', () => {
sidebar.classList.toggle('active');
});
// Close modal with escape key
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') {
closeLogoutModal();
}
});
document.getElementById('logoutModal').addEventListener('click', function (e) {
if (e.target === this) {
closeLogoutModal();
}
});
// Logout button - opens confirmation modal instead of directly logging out
document.getElementById('logoutButton').addEventListener('click', openLogoutModal);
// Confirm logout button
document.getElementById('confirmLogoutBtn').addEventListener('click', handleLogout);
// Load VA logs on page load
loadVALogs();
});