fix(front-end): 3d view only for stl
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 33s
Build, Test and Deploy / build-and-push (push) Successful in 21s
Build, Test and Deploy / deploy (push) Successful in 8s

This commit is contained in:
2026-02-24 13:12:29 +01:00
parent a6eae757c5
commit 4ddd33662d
3 changed files with 41 additions and 41 deletions

View File

@@ -1,14 +1,14 @@
<form [formGroup]="form" (ngSubmit)="onSubmit()"> <form [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="section"> <div class="section">
@if (selectedFile()) { @if (selectedFile()) {
<div class="viewer-wrapper"> <div class="viewer-wrapper">
@if (isStepFile(selectedFile())) { @if (!isStepFile(selectedFile())) {
<div class="step-warning"> <div class="step-warning">
<p>{{ 'CALC.STEP_WARNING' | translate }}</p> <p>{{ 'CALC.STEP_WARNING' | translate }}</p>
</div> </div>
} @else { } @else {
<app-stl-viewer <app-stl-viewer
[file]="selectedFile()" [file]="selectedFile()"
[color]="getSelectedFileColor()"> [color]="getSelectedFileColor()">
</app-stl-viewer> </app-stl-viewer>
@@ -16,11 +16,11 @@
<!-- Close button removed as requested --> <!-- Close button removed as requested -->
</div> </div>
} }
<!-- Initial Dropzone (Visible only when no files) --> <!-- Initial Dropzone (Visible only when no files) -->
@if (items().length === 0) { @if (items().length === 0) {
<app-dropzone <app-dropzone
[label]="'CALC.UPLOAD_LABEL' | translate" [label]="'CALC.UPLOAD_LABEL' | translate"
[subtext]="'CALC.UPLOAD_SUB' | translate" [subtext]="'CALC.UPLOAD_SUB' | translate"
[accept]="acceptedFormats" [accept]="acceptedFormats"
[multiple]="true" [multiple]="true"
@@ -36,14 +36,14 @@
<div class="card-header"> <div class="card-header">
<span class="file-name" [title]="item.file.name">{{ item.file.name }}</span> <span class="file-name" [title]="item.file.name">{{ item.file.name }}</span>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="card-controls"> <div class="card-controls">
<div class="qty-group"> <div class="qty-group">
<label>{{ 'CALC.QTY_SHORT' | translate }}</label> <label>{{ 'CALC.QTY_SHORT' | translate }}</label>
<input <input
type="number" type="number"
min="1" min="1"
[value]="item.quantity" [value]="item.quantity"
(change)="updateItemQuantity(i, $event)" (change)="updateItemQuantity(i, $event)"
class="qty-input" class="qty-input"
@@ -52,14 +52,14 @@
<div class="color-group"> <div class="color-group">
<label>{{ 'CALC.COLOR_LABEL' | translate }}</label> <label>{{ 'CALC.COLOR_LABEL' | translate }}</label>
<app-color-selector <app-color-selector
[selectedColor]="item.color" [selectedColor]="item.color"
[variants]="currentMaterialVariants()" [variants]="currentMaterialVariants()"
(colorSelected)="updateItemColor(i, $event)"> (colorSelected)="updateItemColor(i, $event)">
</app-color-selector> </app-color-selector>
</div> </div>
</div> </div>
<button type="button" class="btn-remove" (click)="removeItem(i); $event.stopPropagation()" title="Remove file"> <button type="button" class="btn-remove" (click)="removeItem(i); $event.stopPropagation()" title="Remove file">
X X
</button> </button>
@@ -67,17 +67,17 @@
</div> </div>
} }
</div> </div>
<!-- "Add Files" Button (Visible only when files exist) --> <!-- "Add Files" Button (Visible only when files exist) -->
<div class="add-more-container"> <div class="add-more-container">
<input #additionalInput type="file" [accept]="acceptedFormats" multiple hidden (change)="onAdditionalFilesSelected($event)"> <input #additionalInput type="file" [accept]="acceptedFormats" multiple hidden (change)="onAdditionalFilesSelected($event)">
<button type="button" class="btn-add-more" (click)="additionalInput.click()"> <button type="button" class="btn-add-more" (click)="additionalInput.click()">
+ {{ 'CALC.ADD_FILES' | translate }} + {{ 'CALC.ADD_FILES' | translate }}
</button> </button>
</div> </div>
} }
@if (items().length === 0 && form.get('itemsTouched')?.value) { @if (items().length === 0 && form.get('itemsTouched')?.value) {
<div class="error-msg">{{ 'CALC.ERR_FILE_REQUIRED' | translate }}</div> <div class="error-msg">{{ 'CALC.ERR_FILE_REQUIRED' | translate }}</div>
} }
@@ -104,7 +104,7 @@
></app-select> ></app-select>
} }
</div> </div>
<!-- Global quantity removed, now per item --> <!-- Global quantity removed, now per item -->
@if (mode() === 'advanced') { @if (mode() === 'advanced') {
@@ -128,7 +128,7 @@
type="number" type="number"
[label]="'CALC.INFILL' | translate" [label]="'CALC.INFILL' | translate"
></app-input> ></app-input>
<div class="checkbox-row"> <div class="checkbox-row">
<input type="checkbox" formControlName="supportEnabled" id="support"> <input type="checkbox" formControlName="supportEnabled" id="support">
<label for="support">{{ 'CALC.SUPPORT' | translate }}</label> <label for="support">{{ 'CALC.SUPPORT' | translate }}</label>
@@ -153,9 +153,9 @@
</div> </div>
} }
<app-button <app-button
type="submit" type="submit"
[disabled]="items().length === 0 || loading()" [disabled]="items().length === 0 || loading()"
[fullWidth]="true"> [fullWidth]="true">
{{ loading() ? (uploadProgress() < 100 ? ('CALC.UPLOADING' | translate) : ('CALC.PROCESSING' | translate)) : ('CALC.CALCULATE' | translate) }} {{ loading() ? (uploadProgress() < 100 ? ('CALC.UPLOADING' | translate) : ('CALC.PROCESSING' | translate)) : ('CALC.CALCULATE' | translate) }}
</app-button> </app-button>

View File

@@ -34,7 +34,7 @@ export class UploadFormComponent implements OnInit {
private fb = inject(FormBuilder); private fb = inject(FormBuilder);
form: FormGroup; form: FormGroup;
items = signal<FormItem[]>([]); items = signal<FormItem[]>([]);
selectedFile = signal<File | null>(null); selectedFile = signal<File | null>(null);
@@ -44,13 +44,13 @@ export class UploadFormComponent implements OnInit {
nozzleDiameters = signal<SimpleOption[]>([]); nozzleDiameters = signal<SimpleOption[]>([]);
infillPatterns = signal<SimpleOption[]>([]); infillPatterns = signal<SimpleOption[]>([]);
layerHeights = signal<SimpleOption[]>([]); layerHeights = signal<SimpleOption[]>([]);
// Store full material options to lookup variants/colors if needed later // Store full material options to lookup variants/colors if needed later
private fullMaterialOptions: MaterialOption[] = []; private fullMaterialOptions: MaterialOption[] = [];
// Computed variants for valid material // Computed variants for valid material
currentMaterialVariants = signal<VariantOption[]>([]); currentMaterialVariants = signal<VariantOption[]>([]);
private updateVariants() { private updateVariants() {
const matCode = this.form.get('material')?.value; const matCode = this.form.get('material')?.value;
if (matCode && this.fullMaterialOptions.length > 0) { if (matCode && this.fullMaterialOptions.length > 0) {
@@ -66,7 +66,7 @@ export class UploadFormComponent implements OnInit {
isStepFile(file: File | null): boolean { isStepFile(file: File | null): boolean {
if (!file) return false; if (!file) return false;
const name = file.name.toLowerCase(); const name = file.name.toLowerCase();
return name.endsWith('.step') || name.endsWith('.stp'); return name.endsWith('.stl');
} }
constructor() { constructor() {
@@ -83,7 +83,7 @@ export class UploadFormComponent implements OnInit {
infillPattern: ['grid'], infillPattern: ['grid'],
supportEnabled: [false] supportEnabled: [false]
}); });
// Listen to material changes to update variants // Listen to material changes to update variants
this.form.get('material')?.valueChanges.subscribe(() => { this.form.get('material')?.valueChanges.subscribe(() => {
this.updateVariants(); this.updateVariants();
@@ -95,7 +95,7 @@ export class UploadFormComponent implements OnInit {
next: (options: OptionsResponse) => { next: (options: OptionsResponse) => {
this.fullMaterialOptions = options.materials; this.fullMaterialOptions = options.materials;
this.updateVariants(); // Trigger initial update this.updateVariants(); // Trigger initial update
this.materials.set(options.materials.map(m => ({ label: m.label, value: m.code }))); this.materials.set(options.materials.map(m => ({ label: m.label, value: m.code })));
this.qualities.set(options.qualities.map(q => ({ label: q.label, value: q.id }))); this.qualities.set(options.qualities.map(q => ({ label: q.label, value: q.id })));
this.infillPatterns.set(options.infillPatterns.map(p => ({ label: p.label, value: p.id }))); this.infillPatterns.set(options.infillPatterns.map(p => ({ label: p.label, value: p.id })));
@@ -194,7 +194,7 @@ export class UploadFormComponent implements OnInit {
getSelectedFileColor(): string { getSelectedFileColor(): string {
const file = this.selectedFile(); const file = this.selectedFile();
if (!file) return '#facf0a'; // Default if (!file) return '#facf0a'; // Default
const item = this.items().find(i => i.file === file); const item = this.items().find(i => i.file === file);
if (item) { if (item) {
const vars = this.currentMaterialVariants(); const vars = this.currentMaterialVariants();
@@ -211,7 +211,7 @@ export class UploadFormComponent implements OnInit {
const input = event.target as HTMLInputElement; const input = event.target as HTMLInputElement;
let val = parseInt(input.value, 10); let val = parseInt(input.value, 10);
if (isNaN(val) || val < 1) val = 1; if (isNaN(val) || val < 1) val = 1;
this.items.update(current => { this.items.update(current => {
const updated = [...current]; const updated = [...current];
updated[index] = { ...updated[index], quantity: val }; updated[index] = { ...updated[index], quantity: val };
@@ -255,33 +255,33 @@ export class UploadFormComponent implements OnInit {
patchSettings(settings: any) { patchSettings(settings: any) {
if (!settings) return; if (!settings) return;
// settings object matches keys in our form? // settings object matches keys in our form?
// Session has: materialCode, etc. derived from QuoteSession entity properties // Session has: materialCode, etc. derived from QuoteSession entity properties
// We need to map them if names differ. // We need to map them if names differ.
const patch: any = {}; const patch: any = {};
if (settings.materialCode) patch.material = settings.materialCode; if (settings.materialCode) patch.material = settings.materialCode;
// Heuristic for Quality if not explicitly stored as "draft/standard/high" // Heuristic for Quality if not explicitly stored as "draft/standard/high"
// But we stored it in session creation? // But we stored it in session creation?
// QuoteSession entity does NOT store "quality" string directly, only layerHeight/infill. // QuoteSession entity does NOT store "quality" string directly, only layerHeight/infill.
// So we might need to deduce it or just set Custom/Advanced. // So we might need to deduce it or just set Custom/Advanced.
// But for Easy mode, we want to show "Standard" etc. // But for Easy mode, we want to show "Standard" etc.
// Actually, let's look at what we have in QuoteSession. // Actually, let's look at what we have in QuoteSession.
// layerHeightMm, infillPercent, etc. // layerHeightMm, infillPercent, etc.
// If we are in Easy mode, we might just set the "quality" dropdown to match approx? // If we are in Easy mode, we might just set the "quality" dropdown to match approx?
// Or if we stored "quality" in notes or separate field? We didn't. // Or if we stored "quality" in notes or separate field? We didn't.
// Let's try to reverse map or defaults. // Let's try to reverse map or defaults.
if (settings.layerHeightMm) { if (settings.layerHeightMm) {
if (settings.layerHeightMm >= 0.28) patch.quality = 'draft'; if (settings.layerHeightMm >= 0.28) patch.quality = 'draft';
else if (settings.layerHeightMm <= 0.12) patch.quality = 'high'; else if (settings.layerHeightMm <= 0.12) patch.quality = 'high';
else patch.quality = 'standard'; else patch.quality = 'standard';
patch.layerHeight = settings.layerHeightMm; patch.layerHeight = settings.layerHeightMm;
} }
if (settings.nozzleDiameterMm) patch.nozzleDiameter = settings.nozzleDiameterMm; if (settings.nozzleDiameterMm) patch.nozzleDiameter = settings.nozzleDiameterMm;
if (settings.infillPercent) patch.infillDensity = settings.infillPercent; if (settings.infillPercent) patch.infillDensity = settings.infillPercent;
if (settings.infillPattern) patch.infillPattern = settings.infillPattern; if (settings.infillPattern) patch.infillPattern = settings.infillPattern;
@@ -294,7 +294,7 @@ export class UploadFormComponent implements OnInit {
onSubmit() { onSubmit() {
console.log('UploadFormComponent: onSubmit triggered'); console.log('UploadFormComponent: onSubmit triggered');
console.log('Form Valid:', this.form.valid, 'Items:', this.items().length); console.log('Form Valid:', this.form.valid, 'Items:', this.items().length);
if (this.form.valid && this.items().length > 0) { if (this.form.valid && this.items().length > 0) {
console.log('UploadFormComponent: Emitting submitRequest', this.form.value); console.log('UploadFormComponent: Emitting submitRequest', this.form.value);
this.submitRequest.emit({ this.submitRequest.emit({

View File

@@ -102,7 +102,7 @@
"PROCESSING": "Elaborazione...", "PROCESSING": "Elaborazione...",
"NOTES_PLACEHOLDER": "Istruzioni specifiche...", "NOTES_PLACEHOLDER": "Istruzioni specifiche...",
"SETUP_NOTE": "* Include {{cost}} Costo di Setup", "SETUP_NOTE": "* Include {{cost}} Costo di Setup",
"STEP_WARNING": "La visualizzazione 3D non è compatibile con i file STEP, ma il calcolatore funziona." "STEP_WARNING": "La visualizzazione 3D non è compatibile con i file step e 3mf, ma il calcolatore funziona."
}, },
"QUOTE": { "QUOTE": {
"PROCEED_ORDER": "Procedi con l'ordine", "PROCEED_ORDER": "Procedi con l'ordine",