2026-01-18 19:20:50 +01:00
|
|
|
@page "/report/{Id:guid}"
|
|
|
|
|
@attribute [Authorize]
|
|
|
|
|
@rendermode InteractiveServer
|
|
|
|
|
|
|
|
|
|
@inject ICVCheckService CVCheckService
|
|
|
|
|
@inject NavigationManager NavigationManager
|
|
|
|
|
@inject AuthenticationStateProvider AuthenticationStateProvider
|
2026-01-20 16:45:43 +01:00
|
|
|
@inject ILogger<Report> Logger
|
2026-01-18 19:20:50 +01:00
|
|
|
|
|
|
|
|
<PageTitle>Verification Report - TrueCV</PageTitle>
|
|
|
|
|
|
|
|
|
|
<div class="container py-5">
|
|
|
|
|
@if (_isLoading)
|
|
|
|
|
{
|
|
|
|
|
<div class="text-center py-5">
|
|
|
|
|
<div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;">
|
|
|
|
|
<span class="visually-hidden">Loading...</span>
|
|
|
|
|
</div>
|
|
|
|
|
<p class="mt-3 text-muted">Loading report...</p>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
else if (_errorMessage is not null)
|
|
|
|
|
{
|
|
|
|
|
<div class="alert alert-danger" role="alert">
|
|
|
|
|
@_errorMessage
|
|
|
|
|
</div>
|
|
|
|
|
<a href="/dashboard" class="btn btn-primary">Back to Dashboard</a>
|
|
|
|
|
}
|
|
|
|
|
else if (_check is not null && _check.Status != "Completed")
|
|
|
|
|
{
|
|
|
|
|
<div class="text-center py-5">
|
|
|
|
|
<div class="card border-0 shadow-sm mx-auto" style="max-width: 500px;">
|
|
|
|
|
<div class="card-body p-5">
|
|
|
|
|
@if (_check.Status == "Processing")
|
|
|
|
|
{
|
|
|
|
|
<div class="spinner-border text-primary mb-3" role="status" style="width: 3rem; height: 3rem;">
|
|
|
|
|
<span class="visually-hidden">Processing...</span>
|
|
|
|
|
</div>
|
|
|
|
|
<h4 class="mb-2">Processing Your CV</h4>
|
|
|
|
|
<p class="text-muted mb-4">Our AI is analyzing the document. This usually takes 1-2 minutes.</p>
|
|
|
|
|
<div class="progress" style="height: 8px;">
|
|
|
|
|
<div class="progress-bar progress-bar-striped progress-bar-animated"
|
|
|
|
|
role="progressbar"
|
|
|
|
|
style="width: 60%">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
else if (_check.Status == "Pending")
|
|
|
|
|
{
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" class="bi bi-hourglass-split text-warning mb-3" viewBox="0 0 16 16">
|
|
|
|
|
<path d="M2.5 15a.5.5 0 1 1 0-1h1v-1a4.5 4.5 0 0 1 2.557-4.06c.29-.139.443-.377.443-.59v-.7c0-.213-.154-.451-.443-.59A4.5 4.5 0 0 1 3.5 3V2h-1a.5.5 0 0 1 0-1h11a.5.5 0 0 1 0 1h-1v1a4.5 4.5 0 0 1-2.557 4.06c-.29.139-.443.377-.443.59v.7c0 .213.154.451.443.59A4.5 4.5 0 0 1 12.5 13v1h1a.5.5 0 0 1 0 1h-11zm2-13v1c0 .537.12 1.045.337 1.5h6.326c.216-.455.337-.963.337-1.5V2h-7zm3 6.35c0 .701-.478 1.236-1.011 1.492A3.5 3.5 0 0 0 4.5 13s.866-1.299 3-1.48V8.35zm1 0v3.17c2.134.181 3 1.48 3 1.48a3.5 3.5 0 0 0-1.989-3.158C8.978 9.586 8.5 9.052 8.5 8.351z"/>
|
|
|
|
|
</svg>
|
|
|
|
|
<h4 class="mb-2">Queued for Processing</h4>
|
|
|
|
|
<p class="text-muted">Your CV is in the queue and will be processed shortly.</p>
|
|
|
|
|
}
|
|
|
|
|
else if (_check.Status == "Failed")
|
|
|
|
|
{
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" class="bi bi-exclamation-triangle text-danger mb-3" viewBox="0 0 16 16">
|
|
|
|
|
<path d="M7.938 2.016A.13.13 0 0 1 8.002 2a.13.13 0 0 1 .063.016.146.146 0 0 1 .054.057l6.857 11.667c.036.06.035.124.002.183a.163.163 0 0 1-.054.06.116.116 0 0 1-.066.017H1.146a.115.115 0 0 1-.066-.017.163.163 0 0 1-.054-.06.176.176 0 0 1 .002-.183L7.884 2.073a.147.147 0 0 1 .054-.057zm1.044-.45a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566z"/>
|
|
|
|
|
<path d="M7.002 12a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 5.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995z"/>
|
|
|
|
|
</svg>
|
|
|
|
|
<h4 class="mb-2">Processing Failed</h4>
|
|
|
|
|
<p class="text-muted">We encountered an error processing your CV. Please try uploading again.</p>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
<p class="text-muted small mt-4">
|
|
|
|
|
File: @_check.OriginalFileName<br />
|
|
|
|
|
Uploaded: @_check.CreatedAt.ToString("dd MMM yyyy HH:mm")
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<button class="btn btn-outline-primary mt-3" @onclick="RefreshStatus">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise me-1" viewBox="0 0 16 16">
|
|
|
|
|
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
|
|
|
|
|
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
|
|
|
|
|
</svg>
|
|
|
|
|
Refresh Status
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
else if (_report is not null && _check is not null)
|
|
|
|
|
{
|
|
|
|
|
<!-- Report Header -->
|
|
|
|
|
<div class="row mb-4">
|
|
|
|
|
<div class="col">
|
|
|
|
|
<nav aria-label="breadcrumb">
|
|
|
|
|
<ol class="breadcrumb">
|
|
|
|
|
<li class="breadcrumb-item"><a href="/dashboard">Dashboard</a></li>
|
|
|
|
|
<li class="breadcrumb-item active" aria-current="page">Report</li>
|
|
|
|
|
</ol>
|
|
|
|
|
</nav>
|
|
|
|
|
<h1 class="fw-bold">Verification Report</h1>
|
|
|
|
|
<p class="text-muted">
|
|
|
|
|
@_check.OriginalFileName | Generated @_report.GeneratedAt.ToString("dd MMM yyyy HH:mm")
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-auto">
|
|
|
|
|
<button class="btn btn-outline-primary" @onclick="DownloadReport">
|
|
|
|
|
<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"/>
|
|
|
|
|
</svg>
|
|
|
|
|
Download Report
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Score Card -->
|
|
|
|
|
<div class="row mb-4">
|
|
|
|
|
<div class="col-12">
|
|
|
|
|
<div class="card border-0 shadow-sm">
|
|
|
|
|
<div class="card-body p-4">
|
|
|
|
|
<div class="row align-items-center">
|
|
|
|
|
<div class="col-md-4 text-center border-end">
|
|
|
|
|
<div class="score-circle @GetScoreColorClass(_report.OverallScore) mx-auto mb-2">
|
|
|
|
|
<span class="score-value">@_report.OverallScore</span>
|
|
|
|
|
</div>
|
|
|
|
|
<h5 class="mb-0">@_report.ScoreLabel</h5>
|
|
|
|
|
<small class="text-muted">Veracity Score</small>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-md-8">
|
|
|
|
|
<div class="row text-center">
|
|
|
|
|
<div class="col-4">
|
|
|
|
|
<h3 class="mb-0 text-primary">@_report.EmploymentVerifications.Count</h3>
|
|
|
|
|
<small class="text-muted">Employers Checked</small>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-4">
|
|
|
|
|
<h3 class="mb-0 text-warning">@_report.TimelineAnalysis.TotalGapMonths</h3>
|
|
|
|
|
<small class="text-muted">Months of Gaps</small>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-4">
|
|
|
|
|
<h3 class="mb-0 text-danger">@_report.Flags.Count</h3>
|
|
|
|
|
<small class="text-muted">Flags Raised</small>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Employment Verification -->
|
|
|
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
|
|
|
<div class="card-header bg-white py-3">
|
|
|
|
|
<h5 class="mb-0 fw-bold">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-building me-2" viewBox="0 0 16 16">
|
|
|
|
|
<path d="M4 2.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1Zm3 0a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1Zm3.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1ZM4 5.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1ZM7.5 5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1Zm2.5.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1ZM4.5 8a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1Zm2.5.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1ZM10.5 8a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1Z"/>
|
|
|
|
|
<path d="M2 1a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V1Zm11 0H3v14h3v-2.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 .5.5V15h3V1Z"/>
|
|
|
|
|
</svg>
|
|
|
|
|
Employment Verification
|
|
|
|
|
</h5>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="card-body p-0">
|
|
|
|
|
<div class="table-responsive">
|
|
|
|
|
<table class="table table-hover mb-0">
|
|
|
|
|
<thead class="table-light">
|
|
|
|
|
<tr>
|
|
|
|
|
<th>Claimed Employer</th>
|
|
|
|
|
<th>Period</th>
|
|
|
|
|
<th>Matched Company</th>
|
|
|
|
|
<th class="text-center">Match Score</th>
|
|
|
|
|
<th class="text-center">Status</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
@foreach (var verification in _report.EmploymentVerifications)
|
|
|
|
|
{
|
|
|
|
|
<tr>
|
|
|
|
|
<td class="fw-medium">@verification.ClaimedCompany</td>
|
|
|
|
|
<td>
|
|
|
|
|
@if (verification.ClaimedStartDate.HasValue)
|
|
|
|
|
{
|
|
|
|
|
<span>@verification.ClaimedStartDate.Value.ToString("MMM yyyy")</span>
|
|
|
|
|
<span> - </span>
|
|
|
|
|
@if (verification.ClaimedEndDate.HasValue)
|
|
|
|
|
{
|
|
|
|
|
<span>@verification.ClaimedEndDate.Value.ToString("MMM yyyy")</span>
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
<span>Present</span>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
<span class="text-muted">Not specified</span>
|
|
|
|
|
}
|
|
|
|
|
</td>
|
|
|
|
|
<td>
|
|
|
|
|
@if (!string.IsNullOrEmpty(verification.MatchedCompanyName))
|
|
|
|
|
{
|
|
|
|
|
<span>@verification.MatchedCompanyName</span>
|
|
|
|
|
@if (!string.IsNullOrEmpty(verification.MatchedCompanyNumber))
|
|
|
|
|
{
|
|
|
|
|
<br /><small class="text-muted">@verification.MatchedCompanyNumber</small>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
<span class="text-muted">No match found</span>
|
|
|
|
|
}
|
|
|
|
|
</td>
|
|
|
|
|
<td class="text-center">
|
|
|
|
|
<span class="badge @GetMatchScoreBadgeClass(verification.MatchScore)">
|
|
|
|
|
@verification.MatchScore%
|
|
|
|
|
</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="text-center">
|
|
|
|
|
@if (verification.IsVerified)
|
|
|
|
|
{
|
|
|
|
|
<span class="badge bg-success">Verified</span>
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
<span class="badge bg-warning text-dark">Unverified</span>
|
|
|
|
|
}
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
@if (!string.IsNullOrEmpty(verification.VerificationNotes))
|
|
|
|
|
{
|
|
|
|
|
<tr class="table-light">
|
|
|
|
|
<td colspan="5" class="small text-muted py-1 ps-4">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-info-circle me-1" viewBox="0 0 16 16">
|
|
|
|
|
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
|
|
|
|
|
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
|
|
|
|
|
</svg>
|
|
|
|
|
@verification.VerificationNotes
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Timeline Analysis -->
|
|
|
|
|
<div class="row mb-4">
|
|
|
|
|
<!-- Gaps -->
|
|
|
|
|
<div class="col-md-6">
|
|
|
|
|
<div class="card border-0 shadow-sm h-100">
|
|
|
|
|
<div class="card-header bg-white py-3">
|
|
|
|
|
<h5 class="mb-0 fw-bold">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-clock-history me-2 text-warning" viewBox="0 0 16 16">
|
|
|
|
|
<path d="M8.515 1.019A7 7 0 0 0 8 1V0a8 8 0 0 1 .589.022l-.074.997zm2.004.45a7.003 7.003 0 0 0-.985-.299l.219-.976c.383.086.76.2 1.126.342l-.36.933zm1.37.71a7.01 7.01 0 0 0-.439-.27l.493-.87a8.025 8.025 0 0 1 .979.654l-.615.789a6.996 6.996 0 0 0-.418-.302zm1.834 1.79a6.99 6.99 0 0 0-.653-.796l.724-.69c.27.285.52.59.747.91l-.818.576zm.744 1.352a7.08 7.08 0 0 0-.214-.468l.893-.45a7.976 7.976 0 0 1 .45 1.088l-.95.313a7.023 7.023 0 0 0-.179-.483zm.53 2.507a6.991 6.991 0 0 0-.1-1.025l.985-.17c.067.386.106.778.116 1.17l-1 .025zm-.131 1.538c.033-.17.06-.339.081-.51l.993.123a7.957 7.957 0 0 1-.23 1.155l-.964-.267c.046-.165.086-.332.12-.501zm-.952 2.379c.184-.29.346-.594.486-.908l.914.405c-.16.36-.345.706-.555 1.038l-.845-.535zm-.964 1.205c.122-.122.239-.248.35-.378l.758.653a8.073 8.073 0 0 1-.401.432l-.707-.707z"/>
|
|
|
|
|
<path d="M8 1a7 7 0 1 0 4.95 11.95l.707.707A8.001 8.001 0 1 1 8 0v1z"/>
|
|
|
|
|
<path d="M7.5 3a.5.5 0 0 1 .5.5v5.21l3.248 1.856a.5.5 0 0 1-.496.868l-3.5-2A.5.5 0 0 1 7 9V3.5a.5.5 0 0 1 .5-.5z"/>
|
|
|
|
|
</svg>
|
|
|
|
|
Employment Gaps
|
|
|
|
|
</h5>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
@if (_report.TimelineAnalysis.Gaps.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
<ul class="list-group list-group-flush">
|
|
|
|
|
@foreach (var gap in _report.TimelineAnalysis.Gaps)
|
|
|
|
|
{
|
|
|
|
|
<li class="list-group-item d-flex justify-content-between align-items-center px-0">
|
|
|
|
|
<span>
|
|
|
|
|
@gap.StartDate.ToString("MMM yyyy") - @gap.EndDate.ToString("MMM yyyy")
|
|
|
|
|
</span>
|
|
|
|
|
<span class="badge bg-warning text-dark rounded-pill">@gap.Months months</span>
|
|
|
|
|
</li>
|
|
|
|
|
}
|
|
|
|
|
</ul>
|
|
|
|
|
<div class="mt-3 p-2 bg-light rounded">
|
|
|
|
|
<small class="text-muted">Total gap time: <strong>@_report.TimelineAnalysis.TotalGapMonths months</strong></small>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
<div class="text-center py-4">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-check-circle text-success mb-2" viewBox="0 0 16 16">
|
|
|
|
|
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
|
|
|
|
|
<path d="M10.97 4.97a.235.235 0 0 0-.02.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05z"/>
|
|
|
|
|
</svg>
|
|
|
|
|
<p class="mb-0 text-muted">No significant gaps detected</p>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Overlaps -->
|
|
|
|
|
<div class="col-md-6">
|
|
|
|
|
<div class="card border-0 shadow-sm h-100">
|
|
|
|
|
<div class="card-header bg-white py-3">
|
|
|
|
|
<h5 class="mb-0 fw-bold">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-intersect me-2 text-danger" viewBox="0 0 16 16">
|
|
|
|
|
<path d="M0 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v2h2a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-2H2a2 2 0 0 1-2-2V2zm5 10v2a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-2v5a2 2 0 0 1-2 2H5zm6-8V2a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2V6a2 2 0 0 1 2-2h5z"/>
|
|
|
|
|
</svg>
|
|
|
|
|
Timeline Overlaps
|
|
|
|
|
</h5>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
@if (_report.TimelineAnalysis.Overlaps.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
<ul class="list-group list-group-flush">
|
|
|
|
|
@foreach (var overlap in _report.TimelineAnalysis.Overlaps)
|
|
|
|
|
{
|
|
|
|
|
<li class="list-group-item px-0">
|
|
|
|
|
<div class="d-flex justify-content-between align-items-start">
|
|
|
|
|
<div>
|
|
|
|
|
<small class="text-muted">@overlap.Company1</small> &
|
|
|
|
|
<small class="text-muted">@overlap.Company2</small>
|
|
|
|
|
</div>
|
|
|
|
|
<span class="badge bg-danger rounded-pill">@overlap.Months months</span>
|
|
|
|
|
</div>
|
|
|
|
|
<small class="text-muted">
|
|
|
|
|
@overlap.OverlapStart.ToString("MMM yyyy") - @overlap.OverlapEnd.ToString("MMM yyyy")
|
|
|
|
|
</small>
|
|
|
|
|
</li>
|
|
|
|
|
}
|
|
|
|
|
</ul>
|
|
|
|
|
<div class="mt-3 p-2 bg-light rounded">
|
|
|
|
|
<small class="text-muted">Total overlap time: <strong>@_report.TimelineAnalysis.TotalOverlapMonths months</strong></small>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
<div class="text-center py-4">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-check-circle text-success mb-2" viewBox="0 0 16 16">
|
|
|
|
|
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
|
|
|
|
|
<path d="M10.97 4.97a.235.235 0 0 0-.02.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05z"/>
|
|
|
|
|
</svg>
|
|
|
|
|
<p class="mb-0 text-muted">No overlapping positions detected</p>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Flags -->
|
|
|
|
|
@if (_report.Flags.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
|
|
|
<div class="card-header bg-white py-3">
|
|
|
|
|
<h5 class="mb-0 fw-bold">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-flag me-2 text-danger" viewBox="0 0 16 16">
|
|
|
|
|
<path d="M14.778.085A.5.5 0 0 1 15 .5V8a.5.5 0 0 1-.314.464L14.5 8l.186.464-.003.001-.006.003-.023.009a12.435 12.435 0 0 1-.397.15c-.264.095-.631.223-1.047.35-.816.252-1.879.523-2.71.523-.847 0-1.548-.28-2.158-.525l-.028-.01C7.68 8.71 7.14 8.5 6.5 8.5c-.7 0-1.638.23-2.437.477A19.626 19.626 0 0 0 3 9.342V15.5a.5.5 0 0 1-1 0V.5a.5.5 0 0 1 1 0v.282c.226-.079.496-.17.79-.26C4.606.272 5.67 0 6.5 0c.84 0 1.524.277 2.121.519l.043.018C9.286.788 9.828 1 10.5 1c.7 0 1.638-.23 2.437-.477a19.587 19.587 0 0 0 1.349-.476l.019-.007.004-.002h.001"/>
|
|
|
|
|
</svg>
|
|
|
|
|
Flags Raised
|
|
|
|
|
</h5>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
@{
|
|
|
|
|
var criticalFlags = _report.Flags.Where(f => f.Severity == "Critical").ToList();
|
|
|
|
|
var warningFlags = _report.Flags.Where(f => f.Severity == "Warning").ToList();
|
|
|
|
|
var infoFlags = _report.Flags.Where(f => f.Severity == "Info").ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@if (criticalFlags.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
<h6 class="text-danger fw-bold mb-3">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-exclamation-octagon me-1" viewBox="0 0 16 16">
|
|
|
|
|
<path d="M4.54.146A.5.5 0 0 1 4.893 0h6.214a.5.5 0 0 1 .353.146l4.394 4.394a.5.5 0 0 1 .146.353v6.214a.5.5 0 0 1-.146.353l-4.394 4.394a.5.5 0 0 1-.353.146H4.893a.5.5 0 0 1-.353-.146L.146 11.46A.5.5 0 0 1 0 11.107V4.893a.5.5 0 0 1 .146-.353L4.54.146zM5.1 1 1 5.1v5.8L5.1 15h5.8l4.1-4.1V5.1L10.9 1H5.1z"/>
|
|
|
|
|
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/>
|
|
|
|
|
</svg>
|
|
|
|
|
Critical Issues
|
|
|
|
|
</h6>
|
|
|
|
|
@foreach (var flag in criticalFlags)
|
|
|
|
|
{
|
|
|
|
|
<div class="alert alert-danger mb-2">
|
|
|
|
|
<strong>@flag.Title</strong>
|
|
|
|
|
<span class="badge bg-danger ms-2">-@flag.ScoreImpact pts</span>
|
|
|
|
|
<p class="mb-0 mt-1 small">@flag.Description</p>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@if (warningFlags.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
<h6 class="text-warning fw-bold mb-3 @(criticalFlags.Count > 0 ? "mt-4" : "")">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-exclamation-triangle me-1" viewBox="0 0 16 16">
|
|
|
|
|
<path d="M7.938 2.016A.13.13 0 0 1 8.002 2a.13.13 0 0 1 .063.016.146.146 0 0 1 .054.057l6.857 11.667c.036.06.035.124.002.183a.163.163 0 0 1-.054.06.116.116 0 0 1-.066.017H1.146a.115.115 0 0 1-.066-.017.163.163 0 0 1-.054-.06.176.176 0 0 1 .002-.183L7.884 2.073a.147.147 0 0 1 .054-.057zm1.044-.45a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566z"/>
|
|
|
|
|
<path d="M7.002 12a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 5.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995z"/>
|
|
|
|
|
</svg>
|
|
|
|
|
Warnings
|
|
|
|
|
</h6>
|
|
|
|
|
@foreach (var flag in warningFlags)
|
|
|
|
|
{
|
|
|
|
|
<div class="alert alert-warning mb-2">
|
|
|
|
|
<strong>@flag.Title</strong>
|
|
|
|
|
<span class="badge bg-warning text-dark ms-2">-@flag.ScoreImpact pts</span>
|
|
|
|
|
<p class="mb-0 mt-1 small">@flag.Description</p>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@if (infoFlags.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
<h6 class="text-info fw-bold mb-3 @(criticalFlags.Count > 0 || warningFlags.Count > 0 ? "mt-4" : "")">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-info-circle me-1" viewBox="0 0 16 16">
|
|
|
|
|
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
|
|
|
|
|
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
|
|
|
|
|
</svg>
|
|
|
|
|
Information
|
|
|
|
|
</h6>
|
|
|
|
|
@foreach (var flag in infoFlags)
|
|
|
|
|
{
|
|
|
|
|
<div class="alert alert-info mb-2">
|
|
|
|
|
<strong>@flag.Title</strong>
|
|
|
|
|
<span class="badge bg-info text-dark ms-2">-@flag.ScoreImpact pts</span>
|
|
|
|
|
<p class="mb-0 mt-1 small">@flag.Description</p>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
.score-circle {
|
|
|
|
|
width: 120px;
|
|
|
|
|
height: 120px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
border: 8px solid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.score-circle.score-high {
|
|
|
|
|
border-color: #198754;
|
|
|
|
|
background-color: rgba(25, 135, 84, 0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.score-circle.score-medium {
|
|
|
|
|
border-color: #ffc107;
|
|
|
|
|
background-color: rgba(255, 193, 7, 0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.score-circle.score-low {
|
|
|
|
|
border-color: #dc3545;
|
|
|
|
|
background-color: rgba(220, 53, 69, 0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.score-value {
|
|
|
|
|
font-size: 2.5rem;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.score-high .score-value {
|
|
|
|
|
color: #198754;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.score-medium .score-value {
|
|
|
|
|
color: #ffc107;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.score-low .score-value {
|
|
|
|
|
color: #dc3545;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
@code {
|
|
|
|
|
[Parameter]
|
|
|
|
|
public Guid Id { get; set; }
|
|
|
|
|
|
|
|
|
|
private CVCheckDto? _check;
|
|
|
|
|
private VeracityReport? _report;
|
|
|
|
|
private bool _isLoading = true;
|
|
|
|
|
private string? _errorMessage;
|
|
|
|
|
private Guid _userId;
|
|
|
|
|
|
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
|
|
|
{
|
|
|
|
|
await LoadData();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task LoadData()
|
|
|
|
|
{
|
|
|
|
|
_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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_check = await CVCheckService.GetCheckForUserAsync(Id, _userId);
|
|
|
|
|
|
|
|
|
|
if (_check is null)
|
|
|
|
|
{
|
|
|
|
|
_errorMessage = "Report not found or you don't have access to view it.";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_check.Status == "Completed")
|
|
|
|
|
{
|
|
|
|
|
_report = await CVCheckService.GetReportAsync(Id, _userId);
|
|
|
|
|
|
|
|
|
|
if (_report is null)
|
|
|
|
|
{
|
|
|
|
|
_errorMessage = "Unable to load the report data.";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-01-20 16:45:43 +01:00
|
|
|
Logger.LogError(ex, "Error loading report data");
|
|
|
|
|
_errorMessage = "An error occurred while loading the report. Please try again.";
|
2026-01-18 19:20:50 +01:00
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
_isLoading = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task RefreshStatus()
|
|
|
|
|
{
|
|
|
|
|
await LoadData();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DownloadReport()
|
|
|
|
|
{
|
|
|
|
|
// TODO: Implement report download functionality
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string GetScoreColorClass(int score)
|
|
|
|
|
{
|
|
|
|
|
return score switch
|
|
|
|
|
{
|
|
|
|
|
> 70 => "score-high",
|
|
|
|
|
>= 50 => "score-medium",
|
|
|
|
|
_ => "score-low"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string GetMatchScoreBadgeClass(int score)
|
|
|
|
|
{
|
|
|
|
|
return score switch
|
|
|
|
|
{
|
|
|
|
|
>= 80 => "bg-success",
|
|
|
|
|
>= 50 => "bg-warning text-dark",
|
|
|
|
|
_ => "bg-danger"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|