Register und Fälle-Posteingang bekommen .page-header, Freitext-Filter
und Paginierung; Register zusätzlich .table-responsive statt
.table-scroll. Nutzerverwaltung bekommt Filter+Paginierung (bleibt
Karten-Liste). Abteilungen nur .page-header (bewusst ohne Paginierung,
typischerweise kleine Listen). Betreiber-Dashboard von .admin-kacheln
auf .stat-cards umgestellt.
Neuer gemeinsamer Helfer matchesQuery() in pagination.go für die
Freitext-Filter aller Listen-Seiten.
Dabei einen bereits live gewesenen CSS-Bug gefunden und behoben:
.filter-bar direkt auf einem <form> verlor gegen "form {
flex-direction: column }" (gleiche Spezifität, nur form setzte die
Property), wodurch die Filterleiste senkrecht statt waagerecht
stapelte — betraf die im letzten Schritt ausgelieferte
Mandanten-Werkzeugkatalog-Seite. Jetzt robust via explizitem
flex-direction: row in .filter-bar selbst.
147 lines
5.0 KiB
Go
147 lines
5.0 KiB
Go
// Ebene 3 — Register und Export (Schritt 6 der Baureihenfolge, siehe
|
|
// CLAUDE.md). Jede Genehmigung erzeugt automatisch einen Registereintrag
|
|
// (siehe handleFallEntscheiden); diese Seiten zeigen und exportieren
|
|
// sie. Reuse des bereits vorhandenen (bisher ungenutzten) go-pdf/fpdf-
|
|
// Dependency statt einer neuen Bibliothek für den PDF-Export.
|
|
package web
|
|
|
|
import (
|
|
"encoding/csv"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/go-pdf/fpdf"
|
|
)
|
|
|
|
type registerEintragView struct {
|
|
Zweck string
|
|
Abteilung string
|
|
Werkzeug string
|
|
Datenklasse string
|
|
Einstufung string
|
|
Auflagen string
|
|
Verantwortlicher string
|
|
EntschiedenAm string
|
|
GueltigBis string
|
|
}
|
|
|
|
type registerListeData struct {
|
|
Title string
|
|
Nav navData
|
|
Eintraege []registerEintragView
|
|
Suche string
|
|
Pagination pagination
|
|
}
|
|
|
|
func (s *Server) registerEintraegeFuerAccount(r *http.Request) ([]registerEintragView, error) {
|
|
eintraege, err := s.store.ListRegistereintraegeForAccount(r.Context(), currentUser(r).AccountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
views := make([]registerEintragView, len(eintraege))
|
|
for i, e := range eintraege {
|
|
gueltigBis := ""
|
|
if e.GueltigBis != nil {
|
|
gueltigBis = e.GueltigBis.Format("02.01.2006")
|
|
}
|
|
views[i] = registerEintragView{
|
|
Zweck: e.Zweck, Abteilung: e.Abteilung, Werkzeug: e.Werkzeug,
|
|
Datenklasse: e.Datenklasse, Einstufung: e.Einstufung, Auflagen: strings.Join(e.Auflagen, "; "),
|
|
Verantwortlicher: e.Verantwortlicher, EntschiedenAm: e.EntschiedenAm.Format("02.01.2006 15:04"), GueltigBis: gueltigBis,
|
|
}
|
|
}
|
|
return views, nil
|
|
}
|
|
|
|
// handleRegisterListe zeigt das Register (alle Genehmigungen des
|
|
// Mandanten) — Ebene 3, für verantwortlicher und pruefer gleichermaßen
|
|
// lesbar.
|
|
func (s *Server) handleRegisterListe(w http.ResponseWriter, r *http.Request) {
|
|
views, err := s.registerEintraegeFuerAccount(r)
|
|
if err != nil {
|
|
http.Error(w, "Register konnte nicht geladen werden: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
q := strings.TrimSpace(r.URL.Query().Get("q"))
|
|
var gefiltert []registerEintragView
|
|
for _, e := range views {
|
|
if matchesQuery(q, e.Zweck, e.Abteilung, e.Werkzeug, e.Verantwortlicher) {
|
|
gefiltert = append(gefiltert, e)
|
|
}
|
|
}
|
|
pag := paginationFromRequest(r, len(gefiltert))
|
|
data := registerListeData{
|
|
Title: "Register", Nav: navFor(r), Eintraege: paginate(gefiltert, pag),
|
|
Suche: q, Pagination: pag,
|
|
}
|
|
if err := s.templates.ExecuteTemplate(w, "register-liste", data); err != nil {
|
|
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
var registerCSVHeader = []string{"Zweck", "Abteilung", "Werkzeug", "Datenklasse", "Einstufung", "Auflagen", "Verantwortliche/r", "Entschieden am", "Gültig bis"}
|
|
|
|
func registerCSVRow(e registerEintragView) []string {
|
|
return []string{e.Zweck, e.Abteilung, e.Werkzeug, e.Datenklasse, e.Einstufung, e.Auflagen, e.Verantwortlicher, e.EntschiedenAm, e.GueltigBis}
|
|
}
|
|
|
|
// handleRegisterExportCSV exportiert das Register als CSV.
|
|
func (s *Server) handleRegisterExportCSV(w http.ResponseWriter, r *http.Request) {
|
|
views, err := s.registerEintraegeFuerAccount(r)
|
|
if err != nil {
|
|
http.Error(w, "Register konnte nicht geladen werden: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "text/csv; charset=utf-8")
|
|
w.Header().Set("Content-Disposition", `attachment; filename="register.csv"`)
|
|
cw := csv.NewWriter(w)
|
|
_ = cw.Write(registerCSVHeader)
|
|
for _, e := range views {
|
|
_ = cw.Write(registerCSVRow(e))
|
|
}
|
|
cw.Flush()
|
|
}
|
|
|
|
// handleRegisterExportPDF exportiert das Register als PDF — eine
|
|
// einfache Tabelle, keine Gestaltung über den Design-System-Anspruch
|
|
// des restlichen Produkts hinaus.
|
|
func (s *Server) handleRegisterExportPDF(w http.ResponseWriter, r *http.Request) {
|
|
views, err := s.registerEintraegeFuerAccount(r)
|
|
if err != nil {
|
|
http.Error(w, "Register konnte nicht geladen werden: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
pdf := fpdf.New("L", "mm", "A4", "")
|
|
// cp1252 deckt deutsche Umlaute/ß ab — die eingebauten Helvetica-
|
|
// Fonts von fpdf können sonst kein UTF-8 darstellen.
|
|
tr := pdf.UnicodeTranslatorFromDescriptor("")
|
|
pdf.AddPage()
|
|
pdf.SetFont("Helvetica", "B", 14)
|
|
pdf.CellFormat(0, 10, tr("Register"), "", 1, "L", false, 0, "")
|
|
pdf.Ln(2)
|
|
|
|
colWidths := []float64{45, 25, 35, 30, 30, 55, 40, 30, 25}
|
|
pdf.SetFont("Helvetica", "B", 8)
|
|
for i, h := range registerCSVHeader {
|
|
pdf.CellFormat(colWidths[i], 8, tr(h), "1", 0, "L", false, 0, "")
|
|
}
|
|
pdf.Ln(-1)
|
|
|
|
pdf.SetFont("Helvetica", "", 8)
|
|
for _, e := range views {
|
|
row := registerCSVRow(e)
|
|
for i, cell := range row {
|
|
pdf.CellFormat(colWidths[i], 8, tr(cell), "1", 0, "L", false, 0, "")
|
|
}
|
|
pdf.Ln(-1)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/pdf")
|
|
w.Header().Set("Content-Disposition", `attachment; filename="register.pdf"`)
|
|
if err := pdf.Output(w); err != nil {
|
|
http.Error(w, fmt.Sprintf("PDF konnte nicht erzeugt werden: %v", err), http.StatusInternalServerError)
|
|
}
|
|
}
|