feat(ui/quiz): implement real-time global chapter quiz generation, submit results to database, and display dynamic statistics on dashboard
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
@inject IIdentityService IdentityService
|
||||
@inject NavigationManager NavigationManager
|
||||
@attribute [Authorize]
|
||||
@implements IDisposable
|
||||
|
||||
|
||||
<PageTitle>Dashboard | Nexus Reader</PageTitle>
|
||||
|
||||
@@ -18,20 +20,20 @@
|
||||
<img src="https://api.dicebear.com/7.x/bottts/svg?seed=Nexus" alt="Profile" class="profile-img" />
|
||||
<div class="avatar-glow"></div>
|
||||
</div>
|
||||
<h1 class="username">[User_Explorer1988]</h1>
|
||||
<h1 class="username">@(string.IsNullOrEmpty(_profile?.DisplayName) ? (_profile?.Email.Split('@')[0] ?? "Użytkownik") : _profile.DisplayName)</h1>
|
||||
|
||||
<div class="status-pills">
|
||||
<div class="status-pill">
|
||||
<span class="pill-label">Books Read:</span>
|
||||
<span class="pill-value">12</span>
|
||||
<span class="pill-label">Książki:</span>
|
||||
<span class="pill-value">@(_profile?.BooksReadCount ?? 0)</span>
|
||||
</div>
|
||||
<div class="status-pill">
|
||||
<span class="pill-label">Concepts Mapped:</span>
|
||||
<span class="pill-value">450</span>
|
||||
<span class="pill-label">Pojęcia:</span>
|
||||
<span class="pill-value">@(_profile?.ConceptsMappedCount ?? 0)</span>
|
||||
</div>
|
||||
<div class="status-pill">
|
||||
<span class="pill-label">Quiz Mastery:</span>
|
||||
<span class="pill-value">88%</span>
|
||||
<span class="pill-label">Średni Wynik:</span>
|
||||
<span class="pill-value">@(_profile?.AverageQuizScore ?? 0)%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,7 +41,7 @@
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="dashboard-content">
|
||||
<h2 class="section-title">Witaj, @(_profile?.Email.Split('@')[0] ?? "Użytkowniku")</h2>
|
||||
<h2 class="section-title">Witaj, @(string.IsNullOrEmpty(_profile?.DisplayName) ? (_profile?.Email.Split('@')[0] ?? "Użytkowniku") : _profile.DisplayName)</h2>
|
||||
|
||||
<div class="main-grid">
|
||||
<!-- Current Reading Card -->
|
||||
@@ -49,7 +51,7 @@
|
||||
<!-- Knowledge Integration -->
|
||||
<section class="integration-card glass-panel">
|
||||
<div class="panel-header">
|
||||
<h4>Knowledge Integration Progress</h4>
|
||||
<h4>Integracja Wiedzy</h4>
|
||||
<NexusIcon Name="arrow-right" Size="16" />
|
||||
</div>
|
||||
<div class="graph-placeholder">
|
||||
@@ -64,19 +66,36 @@
|
||||
<!-- Quiz Summary -->
|
||||
<section class="quiz-card glass-panel">
|
||||
<div class="panel-header">
|
||||
<h4>Quiz Summary: Key Thinkers</h4>
|
||||
<h4>Rozwiązane Quizy</h4>
|
||||
<NexusIcon Name="arrow-right" Size="16" />
|
||||
</div>
|
||||
<div class="quiz-preview">
|
||||
<p class="question">Który artysta namalował 'Ostatnią Wieczerzę'?</p>
|
||||
<div class="quiz-options">
|
||||
<div class="quiz-option active">
|
||||
<span class="option-letter">A)</span> Michal Anioł
|
||||
@if (_profile?.RecentQuizzes != null && _profile.RecentQuizzes.Any())
|
||||
{
|
||||
<div class="quiz-history-list">
|
||||
@foreach (var quiz in _profile.RecentQuizzes)
|
||||
{
|
||||
<div class="quiz-history-item">
|
||||
<div class="quiz-item-header">
|
||||
<span class="quiz-topic">@quiz.Topic</span>
|
||||
<span class="quiz-score badge @(quiz.Percentage >= 80 ? "badge-success" : quiz.Percentage >= 50 ? "badge-warning" : "badge-danger")">
|
||||
@quiz.Score / @quiz.TotalQuestions (@((int)quiz.Percentage)%)
|
||||
</span>
|
||||
</div>
|
||||
<div class="quiz-item-meta">
|
||||
<span class="quiz-date">@quiz.CompletedDate.ToString("g")</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="quiz-option">
|
||||
<span class="option-letter">B)</span> Leonardo da Vinci
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="empty-quiz-state">
|
||||
<p class="question">Brak rozwiązanych quizów</p>
|
||||
<p class="sub-text">Rozwiązuj quizy w trakcie czytania książek, aby śledzić swoje postępy.</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -88,11 +107,36 @@
|
||||
private UserProfileDto? _profile;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
IdentityService.OnStateInvalidated += HandleStateInvalidatedAsync;
|
||||
await LoadProfileAsync();
|
||||
}
|
||||
|
||||
private async Task LoadProfileAsync()
|
||||
{
|
||||
var result = await IdentityService.GetProfileAsync();
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
_profile = result.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
_profile = null;
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task HandleStateInvalidatedAsync()
|
||||
{
|
||||
await InvokeAsync(async () =>
|
||||
{
|
||||
await LoadProfileAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
IdentityService.OnStateInvalidated -= HandleStateInvalidatedAsync;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -404,3 +404,79 @@
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Quiz History Styling --- */
|
||||
.quiz-history-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.quiz-history-item {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.quiz-history-item:hover {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.quiz-item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.quiz-topic {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.quiz-item-meta {
|
||||
display: flex;
|
||||
font-size: 0.75rem;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background: rgba(0, 255, 153, 0.1);
|
||||
color: var(--nexus-neon);
|
||||
border: 1px solid rgba(0, 255, 153, 0.3);
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background: rgba(255, 170, 0, 0.1);
|
||||
color: #ffa800;
|
||||
border: 1px solid rgba(255, 170, 0, 0.3);
|
||||
}
|
||||
|
||||
.badge-danger {
|
||||
background: rgba(255, 50, 50, 0.1);
|
||||
color: #ff3232;
|
||||
border: 1px solid rgba(255, 50, 50, 0.3);
|
||||
}
|
||||
|
||||
.empty-quiz-state {
|
||||
text-align: center;
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.empty-quiz-state .sub-text {
|
||||
font-size: 0.8rem;
|
||||
color: #666666;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user