diff --git a/src/app/app-module.ts b/src/app/app-module.ts
index 7343d2a..4907717 100644
--- a/src/app/app-module.ts
+++ b/src/app/app-module.ts
@@ -57,7 +57,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';
@@ -69,6 +68,12 @@ import { TicketSmallComponent } from './ticket-small/ticket-small.component';
import { TicketListComponent } from './ticket-list/ticket-list.component';
import { StatisticsComponent } from './statistics/statistics.component';
import { ZoomWarningComponent } from './zoom-warning/zoom-warning.component';
+import { SelectionConflictInfoComponent } from './selection-conflict-info/selection-conflict-info.component';
+import { CancellationSuccessComponent } from './cancellation-success/cancellation-success.component';
+import { CancellationFailedComponent } from './cancellation-failed/cancellation-failed.component';
+import { ConversionFailedComponent } from './conversion-failed/conversion-failed.component';
+import { PayForOrderComponent } from './pay-for-order/pay-for-order.component';
+import { CancelOrderDialog } from './cancel-order/cancel-order.dialog';
import { PricelistComponent } from './pricelist/pricelist.component';
@@ -104,7 +109,6 @@ import { PricelistComponent } from './pricelist/pricelist.component';
MovieImportSearchInfoComponent,
LoginDialog,
PerformanceInfoComponent,
- ShoppingCartComponent,
OrderComponent,
SeatSelectionComponent,
NoSeatsInHallComponent,
@@ -116,6 +120,12 @@ import { PricelistComponent } from './pricelist/pricelist.component';
TicketListComponent,
StatisticsComponent,
ZoomWarningComponent,
+ SelectionConflictInfoComponent,
+ CancellationSuccessComponent,
+ CancellationFailedComponent,
+ ConversionFailedComponent,
+ PayForOrderComponent,
+ CancelOrderDialog,
PricelistComponent,
],
imports: [
diff --git a/src/app/app-routing-module.ts b/src/app/app-routing-module.ts
index 72de746..9b8c10d 100644
--- a/src/app/app-routing-module.ts
+++ b/src/app/app-routing-module.ts
@@ -8,7 +8,8 @@ import { ScheduleComponent } from './schedule/schedule.component';
import { TheaterOverlayComponent} from './theater-overlay/theater-overlay.component';
import { MovieImporterComponent } from './movie-importer/movie-importer.component';
import { AuthGuard } from './auth.guard';
-import {StatisticsComponent} from './statistics/statistics.component';
+import { PayForOrderComponent } from './pay-for-order/pay-for-order.component';
+import { StatisticsComponent } from './statistics/statistics.component';
import { PricelistComponent } from './pricelist/pricelist.component';
const routes: Routes = [
@@ -29,7 +30,9 @@ const routes: Routes = [
canActivate: [AuthGuard],
data: { roles: ['admin'] }, // Array von erlaubten Rollen. Derzeit gäbe es 'admin' und 'employee'
},
- { path: 'performance/:performanceId/checkout', component: TheaterOverlayComponent},
+ { path: 'checkout/performance/:performanceId', component: TheaterOverlayComponent},
+ { path: 'checkout/order/:orderId', component: TheaterOverlayComponent},
+ { path: 'checkout/order', component: PayForOrderComponent},
{
path: 'admin/statistics',
component: StatisticsComponent,
diff --git a/src/app/cancel-order/cancel-order.dialog.css b/src/app/cancel-order/cancel-order.dialog.css
new file mode 100644
index 0000000..f785beb
--- /dev/null
+++ b/src/app/cancel-order/cancel-order.dialog.css
@@ -0,0 +1,3 @@
+button {
+ min-width: 100px;
+}
diff --git a/src/app/cancel-order/cancel-order.dialog.html b/src/app/cancel-order/cancel-order.dialog.html
new file mode 100644
index 0000000..0c953f3
--- /dev/null
+++ b/src/app/cancel-order/cancel-order.dialog.html
@@ -0,0 +1,10 @@
+
Möchten Sie Ihre Bestellung wirklich stornieren?
+
+
+ Nach der Stornierung verlieren Sie Ihr Reservierungsrecht und die Sitzplätze können von anderen Kunden in Anspruch genommen werden. Dieser Prozess kann nicht rückgängig gemacht werden.
+
+
+
+ Abbrechen
+ Stornieren
+
diff --git a/src/app/cancel-order/cancel-order.dialog.ts b/src/app/cancel-order/cancel-order.dialog.ts
new file mode 100644
index 0000000..44dc764
--- /dev/null
+++ b/src/app/cancel-order/cancel-order.dialog.ts
@@ -0,0 +1,22 @@
+import { Component } from '@angular/core';
+import { MatDialogRef } from '@angular/material/dialog';
+
+@Component({
+ selector: 'app-cancel-order',
+ standalone: false,
+ templateUrl: './cancel-order.dialog.html',
+ styleUrl: './cancel-order.dialog.css',
+})
+export class CancelOrderDialog {
+ constructor(
+ private dialogRef: MatDialogRef,
+ ) {}
+
+ submit(): void {
+ this.dialogRef.close(true);
+ }
+
+ cancel(): void {
+ this.dialogRef.close(false);
+ }
+}
diff --git a/src/app/shopping-cart/shopping-cart.component.css b/src/app/cancellation-failed/cancellation-failed.component.css
similarity index 100%
rename from src/app/shopping-cart/shopping-cart.component.css
rename to src/app/cancellation-failed/cancellation-failed.component.css
diff --git a/src/app/cancellation-failed/cancellation-failed.component.html b/src/app/cancellation-failed/cancellation-failed.component.html
new file mode 100644
index 0000000..773143e
--- /dev/null
+++ b/src/app/cancellation-failed/cancellation-failed.component.html
@@ -0,0 +1,11 @@
+
+
+ warning
+
+
Stornierung fehlgeschlagen!
+
{{ infoText }}
+
+
Erneut versuchen
+
Zurück zur Codeeingabe
+
+
diff --git a/src/app/cancellation-failed/cancellation-failed.component.ts b/src/app/cancellation-failed/cancellation-failed.component.ts
new file mode 100644
index 0000000..05663f0
--- /dev/null
+++ b/src/app/cancellation-failed/cancellation-failed.component.ts
@@ -0,0 +1,21 @@
+import { Component, input, output } from '@angular/core';
+
+@Component({
+ selector: 'app-cancellation-failed',
+ standalone: false,
+ templateUrl: './cancellation-failed.component.html',
+ styleUrl: './cancellation-failed.component.css',
+})
+export class CancellationFailedComponent {
+ moreThanOne = input(false);
+
+ retry = output();
+
+ infoText!: string;
+
+ ngOnInit(): void {
+ this.infoText = this.moreThanOne()?
+ 'Leider konnten Ihre Sitzplätze nicht storniert werden. Möglicherweise wurden die Tickets bereits bezahlt oder storniert.' :
+ 'Leider konnte Ihr Sitzplatz nicht storniert werden. Möglicherweise wurde das Ticket bereits bezahlt oder storniert.';
+ }
+}
diff --git a/src/app/cancellation-success/cancellation-success.component.css b/src/app/cancellation-success/cancellation-success.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/cancellation-success/cancellation-success.component.html b/src/app/cancellation-success/cancellation-success.component.html
new file mode 100644
index 0000000..166309e
--- /dev/null
+++ b/src/app/cancellation-success/cancellation-success.component.html
@@ -0,0 +1,11 @@
+
+
+ task_alt
+
+
Stornierung erfolgreich!
+
{{ infoText }}
+
+
Zur Programmauswahl
+
Neue Tickets kaufen
+
+
diff --git a/src/app/cancellation-success/cancellation-success.component.ts b/src/app/cancellation-success/cancellation-success.component.ts
new file mode 100644
index 0000000..6110bed
--- /dev/null
+++ b/src/app/cancellation-success/cancellation-success.component.ts
@@ -0,0 +1,27 @@
+import { Component, inject, input } from '@angular/core';
+import { Router } from '@angular/router';
+
+@Component({
+ selector: 'app-cancellation-success',
+ standalone: false,
+ templateUrl: './cancellation-success.component.html',
+ styleUrl: './cancellation-success.component.css',
+})
+export class CancellationSuccessComponent {
+ performanceId = input.required();
+ moreThanOne = input(false);
+
+ router = inject(Router);
+
+ infoText!: string;
+
+ ngOnInit(): void {
+ this.infoText = this.moreThanOne()?
+ 'Ihre Sitzplätze wurden erfolgreich storniert und stehen wieder zur Buchung zur Verfügnug.' :
+ 'Ihr Sitzplatz wurden erfolgreich storniert und steht wieder zur Buchung zur Verfügnug.';
+ }
+
+ navigate() {
+ window.location.href = `/checkout/performance/${this.performanceId()}`;
+ }
+}
diff --git a/src/app/conversion-failed/conversion-failed.component.css b/src/app/conversion-failed/conversion-failed.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/conversion-failed/conversion-failed.component.html b/src/app/conversion-failed/conversion-failed.component.html
new file mode 100644
index 0000000..018a3ad
--- /dev/null
+++ b/src/app/conversion-failed/conversion-failed.component.html
@@ -0,0 +1,11 @@
+
+
+ warning
+
+
Kauf fehlgeschlagen!
+
{{ infoText }}
+
+
Erneut versuchen
+
Zurück zur Codeeingabe
+
+
diff --git a/src/app/conversion-failed/conversion-failed.component.ts b/src/app/conversion-failed/conversion-failed.component.ts
new file mode 100644
index 0000000..3957541
--- /dev/null
+++ b/src/app/conversion-failed/conversion-failed.component.ts
@@ -0,0 +1,21 @@
+import { Component, input, output } from '@angular/core';
+
+@Component({
+ selector: 'app-conversion-failed',
+ standalone: false,
+ templateUrl: './conversion-failed.component.html',
+ styleUrl: './conversion-failed.component.css',
+})
+export class ConversionFailedComponent {
+ moreThanOne = input(false);
+
+ retry = output();
+
+ infoText!: string;
+
+ ngOnInit(): void {
+ this.infoText = this.moreThanOne()?
+ 'Leider konnten Ihre Sitzplätze nicht bezahlt werden. Möglicherweise wurden die Tickets bereits storniert.' :
+ 'Leider konnte Ihr Sitzplatz nicht bezahlt werden. Möglicherweise wurde das Ticket bereits storniert.';
+ }
+}
diff --git a/src/app/http.service.ts b/src/app/http.service.ts
index b0ad392..60d0f7e 100644
--- a/src/app/http.service.ts
+++ b/src/app/http.service.ts
@@ -23,16 +23,6 @@ export class HttpService {
/* Bestellung APIs */
- /* GET /api/bestellung/{id} */
- getAllOrder(id: number): Observable {
- return this.http.get(`${this.baseUrl}bestellung`);
- }
-
- /* GET /api/bestellung/{id} */
- getOrderById(id: number): Observable {
- return this.http.get(`${this.baseUrl}bestellung/${id}`);
- }
-
/* POST /api/bestellung/filter */
getOrdersByFilter(filter: string[]): Observable {
return this.http.post(`${this.baseUrl}bestellung/filter`, filter);
@@ -43,17 +33,11 @@ export class HttpService {
return this.http.post(`${this.baseUrl}bestellung`, order);
}
- /* PUT /api/bestellung/{id} */
- updateOrder(id: number, order: Partial): Observable {
- return this.http.put(`${this.baseUrl}bestellung/${id}`, order);
+ /* PUT /api/bestellung */
+ updateOrder(order: Partial): Observable {
+ return this.http.put(`${this.baseUrl}bestellung`, order);
}
- /* DELETE /api/bestellung/{id} */
- deleteOrder(id: number): Observable {
- return this.http.delete(`${this.baseUrl}bestellung/${id}`);
- }
-
-
/* POST /api/order-transaction/create */
saveAddOrder(req: {order:Bestellung, tickets:Eintrittskarte[]}): Observable<{order:Bestellung, tickets:Eintrittskarte[]}> {
return this.http.post<{order: Bestellung, tickets: Eintrittskarte[]}>(`${this.baseUrl}order-transaction/create`, req);
@@ -62,36 +46,11 @@ export class HttpService {
/* Eintrittskarte APIs */
- /* GET /api/eintrittskarte/{id} */
- getAllTickets(id: number): Observable {
- return this.http.get(`${this.baseUrl}eintrittskarte`);
- }
-
- /* GET /api/eintrittskarte/{id} */
- getTicketById(id: number): Observable {
- return this.http.get(`${this.baseUrl}eintrittskarte/${id}`);
- }
-
/* POST /api/eintrittskarte/filter */
getTicketsByFilter(filter: string[]): Observable {
return this.http.post(`${this.baseUrl}eintrittskarte/filter`, filter);
}
- /* POST /api/eintrittskarte */
- addTicket(order: Omit): Observable {
- return this.http.post(`${this.baseUrl}eintrittskarte`, order);
- }
-
- /* PUT /api/eintrittskarte/{id} */
- updateTicket(id: number, order: Partial): Observable {
- return this.http.put(`${this.baseUrl}eintrittskarte/${id}`, order);
- }
-
- /* DELETE /api/eintrittskarte/{id} */
- deleteTicket(id: number): Observable {
- return this.http.delete(`${this.baseUrl}eintrittskarte/${id}`);
- }
-
/* Kinosaal APIs */
@@ -100,34 +59,14 @@ export class HttpService {
return this.http.get(`${this.baseUrl}kinosaal`);
}
- /* GET /api/kinosaal/{id} */
- getKinosaalById(id: number): Observable {
- return this.http.get(`${this.baseUrl}kinosaal/${id}`);
- }
-
/* POST /api/kinosaal */
addKinosaal(kinosaal: Omit): Observable {
return this.http.post(`${this.baseUrl}kinosaal`, kinosaal);
}
- /* PUT /api/kinosaal/{id} */
- updateKinosaal(id: number, kinosaal: Partial): Observable {
- return this.http.put(`${this.baseUrl}kinosaal/${id}`, kinosaal);
- }
-
- /* DELETE /api/kinosaal/{id} */
- deleteKinosaal(id: number): Observable {
- return this.http.delete(`${this.baseUrl}kinosaal/${id}`);
- }
-
/* Vorstellung APIs */
- /* GET /api/vorstellung */
- getAllPerformaces(): Observable {
- return this.http.get(`${this.baseUrl}vorstellung`);
- }
-
/* GET /api/vorstellung/{id} */
getPerformaceById(id: number): Observable {
return this.http.get(`${this.baseUrl}vorstellung/${id}`);
@@ -138,21 +77,6 @@ export class HttpService {
return this.http.post(`${this.baseUrl}vorstellung/filter`, filter);
}
- /* POST /api/vorstellung */
- addPerformace(vorstellung: Omit): Observable {
- return this.http.post(`${this.baseUrl}vorstellung`, vorstellung);
- }
-
- /* PUT /api/vorstellung/{id} */
- updatePerformace(id: number, vorstellung: Partial): Observable {
- return this.http.put(`${this.baseUrl}vorstellung/${id}`, vorstellung);
- }
-
- /* DELETE /api/vorstellung/{id} */
- deletePerformace(id: number): Observable {
- return this.http.delete(`${this.baseUrl}vorstellung/${id}`);
- }
-
/* Film APIs */
diff --git a/src/app/loading.service.ts b/src/app/loading.service.ts
index 988e652..532b309 100644
--- a/src/app/loading.service.ts
+++ b/src/app/loading.service.ts
@@ -1,7 +1,7 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
-import { MatSnackBar } from '@angular/material/snack-bar';
-import { BehaviorSubject } from 'rxjs';
+import { MatSnackBar, MatSnackBarRef } from '@angular/material/snack-bar';
+import { BehaviorSubject, Subscription } from 'rxjs';
@Injectable({
providedIn: 'root'
@@ -13,6 +13,9 @@ export class LoadingService {
public loading$ = this.loadingSubject.asObservable();
public error$ = this.errorSubject.asObservable();
+ private currentSnackBarRef?: MatSnackBarRef;
+ private currentSubscription?: Subscription;
+
constructor(private snackBar: MatSnackBar) {}
show(): void {
@@ -23,6 +26,7 @@ export class LoadingService {
hide(): void {
this.loadingSubject.next(false);
this.errorSubject.next(false);
+ this.currentSnackBarRef?.dismiss();
}
showError(messageOrError?: string | HttpErrorResponse | any): void {
@@ -35,15 +39,22 @@ export class LoadingService {
const message = this.getErrorMessage(messageOrError);
- const snackBarRef = this.snackBar.open(message, 'Schließen', {
+ if (this.currentSnackBarRef) {
+ this.currentSubscription?.unsubscribe();
+ this.currentSnackBarRef.dismiss();
+ }
+
+ this.currentSnackBarRef = this.snackBar.open(message, 'Schließen', {
duration: 0,
panelClass: ['error-snackbar'],
horizontalPosition: 'center',
verticalPosition: 'bottom'
});
- snackBarRef.afterDismissed().subscribe(() => {
- this.hide();
+ this.currentSubscription = this.currentSnackBarRef.afterDismissed().subscribe(() => {
+ if (!this.loadingSubject.value) {
+ this.hide();
+ }
});
}
diff --git a/src/app/movie-importer/movie-importer.component.html b/src/app/movie-importer/movie-importer.component.html
index 6f614b8..59385ca 100644
--- a/src/app/movie-importer/movie-importer.component.html
+++ b/src/app/movie-importer/movie-importer.component.html
@@ -5,7 +5,7 @@
Film online suchen
-
+
@if (formControl.hasError('noResults')) {
Keine Suchergebnisse gefunden
}
diff --git a/src/app/movie-importer/movie-importer.component.ts b/src/app/movie-importer/movie-importer.component.ts
index c68289b..2419b29 100644
--- a/src/app/movie-importer/movie-importer.component.ts
+++ b/src/app/movie-importer/movie-importer.component.ts
@@ -1,9 +1,10 @@
import { LoadingService } from './../loading.service';
-import { Component, inject } from '@angular/core';
+import { Component, DestroyRef, inject } from '@angular/core';
import { FormControl } from '@angular/forms';
import { catchError, finalize, of, tap } from 'rxjs';
import { HttpService } from '../http.service';
import { OmdbMovie } from '@infinimotion/model-frontend';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-movie-importer',
@@ -21,13 +22,15 @@ export class MovieImporterComponent {
private httpService = inject(HttpService)
public loadingService = inject(LoadingService)
+ private destroyRef = inject(DestroyRef);
+
DoSubmit() {
this.showAll = false;
this.searchForMovies();
}
- searchForMovies() {
+ private searchForMovies() {
this.search_query = this.formControl.value?.trim() || '';
if (this.search_query?.length == 0) return;
@@ -48,7 +51,8 @@ export class MovieImporterComponent {
finalize(() => {
this.isSearching = false;
this.formControl.enable();
- })
+ }),
+ takeUntilDestroyed(this.destroyRef)
).subscribe();
}
diff --git a/src/app/movie-performance/movie-performance.component.ts b/src/app/movie-performance/movie-performance.component.ts
index 6f98ff9..217cec6 100644
--- a/src/app/movie-performance/movie-performance.component.ts
+++ b/src/app/movie-performance/movie-performance.component.ts
@@ -15,7 +15,7 @@ export class MoviePerformanceComponent implements OnInit {
route: string = '';
ngOnInit() {
- this.route = `../performance/${this.id()}/checkout`;
+ this.route = `../checkout/performance/${this.id()}`;
}
startTime = computed(() =>
diff --git a/src/app/navbar/navbar.component.ts b/src/app/navbar/navbar.component.ts
index a8659a7..1e38f32 100644
--- a/src/app/navbar/navbar.component.ts
+++ b/src/app/navbar/navbar.component.ts
@@ -11,6 +11,7 @@ export class NavbarComponent {
navItems: { label:string, path:string }[] = [
{label: 'Programm', path: '/schedule'},
{label: 'Preise', path: '/prices'},
+ {label: 'Bezahlen', path: '/checkout/order'},
{label: 'Film importieren', path: '/admin/movie-importer'},
{label: 'Statistiken', path: '/admin/statistics'},
]
diff --git a/src/app/order/order.component.css b/src/app/order/order.component.css
index 367c809..9732f3d 100644
--- a/src/app/order/order.component.css
+++ b/src/app/order/order.component.css
@@ -1,3 +1,7 @@
+:host {
+ min-width: 500px;
+}
+
mat-stepper {
background: transparent !important;
}
diff --git a/src/app/order/order.component.html b/src/app/order/order.component.html
index 7b5b71d..ab9e36b 100644
--- a/src/app/order/order.component.html
+++ b/src/app/order/order.component.html
@@ -27,43 +27,47 @@
}
-
-
- Warenkorb
+
+
+ Warenkorb
-
+
-
-
- @for (seatCategory of seatCategories(); track $index) {
-
-
- }
- @empty {
-
- }
-
+ @if (selectedSeatsService.hadConflict()) {
+
+ }
-
+
+
+ @for (seatCategory of seatCategories(); track $index) {
+
+
+ }
+ @empty {
+
+ }
+
-
-
-
- Tickets gesamt:
-
-
- {{ getPriceDisplay(totalPrice()) }}
-
-
+
-
-
- Reservieren
- Kaufen
-
+
+
+
+ Tickets gesamt:
+
+
+ {{ getPriceDisplay(totalPrice()) }}
+
+
+
+
+
+ Reservieren
+ Kaufen
+
-
+