From ab7b95a3d7000934ab0d5256816cb0ca881d3e0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joe=20K=C3=BCng?= Date: Thu, 5 Feb 2026 09:44:54 +0100 Subject: [PATCH] feat(web) improvments in calculation page --- .../src/main/resources/application.properties | 4 + .../calculator/calculator-page.component.ts | 93 +++++++++++++------ .../quote-result/quote-result.component.ts | 2 +- .../upload-form/upload-form.component.ts | 73 +++++++++++++-- .../services/quote-estimator.service.ts | 9 +- .../stl-viewer/stl-viewer.component.ts | 40 +++++++- 6 files changed, 179 insertions(+), 42 deletions(-) diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 11ec637..f9efc6e 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -13,3 +13,7 @@ pricing.machine-cost-per-hour=${MACHINE_COST_PER_HOUR:2.0} pricing.energy-cost-per-kwh=${ENERGY_COST_PER_KWH:0.30} pricing.printer-power-watts=${PRINTER_POWER_WATTS:150.0} pricing.markup-percent=${MARKUP_PERCENT:20.0} + +# File Upload Limits +spring.servlet.multipart.max-file-size=200MB +spring.servlet.multipart.max-request-size=200MB diff --git a/frontend/src/app/features/calculator/calculator-page.component.ts b/frontend/src/app/features/calculator/calculator-page.component.ts index 20d75c9..ea22dd7 100644 --- a/frontend/src/app/features/calculator/calculator-page.component.ts +++ b/frontend/src/app/features/calculator/calculator-page.component.ts @@ -22,22 +22,17 @@ import { QuoteEstimatorService, QuoteRequest, QuoteResult } from './services/quo
-
-
- - {{ 'CALC.MODE_EASY' | translate }} - - / - - {{ 'CALC.MODE_ADVANCED' | translate }} - -
+
+
+ {{ 'CALC.MODE_EASY' | translate }} +
+
+ {{ 'CALC.MODE_ADVANCED' | translate }} +
Si รจ verificato un errore durante il calcolo del preventivo. } - @if (result()) { + @if (loading()) { + +
+

Analisi geometria e slicing in corso...

+ Potrebbe richiedere qualche secondo. +
+ } @else if (result()) { } @else { @@ -82,21 +83,61 @@ import { QuoteEstimatorService, QuoteRequest, QuoteResult } from './services/quo } } - .tabs-wrapper { + /* Mode Selector (Segmented Control style) */ + .mode-selector { display: flex; - justify-content: space-between; - align-items: center; + background-color: var(--color-neutral-100); + border-radius: var(--radius-md); + padding: 4px; margin-bottom: var(--space-6); - border-bottom: 1px solid var(--color-border); - padding-bottom: var(--space-2); + gap: 4px; + width: 100%; } - .sub-tabs { font-size: 0.875rem; color: var(--color-text-muted); } - .mode-switch { cursor: pointer; &:hover { color: var(--color-text); } } - .mode-switch.active { font-weight: 700; color: var(--color-brand); } - .divider { margin: 0 var(--space-2); } + .mode-option { + flex: 1; + text-align: center; + padding: 8px 16px; + border-radius: var(--radius-sm); + cursor: pointer; + font-size: 0.875rem; + font-weight: 500; + color: var(--color-text-muted); + transition: all 0.2s ease; + user-select: none; + + &:hover { color: var(--color-text); } + + &.active { + background-color: var(--color-brand); + color: #000; + font-weight: 600; + box-shadow: 0 1px 2px rgba(0,0,0,0.05); + } + } .benefits { padding-left: var(--space-4); color: var(--color-text-muted); line-height: 2; } + + .loading-state { + text-align: center; + padding: var(--space-8); + color: var(--color-text-muted); + + .spinner { + border: 3px solid rgba(0, 0, 0, 0.1); + border-left-color: var(--color-brand); + border-radius: 50%; + width: 32px; + height: 32px; + animation: spin 1s linear infinite; + margin: 0 auto var(--space-4); + } + } + + @keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } `] }) export class CalculatorPageComponent { 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 42d5077..4b701fd 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 @@ -24,7 +24,7 @@ import { QuoteResult } from '../../services/quote-estimator.service'; - {{ result().printTimeHours }}h + {{ result().printTimeHours }}h {{ result().printTimeMinutes }}m 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 a562ffa..db47723 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 @@ -101,12 +101,25 @@ import { QuoteRequest } from '../../services/quote-estimator.service'; > } + @if (loading()) { +
+
+
+
+

Uploading & Analyzing...

+
+ } +
- {{ loading() ? '...' : ('CALC.CALCULATE' | translate) }} + @if (loading()) { + Slicing in progress... + } @else { + {{ 'CALC.CALCULATE' | translate }} + }
@@ -172,6 +185,36 @@ import { QuoteRequest } from '../../services/quote-estimator.service'; cursor: pointer; } } + + /* Progress Bar */ + .progress-container { + margin-top: var(--space-4); + padding: var(--space-4); + background: var(--color-neutral-100); + border-radius: var(--radius-md); + text-align: center; + } + .progress-bar { + height: 6px; + background: var(--color-border); + border-radius: 3px; + overflow: hidden; + margin-bottom: var(--space-2); + position: relative; + } + .progress-fill { + height: 100%; + background: var(--color-brand); + width: 0%; + animation: progress 2s ease-in-out infinite; + } + .progress-text { font-size: 0.875rem; color: var(--color-text-muted); } + + @keyframes progress { + 0% { width: 0%; transform: translateX(-100%); } + 50% { width: 100%; transform: translateX(0); } + 100% { width: 100%; transform: translateX(100%); } + } `] }) export class UploadFormComponent { @@ -230,13 +273,27 @@ export class UploadFormComponent { } onFilesDropped(newFiles: File[]) { - this.files.update(current => [...current, ...newFiles]); - this.form.patchValue({ files: this.files() }); - this.form.get('files')?.markAsTouched(); - - // Select the last added file by default if none selected - if (newFiles.length > 0) { - this.selectedFile.set(newFiles[newFiles.length - 1]); + const MAX_SIZE = 200 * 1024 * 1024; // 200MB + const validFiles: File[] = []; + let hasError = false; + + for (const file of newFiles) { + if (file.size > MAX_SIZE) { + hasError = true; + } else { + validFiles.push(file); + } + } + + if (hasError) { + alert("Alcuni file superano il limite di 200MB e non sono stati aggiunti."); + } + + if (validFiles.length > 0) { + this.files.update(current => [...current, ...validFiles]); + this.form.patchValue({ files: this.files() }); + this.form.get('files')?.markAsTouched(); + this.selectedFile.set(validFiles[validFiles.length - 1]); } } diff --git a/frontend/src/app/features/calculator/services/quote-estimator.service.ts b/frontend/src/app/features/calculator/services/quote-estimator.service.ts index 4bde35e..dc959ad 100644 --- a/frontend/src/app/features/calculator/services/quote-estimator.service.ts +++ b/frontend/src/app/features/calculator/services/quote-estimator.service.ts @@ -21,6 +21,7 @@ export interface QuoteResult { price: number; currency: string; printTimeHours: number; + printTimeMinutes: number; materialUsageGrams: number; setupCost: number; } @@ -104,10 +105,14 @@ export class QuoteEstimatorService { // Total time usually parallel if we have multiple printers, but let's sum for now totalTime = totalTime * request.quantity; + const totalHours = Math.floor(totalTime / 3600); + const totalMinutes = Math.ceil((totalTime % 3600) / 60); + return { - price: Math.round(totalPrice * 100) / 100, + price: Math.round(totalPrice * 100) / 100, // Keep 2 decimals currency: 'CHF', - printTimeHours: Math.ceil(totalTime / 3600), // Ceil hours + printTimeHours: totalHours, + printTimeMinutes: totalMinutes, materialUsageGrams: Math.ceil(totalWeight), setupCost }; diff --git a/frontend/src/app/shared/components/stl-viewer/stl-viewer.component.ts b/frontend/src/app/shared/components/stl-viewer/stl-viewer.component.ts index 7a0f020..45a9ee5 100644 --- a/frontend/src/app/shared/components/stl-viewer/stl-viewer.component.ts +++ b/frontend/src/app/shared/components/stl-viewer/stl-viewer.component.ts @@ -18,6 +18,11 @@ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; Loading 3D Model...
} + @if (file && !loading) { +
+ {{ dimensions.x }} x {{ dimensions.y }} x {{ dimensions.z }} mm +
+ }
`, styles: [` @@ -53,6 +58,18 @@ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; @keyframes spin { to { transform: rotate(360deg); } } + .dims-overlay { + position: absolute; + bottom: 8px; + right: 8px; + background: rgba(0,0,0,0.6); + color: white; + padding: 4px 8px; + border-radius: 4px; + font-size: 0.75rem; + font-family: monospace; + pointer-events: none; + } `] }) export class StlViewerComponent implements OnInit, OnDestroy, OnChanges { @@ -125,6 +142,8 @@ export class StlViewerComponent implements OnInit, OnDestroy, OnChanges { resizeObserver.observe(this.rendererContainer.nativeElement); } + dimensions = { x: 0, y: 0, z: 0 }; + private loadFile(file: File) { this.loading = true; const reader = new FileReader(); @@ -150,22 +169,33 @@ export class StlViewerComponent implements OnInit, OnDestroy, OnChanges { geometry.computeBoundingBox(); geometry.center(); + // Get Dimensions + const boundingBox = geometry.boundingBox!; + const size = new THREE.Vector3(); + boundingBox.getSize(size); + + this.dimensions = { + x: Math.round(size.x * 10) / 10, + y: Math.round(size.y * 10) / 10, + z: Math.round(size.z * 10) / 10 + }; + // Rotate to stand upright (usually necessary for STLs) this.currentMesh.rotation.x = -Math.PI / 2; this.scene.add(this.currentMesh); // Adjust camera to fit object - const boundingBox = geometry.boundingBox!; - const size = new THREE.Vector3(); - boundingBox.getSize(size); const maxDim = Math.max(size.x, size.y, size.z); const fov = this.camera.fov * (Math.PI / 180); - let cameraZ = Math.abs(maxDim / 2 * Math.tan(fov * 2)); // Basic fit - cameraZ *= 2.5; // Zoom out a bit + + // Calculate distance towards camera (z-axis) + let cameraZ = Math.abs(maxDim / 2 / Math.tan(fov / 2)); + cameraZ *= 1.5; // Tighter zoom (reduced from 2.5) this.camera.position.z = cameraZ; this.camera.updateProjectionMatrix(); + this.controls.update(); } catch (err) { console.error('Error loading STL:', err);