dev #45
@@ -94,6 +94,10 @@ public class OptionsController {
|
|||||||
v.getId(),
|
v.getId(),
|
||||||
v.getVariantDisplayName(),
|
v.getVariantDisplayName(),
|
||||||
v.getColorName(),
|
v.getColorName(),
|
||||||
|
v.getColorLabelIt(),
|
||||||
|
v.getColorLabelEn(),
|
||||||
|
v.getColorLabelDe(),
|
||||||
|
v.getColorLabelFr(),
|
||||||
resolveHexColor(v),
|
resolveHexColor(v),
|
||||||
v.getFinishType() != null ? v.getFinishType() : "GLOSSY",
|
v.getFinishType() != null ? v.getFinishType() : "GLOSSY",
|
||||||
v.getStockSpools() != null ? v.getStockSpools().doubleValue() : 0d,
|
v.getStockSpools() != null ? v.getStockSpools().doubleValue() : 0d,
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ public class AdminFilamentVariantDto {
|
|||||||
private String materialTechnicalTypeLabel;
|
private String materialTechnicalTypeLabel;
|
||||||
private String variantDisplayName;
|
private String variantDisplayName;
|
||||||
private String colorName;
|
private String colorName;
|
||||||
|
private String colorLabelIt;
|
||||||
|
private String colorLabelEn;
|
||||||
|
private String colorLabelDe;
|
||||||
|
private String colorLabelFr;
|
||||||
private String colorHex;
|
private String colorHex;
|
||||||
private String finishType;
|
private String finishType;
|
||||||
private String brand;
|
private String brand;
|
||||||
@@ -89,6 +93,38 @@ public class AdminFilamentVariantDto {
|
|||||||
this.colorName = colorName;
|
this.colorName = colorName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getColorLabelIt() {
|
||||||
|
return colorLabelIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelIt(String colorLabelIt) {
|
||||||
|
this.colorLabelIt = colorLabelIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelEn() {
|
||||||
|
return colorLabelEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelEn(String colorLabelEn) {
|
||||||
|
this.colorLabelEn = colorLabelEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelDe() {
|
||||||
|
return colorLabelDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelDe(String colorLabelDe) {
|
||||||
|
this.colorLabelDe = colorLabelDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelFr() {
|
||||||
|
return colorLabelFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelFr(String colorLabelFr) {
|
||||||
|
this.colorLabelFr = colorLabelFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getColorHex() {
|
public String getColorHex() {
|
||||||
return colorHex;
|
return colorHex;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,25 @@ public class AdminShopCategoryDto {
|
|||||||
private String parentCategoryName;
|
private String parentCategoryName;
|
||||||
private String slug;
|
private String slug;
|
||||||
private String name;
|
private String name;
|
||||||
|
private String nameIt;
|
||||||
|
private String nameEn;
|
||||||
|
private String nameDe;
|
||||||
|
private String nameFr;
|
||||||
private String description;
|
private String description;
|
||||||
|
private String descriptionIt;
|
||||||
|
private String descriptionEn;
|
||||||
|
private String descriptionDe;
|
||||||
|
private String descriptionFr;
|
||||||
private String seoTitle;
|
private String seoTitle;
|
||||||
|
private String seoTitleIt;
|
||||||
|
private String seoTitleEn;
|
||||||
|
private String seoTitleDe;
|
||||||
|
private String seoTitleFr;
|
||||||
private String seoDescription;
|
private String seoDescription;
|
||||||
|
private String seoDescriptionIt;
|
||||||
|
private String seoDescriptionEn;
|
||||||
|
private String seoDescriptionDe;
|
||||||
|
private String seoDescriptionFr;
|
||||||
private String ogTitle;
|
private String ogTitle;
|
||||||
private String ogDescription;
|
private String ogDescription;
|
||||||
private Boolean indexable;
|
private Boolean indexable;
|
||||||
@@ -69,6 +85,38 @@ public class AdminShopCategoryDto {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getNameIt() {
|
||||||
|
return nameIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNameIt(String nameIt) {
|
||||||
|
this.nameIt = nameIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNameEn() {
|
||||||
|
return nameEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNameEn(String nameEn) {
|
||||||
|
this.nameEn = nameEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNameDe() {
|
||||||
|
return nameDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNameDe(String nameDe) {
|
||||||
|
this.nameDe = nameDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNameFr() {
|
||||||
|
return nameFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNameFr(String nameFr) {
|
||||||
|
this.nameFr = nameFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
@@ -77,6 +125,38 @@ public class AdminShopCategoryDto {
|
|||||||
this.description = description;
|
this.description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDescriptionIt() {
|
||||||
|
return descriptionIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescriptionIt(String descriptionIt) {
|
||||||
|
this.descriptionIt = descriptionIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescriptionEn() {
|
||||||
|
return descriptionEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescriptionEn(String descriptionEn) {
|
||||||
|
this.descriptionEn = descriptionEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescriptionDe() {
|
||||||
|
return descriptionDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescriptionDe(String descriptionDe) {
|
||||||
|
this.descriptionDe = descriptionDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescriptionFr() {
|
||||||
|
return descriptionFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescriptionFr(String descriptionFr) {
|
||||||
|
this.descriptionFr = descriptionFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getSeoTitle() {
|
public String getSeoTitle() {
|
||||||
return seoTitle;
|
return seoTitle;
|
||||||
}
|
}
|
||||||
@@ -85,6 +165,38 @@ public class AdminShopCategoryDto {
|
|||||||
this.seoTitle = seoTitle;
|
this.seoTitle = seoTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSeoTitleIt() {
|
||||||
|
return seoTitleIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoTitleIt(String seoTitleIt) {
|
||||||
|
this.seoTitleIt = seoTitleIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoTitleEn() {
|
||||||
|
return seoTitleEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoTitleEn(String seoTitleEn) {
|
||||||
|
this.seoTitleEn = seoTitleEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoTitleDe() {
|
||||||
|
return seoTitleDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoTitleDe(String seoTitleDe) {
|
||||||
|
this.seoTitleDe = seoTitleDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoTitleFr() {
|
||||||
|
return seoTitleFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoTitleFr(String seoTitleFr) {
|
||||||
|
this.seoTitleFr = seoTitleFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getSeoDescription() {
|
public String getSeoDescription() {
|
||||||
return seoDescription;
|
return seoDescription;
|
||||||
}
|
}
|
||||||
@@ -93,6 +205,38 @@ public class AdminShopCategoryDto {
|
|||||||
this.seoDescription = seoDescription;
|
this.seoDescription = seoDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSeoDescriptionIt() {
|
||||||
|
return seoDescriptionIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoDescriptionIt(String seoDescriptionIt) {
|
||||||
|
this.seoDescriptionIt = seoDescriptionIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoDescriptionEn() {
|
||||||
|
return seoDescriptionEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoDescriptionEn(String seoDescriptionEn) {
|
||||||
|
this.seoDescriptionEn = seoDescriptionEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoDescriptionDe() {
|
||||||
|
return seoDescriptionDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoDescriptionDe(String seoDescriptionDe) {
|
||||||
|
this.seoDescriptionDe = seoDescriptionDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoDescriptionFr() {
|
||||||
|
return seoDescriptionFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoDescriptionFr(String seoDescriptionFr) {
|
||||||
|
this.seoDescriptionFr = seoDescriptionFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getOgTitle() {
|
public String getOgTitle() {
|
||||||
return ogTitle;
|
return ogTitle;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ public class AdminShopProductVariantDto {
|
|||||||
private String sku;
|
private String sku;
|
||||||
private String variantLabel;
|
private String variantLabel;
|
||||||
private String colorName;
|
private String colorName;
|
||||||
|
private String colorLabelIt;
|
||||||
|
private String colorLabelEn;
|
||||||
|
private String colorLabelDe;
|
||||||
|
private String colorLabelFr;
|
||||||
private String colorHex;
|
private String colorHex;
|
||||||
private String internalMaterialCode;
|
private String internalMaterialCode;
|
||||||
private BigDecimal priceChf;
|
private BigDecimal priceChf;
|
||||||
@@ -50,6 +54,38 @@ public class AdminShopProductVariantDto {
|
|||||||
this.colorName = colorName;
|
this.colorName = colorName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getColorLabelIt() {
|
||||||
|
return colorLabelIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelIt(String colorLabelIt) {
|
||||||
|
this.colorLabelIt = colorLabelIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelEn() {
|
||||||
|
return colorLabelEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelEn(String colorLabelEn) {
|
||||||
|
this.colorLabelEn = colorLabelEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelDe() {
|
||||||
|
return colorLabelDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelDe(String colorLabelDe) {
|
||||||
|
this.colorLabelDe = colorLabelDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelFr() {
|
||||||
|
return colorLabelFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelFr(String colorLabelFr) {
|
||||||
|
this.colorLabelFr = colorLabelFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getColorHex() {
|
public String getColorHex() {
|
||||||
return colorHex;
|
return colorHex;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ public class AdminUpsertFilamentVariantRequest {
|
|||||||
private Long materialTypeId;
|
private Long materialTypeId;
|
||||||
private String variantDisplayName;
|
private String variantDisplayName;
|
||||||
private String colorName;
|
private String colorName;
|
||||||
|
private String colorLabelIt;
|
||||||
|
private String colorLabelEn;
|
||||||
|
private String colorLabelDe;
|
||||||
|
private String colorLabelFr;
|
||||||
private String colorHex;
|
private String colorHex;
|
||||||
private String finishType;
|
private String finishType;
|
||||||
private String brand;
|
private String brand;
|
||||||
@@ -40,6 +44,38 @@ public class AdminUpsertFilamentVariantRequest {
|
|||||||
this.colorName = colorName;
|
this.colorName = colorName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getColorLabelIt() {
|
||||||
|
return colorLabelIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelIt(String colorLabelIt) {
|
||||||
|
this.colorLabelIt = colorLabelIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelEn() {
|
||||||
|
return colorLabelEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelEn(String colorLabelEn) {
|
||||||
|
this.colorLabelEn = colorLabelEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelDe() {
|
||||||
|
return colorLabelDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelDe(String colorLabelDe) {
|
||||||
|
this.colorLabelDe = colorLabelDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelFr() {
|
||||||
|
return colorLabelFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelFr(String colorLabelFr) {
|
||||||
|
this.colorLabelFr = colorLabelFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getColorHex() {
|
public String getColorHex() {
|
||||||
return colorHex;
|
return colorHex;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,25 @@ public class AdminUpsertShopCategoryRequest {
|
|||||||
private UUID parentCategoryId;
|
private UUID parentCategoryId;
|
||||||
private String slug;
|
private String slug;
|
||||||
private String name;
|
private String name;
|
||||||
|
private String nameIt;
|
||||||
|
private String nameEn;
|
||||||
|
private String nameDe;
|
||||||
|
private String nameFr;
|
||||||
private String description;
|
private String description;
|
||||||
|
private String descriptionIt;
|
||||||
|
private String descriptionEn;
|
||||||
|
private String descriptionDe;
|
||||||
|
private String descriptionFr;
|
||||||
private String seoTitle;
|
private String seoTitle;
|
||||||
|
private String seoTitleIt;
|
||||||
|
private String seoTitleEn;
|
||||||
|
private String seoTitleDe;
|
||||||
|
private String seoTitleFr;
|
||||||
private String seoDescription;
|
private String seoDescription;
|
||||||
|
private String seoDescriptionIt;
|
||||||
|
private String seoDescriptionEn;
|
||||||
|
private String seoDescriptionDe;
|
||||||
|
private String seoDescriptionFr;
|
||||||
private String ogTitle;
|
private String ogTitle;
|
||||||
private String ogDescription;
|
private String ogDescription;
|
||||||
private Boolean indexable;
|
private Boolean indexable;
|
||||||
@@ -39,6 +55,38 @@ public class AdminUpsertShopCategoryRequest {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getNameIt() {
|
||||||
|
return nameIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNameIt(String nameIt) {
|
||||||
|
this.nameIt = nameIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNameEn() {
|
||||||
|
return nameEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNameEn(String nameEn) {
|
||||||
|
this.nameEn = nameEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNameDe() {
|
||||||
|
return nameDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNameDe(String nameDe) {
|
||||||
|
this.nameDe = nameDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNameFr() {
|
||||||
|
return nameFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNameFr(String nameFr) {
|
||||||
|
this.nameFr = nameFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
@@ -47,6 +95,38 @@ public class AdminUpsertShopCategoryRequest {
|
|||||||
this.description = description;
|
this.description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDescriptionIt() {
|
||||||
|
return descriptionIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescriptionIt(String descriptionIt) {
|
||||||
|
this.descriptionIt = descriptionIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescriptionEn() {
|
||||||
|
return descriptionEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescriptionEn(String descriptionEn) {
|
||||||
|
this.descriptionEn = descriptionEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescriptionDe() {
|
||||||
|
return descriptionDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescriptionDe(String descriptionDe) {
|
||||||
|
this.descriptionDe = descriptionDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescriptionFr() {
|
||||||
|
return descriptionFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescriptionFr(String descriptionFr) {
|
||||||
|
this.descriptionFr = descriptionFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getSeoTitle() {
|
public String getSeoTitle() {
|
||||||
return seoTitle;
|
return seoTitle;
|
||||||
}
|
}
|
||||||
@@ -55,6 +135,38 @@ public class AdminUpsertShopCategoryRequest {
|
|||||||
this.seoTitle = seoTitle;
|
this.seoTitle = seoTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSeoTitleIt() {
|
||||||
|
return seoTitleIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoTitleIt(String seoTitleIt) {
|
||||||
|
this.seoTitleIt = seoTitleIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoTitleEn() {
|
||||||
|
return seoTitleEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoTitleEn(String seoTitleEn) {
|
||||||
|
this.seoTitleEn = seoTitleEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoTitleDe() {
|
||||||
|
return seoTitleDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoTitleDe(String seoTitleDe) {
|
||||||
|
this.seoTitleDe = seoTitleDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoTitleFr() {
|
||||||
|
return seoTitleFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoTitleFr(String seoTitleFr) {
|
||||||
|
this.seoTitleFr = seoTitleFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getSeoDescription() {
|
public String getSeoDescription() {
|
||||||
return seoDescription;
|
return seoDescription;
|
||||||
}
|
}
|
||||||
@@ -63,6 +175,38 @@ public class AdminUpsertShopCategoryRequest {
|
|||||||
this.seoDescription = seoDescription;
|
this.seoDescription = seoDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSeoDescriptionIt() {
|
||||||
|
return seoDescriptionIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoDescriptionIt(String seoDescriptionIt) {
|
||||||
|
this.seoDescriptionIt = seoDescriptionIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoDescriptionEn() {
|
||||||
|
return seoDescriptionEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoDescriptionEn(String seoDescriptionEn) {
|
||||||
|
this.seoDescriptionEn = seoDescriptionEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoDescriptionDe() {
|
||||||
|
return seoDescriptionDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoDescriptionDe(String seoDescriptionDe) {
|
||||||
|
this.seoDescriptionDe = seoDescriptionDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoDescriptionFr() {
|
||||||
|
return seoDescriptionFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoDescriptionFr(String seoDescriptionFr) {
|
||||||
|
this.seoDescriptionFr = seoDescriptionFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getOgTitle() {
|
public String getOgTitle() {
|
||||||
return ogTitle;
|
return ogTitle;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ public class AdminUpsertShopProductVariantRequest {
|
|||||||
private String sku;
|
private String sku;
|
||||||
private String variantLabel;
|
private String variantLabel;
|
||||||
private String colorName;
|
private String colorName;
|
||||||
|
private String colorLabelIt;
|
||||||
|
private String colorLabelEn;
|
||||||
|
private String colorLabelDe;
|
||||||
|
private String colorLabelFr;
|
||||||
private String colorHex;
|
private String colorHex;
|
||||||
private String internalMaterialCode;
|
private String internalMaterialCode;
|
||||||
private BigDecimal priceChf;
|
private BigDecimal priceChf;
|
||||||
@@ -47,6 +51,38 @@ public class AdminUpsertShopProductVariantRequest {
|
|||||||
this.colorName = colorName;
|
this.colorName = colorName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getColorLabelIt() {
|
||||||
|
return colorLabelIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelIt(String colorLabelIt) {
|
||||||
|
this.colorLabelIt = colorLabelIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelEn() {
|
||||||
|
return colorLabelEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelEn(String colorLabelEn) {
|
||||||
|
this.colorLabelEn = colorLabelEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelDe() {
|
||||||
|
return colorLabelDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelDe(String colorLabelDe) {
|
||||||
|
this.colorLabelDe = colorLabelDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelFr() {
|
||||||
|
return colorLabelFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelFr(String colorLabelFr) {
|
||||||
|
this.colorLabelFr = colorLabelFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getColorHex() {
|
public String getColorHex() {
|
||||||
return colorHex;
|
return colorHex;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ public record OptionsResponse(
|
|||||||
Long id,
|
Long id,
|
||||||
String name,
|
String name,
|
||||||
String colorName,
|
String colorName,
|
||||||
|
String colorLabelIt,
|
||||||
|
String colorLabelEn,
|
||||||
|
String colorLabelDe,
|
||||||
|
String colorLabelFr,
|
||||||
String hexColor,
|
String hexColor,
|
||||||
String finishType,
|
String finishType,
|
||||||
Double stockSpools,
|
Double stockSpools,
|
||||||
|
|||||||
@@ -17,9 +17,17 @@ public class OrderItemDto {
|
|||||||
private String shopProductName;
|
private String shopProductName;
|
||||||
private String shopVariantLabel;
|
private String shopVariantLabel;
|
||||||
private String shopVariantColorName;
|
private String shopVariantColorName;
|
||||||
|
private String shopVariantColorLabelIt;
|
||||||
|
private String shopVariantColorLabelEn;
|
||||||
|
private String shopVariantColorLabelDe;
|
||||||
|
private String shopVariantColorLabelFr;
|
||||||
private String shopVariantColorHex;
|
private String shopVariantColorHex;
|
||||||
private String filamentVariantDisplayName;
|
private String filamentVariantDisplayName;
|
||||||
private String filamentColorName;
|
private String filamentColorName;
|
||||||
|
private String filamentColorLabelIt;
|
||||||
|
private String filamentColorLabelEn;
|
||||||
|
private String filamentColorLabelDe;
|
||||||
|
private String filamentColorLabelFr;
|
||||||
private String filamentColorHex;
|
private String filamentColorHex;
|
||||||
private String quality;
|
private String quality;
|
||||||
private BigDecimal nozzleDiameterMm;
|
private BigDecimal nozzleDiameterMm;
|
||||||
@@ -73,6 +81,18 @@ public class OrderItemDto {
|
|||||||
public String getShopVariantColorName() { return shopVariantColorName; }
|
public String getShopVariantColorName() { return shopVariantColorName; }
|
||||||
public void setShopVariantColorName(String shopVariantColorName) { this.shopVariantColorName = shopVariantColorName; }
|
public void setShopVariantColorName(String shopVariantColorName) { this.shopVariantColorName = shopVariantColorName; }
|
||||||
|
|
||||||
|
public String getShopVariantColorLabelIt() { return shopVariantColorLabelIt; }
|
||||||
|
public void setShopVariantColorLabelIt(String shopVariantColorLabelIt) { this.shopVariantColorLabelIt = shopVariantColorLabelIt; }
|
||||||
|
|
||||||
|
public String getShopVariantColorLabelEn() { return shopVariantColorLabelEn; }
|
||||||
|
public void setShopVariantColorLabelEn(String shopVariantColorLabelEn) { this.shopVariantColorLabelEn = shopVariantColorLabelEn; }
|
||||||
|
|
||||||
|
public String getShopVariantColorLabelDe() { return shopVariantColorLabelDe; }
|
||||||
|
public void setShopVariantColorLabelDe(String shopVariantColorLabelDe) { this.shopVariantColorLabelDe = shopVariantColorLabelDe; }
|
||||||
|
|
||||||
|
public String getShopVariantColorLabelFr() { return shopVariantColorLabelFr; }
|
||||||
|
public void setShopVariantColorLabelFr(String shopVariantColorLabelFr) { this.shopVariantColorLabelFr = shopVariantColorLabelFr; }
|
||||||
|
|
||||||
public String getShopVariantColorHex() { return shopVariantColorHex; }
|
public String getShopVariantColorHex() { return shopVariantColorHex; }
|
||||||
public void setShopVariantColorHex(String shopVariantColorHex) { this.shopVariantColorHex = shopVariantColorHex; }
|
public void setShopVariantColorHex(String shopVariantColorHex) { this.shopVariantColorHex = shopVariantColorHex; }
|
||||||
|
|
||||||
@@ -82,6 +102,18 @@ public class OrderItemDto {
|
|||||||
public String getFilamentColorName() { return filamentColorName; }
|
public String getFilamentColorName() { return filamentColorName; }
|
||||||
public void setFilamentColorName(String filamentColorName) { this.filamentColorName = filamentColorName; }
|
public void setFilamentColorName(String filamentColorName) { this.filamentColorName = filamentColorName; }
|
||||||
|
|
||||||
|
public String getFilamentColorLabelIt() { return filamentColorLabelIt; }
|
||||||
|
public void setFilamentColorLabelIt(String filamentColorLabelIt) { this.filamentColorLabelIt = filamentColorLabelIt; }
|
||||||
|
|
||||||
|
public String getFilamentColorLabelEn() { return filamentColorLabelEn; }
|
||||||
|
public void setFilamentColorLabelEn(String filamentColorLabelEn) { this.filamentColorLabelEn = filamentColorLabelEn; }
|
||||||
|
|
||||||
|
public String getFilamentColorLabelDe() { return filamentColorLabelDe; }
|
||||||
|
public void setFilamentColorLabelDe(String filamentColorLabelDe) { this.filamentColorLabelDe = filamentColorLabelDe; }
|
||||||
|
|
||||||
|
public String getFilamentColorLabelFr() { return filamentColorLabelFr; }
|
||||||
|
public void setFilamentColorLabelFr(String filamentColorLabelFr) { this.filamentColorLabelFr = filamentColorLabelFr; }
|
||||||
|
|
||||||
public String getFilamentColorHex() { return filamentColorHex; }
|
public String getFilamentColorHex() { return filamentColorHex; }
|
||||||
public void setFilamentColorHex(String filamentColorHex) { this.filamentColorHex = filamentColorHex; }
|
public void setFilamentColorHex(String filamentColorHex) { this.filamentColorHex = filamentColorHex; }
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ public record ShopProductVariantOptionDto(
|
|||||||
String sku,
|
String sku,
|
||||||
String variantLabel,
|
String variantLabel,
|
||||||
String colorName,
|
String colorName,
|
||||||
|
String colorLabel,
|
||||||
String colorHex,
|
String colorHex,
|
||||||
BigDecimal priceChf,
|
BigDecimal priceChf,
|
||||||
Boolean isDefault
|
Boolean isDefault
|
||||||
|
|||||||
@@ -24,6 +24,18 @@ public class FilamentVariant {
|
|||||||
@Column(name = "color_name", nullable = false, length = Integer.MAX_VALUE)
|
@Column(name = "color_name", nullable = false, length = Integer.MAX_VALUE)
|
||||||
private String colorName;
|
private String colorName;
|
||||||
|
|
||||||
|
@Column(name = "color_label_it", length = Integer.MAX_VALUE)
|
||||||
|
private String colorLabelIt;
|
||||||
|
|
||||||
|
@Column(name = "color_label_en", length = Integer.MAX_VALUE)
|
||||||
|
private String colorLabelEn;
|
||||||
|
|
||||||
|
@Column(name = "color_label_de", length = Integer.MAX_VALUE)
|
||||||
|
private String colorLabelDe;
|
||||||
|
|
||||||
|
@Column(name = "color_label_fr", length = Integer.MAX_VALUE)
|
||||||
|
private String colorLabelFr;
|
||||||
|
|
||||||
@Column(name = "color_hex", length = Integer.MAX_VALUE)
|
@Column(name = "color_hex", length = Integer.MAX_VALUE)
|
||||||
private String colorHex;
|
private String colorHex;
|
||||||
|
|
||||||
@@ -93,6 +105,38 @@ public class FilamentVariant {
|
|||||||
this.colorName = colorName;
|
this.colorName = colorName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getColorLabelIt() {
|
||||||
|
return colorLabelIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelIt(String colorLabelIt) {
|
||||||
|
this.colorLabelIt = colorLabelIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelEn() {
|
||||||
|
return colorLabelEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelEn(String colorLabelEn) {
|
||||||
|
this.colorLabelEn = colorLabelEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelDe() {
|
||||||
|
return colorLabelDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelDe(String colorLabelDe) {
|
||||||
|
this.colorLabelDe = colorLabelDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelFr() {
|
||||||
|
return colorLabelFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelFr(String colorLabelFr) {
|
||||||
|
this.colorLabelFr = colorLabelFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getColorHex() {
|
public String getColorHex() {
|
||||||
return colorHex;
|
return colorHex;
|
||||||
}
|
}
|
||||||
@@ -173,4 +217,60 @@ public class FilamentVariant {
|
|||||||
this.createdAt = createdAt;
|
this.createdAt = createdAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getColorLabelForLanguage(String language) {
|
||||||
|
return resolveLocalizedValue(
|
||||||
|
language,
|
||||||
|
colorName,
|
||||||
|
colorLabelIt,
|
||||||
|
colorLabelEn,
|
||||||
|
colorLabelDe,
|
||||||
|
colorLabelFr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveLocalizedValue(String language,
|
||||||
|
String fallback,
|
||||||
|
String valueIt,
|
||||||
|
String valueEn,
|
||||||
|
String valueDe,
|
||||||
|
String valueFr) {
|
||||||
|
String normalizedLanguage = normalizeLanguage(language);
|
||||||
|
String preferred = switch (normalizedLanguage) {
|
||||||
|
case "it" -> valueIt;
|
||||||
|
case "en" -> valueEn;
|
||||||
|
case "de" -> valueDe;
|
||||||
|
case "fr" -> valueFr;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
String resolved = firstNonBlank(preferred, fallback);
|
||||||
|
if (resolved != null) {
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
return firstNonBlank(valueIt, valueEn, valueDe, valueFr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeLanguage(String language) {
|
||||||
|
if (language == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String normalized = language.trim().toLowerCase();
|
||||||
|
int separatorIndex = normalized.indexOf('-');
|
||||||
|
if (separatorIndex > 0) {
|
||||||
|
normalized = normalized.substring(0, separatorIndex);
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstNonBlank(String... values) {
|
||||||
|
if (values == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (String value : values) {
|
||||||
|
if (value != null && !value.isBlank()) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import jakarta.persistence.Table;
|
|||||||
import org.hibernate.annotations.ColumnDefault;
|
import org.hibernate.annotations.ColumnDefault;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@@ -23,6 +24,8 @@ import java.util.UUID;
|
|||||||
@Index(name = "ix_shop_category_active_sort", columnList = "is_active, sort_order")
|
@Index(name = "ix_shop_category_active_sort", columnList = "is_active, sort_order")
|
||||||
})
|
})
|
||||||
public class ShopCategory {
|
public class ShopCategory {
|
||||||
|
public static final List<String> SUPPORTED_LANGUAGES = List.of("it", "en", "de", "fr");
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
@Column(name = "shop_category_id", nullable = false)
|
@Column(name = "shop_category_id", nullable = false)
|
||||||
@@ -38,15 +41,63 @@ public class ShopCategory {
|
|||||||
@Column(name = "name", nullable = false, length = Integer.MAX_VALUE)
|
@Column(name = "name", nullable = false, length = Integer.MAX_VALUE)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
@Column(name = "name_it", length = Integer.MAX_VALUE)
|
||||||
|
private String nameIt;
|
||||||
|
|
||||||
|
@Column(name = "name_en", length = Integer.MAX_VALUE)
|
||||||
|
private String nameEn;
|
||||||
|
|
||||||
|
@Column(name = "name_de", length = Integer.MAX_VALUE)
|
||||||
|
private String nameDe;
|
||||||
|
|
||||||
|
@Column(name = "name_fr", length = Integer.MAX_VALUE)
|
||||||
|
private String nameFr;
|
||||||
|
|
||||||
@Column(name = "description", length = Integer.MAX_VALUE)
|
@Column(name = "description", length = Integer.MAX_VALUE)
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
|
@Column(name = "description_it", length = Integer.MAX_VALUE)
|
||||||
|
private String descriptionIt;
|
||||||
|
|
||||||
|
@Column(name = "description_en", length = Integer.MAX_VALUE)
|
||||||
|
private String descriptionEn;
|
||||||
|
|
||||||
|
@Column(name = "description_de", length = Integer.MAX_VALUE)
|
||||||
|
private String descriptionDe;
|
||||||
|
|
||||||
|
@Column(name = "description_fr", length = Integer.MAX_VALUE)
|
||||||
|
private String descriptionFr;
|
||||||
|
|
||||||
@Column(name = "seo_title", length = Integer.MAX_VALUE)
|
@Column(name = "seo_title", length = Integer.MAX_VALUE)
|
||||||
private String seoTitle;
|
private String seoTitle;
|
||||||
|
|
||||||
|
@Column(name = "seo_title_it", length = Integer.MAX_VALUE)
|
||||||
|
private String seoTitleIt;
|
||||||
|
|
||||||
|
@Column(name = "seo_title_en", length = Integer.MAX_VALUE)
|
||||||
|
private String seoTitleEn;
|
||||||
|
|
||||||
|
@Column(name = "seo_title_de", length = Integer.MAX_VALUE)
|
||||||
|
private String seoTitleDe;
|
||||||
|
|
||||||
|
@Column(name = "seo_title_fr", length = Integer.MAX_VALUE)
|
||||||
|
private String seoTitleFr;
|
||||||
|
|
||||||
@Column(name = "seo_description", length = Integer.MAX_VALUE)
|
@Column(name = "seo_description", length = Integer.MAX_VALUE)
|
||||||
private String seoDescription;
|
private String seoDescription;
|
||||||
|
|
||||||
|
@Column(name = "seo_description_it", length = Integer.MAX_VALUE)
|
||||||
|
private String seoDescriptionIt;
|
||||||
|
|
||||||
|
@Column(name = "seo_description_en", length = Integer.MAX_VALUE)
|
||||||
|
private String seoDescriptionEn;
|
||||||
|
|
||||||
|
@Column(name = "seo_description_de", length = Integer.MAX_VALUE)
|
||||||
|
private String seoDescriptionDe;
|
||||||
|
|
||||||
|
@Column(name = "seo_description_fr", length = Integer.MAX_VALUE)
|
||||||
|
private String seoDescriptionFr;
|
||||||
|
|
||||||
@Column(name = "og_title", length = Integer.MAX_VALUE)
|
@Column(name = "og_title", length = Integer.MAX_VALUE)
|
||||||
private String ogTitle;
|
private String ogTitle;
|
||||||
|
|
||||||
@@ -139,6 +190,38 @@ public class ShopCategory {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getNameIt() {
|
||||||
|
return nameIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNameIt(String nameIt) {
|
||||||
|
this.nameIt = nameIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNameEn() {
|
||||||
|
return nameEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNameEn(String nameEn) {
|
||||||
|
this.nameEn = nameEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNameDe() {
|
||||||
|
return nameDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNameDe(String nameDe) {
|
||||||
|
this.nameDe = nameDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNameFr() {
|
||||||
|
return nameFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNameFr(String nameFr) {
|
||||||
|
this.nameFr = nameFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
@@ -147,6 +230,38 @@ public class ShopCategory {
|
|||||||
this.description = description;
|
this.description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDescriptionIt() {
|
||||||
|
return descriptionIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescriptionIt(String descriptionIt) {
|
||||||
|
this.descriptionIt = descriptionIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescriptionEn() {
|
||||||
|
return descriptionEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescriptionEn(String descriptionEn) {
|
||||||
|
this.descriptionEn = descriptionEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescriptionDe() {
|
||||||
|
return descriptionDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescriptionDe(String descriptionDe) {
|
||||||
|
this.descriptionDe = descriptionDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescriptionFr() {
|
||||||
|
return descriptionFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescriptionFr(String descriptionFr) {
|
||||||
|
this.descriptionFr = descriptionFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getSeoTitle() {
|
public String getSeoTitle() {
|
||||||
return seoTitle;
|
return seoTitle;
|
||||||
}
|
}
|
||||||
@@ -155,6 +270,38 @@ public class ShopCategory {
|
|||||||
this.seoTitle = seoTitle;
|
this.seoTitle = seoTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSeoTitleIt() {
|
||||||
|
return seoTitleIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoTitleIt(String seoTitleIt) {
|
||||||
|
this.seoTitleIt = seoTitleIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoTitleEn() {
|
||||||
|
return seoTitleEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoTitleEn(String seoTitleEn) {
|
||||||
|
this.seoTitleEn = seoTitleEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoTitleDe() {
|
||||||
|
return seoTitleDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoTitleDe(String seoTitleDe) {
|
||||||
|
this.seoTitleDe = seoTitleDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoTitleFr() {
|
||||||
|
return seoTitleFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoTitleFr(String seoTitleFr) {
|
||||||
|
this.seoTitleFr = seoTitleFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getSeoDescription() {
|
public String getSeoDescription() {
|
||||||
return seoDescription;
|
return seoDescription;
|
||||||
}
|
}
|
||||||
@@ -163,6 +310,38 @@ public class ShopCategory {
|
|||||||
this.seoDescription = seoDescription;
|
this.seoDescription = seoDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSeoDescriptionIt() {
|
||||||
|
return seoDescriptionIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoDescriptionIt(String seoDescriptionIt) {
|
||||||
|
this.seoDescriptionIt = seoDescriptionIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoDescriptionEn() {
|
||||||
|
return seoDescriptionEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoDescriptionEn(String seoDescriptionEn) {
|
||||||
|
this.seoDescriptionEn = seoDescriptionEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoDescriptionDe() {
|
||||||
|
return seoDescriptionDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoDescriptionDe(String seoDescriptionDe) {
|
||||||
|
this.seoDescriptionDe = seoDescriptionDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoDescriptionFr() {
|
||||||
|
return seoDescriptionFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoDescriptionFr(String seoDescriptionFr) {
|
||||||
|
this.seoDescriptionFr = seoDescriptionFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getOgTitle() {
|
public String getOgTitle() {
|
||||||
return ogTitle;
|
return ogTitle;
|
||||||
}
|
}
|
||||||
@@ -218,4 +397,109 @@ public class ShopCategory {
|
|||||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||||
this.updatedAt = updatedAt;
|
this.updatedAt = updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getNameForLanguage(String language) {
|
||||||
|
return resolveLocalizedValue(language, name, nameIt, nameEn, nameDe, nameFr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNameForLanguage(String language, String value) {
|
||||||
|
switch (normalizeLanguage(language)) {
|
||||||
|
case "it" -> nameIt = value;
|
||||||
|
case "en" -> nameEn = value;
|
||||||
|
case "de" -> nameDe = value;
|
||||||
|
case "fr" -> nameFr = value;
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescriptionForLanguage(String language) {
|
||||||
|
return resolveLocalizedValue(language, description, descriptionIt, descriptionEn, descriptionDe, descriptionFr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescriptionForLanguage(String language, String value) {
|
||||||
|
switch (normalizeLanguage(language)) {
|
||||||
|
case "it" -> descriptionIt = value;
|
||||||
|
case "en" -> descriptionEn = value;
|
||||||
|
case "de" -> descriptionDe = value;
|
||||||
|
case "fr" -> descriptionFr = value;
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoTitleForLanguage(String language) {
|
||||||
|
return resolveLocalizedValue(language, seoTitle, seoTitleIt, seoTitleEn, seoTitleDe, seoTitleFr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoTitleForLanguage(String language, String value) {
|
||||||
|
switch (normalizeLanguage(language)) {
|
||||||
|
case "it" -> seoTitleIt = value;
|
||||||
|
case "en" -> seoTitleEn = value;
|
||||||
|
case "de" -> seoTitleDe = value;
|
||||||
|
case "fr" -> seoTitleFr = value;
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeoDescriptionForLanguage(String language) {
|
||||||
|
return resolveLocalizedValue(language, seoDescription, seoDescriptionIt, seoDescriptionEn, seoDescriptionDe, seoDescriptionFr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeoDescriptionForLanguage(String language, String value) {
|
||||||
|
switch (normalizeLanguage(language)) {
|
||||||
|
case "it" -> seoDescriptionIt = value;
|
||||||
|
case "en" -> seoDescriptionEn = value;
|
||||||
|
case "de" -> seoDescriptionDe = value;
|
||||||
|
case "fr" -> seoDescriptionFr = value;
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveLocalizedValue(String language,
|
||||||
|
String fallback,
|
||||||
|
String valueIt,
|
||||||
|
String valueEn,
|
||||||
|
String valueDe,
|
||||||
|
String valueFr) {
|
||||||
|
String normalizedLanguage = normalizeLanguage(language);
|
||||||
|
String preferred = switch (normalizedLanguage) {
|
||||||
|
case "it" -> valueIt;
|
||||||
|
case "en" -> valueEn;
|
||||||
|
case "de" -> valueDe;
|
||||||
|
case "fr" -> valueFr;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
String resolved = firstNonBlank(preferred, fallback);
|
||||||
|
if (resolved != null) {
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
return firstNonBlank(valueIt, valueEn, valueDe, valueFr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeLanguage(String language) {
|
||||||
|
if (language == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String normalized = language.trim().toLowerCase();
|
||||||
|
int separatorIndex = normalized.indexOf('-');
|
||||||
|
if (separatorIndex > 0) {
|
||||||
|
normalized = normalized.substring(0, separatorIndex);
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstNonBlank(String... values) {
|
||||||
|
if (values == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (String value : values) {
|
||||||
|
if (value != null && !value.isBlank()) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,18 @@ public class ShopProductVariant {
|
|||||||
@Column(name = "color_name", nullable = false, length = Integer.MAX_VALUE)
|
@Column(name = "color_name", nullable = false, length = Integer.MAX_VALUE)
|
||||||
private String colorName;
|
private String colorName;
|
||||||
|
|
||||||
|
@Column(name = "color_label_it", length = Integer.MAX_VALUE)
|
||||||
|
private String colorLabelIt;
|
||||||
|
|
||||||
|
@Column(name = "color_label_en", length = Integer.MAX_VALUE)
|
||||||
|
private String colorLabelEn;
|
||||||
|
|
||||||
|
@Column(name = "color_label_de", length = Integer.MAX_VALUE)
|
||||||
|
private String colorLabelDe;
|
||||||
|
|
||||||
|
@Column(name = "color_label_fr", length = Integer.MAX_VALUE)
|
||||||
|
private String colorLabelFr;
|
||||||
|
|
||||||
@Column(name = "color_hex", length = Integer.MAX_VALUE)
|
@Column(name = "color_hex", length = Integer.MAX_VALUE)
|
||||||
private String colorHex;
|
private String colorHex;
|
||||||
|
|
||||||
@@ -152,6 +164,38 @@ public class ShopProductVariant {
|
|||||||
this.colorName = colorName;
|
this.colorName = colorName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getColorLabelIt() {
|
||||||
|
return colorLabelIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelIt(String colorLabelIt) {
|
||||||
|
this.colorLabelIt = colorLabelIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelEn() {
|
||||||
|
return colorLabelEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelEn(String colorLabelEn) {
|
||||||
|
this.colorLabelEn = colorLabelEn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelDe() {
|
||||||
|
return colorLabelDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelDe(String colorLabelDe) {
|
||||||
|
this.colorLabelDe = colorLabelDe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColorLabelFr() {
|
||||||
|
return colorLabelFr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorLabelFr(String colorLabelFr) {
|
||||||
|
this.colorLabelFr = colorLabelFr;
|
||||||
|
}
|
||||||
|
|
||||||
public String getColorHex() {
|
public String getColorHex() {
|
||||||
return colorHex;
|
return colorHex;
|
||||||
}
|
}
|
||||||
@@ -215,4 +259,60 @@ public class ShopProductVariant {
|
|||||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||||
this.updatedAt = updatedAt;
|
this.updatedAt = updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getColorLabelForLanguage(String language) {
|
||||||
|
return resolveLocalizedValue(
|
||||||
|
language,
|
||||||
|
colorName,
|
||||||
|
colorLabelIt,
|
||||||
|
colorLabelEn,
|
||||||
|
colorLabelDe,
|
||||||
|
colorLabelFr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveLocalizedValue(String language,
|
||||||
|
String fallback,
|
||||||
|
String valueIt,
|
||||||
|
String valueEn,
|
||||||
|
String valueDe,
|
||||||
|
String valueFr) {
|
||||||
|
String normalizedLanguage = normalizeLanguage(language);
|
||||||
|
String preferred = switch (normalizedLanguage) {
|
||||||
|
case "it" -> valueIt;
|
||||||
|
case "en" -> valueEn;
|
||||||
|
case "de" -> valueDe;
|
||||||
|
case "fr" -> valueFr;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
String resolved = firstNonBlank(preferred, fallback);
|
||||||
|
if (resolved != null) {
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
return firstNonBlank(valueIt, valueEn, valueDe, valueFr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeLanguage(String language) {
|
||||||
|
if (language == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String normalized = language.trim().toLowerCase();
|
||||||
|
int separatorIndex = normalized.indexOf('-');
|
||||||
|
if (separatorIndex > 0) {
|
||||||
|
normalized = normalized.substring(0, separatorIndex);
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstNonBlank(String... values) {
|
||||||
|
if (values == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (String value : values) {
|
||||||
|
if (value != null && !value.isBlank()) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,10 +161,21 @@ public class AdminFilamentControllerService {
|
|||||||
String normalizedColorHex = normalizeAndValidateColorHex(payload.getColorHex());
|
String normalizedColorHex = normalizeAndValidateColorHex(payload.getColorHex());
|
||||||
String normalizedFinishType = normalizeAndValidateFinishType(payload.getFinishType(), payload.getIsMatte());
|
String normalizedFinishType = normalizeAndValidateFinishType(payload.getFinishType(), payload.getIsMatte());
|
||||||
String normalizedBrand = normalizeOptional(payload.getBrand());
|
String normalizedBrand = normalizeOptional(payload.getBrand());
|
||||||
|
String fallbackColorLabel = firstNonBlank(
|
||||||
|
normalizeOptional(payload.getColorLabelIt()),
|
||||||
|
normalizeOptional(payload.getColorLabelEn()),
|
||||||
|
normalizeOptional(payload.getColorLabelDe()),
|
||||||
|
normalizeOptional(payload.getColorLabelFr()),
|
||||||
|
normalizedColorName
|
||||||
|
);
|
||||||
|
|
||||||
variant.setFilamentMaterialType(material);
|
variant.setFilamentMaterialType(material);
|
||||||
variant.setVariantDisplayName(normalizedDisplayName);
|
variant.setVariantDisplayName(normalizedDisplayName);
|
||||||
variant.setColorName(normalizedColorName);
|
variant.setColorName(normalizedColorName);
|
||||||
|
variant.setColorLabelIt(firstNonBlank(normalizeOptional(payload.getColorLabelIt()), fallbackColorLabel));
|
||||||
|
variant.setColorLabelEn(firstNonBlank(normalizeOptional(payload.getColorLabelEn()), fallbackColorLabel));
|
||||||
|
variant.setColorLabelDe(firstNonBlank(normalizeOptional(payload.getColorLabelDe()), fallbackColorLabel));
|
||||||
|
variant.setColorLabelFr(firstNonBlank(normalizeOptional(payload.getColorLabelFr()), fallbackColorLabel));
|
||||||
variant.setColorHex(normalizedColorHex);
|
variant.setColorHex(normalizedColorHex);
|
||||||
variant.setFinishType(normalizedFinishType);
|
variant.setFinishType(normalizedFinishType);
|
||||||
variant.setBrand(normalizedBrand);
|
variant.setBrand(normalizedBrand);
|
||||||
@@ -226,6 +237,18 @@ public class AdminFilamentControllerService {
|
|||||||
return normalized.isBlank() ? null : normalized;
|
return normalized.isBlank() ? null : normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String firstNonBlank(String... values) {
|
||||||
|
if (values == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (String value : values) {
|
||||||
|
if (value != null && !value.isBlank()) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private FilamentMaterialType validateAndResolveMaterial(AdminUpsertFilamentVariantRequest payload) {
|
private FilamentMaterialType validateAndResolveMaterial(AdminUpsertFilamentVariantRequest payload) {
|
||||||
if (payload == null || payload.getMaterialTypeId() == null) {
|
if (payload == null || payload.getMaterialTypeId() == null) {
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "Material type id is required");
|
throw new ResponseStatusException(BAD_REQUEST, "Material type id is required");
|
||||||
@@ -306,6 +329,10 @@ public class AdminFilamentControllerService {
|
|||||||
|
|
||||||
dto.setVariantDisplayName(variant.getVariantDisplayName());
|
dto.setVariantDisplayName(variant.getVariantDisplayName());
|
||||||
dto.setColorName(variant.getColorName());
|
dto.setColorName(variant.getColorName());
|
||||||
|
dto.setColorLabelIt(variant.getColorLabelIt());
|
||||||
|
dto.setColorLabelEn(variant.getColorLabelEn());
|
||||||
|
dto.setColorLabelDe(variant.getColorLabelDe());
|
||||||
|
dto.setColorLabelFr(variant.getColorLabelFr());
|
||||||
dto.setColorHex(variant.getColorHex());
|
dto.setColorHex(variant.getColorHex());
|
||||||
dto.setFinishType(variant.getFinishType());
|
dto.setFinishType(variant.getFinishType());
|
||||||
dto.setBrand(variant.getBrand());
|
dto.setBrand(variant.getBrand());
|
||||||
|
|||||||
@@ -67,13 +67,13 @@ public class AdminShopCategoryControllerService {
|
|||||||
@Transactional
|
@Transactional
|
||||||
public AdminShopCategoryDto createCategory(AdminUpsertShopCategoryRequest payload) {
|
public AdminShopCategoryDto createCategory(AdminUpsertShopCategoryRequest payload) {
|
||||||
ensurePayload(payload);
|
ensurePayload(payload);
|
||||||
String normalizedName = normalizeRequiredName(payload.getName());
|
LocalizedCategoryContent localizedContent = normalizeLocalizedCategoryContent(payload);
|
||||||
String normalizedSlug = normalizeAndValidateSlug(payload.getSlug(), normalizedName);
|
String normalizedSlug = normalizeAndValidateSlug(payload.getSlug(), localizedContent.defaultName());
|
||||||
ensureSlugAvailable(normalizedSlug, null);
|
ensureSlugAvailable(normalizedSlug, null);
|
||||||
|
|
||||||
ShopCategory category = new ShopCategory();
|
ShopCategory category = new ShopCategory();
|
||||||
category.setCreatedAt(OffsetDateTime.now());
|
category.setCreatedAt(OffsetDateTime.now());
|
||||||
applyPayload(category, payload, normalizedName, normalizedSlug, null);
|
applyPayload(category, payload, localizedContent, normalizedSlug, null);
|
||||||
|
|
||||||
ShopCategory saved = shopCategoryRepository.save(category);
|
ShopCategory saved = shopCategoryRepository.save(category);
|
||||||
return getCategory(saved.getId());
|
return getCategory(saved.getId());
|
||||||
@@ -86,11 +86,11 @@ public class AdminShopCategoryControllerService {
|
|||||||
ShopCategory category = shopCategoryRepository.findById(categoryId)
|
ShopCategory category = shopCategoryRepository.findById(categoryId)
|
||||||
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Shop category not found"));
|
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Shop category not found"));
|
||||||
|
|
||||||
String normalizedName = normalizeRequiredName(payload.getName());
|
LocalizedCategoryContent localizedContent = normalizeLocalizedCategoryContent(payload);
|
||||||
String normalizedSlug = normalizeAndValidateSlug(payload.getSlug(), normalizedName);
|
String normalizedSlug = normalizeAndValidateSlug(payload.getSlug(), localizedContent.defaultName());
|
||||||
ensureSlugAvailable(normalizedSlug, category.getId());
|
ensureSlugAvailable(normalizedSlug, category.getId());
|
||||||
|
|
||||||
applyPayload(category, payload, normalizedName, normalizedSlug, category.getId());
|
applyPayload(category, payload, localizedContent, normalizedSlug, category.getId());
|
||||||
ShopCategory saved = shopCategoryRepository.save(category);
|
ShopCategory saved = shopCategoryRepository.save(category);
|
||||||
return getCategory(saved.getId());
|
return getCategory(saved.getId());
|
||||||
}
|
}
|
||||||
@@ -112,17 +112,33 @@ public class AdminShopCategoryControllerService {
|
|||||||
|
|
||||||
private void applyPayload(ShopCategory category,
|
private void applyPayload(ShopCategory category,
|
||||||
AdminUpsertShopCategoryRequest payload,
|
AdminUpsertShopCategoryRequest payload,
|
||||||
String normalizedName,
|
LocalizedCategoryContent localizedContent,
|
||||||
String normalizedSlug,
|
String normalizedSlug,
|
||||||
UUID currentCategoryId) {
|
UUID currentCategoryId) {
|
||||||
ShopCategory parentCategory = resolveParentCategory(payload.getParentCategoryId(), currentCategoryId);
|
ShopCategory parentCategory = resolveParentCategory(payload.getParentCategoryId(), currentCategoryId);
|
||||||
|
|
||||||
category.setParentCategory(parentCategory);
|
category.setParentCategory(parentCategory);
|
||||||
category.setSlug(normalizedSlug);
|
category.setSlug(normalizedSlug);
|
||||||
category.setName(normalizedName);
|
category.setName(localizedContent.defaultName());
|
||||||
category.setDescription(normalizeOptional(payload.getDescription()));
|
category.setNameIt(localizedContent.names().get("it"));
|
||||||
category.setSeoTitle(normalizeOptional(payload.getSeoTitle()));
|
category.setNameEn(localizedContent.names().get("en"));
|
||||||
category.setSeoDescription(normalizeOptional(payload.getSeoDescription()));
|
category.setNameDe(localizedContent.names().get("de"));
|
||||||
|
category.setNameFr(localizedContent.names().get("fr"));
|
||||||
|
category.setDescription(localizedContent.defaultDescription());
|
||||||
|
category.setDescriptionIt(localizedContent.descriptions().get("it"));
|
||||||
|
category.setDescriptionEn(localizedContent.descriptions().get("en"));
|
||||||
|
category.setDescriptionDe(localizedContent.descriptions().get("de"));
|
||||||
|
category.setDescriptionFr(localizedContent.descriptions().get("fr"));
|
||||||
|
category.setSeoTitle(localizedContent.defaultSeoTitle());
|
||||||
|
category.setSeoTitleIt(localizedContent.seoTitles().get("it"));
|
||||||
|
category.setSeoTitleEn(localizedContent.seoTitles().get("en"));
|
||||||
|
category.setSeoTitleDe(localizedContent.seoTitles().get("de"));
|
||||||
|
category.setSeoTitleFr(localizedContent.seoTitles().get("fr"));
|
||||||
|
category.setSeoDescription(localizedContent.defaultSeoDescription());
|
||||||
|
category.setSeoDescriptionIt(localizedContent.seoDescriptions().get("it"));
|
||||||
|
category.setSeoDescriptionEn(localizedContent.seoDescriptions().get("en"));
|
||||||
|
category.setSeoDescriptionDe(localizedContent.seoDescriptions().get("de"));
|
||||||
|
category.setSeoDescriptionFr(localizedContent.seoDescriptions().get("fr"));
|
||||||
category.setOgTitle(normalizeOptional(payload.getOgTitle()));
|
category.setOgTitle(normalizeOptional(payload.getOgTitle()));
|
||||||
category.setOgDescription(normalizeOptional(payload.getOgDescription()));
|
category.setOgDescription(normalizeOptional(payload.getOgDescription()));
|
||||||
category.setIndexable(payload.getIndexable() == null || payload.getIndexable());
|
category.setIndexable(payload.getIndexable() == null || payload.getIndexable());
|
||||||
@@ -161,14 +177,6 @@ public class AdminShopCategoryControllerService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String normalizeRequiredName(String name) {
|
|
||||||
String normalized = normalizeOptional(name);
|
|
||||||
if (normalized == null) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "Category name is required");
|
|
||||||
}
|
|
||||||
return normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String normalizeAndValidateSlug(String slug, String fallbackName) {
|
private String normalizeAndValidateSlug(String slug, String fallbackName) {
|
||||||
String source = normalizeOptional(slug);
|
String source = normalizeOptional(slug);
|
||||||
if (source == null) {
|
if (source == null) {
|
||||||
@@ -203,6 +211,103 @@ public class AdminShopCategoryControllerService {
|
|||||||
return normalized.isBlank() ? null : normalized;
|
return normalized.isBlank() ? null : normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String normalizeRequired(String value, String message) {
|
||||||
|
String normalized = normalizeOptional(value);
|
||||||
|
if (normalized == null) {
|
||||||
|
throw new ResponseStatusException(BAD_REQUEST, message);
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalizedCategoryContent normalizeLocalizedCategoryContent(AdminUpsertShopCategoryRequest payload) {
|
||||||
|
String legacyName = normalizeOptional(payload.getName());
|
||||||
|
String fallbackName = firstNonBlank(
|
||||||
|
legacyName,
|
||||||
|
normalizeOptional(payload.getNameIt()),
|
||||||
|
normalizeOptional(payload.getNameEn()),
|
||||||
|
normalizeOptional(payload.getNameDe()),
|
||||||
|
normalizeOptional(payload.getNameFr())
|
||||||
|
);
|
||||||
|
if (fallbackName == null) {
|
||||||
|
throw new ResponseStatusException(BAD_REQUEST, "Category name is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> names = new LinkedHashMap<>();
|
||||||
|
names.put("it", normalizeRequired(firstNonBlank(normalizeOptional(payload.getNameIt()), fallbackName), "Italian category name is required"));
|
||||||
|
names.put("en", normalizeRequired(firstNonBlank(normalizeOptional(payload.getNameEn()), fallbackName), "English category name is required"));
|
||||||
|
names.put("de", normalizeRequired(firstNonBlank(normalizeOptional(payload.getNameDe()), fallbackName), "German category name is required"));
|
||||||
|
names.put("fr", normalizeRequired(firstNonBlank(normalizeOptional(payload.getNameFr()), fallbackName), "French category name is required"));
|
||||||
|
|
||||||
|
String fallbackDescription = firstNonBlank(
|
||||||
|
normalizeOptional(payload.getDescription()),
|
||||||
|
normalizeOptional(payload.getDescriptionIt()),
|
||||||
|
normalizeOptional(payload.getDescriptionEn()),
|
||||||
|
normalizeOptional(payload.getDescriptionDe()),
|
||||||
|
normalizeOptional(payload.getDescriptionFr())
|
||||||
|
);
|
||||||
|
Map<String, String> descriptions = new LinkedHashMap<>();
|
||||||
|
descriptions.put("it", firstNonBlank(normalizeOptional(payload.getDescriptionIt()), fallbackDescription));
|
||||||
|
descriptions.put("en", firstNonBlank(normalizeOptional(payload.getDescriptionEn()), fallbackDescription));
|
||||||
|
descriptions.put("de", firstNonBlank(normalizeOptional(payload.getDescriptionDe()), fallbackDescription));
|
||||||
|
descriptions.put("fr", firstNonBlank(normalizeOptional(payload.getDescriptionFr()), fallbackDescription));
|
||||||
|
|
||||||
|
String fallbackSeoTitle = firstNonBlank(
|
||||||
|
normalizeOptional(payload.getSeoTitle()),
|
||||||
|
normalizeOptional(payload.getSeoTitleIt()),
|
||||||
|
normalizeOptional(payload.getSeoTitleEn()),
|
||||||
|
normalizeOptional(payload.getSeoTitleDe()),
|
||||||
|
normalizeOptional(payload.getSeoTitleFr())
|
||||||
|
);
|
||||||
|
Map<String, String> seoTitles = new LinkedHashMap<>();
|
||||||
|
seoTitles.put("it", firstNonBlank(normalizeOptional(payload.getSeoTitleIt()), fallbackSeoTitle));
|
||||||
|
seoTitles.put("en", firstNonBlank(normalizeOptional(payload.getSeoTitleEn()), fallbackSeoTitle));
|
||||||
|
seoTitles.put("de", firstNonBlank(normalizeOptional(payload.getSeoTitleDe()), fallbackSeoTitle));
|
||||||
|
seoTitles.put("fr", firstNonBlank(normalizeOptional(payload.getSeoTitleFr()), fallbackSeoTitle));
|
||||||
|
|
||||||
|
String fallbackSeoDescription = firstNonBlank(
|
||||||
|
normalizeOptional(payload.getSeoDescription()),
|
||||||
|
normalizeOptional(payload.getSeoDescriptionIt()),
|
||||||
|
normalizeOptional(payload.getSeoDescriptionEn()),
|
||||||
|
normalizeOptional(payload.getSeoDescriptionDe()),
|
||||||
|
normalizeOptional(payload.getSeoDescriptionFr())
|
||||||
|
);
|
||||||
|
Map<String, String> seoDescriptions = new LinkedHashMap<>();
|
||||||
|
seoDescriptions.put("it", validateSeoDescriptionLength(firstNonBlank(normalizeOptional(payload.getSeoDescriptionIt()), fallbackSeoDescription), "Italian"));
|
||||||
|
seoDescriptions.put("en", validateSeoDescriptionLength(firstNonBlank(normalizeOptional(payload.getSeoDescriptionEn()), fallbackSeoDescription), "English"));
|
||||||
|
seoDescriptions.put("de", validateSeoDescriptionLength(firstNonBlank(normalizeOptional(payload.getSeoDescriptionDe()), fallbackSeoDescription), "German"));
|
||||||
|
seoDescriptions.put("fr", validateSeoDescriptionLength(firstNonBlank(normalizeOptional(payload.getSeoDescriptionFr()), fallbackSeoDescription), "French"));
|
||||||
|
|
||||||
|
return new LocalizedCategoryContent(
|
||||||
|
names.get("it"),
|
||||||
|
firstNonBlank(descriptions.get("it"), fallbackDescription),
|
||||||
|
firstNonBlank(seoTitles.get("it"), fallbackSeoTitle),
|
||||||
|
firstNonBlank(seoDescriptions.get("it"), fallbackSeoDescription),
|
||||||
|
names,
|
||||||
|
descriptions,
|
||||||
|
seoTitles,
|
||||||
|
seoDescriptions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String validateSeoDescriptionLength(String value, String languageLabel) {
|
||||||
|
if (value != null && value.length() > 160) {
|
||||||
|
throw new ResponseStatusException(BAD_REQUEST, languageLabel + " SEO description must be at most 160 characters");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstNonBlank(String... values) {
|
||||||
|
if (values == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (String value : values) {
|
||||||
|
if (value != null && !value.isBlank()) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private CategoryContext buildContext() {
|
private CategoryContext buildContext() {
|
||||||
List<ShopCategory> categories = shopCategoryRepository.findAllByOrderBySortOrderAscNameAsc();
|
List<ShopCategory> categories = shopCategoryRepository.findAllByOrderBySortOrderAscNameAsc();
|
||||||
List<ShopProduct> products = shopProductRepository.findAll();
|
List<ShopProduct> products = shopProductRepository.findAll();
|
||||||
@@ -278,9 +383,25 @@ public class AdminShopCategoryControllerService {
|
|||||||
dto.setParentCategoryName(category.getParentCategory() != null ? category.getParentCategory().getName() : null);
|
dto.setParentCategoryName(category.getParentCategory() != null ? category.getParentCategory().getName() : null);
|
||||||
dto.setSlug(category.getSlug());
|
dto.setSlug(category.getSlug());
|
||||||
dto.setName(category.getName());
|
dto.setName(category.getName());
|
||||||
|
dto.setNameIt(category.getNameIt());
|
||||||
|
dto.setNameEn(category.getNameEn());
|
||||||
|
dto.setNameDe(category.getNameDe());
|
||||||
|
dto.setNameFr(category.getNameFr());
|
||||||
dto.setDescription(category.getDescription());
|
dto.setDescription(category.getDescription());
|
||||||
|
dto.setDescriptionIt(category.getDescriptionIt());
|
||||||
|
dto.setDescriptionEn(category.getDescriptionEn());
|
||||||
|
dto.setDescriptionDe(category.getDescriptionDe());
|
||||||
|
dto.setDescriptionFr(category.getDescriptionFr());
|
||||||
dto.setSeoTitle(category.getSeoTitle());
|
dto.setSeoTitle(category.getSeoTitle());
|
||||||
|
dto.setSeoTitleIt(category.getSeoTitleIt());
|
||||||
|
dto.setSeoTitleEn(category.getSeoTitleEn());
|
||||||
|
dto.setSeoTitleDe(category.getSeoTitleDe());
|
||||||
|
dto.setSeoTitleFr(category.getSeoTitleFr());
|
||||||
dto.setSeoDescription(category.getSeoDescription());
|
dto.setSeoDescription(category.getSeoDescription());
|
||||||
|
dto.setSeoDescriptionIt(category.getSeoDescriptionIt());
|
||||||
|
dto.setSeoDescriptionEn(category.getSeoDescriptionEn());
|
||||||
|
dto.setSeoDescriptionDe(category.getSeoDescriptionDe());
|
||||||
|
dto.setSeoDescriptionFr(category.getSeoDescriptionFr());
|
||||||
dto.setOgTitle(category.getOgTitle());
|
dto.setOgTitle(category.getOgTitle());
|
||||||
dto.setOgDescription(category.getOgDescription());
|
dto.setOgDescription(category.getOgDescription());
|
||||||
dto.setIndexable(category.getIndexable());
|
dto.setIndexable(category.getIndexable());
|
||||||
@@ -331,4 +452,16 @@ public class AdminShopCategoryControllerService {
|
|||||||
Map<UUID, Integer> descendantProductCounts
|
Map<UUID, Integer> descendantProductCounts
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record LocalizedCategoryContent(
|
||||||
|
String defaultName,
|
||||||
|
String defaultDescription,
|
||||||
|
String defaultSeoTitle,
|
||||||
|
String defaultSeoDescription,
|
||||||
|
Map<String, String> names,
|
||||||
|
Map<String, String> descriptions,
|
||||||
|
Map<String, String> seoTitles,
|
||||||
|
Map<String, String> seoDescriptions
|
||||||
|
) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -353,6 +353,13 @@ public class AdminShopProductControllerService {
|
|||||||
String normalizedColorName = normalizeRequired(payload.getColorName(), "Variant colorName is required");
|
String normalizedColorName = normalizeRequired(payload.getColorName(), "Variant colorName is required");
|
||||||
String normalizedVariantLabel = normalizeOptional(payload.getVariantLabel());
|
String normalizedVariantLabel = normalizeOptional(payload.getVariantLabel());
|
||||||
String normalizedSku = normalizeOptional(payload.getSku());
|
String normalizedSku = normalizeOptional(payload.getSku());
|
||||||
|
String fallbackColorLabel = firstNonBlank(
|
||||||
|
normalizeOptional(payload.getColorLabelIt()),
|
||||||
|
normalizeOptional(payload.getColorLabelEn()),
|
||||||
|
normalizeOptional(payload.getColorLabelDe()),
|
||||||
|
normalizeOptional(payload.getColorLabelFr()),
|
||||||
|
normalizedColorName
|
||||||
|
);
|
||||||
String normalizedMaterialCode = normalizeRequired(
|
String normalizedMaterialCode = normalizeRequired(
|
||||||
payload.getInternalMaterialCode(),
|
payload.getInternalMaterialCode(),
|
||||||
"Variant internalMaterialCode is required"
|
"Variant internalMaterialCode is required"
|
||||||
@@ -380,6 +387,10 @@ public class AdminShopProductControllerService {
|
|||||||
variant.setSku(normalizedSku);
|
variant.setSku(normalizedSku);
|
||||||
variant.setVariantLabel(normalizedVariantLabel != null ? normalizedVariantLabel : normalizedColorName);
|
variant.setVariantLabel(normalizedVariantLabel != null ? normalizedVariantLabel : normalizedColorName);
|
||||||
variant.setColorName(normalizedColorName);
|
variant.setColorName(normalizedColorName);
|
||||||
|
variant.setColorLabelIt(firstNonBlank(normalizeOptional(payload.getColorLabelIt()), fallbackColorLabel));
|
||||||
|
variant.setColorLabelEn(firstNonBlank(normalizeOptional(payload.getColorLabelEn()), fallbackColorLabel));
|
||||||
|
variant.setColorLabelDe(firstNonBlank(normalizeOptional(payload.getColorLabelDe()), fallbackColorLabel));
|
||||||
|
variant.setColorLabelFr(firstNonBlank(normalizeOptional(payload.getColorLabelFr()), fallbackColorLabel));
|
||||||
variant.setColorHex(normalizeColorHex(payload.getColorHex()));
|
variant.setColorHex(normalizeColorHex(payload.getColorHex()));
|
||||||
variant.setInternalMaterialCode(normalizedMaterialCode);
|
variant.setInternalMaterialCode(normalizedMaterialCode);
|
||||||
variant.setPriceChf(price);
|
variant.setPriceChf(price);
|
||||||
@@ -531,6 +542,10 @@ public class AdminShopProductControllerService {
|
|||||||
dto.setSku(variant.getSku());
|
dto.setSku(variant.getSku());
|
||||||
dto.setVariantLabel(variant.getVariantLabel());
|
dto.setVariantLabel(variant.getVariantLabel());
|
||||||
dto.setColorName(variant.getColorName());
|
dto.setColorName(variant.getColorName());
|
||||||
|
dto.setColorLabelIt(variant.getColorLabelIt());
|
||||||
|
dto.setColorLabelEn(variant.getColorLabelEn());
|
||||||
|
dto.setColorLabelDe(variant.getColorLabelDe());
|
||||||
|
dto.setColorLabelFr(variant.getColorLabelFr());
|
||||||
dto.setColorHex(variant.getColorHex());
|
dto.setColorHex(variant.getColorHex());
|
||||||
dto.setInternalMaterialCode(variant.getInternalMaterialCode());
|
dto.setInternalMaterialCode(variant.getInternalMaterialCode());
|
||||||
dto.setPriceChf(variant.getPriceChf());
|
dto.setPriceChf(variant.getPriceChf());
|
||||||
|
|||||||
@@ -280,11 +280,19 @@ public class AdminOrderControllerService {
|
|||||||
itemDto.setShopProductName(item.getShopProductName());
|
itemDto.setShopProductName(item.getShopProductName());
|
||||||
itemDto.setShopVariantLabel(item.getShopVariantLabel());
|
itemDto.setShopVariantLabel(item.getShopVariantLabel());
|
||||||
itemDto.setShopVariantColorName(item.getShopVariantColorName());
|
itemDto.setShopVariantColorName(item.getShopVariantColorName());
|
||||||
|
itemDto.setShopVariantColorLabelIt(item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelIt() : null);
|
||||||
|
itemDto.setShopVariantColorLabelEn(item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelEn() : null);
|
||||||
|
itemDto.setShopVariantColorLabelDe(item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelDe() : null);
|
||||||
|
itemDto.setShopVariantColorLabelFr(item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelFr() : null);
|
||||||
itemDto.setShopVariantColorHex(item.getShopVariantColorHex());
|
itemDto.setShopVariantColorHex(item.getShopVariantColorHex());
|
||||||
if (item.getFilamentVariant() != null) {
|
if (item.getFilamentVariant() != null) {
|
||||||
itemDto.setFilamentVariantId(item.getFilamentVariant().getId());
|
itemDto.setFilamentVariantId(item.getFilamentVariant().getId());
|
||||||
itemDto.setFilamentVariantDisplayName(item.getFilamentVariant().getVariantDisplayName());
|
itemDto.setFilamentVariantDisplayName(item.getFilamentVariant().getVariantDisplayName());
|
||||||
itemDto.setFilamentColorName(item.getFilamentVariant().getColorName());
|
itemDto.setFilamentColorName(item.getFilamentVariant().getColorName());
|
||||||
|
itemDto.setFilamentColorLabelIt(item.getFilamentVariant().getColorLabelIt());
|
||||||
|
itemDto.setFilamentColorLabelEn(item.getFilamentVariant().getColorLabelEn());
|
||||||
|
itemDto.setFilamentColorLabelDe(item.getFilamentVariant().getColorLabelDe());
|
||||||
|
itemDto.setFilamentColorLabelFr(item.getFilamentVariant().getColorLabelFr());
|
||||||
itemDto.setFilamentColorHex(item.getFilamentVariant().getColorHex());
|
itemDto.setFilamentColorHex(item.getFilamentVariant().getColorHex());
|
||||||
}
|
}
|
||||||
itemDto.setQuality(item.getQuality());
|
itemDto.setQuality(item.getQuality());
|
||||||
|
|||||||
@@ -334,11 +334,19 @@ public class OrderControllerService {
|
|||||||
itemDto.setShopProductName(item.getShopProductName());
|
itemDto.setShopProductName(item.getShopProductName());
|
||||||
itemDto.setShopVariantLabel(item.getShopVariantLabel());
|
itemDto.setShopVariantLabel(item.getShopVariantLabel());
|
||||||
itemDto.setShopVariantColorName(item.getShopVariantColorName());
|
itemDto.setShopVariantColorName(item.getShopVariantColorName());
|
||||||
|
itemDto.setShopVariantColorLabelIt(item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelIt() : null);
|
||||||
|
itemDto.setShopVariantColorLabelEn(item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelEn() : null);
|
||||||
|
itemDto.setShopVariantColorLabelDe(item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelDe() : null);
|
||||||
|
itemDto.setShopVariantColorLabelFr(item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelFr() : null);
|
||||||
itemDto.setShopVariantColorHex(item.getShopVariantColorHex());
|
itemDto.setShopVariantColorHex(item.getShopVariantColorHex());
|
||||||
if (item.getFilamentVariant() != null) {
|
if (item.getFilamentVariant() != null) {
|
||||||
itemDto.setFilamentVariantId(item.getFilamentVariant().getId());
|
itemDto.setFilamentVariantId(item.getFilamentVariant().getId());
|
||||||
itemDto.setFilamentVariantDisplayName(item.getFilamentVariant().getVariantDisplayName());
|
itemDto.setFilamentVariantDisplayName(item.getFilamentVariant().getVariantDisplayName());
|
||||||
itemDto.setFilamentColorName(item.getFilamentVariant().getColorName());
|
itemDto.setFilamentColorName(item.getFilamentVariant().getColorName());
|
||||||
|
itemDto.setFilamentColorLabelIt(item.getFilamentVariant().getColorLabelIt());
|
||||||
|
itemDto.setFilamentColorLabelEn(item.getFilamentVariant().getColorLabelEn());
|
||||||
|
itemDto.setFilamentColorLabelDe(item.getFilamentVariant().getColorLabelDe());
|
||||||
|
itemDto.setFilamentColorLabelFr(item.getFilamentVariant().getColorLabelFr());
|
||||||
itemDto.setFilamentColorHex(item.getFilamentVariant().getColorHex());
|
itemDto.setFilamentColorHex(item.getFilamentVariant().getColorHex());
|
||||||
}
|
}
|
||||||
itemDto.setQuality(item.getQuality());
|
itemDto.setQuality(item.getQuality());
|
||||||
|
|||||||
@@ -81,7 +81,15 @@ public class QuoteSessionResponseAssembler {
|
|||||||
dto.put("shopProductName", item.getShopProductName());
|
dto.put("shopProductName", item.getShopProductName());
|
||||||
dto.put("shopVariantLabel", item.getShopVariantLabel());
|
dto.put("shopVariantLabel", item.getShopVariantLabel());
|
||||||
dto.put("shopVariantColorName", item.getShopVariantColorName());
|
dto.put("shopVariantColorName", item.getShopVariantColorName());
|
||||||
|
dto.put("shopVariantColorLabelIt", item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelIt() : null);
|
||||||
|
dto.put("shopVariantColorLabelEn", item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelEn() : null);
|
||||||
|
dto.put("shopVariantColorLabelDe", item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelDe() : null);
|
||||||
|
dto.put("shopVariantColorLabelFr", item.getShopProductVariant() != null ? item.getShopProductVariant().getColorLabelFr() : null);
|
||||||
dto.put("shopVariantColorHex", item.getShopVariantColorHex());
|
dto.put("shopVariantColorHex", item.getShopVariantColorHex());
|
||||||
|
dto.put("filamentColorLabelIt", item.getFilamentVariant() != null ? item.getFilamentVariant().getColorLabelIt() : null);
|
||||||
|
dto.put("filamentColorLabelEn", item.getFilamentVariant() != null ? item.getFilamentVariant().getColorLabelEn() : null);
|
||||||
|
dto.put("filamentColorLabelDe", item.getFilamentVariant() != null ? item.getFilamentVariant().getColorLabelDe() : null);
|
||||||
|
dto.put("filamentColorLabelFr", item.getFilamentVariant() != null ? item.getFilamentVariant().getColorLabelFr() : null);
|
||||||
dto.put("materialCode", item.getMaterialCode());
|
dto.put("materialCode", item.getMaterialCode());
|
||||||
dto.put("quality", item.getQuality());
|
dto.put("quality", item.getQuality());
|
||||||
dto.put("nozzleDiameterMm", item.getNozzleDiameterMm());
|
dto.put("nozzleDiameterMm", item.getNozzleDiameterMm());
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public class PublicShopCatalogService {
|
|||||||
|
|
||||||
public List<ShopCategoryTreeDto> getCategories(String language) {
|
public List<ShopCategoryTreeDto> getCategories(String language) {
|
||||||
CategoryContext categoryContext = loadCategoryContext(language);
|
CategoryContext categoryContext = loadCategoryContext(language);
|
||||||
return buildCategoryTree(null, categoryContext);
|
return buildCategoryTree(null, categoryContext, language);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShopCategoryDetailDto getCategory(String slug, String language) {
|
public ShopCategoryDetailDto getCategory(String slug, String language) {
|
||||||
@@ -83,7 +83,7 @@ public class PublicShopCatalogService {
|
|||||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Category not found");
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Category not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildCategoryDetail(category, categoryContext);
|
return buildCategoryDetail(category, categoryContext, language);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShopProductCatalogResponseDto getProductCatalog(String categorySlug, Boolean featuredOnly, String language) {
|
public ShopProductCatalogResponseDto getProductCatalog(String categorySlug, Boolean featuredOnly, String language) {
|
||||||
@@ -114,7 +114,7 @@ public class PublicShopCatalogService {
|
|||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
ShopCategoryDetailDto selectedCategoryDetail = selectedCategory != null
|
ShopCategoryDetailDto selectedCategoryDetail = selectedCategory != null
|
||||||
? buildCategoryDetail(selectedCategory, categoryContext)
|
? buildCategoryDetail(selectedCategory, categoryContext, language)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return new ShopProductCatalogResponseDto(
|
return new ShopProductCatalogResponseDto(
|
||||||
@@ -316,53 +316,63 @@ public class PublicShopCatalogService {
|
|||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ShopCategoryTreeDto> buildCategoryTree(UUID parentId, CategoryContext categoryContext) {
|
private List<ShopCategoryTreeDto> buildCategoryTree(UUID parentId,
|
||||||
|
CategoryContext categoryContext,
|
||||||
|
String language) {
|
||||||
return categoryContext.childrenByParentId().getOrDefault(parentId, List.of()).stream()
|
return categoryContext.childrenByParentId().getOrDefault(parentId, List.of()).stream()
|
||||||
.map(category -> new ShopCategoryTreeDto(
|
.map(category -> new ShopCategoryTreeDto(
|
||||||
category.getId(),
|
category.getId(),
|
||||||
category.getParentCategory() != null ? category.getParentCategory().getId() : null,
|
category.getParentCategory() != null ? category.getParentCategory().getId() : null,
|
||||||
category.getSlug(),
|
category.getSlug(),
|
||||||
category.getName(),
|
category.getNameForLanguage(language),
|
||||||
category.getDescription(),
|
category.getDescriptionForLanguage(language),
|
||||||
category.getSeoTitle(),
|
category.getSeoTitleForLanguage(language),
|
||||||
category.getSeoDescription(),
|
category.getSeoDescriptionForLanguage(language),
|
||||||
category.getOgTitle(),
|
category.getOgTitle(),
|
||||||
category.getOgDescription(),
|
category.getOgDescription(),
|
||||||
category.getIndexable(),
|
category.getIndexable(),
|
||||||
category.getSortOrder(),
|
category.getSortOrder(),
|
||||||
categoryContext.descendantProductCounts().getOrDefault(category.getId(), 0),
|
categoryContext.descendantProductCounts().getOrDefault(category.getId(), 0),
|
||||||
selectPrimaryMedia(categoryContext.categoryMediaBySlug().get(categoryMediaUsageKey(category))),
|
selectPrimaryMedia(categoryContext.categoryMediaBySlug().get(categoryMediaUsageKey(category))),
|
||||||
buildCategoryTree(category.getId(), categoryContext)
|
buildCategoryTree(category.getId(), categoryContext, language)
|
||||||
))
|
))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ShopCategoryDetailDto buildCategoryDetail(ShopCategory category, CategoryContext categoryContext) {
|
private ShopCategoryDetailDto buildCategoryDetail(ShopCategory category,
|
||||||
|
CategoryContext categoryContext,
|
||||||
|
String language) {
|
||||||
List<PublicMediaUsageDto> images = categoryContext.categoryMediaBySlug().getOrDefault(categoryMediaUsageKey(category), List.of());
|
List<PublicMediaUsageDto> images = categoryContext.categoryMediaBySlug().getOrDefault(categoryMediaUsageKey(category), List.of());
|
||||||
|
String localizedSeoTitle = category.getSeoTitleForLanguage(language);
|
||||||
|
String localizedSeoDescription = category.getSeoDescriptionForLanguage(language);
|
||||||
return new ShopCategoryDetailDto(
|
return new ShopCategoryDetailDto(
|
||||||
category.getId(),
|
category.getId(),
|
||||||
category.getSlug(),
|
category.getSlug(),
|
||||||
category.getName(),
|
category.getNameForLanguage(language),
|
||||||
category.getDescription(),
|
category.getDescriptionForLanguage(language),
|
||||||
category.getSeoTitle(),
|
localizedSeoTitle,
|
||||||
category.getSeoDescription(),
|
localizedSeoDescription,
|
||||||
category.getOgTitle(),
|
category.getOgTitle(),
|
||||||
category.getOgDescription(),
|
category.getOgDescription(),
|
||||||
category.getIndexable(),
|
category.getIndexable(),
|
||||||
category.getSortOrder(),
|
category.getSortOrder(),
|
||||||
categoryContext.descendantProductCounts().getOrDefault(category.getId(), 0),
|
categoryContext.descendantProductCounts().getOrDefault(category.getId(), 0),
|
||||||
buildCategoryBreadcrumbs(category),
|
buildCategoryBreadcrumbs(category, language),
|
||||||
selectPrimaryMedia(images),
|
selectPrimaryMedia(images),
|
||||||
images,
|
images,
|
||||||
buildCategoryTree(category.getId(), categoryContext)
|
buildCategoryTree(category.getId(), categoryContext, language)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ShopCategoryRefDto> buildCategoryBreadcrumbs(ShopCategory category) {
|
private List<ShopCategoryRefDto> buildCategoryBreadcrumbs(ShopCategory category, String language) {
|
||||||
List<ShopCategoryRefDto> breadcrumbs = new ArrayList<>();
|
List<ShopCategoryRefDto> breadcrumbs = new ArrayList<>();
|
||||||
ShopCategory current = category;
|
ShopCategory current = category;
|
||||||
while (current != null) {
|
while (current != null) {
|
||||||
breadcrumbs.add(new ShopCategoryRefDto(current.getId(), current.getSlug(), current.getName()));
|
breadcrumbs.add(new ShopCategoryRefDto(
|
||||||
|
current.getId(),
|
||||||
|
current.getSlug(),
|
||||||
|
current.getNameForLanguage(language)
|
||||||
|
));
|
||||||
current = current.getParentCategory();
|
current = current.getParentCategory();
|
||||||
}
|
}
|
||||||
java.util.Collections.reverse(breadcrumbs);
|
java.util.Collections.reverse(breadcrumbs);
|
||||||
@@ -399,11 +409,11 @@ public class PublicShopCatalogService {
|
|||||||
new ShopCategoryRefDto(
|
new ShopCategoryRefDto(
|
||||||
entry.product().getCategory().getId(),
|
entry.product().getCategory().getId(),
|
||||||
entry.product().getCategory().getSlug(),
|
entry.product().getCategory().getSlug(),
|
||||||
entry.product().getCategory().getName()
|
entry.product().getCategory().getNameForLanguage(language)
|
||||||
),
|
),
|
||||||
resolvePriceFrom(entry.variants()),
|
resolvePriceFrom(entry.variants()),
|
||||||
resolvePriceTo(entry.variants()),
|
resolvePriceTo(entry.variants()),
|
||||||
toVariantDto(entry.defaultVariant(), entry.defaultVariant(), variantColorHexByMaterialAndColor),
|
toVariantDto(entry.defaultVariant(), entry.defaultVariant(), variantColorHexByMaterialAndColor, language),
|
||||||
selectPrimaryMedia(images),
|
selectPrimaryMedia(images),
|
||||||
toProductModelDto(entry)
|
toProductModelDto(entry)
|
||||||
);
|
);
|
||||||
@@ -432,14 +442,14 @@ public class PublicShopCatalogService {
|
|||||||
new ShopCategoryRefDto(
|
new ShopCategoryRefDto(
|
||||||
entry.product().getCategory().getId(),
|
entry.product().getCategory().getId(),
|
||||||
entry.product().getCategory().getSlug(),
|
entry.product().getCategory().getSlug(),
|
||||||
entry.product().getCategory().getName()
|
entry.product().getCategory().getNameForLanguage(language)
|
||||||
),
|
),
|
||||||
buildCategoryBreadcrumbs(entry.product().getCategory()),
|
buildCategoryBreadcrumbs(entry.product().getCategory(), language),
|
||||||
resolvePriceFrom(entry.variants()),
|
resolvePriceFrom(entry.variants()),
|
||||||
resolvePriceTo(entry.variants()),
|
resolvePriceTo(entry.variants()),
|
||||||
toVariantDto(entry.defaultVariant(), entry.defaultVariant(), variantColorHexByMaterialAndColor),
|
toVariantDto(entry.defaultVariant(), entry.defaultVariant(), variantColorHexByMaterialAndColor, language),
|
||||||
entry.variants().stream()
|
entry.variants().stream()
|
||||||
.map(variant -> toVariantDto(variant, entry.defaultVariant(), variantColorHexByMaterialAndColor))
|
.map(variant -> toVariantDto(variant, entry.defaultVariant(), variantColorHexByMaterialAndColor, language))
|
||||||
.toList(),
|
.toList(),
|
||||||
selectPrimaryMedia(images),
|
selectPrimaryMedia(images),
|
||||||
images,
|
images,
|
||||||
@@ -449,7 +459,8 @@ public class PublicShopCatalogService {
|
|||||||
|
|
||||||
private ShopProductVariantOptionDto toVariantDto(ShopProductVariant variant,
|
private ShopProductVariantOptionDto toVariantDto(ShopProductVariant variant,
|
||||||
ShopProductVariant defaultVariant,
|
ShopProductVariant defaultVariant,
|
||||||
Map<String, String> variantColorHexByMaterialAndColor) {
|
Map<String, String> variantColorHexByMaterialAndColor,
|
||||||
|
String language) {
|
||||||
if (variant == null) {
|
if (variant == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -463,6 +474,7 @@ public class PublicShopCatalogService {
|
|||||||
variant.getSku(),
|
variant.getSku(),
|
||||||
variant.getVariantLabel(),
|
variant.getVariantLabel(),
|
||||||
variant.getColorName(),
|
variant.getColorName(),
|
||||||
|
variant.getColorLabelForLanguage(language),
|
||||||
colorHex,
|
colorHex,
|
||||||
variant.getPriceChf(),
|
variant.getPriceChf(),
|
||||||
defaultVariant != null && Objects.equals(defaultVariant.getId(), variant.getId())
|
defaultVariant != null && Objects.equals(defaultVariant.getId(), variant.getId())
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.printcalculator.entity;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class ShopCategoryTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void localizedAccessorsShouldReturnLanguageSpecificValues() {
|
||||||
|
ShopCategory category = new ShopCategory();
|
||||||
|
category.setName("Desk accessories");
|
||||||
|
category.setNameIt("Accessori da scrivania");
|
||||||
|
category.setNameEn("Desk accessories");
|
||||||
|
category.setNameDe("Schreibtischzubehor");
|
||||||
|
category.setNameFr("Accessoires de bureau");
|
||||||
|
category.setDescription("Legacy description");
|
||||||
|
category.setDescriptionIt("Organizer e accessori stampati per la scrivania.");
|
||||||
|
category.setDescriptionEn("Printed desk organizers and accessories.");
|
||||||
|
category.setDescriptionDe("Gedruckte Organizer und Zubehor fur den Schreibtisch.");
|
||||||
|
category.setDescriptionFr("Accessoires et organiseurs imprimes pour le bureau.");
|
||||||
|
category.setSeoTitle("Legacy SEO title");
|
||||||
|
category.setSeoTitleIt("Accessori da scrivania stampati in 3D");
|
||||||
|
category.setSeoTitleEn("3D printed desk accessories");
|
||||||
|
category.setSeoTitleDe("3D-gedruckte Schreibtischaccessoires");
|
||||||
|
category.setSeoTitleFr("Accessoires de bureau imprimes en 3D");
|
||||||
|
category.setSeoDescription("Legacy SEO description");
|
||||||
|
category.setSeoDescriptionIt("Accessori da scrivania personalizzati e funzionali.");
|
||||||
|
category.setSeoDescriptionEn("Functional custom desk accessories.");
|
||||||
|
category.setSeoDescriptionDe("Funktionale personalisierte Schreibtischaccessoires.");
|
||||||
|
category.setSeoDescriptionFr("Accessoires de bureau fonctionnels et personnalises.");
|
||||||
|
|
||||||
|
assertEquals("Accessori da scrivania", category.getNameForLanguage("it"));
|
||||||
|
assertEquals("Desk accessories", category.getNameForLanguage("en"));
|
||||||
|
assertEquals("Schreibtischzubehor", category.getNameForLanguage("de"));
|
||||||
|
assertEquals("Accessoires de bureau", category.getNameForLanguage("fr"));
|
||||||
|
assertEquals("Gedruckte Organizer und Zubehor fur den Schreibtisch.", category.getDescriptionForLanguage("de"));
|
||||||
|
assertEquals("3D printed desk accessories", category.getSeoTitleForLanguage("en"));
|
||||||
|
assertEquals("Accessoires de bureau fonctionnels et personnalises.", category.getSeoDescriptionForLanguage("fr"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void localizedAccessorsShouldFallbackToLegacyValues() {
|
||||||
|
ShopCategory category = new ShopCategory();
|
||||||
|
category.setName("Desk accessories");
|
||||||
|
category.setDescription("Printed desk organizers and accessories.");
|
||||||
|
category.setSeoTitle("3D printed desk accessories");
|
||||||
|
category.setSeoDescription("Functional custom desk accessories.");
|
||||||
|
|
||||||
|
assertEquals("Desk accessories", category.getNameForLanguage("it"));
|
||||||
|
assertEquals("Printed desk organizers and accessories.", category.getDescriptionForLanguage("de"));
|
||||||
|
assertEquals("3D printed desk accessories", category.getSeoTitleForLanguage("fr-CH"));
|
||||||
|
assertEquals("Functional custom desk accessories.", category.getSeoDescriptionForLanguage("en-US"));
|
||||||
|
}
|
||||||
|
}
|
||||||
116
db.sql
116
db.sql
@@ -44,6 +44,10 @@ create table filament_variant
|
|||||||
|
|
||||||
variant_display_name text not null, -- es: "PLA Nero Opaco BrandX"
|
variant_display_name text not null, -- es: "PLA Nero Opaco BrandX"
|
||||||
color_name text not null, -- Nero, Bianco, ecc.
|
color_name text not null, -- Nero, Bianco, ecc.
|
||||||
|
color_label_it text,
|
||||||
|
color_label_en text,
|
||||||
|
color_label_de text,
|
||||||
|
color_label_fr text,
|
||||||
color_hex text,
|
color_hex text,
|
||||||
finish_type text not null default 'GLOSSY'
|
finish_type text not null default 'GLOSSY'
|
||||||
check (finish_type in ('GLOSSY', 'MATTE', 'MARBLE', 'SILK', 'TRANSLUCENT', 'SPECIAL')),
|
check (finish_type in ('GLOSSY', 'MATTE', 'MARBLE', 'SILK', 'TRANSLUCENT', 'SPECIAL')),
|
||||||
@@ -70,6 +74,22 @@ select filament_variant_id,
|
|||||||
(stock_spools * spool_net_kg) as stock_kg
|
(stock_spools * spool_net_kg) as stock_kg
|
||||||
from filament_variant;
|
from filament_variant;
|
||||||
|
|
||||||
|
alter table filament_variant
|
||||||
|
add column if not exists color_label_it text,
|
||||||
|
add column if not exists color_label_en text,
|
||||||
|
add column if not exists color_label_de text,
|
||||||
|
add column if not exists color_label_fr text;
|
||||||
|
|
||||||
|
update filament_variant
|
||||||
|
set color_label_it = coalesce(nullif(btrim(color_label_it), ''), color_name),
|
||||||
|
color_label_en = coalesce(nullif(btrim(color_label_en), ''), color_name),
|
||||||
|
color_label_de = coalesce(nullif(btrim(color_label_de), ''), color_name),
|
||||||
|
color_label_fr = coalesce(nullif(btrim(color_label_fr), ''), color_name)
|
||||||
|
where nullif(btrim(color_label_it), '') is null
|
||||||
|
or nullif(btrim(color_label_en), '') is null
|
||||||
|
or nullif(btrim(color_label_de), '') is null
|
||||||
|
or nullif(btrim(color_label_fr), '') is null;
|
||||||
|
|
||||||
create table printer_machine_profile
|
create table printer_machine_profile
|
||||||
(
|
(
|
||||||
printer_machine_profile_id bigserial primary key,
|
printer_machine_profile_id bigserial primary key,
|
||||||
@@ -1013,9 +1033,25 @@ CREATE TABLE IF NOT EXISTS shop_category
|
|||||||
parent_category_id uuid REFERENCES shop_category (shop_category_id) ON DELETE SET NULL,
|
parent_category_id uuid REFERENCES shop_category (shop_category_id) ON DELETE SET NULL,
|
||||||
slug text NOT NULL UNIQUE,
|
slug text NOT NULL UNIQUE,
|
||||||
name text NOT NULL,
|
name text NOT NULL,
|
||||||
|
name_it text,
|
||||||
|
name_en text,
|
||||||
|
name_de text,
|
||||||
|
name_fr text,
|
||||||
description text,
|
description text,
|
||||||
|
description_it text,
|
||||||
|
description_en text,
|
||||||
|
description_de text,
|
||||||
|
description_fr text,
|
||||||
seo_title text,
|
seo_title text,
|
||||||
|
seo_title_it text,
|
||||||
|
seo_title_en text,
|
||||||
|
seo_title_de text,
|
||||||
|
seo_title_fr text,
|
||||||
seo_description text,
|
seo_description text,
|
||||||
|
seo_description_it text,
|
||||||
|
seo_description_en text,
|
||||||
|
seo_description_de text,
|
||||||
|
seo_description_fr text,
|
||||||
og_title text,
|
og_title text,
|
||||||
og_description text,
|
og_description text,
|
||||||
indexable boolean NOT NULL DEFAULT true,
|
indexable boolean NOT NULL DEFAULT true,
|
||||||
@@ -1034,6 +1070,66 @@ CREATE INDEX IF NOT EXISTS ix_shop_category_parent_sort
|
|||||||
CREATE INDEX IF NOT EXISTS ix_shop_category_active_sort
|
CREATE INDEX IF NOT EXISTS ix_shop_category_active_sort
|
||||||
ON shop_category (is_active, sort_order, created_at DESC);
|
ON shop_category (is_active, sort_order, created_at DESC);
|
||||||
|
|
||||||
|
ALTER TABLE shop_category
|
||||||
|
ADD COLUMN IF NOT EXISTS name_it text,
|
||||||
|
ADD COLUMN IF NOT EXISTS name_en text,
|
||||||
|
ADD COLUMN IF NOT EXISTS name_de text,
|
||||||
|
ADD COLUMN IF NOT EXISTS name_fr text,
|
||||||
|
ADD COLUMN IF NOT EXISTS description_it text,
|
||||||
|
ADD COLUMN IF NOT EXISTS description_en text,
|
||||||
|
ADD COLUMN IF NOT EXISTS description_de text,
|
||||||
|
ADD COLUMN IF NOT EXISTS description_fr text,
|
||||||
|
ADD COLUMN IF NOT EXISTS seo_title_it text,
|
||||||
|
ADD COLUMN IF NOT EXISTS seo_title_en text,
|
||||||
|
ADD COLUMN IF NOT EXISTS seo_title_de text,
|
||||||
|
ADD COLUMN IF NOT EXISTS seo_title_fr text,
|
||||||
|
ADD COLUMN IF NOT EXISTS seo_description_it text,
|
||||||
|
ADD COLUMN IF NOT EXISTS seo_description_en text,
|
||||||
|
ADD COLUMN IF NOT EXISTS seo_description_de text,
|
||||||
|
ADD COLUMN IF NOT EXISTS seo_description_fr text;
|
||||||
|
|
||||||
|
UPDATE shop_category
|
||||||
|
SET
|
||||||
|
name_it = COALESCE(NULLIF(btrim(name_it), ''), name),
|
||||||
|
name_en = COALESCE(NULLIF(btrim(name_en), ''), name),
|
||||||
|
name_de = COALESCE(NULLIF(btrim(name_de), ''), name),
|
||||||
|
name_fr = COALESCE(NULLIF(btrim(name_fr), ''), name),
|
||||||
|
description_it = COALESCE(NULLIF(btrim(description_it), ''), description),
|
||||||
|
description_en = COALESCE(NULLIF(btrim(description_en), ''), description),
|
||||||
|
description_de = COALESCE(NULLIF(btrim(description_de), ''), description),
|
||||||
|
description_fr = COALESCE(NULLIF(btrim(description_fr), ''), description),
|
||||||
|
seo_title_it = COALESCE(NULLIF(btrim(seo_title_it), ''), seo_title),
|
||||||
|
seo_title_en = COALESCE(NULLIF(btrim(seo_title_en), ''), seo_title),
|
||||||
|
seo_title_de = COALESCE(NULLIF(btrim(seo_title_de), ''), seo_title),
|
||||||
|
seo_title_fr = COALESCE(NULLIF(btrim(seo_title_fr), ''), seo_title),
|
||||||
|
seo_description_it = COALESCE(NULLIF(btrim(seo_description_it), ''), seo_description),
|
||||||
|
seo_description_en = COALESCE(NULLIF(btrim(seo_description_en), ''), seo_description),
|
||||||
|
seo_description_de = COALESCE(NULLIF(btrim(seo_description_de), ''), seo_description),
|
||||||
|
seo_description_fr = COALESCE(NULLIF(btrim(seo_description_fr), ''), seo_description)
|
||||||
|
WHERE
|
||||||
|
NULLIF(btrim(name_it), '') IS NULL
|
||||||
|
OR NULLIF(btrim(name_en), '') IS NULL
|
||||||
|
OR NULLIF(btrim(name_de), '') IS NULL
|
||||||
|
OR NULLIF(btrim(name_fr), '') IS NULL
|
||||||
|
OR (description IS NOT NULL AND (
|
||||||
|
NULLIF(btrim(description_it), '') IS NULL
|
||||||
|
OR NULLIF(btrim(description_en), '') IS NULL
|
||||||
|
OR NULLIF(btrim(description_de), '') IS NULL
|
||||||
|
OR NULLIF(btrim(description_fr), '') IS NULL
|
||||||
|
))
|
||||||
|
OR (seo_title IS NOT NULL AND (
|
||||||
|
NULLIF(btrim(seo_title_it), '') IS NULL
|
||||||
|
OR NULLIF(btrim(seo_title_en), '') IS NULL
|
||||||
|
OR NULLIF(btrim(seo_title_de), '') IS NULL
|
||||||
|
OR NULLIF(btrim(seo_title_fr), '') IS NULL
|
||||||
|
))
|
||||||
|
OR (seo_description IS NOT NULL AND (
|
||||||
|
NULLIF(btrim(seo_description_it), '') IS NULL
|
||||||
|
OR NULLIF(btrim(seo_description_en), '') IS NULL
|
||||||
|
OR NULLIF(btrim(seo_description_de), '') IS NULL
|
||||||
|
OR NULLIF(btrim(seo_description_fr), '') IS NULL
|
||||||
|
));
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS shop_product
|
CREATE TABLE IF NOT EXISTS shop_product
|
||||||
(
|
(
|
||||||
shop_product_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
shop_product_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
@@ -1165,6 +1261,10 @@ CREATE TABLE IF NOT EXISTS shop_product_variant
|
|||||||
sku text UNIQUE,
|
sku text UNIQUE,
|
||||||
variant_label text NOT NULL,
|
variant_label text NOT NULL,
|
||||||
color_name text NOT NULL,
|
color_name text NOT NULL,
|
||||||
|
color_label_it text,
|
||||||
|
color_label_en text,
|
||||||
|
color_label_de text,
|
||||||
|
color_label_fr text,
|
||||||
color_hex text,
|
color_hex text,
|
||||||
internal_material_code text NOT NULL,
|
internal_material_code text NOT NULL,
|
||||||
price_chf numeric(12, 2) NOT NULL DEFAULT 0.00 CHECK (price_chf >= 0),
|
price_chf numeric(12, 2) NOT NULL DEFAULT 0.00 CHECK (price_chf >= 0),
|
||||||
@@ -1181,6 +1281,22 @@ CREATE INDEX IF NOT EXISTS ix_shop_product_variant_product_active_sort
|
|||||||
CREATE INDEX IF NOT EXISTS ix_shop_product_variant_sku
|
CREATE INDEX IF NOT EXISTS ix_shop_product_variant_sku
|
||||||
ON shop_product_variant (sku);
|
ON shop_product_variant (sku);
|
||||||
|
|
||||||
|
ALTER TABLE shop_product_variant
|
||||||
|
ADD COLUMN IF NOT EXISTS color_label_it text,
|
||||||
|
ADD COLUMN IF NOT EXISTS color_label_en text,
|
||||||
|
ADD COLUMN IF NOT EXISTS color_label_de text,
|
||||||
|
ADD COLUMN IF NOT EXISTS color_label_fr text;
|
||||||
|
|
||||||
|
UPDATE shop_product_variant
|
||||||
|
SET color_label_it = COALESCE(NULLIF(btrim(color_label_it), ''), color_name),
|
||||||
|
color_label_en = COALESCE(NULLIF(btrim(color_label_en), ''), color_name),
|
||||||
|
color_label_de = COALESCE(NULLIF(btrim(color_label_de), ''), color_name),
|
||||||
|
color_label_fr = COALESCE(NULLIF(btrim(color_label_fr), ''), color_name)
|
||||||
|
WHERE NULLIF(btrim(color_label_it), '') IS NULL
|
||||||
|
OR NULLIF(btrim(color_label_en), '') IS NULL
|
||||||
|
OR NULLIF(btrim(color_label_de), '') IS NULL
|
||||||
|
OR NULLIF(btrim(color_label_fr), '') IS NULL;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS shop_product_model_asset
|
CREATE TABLE IF NOT EXISTS shop_product_model_asset
|
||||||
(
|
(
|
||||||
shop_product_model_asset_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
shop_product_model_asset_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
|||||||
@@ -40,93 +40,6 @@ export const PRODUCT_COLORS: ColorCategory[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const COLOR_HEX_BY_TRANSLATION_KEY: Record<string, string> = {
|
|
||||||
...Object.fromEntries(
|
|
||||||
PRODUCT_COLORS.flatMap((category) =>
|
|
||||||
category.colors.map((color) => [color.label, color.hex] as const),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
'COLOR.NAME.ORANGE': '#f5a623',
|
|
||||||
'COLOR.NAME.GRAY': '#b7b7b7',
|
|
||||||
'COLOR.NAME.LIGHT_GRAY': '#d8dadd',
|
|
||||||
'COLOR.NAME.DARK_GRAY': '#4f4f4f',
|
|
||||||
'COLOR.NAME.PURPLE': '#7b1fa2',
|
|
||||||
'COLOR.NAME.BEIGE': '#d4c09a',
|
|
||||||
'COLOR.NAME.SAND_BEIGE': '#d7c2a0',
|
|
||||||
};
|
|
||||||
|
|
||||||
const COLOR_TRANSLATION_KEY_BY_VALUE: Record<string, string> = {
|
|
||||||
black: 'COLOR.NAME.BLACK',
|
|
||||||
nero: 'COLOR.NAME.BLACK',
|
|
||||||
noir: 'COLOR.NAME.BLACK',
|
|
||||||
schwarz: 'COLOR.NAME.BLACK',
|
|
||||||
white: 'COLOR.NAME.WHITE',
|
|
||||||
bianco: 'COLOR.NAME.WHITE',
|
|
||||||
blanc: 'COLOR.NAME.WHITE',
|
|
||||||
weiss: 'COLOR.NAME.WHITE',
|
|
||||||
red: 'COLOR.NAME.RED',
|
|
||||||
rosso: 'COLOR.NAME.RED',
|
|
||||||
rouge: 'COLOR.NAME.RED',
|
|
||||||
rot: 'COLOR.NAME.RED',
|
|
||||||
blue: 'COLOR.NAME.BLUE',
|
|
||||||
blu: 'COLOR.NAME.BLUE',
|
|
||||||
bleu: 'COLOR.NAME.BLUE',
|
|
||||||
blau: 'COLOR.NAME.BLUE',
|
|
||||||
green: 'COLOR.NAME.GREEN',
|
|
||||||
verde: 'COLOR.NAME.GREEN',
|
|
||||||
vert: 'COLOR.NAME.GREEN',
|
|
||||||
grun: 'COLOR.NAME.GREEN',
|
|
||||||
yellow: 'COLOR.NAME.YELLOW',
|
|
||||||
giallo: 'COLOR.NAME.YELLOW',
|
|
||||||
jaune: 'COLOR.NAME.YELLOW',
|
|
||||||
gelb: 'COLOR.NAME.YELLOW',
|
|
||||||
orange: 'COLOR.NAME.ORANGE',
|
|
||||||
arancione: 'COLOR.NAME.ORANGE',
|
|
||||||
naranja: 'COLOR.NAME.ORANGE',
|
|
||||||
gris: 'COLOR.NAME.GRAY',
|
|
||||||
gray: 'COLOR.NAME.GRAY',
|
|
||||||
grey: 'COLOR.NAME.GRAY',
|
|
||||||
grigio: 'COLOR.NAME.GRAY',
|
|
||||||
grau: 'COLOR.NAME.GRAY',
|
|
||||||
'light gray': 'COLOR.NAME.LIGHT_GRAY',
|
|
||||||
'light grey': 'COLOR.NAME.LIGHT_GRAY',
|
|
||||||
'grigio chiaro': 'COLOR.NAME.LIGHT_GRAY',
|
|
||||||
'gris clair': 'COLOR.NAME.LIGHT_GRAY',
|
|
||||||
hellgrau: 'COLOR.NAME.LIGHT_GRAY',
|
|
||||||
'dark gray': 'COLOR.NAME.DARK_GRAY',
|
|
||||||
'dark grey': 'COLOR.NAME.DARK_GRAY',
|
|
||||||
'grigio scuro': 'COLOR.NAME.DARK_GRAY',
|
|
||||||
'gris fonce': 'COLOR.NAME.DARK_GRAY',
|
|
||||||
dunkelgrau: 'COLOR.NAME.DARK_GRAY',
|
|
||||||
purple: 'COLOR.NAME.PURPLE',
|
|
||||||
violet: 'COLOR.NAME.PURPLE',
|
|
||||||
viola: 'COLOR.NAME.PURPLE',
|
|
||||||
lila: 'COLOR.NAME.PURPLE',
|
|
||||||
beige: 'COLOR.NAME.BEIGE',
|
|
||||||
'sand beige': 'COLOR.NAME.SAND_BEIGE',
|
|
||||||
'beige sabbia': 'COLOR.NAME.SAND_BEIGE',
|
|
||||||
'beige sable': 'COLOR.NAME.SAND_BEIGE',
|
|
||||||
sandbeige: 'COLOR.NAME.SAND_BEIGE',
|
|
||||||
'matte black': 'COLOR.NAME.MATTE_BLACK',
|
|
||||||
'black matte': 'COLOR.NAME.MATTE_BLACK',
|
|
||||||
'nero opaco': 'COLOR.NAME.MATTE_BLACK',
|
|
||||||
'noir mat': 'COLOR.NAME.MATTE_BLACK',
|
|
||||||
'matt schwarz': 'COLOR.NAME.MATTE_BLACK',
|
|
||||||
'schwarz matt': 'COLOR.NAME.MATTE_BLACK',
|
|
||||||
'matte white': 'COLOR.NAME.MATTE_WHITE',
|
|
||||||
'white matte': 'COLOR.NAME.MATTE_WHITE',
|
|
||||||
'bianco opaco': 'COLOR.NAME.MATTE_WHITE',
|
|
||||||
'blanc mat': 'COLOR.NAME.MATTE_WHITE',
|
|
||||||
'matt weiss': 'COLOR.NAME.MATTE_WHITE',
|
|
||||||
'weiss matt': 'COLOR.NAME.MATTE_WHITE',
|
|
||||||
'matte gray': 'COLOR.NAME.MATTE_GRAY',
|
|
||||||
'matte grey': 'COLOR.NAME.MATTE_GRAY',
|
|
||||||
'grigio opaco': 'COLOR.NAME.MATTE_GRAY',
|
|
||||||
'gris mat': 'COLOR.NAME.MATTE_GRAY',
|
|
||||||
'matt grau': 'COLOR.NAME.MATTE_GRAY',
|
|
||||||
'grau matt': 'COLOR.NAME.MATTE_GRAY',
|
|
||||||
};
|
|
||||||
|
|
||||||
export function normalizeColorValue(value: string | null | undefined): string {
|
export function normalizeColorValue(value: string | null | undefined): string {
|
||||||
return String(value ?? '')
|
return String(value ?? '')
|
||||||
.trim()
|
.trim()
|
||||||
@@ -138,30 +51,7 @@ export function normalizeColorValue(value: string | null | undefined): string {
|
|||||||
.replace(/\s+/g, ' ');
|
.replace(/\s+/g, ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getColorTranslationKey(
|
|
||||||
value: string | null | undefined,
|
|
||||||
): string | null {
|
|
||||||
const normalized = normalizeColorValue(value);
|
|
||||||
return normalized ? COLOR_TRANSLATION_KEY_BY_VALUE[normalized] ?? null : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getColorLabelToken(
|
|
||||||
value: string | null | undefined,
|
|
||||||
): string | null {
|
|
||||||
const raw = String(value ?? '').trim();
|
|
||||||
if (!raw) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getColorTranslationKey(raw) ?? raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findColorHex(value: string | null | undefined): string | null {
|
export function findColorHex(value: string | null | undefined): string | null {
|
||||||
const translationKey = getColorTranslationKey(value);
|
|
||||||
if (translationKey) {
|
|
||||||
return COLOR_HEX_BY_TRANSLATION_KEY[translationKey] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalized = normalizeColorValue(value);
|
const normalized = normalizeColorValue(value);
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
return null;
|
return null;
|
||||||
@@ -179,6 +69,52 @@ export function findColorHex(value: string | null | undefined): string | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LocalizedColorLabelSet {
|
||||||
|
fallback?: string | null;
|
||||||
|
it?: string | null;
|
||||||
|
en?: string | null;
|
||||||
|
de?: string | null;
|
||||||
|
fr?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveLocalizedColorLabel(
|
||||||
|
language: string | null | undefined,
|
||||||
|
labels: LocalizedColorLabelSet,
|
||||||
|
): string | null {
|
||||||
|
const normalizedLanguage = String(language ?? '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.split('-')[0];
|
||||||
|
|
||||||
|
const preferred =
|
||||||
|
normalizedLanguage === 'it'
|
||||||
|
? labels.it
|
||||||
|
: normalizedLanguage === 'en'
|
||||||
|
? labels.en
|
||||||
|
: normalizedLanguage === 'de'
|
||||||
|
? labels.de
|
||||||
|
: normalizedLanguage === 'fr'
|
||||||
|
? labels.fr
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
firstNonBlank(preferred, labels.fallback) ??
|
||||||
|
firstNonBlank(labels.it, labels.en, labels.de, labels.fr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function firstNonBlank(
|
||||||
|
...values: Array<string | null | undefined>
|
||||||
|
): string | null {
|
||||||
|
for (const value of values) {
|
||||||
|
const normalized = String(value ?? '').trim();
|
||||||
|
if (normalized) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export function getColorHex(value: string): string {
|
export function getColorHex(value: string): string {
|
||||||
return findColorHex(value) ?? DEFAULT_BRAND_COLOR;
|
return findColorHex(value) ?? DEFAULT_BRAND_COLOR;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
import { finalize } from 'rxjs';
|
import { finalize } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
findColorHex,
|
findColorHex,
|
||||||
getColorLabelToken,
|
resolveLocalizedColorLabel,
|
||||||
} from '../constants/colors.const';
|
} from '../constants/colors.const';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -147,15 +147,20 @@ export class NavbarComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cartItemVariant(item: ShopCartItem): string | null {
|
cartItemVariant(item: ShopCartItem): string | null {
|
||||||
return (
|
return item.shopVariantLabel || this.cartItemColor(item);
|
||||||
item.shopVariantLabel || getColorLabelToken(item.shopVariantColorName)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cartItemColor(item: ShopCartItem): string | null {
|
cartItemColor(item: ShopCartItem): string | null {
|
||||||
return (
|
return (
|
||||||
getColorLabelToken(item.shopVariantColorName) ??
|
resolveLocalizedColorLabel(this.langService.selectedLang(), {
|
||||||
getColorLabelToken(item.colorCode)
|
fallback: item.shopVariantColorName ?? item.colorCode,
|
||||||
|
it: item.shopVariantColorLabelIt,
|
||||||
|
en: item.shopVariantColorLabelEn,
|
||||||
|
de: item.shopVariantColorLabelDe,
|
||||||
|
fr: item.shopVariantColorLabelFr,
|
||||||
|
}) ??
|
||||||
|
item.shopVariantColorName ??
|
||||||
|
item.colorCode
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,22 @@
|
|||||||
placeholder="Nero, Bianco..."
|
placeholder="Nero, Bianco..."
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="form-field">
|
||||||
|
<span>Label IT</span>
|
||||||
|
<input type="text" [(ngModel)]="newVariant.colorLabelIt" />
|
||||||
|
</label>
|
||||||
|
<label class="form-field">
|
||||||
|
<span>Label EN</span>
|
||||||
|
<input type="text" [(ngModel)]="newVariant.colorLabelEn" />
|
||||||
|
</label>
|
||||||
|
<label class="form-field">
|
||||||
|
<span>Label DE</span>
|
||||||
|
<input type="text" [(ngModel)]="newVariant.colorLabelDe" />
|
||||||
|
</label>
|
||||||
|
<label class="form-field">
|
||||||
|
<span>Label FR</span>
|
||||||
|
<input type="text" [(ngModel)]="newVariant.colorLabelFr" />
|
||||||
|
</label>
|
||||||
<label class="form-field">
|
<label class="form-field">
|
||||||
<span>Hex colore</span>
|
<span>Hex colore</span>
|
||||||
<input
|
<input
|
||||||
@@ -229,7 +245,7 @@
|
|||||||
class="color-dot"
|
class="color-dot"
|
||||||
[style.background-color]="getVariantColorHex(variant)"
|
[style.background-color]="getVariantColorHex(variant)"
|
||||||
></span>
|
></span>
|
||||||
{{ variant.colorName || "N/D" }}
|
{{ variant.colorLabelIt || variant.colorName || "N/D" }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
>Stock spools:
|
>Stock spools:
|
||||||
@@ -290,6 +306,22 @@
|
|||||||
<span>Colore</span>
|
<span>Colore</span>
|
||||||
<input type="text" [(ngModel)]="variant.colorName" />
|
<input type="text" [(ngModel)]="variant.colorName" />
|
||||||
</label>
|
</label>
|
||||||
|
<label class="form-field">
|
||||||
|
<span>Label IT</span>
|
||||||
|
<input type="text" [(ngModel)]="variant.colorLabelIt" />
|
||||||
|
</label>
|
||||||
|
<label class="form-field">
|
||||||
|
<span>Label EN</span>
|
||||||
|
<input type="text" [(ngModel)]="variant.colorLabelEn" />
|
||||||
|
</label>
|
||||||
|
<label class="form-field">
|
||||||
|
<span>Label DE</span>
|
||||||
|
<input type="text" [(ngModel)]="variant.colorLabelDe" />
|
||||||
|
</label>
|
||||||
|
<label class="form-field">
|
||||||
|
<span>Label FR</span>
|
||||||
|
<input type="text" [(ngModel)]="variant.colorLabelFr" />
|
||||||
|
</label>
|
||||||
<label class="form-field">
|
<label class="form-field">
|
||||||
<span>Hex colore</span>
|
<span>Hex colore</span>
|
||||||
<input type="text" [(ngModel)]="variant.colorHex" />
|
<input type="text" [(ngModel)]="variant.colorHex" />
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export class AdminFilamentStockComponent implements OnInit {
|
|||||||
materialTypeId: 0,
|
materialTypeId: 0,
|
||||||
variantDisplayName: '',
|
variantDisplayName: '',
|
||||||
colorName: '',
|
colorName: '',
|
||||||
|
colorLabelIt: '',
|
||||||
|
colorLabelEn: '',
|
||||||
|
colorLabelDe: '',
|
||||||
|
colorLabelFr: '',
|
||||||
colorHex: '',
|
colorHex: '',
|
||||||
finishType: 'GLOSSY',
|
finishType: 'GLOSSY',
|
||||||
brand: '',
|
brand: '',
|
||||||
@@ -206,6 +210,10 @@ export class AdminFilamentStockComponent implements OnInit {
|
|||||||
this.newVariant.materialTypeId || this.materials[0]?.id || 0,
|
this.newVariant.materialTypeId || this.materials[0]?.id || 0,
|
||||||
variantDisplayName: '',
|
variantDisplayName: '',
|
||||||
colorName: '',
|
colorName: '',
|
||||||
|
colorLabelIt: '',
|
||||||
|
colorLabelEn: '',
|
||||||
|
colorLabelDe: '',
|
||||||
|
colorLabelFr: '',
|
||||||
colorHex: '',
|
colorHex: '',
|
||||||
finishType: 'GLOSSY',
|
finishType: 'GLOSSY',
|
||||||
brand: '',
|
brand: '',
|
||||||
@@ -359,6 +367,10 @@ export class AdminFilamentStockComponent implements OnInit {
|
|||||||
materialTypeId: Number(source.materialTypeId),
|
materialTypeId: Number(source.materialTypeId),
|
||||||
variantDisplayName: (source.variantDisplayName || '').trim(),
|
variantDisplayName: (source.variantDisplayName || '').trim(),
|
||||||
colorName: (source.colorName || '').trim(),
|
colorName: (source.colorName || '').trim(),
|
||||||
|
colorLabelIt: (source.colorLabelIt || '').trim() || undefined,
|
||||||
|
colorLabelEn: (source.colorLabelEn || '').trim() || undefined,
|
||||||
|
colorLabelDe: (source.colorLabelDe || '').trim() || undefined,
|
||||||
|
colorLabelFr: (source.colorLabelFr || '').trim() || undefined,
|
||||||
colorHex: (source.colorHex || '').trim() || undefined,
|
colorHex: (source.colorHex || '').trim() || undefined,
|
||||||
finishType: (source.finishType || 'GLOSSY').trim().toUpperCase(),
|
finishType: (source.finishType || 'GLOSSY').trim().toUpperCase(),
|
||||||
brand: (source.brand || '').trim() || undefined,
|
brand: (source.brand || '').trim() || undefined,
|
||||||
|
|||||||
@@ -206,27 +206,16 @@
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="ui-form-field">
|
|
||||||
<span class="ui-form-caption">Nome categoria</span>
|
|
||||||
<input
|
|
||||||
class="ui-form-control"
|
|
||||||
type="text"
|
|
||||||
[(ngModel)]="categoryForm.name"
|
|
||||||
name="categoryName"
|
|
||||||
placeholder="Desk accessories"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="ui-form-field">
|
<label class="ui-form-field">
|
||||||
<span class="ui-form-caption">Slug</span>
|
<span class="ui-form-caption">Slug</span>
|
||||||
<div class="input-with-action">
|
<div class="input-with-action">
|
||||||
<input
|
<input
|
||||||
class="ui-form-control"
|
class="ui-form-control"
|
||||||
type="text"
|
type="text"
|
||||||
[(ngModel)]="categoryForm.slug"
|
[(ngModel)]="categoryForm.slug"
|
||||||
name="categorySlug"
|
name="categorySlug"
|
||||||
placeholder="desk-accessories"
|
placeholder="desk-accessories"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="ui-button ui-button--ghost"
|
class="ui-button ui-button--ghost"
|
||||||
@@ -237,36 +226,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="ui-form-field form-field--wide">
|
|
||||||
<span class="ui-form-caption">Descrizione</span>
|
|
||||||
<textarea
|
|
||||||
class="ui-form-control textarea-control"
|
|
||||||
[(ngModel)]="categoryForm.description"
|
|
||||||
name="categoryDescription"
|
|
||||||
rows="3"
|
|
||||||
></textarea>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="ui-form-field">
|
|
||||||
<span class="ui-form-caption">SEO title</span>
|
|
||||||
<input
|
|
||||||
class="ui-form-control"
|
|
||||||
type="text"
|
|
||||||
[(ngModel)]="categoryForm.seoTitle"
|
|
||||||
name="categorySeoTitle"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="ui-form-field">
|
|
||||||
<span class="ui-form-caption">SEO description</span>
|
|
||||||
<input
|
|
||||||
class="ui-form-control"
|
|
||||||
type="text"
|
|
||||||
[(ngModel)]="categoryForm.seoDescription"
|
|
||||||
name="categorySeoDescription"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="ui-form-field">
|
<label class="ui-form-field">
|
||||||
<span class="ui-form-caption">OG title</span>
|
<span class="ui-form-caption">OG title</span>
|
||||||
<input
|
<input
|
||||||
@@ -288,6 +247,141 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="ui-language-toolbar">
|
||||||
|
<div class="ui-language-toolbar__copy">
|
||||||
|
<span>Lingua contenuti categoria</span>
|
||||||
|
<p>IT / EN / DE / FR</p>
|
||||||
|
</div>
|
||||||
|
<div class="ui-language-toolbar__toggle">
|
||||||
|
<button
|
||||||
|
*ngFor="let language of shopLanguages"
|
||||||
|
type="button"
|
||||||
|
class="ui-language-toolbar__button image-language-button"
|
||||||
|
[class.active]="activeContentLanguage === language"
|
||||||
|
[class.complete]="isCategoryContentLanguageComplete(language)"
|
||||||
|
[class.incomplete]="
|
||||||
|
isCategoryContentLanguageIncomplete(language)
|
||||||
|
"
|
||||||
|
[class.empty]="!isCategoryContentLanguageStarted(language)"
|
||||||
|
(click)="setActiveContentLanguage(language)"
|
||||||
|
>
|
||||||
|
<span class="image-language-button__label">
|
||||||
|
{{ languageLabels[language] }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="image-language-button__state"
|
||||||
|
*ngIf="isCategoryContentLanguageComplete(language)"
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="image-language-button__state"
|
||||||
|
*ngIf="isCategoryContentLanguageIncomplete(language)"
|
||||||
|
>
|
||||||
|
...
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui-form-grid ui-form-grid--two">
|
||||||
|
<label class="ui-form-field">
|
||||||
|
<span class="ui-form-caption">
|
||||||
|
Nome categoria {{ languageLabels[activeContentLanguage] }}
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
class="ui-form-control"
|
||||||
|
type="text"
|
||||||
|
[(ngModel)]="categoryForm.names[activeContentLanguage]"
|
||||||
|
[name]="'category-name-' + activeContentLanguage"
|
||||||
|
placeholder="Desk accessories"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="ui-form-field form-field--wide">
|
||||||
|
<span class="ui-form-caption">
|
||||||
|
Descrizione {{ languageLabels[activeContentLanguage] }}
|
||||||
|
</span>
|
||||||
|
<textarea
|
||||||
|
class="ui-form-control textarea-control"
|
||||||
|
[(ngModel)]="categoryForm.descriptions[activeContentLanguage]"
|
||||||
|
[name]="'category-description-' + activeContentLanguage"
|
||||||
|
rows="3"
|
||||||
|
></textarea>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui-language-toolbar">
|
||||||
|
<div class="ui-language-toolbar__copy">
|
||||||
|
<span>Lingua SEO categoria</span>
|
||||||
|
<p>Stessa lingua attiva dell'editor</p>
|
||||||
|
</div>
|
||||||
|
<div class="ui-language-toolbar__toggle">
|
||||||
|
<button
|
||||||
|
*ngFor="let language of shopLanguages"
|
||||||
|
type="button"
|
||||||
|
class="ui-language-toolbar__button image-language-button"
|
||||||
|
[class.active]="activeContentLanguage === language"
|
||||||
|
[class.complete]="isCategorySeoLanguageComplete(language)"
|
||||||
|
[class.incomplete]="isCategorySeoLanguageIncomplete(language)"
|
||||||
|
[class.empty]="!isCategorySeoLanguageStarted(language)"
|
||||||
|
(click)="setActiveContentLanguage(language)"
|
||||||
|
>
|
||||||
|
<span class="image-language-button__label">
|
||||||
|
{{ languageLabels[language] }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="image-language-button__state"
|
||||||
|
*ngIf="isCategorySeoLanguageComplete(language)"
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="image-language-button__state"
|
||||||
|
*ngIf="isCategorySeoLanguageIncomplete(language)"
|
||||||
|
>
|
||||||
|
...
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui-form-grid ui-form-grid--two">
|
||||||
|
<label class="ui-form-field">
|
||||||
|
<span class="ui-form-caption">
|
||||||
|
SEO title {{ languageLabels[activeContentLanguage] }}
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
class="ui-form-control"
|
||||||
|
type="text"
|
||||||
|
[(ngModel)]="categoryForm.seoTitles[activeContentLanguage]"
|
||||||
|
[name]="'category-seo-title-' + activeContentLanguage"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="ui-form-field form-field--wide">
|
||||||
|
<span class="ui-form-caption">
|
||||||
|
SEO description {{ languageLabels[activeContentLanguage] }}
|
||||||
|
</span>
|
||||||
|
<textarea
|
||||||
|
class="ui-form-control"
|
||||||
|
[(ngModel)]="
|
||||||
|
categoryForm.seoDescriptions[activeContentLanguage]
|
||||||
|
"
|
||||||
|
[name]="'category-seo-description-' + activeContentLanguage"
|
||||||
|
rows="3"
|
||||||
|
></textarea>
|
||||||
|
<span
|
||||||
|
class="seo-counter"
|
||||||
|
[class.seo-counter--danger]="
|
||||||
|
categorySeoDescriptionLength(activeContentLanguage) > 160
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ categorySeoDescriptionLength(activeContentLanguage) }}/160
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="toggle-row">
|
<div class="toggle-row">
|
||||||
<label class="ui-checkbox">
|
<label class="ui-checkbox">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ interface CategoryFormState {
|
|||||||
id: string | null;
|
id: string | null;
|
||||||
parentCategoryId: string | null;
|
parentCategoryId: string | null;
|
||||||
slug: string;
|
slug: string;
|
||||||
name: string;
|
names: Record<ShopLanguage, string>;
|
||||||
description: string;
|
descriptions: Record<ShopLanguage, string>;
|
||||||
seoTitle: string;
|
seoTitles: Record<ShopLanguage, string>;
|
||||||
seoDescription: string;
|
seoDescriptions: Record<ShopLanguage, string>;
|
||||||
ogTitle: string;
|
ogTitle: string;
|
||||||
ogDescription: string;
|
ogDescription: string;
|
||||||
indexable: boolean;
|
indexable: boolean;
|
||||||
@@ -554,7 +554,10 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
slugifyCategoryFromName(): void {
|
slugifyCategoryFromName(): void {
|
||||||
this.categoryForm.slug = this.slugify(this.categoryForm.name);
|
const source =
|
||||||
|
this.categoryForm.names[this.activeContentLanguage] ||
|
||||||
|
this.categoryForm.names['it'];
|
||||||
|
this.categoryForm.slug = this.slugify(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveContentLanguage(language: ShopLanguage): void {
|
setActiveContentLanguage(language: ShopLanguage): void {
|
||||||
@@ -603,6 +606,45 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isCategoryContentLanguageComplete(language: ShopLanguage): boolean {
|
||||||
|
return !!this.categoryForm.names[language].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
isCategoryContentLanguageStarted(language: ShopLanguage): boolean {
|
||||||
|
return (
|
||||||
|
!!this.categoryForm.names[language].trim() ||
|
||||||
|
!!this.categoryForm.descriptions[language].trim()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isCategoryContentLanguageIncomplete(language: ShopLanguage): boolean {
|
||||||
|
return (
|
||||||
|
this.isCategoryContentLanguageStarted(language) &&
|
||||||
|
!this.isCategoryContentLanguageComplete(language)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isCategorySeoLanguageComplete(language: ShopLanguage): boolean {
|
||||||
|
return (
|
||||||
|
!!this.categoryForm.seoTitles[language].trim() &&
|
||||||
|
!!this.categoryForm.seoDescriptions[language].trim()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isCategorySeoLanguageStarted(language: ShopLanguage): boolean {
|
||||||
|
return (
|
||||||
|
!!this.categoryForm.seoTitles[language].trim() ||
|
||||||
|
!!this.categoryForm.seoDescriptions[language].trim()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isCategorySeoLanguageIncomplete(language: ShopLanguage): boolean {
|
||||||
|
return (
|
||||||
|
this.isCategorySeoLanguageStarted(language) &&
|
||||||
|
!this.isCategorySeoLanguageComplete(language)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
preventRichTextToolbarMouseDown(event: MouseEvent): void {
|
preventRichTextToolbarMouseDown(event: MouseEvent): void {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
@@ -1228,10 +1270,10 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
|||||||
id: null,
|
id: null,
|
||||||
parentCategoryId: null,
|
parentCategoryId: null,
|
||||||
slug: '',
|
slug: '',
|
||||||
name: '',
|
names: this.createEmptyLocalizedTextRecord(),
|
||||||
description: '',
|
descriptions: this.createEmptyLocalizedTextRecord(),
|
||||||
seoTitle: '',
|
seoTitles: this.createEmptyLocalizedTextRecord(),
|
||||||
seoDescription: '',
|
seoDescriptions: this.createEmptyLocalizedTextRecord(),
|
||||||
ogTitle: '',
|
ogTitle: '',
|
||||||
ogDescription: '',
|
ogDescription: '',
|
||||||
indexable: true,
|
indexable: true,
|
||||||
@@ -1241,6 +1283,7 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private resetCategoryForm(): void {
|
private resetCategoryForm(): void {
|
||||||
|
this.activeContentLanguage = 'it';
|
||||||
Object.assign(this.categoryForm, this.createEmptyCategoryForm());
|
Object.assign(this.categoryForm, this.createEmptyCategoryForm());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1249,10 +1292,30 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
|||||||
id: category.id,
|
id: category.id,
|
||||||
parentCategoryId: category.parentCategoryId,
|
parentCategoryId: category.parentCategoryId,
|
||||||
slug: category.slug ?? '',
|
slug: category.slug ?? '',
|
||||||
name: category.name ?? '',
|
names: {
|
||||||
description: category.description ?? '',
|
it: category.nameIt ?? category.name ?? '',
|
||||||
seoTitle: category.seoTitle ?? '',
|
en: category.nameEn ?? category.name ?? '',
|
||||||
seoDescription: category.seoDescription ?? '',
|
de: category.nameDe ?? category.name ?? '',
|
||||||
|
fr: category.nameFr ?? category.name ?? '',
|
||||||
|
},
|
||||||
|
descriptions: {
|
||||||
|
it: category.descriptionIt ?? category.description ?? '',
|
||||||
|
en: category.descriptionEn ?? category.description ?? '',
|
||||||
|
de: category.descriptionDe ?? category.description ?? '',
|
||||||
|
fr: category.descriptionFr ?? category.description ?? '',
|
||||||
|
},
|
||||||
|
seoTitles: {
|
||||||
|
it: category.seoTitleIt ?? category.seoTitle ?? '',
|
||||||
|
en: category.seoTitleEn ?? category.seoTitle ?? '',
|
||||||
|
de: category.seoTitleDe ?? category.seoTitle ?? '',
|
||||||
|
fr: category.seoTitleFr ?? category.seoTitle ?? '',
|
||||||
|
},
|
||||||
|
seoDescriptions: {
|
||||||
|
it: category.seoDescriptionIt ?? category.seoDescription ?? '',
|
||||||
|
en: category.seoDescriptionEn ?? category.seoDescription ?? '',
|
||||||
|
de: category.seoDescriptionDe ?? category.seoDescription ?? '',
|
||||||
|
fr: category.seoDescriptionFr ?? category.seoDescription ?? '',
|
||||||
|
},
|
||||||
ogTitle: category.ogTitle ?? '',
|
ogTitle: category.ogTitle ?? '',
|
||||||
ogDescription: category.ogDescription ?? '',
|
ogDescription: category.ogDescription ?? '',
|
||||||
indexable: category.indexable,
|
indexable: category.indexable,
|
||||||
@@ -1265,10 +1328,34 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
|||||||
return {
|
return {
|
||||||
parentCategoryId: this.categoryForm.parentCategoryId || null,
|
parentCategoryId: this.categoryForm.parentCategoryId || null,
|
||||||
slug: this.categoryForm.slug.trim(),
|
slug: this.categoryForm.slug.trim(),
|
||||||
name: this.categoryForm.name.trim(),
|
name: this.categoryForm.names['it'].trim(),
|
||||||
description: this.categoryForm.description.trim(),
|
nameIt: this.categoryForm.names['it'].trim(),
|
||||||
seoTitle: this.categoryForm.seoTitle.trim(),
|
nameEn: this.categoryForm.names['en'].trim(),
|
||||||
seoDescription: this.categoryForm.seoDescription.trim(),
|
nameDe: this.categoryForm.names['de'].trim(),
|
||||||
|
nameFr: this.categoryForm.names['fr'].trim(),
|
||||||
|
description: this.optionalValue(this.categoryForm.descriptions['it']),
|
||||||
|
descriptionIt: this.optionalValue(this.categoryForm.descriptions['it']),
|
||||||
|
descriptionEn: this.optionalValue(this.categoryForm.descriptions['en']),
|
||||||
|
descriptionDe: this.optionalValue(this.categoryForm.descriptions['de']),
|
||||||
|
descriptionFr: this.optionalValue(this.categoryForm.descriptions['fr']),
|
||||||
|
seoTitle: this.optionalValue(this.categoryForm.seoTitles['it']),
|
||||||
|
seoTitleIt: this.optionalValue(this.categoryForm.seoTitles['it']),
|
||||||
|
seoTitleEn: this.optionalValue(this.categoryForm.seoTitles['en']),
|
||||||
|
seoTitleDe: this.optionalValue(this.categoryForm.seoTitles['de']),
|
||||||
|
seoTitleFr: this.optionalValue(this.categoryForm.seoTitles['fr']),
|
||||||
|
seoDescription: this.optionalValue(this.categoryForm.seoDescriptions['it']),
|
||||||
|
seoDescriptionIt: this.optionalValue(
|
||||||
|
this.categoryForm.seoDescriptions['it'],
|
||||||
|
),
|
||||||
|
seoDescriptionEn: this.optionalValue(
|
||||||
|
this.categoryForm.seoDescriptions['en'],
|
||||||
|
),
|
||||||
|
seoDescriptionDe: this.optionalValue(
|
||||||
|
this.categoryForm.seoDescriptions['de'],
|
||||||
|
),
|
||||||
|
seoDescriptionFr: this.optionalValue(
|
||||||
|
this.categoryForm.seoDescriptions['fr'],
|
||||||
|
),
|
||||||
ogTitle: this.categoryForm.ogTitle.trim(),
|
ogTitle: this.categoryForm.ogTitle.trim(),
|
||||||
ogDescription: this.categoryForm.ogDescription.trim(),
|
ogDescription: this.categoryForm.ogDescription.trim(),
|
||||||
indexable: this.categoryForm.indexable,
|
indexable: this.categoryForm.indexable,
|
||||||
@@ -1278,12 +1365,19 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private validateCategoryForm(): string | null {
|
private validateCategoryForm(): string | null {
|
||||||
if (!this.categoryForm.name.trim()) {
|
for (const language of this.shopLanguages) {
|
||||||
return 'Il nome categoria è obbligatorio.';
|
if (!this.categoryForm.names[language].trim()) {
|
||||||
|
return `Il nome categoria ${this.languageLabels[language]} è obbligatorio.`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!this.categoryForm.slug.trim()) {
|
if (!this.categoryForm.slug.trim()) {
|
||||||
return 'Lo slug categoria è obbligatorio.';
|
return 'Lo slug categoria è obbligatorio.';
|
||||||
}
|
}
|
||||||
|
for (const language of this.shopLanguages) {
|
||||||
|
if (this.categoryForm.seoDescriptions[language].trim().length > 160) {
|
||||||
|
return `La SEO description categoria ${this.languageLabels[language]} deve avere massimo 160 caratteri.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1616,6 +1710,10 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
|||||||
sku: this.optionalValue(existingVariant?.sku ?? ''),
|
sku: this.optionalValue(existingVariant?.sku ?? ''),
|
||||||
variantLabel: materialCode,
|
variantLabel: materialCode,
|
||||||
colorName: stockVariant.colorName.trim(),
|
colorName: stockVariant.colorName.trim(),
|
||||||
|
colorLabelIt: this.optionalValue(stockVariant.colorLabelIt ?? ''),
|
||||||
|
colorLabelEn: this.optionalValue(stockVariant.colorLabelEn ?? ''),
|
||||||
|
colorLabelDe: this.optionalValue(stockVariant.colorLabelDe ?? ''),
|
||||||
|
colorLabelFr: this.optionalValue(stockVariant.colorLabelFr ?? ''),
|
||||||
colorHex: this.optionalValue(
|
colorHex: this.optionalValue(
|
||||||
stockVariant.colorHex ?? '',
|
stockVariant.colorHex ?? '',
|
||||||
)?.toUpperCase(),
|
)?.toUpperCase(),
|
||||||
@@ -1714,7 +1812,7 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private stockVariantLabel(variant: AdminFilamentVariant): string {
|
private stockVariantLabel(variant: AdminFilamentVariant): string {
|
||||||
const colorName = variant.colorName.trim();
|
const colorName = (variant.colorLabelIt || variant.colorName).trim();
|
||||||
const variantDisplayName = variant.variantDisplayName.trim();
|
const variantDisplayName = variant.variantDisplayName.trim();
|
||||||
if (
|
if (
|
||||||
variantDisplayName &&
|
variantDisplayName &&
|
||||||
@@ -2193,6 +2291,19 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
|||||||
return this.productForm.seoDescriptions[language].trim().length;
|
return this.productForm.seoDescriptions[language].trim().length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
categorySeoDescriptionLength(language: ShopLanguage): number {
|
||||||
|
return this.categoryForm.seoDescriptions[language].trim().length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createEmptyLocalizedTextRecord(): Record<ShopLanguage, string> {
|
||||||
|
return {
|
||||||
|
it: '',
|
||||||
|
en: '',
|
||||||
|
de: '',
|
||||||
|
fr: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private slugify(source: string): string {
|
private slugify(source: string): string {
|
||||||
return source
|
return source
|
||||||
.normalize('NFD')
|
.normalize('NFD')
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ export interface AdminFilamentVariant {
|
|||||||
materialTechnicalTypeLabel?: string;
|
materialTechnicalTypeLabel?: string;
|
||||||
variantDisplayName: string;
|
variantDisplayName: string;
|
||||||
colorName: string;
|
colorName: string;
|
||||||
|
colorLabelIt: string;
|
||||||
|
colorLabelEn: string;
|
||||||
|
colorLabelDe: string;
|
||||||
|
colorLabelFr: string;
|
||||||
colorHex?: string;
|
colorHex?: string;
|
||||||
finishType?: string;
|
finishType?: string;
|
||||||
brand?: string;
|
brand?: string;
|
||||||
@@ -57,6 +61,10 @@ export interface AdminUpsertFilamentVariantPayload {
|
|||||||
materialTypeId: number;
|
materialTypeId: number;
|
||||||
variantDisplayName: string;
|
variantDisplayName: string;
|
||||||
colorName: string;
|
colorName: string;
|
||||||
|
colorLabelIt?: string;
|
||||||
|
colorLabelEn?: string;
|
||||||
|
colorLabelDe?: string;
|
||||||
|
colorLabelFr?: string;
|
||||||
colorHex?: string;
|
colorHex?: string;
|
||||||
finishType?: string;
|
finishType?: string;
|
||||||
brand?: string;
|
brand?: string;
|
||||||
|
|||||||
@@ -30,9 +30,25 @@ export interface AdminShopCategory {
|
|||||||
parentCategoryName: string | null;
|
parentCategoryName: string | null;
|
||||||
slug: string;
|
slug: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
nameIt: string;
|
||||||
|
nameEn: string;
|
||||||
|
nameDe: string;
|
||||||
|
nameFr: string;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
|
descriptionIt: string | null;
|
||||||
|
descriptionEn: string | null;
|
||||||
|
descriptionDe: string | null;
|
||||||
|
descriptionFr: string | null;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
|
seoTitleIt: string | null;
|
||||||
|
seoTitleEn: string | null;
|
||||||
|
seoTitleDe: string | null;
|
||||||
|
seoTitleFr: string | null;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
|
seoDescriptionIt: string | null;
|
||||||
|
seoDescriptionEn: string | null;
|
||||||
|
seoDescriptionDe: string | null;
|
||||||
|
seoDescriptionFr: string | null;
|
||||||
ogTitle: string | null;
|
ogTitle: string | null;
|
||||||
ogDescription: string | null;
|
ogDescription: string | null;
|
||||||
indexable: boolean;
|
indexable: boolean;
|
||||||
@@ -54,9 +70,25 @@ export interface AdminUpsertShopCategoryPayload {
|
|||||||
parentCategoryId?: string | null;
|
parentCategoryId?: string | null;
|
||||||
slug: string;
|
slug: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
nameIt: string;
|
||||||
|
nameEn: string;
|
||||||
|
nameDe: string;
|
||||||
|
nameFr: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
descriptionIt?: string;
|
||||||
|
descriptionEn?: string;
|
||||||
|
descriptionDe?: string;
|
||||||
|
descriptionFr?: string;
|
||||||
seoTitle?: string;
|
seoTitle?: string;
|
||||||
|
seoTitleIt?: string;
|
||||||
|
seoTitleEn?: string;
|
||||||
|
seoTitleDe?: string;
|
||||||
|
seoTitleFr?: string;
|
||||||
seoDescription?: string;
|
seoDescription?: string;
|
||||||
|
seoDescriptionIt?: string;
|
||||||
|
seoDescriptionEn?: string;
|
||||||
|
seoDescriptionDe?: string;
|
||||||
|
seoDescriptionFr?: string;
|
||||||
ogTitle?: string;
|
ogTitle?: string;
|
||||||
ogDescription?: string;
|
ogDescription?: string;
|
||||||
indexable: boolean;
|
indexable: boolean;
|
||||||
@@ -69,6 +101,10 @@ export interface AdminShopProductVariant {
|
|||||||
sku: string | null;
|
sku: string | null;
|
||||||
variantLabel: string;
|
variantLabel: string;
|
||||||
colorName: string;
|
colorName: string;
|
||||||
|
colorLabelIt: string;
|
||||||
|
colorLabelEn: string;
|
||||||
|
colorLabelDe: string;
|
||||||
|
colorLabelFr: string;
|
||||||
colorHex: string | null;
|
colorHex: string | null;
|
||||||
internalMaterialCode: string;
|
internalMaterialCode: string;
|
||||||
priceChf: number;
|
priceChf: number;
|
||||||
@@ -170,6 +206,10 @@ export interface AdminUpsertShopProductVariantPayload {
|
|||||||
sku?: string;
|
sku?: string;
|
||||||
variantLabel?: string;
|
variantLabel?: string;
|
||||||
colorName: string;
|
colorName: string;
|
||||||
|
colorLabelIt?: string;
|
||||||
|
colorLabelEn?: string;
|
||||||
|
colorLabelDe?: string;
|
||||||
|
colorLabelFr?: string;
|
||||||
colorHex?: string;
|
colorHex?: string;
|
||||||
internalMaterialCode: string;
|
internalMaterialCode: string;
|
||||||
priceChf: number;
|
priceChf: number;
|
||||||
|
|||||||
@@ -75,6 +75,10 @@ export interface VariantOption {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
colorName: string;
|
colorName: string;
|
||||||
|
colorLabelIt?: string;
|
||||||
|
colorLabelEn?: string;
|
||||||
|
colorLabelDe?: string;
|
||||||
|
colorLabelFr?: string;
|
||||||
hexColor: string;
|
hexColor: string;
|
||||||
finishType: string;
|
finishType: string;
|
||||||
stockSpools: number;
|
stockSpools: number;
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ import { StlViewerComponent } from '../../shared/components/stl-viewer/stl-viewe
|
|||||||
import {
|
import {
|
||||||
findColorHex,
|
findColorHex,
|
||||||
getColorHex,
|
getColorHex,
|
||||||
getColorLabelToken,
|
|
||||||
normalizeColorValue,
|
normalizeColorValue,
|
||||||
|
resolveLocalizedColorLabel,
|
||||||
} from '../../core/constants/colors.const';
|
} from '../../core/constants/colors.const';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -257,7 +257,7 @@ export class CheckoutComponent implements OnInit {
|
|||||||
if (variantLabel) {
|
if (variantLabel) {
|
||||||
return variantLabel;
|
return variantLabel;
|
||||||
}
|
}
|
||||||
return getColorLabelToken(item?.shopVariantColorName);
|
return this.localizedShopColorLabel(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
showItemMaterial(item: any): boolean {
|
showItemMaterial(item: any): boolean {
|
||||||
@@ -286,12 +286,7 @@ export class CheckoutComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
itemColorLabel(item: any): string {
|
itemColorLabel(item: any): string {
|
||||||
const shopColor = String(item?.shopVariantColorName ?? '').trim();
|
return this.localizedShopColorLabel(item) || String(item?.colorCode ?? '-');
|
||||||
if (shopColor) {
|
|
||||||
return getColorLabelToken(shopColor) ?? '-';
|
|
||||||
}
|
|
||||||
const raw = String(item?.colorCode ?? '').trim();
|
|
||||||
return getColorLabelToken(raw) ?? '-';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
itemColorSwatch(item: any): string {
|
itemColorSwatch(item: any): string {
|
||||||
@@ -335,6 +330,16 @@ export class CheckoutComponent implements OnInit {
|
|||||||
return !!this.previewLoading()[id];
|
return !!this.previewLoading()[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private localizedShopColorLabel(item: any): string | null {
|
||||||
|
return resolveLocalizedColorLabel(this.languageService.selectedLang(), {
|
||||||
|
fallback: item?.shopVariantColorName ?? item?.colorCode,
|
||||||
|
it: item?.shopVariantColorLabelIt,
|
||||||
|
en: item?.shopVariantColorLabelEn,
|
||||||
|
de: item?.shopVariantColorLabelDe,
|
||||||
|
fr: item?.shopVariantColorLabelFr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
hasPreviewError(item: any): boolean {
|
hasPreviewError(item: any): boolean {
|
||||||
const id = String(item?.id ?? '');
|
const id = String(item?.id ?? '');
|
||||||
if (!id) {
|
if (!id) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
|||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import {
|
import {
|
||||||
findColorHex,
|
findColorHex,
|
||||||
getColorLabelToken,
|
resolveLocalizedColorLabel,
|
||||||
} from '../../core/constants/colors.const';
|
} from '../../core/constants/colors.const';
|
||||||
import {
|
import {
|
||||||
PriceBreakdownComponent,
|
PriceBreakdownComponent,
|
||||||
@@ -29,9 +29,17 @@ interface PublicOrderItem {
|
|||||||
shopProductName?: string;
|
shopProductName?: string;
|
||||||
shopVariantLabel?: string;
|
shopVariantLabel?: string;
|
||||||
shopVariantColorName?: string;
|
shopVariantColorName?: string;
|
||||||
|
shopVariantColorLabelIt?: string;
|
||||||
|
shopVariantColorLabelEn?: string;
|
||||||
|
shopVariantColorLabelDe?: string;
|
||||||
|
shopVariantColorLabelFr?: string;
|
||||||
shopVariantColorHex?: string;
|
shopVariantColorHex?: string;
|
||||||
filamentVariantDisplayName?: string;
|
filamentVariantDisplayName?: string;
|
||||||
filamentColorName?: string;
|
filamentColorName?: string;
|
||||||
|
filamentColorLabelIt?: string;
|
||||||
|
filamentColorLabelEn?: string;
|
||||||
|
filamentColorLabelDe?: string;
|
||||||
|
filamentColorLabelFr?: string;
|
||||||
filamentColorHex?: string;
|
filamentColorHex?: string;
|
||||||
quality?: string;
|
quality?: string;
|
||||||
nozzleDiameterMm?: number;
|
nozzleDiameterMm?: number;
|
||||||
@@ -282,26 +290,14 @@ export class OrderComponent implements OnInit {
|
|||||||
return variantLabel;
|
return variantLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getColorLabelToken(item?.shopVariantColorName);
|
return this.localizedColorLabel(item, 'shop');
|
||||||
}
|
}
|
||||||
|
|
||||||
itemColorLabel(item: PublicOrderItem): string {
|
itemColorLabel(item: PublicOrderItem): string {
|
||||||
const shopColor = String(item?.shopVariantColorName ?? '').trim();
|
|
||||||
if (shopColor) {
|
|
||||||
return getColorLabelToken(shopColor) ?? this.translate.instant('ORDER.NOT_AVAILABLE');
|
|
||||||
}
|
|
||||||
|
|
||||||
const filamentColor = String(item?.filamentColorName ?? '').trim();
|
|
||||||
if (filamentColor) {
|
|
||||||
return (
|
|
||||||
getColorLabelToken(filamentColor) ??
|
|
||||||
this.translate.instant('ORDER.NOT_AVAILABLE')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawColor = String(item?.colorCode ?? '').trim();
|
|
||||||
return (
|
return (
|
||||||
getColorLabelToken(rawColor) ??
|
this.localizedColorLabel(item, 'shop') ||
|
||||||
|
this.localizedColorLabel(item, 'filament') ||
|
||||||
|
String(item?.colorCode ?? '').trim() ||
|
||||||
this.translate.instant('ORDER.NOT_AVAILABLE')
|
this.translate.instant('ORDER.NOT_AVAILABLE')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -333,6 +329,29 @@ export class OrderComponent implements OnInit {
|
|||||||
return !this.isShopItem(item);
|
return !this.isShopItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private localizedColorLabel(
|
||||||
|
item: PublicOrderItem,
|
||||||
|
source: 'shop' | 'filament',
|
||||||
|
): string | null {
|
||||||
|
if (source === 'shop') {
|
||||||
|
return resolveLocalizedColorLabel(this.translate.currentLang, {
|
||||||
|
fallback: item.shopVariantColorName,
|
||||||
|
it: item.shopVariantColorLabelIt,
|
||||||
|
en: item.shopVariantColorLabelEn,
|
||||||
|
de: item.shopVariantColorLabelDe,
|
||||||
|
fr: item.shopVariantColorLabelFr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveLocalizedColorLabel(this.translate.currentLang, {
|
||||||
|
fallback: item.filamentColorName ?? item.colorCode,
|
||||||
|
it: item.filamentColorLabelIt,
|
||||||
|
en: item.filamentColorLabelEn,
|
||||||
|
de: item.filamentColorLabelDe,
|
||||||
|
fr: item.filamentColorLabelFr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
orderKind(order: PublicOrder | null): 'SHOP' | 'CALCULATOR' | 'MIXED' {
|
orderKind(order: PublicOrder | null): 'SHOP' | 'CALCULATOR' | 'MIXED' {
|
||||||
const items = order?.items ?? [];
|
const items = order?.items ?? [];
|
||||||
const hasShop = items.some((item) => this.isShopItem(item));
|
const hasShop = items.some((item) => this.isShopItem(item));
|
||||||
|
|||||||
@@ -28,10 +28,7 @@
|
|||||||
|
|
||||||
<div class="detail-grid">
|
<div class="detail-grid">
|
||||||
<section class="visual-column">
|
<section class="visual-column">
|
||||||
<div
|
<div class="hero-media">
|
||||||
class="hero-media"
|
|
||||||
[class.hero-media--portrait]="selectedImageIsPortrait()"
|
|
||||||
>
|
|
||||||
@if (galleryImages().length > 1) {
|
@if (galleryImages().length > 1) {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -56,7 +53,6 @@
|
|||||||
[src]="imageUrl"
|
[src]="imageUrl"
|
||||||
[alt]="selectedImage().altText || p.name"
|
[alt]="selectedImage().altText || p.name"
|
||||||
class="hero-image"
|
class="hero-image"
|
||||||
(load)="onHeroImageLoad($event)"
|
|
||||||
/>
|
/>
|
||||||
} @else {
|
} @else {
|
||||||
<div class="image-fallback">
|
<div class="image-fallback">
|
||||||
|
|||||||
@@ -107,10 +107,6 @@
|
|||||||
background: #f2eee5;
|
background: #f2eee5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-media--portrait .hero-image {
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-fallback {
|
.image-fallback {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import { LanguageService } from '../../core/services/language.service';
|
|||||||
import {
|
import {
|
||||||
findColorHex,
|
findColorHex,
|
||||||
getColorHex,
|
getColorHex,
|
||||||
getColorLabelToken,
|
|
||||||
} from '../../core/constants/colors.const';
|
} from '../../core/constants/colors.const';
|
||||||
import { AppButtonComponent } from '../../shared/components/app-button/app-button.component';
|
import { AppButtonComponent } from '../../shared/components/app-button/app-button.component';
|
||||||
import { AppCardComponent } from '../../shared/components/app-card/app-card.component';
|
import { AppCardComponent } from '../../shared/components/app-card/app-card.component';
|
||||||
@@ -78,9 +77,6 @@ export class ProductDetailComponent {
|
|||||||
readonly product = signal<ShopProductDetail | null>(null);
|
readonly product = signal<ShopProductDetail | null>(null);
|
||||||
readonly selectedVariantId = signal<string | null>(null);
|
readonly selectedVariantId = signal<string | null>(null);
|
||||||
readonly selectedImageAssetId = signal<string | null>(null);
|
readonly selectedImageAssetId = signal<string | null>(null);
|
||||||
readonly selectedImageOrientation = signal<
|
|
||||||
'portrait' | 'landscape' | 'square' | null
|
|
||||||
>(null);
|
|
||||||
readonly quantity = signal(1);
|
readonly quantity = signal(1);
|
||||||
readonly isAddingToCart = signal(false);
|
readonly isAddingToCart = signal(false);
|
||||||
readonly addSuccess = signal(false);
|
readonly addSuccess = signal(false);
|
||||||
@@ -198,9 +194,6 @@ export class ProductDetailComponent {
|
|||||||
readonly selectedVariantCartQuantity = computed(() =>
|
readonly selectedVariantCartQuantity = computed(() =>
|
||||||
this.shopService.quantityForVariant(this.selectedVariant()?.id),
|
this.shopService.quantityForVariant(this.selectedVariant()?.id),
|
||||||
);
|
);
|
||||||
readonly selectedImageIsPortrait = computed(
|
|
||||||
() => this.selectedImageOrientation() === 'portrait',
|
|
||||||
);
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (!this.shopService.cartLoaded()) {
|
if (!this.shopService.cartLoaded()) {
|
||||||
@@ -315,25 +308,6 @@ export class ProductDetailComponent {
|
|||||||
this.setSelectedImageAssetId(images[nextIndex].mediaAssetId);
|
this.setSelectedImageAssetId(images[nextIndex].mediaAssetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
onHeroImageLoad(event: Event): void {
|
|
||||||
const target = event.target;
|
|
||||||
if (!(target instanceof HTMLImageElement)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target.naturalHeight > target.naturalWidth) {
|
|
||||||
this.selectedImageOrientation.set('portrait');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target.naturalWidth > target.naturalHeight) {
|
|
||||||
this.selectedImageOrientation.set('landscape');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.selectedImageOrientation.set('square');
|
|
||||||
}
|
|
||||||
|
|
||||||
selectVariant(variant: ShopProductVariantOption): void {
|
selectVariant(variant: ShopProductVariantOption): void {
|
||||||
this.selectedVariantId.set(variant.id);
|
this.selectedVariantId.set(variant.id);
|
||||||
this.selectedMaterialKey.set(this.materialKeyForVariant(variant));
|
this.selectedMaterialKey.set(this.materialKeyForVariant(variant));
|
||||||
@@ -407,9 +381,7 @@ export class ProductDetailComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
colorLabel(variant: ShopProductVariantOption): string {
|
colorLabel(variant: ShopProductVariantOption): string {
|
||||||
return (
|
return variant.colorLabel || variant.colorName || variant.variantLabel || '-';
|
||||||
getColorLabelToken(variant.colorName || variant.variantLabel) ?? '-'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
colorHex(variant: ShopProductVariantOption | null | undefined): string {
|
colorHex(variant: ShopProductVariantOption | null | undefined): string {
|
||||||
@@ -512,7 +484,6 @@ export class ProductDetailComponent {
|
|||||||
|
|
||||||
private setSelectedImageAssetId(mediaAssetId: string | null): void {
|
private setSelectedImageAssetId(mediaAssetId: string | null): void {
|
||||||
this.selectedImageAssetId.set(mediaAssetId);
|
this.selectedImageAssetId.set(mediaAssetId);
|
||||||
this.selectedImageOrientation.set(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private normalizeHexColor(value: string | null | undefined): string | null {
|
private normalizeHexColor(value: string | null | undefined): string | null {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export interface ShopProductVariantOption {
|
|||||||
sku: string | null;
|
sku: string | null;
|
||||||
variantLabel: string | null;
|
variantLabel: string | null;
|
||||||
colorName: string | null;
|
colorName: string | null;
|
||||||
|
colorLabel: string | null;
|
||||||
colorHex: string | null;
|
colorHex: string | null;
|
||||||
priceChf: number;
|
priceChf: number;
|
||||||
isDefault: boolean;
|
isDefault: boolean;
|
||||||
@@ -138,6 +139,10 @@ export interface ShopCartItem {
|
|||||||
shopProductName: string | null;
|
shopProductName: string | null;
|
||||||
shopVariantLabel: string | null;
|
shopVariantLabel: string | null;
|
||||||
shopVariantColorName: string | null;
|
shopVariantColorName: string | null;
|
||||||
|
shopVariantColorLabelIt?: string | null;
|
||||||
|
shopVariantColorLabelEn?: string | null;
|
||||||
|
shopVariantColorLabelDe?: string | null;
|
||||||
|
shopVariantColorLabelFr?: string | null;
|
||||||
shopVariantColorHex: string | null;
|
shopVariantColorHex: string | null;
|
||||||
materialCode: string | null;
|
materialCode: string | null;
|
||||||
quality: string | null;
|
quality: string | null;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { SeoService } from '../../core/services/seo.service';
|
|||||||
import { LanguageService } from '../../core/services/language.service';
|
import { LanguageService } from '../../core/services/language.service';
|
||||||
import {
|
import {
|
||||||
findColorHex,
|
findColorHex,
|
||||||
getColorLabelToken,
|
resolveLocalizedColorLabel,
|
||||||
} from '../../core/constants/colors.const';
|
} from '../../core/constants/colors.const';
|
||||||
import { AppButtonComponent } from '../../shared/components/app-button/app-button.component';
|
import { AppButtonComponent } from '../../shared/components/app-button/app-button.component';
|
||||||
import { AppCardComponent } from '../../shared/components/app-card/app-card.component';
|
import { AppCardComponent } from '../../shared/components/app-card/app-card.component';
|
||||||
@@ -161,15 +161,20 @@ export class ShopPageComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cartItemVariant(item: ShopCartItem): string | null {
|
cartItemVariant(item: ShopCartItem): string | null {
|
||||||
return (
|
return item.shopVariantLabel || this.cartItemColor(item);
|
||||||
item.shopVariantLabel || getColorLabelToken(item.shopVariantColorName)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cartItemColor(item: ShopCartItem): string | null {
|
cartItemColor(item: ShopCartItem): string | null {
|
||||||
return (
|
return (
|
||||||
getColorLabelToken(item.shopVariantColorName) ??
|
resolveLocalizedColorLabel(this.languageService.selectedLang(), {
|
||||||
getColorLabelToken(item.colorCode)
|
fallback: item.shopVariantColorName ?? item.colorCode,
|
||||||
|
it: item.shopVariantColorLabelIt,
|
||||||
|
en: item.shopVariantColorLabelEn,
|
||||||
|
de: item.shopVariantColorLabelDe,
|
||||||
|
fr: item.shopVariantColorLabelFr,
|
||||||
|
}) ??
|
||||||
|
item.shopVariantColorName ??
|
||||||
|
item.colorCode
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { Component, input, output, signal, computed } from '@angular/core';
|
import { Component, input, output, signal, computed, inject } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
PRODUCT_COLORS,
|
PRODUCT_COLORS,
|
||||||
getColorHex,
|
getColorHex,
|
||||||
getColorLabelToken,
|
|
||||||
ColorCategory,
|
ColorCategory,
|
||||||
ColorOption,
|
ColorOption,
|
||||||
|
resolveLocalizedColorLabel,
|
||||||
} from '../../../core/constants/colors.const';
|
} from '../../../core/constants/colors.const';
|
||||||
import { VariantOption } from '../../../features/calculator/services/quote-estimator.service';
|
import { VariantOption } from '../../../features/calculator/services/quote-estimator.service';
|
||||||
|
import { LanguageService } from '../../../core/services/language.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-color-selector',
|
selector: 'app-color-selector',
|
||||||
@@ -18,6 +19,7 @@ import { VariantOption } from '../../../features/calculator/services/quote-estim
|
|||||||
styleUrl: './color-selector.component.scss',
|
styleUrl: './color-selector.component.scss',
|
||||||
})
|
})
|
||||||
export class ColorSelectorComponent {
|
export class ColorSelectorComponent {
|
||||||
|
private readonly languageService = inject(LanguageService);
|
||||||
selectedColor = input<string>('Black');
|
selectedColor = input<string>('Black');
|
||||||
selectedVariantId = input<number | null>(null);
|
selectedVariantId = input<number | null>(null);
|
||||||
variants = input<VariantOption[]>([]);
|
variants = input<VariantOption[]>([]);
|
||||||
@@ -33,7 +35,14 @@ export class ColorSelectorComponent {
|
|||||||
const finish = v.finishType || 'AVAILABLE_COLORS';
|
const finish = v.finishType || 'AVAILABLE_COLORS';
|
||||||
const bucket = byFinish.get(finish) || [];
|
const bucket = byFinish.get(finish) || [];
|
||||||
bucket.push({
|
bucket.push({
|
||||||
label: getColorLabelToken(v.colorName) ?? v.colorName,
|
label:
|
||||||
|
resolveLocalizedColorLabel(this.languageService.selectedLang(), {
|
||||||
|
fallback: v.colorName,
|
||||||
|
it: v.colorLabelIt,
|
||||||
|
en: v.colorLabelEn,
|
||||||
|
de: v.colorLabelDe,
|
||||||
|
fr: v.colorLabelFr,
|
||||||
|
}) ?? v.colorName,
|
||||||
value: v.colorName,
|
value: v.colorName,
|
||||||
hex: v.hexColor,
|
hex: v.hexColor,
|
||||||
variantId: v.id,
|
variantId: v.id,
|
||||||
|
|||||||
Reference in New Issue
Block a user