Add performance scheduling feature for employees
Introduces a new PerformanceSchedulingComponent with UI and logic for creating single performances and recurring performance plans. Updates routing, navigation, and service APIs to support scheduling by employees and admins. Also includes minor refactoring and integration of MatDatepickerModule.
This commit is contained in:
@@ -29,6 +29,7 @@ import { MatPaginatorModule } from '@angular/material/paginator';
|
|||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { MatSortModule } from '@angular/material/sort';
|
import { MatSortModule } from '@angular/material/sort';
|
||||||
|
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||||
|
|
||||||
import { HeaderComponent } from './header/header.component';
|
import { HeaderComponent } from './header/header.component';
|
||||||
import { HomeComponent } from './home/home.component';
|
import { HomeComponent } from './home/home.component';
|
||||||
@@ -81,6 +82,7 @@ import { PdfTicketComponent } from './pdf-ticket/pdf-ticket.component';
|
|||||||
import { TestComponent } from './test/test.component';
|
import { TestComponent } from './test/test.component';
|
||||||
import { TicketValidationComponent } from './ticket-validation/ticket-validation.component';
|
import { TicketValidationComponent } from './ticket-validation/ticket-validation.component';
|
||||||
import { TicketValidationResultComponent } from './ticket-validation-result/ticket-validation-result.component';
|
import { TicketValidationResultComponent } from './ticket-validation-result/ticket-validation-result.component';
|
||||||
|
import { PerformanceSchedulingComponent } from './performance-scheduling/performance-scheduling.component';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -136,6 +138,7 @@ import { TicketValidationResultComponent } from './ticket-validation-result/tick
|
|||||||
TestComponent,
|
TestComponent,
|
||||||
TicketValidationComponent,
|
TicketValidationComponent,
|
||||||
TicketValidationResultComponent,
|
TicketValidationResultComponent,
|
||||||
|
PerformanceSchedulingComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
@@ -171,6 +174,7 @@ import { TicketValidationResultComponent } from './ticket-validation-result/tick
|
|||||||
MatSelectModule,
|
MatSelectModule,
|
||||||
MatSortModule,
|
MatSortModule,
|
||||||
PdfTicketComponent,
|
PdfTicketComponent,
|
||||||
|
MatDatepickerModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
provideBrowserGlobalErrorListeners(),
|
provideBrowserGlobalErrorListeners(),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { PerformanceSchedulingComponent } from './performance-scheduling/performance-scheduling.component';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { PocModelComponent } from './poc-model-component/poc-model-component';
|
import { PocModelComponent } from './poc-model-component/poc-model-component';
|
||||||
@@ -68,6 +69,12 @@ const routes: Routes = [
|
|||||||
canActivate: [AuthGuard],
|
canActivate: [AuthGuard],
|
||||||
data: { roles: ['employee'] },
|
data: { roles: ['employee'] },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'employee/scheduling',
|
||||||
|
component: PerformanceSchedulingComponent,
|
||||||
|
canActivate: [AuthGuard],
|
||||||
|
data: { roles: ['employee', 'admin'] },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
StatisticsVorstellung,
|
StatisticsVorstellung,
|
||||||
Sitzkategorie,
|
Sitzkategorie,
|
||||||
Sitzreihe,
|
Sitzreihe,
|
||||||
|
Plan,
|
||||||
} 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';
|
||||||
@@ -81,6 +82,18 @@ export class HttpService {
|
|||||||
return this.http.post<Vorstellung[]>(`${this.baseUrl}vorstellung/filter`, filter);
|
return this.http.post<Vorstellung[]>(`${this.baseUrl}vorstellung/filter`, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* POST /api/vorstellung */
|
||||||
|
createPerformance(performance: Vorstellung): Observable<Vorstellung> {
|
||||||
|
return this.http.post<Vorstellung>(`${this.baseUrl}vorstellung`, performance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plan APIs */
|
||||||
|
|
||||||
|
/* POST /api/plan */
|
||||||
|
createPerformanceSeries(series: Plan): Observable<Plan> {
|
||||||
|
return this.http.post<Plan>(`${this.baseUrl}plan`, series);
|
||||||
|
}
|
||||||
|
|
||||||
/* Film APIs */
|
/* Film APIs */
|
||||||
|
|
||||||
/* GET /api/film */
|
/* GET /api/film */
|
||||||
@@ -108,7 +121,12 @@ export class HttpService {
|
|||||||
|
|
||||||
/* Sitzplatz APIs */
|
/* Sitzplatz APIs */
|
||||||
|
|
||||||
/* POST /api/seat/filter */
|
/* GET /api/sitzplatz */
|
||||||
|
getAllSeats(): Observable<Sitzplatz[]> {
|
||||||
|
return this.http.get<Sitzplatz[]>(`${this.baseUrl}sitzplatz`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* POST /api/sitzplatz/filter */
|
||||||
getSeatsByHallId(hall: number): Observable<Sitzplatz[]> {
|
getSeatsByHallId(hall: number): Observable<Sitzplatz[]> {
|
||||||
return this.http.post<Sitzplatz[]>(`${this.baseUrl}sitzplatz/filter`, [
|
return this.http.post<Sitzplatz[]>(`${this.baseUrl}sitzplatz/filter`, [
|
||||||
`eq;row.hall.id;int;${hall}`,
|
`eq;row.hall.id;int;${hall}`,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export class NavbarComponent {
|
|||||||
{ label: 'Preise', path: '/prices', auth: null },
|
{ label: 'Preise', path: '/prices', auth: null },
|
||||||
{ label: 'Bezahlen', path: '/checkout/order', auth: null },
|
{ label: 'Bezahlen', path: '/checkout/order', auth: null },
|
||||||
{ label: 'Einlasskontrolle', path: '/employee/validation/ticket', auth: ['employee'] },
|
{ label: 'Einlasskontrolle', path: '/employee/validation/ticket', auth: ['employee'] },
|
||||||
|
{ label: 'Filmplanung', path: '/employee/scheduling', auth: ['employee', 'admin'] },
|
||||||
{ label: 'Statistiken', path: '/admin/statistics', auth: ['admin'] },
|
{ label: 'Statistiken', path: '/admin/statistics', auth: ['admin'] },
|
||||||
{ label: 'Saal-Designer', path: '/admin/designer', auth: ['admin'] },
|
{ label: 'Saal-Designer', path: '/admin/designer', auth: ['admin'] },
|
||||||
{ label: 'Film-Importer', path: '/admin/movie-importer', auth: ['admin']},
|
{ label: 'Film-Importer', path: '/admin/movie-importer', auth: ['admin']},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
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, DestroyRef, inject, input, output, signal } from '@angular/core';
|
import { Component, computed, DestroyRef, inject, input, OnInit, output, 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';
|
||||||
@@ -30,7 +30,7 @@ type SubmissionMode = 'reservation' | 'purchase';
|
|||||||
templateUrl: './order.component.html',
|
templateUrl: './order.component.html',
|
||||||
styleUrl: './order.component.css'
|
styleUrl: './order.component.css'
|
||||||
})
|
})
|
||||||
export class OrderComponent {
|
export class OrderComponent implements OnInit {
|
||||||
private fb = inject(FormBuilder);
|
private fb = inject(FormBuilder);
|
||||||
private httpService = inject(HttpService);
|
private httpService = inject(HttpService);
|
||||||
private destroyRef = inject(DestroyRef);
|
private destroyRef = inject(DestroyRef);
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
.gradient-text {
|
||||||
|
background: linear-gradient(to right, #6366f1, #db2777);
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
<app-menu-header label="Filmplanung" icon="edit_calendar"></app-menu-header>
|
||||||
|
|
||||||
|
<div class="flex gap-40 m-auto justify-between p-20">
|
||||||
|
|
||||||
|
<div class="w-130 ml-auto">
|
||||||
|
<h2 class="text-2xl font-bold gradient-text w-fit">
|
||||||
|
Einzelaufführung
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<form [formGroup]="performanceForm" #formDirectivePerformance="ngForm">
|
||||||
|
<ng-template matStepLabel>Einzelaufführung</ng-template>
|
||||||
|
|
||||||
|
<!-- Film -->
|
||||||
|
<mat-form-field class="w-full mt-8">
|
||||||
|
<mat-label>Film</mat-label>
|
||||||
|
<input matInput formControlName="movie" [matAutocomplete]="autoPerformance"/>
|
||||||
|
<mat-autocomplete #autoPerformance="matAutocomplete" autoActiveFirstOption [displayWith]="displayMovie">
|
||||||
|
@for (movie of filteredMoviesPerformance | async; track movie) {
|
||||||
|
<mat-option [value]="movie">
|
||||||
|
{{ movie.title }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-autocomplete>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Kinosaal -->
|
||||||
|
<mat-form-field class="w-full mt-2">
|
||||||
|
<mat-label>Kinosaal</mat-label>
|
||||||
|
<mat-select formControlName="hall">
|
||||||
|
<mat-select-trigger>
|
||||||
|
{{ hallName(performanceForm) }}
|
||||||
|
</mat-select-trigger>
|
||||||
|
@for (hall of halls; track hall) {
|
||||||
|
<mat-option [value]="hall">
|
||||||
|
<div class="flex justify-between w-114">
|
||||||
|
<p>{{ hall.name }}</p>
|
||||||
|
<p class="text-gray-500">
|
||||||
|
{{ hallSeatMap.get(hall.id)?.length }}
|
||||||
|
{{ hallSeatMap.get(hall.id)?.length === 1 ? 'Sitzplatz' : 'Sitzplätze' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<!-- Start -->
|
||||||
|
<div class="flex gap-4 mt-2">
|
||||||
|
<!-- Datum -->
|
||||||
|
<mat-form-field class="flex-1">
|
||||||
|
<mat-label>Datum</mat-label>
|
||||||
|
<input matInput
|
||||||
|
[matDatepicker]="picker"
|
||||||
|
[min]="minDate"
|
||||||
|
formControlName="date">
|
||||||
|
<mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #picker></mat-datepicker>
|
||||||
|
@if (performanceForm.get('date')?.hasError('required')) {
|
||||||
|
<mat-error>Bitte eine gültiges Datum wählen</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<!-- Uhrzeit -->
|
||||||
|
<mat-form-field class="flex-1">
|
||||||
|
<mat-label>Uhrzeit</mat-label>
|
||||||
|
<input matInput
|
||||||
|
type="time"
|
||||||
|
formControlName="time"
|
||||||
|
placeholder="HH:MM">
|
||||||
|
<mat-icon matSuffix>schedule</mat-icon>
|
||||||
|
@if (performanceForm.get('time')?.hasError('required')) {
|
||||||
|
<mat-error>Bitte eine gültige Uhrzeit wählen</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (hasDateTimeError(performanceForm)) {
|
||||||
|
<mat-error class="text-red-600 mt-2">
|
||||||
|
Datum und Uhrzeit müssen in der Zukunft liegen
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Buttons -->
|
||||||
|
<div class="flex space-x-5 mt-10">
|
||||||
|
<button type="submit" mat-button matButton="filled" class="w-full" [disabled]="loading.loading$ | async" (click)="onPerformanceSubmit()">Vorstellung anlegen</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-130 mr-auto">
|
||||||
|
<h2 class="text-2xl font-bold gradient-text w-fit">
|
||||||
|
Vorstellungsplan
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<form [formGroup]="seriesForm" #formDirectiveSeries="ngForm">
|
||||||
|
<ng-template matStepLabel>Vorstellungsplan</ng-template>
|
||||||
|
|
||||||
|
<!-- Film -->
|
||||||
|
<mat-form-field class="w-full mt-8">
|
||||||
|
<mat-label>Film</mat-label>
|
||||||
|
<input matInput formControlName="movie" [matAutocomplete]="autoPlan"/>
|
||||||
|
<mat-autocomplete #autoPlan="matAutocomplete" autoActiveFirstOption [displayWith]="displayMovie">
|
||||||
|
@for (movie of filteredMoviesPlan | async; track movie) {
|
||||||
|
<mat-option [value]="movie">
|
||||||
|
{{ movie.title }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-autocomplete>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<!-- Kinosaal -->
|
||||||
|
<mat-form-field class="w-full mt-2">
|
||||||
|
<mat-label>Kinosaal</mat-label>
|
||||||
|
<mat-select formControlName="hall">
|
||||||
|
<mat-select-trigger>
|
||||||
|
{{ hallName(seriesForm) }}
|
||||||
|
</mat-select-trigger>
|
||||||
|
@for (hall of halls; track hall) {
|
||||||
|
<mat-option [value]="hall">
|
||||||
|
<div class="flex justify-between w-114">
|
||||||
|
<p>{{ hall.name }}</p>
|
||||||
|
<p class="text-gray-500">
|
||||||
|
{{ hallSeatMap.get(hall.id)?.length }}
|
||||||
|
{{ hallSeatMap.get(hall.id)?.length === 1 ? 'Sitzplatz' : 'Sitzplätze' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<!-- Wochentag -->
|
||||||
|
<mat-form-field class="w-full mt-2">
|
||||||
|
<mat-label>Wochentag</mat-label>
|
||||||
|
<mat-select formControlName="weekday">
|
||||||
|
@for (day of weekdays; track day) {
|
||||||
|
<mat-option [value]="day.value">
|
||||||
|
{{ day.label }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<!-- Zeiten -->
|
||||||
|
<div class="flex gap-4 mt-2">
|
||||||
|
<!-- Start -->
|
||||||
|
<mat-form-field class="flex-1">
|
||||||
|
<mat-label>Start-Datum</mat-label>
|
||||||
|
<input matInput
|
||||||
|
[matDatepicker]="picker2"
|
||||||
|
[matDatepickerFilter]="weekdayFilter"
|
||||||
|
[min]="minDate"
|
||||||
|
formControlName="first">
|
||||||
|
|
||||||
|
<mat-datepicker-toggle matIconSuffix [for]="picker2"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #picker2></mat-datepicker>
|
||||||
|
|
||||||
|
@if (performanceForm.get('date')?.hasError('required')) {
|
||||||
|
<mat-error>Bitte eine gültiges Datum wählen</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<!-- Time -->
|
||||||
|
<mat-form-field class="flex-1">
|
||||||
|
<mat-label>Uhrzeit</mat-label>
|
||||||
|
<input matInput
|
||||||
|
type="time"
|
||||||
|
formControlName="time"
|
||||||
|
placeholder="HH:MM">
|
||||||
|
<mat-icon matSuffix>schedule</mat-icon>
|
||||||
|
@if (performanceForm.get('time')?.hasError('required')) {
|
||||||
|
<mat-error>Bitte eine gültige Uhrzeit wählen</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (hasDateTimeError(seriesForm)) {
|
||||||
|
<mat-error class="text-red-600 mt-2">
|
||||||
|
Datum und Uhrzeit müssen in der Zukunft liegen
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Buttons -->
|
||||||
|
<div class="flex space-x-5 mt-10">
|
||||||
|
<button type="submit" mat-button matButton="filled" class="w-full" [disabled]="loading.loading$ | async" (click)="onPlanSubmit()">Serie anlegen</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
import { Component, inject, OnInit, DestroyRef, ViewChild } from '@angular/core';
|
||||||
|
import { AbstractControl, FormBuilder, FormGroup, FormGroupDirective, ValidationErrors, Validators } from '@angular/forms';
|
||||||
|
import { MAT_DATE_LOCALE } from '@angular/material/core';
|
||||||
|
import { provideNativeDateAdapter } from '@angular/material/core';
|
||||||
|
import { HttpService } from '../http.service';
|
||||||
|
import { LoadingService } from '../loading.service';
|
||||||
|
import { catchError, forkJoin, map, Observable, of, startWith } from 'rxjs';
|
||||||
|
import { Film, Kinosaal, Plan, Sitzplatz, Vorstellung } from '@infinimotion/model-frontend';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-performance-scheduling',
|
||||||
|
standalone: false,
|
||||||
|
templateUrl: './performance-scheduling.component.html',
|
||||||
|
styleUrl: './performance-scheduling.component.css',
|
||||||
|
providers: [
|
||||||
|
provideNativeDateAdapter(),
|
||||||
|
{ provide: MAT_DATE_LOCALE, useValue: 'de-DE' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class PerformanceSchedulingComponent implements OnInit {
|
||||||
|
|
||||||
|
private fb = inject(FormBuilder);
|
||||||
|
private http = inject(HttpService);
|
||||||
|
private destroyRef = inject(DestroyRef);
|
||||||
|
|
||||||
|
public loading = inject(LoadingService);
|
||||||
|
|
||||||
|
constructor(private snackBar: MatSnackBar) {}
|
||||||
|
|
||||||
|
@ViewChild('formDirectivePerformance') formDirectivePerformance!: FormGroupDirective;
|
||||||
|
@ViewChild('formDirectiveSeries') formDirectiveSeries!: FormGroupDirective;
|
||||||
|
|
||||||
|
minDate = new Date();
|
||||||
|
|
||||||
|
movies!: Film[];
|
||||||
|
filteredMoviesPerformance!: Observable<Film[]>;
|
||||||
|
filteredMoviesPlan!: Observable<Film[]>;
|
||||||
|
|
||||||
|
halls: Kinosaal[] = [];
|
||||||
|
hallSeatMap = new Map<number, Sitzplatz[]>();
|
||||||
|
|
||||||
|
weekdays = [
|
||||||
|
{ label: 'Montag', value: 1 },
|
||||||
|
{ label: 'Dienstag', value: 2 },
|
||||||
|
{ label: 'Mittwoch', value: 3 },
|
||||||
|
{ label: 'Donnerstag', value: 4 },
|
||||||
|
{ label: 'Freitag', value: 5 },
|
||||||
|
{ label: 'Samstag', value: 6 },
|
||||||
|
{ label: 'Sonntag', value: 7 }
|
||||||
|
];
|
||||||
|
|
||||||
|
weekdayFilter = (date: Date | null): boolean => {
|
||||||
|
if (!date) return false;
|
||||||
|
const selectedWeekday = this.seriesForm.get('weekday')?.value;
|
||||||
|
if (!selectedWeekday) return true;
|
||||||
|
const weekday = date.getDay() === 0 ? 7 : date.getDay();
|
||||||
|
return weekday === selectedWeekday;
|
||||||
|
};
|
||||||
|
|
||||||
|
performanceForm!: FormGroup;
|
||||||
|
seriesForm!: FormGroup;
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.performanceForm = this.fb.group({
|
||||||
|
movie: ['', [Validators.required, Validators.minLength(3)]],
|
||||||
|
hall: ['', Validators.required],
|
||||||
|
date: [null, Validators.required],
|
||||||
|
time: ['', Validators.required]
|
||||||
|
}, {
|
||||||
|
validators: this.dateTimeInFutureValidator()
|
||||||
|
});
|
||||||
|
this.seriesForm = this.fb.group({
|
||||||
|
movie: ['', [Validators.required, Validators.minLength(3)]],
|
||||||
|
hall: ['', Validators.required],
|
||||||
|
weekday: ['', Validators.required],
|
||||||
|
first: [null, Validators.required],
|
||||||
|
time: ['', Validators.required]
|
||||||
|
}, {
|
||||||
|
validators: this.dateTimeInFutureValidator()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wochentagüberprüfung
|
||||||
|
const weekdayControl = this.seriesForm.get('weekday');
|
||||||
|
const dateControl = this.seriesForm.get('first');
|
||||||
|
|
||||||
|
weekdayControl?.valueChanges.subscribe(() => {
|
||||||
|
|
||||||
|
const currentDate = dateControl?.value;
|
||||||
|
if (currentDate && !this.weekdayFilter(currentDate)) {
|
||||||
|
dateControl?.setValue(null);
|
||||||
|
}
|
||||||
|
dateControl?.updateValueAndValidity();
|
||||||
|
});
|
||||||
|
|
||||||
|
dateControl?.valueChanges.subscribe((date: Date | null) => {
|
||||||
|
if (!date) return;
|
||||||
|
|
||||||
|
const weekday = date.getDay() === 0 ? 7 : date.getDay();
|
||||||
|
weekdayControl?.setValue(weekday, { emitEvent: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
this.loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
dateTimeInFutureValidator() {
|
||||||
|
return (formGroup: AbstractControl): ValidationErrors | null => {
|
||||||
|
const dateControl = formGroup.get('date');
|
||||||
|
const timeControl = formGroup.get('time');
|
||||||
|
|
||||||
|
if (!dateControl?.value || !timeControl?.value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedDateTime = this.combineDateAndTime(dateControl.value, timeControl.value);
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
if (selectedDateTime <= now) {
|
||||||
|
return { dateTimeInPast: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
hasDateTimeError(formGroup: AbstractControl): boolean {
|
||||||
|
return formGroup.hasError('dateTimeInPast') &&
|
||||||
|
formGroup.get('date')?.touched === true &&
|
||||||
|
formGroup.get('time')?.touched === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hallName(formGroup: AbstractControl): string | undefined {
|
||||||
|
const hall: Kinosaal | null = formGroup.get('hall')?.value ?? null;
|
||||||
|
return hall?.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayMovie(movie: Film | null): string {
|
||||||
|
return movie ? movie.title : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData() {
|
||||||
|
const moviesRequest = this.http.getMovies();
|
||||||
|
const seatsRequest = this.http.getAllSeats();
|
||||||
|
|
||||||
|
this.loading.show();
|
||||||
|
this.hallSeatMap.clear();
|
||||||
|
this.halls = [];
|
||||||
|
|
||||||
|
forkJoin([moviesRequest, seatsRequest]).subscribe({
|
||||||
|
next: ([movies, seats]) => {
|
||||||
|
this.movies = movies.sort((a, b) => a.title.localeCompare(b.title));
|
||||||
|
|
||||||
|
seats.forEach((seat) => {
|
||||||
|
const hallId = seat.row.hall.id;
|
||||||
|
if (!this.hallSeatMap.has(hallId)) {
|
||||||
|
this.halls.push(seat.row.hall);
|
||||||
|
this.hallSeatMap.set(hallId, []);
|
||||||
|
}
|
||||||
|
this.hallSeatMap.get(hallId)?.push(seat);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.filteredMoviesPerformance = this.performanceForm.get('movie')!.valueChanges.pipe(
|
||||||
|
startWith(''),
|
||||||
|
map(value => this._filterMovies(value))
|
||||||
|
);
|
||||||
|
|
||||||
|
this.filteredMoviesPlan = this.seriesForm.get('movie')!.valueChanges.pipe(
|
||||||
|
startWith(''),
|
||||||
|
map(value => this._filterMovies(value))
|
||||||
|
);
|
||||||
|
|
||||||
|
this.loading.hide()
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error('Fehler beim Laden der Filme/Kinosäle', err);
|
||||||
|
this.loading.showError(err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _filterMovies(value: string | Film): Film[] {
|
||||||
|
const filterValue = typeof value === 'string' ? value.toLowerCase() : value?.title.toLowerCase() ?? '';
|
||||||
|
return this.movies.filter(movie => movie.title.toLowerCase().includes(filterValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
onPerformanceSubmit() {
|
||||||
|
if (!this.performanceForm.valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loading.show();
|
||||||
|
|
||||||
|
const formValue = this.performanceForm.value;
|
||||||
|
const datetime = this.combineDateAndTime(formValue.date, formValue.time);
|
||||||
|
|
||||||
|
const performance = this.generateNewPerformanceObject(datetime, formValue.hall, formValue.movie)
|
||||||
|
|
||||||
|
this.http.createPerformance(performance).pipe(
|
||||||
|
map(performance => {
|
||||||
|
this.loading.hide();
|
||||||
|
|
||||||
|
this.formDirectivePerformance.resetForm();
|
||||||
|
|
||||||
|
this.snackBar.open(
|
||||||
|
`Vorstellung wurde erfolgreich unter der ID ${performance.id} angelegt.`,
|
||||||
|
'Schließen',
|
||||||
|
{
|
||||||
|
duration: 5000,
|
||||||
|
panelClass: ['success-snackbar'],
|
||||||
|
horizontalPosition: 'right',
|
||||||
|
verticalPosition: 'top'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
catchError(err => {
|
||||||
|
this.loading.showError(err);
|
||||||
|
this.performanceForm.setErrors({ serverError: true });
|
||||||
|
console.log('Fehler beim Anlegen der Vorstellung', err);
|
||||||
|
return of(null);
|
||||||
|
}),
|
||||||
|
takeUntilDestroyed(this.destroyRef)
|
||||||
|
).subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateNewPerformanceObject(start: Date, hall: Kinosaal, movie: Film): Vorstellung {
|
||||||
|
return {
|
||||||
|
id: 0, // Wird durch das Backend gesetzt
|
||||||
|
start: start,
|
||||||
|
hall: hall,
|
||||||
|
movie: movie
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private combineDateAndTime(date: Date, time: string): Date {
|
||||||
|
const [hours, minutes] = time.split(':').map(Number);
|
||||||
|
const combined = new Date(date);
|
||||||
|
combined.setHours(hours, minutes, 0, 0);
|
||||||
|
return combined;
|
||||||
|
}
|
||||||
|
|
||||||
|
onPlanSubmit() {
|
||||||
|
if (!this.seriesForm.valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loading.show();
|
||||||
|
|
||||||
|
const formValue = this.seriesForm.value;
|
||||||
|
const series = this.generateNewSeriesObject(formValue.weekday, formValue.time, formValue.first, formValue.hall, formValue.movie)
|
||||||
|
|
||||||
|
this.http.createPerformanceSeries(series).pipe(
|
||||||
|
map(performance => {
|
||||||
|
this.loading.hide();
|
||||||
|
|
||||||
|
this.formDirectiveSeries.resetForm();
|
||||||
|
this.seriesForm.reset({
|
||||||
|
movie: '',
|
||||||
|
hall: '',
|
||||||
|
weekday: '',
|
||||||
|
first: null,
|
||||||
|
time: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
this.snackBar.open(
|
||||||
|
`Vorstellungsplan wurde erfolgreich unter der ID ${performance.id} angelegt.`,
|
||||||
|
'Schließen',
|
||||||
|
{
|
||||||
|
duration: 5000,
|
||||||
|
panelClass: ['success-snackbar'],
|
||||||
|
horizontalPosition: 'right',
|
||||||
|
verticalPosition: 'top'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
catchError(err => {
|
||||||
|
this.loading.showError(err);
|
||||||
|
this.seriesForm.setErrors({ serverError: true });
|
||||||
|
console.log('Fehler beim Anlegen des Plans', err);
|
||||||
|
return of(null);
|
||||||
|
}),
|
||||||
|
takeUntilDestroyed(this.destroyRef)
|
||||||
|
).subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateNewSeriesObject(weekday: number, time: Date, first: Date, hall: Kinosaal, movie: Film): Plan {
|
||||||
|
return {
|
||||||
|
id: 0, // Wird durch das Backend gesetzt
|
||||||
|
weekday: weekday,
|
||||||
|
time: time,
|
||||||
|
first: first,
|
||||||
|
hall: hall,
|
||||||
|
movie: movie
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -110,7 +110,7 @@ export class ScheduleComponent implements OnInit {
|
|||||||
id: perf.id,
|
id: perf.id,
|
||||||
hall: perf.hall.name,
|
hall: perf.hall.name,
|
||||||
start: new Date(perf.start),
|
start: new Date(perf.start),
|
||||||
// utilisation: 0 // TODO: perf.utilisation einrichten
|
// utilisation: 0 // TODO: perf.utilisation einrichten // Zu teuer: wont-fix
|
||||||
};
|
};
|
||||||
movieMap.get(movieId)!.performances.push(performance);
|
movieMap.get(movieId)!.performances.push(performance);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user