@page "/dashboard" @attribute [Authorize] @rendermode InteractiveServer @implements IDisposable @inject ICVCheckService CVCheckService @inject NavigationManager NavigationManager @inject AuthenticationStateProvider AuthenticationStateProvider @inject ILogger Logger @inject IJSRuntime JSRuntime @inject RealCV.Web.Services.IPdfReportService PdfReportService @inject IAuditService AuditService Dashboard - RealCV

Dashboard

View and manage your CV verification checks

New Check
@if (_isLoading) {
Loading...

Loading your checks...

} else if (!string.IsNullOrEmpty(_errorMessage)) { } else if (_checks.Count == 0) {

No CV Checks Yet

Upload your first CV to begin verifying employment history against official company records.

Upload Your First CV
} else {
@_checks.Count
Total Checks
@_checks.Count(c => c.Status == "Completed")
Completed
@_checks.Count(c => c.Status is "Pending" or "Processing")
In Progress
Recent CV Checks
@if (_selectedIds.Count > 0) { @_selectedIds.Count selected }
@if (_selectedIds.Count > 0) { } @_checks.Count total
@foreach (var check in _checks) { }
Candidate Uploaded Status Score Actions

@(!string.IsNullOrEmpty(check.CandidateName) ? check.CandidateName : Path.GetFileNameWithoutExtension(check.OriginalFileName))

@Path.GetExtension(check.OriginalFileName).ToUpperInvariant()

@check.CreatedAt.ToString("dd MMM yyyy")

@check.CreatedAt.ToString("HH:mm")
@switch (check.Status) { case "Completed": Completed break; case "Processing": @(check.ProcessingStage ?? "Processing") break; case "Pending": Queued break; case "Failed": Failed break; default: @check.Status break; } @if (check.VeracityScore.HasValue) {
@check.VeracityScore
} else { -- }
@if (check.Status == "Completed") { View Report } else if (check.Status is "Pending" or "Processing") { } else { Retry }
}
@if (_showDeleteModal) { } @code { private List _checks = []; private bool _isLoading = true; private bool _isExporting; private bool _isDeleting; private string? _errorMessage; private Guid _userId; private System.Threading.Timer? _pollingTimer; private volatile bool _isPolling; private volatile bool _disposed; private volatile bool _isOperationInProgress; // Delete confirmation modal state private bool _showDeleteModal; private CVCheckDto? _checkToDelete; private List _checksToDelete = []; private bool _isBulkDelete; // Selection state private HashSet _selectedIds = []; protected override async Task OnInitializedAsync() { await LoadChecks(); StartPollingIfNeeded(); } private void StartPollingIfNeeded() { if (HasProcessingChecks() && !_isPolling && !_disposed) { _isPolling = true; _pollingTimer = new System.Threading.Timer(async _ => { if (_disposed) return; try { await InvokeAsync(async () => { if (_disposed) return; await LoadChecks(); if (!HasProcessingChecks()) { StopPolling(); } StateHasChanged(); }); } catch (ObjectDisposedException) { // Component was disposed, ignore } }, null, TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(3)); } } private bool HasProcessingChecks() { foreach (var c in _checks) { if (c.Status == "Processing" || c.Status == "Pending") return true; } return false; } private void StopPolling() { _isPolling = false; _pollingTimer?.Dispose(); _pollingTimer = null; } public void Dispose() { _disposed = true; StopPolling(); } private async Task LoadChecks() { if (_isOperationInProgress) return; _isOperationInProgress = true; _isLoading = true; _errorMessage = null; try { var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); var userIdClaim = authState.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out _userId)) { _errorMessage = "Unable to identify user. Please log in again."; return; } _checks = await CVCheckService.GetUserChecksAsync(_userId) ?? []; } catch (Exception ex) { Logger.LogError(ex, "Error loading CV checks"); _errorMessage = "An error occurred while loading checks. Please try again."; } finally { _isLoading = false; _isOperationInProgress = false; } } private void ViewReport(CVCheckDto check) { if (check.Status == "Completed") { NavigationManager.NavigateTo($"/report/{check.Id}"); } } private static string GetScoreBadgeClass(int score) { return score switch { > 70 => "bg-success", >= 50 => "bg-warning text-dark", _ => "bg-danger" }; } private static string GetScoreBadgeColorClass(int score) { return score switch { > 70 => "score-high", >= 50 => "score-medium", _ => "score-low" }; } private static string GetScoreRingClass(int score) => score > 70 ? "high" : score >= 50 ? "medium" : "low"; private static string GetScoreTextClass(int score) => score > 70 ? "text-verified" : score >= 50 ? "text-warning-dark" : "text-danger"; private static string GetScoreDashArray(int score) => score.ToString(); private async Task ExportToPdf() { if (_isExporting) return; _isExporting = true; StateHasChanged(); try { var reportDataList = new List(); foreach (var check in _checks) { if (check.Status != "Completed") continue; var report = await CVCheckService.GetReportAsync(check.Id, _userId); if (report is null) continue; int verifiedCount = 0; int unverifiedCount = 0; foreach (var v in report.EmploymentVerifications) { if (v.IsVerified) verifiedCount++; else unverifiedCount++; } int criticalFlags = 0; int warningFlags = 0; foreach (var f in report.Flags) { if (f.Severity == "Critical") criticalFlags++; else if (f.Severity == "Warning") warningFlags++; } reportDataList.Add(new RealCV.Web.Services.PdfReportData { CandidateName = report.CandidateName ?? Path.GetFileNameWithoutExtension(check.OriginalFileName) ?? "Unknown", UploadDate = check.CreatedAt, Score = report.OverallScore, ScoreLabel = report.ScoreLabel, VerifiedEmployers = verifiedCount, UnverifiedEmployers = unverifiedCount, GapMonths = report.TimelineAnalysis.TotalGapMonths, OverlapMonths = report.TimelineAnalysis.TotalOverlapMonths, CriticalFlags = criticalFlags, WarningFlags = warningFlags }); } var pdfBytes = PdfReportService.GenerateReport(reportDataList); var base64 = Convert.ToBase64String(pdfBytes); var fileName = "RealCV_Report_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".pdf"; await JSRuntime.InvokeVoidAsync("downloadFile", fileName, base64, "application/pdf"); await AuditService.LogAsync(_userId, AuditActions.ReportExported, null, null, $"Exported {reportDataList.Count} reports to PDF"); } catch (Exception ex) { Logger.LogError(ex, "Error exporting PDF"); } finally { _isExporting = false; StateHasChanged(); } } private bool HasCompletedChecks() { foreach (var c in _checks) { if (c.Status == "Completed") return true; } return false; } // Selection methods private void ToggleSelection(Guid id) { if (_selectedIds.Contains(id)) _selectedIds.Remove(id); else _selectedIds.Add(id); } private void ToggleSelectAll() { if (IsAllSelected()) _selectedIds.Clear(); else _selectedIds = _checks.Select(c => c.Id).ToHashSet(); } private bool IsAllSelected() => _checks.Count > 0 && _selectedIds.Count == _checks.Count; // Single delete private void ConfirmDelete(CVCheckDto check) { _checkToDelete = check; _checksToDelete = []; _isBulkDelete = false; _showDeleteModal = true; } // Bulk delete private void ConfirmDeleteSelected() { _checksToDelete = _checks.Where(c => _selectedIds.Contains(c.Id)).ToList(); _checkToDelete = null; _isBulkDelete = true; _showDeleteModal = true; } private void CancelDelete() { _showDeleteModal = false; _checkToDelete = null; _checksToDelete = []; _isBulkDelete = false; } private async Task ExecuteDelete() { if (_isDeleting) return; _isDeleting = true; try { if (_isBulkDelete) { var failedCount = 0; foreach (var check in _checksToDelete) { var success = await CVCheckService.DeleteCheckAsync(check.Id, _userId); if (success) { _checks.RemoveAll(c => c.Id == check.Id); _selectedIds.Remove(check.Id); } else { failedCount++; } } if (failedCount > 0) { _errorMessage = $"Failed to delete {failedCount} CV check(s). Please try again."; } } else if (_checkToDelete != null) { var success = await CVCheckService.DeleteCheckAsync(_checkToDelete.Id, _userId); if (success) { _checks.RemoveAll(c => c.Id == _checkToDelete.Id); _selectedIds.Remove(_checkToDelete.Id); } else { _errorMessage = "Failed to delete the CV check. Please try again."; } } } catch (Exception ex) { Logger.LogError(ex, "Error deleting CV check(s)"); _errorMessage = "Failed to delete CV check(s). Please try again."; } finally { _isDeleting = false; _showDeleteModal = false; _checkToDelete = null; _checksToDelete = []; _isBulkDelete = false; } } }