diff --git a/src/app/common/header/header.component.html b/src/app/common/header/header.component.html index bac6b77c07..d33d403c62 100644 --- a/src/app/common/header/header.component.html +++ b/src/app/common/header/header.component.html @@ -34,6 +34,10 @@ + @if (currentUser.role === 'Student') { + + } + @if (currentUser.role === 'Admin' || currentUser.role === 'Convenor') { + + +
+ + +
+ {{ note.message }} + +
+
+ + + + No notifications + +
+ +
+ +
+ +
+ +
+
+ + + + diff --git a/src/app/common/header/notifications-button/notifications-button.component.spec.ts b/src/app/common/header/notifications-button/notifications-button.component.spec.ts new file mode 100644 index 0000000000..7dedd16e77 --- /dev/null +++ b/src/app/common/header/notifications-button/notifications-button.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NotificationsButtonComponent } from './notifications-button.component'; + +describe('NotificationsButtonComponent', () => { + let component: NotificationsButtonComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NotificationsButtonComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(NotificationsButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/common/header/notifications-button/notifications-button.component.ts b/src/app/common/header/notifications-button/notifications-button.component.ts new file mode 100644 index 0000000000..3a0d1dbe9a --- /dev/null +++ b/src/app/common/header/notifications-button/notifications-button.component.ts @@ -0,0 +1,93 @@ +import { Component, OnInit, OnDestroy, ElementRef, HostListener, ViewChild } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { AppInjector } from 'src/app/app-injector'; +import { DoubtfireConstants } from 'src/app/config/constants/doubtfire-constants'; +import { Subscription } from 'rxjs'; + +// Define structure of a Notification object +interface Notification { + id: number; + message: string; +} + +@Component({ + selector: 'f-notifications-button', + templateUrl: './notifications-button.component.html', + styleUrls: ['./notifications-button.component.css'] +}) +export class NotificationsButtonComponent implements OnInit, OnDestroy { + + // Tracks whether the notifications dropdown is visible + showNotifications = false; + + // List of notifications to be displayed + notifications: Notification[] = []; + + // Track active subscriptions for cleanup + private subscriptions = new Subscription(); + + // Reference to the notification panel in the DOM + @ViewChild('panel') panelRef!: ElementRef; + + private readonly API_URL = AppInjector.get(DoubtfireConstants).API_URL; + + constructor(private http: HttpClient) {} + + ngOnInit() { + this.loadNotifications(); + } + + ngOnDestroy() { + // Prevent memory leaks + this.subscriptions.unsubscribe(); + } + + // Close panel when clicking outside of it + @HostListener('document:click', ['$event']) + onDocumentClick(event: MouseEvent) { + if (this.showNotifications && this.panelRef && !this.panelRef.nativeElement.contains(event.target)) { + this.showNotifications = false; + } + } + + // Toggle the visibility of the notifications dropdown + toggleNotifications() { + this.showNotifications = !this.showNotifications; + } + + // Fetch notifications from the API + loadNotifications() { + this.subscriptions.add( + this.http.get(`${this.API_URL}/notifications`).subscribe({ + next: data => this.notifications = data, + error: err => console.error('Error loading notifications', err) + }) + ); + } + + // Remove a specific notification by ID + dismissNotification(notificationId: number) { + this.subscriptions.add( + this.http.delete(`${this.API_URL}/notifications/${notificationId}`).subscribe({ + next: () => { + this.notifications = this.notifications.filter(note => note.id !== notificationId); + }, + error: err => console.error('Error deleting notification', err) + }) + ); + } + + // Delete all notifications + deleteAllNotifications() { + this.subscriptions.add( + this.http.delete(`${this.API_URL}/notifications`).subscribe({ + next: () => { + this.notifications = []; + }, + error: err => console.error('Error deleting all notifications', err) + }) + ); + } + +} + diff --git a/src/app/doubtfire-angular.module.ts b/src/app/doubtfire-angular.module.ts index c714ff0e5a..e4c0dbad59 100644 --- a/src/app/doubtfire-angular.module.ts +++ b/src/app/doubtfire-angular.module.ts @@ -253,6 +253,7 @@ import {ScormExtensionModalComponent} from './common/modals/scorm-extension-moda import { GradeIconComponent } from './common/grade-icon/grade-icon.component'; import { GradeTaskModalComponent } from './tasks/modals/grade-task-modal/grade-task-modal.component'; import { PrivacyPolicy } from './config/privacy-policy/privacy-policy'; +import {NotificationsButtonComponent} from './common/header/notifications-button/notifications-button.component'; // See https://stackoverflow.com/questions/55721254/how-to-change-mat-datepicker-date-format-to-dd-mm-yyyy-in-simplest-way/58189036#58189036 const MY_DATE_FORMAT = { @@ -389,6 +390,7 @@ import { UnitStudentEnrolmentModalComponent } from './units/modals/unit-student- TaskScormCardComponent, ScormExtensionCommentComponent, ScormExtensionModalComponent, + NotificationsButtonComponent, ], // Services we provide providers: [