From db0322d44337204c98154451d6ce36fe6c9323de Mon Sep 17 00:00:00 2001 From: Piet Ostendorp Date: Thu, 13 Nov 2025 01:48:28 +0100 Subject: [PATCH] Add seat selection and no seats components Introduces SeatSelectionComponent and NoSeatsInHallComponent for improved seat category display and handling cases with no available seats. Updates order flow to show seat categories, loading spinner, and total price. Refactors TheaterOverlayComponent to provide seat categories, and updates styles and dependencies. --- package-lock.json | 8 ++-- package.json | 2 +- src/app/app-module.ts | 6 +++ .../no-seats-in-hall.component.css | 0 .../no-seats-in-hall.component.html | 7 +++ .../no-seats-in-hall.component.ts | 11 +++++ src/app/order/order.component.css | 4 ++ src/app/order/order.component.html | 47 +++++++++++++++++-- src/app/order/order.component.ts | 5 +- .../performance-info.component.html | 4 +- .../seat-selection.component.css | 3 ++ .../seat-selection.component.html | 11 +++++ .../seat-selection.component.ts | 18 +++++++ .../theater-overlay.component.html | 2 +- .../theater-overlay.component.ts | 18 ++++++- src/index.html | 1 + 16 files changed, 131 insertions(+), 16 deletions(-) create mode 100644 src/app/no-seats-in-hall/no-seats-in-hall.component.css create mode 100644 src/app/no-seats-in-hall/no-seats-in-hall.component.html create mode 100644 src/app/no-seats-in-hall/no-seats-in-hall.component.ts create mode 100644 src/app/seat-selection/seat-selection.component.css create mode 100644 src/app/seat-selection/seat-selection.component.html create mode 100644 src/app/seat-selection/seat-selection.component.ts diff --git a/package-lock.json b/package-lock.json index ea5c339..0002880 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@angular/material": "^20.2.9", "@angular/platform-browser": "^20.3.0", "@angular/router": "^20.3.0", - "@infinimotion/model-frontend": "^0.0.85", + "@infinimotion/model-frontend": "^0.0.89", "@tailwindcss/postcss": "^4.1.14", "postcss": "^8.5.6", "rxjs": "~7.8.0", @@ -1298,9 +1298,9 @@ } }, "node_modules/@infinimotion/model-frontend": { - "version": "0.0.85", - "resolved": "https://git.infinimotion.de/api/packages/infinimotion/npm/%40infinimotion%2Fmodel-frontend/-/0.0.85/model-frontend-0.0.85.tgz", - "integrity": "sha512-QPiZNl//Y1JdxtXk+VScc67h1K664z68PUCXRff9fRf4IHlYXtqutc+ainK8vxOVSqqL6EEmDAtbLsRwrG6kRg==", + "version": "0.0.89", + "resolved": "https://git.infinimotion.de/api/packages/infinimotion/npm/%40infinimotion%2Fmodel-frontend/-/0.0.89/model-frontend-0.0.89.tgz", + "integrity": "sha512-lvvQy8RWs41Bz52uBgsUKkwn8teGlgxlmG8Rvsgkh+v1IMVWFWVQmfMS7Rznd0lCZRgK1ByihH80X9eAN12idA==", "license": "ISC" }, "node_modules/@inquirer/ansi": { diff --git a/package.json b/package.json index 5027aa3..a8410ad 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@angular/material": "^20.2.9", "@angular/platform-browser": "^20.3.0", "@angular/router": "^20.3.0", - "@infinimotion/model-frontend": "^0.0.85", + "@infinimotion/model-frontend": "^0.0.89", "@tailwindcss/postcss": "^4.1.14", "postcss": "^8.5.6", "rxjs": "~7.8.0", diff --git a/src/app/app-module.ts b/src/app/app-module.ts index 140cb49..7bdff2c 100644 --- a/src/app/app-module.ts +++ b/src/app/app-module.ts @@ -12,6 +12,7 @@ import { MatIconModule } from '@angular/material/icon'; import { MatTabsModule } from '@angular/material/tabs'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatInputModule } from '@angular/material/input'; @@ -52,6 +53,8 @@ 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'; @NgModule({ @@ -87,6 +90,8 @@ import { OrderComponent } from './order/order.component'; PerformanceInfoComponent, ShoppingCartComponent, OrderComponent, + SeatSelectionComponent, + NoSeatsInHallComponent, ], imports: [ AppRoutingModule, @@ -98,6 +103,7 @@ import { OrderComponent } from './order/order.component'; MatTabsModule, MatToolbarModule, MatProgressBarModule, + MatProgressSpinnerModule, MatSnackBarModule, MatAutocompleteModule, MatInputModule, diff --git a/src/app/no-seats-in-hall/no-seats-in-hall.component.css b/src/app/no-seats-in-hall/no-seats-in-hall.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/no-seats-in-hall/no-seats-in-hall.component.html b/src/app/no-seats-in-hall/no-seats-in-hall.component.html new file mode 100644 index 0000000..08cea86 --- /dev/null +++ b/src/app/no-seats-in-hall/no-seats-in-hall.component.html @@ -0,0 +1,7 @@ +
+ + brightness_alert + +

Huch?! Keine Sitzplätze?

+

Hast du ein Glück, Stehplätze sind kostenlos.

+
diff --git a/src/app/no-seats-in-hall/no-seats-in-hall.component.ts b/src/app/no-seats-in-hall/no-seats-in-hall.component.ts new file mode 100644 index 0000000..bf6ce88 --- /dev/null +++ b/src/app/no-seats-in-hall/no-seats-in-hall.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-no-seats-in-hall', + standalone: false, + templateUrl: './no-seats-in-hall.component.html', + styleUrl: './no-seats-in-hall.component.css' +}) +export class NoSeatsInHallComponent { + +} diff --git a/src/app/order/order.component.css b/src/app/order/order.component.css index 9008a3b..b19ef5b 100644 --- a/src/app/order/order.component.css +++ b/src/app/order/order.component.css @@ -1,3 +1,7 @@ mat-stepper { background: transparent !important; } + +::ng-deep .mat-horizontal-stepper-header{ + pointer-events: none !important; +} diff --git a/src/app/order/order.component.html b/src/app/order/order.component.html index 3d94d59..4f31b79 100644 --- a/src/app/order/order.component.html +++ b/src/app/order/order.component.html @@ -1,4 +1,15 @@
+ +@if (loadingService.loading$ | async){ +
+ +
+} +@else if (performance()) { + Warenkorb - @if (performance()) { - - } + -
- +
+ + @for (seatCategory of seatCategories(); track $index) { +
+ + } + @empty { + + } + +
+ + + + +
+

+ Tickets gesamt: +

+

+ 75,00 € +

+
+ +
+ +
@@ -46,4 +82,5 @@
+}
diff --git a/src/app/order/order.component.ts b/src/app/order/order.component.ts index 330041b..3f3b094 100644 --- a/src/app/order/order.component.ts +++ b/src/app/order/order.component.ts @@ -1,4 +1,5 @@ -import { Vorstellung } from '@infinimotion/model-frontend'; +import { LoadingService } from './../loading.service'; +import { Sitzkategorie, Vorstellung } from '@infinimotion/model-frontend'; import { Component, inject, input } from '@angular/core'; import { FormBuilder, Validators } from '@angular/forms'; @@ -10,8 +11,10 @@ import { FormBuilder, Validators } from '@angular/forms'; }) export class OrderComponent { performance = input(); + seatCategories = input.required(); private _formBuilder = inject(FormBuilder); + loadingService = inject(LoadingService); firstFormGroup = this._formBuilder.group({ firstCtrl: ['', Validators.required], diff --git a/src/app/performance-info/performance-info.component.html b/src/app/performance-info/performance-info.component.html index 4ed4613..06435e9 100644 --- a/src/app/performance-info/performance-info.component.html +++ b/src/app/performance-info/performance-info.component.html @@ -11,9 +11,9 @@

{{ getStartTimeString() }} • {{ performance().hall.name }}

-

{{ movie().title }}

+

{{ movie().title }}

- +
diff --git a/src/app/seat-selection/seat-selection.component.css b/src/app/seat-selection/seat-selection.component.css new file mode 100644 index 0000000..c206bd7 --- /dev/null +++ b/src/app/seat-selection/seat-selection.component.css @@ -0,0 +1,3 @@ +.seat-name { + color: var(--mat-sys-primary); +} diff --git a/src/app/seat-selection/seat-selection.component.html b/src/app/seat-selection/seat-selection.component.html new file mode 100644 index 0000000..853700b --- /dev/null +++ b/src/app/seat-selection/seat-selection.component.html @@ -0,0 +1,11 @@ +

{{ seatCategory().name }}

+
+
+ + {{ seatCategory().icon }} + +

{{ getPriceDisplay(seatCategory().price) }}

+
+

× 2

+

25.00 €

+
diff --git a/src/app/seat-selection/seat-selection.component.ts b/src/app/seat-selection/seat-selection.component.ts new file mode 100644 index 0000000..e9f0f8d --- /dev/null +++ b/src/app/seat-selection/seat-selection.component.ts @@ -0,0 +1,18 @@ +import { Component, input } from '@angular/core'; +import { Sitzkategorie } from '@infinimotion/model-frontend'; + +@Component({ + selector: 'app-seat-selection', + standalone: false, + templateUrl: './seat-selection.component.html', + styleUrl: './seat-selection.component.css' +}) +export class SeatSelectionComponent { + seatCategory = input.required(); + amount: number = 1; + + getPriceDisplay(price: number): string { + return `${(price / 100).toFixed(2)} €`; + } +} + diff --git a/src/app/theater-overlay/theater-overlay.component.html b/src/app/theater-overlay/theater-overlay.component.html index d709d18..c4c736e 100644 --- a/src/app/theater-overlay/theater-overlay.component.html +++ b/src/app/theater-overlay/theater-overlay.component.html @@ -4,6 +4,6 @@ - + diff --git a/src/app/theater-overlay/theater-overlay.component.ts b/src/app/theater-overlay/theater-overlay.component.ts index c8a0e35..0052b0a 100644 --- a/src/app/theater-overlay/theater-overlay.component.ts +++ b/src/app/theater-overlay/theater-overlay.component.ts @@ -2,7 +2,7 @@ import {Component, inject, OnInit} from '@angular/core'; import {HttpService} from '../http.service'; import {LoadingService} from '../loading.service'; import {catchError, forkJoin, of, tap} from 'rxjs'; -import {Sitzplatz, Vorstellung} from '@infinimotion/model-frontend'; +import {Sitzkategorie, Sitzplatz, Vorstellung} from '@infinimotion/model-frontend'; import {TheaterSeatState} from '../model/theater-seat-state.model'; import {ActivatedRoute} from '@angular/router'; @@ -19,6 +19,7 @@ export class TheaterOverlayComponent implements OnInit { showId!: number; seatsPerRow: { seat: Sitzplatz, state: TheaterSeatState }[][] = [] performance: Vorstellung | undefined; + seatCategories: Sitzkategorie[] = []; constructor(private route: ActivatedRoute) {} @@ -52,13 +53,26 @@ loadPerformanceAndSeats() { state: TheaterSeatState }[][] { let rows: { seat: Sitzplatz, state: TheaterSeatState }[][] = []; + const categoryMap = new Map(); + resp.seats.forEach(seat => { if (!rows[seat.row.position]) { rows[seat.row.position] = []; } - let state = resp.booked.find(other => other.id == seat.id) ? TheaterSeatState.BOOKED : resp.reserved.find(other => other.id == seat.id) ? TheaterSeatState.RESERVED : TheaterSeatState.AVAILABLE; + + let state = resp.booked.find(other => other.id == seat.id) ? TheaterSeatState.BOOKED + : resp.reserved.find(other => other.id == seat.id) ? TheaterSeatState.RESERVED + : TheaterSeatState.AVAILABLE; + rows[seat.row.position].push({seat: seat, state: state}); + + if (seat.row.category && !categoryMap.has(seat.row.category.id)) { + categoryMap.set(seat.row.category.id, seat.row.category); + } }); + + this.seatCategories = Array.from(categoryMap.values()).sort((a, b) => a.id - b.id); + rows = rows.filter(row => row.length > 0).sort((a, b) => a[0].seat.row.position - b[0].seat.row.position); rows.forEach(row => row.sort((a, b) => a.seat.position - b.seat.position)); return rows; diff --git a/src/index.html b/src/index.html index da193c0..2df19e0 100644 --- a/src/index.html +++ b/src/index.html @@ -8,6 +8,7 @@ +