feat(digital+toolbar): NEFT/RTGS module, AI menu, polished header icons

- Add /digital/neft-rtgs (entry) and /digital/neft-rtgs-report (analytics):
  KPIs, tabbed views (Trends, Bank-wise, Date-wise, Pending/Pass/Return,
  Branch/Mobile, User-wise), filterable transaction table with CSV export,
  and a non-dismissible Initiate dialog launched from a floating action.
- Make BANL verification mandatory before NEFT/RTGS submit; auto-fill and
  lock beneficiary name from the lookup response, invalidate match on
  A/C or IFSC edit, demo fallback for unknown accounts.
- Add Digital menu links (NEFT/RTGS + Report) in shell top-nav.
- Replace toolbar theme toggle with AI / Intelligence Modules mega menu:
  4 colour-coded columns covering Lending & Risk, Investment & Profit,
  Compliance & Advisory, Governance (9 modules total). Wire RBI
  Notifications Monitoring to /rbi-circulars.
- Unify notifications, settings, AI, and profile toolbar buttons into a
  26px ring style; colourful gradient bell SVG for notifications,
  custom gear+petals AI mark, online dot on the avatar.
- Trim ISO 27001 chip in the top bar.
- Add MPL header to generated .env.ts and its template in version.js.
- Bundles other pending working-tree changes (clients family/nominee
  tab tweaks, auth/2FA fixes, translations) into the same push.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aditya
2026-05-15 11:50:26 +05:30
co-authored by Claude Opus 4.7
parent 6714897688
commit 595054b0b8
35 changed files with 4409 additions and 194 deletions
@@ -51,7 +51,14 @@ export class AddFamilyMemberComponent implements OnInit {
this.route.data.subscribe((data: { clientTemplate: any }) => {
this.addFamilyMemberTemplate = data.clientTemplate.familyMemberOptions;
});
this.clientId = this.route.parent.parent.snapshot.params['clientId'];
// Walk up the route tree until we find a clientId param. Hard-coding parent.parent breaks
// when Angular flattens componentless intermediate routes (family-members has no component),
// which makes the depth unstable — and produces /clients/undefined/familymembers URLs.
let r: ActivatedRoute | null = this.route;
while (r && !r.snapshot.params['clientId']) {
r = r.parent;
}
this.clientId = r?.snapshot.params['clientId'];
}
ngOnInit() {
@@ -14,6 +14,7 @@ import { MatDialog } from '@angular/material/dialog';
/** Custom Components */
import { DeleteDialogComponent } from '../../../shared/delete-dialog/delete-dialog.component';
import { NomineeDialogComponent } from './nominee-dialog/nominee-dialog.component';
import { ClientFamilyMemberDialogComponent } from '../../client-stepper/client-family-members-step/client-family-member-dialog/client-family-member-dialog.component';
/** Custom Services */
import { ClientsService } from '../../clients.service';
@@ -75,7 +76,10 @@ export class FamilyMembersTabComponent {
this.route.data.subscribe((data: { clientFamilyMembers: any }) => {
this.clientFamilyMembers = data.clientFamilyMembers || [];
});
this.clientId = this.route.parent.snapshot.params['clientId'];
// Route hierarchy is :clientId → family-members (wrapper) → this component,
// so the clientId param lives two levels up, not one.
this.clientId =
this.route.parent?.parent?.snapshot.params['clientId'] || this.route.parent?.snapshot.params['clientId'];
}
/** Family members (non-nominee entries) */
@@ -101,52 +105,78 @@ export class FamilyMembersTabComponent {
// ═══ Family Member Actions ═══
/**
* Strip UI-only helper fields (`_linkedClient`, `_marriageCertFile`, `id`)
* from the dialog payload so the backend receives a clean DTO. Mirrors the
* pattern used in client-family-members-step.editFamilyMember.
*/
private toApiPayload(formValue: any): any {
const { id, _linkedClient, _marriageCertFile, ...payload } = formValue || {};
return payload;
}
addFamilyMember() {
this.ensureTemplateLoaded().then(() => {
const dialogRef = this.dialog.open(NomineeDialogComponent, {
width: '700px',
const dialogRef = this.dialog.open(ClientFamilyMemberDialogComponent, {
width: '640px',
maxWidth: '95vw',
panelClass: 'fm-dialog-panel',
data: {
title: 'Add Family Member',
familyMemberOptions: this.familyMemberOptions,
isFamilyMember: true
context: 'Add',
options: this.familyMemberOptions,
existingMembers: this.familyMembers
}
});
dialogRef.afterClosed().subscribe((result: any) => {
if (result) {
this.clientsService.addFamilyMember(this.clientId, result).subscribe((res: any) => {
this.clientsService.getClientFamilyMember(this.clientId, res.resourceId).subscribe((member: any) => {
this.clientFamilyMembers.push(member);
});
});
}
dialogRef.afterClosed().subscribe((response: any) => {
if (!response?.member) return;
const payload = this.toApiPayload(response.member);
this.clientsService.addFamilyMember(this.clientId, payload).subscribe({
next: () => this.refreshFamilyMembers(),
error: (err) => console.error('[FamilyMembers] add failed', err)
});
});
});
}
editFamilyMember(member: any, index: number) {
const realIndex = this.clientFamilyMembers.indexOf(member);
editFamilyMember(member: any, _index: number) {
this.ensureTemplateLoaded().then(() => {
const dialogRef = this.dialog.open(NomineeDialogComponent, {
width: '700px',
const dialogRef = this.dialog.open(ClientFamilyMemberDialogComponent, {
width: '640px',
maxWidth: '95vw',
panelClass: 'fm-dialog-panel',
data: {
title: 'Edit Family Member',
familyMemberOptions: this.familyMemberOptions,
context: 'Edit',
options: this.familyMemberOptions,
member,
isFamilyMember: true
existingMembers: this.familyMembers
}
});
dialogRef.afterClosed().subscribe((result: any) => {
if (result) {
this.clientsService.editFamilyMember(member.clientId, member.id, result).subscribe(() => {
this.clientsService.getClientFamilyMember(this.clientId, member.id).subscribe((updated: any) => {
this.clientFamilyMembers[realIndex] = updated;
});
});
}
dialogRef.afterClosed().subscribe((response: any) => {
if (!response?.member) return;
const payload = this.toApiPayload(response.member);
this.clientsService.editFamilyMember(member.clientId || this.clientId, member.id, payload).subscribe({
next: () => this.refreshFamilyMembers(),
error: (err) => console.error('[FamilyMembers] edit failed', err)
});
});
});
}
/**
* Re-pull the full family-members list from the backend after any
* mutation. More resilient than fetching a single resource by id —
* sidesteps cases where the POST response doesn't carry the new id in
* the expected `resourceId` field.
*/
private refreshFamilyMembers(): void {
this.clientsService.getClientFamilyMembers(this.clientId).subscribe({
next: (data: any) => {
this.clientFamilyMembers = Array.isArray(data) ? data : [];
},
error: (err) => console.error('[FamilyMembers] refresh failed', err)
});
}
// ═══ Nominee Actions ═══
addNominee() {