Make movie schedule components functional
Introduces MovieGroup and Performance models for better type safety and data handling. Refactors movie-related components to use Angular signals (input/computed) and updates templates to bind data dynamically. Updates HttpService to support Vorstellung API endpoints. The schedule component now loads and groups performances by date and movie, passing structured data to child components for rendering.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Kinosaal } from '@infinimotion/model-frontend';
|
||||
import { Kinosaal, Vorstellung } from '@infinimotion/model-frontend';
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { inject, Injectable } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
@@ -6,7 +6,10 @@ import { Observable } from "rxjs";
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class HttpService {
|
||||
private http = inject(HttpClient);
|
||||
private baseUrl = 'https://infinimotion.de/api/';
|
||||
private baseUrl = '/api/';
|
||||
|
||||
|
||||
/* Kinosaal APIs */
|
||||
|
||||
/* GET /api/kinosaal */
|
||||
getAllKinosaal(): Observable<Kinosaal[]> {
|
||||
@@ -32,4 +35,32 @@ export class HttpService {
|
||||
deleteKinosaal(id: number): Observable<void> {
|
||||
return this.http.delete<void>(`${this.baseUrl}kinosaal/${id}`);
|
||||
}
|
||||
|
||||
|
||||
/* Vorstellung APIs */
|
||||
|
||||
/* GET /api/vorstellung */
|
||||
getPerformaces(): Observable<Vorstellung[]> {
|
||||
return this.http.get<Vorstellung[]>(`${this.baseUrl}vorstellung`);
|
||||
}
|
||||
|
||||
/* GET /api/vorstellung/{id} */
|
||||
getPerformaceById(id: number): Observable<Vorstellung> {
|
||||
return this.http.get<Vorstellung>(`${this.baseUrl}vorstellung/${id}`);
|
||||
}
|
||||
|
||||
/* POST /api/vorstellung */
|
||||
addPerformace(vorstellung: Omit<Vorstellung, 'id'>): Observable<Vorstellung> {
|
||||
return this.http.post<Vorstellung>(`${this.baseUrl}vorstellung`, vorstellung);
|
||||
}
|
||||
|
||||
/* PUT /api/vorstellung/{id} */
|
||||
updatePerformace(id: number, vorstellung: Partial<Vorstellung>): Observable<Vorstellung> {
|
||||
return this.http.put<Vorstellung>(`${this.baseUrl}vorstellung/${id}`, vorstellung);
|
||||
}
|
||||
|
||||
/* DELETE /api/vorstellung/{id} */
|
||||
deletePerformace(id: number): Observable<void> {
|
||||
return this.http.delete<void>(`${this.baseUrl}vorstellung/${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
7
src/app/model/movie-group.model.ts
Normal file
7
src/app/model/movie-group.model.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Film } from '@infinimotion/model-frontend';
|
||||
import { Performance } from './performance.model';
|
||||
|
||||
export interface MovieGroup {
|
||||
movie: Film;
|
||||
performances: Performance[];
|
||||
}
|
||||
6
src/app/model/performance.model.ts
Normal file
6
src/app/model/performance.model.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export class Performance {
|
||||
id!: number;
|
||||
hall!: string;
|
||||
start!: Date;
|
||||
utilisation?: number;
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
<span class="flex rounded-sm text-black text-sm px-2 py-1.5">
|
||||
{{ getCategoryText() }}
|
||||
{{ category() }}
|
||||
</span>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Component, input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-movie-category',
|
||||
@@ -7,9 +7,5 @@ import { Component, Input } from '@angular/core';
|
||||
styleUrl: './movie-category.component.css'
|
||||
})
|
||||
export class MovieCategoryComponent {
|
||||
@Input() category: string = '-';
|
||||
|
||||
getCategoryText(): string {
|
||||
return this.category;
|
||||
}
|
||||
category = input<string>('-');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<span class="flex items-center text-black text-sm rounded-sm px-2 py-1.5">
|
||||
<mat-icon class="mr-1" style="font-size: 20px; width: 20px; height: 20px;" fontIcon="schedule"></mat-icon>
|
||||
{{ getDurationText() }}
|
||||
{{ durationText() }}
|
||||
</span>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Component, input, computed } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-movie-duration',
|
||||
@@ -7,9 +7,7 @@ import { Component, Input } from '@angular/core';
|
||||
styleUrl: './movie-duration.component.css'
|
||||
})
|
||||
export class MovieDurationComponent {
|
||||
@Input() duration: number = 0;
|
||||
duration = input<number>(0);
|
||||
|
||||
getDurationText(): string {
|
||||
return `${this.duration} Min.`;
|
||||
}
|
||||
durationText = computed(() => `${this.duration()} Min.`);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<a routerLink="/schedule" class="bg-gray-200 m-2 flex flex-col items-center justify-between rounded-md overflow-hidden text-xl shadow-lg transform transition-all duration-300 hover:scale-105">
|
||||
|
||||
<div class="bg-gradient-to-r from-indigo-500 to-pink-600 w-full text-center text-white font-medium rounded-t-md py-0.5">
|
||||
<p>Kino 1</p>
|
||||
<p>{{ hall() }}</p>
|
||||
</div>
|
||||
|
||||
<h1 class="flex-1 flex items-center justify-center text-black font-bold text-3xl px-2 py-1">
|
||||
15:30
|
||||
{{ startTime() }}
|
||||
</h1>
|
||||
|
||||
<div class="bg-green-600 w-full text-center text-white font-medium rounded-b-md py-0.5">
|
||||
<div [style.background-color]="utilisationBackground()" [class]="fullStyle()" class="w-full text-center text-white font-medium rounded-b-md py-0.5 ">
|
||||
<p>Tickets</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, input, computed } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-movie-performance',
|
||||
@@ -7,5 +7,40 @@ import { Component } from '@angular/core';
|
||||
styleUrl: './movie-performance.component.css'
|
||||
})
|
||||
export class MoviePerformanceComponent {
|
||||
id = input.required<number>();
|
||||
hall = input.required<string>();
|
||||
start = input.required<Date>();
|
||||
utilisation = input<number | undefined>();
|
||||
|
||||
startTime = computed(() =>
|
||||
this.start().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })
|
||||
);
|
||||
|
||||
utilisationBackground = computed(() => {
|
||||
const u = this.utilisation() ?? -1;
|
||||
|
||||
const color_map = new Map<number, string>([
|
||||
[100, '#da1414'],
|
||||
[75, '#e76800'],
|
||||
[50, '#efbe12'],
|
||||
[25, '#8bbb00'],
|
||||
[0, '#10a20b']
|
||||
]);
|
||||
|
||||
for (const [threshold, color] of color_map) {
|
||||
if (u >= threshold) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
return '#424242';
|
||||
});
|
||||
|
||||
fullStyle = computed(() => {
|
||||
if (this.utilisation() === 100) {
|
||||
return 'line-through';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<div class="w-64 mx-auto my-2">
|
||||
<img src="assets/test-movie-poster.jpg" alt="Movie Poster" class="w-full h-auto shadow-md">
|
||||
<img [src]="movie().image" alt="Movie Poster" class="w-full h-auto shadow-md">
|
||||
</div>
|
||||
<div class="flex gap-1 justify-between">
|
||||
<app-movie-rating [rating]="12"></app-movie-rating>
|
||||
<app-movie-duration [duration]="181"></app-movie-duration>
|
||||
<app-movie-category [category]="'Action'"></app-movie-category>
|
||||
<app-movie-rating [rating]="movie().rating"></app-movie-rating>
|
||||
<app-movie-duration [duration]="movie().duration"></app-movie-duration>
|
||||
<app-movie-category [category]="movie().category.name"></app-movie-category>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, input } from '@angular/core';
|
||||
import { Film } from '@infinimotion/model-frontend';
|
||||
|
||||
@Component({
|
||||
selector: 'app-movie-poster',
|
||||
@@ -7,5 +8,5 @@ import { Component } from '@angular/core';
|
||||
styleUrl: './movie-poster.component.css'
|
||||
})
|
||||
export class MoviePosterComponent {
|
||||
|
||||
movie = input.required<Film>();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<span [class]="getRatingColor()" class="text-black flex rounded-sm shadow-md text-sm px-2 py-1.5">
|
||||
{{ getRatingText() }}
|
||||
<span [class]="ratingColor()" class="text-black flex rounded-sm shadow-md text-sm px-2 py-1.5">
|
||||
{{ ratingText() }}
|
||||
</span>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Component, input, computed } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-movie-rating',
|
||||
@@ -7,23 +7,16 @@ import { Component, Input } from '@angular/core';
|
||||
styleUrl: './movie-rating.component.css'
|
||||
})
|
||||
export class MovieRatingComponent {
|
||||
@Input() rating: number = 0;
|
||||
rating = input<number>(0);
|
||||
|
||||
getRatingColor(): string {
|
||||
if (this.rating >= 18) {
|
||||
return 'bg-red-500';
|
||||
} else if (this.rating >= 16) {
|
||||
return 'bg-blue-500';
|
||||
} else if (this.rating >= 12) {
|
||||
return 'bg-green-500';
|
||||
} else if (this.rating >= 6) {
|
||||
return 'bg-yellow-300';
|
||||
} else {
|
||||
return 'bg-white';
|
||||
}
|
||||
}
|
||||
ratingColor = computed(() => {
|
||||
const r = this.rating();
|
||||
if (r >= 18) return 'bg-red-500';
|
||||
if (r >= 16) return 'bg-blue-500';
|
||||
if (r >= 12) return 'bg-green-500';
|
||||
if (r >= 6) return 'bg-yellow-300';
|
||||
return 'bg-white';
|
||||
});
|
||||
|
||||
getRatingText(): string {
|
||||
return `FSK ${this.rating}`;
|
||||
}
|
||||
ratingText = computed(() => `FSK ${this.rating()}`);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<div class="w-3/5 mx-auto flex flex-col md:flex-row gap-4">
|
||||
<app-movie-poster></app-movie-poster>
|
||||
<div class="w-2/3 mx-auto flex flex-col md:flex-row gap-4">
|
||||
<app-movie-poster [movie]="movie"></app-movie-poster>
|
||||
<div>
|
||||
<div class="m-2 mb-4">
|
||||
<h1 class="text-4xl font-bold mb-2">Avengers: Endgame</h1>
|
||||
<p class="text-xl">
|
||||
Long Movie description Long Movie description Long Movie description Long Movie description Long Movie description Long Movie descriptionLong Movie description Long Movie description Long Movie description
|
||||
<h1 class="text-4xl font-bold mb-2">{{ movie.title }}</h1>
|
||||
<p class="text-base">
|
||||
{{ movie.description }}
|
||||
</p>
|
||||
</div>
|
||||
<app-movie-schedule-times></app-movie-schedule-times>
|
||||
<app-movie-schedule-times [performances]="performances"></app-movie-schedule-times>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, input } from '@angular/core';
|
||||
import { MovieGroup } from '../model/movie-group.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-movie-schedule-info',
|
||||
@@ -7,5 +8,13 @@ import { Component } from '@angular/core';
|
||||
styleUrl: './movie-schedule-info.component.css'
|
||||
})
|
||||
export class MovieScheduleInfoComponent {
|
||||
readonly movieGroup = input.required<MovieGroup>();
|
||||
|
||||
get movie() {
|
||||
return this.movieGroup().movie;
|
||||
}
|
||||
|
||||
get performances() {
|
||||
return this.movieGroup().performances;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<div class="flex flex-wrap">
|
||||
<app-movie-performance></app-movie-performance>
|
||||
<app-movie-performance></app-movie-performance>
|
||||
<app-movie-performance></app-movie-performance>
|
||||
@for (perf of performances(); track $index) {
|
||||
<app-movie-performance
|
||||
[id]="perf.id"
|
||||
[hall]="perf.hall"
|
||||
[start]="perf.start"
|
||||
[utilisation]="perf.utilisation">
|
||||
</app-movie-performance>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Performance } from './../model/performance.model';
|
||||
import { Component, input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-movie-schedule-times',
|
||||
@@ -7,5 +8,5 @@ import { Component } from '@angular/core';
|
||||
styleUrl: './movie-schedule-times.component.css'
|
||||
})
|
||||
export class MovieScheduleTimesComponent {
|
||||
|
||||
performances = input.required<Performance[]>();
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<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">
|
||||
@if (getMovieCount(i)> 0) {
|
||||
@for (movie of [].constructor(getMovieCount(i)); track movie) {
|
||||
<app-movie-schedule-info></app-movie-schedule-info>
|
||||
@if (getMovieCount(i) > 0) {
|
||||
@for (group of dateInfo.performances; track group.movie.id) {
|
||||
<app-movie-schedule-info [movieGroup]="group"></app-movie-schedule-info>
|
||||
}
|
||||
} @else {
|
||||
<app-movie-schedule-empty></app-movie-schedule-empty>
|
||||
<app-movie-schedule-empty></app-movie-schedule-empty>
|
||||
}
|
||||
</mat-tab>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, inject, OnInit } from '@angular/core';
|
||||
import { HttpService } from '../http.service';
|
||||
import { Vorstellung } from '@infinimotion/model-frontend';
|
||||
import { Performance } from '../model/performance.model';
|
||||
import { MovieGroup } from '../model/movie-group.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-schedule',
|
||||
@@ -6,16 +10,23 @@ import { Component } from '@angular/core';
|
||||
templateUrl: './schedule.component.html',
|
||||
styleUrl: './schedule.component.css'
|
||||
})
|
||||
export class ScheduleComponent {
|
||||
dates: { label: string; date: Date }[] = [];
|
||||
export class ScheduleComponent implements OnInit {
|
||||
dates: { label: string; date: Date; performances: MovieGroup[] }[] = [];
|
||||
performaces: Vorstellung[] = [];
|
||||
|
||||
private http = inject(HttpService);
|
||||
|
||||
constructor() {
|
||||
this.generateDates();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.loadPerformances();
|
||||
}
|
||||
|
||||
generateDates() {
|
||||
const today = new Date();
|
||||
for (let i = 0; i < 31; i++) {
|
||||
for (let i = 0; i < 14; i++) {
|
||||
const date = new Date(today);
|
||||
date.setDate(today.getDate() + i);
|
||||
|
||||
@@ -28,12 +39,63 @@ dates: { label: string; date: Date }[] = [];
|
||||
label = date.toLocaleDateString('de-DE', { weekday: 'short' }) + '. ' + date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit'});
|
||||
}
|
||||
|
||||
this.dates.push({ label, date });
|
||||
this.dates.push({ label, date, performances: []});
|
||||
}
|
||||
}
|
||||
|
||||
loadPerformances() {
|
||||
this.http.getPerformaces().subscribe({
|
||||
next: (data) => {
|
||||
this.performaces = Array.isArray(data) ? data : [data];
|
||||
this.assignPerformancesToDates();
|
||||
},
|
||||
error: (err) => console.error('Fehler beim Laden der Performances', err),
|
||||
});
|
||||
}
|
||||
|
||||
assignPerformancesToDates() {
|
||||
|
||||
// Gruppieren nach Datum
|
||||
const groupedByDate: { [key: string]: Vorstellung[] } = {};
|
||||
for (const vorstellung of this.performaces) {
|
||||
const dateKey = new Date(vorstellung.start).toDateString();
|
||||
if (!groupedByDate[dateKey]) {
|
||||
groupedByDate[dateKey] = [];
|
||||
}
|
||||
groupedByDate[dateKey].push(vorstellung);
|
||||
}
|
||||
|
||||
// Gruppieren nach Film
|
||||
for (const dateInfo of this.dates) {
|
||||
const dateKey = dateInfo.date.toDateString();
|
||||
const dailyPerformances: Vorstellung[] = groupedByDate[dateKey] || [];
|
||||
|
||||
const movieMap = new Map<number, { movie: typeof dailyPerformances[0]['movie']; performances: Performance[] }>();
|
||||
|
||||
for (const perf of dailyPerformances) {
|
||||
const movieId = perf.movie.id;
|
||||
if (!movieMap.has(movieId)) {
|
||||
movieMap.set(movieId, { movie: perf.movie, performances: [] });
|
||||
}
|
||||
|
||||
const performance: Performance = {
|
||||
id: perf.id,
|
||||
hall: perf.hall.name,
|
||||
start: new Date(perf.start),
|
||||
// utilisation: 0 // TODO: perf.utilisation einrichten
|
||||
};
|
||||
movieMap.get(movieId)!.performances.push(performance);
|
||||
}
|
||||
|
||||
// MovieGroups erstellen
|
||||
dateInfo.performances = Array.from(movieMap.values()).map((entry) => ({
|
||||
movie: entry.movie,
|
||||
performances: entry.performances.sort((a, b) => a.start.getTime() - b.start.getTime()),
|
||||
})) as MovieGroup[];
|
||||
}
|
||||
}
|
||||
|
||||
getMovieCount(index: number): number {
|
||||
// Hier kannst du später die echten Filmzahlen zurückgeben
|
||||
return index === 0 ? 10 : index === 1 ? 0 : 4;
|
||||
return this.dates[index].performances.length;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user