feat(back-end and front-end) 3d visualization for cad
All checks were successful
Build and Deploy / test-backend (push) Successful in 33s
Build and Deploy / test-frontend (push) Successful in 1m5s
Build and Deploy / build-and-push (push) Successful in 43s
Build and Deploy / deploy (push) Successful in 9s
PR Checks / prettier-autofix (pull_request) Successful in 11s
PR Checks / test-backend (pull_request) Successful in 26s
PR Checks / security-sast (pull_request) Successful in 32s
PR Checks / test-frontend (pull_request) Successful in 1m6s

This commit is contained in:
2026-03-04 16:49:18 +01:00
parent 57360bacd0
commit 0ef97eeb9b
11 changed files with 359 additions and 3 deletions

View File

@@ -17,6 +17,7 @@ import {
ToggleOption,
} from '../../shared/components/app-toggle-selector/app-toggle-selector.component';
import { LanguageService } from '../../core/services/language.service';
import { StlViewerComponent } from '../../shared/components/stl-viewer/stl-viewer.component';
@Component({
selector: 'app-checkout',
@@ -29,6 +30,7 @@ import { LanguageService } from '../../core/services/language.service';
AppButtonComponent,
AppCardComponent,
AppToggleSelectorComponent,
StlViewerComponent,
],
templateUrl: './checkout.component.html',
styleUrls: ['./checkout.component.scss'],
@@ -46,6 +48,13 @@ export class CheckoutComponent implements OnInit {
error: string | null = null;
isSubmitting = signal(false); // Add signal for submit state
quoteSession = signal<any>(null); // Add signal for session details
previewFiles = signal<Record<string, File>>({});
previewLoading = signal<Record<string, boolean>>({});
previewErrors = signal<Record<string, boolean>>({});
previewModalOpen = signal(false);
selectedPreviewFile = signal<File | null>(null);
selectedPreviewName = signal('');
selectedPreviewColor = signal('#c9ced6');
userTypeOptions: ToggleOption[] = [
{ label: 'CONTACT.TYPE_PRIVATE', value: 'PRIVATE' },
@@ -153,6 +162,7 @@ export class CheckoutComponent implements OnInit {
this.quoteService.getQuoteSession(this.sessionId).subscribe({
next: (session) => {
this.quoteSession.set(session);
this.loadStlPreviews(session);
console.log('Loaded session:', session);
},
error: (err) => {
@@ -178,6 +188,94 @@ export class CheckoutComponent implements OnInit {
return this.quoteSession()?.cadTotalChf ?? 0;
}
isStlItem(item: any): boolean {
const name = String(item?.originalFilename ?? '').toLowerCase();
return name.endsWith('.stl');
}
previewFile(item: any): File | null {
const id = String(item?.id ?? '');
if (!id) {
return null;
}
return this.previewFiles()[id] ?? null;
}
previewColor(item: any): string {
const raw = String(item?.colorCode ?? '').trim();
return raw || '#c9ced6';
}
isPreviewLoading(item: any): boolean {
const id = String(item?.id ?? '');
if (!id) {
return false;
}
return !!this.previewLoading()[id];
}
hasPreviewError(item: any): boolean {
const id = String(item?.id ?? '');
if (!id) {
return false;
}
return !!this.previewErrors()[id];
}
openPreview(item: any): void {
const file = this.previewFile(item);
if (!file) {
return;
}
this.selectedPreviewFile.set(file);
this.selectedPreviewName.set(String(item?.originalFilename ?? file.name));
this.selectedPreviewColor.set(this.previewColor(item));
this.previewModalOpen.set(true);
}
closePreview(): void {
this.previewModalOpen.set(false);
this.selectedPreviewFile.set(null);
this.selectedPreviewName.set('');
this.selectedPreviewColor.set('#c9ced6');
}
private loadStlPreviews(session: any): void {
if (!this.sessionId || !Array.isArray(session?.items)) {
return;
}
for (const item of session.items) {
if (!this.isStlItem(item)) {
continue;
}
const id = String(item?.id ?? '');
if (!id || this.previewFiles()[id] || this.previewLoading()[id]) {
continue;
}
this.previewLoading.update((prev) => ({ ...prev, [id]: true }));
this.previewErrors.update((prev) => ({ ...prev, [id]: false }));
this.quoteService.getLineItemStlPreview(this.sessionId, id).subscribe({
next: (blob) => {
const originalName = String(item?.originalFilename ?? `${id}.stl`);
const stlName = originalName.toLowerCase().endsWith('.stl')
? originalName
: `${originalName}.stl`;
const previewFile = new File([blob], stlName, { type: 'model/stl' });
this.previewFiles.update((prev) => ({ ...prev, [id]: previewFile }));
this.previewLoading.update((prev) => ({ ...prev, [id]: false }));
},
error: () => {
this.previewErrors.update((prev) => ({ ...prev, [id]: true }));
this.previewLoading.update((prev) => ({ ...prev, [id]: false }));
},
});
}
}
onSubmit() {
if (this.checkoutForm.invalid) {
return;