Backend: - /api/admin/users: GET list, POST create, PUT update, DELETE - Admin users can be added with email, display name, and password - Self-deletion and last-admin deletion are blocked - Admin role auto-assigned on creation Frontend: - AdminUsersTabComponent: list + add/delete admin users - AdminUserService: HTTP client for admin user CRUD - AdminUser interface - 'Admins' tab added to admin dashboard Tests: - 10 new tests for admin user management endpoints - 140 total tests pass (130 original + 10 new)
242 lines
7.5 KiB
TypeScript
242 lines
7.5 KiB
TypeScript
import { Component, inject, OnInit, signal, ViewEncapsulation } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
|
|
import { Show } from '../interfaces/show';
|
|
import { Event as StationEvent } from '../interfaces/event';
|
|
import { HistoryEntry } from '../interfaces/history-entry';
|
|
import { TeamMember } from '../interfaces/team-member';
|
|
import { CommunityHighlight } from '../interfaces/community-highlight';
|
|
import { Underwriter } from '../interfaces/underwriter';
|
|
import { StationConfig } from '../interfaces/station-config';
|
|
import { StationConfigService } from '../services/station-config.service';
|
|
import { AdminShowsTabComponent } from './admin-shows-tab.component';
|
|
import { AdminEventsTabComponent } from './admin-events-tab.component';
|
|
import { AdminHistoryTabComponent } from './admin-history-tab.component';
|
|
import { AdminTeamTabComponent } from './admin-team-tab.component';
|
|
import { AdminCommunityTabComponent } from './admin-community-tab.component';
|
|
import { AdminStationTabComponent } from './admin-station-tab.component';
|
|
import { AdminThemeTabComponent } from './admin-theme-tab.component';
|
|
import { AdminUnderwritersTabComponent } from './admin-underwriters-tab.component';
|
|
import { AdminMobileBuildsTabComponent } from './admin-mobile-builds-tab.component';
|
|
import { AdminShowFormComponent } from './admin-show-form.component';
|
|
import { AdminEventFormComponent } from './admin-event-form.component';
|
|
import { AdminStationFormComponent } from './admin-station-form.component';
|
|
import { AdminHistoryFormComponent } from './admin-history-form.component';
|
|
import { AdminTeamFormComponent } from './admin-team-form.component';
|
|
import { AdminCommunityFormComponent } from './admin-community-form.component';
|
|
import { AdminUnderwriterFormComponent } from './admin-underwriter-form.component';
|
|
import { AdminStatsDashboardComponent } from './admin-stats-dashboard.component';
|
|
import { AdminUsersTabComponent } from './admin-users-tab.component';
|
|
|
|
type TabKey =
|
|
| 'shows'
|
|
| 'events'
|
|
| 'history'
|
|
| 'team'
|
|
| 'community'
|
|
| 'station'
|
|
| 'theme'
|
|
| 'underwriters'
|
|
| 'mobile-builds'
|
|
| 'stats'
|
|
| 'admins';
|
|
|
|
@Component({
|
|
selector: 'app-admin',
|
|
standalone: true,
|
|
encapsulation: ViewEncapsulation.None,
|
|
imports: [
|
|
CommonModule,
|
|
AdminShowsTabComponent,
|
|
AdminEventsTabComponent,
|
|
AdminHistoryTabComponent,
|
|
AdminTeamTabComponent,
|
|
AdminCommunityTabComponent,
|
|
AdminStationTabComponent,
|
|
AdminThemeTabComponent,
|
|
AdminUnderwritersTabComponent,
|
|
AdminMobileBuildsTabComponent,
|
|
AdminStatsDashboardComponent,
|
|
AdminUsersTabComponent,
|
|
AdminShowFormComponent,
|
|
AdminEventFormComponent,
|
|
AdminStationFormComponent,
|
|
AdminHistoryFormComponent,
|
|
AdminTeamFormComponent,
|
|
AdminCommunityFormComponent,
|
|
AdminUnderwriterFormComponent,
|
|
],
|
|
templateUrl: './admin.component.html',
|
|
styleUrl: './admin.component.scss',
|
|
})
|
|
export class AdminComponent implements OnInit {
|
|
private stationConfigService = inject(StationConfigService);
|
|
|
|
activeTab = signal<TabKey>('shows');
|
|
|
|
/** Station config for the station form modal. */
|
|
stationConfig: StationConfig | null = null;
|
|
|
|
ngOnInit(): void {
|
|
this.stationConfigService.load().then(() => {
|
|
this.stationConfig = this.stationConfigService.config();
|
|
});
|
|
}
|
|
|
|
// ── Form modal state ──────────────────────────────────────
|
|
showShowForm = false;
|
|
showEventForm = false;
|
|
showStationForm = false;
|
|
showHistoryForm = false;
|
|
showTeamForm = false;
|
|
showCommunityForm = false;
|
|
showUnderwriterForm = false;
|
|
|
|
editingShow: Show | null = null;
|
|
editingEvent: StationEvent | null = null;
|
|
editingHistory: HistoryEntry | null = null;
|
|
editingTeam: TeamMember | null = null;
|
|
editingCommunity: CommunityHighlight | null = null;
|
|
editingUnderwriter: Underwriter | null = null;
|
|
|
|
// ── Show form ─────────────────────────────────────────────
|
|
openShowForm(): void {
|
|
this.editingShow = null;
|
|
this.showShowForm = true;
|
|
}
|
|
|
|
editShow(show: Show): void {
|
|
this.editingShow = show;
|
|
this.showShowForm = true;
|
|
}
|
|
|
|
onShowSaved(): void {
|
|
this.showShowForm = false;
|
|
this.editingShow = null;
|
|
}
|
|
|
|
onShowClosed(): void {
|
|
this.showShowForm = false;
|
|
this.editingShow = null;
|
|
}
|
|
|
|
// ── Event form ────────────────────────────────────────────
|
|
openEventForm(): void {
|
|
this.editingEvent = null;
|
|
this.showEventForm = true;
|
|
}
|
|
|
|
editEvent(event: StationEvent): void {
|
|
this.editingEvent = event;
|
|
this.showEventForm = true;
|
|
}
|
|
|
|
onEventSaved(): void {
|
|
this.showEventForm = false;
|
|
this.editingEvent = null;
|
|
}
|
|
|
|
onEventClosed(): void {
|
|
this.showEventForm = false;
|
|
this.editingEvent = null;
|
|
}
|
|
|
|
// ── Station form ──────────────────────────────────────────
|
|
openStationForm(): void {
|
|
this.showStationForm = true;
|
|
}
|
|
|
|
onStationSaved(): void {
|
|
this.stationConfig = this.stationConfigService.config();
|
|
this.showStationForm = false;
|
|
}
|
|
|
|
onStationClosed(): void {
|
|
this.showStationForm = false;
|
|
}
|
|
|
|
// ── History form ──────────────────────────────────────────
|
|
openHistoryForm(): void {
|
|
this.editingHistory = null;
|
|
this.showHistoryForm = true;
|
|
}
|
|
|
|
editHistory(entry: HistoryEntry): void {
|
|
this.editingHistory = entry;
|
|
this.showHistoryForm = true;
|
|
}
|
|
|
|
onHistorySaved(): void {
|
|
this.showHistoryForm = false;
|
|
this.editingHistory = null;
|
|
}
|
|
|
|
onHistoryClosed(): void {
|
|
this.showHistoryForm = false;
|
|
this.editingHistory = null;
|
|
}
|
|
|
|
// ── Team form ─────────────────────────────────────────────
|
|
openTeamForm(): void {
|
|
this.editingTeam = null;
|
|
this.showTeamForm = true;
|
|
}
|
|
|
|
editTeam(member: TeamMember): void {
|
|
this.editingTeam = member;
|
|
this.showTeamForm = true;
|
|
}
|
|
|
|
onTeamSaved(): void {
|
|
this.showTeamForm = false;
|
|
this.editingTeam = null;
|
|
}
|
|
|
|
onTeamClosed(): void {
|
|
this.showTeamForm = false;
|
|
this.editingTeam = null;
|
|
}
|
|
|
|
// ── Community form ────────────────────────────────────────
|
|
openCommunityForm(): void {
|
|
this.editingCommunity = null;
|
|
this.showCommunityForm = true;
|
|
}
|
|
|
|
editCommunity(highlight: CommunityHighlight): void {
|
|
this.editingCommunity = highlight;
|
|
this.showCommunityForm = true;
|
|
}
|
|
|
|
onCommunitySaved(): void {
|
|
this.showCommunityForm = false;
|
|
this.editingCommunity = null;
|
|
}
|
|
|
|
onCommunityClosed(): void {
|
|
this.showCommunityForm = false;
|
|
this.editingCommunity = null;
|
|
}
|
|
|
|
// ── Underwriter form ──────────────────────────────────────
|
|
openUnderwriterForm(): void {
|
|
this.editingUnderwriter = null;
|
|
this.showUnderwriterForm = true;
|
|
}
|
|
|
|
editUnderwriter(underwriter: Underwriter): void {
|
|
this.editingUnderwriter = underwriter;
|
|
this.showUnderwriterForm = true;
|
|
}
|
|
|
|
onUnderwriterSaved(): void {
|
|
this.showUnderwriterForm = false;
|
|
this.editingUnderwriter = null;
|
|
}
|
|
|
|
onUnderwriterClosed(): void {
|
|
this.showUnderwriterForm = false;
|
|
this.editingUnderwriter = null;
|
|
}
|
|
}
|