kinosaal designer
This commit is contained in:
@@ -76,6 +76,7 @@ import { ConversionFailedComponent } from './conversion-failed/conversion-failed
|
|||||||
import { PayForOrderComponent } from './pay-for-order/pay-for-order.component';
|
import { PayForOrderComponent } from './pay-for-order/pay-for-order.component';
|
||||||
import { CancelOrderDialog } from './cancel-order/cancel-order.dialog';
|
import { CancelOrderDialog } from './cancel-order/cancel-order.dialog';
|
||||||
import { PricelistComponent } from './pricelist/pricelist.component';
|
import { PricelistComponent } from './pricelist/pricelist.component';
|
||||||
|
import { TheaterLayoutDesignerComponent } from './theater-layout-designer/theater-layout-designer.component';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -129,6 +130,7 @@ import { PricelistComponent } from './pricelist/pricelist.component';
|
|||||||
PayForOrderComponent,
|
PayForOrderComponent,
|
||||||
CancelOrderDialog,
|
CancelOrderDialog,
|
||||||
PricelistComponent,
|
PricelistComponent,
|
||||||
|
TheaterLayoutDesignerComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ import { HomeComponent } from './home/home.component';
|
|||||||
import { MainLayoutComponent } from './layouts/main-layout/main-layout.component';
|
import { MainLayoutComponent } from './layouts/main-layout/main-layout.component';
|
||||||
import { MainComponent } from './main/main.component';
|
import { MainComponent } from './main/main.component';
|
||||||
import { ScheduleComponent } from './schedule/schedule.component';
|
import { ScheduleComponent } from './schedule/schedule.component';
|
||||||
import { TheaterOverlayComponent} from './theater-overlay/theater-overlay.component';
|
import { TheaterOverlayComponent } from './theater-overlay/theater-overlay.component';
|
||||||
import { MovieImporterComponent } from './movie-importer/movie-importer.component';
|
import { MovieImporterComponent } from './movie-importer/movie-importer.component';
|
||||||
import { AuthGuard } from './auth.guard';
|
import { AuthGuard } from './auth.guard';
|
||||||
import { PayForOrderComponent } from './pay-for-order/pay-for-order.component';
|
import { PayForOrderComponent } from './pay-for-order/pay-for-order.component';
|
||||||
import { StatisticsComponent } from './statistics/statistics.component';
|
import { StatisticsComponent } from './statistics/statistics.component';
|
||||||
import { PricelistComponent } from './pricelist/pricelist.component';
|
import { PricelistComponent } from './pricelist/pricelist.component';
|
||||||
|
import { TheaterLayoutDesignerComponent } from './theater-layout-designer/theater-layout-designer.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
// Seiten ohne Layout
|
// Seiten ohne Layout
|
||||||
@@ -30,15 +31,27 @@ const routes: Routes = [
|
|||||||
canActivate: [AuthGuard],
|
canActivate: [AuthGuard],
|
||||||
data: { roles: ['admin'] }, // Array von erlaubten Rollen. Derzeit gäbe es 'admin' und 'employee'
|
data: { roles: ['admin'] }, // Array von erlaubten Rollen. Derzeit gäbe es 'admin' und 'employee'
|
||||||
},
|
},
|
||||||
{ path: 'checkout/performance/:performanceId', component: TheaterOverlayComponent},
|
{ path: 'checkout/performance/:performanceId', component: TheaterOverlayComponent },
|
||||||
{ path: 'checkout/order/:orderId', component: TheaterOverlayComponent},
|
{ path: 'checkout/order/:orderId', component: TheaterOverlayComponent },
|
||||||
{ path: 'checkout/order', component: PayForOrderComponent},
|
{ path: 'checkout/order', component: PayForOrderComponent },
|
||||||
{
|
{
|
||||||
path: 'admin/statistics',
|
path: 'admin/statistics',
|
||||||
component: StatisticsComponent,
|
component: StatisticsComponent,
|
||||||
canActivate: [AuthGuard],
|
canActivate: [AuthGuard],
|
||||||
data: { roles: ['admin'] }, // Array von erlaubten Rollen. Derzeit gäbe es 'admin' und 'employee'
|
data: { roles: ['admin'] }, // Array von erlaubten Rollen. Derzeit gäbe es 'admin' und 'employee'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'admin/designer',
|
||||||
|
component: TheaterLayoutDesignerComponent,
|
||||||
|
canActivate: [AuthGuard],
|
||||||
|
data: { roles: ['admin'] }, // Array von erlaubten Rollen. Derzeit gäbe es 'admin' und 'employee'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'admin/designer/:hallId',
|
||||||
|
component: TheaterLayoutDesignerComponent,
|
||||||
|
canActivate: [AuthGuard],
|
||||||
|
data: { roles: ['admin'] }, // Array von erlaubten Rollen. Derzeit gäbe es 'admin' und 'employee'
|
||||||
|
},
|
||||||
{ path: 'prices', component: PricelistComponent },
|
{ path: 'prices', component: PricelistComponent },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,21 +6,22 @@ import {
|
|||||||
OmdbSearch,
|
OmdbSearch,
|
||||||
Bestellung,
|
Bestellung,
|
||||||
Eintrittskarte,
|
Eintrittskarte,
|
||||||
StatisticsFilm, StatisticsVorstellung,
|
StatisticsFilm,
|
||||||
Sitzkategorie
|
StatisticsVorstellung,
|
||||||
|
Sitzkategorie,
|
||||||
|
Sitzreihe,
|
||||||
} from '@infinimotion/model-frontend';
|
} from '@infinimotion/model-frontend';
|
||||||
import { HttpClient } from "@angular/common/http";
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { inject, Injectable } from "@angular/core";
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { Observable } from "rxjs";
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class HttpService {
|
export class HttpService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private baseUrl = 'https://infinimotion.de/api/';
|
private baseUrl = 'https://infinimotion.de/api/';
|
||||||
|
|
||||||
|
|
||||||
/* Bestellung APIs */
|
/* Bestellung APIs */
|
||||||
|
|
||||||
/* POST /api/bestellung/filter */
|
/* POST /api/bestellung/filter */
|
||||||
@@ -39,11 +40,16 @@ export class HttpService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* POST /api/order-transaction/create */
|
/* POST /api/order-transaction/create */
|
||||||
saveAddOrder(req: {order:Bestellung, tickets:Eintrittskarte[]}): Observable<{order:Bestellung, tickets:Eintrittskarte[]}> {
|
saveAddOrder(req: {
|
||||||
return this.http.post<{order: Bestellung, tickets: Eintrittskarte[]}>(`${this.baseUrl}order-transaction/create`, req);
|
order: Bestellung;
|
||||||
|
tickets: Eintrittskarte[];
|
||||||
|
}): Observable<{ order: Bestellung; tickets: Eintrittskarte[] }> {
|
||||||
|
return this.http.post<{ order: Bestellung; tickets: Eintrittskarte[] }>(
|
||||||
|
`${this.baseUrl}order-transaction/create`,
|
||||||
|
req
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Eintrittskarte APIs */
|
/* Eintrittskarte APIs */
|
||||||
|
|
||||||
/* POST /api/eintrittskarte/filter */
|
/* POST /api/eintrittskarte/filter */
|
||||||
@@ -51,7 +57,6 @@ export class HttpService {
|
|||||||
return this.http.post<Eintrittskarte[]>(`${this.baseUrl}eintrittskarte/filter`, filter);
|
return this.http.post<Eintrittskarte[]>(`${this.baseUrl}eintrittskarte/filter`, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Kinosaal APIs */
|
/* Kinosaal APIs */
|
||||||
|
|
||||||
/* GET /api/kinosaal */
|
/* GET /api/kinosaal */
|
||||||
@@ -64,7 +69,6 @@ export class HttpService {
|
|||||||
return this.http.post<Kinosaal>(`${this.baseUrl}kinosaal`, kinosaal);
|
return this.http.post<Kinosaal>(`${this.baseUrl}kinosaal`, kinosaal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Vorstellung APIs */
|
/* Vorstellung APIs */
|
||||||
|
|
||||||
/* GET /api/vorstellung/{id} */
|
/* GET /api/vorstellung/{id} */
|
||||||
@@ -77,7 +81,6 @@ export class HttpService {
|
|||||||
return this.http.post<Vorstellung[]>(`${this.baseUrl}vorstellung/filter`, filter);
|
return this.http.post<Vorstellung[]>(`${this.baseUrl}vorstellung/filter`, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Film APIs */
|
/* Film APIs */
|
||||||
|
|
||||||
/* GET /api/film */
|
/* GET /api/film */
|
||||||
@@ -90,51 +93,70 @@ export class HttpService {
|
|||||||
return this.http.post<Film[]>(`${this.baseUrl}film/filter`, filter);
|
return this.http.post<Film[]>(`${this.baseUrl}film/filter`, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Show-Seats APIs */
|
/* Show-Seats APIs */
|
||||||
|
|
||||||
/* GET /api/show-seats/{show} */
|
/* GET /api/show-seats/{show} */
|
||||||
getSeatsByShowId(show: number): Observable<{ seats: Sitzplatz[], reserved: Sitzplatz[], booked: Sitzplatz[] }> {
|
getSeatsByShowId(
|
||||||
|
show: number
|
||||||
|
): Observable<{ seats: Sitzplatz[]; reserved: Sitzplatz[]; booked: Sitzplatz[] }> {
|
||||||
return this.http.get<{
|
return this.http.get<{
|
||||||
seats: Sitzplatz[],
|
seats: Sitzplatz[];
|
||||||
reserved: Sitzplatz[],
|
reserved: Sitzplatz[];
|
||||||
booked: Sitzplatz[]
|
booked: Sitzplatz[];
|
||||||
}>(`${this.baseUrl}show-seats/${show}`);
|
}>(`${this.baseUrl}show-seats/${show}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Sitzplatz APIs */
|
||||||
|
|
||||||
|
/* POST /api/seat/filter */
|
||||||
|
getSeatsByHallId(hall: number): Observable<Sitzplatz[]> {
|
||||||
|
return this.http.post<Sitzplatz[]>(`${this.baseUrl}sitzplatz/filter`, [
|
||||||
|
`eq;row.hall.id;int;${hall}`,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* POST /api/sitzplatz */
|
||||||
|
createSeat(seat: Sitzplatz): Observable<Sitzplatz> {
|
||||||
|
return this.http.post<Sitzplatz>(`${this.baseUrl}sitzplatz`, seat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sitzreihe APIs */
|
||||||
|
|
||||||
|
/* POST /api/sitzreihe */
|
||||||
|
createSeatRow(row: Sitzreihe): Observable<Sitzreihe> {
|
||||||
|
return this.http.post<Sitzreihe>(`${this.baseUrl}sitzreihe`, row);
|
||||||
|
}
|
||||||
|
|
||||||
/* Movie Importer APIs */
|
/* Movie Importer APIs */
|
||||||
|
|
||||||
/* GET /api/importer/search */
|
/* GET /api/importer/search */
|
||||||
searchMovie(query: string): Observable<OmdbSearch> {
|
searchMovie(query: string): Observable<OmdbSearch> {
|
||||||
return this.http.get<OmdbSearch>(`${this.baseUrl}importer/search`, {
|
return this.http.get<OmdbSearch>(`${this.baseUrl}importer/search`, {
|
||||||
params: {title: query}
|
params: { title: query },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* POST /api/importer/import */
|
/* POST /api/importer/import */
|
||||||
importMovie(imdbId: string): Observable<Film> {
|
importMovie(imdbId: string): Observable<Film> {
|
||||||
return this.http.post<Film>(`${this.baseUrl}importer/import?id=${imdbId}`, {})
|
return this.http.post<Film>(`${this.baseUrl}importer/import?id=${imdbId}`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Statistics APIs */
|
/* Statistics APIs */
|
||||||
|
|
||||||
/* GET /api/statistics/movies */
|
/* GET /api/statistics/movies */
|
||||||
getMovieStatistics(): Observable<StatisticsFilm[]> {
|
getMovieStatistics(): Observable<StatisticsFilm[]> {
|
||||||
return this.http.get<StatisticsFilm[]>(`${this.baseUrl}statistics/movies`)
|
return this.http.get<StatisticsFilm[]>(`${this.baseUrl}statistics/movies`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* GET /api/statistics/shows */
|
/* GET /api/statistics/shows */
|
||||||
getShowStatistics(): Observable<StatisticsVorstellung[]> {
|
getShowStatistics(): Observable<StatisticsVorstellung[]> {
|
||||||
return this.http.get<StatisticsVorstellung[]>(`${this.baseUrl}statistics/shows`)
|
return this.http.get<StatisticsVorstellung[]>(`${this.baseUrl}statistics/shows`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Sitzkategorie APIs */
|
/* Sitzkategorie APIs */
|
||||||
|
|
||||||
/* GET /api/sitzkategorie */
|
/* GET /api/sitzkategorie */
|
||||||
getSeatCategories(): Observable<Sitzkategorie[]> {
|
getSeatCategories(): Observable<Sitzkategorie[]> {
|
||||||
return this.http.get<Sitzkategorie[]>(`${this.baseUrl}sitzkategorie`)
|
return this.http.get<Sitzkategorie[]>(`${this.baseUrl}sitzkategorie`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,18 +5,19 @@ import { Component, inject, computed, OnInit } from '@angular/core';
|
|||||||
selector: 'app-navbar',
|
selector: 'app-navbar',
|
||||||
standalone: false,
|
standalone: false,
|
||||||
templateUrl: './navbar.component.html',
|
templateUrl: './navbar.component.html',
|
||||||
styleUrl: './navbar.component.css'
|
styleUrl: './navbar.component.css',
|
||||||
})
|
})
|
||||||
export class NavbarComponent {
|
export class NavbarComponent {
|
||||||
navItems: { label:string, path:string }[] = [
|
navItems: { label: string; path: string }[] = [
|
||||||
{label: 'Programm', path: '/schedule'},
|
{ label: 'Programm', path: '/schedule' },
|
||||||
{label: 'Preise', path: '/prices'},
|
{ label: 'Preise', path: '/prices' },
|
||||||
{label: 'Bezahlen', path: '/checkout/order'},
|
{ label: 'Bezahlen', path: '/checkout/order' },
|
||||||
{label: 'Film importieren', path: '/admin/movie-importer'},
|
{ label: 'Film importieren', path: '/admin/movie-importer' },
|
||||||
{label: 'Statistiken', path: '/admin/statistics'},
|
{ label: 'Statistiken', path: '/admin/statistics' },
|
||||||
]
|
{ label: 'Saal-Designer', path: '/admin/designer' },
|
||||||
|
];
|
||||||
|
|
||||||
private auth = inject(AuthService)
|
private auth = inject(AuthService);
|
||||||
|
|
||||||
currentUser = computed(() => this.auth.user());
|
currentUser = computed(() => this.auth.user());
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { computed, Injectable, signal } from '@angular/core';
|
import { computed, Injectable, signal } from '@angular/core';
|
||||||
import { Sitzplatz } from '@infinimotion/model-frontend';
|
import { Sitzplatz, Sitzreihe } from '@infinimotion/model-frontend';
|
||||||
|
import { TheaterLayoutDesignerComponent } from './theater-layout-designer/theater-layout-designer.component';
|
||||||
|
import { TheaterSeatState } from './model/theater-seat-state.model';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class SelectedSeatsService {
|
export class SelectedSeatsService {
|
||||||
|
|
||||||
private selectedSeatsSignal = signal<Sitzplatz[]>([]);
|
private selectedSeatsSignal = signal<Sitzplatz[]>([]);
|
||||||
private seatIsSelectableSignal = signal(true);
|
private seatIsSelectableSignal = signal(true);
|
||||||
private committedSignal = signal(false);
|
private committedSignal = signal(false);
|
||||||
@@ -23,20 +24,26 @@ export class SelectedSeatsService {
|
|||||||
readonly hadConflict = this.hadConflictSignal.asReadonly();
|
readonly hadConflict = this.hadConflictSignal.asReadonly();
|
||||||
|
|
||||||
readonly totalSeats = computed(() => this.selectedSeats().length);
|
readonly totalSeats = computed(() => this.selectedSeats().length);
|
||||||
readonly totalPrice = computed(() => this.selectedSeats().reduce((sum, seat) => sum + seat.row.category.price, 0));
|
readonly totalPrice = computed(() =>
|
||||||
|
this.selectedSeats().reduce((sum, seat) => sum + seat.row.category.price, 0)
|
||||||
|
);
|
||||||
|
|
||||||
pushSelectedSeat(selectedSeat: Sitzplatz): void {
|
pushSelectedSeat(selectedSeat: Sitzplatz): void {
|
||||||
this.selectedSeatsSignal.update(seats => [...seats, selectedSeat]);
|
if (selectedSeat.id < 0) {
|
||||||
|
TheaterLayoutDesignerComponent.interceptSeatSelection(selectedSeat);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.selectedSeatsSignal.update((seats) => [...seats, selectedSeat]);
|
||||||
this.hadConflictSignal.set(false);
|
this.hadConflictSignal.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeSelectedSeat(selectedSeat: Sitzplatz): void {
|
removeSelectedSeat(selectedSeat: Sitzplatz): void {
|
||||||
this.selectedSeatsSignal.update(seats => seats.filter(seat => seat.id !== selectedSeat.id));
|
this.selectedSeatsSignal.update((seats) => seats.filter((seat) => seat.id !== selectedSeat.id));
|
||||||
this.hadConflictSignal.set(false);
|
this.hadConflictSignal.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSeatsByCategory(categoryId: number): Sitzplatz[] {
|
getSeatsByCategory(categoryId: number): Sitzplatz[] {
|
||||||
return this.selectedSeats().filter(seat => seat.row.category.id === categoryId);
|
return this.selectedSeats().filter((seat) => seat.row.category.id === categoryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSelection(): void {
|
clearSelection(): void {
|
||||||
@@ -47,7 +54,7 @@ export class SelectedSeatsService {
|
|||||||
this.hadConflictSignal.set(false);
|
this.hadConflictSignal.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSeatIsSelectable(): boolean{
|
getSeatIsSelectable(): boolean {
|
||||||
return this.seatIsSelectable();
|
return this.seatIsSelectable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,11 +82,11 @@ export class SelectedSeatsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleDebug(): void {
|
toggleDebug(): void {
|
||||||
this.debugSignal.update(debug => !debug);
|
this.debugSignal.update((debug) => !debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
isSeatSelected(seatId: number): boolean {
|
isSeatSelected(seatId: number): boolean {
|
||||||
return this.selectedSeats().some(seat => seat.id === seatId);
|
return this.selectedSeats().some((seat) => seat.id === seatId);
|
||||||
}
|
}
|
||||||
|
|
||||||
setConflict(value: boolean): void {
|
setConflict(value: boolean): void {
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
@if(getHallId() >= 0) {
|
||||||
|
<div
|
||||||
|
class="m-auto w-200 h-10 bg-gray-200 mb-22 cursor-pointer"
|
||||||
|
style="clip-path: polygon(0% 0%, 100% 0%, 90% 100%, 10% 100%)"
|
||||||
|
>
|
||||||
|
<p class="flex justify-center text-lg font-bold p-1.5">Leinwand</p>
|
||||||
|
</div>
|
||||||
|
<div class="mb-5">
|
||||||
|
@for (row of seatsPerRow(); track $index) {
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<!-- Speaker -->
|
||||||
|
<div class="shrink-0 pl-20">
|
||||||
|
@if ($index % 4 === 0) {
|
||||||
|
<mat-icon
|
||||||
|
class="material-symbols-outlined opacity-25"
|
||||||
|
style="font-size: 30px; width: 30px; height: 30px"
|
||||||
|
>
|
||||||
|
speaker
|
||||||
|
</mat-icon>
|
||||||
|
} @if ($index % 4 === 2) {
|
||||||
|
<mat-icon
|
||||||
|
class="material-symbols-outlined opacity-25"
|
||||||
|
style="font-size: 30px; width: 30px; height: 30px"
|
||||||
|
>
|
||||||
|
wall_lamp
|
||||||
|
</mat-icon>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sitzreihe -->
|
||||||
|
<app-seat-row class="flex justify-center" [rowSeatList]="row"></app-seat-row>
|
||||||
|
|
||||||
|
<!-- Speaker -->
|
||||||
|
<div class="shrink-0 pr-20">
|
||||||
|
@if ($index % 4 === 0) {
|
||||||
|
<mat-icon
|
||||||
|
class="material-symbols-outlined opacity-25 mirrored"
|
||||||
|
style="font-size: 30px; width: 30px; height: 30px"
|
||||||
|
>
|
||||||
|
speaker
|
||||||
|
</mat-icon>
|
||||||
|
} @if ($index % 4 === 2) {
|
||||||
|
<mat-icon
|
||||||
|
class="material-symbols-outlined opacity-25 mirrored"
|
||||||
|
style="font-size: 30px; width: 30px; height: 30px"
|
||||||
|
>
|
||||||
|
wall_lamp
|
||||||
|
</mat-icon>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<app-seat-row class="flex justify-center" [rowSeatList]="addRow"></app-seat-row>
|
||||||
|
<br />
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<button mat-button matButton="filled" class="w-1/4" (click)="save()">Speichern</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<br />
|
||||||
|
<div class="w-100 m-auto middle">
|
||||||
|
<form class="order-search-form w-full" (ngSubmit)="navigate()">
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<mat-form-field class="w-full" subscriptSizing="dynamic">
|
||||||
|
<mat-label>Kinosaal-Name</mat-label>
|
||||||
|
<input
|
||||||
|
class="w-full"
|
||||||
|
type="text"
|
||||||
|
matInput
|
||||||
|
placeholder="Galactus"
|
||||||
|
autocomplete="off"
|
||||||
|
name="hallName"
|
||||||
|
[(ngModel)]="hallName"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
class="w-100 mt-2"
|
||||||
|
matButton="filled"
|
||||||
|
color="accent"
|
||||||
|
[disabled]="hallName.length == 0"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Bearbeiten oder Erstellen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
import { Component, inject, OnInit } from '@angular/core';
|
||||||
|
import { Kinosaal, Sitzkategorie, Sitzplatz, Sitzreihe } from '@infinimotion/model-frontend';
|
||||||
|
import { TheaterSeatState } from '../model/theater-seat-state.model';
|
||||||
|
import { SelectedSeatsService } from '../selected-seats.service';
|
||||||
|
import { HttpService } from '../http.service';
|
||||||
|
import { first, firstValueFrom } from 'rxjs';
|
||||||
|
import { TheaterOverlayComponent } from '../theater-overlay/theater-overlay.component';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { LoadingService } from '../loading.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-theater-layout-designer',
|
||||||
|
standalone: false,
|
||||||
|
templateUrl: './theater-layout-designer.component.html',
|
||||||
|
styleUrl: './theater-layout-designer.component.css',
|
||||||
|
})
|
||||||
|
export class TheaterLayoutDesignerComponent implements OnInit {
|
||||||
|
public static seatsPerRow: { seat: Sitzplatz | null; state: TheaterSeatState | null }[][] = [];
|
||||||
|
addRow: { seat: Sitzplatz | null; state: TheaterSeatState | null }[] = [];
|
||||||
|
|
||||||
|
protected selectedSeatsService = inject(SelectedSeatsService);
|
||||||
|
protected http = inject(HttpService);
|
||||||
|
protected route = inject(ActivatedRoute);
|
||||||
|
protected loading = inject(LoadingService);
|
||||||
|
protected router = inject(Router);
|
||||||
|
|
||||||
|
getHallId() {
|
||||||
|
return parseInt(this.route.snapshot.paramMap.get('hallId') ?? '-1');
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
let hallId = this.getHallId();
|
||||||
|
if (hallId == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let seats = await firstValueFrom(this.http.getSeatsByHallId(hallId));
|
||||||
|
let rows: { seat: Sitzplatz | null; state: TheaterSeatState | null }[][] = [];
|
||||||
|
const categoryMap = new Map<number, Sitzkategorie>();
|
||||||
|
|
||||||
|
seats.forEach((seat) => {
|
||||||
|
if (!rows[seat.row.position]) {
|
||||||
|
rows[seat.row.position] = [];
|
||||||
|
}
|
||||||
|
rows[seat.row.position].push({
|
||||||
|
seat: seat,
|
||||||
|
state: TheaterSeatState.RESERVED,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (seat.row.category && !categoryMap.has(seat.row.category.id)) {
|
||||||
|
categoryMap.set(seat.row.category.id, seat.row.category);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
TheaterLayoutDesignerComponent.seatsPerRow = TheaterOverlayComponent.fillSeatsPerRow(rows);
|
||||||
|
let rowCounter = 0;
|
||||||
|
TheaterLayoutDesignerComponent.seatsPerRow.forEach(
|
||||||
|
(row) => (rowCounter = TheaterLayoutDesignerComponent.fillRowWithSettings(rowCounter, row))
|
||||||
|
);
|
||||||
|
|
||||||
|
let realCategories = await firstValueFrom(this.http.getSeatCategories());
|
||||||
|
this.addRow = realCategories.map((category) => {
|
||||||
|
return {
|
||||||
|
seat: {
|
||||||
|
id: -2,
|
||||||
|
position: 1,
|
||||||
|
row: {
|
||||||
|
id: 0,
|
||||||
|
hall: { id: hallId } as Kinosaal,
|
||||||
|
category: category,
|
||||||
|
position: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
state: TheaterSeatState.AVAILABLE,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
seatsPerRow() {
|
||||||
|
return TheaterLayoutDesignerComponent.seatsPerRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fillRowWithSettings(
|
||||||
|
rowCounter: number,
|
||||||
|
row: {
|
||||||
|
seat: Sitzplatz | null;
|
||||||
|
state: TheaterSeatState | null;
|
||||||
|
}[]
|
||||||
|
) {
|
||||||
|
if (!row[0].seat) {
|
||||||
|
return rowCounter + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let categories: Sitzkategorie[] = [
|
||||||
|
{ id: 0, name: '', price: -1, icon: 'check_box_outline_blank' },
|
||||||
|
{ id: 1, name: '', price: -1, icon: 'add' },
|
||||||
|
];
|
||||||
|
categories.forEach((category) => {
|
||||||
|
row.push({
|
||||||
|
seat: {
|
||||||
|
id: -1,
|
||||||
|
position: row.length,
|
||||||
|
row: {
|
||||||
|
id: -1,
|
||||||
|
hall: undefined as unknown as Kinosaal,
|
||||||
|
position: rowCounter,
|
||||||
|
category: category,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
state: TheaterSeatState.AVAILABLE,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return rowCounter + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static addSeatDesigner(selectedSeat: Sitzplatz): void {
|
||||||
|
let row = TheaterLayoutDesignerComponent.seatsPerRow[selectedSeat.row.position];
|
||||||
|
if (selectedSeat.row.category.id == 0) {
|
||||||
|
row.splice(row.length - 2, 0, {
|
||||||
|
seat: null,
|
||||||
|
state: null,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
row.splice(row.length - 2, 0, {
|
||||||
|
seat: { id: 0, position: row.length - 1, row: row[0].seat!.row },
|
||||||
|
state: TheaterSeatState.RESERVED,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static addRowDesigner(selectedSeat: Sitzplatz): void {
|
||||||
|
let rows = TheaterLayoutDesignerComponent.seatsPerRow;
|
||||||
|
let firstSeat = {
|
||||||
|
seat: { id: 0, position: 1, row: { ...selectedSeat.row } },
|
||||||
|
state: TheaterSeatState.RESERVED,
|
||||||
|
};
|
||||||
|
firstSeat.seat!.row.position = rows.length + 1;
|
||||||
|
rows.push([firstSeat]);
|
||||||
|
TheaterLayoutDesignerComponent.fillRowWithSettings(rows.length - 1, rows[rows.length - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static interceptSeatSelection(selectedSeat: Sitzplatz) {
|
||||||
|
if (selectedSeat.id == -1) {
|
||||||
|
this.addSeatDesigner(selectedSeat);
|
||||||
|
} else if (selectedSeat.id == -2) {
|
||||||
|
this.addRowDesigner(selectedSeat);
|
||||||
|
} else {
|
||||||
|
console.log('Fehler: unerwartete Seat-ID: ' + selectedSeat.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async save() {
|
||||||
|
this.loading.show();
|
||||||
|
for (let row of TheaterLayoutDesignerComponent.seatsPerRow) {
|
||||||
|
let seatRow;
|
||||||
|
if (!row[0].seat) {
|
||||||
|
return;
|
||||||
|
} else if (row[0].seat.row.id == 0) {
|
||||||
|
seatRow = await firstValueFrom(this.http.createSeatRow(row[0].seat.row));
|
||||||
|
} else {
|
||||||
|
seatRow = row[0].seat.row;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { seat, state } of row) {
|
||||||
|
if (seat != null && seat.id == 0) {
|
||||||
|
seat.row = seatRow;
|
||||||
|
let createdSeat = await firstValueFrom(this.http.createSeat(seat));
|
||||||
|
seat.id = createdSeat.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.loading.showError('Kinosaal erfolgreich aktualisiert');
|
||||||
|
}
|
||||||
|
|
||||||
|
hallName: string = '';
|
||||||
|
|
||||||
|
async navigate() {
|
||||||
|
let halls = await firstValueFrom(this.http.getAllKinosaal());
|
||||||
|
let hall = halls.filter((hall) => hall.name == this.hallName);
|
||||||
|
if (hall.length == 0) {
|
||||||
|
hall[0] = await firstValueFrom(this.http.addKinosaal({ name: this.hallName }));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.router.navigate(['/admin/designer', hall[0].id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,27 @@
|
|||||||
import { Component, DestroyRef, inject, OnDestroy, OnInit, signal } from '@angular/core';
|
import { Component, DestroyRef, inject, OnDestroy, OnInit, signal } from '@angular/core';
|
||||||
import { HttpService } from '../http.service';
|
import { HttpService } from '../http.service';
|
||||||
import { LoadingService } from '../loading.service';
|
import { LoadingService } from '../loading.service';
|
||||||
import { catchError, filter, finalize, forkJoin, from, fromEvent, interval, merge, of, startWith, switchMap, tap } from 'rxjs';
|
import {
|
||||||
import { Bestellung, Eintrittskarte, Sitzkategorie, Sitzplatz, Vorstellung } from '@infinimotion/model-frontend';
|
catchError,
|
||||||
|
filter,
|
||||||
|
finalize,
|
||||||
|
forkJoin,
|
||||||
|
from,
|
||||||
|
fromEvent,
|
||||||
|
interval,
|
||||||
|
merge,
|
||||||
|
of,
|
||||||
|
startWith,
|
||||||
|
switchMap,
|
||||||
|
tap,
|
||||||
|
} from 'rxjs';
|
||||||
|
import {
|
||||||
|
Bestellung,
|
||||||
|
Eintrittskarte,
|
||||||
|
Sitzkategorie,
|
||||||
|
Sitzplatz,
|
||||||
|
Vorstellung,
|
||||||
|
} from '@infinimotion/model-frontend';
|
||||||
import { TheaterSeatState } from '../model/theater-seat-state.model';
|
import { TheaterSeatState } from '../model/theater-seat-state.model';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { SelectedSeatsService } from '../selected-seats.service';
|
import { SelectedSeatsService } from '../selected-seats.service';
|
||||||
@@ -16,7 +35,7 @@ const INACTIVITY_TIMEOUT_MS = 2 * 60 * 1000;
|
|||||||
selector: 'app-theater-overlay',
|
selector: 'app-theater-overlay',
|
||||||
standalone: false,
|
standalone: false,
|
||||||
templateUrl: './theater-overlay.component.html',
|
templateUrl: './theater-overlay.component.html',
|
||||||
styleUrl: './theater-overlay.component.css'
|
styleUrl: './theater-overlay.component.css',
|
||||||
})
|
})
|
||||||
export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
||||||
private http = inject(HttpService);
|
private http = inject(HttpService);
|
||||||
@@ -36,10 +55,10 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
order: Bestellung | undefined;
|
order: Bestellung | undefined;
|
||||||
blockedSeats: Sitzplatz[] | undefined;
|
blockedSeats: Sitzplatz[] | undefined;
|
||||||
seatCategories: Sitzkategorie[] = [];
|
seatCategories: Sitzkategorie[] = [];
|
||||||
snackBarRef: MatSnackBarRef<TextOnlySnackBar> | undefined
|
snackBarRef: MatSnackBarRef<TextOnlySnackBar> | undefined;
|
||||||
|
|
||||||
performance = signal<Vorstellung | undefined>(undefined);
|
performance = signal<Vorstellung | undefined>(undefined);
|
||||||
seatsPerRow = signal<{ seat: Sitzplatz | null, state: TheaterSeatState | null }[][]>([]);
|
seatsPerRow = signal<{ seat: Sitzplatz | null; state: TheaterSeatState | null }[][]>([]);
|
||||||
|
|
||||||
private isPollingEnabled = signal(true);
|
private isPollingEnabled = signal(true);
|
||||||
private isInitialLoad = signal(true);
|
private isInitialLoad = signal(true);
|
||||||
@@ -60,7 +79,7 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.loadExistingOrder(this.orderCode);
|
this.loadExistingOrder(this.orderCode);
|
||||||
|
|
||||||
this.route.queryParams.subscribe(params => {
|
this.route.queryParams.subscribe((params) => {
|
||||||
if (params['action'] === 'cancel') {
|
if (params['action'] === 'cancel') {
|
||||||
this.resumeWithCancel = true;
|
this.resumeWithCancel = true;
|
||||||
}
|
}
|
||||||
@@ -75,7 +94,6 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
this.setupActivityTracking();
|
this.setupActivityTracking();
|
||||||
this.startAutoRefresh();
|
this.startAutoRefresh();
|
||||||
this.startInactivityCheck();
|
this.startInactivityCheck();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Fallback
|
// Fallback
|
||||||
console.error('Ungültige Checkout-Route');
|
console.error('Ungültige Checkout-Route');
|
||||||
@@ -87,7 +105,7 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
this.isPollingEnabled.set(false);
|
this.isPollingEnabled.set(false);
|
||||||
console.info('[TheaterOverlay] Stopped auto-refresh polling');
|
console.info('[TheaterOverlay] Stopped auto-refresh polling');
|
||||||
|
|
||||||
if(this.snackBarRef) {
|
if (this.snackBarRef) {
|
||||||
this.snackBar.dismiss();
|
this.snackBar.dismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,15 +120,13 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
fromEvent(document, 'touchstart')
|
fromEvent(document, 'touchstart')
|
||||||
);
|
);
|
||||||
|
|
||||||
events$.pipe(
|
events$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
||||||
takeUntilDestroyed(this.destroyRef)
|
|
||||||
).subscribe(() => {
|
|
||||||
this.lastActivityTimestamp.set(Date.now());
|
this.lastActivityTimestamp.set(Date.now());
|
||||||
});
|
});
|
||||||
|
|
||||||
fromEvent(document, 'visibilitychange').pipe(
|
fromEvent(document, 'visibilitychange')
|
||||||
takeUntilDestroyed(this.destroyRef)
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||||
).subscribe(() => {
|
.subscribe(() => {
|
||||||
if (!this.isStepTwoOrHigher()) {
|
if (!this.isStepTwoOrHigher()) {
|
||||||
if (document.hidden) {
|
if (document.hidden) {
|
||||||
console.info('[TheaterOverlay] Tab hidden - pausing polling');
|
console.info('[TheaterOverlay] Tab hidden - pausing polling');
|
||||||
@@ -128,11 +144,13 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private startInactivityCheck() {
|
private startInactivityCheck() {
|
||||||
interval(10000).pipe(
|
interval(10000)
|
||||||
|
.pipe(
|
||||||
filter(() => this.isPollingEnabled()),
|
filter(() => this.isPollingEnabled()),
|
||||||
filter(() => !this.isStepTwoOrHigher()), // Kein Timeout ab Schritt 2
|
filter(() => !this.isStepTwoOrHigher()), // Kein Timeout ab Schritt 2
|
||||||
takeUntilDestroyed(this.destroyRef)
|
takeUntilDestroyed(this.destroyRef)
|
||||||
).subscribe(() => {
|
)
|
||||||
|
.subscribe(() => {
|
||||||
const inactiveDuration = Date.now() - this.lastActivityTimestamp();
|
const inactiveDuration = Date.now() - this.lastActivityTimestamp();
|
||||||
|
|
||||||
if (inactiveDuration >= INACTIVITY_TIMEOUT_MS && !this.inactivityTimeoutReached()) {
|
if (inactiveDuration >= INACTIVITY_TIMEOUT_MS && !this.inactivityTimeoutReached()) {
|
||||||
@@ -149,7 +167,6 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private showInactivitySnackBar() {
|
private showInactivitySnackBar() {
|
||||||
|
|
||||||
this.snackBarRef = this.snackBar.open(
|
this.snackBarRef = this.snackBar.open(
|
||||||
'Sitzplatzaktuallisierung wegen Inaktivität gestoppt.',
|
'Sitzplatzaktuallisierung wegen Inaktivität gestoppt.',
|
||||||
'Fortsetzen',
|
'Fortsetzen',
|
||||||
@@ -157,7 +174,7 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
duration: 0,
|
duration: 0,
|
||||||
panelClass: ['timeout-snackbar'],
|
panelClass: ['timeout-snackbar'],
|
||||||
horizontalPosition: 'center',
|
horizontalPosition: 'center',
|
||||||
verticalPosition: 'bottom'
|
verticalPosition: 'bottom',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -171,7 +188,8 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
private startAutoRefresh() {
|
private startAutoRefresh() {
|
||||||
console.info('[TheaterOverlay] Starting auto-refresh polling');
|
console.info('[TheaterOverlay] Starting auto-refresh polling');
|
||||||
interval(POLLING_INTERVAL_MS).pipe(
|
interval(POLLING_INTERVAL_MS)
|
||||||
|
.pipe(
|
||||||
startWith(POLLING_INTERVAL_MS),
|
startWith(POLLING_INTERVAL_MS),
|
||||||
filter(() => this.isPollingEnabled()),
|
filter(() => this.isPollingEnabled()),
|
||||||
filter(() => !this.selectedSeatService.committed()),
|
filter(() => !this.selectedSeatService.committed()),
|
||||||
@@ -181,7 +199,8 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
filter(() => !this.isStepTwoOrHigher()), // Nicht pollen ab Schritt 2
|
filter(() => !this.isStepTwoOrHigher()), // Nicht pollen ab Schritt 2
|
||||||
switchMap(() => this.loadPerformanceAndSeats()),
|
switchMap(() => this.loadPerformanceAndSeats()),
|
||||||
takeUntilDestroyed(this.destroyRef)
|
takeUntilDestroyed(this.destroyRef)
|
||||||
).subscribe();
|
)
|
||||||
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadPerformanceAndSeats() {
|
private loadPerformanceAndSeats() {
|
||||||
@@ -195,7 +214,7 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
return forkJoin({
|
return forkJoin({
|
||||||
performance: this.http.getPerformaceById(this.showId!),
|
performance: this.http.getPerformaceById(this.showId!),
|
||||||
seats: this.http.getSeatsByShowId(this.showId!)
|
seats: this.http.getSeatsByShowId(this.showId!),
|
||||||
}).pipe(
|
}).pipe(
|
||||||
tap(({ performance, seats }) => {
|
tap(({ performance, seats }) => {
|
||||||
this.performance.set(performance);
|
this.performance.set(performance);
|
||||||
@@ -206,11 +225,11 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
const conflicts = this.getConflictingSeats(seats.reserved);
|
const conflicts = this.getConflictingSeats(seats.reserved);
|
||||||
if (conflicts.length > 0) {
|
if (conflicts.length > 0) {
|
||||||
console.info('[TheaterOverlay] Conflicts! Updating shopping cart.');
|
console.info('[TheaterOverlay] Conflicts! Updating shopping cart.');
|
||||||
conflicts.forEach(seat => this.selectedSeatService.removeSelectedSeat(seat));
|
conflicts.forEach((seat) => this.selectedSeatService.removeSelectedSeat(seat));
|
||||||
this.selectedSeatService.setConflict(true);
|
this.selectedSeatService.setConflict(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selectedSeatService.selectedSeats
|
this.selectedSeatService.selectedSeats;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.blockedSeats = seats.reserved;
|
this.blockedSeats = seats.reserved;
|
||||||
@@ -220,7 +239,7 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
this.loading.hide();
|
this.loading.hide();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
catchError(err => {
|
catchError((err) => {
|
||||||
if (isInitial) {
|
if (isInitial) {
|
||||||
this.loading.showError(err);
|
this.loading.showError(err);
|
||||||
} else {
|
} else {
|
||||||
@@ -242,50 +261,55 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
this.loading.show();
|
this.loading.show();
|
||||||
const ticketFilter = [`eq;order.code;string;${orderCode}`];
|
const ticketFilter = [`eq;order.code;string;${orderCode}`];
|
||||||
|
|
||||||
this.http.getTicketsByFilter(ticketFilter).pipe(
|
this.http
|
||||||
switchMap(tickets => {
|
.getTicketsByFilter(ticketFilter)
|
||||||
|
.pipe(
|
||||||
|
switchMap((tickets) => {
|
||||||
this.tickets = tickets;
|
this.tickets = tickets;
|
||||||
|
|
||||||
if (!tickets.length) {
|
if (!tickets.length) {
|
||||||
return from(this.router.navigate(
|
return from(
|
||||||
['/checkout/order'],
|
this.router.navigate(['/checkout/order'], {
|
||||||
{ queryParams: { error: 'invalid', code: orderCode } }
|
queryParams: { error: 'invalid', code: orderCode },
|
||||||
));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.order = tickets[0].order;
|
this.order = tickets[0].order;
|
||||||
|
|
||||||
if (this.order.booked || this.order.cancelled) {
|
if (this.order.booked || this.order.cancelled) {
|
||||||
return from(this.router.navigate(
|
return from(
|
||||||
['/checkout/order'],
|
this.router.navigate(['/checkout/order'], {
|
||||||
{ queryParams: { error: 'completed', code: orderCode } }
|
queryParams: { error: 'completed', code: orderCode },
|
||||||
));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showId = this.tickets[0].show.id;
|
this.showId = this.tickets[0].show.id;
|
||||||
|
|
||||||
this.selectedSeatService.clearSelection();
|
this.selectedSeatService.clearSelection();
|
||||||
this.tickets.forEach(t => this.selectedSeatService.pushSelectedSeat(t.seat));
|
this.tickets.forEach((t) => this.selectedSeatService.pushSelectedSeat(t.seat));
|
||||||
this.selectedSeatService.setSeatSelectable(false);
|
this.selectedSeatService.setSeatSelectable(false);
|
||||||
|
|
||||||
return this.loadPerformanceAndSeats();
|
return this.loadPerformanceAndSeats();
|
||||||
}),
|
}),
|
||||||
catchError(err => {
|
catchError((err) => {
|
||||||
console.error('Fehler beim Laden der Bestellung', err);
|
console.error('Fehler beim Laden der Bestellung', err);
|
||||||
|
|
||||||
return from(this.router.navigate(
|
return from(
|
||||||
['/checkout/order'],
|
this.router.navigate(['/checkout/order'], {
|
||||||
{ queryParams: { error: 'invalid', code: orderCode } }
|
queryParams: { error: 'invalid', code: orderCode },
|
||||||
));
|
})
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
finalize(() => {
|
finalize(() => {
|
||||||
this.loading.hide();
|
this.loading.hide();
|
||||||
}),
|
}),
|
||||||
takeUntilDestroyed(this.destroyRef)
|
takeUntilDestroyed(this.destroyRef)
|
||||||
).subscribe();
|
)
|
||||||
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private equalSeats(a: Sitzplatz[], b: Sitzplatz[]): boolean {
|
private equalSeats(a: Sitzplatz[], b: Sitzplatz[]): boolean {
|
||||||
if (a === b) return true;
|
if (a === b) return true;
|
||||||
if (a == null || b == null) return false;
|
if (a == null || b == null) return false;
|
||||||
@@ -302,30 +326,32 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getConflictingSeats(blockedSeats: Sitzplatz[]): Sitzplatz[] {
|
private getConflictingSeats(blockedSeats: Sitzplatz[]): Sitzplatz[] {
|
||||||
const blockedIds = new Set(blockedSeats.map(bs => bs.id));
|
const blockedIds = new Set(blockedSeats.map((bs) => bs.id));
|
||||||
return this.selectedSeatService.selectedSeats().filter(
|
return this.selectedSeatService
|
||||||
selectedSeat => blockedIds.has(selectedSeat.id)
|
.selectedSeats()
|
||||||
);
|
.filter((selectedSeat) => blockedIds.has(selectedSeat.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
private converter(resp: { seats: Sitzplatz[], reserved: Sitzplatz[], booked: Sitzplatz[] }): {
|
private converter(resp: { seats: Sitzplatz[]; reserved: Sitzplatz[]; booked: Sitzplatz[] }): {
|
||||||
seat: Sitzplatz | null,
|
seat: Sitzplatz | null;
|
||||||
state: TheaterSeatState | null
|
state: TheaterSeatState | null;
|
||||||
}[][] {
|
}[][] {
|
||||||
let rows: { seat: Sitzplatz | null, state: TheaterSeatState | null }[][] = [];
|
let rows: { seat: Sitzplatz | null; state: TheaterSeatState | null }[][] = [];
|
||||||
const categoryMap = new Map<number, Sitzkategorie>();
|
const categoryMap = new Map<number, Sitzkategorie>();
|
||||||
|
|
||||||
// Sitzplätze sammeln
|
// Sitzplätze sammeln
|
||||||
resp.seats.forEach(seat => {
|
resp.seats.forEach((seat) => {
|
||||||
if (!rows[seat.row.position]) {
|
if (!rows[seat.row.position]) {
|
||||||
rows[seat.row.position] = [];
|
rows[seat.row.position] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
let state = resp.booked.find(other => other.id == seat.id) ? TheaterSeatState.BOOKED
|
let state = resp.booked.find((other) => other.id == seat.id)
|
||||||
: resp.reserved.find(other => other.id == seat.id) ? TheaterSeatState.RESERVED
|
? TheaterSeatState.BOOKED
|
||||||
|
: resp.reserved.find((other) => other.id == seat.id)
|
||||||
|
? TheaterSeatState.RESERVED
|
||||||
: TheaterSeatState.AVAILABLE;
|
: TheaterSeatState.AVAILABLE;
|
||||||
|
|
||||||
rows[seat.row.position].push({seat: seat, state: state});
|
rows[seat.row.position].push({ seat: seat, state: state });
|
||||||
|
|
||||||
if (seat.row.category && !categoryMap.has(seat.row.category.id)) {
|
if (seat.row.category && !categoryMap.has(seat.row.category.id)) {
|
||||||
categoryMap.set(seat.row.category.id, seat.row.category);
|
categoryMap.set(seat.row.category.id, seat.row.category);
|
||||||
@@ -334,24 +360,35 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.seatCategories = Array.from(categoryMap.values()).sort((a, b) => a.id - b.id);
|
this.seatCategories = Array.from(categoryMap.values()).sort((a, b) => a.id - b.id);
|
||||||
|
|
||||||
rows = rows.filter(row => row && row.length > 0).sort((a, b) => a[0].seat!.row.position - b[0].seat!.row.position);
|
return TheaterOverlayComponent.fillSeatsPerRow(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fillSeatsPerRow(
|
||||||
|
rows: {
|
||||||
|
seat: Sitzplatz | null;
|
||||||
|
state: TheaterSeatState | null;
|
||||||
|
}[][]
|
||||||
|
) {
|
||||||
|
rows = rows
|
||||||
|
.filter((row) => row && row.length > 0)
|
||||||
|
.sort((a, b) => a[0].seat!.row.position - b[0].seat!.row.position);
|
||||||
|
|
||||||
if (rows.length === 0) {
|
if (rows.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Leere Plätze auffüllen
|
// Leere Plätze auffüllen
|
||||||
const filledSeats: { seat: Sitzplatz | null, state: TheaterSeatState | null }[][] = [];
|
const filledSeats: { seat: Sitzplatz | null; state: TheaterSeatState | null }[][] = [];
|
||||||
|
|
||||||
rows.forEach(row => {
|
rows.forEach((row) => {
|
||||||
row.sort((a, b) => a.seat!.position - b.seat!.position)
|
row.sort((a, b) => a.seat!.position - b.seat!.position);
|
||||||
|
|
||||||
const minPos = row[0].seat!.position;
|
const minPos = row[0].seat!.position;
|
||||||
const maxPos = row[row.length - 1].seat!.position;
|
const maxPos = row[row.length - 1].seat!.position;
|
||||||
const filledRow: { seat: Sitzplatz | null, state: TheaterSeatState | null }[] = [];
|
const filledRow: { seat: Sitzplatz | null; state: TheaterSeatState | null }[] = [];
|
||||||
|
|
||||||
for (let pos = minPos; pos <= maxPos; pos++) {
|
for (let pos = minPos; pos <= maxPos; pos++) {
|
||||||
const existingSeat = row.find(s => s.seat!.position === pos);
|
const existingSeat = row.find((s) => s.seat!.position === pos);
|
||||||
if (existingSeat) {
|
if (existingSeat) {
|
||||||
filledRow.push(existingSeat);
|
filledRow.push(existingSeat);
|
||||||
} else {
|
} else {
|
||||||
@@ -365,11 +402,14 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
// Leere Reihen auffüllen
|
// Leere Reihen auffüllen
|
||||||
const minRowPos = rows[0][0].seat!.row.position;
|
const minRowPos = rows[0][0].seat!.row.position;
|
||||||
const maxRowPos = rows[rows.length - 1][0].seat!.row.position;
|
const maxRowPos = rows[rows.length - 1][0].seat!.row.position;
|
||||||
const filledRows: { seat: Sitzplatz | null, state: TheaterSeatState | null }[][] = [];
|
const filledRows: { seat: Sitzplatz | null; state: TheaterSeatState | null }[][] = [];
|
||||||
|
|
||||||
let processedIndex = 0;
|
let processedIndex = 0;
|
||||||
for (let rowPos = minRowPos; rowPos <= maxRowPos; rowPos++) {
|
for (let rowPos = minRowPos; rowPos <= maxRowPos; rowPos++) {
|
||||||
if (processedIndex < filledSeats.length && filledSeats[processedIndex][0].seat!.row.position === rowPos) {
|
if (
|
||||||
|
processedIndex < filledSeats.length &&
|
||||||
|
filledSeats[processedIndex][0].seat!.row.position === rowPos
|
||||||
|
) {
|
||||||
filledRows.push(filledSeats[processedIndex]);
|
filledRows.push(filledSeats[processedIndex]);
|
||||||
processedIndex++;
|
processedIndex++;
|
||||||
} else {
|
} else {
|
||||||
@@ -412,5 +452,3 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user