From 715c1e4fe581194fcb05adef68029b303b354e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=81nis=20Kac=C4=93ns?= Date: Mon, 11 May 2026 16:47:01 +0300 Subject: [PATCH] Phase 7: results & review Co-Authored-By: Claude Sonnet 4.6 --- internal/handlers/test.go | 79 +++++++++++++++++++++++++++++++ web/templates/test_results.html | 83 +++++++++++++++++++++++++++------ 2 files changed, 148 insertions(+), 14 deletions(-) diff --git a/internal/handlers/test.go b/internal/handlers/test.go index 5e223b1..c3ac36e 100644 --- a/internal/handlers/test.go +++ b/internal/handlers/test.go @@ -214,6 +214,20 @@ func (h *TestHandler) QuestionPost(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, fmt.Sprintf("/test/%d/results", test.ID), http.StatusSeeOther) } +// ResultItem holds per-question data for the results page. +type ResultItem struct { + Question *models.Question + Answers []*ResultAnswer + UserRight bool // user selected the correct answer + Unanswered bool // user skipped without selecting +} + +// ResultAnswer annotates each answer with display markers. +type ResultAnswer struct { + *models.Answer + UserPicked bool // user selected this answer +} + func (h *TestHandler) ResultsGet(w http.ResponseWriter, r *http.Request) { user := auth.UserFromCtx(r.Context()) testID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) @@ -226,8 +240,73 @@ func (h *TestHandler) ResultsGet(w http.ResponseWriter, r *http.Request) { HTTPError(w, http.StatusNotFound) return } + + testAnswers, err := h.repo.GetTestAnswers(testID) + if err != nil { + slog.Error("get test answers", "err", err) + HTTPError(w, http.StatusInternalServerError) + return + } + // Index test answers by question ID for quick lookup. + taByQ := make(map[string]*models.TestAnswer, len(testAnswers)) + for _, ta := range testAnswers { + taByQ[ta.QuestionID] = ta + } + + var items []ResultItem + nCorrect := 0 + for _, qid := range test.QuestionIDs { + q, answers, err := h.repo.GetQuestion(qid) + if err != nil { + slog.Error("get question for results", "qid", qid, "err", err) + HTTPError(w, http.StatusInternalServerError) + return + } + ta := taByQ[qid] + var selectedID int64 + unanswered := ta == nil || !ta.SelectedAnswerID.Valid + if !unanswered { + selectedID = ta.SelectedAnswerID.Int64 + } + userRight := ta != nil && ta.IsCorrect.Valid && ta.IsCorrect.Bool + if userRight { + nCorrect++ + } + ra := make([]*ResultAnswer, len(answers)) + for i, a := range answers { + ra[i] = &ResultAnswer{ + Answer: a, + UserPicked: !unanswered && a.ID == selectedID, + } + } + items = append(items, ResultItem{ + Question: q, + Answers: ra, + UserRight: userRight, + Unanswered: unanswered, + }) + } + + var timeTaken string + if test.CompletedAt.Valid { + d := test.CompletedAt.Time.Sub(test.CreatedAt).Round(time.Second) + h := int(d.Hours()) + m := int(d.Minutes()) % 60 + s := int(d.Seconds()) % 60 + if h > 0 { + timeTaken = fmt.Sprintf("%dh %dm %ds", h, m, s) + } else if m > 0 { + timeTaken = fmt.Sprintf("%dm %ds", m, s) + } else { + timeTaken = fmt.Sprintf("%ds", s) + } + } + data := BaseData(h.auth, r) data["Test"] = test + data["Items"] = items + data["NCorrect"] = nCorrect + data["TimeTaken"] = timeTaken h.render.Render(w, http.StatusOK, "test_results", data) } diff --git a/web/templates/test_results.html b/web/templates/test_results.html index 0fd492b..e4c556e 100644 --- a/web/templates/test_results.html +++ b/web/templates/test_results.html @@ -1,18 +1,73 @@ {{define "content"}} -
-

Test Complete!

-

Detailed results coming in the next phase.

-
- - Take another test - - - Library - +{{$n := .NCorrect}} +{{$total := .Test.NQuestions}} + + +
+

{{$n}} / {{$total}}

+

{{pct $n $total}}%

+ {{if .TimeTaken}} +

Time: {{.TimeTaken}}

+ {{end}} +
+ + +
+ {{range $i, $item := .Items}} +
+ + +
+
+ + {{if $item.Unanswered}}⬜{{else if $item.UserRight}}✅{{else}}❌{{end}} + +

{{$item.Question.Text}}

+
+ {{if $item.Question.Source}} +

{{$item.Question.Source}}

+ {{end}} +
+ + +
    + {{range $item.Answers}} +
  • + + {{if and .IsCorrect .UserPicked}}✅ + {{else if .IsCorrect}}✅ + {{else if .UserPicked}}❌ + {{else}} {{end}} + + + {{.Text}} + +
  • + {{end}} +
+
+ {{end}} +
+ + + {{end}}