New Angular based web version #1
@@ -1,9 +1,17 @@
|
|||||||
<main class="main">
|
<main class="main">
|
||||||
<header class="h-12 bg-primary text-primary-contrast">
|
<header class="h-12 bg-primary text-primary-contrast">
|
||||||
<div class="header max-content-width mx-auto">
|
<div class="header max-content-width mx-auto flex items-center justify-between">
|
||||||
<a routerLink="/" class="reset cursor-pointer">
|
<a routerLink="/" class="reset cursor-pointer">
|
||||||
Vegasco
|
Vegasco
|
||||||
</a>
|
</a>
|
||||||
|
<span class="flex items-center gap-4">
|
||||||
|
<a routerLink="/entries" class="reset cursor-pointer">
|
||||||
|
Einträge
|
||||||
|
</a>
|
||||||
|
<a routerLink="/cars" class="reset cursor-pointer">
|
||||||
|
Autos
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="content max-content-width mx-auto">
|
<div class="content max-content-width mx-auto">
|
||||||
|
|||||||
@@ -9,5 +9,9 @@ export const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'entries',
|
path: 'entries',
|
||||||
loadChildren: () => import('./modules/entries/entries.routes').then(m => m.routes)
|
loadChildren: () => import('./modules/entries/entries.routes').then(m => m.routes)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'cars',
|
||||||
|
loadChildren: () => import('./modules/cars/cars.routes').then(m => m.routes)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterLink, RouterOutlet } from '@angular/router';
|
||||||
import { MessageService } from 'primeng/api';
|
import { MessageService } from 'primeng/api';
|
||||||
import { ToastModule } from 'primeng/toast';
|
import { ToastModule } from 'primeng/toast';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
imports: [RouterOutlet, ToastModule],
|
imports: [RouterLink, RouterOutlet, ToastModule],
|
||||||
providers: [MessageService],
|
providers: [MessageService],
|
||||||
templateUrl: './app.html',
|
templateUrl: './app.html',
|
||||||
styleUrl: './app.scss'
|
styleUrl: './app.scss'
|
||||||
|
|||||||
17
src/Vegasco-Web/src/app/modules/cars/cars.routes.ts
Normal file
17
src/Vegasco-Web/src/app/modules/cars/cars.routes.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Routes } from "@angular/router";
|
||||||
|
import { CarsComponent } from "./cars/cars.component";
|
||||||
|
|
||||||
|
export const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: CarsComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'create',
|
||||||
|
loadComponent: () => import('./edit-car/edit-car.component').then(m => m.EditCarComponent)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'edit/:id',
|
||||||
|
loadComponent: () => import('./edit-car/edit-car.component').then(m => m.EditCarComponent)
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<section>
|
||||||
|
<p-scrollTop />
|
||||||
|
<div class="mb-4 flex gap-2 md:justify-end">
|
||||||
|
<div>
|
||||||
|
<p-button label="Erstellen" routerLink="/cars/create">
|
||||||
|
<ng-icon name="matAddSharp"></ng-icon>
|
||||||
|
</p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if (nonDeletedCars$ | async; as cars) {
|
||||||
|
<p-dataView
|
||||||
|
[value]="cars"
|
||||||
|
[paginator]="true"
|
||||||
|
[rows]="25"
|
||||||
|
[rowsPerPageOptions]="[10, 25, 50, 100]"
|
||||||
|
[pageLinks]="0"
|
||||||
|
[showCurrentPageReport]="true"
|
||||||
|
currentPageReportTemplate="{currentPage} / {totalPages}"
|
||||||
|
layout="list">
|
||||||
|
<ng-template #list let-cars>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
@for (car of cars; track car.id) {
|
||||||
|
<app-car-card [car]="car"
|
||||||
|
(carDeleted)="onCarDeleted($event)" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-dataView>
|
||||||
|
} @else {
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
@for (_ of skeletonsIterationSource; track $index) {
|
||||||
|
<p-skeleton height="4rem" styleClass="mb-2" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
th, td {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
117
src/Vegasco-Web/src/app/modules/cars/cars/cars.component.ts
Normal file
117
src/Vegasco-Web/src/app/modules/cars/cars/cars.component.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import { AsyncPipe, CommonModule } from '@angular/common';
|
||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { Component, DestroyRef, inject } from '@angular/core';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { RouterLink } from '@angular/router';
|
||||||
|
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||||
|
import {
|
||||||
|
matAddSharp,
|
||||||
|
} from '@ng-icons/material-icons/sharp';
|
||||||
|
import { CarClient } from '@vegasco-web/api/cars/car-client';
|
||||||
|
import { MessageService } from 'primeng/api';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { DataViewModule } from 'primeng/dataview';
|
||||||
|
import { ScrollTopModule } from 'primeng/scrolltop';
|
||||||
|
import { SelectModule } from 'primeng/select';
|
||||||
|
import { SkeletonModule } from 'primeng/skeleton';
|
||||||
|
import {
|
||||||
|
BehaviorSubject,
|
||||||
|
catchError,
|
||||||
|
combineLatest,
|
||||||
|
EMPTY,
|
||||||
|
map,
|
||||||
|
Observable,
|
||||||
|
throwError
|
||||||
|
} from 'rxjs';
|
||||||
|
import { CarCardComponent } from './components/car-card/car-card.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-entries',
|
||||||
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
ButtonModule,
|
||||||
|
CommonModule,
|
||||||
|
DataViewModule,
|
||||||
|
CarCardComponent,
|
||||||
|
NgIconComponent,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
RouterLink,
|
||||||
|
ScrollTopModule,
|
||||||
|
SelectModule,
|
||||||
|
SkeletonModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
provideIcons({
|
||||||
|
matAddSharp,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
templateUrl: './cars.component.html',
|
||||||
|
styleUrl: './cars.component.scss'
|
||||||
|
})
|
||||||
|
export class CarsComponent {
|
||||||
|
private readonly carClient = inject(CarClient);
|
||||||
|
private readonly messageService = inject(MessageService);
|
||||||
|
|
||||||
|
protected readonly nonDeletedCars$: Observable<Car[]>;
|
||||||
|
|
||||||
|
protected readonly skeletonsIterationSource = Array(10).fill(0);
|
||||||
|
|
||||||
|
private readonly deletedCars$ = new BehaviorSubject(<string[]>[]);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const cars$ = this.carClient.getAll()
|
||||||
|
.pipe(
|
||||||
|
map(response => response.cars),
|
||||||
|
map((cars) => cars
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name))),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.nonDeletedCars$ = combineLatest([
|
||||||
|
cars$,
|
||||||
|
this.deletedCars$
|
||||||
|
])
|
||||||
|
.pipe(
|
||||||
|
takeUntilDestroyed(),
|
||||||
|
map(([cars, deletedCars]) => cars.filter(car => !deletedCars.includes(car.id))),
|
||||||
|
catchError((error) => this.handleGetCarsError(error)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onCarDeleted(entry: Car): void {
|
||||||
|
this.deletedCars$.next([...this.deletedCars$.value, entry.id]);
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Auto gelöscht',
|
||||||
|
detail: 'Das Auto wurde erfolgreich gelöscht.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleGetCarsError(error: unknown): Observable<never> {
|
||||||
|
if (!(error instanceof HttpErrorResponse)) {
|
||||||
|
return throwError(() => new Error('An unexpected error occurred'));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (true) {
|
||||||
|
case error.status >= 500 && error.status <= 599:
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Serverfehler',
|
||||||
|
detail:
|
||||||
|
'Beim Abrufen der Einträge ist ein Fehler aufgetreten. Bitte versuche es erneut.',
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error(error);
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Unerwarteter Fehler',
|
||||||
|
detail:
|
||||||
|
'Beim Abrufen der Einträge hat der Server eine unerwartete Antwort zurückgegeben.',
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<p-confirmDialog></p-confirmDialog>
|
||||||
|
|
||||||
|
<div class="flex rounded-border shadow">
|
||||||
|
<div class="grow p-4 pos-relative edit-button" (click)="navigateToEdit()" role="button"
|
||||||
|
aria-roledescription="Bearbeite diesen Eintrag">
|
||||||
|
<div class="grid grid-cols-4 gap-4">
|
||||||
|
|
||||||
|
<div class="col-span-4 sm:col-span-2 md:col-span-1 flex my-auto items-center justify-center">
|
||||||
|
<div class="flex gap-2 items-center">
|
||||||
|
<ng-icon name="matDirectionsCarOutline" />
|
||||||
|
<div>{{ car().name }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-red-500 text-white rounded-r text-center flex flex-col justify-center">
|
||||||
|
<button type="button" title="Löschen" class="reset cursor-pointer primary-color-text p-4 h-full rounded-r"
|
||||||
|
(click)="confirmDeleteCar()">
|
||||||
|
<ng-icon name="matDeleteSharp"></ng-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.edit-button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { Component, DestroyRef, inject, input, output } from '@angular/core';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||||
|
import {
|
||||||
|
matDirectionsCarOutline,
|
||||||
|
} from '@ng-icons/material-icons/outline';
|
||||||
|
import {
|
||||||
|
matDeleteSharp
|
||||||
|
} from '@ng-icons/material-icons/sharp';
|
||||||
|
import { CarClient } from '@vegasco-web/api/cars/car-client';
|
||||||
|
import { RoutingService } from '@vegasco-web/services/routing.service';
|
||||||
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { CardModule } from 'primeng/card';
|
||||||
|
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
||||||
|
import { catchError, EMPTY, Observable, tap, throwError } from 'rxjs';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-car-card',
|
||||||
|
imports: [
|
||||||
|
ButtonModule,
|
||||||
|
CardModule,
|
||||||
|
ConfirmDialogModule,
|
||||||
|
NgIconComponent,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
provideIcons({
|
||||||
|
matDeleteSharp,
|
||||||
|
matDirectionsCarOutline,
|
||||||
|
}),
|
||||||
|
ConfirmationService,
|
||||||
|
],
|
||||||
|
templateUrl: './car-card.component.html',
|
||||||
|
styleUrl: './car-card.component.scss'
|
||||||
|
})
|
||||||
|
export class CarCardComponent {
|
||||||
|
readonly car = input.required<Car>();
|
||||||
|
|
||||||
|
readonly carDeleted = output<Car>();
|
||||||
|
|
||||||
|
private readonly routingService = inject(RoutingService);
|
||||||
|
private readonly carClient = inject(CarClient);
|
||||||
|
private readonly messageService = inject(MessageService);
|
||||||
|
private readonly confirmationService = inject(ConfirmationService);
|
||||||
|
|
||||||
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
|
|
||||||
|
async navigateToEdit(): Promise<void> {
|
||||||
|
await this.routingService.navigateToEditCar(this.car().id);
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmDeleteCar(): void {
|
||||||
|
this.confirmationService.confirm({
|
||||||
|
closeOnEscape: true,
|
||||||
|
dismissableMask: true,
|
||||||
|
header: 'Bist du sicher?',
|
||||||
|
message: `Möchtest du das Auto "${this.car().name}" wirklich löschen?`,
|
||||||
|
acceptButtonProps: {
|
||||||
|
label: 'Löschen',
|
||||||
|
severity: 'danger',
|
||||||
|
},
|
||||||
|
rejectButtonProps: {
|
||||||
|
label: 'Abbrechen',
|
||||||
|
outlined: true,
|
||||||
|
},
|
||||||
|
accept: () => this.deleteCar(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteCar(): void {
|
||||||
|
this.carClient.delete(this.car().id)
|
||||||
|
.pipe(
|
||||||
|
takeUntilDestroyed(this.destroyRef),
|
||||||
|
tap(() => this.carDeleted.emit(this.car())),
|
||||||
|
catchError((error) => this.handleError(error)),
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleError(error: unknown): Observable<never> {
|
||||||
|
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 Löschen des Autos ist ein Fehler aufgetreten. Bitte versuche es erneut.',
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case error.status === 400:
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Clientfehler',
|
||||||
|
detail:
|
||||||
|
'Die Anwendung scheint falsche Daten an den Server zu senden.',
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error(error);
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Unerwarteter Fehler',
|
||||||
|
detail:
|
||||||
|
'Beim Löschen des Autos hat der Server eine unerwartete Antwort zurückgegeben.',
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<span class="required">*</span>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.required {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-required-marker',
|
||||||
|
imports: [
|
||||||
|
],
|
||||||
|
templateUrl: './required-marker.component.html',
|
||||||
|
styleUrl: './required-marker.component.scss'
|
||||||
|
})
|
||||||
|
export class RequiredMarkerComponent {
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
@if (!isCarDataLoaded()) {
|
||||||
|
<div class="flex flex-col gap-6">
|
||||||
|
<p-skeleton height="3.5rem" />
|
||||||
|
<div class="flex flex-row gap-4">
|
||||||
|
<p-skeleton height="3.5rem" width="10rem" />
|
||||||
|
<p-skeleton height="3.5rem" width="10rem" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<form [formGroup]="formGroup" class="flex flex-col gap-4" (ngSubmit)="onSubmit()">
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<label [for]="formFieldNames.name">
|
||||||
|
Name
|
||||||
|
<app-required-marker />
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
placeholder="Name eingeben"
|
||||||
|
type="text"
|
||||||
|
pInputText
|
||||||
|
[formControlName]="formFieldNames.name" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<p-button type="button" label="Abbrechen" (click)="navigateToOverviewPage()" severity="warn" />
|
||||||
|
<p-button type="submit" label="Abschicken" severity="success" [disabled]="formGroup.invalid" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
}
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { Component, DestroyRef, inject, input, OnInit, signal } from '@angular/core';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
import { CarClient } from '@vegasco-web/api/cars/car-client';
|
||||||
|
import { RoutingService } from '@vegasco-web/services/routing.service';
|
||||||
|
import { MessageService } from 'primeng/api';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { ChipModule } from 'primeng/chip';
|
||||||
|
import { DatePickerModule } from 'primeng/datepicker';
|
||||||
|
import { FloatLabelModule } from 'primeng/floatlabel';
|
||||||
|
import { InputGroupModule } from 'primeng/inputgroup';
|
||||||
|
import { InputGroupAddonModule } from 'primeng/inputgroupaddon';
|
||||||
|
import { InputNumberModule } from 'primeng/inputnumber';
|
||||||
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
|
import { MultiSelectModule } from 'primeng/multiselect';
|
||||||
|
import { SelectModule } from 'primeng/select';
|
||||||
|
import { SkeletonModule } from 'primeng/skeleton';
|
||||||
|
import { catchError, EMPTY, Observable, switchMap, tap, throwError } from 'rxjs';
|
||||||
|
import { RequiredMarkerComponent } from './components/required-marker.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-edit-entry',
|
||||||
|
imports: [
|
||||||
|
ButtonModule,
|
||||||
|
ChipModule,
|
||||||
|
DatePickerModule,
|
||||||
|
FloatLabelModule,
|
||||||
|
InputGroupAddonModule,
|
||||||
|
InputGroupModule,
|
||||||
|
InputNumberModule,
|
||||||
|
InputTextModule,
|
||||||
|
MultiSelectModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
RequiredMarkerComponent,
|
||||||
|
SelectModule,
|
||||||
|
SkeletonModule,
|
||||||
|
],
|
||||||
|
templateUrl: './edit-car.component.html',
|
||||||
|
styleUrl: './edit-car.component.scss'
|
||||||
|
})
|
||||||
|
export class EditCarComponent implements OnInit {
|
||||||
|
private readonly carClient = inject(CarClient);
|
||||||
|
private readonly routingService = inject(RoutingService);
|
||||||
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
|
private readonly messageService = inject(MessageService);
|
||||||
|
|
||||||
|
protected readonly id = input<string | undefined>(undefined);
|
||||||
|
|
||||||
|
protected readonly today = new Date();
|
||||||
|
|
||||||
|
protected readonly formFieldNames = {
|
||||||
|
name: 'name',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
protected readonly formGroup = new FormGroup({
|
||||||
|
[this.formFieldNames.name]: new FormControl<string | null>({ value: null, disabled: true }, [Validators.required]),
|
||||||
|
});
|
||||||
|
|
||||||
|
protected readonly isCarDataLoaded = signal(false);
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadEntryDetailsAndEnableControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadEntryDetailsAndEnableControls() {
|
||||||
|
const carId = this.id();
|
||||||
|
|
||||||
|
if (carId === undefined || carId === null) {
|
||||||
|
this.enableFormControls();
|
||||||
|
this.isCarDataLoaded.set(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.carClient
|
||||||
|
.getSingle(carId)
|
||||||
|
.pipe(
|
||||||
|
takeUntilDestroyed(this.destroyRef),
|
||||||
|
catchError((error) => this.handleGetError(error)),
|
||||||
|
tap((car) => {
|
||||||
|
this.formGroup.patchValue({
|
||||||
|
[this.formFieldNames.name]: car.name,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
tap(() => {
|
||||||
|
this.enableFormControls();
|
||||||
|
this.isCarDataLoaded.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<void> {
|
||||||
|
await this.routingService.navigateToCars();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
if (this.formGroup.invalid) {
|
||||||
|
this.formGroup.markAllAsTouched();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var carId = this.id();
|
||||||
|
if (carId === undefined || carId === null) {
|
||||||
|
this.createCar();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateCar(carId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFormData() {
|
||||||
|
return {
|
||||||
|
name: this.formGroup.controls[this.formFieldNames.name].value!,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
createCar() {
|
||||||
|
var request: CreateCarRequest = this.getFormData();
|
||||||
|
this.carClient.create(request)
|
||||||
|
.pipe(
|
||||||
|
takeUntilDestroyed(this.destroyRef),
|
||||||
|
catchError((error) => this.handleCreateOrUpdateError(error, false)),
|
||||||
|
switchMap(() => this.routingService.navigateToCars())
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCar(id: string) {
|
||||||
|
var request: UpdateCarRequest = this.getFormData();
|
||||||
|
this.carClient.update(id, request)
|
||||||
|
.pipe(
|
||||||
|
takeUntilDestroyed(this.destroyRef),
|
||||||
|
catchError((error) => this.handleCreateOrUpdateError(error, true)),
|
||||||
|
switchMap(() => this.routingService.navigateToCars())
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleGetError(error: unknown): Observable<never> {
|
||||||
|
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 Abrufen des Autos ist ein Fehler aufgetreten. Bitte versuche es erneut.',
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error(error);
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Unerwarteter Fehler',
|
||||||
|
detail:
|
||||||
|
'Beim Abrufen des Autos hat der Server eine unerwartete Antwort zurückgegeben.',
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleCreateOrUpdateError(error: unknown, isUpdate: boolean): Observable<never> {
|
||||||
|
if (!(error instanceof HttpErrorResponse)) {
|
||||||
|
return throwError(() => error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const action = isUpdate ? 'Aktualisieren' : 'Erstellen';
|
||||||
|
|
||||||
|
switch (true) {
|
||||||
|
case error.status >= 500 && error.status <= 599:
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Serverfehler',
|
||||||
|
detail:
|
||||||
|
`Beim ${action} des Eintrags ist ein Fehler aufgetreten. Bitte versuche es erneut.`,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case error.status === 400:
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Clientfehler',
|
||||||
|
detail:
|
||||||
|
'Die Anwendung scheint falsche Daten an den Server zu senden.',
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error(error);
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Unerwarteter Fehler',
|
||||||
|
detail:
|
||||||
|
`Beim ${action} des Eintrags hat der Server eine unerwartete Antwort zurückgegeben.`,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -169,6 +169,11 @@ export class EditEntryComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSubmit(): void {
|
onSubmit(): void {
|
||||||
|
if (this.formGroup.invalid) {
|
||||||
|
this.formGroup.markAllAsTouched();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var entryId = this.id();
|
var entryId = this.id();
|
||||||
if (entryId === undefined || entryId === null) {
|
if (entryId === undefined || entryId === null) {
|
||||||
this.createEntry();
|
this.createEntry();
|
||||||
|
|||||||
@@ -18,4 +18,16 @@ export class RoutingService {
|
|||||||
async navigateToCreateEntry(): Promise<void> {
|
async navigateToCreateEntry(): Promise<void> {
|
||||||
await this.router.navigate(['entries', 'create']);
|
await this.router.navigate(['entries', 'create']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async navigateToCars(): Promise<void> {
|
||||||
|
await this.router.navigateByUrl('/cars');
|
||||||
|
}
|
||||||
|
|
||||||
|
async navigateToEditCar(entryId: string): Promise<void> {
|
||||||
|
await this.router.navigate(['cars', 'edit', entryId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async navigateToCreateCar(): Promise<void> {
|
||||||
|
await this.router.navigate(['cars', 'create']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user