From 609a2eb586861ef8290534aa6abf9d2724842631 Mon Sep 17 00:00:00 2001 From: Ayush8923 Date: Thu, 19 Dec 2024 10:56:22 +0530 Subject: [PATCH 1/3] feat(*): notification list logic updates --- .../notification-item.component.html | 6 +++--- .../notification/notification.component.html | 8 +++++--- .../features/notification/notification.component.ts | 13 +++++++++---- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/app/features/notification/notification-item/notification-item.component.html b/src/app/features/notification/notification-item/notification-item.component.html index cf9731ed1a..3763a68684 100644 --- a/src/app/features/notification/notification-item/notification-item.component.html +++ b/src/app/features/notification/notification-item/notification-item.component.html @@ -33,9 +33,9 @@ - - {{ notification.readStatus ? "Mark as Unread" : "Mark as Read" }} - + {{ + notification.readStatus ? "Mark as Unread" : "Mark as Read" + }} - diff --git a/src/app/features/notification/notification.component.ts b/src/app/features/notification/notification.component.ts index 4a6d75d32f..679d455146 100644 --- a/src/app/features/notification/notification.component.ts +++ b/src/app/features/notification/notification.component.ts @@ -37,6 +37,7 @@ export class NotificationComponent implements OnInit { public allNotifications: NotificationEvent[] = []; public unreadNotifications: NotificationEvent[] = []; public selectedTab = 0; + public hasNotificationEnabled = false; constructor( private entityMapper: EntityMapperService, @@ -60,7 +61,6 @@ export class NotificationComponent implements OnInit { const notifications = this.mockNotificationsService.getNotifications() as unknown as NotificationEvent[]; - // The user is hardcoded for testing purposes, need to remove this. this.filterUserNotifications( notifications, this.sessionInfo.value?.entityId, @@ -85,9 +85,14 @@ export class NotificationComponent implements OnInit { ); } - markAllRead($event: Event) { - $event.stopPropagation(); - Logging.log("All notifications marked as read"); + async markAllRead() { + this.allNotifications.forEach((notification) => { + if (!notification.readStatus) { + notification.readStatus = true; + this.entityMapper.save(notification); + } + }); + this.loadAndProcessNotifications(); } enableNotificationForUser() { From 7f141792b833c0f15c35c1d210e22edc28728c72 Mon Sep 17 00:00:00 2001 From: Ayush8923 Date: Wed, 25 Dec 2024 15:58:01 +0530 Subject: [PATCH 2/3] fix(*): delete notification + enable notification logic for user --- .../notification/model/notification-config.ts | 28 ++++++++++ .../notification/notification.component.ts | 53 ++++++++++--------- 2 files changed, 55 insertions(+), 26 deletions(-) create mode 100644 src/app/features/notification/model/notification-config.ts diff --git a/src/app/features/notification/model/notification-config.ts b/src/app/features/notification/model/notification-config.ts new file mode 100644 index 0000000000..1aa625653d --- /dev/null +++ b/src/app/features/notification/model/notification-config.ts @@ -0,0 +1,28 @@ +import { Entity } from "../../../core/entity/model/entity"; +import { DatabaseField } from "../../../core/entity/database-field.decorator"; +import { DatabaseEntity } from "../../../core/entity/database-entity.decorator"; + +/** + * This represents one specific notification config for one specific user, + */ +@DatabaseEntity("NotificationConfig") +export class NotificationConfig extends Entity { + @DatabaseField() channels: { [key: string]: boolean }; + + @DatabaseField() notificationTypes: NotificationType[]; + + public override toString(): string { + return `NotificationConfig: ${this.notificationTypes}`; + } +} + +/** + * Represents a specific notification type configuration. + */ +export class NotificationType { + @DatabaseField() notificationType: string; + @DatabaseField() enabled: boolean; + @DatabaseField() channels: { [key: string]: boolean }; + @DatabaseField() entityType: string; + @DatabaseField() conditions: any; +} diff --git a/src/app/features/notification/notification.component.ts b/src/app/features/notification/notification.component.ts index 92e071087b..44afff3a3e 100644 --- a/src/app/features/notification/notification.component.ts +++ b/src/app/features/notification/notification.component.ts @@ -10,9 +10,10 @@ import { NotificationEvent } from "./model/notification-event"; import { EntityMapperService } from "app/core/entity/entity-mapper/entity-mapper.service"; import { MatTabsModule } from "@angular/material/tabs"; import { NotificationItemComponent } from "./notification-item/notification-item.component"; -import { MockNotificationsService } from "./mock-notification.service"; import { SessionSubject } from "app/core/session/auth/session-info"; import { closeOnlySubmenu } from "./close-only-submenu"; +import { NotificationConfig } from "./model/notification-config"; +import { AlertService } from "app/core/alerts/alert.service"; @Component({ selector: "app-notification", @@ -37,11 +38,12 @@ export class NotificationComponent implements OnInit { public unreadNotifications: NotificationEvent[] = []; public selectedTab = 0; public hasNotificationEnabled = false; + protected readonly closeOnlySubmenu = closeOnlySubmenu; constructor( private entityMapper: EntityMapperService, - private mockNotificationsService: MockNotificationsService, private sessionInfo: SessionSubject, + private alertService: AlertService, ) {} ngOnInit(): void { @@ -52,15 +54,8 @@ export class NotificationComponent implements OnInit { * Loads all notifications and processes them to update the list and unread count. */ private async loadAndProcessNotifications(): Promise { - // TODO: Need to uncomment this after the notification list UI testing is completed. - // const notifications = - // await this.entityMapper.loadType(NotificationEvent); - - // TODO: Need to remove this line once the notification list UI tests are completed. const notifications = - this.mockNotificationsService.getNotifications() as unknown as NotificationEvent[]; - - // The user is hardcoded for testing purposes, need to remove this. + await this.entityMapper.loadType(NotificationEvent); this.filterUserNotifications(notifications); } @@ -91,9 +86,24 @@ export class NotificationComponent implements OnInit { this.loadAndProcessNotifications(); } - enableNotificationForUser() { - // TODO: Need to implement the logic so that the notification is enabled corresponding to the user. - Logging.log("Notification enabled"); + async enableNotificationForUser() { + // TODO: Implement the logic to called the getToken function from the NotificationService file, Once the PR #2692 merged. + this.hasNotificationEnabled = !this.hasNotificationEnabled; + const notificationConfig = new NotificationConfig(); + + // TODO: Currently, email notification are disabled. Update this logic once the email notification feature is implemented. + notificationConfig.channels = { + push: this.hasNotificationEnabled, + email: false, + }; + notificationConfig.notificationTypes = []; + + try { + await this.entityMapper.save(notificationConfig); + Logging.log("Notification saved successfully."); + } catch (error) { + throw error("Error saving notification: ", error); + } } async updateReadStatus(notification: NotificationEvent, newStatus: boolean) { @@ -102,18 +112,9 @@ export class NotificationComponent implements OnInit { this.filterUserNotifications(this.allNotifications); } - deleteNotification(notification: NotificationEvent) { - // Need to add/update this logic to delete the notification from the CouchDB - this.allNotifications = this.allNotifications.filter( - (n) => n !== notification, - ); - this.unreadNotifications = this.unreadNotifications.filter( - (n) => n !== notification, - ); - Logging.log("Notification deleted"); - - this.filterUserNotifications(this.allNotifications); + async deleteNotification(notification: NotificationEvent) { + await this.entityMapper.remove(notification); + this.alertService.addInfo(`Notification deleted successfully`); + this.loadAndProcessNotifications(); } - - protected readonly closeOnlySubmenu = closeOnlySubmenu; } From ea49d4cae50bc44570a47bc81eb8df3f338c9fc0 Mon Sep 17 00:00:00 2001 From: Ayush8923 Date: Wed, 25 Dec 2024 22:48:30 +0530 Subject: [PATCH 3/3] fix(*): implement the logic for notification list actions --- .../notification/model/notification-config.ts | 4 +- .../notification/notification.component.ts | 109 +++++++++++++----- 2 files changed, 83 insertions(+), 30 deletions(-) diff --git a/src/app/features/notification/model/notification-config.ts b/src/app/features/notification/model/notification-config.ts index 1aa625653d..f861a5e698 100644 --- a/src/app/features/notification/model/notification-config.ts +++ b/src/app/features/notification/model/notification-config.ts @@ -11,8 +11,8 @@ export class NotificationConfig extends Entity { @DatabaseField() notificationTypes: NotificationType[]; - public override toString(): string { - return `NotificationConfig: ${this.notificationTypes}`; + constructor(id = null) { + super(id); } } diff --git a/src/app/features/notification/notification.component.ts b/src/app/features/notification/notification.component.ts index 44afff3a3e..27d1c7ff67 100644 --- a/src/app/features/notification/notification.component.ts +++ b/src/app/features/notification/notification.component.ts @@ -1,11 +1,11 @@ import { Component, OnInit } from "@angular/core"; +import { Subject } from "rxjs"; import { MatBadgeModule } from "@angular/material/badge"; import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; import { MatMenu, MatMenuModule, MatMenuTrigger } from "@angular/material/menu"; import { MatButtonModule } from "@angular/material/button"; import { FormsModule } from "@angular/forms"; import { MatTooltipModule } from "@angular/material/tooltip"; -import { Logging } from "app/core/logging/logging.service"; import { NotificationEvent } from "./model/notification-event"; import { EntityMapperService } from "app/core/entity/entity-mapper/entity-mapper.service"; import { MatTabsModule } from "@angular/material/tabs"; @@ -36,6 +36,7 @@ import { AlertService } from "app/core/alerts/alert.service"; export class NotificationComponent implements OnInit { public allNotifications: NotificationEvent[] = []; public unreadNotifications: NotificationEvent[] = []; + private notificationsSubject = new Subject(); public selectedTab = 0; public hasNotificationEnabled = false; protected readonly closeOnlySubmenu = closeOnlySubmenu; @@ -47,6 +48,10 @@ export class NotificationComponent implements OnInit { ) {} ngOnInit(): void { + this.notificationsSubject.subscribe((notifications) => { + this.filterUserNotifications(notifications); + }); + this.loadAndProcessNotifications(); } @@ -56,17 +61,35 @@ export class NotificationComponent implements OnInit { private async loadAndProcessNotifications(): Promise { const notifications = await this.entityMapper.loadType(NotificationEvent); - this.filterUserNotifications(notifications); + const user = this.sessionInfo.value?.id; + + const notificationConfig = await this.loadNotificationConfig(user); + this.hasNotificationEnabled = notificationConfig?.channels.push ?? false; + + this.notificationsSubject.next(notifications); + } + + /** + * Loads the user's notification configuration. + */ + private async loadNotificationConfig( + user: string, + ): Promise { + try { + return await this.entityMapper.load( + NotificationConfig, + user, + ); + } catch (error) { + return null; + } } /** - * Filters notifications based on the sender. - * @param notifications - The list of notifications. - * @param user - The user to filter notifications. + * Filters notifications based on the sender and read status. */ private filterUserNotifications(notifications: NotificationEvent[]) { const user = this.sessionInfo.value?.id; - this.allNotifications = notifications.filter( (notification) => notification.sentBy === user, ); @@ -76,45 +99,75 @@ export class NotificationComponent implements OnInit { ); } - async markAllRead() { - this.allNotifications.forEach((notification) => { - if (!notification.readStatus) { - notification.readStatus = true; - this.entityMapper.save(notification); - } - }); - this.loadAndProcessNotifications(); + /** + * Marks all notifications as read. + */ + async markAllRead(): Promise { + const unreadNotifications = this.allNotifications.filter( + (notification) => !notification.readStatus, + ); + await this.updateReadStatusForNotifications(unreadNotifications, true); } - async enableNotificationForUser() { - // TODO: Implement the logic to called the getToken function from the NotificationService file, Once the PR #2692 merged. + /** + * Toggles the notification setting for the user. + */ + async enableNotificationForUser(): Promise { this.hasNotificationEnabled = !this.hasNotificationEnabled; - const notificationConfig = new NotificationConfig(); + const user = this.sessionInfo.value?.id; - // TODO: Currently, email notification are disabled. Update this logic once the email notification feature is implemented. - notificationConfig.channels = { + const notificationConfig = await this.loadNotificationConfig(user); + const notificationChannels = { push: this.hasNotificationEnabled, email: false, }; - notificationConfig.notificationTypes = []; - try { - await this.entityMapper.save(notificationConfig); - Logging.log("Notification saved successfully."); - } catch (error) { - throw error("Error saving notification: ", error); + if (notificationConfig) { + notificationConfig.channels = notificationChannels; + await this.entityMapper.save(notificationConfig); + } else { + const newNotificationConfig = new NotificationConfig(user); + newNotificationConfig.channels = notificationChannels; + await this.entityMapper.save(newNotificationConfig); } + + this.alertService.addInfo( + `Notification ${this.hasNotificationEnabled ? "enabled" : "disabled"}.`, + ); } - async updateReadStatus(notification: NotificationEvent, newStatus: boolean) { + /** + * Updates the read status for multiple notifications. + */ + private async updateReadStatusForNotifications( + notifications: NotificationEvent[], + newStatus: boolean, + ): Promise { + for (const notification of notifications) { + notification.readStatus = newStatus; + await this.entityMapper.save(notification); + } + this.filterUserNotifications(this.allNotifications); + } + + /** + * Updates the read status of a single notification. + */ + async updateReadStatus( + notification: NotificationEvent, + newStatus: boolean, + ): Promise { notification.readStatus = newStatus; await this.entityMapper.save(notification); this.filterUserNotifications(this.allNotifications); } - async deleteNotification(notification: NotificationEvent) { + /** + * Deletes a user notification. + */ + async deleteNotification(notification: NotificationEvent): Promise { await this.entityMapper.remove(notification); - this.alertService.addInfo(`Notification deleted successfully`); + this.alertService.addInfo("Notification deleted successfully"); this.loadAndProcessNotifications(); } }