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"
- }}
-
-
0"
- >
- 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" }}
-
-
0">
- 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),
+ );
+}