diff --git a/src/app/app-module.ts b/src/app/app-module.ts index c0e7b03..286e276 100644 --- a/src/app/app-module.ts +++ b/src/app/app-module.ts @@ -66,8 +66,6 @@ import { PurchaseSuccessComponent } from './purchase-success/purchase-success.co import { PurchaseFailedComponent } from './purchase-failed/purchase-failed.component'; import { TicketSmallComponent } from './ticket-small/ticket-small.component'; import { TicketListComponent } from './ticket-list/ticket-list.component'; -import { StatisticsComponent } from './statistics/statistics.component'; - @NgModule({ @@ -111,7 +109,6 @@ import { StatisticsComponent } from './statistics/statistics.component'; PurchaseFailedComponent, TicketSmallComponent, TicketListComponent, - StatisticsComponent, ], imports: [ AppRoutingModule, diff --git a/src/app/app-routing-module.ts b/src/app/app-routing-module.ts index 178b014..2c6fe8a 100644 --- a/src/app/app-routing-module.ts +++ b/src/app/app-routing-module.ts @@ -13,7 +13,7 @@ import {StatisticsComponent} from './statistics/statistics.component'; const routes: Routes = [ // Seiten ohne Layout { path: 'landing', component: HomeComponent }, - { path: 'poc-model', component: PocModelComponent }, + { path: 'poc-model', component: PocModelComponent, data: { allowMobile: true } }, // Seiten mit MainLayout { @@ -29,12 +29,6 @@ const routes: Routes = [ data: { roles: ['admin'] }, // Array von erlaubten Rollen. Derzeit gäbe es 'admin' und 'employee' }, { path: 'selection/performance/:id', component: TheaterOverlayComponent}, - { - path: 'admin/statistics', - component: StatisticsComponent, - canActivate: [AuthGuard], - data: { roles: ['admin'] }, // Array von erlaubten Rollen. Derzeit gäbe es 'admin' und 'employee' - }, ], }, diff --git a/src/app/app.html b/src/app/app.html index 67e7bd4..bc230ce 100644 --- a/src/app/app.html +++ b/src/app/app.html @@ -1 +1,2 @@ + diff --git a/src/app/device-detection.service.ts b/src/app/device-detection.service.ts new file mode 100644 index 0000000..c4035ea --- /dev/null +++ b/src/app/device-detection.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class DeviceDetectionService { + private _isMobile: boolean; + + constructor() { + this._isMobile = this.checkIfMobile(); + } + + isMobile(): boolean { + return this._isMobile; + } + + private checkIfMobile(): boolean { + const userAgent = navigator.userAgent.toLowerCase(); + const isMobileUA = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent); + const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; + const isSmallScreen = window.innerWidth < 768; + + return isMobileUA || (isTouchDevice && isSmallScreen); + } + + recheckDevice(): void { + this._isMobile = this.checkIfMobile(); + } +} diff --git a/src/app/movie-performance/movie-performance.component.html b/src/app/movie-performance/movie-performance.component.html index f1811e4..1584f95 100644 --- a/src/app/movie-performance/movie-performance.component.html +++ b/src/app/movie-performance/movie-performance.component.html @@ -1,6 +1,6 @@ -
+

{{ hall() }}

diff --git a/src/app/movie-performance/movie-performance.component.ts b/src/app/movie-performance/movie-performance.component.ts index b6925d0..6f98ff9 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 = `../selection/performance/${this.id()}`; + this.route = `../performance/${this.id()}/checkout`; } startTime = computed(() => diff --git a/src/app/performance-info/performance-info.component.html b/src/app/performance-info/performance-info.component.html index 786c373..ee30310 100644 --- a/src/app/performance-info/performance-info.component.html +++ b/src/app/performance-info/performance-info.component.html @@ -13,7 +13,7 @@

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

{{ movie().title }}

- +
diff --git a/src/app/purchase-failed/purchase-failed.component.html b/src/app/purchase-failed/purchase-failed.component.html index 6cc5888..d841ce0 100644 --- a/src/app/purchase-failed/purchase-failed.component.html +++ b/src/app/purchase-failed/purchase-failed.component.html @@ -6,6 +6,6 @@

Leider konnten Ihre Sitzplätze nicht gekauft werden. Dies kann passieren, wenn andere Nutzer zeitgleich versucht haben, dieselben Sitzplätze zu kaufen.

- + diff --git a/src/app/purchase-success/purchase-success.component.html b/src/app/purchase-success/purchase-success.component.html index 4d40726..c439043 100644 --- a/src/app/purchase-success/purchase-success.component.html +++ b/src/app/purchase-success/purchase-success.component.html @@ -2,10 +2,10 @@

Vielen Dank für Ihren Einkauf!

Ihre Sitzplätze wurden erfolgreich gebucht.

- + - - + + diff --git a/src/app/reservation-failed/reservation-failed.component.html b/src/app/reservation-failed/reservation-failed.component.html index a8913c0..abd1532 100644 --- a/src/app/reservation-failed/reservation-failed.component.html +++ b/src/app/reservation-failed/reservation-failed.component.html @@ -6,6 +6,6 @@

Leider konnten Ihre Sitzplätze nicht reserviert werden. Dies kann passieren, wenn andere Nutzer gleichzeitig versucht haben, dieselben Sitzplätze zu reservieren.

- + diff --git a/src/app/reservation-success/reservation-success.component.html b/src/app/reservation-success/reservation-success.component.html index 0a463a0..8d28e59 100644 --- a/src/app/reservation-success/reservation-success.component.html +++ b/src/app/reservation-success/reservation-success.component.html @@ -1,17 +1,15 @@

Reservierung erfolgreich!

-

Ihre Sitzplätze wurden erfolgreich reserviert. Bitte nennen sie den folgenden Code an der Kasse, um Ihre Reservierung in eine Buchung umzuwandeln.

{{ order().code }}
- - -
+ + +
Reservierung stornieren
- diff --git a/src/app/theater-layout/theater-layout.component.html b/src/app/theater-layout/theater-layout.component.html index 5637093..0563b21 100644 --- a/src/app/theater-layout/theater-layout.component.html +++ b/src/app/theater-layout/theater-layout.component.html @@ -3,12 +3,12 @@ Leinwand

-
+
@for (row of seatsPerRow(); track $index) {
-
+
@if ($index % 4 === 0) { speaker @@ -25,7 +25,7 @@ -
+
@if ($index % 4 === 0) { speaker diff --git a/src/app/theater-overlay/theater-overlay.component.html b/src/app/theater-overlay/theater-overlay.component.html index 7a88cd2..78bd5b8 100644 --- a/src/app/theater-overlay/theater-overlay.component.html +++ b/src/app/theater-overlay/theater-overlay.component.html @@ -1,8 +1,8 @@ -
+
-
+
@if (!performance && (loading.loading$ | async)){
@@ -18,6 +18,6 @@
- +
diff --git a/src/app/theater-overlay/theater-overlay.component.ts b/src/app/theater-overlay/theater-overlay.component.ts index 68621eb..f588044 100644 --- a/src/app/theater-overlay/theater-overlay.component.ts +++ b/src/app/theater-overlay/theater-overlay.component.ts @@ -25,6 +25,7 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy { readonly loading = inject(LoadingService); showId!: number; + orderId?: string; seatsPerRow = signal<{ seat: Sitzplatz | null, state: TheaterSeatState | null }[][]>([]); performance: Vorstellung | undefined; seatCategories: Sitzkategorie[] = []; @@ -33,7 +34,8 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy { private isInitialLoad = signal(true); ngOnInit() { - this.showId = Number(this.route.snapshot.paramMap.get('id')!); + this.showId = Number(this.route.snapshot.paramMap.get('performanceId')!); + this.orderId = this.route.snapshot.queryParams['paramName']; this.selectedSeatService.clearSelection(); this.selectedSeatService.setSeatSelectable(true); diff --git a/src/app/zoom-detection.service.ts b/src/app/zoom-detection.service.ts new file mode 100644 index 0000000..d9e3311 --- /dev/null +++ b/src/app/zoom-detection.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, fromEvent } from 'rxjs'; +import { debounceTime } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class ZoomDetectionService { + private zoomLevel$ = new BehaviorSubject(this.getZoomLevel()); + + constructor() { + // Zoom-Änderungen überwachen + fromEvent(window, 'resize') + .pipe(debounceTime(200)) + .subscribe(() => { + this.zoomLevel$.next(this.getZoomLevel()); + }); + } + + getZoomLevel(): number { + const devicePixelRatio = window.devicePixelRatio || 1; + return devicePixelRatio; + } + + getZoomLevel$() { + return this.zoomLevel$.asObservable(); + } + + isZoomOutOfRange(minZoom: number = 0.95, maxZoom: number = 1.05): boolean { + const currentZoom = this.getZoomLevel(); + return currentZoom < minZoom || currentZoom > maxZoom; + } +} diff --git a/src/app/zoom-warning/zoom-warning.component.css b/src/app/zoom-warning/zoom-warning.component.css new file mode 100644 index 0000000..21fcb9c --- /dev/null +++ b/src/app/zoom-warning/zoom-warning.component.css @@ -0,0 +1,14 @@ +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(20px) scale(0.9); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.zoom-info-box { + animation: slideIn 0.3s ease-out; +} diff --git a/src/app/zoom-warning/zoom-warning.component.html b/src/app/zoom-warning/zoom-warning.component.html new file mode 100644 index 0000000..ea79125 --- /dev/null +++ b/src/app/zoom-warning/zoom-warning.component.html @@ -0,0 +1,81 @@ +@if (isOutOfRange && !isDismissed && !isMobile) { +
+ + +
+ + screenshot_monitor + + + warning + +
+ +

Browser-Zoom nicht optimal!

+

Ihr Browser-Zoom ist auf {{ currentZoomPercentage }}% eingestellt.
Für die beste Darstellung empfehlen wir 100%.

+ +
+
+

Browser-Zoom zurücksetzen:

+
+

+ Strg + / + + + + 0 + (Windows, Linux / Mac) +

+
+
+ +
+

Windows-Skalierung prüfen:

+
    +
  1. Öffnen Sie die Windows-Einstellungen
  2. +
  3. Navigieren Sie zu SystemBildschirm
  4. +
  5. Setzen Sie unter "Skalierung" den Wert auf 100%
  6. +
+
+
+
+} + +@if (showMobileWarning) { +
+
+ +

InfiniMotion

+
+
+
+
+
+ + screenshot_monitor + + + warning + +
+ +

Nur am PC verfügbar

+ +

+ Diese Anwendung ist für die Nutzung am Desktop-PC optimiert und kann auf mobilen Geräten nicht verwendet werden. +

+ +
+

Deine derzeitige URL:

+

+ {{ currentUrl }} +

+
+
+
+} diff --git a/src/app/zoom-warning/zoom-warning.component.ts b/src/app/zoom-warning/zoom-warning.component.ts new file mode 100644 index 0000000..8802167 --- /dev/null +++ b/src/app/zoom-warning/zoom-warning.component.ts @@ -0,0 +1,100 @@ +import { DeviceDetectionService } from './../device-detection.service'; +import { Component, HostListener, inject, OnDestroy, OnInit } from '@angular/core'; +import { filter, Subject, takeUntil } from 'rxjs'; +import { ZoomDetectionService } from '../zoom-detection.service'; +import { NavigationEnd, Router } from '@angular/router'; + +@Component({ + selector: 'app-zoom-warning', + standalone: false, + templateUrl: './zoom-warning.component.html', + styleUrl: './zoom-warning.component.css', +}) +export class ZoomWarningComponent implements OnInit, OnDestroy { + currentZoomPercentage = 100; + isOutOfRange = false; + isDismissed = false; + isMobile = false; + showMobileWarning = false; + currentUrl = ''; + + private destroy$ = new Subject(); + private currentZoomLevel = 1; + private lastZoomLevel = 0; + + zoomDetectionService = inject(ZoomDetectionService); + deviceDetectionService = inject(DeviceDetectionService) + + constructor(private router: Router) { + this.isMobile = this.deviceDetectionService.isMobile(); + this.currentUrl = window.location.href; + this.checkIfShouldShowMobileWarning(); + } + + ngOnInit() { + this.router.events + .pipe( + filter(event => event instanceof NavigationEnd), + takeUntil(this.destroy$) + ) + .subscribe(() => { + this.checkIfShouldShowMobileWarning(); + }); + + if (!this.isMobile) { + this.updateZoomInfo(); + + this.zoomDetectionService.getZoomLevel$() + .pipe(takeUntil(this.destroy$)) + .subscribe((newZoomLevel) => { + if (Math.abs(newZoomLevel - this.lastZoomLevel) > 0.01) { + this.lastZoomLevel = newZoomLevel; + this.isDismissed = false; + this.updateZoomInfo(); + } + }); + } + } + + private checkIfShouldShowMobileWarning() { + if (!this.isMobile) { + this.showMobileWarning = false; + return; + } + + const currentRoute = this.router.routerState.root; + const allowMobile = this.getRouteData(currentRoute, 'allowMobile'); + + this.showMobileWarning = !allowMobile; + } + + private getRouteData(route: any, key: string): any { + while (route) { + if (route.snapshot?.data?.[key] !== undefined) { + return route.snapshot.data[key]; + } + route = route.firstChild; + } + return false; + } + + private updateZoomInfo() { + this.currentZoomLevel = this.zoomDetectionService.getZoomLevel(); + this.currentZoomPercentage = Math.round(this.currentZoomLevel * 100); + this.isOutOfRange = this.zoomDetectionService.isZoomOutOfRange(); + } + + getCompensationTransform(): string { + const scale = 1 / this.currentZoomLevel; + return `scale(${scale})`; + } + + dismissWarning() { + this.isDismissed = true; + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } +}