feat(back-end and front-end): calculator improvements
All checks were successful
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / prettier-autofix (pull_request) Successful in 11s
PR Checks / test-frontend (pull_request) Successful in 1m0s

This commit is contained in:
2026-03-05 18:30:37 +01:00
parent 93b0b55f43
commit 235fe7780d
41 changed files with 3503 additions and 1280 deletions

View File

@@ -249,11 +249,13 @@
{{ "CHECKOUT.MATERIAL" | translate }}:
{{ itemMaterial(item) }}
</span>
<span
*ngIf="item.colorCode"
class="color-dot"
[style.background-color]="item.colorCode"
></span>
<span class="item-color" *ngIf="itemColorLabel(item) !== '-'">
<span
class="color-dot"
[style.background-color]="itemColorSwatch(item)"
></span>
<span class="color-name">{{ itemColorLabel(item) }}</span>
</span>
</div>
<div class="item-specs-sub">
{{ item.printTimeSeconds / 3600 | number: "1.1-1" }}h |
@@ -328,7 +330,14 @@
</div>
<div class="total-row">
<span>{{ "CHECKOUT.SETUP_FEE" | translate }}</span>
<span>{{ session.session.setupCostChf | currency: "CHF" }}</span>
<span>{{
(session.baseSetupCostChf ?? session.session.setupCostChf)
| currency: "CHF"
}}</span>
</div>
<div class="total-row" *ngIf="(session.nozzleChangeCostChf || 0) > 0">
<span>Cambio Ugello</span>
<span>{{ session.nozzleChangeCostChf | currency: "CHF" }}</span>
</div>
<div class="total-row">
<span>{{ "CHECKOUT.SHIPPING" | translate }}</span>

View File

@@ -230,12 +230,24 @@ app-toggle-selector.user-type-selector-compact {
font-size: 0.85rem;
color: var(--color-text-muted);
.item-color {
display: inline-flex;
align-items: center;
gap: 6px;
}
.color-dot {
width: 14px;
height: 14px;
border-radius: 50%;
display: inline-block;
border: 1px solid var(--color-border);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35);
}
.color-name {
font-weight: 500;
color: var(--color-text-muted);
}
}

View File

@@ -18,6 +18,7 @@ import {
} from '../../shared/components/app-toggle-selector/app-toggle-selector.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';
@Component({
selector: 'app-checkout',
@@ -55,6 +56,8 @@ export class CheckoutComponent implements OnInit {
selectedPreviewFile = signal<File | null>(null);
selectedPreviewName = signal('');
selectedPreviewColor = signal('#c9ced6');
private variantHexById = new Map<number, string>();
private variantHexByColorName = new Map<string, string>();
userTypeOptions: ToggleOption[] = [
{ label: 'CONTACT.TYPE_PRIVATE', value: 'PRIVATE' },
@@ -128,6 +131,8 @@ export class CheckoutComponent implements OnInit {
}
ngOnInit(): void {
this.loadMaterialColorPalette();
this.route.queryParams.subscribe((params) => {
this.sessionId = params['session'];
if (!this.sessionId) {
@@ -212,8 +217,40 @@ export class CheckoutComponent implements OnInit {
}
previewColor(item: any): string {
return this.itemColorSwatch(item);
}
itemColorLabel(item: any): string {
const raw = String(item?.colorCode ?? '').trim();
return raw || '#c9ced6';
return raw || '-';
}
itemColorSwatch(item: any): string {
const variantId = Number(item?.filamentVariantId);
if (Number.isFinite(variantId) && this.variantHexById.has(variantId)) {
return this.variantHexById.get(variantId)!;
}
const raw = String(item?.colorCode ?? '').trim();
if (!raw) {
return '#c9ced6';
}
if (this.isHexColor(raw)) {
return raw;
}
const byName = this.variantHexByColorName.get(raw.toLowerCase());
if (byName) {
return byName;
}
const fallback = getColorHex(raw);
if (fallback && fallback !== '#facf0a') {
return fallback;
}
return '#c9ced6';
}
isPreviewLoading(item: any): boolean {
@@ -250,6 +287,41 @@ export class CheckoutComponent implements OnInit {
this.selectedPreviewColor.set('#c9ced6');
}
private loadMaterialColorPalette(): void {
this.quoteService.getOptions().subscribe({
next: (options) => {
this.variantHexById.clear();
this.variantHexByColorName.clear();
for (const material of options?.materials || []) {
for (const variant of material?.variants || []) {
const variantId = Number(variant?.id);
const colorHex = String(variant?.hexColor || '').trim();
const colorName = String(variant?.colorName || '').trim();
if (Number.isFinite(variantId) && colorHex) {
this.variantHexById.set(variantId, colorHex);
}
if (colorName && colorHex) {
this.variantHexByColorName.set(colorName.toLowerCase(), colorHex);
}
}
}
},
error: () => {
this.variantHexById.clear();
this.variantHexByColorName.clear();
},
});
}
private isHexColor(value?: string): boolean {
return (
typeof value === 'string' &&
/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(value)
);
}
private loadStlPreviews(session: any): void {
if (
!this.sessionId ||