package web import ( "fmt" "fuj-management/go/internal/web/api" "html/template" "log/slog" "net/http" "net/url" "strconv" ) // PageData is the view model passed to every HTML template. type PageData struct { Active string Build BuildInfo } // AdultsPageData is the view model for the /adults HTML page. type AdultsPageData struct { PageData Data api.AdultsResponse Error string } // JuniorsPageData is the view model for the /juniors HTML page. type JuniorsPageData struct { PageData Data api.JuniorsResponse Error string } // Renderer parses and executes HTML templates from the embedded FS. type Renderer struct { tmpls map[string]*template.Template } var pageNames = []string{"adults", "juniors", "payments", "sync", "flush_cache"} // qrHref builds the /qr query URL for a single-month Pay button. // rawMonth is "YYYY-MM"; it is converted to "MM/YYYY" in the QR message. func qrHref(account string, amount int, name, rawMonth string) string { // Convert "YYYY-MM" → "MM/YYYY" to match Python's showPayQR JS. if len(rawMonth) == 7 && rawMonth[4] == '-' { rawMonth = rawMonth[5:] + "/" + rawMonth[:4] } msg := name + ": " + rawMonth return "/qr?" + url.Values{ "account": {account}, "amount": {strconv.Itoa(amount)}, "message": {msg}, }.Encode() } // qrHrefAll builds the /qr query URL for a Pay-All button. // rawPeriods is the "+" -joined MM/YYYY string from MemberRow.RawUnpaidPeriods. func qrHrefAll(account string, amount int, name, rawPeriods string) string { msg := name + ": " + rawPeriods return "/qr?" + url.Values{ "account": {account}, "amount": {strconv.Itoa(amount)}, "message": {msg}, }.Encode() } var tmplFuncs = template.FuncMap{ "qrHref": qrHref, "qrHrefAll": qrHrefAll, } // NewRenderer parses all templates from the embedded FS. // A parse failure should be treated as a startup-time fatal error. func NewRenderer() (*Renderer, error) { tmpls := make(map[string]*template.Template, len(pageNames)) for _, name := range pageNames { t, err := template.New("").Funcs(tmplFuncs).ParseFS(templateFS, "templates/base.tmpl", "templates/partials/nav.tmpl", "templates/partials/footer.tmpl", "templates/"+name+".tmpl", ) if err != nil { return nil, fmt.Errorf("parse template %q: %w", name, err) } tmpls[name] = t } return &Renderer{tmpls: tmpls}, nil } // Render executes the named template with data, writing to w. func (r *Renderer) Render(w http.ResponseWriter, name string, data any) { t, ok := r.tmpls[name] if !ok { http.Error(w, "template not found: "+name, http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") if err := t.ExecuteTemplate(w, "base", data); err != nil { slog.Error("render template", "name", name, "err", err) } }