diff --git a/src/app/app-module.ts b/src/app/app-module.ts index 58a44ac..a2cab07 100644 --- a/src/app/app-module.ts +++ b/src/app/app-module.ts @@ -23,6 +23,8 @@ import { MatDividerModule } from '@angular/material/divider'; import { MatDialogClose, MatDialogTitle, MatDialogContent, MatDialogActions } from "@angular/material/dialog"; import { MatStepperModule } from '@angular/material/stepper'; import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatBadgeModule } from '@angular/material/badge'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { HeaderComponent } from './header/header.component'; import { HomeComponent } from './home/home.component'; @@ -135,6 +137,8 @@ import { StatisticsComponent } from './statistics/statistics.component'; NgxMaskDirective, NgxMaskPipe, QRCodeComponent, + MatBadgeModule, + MatTooltipModule, ], providers: [ provideBrowserGlobalErrorListeners(), diff --git a/src/app/http.service.ts b/src/app/http.service.ts index 5aefa63..3a563ec 100644 --- a/src/app/http.service.ts +++ b/src/app/http.service.ts @@ -53,6 +53,12 @@ export class HttpService { } + /* 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); + } + + /* Eintrittskarte APIs */ /* GET /api/eintrittskarte/{id} */ diff --git a/src/app/main/main.component.html b/src/app/main/main.component.html index 9979e51..20d8c07 100644 --- a/src/app/main/main.component.html +++ b/src/app/main/main.component.html @@ -1,11 +1,11 @@ -
-
+
+

Willkommen bei

-
+
 InfiniMotion

! 🎬

@@ -41,7 +41,7 @@ Wir haben uns bei Gestaltung und Stil bewusst an bestehenden Kinowebsites orientiert. Dabei handelt es sich um eine rein stilistische Anlehnung; diese Seite verfolgt keinerlei kommerzielle Zwecke und dient ausschließlich universitären Zwecken. Marken, Designs oder Funktionalitäten, die bekannten Anbietern ähneln, sind nicht als Kopie zum Wettbewerb gedacht, sondern als pragmatische Inspirationsquelle im Rahmen der Praxisarbeit. - + https://infinimotion.de wird zum Projektende offline genommen. @@ -52,57 +52,57 @@
-
- +
+ - - - Sprint #0:    Planung, Installation und Vorbereitung - - + + + Sprint #0:    Planung, Installation und Vorbereitung + + - - - Sprint #1:    Programmübersicht - - + + + Sprint #1:    Programmübersicht + + - - - Sprint #2:    Kinosäle anzeigen - - + + + Sprint #2:    Kinosäle anzeigen + + - - - Sprint #3:    Vorstellungstickets reservieren und buchen - - + + + Sprint #3:    Vorstellungstickets reservieren und buchen + + - - - Sprint #4:    Statistiken auswerten und anzeigen - - + + + Sprint #4:    Statistiken auswerten und anzeigen + + - - - Sprint #5:    Aufbereitung und Optimierung - - - - + + + Sprint #5:    Aufbereitung und Optimierung + + + +
diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index 706cf1a..8bfe389 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -7,7 +7,7 @@ import { Component } from '@angular/core'; styleUrl: './main.component.css' }) export class MainComponent { - currentSprint = 3; + currentSprint = 4; isCompleted(index: number): boolean { return index <= this.currentSprint; diff --git a/src/app/menu-header/menu-header.component.html b/src/app/menu-header/menu-header.component.html index beb2792..a571725 100644 --- a/src/app/menu-header/menu-header.component.html +++ b/src/app/menu-header/menu-header.component.html @@ -3,8 +3,8 @@ @if ( icon() ) { {{ icon() }} } -

- {{ title() }} +

+ {{ label() }}

@if ( searchBar() ) { diff --git a/src/app/menu-header/menu-header.component.ts b/src/app/menu-header/menu-header.component.ts index 4dd4ec2..ea79579 100644 --- a/src/app/menu-header/menu-header.component.ts +++ b/src/app/menu-header/menu-header.component.ts @@ -7,7 +7,7 @@ import { Component, input, output } from '@angular/core'; styleUrl: './menu-header.component.css' }) export class MenuHeaderComponent { - title = input.required(); + label = input.required(); icon = input(); searchBar = input(false); diff --git a/src/app/movie-importer/movie-importer.component.html b/src/app/movie-importer/movie-importer.component.html index 42b2ea2..6f614b8 100644 --- a/src/app/movie-importer/movie-importer.component.html +++ b/src/app/movie-importer/movie-importer.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/app/movie-search/movie-search.component.html b/src/app/movie-search/movie-search.component.html index add7471..7b0f9b6 100644 --- a/src/app/movie-search/movie-search.component.html +++ b/src/app/movie-search/movie-search.component.html @@ -1,16 +1,22 @@ - - - Film suchen - +
- - - + @if (searchControl.value && searchControl.value.length > 0) { + + } - - @for (option of filteredOptions | async; track option) { - {{option}} - } - - - +
+ + Film suchen + + + + @for (option of filteredOptions | async; track option) { + {{option}} + } + + +
+ +
diff --git a/src/app/order/order.component.ts b/src/app/order/order.component.ts index 8b13379..84aa2d0 100644 --- a/src/app/order/order.component.ts +++ b/src/app/order/order.component.ts @@ -161,37 +161,31 @@ export class OrderComponent { submitOrder(order: Bestellung, seats: Sitzplatz[], performance: Vorstellung, mode: SubmissionMode) { - this.httpService.addOrder(order).pipe( - // Order erstellen - switchMap(createdOrder => { - // Tickets parallel erstellen - const ticketObservables = seats.map(seat => { - const ticket = this.generateNewTicketObject(performance, seat, createdOrder); - return this.httpService.addTicket(ticket); - }); + // Tickets anlegen + const tickets = seats.map(seat => { + return this.generateNewTicketObject(performance, seat, order);; + }); - // Warten bis alles fertig sind - return forkJoin(ticketObservables).pipe( - tap(createdTickets => { - // Success Handling - if (mode === 'reservation') { - this.orderState.set({ - status: 'reservation-success', - order: createdOrder - }); - } else { - this.orderState.set({ - status: 'purchase-success', - tickets: createdTickets - }); - } + // Transaktionssicher Sitzplatzbuchung + this.httpService.saveAddOrder({order, tickets}).pipe( + tap(createdOrderAndTickets => { + // Success Handling + if (mode === 'reservation') { + this.orderState.set({ + status: 'reservation-success', + order: createdOrderAndTickets.order + }); + } else { + this.orderState.set({ + status: 'purchase-success', + tickets: createdOrderAndTickets.tickets + }); + } - this.selectedSeatsService.commit(); - this.loadingService.hide(); - this.showConfetti(); - }) - ); + this.selectedSeatsService.commit(); + this.loadingService.hide(); + this.showConfetti(); }), catchError(err => { // Error handling diff --git a/src/app/schedule/schedule.component.css b/src/app/schedule/schedule.component.css index e80c311..239f2e2 100644 --- a/src/app/schedule/schedule.component.css +++ b/src/app/schedule/schedule.component.css @@ -5,3 +5,7 @@ ::ng-deep .mat-mdc-tab .mdc-tab-indicator__content--underline { border-color: #6366f1 !important; /* indigo-500 */ } + +.mat-badge-content { + background: #dd2979; +} diff --git a/src/app/schedule/schedule.component.html b/src/app/schedule/schedule.component.html index 902dab4..84b6683 100644 --- a/src/app/schedule/schedule.component.html +++ b/src/app/schedule/schedule.component.html @@ -1,20 +1,25 @@ - + @for (dateInfo of dates; track dateInfo.date; let i = $index) { + + + {{ dateInfo.label }} + + @if (getMovieCount(i) > 0) { - @if (hasSearchResults(i)) { - @for (group of dateInfo.performances; track group.movie.id) { - @if (group.movie.title.toLowerCase().includes(movieSearchResult.toLowerCase())) { - - } + @for (group of dateInfo.performances; track group.movie.id) { + @if (group.movie.title.toLowerCase().includes(movieSearchResult.toLowerCase())) { + } - } @else { - } } @else { - + @if (isSearch()) { + + } @else { + + } } } diff --git a/src/app/schedule/schedule.component.ts b/src/app/schedule/schedule.component.ts index 5d88ada..cd58a67 100644 --- a/src/app/schedule/schedule.component.ts +++ b/src/app/schedule/schedule.component.ts @@ -31,15 +31,7 @@ export class ScheduleComponent implements OnInit { this.loadPerformances(this.bookableDays); } - hasSearchResults(dateIndex: number): boolean { - if (!this.movieSearchResult) return true; - - return this.dates[dateIndex].performances.some(group => - group.movie.title.toLowerCase().includes(this.movieSearchResult.toLowerCase()) - ); - } - - generateDates(bookableDays: number) { + private generateDates(bookableDays: number) { const today = new Date(); for (let i = 0; i < bookableDays; i++) { const date = new Date(today); @@ -58,7 +50,7 @@ export class ScheduleComponent implements OnInit { } } - loadPerformances(bookableDays: number) { + private loadPerformances(bookableDays: number) { this.loading.show(); const filter = this.generateDateFilter(bookableDays); this.http.getPerformacesByFilter(filter).pipe( @@ -75,22 +67,21 @@ export class ScheduleComponent implements OnInit { ).subscribe(); } -private generateDateFilter(bookableDays: number): string[] { - const startDate = new Date(); - const endDate = new Date(); - endDate.setDate(startDate.getDate() + bookableDays - 1); + private generateDateFilter(bookableDays: number): string[] { + const startDate = new Date(); + const endDate = new Date(); + endDate.setDate(startDate.getDate() + bookableDays - 1); - const startStr = startDate.toISOString().split('T')[0] + 'T00:00:00'; - const endStr = endDate.toISOString().split('T')[0] + 'T23:59:59'; + const startStr = startDate.toISOString().split('T')[0] + 'T00:00:00'; + const endStr = endDate.toISOString().split('T')[0] + 'T23:59:59'; - return [ - `ge;start;date;${startStr}`, - `le;start;date;${endStr}`, - ]; -} + return [ + `ge;start;date;${startStr}`, + `le;start;date;${endStr}`, + ]; + } - - assignPerformancesToDates() { + private assignPerformancesToDates() { // Gruppieren nach Datum const groupedByDate: { [key: string]: Vorstellung[] } = {}; @@ -133,6 +124,22 @@ private generateDateFilter(bookableDays: number): string[] { } getMovieCount(index: number): number { - return this.dates[index].performances.length; + if (!this.dates[index]?.performances) { + return 0; + } + + const performances = this.dates[index].performances; + + if (!this.isSearch()) { + return performances.length; + } + + return performances.filter(group => + group.movie.title.toLowerCase().includes(this.movieSearchResult.toLowerCase()) + ).length; + } + + isSearch(): boolean { + return !!this.movieSearchResult && this.movieSearchResult.trim() !== ''; } } diff --git a/src/app/theater-overlay/theater-overlay.component.html b/src/app/theater-overlay/theater-overlay.component.html index f2d7a36..7a88cd2 100644 --- a/src/app/theater-overlay/theater-overlay.component.html +++ b/src/app/theater-overlay/theater-overlay.component.html @@ -1,4 +1,4 @@ - +