Compare commits

...

3 Commits

Author SHA1 Message Date
d8f82bb2d1 Add entry filtering
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-19 13:38:40 +02:00
390241aa53 Add sending entries to api 2025-06-19 13:38:29 +02:00
b323f7a29f Fix api clients 2025-06-19 13:38:15 +02:00
5 changed files with 130 additions and 96 deletions

View File

@@ -22,8 +22,8 @@ export class CarClient {
return this.http.post<Car>(`${this.apiBasePath}/v1/cars`, request); return this.http.post<Car>(`${this.apiBasePath}/v1/cars`, request);
} }
update(request: UpdateCarRequest): Observable<Car> { update(id: string, request: UpdateCarRequest): Observable<Car> {
return this.http.put<Car>(`${this.apiBasePath}/v1/cars`, request); return this.http.put<Car>(`${this.apiBasePath}/v1/cars/${id}`, request);
} }
delete(id: string): Observable<void> { delete(id: string): Observable<void> {

View File

@@ -18,12 +18,12 @@ export class ConsumptionClient {
return this.http.get<ConsumptionEntry>(`${this.apiBasePath}/v1/consumptions/${id}`); return this.http.get<ConsumptionEntry>(`${this.apiBasePath}/v1/consumptions/${id}`);
} }
create(request: CreateCarRequest): Observable<ConsumptionEntry> { create(request: CreateConsumptionEntry): Observable<ConsumptionEntry> {
return this.http.post<ConsumptionEntry>(`${this.apiBasePath}/v1/consumptions`, request); return this.http.post<ConsumptionEntry>(`${this.apiBasePath}/v1/consumptions`, request);
} }
update(request: UpdateCarRequest): Observable<ConsumptionEntry> { update(id: string, request: UpdateConsumptionEntry): Observable<ConsumptionEntry> {
return this.http.put<ConsumptionEntry>(`${this.apiBasePath}/v1/consumptions`, request); return this.http.put<ConsumptionEntry>(`${this.apiBasePath}/v1/consumptions/${id}`, request);
} }
delete(id: string): Observable<void> { delete(id: string): Observable<void> {

View File

@@ -1,4 +1,4 @@
import { Component, computed, inject, Signal } from '@angular/core'; import { Component, computed, DestroyRef, inject, input, Signal } from '@angular/core';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
@@ -14,8 +14,11 @@ import { InputTextModule } from 'primeng/inputtext';
import { MultiSelectModule } from 'primeng/multiselect'; import { MultiSelectModule } from 'primeng/multiselect';
import { SelectModule } from 'primeng/select'; import { SelectModule } from 'primeng/select';
import { SkeletonModule } from 'primeng/skeleton'; import { SkeletonModule } from 'primeng/skeleton';
import { map } from 'rxjs'; import { catchError, EMPTY, map, Observable, switchMap, tap, throwError } from 'rxjs';
import { RequiredMarkerComponent } from './components/required-marker.component'; import { RequiredMarkerComponent } from './components/required-marker.component';
import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client';
import { HttpErrorResponse } from '@angular/common/http';
import { MessageService } from 'primeng/api';
@Component({ @Component({
selector: 'app-edit-entry', selector: 'app-edit-entry',
@@ -40,14 +43,19 @@ import { RequiredMarkerComponent } from './components/required-marker.component'
export class EditEntryComponent { export class EditEntryComponent {
private readonly formBuilder = inject(FormBuilder); private readonly formBuilder = inject(FormBuilder);
private readonly carClient = inject(CarClient); private readonly carClient = inject(CarClient);
private readonly consumptionClient = inject(ConsumptionClient);
private readonly router = inject(Router); private readonly router = inject(Router);
private readonly destroyRef = inject(DestroyRef);
private readonly messageService = inject(MessageService);
protected readonly entryId = input<string | undefined>(undefined);
protected readonly formFieldNames = { protected readonly formFieldNames = {
car: 'car', car: 'car',
date: 'date', date: 'date',
mileage: 'mileage', mileage: 'mileage',
amount: 'amount', amount: 'amount',
} } as const;
protected readonly formGroup = this.formBuilder.group({ protected readonly formGroup = this.formBuilder.group({
[this.formFieldNames.car]: [<Car | null>null, Validators.required], [this.formFieldNames.car]: [<Car | null>null, Validators.required],
@@ -84,88 +92,82 @@ export class EditEntryComponent {
} }
onSubmit(): void { onSubmit(): void {
// if (!this.entryId) { var entryId = this.entryId();
// this.createEntry(); if (entryId === undefined || entryId === null) {
// return; this.createEntry();
// } return;
}
// this.updateEntry(); this.updateEntry(entryId);
}
private getFormData() {
var dateTime = new Date((this.formGroup.controls[this.formFieldNames.date].value ?? new Date).setHours(0, 0, 0, 0));
return {
carId: this.formGroup.controls[this.formFieldNames.car].value!.id,
dateTime: dateTime.toISOString(),
distance: this.formGroup.controls[this.formFieldNames.mileage].value!,
amount: this.formGroup.controls[this.formFieldNames.amount].value!,
ignoreInCalculation: false,
};
} }
createEntry() { createEntry() {
// this.api var request: CreateConsumptionEntry = this.getFormData();
// .createWeightEntry( this.consumptionClient.create(request)
// this.getWeighedAt(), .pipe(
// this.formGroup.controls['weight'].value, takeUntilDestroyed(this.destroyRef),
// this.formGroup.controls['comment'].value, catchError((error) => this.handleError(error)),
// this.formGroup.controls['rabbit'].value!.id, switchMap(() => this.router.navigateByUrl('/entries'))
// this.formGroup.controls['medicines'].value?.map((x) => x.id) ?? [], )
// ) .subscribe();
// .subscribe({
// next: (_) => {
// this.router.navigateByUrl('/weight-entries');
// },
// error: (error: HttpErrorResponse) => {
// 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;
// case error.status == 400:
// this.messageService.add({
// severity: 'error',
// summary: 'Clientfehler',
// detail:
// 'Die Anwendung scheint falsche Daten an den Server zu senden.',
// });
// break;
// default:
// break;
// }
// },
// });
} }
updateEntry() { updateEntry(id: string) {
// this.api var request: UpdateConsumptionEntry = this.getFormData();
// .updateWeightEntry( this.consumptionClient.update(id, request)
// this.entryId!, .pipe(
// this.getWeighedAt(), takeUntilDestroyed(this.destroyRef),
// this.formGroup.controls['weight'].value, catchError((error) => this.handleError(error)),
// this.formGroup.controls['comment'].value, switchMap(() => this.router.navigateByUrl('/entries'))
// this.formGroup.controls['rabbit'].value!.id, )
// this.formGroup.controls['medicines'].value?.map((x) => x.id) ?? [], .subscribe();
// ) }
// .subscribe({
// next: (_) => { private handleError(error: unknown): Observable<never> {
// this.router.navigateByUrl('/weight-entries'); if (!(error instanceof HttpErrorResponse)) {
// }, return throwError(() => error);
// error: (error: HttpErrorResponse) => { }
// switch (true) {
// case error.status >= 500 && error.status <= 599: switch (true) {
// this.messageService.add({ case error.status >= 500 && error.status <= 599:
// severity: 'error', this.messageService.add({
// summary: 'Serverfehler', severity: 'error',
// detail: summary: 'Serverfehler',
// 'Beim Aktualisieren des Eintrags ist ein Fehler aufgetreten. Bitte versuche es erneut.', detail:
// }); 'Beim Erstellen des Eintrags ist ein Fehler aufgetreten. Bitte versuche es erneut.',
// break; });
// case error.status == 400: break;
// this.messageService.add({ case error.status == 400:
// severity: 'error', this.messageService.add({
// summary: 'Clientfehler', severity: 'error',
// detail: summary: 'Clientfehler',
// 'Die Anwendung scheint falsche Daten an den Server zu senden.', detail:
// }); 'Die Anwendung scheint falsche Daten an den Server zu senden.',
// break; });
// default: break;
// 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;
} }
} }

View File

@@ -2,8 +2,8 @@
<p-scrollTop /> <p-scrollTop />
<div class="mb-4 flex gap-2 md:justify-between"> <div class="mb-4 flex gap-2 md:justify-between">
<div class="basis-full lg:basis-1/4 md:basis-1/2 p-0"> <div class="basis-full lg:basis-1/4 md:basis-1/2 p-0">
<!-- <p-select styleClass="w-full" [formControl]="selectedRabbit" placeholder="Kaninchen" [showClear]="true" <p-select styleClass="w-full" [formControl]="selectedCar" placeholder="Auto" [showClear]="true"
[options]="(rabbits$ | async)!" optionLabel="name" /> --> [options]="(cars$ | async)!" optionLabel="name" />
</div> </div>
<div> <div>
<p-button label="Erstellen" routerLink="/entries/create"> <p-button label="Erstellen" routerLink="/entries/create">

View File

@@ -1,18 +1,22 @@
import { AsyncPipe, CommonModule } from '@angular/common'; import { AsyncPipe, CommonModule } from '@angular/common';
import { Component, inject } from '@angular/core'; import { Component, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ReactiveFormsModule } from '@angular/forms'; import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { RouterLink } from '@angular/router'; import { RouterLink } from '@angular/router';
import { CarClient } from '@vegasco-web/api/cars/car-client'; import { CarClient } from '@vegasco-web/api/cars/car-client';
import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client'; import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client';
import { MessageService } from 'primeng/api';
import { ButtonModule } from 'primeng/button'; import { ButtonModule } from 'primeng/button';
import { DataViewModule } from 'primeng/dataview'; import { DataViewModule } from 'primeng/dataview';
import { ScrollTopModule } from 'primeng/scrolltop'; import { ScrollTopModule } from 'primeng/scrolltop';
import { SelectModule } from 'primeng/select'; import { SelectModule } from 'primeng/select';
import { SkeletonModule } from 'primeng/skeleton'; import { SkeletonModule } from 'primeng/skeleton';
import { import {
BehaviorSubject,
combineLatest,
map, map,
Observable, Observable,
startWith,
tap tap
} from 'rxjs'; } from 'rxjs';
@@ -35,29 +39,57 @@ import {
export class EntriesComponent { export class EntriesComponent {
private readonly consumptionClient = inject(ConsumptionClient); private readonly consumptionClient = inject(ConsumptionClient);
private readonly carClient = inject(CarClient); private readonly carClient = inject(CarClient);
private readonly messageService = inject(MessageService);
protected readonly consumptionEntries$: Observable<ConsumptionEntry[]>; protected readonly consumptionEntries$: Observable<ConsumptionEntry[]>;
protected readonly cars$: Observable<Car[]>; protected readonly cars$: Observable<Car[]>;
protected readonly skeletonsIterationSource = Array(10).fill(0); protected readonly skeletonsIterationSource = Array(10).fill(0);
protected readonly selectedCar = new FormControl<Car | null | undefined>(null);
private readonly deletedEntries$ = new BehaviorSubject(<string[]>[]);
constructor() { constructor() {
this.consumptionEntries$ = this.consumptionClient.getAll() const consumptionEntries = this.consumptionClient.getAll()
.pipe(
map(response => response.consumptions)
);
this.consumptionEntries$ = combineLatest([
consumptionEntries,
this.selectedCar.valueChanges.pipe(startWith(null)),
this.deletedEntries$,
])
.pipe( .pipe(
takeUntilDestroyed(), takeUntilDestroyed(),
tap((response) => { map(([entries, selectedCar, deletedEntries]) => {
console.log('Entries response:', response); const nonDeletedEntries =
}), deletedEntries.length === 0
map(response => response.consumptions) ? entries
: entries.filter(entry => !deletedEntries.includes(entry.id));
if (!selectedCar) {
return nonDeletedEntries;
}
return nonDeletedEntries.filter(entry => entry.carId === selectedCar.id);
})
); );
this.cars$ = this.carClient.getAll() this.cars$ = this.carClient.getAll()
.pipe( .pipe(
takeUntilDestroyed(), takeUntilDestroyed(),
tap((response) => {
console.log('Cars response:', response);
}),
map(response => response.cars) map(response => response.cars)
); );
} }
onEntryDeleted(entry: ConsumptionEntry): void {
this.deletedEntries$.next([...this.deletedEntries$.value, entry.id]);
this.messageService.add({
severity: 'success',
summary: 'Eintrag gelöscht',
detail: 'Der Eintrag wurde erfolgreich gelöscht.',
});
}
} }