Compare commits
12 Commits
ec5ff123ac
...
add_image_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7533a8deb | ||
| cf4d14a3cb | |||
| 30eb82ba53 | |||
| a2a2c35ab3 | |||
| 684f910090 | |||
| 5feaa9e15c | |||
|
|
04e2549a66 | ||
| fb4f7048ab | |||
|
|
a3f01eca7c | ||
|
|
1fa6206b75 | ||
|
|
fa89723eb6 | ||
|
|
8665c566ee |
@@ -1127,21 +1127,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/restricted/menu/get-menu": {
|
||||
"/api/v1/restricted/menu/get-category-tree": {
|
||||
"get": {
|
||||
"tags": ["Menu"],
|
||||
"summary": "Get menu structure",
|
||||
"description": "Returns the menu structure for the current language. Requires authentication.",
|
||||
"operationId": "getMenu",
|
||||
"summary": "Get category tree",
|
||||
"description": "Returns the category tree rooted at the given category ID for the current language. Requires authentication.",
|
||||
"operationId": "getCategoryTree",
|
||||
"security": [
|
||||
{
|
||||
"CookieAuth": [],
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "root_category_id",
|
||||
"in": "query",
|
||||
"description": "Root category ID to build the tree from",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Menu retrieved successfully",
|
||||
"description": "Category tree retrieved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
@@ -1151,7 +1162,73 @@
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"description": "Invalid request or root category not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Not authenticated",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/restricted/menu/get-breadcrumb": {
|
||||
"get": {
|
||||
"tags": ["Menu"],
|
||||
"summary": "Get breadcrumb",
|
||||
"description": "Returns the breadcrumb path from the root category to the specified category for the current language. Requires authentication.",
|
||||
"operationId": "getBreadcrumb",
|
||||
"security": [
|
||||
{
|
||||
"CookieAuth": [],
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "root_category_id",
|
||||
"in": "query",
|
||||
"description": "Root category ID (breadcrumb starting point)",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "category_id",
|
||||
"in": "query",
|
||||
"description": "Target category ID (breadcrumb destination)",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Breadcrumb retrieved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request, category not found, or root never reached",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
@@ -1221,7 +1298,23 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/B2BTopMenu"
|
||||
},
|
||||
"description": "Root menu items with nested children"
|
||||
},
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"description": "Number of root menu items"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1995,46 +2088,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"MenuItem": {
|
||||
"type": "object",
|
||||
"description": "Menu item structure",
|
||||
"properties": {
|
||||
"category_id": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"description": "Category ID"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Menu item label"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/components/schemas/MenuItemParams"
|
||||
},
|
||||
"children": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/MenuItem"
|
||||
},
|
||||
"description": "Child menu items"
|
||||
}
|
||||
}
|
||||
},
|
||||
"MenuItemParams": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"category_id": {
|
||||
"type": "integer",
|
||||
"format": "uint"
|
||||
},
|
||||
"link_rewrite": {
|
||||
"type": "string"
|
||||
},
|
||||
"locale": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Route": {
|
||||
"type": "object",
|
||||
"description": "Application route",
|
||||
@@ -2338,6 +2391,58 @@
|
||||
"description": "Build date in RFC3339 format"
|
||||
}
|
||||
}
|
||||
},
|
||||
"CategoryInBreadcrumb": {
|
||||
"type": "object",
|
||||
"description": "A single item in a category breadcrumb path",
|
||||
"properties": {
|
||||
"category_id": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"description": "Category ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Category name"
|
||||
}
|
||||
}
|
||||
},
|
||||
"B2BTopMenu": {
|
||||
"type": "object",
|
||||
"description": "Top-level menu item for B2B back-office",
|
||||
"properties": {
|
||||
"menu_id": {
|
||||
"type": "integer",
|
||||
"description": "Menu item ID"
|
||||
},
|
||||
"label": {
|
||||
"type": "object",
|
||||
"description": "Menu label as JSON (multilingual, e.g. {\"en\": \"Dashboard\", \"pl\": \"Panel\"})"
|
||||
},
|
||||
"parent_id": {
|
||||
"type": "integer",
|
||||
"description": "Parent menu ID (null for root items)"
|
||||
},
|
||||
"params": {
|
||||
"type": "object",
|
||||
"description": "Menu item parameters as JSON"
|
||||
},
|
||||
"active": {
|
||||
"type": "integer",
|
||||
"description": "Active status (1 = active, 0 = inactive)"
|
||||
},
|
||||
"position": {
|
||||
"type": "integer",
|
||||
"description": "Sort position"
|
||||
},
|
||||
"children": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/B2BTopMenu"
|
||||
},
|
||||
"description": "Child menu items"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securitySchemes": {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package restricted
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"git.ma-al.com/goc_daniel/b2b/app/service/menuService"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
||||
@@ -23,25 +25,64 @@ func NewMenuHandler() *MenuHandler {
|
||||
func MenuHandlerRoutes(r fiber.Router) fiber.Router {
|
||||
handler := NewMenuHandler()
|
||||
|
||||
r.Get("/get-menu", handler.GetMenu)
|
||||
r.Get("/get-category-tree", handler.GetCategoryTree)
|
||||
r.Get("/get-breadcrumb", handler.GetBreadcrumb)
|
||||
r.Get("/get-top-menu", handler.GetTopMenu)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (h *MenuHandler) GetMenu(c fiber.Ctx) error {
|
||||
func (h *MenuHandler) GetCategoryTree(c fiber.Ctx) error {
|
||||
lang_id, ok := c.Locals("langID").(uint)
|
||||
if !ok {
|
||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||
}
|
||||
menu, err := h.menuService.GetMenu(lang_id)
|
||||
|
||||
root_category_id_attribute := c.Query("root_category_id")
|
||||
root_category_id, err := strconv.Atoi(root_category_id_attribute)
|
||||
if err != nil {
|
||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||
}
|
||||
|
||||
category_tree, err := h.menuService.GetCategoryTree(uint(root_category_id), lang_id)
|
||||
if err != nil {
|
||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||
}
|
||||
|
||||
return c.JSON(response.Make(&menu, 0, i18n.T_(c, response.Message_OK)))
|
||||
return c.JSON(response.Make(&category_tree, 0, i18n.T_(c, response.Message_OK)))
|
||||
}
|
||||
|
||||
func (h *MenuHandler) GetBreadcrumb(c fiber.Ctx) error {
|
||||
lang_id, ok := c.Locals("langID").(uint)
|
||||
if !ok {
|
||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||
}
|
||||
|
||||
root_category_id_attribute := c.Query("root_category_id")
|
||||
root_category_id, err := strconv.Atoi(root_category_id_attribute)
|
||||
if err != nil {
|
||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||
}
|
||||
|
||||
category_id_attribute := c.Query("category_id")
|
||||
category_id, err := strconv.Atoi(category_id_attribute)
|
||||
if err != nil {
|
||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||
}
|
||||
|
||||
breadcrumb, err := h.menuService.GetBreadcrumb(uint(root_category_id), uint(category_id), lang_id)
|
||||
if err != nil {
|
||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||
}
|
||||
|
||||
return c.JSON(response.Make(&breadcrumb, 0, i18n.T_(c, response.Message_OK)))
|
||||
}
|
||||
|
||||
func (h *MenuHandler) GetTopMenu(c fiber.Ctx) error {
|
||||
|
||||
@@ -21,10 +21,12 @@ type SettingsResponse struct {
|
||||
|
||||
// AppSettings represents app configuration
|
||||
type AppSettings struct {
|
||||
Name string `json:"name"`
|
||||
Environment string `json:"environment"`
|
||||
BaseURL string `json:"base_url"`
|
||||
PasswordRegex string `json:"password_regex"`
|
||||
Name string `json:"name"`
|
||||
Environment string `json:"environment"`
|
||||
BaseURL string `json:"base_url"`
|
||||
PasswordRegex string `json:"password_regex"`
|
||||
CategoryTreeRootID uint `json:"category_tree_root_id"`
|
||||
ShopDefaultLanguage uint `json:"shop_default_language"`
|
||||
// Config config.Config `json:"config"`
|
||||
}
|
||||
|
||||
@@ -65,10 +67,12 @@ func (h *SettingsHandler) GetSettings(cfg *config.Config) fiber.Handler {
|
||||
return func(c fiber.Ctx) error {
|
||||
settings := SettingsResponse{
|
||||
App: AppSettings{
|
||||
Name: cfg.App.Name,
|
||||
Environment: cfg.App.Environment,
|
||||
BaseURL: cfg.App.BaseURL,
|
||||
PasswordRegex: constdata.PASSWORD_VALIDATION_REGEX,
|
||||
Name: cfg.App.Name,
|
||||
Environment: cfg.App.Environment,
|
||||
BaseURL: cfg.App.BaseURL,
|
||||
PasswordRegex: constdata.PASSWORD_VALIDATION_REGEX,
|
||||
CategoryTreeRootID: constdata.CATEGORY_TREE_ROOT_ID,
|
||||
ShopDefaultLanguage: constdata.SHOP_DEFAULT_LANGUAGE,
|
||||
// Config: *config.Get(),
|
||||
},
|
||||
Server: ServerSettings{
|
||||
|
||||
33
app/model/category.go
Normal file
33
app/model/category.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package model
|
||||
|
||||
type ScannedCategory struct {
|
||||
CategoryID uint `gorm:"column:category_id;primaryKey"`
|
||||
Name string `gorm:"column:name"`
|
||||
Active uint `gorm:"column:active"`
|
||||
Position uint `gorm:"column:position"`
|
||||
ParentID uint `gorm:"column:id_parent"`
|
||||
IsRoot uint `gorm:"column:is_root_category"`
|
||||
LinkRewrite string `gorm:"column:link_rewrite"`
|
||||
IsoCode string `gorm:"column:iso_code"`
|
||||
|
||||
Visited bool //this is for internal backend use only
|
||||
}
|
||||
|
||||
type Category struct {
|
||||
CategoryID uint `json:"category_id" form:"category_id"`
|
||||
Label string `json:"label" form:"label"`
|
||||
// Active bool `json:"active" form:"active"`
|
||||
Params CategoryParams `json:"params" form:"params"`
|
||||
Children []Category `json:"children" form:"children"`
|
||||
}
|
||||
|
||||
type CategoryParams struct {
|
||||
CategoryID uint `json:"category_id" form:"category_id"`
|
||||
LinkRewrite string `json:"link_rewrite" form:"link_rewrite"`
|
||||
Locale string `json:"locale" form:"locale"`
|
||||
}
|
||||
|
||||
type CategoryInBreadcrumb struct {
|
||||
CategoryID uint `json:"category_id" form:"category_id"`
|
||||
Name string `json:"name" form:"name"`
|
||||
}
|
||||
@@ -84,28 +84,4 @@ type ProductFilters struct {
|
||||
InStock uint `query:"stock,omitempty"`
|
||||
}
|
||||
|
||||
type ScannedCategory struct {
|
||||
CategoryID uint `gorm:"column:category_id;primaryKey"`
|
||||
Name string `gorm:"column:name"`
|
||||
Active uint `gorm:"column:active"`
|
||||
Position uint `gorm:"column:position"`
|
||||
ParentID uint `gorm:"column:id_parent"`
|
||||
IsRoot uint `gorm:"column:is_root_category"`
|
||||
LinkRewrite string `gorm:"column:link_rewrite"`
|
||||
IsoCode string `gorm:"column:iso_code"`
|
||||
}
|
||||
type Category struct {
|
||||
CategoryID uint `json:"category_id" form:"category_id"`
|
||||
Label string `json:"label" form:"label"`
|
||||
// Active bool `json:"active" form:"active"`
|
||||
Params CategpryParams `json:"params" form:"params"`
|
||||
Children []Category `json:"children" form:"children"`
|
||||
}
|
||||
|
||||
type CategpryParams struct {
|
||||
CategoryID uint `json:"category_id" form:"category_id"`
|
||||
LinkRewrite string `json:"link_rewrite" form:"link_rewrite"`
|
||||
Locale string `json:"locale" form:"locale"`
|
||||
}
|
||||
|
||||
type FeatVal = map[uint][]uint
|
||||
|
||||
@@ -18,7 +18,10 @@ type ProductDescription struct {
|
||||
AvailableLater string `gorm:"column:available_later;type:varchar(255)" json:"available_later" form:"available_later"`
|
||||
DeliveryInStock string `gorm:"column:delivery_in_stock;type:varchar(255)" json:"delivery_in_stock" form:"delivery_in_stock"`
|
||||
DeliveryOutStock string `gorm:"column:delivery_out_stock;type:varchar(255)" json:"delivery_out_stock" form:"delivery_out_stock"`
|
||||
Usage string `gorm:"column:usage;type:text" json:"usage" form:"usage"`
|
||||
Usage string `gorm:"column:_usage_;type:text" json:"usage" form:"usage"`
|
||||
|
||||
ImageLink string `gorm:"column:image_link" json:"image_link"`
|
||||
ExistsInDatabase bool `gorm:"-" json:"exists_in_database"`
|
||||
}
|
||||
|
||||
type ProductRow struct {
|
||||
|
||||
@@ -37,12 +37,11 @@ func (r *CategoriesRepo) GetAllCategories(idLang uint) ([]model.ScannedCategory,
|
||||
ps_category_lang.link_rewrite AS link_rewrite,
|
||||
ps_lang.iso_code AS iso_code
|
||||
`).
|
||||
Joins(`LEFT JOIN ? ON ??.id_category = ??.id_category AND ??.id_shop = ? AND ??.id_lang = ?`,
|
||||
categoryLangTbl, categoryLangTbl, categoryTbl, categoryLangTbl, constdata.SHOP_ID, categoryLangTbl, idLang).
|
||||
Joins(`LEFT JOIN ? ON ??.id_category = ??.id_category AND ??.id_shop = ?`,
|
||||
categoryShopTbl, categoryShopTbl, categoryTbl, categoryShopTbl, constdata.SHOP_ID).
|
||||
Joins(`JOIN ? ON ??.id_lang = ??.id_lang`,
|
||||
langTbl, langTbl, categoryLangTbl).
|
||||
Joins(`LEFT JOIN `+categoryLangTbl+` ON `+categoryLangTbl+`.id_category = `+categoryTbl+`.id_category AND `+categoryLangTbl+`.id_shop = ? AND `+categoryLangTbl+`.id_lang = ?`,
|
||||
constdata.SHOP_ID, idLang).
|
||||
Joins(`LEFT JOIN `+categoryShopTbl+` ON `+categoryShopTbl+`.id_category = `+categoryTbl+`.id_category AND `+categoryShopTbl+`.id_shop = ?`,
|
||||
constdata.SHOP_ID).
|
||||
Joins(`JOIN ` + langTbl + ` ON ` + langTbl + `.id_lang = ` + categoryLangTbl + `.id_lang`).
|
||||
Scan(&allCategories).Error
|
||||
|
||||
return allCategories, err
|
||||
|
||||
@@ -32,7 +32,7 @@ func (repo *ListRepo) ListProducts(id_lang uint, p find.Paging, filt *filters.Fi
|
||||
ps.id_product AS product_id,
|
||||
pl.name AS name,
|
||||
pl.link_rewrite AS link_rewrite,
|
||||
CONCAT(?, ims.id_image, '-small_default/', pl.link_rewrite, '.webp') AS image_link,
|
||||
CONCAT(?, '/', ims.id_image, '-small_default/', pl.link_rewrite, '.webp') AS image_link,
|
||||
cl.name AS category_name,
|
||||
p.reference AS reference,
|
||||
COALESCE(v.variants_number, 0) AS variants_number,
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package productDescriptionRepo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/db"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
|
||||
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||
"github.com/WinterYukky/gorm-extra-clause-plugin/exclause"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UIProductDescriptionRepo interface {
|
||||
@@ -28,14 +31,42 @@ func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productid
|
||||
var ProductDescription model.ProductDescription
|
||||
|
||||
err := db.Get().
|
||||
Model(dbmodel.PsProductLang{}).
|
||||
Where(&dbmodel.PsProductLang{
|
||||
IDProduct: int32(productID),
|
||||
IDShop: int32(constdata.SHOP_ID),
|
||||
IDLang: int32(productid_lang),
|
||||
}).
|
||||
Select(`
|
||||
`+dbmodel.PsProductLangCols.IDProduct.TabCol()+` AS id_product,
|
||||
`+dbmodel.PsProductLangCols.IDShop.TabCol()+` AS id_shop,
|
||||
`+dbmodel.PsProductLangCols.IDLang.TabCol()+` AS id_lang,
|
||||
`+dbmodel.PsProductLangCols.Description.TabCol()+` AS description,
|
||||
`+dbmodel.PsProductLangCols.DescriptionShort.TabCol()+` AS description_short,
|
||||
`+dbmodel.PsProductLangCols.LinkRewrite.TabCol()+` AS link_rewrite,
|
||||
`+dbmodel.PsProductLangCols.MetaDescription.TabCol()+` AS meta_description,
|
||||
`+dbmodel.PsProductLangCols.MetaKeywords.TabCol()+` AS meta_keywords,
|
||||
`+dbmodel.PsProductLangCols.MetaTitle.TabCol()+` AS meta_title,
|
||||
`+dbmodel.PsProductLangCols.Name.TabCol()+` AS name,
|
||||
`+dbmodel.PsProductLangCols.AvailableNow.TabCol()+` AS available_now,
|
||||
`+dbmodel.PsProductLangCols.AvailableLater.TabCol()+` AS available_later,
|
||||
`+dbmodel.PsProductLangCols.DeliveryInStock.TabCol()+` AS delivery_in_stock,
|
||||
`+dbmodel.PsProductLangCols.DeliveryOutStock.TabCol()+` AS delivery_out_stock,
|
||||
`+dbmodel.PsProductLangCols.Usage.TabCol()+` AS _usage_,
|
||||
CONCAT(?, '/', `+dbmodel.PsImageShopCols.IDImage.TabCol()+`, '-large_default/', `+dbmodel.PsProductLangCols.LinkRewrite.TabCol()+`, '.webp') AS image_link
|
||||
`, config.Get().Image.ImagePrefix).
|
||||
Joins("JOIN " + dbmodel.TableNamePsImageShop +
|
||||
" ON " + dbmodel.PsImageShopCols.IDProduct.TabCol() + "=" + dbmodel.PsProductLangCols.IDProduct.TabCol() +
|
||||
" AND " + dbmodel.PsImageShopCols.Cover.TabCol() + " = 1").
|
||||
First(&ProductDescription).Error
|
||||
if err != nil {
|
||||
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// handle "not found" case only
|
||||
ProductDescription.ExistsInDatabase = false
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("database error: %w", err)
|
||||
} else {
|
||||
ProductDescription.ExistsInDatabase = true
|
||||
}
|
||||
|
||||
return &ProductDescription, nil
|
||||
@@ -50,6 +81,7 @@ func (r *ProductDescriptionRepo) CreateIfDoesNotExist(productID uint, productid_
|
||||
}
|
||||
|
||||
err := db.Get().
|
||||
Model(dbmodel.PsProductLang{}).
|
||||
Where(&dbmodel.PsProductLang{
|
||||
IDProduct: int32(productID),
|
||||
IDShop: int32(constdata.SHOP_ID),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package menuService
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||
@@ -21,7 +22,7 @@ func New() *MenuService {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MenuService) GetMenu(id_lang uint) (*model.Category, error) {
|
||||
func (s *MenuService) GetCategoryTree(root_category_id uint, id_lang uint) (*model.Category, error) {
|
||||
all_categories, err := s.categoriesRepo.GetAllCategories(id_lang)
|
||||
if err != nil {
|
||||
return &model.Category{}, err
|
||||
@@ -31,7 +32,7 @@ func (s *MenuService) GetMenu(id_lang uint) (*model.Category, error) {
|
||||
root_index := 0
|
||||
root_found := false
|
||||
for i := 0; i < len(all_categories); i++ {
|
||||
if all_categories[i].IsRoot == 1 {
|
||||
if all_categories[i].CategoryID == root_category_id {
|
||||
root_index = i
|
||||
root_found = true
|
||||
break
|
||||
@@ -44,6 +45,7 @@ func (s *MenuService) GetMenu(id_lang uint) (*model.Category, error) {
|
||||
// now create the children and reorder them according to position
|
||||
id_to_index := make(map[uint]int)
|
||||
for i := 0; i < len(all_categories); i++ {
|
||||
all_categories[i].Visited = false
|
||||
id_to_index[all_categories[i].CategoryID] = i
|
||||
}
|
||||
|
||||
@@ -58,19 +60,32 @@ func (s *MenuService) GetMenu(id_lang uint) (*model.Category, error) {
|
||||
}
|
||||
|
||||
// finally, create the tree
|
||||
tree := s.createTree(root_index, &all_categories, &children_indices)
|
||||
tree, success := s.createTree(root_index, &all_categories, &children_indices)
|
||||
if !success {
|
||||
return &tree, responseErrors.ErrCircularDependency
|
||||
}
|
||||
|
||||
return &tree, nil
|
||||
}
|
||||
|
||||
func (s *MenuService) createTree(index int, all_categories *([]model.ScannedCategory), children_indices *(map[int][]ChildWithPosition)) model.Category {
|
||||
func (s *MenuService) createTree(index int, all_categories *([]model.ScannedCategory), children_indices *(map[int][]ChildWithPosition)) (model.Category, bool) {
|
||||
node := s.scannedToNormalCategory((*all_categories)[index])
|
||||
|
||||
if (*all_categories)[index].Visited {
|
||||
return node, false
|
||||
}
|
||||
(*all_categories)[index].Visited = true
|
||||
|
||||
for i := 0; i < len((*children_indices)[index]); i++ {
|
||||
node.Children = append(node.Children, s.createTree((*children_indices)[index][i].Index, all_categories, children_indices))
|
||||
next_child, success := s.createTree((*children_indices)[index][i].Index, all_categories, children_indices)
|
||||
if !success {
|
||||
return node, false
|
||||
}
|
||||
node.Children = append(node.Children, next_child)
|
||||
}
|
||||
|
||||
return node
|
||||
(*all_categories)[index].Visited = false // just in case we have a "diamond" diagram
|
||||
return node, true
|
||||
}
|
||||
|
||||
func (s *MenuService) GetRoutes(id_lang uint) ([]model.Route, error) {
|
||||
@@ -83,7 +98,7 @@ func (s *MenuService) scannedToNormalCategory(scanned model.ScannedCategory) mod
|
||||
normal.CategoryID = scanned.CategoryID
|
||||
normal.Label = scanned.Name
|
||||
// normal.Active = scanned.Active == 1
|
||||
normal.Params = model.CategpryParams{CategoryID: normal.CategoryID, LinkRewrite: scanned.LinkRewrite, Locale: scanned.IsoCode}
|
||||
normal.Params = model.CategoryParams{CategoryID: normal.CategoryID, LinkRewrite: scanned.LinkRewrite, Locale: scanned.IsoCode}
|
||||
normal.Children = []model.Category{}
|
||||
return normal
|
||||
}
|
||||
@@ -98,6 +113,69 @@ func (a ByPosition) Len() int { return len(a) }
|
||||
func (a ByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByPosition) Less(i, j int) bool { return a[i].Position < a[j].Position }
|
||||
|
||||
func (s *MenuService) GetBreadcrumb(root_category_id uint, start_category_id uint, id_lang uint) ([]model.CategoryInBreadcrumb, error) {
|
||||
all_categories, err := s.categoriesRepo.GetAllCategories(id_lang)
|
||||
if err != nil {
|
||||
return []model.CategoryInBreadcrumb{}, err
|
||||
}
|
||||
|
||||
breadcrumb := []model.CategoryInBreadcrumb{}
|
||||
|
||||
start_index := 0
|
||||
start_found := false
|
||||
for i := 0; i < len(all_categories); i++ {
|
||||
if all_categories[i].CategoryID == start_category_id {
|
||||
start_index = i
|
||||
start_found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !start_found {
|
||||
return []model.CategoryInBreadcrumb{}, responseErrors.ErrStartCategoryNotFound
|
||||
}
|
||||
|
||||
// map category ids to indices
|
||||
id_to_index := make(map[uint]int)
|
||||
for i := 0; i < len(all_categories); i++ {
|
||||
all_categories[i].Visited = false
|
||||
id_to_index[all_categories[i].CategoryID] = i
|
||||
}
|
||||
|
||||
// do a simple graph traversal, always jumping from node to its parent
|
||||
index := start_index
|
||||
success := true
|
||||
for {
|
||||
if all_categories[index].Visited {
|
||||
success = false
|
||||
break
|
||||
}
|
||||
all_categories[index].Visited = true
|
||||
|
||||
var next_category model.CategoryInBreadcrumb
|
||||
next_category.CategoryID = all_categories[index].CategoryID
|
||||
next_category.Name = all_categories[index].Name
|
||||
breadcrumb = append(breadcrumb, next_category)
|
||||
|
||||
if all_categories[index].CategoryID == root_category_id {
|
||||
break
|
||||
}
|
||||
next_index, ok := id_to_index[all_categories[index].ParentID]
|
||||
if !ok {
|
||||
success = false
|
||||
break
|
||||
}
|
||||
index = next_index
|
||||
}
|
||||
|
||||
slices.Reverse(breadcrumb)
|
||||
|
||||
if !success {
|
||||
return breadcrumb, responseErrors.ErrRootNeverReached
|
||||
}
|
||||
|
||||
return breadcrumb, nil
|
||||
}
|
||||
|
||||
func (s *MenuService) GetTopMenu(id uint) ([]*model.B2BTopMenu, error) {
|
||||
items, err := s.routesRepo.GetTopMenu(id)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,6 +3,11 @@ package constdata
|
||||
// PASSWORD_VALIDATION_REGEX is used by the frontend (JavaScript supports lookaheads).
|
||||
const PASSWORD_VALIDATION_REGEX = `^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{10,}$`
|
||||
const SHOP_ID = 1
|
||||
const SHOP_DEFAULT_LANGUAGE = 1
|
||||
|
||||
// CATEGORY_TREE_ROOT_ID corresponds to id_category in ps_category which has is_root_category=1
|
||||
const CATEGORY_TREE_ROOT_ID = 2
|
||||
|
||||
const MAX_AMOUNT_OF_CARTS_PER_USER = 10
|
||||
const DEFAULT_NEW_CART_NAME = "new cart"
|
||||
|
||||
|
||||
@@ -50,7 +50,10 @@ var (
|
||||
ErrBadPaging = errors.New("bad or missing paging attribute value in header")
|
||||
|
||||
// Typed errors for menu handler
|
||||
ErrNoRootFound = errors.New("no root found in categories table")
|
||||
ErrNoRootFound = errors.New("no root found in categories table")
|
||||
ErrCircularDependency = errors.New("circular dependency structure in tree (could be caused by improper root id)")
|
||||
ErrStartCategoryNotFound = errors.New("the start category has not been found")
|
||||
ErrRootNeverReached = errors.New("the root category is not an ancestor of start category")
|
||||
|
||||
// Typed errors for carts handler
|
||||
ErrMaxAmtOfCartsReached = errors.New("maximal amount of carts reached")
|
||||
@@ -145,6 +148,12 @@ func GetErrorCode(c fiber.Ctx, err error) string {
|
||||
|
||||
case errors.Is(err, ErrNoRootFound):
|
||||
return i18n.T_(c, "error.no_root_found")
|
||||
case errors.Is(err, ErrCircularDependency):
|
||||
return i18n.T_(c, "error.circular_dependency")
|
||||
case errors.Is(err, ErrStartCategoryNotFound):
|
||||
return i18n.T_(c, "error.start_category_not_found")
|
||||
case errors.Is(err, ErrRootNeverReached):
|
||||
return i18n.T_(c, "error.root_never_reached")
|
||||
|
||||
case errors.Is(err, ErrMaxAmtOfCartsReached):
|
||||
return i18n.T_(c, "error.max_amt_of_carts_reached")
|
||||
@@ -189,6 +198,9 @@ func GetErrorStatus(err error) int {
|
||||
errors.Is(err, ErrInvalidXHTML),
|
||||
errors.Is(err, ErrBadPaging),
|
||||
errors.Is(err, ErrNoRootFound),
|
||||
errors.Is(err, ErrCircularDependency),
|
||||
errors.Is(err, ErrStartCategoryNotFound),
|
||||
errors.Is(err, ErrRootNeverReached),
|
||||
errors.Is(err, ErrMaxAmtOfCartsReached),
|
||||
errors.Is(err, ErrUserHasNoSuchCart),
|
||||
errors.Is(err, ErrProductOrItsVariationDoesNotExist):
|
||||
|
||||
57
bo/components.d.ts
vendored
Normal file
57
bo/components.d.ts
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// biome-ignore lint: disable
|
||||
// oxlint-disable
|
||||
// ------
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
|
||||
export {}
|
||||
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
CartDetails: typeof import('./src/components/customer/CartDetails.vue')['default']
|
||||
CategoryMenu: typeof import('./src/components/inner/categoryMenu.vue')['default']
|
||||
CategoryMenuListing: typeof import('./src/components/inner/categoryMenuListing.vue')['default']
|
||||
Cs_PrivacyPolicyView: typeof import('./src/components/terms/cs_PrivacyPolicyView.vue')['default']
|
||||
Cs_TermsAndConditionsView: typeof import('./src/components/terms/cs_TermsAndConditionsView.vue')['default']
|
||||
En_PrivacyPolicyView: typeof import('./src/components/terms/en_PrivacyPolicyView.vue')['default']
|
||||
En_TermsAndConditionsView: typeof import('./src/components/terms/en_TermsAndConditionsView.vue')['default']
|
||||
LangSwitch: typeof import('./src/components/inner/langSwitch.vue')['default']
|
||||
PageAddresses: typeof import('./src/components/customer/PageAddresses.vue')['default']
|
||||
PageCarts: typeof import('./src/components/customer/PageCarts.vue')['default']
|
||||
PageOrders: typeof import('./src/components/customer/PageOrders.vue')['default']
|
||||
PageProduct: typeof import('./src/components/customer/PageProduct.vue')['default']
|
||||
PageProducts: typeof import('./src/components/admin/PageProducts.vue')['default']
|
||||
PageProfileDetails: typeof import('./src/components/customer/PageProfileDetails.vue')['default']
|
||||
PageProfileDetailsAddInfo: typeof import('./src/components/customer/PageProfileDetailsAddInfo.vue')['default']
|
||||
PageStatistic: typeof import('./src/components/customer/PageStatistic.vue')['default']
|
||||
Pl_PrivacyPolicyView: typeof import('./src/components/terms/pl_PrivacyPolicyView.vue')['default']
|
||||
Pl_TermsAndConditionsView: typeof import('./src/components/terms/pl_TermsAndConditionsView.vue')['default']
|
||||
ProductCustomization: typeof import('./src/components/customer/components/ProductCustomization.vue')['default']
|
||||
ProductDetailView: typeof import('./src/components/admin/ProductDetailView.vue')['default']
|
||||
ProductVariants: typeof import('./src/components/customer/components/ProductVariants.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
ThemeSwitch: typeof import('./src/components/inner/themeSwitch.vue')['default']
|
||||
TopBar: typeof import('./src/components/TopBar.vue')['default']
|
||||
TopBarLogin: typeof import('./src/components/TopBarLogin.vue')['default']
|
||||
UAlert: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Alert.vue')['default']
|
||||
UButton: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Button.vue')['default']
|
||||
UCard: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Card.vue')['default']
|
||||
UCheckbox: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Checkbox.vue')['default']
|
||||
UDrawer: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Drawer.vue')['default']
|
||||
UForm: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Form.vue')['default']
|
||||
UFormField: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/FormField.vue')['default']
|
||||
UIcon: typeof import('./node_modules/@nuxt/ui/dist/runtime/vue/components/Icon.vue')['default']
|
||||
UInput: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Input.vue')['default']
|
||||
UInputNumber: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/InputNumber.vue')['default']
|
||||
UModal: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Modal.vue')['default']
|
||||
UNavigationMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/NavigationMenu.vue')['default']
|
||||
UPagination: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Pagination.vue')['default']
|
||||
USelect: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Select.vue')['default']
|
||||
USelectMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/SelectMenu.vue')['default']
|
||||
UTable: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Table.vue')['default']
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,11 @@
|
||||
<template>
|
||||
<suspense>
|
||||
<component :is="Default || 'div'">
|
||||
<div class="container mx-auto mt-20">
|
||||
<!-- <UNavigationMenu orientation="vertical" :items="listing" class="data-[orientation=vertical]:w-48">
|
||||
<template #item="{ item, active }">
|
||||
<div class="flex items-center gap-2 px-3 py-2">
|
||||
<UIcon name="i-heroicons-book-open" />
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</UNavigationMenu> -->
|
||||
<h1 class="text-2xl font-bold mb-6 text-gray-900 dark:text-white">Products</h1>
|
||||
<div v-if="loading" class="text-center py-8">
|
||||
<span class="text-gray-600 dark:text-gray-400">Loading products...</span>
|
||||
</div>
|
||||
<div v-else-if="error" class="mb-4 p-3 bg-red-100 text-red-700 rounded">
|
||||
{{ error }}
|
||||
</div>
|
||||
<div v-else class="overflow-x-auto">
|
||||
<div class="flex gap-2">
|
||||
<CategoryMenuListing />
|
||||
<UTable :data="productsList" :columns="columns" class="flex-1">
|
||||
<template #expanded="{ row }">
|
||||
<UTable :data="productsList.slice(0, 3)" :columns="columnsChild" :ui="{
|
||||
thead: 'hidden'
|
||||
}" />
|
||||
</template>
|
||||
</UTable>
|
||||
</div>
|
||||
<div class="flex justify-center items-center py-8">
|
||||
<UPagination v-model:page="page" :total="total" :page-size="perPage" />
|
||||
</div>
|
||||
<div v-if="productsList.length === 0" class="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
No products found
|
||||
</div>
|
||||
<div class="flex gap-10">
|
||||
<CategoryMenu />
|
||||
<div class="w-full flex flex-col items-center gap-4">
|
||||
<UTable :data="productsList" :columns="columns" class="flex-1 w-full" />
|
||||
<UPagination v-model:page="page" :total="total" :items-per-page="perPage" />
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
@@ -46,26 +18,20 @@ import { useFetchJson } from '@/composable/useFetchJson'
|
||||
import Default from '@/layouts/default.vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
import CategoryMenuListing from '../inner/categoryMenuListing.vue'
|
||||
|
||||
interface Product {
|
||||
reference: number
|
||||
product_id: number
|
||||
name: string
|
||||
image_link: string
|
||||
link_rewrite: string
|
||||
}
|
||||
import CategoryMenu from '../inner/categoryMenu.vue'
|
||||
import type { Product } from '@/types/product'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const perPage = ref(15)
|
||||
const page = computed({
|
||||
get: () => Number(route.query.page) || 1,
|
||||
get: () => Number(route.query.p) || 1,
|
||||
set: (val: number) => {
|
||||
router.push({
|
||||
query: {
|
||||
...route.query,
|
||||
page: val
|
||||
p: val
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -101,7 +67,6 @@ const sortField = computed({
|
||||
}
|
||||
})
|
||||
|
||||
const perPage = ref(15)
|
||||
const total = ref(0)
|
||||
|
||||
interface ApiResponse {
|
||||
@@ -162,17 +127,25 @@ async function fetchProductList() {
|
||||
error.value = null
|
||||
|
||||
const params = new URLSearchParams()
|
||||
|
||||
Object.entries(route.query).forEach(([key, value]) => {
|
||||
if (value) params.append(key, String(value))
|
||||
if (value === undefined || value === null) return
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(v => params.append(key, String(v)))
|
||||
} else {
|
||||
params.append(key, String(value))
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
const url = `/api/v1/restricted/list-products/get-listing?${params}`
|
||||
if (route.params.category_id)
|
||||
params.append('category_id', String(route.params.category_id))
|
||||
|
||||
const url = `/api/v1/restricted/list/list-products?elems=${perPage.value}&${params.toString()}`
|
||||
try {
|
||||
const response = await useFetchJson<ApiResponse>(url)
|
||||
productsList.value = response.items || []
|
||||
total.value = response.count || 0
|
||||
total.value = Number(response.count) || 0
|
||||
} catch (e: unknown) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to load products'
|
||||
} finally {
|
||||
@@ -182,16 +155,11 @@ async function fetchProductList() {
|
||||
|
||||
function goToProduct(productId: number) {
|
||||
router.push({
|
||||
name: 'product-detail',
|
||||
params: { id: productId }
|
||||
name: 'customer-product-details',
|
||||
params: { product_id: productId }
|
||||
})
|
||||
}
|
||||
|
||||
const selectedCount = ref({
|
||||
product_id: null,
|
||||
count: 0
|
||||
})
|
||||
|
||||
function getIcon(name: string) {
|
||||
if (sortField.value[0] === name) {
|
||||
if (sortField.value[1] === 'asc') return 'i-lucide-arrow-up-narrow-wide'
|
||||
@@ -205,25 +173,7 @@ const UInput = resolveComponent('UInput')
|
||||
const UButton = resolveComponent('UButton')
|
||||
const UIcon = resolveComponent('UIcon')
|
||||
|
||||
const columns: TableColumn<Payment>[] = [
|
||||
{
|
||||
id: 'expand',
|
||||
cell: ({ row }) =>
|
||||
h(UButton, {
|
||||
color: 'neutral',
|
||||
variant: 'ghost',
|
||||
icon: 'i-lucide-chevron-down',
|
||||
square: true,
|
||||
'aria-label': 'Expand',
|
||||
ui: {
|
||||
leadingIcon: [
|
||||
'transition-transform',
|
||||
row.getIsExpanded() ? 'duration-200 rotate-180' : ''
|
||||
]
|
||||
},
|
||||
onClick: () => row.toggleExpanded()
|
||||
})
|
||||
},
|
||||
const columns: TableColumn<Product>[] = [
|
||||
{
|
||||
accessorKey: 'product_id',
|
||||
header: ({ column }) => {
|
||||
@@ -314,108 +264,17 @@ const columns: TableColumn<Payment>[] = [
|
||||
},
|
||||
cell: ({ row }) => row.getValue('quantity') as number
|
||||
},
|
||||
{
|
||||
accessorKey: 'count',
|
||||
header: 'Count',
|
||||
cell: ({ row }) => {
|
||||
return h(UInputNumber, {
|
||||
modelValue: selectedCount.value.product_id === row.original.product_id ? selectedCount.value.count : 0,
|
||||
'onUpdate:modelValue': (val: number) => {
|
||||
if (val)
|
||||
selectedCount.value = {
|
||||
product_id: row.original.product_id,
|
||||
count: val
|
||||
}
|
||||
else {
|
||||
selectedCount.value = {
|
||||
product_id: null,
|
||||
count: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
min: 0,
|
||||
max: row.original.quantity
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'count',
|
||||
header: '',
|
||||
cell: ({ row }) => {
|
||||
return h(UButton, {
|
||||
onClick: () => {
|
||||
console.log('Clicked', row.original)
|
||||
goToProduct(row.original.product_id)
|
||||
},
|
||||
color: selectedCount.value.product_id !== row.original.product_id ? 'info' : 'primary',
|
||||
disabled: selectedCount.value.product_id !== row.original.product_id,
|
||||
color: 'primary',
|
||||
variant: 'solid'
|
||||
}, 'Add to cart')
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
const columnsChild: TableColumn<Payment>[] = [
|
||||
{
|
||||
accessorKey: 'product_id',
|
||||
header: '',
|
||||
cell: ({ row }) => `#${row.getValue('product_id') as number}`
|
||||
},
|
||||
{
|
||||
accessorKey: 'image_link',
|
||||
header: '',
|
||||
cell: ({ row }) => {
|
||||
return h('img', {
|
||||
src: row.getValue('image_link') as string,
|
||||
style: 'width:40px;height:40px;object-fit:cover;'
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: '',
|
||||
cell: ({ row }) => row.getValue('name') as string
|
||||
},
|
||||
{
|
||||
accessorKey: 'quantity',
|
||||
header: '',
|
||||
cell: ({ row }) => row.getValue('quantity') as number
|
||||
},
|
||||
{
|
||||
accessorKey: 'count',
|
||||
header: '',
|
||||
cell: ({ row }) => {
|
||||
return h(UInputNumber, {
|
||||
modelValue: selectedCount.value.product_id === row.original.product_id ? selectedCount.value.count : 0,
|
||||
'onUpdate:modelValue': (val: number) => {
|
||||
if (val)
|
||||
selectedCount.value = {
|
||||
product_id: row.original.product_id,
|
||||
count: val
|
||||
}
|
||||
else {
|
||||
selectedCount.value = {
|
||||
product_id: null,
|
||||
count: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
min: 0,
|
||||
max: row.original.quantity
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'count',
|
||||
header: '',
|
||||
cell: ({ row }) => {
|
||||
return h(UButton, {
|
||||
onClick: () => {
|
||||
console.log('Clicked', row.original)
|
||||
},
|
||||
color: selectedCount.value.product_id !== row.original.product_id ? 'info' : 'primary',
|
||||
disabled: selectedCount.value.product_id !== row.original.product_id,
|
||||
variant: 'solid'
|
||||
}, 'Add to cart')
|
||||
}, () => 'Show product')
|
||||
},
|
||||
}
|
||||
]
|
||||
@@ -427,4 +286,4 @@ watch(
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -1,126 +1,130 @@
|
||||
<template>
|
||||
<component :is="Default || 'div'">
|
||||
<div class="container my-10 mx-auto ">
|
||||
<div class="container my-10 mx-auto ">
|
||||
|
||||
<div
|
||||
class="flex items-end justify-between gap-4 mb-6 bg-(--second-light) dark:bg-(--main-dark) border border-(--border-light) dark:border-(--border-dark) p-4 rounded-md">
|
||||
<div class="flex items-end gap-3">
|
||||
<USelect v-model="selectedLanguage" :items="availableLangs" variant="outline" class="w-40!" valueKey="iso_code">
|
||||
<template #default="{ modelValue }">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-md">{{availableLangs.find(x => x.iso_code == modelValue)?.flag}}</span>
|
||||
<span class="font-medium dark:text-white text-black">{{availableLangs.find(x => x.iso_code ==
|
||||
modelValue)?.name}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #item-leading="{ item }">
|
||||
<div class="flex items-center rounded-md cursor-pointer transition-colors">
|
||||
<span class="text-md">{{ item.flag }}</span>
|
||||
<span class="ml-2 dark:text-white text-black font-medium">{{ item.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</USelect>
|
||||
<div
|
||||
class="flex items-end justify-between gap-4 mb-6 bg-(--second-light) dark:bg-(--main-dark) border border-(--border-light) dark:border-(--border-dark) p-4 rounded-md">
|
||||
<div class="flex items-end gap-3">
|
||||
<USelect v-model="selectedLanguage" :items="availableLangs" variant="outline" class="w-40!"
|
||||
valueKey="iso_code">
|
||||
<template #default="{ modelValue }">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-md">{{availableLangs.find(x => x.iso_code == modelValue)?.flag}}</span>
|
||||
<span class="font-medium dark:text-white text-black">{{availableLangs.find(x => x.iso_code ==
|
||||
modelValue)?.name}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #item-leading="{ item }">
|
||||
<div class="flex items-center rounded-md cursor-pointer transition-colors">
|
||||
<span class="text-md">{{ item.flag }}</span>
|
||||
<span class="ml-2 dark:text-white text-black font-medium">{{ item.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</USelect>
|
||||
</div>
|
||||
<UButton @click="translateToSelectedLanguage" color="primary" :loading="translating"
|
||||
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) px-12!">
|
||||
Translate
|
||||
</UButton>
|
||||
</div>
|
||||
<UButton @click="translateToSelectedLanguage" color="primary" :loading="translating"
|
||||
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) px-12!">
|
||||
Translate
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<div v-if="translating" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
<div class="flex flex-col items-center gap-4 p-8 bg-(--main-light) dark:bg-(--main-dark) rounded-lg shadow-xl">
|
||||
<div v-if="translating" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
<div class="flex flex-col items-center gap-4 p-8 bg-(--main-light) dark:bg-(--main-dark) rounded-lg shadow-xl">
|
||||
<UIcon name="svg-spinners:ring-resize" class="text-4xl text-primary" />
|
||||
<p class="text-lg font-medium dark:text-white text-black">Translating...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="productStore.loading" class="flex items-center justify-center py-20">
|
||||
<UIcon name="svg-spinners:ring-resize" class="text-4xl text-primary" />
|
||||
<p class="text-lg font-medium dark:text-white text-black">Translating...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="productStore.loading" class="flex items-center justify-center py-20">
|
||||
<UIcon name="svg-spinners:ring-resize" class="text-4xl text-primary" />
|
||||
</div>
|
||||
<div v-else-if="productStore.error" class="flex items-center justify-center py-20">
|
||||
<p class="text-red-500">{{ productStore.error }}</p>
|
||||
</div>
|
||||
<div v-else-if="productStore.productDescription" class="flex items-start gap-30">
|
||||
<div class="w-80 h-80 bg-(--second-light) dark:bg-gray-700 rounded-lg flex items-center justify-center">
|
||||
<span class="text-gray-500 dark:text-gray-400">Product Image</span>
|
||||
<div v-else-if="productStore.error" class="flex items-center justify-center py-20">
|
||||
<p class="text-red-500">{{ productStore.error }}</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-[25px] font-bold text-black dark:text-white">
|
||||
{{ productStore.productDescription.name || 'Product Name' }}
|
||||
</p>
|
||||
<p v-html="productStore.productDescription.description_short" class="text-black dark:text-white"></p>
|
||||
<div class="space-y-[10px]">
|
||||
<div class="flex items-center gap-1">
|
||||
<UIcon name="lets-icons:done-ring-round-fill" class="text-[20px] text-green-600" />
|
||||
<p class="text-[16px] font-bold text-(--accent-blue-light) dark:text-(--accent-blue-dark)">
|
||||
{{ productStore.productDescription.available_now }}
|
||||
</p>
|
||||
<div v-else-if="productStore.productDescription" class="flex items-start gap-30">
|
||||
<div class="w-80 h-80 bg-(--second-light) dark:bg-gray-700 rounded-lg flex items-center justify-center">
|
||||
<span class="text-gray-500 dark:text-gray-400">Product Image</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-[25px] font-bold text-black dark:text-white">
|
||||
{{ productStore.productDescription.name || 'Product Name' }}
|
||||
</p>
|
||||
<p v-html="productStore.productDescription.description_short" class="text-black dark:text-white"></p>
|
||||
<div class="space-y-[10px]">
|
||||
<div class="flex items-center gap-1">
|
||||
<UIcon name="lets-icons:done-ring-round-fill" class="text-[20px] text-green-600" />
|
||||
<p class="text-[16px] font-bold text-(--accent-blue-light) dark:text-(--accent-blue-dark)">
|
||||
{{ productStore.productDescription.available_now }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<UIcon name="marketeq:car-shipping" class="text-[25px] text-green-600" />
|
||||
<p class="text-[18px] font-bold text-black dark:text-white">
|
||||
{{ productStore.productDescription.delivery_in_stock || 'Delivery information' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<UIcon name="marketeq:car-shipping" class="text-[25px] text-green-600" />
|
||||
<p class="text-[18px] font-bold text-black dark:text-white">
|
||||
{{ productStore.productDescription.delivery_in_stock || 'Delivery information' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="productStore.productDescription" class="mt-16">
|
||||
<div class="flex gap-4 my-6">
|
||||
<UButton @click="activeTab = 'description'"
|
||||
:class="['cursor-pointer', activeTab === 'description' ? 'bg-blue-500 text-white' : '']" color="neutral"
|
||||
variant="outline">
|
||||
<p class="dark:text-white">Description</p>
|
||||
</UButton>
|
||||
|
||||
<UButton @click="activeTab = 'usage'"
|
||||
:class="['cursor-pointer', activeTab === 'usage' ? 'bg-blue-500 text-white' : '']" color="neutral"
|
||||
variant="outline">
|
||||
<p class="dark:text-white">Usage</p>
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'usage'"
|
||||
class="px-8 py-4 border border-(--border-light) dark:border-(--border-dark) rounded-md bg-(--second-light) dark:bg-(--main-dark)">
|
||||
|
||||
<div class="flex justify-end items-center gap-3 mb-4">
|
||||
<UButton v-if="!isEditing" @click="enableEdit"
|
||||
class="flex items-center gap-2 m-2 cursor-pointer bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)!">
|
||||
<p class="text-white">Change Text</p>
|
||||
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
||||
</UButton>
|
||||
<UButton v-if="isEditing" @click="saveText" color="neutral" variant="outline" class="p-2.5 cursor-pointer">
|
||||
<p class="dark:text-white text-black">Save the edited text</p>
|
||||
</UButton>
|
||||
<UButton v-if="isEditing" @click="cancelEdit" color="neutral" variant="outline"
|
||||
class="p-2.5 cursor-pointer">
|
||||
Cancel
|
||||
</UButton>
|
||||
</div>
|
||||
<p ref="usageRef" v-html="productStore.productDescription.usage"
|
||||
class="flex flex-col justify-center w-full text-start dark:text-white! text-black!"></p>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'description'"
|
||||
class="px-8 py-4 border border-(--border-light) dark:border-(--border-dark) rounded-md bg-(--second-light) dark:bg-(--main-dark)">
|
||||
<div class="flex items-center justify-end gap-3 mb-4">
|
||||
<UButton v-if="!descriptionEdit.isEditing.value" @click="enableDescriptionEdit"
|
||||
class="flex items-center gap-2 m-2 cursor-pointer bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)!">
|
||||
<p class="text-white">Change Text</p>
|
||||
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
||||
</UButton>
|
||||
<UButton v-if="descriptionEdit.isEditing.value" @click="saveDescription" color="neutral" variant="outline"
|
||||
class="p-2.5 cursor-pointer">
|
||||
<p class="dark:text-white text-black ">Save the edited text</p>
|
||||
</UButton>
|
||||
<UButton v-if="descriptionEdit.isEditing.value" @click="cancelDescriptionEdit" color="neutral"
|
||||
variant="outline" class="p-2.5 cursor-pointer">Cancel</UButton>
|
||||
</div>
|
||||
<div ref="descriptionRef" v-html="productStore.productDescription.description"
|
||||
class="flex flex-col justify-center dark:text-white text-black">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="productStore.productDescription" class="mt-16">
|
||||
<div class="flex gap-4 my-6">
|
||||
<UButton @click="activeTab = 'description'"
|
||||
:class="['cursor-pointer', activeTab === 'description' ? 'bg-blue-500 text-white' : '']" color="neutral"
|
||||
variant="outline">
|
||||
<p class="dark:text-white">Description</p>
|
||||
</UButton>
|
||||
|
||||
<UButton @click="activeTab = 'usage'"
|
||||
:class="['cursor-pointer', activeTab === 'usage' ? 'bg-blue-500 text-white' : '']" color="neutral"
|
||||
variant="outline">
|
||||
<p class="dark:text-white">Usage</p>
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'usage'"
|
||||
class="px-8 py-4 border border-(--border-light) dark:border-(--border-dark) rounded-md bg-(--second-light) dark:bg-(--main-dark)">
|
||||
|
||||
<div class="flex justify-end items-center gap-3 mb-4">
|
||||
<UButton v-if="!isEditing" @click="enableEdit"
|
||||
class="flex items-center gap-2 m-2 cursor-pointer bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)!">
|
||||
<p class="text-white">Change Text</p>
|
||||
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
||||
</UButton>
|
||||
<UButton v-if="isEditing" @click="saveText" color="neutral" variant="outline" class="p-2.5 cursor-pointer">
|
||||
<p class="dark:text-white text-black">Save the edited text</p>
|
||||
</UButton>
|
||||
<UButton v-if="isEditing" @click="cancelEdit" color="neutral" variant="outline" class="p-2.5 cursor-pointer">
|
||||
Cancel
|
||||
</UButton>
|
||||
</div>
|
||||
<p ref="usageRef" v-html="productStore.productDescription.usage"
|
||||
class="flex flex-col justify-center w-full text-start dark:text-white! text-black!"></p>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'description'"
|
||||
class="px-8 py-4 border border-(--border-light) dark:border-(--border-dark) rounded-md bg-(--second-light) dark:bg-(--main-dark)">
|
||||
<div class="flex items-center justify-end gap-3 mb-4">
|
||||
<UButton v-if="!descriptionEdit.isEditing.value" @click="enableDescriptionEdit"
|
||||
class="flex items-center gap-2 m-2 cursor-pointer bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)!">
|
||||
<p class="text-white">Change Text</p>
|
||||
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
||||
</UButton>
|
||||
<UButton v-if="descriptionEdit.isEditing.value" @click="saveDescription" color="neutral" variant="outline" class="p-2.5 cursor-pointer">
|
||||
<p class="dark:text-white text-black ">Save the edited text</p>
|
||||
</UButton>
|
||||
<UButton v-if="descriptionEdit.isEditing.value" @click="cancelDescriptionEdit" color="neutral" variant="outline" class="p-2.5 cursor-pointer">Cancel</UButton>
|
||||
</div>
|
||||
<div ref="descriptionRef" v-html="productStore.productDescription.description"
|
||||
class="flex flex-col justify-center dark:text-white text-black">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
@@ -142,7 +146,7 @@ const isEditing = ref(false)
|
||||
|
||||
const availableLangs = computed(() => langs)
|
||||
|
||||
const selectedLanguage = ref('pl')
|
||||
const selectedLanguage = ref('en')
|
||||
|
||||
const currentLangId = ref(2)
|
||||
const productID = ref<number>(0)
|
||||
@@ -176,7 +180,7 @@ const translateToSelectedLanguage = async () => {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const id = route.params.id
|
||||
const id = route.params.product_id
|
||||
if (id) {
|
||||
productID.value = Number(id)
|
||||
await fetchForLanguage(selectedLanguage.value)
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</div>
|
||||
<div v-else class="overflow-x-auto">
|
||||
<div class="flex gap-2">
|
||||
<CategoryMenuListing />
|
||||
<CategoryMenu />
|
||||
<UTable :data="productsList" :columns="columns" class="flex-1">
|
||||
<template #expanded="{ row }">
|
||||
<UTable :data="productsList.slice(0, 3)" :columns="columnsChild" :ui="{
|
||||
@@ -46,7 +46,7 @@ import { useFetchJson } from '@/composable/useFetchJson'
|
||||
import Default from '@/layouts/default.vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
import CategoryMenuListing from '../inner/categoryMenuListing.vue'
|
||||
import CategoryMenu from '../inner/categoryMenu.vue'
|
||||
|
||||
interface Product {
|
||||
reference: number
|
||||
@@ -167,7 +167,7 @@ async function fetchProductList() {
|
||||
if (value) params.append(key, String(value))
|
||||
})
|
||||
|
||||
const url = `/api/v1/restricted/list-products/get-listing?${params}`
|
||||
const url = `/api/v1/restricted/list/list-products?${params}`
|
||||
|
||||
try {
|
||||
const response = await useFetchJson<ApiResponse>(url)
|
||||
|
||||
@@ -1,20 +1,57 @@
|
||||
<template>
|
||||
<UNavigationMenu orientation="vertical" type="single" :items="items" class="data-[orientation=vertical]:w-72" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getMenu } from '@/router/menu'
|
||||
import type { NavigationMenuItem } from '@nuxt/ui';
|
||||
import { ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
let menu = await getMenu() as NavigationMenuItem[]
|
||||
|
||||
const openAll = ref(false)
|
||||
const route = useRoute()
|
||||
|
||||
let categoryId = ref(route.params.category_id)
|
||||
function findPath(tree: NavigationMenuItem[], id: number, path: Array<number> = []): Array<number> | null {
|
||||
for (let item of tree) {
|
||||
let newPath: Array<number> = [...path, item.category_id]
|
||||
if (item.category_id === id) {
|
||||
return newPath
|
||||
}
|
||||
if (item.children) {
|
||||
const result: Array<number> | null = findPath(item.children, id, newPath)
|
||||
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
let path = findPath(menu, Number(categoryId.value))
|
||||
function adaptMenu(menu: NavigationMenuItem[]) {
|
||||
for (const item of menu) {
|
||||
if (item.children && item.children.length > 0) {
|
||||
console.log(item);
|
||||
item.open = path && path.includes(item.category_id) ? true : openAll.value
|
||||
adaptMenu(item.children);
|
||||
item.open = openAll.value
|
||||
item.children.unshift({ label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: { name: 'category', params: item.params } })
|
||||
item.children.unshift({
|
||||
label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: {
|
||||
name: 'customer-products-category', params: {
|
||||
category_id: item.params.category_id,
|
||||
link_rewrite: item.params.link_rewrite
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
item.to = { name: 'category', params: item.params };
|
||||
item.to = {
|
||||
name: 'customer-products-category', params: {
|
||||
category_id: item.params.category_id,
|
||||
link_rewrite: item.params.link_rewrite
|
||||
}
|
||||
};
|
||||
item.icon = 'i-lucide-file-text'
|
||||
}
|
||||
}
|
||||
@@ -22,15 +59,10 @@ function adaptMenu(menu: NavigationMenuItem[]) {
|
||||
}
|
||||
|
||||
menu = adaptMenu(menu)
|
||||
|
||||
const items = ref<NavigationMenuItem[][]>([
|
||||
[
|
||||
...menu as NavigationMenuItem[]
|
||||
],
|
||||
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UNavigationMenu orientation="vertical" type="single" :items="items" class="p-4" />
|
||||
</template>
|
||||
</script>
|
||||
@@ -1,35 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { getMenu } from '@/router/menu'
|
||||
import type { NavigationMenuItem } from '@nuxt/ui';
|
||||
import { ref } from 'vue';
|
||||
let menu = await getMenu() as NavigationMenuItem[]
|
||||
|
||||
const openAll = ref(false)
|
||||
|
||||
function adaptMenu(menu: NavigationMenuItem[]) {
|
||||
for (const item of menu) {
|
||||
if (item.children && item.children.length > 0) {
|
||||
adaptMenu(item.children);
|
||||
item.open = openAll.value
|
||||
item.children.unshift({ label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: { name: 'category', params: item.params } })
|
||||
} else {
|
||||
item.to = { name: 'category', params: item.params };
|
||||
item.icon = 'i-lucide-file-text'
|
||||
}
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
|
||||
menu = adaptMenu(menu)
|
||||
|
||||
const items = ref<NavigationMenuItem[][]>([
|
||||
[
|
||||
...menu as NavigationMenuItem[]
|
||||
],
|
||||
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UNavigationMenu orientation="vertical" type="single" :items="items" class="p-4" />
|
||||
</template>
|
||||
@@ -59,7 +59,6 @@ async function setRoutes() {
|
||||
const componentName = item.component
|
||||
const [, folder] = componentName.split('/')
|
||||
const componentPath = `/src${componentName}`
|
||||
console.log(componentPath);
|
||||
|
||||
|
||||
let modules =
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { useFetchJson } from "@/composable/useFetchJson";
|
||||
import type { MenuItem, Route } from "@/types/menu";
|
||||
import { ref } from "vue";
|
||||
import { settings } from "./settings";
|
||||
|
||||
|
||||
const categoryId = ref()
|
||||
export const getMenu = async () => {
|
||||
const resp = await useFetchJson<MenuItem>('/api/v1/restricted/menu/get-menu');
|
||||
if(!categoryId.value){
|
||||
categoryId.value = settings['app'].category_tree_root_id
|
||||
}
|
||||
const resp = await useFetchJson<MenuItem>(`/api/v1/restricted/menu/get-category-tree?root_category_id=${categoryId.value}`);
|
||||
return resp.items.children
|
||||
}
|
||||
|
||||
|
||||
export const getRoutes = async () => {
|
||||
const resp = await useFetchJson<Route[]>('/api/v1/public/menu/get-routes');
|
||||
|
||||
|
||||
@@ -13,24 +13,24 @@ const products = [
|
||||
// type CategoryProducts = {}
|
||||
|
||||
export const useCategoryStore = defineStore('category', () => {
|
||||
const id_category = ref(0)
|
||||
const idCategory = ref(0)
|
||||
const categoryProducts = ref(products)
|
||||
|
||||
|
||||
function setCategoryID(id: number) {
|
||||
id_category.value = id
|
||||
idCategory.value = id
|
||||
}
|
||||
|
||||
async function getCategoryProducts() {
|
||||
return new Promise<typeof products>((resolve) => {
|
||||
setTimeout(() => {
|
||||
console.log('Fetching products from category id: ', id_category.value);
|
||||
// console.log('Fetching products from category id: ', idCategory.value);
|
||||
resolve(categoryProducts.value)
|
||||
}, 2000 * Math.random())
|
||||
})
|
||||
}
|
||||
return {
|
||||
id_category,
|
||||
idCategory,
|
||||
getCategoryProducts,
|
||||
setCategoryID
|
||||
}
|
||||
|
||||
@@ -34,9 +34,8 @@ export const useProductStore = defineStore('product', () => {
|
||||
|
||||
try {
|
||||
const response = await useFetchJson<ProductDescription>(
|
||||
`/api/v1/restricted/product-description/get-product-description?productID=${productID}&productLangID=${langId}`
|
||||
`/api/v1/restricted/product-translation/get-product-description?productID=${productID}&productLangID=${langId}`
|
||||
)
|
||||
console.log(response, 'dfsfsdf');
|
||||
productDescription.value = response.items
|
||||
|
||||
} catch (e: unknown) {
|
||||
|
||||
10
bo/src/types/product.d.ts
vendored
10
bo/src/types/product.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
export interface ProductDescription {
|
||||
export interface ProductDescription {
|
||||
id?: number
|
||||
name?: string
|
||||
description: string
|
||||
@@ -6,4 +6,12 @@
|
||||
meta_description: string
|
||||
available_now: string
|
||||
usage: string
|
||||
}
|
||||
|
||||
export interface Product {
|
||||
reference: number
|
||||
product_id: number
|
||||
name: string
|
||||
image_link: string
|
||||
link_rewrite: string
|
||||
}
|
||||
1
bo/src/types/settings.d.ts
vendored
1
bo/src/types/settings.d.ts
vendored
@@ -7,6 +7,7 @@ export interface Settings {
|
||||
}
|
||||
|
||||
export interface App {
|
||||
category_tree_root_id: number
|
||||
name: string
|
||||
environment: string
|
||||
base_url: string
|
||||
|
||||
22
bruno/b2b-daniel/get-breadcrumb.yml
Normal file
22
bruno/b2b-daniel/get-breadcrumb.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
info:
|
||||
name: get-breadcrumb
|
||||
type: http
|
||||
seq: 18
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: http://localhost:3000/api/v1/restricted/menu/get-breadcrumb?root_category_id=10&category_id=13
|
||||
params:
|
||||
- name: root_category_id
|
||||
value: "10"
|
||||
type: query
|
||||
- name: category_id
|
||||
value: "13"
|
||||
type: query
|
||||
auth: inherit
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
@@ -1,14 +1,14 @@
|
||||
info:
|
||||
name: get-menu
|
||||
name: get-category-tree
|
||||
type: http
|
||||
seq: 5
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: http://localhost:3000/api/v1/restricted/menu/get-menu?lang_id=1
|
||||
url: http://localhost:3000/api/v1/restricted/menu/get-category-tree?root_category_id=10
|
||||
params:
|
||||
- name: lang_id
|
||||
value: "1"
|
||||
- name: root_category_id
|
||||
value: "10"
|
||||
type: query
|
||||
auth: inherit
|
||||
|
||||
22
bruno/b2b-daniel/get-product-description.yml
Normal file
22
bruno/b2b-daniel/get-product-description.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
info:
|
||||
name: get-product-description
|
||||
type: http
|
||||
seq: 17
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: http://localhost:3000/api/v1/restricted/product-translation/get-product-description?productID=51&productLangID=1
|
||||
params:
|
||||
- name: productID
|
||||
value: "51"
|
||||
type: query
|
||||
- name: productLangID
|
||||
value: "1"
|
||||
type: query
|
||||
auth: inherit
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
Reference in New Issue
Block a user