Files
Jānis Kacēns 177b4e8fd8 Phase 5: library & stats
- Full library home page: question list with per-user mastery stats,
  search/source filter, sort (A-Z, weakest first, most-seen), source
  breakdown in header, Take a test CTA.
- GET/POST /questions/{id}: view and edit question text, source, answers;
  radio-select correct answer; shows seen×/correct% stat.
- POST /questions/{id}/delete: hard delete (cascades to answers via FK).
- repo: ListQuestions supports SortWeakest/SortMostSeen via LEFT JOIN;
  added CountBySource, UpdateQuestion, UpdateAnswers, DeleteQuestion.
- render: added pct template func (correct*100/seen).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 13:49:22 +03:00

80 lines
3.1 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "content"}}
<div class="mb-5 flex items-baseline justify-between">
<h1 class="text-2xl font-bold text-gray-800">Library</h1>
<a href="/upload" class="text-sm text-blue-600 hover:underline">+ Upload</a>
</div>
{{if eq .TotalQ 0}}
<div class="text-center py-16 text-gray-400">
<p class="text-lg">No questions yet.</p>
<a href="/upload" class="mt-4 inline-block text-blue-600 hover:underline text-sm">Upload a document to get started</a>
</div>
{{else}}
<div class="mb-5 text-sm text-gray-500">
{{.TotalQ}} question{{if ne .TotalQ 1}}s{{end}} · {{.TotalA}} answer{{if ne .TotalA 1}}s{{end}}
{{if .SourceStats}}
<span class="mx-1">·</span>
{{range $i, $s := .SourceStats}}{{if $i}}<span class="mx-0.5 text-gray-300">|</span>{{end}}<span>{{$s.Source}} ({{$s.Count}})</span>{{end}}
{{end}}
</div>
<form method="get" action="/" class="flex flex-wrap gap-2 mb-4">
<input type="text" name="q" value="{{.Search}}" placeholder="Search…"
class="flex-1 min-w-0 border border-gray-300 rounded-md px-3 py-1.5 text-sm
focus:outline-none focus:ring-2 focus:ring-blue-500">
{{if .SourceStats}}
<select name="source" onchange="this.form.submit()"
class="border border-gray-300 rounded-md px-2 py-1.5 text-sm
focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white">
<option value="">All sources</option>
{{range .SourceStats}}
<option value="{{.Source}}" {{if eq .Source $.SelectedSource}}selected{{end}}>{{.Source}}</option>
{{end}}
</select>
{{end}}
<select name="sort" onchange="this.form.submit()"
class="border border-gray-300 rounded-md px-2 py-1.5 text-sm
focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white">
<option value="alpha" {{if eq .Sort "alpha"}}selected{{end}}>AZ</option>
<option value="weakest" {{if eq .Sort "weakest"}}selected{{end}}>Weakest first</option>
<option value="seen" {{if eq .Sort "seen"}}selected{{end}}>Most seen</option>
</select>
<button type="submit"
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-1.5 rounded-md text-sm font-medium">
Search
</button>
</form>
{{if .Questions}}
<div class="space-y-2">
{{range .Questions}}
<a href="/questions/{{.Q.ID}}"
class="block bg-white border border-gray-200 rounded-lg px-4 py-3 hover:border-blue-300 hover:shadow-sm transition-all">
<div class="flex items-start justify-between gap-4">
<p class="text-sm text-gray-800 line-clamp-2">{{.Q.Text}}</p>
<span class="text-xs text-gray-400 whitespace-nowrap mt-0.5 flex-shrink-0">
{{if .Stat}}{{.Stat.TimesSeen}}× · {{pct .Stat.TimesCorrect .Stat.TimesSeen}}%{{else}}—{{end}}
</span>
</div>
{{if .Q.Source}}
<p class="text-xs text-gray-400 mt-1">{{.Q.Source}}</p>
{{end}}
</a>
{{end}}
</div>
{{else}}
<p class="text-center py-8 text-gray-400 text-sm">No questions match your filter.</p>
{{end}}
<div class="mt-6 text-center">
<a href="/test/new"
class="inline-block bg-blue-600 hover:bg-blue-700 text-white px-8 py-2.5 rounded-md
text-sm font-semibold shadow-sm">
Take a test
</a>
</div>
{{end}}
{{end}}