From a4b85b01bd6c5c8faf35f16af699bec6bb3def88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joe=20K=C3=BCng?= Date: Thu, 5 Mar 2026 15:02:26 +0100 Subject: [PATCH] feat(front-end): calculator improvements --- frontend/src/app/app.component.ts | 7 ++- frontend/src/app/app.routes.ts | 46 +++++++++++++++++++ .../src/app/features/about/about.routes.ts | 10 +++- .../features/calculator/calculator.routes.ts | 18 +++++++- .../quote-result/quote-result.component.html | 16 ++++++- .../quote-result/quote-result.component.scss | 11 +++++ .../quote-result/quote-result.component.ts | 14 ++++++ .../app/features/contact/contact.routes.ts | 5 ++ .../src/app/features/home/home.component.html | 11 +++-- .../src/app/features/legal/legal.routes.ts | 10 ++++ frontend/src/app/features/shop/shop.routes.ts | 20 +++++++- frontend/src/index.html | 8 +++- 12 files changed, 162 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index cb93b2a..53a2fdb 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -1,5 +1,6 @@ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { RouterOutlet } from '@angular/router'; +import { SeoService } from './core/services/seo.service'; @Component({ selector: 'app-root', @@ -8,4 +9,6 @@ import { RouterOutlet } from '@angular/router'; templateUrl: './app.component.html', styleUrl: './app.component.scss', }) -export class AppComponent {} +export class AppComponent { + private readonly seoService = inject(SeoService); +} diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 2b5024f..c423e3b 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -5,6 +5,11 @@ const appChildRoutes: Routes = [ path: '', loadComponent: () => import('./features/home/home.component').then((m) => m.HomeComponent), + data: { + seoTitle: '3D fab | Stampa 3D su misura', + seoDescription: + 'Servizio di stampa 3D con preventivo online immediato per prototipi, piccole serie e pezzi personalizzati.', + }, }, { path: 'calculator', @@ -12,21 +17,42 @@ const appChildRoutes: Routes = [ import('./features/calculator/calculator.routes').then( (m) => m.CALCULATOR_ROUTES, ), + data: { + seoTitle: 'Calcolatore preventivo stampa 3D | 3D fab', + seoDescription: + 'Carica il file 3D e ottieni prezzo e tempi in pochi secondi con slicing reale.', + }, }, { path: 'shop', loadChildren: () => import('./features/shop/shop.routes').then((m) => m.SHOP_ROUTES), + data: { + seoTitle: 'Shop 3D fab', + seoDescription: + 'Catalogo prodotti stampati in 3D e soluzioni tecniche pronte all uso.', + seoRobots: 'noindex, nofollow', + }, }, { path: 'about', loadChildren: () => import('./features/about/about.routes').then((m) => m.ABOUT_ROUTES), + data: { + seoTitle: 'Chi siamo | 3D fab', + seoDescription: + 'Scopri il team 3D fab e il laboratorio di stampa 3D con sedi in Ticino e Bienne.', + }, }, { path: 'contact', loadChildren: () => import('./features/contact/contact.routes').then((m) => m.CONTACT_ROUTES), + data: { + seoTitle: 'Contatti | 3D fab', + seoDescription: + 'Contatta 3D fab per preventivi, supporto tecnico e richieste personalizzate di stampa 3D.', + }, }, { path: 'checkout/cad', @@ -34,6 +60,10 @@ const appChildRoutes: Routes = [ import('./features/checkout/checkout.component').then( (m) => m.CheckoutComponent, ), + data: { + seoTitle: 'Checkout | 3D fab', + seoRobots: 'noindex, nofollow', + }, }, { path: 'checkout', @@ -41,16 +71,28 @@ const appChildRoutes: Routes = [ import('./features/checkout/checkout.component').then( (m) => m.CheckoutComponent, ), + data: { + seoTitle: 'Checkout | 3D fab', + seoRobots: 'noindex, nofollow', + }, }, { path: 'order/:orderId', loadComponent: () => import('./features/order/order.component').then((m) => m.OrderComponent), + data: { + seoTitle: 'Ordine | 3D fab', + seoRobots: 'noindex, nofollow', + }, }, { path: 'co/:orderId', loadComponent: () => import('./features/order/order.component').then((m) => m.OrderComponent), + data: { + seoTitle: 'Ordine | 3D fab', + seoRobots: 'noindex, nofollow', + }, }, { path: '', @@ -61,6 +103,10 @@ const appChildRoutes: Routes = [ path: 'admin', loadChildren: () => import('./features/admin/admin.routes').then((m) => m.ADMIN_ROUTES), + data: { + seoTitle: 'Admin | 3D fab', + seoRobots: 'noindex, nofollow', + }, }, { path: '**', diff --git a/frontend/src/app/features/about/about.routes.ts b/frontend/src/app/features/about/about.routes.ts index 0128c8e..3d7d210 100644 --- a/frontend/src/app/features/about/about.routes.ts +++ b/frontend/src/app/features/about/about.routes.ts @@ -2,5 +2,13 @@ import { Routes } from '@angular/router'; import { AboutPageComponent } from './about-page.component'; export const ABOUT_ROUTES: Routes = [ - { path: '', component: AboutPageComponent }, + { + path: '', + component: AboutPageComponent, + data: { + seoTitle: 'Chi siamo | 3D fab', + seoDescription: + 'Siamo un laboratorio di stampa 3D orientato a prototipi, ricambi e produzioni su misura.', + }, + }, ]; diff --git a/frontend/src/app/features/calculator/calculator.routes.ts b/frontend/src/app/features/calculator/calculator.routes.ts index 5a670cd..134457b 100644 --- a/frontend/src/app/features/calculator/calculator.routes.ts +++ b/frontend/src/app/features/calculator/calculator.routes.ts @@ -3,10 +3,24 @@ import { CalculatorPageComponent } from './calculator-page.component'; export const CALCULATOR_ROUTES: Routes = [ { path: '', redirectTo: 'basic', pathMatch: 'full' }, - { path: 'basic', component: CalculatorPageComponent, data: { mode: 'easy' } }, + { + path: 'basic', + component: CalculatorPageComponent, + data: { + mode: 'easy', + seoTitle: 'Calcolatore stampa 3D base | 3D fab', + seoDescription: + 'Calcola rapidamente il prezzo della tua stampa 3D in modalita base.', + }, + }, { path: 'advanced', component: CalculatorPageComponent, - data: { mode: 'advanced' }, + data: { + mode: 'advanced', + seoTitle: 'Calcolatore stampa 3D avanzato | 3D fab', + seoDescription: + 'Configura parametri avanzati e ottieni un preventivo preciso con slicing reale.', + }, }, ]; diff --git a/frontend/src/app/features/calculator/components/quote-result/quote-result.component.html b/frontend/src/app/features/calculator/components/quote-result/quote-result.component.html index d7141b7..2c81b84 100644 --- a/frontend/src/app/features/calculator/components/quote-result/quote-result.component.html +++ b/frontend/src/app/features/calculator/components/quote-result/quote-result.component.html @@ -45,6 +45,12 @@

{{ result().notes }}

} + @if (recalculationRequired()) { +
+ Hai modificato i parametri di stampa. Ricalcola il preventivo prima di + procedere con l'ordine. +
+ }
@@ -104,7 +110,10 @@
@if (!hasQuantityOverLimit()) { - + {{ "QUOTE.PROCEED_ORDER" | translate }} } @else { @@ -112,6 +121,11 @@ "QUOTE.MAX_QTY_NOTICE" | translate: { max: directOrderLimit } }} } + @if (recalculationRequired()) { + + Ricalcola il preventivo per riattivare il checkout. + + }
diff --git a/frontend/src/app/features/calculator/components/quote-result/quote-result.component.scss b/frontend/src/app/features/calculator/components/quote-result/quote-result.component.scss index 38913fa..b4e94b2 100644 --- a/frontend/src/app/features/calculator/components/quote-result/quote-result.component.scss +++ b/frontend/src/app/features/calculator/components/quote-result/quote-result.component.scss @@ -184,3 +184,14 @@ white-space: pre-wrap; /* Preserve line breaks */ } } + +.recalc-banner { + margin-top: var(--space-4); + margin-bottom: var(--space-4); + padding: var(--space-3); + border: 1px solid #f0c95a; + background: #fff8e1; + border-radius: var(--radius-md); + color: #6f5b1a; + font-size: 0.9rem; +} diff --git a/frontend/src/app/features/calculator/components/quote-result/quote-result.component.ts b/frontend/src/app/features/calculator/components/quote-result/quote-result.component.ts index 5da71ec..ce0df34 100644 --- a/frontend/src/app/features/calculator/components/quote-result/quote-result.component.ts +++ b/frontend/src/app/features/calculator/components/quote-result/quote-result.component.ts @@ -35,6 +35,7 @@ export class QuoteResultComponent implements OnDestroy { readonly quantityAutoRefreshMs = 2000; result = input.required(); + recalculationRequired = input(false); consult = output(); proceed = output(); itemChange = output<{ @@ -43,6 +44,12 @@ export class QuoteResultComponent implements OnDestroy { fileName: string; quantity: number; }>(); + itemQuantityPreviewChange = output<{ + id?: string; + index: number; + fileName: string; + quantity: number; + }>(); // Local mutable state for items to handle quantity changes items = signal([]); @@ -87,6 +94,13 @@ export class QuoteResultComponent implements OnDestroy { return updated; }); + this.itemQuantityPreviewChange.emit({ + id: item.id, + index, + fileName: item.fileName, + quantity: normalizedQty, + }); + this.scheduleQuantityRefresh(index, key); } diff --git a/frontend/src/app/features/contact/contact.routes.ts b/frontend/src/app/features/contact/contact.routes.ts index f8ef5c5..6ee01b5 100644 --- a/frontend/src/app/features/contact/contact.routes.ts +++ b/frontend/src/app/features/contact/contact.routes.ts @@ -5,5 +5,10 @@ export const CONTACT_ROUTES: Routes = [ path: '', loadComponent: () => import('./contact-page.component').then((m) => m.ContactPageComponent), + data: { + seoTitle: 'Contatti | 3D fab', + seoDescription: + 'Richiedi informazioni, preventivi personalizzati o supporto per progetti di stampa 3D.', + }, }, ]; diff --git a/frontend/src/app/features/home/home.component.html b/frontend/src/app/features/home/home.component.html index 2255de4..6299a99 100644 --- a/frontend/src/app/features/home/home.component.html +++ b/frontend/src/app/features/home/home.component.html @@ -37,28 +37,31 @@
- + Prototipazione 3D

{{ "HOME.CAP_1_TITLE" | translate }}

{{ "HOME.CAP_1_TEXT" | translate }}

- + Confronto pezzo originale e stampato in 3D

{{ "HOME.CAP_2_TITLE" | translate }}

{{ "HOME.CAP_2_TEXT" | translate }}

- + Piccole serie stampate in 3D

{{ "HOME.CAP_3_TITLE" | translate }}

{{ "HOME.CAP_3_TEXT" | translate }}

- + Servizio CAD per stampa 3D

{{ "HOME.CAP_4_TITLE" | translate }}

{{ "HOME.CAP_4_TEXT" | translate }}

diff --git a/frontend/src/app/features/legal/legal.routes.ts b/frontend/src/app/features/legal/legal.routes.ts index 78cc23e..274bfd7 100644 --- a/frontend/src/app/features/legal/legal.routes.ts +++ b/frontend/src/app/features/legal/legal.routes.ts @@ -5,10 +5,20 @@ export const LEGAL_ROUTES: Routes = [ path: 'privacy', loadComponent: () => import('./privacy/privacy.component').then((m) => m.PrivacyComponent), + data: { + seoTitle: 'Privacy Policy | 3D fab', + seoDescription: + 'Informativa privacy di 3D fab: trattamento dati, finalita e contatti.', + }, }, { path: 'terms', loadComponent: () => import('./terms/terms.component').then((m) => m.TermsComponent), + data: { + seoTitle: 'Termini e condizioni | 3D fab', + seoDescription: + 'Termini e condizioni del servizio di stampa 3D e del calcolatore preventivi.', + }, }, ]; diff --git a/frontend/src/app/features/shop/shop.routes.ts b/frontend/src/app/features/shop/shop.routes.ts index 22a7fb3..b94949b 100644 --- a/frontend/src/app/features/shop/shop.routes.ts +++ b/frontend/src/app/features/shop/shop.routes.ts @@ -3,6 +3,22 @@ import { ShopPageComponent } from './shop-page.component'; import { ProductDetailComponent } from './product-detail.component'; export const SHOP_ROUTES: Routes = [ - { path: '', component: ShopPageComponent }, - { path: ':id', component: ProductDetailComponent }, + { + path: '', + component: ShopPageComponent, + data: { + seoTitle: 'Shop 3D fab', + seoDescription: + 'Lo shop 3D fab e in allestimento. Intanto puoi usare il calcolatore per ottenere un preventivo.', + seoRobots: 'noindex, nofollow', + }, + }, + { + path: ':id', + component: ProductDetailComponent, + data: { + seoTitle: 'Prodotto | 3D fab', + seoRobots: 'noindex, nofollow', + }, + }, ]; diff --git a/frontend/src/index.html b/frontend/src/index.html index 91a4429..140c33c 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -2,8 +2,12 @@ - - 3D fab + 3D fab | Stampa 3D su misura + +