produzione 1 #9
@@ -1,4 +1,4 @@
|
||||
import { Component, signal } from '@angular/core';
|
||||
import { Component, signal, ViewChild } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
@@ -37,6 +37,7 @@ import { Router } from '@angular/router';
|
||||
</div>
|
||||
|
||||
<app-upload-form
|
||||
#uploadForm
|
||||
[mode]="mode()"
|
||||
[loading]="loading()"
|
||||
[uploadProgress]="uploadProgress()"
|
||||
@@ -60,7 +61,11 @@ import { Router } from '@angular/router';
|
||||
</div>
|
||||
</app-card>
|
||||
} @else if (result()) {
|
||||
<app-quote-result [result]="result()!" (consult)="onConsult()"></app-quote-result>
|
||||
<app-quote-result
|
||||
[result]="result()!"
|
||||
(consult)="onConsult()"
|
||||
(itemChange)="uploadForm.updateItemQuantityByName($event.fileName, $event.quantity)"
|
||||
></app-quote-result>
|
||||
} @else {
|
||||
<app-card>
|
||||
<h3>{{ 'CALC.BENEFITS_TITLE' | translate }}</h3>
|
||||
@@ -177,6 +182,8 @@ export class CalculatorPageComponent {
|
||||
uploadProgress = signal(0);
|
||||
result = signal<QuoteResult | null>(null);
|
||||
error = signal<boolean>(false);
|
||||
|
||||
@ViewChild('uploadForm') uploadForm!: UploadFormComponent;
|
||||
|
||||
constructor(private estimator: QuoteEstimatorService, private router: Router) {}
|
||||
|
||||
|
||||
@@ -15,7 +15,32 @@ import { QuoteResult, QuoteItem } from '../../services/quote-estimator.service';
|
||||
<app-card>
|
||||
<h3 class="title">{{ 'CALC.RESULT' | translate }}</h3>
|
||||
|
||||
<!-- Detailed Items List -->
|
||||
<!-- Summary Grid (NOW ON TOP) -->
|
||||
<div class="result-grid">
|
||||
<app-summary-card
|
||||
class="item full-width"
|
||||
[label]="'CALC.COST' | translate"
|
||||
[large]="true"
|
||||
[highlight]="true">
|
||||
{{ totals().price | currency:result().currency }}
|
||||
</app-summary-card>
|
||||
|
||||
<app-summary-card [label]="'CALC.TIME' | translate">
|
||||
{{ totals().hours }}h {{ totals().minutes }}m
|
||||
</app-summary-card>
|
||||
|
||||
<app-summary-card [label]="'CALC.MATERIAL' | translate">
|
||||
{{ totals().weight }}g
|
||||
</app-summary-card>
|
||||
</div>
|
||||
|
||||
<div class="setup-note">
|
||||
<small>* Include {{ result().setupCost | currency:result().currency }} Setup Cost</small>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- Detailed Items List (NOW ON BOTTOM) -->
|
||||
<div class="items-list">
|
||||
@for (item of items(); track item.fileName; let i = $index) {
|
||||
<div class="item-row">
|
||||
@@ -44,31 +69,6 @@ import { QuoteResult, QuoteItem } from '../../services/quote-estimator.service';
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- Summary Grid -->
|
||||
<div class="result-grid">
|
||||
<app-summary-card
|
||||
class="item full-width"
|
||||
[label]="'CALC.COST' | translate"
|
||||
[large]="true"
|
||||
[highlight]="true">
|
||||
{{ totals().price | currency:result().currency }}
|
||||
</app-summary-card>
|
||||
|
||||
<app-summary-card [label]="'CALC.TIME' | translate">
|
||||
{{ totals().hours }}h {{ totals().minutes }}m
|
||||
</app-summary-card>
|
||||
|
||||
<app-summary-card [label]="'CALC.MATERIAL' | translate">
|
||||
{{ totals().weight }}g
|
||||
</app-summary-card>
|
||||
</div>
|
||||
|
||||
<div class="setup-note">
|
||||
<small>* Include {{ result().setupCost | currency:result().currency }} Setup Cost</small>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<app-button variant="primary" [fullWidth]="true">{{ 'CALC.ORDER' | translate }}</app-button>
|
||||
<app-button variant="outline" [fullWidth]="true" (click)="consult.emit()">{{ 'CALC.CONSULT' | translate }}</app-button>
|
||||
@@ -159,6 +159,7 @@ import { QuoteResult, QuoteItem } from '../../services/quote-estimator.service';
|
||||
export class QuoteResultComponent {
|
||||
result = input.required<QuoteResult>();
|
||||
consult = output<void>();
|
||||
itemChange = output<{fileName: string, quantity: number}>();
|
||||
|
||||
// Local mutable state for items to handle quantity changes
|
||||
items = signal<QuoteItem[]>([]);
|
||||
@@ -180,6 +181,11 @@ export class QuoteResultComponent {
|
||||
updated[index] = { ...updated[index], quantity: qty };
|
||||
return updated;
|
||||
});
|
||||
|
||||
this.itemChange.emit({
|
||||
fileName: this.items()[index].fileName,
|
||||
quantity: qty
|
||||
});
|
||||
}
|
||||
|
||||
totals = computed(() => {
|
||||
|
||||
@@ -31,15 +31,16 @@ interface FormItem {
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Dropzone always available if we want to add more, or hide if list not empty? -->
|
||||
<!-- User wants to "add more", so dropzone should remain or be available -->
|
||||
<app-dropzone
|
||||
[label]="'CALC.UPLOAD_LABEL' | translate"
|
||||
[subtext]="'CALC.UPLOAD_SUB' | translate"
|
||||
[accept]="acceptedFormats"
|
||||
[multiple]="true"
|
||||
(filesDropped)="onFilesDropped($event)">
|
||||
</app-dropzone>
|
||||
<!-- Initial Dropzone (Visible only when no files) -->
|
||||
@if (items().length === 0) {
|
||||
<app-dropzone
|
||||
[label]="'CALC.UPLOAD_LABEL' | translate"
|
||||
[subtext]="'CALC.UPLOAD_SUB' | translate"
|
||||
[accept]="acceptedFormats"
|
||||
[multiple]="true"
|
||||
(filesDropped)="onFilesDropped($event)">
|
||||
</app-dropzone>
|
||||
}
|
||||
|
||||
<!-- New File List with Details -->
|
||||
@if (items().length > 0) {
|
||||
@@ -69,6 +70,14 @@ interface FormItem {
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- "Add Files" Button (Visible only when files exist) -->
|
||||
<div class="add-more-container">
|
||||
<input #additionalInput type="file" [accept]="acceptedFormats" multiple hidden (change)="onAdditionalFilesSelected($event)">
|
||||
<app-button variant="outline" [fullWidth]="true" (click)="additionalInput.click()">
|
||||
+ {{ 'CALC.ADD_FILES' | translate }}
|
||||
</app-button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (items().length === 0 && form.get('itemsTouched')?.value) {
|
||||
@@ -363,6 +372,26 @@ export class UploadFormComponent {
|
||||
}
|
||||
}
|
||||
|
||||
onAdditionalFilesSelected(event: Event) {
|
||||
const input = event.target as HTMLInputElement;
|
||||
if (input.files && input.files.length > 0) {
|
||||
this.onFilesDropped(Array.from(input.files));
|
||||
// Reset input so same files can be selected again if needed
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
updateItemQuantityByName(fileName: string, quantity: number) {
|
||||
this.items.update(current => {
|
||||
return current.map(item => {
|
||||
if (item.file.name === fileName) {
|
||||
return { ...item, quantity };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
selectFile(file: File) {
|
||||
if (this.selectedFile() === file) {
|
||||
// toggle off? no, keep active
|
||||
|
||||
Reference in New Issue
Block a user