Improve movie search & title fix

This commit is contained in:
2025-11-17 22:42:17 +01:00
parent bd56f3242e
commit e5fcdfe212
9 changed files with 78 additions and 52 deletions

View File

@@ -23,6 +23,8 @@ import { MatDividerModule } from '@angular/material/divider';
import { MatDialogClose, MatDialogTitle, MatDialogContent, MatDialogActions } from "@angular/material/dialog"; import { MatDialogClose, MatDialogTitle, MatDialogContent, MatDialogActions } from "@angular/material/dialog";
import { MatStepperModule } from '@angular/material/stepper'; import { MatStepperModule } from '@angular/material/stepper';
import { MatCheckboxModule } from '@angular/material/checkbox'; 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 { HeaderComponent } from './header/header.component';
import { HomeComponent } from './home/home.component'; import { HomeComponent } from './home/home.component';
@@ -133,6 +135,8 @@ import { TicketListComponent } from './ticket-list/ticket-list.component';
NgxMaskDirective, NgxMaskDirective,
NgxMaskPipe, NgxMaskPipe,
QRCodeComponent, QRCodeComponent,
MatBadgeModule,
MatTooltipModule,
], ],
providers: [ providers: [
provideBrowserGlobalErrorListeners(), provideBrowserGlobalErrorListeners(),

View File

@@ -3,8 +3,8 @@
@if ( icon() ) { @if ( icon() ) {
<mat-icon style="font-size: 35px; width: 35px; height: 35px; opacity: 50%;">{{ icon() }}</mat-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"> <p class="text-2xl font-medium pl-2 bg-linear-to-r from-indigo-500 to-pink-600 bg-clip-text text-transparent">
{{ title() }} {{ label() }}
</p> </p>
</div> </div>
@if ( searchBar() ) { @if ( searchBar() ) {

View File

@@ -7,7 +7,7 @@ import { Component, input, output } from '@angular/core';
styleUrl: './menu-header.component.css' styleUrl: './menu-header.component.css'
}) })
export class MenuHeaderComponent { export class MenuHeaderComponent {
title = input.required<string>(); label = input.required<string>();
icon = input<string>(); icon = input<string>();
searchBar = input<boolean>(false); searchBar = input<boolean>(false);

View File

@@ -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"> <div class="w-6/10 m-auto my-20">
<form class="movie-search-form w-full" (ngSubmit)="DoSubmit()"> <form class="movie-search-form w-full" (ngSubmit)="DoSubmit()">

View File

@@ -1,16 +1,22 @@
<form class="movie-search-form w-88"> <div class="flex items-center space-x-4">
<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')) { --> @if (searchControl.value && searchControl.value.length > 0) {
<!-- <mat-error>Film existiert nicht</mat-error> --> <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>
}
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete"> <form class="movie-search-form w-88">
@for (option of filteredOptions | async; track option) { <mat-form-field class="w-full" subscriptSizing="dynamic">
<mat-option [value]="option">{{option}}</mat-option> <mat-label>Film suchen</mat-label>
} <input class="w-full" type="text" matInput [formControl]="searchControl" [matAutocomplete]="auto">
</mat-autocomplete>
</mat-form-field> <mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
</form> @for (option of filteredOptions | async; track option) {
<mat-option [value]="option">{{option}}</mat-option>
}
</mat-autocomplete>
</mat-form-field>
</form>
</div>

View File

@@ -5,3 +5,7 @@
::ng-deep .mat-mdc-tab .mdc-tab-indicator__content--underline { ::ng-deep .mat-mdc-tab .mdc-tab-indicator__content--underline {
border-color: #6366f1 !important; /* indigo-500 */ border-color: #6366f1 !important; /* indigo-500 */
} }
.mat-badge-content {
background: #dd2979;
}

View File

@@ -1,20 +1,25 @@
<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> <mat-tab-group mat-stretch-tabs>
@for (dateInfo of dates; track dateInfo.date; let i = $index) { @for (dateInfo of dates; track dateInfo.date; let i = $index) {
<mat-tab [label]="dateInfo.label"> <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 (getMovieCount(i) > 0) {
@if (hasSearchResults(i)) { @for (group of dateInfo.performances; track group.movie.id) {
@for (group of dateInfo.performances; track group.movie.id) { @if (group.movie.title.toLowerCase().includes(movieSearchResult.toLowerCase())) {
@if (group.movie.title.toLowerCase().includes(movieSearchResult.toLowerCase())) { <app-movie-schedule-info [movieGroup]="group"></app-movie-schedule-info>
<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>
} }
} @else { } @else {
<app-movie-schedule-empty></app-movie-schedule-empty> @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>
} }

View File

@@ -31,15 +31,7 @@ export class ScheduleComponent implements OnInit {
this.loadPerformances(this.bookableDays); this.loadPerformances(this.bookableDays);
} }
hasSearchResults(dateIndex: number): boolean { private generateDates(bookableDays: number) {
if (!this.movieSearchResult) return true;
return this.dates[dateIndex].performances.some(group =>
group.movie.title.toLowerCase().includes(this.movieSearchResult.toLowerCase())
);
}
generateDates(bookableDays: number) {
const today = new Date(); const today = new Date();
for (let i = 0; i < bookableDays; i++) { for (let i = 0; i < bookableDays; i++) {
const date = new Date(today); const date = new Date(today);
@@ -58,7 +50,7 @@ export class ScheduleComponent implements OnInit {
} }
} }
loadPerformances(bookableDays: number) { private loadPerformances(bookableDays: number) {
this.loading.show(); this.loading.show();
const filter = this.generateDateFilter(bookableDays); const filter = this.generateDateFilter(bookableDays);
this.http.getPerformacesByFilter(filter).pipe( this.http.getPerformacesByFilter(filter).pipe(
@@ -75,22 +67,21 @@ export class ScheduleComponent implements OnInit {
).subscribe(); ).subscribe();
} }
private generateDateFilter(bookableDays: number): string[] { private generateDateFilter(bookableDays: number): string[] {
const startDate = new Date(); const startDate = new Date();
const endDate = new Date(); const endDate = new Date();
endDate.setDate(startDate.getDate() + bookableDays - 1); endDate.setDate(startDate.getDate() + bookableDays - 1);
const startStr = startDate.toISOString().split('T')[0] + 'T00:00:00'; const startStr = startDate.toISOString().split('T')[0] + 'T00:00:00';
const endStr = endDate.toISOString().split('T')[0] + 'T23:59:59'; const endStr = endDate.toISOString().split('T')[0] + 'T23:59:59';
return [ return [
`ge;start;date;${startStr}`, `ge;start;date;${startStr}`,
`le;start;date;${endStr}`, `le;start;date;${endStr}`,
]; ];
} }
private assignPerformancesToDates() {
assignPerformancesToDates() {
// Gruppieren nach Datum // Gruppieren nach Datum
const groupedByDate: { [key: string]: Vorstellung[] } = {}; const groupedByDate: { [key: string]: Vorstellung[] } = {};
@@ -133,6 +124,22 @@ private generateDateFilter(bookableDays: number): string[] {
} }
getMovieCount(index: number): number { 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() !== '';
} }
} }

View File

@@ -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"> <div class="flex justify-between h-100">