From 33779a6eb803589ce9de7befc5cd3a0c0356d0a6 Mon Sep 17 00:00:00 2001 From: Aakash Date: Wed, 17 Dec 2025 20:01:21 +0530 Subject: [PATCH] Added logs in client dashboard --- public/css/dashboard.css | 142 +++++++ .../clientDashboardGetVaLogs.controller.js | 190 +++------ views/client/dashboard-va-logs.ejs | 388 +++++++++++++----- 3 files changed, 485 insertions(+), 235 deletions(-) diff --git a/public/css/dashboard.css b/public/css/dashboard.css index 1921186..303b46f 100644 --- a/public/css/dashboard.css +++ b/public/css/dashboard.css @@ -1708,4 +1708,146 @@ tr[data-vaid]:hover { .va-action:hover { transform: scale(1); +} + + +/* Accordion Styles */ +.accordion { + margin-bottom: 1rem; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + background: var(--card-bg, #ffffff); +} + +.accordion-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1.25rem 1.5rem; + background: var(--primary-color, #4f46e5); + color: white; + cursor: pointer; + transition: background 0.3s ease; +} + +.accordion-header:hover { + background: var(--primary-dark, #4338ca); +} + +.accordion-title { + display: flex; + align-items: center; + gap: 1rem; + font-weight: 600; + font-size: 1.1rem; + text-transform: capitalize; +} + +.accordion-icon { + transition: transform 0.3s ease; +} + +.accordion.active .accordion-icon { + transform: rotate(180deg); +} + +.accordion-content { + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease-out; +} + +.accordion.active .accordion-content { + max-height: 5000px; +} + +.table-wrapper { + overflow-x: auto; +} + +.data-table { + width: 100%; + border-collapse: collapse; +} + +.data-table th { + background: var(--bg-secondary, #f8fafc); + padding: 1rem; + text-align: left; + font-weight: 600; + color: var(--text-primary, #1e293b); + border-bottom: 2px solid var(--border-color, #e2e8f0); +} + +.data-table td { + padding: 1rem; + border-bottom: 1px solid var(--border-color, #e2e8f0); + color: var(--text-primary, #1e293b); +} + +.data-table tr:hover { + background: var(--hover-bg, #f1f5f9); +} + +.no-data-row td { + text-align: center; + padding: 2rem; + color: var(--text-secondary, #64748b); +} + +.time-badge { + background: var(--success-color, #10b981); + color: white; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.875rem; + font-weight: 600; +} + +.loading-spinner { + text-align: center; + padding: 2rem; +} + +.spinner { + width: 40px; + height: 40px; + border: 4px solid var(--border-color, #e2e8f0); + border-top: 4px solid var(--primary-color, #4f46e5); + border-radius: 50%; + animation: spin 1s linear infinite; + margin: 0 auto; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.empty-state { + text-align: center; + padding: 3rem; + color: var(--text-secondary, #64748b); +} + +.empty-state i { + font-size: 3rem; + margin-bottom: 1rem; + color: var(--border-color, #e2e8f0); +} + +.empty-state h3 { + margin-bottom: 0.5rem; + color: var(--text-primary, #1e293b); +} + +.logs-count { + background: var(--primary-light, #c7d2fe); + color: var(--primary-color, #4f46e5); + padding: 0.125rem 0.5rem; + border-radius: 12px; + font-size: 0.75rem; + font-weight: 600; + margin-left: 0.5rem; } \ No newline at end of file diff --git a/src/controllers/client/dashboard/clientDashboardGetVaLogs.controller.js b/src/controllers/client/dashboard/clientDashboardGetVaLogs.controller.js index 06c1f2d..c8a700f 100644 --- a/src/controllers/client/dashboard/clientDashboardGetVaLogs.controller.js +++ b/src/controllers/client/dashboard/clientDashboardGetVaLogs.controller.js @@ -1,3 +1,5 @@ +import { VAModel, ClientModel, ProjectModel, WorkTrackModel } from "../../../models/vaModel.js"; + async function clientDashboardGetVaLogsController(req, res) { try { const clientId = req.body.clientId; @@ -12,95 +14,59 @@ async function clientDashboardGetVaLogsController(req, res) { return res.status(404).json({ success: false, message: "Virtual Assistant not found" }); } - // Get the last 15 days - const endDate = new Date(); - endDate.setHours(23, 59, 59, 999); // End of day - const startDate = new Date(); - startDate.setDate(startDate.getDate() - 15); - startDate.setHours(0, 0, 0, 0); // Start of day - - // Get shared projects + // Get shared active projects const sharedProjects = await ProjectModel.find({ clientId: clientUser._id, 'vaAssigned.vaId': vaId, status: "ACTIVE" - }).select('name hourlyRate dedicatedHours vaWorkingScheduleStart vaWorkingScheduleEnd isFlexibleAllowed'); + }).select('name'); if (!sharedProjects || sharedProjects.length === 0) { return res.status(200).json({ success: true, message: "No shared projects found", - data: {} + data: { projectLogs: [] } }); } - const projectLogs = {}; const projectIds = sharedProjects.map(p => p._id); - - // Get all work tracks for the last 15 days + + // Get work tracks for the last 15 days + const fifteenDaysAgo = new Date(); + fifteenDaysAgo.setDate(fifteenDaysAgo.getDate() - 15); + + // Get all work tracks const workTracks = await WorkTrackModel.find({ vaId: vaId, projectId: { $in: projectIds }, - from: { $gte: startDate }, - to: { $lte: endDate } + from: { $gte: fifteenDaysAgo } }) .sort({ from: -1 }) .lean(); - // Process each project - for (const project of sharedProjects) { - const projectIdStr = project._id.toString(); - + // Build project logs array + const projectLogs = []; + let totalWorkTracks = 0; + let totalHours = 0; + + sharedProjects.forEach(project => { // Filter tracks for this project const projectTracks = workTracks.filter(track => - track.projectId.toString() === projectIdStr + track.projectId.toString() === project._id.toString() ); - - // Group tracks by date - const tracksByDate = {}; - projectTracks.forEach(track => { - const dateKey = new Date(track.from).toISOString().split('T')[0]; - if (!tracksByDate[dateKey]) { - tracksByDate[dateKey] = []; - } - tracksByDate[dateKey].push(track); - }); - - // Create daily logs for the last 15 days - const dailyLogs = {}; - let totalProjectHours = 0; - let totalProjectTracks = 0; - - // Generate dates for the last 15 days - for (let i = 0; i < 15; i++) { - const date = new Date(); - date.setDate(date.getDate() - i); - date.setHours(0, 0, 0, 0); - const dateStr = date.toISOString().split('T')[0]; + + // Only add project if it has tracks + if (projectTracks.length > 0) { + // Calculate project total hours + const projectTotalHours = projectTracks.reduce((sum, track) => sum + (track.totalTime || 0), 0); - const dayTracks = tracksByDate[dateStr] || []; - const totalTimeDay = dayTracks.reduce((sum, track) => sum + (track.totalTime || 0), 0); - - // Calculate productive time vs idle time if logs are available - let productiveTime = 0; - let idleTime = 0; - - if (dayTracks.length > 0) { - dayTracks.forEach(track => { - if (track.logs && Array.isArray(track.logs)) { - track.logs.forEach(log => { - if (log.status === 'productive' || log.status === 'working') { - productiveTime += log.duration || 0; - } else if (log.status === 'idle' || log.status === 'away') { - idleTime += log.duration || 0; - } - }); - } - }); - } - - dailyLogs[dateStr] = { - logs: dayTracks.map(track => ({ + projectLogs.push({ + projectDetails: { + id: project._id, + name: project.name, + hourlyRate: project.hourlyRate + }, + workTrackLogs: projectTracks.map(track => ({ id: track._id, from: track.from, to: track.to, @@ -108,86 +74,44 @@ async function clientDashboardGetVaLogsController(req, res) { logs: track.logs || [], createdAt: track.createdAt, updatedAt: track.updatedAt - })), - summary: { - totalTracks: dayTracks.length, - totalTime: totalTimeDay, - productiveTime: productiveTime, - idleTime: idleTime, - efficiency: totalTimeDay > 0 ? (productiveTime / totalTimeDay) * 100 : 0 - } - }; - - totalProjectHours += totalTimeDay; - totalProjectTracks += dayTracks.length; + })) + }); + + totalWorkTracks += projectTracks.length; + totalHours += projectTotalHours; } + }); - projectLogs[projectIdStr] = { - projectDetails: { - id: project._id, - name: project.name, - hourlyRate: project.hourlyRate, - schedule: { - start: project.vaWorkingScheduleStart, - end: project.vaWorkingScheduleEnd, - isFlexibleAllowed: project.isFlexibleAllowed - } - }, - dailyLogs: dailyLogs, - summary: { - totalWorkTracks: totalProjectTracks, - totalHours: totalProjectHours, - averageDailyHours: totalProjectHours / 15, - daysWithActivity: Object.values(dailyLogs).filter(day => day.logs.length > 0).length, - lastActivity: projectTracks.length > 0 - ? new Date(projectTracks[0].from) - : null + const response = { + success: true, + message: "VA logs retrieved successfully", + data: { + projectLogs: projectLogs + } + }; + + // Add summary if needed (optional) + if (projectLogs.length > 0) { + response.data.summary = { + totalProjects: projectLogs.length, + totalWorkTracks: totalWorkTracks, + totalHours: totalHours, + dateRange: { + startDate: fifteenDaysAgo.toISOString().split('T')[0], + endDate: new Date().toISOString().split('T')[0] } }; } - // Calculate overall statistics - const allTracks = workTracks; - const totalHoursAllProjects = allTracks.reduce((sum, track) => sum + (track.totalTime || 0), 0); - - return res.status(200).json({ - success: true, - message: "VA logs retrieved successfully", - data: { - vaDetails: { - id: vaUser._id, - name: `${vaUser.userDetails?.firstName || ''} ${vaUser.userDetails?.lastName || ''}`.trim(), - email: vaUser.userDetails?.email, - status: vaUser.status, - schedule: vaUser.schedule - }, - dateRange: { - startDate: startDate.toISOString().split('T')[0], - endDate: endDate.toISOString().split('T')[0], - days: 15 - }, - projectLogs: projectLogs, - summary: { - totalProjects: sharedProjects.length, - totalWorkTracks: allTracks.length, - totalHours: totalHoursAllProjects, - averageDailyHours: totalHoursAllProjects / 15, - projectsWithActivity: Object.values(projectLogs) - .filter(project => project.summary.totalWorkTracks > 0).length - } - } - }); + return res.status(200).json(response); } catch (error) { console.error('Error fetching VA logs:', error); return res.status(500).json({ success: false, - message: 'Failed to load VA logs', - error: process.env.NODE_ENV === 'development' ? error.message : undefined + message: 'Failed to load VA logs' }); } } -export { - clientDashboardGetVaLogsController -}; \ No newline at end of file +export { clientDashboardGetVaLogsController }; \ No newline at end of file diff --git a/views/client/dashboard-va-logs.ejs b/views/client/dashboard-va-logs.ejs index 0a98804..b88b2ed 100644 --- a/views/client/dashboard-va-logs.ejs +++ b/views/client/dashboard-va-logs.ejs @@ -9,20 +9,6 @@ - -