package handlers import ( "net/http" "strconv" "strings" "git.mchus.pro/mchus/quoteforge/internal/localdb" "git.mchus.pro/mchus/quoteforge/internal/models" "git.mchus.pro/mchus/quoteforge/internal/repository" "git.mchus.pro/mchus/quoteforge/internal/services" "github.com/gin-gonic/gin" ) type ComponentHandler struct { componentService *services.ComponentService localDB *localdb.LocalDB } func NewComponentHandler(componentService *services.ComponentService, localDB *localdb.LocalDB) *ComponentHandler { return &ComponentHandler{ componentService: componentService, localDB: localDB, } } func (h *ComponentHandler) List(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "20")) if page < 1 { page = 1 } if perPage < 1 { perPage = 20 } filter := repository.ComponentFilter{ Category: c.Query("category"), Search: c.Query("search"), HasPrice: c.Query("has_price") == "true", ExcludeHidden: c.Query("include_hidden") != "true", // По умолчанию скрытые не показываются } localFilter := localdb.ComponentFilter{ Category: filter.Category, Search: filter.Search, HasPrice: filter.HasPrice, } offset := (page - 1) * perPage localComps, total, err := h.localDB.ListComponents(localFilter, offset, perPage) if err != nil { RespondError(c, http.StatusInternalServerError, "internal server error", err) return } components := make([]services.ComponentView, len(localComps)) for i, lc := range localComps { components[i] = services.ComponentView{ LotName: lc.LotName, Description: lc.LotDescription, Category: lc.Category, CategoryName: lc.Category, Model: lc.Model, } } totalPages := int((total + int64(perPage) - 1) / int64(perPage)) if totalPages < 1 { totalPages = 1 } c.JSON(http.StatusOK, &services.ComponentListResult{ Items: components, TotalCount: total, Page: page, PerPage: perPage, TotalPages: totalPages, }) } func (h *ComponentHandler) Get(c *gin.Context) { lotName := c.Param("lot_name") component, err := h.localDB.GetLocalComponent(lotName) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "component not found"}) return } c.JSON(http.StatusOK, services.ComponentView{ LotName: component.LotName, Description: component.LotDescription, Category: component.Category, CategoryName: component.Category, Model: component.Model, }) } func (h *ComponentHandler) GetCategories(c *gin.Context) { // Build display_order lookup from the canonical list. orderMap := make(map[string]int, len(models.DefaultCategories)) for _, cat := range models.DefaultCategories { orderMap[strings.ToUpper(cat.Code)] = cat.DisplayOrder } codes, err := h.localDB.GetLocalComponentCategories() if err == nil && len(codes) > 0 { categories := make([]models.Category, 0, len(codes)) for _, code := range codes { trimmed := strings.TrimSpace(code) if trimmed == "" { continue } order := orderMap[strings.ToUpper(trimmed)] if order == 0 { order = models.MaxKnownDisplayOrder + 1 } categories = append(categories, models.Category{ Code: trimmed, Name: trimmed, DisplayOrder: order, }) } c.JSON(http.StatusOK, categories) return } c.JSON(http.StatusOK, models.DefaultCategories) } func (h *ComponentHandler) GetConfiguratorSettings(c *gin.Context) { s, _ := h.localDB.GetConfiguratorSettings() if s == nil { s = &localdb.ConfiguratorSettings{} } if len(s.ConfigTypes) == 0 { s.ConfigTypes = defaultConfigTypes() } if len(s.TabConfig) == 0 { s.TabConfig = defaultTabConfig() } if len(s.AlwaysVisibleTabs) == 0 { s.AlwaysVisibleTabs = []string{"base", "storage", "pci"} } if len(s.RequiredCategories) == 0 { s.RequiredCategories = map[string][]string{"server": {"CPU", "MEM", "BB"}} } c.JSON(http.StatusOK, s) } func defaultConfigTypes() []localdb.ConfigTypeDef { return []localdb.ConfigTypeDef{ { Code: "server", NameRu: "Сервер", DisplayOrder: 10, Categories: []string{ "MB", "CPU", "MEM", "RAID", "SSD", "HDD", "M2", "EDSFF", "HHHL", "GPU", "NIC", "HCA", "DPU", "HBA", "PSU", "PS", "ACC", "RISERS", "CARD", "BB", }, }, { Code: "storage", NameRu: "СХД", DisplayOrder: 20, Categories: []string{ "DKC", "CPU", "MEM", "PS", "SSD", "HDD", "M2", "EDSFF", "HHHL", "NIC", "HBA", "HCA", "ACC", "CARD", }, }, } } func defaultTabConfig() []localdb.TabDef { return []localdb.TabDef{ { Key: "base", Label: "Base", SingleSelect: true, Categories: []string{"MB", "CPU", "MEM", "ENC", "DKC", "CTL"}, }, { Key: "storage", Label: "Storage", SingleSelect: false, Categories: []string{"RAID", "M2", "SSD", "HDD", "EDSFF", "HHHL"}, Sections: []localdb.TabSection{ {Title: "RAID Контроллеры", Categories: []string{"RAID"}}, {Title: "Диски", Categories: []string{"M2", "SSD", "HDD", "EDSFF", "HHHL"}}, }, }, { Key: "pci", Label: "PCI", SingleSelect: false, Categories: []string{"GPU", "DPU", "NIC", "HCA", "HBA", "HIC"}, Sections: []localdb.TabSection{ {Title: "GPU / DPU", Categories: []string{"GPU", "DPU"}}, {Title: "NIC / HCA", Categories: []string{"NIC", "HCA"}}, {Title: "HBA", Categories: []string{"HBA"}}, {Title: "HIC", Categories: []string{"HIC"}}, }, }, { Key: "power", Label: "Power", SingleSelect: false, Categories: []string{"PS", "PSU"}, }, { Key: "accessories", Label: "Accessories", SingleSelect: false, Categories: []string{"ACC", "CARD"}, }, { Key: "sw", Label: "SW", SingleSelect: false, Categories: []string{"SW"}, }, } }