feat: Filter und Paginierung für den Werkzeugkatalog, Claude Code als Katalogeintrag
Beide Katalog-Ansichten (Betreiber + Mandant) haben jetzt eine Filterleiste (Freitext über Name/Anbieter/Verarbeitungsländer, Zweck-Dropdown) und serverseitige Paginierung (20/Seite) über ein GET-Formular, kein JavaScript. pagination.go ist generisch gehalten (Go-Generics) für Wiederverwendung in weiteren Listen. Claude Code als eigener Katalogeintrag ergänzt (nicht identisch mit Claude for Work: läuft lokal, hat Dateizugriff, lokale Sitzungs- protokolle mit eigenem Aufbewahrungsrisiko).
This commit is contained in:
75
internal/web/pagination.go
Normal file
75
internal/web/pagination.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// pagination.go — wiederverwendbare serverseitige Paginierung für
|
||||
// Listen-Seiten (Werkzeugkatalog, Register, ...). Bewusst ohne
|
||||
// JavaScript: Seiten-/Filterwechsel läuft über ein einziges GET-Formular,
|
||||
// das Query-Parameter (q, zweck, page, ...) setzt — siehe CLAUDE.md,
|
||||
// Frontend.
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const defaultPerPage = 20
|
||||
|
||||
// pagination beschreibt die aktuelle Seite einer Liste. Templates lesen
|
||||
// daraus Seitenzahl, Gesamtzahl und ob Vor/Zurück-Buttons sichtbar sein
|
||||
// sollen.
|
||||
type pagination struct {
|
||||
Page int
|
||||
PerPage int
|
||||
Total int
|
||||
TotalPages int
|
||||
HasPrev bool
|
||||
HasNext bool
|
||||
PrevPage int
|
||||
NextPage int
|
||||
}
|
||||
|
||||
// paginationFromRequest liest "page" aus der Query (Standard: 1, nie
|
||||
// kleiner 1, nie größer als die letzte tatsächlich vorhandene Seite).
|
||||
// per_page ist bewusst nicht vom Nutzer einstellbar (kein Query-Param
|
||||
// dafür) — eine feste Seitengröße hält die Filter-URLs einfach und
|
||||
// verhindert versehentlich riesige Anfragen.
|
||||
func paginationFromRequest(r *http.Request, total int) pagination {
|
||||
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
perPage := defaultPerPage
|
||||
totalPages := (total + perPage - 1) / perPage
|
||||
if totalPages < 1 {
|
||||
totalPages = 1
|
||||
}
|
||||
if page > totalPages {
|
||||
page = totalPages
|
||||
}
|
||||
return pagination{
|
||||
Page: page, PerPage: perPage, Total: total, TotalPages: totalPages,
|
||||
HasPrev: page > 1, HasNext: page < totalPages,
|
||||
PrevPage: page - 1, NextPage: page + 1,
|
||||
}
|
||||
}
|
||||
|
||||
// bounds liefert den Start-/End-Index für die aktuelle Seite innerhalb
|
||||
// der (bereits gefilterten) Gesamtliste.
|
||||
func (p pagination) bounds() (start, end int) {
|
||||
start = (p.Page - 1) * p.PerPage
|
||||
end = start + p.PerPage
|
||||
if end > p.Total {
|
||||
end = p.Total
|
||||
}
|
||||
if start > end {
|
||||
start = end
|
||||
}
|
||||
return start, end
|
||||
}
|
||||
|
||||
// paginate schneidet den Ausschnitt für die aktuelle Seite aus einer
|
||||
// bereits gefilterten/sortierten Liste heraus — generisch, damit jede
|
||||
// Listen-Seite (Werkzeuge, Register, ...) dieselbe Funktion nutzt statt
|
||||
// eigene Slice-Arithmetik zu wiederholen.
|
||||
func paginate[T any](items []T, p pagination) []T {
|
||||
start, end := p.bounds()
|
||||
return items[start:end]
|
||||
}
|
||||
Reference in New Issue
Block a user