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,169 @@
<!--
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="cic-page">
<div class="cic-head">
<div>
<h1>Credit Bureau Dashboard</h1>
<p>
Enquiries to the Credit Information Companies (CIBIL, Experian, Equifax, CRIF High Mark) over the last 30 days.
</p>
</div>
<div class="cic-actions">
<button class="cic-btn" (click)="load()" [disabled]="loading">Refresh</button>
<a class="cic-btn primary" routerLink="/credit-bureau/new-enquiry">New Enquiry</a>
</div>
</div>
@if (error) {
<div class="cic-alert error">{{ error }}</div>
}
@if (loading && !data) {
<div class="cic-card cic-empty">Loading…</div>
}
@if (data) {
@if (whitelistedCount === 0) {
<div class="cic-alert warn">
No credit bureau is whitelisted yet, so no enquiry can be run.
<a routerLink="/credit-bureau/bureaus">Open CIC Whitelist &amp; Configuration</a> to enable one.
</div>
}
<div class="cic-grid cols-4" style="margin-bottom: 16px">
<div class="cic-stat">
<div class="k">Enquiries today</div>
<div class="v">{{ data.enquiriesToday }}</div>
<div class="s">{{ data.enquiriesLast30Days }} in the last 30 days</div>
</div>
<div class="cic-stat">
<div class="k">Success rate</div>
<div class="v">{{ successRate }}%</div>
<div class="s">{{ statusCount('FAILED') }} failed · {{ statusCount('NO_HIT') }} no-hit</div>
</div>
<div class="cic-stat">
<div class="k">Average score</div>
<div class="v" [class]="'tone-' + scoreBand(data.averageScoreLast30Days).tone">
{{ data.averageScoreLast30Days ? (data.averageScoreLast30Days | number: '1.0-0') : '—' }}
</div>
<div class="s">{{ scoreBand(data.averageScoreLast30Days).label }}</div>
</div>
<div class="cic-stat">
<div class="k">Active consents</div>
<div class="v">{{ data.activeConsents }}</div>
<div class="s">
Charges ₹{{ data.chargesLast30Days | number: '1.0-2' }} · {{ statusCount('REUSED') }} reports reused
</div>
</div>
</div>
<div class="cic-split">
<div class="cic-card">
<h2>
Bureaus <span class="cic-card-sub">{{ whitelistedCount }} of {{ data.bureaus.length }} whitelisted</span>
</h2>
<div class="cic-table-wrap">
<table class="cic-table">
<thead>
<tr>
<th>Bureau</th>
<th>Whitelisted</th>
<th>Environment</th>
<th class="num">Enquiries (30d)</th>
<th class="num">Failed</th>
</tr>
</thead>
<tbody>
@for (b of data.bureaus; track b.id) {
<tr>
<td>
<b>{{ b.code }}</b>
@if (b.isDefault) {
<span class="cic-badge brand">Default</span>
}
<div class="muted" style="font-size: 12px">{{ b.name }}</div>
</td>
<td>
<span class="cic-badge" [class.good]="b.whitelisted">{{ b.whitelisted ? 'Yes' : 'No' }}</span>
</td>
<td>
<span
class="cic-badge"
[class.warn]="b.environment === 'SIMULATOR'"
[class.ok]="b.environment === 'UAT'"
[class.good]="b.environment === 'PRODUCTION'"
>{{ b.environment }}</span
>
</td>
<td class="num">{{ b.enquiries }}</td>
<td class="num" [class.tone-bad]="b.failed > 0">{{ b.failed }}</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<div class="cic-card">
<h2>Score spread <span class="cic-card-sub">successful enquiries, 30 days</span></h2>
@for (band of data.scoreBandsLast30Days; track band.label) {
<div style="margin-bottom: 10px">
<div style="display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 4px">
<span>{{ band.label }}</span
><b>{{ band.count }}</b>
</div>
<div class="cic-bar"><div [style.width.%]="(band.count / maxBand) * 100"></div></div>
</div>
}
</div>
</div>
<div class="cic-card">
<h2>
Recent enquiries
<a class="cic-btn sm" routerLink="/credit-bureau/enquiries">View all</a>
</h2>
@if (!data.recent?.length) {
<div class="cic-empty">No enquiry yet.</div>
} @else {
<div class="cic-table-wrap">
<table class="cic-table">
<thead>
<tr>
<th>Reference</th>
<th>Customer</th>
<th>Bureau</th>
<th>Status</th>
<th class="num">Score</th>
<th>By</th>
<th>When</th>
</tr>
</thead>
<tbody>
@for (e of data.recent; track e.id) {
<tr class="clickable" (click)="open(e.id)">
<td class="cic-mono">{{ e.enquiryRef }}</td>
<td>{{ e.clientName }}</td>
<td>{{ e.bureauCode }}</td>
<td>
<span class="cic-badge" [class]="'cic-badge ' + statusTone(e.status)">{{ e.status }}</span>
</td>
<td class="num">
<b [class]="'tone-' + scoreBand(e.score).tone">{{ e.score ?? '—' }}</b>
</td>
<td>{{ e.requestedByName }}</td>
<td class="muted">{{ e.requestedOn | slice: 0 : 16 }}</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
}
</div>