Compare commits

...

3 Commits

Author SHA1 Message Date
f173d46c2e Copy more stuff to make app compile
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-18 21:15:26 +02:00
73fbe30b3d Add missing information on overview page for it to compile 2025-06-18 20:54:21 +02:00
229bfe0b79 Add import paths 2025-06-18 20:54:04 +02:00
7 changed files with 196 additions and 99 deletions

View File

@@ -1,14 +1,14 @@
import {inject, Injectable} from "@angular/core"; import { HttpClient } from '@angular/common/http';
import {HttpClient} from '@angular/common/http'; import { inject, Injectable } from "@angular/core";
import {map, Observable} from 'rxjs'; import { map, Observable } from 'rxjs';
import {API_BASE_PATH} from '../api-base-path'; import { API_BASE_PATH } from "../api-base-path";
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class CarClient { export class CarClient {
private readonly http = inject(HttpClient); private readonly http = inject(HttpClient);
private readonly apiBasePath = inject(API_BASE_PATH, {optional: true}); private readonly apiBasePath = inject(API_BASE_PATH, { optional: true });
getAll(): Observable<GetCarsResponse> { getAll(): Observable<GetCarsResponse> {
return this.http.get<GetCarsResponse>(`${this.apiBasePath}/v1/cars`); return this.http.get<GetCarsResponse>(`${this.apiBasePath}/v1/cars`);

View File

@@ -1,12 +1,12 @@
import { environment } from '../../environments/environment'; import { environment } from '@vegasco-web/environments/environment';
import { import {
provideKeycloak,
createInterceptorCondition,
IncludeBearerTokenCondition,
INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG,
withAutoRefreshToken,
AutoRefreshTokenService, AutoRefreshTokenService,
UserActivityService createInterceptorCondition,
INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG,
IncludeBearerTokenCondition,
provideKeycloak,
UserActivityService,
withAutoRefreshToken
} from 'keycloak-angular'; } from 'keycloak-angular';
const serverHostBearerInterceptorCondition = createInterceptorCondition<IncludeBearerTokenCondition>({ const serverHostBearerInterceptorCondition = createInterceptorCondition<IncludeBearerTokenCondition>({

View File

@@ -1,83 +1,59 @@
@if (!loaded) { @if (isLoading()) {
<p-skeleton height="4rem" styleClass="mb-2" /> <p-skeleton height="4rem" styleClass="mb-2" />
} @else { } @else {
<form [formGroup]="formGroup" class="flex flex-col gap-4" (ngSubmit)="onSubmit()"> <form [formGroup]="formGroup" class="flex flex-col gap-4" (ngSubmit)="onSubmit()">
<div class="flex gap-2 w-full">
<div class="flex flex-col gap-2 w-full">
<label for="date"> Datum </label>
<p-datepicker [iconDisplay]="'input'"
[firstDayOfWeek]="1"
placeholder="Datum auswählen"
[showIcon]="true"
inputId="date"
formControlName="date"
styleClass="w-full"
dateFormat="dd.mm.yy" />
</div>
<div class="flex flex-col gap-2 w-full">
<label for="time"> Uhrzeit </label>
<p-datepicker [iconDisplay]="'input'"
[showIcon]="true"
[timeOnly]="true"
placeholder="Uhrzeit auswählen"
inputId="time"
formControlName="time"
styleClass="w-full"
inputStyleClass="w-full" />
</div>
</div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<label for="rabbit"> Kaninchen </label> <label [for]="formFieldNames.car"> Auto </label>
@if (rabbits$ | async; as rabbits) { @if (cars(); as cars) {
<p-select <p-select
[options]="rabbits" [options]="cars"
placeholder="Kaninchen auswählen" placeholder="Auto auswählen"
formControlName="rabbit" [formControlName]="formFieldNames.car"
optionLabel="name" [optionLabel]="formFieldNames.car"
inputId="rabbit" [inputId]="formFieldNames.car"
styleClass="w-full" /> styleClass="w-full" />
} }
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<label for="medicines"> Medizin </label> <label [for]="formFieldNames.date"> Datum </label>
@if (medicines$ | async; as medicines) { <p-datepicker [iconDisplay]="'input'"
<p-multiSelect [firstDayOfWeek]="1"
[options]="medicines" placeholder="Datum auswählen"
[showToggleAll]="false" [showIcon]="true"
[showHeader]="false" [inputId]="formFieldNames.date"
[autoOptionFocus]="false" [formControlName]="formFieldNames.date"
[filter]="false" styleClass="w-full"
[showClear]="true" dateFormat="dd.mm.yy" />
[maxSelectedLabels]="medicines.length"
optionLabel="abbreviation"
formControlName="medicines"
inputId="medicines"
placeholder="Medizin auswählen"
display="chip"
styleClass="w-full">
<ng-template let-medicine pTemplate="item">
{{ medicine.name }} ({{ medicine.abbreviation }})
</ng-template>
</p-multiSelect>
}
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<label for="weight"> Gewicht </label> <label [for]="formFieldNames.mileage"> Kilometerstand </label>
<p-inputGroup> <p-inputGroup>
<input id="weight" placeholder="Gewicht eingeben" type="number" min="1" pInputText formControlName="weight" /> <input
<p-inputGroupAddon>g</p-inputGroupAddon> id="mileage"
placeholder="Kilometerstand eingeben"
type="number"
min="1"
pInputText
[formControlName]="formFieldNames.mileage" />
<p-inputGroupAddon>km</p-inputGroupAddon>
</p-inputGroup> </p-inputGroup>
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<label for="comment"> Kommentar </label> <label [for]="formFieldNames.amount"> Menge </label>
<input pInputText id="comment" placeholder="Kommentar eingeben" type="text" formControlName="comment" <p-inputGroup>
class="w-full" /> <input
id="amount"
placeholder="Menge eingeben"
type="number"
min="1"
pInputText
[formControlName]="formFieldNames.amount" />
<p-inputGroupAddon>l</p-inputGroupAddon>
</p-inputGroup>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">

View File

@@ -1,6 +1,8 @@
import { AsyncPipe } from '@angular/common'; import { Component, computed, inject, Signal } from '@angular/core';
import { Component, inject } from '@angular/core'; 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 { CarClient } from '@vegasco-web/api/cars/car-client';
import { ButtonModule } from 'primeng/button'; import { ButtonModule } from 'primeng/button';
import { ChipModule } from 'primeng/chip'; import { ChipModule } from 'primeng/chip';
import { DatePickerModule } from 'primeng/datepicker'; import { DatePickerModule } from 'primeng/datepicker';
@@ -12,11 +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';
@Component({ @Component({
selector: 'app-edit-entry', selector: 'app-edit-entry',
imports: [ imports: [
AsyncPipe,
ButtonModule, ButtonModule,
ChipModule, ChipModule,
DatePickerModule, DatePickerModule,
@@ -35,16 +37,133 @@ import { SkeletonModule } from 'primeng/skeleton';
}) })
export class EditEntryComponent { export class EditEntryComponent {
private readonly formBuilder = inject(FormBuilder); private readonly formBuilder = inject(FormBuilder);
private readonly carClient = inject(CarClient);
private readonly router = inject(Router);
protected readonly formFieldNames = {
car: 'car',
date: 'date',
mileage: 'mileage',
amount: 'amount',
}
protected readonly formGroup = this.formBuilder.group({ protected readonly formGroup = this.formBuilder.group({
date: [<Date | null>null], [this.formFieldNames.car]: [<Car | null>null, Validators.required],
time: [<Date | null>null], [this.formFieldNames.date]: [<Date | null>null],
rabbit: [<Rabbit | null>null, Validators.required], [this.formFieldNames.mileage]: [
weight: [
<number | null>null, <number | null>null,
Validators.min(1), [Validators.required, Validators.min(1)],
],
[this.formFieldNames.amount]: [
<number | null>null,
[Validators.required, Validators.min(1)],
], ],
medicines: [<Medicine[]>[]],
comment: [<string | null>null],
}); });
protected readonly cars: Signal<Car[] | undefined>;
protected readonly isLoading = computed(() => {
return this.cars() === undefined;
})
constructor() {
this.cars = toSignal(
this.carClient
.getAll()
.pipe(
takeUntilDestroyed(),
map(response => response.cars)
),
);
}
async navigateToOverviewPage(): Promise<void> {
await this.router.navigateByUrl(`/entries`);
}
onSubmit(): void {
// if (!this.entryId) {
// this.createEntry();
// return;
// }
// this.updateEntry();
}
createEntry() {
// this.api
// .createWeightEntry(
// this.getWeighedAt(),
// this.formGroup.controls['weight'].value,
// this.formGroup.controls['comment'].value,
// this.formGroup.controls['rabbit'].value!.id,
// this.formGroup.controls['medicines'].value?.map((x) => x.id) ?? [],
// )
// .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() {
// this.api
// .updateWeightEntry(
// this.entryId!,
// this.getWeighedAt(),
// this.formGroup.controls['weight'].value,
// this.formGroup.controls['comment'].value,
// this.formGroup.controls['rabbit'].value!.id,
// this.formGroup.controls['medicines'].value?.map((x) => x.id) ?? [],
// )
// .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 Aktualisieren 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;
// }
// },
// });
}
} }

View File

@@ -16,11 +16,11 @@
<p-dataView <p-dataView
[value]="entries" [value]="entries"
[paginator]="true" [paginator]="true"
[rows]="rowsPerPageDefaultOption" [rows]="25"
[rowsPerPageOptions]="rowsPerPageOptions" [rowsPerPageOptions]="[10, 25, 50, 100]"
[pageLinks]="0" [pageLinks]="0"
[showCurrentPageReport]="true" [showCurrentPageReport]="true"
[currentPageReportTemplate]="currentPageReportTemplate" currentPageReportTemplate="{currentPage} / {totalPages}"
layout="list" layout="list"
> >
<ng-template #list let-entries> <ng-template #list let-entries>

View File

@@ -1,26 +1,20 @@
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 { FormControl, ReactiveFormsModule } from '@angular/forms'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ReactiveFormsModule } from '@angular/forms';
import { RouterLink } from '@angular/router'; import { RouterLink } from '@angular/router';
import { MessageService } from 'primeng/api'; import { CarClient } from '@vegasco-web/api/cars/car-client';
import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client';
import { ButtonModule } from 'primeng/button'; import { ButtonModule } from 'primeng/button';
import { DataViewModule } from 'primeng/dataview'; import { DataViewModule } from 'primeng/dataview';
import { SelectModule } from 'primeng/select';
import { ScrollTopModule } from 'primeng/scrolltop'; import { ScrollTopModule } from 'primeng/scrolltop';
import { SelectModule } from 'primeng/select';
import { SkeletonModule } from 'primeng/skeleton'; import { SkeletonModule } from 'primeng/skeleton';
import { import {
BehaviorSubject,
combineLatest,
map, map,
Observable, Observable,
of, tap
startWith,
tap,
} from 'rxjs'; } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {CarClient} from '../../../api/cars/car-client';
import {ConsumptionClient} from '../../../api/consumptions/consumption-client';
@Component({ @Component({
selector: 'app-entries', selector: 'app-entries',
@@ -45,6 +39,8 @@ export class EntriesComponent {
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);
constructor() { constructor() {
this.consumptionEntries$ = this.consumptionClient.getAll() this.consumptionEntries$ = this.consumptionClient.getAll()
.pipe( .pipe(

View File

@@ -3,6 +3,12 @@
{ {
"compileOnSave": false, "compileOnSave": false,
"compilerOptions": { "compilerOptions": {
"baseUrl": "./",
"paths": {
"@vegasco-web/*": ["src/app/*"],
"@vegasco-web/assets/*": ["assets/*"],
"@vegasco-web/environments/*": ["src/environments/*"]
},
"strict": true, "strict": true,
"noImplicitOverride": true, "noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true, "noPropertyAccessFromIndexSignature": true,