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 { CommonModule } from '@angular/common';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
@@ -37,6 +37,7 @@ import { Router } from '@angular/router';
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-upload-form
|
<app-upload-form
|
||||||
|
#uploadForm
|
||||||
[mode]="mode()"
|
[mode]="mode()"
|
||||||
[loading]="loading()"
|
[loading]="loading()"
|
||||||
[uploadProgress]="uploadProgress()"
|
[uploadProgress]="uploadProgress()"
|
||||||
@@ -60,7 +61,11 @@ import { Router } from '@angular/router';
|
|||||||
</div>
|
</div>
|
||||||
</app-card>
|
</app-card>
|
||||||
} @else if (result()) {
|
} @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 {
|
} @else {
|
||||||
<app-card>
|
<app-card>
|
||||||
<h3>{{ 'CALC.BENEFITS_TITLE' | translate }}</h3>
|
<h3>{{ 'CALC.BENEFITS_TITLE' | translate }}</h3>
|
||||||
@@ -177,6 +182,8 @@ export class CalculatorPageComponent {
|
|||||||
uploadProgress = signal(0);
|
uploadProgress = signal(0);
|
||||||
result = signal<QuoteResult | null>(null);
|
result = signal<QuoteResult | null>(null);
|
||||||
error = signal<boolean>(false);
|
error = signal<boolean>(false);
|
||||||
|
|
||||||
|
@ViewChild('uploadForm') uploadForm!: UploadFormComponent;
|
||||||
|
|
||||||
constructor(private estimator: QuoteEstimatorService, private router: Router) {}
|
constructor(private estimator: QuoteEstimatorService, private router: Router) {}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,32 @@ import { QuoteResult, QuoteItem } from '../../services/quote-estimator.service';
|
|||||||
<app-card>
|
<app-card>
|
||||||
<h3 class="title">{{ 'CALC.RESULT' | translate }}</h3>
|
<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">
|
<div class="items-list">
|
||||||
@for (item of items(); track item.fileName; let i = $index) {
|
@for (item of items(); track item.fileName; let i = $index) {
|
||||||
<div class="item-row">
|
<div class="item-row">
|
||||||
@@ -44,31 +69,6 @@ import { QuoteResult, QuoteItem } from '../../services/quote-estimator.service';
|
|||||||
}
|
}
|
||||||
</div>
|
</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">
|
<div class="actions">
|
||||||
<app-button variant="primary" [fullWidth]="true">{{ 'CALC.ORDER' | translate }}</app-button>
|
<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>
|
<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 {
|
export class QuoteResultComponent {
|
||||||
result = input.required<QuoteResult>();
|
result = input.required<QuoteResult>();
|
||||||
consult = output<void>();
|
consult = output<void>();
|
||||||
|
itemChange = output<{fileName: string, quantity: number}>();
|
||||||
|
|
||||||
// Local mutable state for items to handle quantity changes
|
// Local mutable state for items to handle quantity changes
|
||||||
items = signal<QuoteItem[]>([]);
|
items = signal<QuoteItem[]>([]);
|
||||||
@@ -180,6 +181,11 @@ export class QuoteResultComponent {
|
|||||||
updated[index] = { ...updated[index], quantity: qty };
|
updated[index] = { ...updated[index], quantity: qty };
|
||||||
return updated;
|
return updated;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.itemChange.emit({
|
||||||
|
fileName: this.items()[index].fileName,
|
||||||
|
quantity: qty
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
totals = computed(() => {
|
totals = computed(() => {
|
||||||
|
|||||||
@@ -31,15 +31,16 @@ interface FormItem {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- Dropzone always available if we want to add more, or hide if list not empty? -->
|
<!-- Initial Dropzone (Visible only when no files) -->
|
||||||
<!-- User wants to "add more", so dropzone should remain or be available -->
|
@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"
|
||||||
(filesDropped)="onFilesDropped($event)">
|
(filesDropped)="onFilesDropped($event)">
|
||||||
</app-dropzone>
|
</app-dropzone>
|
||||||
|
}
|
||||||
|
|
||||||
<!-- New File List with Details -->
|
<!-- New File List with Details -->
|
||||||
@if (items().length > 0) {
|
@if (items().length > 0) {
|
||||||
@@ -69,6 +70,14 @@ interface FormItem {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</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) {
|
@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) {
|
selectFile(file: File) {
|
||||||
if (this.selectedFile() === file) {
|
if (this.selectedFile() === file) {
|
||||||
// toggle off? no, keep active
|
// toggle off? no, keep active
|
||||||
|
|||||||
Reference in New Issue
Block a user