@@ -236,7 +236,7 @@ <h5 class="text-muted">No Invitations Created</h5>
236236 <button class="btn btn-outline-info" onclick="viewInvitationUrl('${ invitation . invitation_token } ', '${ invitation . candidate_name } ', '${ invitation . email } ')" title="View URL">
237237 <i class="fas fa-link"></i>
238238 </button>
239- <button class="btn btn-outline-primary" onclick="showActivity(${ invitation . id } , '${ invitation . candidate_name } ')" title="View Activity">
239+ <button class="btn btn-outline-primary" onclick="showActivity(${ invitation . target_user_id } , '${ invitation . candidate_name } ')" title="View Activity">
240240 <i class="fas fa-chart-line"></i>
241241 </button>
242242 ${ invitation . is_active ? `
@@ -368,7 +368,7 @@ <h5 class="text-muted">No Invitations Created</h5>
368368 } ) ;
369369}
370370
371- function showActivity ( invitationId , candidateName ) {
371+ function showActivity ( userId , candidateName ) {
372372 document . getElementById ( 'activityCandidateName' ) . textContent = candidateName ;
373373
374374 const modal = new bootstrap . Modal ( document . getElementById ( 'activityModal' ) ) ;
@@ -384,22 +384,136 @@ <h5 class="text-muted">No Invitations Created</h5>
384384 </div>
385385 ` ;
386386
387- // Note: This would need the user_id, not invitation_id
388- // For now, show placeholder
389- setTimeout ( ( ) => {
387+ // Check if userId is null (no user account created yet)
388+ if ( ! userId || userId === 'null' ) {
390389 document . getElementById ( 'activityContent' ) . innerHTML = `
391390 <div class="alert alert-info">
392- <h6>Activity Tracking</h6>
393- <p class="mb-0">Detailed candidate activity tracking will be displayed here, including:</p>
394- <ul class="mb-0">
395- <li>Query attempts and results</li>
396- <li>Time spent on assessment</li>
397- <li>Pages visited and navigation patterns</li>
398- <li>Challenge completion status</li>
399- </ul>
391+ <h6><i class="fas fa-info-circle me-2"></i>No Account Created Yet</h6>
392+ <p class="mb-0">This candidate hasn't used their invitation link yet, so no user account exists.</p>
393+ <p class="mb-0 mt-2"><small>Activity will appear here once they access their invitation URL and perform actions.</small></p>
394+ </div>
395+ ` ;
396+ return ;
397+ }
398+
399+ // Fetch the actual activity data directly with the user_id
400+ fetch ( `/api/admin/candidates/${ userId } /activity` )
401+ . then ( response => response . json ( ) )
402+ . then ( data => {
403+ if ( data . success ) {
404+ displayCandidateActivity ( data . activities , data . summary ) ;
405+ } else {
406+ throw new Error ( data . error || 'Failed to load activity' ) ;
407+ }
408+ } )
409+ . catch ( error => {
410+ console . error ( 'Error loading candidate activity:' , error ) ;
411+ document . getElementById ( 'activityContent' ) . innerHTML = `
412+ <div class="alert alert-danger">
413+ <h6>Error Loading Activity</h6>
414+ <p class="mb-0">Unable to load candidate activity: ${ error . message } </p>
415+ <p class="mb-0 mt-2"><small>This might be because the candidate hasn't performed any activities yet, or there was a technical issue.</small></p>
416+ </div>
417+ ` ;
418+ } ) ;
419+ }
420+
421+ function displayCandidateActivity ( activities , summary ) {
422+ const container = document . getElementById ( 'activityContent' ) ;
423+
424+ if ( ! activities || activities . length === 0 ) {
425+ container . innerHTML = `
426+ <div class="alert alert-info">
427+ <h6><i class="fas fa-info-circle me-2"></i>No Activity Yet</h6>
428+ <p class="mb-0">This candidate hasn't performed any tracked activities yet.</p>
429+ <p class="mb-0 mt-2"><small>Activities include: query execution, page navigation, login/logout events, and impersonation sessions.</small></p>
430+ </div>
431+ ` ;
432+ return ;
433+ }
434+
435+ let html = `
436+ <div class="row mb-3">
437+ <div class="col-md-6">
438+ <div class="card border-0 bg-light">
439+ <div class="card-body text-center py-2">
440+ <h6 class="mb-1">${ summary ?. activity_count || activities . length } </h6>
441+ <small class="text-muted">Total Activities</small>
442+ </div>
443+ </div>
400444 </div>
445+ <div class="col-md-6">
446+ <div class="card border-0 bg-light">
447+ <div class="card-body text-center py-2">
448+ <h6 class="mb-1">${ summary ?. query_attempts || 0 } </h6>
449+ <small class="text-muted">Query Attempts</small>
450+ </div>
451+ </div>
452+ </div>
453+ </div>
454+
455+ <h6 class="mb-3">Recent Activity</h6>
456+ <div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
457+ <table class="table table-sm table-hover">
458+ <thead class="table-light sticky-top">
459+ <tr>
460+ <th>Time</th>
461+ <th>Activity</th>
462+ <th>Details</th>
463+ <th>Duration</th>
464+ </tr>
465+ </thead>
466+ <tbody>
467+ ` ;
468+
469+ activities . forEach ( activity => {
470+ const timestamp = new Date ( activity . timestamp ) . toLocaleString ( ) ;
471+ const activityIcon = getActivityIcon ( activity . activity_type ) ;
472+ const activityClass = getActivityClass ( activity . activity_type , activity . success ) ;
473+ const duration = activity . execution_time_ms ? `${ activity . execution_time_ms } ms` : '-' ;
474+
475+ html += `
476+ <tr class="${ activityClass } ">
477+ <td><small>${ timestamp } </small></td>
478+ <td>
479+ <span class="badge bg-secondary me-1">${ activityIcon } </span>
480+ <small>${ activity . activity_type . replace ( '_' , ' ' ) } </small>
481+ </td>
482+ <td>
483+ <small>${ activity . details || '-' } </small>
484+ ${ activity . query_text ? `<br><code style="font-size: 0.7em;">${ activity . query_text . substring ( 0 , 100 ) } ${ activity . query_text . length > 100 ? '...' : '' } </code>` : '' }
485+ ${ activity . error_message ? `<br><small class="text-danger">Error: ${ activity . error_message } </small>` : '' }
486+ </td>
487+ <td><small>${ duration } </small></td>
488+ </tr>
401489 ` ;
402- } , 1000 ) ;
490+ } ) ;
491+
492+ html += '</tbody></table></div>' ;
493+ container . innerHTML = html ;
494+ }
495+
496+ function getActivityIcon ( activityType ) {
497+ const icons = {
498+ 'query_executed' : 'π' ,
499+ 'page_access' : 'π' ,
500+ 'candidate_login' : 'π' ,
501+ 'candidate_logout' : 'πͺ' ,
502+ 'impersonation_started' : 'π€' ,
503+ 'impersonation_ended' : 'β' ,
504+ 'impersonated_page_access' : 'ππ€'
505+ } ;
506+ return icons [ activityType ] || 'π' ;
507+ }
508+
509+ function getActivityClass ( activityType , success ) {
510+ if ( activityType === 'query_executed' && success === false ) {
511+ return 'table-warning' ;
512+ }
513+ if ( activityType . includes ( 'impersonation' ) ) {
514+ return 'table-info' ;
515+ }
516+ return '' ;
403517}
404518
405519function refreshInvitations ( ) {
0 commit comments