From be680da69262b051f7b41d4586debc3321f14ab0 Mon Sep 17 00:00:00 2001 From: Piet Ostendorp Date: Sat, 15 Nov 2025 02:28:43 +0100 Subject: [PATCH] Add auto-refresh polling to theater overlay Introduces periodic polling to refresh seat and performance data in TheaterOverlayComponent using RxJS interval and Angular signals. Polling is paused when the component is destroyed or manually via new methods, and the template is updated to use the seatsPerRow signal. --- .../theater-overlay.component.html | 2 +- .../theater-overlay.component.ts | 110 +++++++++++++----- 2 files changed, 81 insertions(+), 31 deletions(-) diff --git a/src/app/theater-overlay/theater-overlay.component.html b/src/app/theater-overlay/theater-overlay.component.html index 315e341..f2d7a36 100644 --- a/src/app/theater-overlay/theater-overlay.component.html +++ b/src/app/theater-overlay/theater-overlay.component.html @@ -13,7 +13,7 @@ } @else { - + } diff --git a/src/app/theater-overlay/theater-overlay.component.ts b/src/app/theater-overlay/theater-overlay.component.ts index 9050e31..5a3e55c 100644 --- a/src/app/theater-overlay/theater-overlay.component.ts +++ b/src/app/theater-overlay/theater-overlay.component.ts @@ -1,11 +1,14 @@ -import {Component, inject, OnInit} from '@angular/core'; +import {Component, DestroyRef, inject, OnDestroy, OnInit, signal} from '@angular/core'; import {HttpService} from '../http.service'; import {LoadingService} from '../loading.service'; -import {catchError, forkJoin, of, tap} from 'rxjs'; +import {catchError, filter, forkJoin, interval, of, startWith, switchMap, tap} from 'rxjs'; import {Sitzkategorie, Sitzplatz, Vorstellung} from '@infinimotion/model-frontend'; import {TheaterSeatState} from '../model/theater-seat-state.model'; import {ActivatedRoute} from '@angular/router'; import {SelectedSeatsService} from '../selected-seats.service'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +const POLLING_INTERVAL_MS = 5000; @Component({ selector: 'app-theater-overlay', @@ -13,43 +16,78 @@ import {SelectedSeatsService} from '../selected-seats.service'; templateUrl: './theater-overlay.component.html', styleUrl: './theater-overlay.component.css' }) -export class TheaterOverlayComponent implements OnInit { +export class TheaterOverlayComponent implements OnInit, OnDestroy { private http = inject(HttpService); - loading = inject(LoadingService) + private route = inject(ActivatedRoute); + private destroyRef = inject(DestroyRef); + private selectedSeatService = inject(SelectedSeatsService); + + readonly loading = inject(LoadingService); showId!: number; - seatsPerRow: { seat: Sitzplatz, state: TheaterSeatState }[][] = [] + seatsPerRow = signal<{ seat: Sitzplatz, state: TheaterSeatState }[][]>([]); performance: Vorstellung | undefined; seatCategories: Sitzkategorie[] = []; - constructor(private route: ActivatedRoute, private selectedSeatService : SelectedSeatsService) {} + private isPollingEnabled = signal(true); + private isInitialLoad = signal(true); -ngOnInit() { - this.showId = Number(this.route.snapshot.paramMap.get('id')!); - this.selectedSeatService.clearSelection(); - this.selectedSeatService.setSeatSelectable(true); - this.loadPerformanceAndSeats(); -} + ngOnInit() { + this.showId = Number(this.route.snapshot.paramMap.get('id')!); + this.selectedSeatService.clearSelection(); + this.selectedSeatService.setSeatSelectable(true); -loadPerformanceAndSeats() { - this.loading.show(); + this.startAutoRefresh(); + } - forkJoin({ - performance: this.http.getPerformaceById(this.showId), - seats: this.http.getSeatsByShowId(this.showId) - }).pipe( - tap(({ performance, seats }) => { - this.performance = performance; - this.seatsPerRow = this.converter(seats); - this.loading.hide(); - }), - catchError(err => { - this.loading.showError(err); - console.error('Fehler beim Laden', err); - return of({ performance: null, seats: [] }); - }) - ).subscribe(); -} + ngOnDestroy() { + this.isPollingEnabled.set(false); + } + + private startAutoRefresh() { + interval(POLLING_INTERVAL_MS).pipe( + startWith(0), + filter(() => this.isPollingEnabled()), + filter(() => !this.selectedSeatService.committed()), + switchMap(() => this.loadPerformanceAndSeats()), + takeUntilDestroyed(this.destroyRef) + ).subscribe(); + } + + private loadPerformanceAndSeats() { + if (this.isInitialLoad()) { + this.loading.show(); + } + + return forkJoin({ + performance: this.http.getPerformaceById(this.showId), + seats: this.http.getSeatsByShowId(this.showId) + }).pipe( + tap(({ performance, seats }) => { + this.performance = performance; + this.seatsPerRow.set(this.converter(seats)); + + if (this.isInitialLoad()) { + this.loading.hide(); + this.isInitialLoad.set(false); + } + }), + catchError(err => { + if (this.isInitialLoad()) { + this.loading.showError(err); + } else { + console.warn('Fehler beim Aktualisieren der Sitze:', err); + } + + if (this.isInitialLoad()) { + this.loading.hide(); + this.isInitialLoad.set(false); + } + + return of({ performance: null, seats: { seats: [], reserved: [], booked: [] } }); + }) + ); + } converter(resp: { seats: Sitzplatz[], reserved: Sitzplatz[], booked: Sitzplatz[] }): { seat: Sitzplatz, @@ -80,6 +118,18 @@ loadPerformanceAndSeats() { rows.forEach(row => row.sort((a, b) => a.seat.position - b.seat.position)); return rows; } + + refreshSeats(): void { + this.loadPerformanceAndSeats().subscribe(); + } + + pausePolling(): void { //TODO: Ab Stepper Schritt 2 Polling pausieren + this.isPollingEnabled.set(false); + } + + resumePolling(): void { + this.isPollingEnabled.set(true); + } }