diff --git a/frontend/src/app/features/calculator/calculator-page.component.ts b/frontend/src/app/features/calculator/calculator-page.component.ts index 6737d44..1bfdff4 100644 --- a/frontend/src/app/features/calculator/calculator-page.component.ts +++ b/frontend/src/app/features/calculator/calculator-page.component.ts @@ -203,6 +203,13 @@ export class CalculatorPageComponent implements OnInit { this.uploadForm.patchSettings(session); items.forEach((item, index) => { + // Preserve persisted quantities when restoring from session. + // Without this, setFiles() defaults every item back to 1. + this.uploadForm.updateItemQuantityByIndex( + index, + Number(item.quantity || 1), + ); + const tracked = this.toTrackedSettingsFromSessionItem( item, this.toTrackedSettingsFromSession(session), 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 ac81350..e656156 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 @@ -1,17 +1,14 @@

{{ "CALC.RESULT" | translate }}

- -
- - {{ costBreakdown().subtotal | currency: result().currency }} - + +
{{ totals().hours }}h {{ totals().minutes }}m @@ -96,25 +93,6 @@ }
-
-
- Costo di Avvio - {{ costBreakdown().baseSetup | currency: result().currency }} -
- @if (costBreakdown().nozzleChange > 0) { -
- Cambio Ugello - {{ - costBreakdown().nozzleChange | currency: result().currency - }} -
- } -
- Totale - {{ costBreakdown().total | currency: result().currency }} -
-
-
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 6ab0848..ade1db5 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 @@ -122,9 +122,6 @@ gap: var(--space-4); } } -.full-width { - grid-column: span 2; -} .setup-note { text-align: center; @@ -205,35 +202,3 @@ color: #6f5b1a; font-size: 0.9rem; } - -.cost-breakdown { - margin-top: var(--space-2); - margin-bottom: var(--space-4); - padding: var(--space-4); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); - background: var(--color-neutral-100); -} - -.cost-row, -.cost-total { - display: flex; - justify-content: space-between; - align-items: center; - gap: var(--space-3); -} - -.cost-row { - color: var(--color-text); - font-size: 0.95rem; - margin-bottom: var(--space-2); -} - -.cost-total { - margin-top: var(--space-3); - padding-top: var(--space-3); - border-top: 2px solid var(--color-border); - font-size: 1.2rem; - font-weight: 700; - color: var(--color-text); -} 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 939b5ad..5121b07 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 @@ -13,6 +13,10 @@ import { TranslateModule } from '@ngx-translate/core'; import { AppCardComponent } from '../../../../shared/components/app-card/app-card.component'; import { AppButtonComponent } from '../../../../shared/components/app-button/app-button.component'; import { SummaryCardComponent } from '../../../../shared/components/summary-card/summary-card.component'; +import { + PriceBreakdownComponent, + PriceBreakdownRow, +} from '../../../../shared/components/price-breakdown/price-breakdown.component'; import { QuoteResult, QuoteItem } from '../../services/quote-estimator.service'; @Component({ @@ -25,6 +29,7 @@ import { QuoteResult, QuoteItem } from '../../services/quote-estimator.service'; AppCardComponent, AppButtonComponent, SummaryCardComponent, + PriceBreakdownComponent, ], templateUrl: './quote-result.component.html', styleUrl: './quote-result.component.scss', @@ -159,6 +164,26 @@ export class QuoteResultComponent implements OnDestroy { }; }); + priceBreakdownRows = computed(() => { + const breakdown = this.costBreakdown(); + + return [ + { + labelKey: 'CHECKOUT.SUBTOTAL', + amount: breakdown.subtotal, + }, + { + labelKey: 'CHECKOUT.SETUP_FEE', + amount: breakdown.baseSetup, + }, + { + label: 'Cambio Ugello', + amount: breakdown.nozzleChange, + visible: breakdown.nozzleChange > 0, + }, + ]; + }); + totals = computed(() => { const currentItems = this.items(); let time = 0; diff --git a/frontend/src/app/features/calculator/components/upload-form/upload-form.component.html b/frontend/src/app/features/calculator/components/upload-form/upload-form.component.html index d2e9417..064d3d1 100644 --- a/frontend/src/app/features/calculator/components/upload-form/upload-form.component.html +++ b/frontend/src/app/features/calculator/components/upload-form/upload-form.component.html @@ -49,7 +49,7 @@ type="number" min="1" [value]="item.quantity" - (change)="updateItemQuantity(i, $event)" + (input)="updateItemQuantity(i, $event)" class="qty-input" (click)="$event.stopPropagation()" /> diff --git a/frontend/src/app/features/calculator/components/upload-form/upload-form.component.ts b/frontend/src/app/features/calculator/components/upload-form/upload-form.component.ts index c0975eb..f86408e 100644 --- a/frontend/src/app/features/calculator/components/upload-form/upload-form.component.ts +++ b/frontend/src/app/features/calculator/components/upload-form/upload-form.component.ts @@ -138,7 +138,7 @@ export class UploadFormComponent implements OnInit { layerHeight: [0.2, [Validators.min(0.05), Validators.max(1.0)]], nozzleDiameter: [0.4, Validators.required], infillPattern: ['grid', Validators.required], - supportEnabled: [false], + supportEnabled: [true], }); this.form.get('material')?.valueChanges.subscribe((value) => { @@ -189,6 +189,19 @@ export class UploadFormComponent implements OnInit { this.emitPrintSettingsChange(); this.emitItemSettingsDiffChange(); }); + + effect(() => { + if (this.mode() !== 'advanced') { + return; + } + + if (this.items().length > 0 || this.sameSettingsForAll()) { + return; + } + + this.sameSettingsForAll.set(true); + this.form.get('syncAllItems')?.setValue(true, { emitEvent: false }); + }); } ngOnInit() { @@ -408,10 +421,6 @@ export class UploadFormComponent implements OnInit { this.items.update((current) => { if (index >= current.length) return current; - if (this.sameSettingsForAll()) { - return current.map((item) => ({ ...item, quantity: normalizedQty })); - } - return current.map((item, idx) => idx === index ? { ...item, quantity: normalizedQty } : item, ); @@ -426,10 +435,6 @@ export class UploadFormComponent implements OnInit { let matched = false; return current.map((item) => { - if (this.sameSettingsForAll()) { - return { ...item, quantity: normalizedQty }; - } - if (!matched && this.normalizeFileName(item.file.name) === targetName) { matched = true; return { ...item, quantity: normalizedQty }; diff --git a/frontend/src/app/features/checkout/checkout.component.html b/frontend/src/app/features/checkout/checkout.component.html index 5cda654..6bec838 100644 --- a/frontend/src/app/features/checkout/checkout.component.html +++ b/frontend/src/app/features/checkout/checkout.component.html @@ -323,34 +323,13 @@
-
-
- {{ "CHECKOUT.SUBTOTAL" | translate }} - {{ session.itemsTotalChf | currency: "CHF" }} -
-
- {{ "CHECKOUT.SETUP_FEE" | translate }} - {{ - session.baseSetupCostChf ?? session.session.setupCostChf - | currency: "CHF" - }} -
-
- Cambio Ugello - {{ session.nozzleChangeCostChf | currency: "CHF" }} -
-
- {{ "CHECKOUT.SHIPPING" | translate }} - {{ session.shippingCostChf | currency: "CHF" }} -
-
- {{ "CHECKOUT.TOTAL" | translate }} - {{ session.grandTotalChf | currency: "CHF" }} -
-
+
diff --git a/frontend/src/app/features/checkout/checkout.component.scss b/frontend/src/app/features/checkout/checkout.component.scss index d757b5f..81b550f 100644 --- a/frontend/src/app/features/checkout/checkout.component.scss +++ b/frontend/src/app/features/checkout/checkout.component.scss @@ -355,32 +355,6 @@ app-toggle-selector.user-type-selector-compact { padding-right: var(--space-3); } -.summary-totals { - background: var(--color-neutral-100); - padding: var(--space-4); - border-radius: var(--radius-md); - margin-top: var(--space-6); - - .total-row { - display: flex; - justify-content: space-between; - margin-bottom: var(--space-2); - font-size: 0.95rem; - color: var(--color-text); - } - - .grand-total { - display: flex; - justify-content: space-between; - color: var(--color-text); - font-weight: 700; - font-size: 1.5rem; - margin-top: var(--space-4); - padding-top: var(--space-4); - border-top: 2px solid var(--color-border); - } -} - .actions { margin-top: var(--space-8); diff --git a/frontend/src/app/features/checkout/checkout.component.ts b/frontend/src/app/features/checkout/checkout.component.ts index 72ab9a3..039a8e2 100644 --- a/frontend/src/app/features/checkout/checkout.component.ts +++ b/frontend/src/app/features/checkout/checkout.component.ts @@ -16,6 +16,10 @@ import { AppToggleSelectorComponent, ToggleOption, } from '../../shared/components/app-toggle-selector/app-toggle-selector.component'; +import { + PriceBreakdownComponent, + PriceBreakdownRow, +} from '../../shared/components/price-breakdown/price-breakdown.component'; import { LanguageService } from '../../core/services/language.service'; import { StlViewerComponent } from '../../shared/components/stl-viewer/stl-viewer.component'; import { getColorHex } from '../../core/constants/colors.const'; @@ -31,6 +35,7 @@ import { getColorHex } from '../../core/constants/colors.const'; AppButtonComponent, AppCardComponent, AppToggleSelectorComponent, + PriceBreakdownComponent, StlViewerComponent, ], templateUrl: './checkout.component.html', @@ -197,6 +202,29 @@ export class CheckoutComponent implements OnInit { return this.quoteSession()?.cadTotalChf ?? 0; } + checkoutPriceBreakdownRows(session: any): PriceBreakdownRow[] { + return [ + { + labelKey: 'CHECKOUT.SUBTOTAL', + amount: session?.itemsTotalChf ?? 0, + }, + { + labelKey: 'CHECKOUT.SETUP_FEE', + amount: + session?.baseSetupCostChf ?? session?.session?.setupCostChf ?? 0, + }, + { + label: 'Cambio Ugello', + amount: session?.nozzleChangeCostChf ?? 0, + visible: (session?.nozzleChangeCostChf ?? 0) > 0, + }, + { + labelKey: 'CHECKOUT.SHIPPING', + amount: session?.shippingCostChf ?? 0, + }, + ]; + } + itemMaterial(item: any): string { return String( item?.materialCode ?? this.quoteSession()?.session?.materialCode ?? '-', diff --git a/frontend/src/app/features/order/order.component.html b/frontend/src/app/features/order/order.component.html index 747be86..c2548ee 100644 --- a/frontend/src/app/features/order/order.component.html +++ b/frontend/src/app/features/order/order.component.html @@ -193,28 +193,12 @@

#{{ getDisplayOrderNumber(o) }}

-
-
- {{ "PAYMENT.SUBTOTAL" | translate }} - {{ o.subtotalChf | currency: "CHF" }} -
-
- Servizio CAD ({{ o.cadHours || 0 }}h) - {{ o.cadTotalChf | currency: "CHF" }} -
-
- {{ "PAYMENT.SHIPPING" | translate }} - {{ o.shippingCostChf | currency: "CHF" }} -
-
- {{ "PAYMENT.SETUP_FEE" | translate }} - {{ o.setupCostChf | currency: "CHF" }} -
-
- {{ "PAYMENT.TOTAL" | translate }} - {{ o.totalChf | currency: "CHF" }} -
-
+
diff --git a/frontend/src/app/features/order/order.component.scss b/frontend/src/app/features/order/order.component.scss index 766fa4b..d8d275a 100644 --- a/frontend/src/app/features/order/order.component.scss +++ b/frontend/src/app/features/order/order.component.scss @@ -184,31 +184,6 @@ top: var(--space-6); } -.summary-totals { - background: var(--color-neutral-100); - padding: var(--space-6); - border-radius: var(--radius-md); - - .total-row { - display: flex; - justify-content: space-between; - margin-bottom: var(--space-2); - font-size: 0.95rem; - color: var(--color-text-muted); - } - - .grand-total-row { - display: flex; - justify-content: space-between; - color: var(--color-text); - font-weight: 700; - font-size: 1.5rem; - margin-top: var(--space-4); - padding-top: var(--space-4); - border-top: 2px solid var(--color-border); - } -} - .actions { margin-top: var(--space-8); } diff --git a/frontend/src/app/features/order/order.component.ts b/frontend/src/app/features/order/order.component.ts index 3ad4c05..18d8b86 100644 --- a/frontend/src/app/features/order/order.component.ts +++ b/frontend/src/app/features/order/order.component.ts @@ -6,6 +6,10 @@ import { AppCardComponent } from '../../shared/components/app-card/app-card.comp import { QuoteEstimatorService } from '../calculator/services/quote-estimator.service'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { environment } from '../../../environments/environment'; +import { + PriceBreakdownComponent, + PriceBreakdownRow, +} from '../../shared/components/price-breakdown/price-breakdown.component'; @Component({ selector: 'app-order', @@ -15,6 +19,7 @@ import { environment } from '../../../environments/environment'; AppButtonComponent, AppCardComponent, TranslateModule, + PriceBreakdownComponent, ], templateUrl: './order.component.html', styleUrl: './order.component.scss', @@ -171,6 +176,28 @@ export class OrderComponent implements OnInit { return this.translate.instant('ORDER.NOT_AVAILABLE'); } + orderPriceBreakdownRows(order: any): PriceBreakdownRow[] { + return [ + { + labelKey: 'PAYMENT.SUBTOTAL', + amount: order?.subtotalChf ?? 0, + }, + { + label: `Servizio CAD (${order?.cadHours || 0}h)`, + amount: order?.cadTotalChf ?? 0, + visible: (order?.cadTotalChf ?? 0) > 0, + }, + { + labelKey: 'PAYMENT.SHIPPING', + amount: order?.shippingCostChf ?? 0, + }, + { + labelKey: 'PAYMENT.SETUP_FEE', + amount: order?.setupCostChf ?? 0, + }, + ]; + } + private extractOrderNumber(orderId: string): string { return orderId.split('-')[0]; } diff --git a/frontend/src/app/shared/components/price-breakdown/price-breakdown.component.html b/frontend/src/app/shared/components/price-breakdown/price-breakdown.component.html new file mode 100644 index 0000000..c4269f8 --- /dev/null +++ b/frontend/src/app/shared/components/price-breakdown/price-breakdown.component.html @@ -0,0 +1,25 @@ +
+
+ + + {{ row.label }} + + + {{ row.labelKey ? (row.labelKey | translate) : "" }} + + + {{ row.amount | currency: currency() }} +
+ +
+ + + {{ totalLabel() }} + + + {{ totalLabelKey() ? (totalLabelKey() | translate) : "" }} + + + {{ total() | currency: currency() }} +
+
diff --git a/frontend/src/app/shared/components/price-breakdown/price-breakdown.component.scss b/frontend/src/app/shared/components/price-breakdown/price-breakdown.component.scss new file mode 100644 index 0000000..d7e13b7 --- /dev/null +++ b/frontend/src/app/shared/components/price-breakdown/price-breakdown.component.scss @@ -0,0 +1,31 @@ +.price-breakdown { + margin-top: var(--space-2); + margin-bottom: var(--space-4); + padding: var(--space-4); + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background: var(--color-neutral-100); +} + +.price-row, +.price-total { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--space-3); +} + +.price-row { + color: var(--color-text); + font-size: 0.95rem; + margin-bottom: var(--space-2); +} + +.price-total { + margin-top: var(--space-3); + padding-top: var(--space-3); + border-top: 2px solid var(--color-border); + font-size: 1.2rem; + font-weight: 700; + color: var(--color-text); +} diff --git a/frontend/src/app/shared/components/price-breakdown/price-breakdown.component.ts b/frontend/src/app/shared/components/price-breakdown/price-breakdown.component.ts new file mode 100644 index 0000000..9db3ba3 --- /dev/null +++ b/frontend/src/app/shared/components/price-breakdown/price-breakdown.component.ts @@ -0,0 +1,29 @@ +import { CommonModule } from '@angular/common'; +import { Component, computed, input } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; + +export interface PriceBreakdownRow { + label?: string; + labelKey?: string; + amount: number; + visible?: boolean; +} + +@Component({ + selector: 'app-price-breakdown', + standalone: true, + imports: [CommonModule, TranslateModule], + templateUrl: './price-breakdown.component.html', + styleUrl: './price-breakdown.component.scss', +}) +export class PriceBreakdownComponent { + rows = input([]); + total = input.required(); + currency = input('CHF'); + totalLabel = input(''); + totalLabelKey = input(''); + + visibleRows = computed(() => + this.rows().filter((row) => row.visible === undefined || row.visible), + ); +}