Files
PriceForge/internal/handlers/web.go

179 lines
4.3 KiB
Go

package handlers
import (
"html/template"
"os"
"path/filepath"
"strconv"
qfassets "git.mchus.pro/mchus/priceforge"
"git.mchus.pro/mchus/priceforge/internal/repository"
"git.mchus.pro/mchus/priceforge/internal/services"
"github.com/gin-gonic/gin"
)
type WebHandler struct {
templates map[string]*template.Template
componentService *services.ComponentService
}
func NewWebHandler(templatesPath string, componentService *services.ComponentService) (*WebHandler, error) {
funcMap := template.FuncMap{
"sub": func(a, b int) int { return a - b },
"add": func(a, b int) int { return a + b },
"mul": func(a, b int) int { return a * b },
"div": func(a, b int) int {
if b == 0 {
return 0
}
return (a + b - 1) / b
},
"deref": func(f *float64) float64 {
if f == nil {
return 0
}
return *f
},
"jsesc": func(s string) string {
// Escape string for safe use in JavaScript
result := ""
for _, r := range s {
switch r {
case '\\':
result += "\\\\"
case '\'':
result += "\\'"
case '"':
result += "\\\""
case '\n':
result += "\\n"
case '\r':
result += "\\r"
case '\t':
result += "\\t"
default:
result += string(r)
}
}
return result
},
}
templates := make(map[string]*template.Template)
basePath := filepath.Join(templatesPath, "base.html")
useDisk := false
if stat, statErr := os.Stat(templatesPath); statErr == nil && stat.IsDir() {
useDisk = true
}
// Load each page template with base
simplePages := []string{"login.html", "admin_pricing.html", "pricelists.html", "pricelist_detail.html"}
for _, page := range simplePages {
pagePath := filepath.Join(templatesPath, page)
var tmpl *template.Template
var err error
if useDisk {
tmpl, err = template.New("").Funcs(funcMap).ParseFiles(basePath, pagePath)
} else {
tmpl, err = template.New("").Funcs(funcMap).ParseFS(
qfassets.TemplatesFS,
"web/templates/base.html",
"web/templates/"+page,
)
}
if err != nil {
return nil, err
}
templates[page] = tmpl
}
// Load partial templates (no base needed)
partials := []string{"components_list.html"}
for _, partial := range partials {
partialPath := filepath.Join(templatesPath, partial)
var tmpl *template.Template
var err error
if useDisk {
tmpl, err = template.New("").Funcs(funcMap).ParseFiles(partialPath)
} else {
tmpl, err = template.New("").Funcs(funcMap).ParseFS(
qfassets.TemplatesFS,
"web/templates/"+partial,
)
}
if err != nil {
return nil, err
}
templates[partial] = tmpl
}
return &WebHandler{
templates: templates,
componentService: componentService,
}, nil
}
func (h *WebHandler) render(c *gin.Context, name string, data gin.H) {
c.Header("Content-Type", "text/html; charset=utf-8")
tmpl, ok := h.templates[name]
if !ok {
c.String(500, "Template not found: %s", name)
return
}
// Execute the page template which will use base
if err := tmpl.ExecuteTemplate(c.Writer, name, data); err != nil {
c.String(500, "Template error: %v", err)
}
}
func (h *WebHandler) Index(c *gin.Context) {
c.Redirect(302, "/admin/pricing")
}
func (h *WebHandler) Login(c *gin.Context) {
h.render(c, "login.html", nil)
}
func (h *WebHandler) AdminPricing(c *gin.Context) {
h.render(c, "admin_pricing.html", gin.H{"ActivePage": "admin"})
}
func (h *WebHandler) Pricelists(c *gin.Context) {
h.render(c, "pricelists.html", gin.H{"ActivePage": "pricelists"})
}
func (h *WebHandler) PricelistDetail(c *gin.Context) {
h.render(c, "pricelist_detail.html", gin.H{"ActivePage": "pricelists"})
}
// Partials for htmx
func (h *WebHandler) ComponentsPartial(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
filter := repository.ComponentFilter{
Category: c.Query("category"),
Search: c.Query("search"),
}
data := gin.H{
"Components": []interface{}{},
"Total": int64(0),
"Page": page,
"PerPage": 20,
}
result, err := h.componentService.List(filter, page, 20)
if err == nil && result != nil {
data["Components"] = result.Components
data["Total"] = result.Total
data["Page"] = result.Page
data["PerPage"] = result.PerPage
}
c.Header("Content-Type", "text/html; charset=utf-8")
if tmpl, ok := h.templates["components_list.html"]; ok {
tmpl.ExecuteTemplate(c.Writer, "components_list.html", data)
}
}