Enhance UI design and update to UK English
- Add custom CSS design system with brand colours and variables - Enhance Report page with SVG score ring and improved flag styling - Improve Dashboard with better table design and score badges - Enhance Check page upload area with animated icon and file styling - Update spellings to UK English (analysing, recognised) - Add user-select: none to prevent text cursor on clickable elements - All date formats already use UK-friendly dd MMM yyyy format Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -46,10 +46,18 @@
|
||||
@if (_isLoading)
|
||||
{
|
||||
<div class="text-center py-5">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<div class="placeholder-glow mb-4">
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-md-4"><div class="placeholder col-12 rounded-4" style="height: 100px;"></div></div>
|
||||
<div class="col-md-4"><div class="placeholder col-12 rounded-4" style="height: 100px;"></div></div>
|
||||
<div class="col-md-4"><div class="placeholder col-12 rounded-4" style="height: 100px;"></div></div>
|
||||
</div>
|
||||
<div class="placeholder col-12 rounded-4" style="height: 300px;"></div>
|
||||
</div>
|
||||
<div class="spinner-border text-primary" role="status" style="width: 2.5rem; height: 2.5rem;">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-3 text-muted">Loading your checks...</p>
|
||||
<p class="mt-3 text-muted fw-medium">Loading your checks...</p>
|
||||
</div>
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(_errorMessage))
|
||||
@@ -61,19 +69,23 @@
|
||||
else if (_checks.Count == 0)
|
||||
{
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body text-center py-5">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="currentColor" class="bi bi-file-earmark-text text-muted mb-3" viewBox="0 0 16 16">
|
||||
<path d="M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z"/>
|
||||
<path d="M9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5L9.5 0zm0 1v2A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/>
|
||||
</svg>
|
||||
<h4>No CV Checks Yet</h4>
|
||||
<p class="text-muted mb-4">Start by uploading your first CV for verification</p>
|
||||
<a href="/check" class="btn btn-primary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-upload me-1" viewBox="0 0 16 16">
|
||||
<div class="card-body text-center py-5 px-4">
|
||||
<div class="empty-state-icon mb-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" class="bi bi-file-earmark-plus" viewBox="0 0 16 16">
|
||||
<path d="M8 6.5a.5.5 0 0 1 .5.5v1.5H10a.5.5 0 0 1 0 1H8.5V11a.5.5 0 0 1-1 0V9.5H6a.5.5 0 0 1 0-1h1.5V7a.5.5 0 0 1 .5-.5z"/>
|
||||
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h4 class="fw-bold mb-2">No CV Checks Yet</h4>
|
||||
<p class="text-muted mb-4 mx-auto" style="max-width: 400px;">
|
||||
Upload your first CV to begin verifying employment history against official company records.
|
||||
</p>
|
||||
<a href="/check" class="btn btn-primary btn-lg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" class="bi bi-upload me-2" 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 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z"/>
|
||||
</svg>
|
||||
Upload CV
|
||||
Upload Your First CV
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,106 +151,130 @@
|
||||
|
||||
<!-- Checks List -->
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="mb-0 fw-bold">Recent CV Checks</h5>
|
||||
<div class="card-header bg-white py-3 border-bottom">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0 fw-bold">Recent CV Checks</h5>
|
||||
<span class="badge bg-light text-muted">@_checks.Count total</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>File Name</th>
|
||||
<th>Date</th>
|
||||
<th class="text-center">Status</th>
|
||||
<th class="text-center">Score</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var check in _checks)
|
||||
{
|
||||
<tr class="@(check.Status == "Completed" ? "cursor-pointer" : "")"
|
||||
@onclick="() => ViewReport(check)">
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-file-earmark-text text-primary me-2" viewBox="0 0 16 16">
|
||||
<path d="M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z"/>
|
||||
<path d="M9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5L9.5 0zm0 1v2A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead>
|
||||
<tr class="bg-light">
|
||||
<th class="border-0 ps-4 py-3 text-uppercase small fw-semibold text-muted" style="letter-spacing: 0.05em;">Candidate</th>
|
||||
<th class="border-0 py-3 text-uppercase small fw-semibold text-muted" style="letter-spacing: 0.05em;">Uploaded</th>
|
||||
<th class="border-0 py-3 text-uppercase small fw-semibold text-muted text-center" style="letter-spacing: 0.05em;">Status</th>
|
||||
<th class="border-0 py-3 text-uppercase small fw-semibold text-muted text-center" style="letter-spacing: 0.05em;">Score</th>
|
||||
<th class="border-0 py-3 pe-4 text-uppercase small fw-semibold text-muted text-end" style="letter-spacing: 0.05em;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var check in _checks)
|
||||
{
|
||||
<tr class="@(check.Status == "Completed" ? "cursor-pointer" : "")"
|
||||
@onclick="() => ViewReport(check)">
|
||||
<td class="ps-4 py-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="file-icon-wrapper me-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-file-earmark-person text-primary" viewBox="0 0 16 16">
|
||||
<path d="M11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
|
||||
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2zM9.5 3A1.5 1.5 0 0 0 11 4.5h2v9.255S12 12 8 12s-5 1.755-5 1.755V2a1 1 0 0 1 1-1h5.5v2z"/>
|
||||
</svg>
|
||||
<span class="fw-medium">@check.OriginalFileName</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-muted">@check.CreatedAt.ToString("dd MMM yyyy HH:mm")</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
@switch (check.Status)
|
||||
{
|
||||
case "Completed":
|
||||
<span class="badge bg-success">Completed</span>
|
||||
break;
|
||||
case "Processing":
|
||||
<span class="badge bg-primary">
|
||||
<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true" style="width: 0.7rem; height: 0.7rem;"></span>
|
||||
@(check.ProcessingStage ?? "Processing")
|
||||
</span>
|
||||
break;
|
||||
case "Pending":
|
||||
<span class="badge bg-secondary">Pending</span>
|
||||
break;
|
||||
case "Failed":
|
||||
<span class="badge bg-danger">Failed</span>
|
||||
break;
|
||||
default:
|
||||
<span class="badge bg-secondary">@check.Status</span>
|
||||
break;
|
||||
}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
@if (check.VeracityScore.HasValue)
|
||||
{
|
||||
<span class="badge @GetScoreBadgeClass(check.VeracityScore.Value) fs-6">
|
||||
@check.VeracityScore
|
||||
<div>
|
||||
<p class="mb-0 fw-semibold text-dark">@Path.GetFileNameWithoutExtension(check.OriginalFileName)</p>
|
||||
<small class="text-muted">@Path.GetExtension(check.OriginalFileName).ToUpperInvariant()</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<div>
|
||||
<p class="mb-0 small">@check.CreatedAt.ToString("dd MMM yyyy")</p>
|
||||
<small class="text-muted">@check.CreatedAt.ToString("HH:mm")</small>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3 text-center">
|
||||
@switch (check.Status)
|
||||
{
|
||||
case "Completed":
|
||||
<span class="badge rounded-pill bg-success-subtle text-success px-3 py-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-check-circle-fill me-1" viewBox="0 0 16 16">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.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-.01-1.05z"/>
|
||||
</svg>
|
||||
Completed
|
||||
</span>
|
||||
break;
|
||||
case "Processing":
|
||||
<span class="badge rounded-pill bg-primary-subtle text-primary px-3 py-2">
|
||||
<span class="spinner-border spinner-border-sm me-1" role="status" style="width: 0.75rem; height: 0.75rem;"></span>
|
||||
@(check.ProcessingStage ?? "Processing")
|
||||
</span>
|
||||
break;
|
||||
case "Pending":
|
||||
<span class="badge rounded-pill bg-secondary-subtle text-secondary px-3 py-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-clock me-1" viewBox="0 0 16 16">
|
||||
<path d="M8 3.5a.5.5 0 0 0-1 0V9a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 8.71V3.5z"/>
|
||||
<path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm7-8A7 7 0 1 1 1 8a7 7 0 0 1 14 0z"/>
|
||||
</svg>
|
||||
Queued
|
||||
</span>
|
||||
break;
|
||||
case "Failed":
|
||||
<span class="badge rounded-pill bg-danger-subtle text-danger px-3 py-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-x-circle-fill me-1" viewBox="0 0 16 16">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/>
|
||||
</svg>
|
||||
Failed
|
||||
</span>
|
||||
break;
|
||||
default:
|
||||
<span class="badge rounded-pill bg-secondary-subtle text-secondary px-3 py-2">@check.Status</span>
|
||||
break;
|
||||
}
|
||||
</td>
|
||||
<td class="py-3 text-center">
|
||||
@if (check.VeracityScore.HasValue)
|
||||
{
|
||||
<div class="score-badge @GetScoreBadgeColorClass(check.VeracityScore.Value)">
|
||||
<span class="score-number">@check.VeracityScore</span>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">--</span>
|
||||
}
|
||||
</td>
|
||||
<td class="py-3 pe-4 text-end">
|
||||
<div class="btn-group" role="group">
|
||||
@if (check.Status == "Completed")
|
||||
{
|
||||
<a href="/report/@check.Id" class="btn btn-sm btn-primary" @onclick:stopPropagation="true">
|
||||
View Report
|
||||
</a>
|
||||
}
|
||||
else if (check.Status is "Pending" or "Processing")
|
||||
{
|
||||
<button class="btn btn-sm btn-outline-secondary" disabled>
|
||||
Processing...
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">-</span>
|
||||
<a href="/check" class="btn btn-sm btn-outline-warning" @onclick:stopPropagation="true">
|
||||
Retry
|
||||
</a>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group" role="group">
|
||||
@if (check.Status == "Completed")
|
||||
{
|
||||
<a href="/report/@check.Id" class="btn btn-sm btn-outline-primary" @onclick:stopPropagation="true">
|
||||
View Report
|
||||
</a>
|
||||
}
|
||||
else if (check.Status is "Pending" or "Processing")
|
||||
{
|
||||
<button class="btn btn-sm btn-outline-secondary" disabled>
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a href="/check" class="btn btn-sm btn-outline-warning" @onclick:stopPropagation="true">
|
||||
Retry
|
||||
</a>
|
||||
}
|
||||
<button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteCheck(check.Id)" @onclick:stopPropagation="true" title="Delete">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
|
||||
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z"/>
|
||||
<path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteCheck(check.Id)" @onclick:stopPropagation="true" title="Delete">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-trash3" viewBox="0 0 16 16">
|
||||
<path d="M6.5 1h3a.5.5 0 0 1 .5.5v1H6v-1a.5.5 0 0 1 .5-.5ZM11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3A1.5 1.5 0 0 0 5 1.5v1H2.506a.58.58 0 0 0-.01 0H1.5a.5.5 0 0 0 0 1h.538l.853 10.66A2 2 0 0 0 4.885 16h6.23a2 2 0 0 0 1.994-1.84l.853-10.66h.538a.5.5 0 0 0 0-1h-.995a.59.59 0 0 0-.01 0H11Zm1.958 1-.846 10.58a1 1 0 0 1-.997.92h-6.23a1 1 0 0 1-.997-.92L3.042 3.5h9.916Zm-7.487 1a.5.5 0 0 1 .528.47l.5 8.5a.5.5 0 0 1-.998.06L5 5.03a.5.5 0 0 1 .47-.53Zm5.058 0a.5.5 0 0 1 .47.53l-.5 8.5a.5.5 0 1 1-.998-.06l.5-8.5a.5.5 0 0 1 .528-.47ZM8 4.5a.5.5 0 0 1 .5.5v8.5a.5.5 0 0 1-1 0V5a.5.5 0 0 1 .5-.5Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -247,10 +283,89 @@
|
||||
<style>
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.cursor-pointer:hover {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
background-color: rgba(37, 99, 235, 0.04);
|
||||
}
|
||||
|
||||
.empty-state-icon {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
|
||||
border-radius: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.file-icon-wrapper {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.score-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.score-badge.score-high {
|
||||
background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
|
||||
color: #047857;
|
||||
}
|
||||
|
||||
.score-badge.score-medium {
|
||||
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
.score-badge.score-low {
|
||||
background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.score-number {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
@@media (max-width: 768px) {
|
||||
.d-flex.justify-content-between.align-items-center.mb-4 {
|
||||
flex-direction: column;
|
||||
align-items: stretch !important;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.d-flex.gap-2 {
|
||||
width: 100%;
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.d-flex.gap-2 .btn {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.row.mb-4 .col-md-4 {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.score-badge {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -375,6 +490,16 @@
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetScoreBadgeColorClass(int score)
|
||||
{
|
||||
return score switch
|
||||
{
|
||||
> 70 => "score-high",
|
||||
>= 50 => "score-medium",
|
||||
_ => "score-low"
|
||||
};
|
||||
}
|
||||
|
||||
private async Task ExportToPdf()
|
||||
{
|
||||
if (_isExporting) return;
|
||||
|
||||
Reference in New Issue
Block a user