Phase 7: results & review
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user