Merge branch 'main' of git.infinimotion.de:infinimotion/frontend
This commit is contained in:
@@ -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(),
|
||||
|
||||
@@ -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} */
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<div class="flex flex-row items-center justify-center gap-8 h-1/1">
|
||||
<div class="max-w-xl m-auto">
|
||||
<div class="flex items-center h-1/1 justify-center space-x-30">
|
||||
<div class="max-w-xl">
|
||||
<section class="felx flex-row">
|
||||
<div class="flex items-center">
|
||||
<h1 class="text-3xl font-bold">
|
||||
Willkommen bei
|
||||
</h1>
|
||||
<div class="bg-gradient-to-r from-indigo-500 to-pink-600 bg-clip-text text-transparent text-3xl font-bold">
|
||||
<div class="bg-linear-to-r from-indigo-500 to-pink-600 bg-clip-text text-transparent text-3xl font-bold">
|
||||
InfiniMotion
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold">! 🎬</h1>
|
||||
@@ -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.
|
||||
<a href="https://infinimotion.de" target="_blank" class="bg-gradient-to-r from-indigo-500 to-pink-600 bg-clip-text text-transparent">
|
||||
<a href="https://infinimotion.de" target="_blank" class="bg-linear-to-r from-indigo-500 to-pink-600 bg-clip-text text-transparent">
|
||||
https://infinimotion.de
|
||||
</a>
|
||||
wird zum Projektende offline genommen.
|
||||
@@ -52,7 +52,7 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="max-w-md mr-60 mt-10">
|
||||
<div class="mt-10">
|
||||
<mat-vertical-stepper [linear]="false" [selectedIndex]="5" class="always-open-stepper">
|
||||
|
||||
<mat-step
|
||||
@@ -81,7 +81,7 @@
|
||||
|
||||
<mat-step
|
||||
[completed]="isCompleted(3)"
|
||||
[editable]="isEditable(3)">
|
||||
[editable]="true">
|
||||
<ng-template matStepLabel>
|
||||
<span>Sprint #3: Vorstellungstickets reservieren und buchen</span>
|
||||
</ng-template>
|
||||
@@ -104,5 +104,5 @@
|
||||
</mat-step>
|
||||
|
||||
</mat-vertical-stepper>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
@if ( icon() ) {
|
||||
<mat-icon style="font-size: 35px; width: 35px; height: 35px; opacity: 50%;">{{ icon() }}</mat-icon>
|
||||
}
|
||||
<p class="text-2xl font-medium pl-2 bg-gradient-to-r from-indigo-500 to-pink-600 bg-clip-text text-transparent">
|
||||
{{ title() }}
|
||||
<p class="text-2xl font-medium pl-2 bg-linear-to-r from-indigo-500 to-pink-600 bg-clip-text text-transparent">
|
||||
{{ label() }}
|
||||
</p>
|
||||
</div>
|
||||
@if ( searchBar() ) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Component, input, output } from '@angular/core';
|
||||
styleUrl: './menu-header.component.css'
|
||||
})
|
||||
export class MenuHeaderComponent {
|
||||
title = input.required<string>();
|
||||
label = input.required<string>();
|
||||
icon = input<string>();
|
||||
|
||||
searchBar = input<boolean>(false);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<app-menu-header title="Film aus IMDb importieren" icon="cloud_download"></app-menu-header>
|
||||
<app-menu-header label="Film aus IMDb importieren" icon="cloud_download"></app-menu-header>
|
||||
|
||||
<div class="w-6/10 m-auto my-20">
|
||||
<form class="movie-search-form w-full" (ngSubmit)="DoSubmit()">
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
<form class="movie-search-form w-88">
|
||||
<div class="flex items-center space-x-4">
|
||||
|
||||
@if (searchControl.value && searchControl.value.length > 0) {
|
||||
<button mat-icon-button #tooltip="matTooltip" matTooltip="Filter löschen" matTooltipPosition="above" class="w-11! h-11! opacity-50" (click)="searchControl.setValue('')">
|
||||
<mat-icon style="font-size: 25px; width: 25px; height: 25px;">filter_alt_off</mat-icon>
|
||||
</button>
|
||||
}
|
||||
|
||||
<form class="movie-search-form w-88">
|
||||
<mat-form-field class="w-full" subscriptSizing="dynamic">
|
||||
<mat-label>Film suchen</mat-label>
|
||||
<input class="w-full" type="text" matInput [formControl]="searchControl" [matAutocomplete]="auto" (click)="searchControl.setValue('')">
|
||||
|
||||
<!-- @if (searchControl.hasError('filmNotFound')) { -->
|
||||
<!-- <mat-error>Film existiert nicht</mat-error> -->
|
||||
<!-- } -->
|
||||
<input class="w-full" type="text" matInput [formControl]="searchControl" [matAutocomplete]="auto">
|
||||
|
||||
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
|
||||
@for (option of filteredOptions | async; track option) {
|
||||
@@ -13,4 +17,6 @@
|
||||
}
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -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 => {
|
||||
// Transaktionssicher Sitzplatzbuchung
|
||||
this.httpService.saveAddOrder({order, tickets}).pipe(
|
||||
tap(createdOrderAndTickets => {
|
||||
// Success Handling
|
||||
if (mode === 'reservation') {
|
||||
this.orderState.set({
|
||||
status: 'reservation-success',
|
||||
order: createdOrder
|
||||
order: createdOrderAndTickets.order
|
||||
});
|
||||
} else {
|
||||
this.orderState.set({
|
||||
status: 'purchase-success',
|
||||
tickets: createdTickets
|
||||
tickets: createdOrderAndTickets.tickets
|
||||
});
|
||||
}
|
||||
|
||||
this.selectedSeatsService.commit();
|
||||
this.loadingService.hide();
|
||||
this.showConfetti();
|
||||
})
|
||||
);
|
||||
}),
|
||||
catchError(err => {
|
||||
// Error handling
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
<app-menu-header title="Programmübersicht" icon="event" [searchBar]="true" (movieSearchResult)="movieSearchResult = $event"></app-menu-header>
|
||||
<app-menu-header label="Programmübersicht" icon="event" [searchBar]="true" (movieSearchResult)="movieSearchResult = $event"></app-menu-header>
|
||||
|
||||
<mat-tab-group mat-stretch-tabs>
|
||||
@for (dateInfo of dates; track dateInfo.date; let i = $index) {
|
||||
<mat-tab [label]="dateInfo.label">
|
||||
<ng-template mat-tab-label>
|
||||
<span [matBadge]="getMovieCount(i)" matBadgeOverlap="false" [matBadgeHidden]="!isSearch() || getMovieCount(i) === 0" [class]="(isSearch() && getMovieCount(i) === 0)? 'text-gray-300' : ''">
|
||||
{{ dateInfo.label }}
|
||||
</span>
|
||||
</ng-template>
|
||||
@if (getMovieCount(i) > 0) {
|
||||
@if (hasSearchResults(i)) {
|
||||
@for (group of dateInfo.performances; track group.movie.id) {
|
||||
@if (group.movie.title.toLowerCase().includes(movieSearchResult.toLowerCase())) {
|
||||
<app-movie-schedule-info [movieGroup]="group"></app-movie-schedule-info>
|
||||
}
|
||||
}
|
||||
} @else {
|
||||
<app-movie-schedule-no-search-result [search]="movieSearchResult" [date]="dates[i].date" ></app-movie-schedule-no-search-result>
|
||||
}
|
||||
@if (isSearch()) {
|
||||
<app-movie-schedule-no-search-result [search]="movieSearchResult" [date]="dates[i].date"></app-movie-schedule-no-search-result>
|
||||
} @else {
|
||||
<app-movie-schedule-empty></app-movie-schedule-empty>
|
||||
}
|
||||
}
|
||||
</mat-tab>
|
||||
}
|
||||
</mat-tab-group>
|
||||
|
||||
@@ -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,7 +67,7 @@ export class ScheduleComponent implements OnInit {
|
||||
).subscribe();
|
||||
}
|
||||
|
||||
private generateDateFilter(bookableDays: number): string[] {
|
||||
private generateDateFilter(bookableDays: number): string[] {
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setDate(startDate.getDate() + bookableDays - 1);
|
||||
@@ -87,10 +79,9 @@ private generateDateFilter(bookableDays: number): string[] {
|
||||
`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() !== '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<app-menu-header title="Vorstellungstickets kaufen" icon="local_activity" [backToSchedule]="true"></app-menu-header>
|
||||
<app-menu-header label="Vorstellungstickets kaufen" icon="local_activity" [backToSchedule]="true"></app-menu-header>
|
||||
|
||||
<div class="flex justify-between h-100">
|
||||
|
||||
|
||||
Reference in New Issue
Block a user