Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/app/common/header/header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@

<span class="grow"></span>

@if (currentUser.role === 'Student') {
<f-notifications-button></f-notifications-button>
}

@if (currentUser.role === 'Admin' || currentUser.role === 'Convenor') {
<button
#menuState="matMenuTrigger"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
.notification-panel {
position: absolute;
top: 60px;
right: 20px;
width: 400px;
max-height: 70vh;
background: white;
border: 1px solid #ccc;
border-radius: 6px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.15);
z-index: 1000;
overflow-y: auto;
padding: 20px 16px 16px;
scrollbar-width: thin;
scrollbar-color: #ccc transparent;
}

.notification-panel::-webkit-scrollbar {
width: 6px;
}

.notification-panel::-webkit-scrollbar-thumb {
background-color: #ccc;
border-radius: 10px;
}

.notification-content {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
gap: 8px;
}

.notification-content span {
flex: 1;
margin-right: 8px;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
}

.delete-all-container {
display: flex;
justify-content: center;
margin-top: 4px;
}

.notification-button {
position: relative;
}

.notification-panel mat-list-item {
border-bottom: 1px solid #e0e0e0;
padding-bottom: 8px;
margin-bottom: 8px;
}

.notification-badge {
position: absolute;
top: 4px;
right: 4px;
background: red;
color: white;
font-size: 10px;
font-weight: bold;
border-radius: 50%;
padding: 2px 6px;
line-height: 1;
min-width: 16px;
text-align: center;
}






Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<button mat-icon-button (click)="toggleNotifications()" aria-label="Notifications" class="notification-button">
<mat-icon>notifications</mat-icon>
<span *ngIf="notifications.length > 0" class="notification-badge">{{ notifications.length }}</span>
</button>

<!-- Notification List Panel -->
<div *ngIf="showNotifications" class="notification-panel" #panel>
<mat-list>
<mat-list-item *ngFor="let note of notifications">
<div class="notification-content">
<span class="mat-body-2">{{ note.message }}</span>
<button mat-icon-button aria-label="Dismiss notification" (click)="dismissNotification(note.id)">
<mat-icon>close</mat-icon>
</button>
</div>
</mat-list-item>

<!-- If no notifications -->
<mat-list-item *ngIf="notifications.length === 0">
<span class="mat-body-2">No notifications</span>
</mat-list-item>
</mat-list>

<div *ngIf="notifications.length > 0" class="delete-all-container">
<button mat-button color="warn" (click)="deleteAllNotifications()">Delete All</button>
</div>

<div class="close-container">
<button mat-button (click)="toggleNotifications()">Close</button>
</div>
</div>




Original file line number Diff line number Diff line change
@@ -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<NotificationsButtonComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NotificationsButtonComponent]
})
.compileComponents();

fixture = TestBed.createComponent(NotificationsButtonComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// import { Component, ElementRef, HostListener, ViewChild } from '@angular/core';
import { Component, OnInit } 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';

// 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 {

// Tracks whether the notifications dropdown is visible
showNotifications = false;

// List of notifications to be displayed
notifications: Notification[] = [];

private readonly API_URL = AppInjector.get(DoubtfireConstants).API_URL;
constructor(private http: HttpClient) {}

ngOnInit() {
this.loadNotifications();
}

// Toggle the visibility of the notifications dropdown
toggleNotifications() {
this.showNotifications = !this.showNotifications;
console.log('showNotifications toggled:', this.showNotifications);
}

// Fetch notifications from the API
loadNotifications() {
this.http.get<Notification[]>(`${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) {
console.log('Dismissed notification with ID:', notificationId);
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() {
console.log('All notifications deleted');
this.http.delete(`${this.API_URL}/notifications`).subscribe({
next: () => {
this.notifications = [];
},
error: err => console.error('Error deleting all notifications', err)
});
}

}
2 changes: 2 additions & 0 deletions src/app/doubtfire-angular.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -389,6 +390,7 @@ import { UnitStudentEnrolmentModalComponent } from './units/modals/unit-student-
TaskScormCardComponent,
ScormExtensionCommentComponent,
ScormExtensionModalComponent,
NotificationsButtonComponent,
],
// Services we provide
providers: [
Expand Down