New Angular based web version #1
@@ -18,6 +18,9 @@
|
|||||||
"@angular/forms": "^19.2.14",
|
"@angular/forms": "^19.2.14",
|
||||||
"@angular/platform-browser": "^19.2.14",
|
"@angular/platform-browser": "^19.2.14",
|
||||||
"@angular/router": "^19.2.14",
|
"@angular/router": "^19.2.14",
|
||||||
|
"@ng-icons/core": "^31.4.0",
|
||||||
|
"@ng-icons/material-file-icons": "^31.4.0",
|
||||||
|
"@ng-icons/material-icons": "^31.4.0",
|
||||||
"@primeng/themes": "^19.1.3",
|
"@primeng/themes": "^19.1.3",
|
||||||
"@tailwindcss/postcss": "^4.1.10",
|
"@tailwindcss/postcss": "^4.1.10",
|
||||||
"keycloak-angular": "^19.0.2",
|
"keycloak-angular": "^19.0.2",
|
||||||
|
|||||||
37
src/Vegasco-Web/pnpm-lock.yaml
generated
37
src/Vegasco-Web/pnpm-lock.yaml
generated
@@ -26,6 +26,15 @@ importers:
|
|||||||
'@angular/router':
|
'@angular/router':
|
||||||
specifier: ^19.2.14
|
specifier: ^19.2.14
|
||||||
version: 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
version: 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||||
|
'@ng-icons/core':
|
||||||
|
specifier: ^31.4.0
|
||||||
|
version: 31.4.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||||
|
'@ng-icons/material-file-icons':
|
||||||
|
specifier: ^31.4.0
|
||||||
|
version: 31.4.0
|
||||||
|
'@ng-icons/material-icons':
|
||||||
|
specifier: ^31.4.0
|
||||||
|
version: 31.4.0
|
||||||
'@primeng/themes':
|
'@primeng/themes':
|
||||||
specifier: ^19.1.3
|
specifier: ^19.1.3
|
||||||
version: 19.1.3
|
version: 19.1.3
|
||||||
@@ -1305,6 +1314,19 @@ packages:
|
|||||||
resolution: {integrity: sha512-zM0mVWSXE0a0h9aKACLwKmD6nHcRiKrPpCfvaKqG1CqDEyjEawId0ocXxVzPMCAm6kkWr2P025msfxXEnt8UGQ==}
|
resolution: {integrity: sha512-zM0mVWSXE0a0h9aKACLwKmD6nHcRiKrPpCfvaKqG1CqDEyjEawId0ocXxVzPMCAm6kkWr2P025msfxXEnt8UGQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
|
||||||
|
'@ng-icons/core@31.4.0':
|
||||||
|
resolution: {integrity: sha512-JfLiJGDX/ihWmawcnLGXtwyCqMi2qXz7gMJyXXWdUN5JA18EAnt3JnyuxDAGkoU/u7wRlcOI7irlXHU4spAKOg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@angular/common': '>=18.0.0'
|
||||||
|
'@angular/core': '>=18.0.0'
|
||||||
|
rxjs: ^6.5.3 || ^7.4.0
|
||||||
|
|
||||||
|
'@ng-icons/material-file-icons@31.4.0':
|
||||||
|
resolution: {integrity: sha512-Ffh61ghuuDRxelfTe/rHQ5IFCqUget/JeZ/NLq6QWLBycxUC6PjiEIIAXQvnVmYwCHNgxjBIRExP1/+vdHriNQ==}
|
||||||
|
|
||||||
|
'@ng-icons/material-icons@31.4.0':
|
||||||
|
resolution: {integrity: sha512-JCxwM0LXwOgT5LD99p5TwPM6dPQ5x1BGieNzAstz7vk5+aiASg3fqs3rjNx7CbN3c2QjJ8+KuKrCCBzT9DCkOQ==}
|
||||||
|
|
||||||
'@ngtools/webpack@19.2.15':
|
'@ngtools/webpack@19.2.15':
|
||||||
resolution: {integrity: sha512-H37nop/wWMkSgoU2VvrMzanHePdLRRrX52nC5tT2ZhH3qP25+PrnMyw11PoLDLv3iWXC68uB1AiKNIT+jiQbuQ==}
|
resolution: {integrity: sha512-H37nop/wWMkSgoU2VvrMzanHePdLRRrX52nC5tT2ZhH3qP25+PrnMyw11PoLDLv3iWXC68uB1AiKNIT+jiQbuQ==}
|
||||||
engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
|
engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
|
||||||
@@ -5797,6 +5819,21 @@ snapshots:
|
|||||||
'@napi-rs/nice-win32-x64-msvc': 1.0.1
|
'@napi-rs/nice-win32-x64-msvc': 1.0.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@ng-icons/core@31.4.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)':
|
||||||
|
dependencies:
|
||||||
|
'@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||||
|
'@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1)
|
||||||
|
rxjs: 7.8.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@ng-icons/material-file-icons@31.4.0':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@ng-icons/material-icons@31.4.0':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@ngtools/webpack@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.4))':
|
'@ngtools/webpack@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@angular/compiler-cli': 19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3)
|
'@angular/compiler-cli': 19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3)
|
||||||
|
|||||||
9
src/Vegasco-Web/src/app/api/models/consumption.ts
Normal file
9
src/Vegasco-Web/src/app/api/models/consumption.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export interface Consumption {
|
||||||
|
id: string;
|
||||||
|
dateTime: string;
|
||||||
|
distance: number;
|
||||||
|
amount: number;
|
||||||
|
ignoreInCalculation: boolean;
|
||||||
|
carId: string;
|
||||||
|
car: Car;
|
||||||
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
import { Component, computed, DestroyRef, inject, input, 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 { 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 { RoutingService } from '@vegasco-web/services/routing.service';
|
||||||
|
import { MessageService } from 'primeng/api';
|
||||||
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';
|
||||||
@@ -14,11 +17,8 @@ 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 { catchError, EMPTY, map, Observable, switchMap, tap, throwError } from 'rxjs';
|
import { catchError, EMPTY, map, Observable, switchMap, 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',
|
||||||
@@ -44,7 +44,7 @@ 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 consumptionClient = inject(ConsumptionClient);
|
||||||
private readonly router = inject(Router);
|
private readonly routingService = inject(RoutingService);
|
||||||
private readonly destroyRef = inject(DestroyRef);
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
private readonly messageService = inject(MessageService);
|
private readonly messageService = inject(MessageService);
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ export class EditEntryComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async navigateToOverviewPage(): Promise<void> {
|
async navigateToOverviewPage(): Promise<void> {
|
||||||
await this.router.navigateByUrl(`/entries`);
|
await this.routingService.navigateToEntries();
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit(): void {
|
onSubmit(): void {
|
||||||
@@ -119,7 +119,7 @@ export class EditEntryComponent {
|
|||||||
.pipe(
|
.pipe(
|
||||||
takeUntilDestroyed(this.destroyRef),
|
takeUntilDestroyed(this.destroyRef),
|
||||||
catchError((error) => this.handleError(error)),
|
catchError((error) => this.handleError(error)),
|
||||||
switchMap(() => this.router.navigateByUrl('/entries'))
|
switchMap(() => this.routingService.navigateToEntries())
|
||||||
)
|
)
|
||||||
.subscribe();
|
.subscribe();
|
||||||
}
|
}
|
||||||
@@ -130,7 +130,7 @@ export class EditEntryComponent {
|
|||||||
.pipe(
|
.pipe(
|
||||||
takeUntilDestroyed(this.destroyRef),
|
takeUntilDestroyed(this.destroyRef),
|
||||||
catchError((error) => this.handleError(error)),
|
catchError((error) => this.handleError(error)),
|
||||||
switchMap(() => this.router.navigateByUrl('/entries'))
|
switchMap(() => this.routingService.navigateToEntries())
|
||||||
)
|
)
|
||||||
.subscribe();
|
.subscribe();
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@ export class EditEntryComponent {
|
|||||||
'Beim Erstellen des Eintrags ist ein Fehler aufgetreten. Bitte versuche es erneut.',
|
'Beim Erstellen des Eintrags ist ein Fehler aufgetreten. Bitte versuche es erneut.',
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case error.status == 400:
|
case error.status === 400:
|
||||||
this.messageService.add({
|
this.messageService.add({
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
summary: 'Clientfehler',
|
summary: 'Clientfehler',
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<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>{{ entry().dateTime | date:"dd.MM.yyyy" }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-4 sm:col-span-2 md:col-span-1 flex my-auto items-center justify-center">
|
||||||
|
<div>{{ entry().car.name }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-4 sm:col-span-2 md:col-span-1 flex my-auto items-center justify-center">
|
||||||
|
{{entry().distance }} km
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-4 sm:col-span-2 md:col-span-1 flex my-auto items-center justify-center">
|
||||||
|
{{entry().amount }} ℓ
|
||||||
|
</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)="confirmDeleteEntry()">
|
||||||
|
<ng-icon name="matDeleteSharp"></ng-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.edit-button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
import { DatePipe } from '@angular/common';
|
||||||
|
import { Component, DestroyRef, inject, input, output } from '@angular/core';
|
||||||
|
import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client';
|
||||||
|
import { Consumption } from '@vegasco-web/api/models/consumption';
|
||||||
|
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 { TooltipModule } from 'primeng/tooltip';
|
||||||
|
import {
|
||||||
|
matCalendarMonthSharp,
|
||||||
|
matCommentSharp,
|
||||||
|
matDeleteSharp,
|
||||||
|
matMedicationSharp,
|
||||||
|
matPetsSharp,
|
||||||
|
matScaleSharp,
|
||||||
|
} from '@ng-icons/material-icons/sharp';
|
||||||
|
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { catchError, EMPTY, Observable, takeUntil, tap, throwError } from 'rxjs';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-entry-card',
|
||||||
|
imports: [
|
||||||
|
DatePipe,
|
||||||
|
TooltipModule,
|
||||||
|
ButtonModule,
|
||||||
|
CardModule,
|
||||||
|
ConfirmDialogModule,
|
||||||
|
NgIconComponent,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
provideIcons({
|
||||||
|
matDeleteSharp,
|
||||||
|
}),
|
||||||
|
ConfirmationService,
|
||||||
|
],
|
||||||
|
templateUrl: './entry-card.component.html',
|
||||||
|
styleUrl: './entry-card.component.scss'
|
||||||
|
})
|
||||||
|
export class EntryCardComponent {
|
||||||
|
readonly entry = input.required<Consumption>();
|
||||||
|
|
||||||
|
readonly entryDeleted = output<Consumption>();
|
||||||
|
|
||||||
|
private readonly routingService = inject(RoutingService);
|
||||||
|
private readonly consumptionClient = inject(ConsumptionClient);
|
||||||
|
private readonly messageService = inject(MessageService);
|
||||||
|
private readonly confirmationService = inject(ConfirmationService);
|
||||||
|
|
||||||
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
|
|
||||||
|
async navigateToEdit(): Promise<void> {
|
||||||
|
await this.routingService.navigateToEditEntry(this.entry().id);
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmDeleteEntry(): void {
|
||||||
|
const weighedAt = new Date(
|
||||||
|
Date.parse(this.entry().dateTime),
|
||||||
|
).toLocaleString('de-DE', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
});
|
||||||
|
|
||||||
|
this.confirmationService.confirm({
|
||||||
|
closeOnEscape: true,
|
||||||
|
dismissableMask: true,
|
||||||
|
header: 'Bist du sicher?',
|
||||||
|
message: `Möchtest du diesen Eintrag (${weighedAt} für ${this.entry().car.name}) wirklich löschen?`,
|
||||||
|
acceptButtonProps: {
|
||||||
|
label: 'Löschen',
|
||||||
|
severity: 'danger',
|
||||||
|
},
|
||||||
|
rejectButtonProps: {
|
||||||
|
label: 'Abbrechen',
|
||||||
|
outlined: true,
|
||||||
|
},
|
||||||
|
accept: () => this.deleteEntry(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteEntry(): void {
|
||||||
|
this.consumptionClient.delete(this.entry().id)
|
||||||
|
.pipe(
|
||||||
|
takeUntilDestroyed(this.destroyRef),
|
||||||
|
tap(() => this.entryDeleted.emit(this.entry())),
|
||||||
|
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 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:
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,9 +26,8 @@
|
|||||||
<ng-template #list let-entries>
|
<ng-template #list let-entries>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
@for (entry of entries; track entry.id) {
|
@for (entry of entries; track entry.id) {
|
||||||
{{ entry | json }}
|
<app-entry-card [entry]="entry"
|
||||||
<!-- <app-weight-entry-card [weightEntry]="weightEntry"
|
(entryDeleted)="onEntryDeleted($event)" />
|
||||||
(entryDeleted)="onEntryDeleted($event)"></app-weight-entry-card> -->
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { Component, inject } from '@angular/core';
|
|||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
import { FormControl, 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 { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client';
|
|
||||||
import { MessageService } from 'primeng/api';
|
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';
|
||||||
@@ -16,9 +14,10 @@ import {
|
|||||||
combineLatest,
|
combineLatest,
|
||||||
map,
|
map,
|
||||||
Observable,
|
Observable,
|
||||||
startWith,
|
startWith
|
||||||
tap
|
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
|
import { EntriesOverviewService } from './services/entries-overview.service';
|
||||||
|
import { EntryCardComponent } from './components/entry-card/entry-card.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-entries',
|
selector: 'app-entries',
|
||||||
@@ -27,18 +26,21 @@ import {
|
|||||||
ButtonModule,
|
ButtonModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
DataViewModule,
|
DataViewModule,
|
||||||
|
EntryCardComponent,
|
||||||
SkeletonModule,
|
SkeletonModule,
|
||||||
SelectModule,
|
SelectModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
ScrollTopModule,
|
ScrollTopModule,
|
||||||
],
|
],
|
||||||
|
providers: [
|
||||||
|
EntriesOverviewService,
|
||||||
|
],
|
||||||
templateUrl: './entries.component.html',
|
templateUrl: './entries.component.html',
|
||||||
styleUrl: './entries.component.scss'
|
styleUrl: './entries.component.scss'
|
||||||
})
|
})
|
||||||
export class EntriesComponent {
|
export class EntriesComponent {
|
||||||
private readonly consumptionClient = inject(ConsumptionClient);
|
private readonly entriesOverviewService = inject(EntriesOverviewService);
|
||||||
private readonly carClient = inject(CarClient);
|
|
||||||
private readonly messageService = inject(MessageService);
|
private readonly messageService = inject(MessageService);
|
||||||
|
|
||||||
protected readonly consumptionEntries$: Observable<ConsumptionEntry[]>;
|
protected readonly consumptionEntries$: Observable<ConsumptionEntry[]>;
|
||||||
@@ -51,13 +53,11 @@ export class EntriesComponent {
|
|||||||
private readonly deletedEntries$ = new BehaviorSubject(<string[]>[]);
|
private readonly deletedEntries$ = new BehaviorSubject(<string[]>[]);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const consumptionEntries = this.consumptionClient.getAll()
|
const entries = this.entriesOverviewService.getEntries()
|
||||||
.pipe(
|
.pipe(takeUntilDestroyed());
|
||||||
map(response => response.consumptions)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.consumptionEntries$ = combineLatest([
|
this.consumptionEntries$ = combineLatest([
|
||||||
consumptionEntries,
|
entries,
|
||||||
this.selectedCar.valueChanges.pipe(startWith(null)),
|
this.selectedCar.valueChanges.pipe(startWith(null)),
|
||||||
this.deletedEntries$,
|
this.deletedEntries$,
|
||||||
])
|
])
|
||||||
@@ -77,11 +77,8 @@ export class EntriesComponent {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
this.cars$ = this.carClient.getAll()
|
this.cars$ = this.entriesOverviewService.getCars()
|
||||||
.pipe(
|
.pipe(takeUntilDestroyed());
|
||||||
takeUntilDestroyed(),
|
|
||||||
map(response => response.cars)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onEntryDeleted(entry: ConsumptionEntry): void {
|
onEntryDeleted(entry: ConsumptionEntry): void {
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { inject, Injectable } from "@angular/core";
|
||||||
|
import { CarClient } from "@vegasco-web/api/cars/car-client";
|
||||||
|
import { ConsumptionClient } from "@vegasco-web/api/consumptions/consumption-client";
|
||||||
|
import { Consumption } from "@vegasco-web/api/models/consumption";
|
||||||
|
import { RoutingService } from "@vegasco-web/services/routing.service";
|
||||||
|
import { combineLatest, map, Observable, shareReplay } from "rxjs";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class EntriesOverviewService {
|
||||||
|
private readonly carClient = inject(CarClient);
|
||||||
|
private readonly consumptionClient = inject(ConsumptionClient);
|
||||||
|
private readonly routingService = inject(RoutingService);
|
||||||
|
|
||||||
|
private cachedCars$: Observable<Car[]> | null = null;
|
||||||
|
|
||||||
|
private ensureCarsAreCached(): void {
|
||||||
|
if (this.cachedCars$ !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cachedCars$ = this.carClient.getAll()
|
||||||
|
.pipe(
|
||||||
|
map(response => response.cars),
|
||||||
|
shareReplay(1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getEntries(): Observable<Consumption[]> {
|
||||||
|
this.ensureCarsAreCached();
|
||||||
|
|
||||||
|
const entries$ = this.consumptionClient.getAll()
|
||||||
|
.pipe(map(response => response.consumptions));
|
||||||
|
|
||||||
|
return combineLatest([this.cachedCars$!, entries$])
|
||||||
|
.pipe(
|
||||||
|
map(([cars, entries]) => {
|
||||||
|
return entries.map((entry): Consumption => ({
|
||||||
|
...entry,
|
||||||
|
car: cars.find(car => car.id === entry.carId)!
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getCars(): Observable<Car[]> {
|
||||||
|
this.ensureCarsAreCached();
|
||||||
|
return this.cachedCars$!;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/Vegasco-Web/src/app/services/routing.service.ts
Normal file
21
src/Vegasco-Web/src/app/services/routing.service.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { inject, Injectable } from "@angular/core";
|
||||||
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class RoutingService {
|
||||||
|
private readonly router = inject(Router);
|
||||||
|
|
||||||
|
async navigateToEntries(): Promise<void> {
|
||||||
|
await this.router.navigateByUrl('/entries');
|
||||||
|
}
|
||||||
|
|
||||||
|
async navigateToEditEntry(entryId: string): Promise<void> {
|
||||||
|
await this.router.navigate(['entries', 'edit', entryId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async navigateToCreateEntry(): Promise<void> {
|
||||||
|
await this.router.navigate(['entries', 'create']);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user