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,178 @@
<!--
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/.
-->
<div class="report-page">
<div class="page-header">
<h2>{{ 'labels.text.FMR Submission' | translate }}</h2>
<p class="subtitle">{{ 'labels.text.Submit Fraud Monitoring Return to RBI FUA System' | translate }}</p>
</div>
<div class="report-layout">
<mat-card class="report-card wide-card">
<mat-card-header>
<mat-card-title>
<fa-icon icon="university" class="mr-05"></fa-icon>
{{ 'labels.text.Fraud Monitoring Return (FMR)' | translate }}
</mat-card-title>
</mat-card-header>
<mat-card-content>
@if (result) {
<div class="success-result">
<div class="success-icon"><fa-icon icon="check-circle" size="3x"></fa-icon></div>
<h3>{{ 'labels.text.FMR Submitted Successfully' | translate }}</h3>
<div class="result-details">
<div class="result-row">
<span class="result-label">{{ 'labels.text.FMR Reference' | translate }}</span
><span class="result-value mono">{{ result.fmrReferenceNumber }}</span>
</div>
<div class="result-row">
<span class="result-label">{{ 'labels.text.Bank IFSC' | translate }}</span
><span class="result-value mono">{{ result.reportingBankIfsc }}</span>
</div>
<div class="result-row">
<span class="result-label">{{ 'labels.text.Fraud Category' | translate }}</span
><span class="result-value">{{ result.fraudCategory }}</span>
</div>
<div class="result-row">
<span class="result-label">{{ 'labels.text.Fraud Amount' | translate }}</span
><span class="result-value">&#8377;{{ result.fraudAmount?.toLocaleString() }}</span>
</div>
<div class="result-row">
<span class="result-label">{{ 'labels.text.Status' | translate }}</span
><span class="result-value status-submitted">{{ result.status }}</span>
</div>
<div class="result-row">
<span class="result-label">{{ 'labels.text.Submitted At' | translate }}</span
><span class="result-value">{{ result.submittedAt }}</span>
</div>
<div class="result-row">
<span class="result-label">{{ 'labels.text.Next FMR Due Date' | translate }}</span
><span class="result-value">{{ result.nextDueDate }}</span>
</div>
</div>
<button mat-raised-button color="primary" (click)="resetForm()" class="mt-16">
<fa-icon icon="plus" class="mr-05"></fa-icon> {{ 'labels.text.New FMR' | translate }}
</button>
</div>
} @else {
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="form-row">
<mat-form-field appearance="outline" class="half-width">
<mat-label>{{ 'labels.text.Reporting Bank IFSC' | translate }}</mat-label>
<input matInput formControlName="reportingBankIfsc" readonly />
</mat-form-field>
<mat-form-field appearance="outline" class="half-width">
<mat-label>{{ 'labels.text.Reporting Bank Name' | translate }}</mat-label>
<input matInput formControlName="reportingBankName" readonly />
</mat-form-field>
</div>
<div class="form-row">
<mat-form-field appearance="outline" class="half-width">
<mat-label>{{ 'labels.text.Fraud Category' | translate }}</mat-label>
<mat-select formControlName="fraudCategory">
@for (cat of fraudCategories; track cat) {
<mat-option [value]="cat">{{ cat }}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="half-width">
<mat-label>{{ 'labels.text.Sub-Category' | translate }}</mat-label>
<mat-select formControlName="fraudSubCategory">
@for (sub of fraudSubCategories; track sub) {
<mat-option [value]="sub">{{ sub }}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<mat-form-field appearance="outline" class="full-width">
<mat-label>{{ 'labels.text.Fraud Amount (INR)' | translate }}</mat-label>
<input matInput type="number" formControlName="fraudAmount" placeholder="0.00" />
</mat-form-field>
<div class="form-row">
<mat-form-field appearance="outline" class="half-width">
<mat-label>{{ 'labels.text.Account Number' | translate }}</mat-label>
<input matInput formControlName="accountNumber" />
</mat-form-field>
<mat-form-field appearance="outline" class="half-width">
<mat-label>{{ 'labels.text.Account Holder Name' | translate }}</mat-label>
<input matInput formControlName="accountHolderName" />
</mat-form-field>
</div>
<div class="form-row">
<mat-form-field appearance="outline" class="half-width">
<mat-label>{{ 'labels.text.Date of Detection' | translate }}</mat-label>
<input matInput [matDatepicker]="detPicker" formControlName="dateOfDetection" />
<mat-datepicker-toggle matSuffix [for]="detPicker"></mat-datepicker-toggle>
<mat-datepicker #detPicker></mat-datepicker>
</mat-form-field>
<mat-form-field appearance="outline" class="half-width">
<mat-label>{{ 'labels.text.Date of Fraud' | translate }}</mat-label>
<input matInput [matDatepicker]="fraudPicker" formControlName="dateOfFraud" />
<mat-datepicker-toggle matSuffix [for]="fraudPicker"></mat-datepicker-toggle>
<mat-datepicker #fraudPicker></mat-datepicker>
</mat-form-field>
</div>
<mat-form-field appearance="outline" class="full-width">
<mat-label>{{ 'labels.text.Modus Operandi' | translate }}</mat-label>
<textarea
matInput
formControlName="modus"
rows="3"
placeholder="Describe the method used to commit fraud..."
></textarea>
</mat-form-field>
<mat-form-field appearance="outline" class="full-width">
<mat-label>{{ 'labels.text.Action Taken' | translate }}</mat-label>
<mat-select formControlName="actionTaken">
@for (action of actionsTaken; track action) {
<mat-option [value]="action">{{ action }}</mat-option>
}
</mat-select>
</mat-form-field>
<div class="checkbox-row">
<mat-checkbox formControlName="staffInvolved" color="warn">{{
'labels.text.Staff Involved in Fraud' | translate
}}</mat-checkbox>
</div>
<mat-form-field appearance="outline" class="full-width">
<mat-label>{{ 'labels.text.Remarks' | translate }}</mat-label>
<textarea
matInput
formControlName="remarks"
rows="2"
placeholder="{{ 'labels.text.Enter remarks...' | translate }}"
></textarea>
</mat-form-field>
<button
mat-raised-button
color="primary"
type="submit"
[disabled]="isSubmitting"
class="full-width submit-btn"
>
@if (isSubmitting) {
<mat-spinner diameter="20" class="inline-spinner"></mat-spinner>
} @else {
<fa-icon icon="paper-plane" class="mr-05"></fa-icon> {{ 'labels.text.Submit to RBI' | translate }}
}
</button>
</form>
}
</mat-card-content>
</mat-card>
</div>
</div>
@@ -0,0 +1,108 @@
/**
* 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/.
*/
.report-page {
padding: 24px;
max-width: 1200px;
margin: 0 auto;
}
.page-header {
margin-bottom: 24px;
h2 {
margin: 0;
font-size: 24px;
font-weight: 600;
}
.subtitle {
margin: 4px 0 0;
color: #666;
font-size: 14px;
}
}
.report-layout {
display: flex;
gap: 24px;
}
.report-card {
flex: 1;
max-width: 700px;
}
.wide-card {
max-width: 900px;
}
.full-width {
width: 100%;
}
.half-width {
flex: 1;
}
.form-row {
display: flex;
gap: 16px;
}
.mr-05 {
margin-right: 0.5rem;
}
.mt-16 {
margin-top: 16px;
}
.submit-btn {
margin-top: 8px;
height: 48px;
font-size: 16px;
}
.inline-spinner {
display: inline-block;
margin-right: 8px;
}
.checkbox-row {
margin: 8px 0 16px;
}
.success-result {
text-align: center;
padding: 32px 16px;
.success-icon {
color: #10b981;
margin-bottom: 16px;
}
h3 {
color: #10b981;
margin-bottom: 24px;
}
}
.result-details {
text-align: left;
max-width: 500px;
margin: 0 auto;
background: #f8f9fa;
border-radius: 8px;
padding: 16px;
}
.result-row {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #eee;
&:last-child {
border-bottom: none;
}
}
.result-label {
color: #666;
font-weight: 500;
}
.result-value {
font-weight: 600;
}
.mono {
font-family: 'Roboto Mono', monospace;
}
.status-submitted {
color: #1e88e5;
font-weight: 700;
}
@@ -0,0 +1,192 @@
/**
* 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, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatCardModule } from '@angular/material/card';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
import { MatSelectModule } from '@angular/material/select';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { MatDialogModule, MatDialog } from '@angular/material/dialog';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { TranslateModule } from '@ngx-translate/core';
import { CfcfrmsService } from '../cfcfrms.service';
import { ConfirmDialogComponent } from '../components/confirm-dialog/confirm-dialog.component';
@Component({
selector: 'cfcfrms-fmr-submission',
standalone: true,
templateUrl: './fmr-submission.component.html',
styleUrls: ['./fmr-submission.component.scss'],
imports: [
CommonModule,
ReactiveFormsModule,
MatCardModule,
MatFormFieldModule,
MatInputModule,
MatButtonModule,
MatSelectModule,
MatDatepickerModule,
MatCheckboxModule,
MatProgressSpinnerModule,
MatSnackBarModule,
MatDialogModule,
FaIconComponent,
TranslateModule
]
})
export class FmrSubmissionComponent {
private fb = inject(FormBuilder);
private service = inject(CfcfrmsService);
private snackBar = inject(MatSnackBar);
private dialog = inject(MatDialog);
form: FormGroup = this.fb.group({
reportingBankIfsc: [
'LINC0000001',
Validators.required
],
reportingBankName: [
'LincPay Digital Bank',
Validators.required
],
fraudCategory: [
'',
Validators.required
],
fraudSubCategory: [
'',
Validators.required
],
fraudAmount: [
null,
[
Validators.required,
Validators.min(1)
]
],
accountNumber: [
'',
Validators.required
],
accountHolderName: [
'',
Validators.required
],
dateOfDetection: [
'',
Validators.required
],
dateOfFraud: [
'',
Validators.required
],
modus: [
'',
Validators.required
],
actionTaken: [
'',
Validators.required
],
staffInvolved: [false],
remarks: ['']
});
isSubmitting = false;
result: any = null;
fraudCategories = [
'Advance Fee Fraud',
'Card / Internet Fraud',
'Cheque / DD Fraud',
'Deposit Related Fraud',
'Forex Fraud',
'KYC Related Fraud',
'Loan Related Fraud',
'Misappropriation / Criminal Breach of Trust',
'Cyber Fraud',
'Other'
];
fraudSubCategories = [
'Phishing / Vishing',
'UPI Fraud',
'Internet Banking Fraud',
'SIM Swap',
'QR Code Scam',
'OTP Fraud',
'Account Takeover',
'Identity Theft',
'Social Engineering',
'Other'
];
actionsTaken = [
'FIR filed with local police',
'Complaint filed with Cyber Cell',
'Internal investigation initiated',
'Account frozen / debited',
'Funds recovered partially',
'Funds recovered fully',
'Reported to I4C/CFCFRMS',
'No action (under review)'
];
onSubmit(): void {
if (this.form.invalid) {
this.form.markAllAsTouched();
return;
}
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
data: {
title: 'Confirm FMR Submission',
message: 'Submit this Fraud Monitoring Return to RBI FUA system? This is a regulatory submission.'
}
});
dialogRef.afterClosed().subscribe((confirmed) => {
if (!confirmed) return;
this.isSubmitting = true;
const val = this.form.value;
const payload = {
...val,
dateOfDetection: new Date(val.dateOfDetection).toISOString().split('T')[0],
dateOfFraud: new Date(val.dateOfFraud).toISOString().split('T')[0]
};
this.service.submitFmr(payload).subscribe({
next: (res) => {
this.result = res;
this.isSubmitting = false;
this.snackBar.open('FMR submitted to RBI successfully', 'OK', { duration: 4000 });
},
error: () => {
this.isSubmitting = false;
this.snackBar.open('Failed to submit FMR', 'OK', { duration: 4000 });
}
});
});
}
resetForm(): void {
this.form.reset({
reportingBankIfsc: 'LINC0000001',
reportingBankName: 'LincPay Digital Bank',
staffInvolved: false
});
this.result = null;
}
}