style: apply prettier formatting

This commit is contained in:
printcalc-ci
2026-03-03 11:46:26 +00:00
parent dd6f723271
commit 20293cc044
131 changed files with 5674 additions and 3482 deletions

View File

@@ -1,4 +1,10 @@
import { Component, signal, ViewChild, ElementRef, OnInit } from '@angular/core';
import {
Component,
signal,
ViewChild,
ElementRef,
OnInit,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { forkJoin } from 'rxjs';
@@ -8,7 +14,11 @@ import { AppCardComponent } from '../../shared/components/app-card/app-card.comp
import { AppAlertComponent } from '../../shared/components/app-alert/app-alert.component';
import { UploadFormComponent } from './components/upload-form/upload-form.component';
import { QuoteResultComponent } from './components/quote-result/quote-result.component';
import { QuoteRequest, QuoteResult, QuoteEstimatorService } from './services/quote-estimator.service';
import {
QuoteRequest,
QuoteResult,
QuoteEstimatorService,
} from './services/quote-estimator.service';
import { SuccessStateComponent } from '../../shared/components/success-state/success-state.component';
import { Router, ActivatedRoute } from '@angular/router';
import { LanguageService } from '../../core/services/language.service';
@@ -16,142 +26,155 @@ import { LanguageService } from '../../core/services/language.service';
@Component({
selector: 'app-calculator-page',
standalone: true,
imports: [CommonModule, TranslateModule, AppCardComponent, AppAlertComponent, UploadFormComponent, QuoteResultComponent, SuccessStateComponent],
imports: [
CommonModule,
TranslateModule,
AppCardComponent,
AppAlertComponent,
UploadFormComponent,
QuoteResultComponent,
SuccessStateComponent,
],
templateUrl: './calculator-page.component.html',
styleUrl: './calculator-page.component.scss'
styleUrl: './calculator-page.component.scss',
})
export class CalculatorPageComponent implements OnInit {
mode = signal<any>('easy');
step = signal<'upload' | 'quote' | 'details' | 'success'>('upload');
loading = signal(false);
uploadProgress = signal(0);
result = signal<QuoteResult | null>(null);
error = signal<boolean>(false);
errorKey = signal<string>('CALC.ERROR_GENERIC');
orderSuccess = signal(false);
@ViewChild('uploadForm') uploadForm!: UploadFormComponent;
@ViewChild('resultCol') resultCol!: ElementRef;
constructor(
private estimator: QuoteEstimatorService,
private estimator: QuoteEstimatorService,
private router: Router,
private route: ActivatedRoute,
private languageService: LanguageService
private languageService: LanguageService,
) {}
ngOnInit() {
this.route.data.subscribe(data => {
this.route.data.subscribe((data) => {
if (data['mode']) {
this.mode.set(data['mode']);
}
});
this.route.queryParams.subscribe(params => {
const sessionId = params['session'];
if (sessionId) {
// Avoid reloading if we just calculated this session
const currentRes = this.result();
if (!currentRes || currentRes.sessionId !== sessionId) {
this.loadSession(sessionId);
}
this.route.queryParams.subscribe((params) => {
const sessionId = params['session'];
if (sessionId) {
// Avoid reloading if we just calculated this session
const currentRes = this.result();
if (!currentRes || currentRes.sessionId !== sessionId) {
this.loadSession(sessionId);
}
}
});
}
loadSession(sessionId: string) {
this.loading.set(true);
this.estimator.getQuoteSession(sessionId).subscribe({
next: (data) => {
// 1. Map to Result
const result = this.estimator.mapSessionToQuoteResult(data);
if (this.isInvalidQuote(result)) {
this.setQuoteError('CALC.ERROR_ZERO_PRICE');
this.loading.set(false);
return;
}
this.loading.set(true);
this.estimator.getQuoteSession(sessionId).subscribe({
next: (data) => {
// 1. Map to Result
const result = this.estimator.mapSessionToQuoteResult(data);
if (this.isInvalidQuote(result)) {
this.setQuoteError('CALC.ERROR_ZERO_PRICE');
this.loading.set(false);
return;
}
this.error.set(false);
this.errorKey.set('CALC.ERROR_GENERIC');
this.result.set(result);
this.step.set('quote');
// 2. Determine Mode (Heuristic)
// If we have custom settings, maybe Advanced?
// For now, let's stick to current mode or infer from URL if possible.
// Actually, we can check if settings deviate from Easy defaults.
// But let's leave it as is or default to Advanced if not sure.
// data.session.materialCode etc.
// 3. Download Files & Restore Form
this.restoreFilesAndSettings(data.session, data.items);
},
error: (err) => {
console.error('Failed to load session', err);
this.setQuoteError('CALC.ERROR_GENERIC');
this.loading.set(false);
}
});
this.error.set(false);
this.errorKey.set('CALC.ERROR_GENERIC');
this.result.set(result);
this.step.set('quote');
// 2. Determine Mode (Heuristic)
// If we have custom settings, maybe Advanced?
// For now, let's stick to current mode or infer from URL if possible.
// Actually, we can check if settings deviate from Easy defaults.
// But let's leave it as is or default to Advanced if not sure.
// data.session.materialCode etc.
// 3. Download Files & Restore Form
this.restoreFilesAndSettings(data.session, data.items);
},
error: (err) => {
console.error('Failed to load session', err);
this.setQuoteError('CALC.ERROR_GENERIC');
this.loading.set(false);
},
});
}
restoreFilesAndSettings(session: any, items: any[]) {
if (!items || items.length === 0) {
this.loading.set(false);
return;
}
if (!items || items.length === 0) {
this.loading.set(false);
return;
}
// Download all files
const downloads = items.map(item =>
this.estimator.getLineItemContent(session.id, item.id).pipe(
map((blob: Blob) => {
return {
blob,
fileName: item.originalFilename,
// We need to match the file object to the item so we can set colors ideally.
// UploadForm.setFiles takes File[].
// We might need to handle matching but UploadForm just pushes them.
// If order is preserved, we are good. items from backend are list.
};
})
)
);
forkJoin(downloads).subscribe({
next: (results: any[]) => {
const files = results.map(res => new File([res.blob], res.fileName, { type: 'application/octet-stream' }));
if (this.uploadForm) {
this.uploadForm.setFiles(files);
this.uploadForm.patchSettings(session);
// Also restore colors?
// setFiles inits with 'Black'. We need to update them if they differ.
// items has colorCode.
setTimeout(() => {
if (this.uploadForm) {
items.forEach((item, index) => {
// Assuming index matches.
// Need to be careful if items order changed, but usually ID sort or insert order.
if (item.colorCode) {
this.uploadForm.updateItemColor(index, {
colorName: item.colorCode,
filamentVariantId: item.filamentVariantId
});
}
});
}
// Download all files
const downloads = items.map((item) =>
this.estimator.getLineItemContent(session.id, item.id).pipe(
map((blob: Blob) => {
return {
blob,
fileName: item.originalFilename,
// We need to match the file object to the item so we can set colors ideally.
// UploadForm.setFiles takes File[].
// We might need to handle matching but UploadForm just pushes them.
// If order is preserved, we are good. items from backend are list.
};
}),
),
);
forkJoin(downloads).subscribe({
next: (results: any[]) => {
const files = results.map(
(res) =>
new File([res.blob], res.fileName, {
type: 'application/octet-stream',
}),
);
if (this.uploadForm) {
this.uploadForm.setFiles(files);
this.uploadForm.patchSettings(session);
// Also restore colors?
// setFiles inits with 'Black'. We need to update them if they differ.
// items has colorCode.
setTimeout(() => {
if (this.uploadForm) {
items.forEach((item, index) => {
// Assuming index matches.
// Need to be careful if items order changed, but usually ID sort or insert order.
if (item.colorCode) {
this.uploadForm.updateItemColor(index, {
colorName: item.colorCode,
filamentVariantId: item.filamentVariantId,
});
}
this.loading.set(false);
},
error: (err: any) => {
console.error('Failed to download files', err);
this.loading.set(false);
// Still show result? Yes.
}
});
}
});
}
});
}
this.loading.set(false);
},
error: (err: any) => {
console.error('Failed to download files', err);
this.loading.set(false);
// Still show result? Yes.
},
});
}
onCalculate(req: QuoteRequest) {
@@ -166,46 +189,49 @@ export class CalculatorPageComponent implements OnInit {
// Auto-scroll on mobile to make analysis visible
setTimeout(() => {
if (this.resultCol && window.innerWidth < 768) {
this.resultCol.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
if (this.resultCol && window.innerWidth < 768) {
this.resultCol.nativeElement.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}, 100);
this.estimator.calculate(req).subscribe({
next: (event) => {
if (typeof event === 'number') {
this.uploadProgress.set(event);
this.uploadProgress.set(event);
} else {
// It's the result
const res = event as QuoteResult;
if (this.isInvalidQuote(res)) {
this.setQuoteError('CALC.ERROR_ZERO_PRICE');
this.loading.set(false);
return;
}
this.error.set(false);
this.errorKey.set('CALC.ERROR_GENERIC');
this.result.set(res);
// It's the result
const res = event as QuoteResult;
if (this.isInvalidQuote(res)) {
this.setQuoteError('CALC.ERROR_ZERO_PRICE');
this.loading.set(false);
this.uploadProgress.set(100);
this.step.set('quote');
return;
}
// Update URL with session ID without reloading
if (res.sessionId) {
this.router.navigate([], {
relativeTo: this.route,
queryParams: { session: res.sessionId },
queryParamsHandling: 'merge', // merge with existing params like 'mode' if any
replaceUrl: true // prevent cluttering history, or false if we want back button to work. replaceUrl seems safer for "state update"
});
}
this.error.set(false);
this.errorKey.set('CALC.ERROR_GENERIC');
this.result.set(res);
this.loading.set(false);
this.uploadProgress.set(100);
this.step.set('quote');
// Update URL with session ID without reloading
if (res.sessionId) {
this.router.navigate([], {
relativeTo: this.route,
queryParams: { session: res.sessionId },
queryParamsHandling: 'merge', // merge with existing params like 'mode' if any
replaceUrl: true, // prevent cluttering history, or false if we want back button to work. replaceUrl seems safer for "state update"
});
}
}
},
error: () => {
this.setQuoteError('CALC.ERROR_GENERIC');
this.loading.set(false);
}
},
});
}
@@ -214,7 +240,7 @@ export class CalculatorPageComponent implements OnInit {
if (res && res.sessionId) {
this.router.navigate(
['/', this.languageService.selectedLang(), 'checkout'],
{ queryParams: { session: res.sessionId } }
{ queryParams: { session: res.sessionId } },
);
} else {
console.error('No session ID found in quote result');
@@ -226,59 +252,67 @@ export class CalculatorPageComponent implements OnInit {
this.step.set('quote');
}
onItemChange(event: {id?: string, index: number, fileName: string, quantity: number}) {
// 1. Update local form for consistency (UI feedback)
if (this.uploadForm) {
this.uploadForm.updateItemQuantityByIndex(event.index, event.quantity);
this.uploadForm.updateItemQuantityByName(event.fileName, event.quantity);
}
// 2. Update backend session if ID exists
if (event.id) {
const currentSessionId = this.result()?.sessionId;
if (!currentSessionId) return;
onItemChange(event: {
id?: string;
index: number;
fileName: string;
quantity: number;
}) {
// 1. Update local form for consistency (UI feedback)
if (this.uploadForm) {
this.uploadForm.updateItemQuantityByIndex(event.index, event.quantity);
this.uploadForm.updateItemQuantityByName(event.fileName, event.quantity);
}
this.estimator.updateLineItem(event.id, { quantity: event.quantity }).subscribe({
next: () => {
// 3. Fetch the updated session totals from the backend
this.estimator.getQuoteSession(currentSessionId).subscribe({
next: (sessionData) => {
const newResult = this.estimator.mapSessionToQuoteResult(sessionData);
// Preserve notes
newResult.notes = this.result()?.notes;
// 2. Update backend session if ID exists
if (event.id) {
const currentSessionId = this.result()?.sessionId;
if (!currentSessionId) return;
if (this.isInvalidQuote(newResult)) {
this.setQuoteError('CALC.ERROR_ZERO_PRICE');
return;
}
this.estimator
.updateLineItem(event.id, { quantity: event.quantity })
.subscribe({
next: () => {
// 3. Fetch the updated session totals from the backend
this.estimator.getQuoteSession(currentSessionId).subscribe({
next: (sessionData) => {
const newResult =
this.estimator.mapSessionToQuoteResult(sessionData);
// Preserve notes
newResult.notes = this.result()?.notes;
this.error.set(false);
this.errorKey.set('CALC.ERROR_GENERIC');
this.result.set(newResult);
},
error: (err) => {
console.error('Failed to refresh session totals', err);
}
});
if (this.isInvalidQuote(newResult)) {
this.setQuoteError('CALC.ERROR_ZERO_PRICE');
return;
}
this.error.set(false);
this.errorKey.set('CALC.ERROR_GENERIC');
this.result.set(newResult);
},
error: (err) => {
console.error('Failed to update line item', err);
}
});
}
console.error('Failed to refresh session totals', err);
},
});
},
error: (err) => {
console.error('Failed to update line item', err);
},
});
}
}
onSubmitOrder(orderData: any) {
console.log('Order Submitted:', orderData);
this.orderSuccess.set(true);
this.step.set('success');
this.step.set('success');
}
onNewQuote() {
this.step.set('upload');
this.result.set(null);
this.orderSuccess.set(false);
this.mode.set('easy'); // Reset to default
this.step.set('upload');
this.result.set(null);
this.orderSuccess.set(false);
this.mode.set('easy'); // Reset to default
}
private currentRequest: QuoteRequest | null = null;
@@ -290,25 +324,25 @@ export class CalculatorPageComponent implements OnInit {
let details = `Richiesta Preventivo:\n`;
details += `- Materiale: ${req.material}\n`;
details += `- Qualità: ${req.quality}\n`;
details += `- File:\n`;
req.items.forEach(item => {
details += ` * ${item.file.name} (Qtà: ${item.quantity}`;
if (item.color) {
details += `, Colore: ${item.color}`;
}
details += `)\n`;
req.items.forEach((item) => {
details += ` * ${item.file.name} (Qtà: ${item.quantity}`;
if (item.color) {
details += `, Colore: ${item.color}`;
}
details += `)\n`;
});
if (req.mode === 'advanced') {
if (req.infillDensity) details += `- Infill: ${req.infillDensity}%\n`;
if (req.infillDensity) details += `- Infill: ${req.infillDensity}%\n`;
}
if (req.notes) details += `\nNote: ${req.notes}`;
this.estimator.setPendingConsultation({
files: req.items.map(i => i.file),
message: details
files: req.items.map((i) => i.file),
message: details,
});
this.router.navigate(['/', this.languageService.selectedLang(), 'contact']);