Add IMDb movie importer feature and unify header

Introduces a new movie importer feature allowing admins to search and import movies from IMDb, including new components for search, result display, and error handling. Replaces the schedule header with a reusable menu header component. Updates routing, navigation, and HTTP service to support the new importer. Adds a poster placeholder image and improves poster error handling.
This commit is contained in:
2025-11-07 01:57:42 +01:00
parent bd7a0ed9f1
commit 4f5a8e9661
25 changed files with 338 additions and 42 deletions

View File

@@ -0,0 +1,4 @@
:host {
display: block;
margin: 60px 0;
}

View File

@@ -0,0 +1,22 @@
<div class="flex flex-col md:flex-row gap-1">
<div class="w-1/5">
<img
[src]="movie().poster && movie().poster !== 'N/A' ? movie().poster :'assets/poster_placeholder.png'"
alt="Movie Poster"
class="w-40 h-auto shadow-md rounded-md"
(error)="onPosterError($event)"
>
</div>
<div class="mx-8 my-auto w-4/5">
<h1 class="text-4xl font-bold mb-2">{{ movie().title }}</h1>
<h2 class="text-xl mb-8">
Erscheinungsjahr: {{ movie().year }}
</h2>
<button matFab extended class="mb-3" (click)="importMovie(movie().imdbID, movie().title)" [disabled]="this.buttonDisabled">
<mat-icon>{{ buttonIcon }}</mat-icon>
{{ buttonText }}
</button>
</div>
</div>

View File

@@ -0,0 +1,68 @@
import { Component, inject, input } from '@angular/core';
import { Film, OmdbMovie } from '@infinimotion/model-frontend';
import { HttpService } from '../http.service';
import { LoadingService } from '../loading.service';
import { catchError, EMPTY, of, switchMap, tap } from 'rxjs';
@Component({
selector: 'app-movie-import-search-info',
standalone: false,
templateUrl: './movie-import-search-info.component.html',
styleUrl: './movie-import-search-info.component.css'
})
export class MovieImportSearchInfoComponent {
readonly movie = input.required<OmdbMovie>();
importedMovie: Film | undefined;
buttonDisabled = false;
buttonText: string = "Film von IMDb importieren";
buttonIcon: string = "cloud_download"
private http = inject(HttpService)
private loading = inject(LoadingService)
importMovie(imdbId: string, title: string) {
this.buttonDisabled = true;
this.loading.show();
const filter = this.generateTitleFilter(title);
this.http.getMoviesByFilter(filter).pipe(
switchMap(movies => {
if (movies.length !== 0) {
this.loading.showError(`Dublette erkannt! Film '${title}' existiert bereits.`);
console.warn('Dublette erkannt. Import abgebrochen.');
this.buttonText = 'Film existiert bereits'
this.buttonIcon = 'file_present';
return EMPTY;
}
return this.http.importMovie(imdbId);
}),
tap(importedMovie => {
this.importedMovie = importedMovie;
this.buttonText = `Film erfolgreich importiert (Id: ${importedMovie.id})`;
this.buttonIcon = 'cloud_done';
this.loading.hide();
}),
catchError(err => {
this.buttonDisabled = false;
this.loading.showError(err);
console.error('Fehler beim Import oder bei der Dublettenprüfung', err);
return of([]);
})
).subscribe();
}
private generateTitleFilter(title: string): string[] {
return [`eq;title;string;${title}`]
}
onPosterError(event: Event) {
const img = event.target as HTMLImageElement;
const placeholder = 'assets/poster_placeholder.png';
if (img.src !== window.location.origin + placeholder) {
img.src = placeholder;
}
}
}