Refactor order process to fix flicker bug

This commit is contained in:
2025-11-15 01:53:13 +01:00
parent 4ec3795697
commit e5707709bf
8 changed files with 366 additions and 337 deletions

View File

@@ -17,7 +17,7 @@
></app-performance-info> ></app-performance-info>
</div> </div>
@if(isSubmitting) { @if(isSubmitting()) {
<div class="absolute top-55 z-25 w-full px-6 my-auto"> <div class="absolute top-55 z-25 w-full px-6 my-auto">
<mat-progress-spinner <mat-progress-spinner
class="m-auto" class="m-auto"
@@ -69,13 +69,13 @@
<div class="performance-info-space"></div> <div class="performance-info-space"></div>
@if (seatsReserved && !isSubmitting) { @if (isReservationSuccess() && !isSubmitting()) {
<div class="h-4"></div> <div class="h-4"></div>
@if (successful) { <app-reservation-success [order]="createdOrder()!"></app-reservation-success>
<app-reservation-success [order]="this.createdOrder"></app-reservation-success>
} @else {
<app-reservation-failed></app-reservation-failed>
} }
@else if (isReservationError() && !isSubmitting()) {
<div class="h-4"></div>
<app-reservation-failed></app-reservation-failed>
} }
@else { @else {
@@ -95,34 +95,35 @@
<!-- Checkbox --> <!-- Checkbox -->
<div class="w-full my-4"> <div class="w-full my-4">
<mat-checkbox required formControlName="accept" class="checkbox-invalid" [class]="{ 'checkbox-invalid': submitted && fData['accept'].hasError('required') }"> <mat-checkbox required formControlName="accept" class="checkbox-invalid" [class.checkbox-invalid]="submitted() && fData['accept'].hasError('required')">
Ich akzeptiere die AGB und die Datenbestimmung Ich akzeptiere die AGB und die Datenbestimmung
</mat-checkbox> </mat-checkbox>
</div> </div>
<!-- Buttons --> <!-- Buttons -->
<div class="flex space-x-5 mt-10"> <div class="flex space-x-5 mt-10">
<button type="button" mat-button matButton="outlined" (click)="stepper.reset()" class="w-1/3" [disabled]="isSubmitting">Zurück</button> <button type="button" mat-button matButton="outlined" (click)="stepper.reset()" class="w-1/3" [disabled]="isSubmitting()">Zurück</button>
<button type="submit" mat-button matButton="filled" (click)="nextPhaseButtonClicked(stepper)" class="w-2/3" [disabled]="isSubmitting">{{ secondPhaseButtonText }}</button> <button type="submit" mat-button matButton="filled" (click)="nextPhaseButtonClicked(stepper)" class="w-2/3" [disabled]="isSubmitting()">{{ secondPhaseButtonText() }}</button>
</div> </div>
} }
</form> </form>
</mat-step> </mat-step>
<mat-step [stepControl]="paymentForm"> <mat-step [stepControl]="paymentForm">
<form [formGroup]="paymentForm" (ngSubmit)="onSubmit()"> <form [formGroup]="paymentForm" (ngSubmit)="makePurchase()">
<ng-template matStepLabel>Zahlung</ng-template> <ng-template matStepLabel>Zahlung</ng-template>
<div class="performance-info-space"></div> <div class="performance-info-space"></div>
@if (seatsPurchased && !isSubmitting) { @if (isPurchaseSuccess() && !isSubmitting()) {
<div class="h-4"></div> <div class="h-4"></div>
@if (successful) { <app-purchase-success [tickets]="createdTickets()"></app-purchase-success>
<app-purchase-success [tickets]="createdTickets"></app-purchase-success>
} @else {
<app-purchase-failed></app-purchase-failed>
} }
@else if (isPurchaseError() && !isSubmitting()) {
<div class="h-4"></div>
<app-purchase-failed></app-purchase-failed>
} }
@else { @else {
@@ -136,7 +137,6 @@
placeholder="1111 2222 3333 4444" placeholder="1111 2222 3333 4444"
/> />
@if (fPayment['cardNumber'].hasError('pattern')) { <mat-error>Ungültige Kartennummer</mat-error> } @if (fPayment['cardNumber'].hasError('pattern')) { <mat-error>Ungültige Kartennummer</mat-error> }
</mat-form-field> </mat-form-field>
<!-- Card Name --> <!-- Card Name -->
@@ -179,10 +179,10 @@
<!-- Buttons --> <!-- Buttons -->
<div class="flex space-x-4 mt-8"> <div class="flex space-x-4 mt-8">
<button mat-stroked-button color="primary" matStepperPrevious type="button" [disabled]="isSubmitting" class="w-1/3"> <button mat-stroked-button color="primary" matStepperPrevious type="button" [disabled]="isSubmitting()" class="w-1/3">
Zurück Zurück
</button> </button>
<button mat-flat-button color="accent" matStepperNext type="submit" [disabled]="isSubmitting" (click)="makePurchase()" class="w-2/3"> <button mat-flat-button color="accent" matStepperNext type="submit" [disabled]="isSubmitting()" class="w-2/3">
{{ getPriceDisplay(totalPrice()) }} jetzt bezahlen {{ getPriceDisplay(totalPrice()) }} jetzt bezahlen
</button> </button>
</div> </div>

View File

@@ -1,12 +1,23 @@
import { SelectedSeatsService } from './../selected-seats.service'; import { SelectedSeatsService } from './../selected-seats.service';
import { LoadingService } from './../loading.service'; import { LoadingService } from './../loading.service';
import { Bestellung, Eintrittskarte, Sitzkategorie, Sitzplatz, Vorstellung } from '@infinimotion/model-frontend'; import { Bestellung, Eintrittskarte, Sitzkategorie, Sitzplatz, Vorstellung } from '@infinimotion/model-frontend';
import { Component, computed, inject, input } from '@angular/core'; import { Component, computed, DestroyRef, inject, input, signal } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { StepperSelectionEvent } from '@angular/cdk/stepper'; import { StepperSelectionEvent } from '@angular/cdk/stepper';
import { HttpService } from '../http.service'; import { HttpService } from '../http.service';
import { catchError, tap, finalize } from 'rxjs'; import { catchError, tap, finalize, switchMap, map, EMPTY, forkJoin } from 'rxjs';
import { MatStepper } from '@angular/material/stepper'; import { MatStepper } from '@angular/material/stepper';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
type OrderState =
| { status: 'idle' }
| { status: 'submitting' }
| { status: 'reservation-success'; order: Bestellung }
| { status: 'reservation-error'; error: any }
| { status: 'purchase-success'; tickets: Eintrittskarte[] }
| { status: 'purchase-error'; error: any };
type SubmissionMode = 'reservation' | 'purchase';
@Component({ @Component({
selector: 'app-order', selector: 'app-order',
@@ -15,148 +26,194 @@ import { MatStepper } from '@angular/material/stepper';
styleUrl: './order.component.css' styleUrl: './order.component.css'
}) })
export class OrderComponent { export class OrderComponent {
paymentForm!: FormGroup; private fb = inject(FormBuilder);
dataForm!: FormGroup; private httpService = inject(HttpService);
private destroyRef = inject(DestroyRef);
submitted = false; readonly loadingService = inject(LoadingService);
readonly selectedSeatsService = inject(SelectedSeatsService);
performance = input<Vorstellung>(); performance = input<Vorstellung>();
seatCategories = input.required<Sitzkategorie[]>(); seatCategories = input.required<Sitzkategorie[]>();
loadingService = inject(LoadingService); paymentForm!: FormGroup;
private httpService = inject(HttpService) dataForm!: FormGroup;
private selectedSeatsService = inject(SelectedSeatsService);
orderState = signal<OrderState>({ status: 'idle' });
submissionMode = signal<SubmissionMode | null>(null);
submitted = signal(false);
confetti: any; confetti: any;
constructor(private fb: FormBuilder) {} totalPrice = this.selectedSeatsService.totalPrice;
totalSeats = this.selectedSeatsService.totalSeats;
isSubmitting = computed(() => this.orderState().status === 'submitting');
secondPhaseButtonText = computed(() => {
const mode = this.submissionMode();
if (!mode) return 'Loading...';
if (mode === 'reservation') {
return this.totalSeats() > 1 ? 'Sitzplätze reservieren' : 'Sitzplatz reservieren';
}
return 'Weiter zur Zahlung';
});
isReservationSuccess = computed(() =>
this.orderState().status === 'reservation-success'
);
isPurchaseSuccess = computed(() =>
this.orderState().status === 'purchase-success'
);
isReservationError = computed(() =>
this.orderState().status === 'reservation-error'
);
isPurchaseError = computed(() =>
this.orderState().status === 'purchase-error'
);
createdOrder = computed(() => {
const state = this.orderState();
return state.status === 'reservation-success' ? state.order : null;
});
createdTickets = computed(() => {
const state = this.orderState();
return state.status === 'purchase-success' ? state.tickets : [];
});
// Form-Validation
async ngOnInit() { async ngOnInit() {
this.dataForm = this.fb.group({
name: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]],
accept: ['', Validators.requiredTrue],
});
this.paymentForm = this.fb.group({ this.paymentForm = this.fb.group({
cardNumber: ['', [Validators.required, Validators.pattern(/^\d{16}$/)]], cardNumber: ['', [Validators.required, Validators.pattern(/^\d{16}$/)]],
cardName: ['', [Validators.required, Validators.minLength(3)]], cardName: ['', [Validators.required, Validators.minLength(3)]],
expiry: ['', [Validators.required, Validators.pattern(/^(0[1-9]|1[0-2])\/\d{2}$/)]], expiry: ['', [Validators.required, Validators.pattern(/^(0[1-9]|1[0-2])\/\d{2}$/)]],
cvv: ['', [Validators.required, Validators.pattern(/^\d{3,4}$/)]], cvv: ['', [Validators.required, Validators.pattern(/^\d{3,4}$/)]],
}); });
this.dataForm = this.fb.group({
name: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]],
accept: ['', Validators.requiredTrue],
});
this.confetti = (await import('canvas-confetti')).default; this.confetti = (await import('canvas-confetti')).default;
} }
get fData() { return this.dataForm.controls; } get fData() { return this.dataForm.controls; }
get fPayment() { return this.paymentForm.controls; } get fPayment() { return this.paymentForm.controls; }
onSubmit() {
if (this.dataForm.invalid) return;
if (this.paymentForm.invalid) return;
}
onStepChange(event: StepperSelectionEvent) { onStepChange(event: StepperSelectionEvent) {
this.submitted = false; this.submitted.set(false);
this.selectedSeatsService.setSeatSelectable(event.selectedIndex === 0);
if(event.selectedIndex != 0) {
this.selectedSeatsService.setSeatIsSelectableFalse()
} else {
this.selectedSeatsService.setSeatIsSelectableTrue()
} }
reservationClicked() {
this.submissionMode.set('reservation');
}
purchaseClicked() {
this.submissionMode.set('purchase');
} }
nextPhaseButtonClicked(stepper: MatStepper) { nextPhaseButtonClicked(stepper: MatStepper) {
this.submitted = true; this.submitted.set(true);
if (this.dataForm.invalid) return;
if (this.submissionMode === "reservation") { if (this.dataForm.invalid) {
return;
}
if (this.submissionMode() === 'reservation') {
this.makeReservation(); this.makeReservation();
} else if (this.submissionMode === "purchase") { } else if (this.submissionMode() === 'purchase') {
stepper.next(); stepper.next();
} }
} }
totalPrice = computed(() =>
this.selectedSeatsService.getSelectedSeatsList().reduce((sum, seat) => sum + seat.row.category.price, 0)
);
totalSeats = computed(() =>
this.selectedSeatsService.getSelectedSeatsList().length
);
getPriceDisplay(price: number): string {
return `${(price / 100).toFixed(2)}`;
}
isSubmitting: boolean = false;
secondPhaseButtonText: string = "Loading..."
submissionMode!: 'reservation' | 'purchase';
seatsReserved: boolean = false;
seatsPurchased: boolean = false;
successful: boolean = false;
reservationClicked() {
this.submissionMode = "reservation";
if (this.totalSeats() > 1) {
this.secondPhaseButtonText = "Sitzplätze reservieren"
} else {
this.secondPhaseButtonText = "Sitzplatz reservieren"
}
}
purchaseClicked() {
this.submissionMode = "purchase"
this.secondPhaseButtonText = "Weiter zur Zahlung"
}
makeReservation() { makeReservation() {
this.orderState.set({ status: 'submitting' });
this.loadingService.show(); this.loadingService.show();
this.disableInputs() this.disableForms();
const order = this.generateNewOrderObject(this.dataForm.value.email, false); const order = this.generateNewOrderObject(this.dataForm.value.email, false);
const seats = this.selectedSeatsService.getSelectedSeatsList(); const seats = this.selectedSeatsService.selectedSeats();
const performance = this.performance()!; const performance = this.performance()!;
this.sendToBackend(order, seats, performance);
this.seatsReserved = true; this.submitOrder(order, seats, performance, 'reservation');
} }
makePurchase() { makePurchase() {
this.loadingService.show(); if (this.paymentForm.invalid) {
this.disableInputs() return;
const order = this.generateNewOrderObject(this.dataForm.value.email, true);
const seats = this.selectedSeatsService.getSelectedSeatsList();
const performance = this.performance()!;
this.sendToBackend(order, seats, performance);
this.seatsPurchased = true;
} }
createdOrder!: Bestellung; this.orderState.set({ status: 'submitting' });
createdTickets!: Eintrittskarte[]; this.loadingService.show();
this.disableForms();
sendToBackend(order: Bestellung, seats: Sitzplatz[], performance: Vorstellung) { const order = this.generateNewOrderObject(this.dataForm.value.email, true);
const seats = this.selectedSeatsService.selectedSeats();
const performance = this.performance()!;
this.submitOrder(order, seats, performance, 'purchase');
}
submitOrder(order: Bestellung, seats: Sitzplatz[], performance: Vorstellung, mode: SubmissionMode) {
this.httpService.addOrder(order).pipe( this.httpService.addOrder(order).pipe(
tap(createdOrder => { // Order erstellen
this.createdOrder = createdOrder; switchMap(createdOrder => {
const ticketCreations = seats.map(seat => { // Tickets parallel erstellen
const ticketObservables = seats.map(seat => {
const ticket = this.generateNewTicketObject(performance, seat, createdOrder); const ticket = this.generateNewTicketObject(performance, seat, createdOrder);
return this.httpService.addTicket(ticket); return this.httpService.addTicket(ticket);
}); });
Promise.all(ticketCreations.map(obs => obs.toPromise())) // Warten bis alles fertig sind
.then(async createdTickets => { return forkJoin(ticketObservables).pipe(
this.createdTickets = createdTickets.filter( tap(createdTickets => {
(ticket): ticket is Eintrittskarte => ticket !== undefined // Success Handling
); if (mode === 'reservation') {
this.orderState.set({
status: 'reservation-success',
order: createdOrder
});
} else {
this.orderState.set({
status: 'purchase-success',
tickets: createdTickets
});
}
this.successful = true; this.selectedSeatsService.commit();
this.selectedSeatsService.setCommitedTrue();
this.loadingService.hide(); this.loadingService.hide();
this.showConfetti();
})
);
}),
catchError(err => {
// Error handling
console.error('Fehler beim Anlegen der Bestellung/Tickets:', err);
this.loadingService.showError(err);
if (mode === 'reservation') {
this.orderState.set({ status: 'reservation-error', error: err });
} else {
this.orderState.set({ status: 'purchase-error', error: err });
}
return EMPTY;
}),
finalize(() => {
this.enableForms();
}),
takeUntilDestroyed(this.destroyRef)
).subscribe();
}
private showConfetti() {
this.confetti({ this.confetti({
particleCount: 100, particleCount: 100,
angle: 0, angle: 0,
@@ -169,29 +226,8 @@ export class OrderComponent {
spread: 180, spread: 180,
origin: { x: 1.1, y: 0.75 } origin: { x: 1.1, y: 0.75 }
}); });
})
.catch(err => {
this.loadingService.showError(err);
this.successful = false;
console.error('Fehler beim Anlegen der Eintrittskarten', err);
});
}),
catchError(err => {
this.loadingService.showError(err);
this.successful = false;
console.error('Fehler beim Anlegen der Bestellung', err);
return [];
}),
finalize(() => {
this.enableInputs();
})
).subscribe();
} }
private generateCode(length: number = 6): string { private generateCode(length: number = 6): string {
const chars = "ABCDEFGHJKLMNPQRSUVWXYZ23456789"; const chars = "ABCDEFGHJKLMNPQRSUVWXYZ23456789";
let result = ""; let result = "";
@@ -206,7 +242,7 @@ export class OrderComponent {
private generateNewOrderObject(mail: string, isBooked: boolean): Bestellung { private generateNewOrderObject(mail: string, isBooked: boolean): Bestellung {
return{ return{
id: 0, // Wird durch Backend gesetzt id: 0, // Wird durch das Backend gesetzt
mail: mail, mail: mail,
code: this.generateCode(length=6), code: this.generateCode(length=6),
reserved: new Date(), reserved: new Date(),
@@ -217,7 +253,7 @@ export class OrderComponent {
private generateNewTicketObject(show: Vorstellung, seat: Sitzplatz, order: Bestellung): Eintrittskarte { private generateNewTicketObject(show: Vorstellung, seat: Sitzplatz, order: Bestellung): Eintrittskarte {
return { return {
id: 0, // Wird durch Backend gesetzt id: 0, // Wird durch das Backend gesetzt
code: 'T' + this.generateCode(length=7), code: 'T' + this.generateCode(length=7),
show: show, show: show,
seat: seat, seat: seat,
@@ -225,16 +261,17 @@ export class OrderComponent {
}; };
} }
private disableInputs() { private disableForms(): void {
this.dataForm.disable(); this.dataForm.disable();
this.paymentForm.disable(); this.paymentForm.disable();
this.isSubmitting = true;
} }
private enableInputs() { private enableForms(): void {
this.dataForm.enable(); this.dataForm.enable();
this.paymentForm.enable(); this.paymentForm.enable();
this.isSubmitting = false;
} }
getPriceDisplay(price: number): string {
return `${(price / 100).toFixed(2)}`;
}
} }

View File

@@ -3,7 +3,7 @@
warning warning
</mat-icon> </mat-icon>
<h1 class="text-xl font-bold">Kauf fehlgeschlagen!</h1> <h1 class="text-xl font-bold">Kauf fehlgeschlagen!</h1>
<p class="text-center">Leider konnten Ihre Sitzplätze nicht gebucht werden.<br>Dies kann passieren, wenn andere Nutzer gleichzeitig versucht haben, dieselben Sitzplätze zu kaufen.</p> <p class="text-center">Leider konnten Ihre Sitzplätze nicht gekauft werden. Dies kann passieren, wenn andere Nutzer zeitgleich versucht haben, dieselben Sitzplätze zu kaufen.</p>
<button mat-button matButton="filled" class="error-button mt-4 w-80">Erneut versuchen</button> <button mat-button matButton="filled" class="error-button mt-4 w-80">Erneut versuchen</button>
<button routerLink="/schedule" mat-button matButton="outlined" color="accent" class="error-button w-80">Zurück zur Programmauswahl</button> <button routerLink="/schedule" mat-button matButton="outlined" color="accent" class="error-button w-80">Zurück zur Programmauswahl</button>

View File

@@ -3,7 +3,7 @@
warning warning
</mat-icon> </mat-icon>
<h1 class="text-xl font-bold">Reservierung fehlgeschlagen!</h1> <h1 class="text-xl font-bold">Reservierung fehlgeschlagen!</h1>
<p class="text-center">Leider konnten Ihre Sitzplätze nicht reserviert werden.<br>Dies kann passieren, wenn andere Nutzer gleichzeitig versucht haben, dieselben Sitzplätze zu reservieren.</p> <p class="text-center">Leider konnten Ihre Sitzplätze nicht reserviert werden. Dies kann passieren, wenn andere Nutzer gleichzeitig versucht haben, dieselben Sitzplätze zu reservieren.</p>
<button mat-button matButton="filled" class="error-button mt-4 w-80">Erneut versuchen</button> <button mat-button matButton="filled" class="error-button mt-4 w-80">Erneut versuchen</button>
<button routerLink="/schedule" mat-button matButton="outlined" color="accent" class="error-button w-80">Zurück zur Programmauswahl</button> <button routerLink="/schedule" mat-button matButton="outlined" color="accent" class="error-button w-80">Zurück zur Programmauswahl</button>

View File

@@ -14,7 +14,7 @@ export class SeatSelectionComponent {
SelectedSeatsService = inject(SelectedSeatsService); SelectedSeatsService = inject(SelectedSeatsService);
selectedSeatsByCategory = computed(() => selectedSeatsByCategory = computed(() =>
this.SelectedSeatsService.getSelectedSeatsByCategory(this.seatCategory().id).length this.SelectedSeatsService.getSeatsByCategory(this.seatCategory().id).length
); );
totalCategoryPrice = computed(() => totalCategoryPrice = computed(() =>

View File

@@ -20,7 +20,7 @@ export class SeatComponent{
getSeatStateColor(): string { getSeatStateColor(): string {
if (this.isSelectedAndAvaliable()) { if (this.isSelectedAndAvaliable()) {
return this.seatService.getCommited()? '#00c951' : '#6366f1'; return this.seatService.committed()? '#00c951' : '#6366f1';
} }
if (!this.seatService.getSeatIsSelectable()) { if (!this.seatService.getSeatIsSelectable()) {
@@ -29,9 +29,9 @@ export class SeatComponent{
switch (this.state()) { switch (this.state()) {
case TheaterSeatState.RESERVED: case TheaterSeatState.RESERVED:
return this.seatService.getDebug()? '#f7e8c3' : '#c0c0c0'; return this.seatService.debug()? '#f7e8c3' : '#c0c0c0';
case TheaterSeatState.BOOKED: case TheaterSeatState.BOOKED:
return this.seatService.getDebug()? '#ffc9c9' : '#c0c0c0'; return this.seatService.debug()? '#ffc9c9' : '#c0c0c0';
default: default:
case TheaterSeatState.AVAILABLE: case TheaterSeatState.AVAILABLE:
return '#1B1B23'; return '#1B1B23';

View File

@@ -1,69 +1,61 @@
import { Injectable, signal } from '@angular/core'; import { computed, Injectable, signal } from '@angular/core';
import {Sitzplatz} from '@infinimotion/model-frontend'; import {Sitzplatz} from '@infinimotion/model-frontend';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class SelectedSeatsService { export class SelectedSeatsService {
private selectedSeatsSignal = signal<Sitzplatz[]>([]);
private seatIsSelectable: boolean = true;
private commited = false;
private debug = false;
get selectedSeats() { private selectedSeatsSignal = signal<Sitzplatz[]>([]);
return this.selectedSeatsSignal; private seatIsSelectableSignal = signal(true);
} private committedSignal = signal(false);
private debugSignal = signal(false);
readonly selectedSeats = this.selectedSeatsSignal.asReadonly();
readonly seatIsSelectable = this.seatIsSelectableSignal.asReadonly();
readonly committed = this.committedSignal.asReadonly();
readonly debug = this.debugSignal.asReadonly();
readonly totalSeats = computed(() => this.selectedSeats().length);
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]); this.selectedSeatsSignal.update(seats => [...seats, selectedSeat]);
} }
removeSelectedSeat(selectedSeat: Sitzplatz): void { removeSelectedSeat(selectedSeat: Sitzplatz): void {
this.selectedSeatsSignal.update(seats => this.selectedSeatsSignal.update(seats => seats.filter(seat => seat.id !== selectedSeat.id));
seats.filter(seat => seat.id !== selectedSeat.id)
);
} }
getSelectedSeatsList(): Sitzplatz[] { getSeatsByCategory(categoryId: number): Sitzplatz[] {
return this.selectedSeatsSignal(); return this.selectedSeats().filter(seat => seat.row.category.id === categoryId);
} }
getSelectedSeatsByCategory(categoryId: number): Sitzplatz[] { clearSelection(): void {
return this.selectedSeatsSignal().filter(seat => seat.row.category.id === categoryId);
}
clearSelectedSeatsList(): void {
this.selectedSeatsSignal.set([]); this.selectedSeatsSignal.set([]);
this.commited = false; this.committedSignal.set(false);
} }
getSeatIsSelectable(): boolean{ getSeatIsSelectable(): boolean{
return this.seatIsSelectable; return this.seatIsSelectable();
} }
setSeatIsSelectableTrue(): void { setSeatSelectable(selectable: boolean): void {
this.seatIsSelectable = true; this.seatIsSelectableSignal.set(selectable);
this.commited = false; if (selectable) {
this.committedSignal.set(false);
}
} }
setSeatIsSelectableFalse(): void { commit(): void {
this.seatIsSelectable = false; this.committedSignal.set(true);
}
getCommited(): boolean {
return this.commited;
}
setCommitedTrue(): void {
this.commited = true;
}
getDebug(): boolean {
return this.debug;
} }
toggleDebug(): void { toggleDebug(): void {
this.debug = !this.debug; this.debugSignal.update(debug => !debug);
} }
isSeatSelected(seatId: number): boolean {
return this.selectedSeats().some(seat => seat.id === seatId);
}
} }

View File

@@ -26,8 +26,8 @@ export class TheaterOverlayComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.showId = Number(this.route.snapshot.paramMap.get('id')!); this.showId = Number(this.route.snapshot.paramMap.get('id')!);
this.selectedSeatService.clearSelectedSeatsList(); this.selectedSeatService.clearSelection();
this.selectedSeatService.setSeatIsSelectableTrue(); this.selectedSeatService.setSeatSelectable(true);
this.loadPerformanceAndSeats(); this.loadPerformanceAndSeats();
} }