Add multi-file upload, auto-refresh reports, and CSV export
- Allow recruiters to upload multiple CVs at once with batch processing - Add auto-refresh polling on Report page during CV processing - Add CSV export button on Dashboard for completed check summaries - Update logo and reset to Bootstrap default styling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
@page "/report/{Id:guid}"
|
||||
@attribute [Authorize]
|
||||
@rendermode InteractiveServer
|
||||
@implements IDisposable
|
||||
|
||||
@inject ICVCheckService CVCheckService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
@inject ILogger<Report> Logger
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<PageTitle>Verification Report - TrueCV</PageTitle>
|
||||
|
||||
@@ -96,7 +98,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-outline-primary" @onclick="DownloadReport">
|
||||
<button class="btn btn-outline-primary" @onclick="DownloadReport" disabled="@(_report is null)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-download me-1" viewBox="0 0 16 16">
|
||||
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
|
||||
<path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/>
|
||||
@@ -468,10 +470,48 @@
|
||||
private bool _isLoading = true;
|
||||
private string? _errorMessage;
|
||||
private Guid _userId;
|
||||
private System.Threading.Timer? _pollingTimer;
|
||||
private bool _isPolling;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadData();
|
||||
StartPollingIfNeeded();
|
||||
}
|
||||
|
||||
private void StartPollingIfNeeded()
|
||||
{
|
||||
if (_check is not null && (_check.Status == "Processing" || _check.Status == "Pending") && !_isPolling)
|
||||
{
|
||||
_isPolling = true;
|
||||
_pollingTimer = new System.Threading.Timer(async _ =>
|
||||
{
|
||||
await InvokeAsync(async () =>
|
||||
{
|
||||
await LoadData();
|
||||
|
||||
// Stop polling if processing is complete or failed
|
||||
if (_check is null || _check.Status == "Completed" || _check.Status == "Failed")
|
||||
{
|
||||
StopPolling();
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
});
|
||||
}, null, TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(3));
|
||||
}
|
||||
}
|
||||
|
||||
private void StopPolling()
|
||||
{
|
||||
_isPolling = false;
|
||||
_pollingTimer?.Dispose();
|
||||
_pollingTimer = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopPolling();
|
||||
}
|
||||
|
||||
private async Task LoadData()
|
||||
@@ -524,9 +564,76 @@
|
||||
await LoadData();
|
||||
}
|
||||
|
||||
private void DownloadReport()
|
||||
private async Task DownloadReport()
|
||||
{
|
||||
// TODO: Implement report download functionality
|
||||
if (_report is null || _check is null) return;
|
||||
|
||||
try
|
||||
{
|
||||
var reportData = new
|
||||
{
|
||||
CandidateName = _check.OriginalFileName,
|
||||
GeneratedAt = _report.GeneratedAt,
|
||||
OverallScore = _report.OverallScore,
|
||||
ScoreLabel = _report.ScoreLabel,
|
||||
EmploymentVerifications = _report.EmploymentVerifications.Select(v => new
|
||||
{
|
||||
v.ClaimedCompany,
|
||||
ClaimedStartDate = v.ClaimedStartDate?.ToString("yyyy-MM"),
|
||||
ClaimedEndDate = v.ClaimedEndDate?.ToString("yyyy-MM"),
|
||||
v.MatchedCompanyName,
|
||||
v.MatchedCompanyNumber,
|
||||
v.MatchScore,
|
||||
v.IsVerified,
|
||||
v.VerificationNotes
|
||||
}),
|
||||
TimelineAnalysis = new
|
||||
{
|
||||
_report.TimelineAnalysis.TotalGapMonths,
|
||||
_report.TimelineAnalysis.TotalOverlapMonths,
|
||||
Gaps = _report.TimelineAnalysis.Gaps.Select(g => new
|
||||
{
|
||||
StartDate = g.StartDate.ToString("yyyy-MM"),
|
||||
EndDate = g.EndDate.ToString("yyyy-MM"),
|
||||
g.Months
|
||||
}),
|
||||
Overlaps = _report.TimelineAnalysis.Overlaps.Select(o => new
|
||||
{
|
||||
o.Company1,
|
||||
o.Company2,
|
||||
OverlapStart = o.OverlapStart.ToString("yyyy-MM"),
|
||||
OverlapEnd = o.OverlapEnd.ToString("yyyy-MM"),
|
||||
o.Months
|
||||
})
|
||||
},
|
||||
Flags = _report.Flags.Select(f => new
|
||||
{
|
||||
f.Severity,
|
||||
f.Title,
|
||||
f.Description,
|
||||
f.ScoreImpact
|
||||
})
|
||||
};
|
||||
|
||||
var json = System.Text.Json.JsonSerializer.Serialize(reportData, new System.Text.Json.JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
|
||||
var fileName = $"TrueCV_Report_{Path.GetFileNameWithoutExtension(_check.OriginalFileName)}_{DateTime.Now:yyyyMMdd}.json";
|
||||
await DownloadFileAsync(fileName, json, "application/json");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error downloading report");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadFileAsync(string fileName, string content, string contentType)
|
||||
{
|
||||
var bytes = System.Text.Encoding.UTF8.GetBytes(content);
|
||||
var base64 = Convert.ToBase64String(bytes);
|
||||
await JSRuntime.InvokeVoidAsync("downloadFile", fileName, base64, contentType);
|
||||
}
|
||||
|
||||
private static string GetScoreColorClass(int score)
|
||||
|
||||
Reference in New Issue
Block a user