248 lines
8.4 KiB
Plaintext
248 lines
8.4 KiB
Plaintext
@using MediatR
|
|
@using NexusReader.Application.Queries.Quiz
|
|
@using NexusReader.Application.Commands.Quiz
|
|
@using NexusReader.Application.Abstractions.Services
|
|
@using NexusReader.UI.Shared.Components.Atoms
|
|
@using NexusReader.UI.Shared.Services
|
|
@inject IMediator Mediator
|
|
@inject IPlatformService PlatformService
|
|
@inject IQuizStateService QuizService
|
|
@inject IIdentityService IdentityService
|
|
@inject IKnowledgeGraphService GraphService
|
|
@inject KnowledgeCoordinator Coordinator
|
|
|
|
<div class="knowledge-check">
|
|
<div class="quiz-header">
|
|
<span class="header-title">Sprawdzian Wiedzy</span>
|
|
<button class="expand-btn">⌵</button>
|
|
</div>
|
|
|
|
@if (QuizService.IsHydrating || _isGenerating)
|
|
{
|
|
<div class="loading-state shimmer">Skanowanie wiedzy przez AI...</div>
|
|
}
|
|
else if (_isSubmitted)
|
|
{
|
|
<div class="submitted-container">
|
|
<div class="success-icon-wrapper">
|
|
<NexusIcon Name="check" Size="48" Class="success-glow" />
|
|
</div>
|
|
<h3 class="submitted-title">Gratulacje!</h3>
|
|
<p class="submitted-text">Sprawdzian zakończony pomyślnie. Twój wynik został zapisany w bazie danych.</p>
|
|
|
|
<div class="score-card">
|
|
<div class="score-main">
|
|
<span class="score-num">@_score</span>
|
|
<span class="score-divider">/</span>
|
|
<span class="score-total">@_totalQuestions</span>
|
|
</div>
|
|
<div class="score-percent">@((int)_percentage)% poprawnych odpowiedzi</div>
|
|
</div>
|
|
|
|
<button class="reset-quiz-btn" @onclick="CloseQuiz">
|
|
<span>ZAKOŃCZ</span>
|
|
</button>
|
|
</div>
|
|
}
|
|
else if (QuizService.CurrentQuiz != null)
|
|
{
|
|
<div class="quiz-body">
|
|
@foreach (var question in QuizService.CurrentQuiz.Questions)
|
|
{
|
|
<div class="question-container">
|
|
<p class="question-text">@question.Question</p>
|
|
|
|
<div class="options-list">
|
|
@for (int i = 0; i < question.Options.Count; i++)
|
|
{
|
|
var index = i;
|
|
var letter = (char)('A' + i);
|
|
<button class="option-item @GetOptionClass(question, index)"
|
|
@onclick="() => SelectOptionAsync(question, index)"
|
|
disabled="@_states.ContainsKey(question)">
|
|
<span class="option-letter">@letter)</span>
|
|
<span class="option-text">@question.Options[index]</span>
|
|
</button>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="quiz-footer">
|
|
<button class="submit-btn" disabled="@(!AllQuestionsAnswered() || _isSubmitting)" @onclick="SubmitQuizAsync">
|
|
@if (_isSubmitting)
|
|
{
|
|
<span>Zapisywanie...</span>
|
|
}
|
|
else
|
|
{
|
|
<span>Wyślij</span>
|
|
}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="empty-quiz-state">
|
|
<div class="empty-icon-wrapper">
|
|
<NexusIcon Name="robot" Size="48" Class="neon-glow" />
|
|
</div>
|
|
<h3 class="empty-title">Brak Aktywnego Quizu</h3>
|
|
<p class="empty-text">Generuj spersonalizowany sprawdzian wiedzy na podstawie bieżącego rozdziału książki.</p>
|
|
|
|
<button class="generate-quiz-btn" @onclick="GenerateChapterQuizAsync" disabled="@(string.IsNullOrWhiteSpace(Coordinator.CurrentFullPageContent))">
|
|
<span>GENERUJ QUIZ DLA ROZDZIAŁU</span>
|
|
</button>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@code {
|
|
[Parameter] public string ContextBlockId { get; set; } = string.Empty;
|
|
|
|
private Dictionary<QuizQuestionDto, (int SelectedIndex, bool IsCorrect)> _states = new();
|
|
private bool _isSubmitting = false;
|
|
private bool _isSubmitted = false;
|
|
private bool _isGenerating = false;
|
|
private int _score = 0;
|
|
private int _totalQuestions = 0;
|
|
private double _percentage = 0.0;
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
QuizService.OnQuizUpdated += HandleUpdate;
|
|
}
|
|
|
|
private Task HandleUpdate() => InvokeAsync(StateHasChanged);
|
|
|
|
public void Dispose()
|
|
{
|
|
QuizService.OnQuizUpdated -= HandleUpdate;
|
|
}
|
|
|
|
private async Task GenerateChapterQuizAsync()
|
|
{
|
|
if (_isGenerating || string.IsNullOrWhiteSpace(Coordinator.CurrentFullPageContent)) return;
|
|
|
|
_isGenerating = true;
|
|
StateHasChanged();
|
|
|
|
try
|
|
{
|
|
await Coordinator.RequestSummaryAndQuizAsync(Coordinator.CurrentFullPageContent);
|
|
}
|
|
finally
|
|
{
|
|
_isGenerating = false;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private async Task SelectOptionAsync(QuizQuestionDto question, int index)
|
|
{
|
|
if (_states.ContainsKey(question)) return;
|
|
|
|
// Haptic feedback
|
|
await PlatformService.VibrateAsync(40);
|
|
|
|
var cmd = new SubmitAnswerCommand(index, question.CorrectIndex);
|
|
var res = await Mediator.Send(cmd);
|
|
|
|
_states[question] = (index, res.IsSuccess);
|
|
|
|
if (res.IsSuccess)
|
|
await PlatformService.VibrateSuccessAsync();
|
|
else
|
|
await PlatformService.VibrateErrorAsync();
|
|
|
|
StateHasChanged();
|
|
}
|
|
|
|
private bool AllQuestionsAnswered()
|
|
{
|
|
return QuizService.CurrentQuiz != null && _states.Count == QuizService.CurrentQuiz.Questions.Count;
|
|
}
|
|
|
|
private async Task SubmitQuizAsync()
|
|
{
|
|
if (QuizService.CurrentQuiz == null || !AllQuestionsAnswered() || _isSubmitting) return;
|
|
|
|
_isSubmitting = true;
|
|
StateHasChanged();
|
|
|
|
try
|
|
{
|
|
_score = _states.Values.Count(s => s.IsCorrect);
|
|
_totalQuestions = QuizService.CurrentQuiz.Questions.Count;
|
|
_percentage = _totalQuestions > 0 ? ((double)_score / _totalQuestions) * 100 : 0.0;
|
|
|
|
string topic = "Quiz wiedzy";
|
|
var graph = GraphService.CurrentGraphData;
|
|
if (graph != null && !string.IsNullOrEmpty(QuizService.CurrentQuizBlockId))
|
|
{
|
|
var node = graph.Nodes.FirstOrDefault(n => n.Id == QuizService.CurrentQuizBlockId);
|
|
if (node != null && !string.IsNullOrEmpty(node.Label))
|
|
{
|
|
topic = $"Test: {node.Label}";
|
|
}
|
|
}
|
|
|
|
var profileResult = await IdentityService.GetProfileAsync();
|
|
if (profileResult.IsSuccess && profileResult.Value != null)
|
|
{
|
|
var userId = profileResult.Value.UserId;
|
|
|
|
var cmd = new SubmitQuizResultCommand(userId, topic, _score, _totalQuestions);
|
|
var result = await Mediator.Send(cmd);
|
|
|
|
if (result.IsSuccess)
|
|
{
|
|
IdentityService.ClearCache();
|
|
_isSubmitted = true;
|
|
await PlatformService.VibrateSuccessAsync();
|
|
}
|
|
else
|
|
{
|
|
await PlatformService.VibrateErrorAsync();
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
await PlatformService.VibrateErrorAsync();
|
|
}
|
|
finally
|
|
{
|
|
_isSubmitting = false;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private void CloseQuiz()
|
|
{
|
|
_isSubmitted = false;
|
|
_states.Clear();
|
|
QuizService.SetQuiz(null, null);
|
|
}
|
|
|
|
private string GetBlockClass(QuizQuestionDto question)
|
|
{
|
|
if (!_states.TryGetValue(question, out var state)) return "";
|
|
return state.IsCorrect ? "state-correct" : "state-incorrect";
|
|
}
|
|
|
|
private string GetOptionClass(QuizQuestionDto question, int index)
|
|
{
|
|
if (!_states.TryGetValue(question, out var state)) return "";
|
|
|
|
if (state.SelectedIndex == index)
|
|
return state.IsCorrect ? "option-correct" : "option-incorrect";
|
|
|
|
if (state.IsCorrect == false && question.CorrectIndex == index)
|
|
return "option-revealed-correct";
|
|
|
|
return "option-faded";
|
|
}
|
|
}
|