From 0bd38877016da15e2a940bc8cfa8f9f89ece8f19 Mon Sep 17 00:00:00 2001 From: Piet Ostendorp Date: Wed, 5 Nov 2025 01:04:56 +0100 Subject: [PATCH 1/2] Add movie search and schedule header components Introduces MovieSearchComponent, ScheduleHeaderComponent, and MovieScheduleNoSearchResultComponent for improved movie search and schedule display. Updates schedule and navbar to support search functionality and UI enhancements. Adds movie fetching to HttpService and refines layout and styles for better user experience. --- src/app/app-module.ts | 25 ++++++-- src/app/header-2/header-2.component.html | 2 +- src/app/http.service.ts | 9 ++- ...ie-schedule-no-search-result.component.css | 0 ...e-schedule-no-search-result.component.html | 5 ++ ...vie-schedule-no-search-result.component.ts | 12 ++++ .../movie-search/movie-search.component.css | 0 .../movie-search/movie-search.component.html | 16 +++++ .../movie-search/movie-search.component.ts | 62 +++++++++++++++++++ src/app/navbar/navbar.component.css | 2 +- src/app/navbar/navbar.component.html | 2 +- src/app/navbar/navbar.component.ts | 2 +- .../schedule-header.component.css | 6 ++ .../schedule-header.component.html | 10 +++ .../schedule-header.component.ts | 11 ++++ src/app/schedule/schedule.component.html | 12 +++- src/app/schedule/schedule.component.ts | 10 +++ 17 files changed, 174 insertions(+), 12 deletions(-) create mode 100644 src/app/movie-schedule-no-search-result/movie-schedule-no-search-result.component.css create mode 100644 src/app/movie-schedule-no-search-result/movie-schedule-no-search-result.component.html create mode 100644 src/app/movie-schedule-no-search-result/movie-schedule-no-search-result.component.ts create mode 100644 src/app/movie-search/movie-search.component.css create mode 100644 src/app/movie-search/movie-search.component.html create mode 100644 src/app/movie-search/movie-search.component.ts create mode 100644 src/app/schedule-header/schedule-header.component.css create mode 100644 src/app/schedule-header/schedule-header.component.html create mode 100644 src/app/schedule-header/schedule-header.component.ts diff --git a/src/app/app-module.ts b/src/app/app-module.ts index ec14d8c..970fd36 100644 --- a/src/app/app-module.ts +++ b/src/app/app-module.ts @@ -1,9 +1,10 @@ import { CommonModule } from '@angular/common'; import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core'; -import { FormsModule } from '@angular/forms'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; import { provideHttpClient, withFetch } from '@angular/common/http'; + import { AppRoutingModule } from './app-routing-module'; import { App } from './app'; @@ -12,7 +13,11 @@ import { MatTabsModule } from '@angular/material/tabs'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatSnackBarModule } from '@angular/material/snack-bar'; - +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatInputModule } from '@angular/material/input'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconButton } from '@angular/material/button'; +import { MatDividerModule } from '@angular/material/divider'; import { HeaderComponent } from './header/header.component'; import { HomeComponent } from './home/home.component'; @@ -34,8 +39,10 @@ import { Header2Component } from './header-2/header-2.component'; import { TheaterOverlayComponent } from './theater-overlay/theater-overlay.component'; import { SeatComponent } from './seat/seat.component'; import { SeatRowComponent } from './seat-row/seat-row.component'; -import {MatIconButton} from '@angular/material/button'; import { TheaterLayoutComponent } from './theater-layout/theater-layout.component'; +import { MovieSearchComponent } from './movie-search/movie-search.component'; +import { ScheduleHeaderComponent } from './schedule-header/schedule-header.component'; +import { MovieScheduleNoSearchResultComponent } from './movie-schedule-no-search-result/movie-schedule-no-search-result.component'; @NgModule({ @@ -60,11 +67,15 @@ import { TheaterLayoutComponent } from './theater-layout/theater-layout.componen TheaterOverlayComponent, SeatComponent, SeatRowComponent, - TheaterLayoutComponent + TheaterLayoutComponent, + MovieSearchComponent, + ScheduleHeaderComponent, + MovieScheduleNoSearchResultComponent ], imports: [ AppRoutingModule, BrowserModule, + ReactiveFormsModule, CommonModule, FormsModule, MatIconModule, @@ -72,7 +83,11 @@ import { TheaterLayoutComponent } from './theater-layout/theater-layout.componen MatToolbarModule, MatProgressBarModule, MatSnackBarModule, - MatIconButton + MatAutocompleteModule, + MatInputModule, + MatFormFieldModule, + MatIconButton, + MatDividerModule ], providers: [ provideBrowserGlobalErrorListeners(), diff --git a/src/app/header-2/header-2.component.html b/src/app/header-2/header-2.component.html index 9545660..ce95d56 100644 --- a/src/app/header-2/header-2.component.html +++ b/src/app/header-2/header-2.component.html @@ -6,7 +6,7 @@
-

+

Absolut war gestern, Bewegung ist heute!

diff --git a/src/app/http.service.ts b/src/app/http.service.ts index 5efe788..91dca64 100644 --- a/src/app/http.service.ts +++ b/src/app/http.service.ts @@ -1,4 +1,4 @@ -import {Kinosaal, Sitzplatz, Vorstellung} from '@infinimotion/model-frontend'; +import { Kinosaal, Sitzplatz, Vorstellung, Film } from '@infinimotion/model-frontend'; import { HttpClient } from "@angular/common/http"; import { inject, Injectable } from "@angular/core"; import { Observable } from "rxjs"; @@ -67,6 +67,13 @@ export class HttpService { } + /* Vorstellung APIs */ + + /* GET /api/film */ + getMovies(): Observable { + return this.http.get(`${this.baseUrl}film`); + } + /* Show-Seats APIs*/ diff --git a/src/app/movie-schedule-no-search-result/movie-schedule-no-search-result.component.css b/src/app/movie-schedule-no-search-result/movie-schedule-no-search-result.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/movie-schedule-no-search-result/movie-schedule-no-search-result.component.html b/src/app/movie-schedule-no-search-result/movie-schedule-no-search-result.component.html new file mode 100644 index 0000000..5685ffe --- /dev/null +++ b/src/app/movie-schedule-no-search-result/movie-schedule-no-search-result.component.html @@ -0,0 +1,5 @@ +
+ search_off +

Keine Vorstellungen gefunden

+

Für '{{ search() }}' finden am {{ date().toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric'} )}} kein Vorstellunge statt.

+
diff --git a/src/app/movie-schedule-no-search-result/movie-schedule-no-search-result.component.ts b/src/app/movie-schedule-no-search-result/movie-schedule-no-search-result.component.ts new file mode 100644 index 0000000..4c91a0b --- /dev/null +++ b/src/app/movie-schedule-no-search-result/movie-schedule-no-search-result.component.ts @@ -0,0 +1,12 @@ +import { Component, input } from '@angular/core'; + +@Component({ + selector: 'app-movie-schedule-no-search-result', + standalone: false, + templateUrl: './movie-schedule-no-search-result.component.html', + styleUrl: './movie-schedule-no-search-result.component.css' +}) +export class MovieScheduleNoSearchResultComponent { + search = input.required(); + date = input.required(); +} diff --git a/src/app/movie-search/movie-search.component.css b/src/app/movie-search/movie-search.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/movie-search/movie-search.component.html b/src/app/movie-search/movie-search.component.html new file mode 100644 index 0000000..add7471 --- /dev/null +++ b/src/app/movie-search/movie-search.component.html @@ -0,0 +1,16 @@ +
+ + Film suchen + + + + + + + + @for (option of filteredOptions | async; track option) { + {{option}} + } + + +
diff --git a/src/app/movie-search/movie-search.component.ts b/src/app/movie-search/movie-search.component.ts new file mode 100644 index 0000000..ad27356 --- /dev/null +++ b/src/app/movie-search/movie-search.component.ts @@ -0,0 +1,62 @@ +import { Component, inject, OnInit, output, ViewEncapsulation } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { Observable, of } from 'rxjs'; +import { catchError, map, startWith, tap } from 'rxjs/operators'; +import { HttpService } from '../http.service'; + +@Component({ + selector: 'app-movie-search', + standalone: false, + templateUrl: './movie-search.component.html', + styleUrl: './movie-search.component.css', + encapsulation: ViewEncapsulation.None +}) +export class MovieSearchComponent implements OnInit { + + movieSearchResult = output(); + + options: string[] = []; + filteredOptions: Observable = new Observable(); + + searchControl = new FormControl('', (control) => { + if (!control.value) return null; + + const value = control.value.toLowerCase(); + const found = this.options.some(option => + option.toLowerCase().includes(value) + ); + return found ? null : { filmNotFound: true }; + }); + + private http = inject(HttpService) + + ngOnInit() { + this.loadMovies(); + this.filteredOptions = this.searchControl.valueChanges.pipe( + startWith(''), + tap(value => this.movieSearchResult.emit(value || '')), + map(value => this._filter(value || '')) + ); + } + + private _filter(value: string): string[] { + const filterValue = value.toLowerCase(); + return this.options.filter(option => + option.toLowerCase().includes(filterValue) + ); + } + + private loadMovies() { + this.http.getMovies().pipe( + tap(movies => { + this.options = movies + .map(movie => movie.title) + .sort(); + }), + catchError(err => { + console.error('Fehler beim Laden der Filme', err); + return of([]); + }) + ).subscribe(); + } +} diff --git a/src/app/navbar/navbar.component.css b/src/app/navbar/navbar.component.css index 27299ec..7d41e7d 100644 --- a/src/app/navbar/navbar.component.css +++ b/src/app/navbar/navbar.component.css @@ -6,7 +6,7 @@ nav { } .navbar { - width: 200px; + width: 250px; background-color: white; padding: 5px; transition: color .2s; diff --git a/src/app/navbar/navbar.component.html b/src/app/navbar/navbar.component.html index e738b6b..75353b1 100644 --- a/src/app/navbar/navbar.component.html +++ b/src/app/navbar/navbar.component.html @@ -7,7 +7,7 @@ + class="text-2xl pl-3 hover:bg-gray-200 gradient-text rounded-sm"> {{ item.label }} diff --git a/src/app/navbar/navbar.component.ts b/src/app/navbar/navbar.component.ts index ac2bf55..b060804 100644 --- a/src/app/navbar/navbar.component.ts +++ b/src/app/navbar/navbar.component.ts @@ -12,7 +12,7 @@ export class NavbarComponent { */ navItems:{label:string, path:string}[] = [ - {label: 'Schedule', path: '/schedule'}, + {label: 'Programm', path: '/schedule'}, {label: 'Kinosaal-test', path: '/theater-overlay'}, ] } diff --git a/src/app/schedule-header/schedule-header.component.css b/src/app/schedule-header/schedule-header.component.css new file mode 100644 index 0000000..343f510 --- /dev/null +++ b/src/app/schedule-header/schedule-header.component.css @@ -0,0 +1,6 @@ +.schedule-header { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; +} diff --git a/src/app/schedule-header/schedule-header.component.html b/src/app/schedule-header/schedule-header.component.html new file mode 100644 index 0000000..cfb017f --- /dev/null +++ b/src/app/schedule-header/schedule-header.component.html @@ -0,0 +1,10 @@ +
+
+ event +

+ Programmübersicht +

+
+ +
+ diff --git a/src/app/schedule-header/schedule-header.component.ts b/src/app/schedule-header/schedule-header.component.ts new file mode 100644 index 0000000..8ac6849 --- /dev/null +++ b/src/app/schedule-header/schedule-header.component.ts @@ -0,0 +1,11 @@ +import { Component, output } from '@angular/core'; + +@Component({ + selector: 'app-schedule-header', + standalone: false, + templateUrl: './schedule-header.component.html', + styleUrl: './schedule-header.component.css' +}) +export class ScheduleHeaderComponent { + movieSearchResult = output(); +} diff --git a/src/app/schedule/schedule.component.html b/src/app/schedule/schedule.component.html index cfcd93a..f5924df 100644 --- a/src/app/schedule/schedule.component.html +++ b/src/app/schedule/schedule.component.html @@ -1,9 +1,17 @@ + + @for (dateInfo of dates; track dateInfo.date; let i = $index) { @if (getMovieCount(i) > 0) { - @for (group of dateInfo.performances; track group.movie.id) { - + @if (hasSearchResults(i)) { + @for (group of dateInfo.performances; track group.movie.id) { + @if (group.movie.title.toLowerCase().includes(movieSearchResult.toLowerCase())) { + + } + } + } @else { + } } @else { diff --git a/src/app/schedule/schedule.component.ts b/src/app/schedule/schedule.component.ts index c7bbc7a..88482f3 100644 --- a/src/app/schedule/schedule.component.ts +++ b/src/app/schedule/schedule.component.ts @@ -16,6 +16,8 @@ export class ScheduleComponent implements OnInit { dates: { label: string; date: Date; performances: MovieGroup[] }[] = []; performaces: Vorstellung[] = []; + movieSearchResult: string = ''; + private http = inject(HttpService); private loading = inject(LoadingService) @@ -27,6 +29,14 @@ export class ScheduleComponent implements OnInit { this.loadPerformances(); } + 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() { const today = new Date(); for (let i = 0; i < 14; i++) { From 6f98882564a31f2fb086b2647e5120672282bbdc Mon Sep 17 00:00:00 2001 From: Piet Ostendorp Date: Wed, 5 Nov 2025 11:05:40 +0100 Subject: [PATCH 2/2] Add performance filtering by date range Introduces a new API method in HttpService to fetch performances by a date filter. ScheduleComponent now generates a date filter for the next 14 days and loads performances using the new filtered endpoint, improving flexibility for bookable days. --- src/app/http.service.ts | 7 +++++- src/app/schedule/schedule.component.ts | 32 ++++++++++++++++++++------ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/app/http.service.ts b/src/app/http.service.ts index 91dca64..2429917 100644 --- a/src/app/http.service.ts +++ b/src/app/http.service.ts @@ -51,6 +51,11 @@ export class HttpService { return this.http.get(`${this.baseUrl}vorstellung/${id}`); } + /* POST /api/vorstellung/filter */ + getPerformaceByFilter(filter: string[]): Observable { + return this.http.post(`${this.baseUrl}vorstellung/filter`, filter); + } + /* POST /api/vorstellung */ addPerformace(vorstellung: Omit): Observable { return this.http.post(`${this.baseUrl}vorstellung`, vorstellung); @@ -67,7 +72,7 @@ export class HttpService { } - /* Vorstellung APIs */ + /* Film APIs */ /* GET /api/film */ getMovies(): Observable { diff --git a/src/app/schedule/schedule.component.ts b/src/app/schedule/schedule.component.ts index 88482f3..aae539d 100644 --- a/src/app/schedule/schedule.component.ts +++ b/src/app/schedule/schedule.component.ts @@ -18,15 +18,17 @@ export class ScheduleComponent implements OnInit { movieSearchResult: string = ''; + private readonly bookableDays: number = 14; + private http = inject(HttpService); private loading = inject(LoadingService) constructor() { - this.generateDates(); + this.generateDates(this.bookableDays); } ngOnInit() { - this.loadPerformances(); + this.loadPerformances(this.bookableDays); } hasSearchResults(dateIndex: number): boolean { @@ -37,9 +39,9 @@ export class ScheduleComponent implements OnInit { ); } - generateDates() { + generateDates(bookableDays: number) { const today = new Date(); - for (let i = 0; i < 14; i++) { + for (let i = 0; i < bookableDays; i++) { const date = new Date(today); date.setDate(today.getDate() + i); @@ -56,9 +58,10 @@ export class ScheduleComponent implements OnInit { } } - loadPerformances() { + loadPerformances(bookableDays: number) { this.loading.show(); - this.http.getPerformaces().pipe( + const filter = this.generateDateFilter(bookableDays); + this.http.getPerformaceByFilter(filter).pipe( map(data => Array.isArray(data) ? data : [data]), tap(performaces => { this.performaces = performaces; @@ -67,12 +70,27 @@ export class ScheduleComponent implements OnInit { }), catchError(err => { this.loading.showError(err); - console.error('Fehler beim Laden der Vorstellung', err); + console.error('Fehler beim Laden der Vorstellungen', err); return of([]); }) ).subscribe(); } +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'; + + return [ + `ge;start;date;${startStr}`, + `le;start;date;${endStr}`, + ]; +} + + assignPerformancesToDates() { // Gruppieren nach Datum