diff --git a/frontend/src/app/core/layout/navbar.component.html b/frontend/src/app/core/layout/navbar.component.html index 36f5b5b..46d2bd8 100644 --- a/frontend/src/app/core/layout/navbar.component.html +++ b/frontend/src/app/core/layout/navbar.component.html @@ -62,7 +62,9 @@ > - + @if (cartItemCount() > 0) { {{ cartItemCount() }} @@ -145,7 +147,9 @@
-

{{ errorMessage }}

+

+ {{ errorMessage }} +

{{ successMessage }}

@@ -49,7 +55,9 @@ class="ui-button ui-button--ghost" (click)="toggleCategoryManager()" > - {{ showCategoryManager ? "Chiudi categorie" : "Gestisci categorie" }} + {{ + showCategoryManager ? "Chiudi categorie" : "Gestisci categorie" + }} @@ -89,7 +97,10 @@ [ngModel]="productStatusFilter" (ngModelChange)="onProductStatusFilterChange($event)" > - @@ -97,7 +108,10 @@ -
+

Categorie shop

@@ -146,10 +160,16 @@

- {{ categoryForm.id ? "Modifica categoria" : "Nuova categoria" }} + {{ + categoryForm.id ? "Modifica categoria" : "Nuova categoria" + }}

-

Aggiorna struttura, SEO e visibilità.

-

Crea una nuova categoria del catalogo.

+

+ Aggiorna struttura, SEO e visibilità. +

+

+ Crea una nuova categoria del catalogo. +

@@ -340,9 +360,13 @@
{{ product.categoryName }} - {{ product.activeVariantCount }} / {{ product.variantCount }} - {{ product.priceFromChf | currency: "CHF" : "symbol" : "1.2-2" }} + {{ product.activeVariantCount }} / {{ product.variantCount }} + + + {{ + product.priceFromChf | currency: "CHF" : "symbol" : "1.2-2" + }} - {{ @@ -366,7 +390,9 @@ - Nessun prodotto trovato con i filtri correnti. + + Nessun prodotto trovato con i filtri correnti. + @@ -388,7 +414,11 @@

- {{ productMode === "create" ? "Nuovo prodotto" : selectedProduct?.name }} + {{ + productMode === "create" + ? "Nuovo prodotto" + : selectedProduct?.name + }}

Compila i campi e salva per creare un nuovo prodotto shop. @@ -412,20 +442,29 @@

-

Caricamento dettaglio...

+

+ Caricamento dettaglio... +

-
+
Categoria {{ selectedProduct.categoryName }}
Creato il - {{ selectedProduct.createdAt | date: "dd.MM.yyyy HH:mm" }} + {{ + selectedProduct.createdAt | date: "dd.MM.yyyy HH:mm" + }}
Aggiornato il - {{ selectedProduct.updatedAt | date: "dd.MM.yyyy HH:mm" }} + {{ + selectedProduct.updatedAt | date: "dd.MM.yyyy HH:mm" + }}
Media @@ -530,7 +569,9 @@

Contenuti localizzati

-

Nome obbligatorio in tutte le lingue. Descrizioni opzionali.

+

+ Nome obbligatorio in tutte le lingue. Descrizioni opzionali. +

@@ -650,7 +691,11 @@

Varianti

Colori, materiale interno, SKU e prezzi.

-
@@ -658,12 +703,20 @@

- {{ variant.variantLabel || variant.colorName || "Nuova variante" }} + {{ + variant.variantLabel || + variant.colorName || + "Nuova variante" + }}

Ordine {{ variant.sortOrder }}

@@ -801,8 +854,8 @@

Immagini e modello 3D

- Upload protetto con whitelist tipi file; il modello 3D è disponibile - solo dopo il primo salvataggio del prodotto. + Upload protetto con whitelist tipi file; il modello 3D è + disponibile solo dopo il primo salvataggio del prodotto.

@@ -811,12 +864,17 @@ Salva prima il prodotto per collegare immagini e modello 3D.
-
+

Nuova immagine prodotto

-

JPG, PNG o WEBP con titolo e alt text in tutte le lingue.

+

+ JPG, PNG o WEBP con titolo e alt text in tutte le lingue. +

@@ -840,7 +898,10 @@
-
+
@@ -854,7 +915,9 @@ *ngFor="let language of mediaLanguages" type="button" class="ui-language-toolbar__button" - [class.active]="imageUploadState.activeLanguage === language" + [class.active]=" + imageUploadState.activeLanguage === language + " [class.complete]="isImageLanguageComplete(language)" [class.incomplete]="!isImageLanguageComplete(language)" (click)="setActiveImageLanguage(language)" @@ -872,7 +935,9 @@ class="ui-form-control" type="text" [(ngModel)]=" - imageUploadState.translations[imageUploadState.activeLanguage].title + imageUploadState.translations[ + imageUploadState.activeLanguage + ].title " name="productImageTitle" /> @@ -880,13 +945,16 @@
@@ -931,31 +1003,49 @@

Immagini attive

-

{{ productImages.length }} immagini collegate al prodotto.

+

+ {{ productImages.length }} immagini collegate al prodotto. +

-
+
- +
- {{ image.translations[imageUploadState.activeLanguage].title || "Senza titolo" }} + {{ + image.translations[imageUploadState.activeLanguage] + .title || "Senza titolo" + }} - + Primaria

- {{ image.translations[imageUploadState.activeLanguage].altText || "Alt text mancante" }} + {{ + image.translations[imageUploadState.activeLanguage] + .altText || "Alt text mancante" + }}

@@ -982,7 +1072,9 @@ type="button" class="ui-button ui-button--ghost" (click)="setPrimaryImage(image)" - [disabled]="isImageBusy(image.usageId) || image.isPrimary" + [disabled]=" + isImageBusy(image.usageId) || image.isPrimary + " > {{ image.isPrimary ? "Primaria" : "Rendi primaria" }} @@ -1012,7 +1104,10 @@
-
+
File @@ -1025,7 +1120,8 @@
Bounding box - {{ model.boundingBoxXMm || 0 }} × {{ model.boundingBoxYMm || 0 }} × + {{ model.boundingBoxXMm || 0 }} × + {{ model.boundingBoxYMm || 0 }} × {{ model.boundingBoxZMm || 0 }} mm
@@ -1053,7 +1149,9 @@
- Carica o sostituisci modello + Carica o sostituisci modello -
Nessun modello 3D caricato per questo prodotto.
+
+ Nessun modello 3D caricato per questo prodotto. +
diff --git a/frontend/src/app/features/admin/pages/admin-shop.component.ts b/frontend/src/app/features/admin/pages/admin-shop.component.ts index 7b1273b..5dc50fd 100644 --- a/frontend/src/app/features/admin/pages/admin-shop.component.ts +++ b/frontend/src/app/features/admin/pages/admin-shop.component.ts @@ -170,7 +170,8 @@ export class AdminShopComponent implements OnInit, OnDestroy { readonly categoryForm: CategoryFormState = this.createEmptyCategoryForm(); readonly productForm: ProductFormState = this.createEmptyProductForm(); - imageUploadState: ProductImageUploadState = this.createEmptyImageUploadState(); + imageUploadState: ProductImageUploadState = + this.createEmptyImageUploadState(); modelUploadFile: File | null = null; ngOnInit(): void { @@ -220,7 +221,10 @@ export class AdminShopComponent implements OnInit, OnDestroy { const targetProductId = preferredProductId ?? (this.productMode === 'edit' ? this.selectedProductId : null); - if (targetProductId && products.some((product) => product.id === targetProductId)) { + if ( + targetProductId && + products.some((product) => product.id === targetProductId) + ) { this.openProduct(targetProductId); return; } @@ -524,8 +528,7 @@ export class AdminShopComponent implements OnInit, OnDestroy { } addVariant(): void { - const sortOrder = - (this.productForm.variants.at(-1)?.sortOrder ?? -1) + 1; + const sortOrder = (this.productForm.variants.at(-1)?.sortOrder ?? -1) + 1; const firstVariant = this.productForm.variants.length === 0; this.productForm.variants = [ ...this.productForm.variants, @@ -621,11 +624,17 @@ export class AdminShopComponent implements OnInit, OnDestroy { } deleteModel(): void { - if (!this.selectedProductId || this.deletingModel || !this.selectedProduct?.model3d) { + if ( + !this.selectedProductId || + this.deletingModel || + !this.selectedProduct?.model3d + ) { return; } - if (!window.confirm('Rimuovere il modello 3D associato a questo prodotto?')) { + if ( + !window.confirm('Rimuovere il modello 3D associato a questo prodotto?') + ) { return; } @@ -709,11 +718,15 @@ export class AdminShopComponent implements OnInit, OnDestroy { } getActiveImageTranslation(): AdminMediaTranslation { - return this.imageUploadState.translations[this.imageUploadState.activeLanguage]; + return this.imageUploadState.translations[ + this.imageUploadState.activeLanguage + ]; } isImageLanguageComplete(language: AdminMediaLanguage): boolean { - return this.isTranslationComplete(this.imageUploadState.translations[language]); + return this.isTranslationComplete( + this.imageUploadState.translations[language], + ); } uploadProductImage(): void { @@ -930,7 +943,8 @@ export class AdminShopComponent implements OnInit, OnDestroy { const searchNeedle = this.productSearchTerm.trim().toLowerCase(); this.filteredProducts = this.products.filter((product) => { const matchesCategory = - this.categoryFilter === 'ALL' || product.categoryId === this.categoryFilter; + this.categoryFilter === 'ALL' || + product.categoryId === this.categoryFilter; const matchesStatus = this.productStatusFilter === 'ALL' || (this.productStatusFilter === 'ACTIVE' && product.isActive) || @@ -1215,7 +1229,10 @@ export class AdminShopComponent implements OnInit, OnDestroy { if (!Number.isFinite(price) || price < 0) { return `La variante "${variant.colorName.trim()}" ha un prezzo non valido.`; } - if (variant.colorHex.trim() && !/^#[0-9A-Fa-f]{6}$/.test(variant.colorHex.trim())) { + if ( + variant.colorHex.trim() && + !/^#[0-9A-Fa-f]{6}$/.test(variant.colorHex.trim()) + ) { return `La variante "${variant.colorName.trim()}" ha un colore HEX non valido.`; } if (variant.isDefault) { @@ -1426,7 +1443,10 @@ export class AdminShopComponent implements OnInit, OnDestroy { } private deriveDefaultTitle(filename: string): string { - return filename.replace(/\.[^.]+$/, '').replace(/[-_]+/g, ' ').trim(); + return filename + .replace(/\.[^.]+$/, '') + .replace(/[-_]+/g, ' ') + .trim(); } private optionalValue(value: string): string | undefined { @@ -1445,7 +1465,9 @@ export class AdminShopComponent implements OnInit, OnDestroy { private resolveFileExtension(filename: string): string { const lastDotIndex = filename.lastIndexOf('.'); - return lastDotIndex >= 0 ? filename.slice(lastDotIndex + 1).toLowerCase() : ''; + return lastDotIndex >= 0 + ? filename.slice(lastDotIndex + 1).toLowerCase() + : ''; } private isAllowedImageType(mimeType: string, filename: string): boolean { diff --git a/frontend/src/app/features/admin/services/admin-shop.service.ts b/frontend/src/app/features/admin/services/admin-shop.service.ts index f91b580..2e73890 100644 --- a/frontend/src/app/features/admin/services/admin-shop.service.ts +++ b/frontend/src/app/features/admin/services/admin-shop.service.ts @@ -152,7 +152,8 @@ export interface AdminShopProduct { updatedAt: string; } -export interface AdminShopMediaUsage extends Omit { +export interface AdminShopMediaUsage + extends Omit { translations: Record; } @@ -214,9 +215,12 @@ export class AdminShopService { } getCategoryTree(): Observable { - return this.http.get(`${this.categoriesBaseUrl}/tree`, { - withCredentials: true, - }); + return this.http.get( + `${this.categoriesBaseUrl}/tree`, + { + withCredentials: true, + }, + ); } getCategory(categoryId: string): Observable { @@ -258,9 +262,12 @@ export class AdminShopService { } getProduct(productId: string): Observable { - return this.http.get(`${this.productsBaseUrl}/${productId}`, { - withCredentials: true, - }); + return this.http.get( + `${this.productsBaseUrl}/${productId}`, + { + withCredentials: true, + }, + ); } createProduct( @@ -288,7 +295,10 @@ export class AdminShopService { }); } - uploadProductModel(productId: string, file: File): Observable { + uploadProductModel( + productId: string, + file: File, + ): Observable { const formData = new FormData(); formData.append('file', file); return this.http.post( @@ -299,9 +309,12 @@ export class AdminShopService { } deleteProductModel(productId: string): Observable { - return this.http.delete(`${this.productsBaseUrl}/${productId}/model`, { - withCredentials: true, - }); + return this.http.delete( + `${this.productsBaseUrl}/${productId}/model`, + { + withCredentials: true, + }, + ); } listMediaAssets(): Observable { diff --git a/frontend/src/app/features/shop/components/product-card/product-card.component.scss b/frontend/src/app/features/shop/components/product-card/product-card.component.scss index 6d7c427..ddda67b 100644 --- a/frontend/src/app/features/shop/components/product-card/product-card.component.scss +++ b/frontend/src/app/features/shop/components/product-card/product-card.component.scss @@ -40,7 +40,11 @@ align-items: flex-end; padding: var(--space-5); background: - radial-gradient(circle at top right, rgba(250, 207, 10, 0.24), transparent 36%), + radial-gradient( + circle at top right, + rgba(250, 207, 10, 0.24), + transparent 36% + ), linear-gradient(160deg, #f7f4ed 0%, #ece7db 100%); } diff --git a/frontend/src/app/features/shop/product-detail.component.scss b/frontend/src/app/features/shop/product-detail.component.scss index 312893f..e5d66af 100644 --- a/frontend/src/app/features/shop/product-detail.component.scss +++ b/frontend/src/app/features/shop/product-detail.component.scss @@ -56,7 +56,11 @@ align-items: flex-end; padding: var(--space-6); background: - radial-gradient(circle at top right, rgba(250, 207, 10, 0.24), transparent 34%), + radial-gradient( + circle at top right, + rgba(250, 207, 10, 0.24), + transparent 34% + ), linear-gradient(160deg, #f8f4ea 0%, #eee8db 100%); } @@ -314,13 +318,12 @@ h1 { } .skeleton-block { - background: - linear-gradient( - 110deg, - rgba(255, 255, 255, 0.7) 8%, - rgba(238, 235, 226, 0.95) 18%, - rgba(255, 255, 255, 0.7) 33% - ); + background: linear-gradient( + 110deg, + rgba(255, 255, 255, 0.7) 8%, + rgba(238, 235, 226, 0.95) 18%, + rgba(255, 255, 255, 0.7) 33% + ); background-size: 220% 100%; animation: skeleton 1.35s linear infinite; } diff --git a/frontend/src/app/features/shop/shop-page.component.html b/frontend/src/app/features/shop/shop-page.component.html index 7d9a814..dc63721 100644 --- a/frontend/src/app/features/shop/shop-page.component.html +++ b/frontend/src/app/features/shop/shop-page.component.html @@ -4,7 +4,9 @@

{{ selectedCategory() - ? (selectedCategory()?.description || ("SHOP.CATEGORY_META" | translate: { count: selectedCategory()?.productCount || 0 })) + ? selectedCategory()?.description || + ("SHOP.CATEGORY_META" + | translate: { count: selectedCategory()?.productCount || 0 }) : ("SHOP.CUSTOM_PART_CTA" | translate) }}

@@ -20,7 +22,9 @@
-

{{ "SHOP.CATEGORY_PANEL_KICKER" | translate }}

+

+ {{ "SHOP.CATEGORY_PANEL_KICKER" | translate }} +

{{ "SHOP.CATEGORY_PANEL_TITLE" | translate }}

@@ -100,7 +104,9 @@