feat: add CFCFRMS module, bank menus, mock backend, and dashboard redesign

- CFCFRMS cyber fraud complaint management with dashboard, complaints,
  escalation, report suspect/mule/fraud txn, FMR submission, CFR lookup
- Mock HTTP interceptor with 20 dummy complaints and all endpoints
- Simulator service for I4C/MHA integration testing
- 3-row menu bar with 22 items (7+8+8 layout)
- Mega menus for General Ledger, Sub Ledger, Deposits, Share, Others,
  HRM, RBI with search and grouped columns
- Dropdowns for Cashier, Clearing, NPA, CKYC, Advances, Letter Mgmt,
  Insurance, Digital
- Dashboard redesign with regulatory, financial health, deposits,
  risk/NPA sections using static banking data
- All translations added to en-US.json
- FontAwesome icons registered for all new menu items

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aditya
2026-03-18 18:05:43 +05:30
co-authored by Claude Opus 4.6
parent 0a32d7135e
commit b3a4cce0cf
55 changed files with 9667 additions and 298 deletions
@@ -0,0 +1,175 @@
/**
* Copyright since 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { Component, OnInit, inject, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { MatCardModule } from '@angular/material/card';
import { MatTableModule } from '@angular/material/table';
import { MatButtonModule } from '@angular/material/button';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatIconModule } from '@angular/material/icon';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { Chart, registerables } from 'chart.js';
import { TranslateModule } from '@ngx-translate/core';
import { CfcfrmsService } from '../cfcfrms.service';
import { StatusBadgeComponent } from '../components/status-badge/status-badge.component';
import { Complaint, DashboardStats, ComplaintStatus } from '../models/cfcfrms.models';
Chart.register(...registerables);
@Component({
selector: 'cfcfrms-dashboard',
standalone: true,
imports: [
CommonModule,
RouterModule,
MatCardModule,
MatTableModule,
MatButtonModule,
MatProgressSpinnerModule,
MatIconModule,
FaIconComponent,
TranslateModule,
StatusBadgeComponent
],
templateUrl: './cfcfrms-dashboard.component.html',
styleUrls: ['./cfcfrms-dashboard.component.scss']
})
export class CfcfrmsDashboardComponent implements OnInit, AfterViewInit {
private cfcfrmsService = inject(CfcfrmsService);
stats: DashboardStats;
recentComplaints: Complaint[] = [];
isLoading = true;
displayedColumns = [
'ticketId',
'complainantName',
'fraudAmount',
'status',
'createdAt',
'actions'
];
@ViewChild('donutCanvas') donutCanvas: ElementRef<HTMLCanvasElement>;
@ViewChild('barCanvas') barCanvas: ElementRef<HTMLCanvasElement>;
private donutChart: any;
private barChart: any;
ngOnInit(): void {
this.loadData();
}
ngAfterViewInit(): void {}
loadData(): void {
this.isLoading = true;
this.cfcfrmsService.getStats().subscribe({
next: (stats) => {
this.stats = stats;
setTimeout(() => this.renderCharts(), 100);
},
error: () => {
this.stats = {
totalComplaints: 0,
pendingReview: 0,
lienMarked: 0,
fundsRecovered: 0,
statusDistribution: {},
amountByStatus: {}
};
}
});
this.cfcfrmsService.getComplaints({ page: 0, size: 5 }).subscribe({
next: (res) => {
this.recentComplaints = res.content;
this.isLoading = false;
},
error: () => {
this.recentComplaints = [];
this.isLoading = false;
}
});
}
private renderCharts(): void {
if (!this.stats) return;
const statusColors: Record<string, string> = {
RECEIVED: '#1E88E5',
UNDER_REVIEW: '#F59E0B',
LIEN_MARKED: '#EAB308',
DEBIT_FROZEN: '#EF4444',
FUNDS_MOVED: '#8B5CF6',
RESOLVED: '#10B981',
REJECTED: '#6B7280'
};
// Donut chart
if (this.donutCanvas) {
const dist = this.stats.statusDistribution || {};
const labels = Object.keys(dist);
const data = Object.values(dist);
const colors = labels.map((l) => statusColors[l] || '#999');
if (this.donutChart) this.donutChart.destroy();
this.donutChart = new Chart(this.donutCanvas.nativeElement, {
type: 'doughnut',
data: {
labels: labels.map((l) => l.replace('_', ' ')),
datasets: [{ data, backgroundColor: colors, borderWidth: 2, borderColor: '#fff' }]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'bottom', labels: { padding: 16, font: { size: 12 } } }
}
}
});
}
// Bar chart
if (this.barCanvas) {
const amt = this.stats.amountByStatus || {};
const labels = Object.keys(amt);
const data = Object.values(amt);
const colors = labels.map((l) => statusColors[l] || '#999');
if (this.barChart) this.barChart.destroy();
this.barChart = new Chart(this.barCanvas.nativeElement, {
type: 'bar',
data: {
labels: labels.map((l) => l.replace('_', ' ')),
datasets: [{ label: 'Fraud Amount (₹)', data, backgroundColor: colors, borderRadius: 4 }]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
y: { beginAtZero: true, ticks: { callback: (v: any) => '₹' + (v / 1000).toFixed(0) + 'K' } },
x: { ticks: { font: { size: 11 } } }
}
}
});
}
}
formatCurrency(amount: number): string {
return new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR', maximumFractionDigits: 0 }).format(
amount
);
}
formatDate(dateStr: string): string {
return new Date(dateStr).toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' });
}
}