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.
This commit is contained in:
@@ -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,
|
||||
|
||||
7
src/app/no-seats-in-hall/no-seats-in-hall.component.html
Normal file
7
src/app/no-seats-in-hall/no-seats-in-hall.component.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<div class="flex flex-col items-center justify-center py-8 text-gray-500 m-auto">
|
||||
<mat-icon class="material-symbols-outlined text-6xl mb-4 opacity-50" style="font-size: 40px; width: 40px; height: 40px">
|
||||
brightness_alert
|
||||
</mat-icon>
|
||||
<p class="text-lg">Huch?! Keine Sitzplätze?</p>
|
||||
<p class="text-sm">Hast du ein Glück, Stehplätze sind kostenlos.</p>
|
||||
</div>
|
||||
11
src/app/no-seats-in-hall/no-seats-in-hall.component.ts
Normal file
11
src/app/no-seats-in-hall/no-seats-in-hall.component.ts
Normal file
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
mat-stepper {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-horizontal-stepper-header{
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
<div class="w-full h-full">
|
||||
|
||||
@if (loadingService.loading$ | async){
|
||||
<div class="w-full h-full flex items-center justify-center">
|
||||
<mat-progress-spinner
|
||||
mode="indeterminate"
|
||||
diameter="50"
|
||||
></mat-progress-spinner>
|
||||
</div>
|
||||
}
|
||||
@else if (performance()) {
|
||||
|
||||
<mat-stepper
|
||||
orientation="horizontal"
|
||||
linear="true"
|
||||
@@ -8,12 +19,37 @@
|
||||
<form [formGroup]="firstFormGroup">
|
||||
<ng-template matStepLabel>Warenkorb</ng-template>
|
||||
|
||||
@if (performance()) {
|
||||
<app-performance-info class="w-full h-50" [performance]="performance()!"></app-performance-info>
|
||||
}
|
||||
<app-performance-info class="w-full h-50" [performance]="performance()!"></app-performance-info>
|
||||
|
||||
<div>
|
||||
<button mat-button matStepperNext>Next</button>
|
||||
<div class="mb-4 mt-2 p-2">
|
||||
|
||||
@for (seatCategory of seatCategories(); track $index) {
|
||||
<div class="h-2"></div>
|
||||
<app-seat-selection
|
||||
[seatCategory]="seatCategory"
|
||||
></app-seat-selection>
|
||||
}
|
||||
@empty {
|
||||
<app-no-seats-in-hall></app-no-seats-in-hall>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<div class="flex justify-between p-2 mt-1 items-baseline">
|
||||
<p class="font-semibold text-lg">
|
||||
Tickets gesamt:
|
||||
</p>
|
||||
<p class="font-semibold text-2xl bg-gradient-to-r from-indigo-500 to-pink-600 bg-clip-text text-transparent">
|
||||
75,00 €
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-5 mt-10">
|
||||
<button mat-button matButton="outlined" matStepperNext class="w-1/2">Reservieren</button>
|
||||
<button mat-button matButton="filled" matStepperNext class="w-1/2">Buchen</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
@@ -46,4 +82,5 @@
|
||||
</div>
|
||||
</mat-step>
|
||||
</mat-stepper>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -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<Vorstellung>();
|
||||
seatCategories = input.required<Sitzkategorie[]>();
|
||||
|
||||
private _formBuilder = inject(FormBuilder);
|
||||
loadingService = inject(LoadingService);
|
||||
|
||||
firstFormGroup = this._formBuilder.group({
|
||||
firstCtrl: ['', Validators.required],
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
|
||||
<div class="text-md">
|
||||
<h3 class="opacity-75">{{ getStartTimeString() }} • {{ performance().hall.name }}</h3>
|
||||
<h1 class="font-semibold my-1">{{ movie().title }}</h1>
|
||||
<h1 class="font-semibold mb-1.5">{{ movie().title }}</h1>
|
||||
<div class="flex items-center">
|
||||
<app-movie-rating [rating]="movie().rating" class="rounded-sm shadow-xs px-0.75 py-0.2 text-sm"></app-movie-rating>
|
||||
<app-movie-rating [rating]="movie().rating" class="rounded-sm shadow-xs px-1 py-0.25 text-sm"></app-movie-rating>
|
||||
<app-movie-duration [duration]="movie().duration" [showIcon]="false" class="ml-1.5 opacity-75"></app-movie-duration>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
3
src/app/seat-selection/seat-selection.component.css
Normal file
3
src/app/seat-selection/seat-selection.component.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.seat-name {
|
||||
color: var(--mat-sys-primary);
|
||||
}
|
||||
11
src/app/seat-selection/seat-selection.component.html
Normal file
11
src/app/seat-selection/seat-selection.component.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<h2 class="seat-name mb-1">{{ seatCategory().name }}</h2>
|
||||
<div class="flex items-center justify-between text-lg">
|
||||
<div class="flex items-center space-x-2 w-3/7">
|
||||
<mat-icon style="font-size: 30px; width: 30px; height: 30px">
|
||||
{{ seatCategory().icon }}
|
||||
</mat-icon>
|
||||
<p>{{ getPriceDisplay(seatCategory().price) }}</p>
|
||||
</div>
|
||||
<p>× 2</p>
|
||||
<p class="w-2/7 text-right">25.00 €</p>
|
||||
</div>
|
||||
18
src/app/seat-selection/seat-selection.component.ts
Normal file
18
src/app/seat-selection/seat-selection.component.ts
Normal file
@@ -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<Sitzkategorie>();
|
||||
amount: number = 1;
|
||||
|
||||
getPriceDisplay(price: number): string {
|
||||
return `${(price / 100).toFixed(2)} €`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
|
||||
<app-theater-layout [seatsPerRow]="seatsPerRow" class="m-10 w-7/10"></app-theater-layout>
|
||||
|
||||
<app-order class="m-10 w-3/10" [performance]="performance"></app-order>
|
||||
<app-order class="m-10 w-3/10" [performance]="performance" [seatCategories]="seatCategories"></app-order>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -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<number, Sitzkategorie>();
|
||||
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user