diff --git a/src/app/app-module.ts b/src/app/app-module.ts index 2794819..a8554ec 100644 --- a/src/app/app-module.ts +++ b/src/app/app-module.ts @@ -54,7 +54,6 @@ import { MovieImportNoSearchResultComponent } from './movie-import-no-search-res import { MovieImportSearchInfoComponent } from './movie-import-search-info/movie-import-search-info.component'; import { LoginDialog } from './login/login.dialog'; import { PerformanceInfoComponent } from './performance-info/performance-info.component'; -import { ShoppingCartComponent } from './shopping-cart/shopping-cart.component'; import { OrderComponent } from './order/order.component'; import { SeatSelectionComponent } from './seat-selection/seat-selection.component'; import { NoSeatsInHallComponent } from './no-seats-in-hall/no-seats-in-hall.component'; @@ -65,6 +64,7 @@ import { PurchaseFailedComponent } from './purchase-failed/purchase-failed.compo import { TicketSmallComponent } from './ticket-small/ticket-small.component'; import { TicketListComponent } from './ticket-list/ticket-list.component'; import { ZoomWarningComponent } from './zoom-warning/zoom-warning.component'; +import { SelectionConflictInfoComponent } from './selection-conflict-info/selection-conflict-info.component'; @NgModule({ @@ -98,7 +98,6 @@ import { ZoomWarningComponent } from './zoom-warning/zoom-warning.component'; MovieImportSearchInfoComponent, LoginDialog, PerformanceInfoComponent, - ShoppingCartComponent, OrderComponent, SeatSelectionComponent, NoSeatsInHallComponent, @@ -109,6 +108,7 @@ import { ZoomWarningComponent } from './zoom-warning/zoom-warning.component'; TicketSmallComponent, TicketListComponent, ZoomWarningComponent, + SelectionConflictInfoComponent, ], imports: [ AppRoutingModule, diff --git a/src/app/order/order.component.html b/src/app/order/order.component.html index 7b5b71d..5e8dffd 100644 --- a/src/app/order/order.component.html +++ b/src/app/order/order.component.html @@ -33,6 +33,10 @@
+ @if (selectedSeatsService.hadConflict()) { + + } +
@for (seatCategory of seatCategories(); track $index) { diff --git a/src/app/order/order.component.ts b/src/app/order/order.component.ts index 04717de..4d2665d 100644 --- a/src/app/order/order.component.ts +++ b/src/app/order/order.component.ts @@ -1,11 +1,11 @@ import { SelectedSeatsService } from './../selected-seats.service'; import { LoadingService } from './../loading.service'; import { Bestellung, Eintrittskarte, Sitzkategorie, Sitzplatz, Vorstellung } from '@infinimotion/model-frontend'; -import { Component, computed, DestroyRef, EventEmitter, inject, input, output, Output, signal, ViewChild } from '@angular/core'; +import { Component, computed, DestroyRef, inject, input, output, signal } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { StepperSelectionEvent } from '@angular/cdk/stepper'; import { HttpService } from '../http.service'; -import { catchError, tap, finalize, switchMap, map, EMPTY, forkJoin } from 'rxjs'; +import { catchError, tap, finalize, EMPTY } from 'rxjs'; import { MatStepper } from '@angular/material/stepper'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; diff --git a/src/app/selected-seats.service.ts b/src/app/selected-seats.service.ts index 18a84ff..2fe8456 100644 --- a/src/app/selected-seats.service.ts +++ b/src/app/selected-seats.service.ts @@ -1,5 +1,5 @@ import { computed, Injectable, signal } from '@angular/core'; -import {Sitzplatz} from '@infinimotion/model-frontend'; +import { Sitzplatz } from '@infinimotion/model-frontend'; @Injectable({ providedIn: 'root', @@ -10,21 +10,25 @@ export class SelectedSeatsService { private seatIsSelectableSignal = signal(true); private committedSignal = signal(false); private debugSignal = signal(false); + private hadConflictSignal = signal(false); readonly selectedSeats = this.selectedSeatsSignal.asReadonly(); readonly seatIsSelectable = this.seatIsSelectableSignal.asReadonly(); readonly committed = this.committedSignal.asReadonly(); readonly debug = this.debugSignal.asReadonly(); + readonly hadConflict = this.hadConflictSignal.asReadonly(); readonly totalSeats = computed(() => this.selectedSeats().length); readonly totalPrice = computed(() => this.selectedSeats().reduce((sum, seat) => sum + seat.row.category.price, 0)); pushSelectedSeat(selectedSeat: Sitzplatz): void { this.selectedSeatsSignal.update(seats => [...seats, selectedSeat]); + this.setConflict(false); } removeSelectedSeat(selectedSeat: Sitzplatz): void { this.selectedSeatsSignal.update(seats => seats.filter(seat => seat.id !== selectedSeat.id)); + this.setConflict(false); } getSeatsByCategory(categoryId: number): Sitzplatz[] { @@ -58,4 +62,8 @@ export class SelectedSeatsService { isSeatSelected(seatId: number): boolean { return this.selectedSeats().some(seat => seat.id === seatId); } + + setConflict(value: boolean): void { + this.hadConflictSignal.set(value); + } } diff --git a/src/app/shopping-cart/shopping-cart.component.css b/src/app/selection-conflict-info/selection-conflict-info.component.css similarity index 100% rename from src/app/shopping-cart/shopping-cart.component.css rename to src/app/selection-conflict-info/selection-conflict-info.component.css diff --git a/src/app/selection-conflict-info/selection-conflict-info.component.html b/src/app/selection-conflict-info/selection-conflict-info.component.html new file mode 100644 index 0000000..d6f9b7e --- /dev/null +++ b/src/app/selection-conflict-info/selection-conflict-info.component.html @@ -0,0 +1,16 @@ +
+ +
+ + warning + +
+ +
+
+

Sitzplatz aus dem Warenkorb entfernt!

+
+

Leider ist der von Ihnen gewählte Sitzplazt nicht mehr verfügbar. Bitte wählen Sie einen anderen.

+
+ +
diff --git a/src/app/selection-conflict-info/selection-conflict-info.component.ts b/src/app/selection-conflict-info/selection-conflict-info.component.ts new file mode 100644 index 0000000..57ed39c --- /dev/null +++ b/src/app/selection-conflict-info/selection-conflict-info.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-selection-conflict-info', + standalone: false, + templateUrl: './selection-conflict-info.component.html', + styleUrl: './selection-conflict-info.component.css', +}) +export class SelectionConflictInfoComponent { + +} diff --git a/src/app/shopping-cart/shopping-cart.component.html b/src/app/shopping-cart/shopping-cart.component.html deleted file mode 100644 index 5aff33e..0000000 --- a/src/app/shopping-cart/shopping-cart.component.html +++ /dev/null @@ -1 +0,0 @@ -

shopping-cart works!

diff --git a/src/app/shopping-cart/shopping-cart.component.ts b/src/app/shopping-cart/shopping-cart.component.ts deleted file mode 100644 index 382aba1..0000000 --- a/src/app/shopping-cart/shopping-cart.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-shopping-cart', - standalone: false, - templateUrl: './shopping-cart.component.html', - styleUrl: './shopping-cart.component.css' -}) -export class ShoppingCartComponent { - -} diff --git a/src/app/theater-overlay/theater-overlay.component.ts b/src/app/theater-overlay/theater-overlay.component.ts index 961fb45..8fc9a88 100644 --- a/src/app/theater-overlay/theater-overlay.component.ts +++ b/src/app/theater-overlay/theater-overlay.component.ts @@ -7,7 +7,6 @@ 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'; -import { MatDialog } from '@angular/material/dialog'; import { MatSnackBar, MatSnackBarRef, TextOnlySnackBar } from '@angular/material/snack-bar'; const POLLING_INTERVAL_MS = 5 * 1000; @@ -24,14 +23,14 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy { private route = inject(ActivatedRoute); private destroyRef = inject(DestroyRef); private selectedSeatService = inject(SelectedSeatsService); - private dialog = inject(MatDialog); readonly loading = inject(LoadingService); showId!: number; orderId?: string; - seatsPerRow = signal<{ seat: Sitzplatz | null, state: TheaterSeatState | null }[][]>([]); performance: Vorstellung | undefined; + blockedSeats: Sitzplatz[] | undefined; + seatsPerRow = signal<{ seat: Sitzplatz | null, state: TheaterSeatState | null }[][]>([]); seatCategories: Sitzkategorie[] = []; snackBarRef: MatSnackBarRef | undefined @@ -169,6 +168,21 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy { }).pipe( tap(({ performance, seats }) => { this.performance = performance; + + if (this.blockedSeats && !this.equalSeats(this.blockedSeats, seats.reserved)) { + console.info('[TheaterOverlay] External booking detected. Checking for conflicts.'); + + const conflicts = this.getConflictingSeats(seats.reserved); + if (conflicts.length > 0) { + console.info('[TheaterOverlay] Conflicts! Updating shopping cart.'); + conflicts.forEach(seat => this.selectedSeatService.removeSelectedSeat(seat)); + this.selectedSeatService.setConflict(true); + } + + this.selectedSeatService.selectedSeats + } + this.blockedSeats = seats.reserved; + this.seatsPerRow.set(this.converter(seats)); if (this.isInitialLoad()) { @@ -197,6 +211,28 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy { ); } + private equalSeats(a: Sitzplatz[], b: Sitzplatz[]): boolean { + if (a === b) return true; + if (a == null || b == null) return false; + if (a.length !== b.length) return false; + + // Arrays kopieren und sortieren + const sortedA = [...a].sort((a, b) => a.id - b.id); + const sortedB = [...b].sort((a, b) => a.id - b.id); + + for (let i = 0; i < sortedA.length; ++i) { + if (sortedA[i].id !== sortedB[i].id) return false; + } + return true; + } + + private getConflictingSeats(blockedSeats: Sitzplatz[]): Sitzplatz[] { + const blockedIds = new Set(blockedSeats.map(bs => bs.id)); + return this.selectedSeatService.selectedSeats().filter( + selectedSeat => blockedIds.has(selectedSeat.id) + ); + } + private converter(resp: { seats: Sitzplatz[], reserved: Sitzplatz[], booked: Sitzplatz[] }): { seat: Sitzplatz | null, state: TheaterSeatState | null