Remove Programs tab and all supporting code

The Programs feature is superseded by Shows, which provides a more
flexible model with schedule slots. This commit removes all Programs
code across frontend and backend:

Frontend:
- Delete admin-program-form component (ts/html/scss)
- Delete program.service.ts and program.ts interface
- Remove Programs tab, form modal, and all CRUD logic from admin
  component (ts + html)

Backend:
- Delete programs API router
- Remove Program ORM model and ProgramCreate/Update/Response schemas
- Remove programs router registration from main.py
- Remove Program from seed data, seed helpers, and truncate/reset
This commit is contained in:
2026-06-22 05:42:43 +00:00
parent eb9a00f21a
commit 9049b066ba
13 changed files with 7 additions and 622 deletions
+1 -2
View File
@@ -2,7 +2,7 @@ from fastapi import APIRouter
from app.config import settings from app.config import settings
from app.database import get_session from app.database import get_session
from app.models import DonationTier, Event, Program, StationConfig, Show, ShowSchedule from app.models import DonationTier, Event, StationConfig, Show, ShowSchedule
router = APIRouter() router = APIRouter()
@@ -19,7 +19,6 @@ async def reset_database():
async with async_session() as session: async with async_session() as session:
await session.execute(ShowSchedule.__table__.delete()) await session.execute(ShowSchedule.__table__.delete())
await session.execute(Show.__table__.delete()) await session.execute(Show.__table__.delete())
await session.execute(Program.__table__.delete())
await session.execute(Event.__table__.delete()) await session.execute(Event.__table__.delete())
await session.execute(DonationTier.__table__.delete()) await session.execute(DonationTier.__table__.delete())
await session.execute(StationConfig.__table__.delete()) await session.execute(StationConfig.__table__.delete())
-84
View File
@@ -1,84 +0,0 @@
from typing import Optional
from fastapi import APIRouter, Depends, Query
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
from app.auth import get_current_admin_user
from app.database import get_session
from app.models import Program
from app.schemas import ProgramCreate, ProgramUpdate, ProgramResponse
from app.user_models import User
router = APIRouter()
def _day_filter(day: int):
"""Return the SQLAlchemy filter for a given day_of_week."""
return Program.day_of_week == day
@router.get("/", response_model=list[ProgramResponse])
async def list_programs(
day: Optional[int] = Query(None, description="Filter by day_of_week (1=Mon … 7=Sun)"),
session: AsyncSession = Depends(get_session),
):
query = select(Program).order_by(Program.day_of_week, Program.time)
if day is not None:
query = query.filter(_day_filter(day))
result = await session.execute(query)
return list(result.scalars().all())
@router.get("/{program_id}", response_model=ProgramResponse)
async def get_program(program_id: int, session: AsyncSession = Depends(get_session)):
result = await session.execute(select(Program).where(Program.id == program_id))
program = result.scalar_one_or_none()
if not program:
return {"error": "Program not found"}
return program
@router.post("/", response_model=ProgramResponse, status_code=201)
async def create_program(
payload: ProgramCreate,
session: AsyncSession = Depends(get_session),
_: User = Depends(get_current_admin_user),
):
program = Program(**payload.model_dump())
session.add(program)
await session.commit()
await session.refresh(program)
return program
@router.put("/{program_id}", response_model=ProgramResponse)
async def update_program(
program_id: int,
payload: ProgramUpdate,
session: AsyncSession = Depends(get_session),
_: User = Depends(get_current_admin_user),
):
result = await session.execute(select(Program).where(Program.id == program_id))
program = result.scalar_one_or_none()
if not program:
return {"error": "Program not found"}
for key, value in payload.model_dump(exclude_unset=True).items():
setattr(program, key, value)
await session.commit()
await session.refresh(program)
return program
@router.delete("/{program_id}", status_code=204)
async def delete_program(
program_id: int,
session: AsyncSession = Depends(get_session),
_: User = Depends(get_current_admin_user),
):
result = await session.execute(select(Program).where(Program.id == program_id))
program = result.scalar_one_or_none()
if not program:
return {"error": "Program not found"}
await session.delete(program)
await session.commit()
+2 -3
View File
@@ -8,9 +8,9 @@ from sqlalchemy import func, select
from app.config import settings from app.config import settings
from app.database import async_session, engine from app.database import async_session, engine
from app.models import Base, Program, Show, StationConfig, HistoryEntry, TeamMember, CommunityHighlight from app.models import Base, Show, StationConfig, HistoryEntry, TeamMember, CommunityHighlight
from app.user_models import User from app.user_models import User
from app.api import programs, events, tiers, auth, station_config, history, team, community, shows, upload from app.api import events, tiers, auth, station_config, history, team, community, shows, upload
async def _ensure_station_config(session): async def _ensure_station_config(session):
@@ -92,7 +92,6 @@ app.add_middleware(
# Register routers # Register routers
app.include_router(auth.router, prefix="/api/auth", tags=["auth"]) app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
app.include_router(programs.router, prefix="/api/programs", tags=["programs"])
app.include_router(events.router, prefix="/api/events", tags=["events"]) app.include_router(events.router, prefix="/api/events", tags=["events"])
app.include_router(tiers.router, prefix="/api/tiers", tags=["tiers"]) app.include_router(tiers.router, prefix="/api/tiers", tags=["tiers"])
app.include_router(station_config.router, prefix="/api/station-config", tags=["station-config"]) app.include_router(station_config.router, prefix="/api/station-config", tags=["station-config"])
-12
View File
@@ -17,18 +17,6 @@ class Base(DeclarativeBase):
pass pass
class Program(Base):
__tablename__ = "programs"
id = Column(Integer, primary_key=True, autoincrement=True)
day_of_week = Column(Integer, nullable=False) # 1=Mon … 7=Sun
day_label = Column(String(10), nullable=False) # "Monday", …
time = Column(String(10), nullable=False) # "6:00 AM"
title = Column(String(200), nullable=False)
host = Column(String(100), nullable=False)
genre = Column(String(80), nullable=False)
class Show(Base): class Show(Base):
__tablename__ = "shows" __tablename__ = "shows"
+1 -35
View File
@@ -4,7 +4,7 @@ from typing import Optional
from pydantic import BaseModel, computed_field, field_validator from pydantic import BaseModel, computed_field, field_validator
# ── Program ────────────────────────────────────────────── # ── Shared ───────────────────────────────────────────────
DAY_NAMES = { DAY_NAMES = {
1: "Monday", 1: "Monday",
@@ -17,40 +17,6 @@ DAY_NAMES = {
} }
class ProgramCreate(BaseModel):
day_of_week: int
day_label: str
time: str
title: str
host: str
genre: str
class ProgramUpdate(BaseModel):
day_of_week: Optional[int] = None
day_label: Optional[str] = None
time: Optional[str] = None
title: Optional[str] = None
host: Optional[str] = None
genre: Optional[str] = None
class ProgramResponse(BaseModel):
id: int
day_of_week: int
time: str
title: str
host: str
genre: str
@computed_field
@property
def day_label(self) -> str:
return DAY_NAMES.get(self.day_of_week, "Unknown")
model_config = {"from_attributes": True}
# ── Event ──────────────────────────────────────────────── # ── Event ────────────────────────────────────────────────
class EventCreate(BaseModel): class EventCreate(BaseModel):
+1 -36
View File
@@ -14,24 +14,11 @@ from datetime import date
from sqlalchemy import select from sqlalchemy import select
from app.database import async_session from app.database import async_session
from app.models import Program, Show, ShowSchedule, Event, DonationTier, StationConfig from app.models import Show, ShowSchedule, Event, DonationTier, StationConfig
from app.models import HistoryEntry, TeamMember, CommunityHighlight from app.models import HistoryEntry, TeamMember, CommunityHighlight
# ── Seed data ────────────────────────────────────────────── # ── Seed data ──────────────────────────────────────────────
PROGRAMS: list[dict] = [
{"day_of_week": 1, "day_label": "Monday", "time": "6:00 AM", "title": "Morning Mountain Mist", "host": "Diana Walsh", "genre": "Ambient / Nature"},
{"day_of_week": 1, "day_label": "Monday", "time": "8:00 AM", "title": "Community Roundup", "host": "Tom Breen", "genre": "News / Talk"},
{"day_of_week": 1, "day_label": "Monday", "time": "10:00 AM", "title": "Bluegrass Trails", "host": "Sarah Lynn", "genre": "Bluegrass"},
{"day_of_week": 1, "day_label": "Monday", "time": "12:00 PM", "title": "Lunchtime Jazz", "host": "Mike Darrow", "genre": "Jazz"},
{"day_of_week": 1, "day_label": "Monday", "time": "2:00 PM", "title": "Folk Roots Hour", "host": "Nadia Cole", "genre": "Folk"},
{"day_of_week": 1, "day_label": "Monday", "time": "4:00 PM", "title": "Afternoon Acoustics", "host": "Jen Reeves", "genre": "Acoustic"},
{"day_of_week": 1, "day_label": "Monday", "time": "6:00 PM", "title": "Evening Echoes", "host": "Carlos Mendez", "genre": "Indie / Alternative"},
{"day_of_week": 1, "day_label": "Monday", "time": "8:00 PM", "title": "Classical Mountains", "host": "Elena Cross", "genre": "Classical"},
{"day_of_week": 1, "day_label": "Monday", "time": "10:00 PM", "title": "Night Owl Session", "host": "DJ Kofi", "genre": "Electronic"},
{"day_of_week": 1, "day_label": "Monday", "time": "12:00 AM", "title": "Late Night Jazz", "host": "Mike Darrow", "genre": "Jazz"},
]
SHOWS: list[dict] = [ SHOWS: list[dict] = [
{ {
"title": "Morning Mountain Mist", "title": "Morning Mountain Mist",
@@ -345,22 +332,6 @@ COMMUNITY_HIGHLIGHTS: list[dict] = [
# ── Helpers ──────────────────────────────────────────────── # ── Helpers ────────────────────────────────────────────────
async def _upsert_program(session, data: dict) -> None:
"""Get-or-create a program matched on (day_of_week, time)."""
existing = await session.execute(
select(Program).where(
Program.day_of_week == data["day_of_week"],
Program.time == data["time"],
)
)
program = existing.scalar_one_or_none()
if program:
for key, value in data.items():
setattr(program, key, value)
else:
session.add(Program(**data))
async def _upsert_event(session, data: dict) -> None: async def _upsert_event(session, data: dict) -> None:
"""Get-or-create an event matched on (date, title).""" """Get-or-create an event matched on (date, title)."""
existing = await session.execute( existing = await session.execute(
@@ -476,11 +447,6 @@ async def _upsert_community_highlight(session, data: dict) -> None:
async def seed() -> None: async def seed() -> None:
"""Populate the database with station data (idempotent).""" """Populate the database with station data (idempotent)."""
async with async_session() as session: async with async_session() as session:
for data in PROGRAMS:
await _upsert_program(session, data)
await session.commit()
print(f" ✓ Seeded {len(PROGRAMS)} programs")
for data in EVENTS: for data in EVENTS:
await _upsert_event(session, data) await _upsert_event(session, data)
await session.commit() await session.commit()
@@ -524,7 +490,6 @@ async def truncate_all() -> None:
# Delete child tables first to respect foreign keys # Delete child tables first to respect foreign keys
await session.execute(ShowSchedule.__table__.delete()) await session.execute(ShowSchedule.__table__.delete())
await session.execute(Show.__table__.delete()) await session.execute(Show.__table__.delete())
await session.execute(Program.__table__.delete())
await session.execute(Event.__table__.delete()) await session.execute(Event.__table__.delete())
await session.execute(DonationTier.__table__.delete()) await session.execute(DonationTier.__table__.delete())
await session.execute(StationConfig.__table__.delete()) await session.execute(StationConfig.__table__.delete())
@@ -1,54 +0,0 @@
<div class="form-overlay" (click)="onClose()">
<div class="form-dialog" (click)="$event.stopPropagation()">
<div class="form-header">
<h2>{{ id ? 'Edit Program' : 'Add Program' }}</h2>
<button type="button" class="btn-close" (click)="onClose()">&times;</button>
</div>
@if (error) {
<div class="form-error" role="alert">{{ error }}</div>
}
<form (ngSubmit)="onSubmit()" class="admin-form" #form="ngForm">
<div class="form-row">
<div class="form-group">
<label for="day">Day</label>
<select id="day" [(ngModel)]="day_of_week" name="day" (change)="onDayChange()" required>
@for (day of days; track day.value) {
<option [value]="day.value">{{ day.label }}</option>
}
</select>
</div>
<div class="form-group">
<label for="time">Time</label>
<input id="time" type="text" [(ngModel)]="time" name="time" placeholder="6:00 AM" required [class.is-invalid]="submitted && !time">
</div>
</div>
<div class="form-group">
<label for="title">Title</label>
<input id="title" type="text" [(ngModel)]="title" name="title" placeholder="Program title" required [class.is-invalid]="submitted && !title">
</div>
<div class="form-row">
<div class="form-group">
<label for="host">Host</label>
<input id="host" type="text" [(ngModel)]="host" name="host" placeholder="Host name" required [class.is-invalid]="submitted && !host">
</div>
<div class="form-group">
<label for="genre">Genre</label>
<input id="genre" type="text" [(ngModel)]="genre" name="genre" placeholder="Genre" required [class.is-invalid]="submitted && !genre">
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-cancel" (click)="onClose()">Cancel</button>
<button type="submit" class="btn btn-save" [disabled]="loading">
{{ loading ? 'Saving…' : (id ? 'Update' : 'Create') }}
</button>
</div>
</form>
</div>
</div>
@@ -1,129 +0,0 @@
@use '../../styles/variables' as *;
@use '../../styles/mixins' as *;
.form-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
@include flex-center;
z-index: $z-modal;
@include fade-in;
}
.form-dialog {
background: $neutral-white;
border-radius: $radius-lg;
padding: $spacing-xl;
width: 90%;
max-width: 560px;
box-shadow: $shadow-xl;
}
.form-header {
@include flex-between;
margin-bottom: $spacing-lg;
h2 {
font-family: $font-heading;
color: $primary-blue;
margin: 0;
font-size: 1.4rem;
}
}
.btn-close {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: $neutral-medium;
line-height: 1;
padding: $spacing-xs;
&:hover {
color: $neutral-dark;
}
}
.form-error {
background: rgba($danger-red, 0.1);
color: $danger-red;
padding: $spacing-sm $spacing-md;
border-radius: $radius-md;
font-size: 0.875rem;
margin-bottom: $spacing-md;
text-align: center;
}
.admin-form {
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: $spacing-md;
}
.form-group {
margin-bottom: $spacing-md;
label {
display: block;
font-size: 0.875rem;
font-weight: 600;
color: $neutral-dark;
margin-bottom: $spacing-xs;
}
input, select, textarea {
width: 100%;
padding: $spacing-sm $spacing-md;
border: 1px solid $neutral-light;
border-radius: $radius-md;
font-size: 0.95rem;
transition: border-color $transition-fast;
box-sizing: border-box;
&:focus {
outline: none;
border-color: $primary-blue;
box-shadow: 0 0 0 3px rgba($primary-blue, 0.15);
}
&.is-invalid {
border-color: $danger-red;
box-shadow: 0 0 0 3px rgba($danger-red, 0.15);
}
}
}
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: $spacing-sm;
margin-top: $spacing-md;
}
.btn-save {
@include button-style($primary-blue, $neutral-white, $primary-blue-light);
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.btn-cancel {
background: $neutral-light;
color: $neutral-dark;
border: none;
border-radius: $radius-md;
padding: $spacing-sm $spacing-lg;
font-size: 0.95rem;
cursor: pointer;
transition: background $transition-fast;
&:hover {
background: $neutral-medium;
color: $neutral-white;
}
}
@@ -1,126 +0,0 @@
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 { Program } from '../interfaces/program';
import { ProgramService } from '../services/program.service';
@Component({
selector: 'app-admin-program-form',
standalone: true,
imports: [CommonModule, FormsModule],
templateUrl: './admin-program-form.component.html',
styleUrl: './admin-program-form.component.scss',
})
export class AdminProgramFormComponent implements OnChanges {
private programService = inject(ProgramService);
@Input() editItem: Program | 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;
day_of_week = 1;
day_label = 'Monday';
time = '';
title = '';
host = '';
genre = '';
loading = false;
error = '';
submitted = false;
ngOnChanges(changes: SimpleChanges): void {
if (changes['editItem'] && this.editItem) {
this.edit(this.editItem);
}
}
/** Populate form for editing an existing program. */
edit(program: Program): void {
this.id = program.id;
this.day_of_week = program.day_of_week;
this.day_label = program.day_label;
this.time = program.time;
this.title = program.title;
this.host = program.host;
this.genre = program.genre;
}
/** Reset the form for a new entry. */
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.day_of_week = 1;
this.day_label = 'Monday';
this.time = '';
this.title = '';
this.host = '';
this.genre = '';
this.error = '';
this.submitted = false;
}
/** Close the dialog without saving. */
onClose(): void {
this.closed.emit();
this.reset();
}
async onSubmit(): Promise<void> {
this.submitted = true;
if (!this.title || !this.time || !this.host || !this.genre) {
this.error = 'Please fill in all required fields.';
return;
}
this.loading = true;
this.error = '';
const payload = {
day_of_week: this.day_of_week,
day_label: this.day_label,
time: this.time,
title: this.title,
host: this.host,
genre: this.genre,
};
try {
if (this.id) {
await firstValueFrom(this.programService.updateProgram(this.id, payload));
} else {
await firstValueFrom(this.programService.createProgram(payload));
}
this.saved.emit();
this.reset();
} catch (err: any) {
this.error = this.formatError(err);
} finally {
this.loading = false;
}
}
onDayChange(): void {
const day = this.days.find((d) => d.value === this.day_of_week);
if (day) this.day_label = day.label;
}
}
+1 -51
View File
@@ -2,7 +2,7 @@
<div class="container"> <div class="container">
<div class="admin-header"> <div class="admin-header">
<h1>Admin Dashboard</h1> <h1>Admin Dashboard</h1>
<p>Manage shows, programs, events, tiers, history, team, and community content</p> <p>Manage shows, events, tiers, history, team, and community content</p>
</div> </div>
<!-- Tabs --> <!-- Tabs -->
@@ -12,11 +12,6 @@
[class.active]="activeTab() === 'shows'" [class.active]="activeTab() === 'shows'"
(click)="activeTab.set('shows')" (click)="activeTab.set('shows')"
>Shows</button> >Shows</button>
<button
class="tab-btn"
[class.active]="activeTab() === 'programs'"
(click)="activeTab.set('programs')"
>Programs</button>
<button <button
class="tab-btn" class="tab-btn"
[class.active]="activeTab() === 'events'" [class.active]="activeTab() === 'events'"
@@ -95,48 +90,6 @@
} }
} }
<!-- Programs Tab -->
@if (activeTab() === 'programs') {
@if (programs$ | async; as programs) {
<div class="tab-content">
<div class="tab-toolbar">
<h2>Programs ({{ programs.length }})</h2>
<button class="btn btn-add" (click)="openProgramForm()">+ Add Program</button>
</div>
<table class="admin-table">
<thead>
<tr>
<th>Day</th>
<th>Time</th>
<th>Title</th>
<th>Host</th>
<th>Genre</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@for (program of programs; track program.id) {
<tr>
<td>{{ program.day_label }}</td>
<td>{{ program.time }}</td>
<td>{{ program.title }}</td>
<td>{{ program.host }}</td>
<td>{{ program.genre }}</td>
<td class="actions">
<button class="btn-icon btn-edit" (click)="editProgram(program)">Edit</button>
<button class="btn-icon btn-delete" (click)="deleteProgram(program.id)">Delete</button>
</td>
</tr>
} @empty {
<tr><td colspan="6" class="empty-row">No programs yet. Click "Add Program" to create one.</td></tr>
}
</tbody>
</table>
</div>
}
}
<!-- Events Tab --> <!-- Events Tab -->
@if (activeTab() === 'events') { @if (activeTab() === 'events') {
@if (events$ | async; as events) { @if (events$ | async; as events) {
@@ -393,9 +346,6 @@
@if (showShowForm) { @if (showShowForm) {
<app-admin-show-form [editItem]="editingShow" (saved)="onShowSaved()" (closed)="onShowClosed()" /> <app-admin-show-form [editItem]="editingShow" (saved)="onShowSaved()" (closed)="onShowClosed()" />
} }
@if (showProgramForm) {
<app-admin-program-form [editItem]="editingProgram" (saved)="onProgramSaved()" (closed)="onProgramClosed()" />
}
@if (showEventForm) { @if (showEventForm) {
<app-admin-event-form [editItem]="editingEvent" (saved)="onEventSaved()" (closed)="onEventClosed()" /> <app-admin-event-form [editItem]="editingEvent" (saved)="onEventSaved()" (closed)="onEventClosed()" />
} }
+1 -44
View File
@@ -3,7 +3,6 @@ import { CommonModule, AsyncPipe } from '@angular/common';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { BehaviorSubject, firstValueFrom } from 'rxjs'; import { BehaviorSubject, firstValueFrom } from 'rxjs';
import { Program } from '../interfaces/program';
import { Show } from '../interfaces/show'; import { Show } from '../interfaces/show';
import { Event } from '../interfaces/event'; import { Event } from '../interfaces/event';
import { Tier } from '../interfaces/tier'; import { Tier } from '../interfaces/tier';
@@ -11,7 +10,6 @@ import { StationConfig } from '../interfaces/station-config';
import { HistoryEntry } from '../interfaces/history-entry'; import { HistoryEntry } from '../interfaces/history-entry';
import { TeamMember } from '../interfaces/team-member'; import { TeamMember } from '../interfaces/team-member';
import { CommunityHighlight } from '../interfaces/community-highlight'; import { CommunityHighlight } from '../interfaces/community-highlight';
import { ProgramService } from '../services/program.service';
import { ShowService } from '../services/show.service'; import { ShowService } from '../services/show.service';
import { EventService } from '../services/event.service'; import { EventService } from '../services/event.service';
import { TierService } from '../services/tier.service'; import { TierService } from '../services/tier.service';
@@ -19,7 +17,6 @@ import { StationConfigService } from '../services/station-config.service';
import { HistoryEntryService } from '../services/history-entry.service'; import { HistoryEntryService } from '../services/history-entry.service';
import { TeamMemberService } from '../services/team-member.service'; import { TeamMemberService } from '../services/team-member.service';
import { CommunityHighlightService } from '../services/community-highlight.service'; import { CommunityHighlightService } from '../services/community-highlight.service';
import { AdminProgramFormComponent } from './admin-program-form.component';
import { AdminShowFormComponent } from './admin-show-form.component'; import { AdminShowFormComponent } from './admin-show-form.component';
import { AdminEventFormComponent } from './admin-event-form.component'; import { AdminEventFormComponent } from './admin-event-form.component';
import { AdminTierFormComponent } from './admin-tier-form.component'; import { AdminTierFormComponent } from './admin-tier-form.component';
@@ -28,7 +25,7 @@ import { AdminHistoryFormComponent } from './admin-history-form.component';
import { AdminTeamFormComponent } from './admin-team-form.component'; import { AdminTeamFormComponent } from './admin-team-form.component';
import { AdminCommunityFormComponent } from './admin-community-form.component'; import { AdminCommunityFormComponent } from './admin-community-form.component';
type TabKey = 'shows' | 'programs' | 'events' | 'tiers' | 'station' | 'history' | 'team' | 'community'; type TabKey = 'shows' | 'events' | 'tiers' | 'station' | 'history' | 'team' | 'community';
@Component({ @Component({
selector: 'app-admin', selector: 'app-admin',
@@ -38,7 +35,6 @@ type TabKey = 'shows' | 'programs' | 'events' | 'tiers' | 'station' | 'history'
AsyncPipe, AsyncPipe,
FormsModule, FormsModule,
AdminShowFormComponent, AdminShowFormComponent,
AdminProgramFormComponent,
AdminEventFormComponent, AdminEventFormComponent,
AdminTierFormComponent, AdminTierFormComponent,
AdminStationFormComponent, AdminStationFormComponent,
@@ -50,7 +46,6 @@ type TabKey = 'shows' | 'programs' | 'events' | 'tiers' | 'station' | 'history'
styleUrl: './admin.component.scss', styleUrl: './admin.component.scss',
}) })
export class AdminComponent implements OnInit { export class AdminComponent implements OnInit {
private programService = inject(ProgramService);
private showService = inject(ShowService); private showService = inject(ShowService);
private eventService = inject(EventService); private eventService = inject(EventService);
private tierService = inject(TierService); private tierService = inject(TierService);
@@ -63,7 +58,6 @@ export class AdminComponent implements OnInit {
/** Item currently being edited (set by editXxx, cleared on save/close). */ /** Item currently being edited (set by editXxx, cleared on save/close). */
editingShow: Show | null = null; editingShow: Show | null = null;
editingProgram: Program | null = null;
editingEvent: Event | null = null; editingEvent: Event | null = null;
editingTier: Tier | null = null; editingTier: Tier | null = null;
editingHistory: HistoryEntry | null = null; editingHistory: HistoryEntry | null = null;
@@ -72,7 +66,6 @@ export class AdminComponent implements OnInit {
/** BehaviorSubjects guarantee change detection via the async pipe — start empty, populated on load. */ /** BehaviorSubjects guarantee change detection via the async pipe — start empty, populated on load. */
readonly shows$ = new BehaviorSubject<Show[]>([]); readonly shows$ = new BehaviorSubject<Show[]>([]);
readonly programs$ = new BehaviorSubject<Program[]>([]);
readonly events$ = new BehaviorSubject<Event[]>([]); readonly events$ = new BehaviorSubject<Event[]>([]);
readonly tiers$ = new BehaviorSubject<Tier[]>([]); readonly tiers$ = new BehaviorSubject<Tier[]>([]);
readonly stationConfig$ = new BehaviorSubject<StationConfig | null>(null); readonly stationConfig$ = new BehaviorSubject<StationConfig | null>(null);
@@ -81,7 +74,6 @@ export class AdminComponent implements OnInit {
readonly communityHighlights$ = new BehaviorSubject<CommunityHighlight[]>([]); readonly communityHighlights$ = new BehaviorSubject<CommunityHighlight[]>([]);
showShowForm = false; showShowForm = false;
showProgramForm = false;
showEventForm = false; showEventForm = false;
showTierForm = false; showTierForm = false;
showStationForm = false; showStationForm = false;
@@ -98,11 +90,6 @@ export class AdminComponent implements OnInit {
this.shows$.next(data); this.shows$.next(data);
} }
async reloadPrograms(): Promise<void> {
const data = await firstValueFrom(this.programService.getPrograms());
this.programs$.next(data);
}
async reloadEvents(): Promise<void> { async reloadEvents(): Promise<void> {
const data = await firstValueFrom(this.eventService.getEvents()); const data = await firstValueFrom(this.eventService.getEvents());
this.events$.next(data); this.events$.next(data);
@@ -136,7 +123,6 @@ export class AdminComponent implements OnInit {
async loadAll(): Promise<void> { async loadAll(): Promise<void> {
await Promise.all([ await Promise.all([
this.reloadShows(), this.reloadShows(),
this.reloadPrograms(),
this.reloadEvents(), this.reloadEvents(),
this.reloadTiers(), this.reloadTiers(),
this.reloadStationConfig(), this.reloadStationConfig(),
@@ -175,35 +161,6 @@ export class AdminComponent implements OnInit {
await this.reloadShows(); await this.reloadShows();
} }
// ── Program CRUD ────────────────────────────────────────
openProgramForm(): void {
this.editingProgram = null;
this.showProgramForm = true;
}
editProgram(program: Program): void {
this.editingProgram = program;
this.showProgramForm = true;
}
onProgramSaved(): void {
this.reloadPrograms();
this.showProgramForm = false;
this.editingProgram = null;
}
onProgramClosed(): void {
this.showProgramForm = false;
this.editingProgram = null;
}
async deleteProgram(id: number): Promise<void> {
if (!confirm('Delete this program?')) return;
await firstValueFrom(this.programService.deleteProgram(id));
await this.reloadPrograms();
}
// ── Event CRUD ────────────────────────────────────────── // ── Event CRUD ──────────────────────────────────────────
openEventForm(): void { openEventForm(): void {
-9
View File
@@ -1,9 +0,0 @@
export interface Program {
id: number;
day_of_week: number;
day_label: string;
time: string;
title: string;
host: string;
genre: string;
}
-37
View File
@@ -1,37 +0,0 @@
import { Injectable, inject } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Program } from '../interfaces/program';
import { getAppConfig } from './app-config.service';
@Injectable({
providedIn: 'root',
})
export class ProgramService {
private http = inject(HttpClient);
private baseUrl = `${getAppConfig().apiBaseUrl}/api/programs`;
/** Fetch all programs, optionally filtered by day_of_week (1=Mon … 7=Sun). */
getPrograms(day?: number): Observable<Program[]> {
let params = new HttpParams();
if (day !== undefined) params = params.set('day', String(day));
return this.http.get<Program[]>(this.baseUrl, { params });
}
getProgram(id: number): Observable<Program> {
return this.http.get<Program>(`${this.baseUrl}/${id}`);
}
createProgram(payload: Omit<Program, 'id'>): Observable<Program> {
return this.http.post<Program>(this.baseUrl, payload);
}
updateProgram(id: number, payload: Partial<Program>): Observable<Program> {
return this.http.put<Program>(`${this.baseUrl}/${id}`, payload);
}
deleteProgram(id: number): Observable<void> {
return this.http.delete<void>(`${this.baseUrl}/${id}`);
}
}