dev #37

Merged
JoeKung merged 47 commits from dev into main 2026-03-10 17:43:46 +01:00
9 changed files with 81 additions and 47 deletions
Showing only changes of commit 52c6239f6d - Show all commits

View File

@@ -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>

View File

@@ -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>

View File

@@ -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
);
}

View File

@@ -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>

View File

@@ -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">

View File

@@ -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,

View File

@@ -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];

View File

@@ -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) => {

View File

@@ -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>