Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions api/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1039,3 +1039,81 @@ func TestDashboardStatsArePerUser(t *testing.T) {
}
}
}

// TestGetProgressHandlerQuizzesLast7Days verifies the quizzes_last_7_days field
// counts only quiz attempts started within the last 7 days.
func TestGetProgressHandlerQuizzesLast7Days(t *testing.T) {
db := database.NewInMemoryDB()
handler := NewHandler(db)

user := &models.User{Username: "testuser7days", Email: "7days@example.com"}
if err := db.CreateUser(user); err != nil {
t.Fatalf("Failed to create user: %v", err)
}

now := time.Now()

// Attempt started 3 days ago (within last 7 days)
recent1 := &models.QuizAttempt{
UserID: user.ID,
StartTime: now.AddDate(0, 0, -3),
TotalQuestions: 5,
CorrectAnswers: 3,
QuestionIDs: []string{},
QuestionResults: map[string]bool{},
}
if err := db.CreateQuizAttempt(recent1); err != nil {
t.Fatalf("Failed to create recent quiz attempt: %v", err)
}

// Attempt started 6 days ago (within last 7 days)
recent2 := &models.QuizAttempt{
UserID: user.ID,
StartTime: now.AddDate(0, 0, -6),
TotalQuestions: 5,
CorrectAnswers: 4,
QuestionIDs: []string{},
QuestionResults: map[string]bool{},
}
if err := db.CreateQuizAttempt(recent2); err != nil {
t.Fatalf("Failed to create recent quiz attempt: %v", err)
}

// Attempt started 10 days ago (older than 7 days, should NOT be counted)
old := &models.QuizAttempt{
UserID: user.ID,
StartTime: now.AddDate(0, 0, -10),
TotalQuestions: 5,
CorrectAnswers: 2,
QuestionIDs: []string{},
QuestionResults: map[string]bool{},
}
if err := db.CreateQuizAttempt(old); err != nil {
t.Fatalf("Failed to create old quiz attempt: %v", err)
}

req := httptest.NewRequest(http.MethodGet, "/progress?user_id="+strconv.Itoa(user.ID), nil)
w := httptest.NewRecorder()
handler.GetProgressHandler(w, req)

if w.Code != http.StatusOK {
t.Errorf("Expected status %d, got %d", http.StatusOK, w.Code)
}

var progress map[string]interface{}
if err := json.NewDecoder(w.Body).Decode(&progress); err != nil {
t.Fatalf("Failed to decode response: %v", err)
}

// total_quizzes should include all 3 attempts
totalQuizzes := int(progress["total_quizzes"].(float64))
if totalQuizzes != 3 {
t.Errorf("Expected total_quizzes to be 3, got %d", totalQuizzes)
}

// quizzes_last_7_days should only count the 2 recent ones
quizzesLast7Days := int(progress["quizzes_last_7_days"].(float64))
if quizzesLast7Days != 2 {
t.Errorf("Expected quizzes_last_7_days to be 2 (3-day and 6-day old), got %d", quizzesLast7Days)
}
}
21 changes: 14 additions & 7 deletions api/quiz.go
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,13 @@ func (h *Handler) GetProgressHandler(w http.ResponseWriter, r *http.Request) {
// Calculate statistics
totalQuestions := 0
totalCorrect := 0
sevenDaysAgo := time.Now().AddDate(0, 0, -7)
quizzesLast7Days := 0
for _, qa := range quizAttempts {
// Count quizzes started in the last 7 days (inclusive of exactly 7 days ago)
if !qa.StartTime.Before(sevenDaysAgo) {
quizzesLast7Days++
}
// Count questions answered from all quizzes (completed and in-progress)
// For completed quizzes, all questions were answered
// For in-progress quizzes, count only the questions that have been answered
Expand All @@ -732,13 +738,14 @@ func (h *Handler) GetProgressHandler(w http.ResponseWriter, r *http.Request) {
}

progress := map[string]interface{}{
"user": user,
"total_points": user.TotalPoints,
"total_quizzes": len(quizAttempts),
"total_questions": totalQuestions,
"total_correct": totalCorrect,
"quiz_attempts": quizAttempts,
"question_attempts": questionAttempts,
"user": user,
"total_points": user.TotalPoints,
"total_quizzes": len(quizAttempts),
"quizzes_last_7_days": quizzesLast7Days,
"total_questions": totalQuestions,
"total_correct": totalCorrect,
"quiz_attempts": quizAttempts,
"question_attempts": questionAttempts,
}

w.Header().Set("Content-Type", "application/json")
Expand Down
50 changes: 29 additions & 21 deletions static/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -94,36 +94,44 @@ <h4 class="mb-0">📝 Assigned Exams</h4>
</div>

<!-- Progress Summary -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card bg-primary text-white">
<div class="card-body">
<h5 class="card-title">Total Points</h5>
<h2 id="totalPoints" class="mb-0">0</h2>
<div class="row mb-4 row-cols-2 row-cols-md-5 g-2">
<div class="col">
<div class="card bg-primary text-white h-100">
<div class="card-body p-2">
<h6 class="card-title">Total Points</h6>
<h3 id="totalPoints" class="mb-0">0</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body">
<h5 class="card-title">Quizzes Taken</h5>
<h2 id="totalQuizzes" class="mb-0">0</h2>
<div class="col">
<div class="card bg-success text-white h-100">
<div class="card-body p-2">
<h6 class="card-title">Quizzes Taken</h6>
<h3 id="totalQuizzes" class="mb-0">0</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body">
<h5 class="card-title">Questions Answered</h5>
<h2 id="totalQuestions" class="mb-0">0</h2>
<div class="col">
<div class="card bg-secondary text-white h-100">
<div class="card-body p-2">
<h6 class="card-title">Quizzes Taken (last 7 days)</h6>
<h3 id="quizzesLast7Days" class="mb-0">0</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-warning text-white">
<div class="card-body">
<h5 class="card-title">Correct Answers</h5>
<h2 id="totalCorrect" class="mb-0">0</h2>
<div class="col">
<div class="card bg-info text-white h-100">
<div class="card-body p-2">
<h6 class="card-title">Questions Answered</h6>
<h3 id="totalQuestions" class="mb-0">0</h3>
</div>
</div>
</div>
<div class="col">
<div class="card bg-warning text-white h-100">
<div class="card-body p-2">
<h6 class="card-title">Correct Answers</h6>
<h3 id="totalCorrect" class="mb-0">0</h3>
</div>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions static/js/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ async function loadDashboard() {
// Update stats
document.getElementById('totalPoints').textContent = progressData.total_points || 0;
document.getElementById('totalQuizzes').textContent = progressData.total_quizzes || 0;
document.getElementById('quizzesLast7Days').textContent = progressData.quizzes_last_7_days || 0;
document.getElementById('totalQuestions').textContent = progressData.total_questions || 0;
document.getElementById('totalCorrect').textContent = progressData.total_correct || 0;

Expand Down
Loading