feat(): new feature
This commit is contained in:
@@ -1,9 +1,28 @@
|
||||
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||
import { ApplicationConfig, LOCALE_ID, provideZoneChangeDetection } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
import {provideHttpClient} from '@angular/common/http';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
|
||||
const resolveLocale = () => {
|
||||
if (typeof navigator === 'undefined') {
|
||||
return 'de-CH';
|
||||
}
|
||||
const languages = navigator.languages ?? [];
|
||||
if (navigator.language === 'it-CH' || languages.includes('it-CH')) {
|
||||
return 'it-CH';
|
||||
}
|
||||
if (navigator.language === 'de-CH' || languages.includes('de-CH')) {
|
||||
return 'de-CH';
|
||||
}
|
||||
return 'de-CH';
|
||||
};
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideHttpClient()]
|
||||
providers: [
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideRouter(routes),
|
||||
provideHttpClient(),
|
||||
{ provide: LOCALE_ID, useFactory: resolveLocale }
|
||||
]
|
||||
};
|
||||
|
||||
@@ -30,17 +30,17 @@
|
||||
|
||||
<div *ngIf="results" class="results-section">
|
||||
<div class="total-price">
|
||||
<h3>Total Estimate: € {{ results.cost.total | number:'1.2-2' }}</h3>
|
||||
<h3>Total Estimate: {{ results?.cost?.total | currency:'CHF' }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="details-grid">
|
||||
<div class="detail-item">
|
||||
<span class="label">Print Time:</span>
|
||||
<span class="value">{{ results.print_time_formatted }}</span>
|
||||
<span class="value">{{ results?.print_time_formatted }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="label">Material Used:</span>
|
||||
<span class="value">{{ results.material_grams | number:'1.1-1' }} g</span>
|
||||
<span class="value">{{ results?.material_grams | number:'1.1-1' }} g</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -48,22 +48,22 @@
|
||||
<ul class="breakdown-list">
|
||||
<li>
|
||||
<span>Material</span>
|
||||
<span>€ {{ results.cost.material | number:'1.2-2' }}</span>
|
||||
<span>{{ results?.cost?.material | currency:'CHF' }}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Machine Time</span>
|
||||
<span>€ {{ results.cost.machine | number:'1.2-2' }}</span>
|
||||
<span>{{ results?.cost?.machine | currency:'CHF' }}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Energy</span>
|
||||
<span>€ {{ results.cost.energy | number:'1.2-2' }}</span>
|
||||
<span>{{ results?.cost?.energy | currency:'CHF' }}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Service/Markup</span>
|
||||
<span>€ {{ results.cost.markup | number:'1.2-2' }}</span>
|
||||
<span>{{ results?.cost?.markup | currency:'CHF' }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -47,24 +47,24 @@
|
||||
<div class="params-form">
|
||||
<h3>Print Settings</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Machine</label>
|
||||
<select [(ngModel)]="params.machine">
|
||||
<option *ngFor="let m of machines" [value]="m">{{ m }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Material</label>
|
||||
<select [(ngModel)]="params.filament">
|
||||
<option *ngFor="let f of filaments" [value]="f">{{ f }}</option>
|
||||
<option *ngFor="let m of materialOptions" [value]="m.value">{{ m.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Quality / Process</label>
|
||||
<label>Color</label>
|
||||
<select [(ngModel)]="params.material_color">
|
||||
<option *ngFor="let c of colorOptions" [value]="c">{{ c }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Quality</label>
|
||||
<select [(ngModel)]="params.quality">
|
||||
<option *ngFor="let q of qualities" [value]="q">{{ q }}</option>
|
||||
<option *ngFor="let q of qualityOptions" [value]="q.value">{{ q.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -75,6 +75,13 @@
|
||||
<span>{{ params.infill_density }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Infill Pattern</label>
|
||||
<select [(ngModel)]="params.infill_pattern">
|
||||
<option *ngFor="let p of infillPatternOptions" [value]="p.value">{{ p.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
@@ -92,7 +99,7 @@
|
||||
<div class="card result-card">
|
||||
<h2>Estimated Cost</h2>
|
||||
<div class="price-big">
|
||||
{{ quoteResult?.cost?.total | currency:'EUR' }}
|
||||
{{ quoteResult?.cost?.total | currency:'CHF' }}
|
||||
</div>
|
||||
|
||||
<div class="specs-list">
|
||||
@@ -104,10 +111,6 @@
|
||||
<span>Material Weight</span>
|
||||
<strong>{{ quoteResult?.material_grams | number:'1.0-0' }}g</strong>
|
||||
</div>
|
||||
<div class="spec-item">
|
||||
<span>Printer</span>
|
||||
<strong>{{ quoteResult?.printer }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Param Summary (Future Proofing) -->
|
||||
@@ -118,13 +121,21 @@
|
||||
<span>{{ params.infill_density }}%</span>
|
||||
</div>
|
||||
<div class="spec-item compact">
|
||||
<span>Layer Height</span>
|
||||
<span>{{ params.layer_height || 'Standard' }}</span>
|
||||
<span>Infill Pattern</span>
|
||||
<span>{{ infillPatternLabel }}</span>
|
||||
</div>
|
||||
<div class="spec-item compact">
|
||||
<span>Material</span>
|
||||
<span>{{ materialLabel }}</span>
|
||||
</div>
|
||||
<div class="spec-item compact">
|
||||
<span>Color</span>
|
||||
<span>{{ params.material_color }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="note-box">
|
||||
<p>Note: Advanced parameters are saved for review but estimation currently uses standard profile benchmarks.</p>
|
||||
<p>Note: Color does not affect the estimate. Printer is fixed to Bambu Lab A1.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.range-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, inject, OnInit } from '@angular/core';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
@@ -12,7 +12,7 @@ import { StlViewerComponent } from '../../common/stl-viewer/stl-viewer.component
|
||||
templateUrl: './advanced-quote.component.html',
|
||||
styleUrls: ['./advanced-quote.component.scss']
|
||||
})
|
||||
export class AdvancedQuoteComponent implements OnInit {
|
||||
export class AdvancedQuoteComponent {
|
||||
printService = inject(PrintService);
|
||||
|
||||
selectedFile: File | null = null;
|
||||
@@ -20,27 +20,56 @@ export class AdvancedQuoteComponent implements OnInit {
|
||||
isCalculating = false;
|
||||
quoteResult: any = null;
|
||||
|
||||
// Available Profiles
|
||||
machines: string[] = [];
|
||||
filaments: string[] = [];
|
||||
qualities: string[] = [];
|
||||
// Selectable options (mapped to backend profile ids where needed)
|
||||
readonly materialOptions = [
|
||||
{ value: 'pla_basic', label: 'PLA' },
|
||||
{ value: 'petg_basic', label: 'PETG' },
|
||||
{ value: 'abs_basic', label: 'ABS' },
|
||||
{ value: 'tpu_95a', label: 'TPU 95A' }
|
||||
];
|
||||
readonly colorOptions = [
|
||||
'Black',
|
||||
'White',
|
||||
'Gray',
|
||||
'Red',
|
||||
'Blue',
|
||||
'Green',
|
||||
'Yellow'
|
||||
];
|
||||
readonly qualityOptions = [
|
||||
{ value: 'draft', label: 'Draft' },
|
||||
{ value: 'standard', label: 'Standard' },
|
||||
{ value: 'fine', label: 'Fine' }
|
||||
];
|
||||
readonly infillPatternOptions = [
|
||||
{ value: 'grid', label: 'Grid' },
|
||||
{ value: 'gyroid', label: 'Gyroid' },
|
||||
{ value: 'cubic', label: 'Cubic' },
|
||||
{ value: 'triangles', label: 'Triangles' },
|
||||
{ value: 'rectilinear', label: 'Rectilinear' },
|
||||
{ value: 'crosshatch', label: 'Crosshatch' },
|
||||
{ value: 'zig-zag', label: 'Zig-zag' },
|
||||
{ value: 'alignedrectilinear', label: 'Aligned Rectilinear' }
|
||||
];
|
||||
|
||||
// Parameters
|
||||
params = {
|
||||
machine: 'bambu_a1',
|
||||
filament: 'pla_basic',
|
||||
material_color: 'Black',
|
||||
quality: 'standard',
|
||||
layer_height: null,
|
||||
infill_density: 15,
|
||||
infill_pattern: 'grid',
|
||||
support_enabled: false
|
||||
};
|
||||
|
||||
ngOnInit() {
|
||||
this.printService.getProfiles().subscribe(data => {
|
||||
this.machines = data.machines;
|
||||
this.filaments = data.filaments;
|
||||
this.qualities = data.processes;
|
||||
});
|
||||
get materialLabel(): string {
|
||||
const match = this.materialOptions.find(option => option.value === this.params.filament);
|
||||
return match ? match.label : this.params.filament;
|
||||
}
|
||||
|
||||
get infillPatternLabel(): string {
|
||||
const match = this.infillPatternOptions.find(option => option.value === this.params.infill_pattern);
|
||||
return match ? match.label : this.params.infill_pattern;
|
||||
}
|
||||
|
||||
onDragOver(event: DragEvent) {
|
||||
@@ -92,10 +121,10 @@ export class AdvancedQuoteComponent implements OnInit {
|
||||
|
||||
// Use PrintService
|
||||
this.printService.calculateQuote(this.selectedFile, {
|
||||
machine: this.params.machine,
|
||||
filament: this.params.filament,
|
||||
quality: this.params.quality,
|
||||
infill_density: this.params.infill_density,
|
||||
infill_pattern: this.params.infill_pattern,
|
||||
// Optional mappings if user selected overrides
|
||||
// layer_height: this.params.layer_height,
|
||||
// support_enabled: this.params.support_enabled
|
||||
|
||||
@@ -51,32 +51,32 @@
|
||||
<h3>Select Strength</h3>
|
||||
<div class="strength-options">
|
||||
<div class="strength-card"
|
||||
[class.active]="selectedStrength === 'fragile'"
|
||||
(click)="selectStrength('fragile')">
|
||||
[class.active]="selectedStrength === 'standard'"
|
||||
(click)="selectStrength('standard')">
|
||||
<span class="emoji">🥚</span>
|
||||
<div class="info">
|
||||
<h4>Standard</h4>
|
||||
<p>For decorative parts</p>
|
||||
<p>Balanced strength</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="strength-card"
|
||||
[class.active]="selectedStrength === 'medium'"
|
||||
(click)="selectStrength('medium')">
|
||||
[class.active]="selectedStrength === 'strong'"
|
||||
(click)="selectStrength('strong')">
|
||||
<span class="emoji">🧱</span>
|
||||
<div class="info">
|
||||
<h4>Strong</h4>
|
||||
<p>For functional parts</p>
|
||||
<p>Higher infill</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="strength-card"
|
||||
[class.active]="selectedStrength === 'resistant'"
|
||||
(click)="selectStrength('resistant')">
|
||||
[class.active]="selectedStrength === 'ultra'"
|
||||
(click)="selectStrength('ultra')">
|
||||
<span class="emoji">🦾</span>
|
||||
<div class="info">
|
||||
<h4>Ultra</h4>
|
||||
<p>Max strength & durability</p>
|
||||
<p>Max infill</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -97,21 +97,17 @@
|
||||
<div class="card result-card">
|
||||
<h2>Estimated Cost</h2>
|
||||
<div class="price-big">
|
||||
{{ quoteResult.cost.total | currency:'EUR' }}
|
||||
{{ quoteResult?.cost?.total | currency:'CHF' }}
|
||||
</div>
|
||||
|
||||
<div class="specs-list">
|
||||
<div class="spec-item">
|
||||
<span>Print Time</span>
|
||||
<strong>{{ quoteResult.print_time_formatted }}</strong>
|
||||
<strong>{{ quoteResult?.print_time_formatted }}</strong>
|
||||
</div>
|
||||
<div class="spec-item">
|
||||
<span>Material</span>
|
||||
<strong>{{ quoteResult.material_grams | number:'1.0-0' }}g</strong>
|
||||
</div>
|
||||
<div class="spec-item">
|
||||
<span>Printer</span>
|
||||
<strong>{{ quoteResult.printer }}</strong>
|
||||
<strong>{{ quoteResult?.material_grams | number:'1.0-0' }}g</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,10 +15,15 @@ export class BasicQuoteComponent {
|
||||
printService = inject(PrintService);
|
||||
|
||||
selectedFile: File | null = null;
|
||||
selectedStrength: 'fragile' | 'medium' | 'resistant' = 'medium';
|
||||
selectedStrength: 'standard' | 'strong' | 'ultra' = 'standard';
|
||||
isDragOver = false;
|
||||
isCalculating = false;
|
||||
quoteResult: any = null;
|
||||
private strengthToSettings: Record<'standard' | 'strong' | 'ultra', { infill_density: number; quality: 'draft' | 'standard' | 'fine' }> = {
|
||||
standard: { infill_density: 15, quality: 'standard' },
|
||||
strong: { infill_density: 30, quality: 'standard' },
|
||||
ultra: { infill_density: 50, quality: 'standard' }
|
||||
};
|
||||
|
||||
onDragOver(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
@@ -62,7 +67,7 @@ export class BasicQuoteComponent {
|
||||
this.quoteResult = null;
|
||||
}
|
||||
|
||||
selectStrength(strength: 'fragile' | 'medium' | 'resistant') {
|
||||
selectStrength(strength: 'standard' | 'strong' | 'ultra') {
|
||||
this.selectedStrength = strength;
|
||||
}
|
||||
|
||||
@@ -71,10 +76,21 @@ export class BasicQuoteComponent {
|
||||
|
||||
this.isCalculating = true;
|
||||
|
||||
this.printService.calculateQuote(this.selectedFile, { strength: this.selectedStrength })
|
||||
const settings = this.strengthToSettings[this.selectedStrength];
|
||||
|
||||
this.printService.calculateQuote(this.selectedFile, {
|
||||
quality: settings.quality,
|
||||
infill_density: settings.infill_density
|
||||
})
|
||||
.subscribe({
|
||||
next: (res) => {
|
||||
this.quoteResult = res;
|
||||
if (res?.success) {
|
||||
this.quoteResult = res.data;
|
||||
} else {
|
||||
console.error('Quote API returned error:', res?.error);
|
||||
alert('Calculation failed: ' + (res?.error || 'Unknown error'));
|
||||
this.quoteResult = null;
|
||||
}
|
||||
this.isCalculating = false;
|
||||
},
|
||||
error: (err) => {
|
||||
|
||||
Reference in New Issue
Block a user