From 2e3000c3fcaf2628e30399fc719b19516713d465 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 18:49:04 +0200 Subject: [PATCH] Add loading entry data when updating an entry --- .../edit-entry/edit-entry.component.ts | 137 ++++++++++++++---- 1 file changed, 106 insertions(+), 31 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts index 6e0a5c4..b36771b 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts @@ -1,7 +1,7 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component, computed, DestroyRef, inject, input, Signal } from '@angular/core'; +import { Component, computed, DestroyRef, inject, input, OnInit, signal, Signal } from '@angular/core'; import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; -import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { CarClient } from '@vegasco-web/api/cars/car-client'; import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client'; import { RoutingService } from '@vegasco-web/services/routing.service'; @@ -17,7 +17,7 @@ import { InputTextModule } from 'primeng/inputtext'; import { MultiSelectModule } from 'primeng/multiselect'; import { SelectModule } from 'primeng/select'; import { SkeletonModule } from 'primeng/skeleton'; -import { catchError, EMPTY, map, Observable, switchMap, throwError } from 'rxjs'; +import { catchError, combineLatest, EMPTY, filter, map, Observable, switchMap, tap, throwError } from 'rxjs'; import { RequiredMarkerComponent } from './components/required-marker.component'; @Component({ @@ -40,15 +40,14 @@ import { RequiredMarkerComponent } from './components/required-marker.component' templateUrl: './edit-entry.component.html', styleUrl: './edit-entry.component.scss' }) -export class EditEntryComponent { - private readonly formBuilder = inject(FormBuilder); +export class EditEntryComponent implements OnInit { private readonly carClient = inject(CarClient); private readonly consumptionClient = inject(ConsumptionClient); private readonly routingService = inject(RoutingService); private readonly destroyRef = inject(DestroyRef); private readonly messageService = inject(MessageService); - protected readonly entryId = input(undefined); + protected readonly id = input(undefined); protected readonly formFieldNames = { car: 'car', @@ -57,34 +56,82 @@ export class EditEntryComponent { amount: 'amount', } as const; - protected readonly formGroup = this.formBuilder.group({ - [this.formFieldNames.car]: [null, Validators.required], - [this.formFieldNames.date]: [new Date(), Validators.required], - [this.formFieldNames.mileage]: [ - null, - [Validators.required, Validators.min(1)], - ], - [this.formFieldNames.amount]: [ - null, - [Validators.required, Validators.min(1)], - ], + protected readonly formGroup = new FormGroup({ + [this.formFieldNames.car]: new FormControl({ value: null, disabled: true }, [Validators.required]), + [this.formFieldNames.date]: new FormControl({ value: new Date(), disabled: true }, [Validators.required]), + [this.formFieldNames.mileage]: new FormControl({ value: null, disabled: true }, [Validators.required, Validators.min(1)]), + [this.formFieldNames.amount]: new FormControl({ value: null, disabled: true }, [Validators.required, Validators.min(1)]), }); + private readonly cars$: Observable; protected readonly cars: Signal; + private readonly isEntryDataLoaded = signal(false); + protected readonly isLoading = computed(() => { - return this.cars() === undefined; - }) + var cars = this.cars(); + var isEntryDataLoaded = this.isEntryDataLoaded(); + return cars === undefined || !isEntryDataLoaded; + }); constructor() { - this.cars = toSignal( - this.carClient - .getAll() - .pipe( - takeUntilDestroyed(), - map(response => response.cars) - ), - ); + this.cars$ = this.carClient + .getAll() + .pipe( + takeUntilDestroyed(), + map(response => response.cars) + ); + this.cars = toSignal(this.cars$); + } + + ngOnInit(): void { + this.loadEntryDetailsAndEnableControls(); + } + + private loadEntryDetailsAndEnableControls() { + const entryId = this.id(); + + if (entryId === undefined || entryId === null) { + this.enableFormControls(); + this.isEntryDataLoaded.set(true); + return; + } + + const consumption$ = this.consumptionClient + .getSingle(entryId); + + combineLatest([ + consumption$, this.cars$ + ]) + .pipe( + filter(([_, cars]) => cars !== undefined), + takeUntilDestroyed(this.destroyRef), + catchError((error) => this.handleGetError(error)), + tap(([consumption, cars]) => { + this.formGroup.patchValue({ + [this.formFieldNames.car]: cars!.find(c => c.id === consumption.carId) ?? null, + [this.formFieldNames.date]: new Date(consumption.dateTime), + [this.formFieldNames.mileage]: consumption.distance, + [this.formFieldNames.amount]: consumption.amount, + }); + }), + tap(() => { + this.enableFormControls(); + this.isEntryDataLoaded.set(true); + }), + ) + .subscribe(); + } + + private enableFormControls(): void { + for (const controlName of Object.values(this.formFieldNames)) { + const control = this.formGroup.get(controlName); + if (control) { + control.enable(); + } else { + console.warn(`Form control '${controlName}' not found.`); + } + } } async navigateToOverviewPage(): Promise { @@ -92,7 +139,7 @@ export class EditEntryComponent { } onSubmit(): void { - var entryId = this.entryId(); + var entryId = this.id(); if (entryId === undefined || entryId === null) { this.createEntry(); return; @@ -118,7 +165,7 @@ export class EditEntryComponent { this.consumptionClient.create(request) .pipe( takeUntilDestroyed(this.destroyRef), - catchError((error) => this.handleError(error)), + catchError((error) => this.handleCreateOrUpdateError(error)), switchMap(() => this.routingService.navigateToEntries()) ) .subscribe(); @@ -129,13 +176,41 @@ export class EditEntryComponent { this.consumptionClient.update(id, request) .pipe( takeUntilDestroyed(this.destroyRef), - catchError((error) => this.handleError(error)), + catchError((error) => this.handleCreateOrUpdateError(error)), switchMap(() => this.routingService.navigateToEntries()) ) .subscribe(); } - private handleError(error: unknown): Observable { + private handleGetError(error: unknown): Observable { + if (!(error instanceof HttpErrorResponse)) { + return throwError(() => error); + } + + switch (true) { + case error.status >= 500 && error.status <= 599: + this.messageService.add({ + severity: 'error', + summary: 'Serverfehler', + detail: + 'Beim Erstellen des Eintrags ist ein Fehler aufgetreten. Bitte versuche es erneut.', + }); + break; + default: + console.error(error); + this.messageService.add({ + severity: 'error', + summary: 'Unerwarteter Fehler', + detail: + 'Beim Erstellen des Eintrags hat der Server eine unerwartete Antwort zurückgegeben.', + }); + break; + } + + return EMPTY; + } + + private handleCreateOrUpdateError(error: unknown): Observable { if (!(error instanceof HttpErrorResponse)) { return throwError(() => error); }