364 lines
9.2 KiB
Go
364 lines
9.2 KiB
Go
package templates
|
|
|
|
import (
|
|
"fmt"
|
|
"html"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
pscart "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cart"
|
|
pscatalog "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/catalog"
|
|
"git.ma-al.com/goc_marek/ps_shop/internal/viewmodel"
|
|
)
|
|
|
|
var htmlTagPattern = regexp.MustCompile(`<[^>]+>`)
|
|
|
|
func menuListClass(depth int) string {
|
|
if depth == 0 {
|
|
return "flex min-w-0 flex-col gap-2 text-sm"
|
|
}
|
|
return "mt-2 space-y-2 border-l border-stone-200 pl-4 text-sm"
|
|
}
|
|
|
|
func menuLinkClass(depth int) string {
|
|
if depth == 0 {
|
|
return "inline-flex items-center gap-2 px-2 py-2 text-[1.02rem] font-medium text-stone-900 transition hover:text-amber-600"
|
|
}
|
|
return "inline-flex items-center gap-2 text-stone-700 transition hover:text-amber-600"
|
|
}
|
|
|
|
func menuItemClass(depth int, hasChildren bool) string {
|
|
if depth == 0 && hasChildren {
|
|
return "min-w-0"
|
|
}
|
|
return "min-w-0"
|
|
}
|
|
|
|
func desktopNavItemClass(hasChildren bool) string {
|
|
if hasChildren {
|
|
return "desktop-nav__entry desktop-nav__entry--has-children"
|
|
}
|
|
return "desktop-nav__entry"
|
|
}
|
|
|
|
func desktopNavLinkClass() string {
|
|
return "desktop-nav__link"
|
|
}
|
|
|
|
func hasHeaderLocale(locale pscatalog.HeaderLocaleData) bool {
|
|
return len(locale.Languages) > 0 || len(locale.Countries) > 0
|
|
}
|
|
|
|
func pageLanguage(locale pscatalog.HeaderLocaleData) string {
|
|
code := strings.TrimSpace(locale.CurrentLanguage.Code)
|
|
if code == "" {
|
|
return "en"
|
|
}
|
|
return strings.ToLower(code)
|
|
}
|
|
|
|
func localeOptionClass(option pscatalog.LocaleOption, current pscatalog.LocaleOption) string {
|
|
base := "locale-picker__item"
|
|
if option.ID != 0 && option.ID == current.ID {
|
|
return base + " locale-picker__item--active"
|
|
}
|
|
if option.Code != "" && option.Code == current.Code {
|
|
return base + " locale-picker__item--active"
|
|
}
|
|
return base
|
|
}
|
|
|
|
func productInitial(name string) string {
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
return "P"
|
|
}
|
|
return strings.ToUpper(name[:1])
|
|
}
|
|
|
|
func menuPanelID(id int64) string {
|
|
if id <= 0 {
|
|
return "mega-menu-panel"
|
|
}
|
|
return "mega-menu-panel-" + strconv.FormatInt(id, 10)
|
|
}
|
|
|
|
func moneyWithCurrency(amount float64, sign, code string) string {
|
|
formatted := fmt.Sprintf("%.2f", amount)
|
|
sign = strings.TrimSpace(sign)
|
|
switch {
|
|
case sign != "":
|
|
return formatted + " " + sign
|
|
default:
|
|
return formatted
|
|
}
|
|
}
|
|
|
|
func taxLabel(rate float64) string {
|
|
return fmt.Sprintf("Tax %.2f%%", rate)
|
|
}
|
|
|
|
func conversionRateLabel(rate float64, code string) string {
|
|
code = strings.TrimSpace(code)
|
|
if code == "" {
|
|
return fmt.Sprintf("Rate %.6f", rate)
|
|
}
|
|
return fmt.Sprintf("Rate %.6f %s", rate, code)
|
|
}
|
|
|
|
func localizedCartPath(locale pscatalog.HeaderLocaleData) string {
|
|
code := strings.ToLower(strings.TrimSpace(locale.CurrentLanguage.Code))
|
|
if code == "" {
|
|
return "/cart"
|
|
}
|
|
return "/" + code + "/cart"
|
|
}
|
|
|
|
func plainTextHTML(value string) string {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return ""
|
|
}
|
|
value = htmlTagPattern.ReplaceAllString(value, " ")
|
|
value = html.UnescapeString(value)
|
|
return strings.Join(strings.Fields(value), " ")
|
|
}
|
|
|
|
func truncatedPlainTextHTML(value string, maxChars int) string {
|
|
value = plainTextHTML(value)
|
|
if value == "" || maxChars <= 0 {
|
|
return value
|
|
}
|
|
runes := []rune(value)
|
|
if len(runes) <= maxChars {
|
|
return value
|
|
}
|
|
cut := strings.TrimSpace(string(runes[:maxChars]))
|
|
if idx := strings.LastIndex(cut, " "); idx >= maxChars/2 {
|
|
cut = strings.TrimSpace(cut[:idx])
|
|
}
|
|
if cut == "" {
|
|
return value
|
|
}
|
|
return cut + "..."
|
|
}
|
|
|
|
func combinationAttributeLabel(publicName, group string) string {
|
|
publicName = strings.TrimSpace(publicName)
|
|
if publicName != "" {
|
|
return publicName
|
|
}
|
|
return strings.TrimSpace(group)
|
|
}
|
|
|
|
func isHexColor(value string) bool {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return false
|
|
}
|
|
if !strings.HasPrefix(value, "#") {
|
|
value = "#" + value
|
|
}
|
|
if len(value) != 7 {
|
|
return false
|
|
}
|
|
for _, r := range value[1:] {
|
|
if (r < '0' || r > '9') && (r < 'a' || r > 'f') && (r < 'A' || r > 'F') {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func cartCurrencyCode(data viewmodel.CartPageData) string {
|
|
if code := cartCurrencyField(data.Cart.Items, func(item pscart.Item) string { return item.CurrencyCode }); code != "" {
|
|
return code
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func cartCurrencySign(data viewmodel.CartPageData) string {
|
|
if sign := cartCurrencyField(data.Cart.Items, func(item pscart.Item) string { return item.CurrencySign }); sign != "" {
|
|
return sign
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func cartCurrencyField(items []pscart.Item, pick func(pscart.Item) string) string {
|
|
for _, item := range items {
|
|
value := strings.TrimSpace(pick(item))
|
|
if value != "" {
|
|
return value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func cartItemAttributeLabel(item pscart.Item) string {
|
|
if len(item.Attributes) == 0 {
|
|
return ""
|
|
}
|
|
|
|
parts := make([]string, 0, len(item.Attributes))
|
|
for _, attribute := range item.Attributes {
|
|
group := strings.TrimSpace(attribute.Group)
|
|
value := strings.TrimSpace(attribute.Value)
|
|
if value == "" {
|
|
continue
|
|
}
|
|
if group != "" {
|
|
parts = append(parts, group+": "+value)
|
|
continue
|
|
}
|
|
parts = append(parts, value)
|
|
}
|
|
|
|
return strings.Join(parts, " • ")
|
|
}
|
|
|
|
func layoutCartItems(summary *pscart.Summary) int64 {
|
|
if summary == nil || summary.TotalItems <= 0 {
|
|
return 0
|
|
}
|
|
return summary.TotalItems
|
|
}
|
|
|
|
func categoryPageStart(pagination viewmodel.CategoryPagination) int64 {
|
|
if pagination.TotalItems <= 0 || pagination.Page <= 0 || pagination.PerPage <= 0 {
|
|
return 0
|
|
}
|
|
return int64((pagination.Page-1)*pagination.PerPage) + 1
|
|
}
|
|
|
|
func categoryPageEnd(pagination viewmodel.CategoryPagination, loaded int) int64 {
|
|
if pagination.TotalItems <= 0 || loaded <= 0 {
|
|
return 0
|
|
}
|
|
end := categoryPageStart(pagination) + int64(loaded) - 1
|
|
if end > pagination.TotalItems {
|
|
return pagination.TotalItems
|
|
}
|
|
return end
|
|
}
|
|
|
|
type productVariantGroupView struct {
|
|
Key string
|
|
Label string
|
|
GroupType string
|
|
Options []productVariantOptionView
|
|
}
|
|
|
|
type productVariantOptionView struct {
|
|
Value string
|
|
ColorStyle string
|
|
CombinationIDs string
|
|
Selected bool
|
|
}
|
|
|
|
func productVariantGroups(combinations []pscatalog.ProductCombination, defaultID int64) []productVariantGroupView {
|
|
groups := make([]productVariantGroupView, 0)
|
|
groupIndex := make(map[string]int)
|
|
optionIndex := make(map[string]map[string]int)
|
|
|
|
for _, combination := range combinations {
|
|
for _, attribute := range combination.Attributes {
|
|
label := combinationAttributeLabel(attribute.PublicName, attribute.Group)
|
|
if label == "" {
|
|
continue
|
|
}
|
|
key := strings.ToLower(strings.TrimSpace(label))
|
|
idx, exists := groupIndex[key]
|
|
if !exists {
|
|
groups = append(groups, productVariantGroupView{
|
|
Key: "variant-group-" + strconv.Itoa(len(groups)),
|
|
Label: label,
|
|
GroupType: normalizedGroupType(attribute.GroupType),
|
|
Options: make([]productVariantOptionView, 0),
|
|
})
|
|
idx = len(groups) - 1
|
|
groupIndex[key] = idx
|
|
optionIndex[key] = make(map[string]int)
|
|
}
|
|
|
|
optionKey := strings.ToLower(strings.TrimSpace(attribute.Value)) + "|" + normalizedColorStyle(attribute.Color)
|
|
optIdx, exists := optionIndex[key][optionKey]
|
|
if !exists {
|
|
groups[idx].Options = append(groups[idx].Options, productVariantOptionView{
|
|
Value: attribute.Value,
|
|
ColorStyle: normalizedColorStyle(attribute.Color),
|
|
CombinationIDs: strconv.FormatInt(combination.ID, 10),
|
|
Selected: combination.ID == defaultID,
|
|
})
|
|
optionIndex[key][optionKey] = len(groups[idx].Options) - 1
|
|
continue
|
|
}
|
|
|
|
option := &groups[idx].Options[optIdx]
|
|
option.CombinationIDs += "," + strconv.FormatInt(combination.ID, 10)
|
|
if combination.ID == defaultID {
|
|
option.Selected = true
|
|
}
|
|
}
|
|
}
|
|
|
|
return groups
|
|
}
|
|
|
|
func normalizedGroupType(value string) string {
|
|
value = strings.ToLower(strings.TrimSpace(value))
|
|
switch value {
|
|
case "color", "radio", "select":
|
|
return value
|
|
default:
|
|
return "select"
|
|
}
|
|
}
|
|
|
|
func normalizedColorStyle(value string) string {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return ""
|
|
}
|
|
if !strings.HasPrefix(value, "#") {
|
|
value = "#" + value
|
|
}
|
|
if !isHexColor(value) {
|
|
return ""
|
|
}
|
|
return value
|
|
}
|
|
|
|
func variantColorOptionClass(selected bool) string {
|
|
if selected {
|
|
return "inline-flex min-h-10 min-w-10 items-center justify-center border border-stone-900 p-0.5 ring-1 ring-stone-900 transition"
|
|
}
|
|
return "inline-flex min-h-10 min-w-10 items-center justify-center border border-stone-300 p-0.5 transition hover:border-stone-700"
|
|
}
|
|
|
|
func variantRadioOptionClass(selected bool) string {
|
|
if selected {
|
|
return "rounded-full border border-stone-900 bg-stone-900 px-4 py-2 text-sm font-medium text-stone-50 transition"
|
|
}
|
|
return "rounded-full border border-stone-300 bg-white px-4 py-2 text-sm font-medium text-stone-900 transition hover:border-stone-700"
|
|
}
|
|
|
|
func variantSelectOptionClass(selected bool) string {
|
|
if selected {
|
|
return "w-full rounded-2xl bg-stone-900 px-4 py-3 text-left text-sm font-medium text-stone-50 transition"
|
|
}
|
|
return "w-full rounded-2xl px-4 py-3 text-left text-sm font-medium text-stone-700 transition hover:bg-stone-100 hover:text-stone-950"
|
|
}
|
|
|
|
func selectedVariantOptionValue(options []productVariantOptionView) string {
|
|
for _, option := range options {
|
|
if option.Selected {
|
|
return option.Value
|
|
}
|
|
}
|
|
if len(options) == 0 {
|
|
return ""
|
|
}
|
|
return options[0].Value
|
|
}
|