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{"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) 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) } }