dev #37
@@ -257,9 +257,7 @@
|
||||
<p class="file-name">
|
||||
<strong>{{ itemDisplayName(item) }}</strong>
|
||||
</p>
|
||||
<span
|
||||
class="item-kind-badge"
|
||||
>
|
||||
<span class="item-kind-badge">
|
||||
{{ isShopItem(item) ? "Shop" : "Calcolatore" }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -714,9 +714,7 @@
|
||||
</span>
|
||||
<textarea
|
||||
class="ui-form-control"
|
||||
[(ngModel)]="
|
||||
productForm.seoDescriptions[activeContentLanguage]
|
||||
"
|
||||
[(ngModel)]="productForm.seoDescriptions[activeContentLanguage]"
|
||||
[name]="'product-seo-description-' + activeContentLanguage"
|
||||
rows="3"
|
||||
></textarea>
|
||||
|
||||
@@ -605,7 +605,9 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
availableMaterialChoices(currentMaterialCode: string): string[] {
|
||||
const normalizedCurrentMaterialCode = currentMaterialCode.trim().toUpperCase();
|
||||
const normalizedCurrentMaterialCode = currentMaterialCode
|
||||
.trim()
|
||||
.toUpperCase();
|
||||
const selectedCodes = new Set(
|
||||
this.productForm.materials
|
||||
.map((material) => material.materialCode.trim().toUpperCase())
|
||||
@@ -804,10 +806,7 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
||||
|
||||
isImageLanguageStarted(language: AdminMediaLanguage): boolean {
|
||||
const translation = this.imageUploadState.translations[language];
|
||||
return (
|
||||
!!translation.title.trim() ||
|
||||
!!translation.altText.trim()
|
||||
);
|
||||
return !!translation.title.trim() || !!translation.altText.trim();
|
||||
}
|
||||
|
||||
isImageLanguageIncomplete(language: AdminMediaLanguage): boolean {
|
||||
@@ -1291,7 +1290,9 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
||||
|
||||
const groups = new Map<string, AdminShopProductVariant[]>();
|
||||
for (const variant of variants) {
|
||||
const materialCode = (variant.internalMaterialCode ?? '').trim().toUpperCase();
|
||||
const materialCode = (variant.internalMaterialCode ?? '')
|
||||
.trim()
|
||||
.toUpperCase();
|
||||
if (!materialCode) {
|
||||
continue;
|
||||
}
|
||||
@@ -1403,7 +1404,9 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
||||
seoTitleEn: this.optionalValue(this.productForm.seoTitles['en']),
|
||||
seoTitleDe: this.optionalValue(this.productForm.seoTitles['de']),
|
||||
seoTitleFr: this.optionalValue(this.productForm.seoTitles['fr']),
|
||||
seoDescription: this.optionalValue(this.productForm.seoDescriptions['it']),
|
||||
seoDescription: this.optionalValue(
|
||||
this.productForm.seoDescriptions['it'],
|
||||
),
|
||||
seoDescriptionIt: this.optionalValue(
|
||||
this.productForm.seoDescriptions['it'],
|
||||
),
|
||||
@@ -1461,9 +1464,14 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
||||
let defaultVariantKeyForMaterial: string | null = null;
|
||||
|
||||
if (material.isDefault && persistedDefaultKey) {
|
||||
defaultVariantKeyForMaterial = stockVariants
|
||||
defaultVariantKeyForMaterial =
|
||||
stockVariants
|
||||
.map((variant) =>
|
||||
this.variantKey(materialCode, variant.colorName, variant.colorHex),
|
||||
this.variantKey(
|
||||
materialCode,
|
||||
variant.colorName,
|
||||
variant.colorHex,
|
||||
),
|
||||
)
|
||||
.find((variantKey) => variantKey === persistedDefaultKey) ?? null;
|
||||
}
|
||||
@@ -1487,7 +1495,9 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
||||
sku: this.optionalValue(existingVariant?.sku ?? ''),
|
||||
variantLabel: materialCode,
|
||||
colorName: stockVariant.colorName.trim(),
|
||||
colorHex: this.optionalValue(stockVariant.colorHex ?? '')?.toUpperCase(),
|
||||
colorHex: this.optionalValue(
|
||||
stockVariant.colorHex ?? '',
|
||||
)?.toUpperCase(),
|
||||
internalMaterialCode: materialCode,
|
||||
priceChf: Number(material.priceChf),
|
||||
isDefault,
|
||||
@@ -1518,7 +1528,9 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
||||
).sort((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
private stockVariantsForMaterial(materialCode: string): AdminFilamentVariant[] {
|
||||
private stockVariantsForMaterial(
|
||||
materialCode: string,
|
||||
): AdminFilamentVariant[] {
|
||||
const targetMaterialCode = materialCode.trim().toUpperCase();
|
||||
const seenKeys = new Set<string>();
|
||||
|
||||
@@ -1529,7 +1541,8 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
||||
)
|
||||
.sort((left, right) => {
|
||||
const leftName = `${left.colorName} ${left.variantDisplayName}`.trim();
|
||||
const rightName = `${right.colorName} ${right.variantDisplayName}`.trim();
|
||||
const rightName =
|
||||
`${right.colorName} ${right.variantDisplayName}`.trim();
|
||||
return leftName.localeCompare(rightName);
|
||||
})
|
||||
.filter((variant) => {
|
||||
@@ -1554,8 +1567,9 @@ export class AdminShopComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
|
||||
return (
|
||||
this.stockMaterialCodes().find((materialCode) => !selectedCodes.has(materialCode)) ??
|
||||
null
|
||||
this.stockMaterialCodes().find(
|
||||
(materialCode) => !selectedCodes.has(materialCode),
|
||||
) ?? null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,18 +56,15 @@
|
||||
(click)="addToCart()"
|
||||
[disabled]="!defaultVariantId() || addingToCart()"
|
||||
>
|
||||
{{
|
||||
(addingToCart() ? "SHOP.ADDING" : "SHOP.ADD_CART") | translate
|
||||
}}
|
||||
{{ (addingToCart() ? "SHOP.ADDING" : "SHOP.ADD_CART") | translate }}
|
||||
</button>
|
||||
|
||||
<a
|
||||
[routerLink]="productLink()"
|
||||
[state]="navigationState()"
|
||||
class="view-btn"
|
||||
>{{
|
||||
"SHOP.DETAILS" | translate
|
||||
}}</a>
|
||||
>{{ "SHOP.DETAILS" | translate }}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -83,7 +83,9 @@
|
||||
@if (p.model3d) {
|
||||
<div class="model-launch-row">
|
||||
<div>
|
||||
<p class="viewer-kicker">{{ "SHOP.MODEL_3D" | translate }}</p>
|
||||
<p class="viewer-kicker">
|
||||
{{ "SHOP.MODEL_3D" | translate }}
|
||||
</p>
|
||||
<div class="dimensions dimensions-inline">
|
||||
<span>
|
||||
X
|
||||
@@ -151,7 +153,9 @@
|
||||
<button
|
||||
type="button"
|
||||
class="material-option"
|
||||
[class.active]="selectedMaterial()?.key === material.key"
|
||||
[class.active]="
|
||||
selectedMaterial()?.key === material.key
|
||||
"
|
||||
(click)="selectMaterial(material.key)"
|
||||
>
|
||||
<span class="material-copy">
|
||||
@@ -228,7 +232,7 @@
|
||||
></button>
|
||||
<div class="color-popup">
|
||||
<div class="color-popup__category">
|
||||
{{ (selectedMaterial()?.label || "") | uppercase }}
|
||||
{{ selectedMaterial()?.label || "" | uppercase }}
|
||||
</div>
|
||||
|
||||
<div class="color-popup__grid">
|
||||
|
||||
@@ -139,8 +139,8 @@ export class ProductDetailComponent {
|
||||
);
|
||||
});
|
||||
|
||||
readonly colorOptions = computed<ShopProductVariantOption[]>(() =>
|
||||
this.selectedMaterial()?.variants ?? [],
|
||||
readonly colorOptions = computed<ShopProductVariantOption[]>(
|
||||
() => this.selectedMaterial()?.variants ?? [],
|
||||
);
|
||||
|
||||
readonly selectedMaterialProperties = computed<ShopMaterialProperty[]>(() =>
|
||||
@@ -494,11 +494,15 @@ export class ProductDetailComponent {
|
||||
});
|
||||
}
|
||||
|
||||
private materialLabelForVariant(variant: ShopProductVariantOption | null): string {
|
||||
private materialLabelForVariant(
|
||||
variant: ShopProductVariantOption | null,
|
||||
): string {
|
||||
return String(variant?.variantLabel || '').trim() || 'Standard';
|
||||
}
|
||||
|
||||
private materialKeyForVariant(variant: ShopProductVariantOption | null): string | null {
|
||||
private materialKeyForVariant(
|
||||
variant: ShopProductVariantOption | null,
|
||||
): string | null {
|
||||
if (!variant) {
|
||||
return null;
|
||||
}
|
||||
@@ -508,7 +512,9 @@ export class ProductDetailComponent {
|
||||
private materialPropertiesFor(
|
||||
materialLabel: string | null | undefined,
|
||||
): ShopMaterialProperty[] {
|
||||
const normalized = String(materialLabel ?? '').trim().toUpperCase();
|
||||
const normalized = String(materialLabel ?? '')
|
||||
.trim()
|
||||
.toUpperCase();
|
||||
|
||||
if (normalized.includes('ASA')) {
|
||||
return [
|
||||
@@ -600,7 +606,13 @@ export class ProductDetailComponent {
|
||||
|
||||
const currentTree = this.router.parseUrl(this.router.url);
|
||||
const targetTree = this.router.createUrlTree(
|
||||
['/', this.languageService.selectedLang(), 'shop', 'p', targetProductSlug],
|
||||
[
|
||||
'/',
|
||||
this.languageService.selectedLang(),
|
||||
'shop',
|
||||
'p',
|
||||
targetProductSlug,
|
||||
],
|
||||
{
|
||||
queryParams: currentTree.queryParams,
|
||||
fragment: currentTree.fragment ?? undefined,
|
||||
|
||||
@@ -38,8 +38,12 @@ export class ShopRouteService {
|
||||
return idPrefix ? `${idPrefix}-${tail}` : tail;
|
||||
}
|
||||
|
||||
resolveProductLookup(productPathSegment: string | null | undefined): ShopProductLookup {
|
||||
const normalized = String(productPathSegment ?? '').trim().toLowerCase();
|
||||
resolveProductLookup(
|
||||
productPathSegment: string | null | undefined,
|
||||
): ShopProductLookup {
|
||||
const normalized = String(productPathSegment ?? '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (!normalized) {
|
||||
return {
|
||||
idPrefix: null,
|
||||
@@ -89,7 +93,9 @@ export class ShopRouteService {
|
||||
}
|
||||
|
||||
private productIdPrefix(productId: string | null | undefined): string {
|
||||
const normalized = String(productId ?? '').trim().toLowerCase();
|
||||
const normalized = String(productId ?? '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const canonicalUuidMatch = normalized.match(/^([0-9a-f]{8})-/);
|
||||
if (canonicalUuidMatch) {
|
||||
return canonicalUuidMatch[1];
|
||||
|
||||
@@ -273,15 +273,16 @@ export class ShopService {
|
||||
getProductByPublicPath(
|
||||
productPathSegment: string,
|
||||
): Observable<ShopProductDetail> {
|
||||
const lookup = this.shopRouteService.resolveProductLookup(productPathSegment);
|
||||
const lookup =
|
||||
this.shopRouteService.resolveProductLookup(productPathSegment);
|
||||
if (!lookup.idPrefix && lookup.slugHint) {
|
||||
return this.getProduct(lookup.slugHint);
|
||||
}
|
||||
|
||||
return this.getProductCatalog().pipe(
|
||||
map((catalog) =>
|
||||
catalog.products.find(
|
||||
(product) => product.id.toLowerCase().startsWith(lookup.idPrefix ?? ''),
|
||||
catalog.products.find((product) =>
|
||||
product.id.toLowerCase().startsWith(lookup.idPrefix ?? ''),
|
||||
),
|
||||
),
|
||||
switchMap((product) => {
|
||||
|
||||
@@ -32,7 +32,9 @@
|
||||
[class.active]="!currentCategorySlug()"
|
||||
(click)="navigateToCategory()"
|
||||
>
|
||||
<span class="category-name">{{ "SHOP.ALL_CATEGORIES" | translate }}</span>
|
||||
<span class="category-name">{{
|
||||
"SHOP.ALL_CATEGORIES" | translate
|
||||
}}</span>
|
||||
</button>
|
||||
|
||||
<div class="category-list">
|
||||
@@ -227,7 +229,9 @@
|
||||
<app-card class="shop-custom-cta-card">
|
||||
<div class="shop-custom-cta-inner">
|
||||
<div class="shop-custom-cta-copy">
|
||||
<p class="panel-kicker">{{ "SHOP.CUSTOM_PART_FOOTER_TITLE" | translate }}</p>
|
||||
<p class="panel-kicker">
|
||||
{{ "SHOP.CUSTOM_PART_FOOTER_TITLE" | translate }}
|
||||
</p>
|
||||
<h2 class="shop-custom-cta-title">
|
||||
{{ "SHOP.CUSTOM_PART_FOOTER_TEXT" | translate }}
|
||||
</h2>
|
||||
|
||||
Reference in New Issue
Block a user