196 lines
5.4 KiB
TypeScript
196 lines
5.4 KiB
TypeScript
import { Component, EventEmitter, inject, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { FormsModule } from '@angular/forms';
|
|
import { firstValueFrom } from 'rxjs';
|
|
|
|
import { Show } from '../interfaces/show';
|
|
import { ShowService, ShowScheduleCreate, ShowCreatePayload, ShowUpdatePayload } from '../services/show.service';
|
|
import { UploadService } from '../services/upload.service';
|
|
import { getAppConfig } from '../services/app-config.service';
|
|
|
|
@Component({
|
|
selector: 'app-admin-show-form',
|
|
standalone: true,
|
|
imports: [CommonModule, FormsModule],
|
|
templateUrl: './admin-show-form.component.html',
|
|
styleUrl: './admin-show-form.component.scss',
|
|
})
|
|
export class AdminShowFormComponent implements OnChanges {
|
|
private showService = inject(ShowService);
|
|
private uploadService = inject(UploadService);
|
|
|
|
@Input() editItem: Show | null = null;
|
|
@Output() saved = new EventEmitter<void>();
|
|
@Output() closed = new EventEmitter<void>();
|
|
|
|
readonly days = [
|
|
{ value: 1, label: 'Monday' },
|
|
{ value: 2, label: 'Tuesday' },
|
|
{ value: 3, label: 'Wednesday' },
|
|
{ value: 4, label: 'Thursday' },
|
|
{ value: 5, label: 'Friday' },
|
|
{ value: 6, label: 'Saturday' },
|
|
{ value: 7, label: 'Sunday' },
|
|
];
|
|
|
|
id: number | null = null;
|
|
title = '';
|
|
description = '';
|
|
host = '';
|
|
genre = '';
|
|
show_art_url = '';
|
|
hero_image_url = '';
|
|
display_order = 0;
|
|
schedules: ShowScheduleCreate[] = [
|
|
{ day_of_week: 1, time: '' },
|
|
];
|
|
|
|
loading = false;
|
|
uploading = false;
|
|
error = '';
|
|
success = '';
|
|
submitted = false;
|
|
|
|
ngOnChanges(changes: SimpleChanges): void {
|
|
if (changes['editItem'] && this.editItem) {
|
|
this.edit(this.editItem);
|
|
}
|
|
}
|
|
|
|
edit(show: Show): void {
|
|
this.id = show.id;
|
|
this.title = show.title;
|
|
this.description = show.description;
|
|
this.host = show.host;
|
|
this.genre = show.genre;
|
|
this.show_art_url = show.show_art_url || '';
|
|
this.hero_image_url = show.hero_image_url || '';
|
|
this.display_order = show.display_order;
|
|
this.schedules = show.schedules.map((s) => ({
|
|
day_of_week: s.day_of_week,
|
|
time: s.time,
|
|
}));
|
|
}
|
|
|
|
formatError(err: any): string {
|
|
if (err?.error?.detail) return err.error.detail;
|
|
if (err?.error?.message) return err.error.message;
|
|
if (err?.message) return err.message;
|
|
return 'Failed to save. Please try again.';
|
|
}
|
|
|
|
reset(): void {
|
|
this.id = null;
|
|
this.title = '';
|
|
this.description = '';
|
|
this.host = '';
|
|
this.genre = '';
|
|
this.show_art_url = '';
|
|
this.hero_image_url = '';
|
|
this.display_order = 0;
|
|
this.schedules = [{ day_of_week: 1, time: '' }];
|
|
this.error = '';
|
|
this.success = '';
|
|
this.submitted = false;
|
|
}
|
|
|
|
onClose(): void {
|
|
this.closed.emit();
|
|
this.reset();
|
|
}
|
|
|
|
/** Add a new empty schedule slot row. */
|
|
addScheduleSlot(): void {
|
|
this.schedules.push({ day_of_week: 1, time: '' });
|
|
}
|
|
|
|
/** Remove a schedule slot row by index. */
|
|
removeScheduleSlot(index: number): void {
|
|
this.schedules.splice(index, 1);
|
|
}
|
|
|
|
/** Handle image file upload and set the URL field. */
|
|
async onImageUpload(event: Event, field: string): Promise<void> {
|
|
const input = event.target as HTMLInputElement;
|
|
if (!input?.files?.length) return;
|
|
|
|
const file = input.files[0];
|
|
if (!file.type.startsWith('image/')) {
|
|
this.error = 'Only image files are allowed.';
|
|
return;
|
|
}
|
|
if (file.size > 5 * 1024 * 1024) {
|
|
this.error = 'File size exceeds 5 MB limit.';
|
|
return;
|
|
}
|
|
|
|
this.uploading = true;
|
|
this.error = '';
|
|
|
|
try {
|
|
const url = await this.uploadService.uploadImage(file);
|
|
(this as any)[field] = url;
|
|
this.success = 'Image uploaded successfully.';
|
|
} catch (err: any) {
|
|
this.error = this.formatError(err);
|
|
} finally {
|
|
this.uploading = false;
|
|
}
|
|
}
|
|
|
|
/** Resolve a relative uploads/… path to an absolute URL for preview. */
|
|
getPreviewUrl(url: string): string {
|
|
if (!url) return '';
|
|
if (url.startsWith('http://') || url.startsWith('https://')) return url;
|
|
return `${getAppConfig().apiBaseUrl}/${url}`;
|
|
}
|
|
|
|
async onSubmit(): Promise<void> {
|
|
this.submitted = true;
|
|
|
|
// Validate required fields
|
|
if (!this.title || !this.host || !this.genre) {
|
|
this.error = 'Please fill in all required fields.';
|
|
return;
|
|
}
|
|
|
|
// Validate schedules
|
|
const validSchedules = this.schedules.filter(
|
|
(s) => s.day_of_week && s.time,
|
|
);
|
|
if (validSchedules.length === 0) {
|
|
this.error = 'Add at least one schedule slot with a day and time.';
|
|
return;
|
|
}
|
|
|
|
this.loading = true;
|
|
this.error = '';
|
|
|
|
const payload = {
|
|
title: this.title,
|
|
description: this.description,
|
|
host: this.host,
|
|
genre: this.genre,
|
|
show_art_url: this.show_art_url || null,
|
|
hero_image_url: this.hero_image_url || null,
|
|
display_order: this.display_order,
|
|
schedules: validSchedules,
|
|
};
|
|
|
|
try {
|
|
if (this.id) {
|
|
const updatePayload: ShowUpdatePayload = { ...payload };
|
|
await firstValueFrom(this.showService.updateShow(this.id, updatePayload));
|
|
} else {
|
|
await firstValueFrom(this.showService.createShow(payload as ShowCreatePayload));
|
|
}
|
|
this.saved.emit();
|
|
this.reset();
|
|
} catch (err: any) {
|
|
this.error = this.formatError(err);
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
}
|
|
}
|