WEB-509:fix(notifications): resolve account navigation using entity resolution (#3032)

improve
This commit is contained in:
Shubham Kumar
2026-01-21 14:54:17 -06:00
committed by GitHub
parent e9e8876c5d
commit 7bdd7112b0
6 changed files with 263 additions and 13 deletions
@@ -30,7 +30,7 @@
mat-row mat-row
*matRowDef="let row; columns: displayedColumns" *matRowDef="let row; columns: displayedColumns"
class="select-row" class="select-row"
[routerLink]="[routeMap[row.objectType], row.objectId]" (click)="navigateToNotification(row)"
></tr> ></tr>
<tr *matNoDataRow> <tr *matNoDataRow>
<td [attr.colspan]="displayedColumns.length">You don't have any notifications</td> <td [attr.colspan]="displayedColumns.length">You don't have any notifications</td>
@@ -6,6 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
.select-row:hover { .select-row {
cursor: pointer; cursor: pointer;
} }
@@ -24,8 +24,9 @@ import {
MatRow, MatRow,
MatNoDataRow MatNoDataRow
} from '@angular/material/table'; } from '@angular/material/table';
import { ActivatedRoute, RouterLink } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { NotificationsService } from '../notifications.service';
/** /**
* Notifications Page Component * Notifications Page Component
@@ -54,6 +55,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
}) })
export class NotificationsPageComponent implements OnInit { export class NotificationsPageComponent implements OnInit {
private route = inject(ActivatedRoute); private route = inject(ActivatedRoute);
private router = inject(Router);
private notificationsService = inject(NotificationsService);
/** Notifications data. */ /** Notifications data. */
notificationsData: any; notificationsData: any;
@@ -112,4 +115,88 @@ export class NotificationsPageComponent implements OnInit {
this.dataSource.paginator = this.paginator; this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort; this.dataSource.sort = this.sort;
} }
/**
* Navigate to notification object with proper entity context
* @param {any} notification Notification object
*/
navigateToNotification(notification: any): void {
const objectType = notification.objectType;
const objectId = notification.objectId;
// For entities that don't require parent context (client, group, center, products)
if ([
'client',
'group',
'center',
'shareProduct',
'loanProduct'
].includes(objectType)) {
this.router.navigate([
this.routeMap[objectType],
objectId
]);
return;
}
// For account types that require parent entity (client/group) ID
switch (objectType) {
case 'loan':
this.notificationsService.getLoanAccount(objectId).subscribe((account) => {
if (account && (account.clientId || account.groupId)) {
const entityType = account.clientId ? 'clients' : 'groups';
const entityId = account.clientId || account.groupId;
this.router.navigate([`/${entityType}/${entityId}/loans-accounts/${account.accountId}`]);
}
});
break;
case 'savingsAccount':
this.notificationsService.getSavingsAccount(objectId).subscribe((account) => {
if (account && (account.clientId || account.groupId)) {
const entityType = account.clientId ? 'clients' : 'groups';
const entityId = account.clientId || account.groupId;
this.router.navigate([`/${entityType}/${entityId}/savings-accounts/${account.accountId}`]);
}
});
break;
case 'fixedDeposit':
this.notificationsService.getFixedDepositAccount(objectId).subscribe((account) => {
if (account && (account.clientId || account.groupId)) {
const entityType = account.clientId ? 'clients' : 'groups';
const entityId = account.clientId || account.groupId;
this.router.navigate([`/${entityType}/${entityId}/fixed-deposits-accounts/${account.accountId}`]);
}
});
break;
case 'recurringDepositAccount':
this.notificationsService.getRecurringDepositAccount(objectId).subscribe((account) => {
if (account && (account.clientId || account.groupId)) {
const entityType = account.clientId ? 'clients' : 'groups';
const entityId = account.clientId || account.groupId;
this.router.navigate([`/${entityType}/${entityId}/recurring-deposits-accounts/${account.accountId}`]);
}
});
break;
case 'shareAccount':
this.notificationsService.getShareAccount(objectId).subscribe((account) => {
if (account && (account.clientId || account.groupId)) {
const entityType = account.clientId ? 'clients' : 'groups';
const entityId = account.clientId || account.groupId;
this.router.navigate([`/${entityType}/${entityId}/shares-accounts/${account.accountId}`]);
}
});
break;
default:
// Fallback to old behavior for unknown types
this.router.navigate([
this.routeMap[objectType],
objectId
]);
}
}
} }
@@ -12,6 +12,7 @@ import { HttpClient, HttpParams } from '@angular/common/http';
/** rxjs Imports */ /** rxjs Imports */
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
/** /**
* Notification service. * Notification service.
@@ -37,6 +38,86 @@ export class NotificationsService {
return this.http.put('/notifications', {}); return this.http.put('/notifications', {});
} }
/**
* Get loan account details to retrieve client/group ID
* @param {string} loanId Loan Account ID
* @returns {Observable<any>} Loan account with clientId or groupId
*/
getLoanAccount(loanId: string): Observable<any> {
return this.http.get(`/loans/${loanId}`).pipe(
map((response: any) => ({
clientId: response.clientId,
groupId: response.groupId,
accountId: loanId
})),
catchError(() => of(null))
);
}
/**
* Get savings account details to retrieve client/group ID
* @param {string} savingsId Savings Account ID
* @returns {Observable<any>} Savings account with clientId or groupId
*/
getSavingsAccount(savingsId: string): Observable<any> {
return this.http.get(`/savingsaccounts/${savingsId}`).pipe(
map((response: any) => ({
clientId: response.clientId,
groupId: response.groupId,
accountId: savingsId
})),
catchError(() => of(null))
);
}
/**
* Get fixed deposit account details to retrieve client/group ID
* @param {string} accountId Fixed Deposit Account ID
* @returns {Observable<any>} Fixed deposit account with clientId or groupId
*/
getFixedDepositAccount(accountId: string): Observable<any> {
return this.http.get(`/fixeddepositaccounts/${accountId}`).pipe(
map((response: any) => ({
clientId: response.clientId,
groupId: response.groupId,
accountId: accountId
})),
catchError(() => of(null))
);
}
/**
* Get recurring deposit account details to retrieve client/group ID
* @param {string} accountId Recurring Deposit Account ID
* @returns {Observable<any>} Recurring deposit account with clientId or groupId
*/
getRecurringDepositAccount(accountId: string): Observable<any> {
return this.http.get(`/recurringdepositaccounts/${accountId}`).pipe(
map((response: any) => ({
clientId: response.clientId,
groupId: response.groupId,
accountId: accountId
})),
catchError(() => of(null))
);
}
/**
* Get share account details to retrieve client/group ID
* @param {string} accountId Share Account ID
* @returns {Observable<any>} Share account with clientId or groupId
*/
getShareAccount(accountId: string): Observable<any> {
return this.http.get(`/accounts/share/${accountId}`).pipe(
map((response: any) => ({
clientId: response.clientId,
groupId: response.groupId,
accountId: accountId
})),
catchError(() => of(null))
);
}
/** /**
* @returns {Observable<any>} Mock Unread Notifications for Testing. * @returns {Observable<any>} Mock Unread Notifications for Testing.
*/ */
@@ -41,11 +41,7 @@
</div> </div>
<div class="notifications-list"> <div class="notifications-list">
@for (notification of unreadNotifications; track notification) { @for (notification of unreadNotifications; track notification) {
<button <button mat-menu-item class="notification-item unread" (click)="navigateToNotification(notification, $event)">
mat-menu-item
class="notification-item unread"
[routerLink]="[routeMap[notification.objectType], notification.objectId]"
>
<div class="notification-content"> <div class="notification-content">
<span class="notification-text">{{ notification.content }}</span> <span class="notification-text">{{ notification.content }}</span>
<span class="unread-indicator">●</span> <span class="unread-indicator">●</span>
@@ -55,11 +51,7 @@
} }
@for (notification of displayedReadNotifications; track notification) { @for (notification of displayedReadNotifications; track notification) {
<button <button mat-menu-item class="notification-item read" (click)="navigateToNotification(notification, $event)">
mat-menu-item
class="notification-item read"
[routerLink]="[routeMap[notification.objectType], notification.objectId]"
>
<div class="notification-content"> <div class="notification-content">
<span class="notification-text">{{ notification.content }}</span> <span class="notification-text">{{ notification.content }}</span>
</div> </div>
@@ -8,6 +8,7 @@
/** Angular Imports */ /** Angular Imports */
import { Component, OnInit, OnDestroy, ViewEncapsulation, inject } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewEncapsulation, inject } from '@angular/core';
import { Router } from '@angular/router';
/** RxJS Imports */ /** RxJS Imports */
import { forkJoin } from 'rxjs'; import { forkJoin } from 'rxjs';
@@ -45,6 +46,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
}) })
export class NotificationsTrayComponent implements OnInit, OnDestroy { export class NotificationsTrayComponent implements OnInit, OnDestroy {
notificationsService = inject(NotificationsService); notificationsService = inject(NotificationsService);
private router = inject(Router);
/** Wait time between API status calls 60 seg */ /** Wait time between API status calls 60 seg */
waitTime = environment.waitTimeForNotifications || 60; waitTime = environment.waitTimeForNotifications || 60;
@@ -143,4 +145,92 @@ export class NotificationsTrayComponent implements OnInit, OnDestroy {
this.setNotifications(); this.setNotifications();
}); });
} }
/**
* Navigate to notification object with proper entity context
* @param {any} notification Notification object
* @param {Event} event Click event
*/
navigateToNotification(notification: any, event: Event): void {
event.preventDefault();
event.stopPropagation();
const objectType = notification.objectType;
const objectId = notification.objectId;
// For entities that don't require parent context (client, group, center, products)
if ([
'client',
'group',
'center',
'shareProduct',
'loanProduct'
].includes(objectType)) {
this.router.navigate([
this.routeMap[objectType],
objectId
]);
return;
}
// For account types that require parent entity (client/group) ID
switch (objectType) {
case 'loan':
this.notificationsService.getLoanAccount(objectId).subscribe((account) => {
if (account && (account.clientId || account.groupId)) {
const entityType = account.clientId ? 'clients' : 'groups';
const entityId = account.clientId || account.groupId;
this.router.navigate([`/${entityType}/${entityId}/loans-accounts/${account.accountId}`]);
}
});
break;
case 'savingsAccount':
this.notificationsService.getSavingsAccount(objectId).subscribe((account) => {
if (account && (account.clientId || account.groupId)) {
const entityType = account.clientId ? 'clients' : 'groups';
const entityId = account.clientId || account.groupId;
this.router.navigate([`/${entityType}/${entityId}/savings-accounts/${account.accountId}`]);
}
});
break;
case 'fixedDeposit':
this.notificationsService.getFixedDepositAccount(objectId).subscribe((account) => {
if (account && (account.clientId || account.groupId)) {
const entityType = account.clientId ? 'clients' : 'groups';
const entityId = account.clientId || account.groupId;
this.router.navigate([`/${entityType}/${entityId}/fixed-deposits-accounts/${account.accountId}`]);
}
});
break;
case 'recurringDepositAccount':
this.notificationsService.getRecurringDepositAccount(objectId).subscribe((account) => {
if (account && (account.clientId || account.groupId)) {
const entityType = account.clientId ? 'clients' : 'groups';
const entityId = account.clientId || account.groupId;
this.router.navigate([`/${entityType}/${entityId}/recurring-deposits-accounts/${account.accountId}`]);
}
});
break;
case 'shareAccount':
this.notificationsService.getShareAccount(objectId).subscribe((account) => {
if (account && (account.clientId || account.groupId)) {
const entityType = account.clientId ? 'clients' : 'groups';
const entityId = account.clientId || account.groupId;
this.router.navigate([`/${entityType}/${entityId}/shares-accounts/${account.accountId}`]);
}
});
break;
default:
// Fallback to old behavior for unknown types
this.router.navigate([
this.routeMap[objectType],
objectId
]);
}
}
} }