feat(cic): Credit Bureau menu - CIBIL / CIC enquiry, consent, whitelist and API mapping

Separate top-nav menu with dashboard, new enquiry, enquiry history and report,
consent register, CIC whitelist & configuration, and API mapping with preview.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-19 12:18:32 +05:30
co-authored by Claude Opus 5.5
parent 55f5c1cf32
commit 1b70fb4534
23 changed files with 4173 additions and 0 deletions
@@ -0,0 +1,83 @@
/**
* 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 { CommonModule } from '@angular/common';
import { Component, OnInit, inject } from '@angular/core';
import { Router, RouterModule } from '@angular/router';
import { CicService, cicError, cicScoreBand } from '../cic.service';
/** Credit Bureau overview: volumes, outcomes, score spread and the state of each bureau. */
@Component({
selector: 'mifosx-cic-dashboard',
standalone: true,
imports: [
CommonModule,
RouterModule
],
templateUrl: './cic-dashboard.component.html',
styleUrls: ['../cic-shared.scss']
})
export class CicDashboardComponent implements OnInit {
private cic = inject(CicService);
private router = inject(Router);
loading = false;
error: string | null = null;
data: any = null;
readonly scoreBand = cicScoreBand;
ngOnInit(): void {
this.load();
}
load(): void {
this.loading = true;
this.error = null;
this.cic.dashboard().subscribe({
next: (d) => {
this.data = d;
this.loading = false;
},
error: (e) => {
this.error = cicError(e, 'Could not load the credit bureau dashboard.');
this.loading = false;
}
});
}
statusCount(status: string): number {
return Number(this.data?.byStatusLast30Days?.[status] ?? 0);
}
get successRate(): number {
const total = Number(this.data?.enquiriesLast30Days ?? 0);
if (!total) return 0;
return Math.round(((this.statusCount('SUCCESS') + this.statusCount('REUSED')) / total) * 100);
}
get maxBand(): number {
return Math.max(1, ...(this.data?.scoreBandsLast30Days ?? []).map((b: any) => Number(b.count)));
}
get whitelistedCount(): number {
return (this.data?.bureaus ?? []).filter((b: any) => b.whitelisted).length;
}
statusTone(status: string): string {
return status === 'SUCCESS' ? 'good' : status === 'REUSED' ? 'ok' : status === 'NO_HIT' ? 'warn' : 'bad';
}
open(id: number): void {
this.router.navigate([
'/credit-bureau/enquiries',
id
]);
}
}