import { Component, signal, ViewChild, ElementRef, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; import { forkJoin } from 'rxjs'; import { map } from 'rxjs/operators'; import { AppCardComponent } from '../../shared/components/app-card/app-card.component'; 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 { SuccessStateComponent } from '../../shared/components/success-state/success-state.component'; import { Router, ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-calculator-page', standalone: true, imports: [CommonModule, TranslateModule, AppCardComponent, AppAlertComponent, UploadFormComponent, QuoteResultComponent, SuccessStateComponent], templateUrl: './calculator-page.component.html', styleUrl: './calculator-page.component.scss' }) export class CalculatorPageComponent implements OnInit { mode = signal('easy'); step = signal<'upload' | 'quote' | 'details' | 'success'>('upload'); loading = signal(false); uploadProgress = signal(0); result = signal(null); error = signal(null); orderSuccess = signal(false); @ViewChild('uploadForm') uploadForm!: UploadFormComponent; @ViewChild('resultCol') resultCol!: ElementRef; constructor( private estimator: QuoteEstimatorService, private router: Router, private route: ActivatedRoute ) {} ngOnInit() { this.route.data.subscribe(data => { if (data['mode']) { this.mode.set(data['mode']); } }); this.route.queryParams.subscribe(params => { const sessionId = params['session']; if (sessionId && sessionId !== this.result()?.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); 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.error.set('Failed to load session'); this.loading.set(false); } }); } restoreFilesAndSettings(session: any, items: any[]) { 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' })); const colors = items.map(i => i.colorCode || 'Black'); if (this.uploadForm) { this.uploadForm.setFiles(files, colors); this.uploadForm.patchSettings(session); // Also restore colors? // setFiles inits with correct colors now. 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, item.colorCode); } if (item.quantity) { this.uploadForm.updateItemQuantityAtIndex(index, item.quantity); } }); this.uploadForm.updateItemIdsByIndex(items.map(i => i.id)); } }); } 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) { // ... (logic remains the same, simplified for diff) this.currentRequest = req; this.loading.set(true); this.uploadProgress.set(0); this.error.set(null); this.result.set(null); this.orderSuccess.set(false); // Auto-scroll on mobile to make analysis visible setTimeout(() => { 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); } else { // It's the result const res = event as QuoteResult; this.result.set(res); this.loading.set(false); this.uploadProgress.set(100); this.step.set('quote'); // Sync IDs back to upload form for future updates if (this.uploadForm) { this.uploadForm.updateItemIdsByIndex(res.items.map(i => i.id)); } // 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: (err) => { if (typeof err === 'string') { this.error.set(err); } else { this.error.set('GENERIC'); } this.loading.set(false); } }); } onProceed() { const res = this.result(); if (res && res.sessionId) { this.router.navigate(['/checkout'], { queryParams: { session: res.sessionId } }); } else { console.error('No session ID found in quote result'); // Fallback or error handling } } onCancelDetails() { this.step.set('quote'); } onItemChange(event: {id?: string, fileName: string, quantity: number, index: number}) { // 1. Update local form for consistency (UI feedback) if (this.uploadForm) { this.uploadForm.updateItemQuantityAtIndex(event.index, event.quantity); } // 2. Update backend session if ID exists if (event.id) { this.estimator.updateLineItem(event.id, { quantity: event.quantity }).subscribe({ next: (res) => console.log('Line item updated', res), error: (err) => console.error('Failed to update line item', err) }); } } onItemRemoved(event: {index: number, id?: string}) { // 1. Update local result if exists to keep UI in sync const currentRes = this.result(); if (currentRes) { const updatedItems = [...currentRes.items]; updatedItems.splice(event.index, 1); // Recalculate totals locally for immediate feedback let totalTime = 0; let totalWeight = 0; let itemsPrice = 0; updatedItems.forEach(i => { totalTime += i.unitTime * i.quantity; totalWeight += i.unitWeight * i.quantity; itemsPrice += i.unitPrice * i.quantity; }); this.result.set({ ...currentRes, items: updatedItems, totalPrice: Math.round((itemsPrice + currentRes.setupCost) * 100) / 100, totalTimeHours: Math.floor(totalTime / 3600), totalTimeMinutes: Math.ceil((totalTime % 3600) / 60), totalWeight: Math.ceil(totalWeight) }); } // 2. Delete from backend if ID exists if (event.id && currentRes?.sessionId) { this.estimator.deleteLineItem(currentRes.sessionId, event.id).subscribe({ next: () => console.log('Line item deleted from backend'), error: (err) => console.error('Failed to delete line item', err) }); } } onSubmitOrder(orderData: any) { console.log('Order Submitted:', orderData); this.orderSuccess.set(true); this.step.set('success'); } onNewQuote() { this.step.set('upload'); this.result.set(null); this.orderSuccess.set(false); this.mode.set('easy'); // Reset to default } private currentRequest: QuoteRequest | null = null; onConsult() { if (!this.currentRequest) return; const req = this.currentRequest; 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`; }); if (req.mode === 'advanced') { 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 }); this.router.navigate(['/contact']); } setMode(mode: 'easy' | 'advanced') { const path = mode === 'easy' ? 'basic' : 'advanced'; this.router.navigate(['../', path], { relativeTo: this.route, queryParamsHandling: 'merge' }); } }