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>
This commit is contained in:
+77
-5
@@ -1,7 +1,79 @@
|
||||
{{define "content"}}
|
||||
<h1 class="text-2xl font-bold text-gray-800 mb-2">Library</h1>
|
||||
<p class="text-gray-500 text-sm">
|
||||
No questions yet.
|
||||
<a href="/upload" class="text-blue-600 hover:underline">Upload a document</a> to get started.
|
||||
</p>
|
||||
<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}}>A–Z</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}}
|
||||
|
||||
Reference in New Issue
Block a user