import {Component, OnDestroy, OnInit} from '@angular/core'; import {Subject} from 'rxjs'; import {takeUntil} from 'rxjs/operators'; import {HttpErrorResponse} from '@angular/common/http'; import {ApiService} from '../../services/api.service'; import {UtilsService} from '../../services/utils.service'; import {AdminAuthService} from '../../services/admin-auth.service'; import {Event} from '../../models/event'; import {User} from '../../models/user'; @Component({ selector: 'app-admin', templateUrl: './admin.component.html', styleUrls: ['./admin.component.css'] }) export class AdminComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); isLoggedIn: boolean = false; events: Event[] = []; selectedCalendar: string = ''; name: string = ''; eventFilter: string = 'future'; // Default value for filter eventSorting: string = 'start_asc'; // Default value for sorting /** * Why the page is not showing events, when it is not. * * 'denied' and 'unavailable' are kept apart on purpose. Only a 401 is worth * sending someone to the login page; bouncing them there for a 403 produces * a loop where signing in succeeds and lands them straight back here, and * bouncing them for an unreachable API produces the same loop with no way * out at all. */ failure: 'denied' | 'unavailable' | null = null; constructor( private api: ApiService ) { } ngOnInit(): void { this.api.me().pipe(takeUntil(this.destroy$)).subscribe({ next: (user: User) => { this.isLoggedIn = true; this.name = user.fullName; UtilsService.saveNameToLocalStorage(user.fullName); // Signed in, but not for this app. The API would answer 403 to // every events call, so say so once instead of failing per call. if (!user.apps.includes('calendar')) { this.failure = 'denied'; return; } this.getEvents(); }, error: (error: HttpErrorResponse) => { if (error.status === 401) { AdminAuthService.goToLogin(); return; } this.failure = error.status === 403 ? 'denied' : 'unavailable'; } }); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } getEvents(): void { this.events = []; if (this.selectedCalendar === '') { return; } this.api.getEvents(this.selectedCalendar).pipe(takeUntil(this.destroy$)).subscribe({ next: (events: Event[]): void => { for (let event of events) { if (event.status !== 'DELETED') { this.events.push({ ...event, startDateTime: new Date(event.startDateTime), endDateTime: new Date(event.endDateTime), createdDate: new Date(event.createdDate), lastModifiedDate: new Date(event.lastModifiedDate) }); } } this.filterEvents(); this.sortEvents(); }, // Without this a failed load is indistinguishable from an empty // calendar - which is exactly what the old bundle looks like against // the post-cutover API, and how a deploy in progress gets mistaken for // lost data. error: (error: HttpErrorResponse): void => { if (error.status === 401) { AdminAuthService.goToLogin(); return; } this.failure = error.status === 403 ? 'denied' : 'unavailable'; } }); } handleCalendarChange() { this.getEvents(); } getCalendarId(text: string): number { switch (text) { case 'public': return 1; case 'members': return 2; case 'choir': return 4; case 'management': return 3; case 'birthdays': return 5; default: return -1; } } filterEvents() { this.events = this.events.filter((event) => { return this.checkEventMeetsFilterCriteria(event); }); } checkEventMeetsFilterCriteria(event: Event): boolean { // Always return birthdays regardless of date if(event.calendarId === 5) { return true; } switch (this.eventFilter) { case '': return true; case 'all': return true; case 'future': return this.reduceToDay(new Date(event.endDateTime)) >= this.reduceToDay(new Date()); case 'past': return this.reduceToDay(new Date(event.endDateTime)) < this.reduceToDay(new Date()); default: return true; } } reduceToDay(date: Date): Date { let dayOnlyDate: Date = new Date(); dayOnlyDate.setFullYear(date.getFullYear()); dayOnlyDate.setMonth(date.getMonth()); dayOnlyDate.setDate(date.getDate()); dayOnlyDate.setHours(0, 0, 0, 0); return dayOnlyDate; } getUserName(): string { return this.name; } sortEvents(): void { this.events.sort((a, b) => { switch (this.eventSorting) { case '': return 1; case 'start_asc': return a.startDateTime > b.startDateTime ? 1 : -1; case 'start_desc': return a.startDateTime > b.startDateTime ? -1 : 1; case 'created_asc': return a.createdDate > b.createdDate ? 1 : -1; case 'created_desc': return a.createdDate > b.createdDate ? -1 : 1; default: return 1; } }); } /** * There is no sign-in form here any more, and no account creation: accounts * exist only by invitation from the admin app. Both are one redirect. */ signIn(): void { AdminAuthService.goToLogin(); } logout(): void { UtilsService.clearName(); void AdminAuthService.signOut(); } reload(): void { window.location.reload(); } }