diff --git a/frontend/src/app/pages/show-list-page.component.ts b/frontend/src/app/pages/show-list-page.component.ts index 1b85733..280f548 100644 --- a/frontend/src/app/pages/show-list-page.component.ts +++ b/frontend/src/app/pages/show-list-page.component.ts @@ -1,49 +1,75 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, DestroyRef, inject, signal } from '@angular/core'; import { RouterLink } from '@angular/router'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; -import { MatChipsModule } from '@angular/material/chips'; +import { MatIconModule } from '@angular/material/icon'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; -type DemoShow = { - slug: string; - title: string; - summary: string; - venue: string; - startsAt: string; -}; +import { ShowListItem, ShowsApiService } from '../services/shows-api.service'; @Component({ standalone: true, - imports: [RouterLink, MatButtonModule, MatCardModule, MatChipsModule], + imports: [RouterLink, MatButtonModule, MatCardModule, MatIconModule, MatProgressSpinnerModule], template: `
-
- @for (show of demoShows; track show.slug) { + @if (isLoading()) { +
+ +

Loading shows...

+
+ } @else if (errorMessage()) { + + +
+ error +
+

Could not load shows

+

{{ errorMessage() }}

+
+
+
+ + + +
+ } @else if (shows().length === 0) { + + +
+ theaters +
+

No shows published yet

+

Published productions will appear here as soon as they are available.

+
+
+
+
+ } @else { +
+ @for (show of shows(); track show.slug) { {{ show.title }} - {{ show.venue }}

{{ show.summary }}

- - {{ show.startsAt }} -
Open detail
- } -
+ } +
+ }
`, styles: [` @@ -85,13 +111,56 @@ type DemoShow = { gap: 20px; } - .show-card { + .status-panel, + .status-copy { + display: flex; + align-items: center; + gap: 16px; + } + + .status-panel { + min-height: 220px; + justify-content: center; + color: var(--azionelab-muted); + } + + .status-card { + max-width: 680px; border-radius: 8px; border: 1px solid var(--azionelab-border); background: var(--azionelab-surface); box-shadow: var(--azionelab-shadow); } + .status-copy { + align-items: flex-start; + } + + .status-copy h2 { + margin: 0 0 8px; + font-size: 1.2rem; + } + + .status-copy p { + margin: 0; + color: var(--azionelab-muted); + line-height: 1.6; + } + + .show-card { + display: flex; + flex-direction: column; + min-height: 220px; + border-radius: 8px; + border: 1px solid var(--azionelab-border); + background: var(--azionelab-surface); + box-shadow: var(--azionelab-shadow); + } + + .show-card mat-card-content { + flex: 1; + } + .show-card p { color: var(--azionelab-muted); line-height: 1.6; @@ -106,20 +175,37 @@ type DemoShow = { changeDetection: ChangeDetectionStrategy.OnPush, }) export class ShowListPageComponent { - protected readonly demoShows: DemoShow[] = [ - { - slug: 'open-stage', - title: 'Open Stage', - summary: 'Placeholder list item for the first published production.', - venue: 'AzioneLab Theatre', - startsAt: 'May 15, 20:30', - }, - { - slug: 'city-echoes', - title: 'City Echoes', - summary: 'Second sample entry showing how cards will map to live API data.', - venue: 'Studio Nuovo', - startsAt: 'May 22, 18:00', - }, - ]; + private readonly destroyRef = inject(DestroyRef); + private readonly showsApi = inject(ShowsApiService); + + protected readonly shows = signal([]); + protected readonly isLoading = signal(true); + protected readonly errorMessage = signal(''); + + constructor() { + this.loadShows(); + } + + protected reload(): void { + this.loadShows(); + } + + private loadShows(): void { + this.isLoading.set(true); + this.errorMessage.set(''); + + this.showsApi.listShows() + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe({ + next: ({ results }) => { + this.shows.set(results); + this.isLoading.set(false); + }, + error: () => { + this.shows.set([]); + this.errorMessage.set('Please try again in a moment.'); + this.isLoading.set(false); + }, + }); + } } diff --git a/frontend/src/app/services/shows-api.service.ts b/frontend/src/app/services/shows-api.service.ts new file mode 100644 index 0000000..86e4b45 --- /dev/null +++ b/frontend/src/app/services/shows-api.service.ts @@ -0,0 +1,29 @@ +import { inject, Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { API_BASE_URL } from './api-config.token'; + +export type ShowListItem = { + id: number; + title: string; + slug: string; + summary: string; + poster_image: string; +}; + +type ShowListResponse = { + results: ShowListItem[]; +}; + +@Injectable({ + providedIn: 'root', +}) +export class ShowsApiService { + private readonly http = inject(HttpClient); + private readonly apiBaseUrl = inject(API_BASE_URL); + + listShows(): Observable { + return this.http.get(`${this.apiBaseUrl}/shows/`); + } +}