From f489073118ed41c056a69116d9718794e6313bda Mon Sep 17 00:00:00 2001 From: Piet Ostendorp Date: Wed, 12 Nov 2025 10:33:34 +0100 Subject: [PATCH] Add order and performance info components to ticket overlay Introduces OrderComponent, PerformanceInfoComponent, and ShoppingCartComponent for the ticket purchase flow. Updates theater-overlay to display seat selection alongside order details and performance info. Refactors seat and performance data loading, improves UI structure, and enhances movie info display components for consistency. --- src/app/app-module.ts | 6 +++ .../menu-header/menu-header.component.html | 8 +++ src/app/menu-header/menu-header.component.ts | 2 + .../movie-category.component.html | 2 +- .../movie-duration.component.html | 6 ++- .../movie-duration.component.ts | 4 ++ .../movie-poster/movie-poster.component.html | 6 +-- .../movie-rating/movie-rating.component.html | 2 +- .../movie-rating/movie-rating.component.ts | 7 ++- src/app/order/order.component.css | 3 ++ src/app/order/order.component.html | 49 +++++++++++++++++++ src/app/order/order.component.ts | 22 +++++++++ .../performance-info.component.css | 14 ++++++ .../performance-info.component.html | 21 ++++++++ .../performance-info.component.ts | 30 ++++++++++++ .../shopping-cart/shopping-cart.component.css | 0 .../shopping-cart.component.html | 1 + .../shopping-cart/shopping-cart.component.ts | 11 +++++ .../theater-layout.component.css | 4 ++ .../theater-layout.component.html | 13 +++-- .../theater-overlay.component.html | 16 +++--- .../theater-overlay.component.ts | 46 +++++++++-------- 22 files changed, 234 insertions(+), 39 deletions(-) create mode 100644 src/app/order/order.component.css create mode 100644 src/app/order/order.component.html create mode 100644 src/app/order/order.component.ts create mode 100644 src/app/performance-info/performance-info.component.css create mode 100644 src/app/performance-info/performance-info.component.html create mode 100644 src/app/performance-info/performance-info.component.ts create mode 100644 src/app/shopping-cart/shopping-cart.component.css create mode 100644 src/app/shopping-cart/shopping-cart.component.html create mode 100644 src/app/shopping-cart/shopping-cart.component.ts diff --git a/src/app/app-module.ts b/src/app/app-module.ts index 3df7d00..140cb49 100644 --- a/src/app/app-module.ts +++ b/src/app/app-module.ts @@ -49,6 +49,9 @@ import { MovieImporterComponent } from './movie-importer/movie-importer.componen import { MovieImportNoSearchResultComponent } from './movie-import-no-search-result/movie-import-no-search-result.component'; 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'; @NgModule({ @@ -81,6 +84,9 @@ import { LoginDialog } from './login/login.dialog'; MovieImportNoSearchResultComponent, MovieImportSearchInfoComponent, LoginDialog, + PerformanceInfoComponent, + ShoppingCartComponent, + OrderComponent, ], imports: [ AppRoutingModule, diff --git a/src/app/menu-header/menu-header.component.html b/src/app/menu-header/menu-header.component.html index 1941a29..beb2792 100644 --- a/src/app/menu-header/menu-header.component.html +++ b/src/app/menu-header/menu-header.component.html @@ -10,5 +10,13 @@ @if ( searchBar() ) { } + @if ( backToSchedule() ) { +
+ + arrow_back + Zurück zur Programmübersicht + +
+ } diff --git a/src/app/menu-header/menu-header.component.ts b/src/app/menu-header/menu-header.component.ts index 18155ff..4dd4ec2 100644 --- a/src/app/menu-header/menu-header.component.ts +++ b/src/app/menu-header/menu-header.component.ts @@ -12,4 +12,6 @@ export class MenuHeaderComponent { searchBar = input(false); movieSearchResult = output(); + + backToSchedule = input(false); } diff --git a/src/app/movie-category/movie-category.component.html b/src/app/movie-category/movie-category.component.html index 72757f5..3ff5e5c 100644 --- a/src/app/movie-category/movie-category.component.html +++ b/src/app/movie-category/movie-category.component.html @@ -1,3 +1,3 @@ - + {{ category() }} diff --git a/src/app/movie-duration/movie-duration.component.html b/src/app/movie-duration/movie-duration.component.html index 73845a3..b42ea62 100644 --- a/src/app/movie-duration/movie-duration.component.html +++ b/src/app/movie-duration/movie-duration.component.html @@ -1,4 +1,6 @@ - - + + @if (showIcon()) { + + } {{ durationText() }} diff --git a/src/app/movie-duration/movie-duration.component.ts b/src/app/movie-duration/movie-duration.component.ts index e029d3e..0bc94b6 100644 --- a/src/app/movie-duration/movie-duration.component.ts +++ b/src/app/movie-duration/movie-duration.component.ts @@ -8,9 +8,13 @@ import { Component, input, computed } from '@angular/core'; }) export class MovieDurationComponent { duration = input(0); + showIcon = input(true); durationText = computed(() => { if (this.duration() > 0) { + if (!this.showIcon()) { + return `${this.duration()} Minuten`; + } return `${this.duration()} Min.`; } return 'N/A'; diff --git a/src/app/movie-poster/movie-poster.component.html b/src/app/movie-poster/movie-poster.component.html index 1378eab..c038336 100644 --- a/src/app/movie-poster/movie-poster.component.html +++ b/src/app/movie-poster/movie-poster.component.html @@ -7,7 +7,7 @@ >
- - - + + +
diff --git a/src/app/movie-rating/movie-rating.component.html b/src/app/movie-rating/movie-rating.component.html index a8e4c76..9427cf9 100644 --- a/src/app/movie-rating/movie-rating.component.html +++ b/src/app/movie-rating/movie-rating.component.html @@ -1,3 +1,3 @@ - + {{ ratingText() }} diff --git a/src/app/movie-rating/movie-rating.component.ts b/src/app/movie-rating/movie-rating.component.ts index 60951ab..8dfb344 100644 --- a/src/app/movie-rating/movie-rating.component.ts +++ b/src/app/movie-rating/movie-rating.component.ts @@ -1,4 +1,4 @@ -import { Component, input, computed } from '@angular/core'; +import { Component, input, computed, HostBinding } from '@angular/core'; @Component({ selector: 'app-movie-rating', @@ -7,6 +7,11 @@ import { Component, input, computed } from '@angular/core'; styleUrl: './movie-rating.component.css' }) export class MovieRatingComponent { + + @HostBinding('class') get hostClasses(): string { + return this.ratingColor(); + } + rating = input(0); ratingColor = computed(() => { diff --git a/src/app/order/order.component.css b/src/app/order/order.component.css new file mode 100644 index 0000000..9008a3b --- /dev/null +++ b/src/app/order/order.component.css @@ -0,0 +1,3 @@ +mat-stepper { + background: transparent !important; +} diff --git a/src/app/order/order.component.html b/src/app/order/order.component.html new file mode 100644 index 0000000..3d94d59 --- /dev/null +++ b/src/app/order/order.component.html @@ -0,0 +1,49 @@ +
+ + +
+ Warenkorb + + @if (performance()) { + + } + +
+ +
+
+
+ + +
+ Anschrift + + Address + + +
+ + +
+
+
+- + + Zahlung +

You are now done.

+
+ + +
+
+
+
diff --git a/src/app/order/order.component.ts b/src/app/order/order.component.ts new file mode 100644 index 0000000..330041b --- /dev/null +++ b/src/app/order/order.component.ts @@ -0,0 +1,22 @@ +import { Vorstellung } from '@infinimotion/model-frontend'; +import { Component, inject, input } from '@angular/core'; +import { FormBuilder, Validators } from '@angular/forms'; + +@Component({ + selector: 'app-order', + standalone: false, + templateUrl: './order.component.html', + styleUrl: './order.component.css' +}) +export class OrderComponent { + performance = input(); + + private _formBuilder = inject(FormBuilder); + + firstFormGroup = this._formBuilder.group({ + firstCtrl: ['', Validators.required], + }); + secondFormGroup = this._formBuilder.group({ + secondCtrl: ['', Validators.required], + }); +} diff --git a/src/app/performance-info/performance-info.component.css b/src/app/performance-info/performance-info.component.css new file mode 100644 index 0000000..69e9094 --- /dev/null +++ b/src/app/performance-info/performance-info.component.css @@ -0,0 +1,14 @@ +.info-box { + color: var(--mat-sys-on-surface); +} + +::ng-deep .mat-step-header .mat-step-icon { + background-color: #ccc; + color: white; +} + + +::ng-deep .mat-step-header .mat-step-icon-selected, +::ng-deep .mat-step-icon-state-edit { + background-color: var(--color-indigo-500) !important; +} diff --git a/src/app/performance-info/performance-info.component.html b/src/app/performance-info/performance-info.component.html new file mode 100644 index 0000000..4ed4613 --- /dev/null +++ b/src/app/performance-info/performance-info.component.html @@ -0,0 +1,21 @@ +
+ +
+ Movie Poster +
+ +
+

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

+

{{ movie().title }}

+
+ + +
+
+ +
diff --git a/src/app/performance-info/performance-info.component.ts b/src/app/performance-info/performance-info.component.ts new file mode 100644 index 0000000..bc35f8b --- /dev/null +++ b/src/app/performance-info/performance-info.component.ts @@ -0,0 +1,30 @@ +import { Component, computed, input } from '@angular/core'; +import { Vorstellung } from '@infinimotion/model-frontend'; + +@Component({ + selector: 'app-performance-info', + standalone: false, + templateUrl: './performance-info.component.html', + styleUrl: './performance-info.component.css' +}) +export class PerformanceInfoComponent { + performance = input.required(); + + getStartTimeString(): string { + const date = new Date(this.performance().start); + return date.toLocaleDateString('de-DE', { weekday: 'short' }) + '. ' + date.toLocaleDateString('de-DE') + ', ' + date.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) + ' Uhr'; + } + + movie() { + return this.performance().movie + } + + onPosterError(event: Event) { + const img = event.target as HTMLImageElement; + const placeholder = 'assets/poster_placeholder.png'; + + if (img.src !== window.location.origin + placeholder) { + img.src = placeholder; + } + } +} diff --git a/src/app/shopping-cart/shopping-cart.component.css b/src/app/shopping-cart/shopping-cart.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/shopping-cart/shopping-cart.component.html b/src/app/shopping-cart/shopping-cart.component.html new file mode 100644 index 0000000..5aff33e --- /dev/null +++ b/src/app/shopping-cart/shopping-cart.component.html @@ -0,0 +1 @@ +

shopping-cart works!

diff --git a/src/app/shopping-cart/shopping-cart.component.ts b/src/app/shopping-cart/shopping-cart.component.ts new file mode 100644 index 0000000..382aba1 --- /dev/null +++ b/src/app/shopping-cart/shopping-cart.component.ts @@ -0,0 +1,11 @@ +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-layout/theater-layout.component.css b/src/app/theater-layout/theater-layout.component.css index e69de29..055c8c8 100644 --- a/src/app/theater-layout/theater-layout.component.css +++ b/src/app/theater-layout/theater-layout.component.css @@ -0,0 +1,4 @@ +:host { +border-radius: 0.5rem; + background-color: white; +} diff --git a/src/app/theater-layout/theater-layout.component.html b/src/app/theater-layout/theater-layout.component.html index a864e27..cd0560d 100644 --- a/src/app/theater-layout/theater-layout.component.html +++ b/src/app/theater-layout/theater-layout.component.html @@ -1,3 +1,10 @@ -@for (row of seatsPerRow(); track $index) { - -} +
+

+ Leinwand +

+
+
+ @for (row of seatsPerRow(); track $index) { + + } +
diff --git a/src/app/theater-overlay/theater-overlay.component.html b/src/app/theater-overlay/theater-overlay.component.html index bb9e257..d709d18 100644 --- a/src/app/theater-overlay/theater-overlay.component.html +++ b/src/app/theater-overlay/theater-overlay.component.html @@ -1,9 +1,9 @@ -
-

- Leinwand -

-
-
- -
+ +
+ + + + + +
diff --git a/src/app/theater-overlay/theater-overlay.component.ts b/src/app/theater-overlay/theater-overlay.component.ts index a87cb30..c8a0e35 100644 --- a/src/app/theater-overlay/theater-overlay.component.ts +++ b/src/app/theater-overlay/theater-overlay.component.ts @@ -1,8 +1,8 @@ import {Component, inject, OnInit} from '@angular/core'; import {HttpService} from '../http.service'; import {LoadingService} from '../loading.service'; -import {catchError, of, tap} from 'rxjs'; -import {Sitzplatz} from '@infinimotion/model-frontend'; +import {catchError, forkJoin, of, tap} from 'rxjs'; +import {Sitzplatz, Vorstellung} from '@infinimotion/model-frontend'; import {TheaterSeatState} from '../model/theater-seat-state.model'; import {ActivatedRoute} from '@angular/router'; @@ -18,28 +18,34 @@ export class TheaterOverlayComponent implements OnInit { showId!: number; seatsPerRow: { seat: Sitzplatz, state: TheaterSeatState }[][] = [] + performance: Vorstellung | undefined; constructor(private route: ActivatedRoute) {} - ngOnInit() { - this.showId = Number(this.route.snapshot.paramMap.get('id')!); - this.loadShowSeats(); - } +ngOnInit() { + this.showId = Number(this.route.snapshot.paramMap.get('id')!); + this.loadPerformanceAndSeats(); +} - loadShowSeats() { - this.loading.show(); - this.http.getSeatsByShowId(this.showId).pipe( - tap((data) => { - this.seatsPerRow = this.converter(data) - this.loading.hide(); - }), - catchError(err => { - this.loading.showError(err); - console.error('Fehler beim Laden der Vorstellung', err); - return of([]); - }) - ).subscribe(); - } +loadPerformanceAndSeats() { + this.loading.show(); + + 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(); +} converter(resp: { seats: Sitzplatz[], reserved: Sitzplatz[], booked: Sitzplatz[] }): { seat: Sitzplatz,