feat: build CKYC Reports dashboard with upload, charts, table & search

- Replace under-development placeholder with a full reports module
- Tabbed UI: Dashboard, File Upload, Customer Report, Search
- Dashboard: 6 KPI cards (Total / Male / Female / Missing PAN / Missing Aadhaar / Duplicate Mobile) and 3 Chart.js charts (state-wise donut, branch-wise horizontal bar, daily upload trend line)
- File Upload: drag-and-drop with click-to-browse, animated progress bar, success/error report with record-type breakdown (10 / 20 / 30 / 70)
- CKYC parser handles pipe-delimited CBS-export format: customer (20), KYC ID (30 — C=PAN, E=Aadhaar, B=Passport, V=Voter, D=DL), and document attachment (70)
- Customer Report: filterable table (Name / Branch / City / Gender / Date Range), pagination, CSV / Excel / PDF exports
- Search: by Name / Mobile / PAN / CIF with detail cards and per-result export
- Seed sample data so the dashboard is meaningful before any upload
- Print stylesheet for native PDF export

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aditya
2026-04-28 15:38:01 +05:30
co-authored by Claude Opus 4.7
parent 52e3395345
commit 634003a75c
4 changed files with 2025 additions and 12 deletions
@@ -6,14 +6,397 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<mat-card-content>
<div class="placeholder-content">
<fa-icon icon="chart-pie" size="3x" class="placeholder-icon"></fa-icon>
<h3>{{ 'labels.heading.CKYC Reports' | translate }}</h3>
<p>{{ 'labels.text.This section is under development.' | translate }}</p>
<div class="ck-root">
<!-- HEADER -->
<div class="ck-head">
<div class="ck-head-left">
<fa-icon icon="chart-pie" class="ck-head-ic"></fa-icon>
<div>
<h2 class="ck-head-t">CKYC Reports</h2>
<div class="ck-head-s">Customer KYC analytics, file ingestion &amp; search</div>
</div>
</mat-card-content>
</mat-card>
</div>
@if (upload.fileName) {
<div class="ck-file-tag">
<fa-icon icon="file-csv"></fa-icon>
<span>{{ upload.fileName }}</span>
<span class="ck-file-meta">· {{ upload.customers }} customers · {{ upload.totalRecords }} records</span>
</div>
}
</div>
<!-- TAB BAR -->
<div class="ck-tabs">
<button class="ck-tab" [class.active]="activeTab === 'dashboard'" (click)="showTab('dashboard')">
<fa-icon icon="chart-pie"></fa-icon> Dashboard
</button>
<button class="ck-tab" [class.active]="activeTab === 'upload'" (click)="showTab('upload')">
<fa-icon icon="upload"></fa-icon> File Upload
</button>
<button class="ck-tab" [class.active]="activeTab === 'customers'" (click)="showTab('customers')">
<fa-icon icon="users"></fa-icon> Customer Report
</button>
<button class="ck-tab" [class.active]="activeTab === 'search'" (click)="showTab('search')">
<fa-icon icon="search"></fa-icon> Search
</button>
</div>
<!-- DASHBOARD -->
@if (activeTab === 'dashboard') {
<div class="ck-section">
<!-- KPI CARDS -->
<div class="ck-kpis">
<div class="ck-kpi ck-kpi--blue">
<div class="ck-kpi-ic"><fa-icon icon="users"></fa-icon></div>
<div class="ck-kpi-body">
<div class="ck-kpi-n">{{ totalCustomers }}</div>
<div class="ck-kpi-l">Total Customers</div>
</div>
</div>
<div class="ck-kpi ck-kpi--teal">
<div class="ck-kpi-ic"><fa-icon icon="user"></fa-icon></div>
<div class="ck-kpi-body">
<div class="ck-kpi-n">{{ maleCount }}</div>
<div class="ck-kpi-l">Male</div>
</div>
</div>
<div class="ck-kpi ck-kpi--pink">
<div class="ck-kpi-ic"><fa-icon icon="user"></fa-icon></div>
<div class="ck-kpi-body">
<div class="ck-kpi-n">{{ femaleCount }}</div>
<div class="ck-kpi-l">Female</div>
</div>
</div>
<div class="ck-kpi ck-kpi--amber">
<div class="ck-kpi-ic"><fa-icon icon="id-card"></fa-icon></div>
<div class="ck-kpi-body">
<div class="ck-kpi-n">{{ missingPanCount }}</div>
<div class="ck-kpi-l">Missing PAN</div>
</div>
</div>
<div class="ck-kpi ck-kpi--orange">
<div class="ck-kpi-ic"><fa-icon icon="id-card-alt"></fa-icon></div>
<div class="ck-kpi-body">
<div class="ck-kpi-n">{{ missingAadhaarCount }}</div>
<div class="ck-kpi-l">Missing Aadhaar</div>
</div>
</div>
<div class="ck-kpi ck-kpi--red">
<div class="ck-kpi-ic"><fa-icon icon="phone"></fa-icon></div>
<div class="ck-kpi-body">
<div class="ck-kpi-n">{{ duplicateMobileCount }}</div>
<div class="ck-kpi-l">Duplicate Mobile</div>
</div>
</div>
</div>
<!-- CHARTS -->
<div class="ck-charts">
<div class="ck-chart-card ck-chart-1">
<div class="ck-chart-h">
<fa-icon icon="chart-pie" class="ck-chart-ic"></fa-icon>
<h4>State-wise Customers</h4>
</div>
<div class="ck-chart-body"><canvas #stateChartCanvas></canvas></div>
</div>
<div class="ck-chart-card ck-chart-2">
<div class="ck-chart-h">
<fa-icon icon="chart-bar" class="ck-chart-ic"></fa-icon>
<h4>Branch-wise Customers (Top 10)</h4>
</div>
<div class="ck-chart-body"><canvas #branchChartCanvas></canvas></div>
</div>
<div class="ck-chart-card ck-chart-3">
<div class="ck-chart-h">
<fa-icon icon="chart-line" class="ck-chart-ic"></fa-icon>
<h4>Daily Upload Trend</h4>
</div>
<div class="ck-chart-body"><canvas #trendChartCanvas></canvas></div>
</div>
</div>
</div>
}
<!-- FILE UPLOAD -->
@if (activeTab === 'upload') {
<div class="ck-section">
<div class="ck-up-card">
<div
class="ck-up-drop"
[class.dragging]="isDragging"
(dragover)="onDragOver($event)"
(dragleave)="onDragLeave($event)"
(drop)="onDrop($event)"
(click)="filePicker.click()"
>
<fa-icon icon="upload" class="ck-up-ic"></fa-icon>
<h3 class="ck-up-t">Drag &amp; drop your CKYC file here</h3>
<p class="ck-up-s">or click to browse — pipe-delimited (.txt) format</p>
<input #filePicker type="file" accept=".txt,.csv,.dat,text/plain" hidden (change)="onFilePicked($event)" />
</div>
@if (upload.status !== 'idle') {
<div class="ck-up-progress">
<div class="ck-up-progress-h">
<span class="ck-up-progress-name">{{ upload.fileName }}</span>
<span class="ck-up-progress-pct">{{ uploadProgress | number: '1.0-0' }}%</span>
</div>
<div class="ck-up-progress-bar">
<div
class="ck-up-progress-fill"
[style.width.%]="uploadProgress"
[class.success]="upload.status === 'success'"
[class.error]="upload.status === 'error'"
></div>
</div>
</div>
}
@if (upload.status === 'success') {
<div class="ck-up-report ck-up-report--ok">
<div class="ck-up-report-h">
<fa-icon icon="check-circle" class="ck-up-report-ic"></fa-icon>
<span>Parsed successfully</span>
</div>
<div class="ck-up-stats">
<div class="ck-up-stat">
<div class="ck-up-stat-n">{{ upload.totalRecords }}</div>
<div class="ck-up-stat-l">Total Records</div>
</div>
<div class="ck-up-stat">
<div class="ck-up-stat-n">{{ upload.customers }}</div>
<div class="ck-up-stat-l">Customers (20)</div>
</div>
<div class="ck-up-stat">
<div class="ck-up-stat-n">{{ upload.ids }}</div>
<div class="ck-up-stat-l">KYC IDs (30)</div>
</div>
<div class="ck-up-stat">
<div class="ck-up-stat-n">{{ upload.documents }}</div>
<div class="ck-up-stat-l">Documents (70)</div>
</div>
</div>
</div>
}
@if (upload.status === 'error') {
<div class="ck-up-report ck-up-report--err">
<div class="ck-up-report-h">
<fa-icon icon="exclamation-triangle" class="ck-up-report-ic"></fa-icon>
<span>Upload failed</span>
</div>
@for (err of upload.errors; track err) {
<div class="ck-up-err">{{ err }}</div>
}
</div>
}
</div>
<!-- Schema reference -->
<div class="ck-info-card">
<h4>CKYC File Schema</h4>
<div class="ck-schema">
<div class="ck-schema-row"><code>10|...</code><span>File header (1 line)</span></div>
<div class="ck-schema-row"><code>20|seq|...</code><span>Customer demographic record</span></div>
<div class="ck-schema-row">
<code>30|seq|type|value</code><span>KYC ID — C=PAN, E=Aadhaar, B=Passport, V=Voter, D=DL</span>
</div>
<div class="ck-schema-row"><code>70|seq|file|...</code><span>Document attachment</span></div>
</div>
</div>
</div>
}
<!-- CUSTOMER REPORT -->
@if (activeTab === 'customers') {
<div class="ck-section">
<div class="ck-filters">
<div class="ck-filter">
<label>Name</label>
<input class="ck-inp" type="text" [(ngModel)]="filterName" placeholder="Search by name" />
</div>
<div class="ck-filter">
<label>Branch</label>
<input class="ck-inp" type="text" [(ngModel)]="filterBranch" placeholder="Branch" />
</div>
<div class="ck-filter">
<label>City</label>
<input class="ck-inp" type="text" [(ngModel)]="filterCity" placeholder="City" />
</div>
<div class="ck-filter">
<label>Gender</label>
<select class="ck-inp" [(ngModel)]="filterGender">
<option value="">All</option>
<option value="M">Male</option>
<option value="F">Female</option>
</select>
</div>
<div class="ck-filter">
<label>Date From</label>
<input class="ck-inp" type="date" [(ngModel)]="filterDateFrom" />
</div>
<div class="ck-filter">
<label>Date To</label>
<input class="ck-inp" type="date" [(ngModel)]="filterDateTo" />
</div>
<div class="ck-filter ck-filter-actions">
<button class="ck-btn ck-btn-g" (click)="resetFilters()">Reset</button>
</div>
</div>
<div class="ck-export-bar">
<span class="ck-export-meta">{{ filteredCustomers.length }} matching records</span>
<div class="ck-export-btns">
<button class="ck-btn ck-btn-csv" (click)="exportCsv()"><fa-icon icon="file-csv"></fa-icon> CSV</button>
<button class="ck-btn ck-btn-xls" (click)="exportExcel()"><fa-icon icon="file-excel"></fa-icon> Excel</button>
<button class="ck-btn ck-btn-pdf" (click)="exportPdf()"><fa-icon icon="file-pdf"></fa-icon> PDF</button>
</div>
</div>
<div class="ck-table-wrap">
<table class="ck-table">
<thead>
<tr>
<th>CIF</th>
<th>Name</th>
<th>Gender</th>
<th>DOB</th>
<th>Mobile</th>
<th>PAN</th>
<th>Aadhaar</th>
<th>City</th>
<th>Branch</th>
<th>Upload</th>
</tr>
</thead>
<tbody>
@for (c of pagedCustomers; track c.id) {
<tr>
<td class="mono">{{ c.id }}</td>
<td>{{ c.fullName }}</td>
<td>
<span class="ck-gen" [class.m]="c.gender === 'M'" [class.f]="c.gender === 'F'">{{
c.gender || '—'
}}</span>
</td>
<td>{{ c.dob }}</td>
<td class="mono">{{ c.mobile || '—' }}</td>
<td class="mono">{{ c.pan || '—' }}</td>
<td class="mono">{{ c.aadhaarMasked || '—' }}</td>
<td>{{ c.city }}</td>
<td>{{ c.branch }}</td>
<td>{{ c.uploadDate }}</td>
</tr>
} @empty {
<tr>
<td colspan="10" class="ck-empty">No records match the filters.</td>
</tr>
}
</tbody>
</table>
</div>
<div class="ck-pager">
<button class="ck-btn ck-btn-g" (click)="changePage(-1)" [disabled]="pageIndex === 0">‹ Prev</button>
<span>Page {{ pageIndex + 1 }} of {{ pageCount }}</span>
<button class="ck-btn ck-btn-g" (click)="changePage(1)" [disabled]="pageIndex >= pageCount - 1">Next ›</button>
</div>
</div>
}
<!-- SEARCH -->
@if (activeTab === 'search') {
<div class="ck-section">
<div class="ck-search-bar">
<select class="ck-inp ck-search-type" [(ngModel)]="searchType">
<option value="name">Name</option>
<option value="mobile">Mobile</option>
<option value="pan">PAN</option>
<option value="cif">Customer ID (CIF)</option>
</select>
<input
class="ck-inp ck-search-inp"
type="text"
[(ngModel)]="searchQuery"
(keyup.enter)="performSearch()"
[placeholder]="'Search by ' + searchType + '…'"
/>
<button class="ck-btn ck-btn-t" (click)="performSearch()"><fa-icon icon="search"></fa-icon> Search</button>
</div>
@if (searchPerformed) {
<div class="ck-export-bar">
<span class="ck-export-meta"
>{{ searchResults.length }} match{{ searchResults.length === 1 ? '' : 'es' }}</span
>
@if (searchResults.length > 0) {
<div class="ck-export-btns">
<button class="ck-btn ck-btn-csv" (click)="exportCsv(searchResults)">
<fa-icon icon="file-csv"></fa-icon> CSV
</button>
<button class="ck-btn ck-btn-xls" (click)="exportExcel(searchResults)">
<fa-icon icon="file-excel"></fa-icon> Excel
</button>
<button class="ck-btn ck-btn-pdf" (click)="exportPdf()"><fa-icon icon="file-pdf"></fa-icon> PDF</button>
</div>
}
</div>
<div class="ck-search-results">
@for (c of searchResults; track c.id) {
<div class="ck-search-card">
<div class="ck-search-card-head">
<div class="ck-search-card-av">
{{ (c.firstName || c.fullName).charAt(0) }}{{ c.lastName.charAt(0) }}
</div>
<div>
<div class="ck-search-card-name">{{ c.fullName }}</div>
<div class="ck-search-card-cif mono">{{ c.id }}</div>
</div>
<span class="ck-gen" [class.m]="c.gender === 'M'" [class.f]="c.gender === 'F'">{{
c.gender || '—'
}}</span>
</div>
<div class="ck-search-card-grid">
<div>
<label>DOB</label><span>{{ c.dob || '—' }}</span>
</div>
<div>
<label>Mobile</label><span class="mono">{{ c.mobile || '—' }}</span>
</div>
<div>
<label>PAN</label><span class="mono">{{ c.pan || '—' }}</span>
</div>
<div>
<label>Aadhaar</label><span class="mono">{{ c.aadhaarMasked || '—' }}</span>
</div>
<div class="ck-search-card-addr">
<label>Address</label>
<span
>{{ c.address1 }} {{ c.address2 }} {{ c.address3 }}, {{ c.city }}, {{ c.state }} {{ c.pin }}</span
>
</div>
<div>
<label>Branch</label><span>{{ c.branch }}</span>
</div>
</div>
</div>
} @empty {
<div class="ck-empty-card">
<fa-icon icon="search" class="ck-empty-ic"></fa-icon>
<p>No customers found for "{{ searchQuery }}"</p>
</div>
}
</div>
} @else {
<div class="ck-empty-card">
<fa-icon icon="search" class="ck-empty-ic"></fa-icon>
<p>Enter a query above and press Search.</p>
</div>
}
</div>
}
<!-- TOAST -->
<div class="ck-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
</div>
@@ -0,0 +1,916 @@
/**
* 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/.
*/
:host {
--ck-navy: #0b1e3d;
--ck-teal: #0e7e6b;
--ck-teal-l: #e0f4f0;
--ck-blue: #1d4ed8;
--ck-blue-l: #eff6ff;
--ck-amber: #d97706;
--ck-amber-l: #fef3c7;
--ck-orange: #ea580c;
--ck-orange-l: #fff0e6;
--ck-red: #c0392b;
--ck-red-l: #fdecea;
--ck-pink: #db2777;
--ck-pink-l: #fce7f3;
--ck-green: #15803d;
--ck-green-l: #dcfce7;
--ck-g50: #f8fafc;
--ck-g100: #f1f5f9;
--ck-g200: #e2e8f0;
--ck-g300: #cbd5e1;
--ck-g500: #64748b;
--ck-g700: #334155;
--ck-g900: #0f172a;
display: block;
font-family:
'DM Sans',
system-ui,
-apple-system,
sans-serif;
background: var(--ck-g50);
color: var(--ck-g900);
font-size: 14px;
line-height: 1.5;
}
.ck-root {
padding: 18px 22px;
max-width: 1400px;
margin: 0 auto;
}
/* HEADER */
.ck-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
flex-wrap: wrap;
margin-bottom: 16px;
}
.ck-head-left {
display: flex;
align-items: center;
gap: 14px;
}
.ck-head-ic {
font-size: 26px;
color: var(--ck-teal);
}
.ck-head-t {
margin: 0;
font-size: 19px;
font-weight: 700;
color: var(--ck-g900);
}
.ck-head-s {
font-size: 12px;
color: var(--ck-g500);
}
.ck-file-tag {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
border-radius: 8px;
background: var(--ck-teal-l);
color: var(--ck-teal);
font-size: 12px;
font-weight: 500;
fa-icon {
color: var(--ck-teal);
}
}
.ck-file-meta {
color: var(--ck-g500);
font-weight: 400;
}
/* TABS */
.ck-tabs {
display: flex;
gap: 4px;
background: #fff;
border: 1px solid var(--ck-g200);
border-radius: 10px;
padding: 4px;
margin-bottom: 16px;
overflow-x: auto;
}
.ck-tab {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 9px 16px;
border: none;
background: transparent;
font-size: 13px;
font-weight: 500;
color: var(--ck-g500);
cursor: pointer;
border-radius: 7px;
white-space: nowrap;
transition: all 0.15s;
font-family: inherit;
&:hover {
color: var(--ck-g900);
}
&.active {
background: var(--ck-teal);
color: #fff;
box-shadow: 0 2px 6px rgb(14 126 107 / 25%);
}
}
.ck-section {
display: flex;
flex-direction: column;
gap: 16px;
}
/* KPI CARDS */
.ck-kpis {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 12px;
}
.ck-kpi {
background: #fff;
border: 1px solid var(--ck-g200);
border-radius: 12px;
padding: 16px;
display: flex;
align-items: center;
gap: 14px;
box-shadow: 0 1px 3px rgb(0 0 0 / 5%);
transition: all 0.15s;
&:hover {
box-shadow: 0 4px 14px rgb(0 0 0 / 10%);
transform: translateY(-2px);
}
}
.ck-kpi-ic {
width: 42px;
height: 42px;
border-radius: 11px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.ck-kpi-n {
font-size: 24px;
font-weight: 700;
line-height: 1.1;
color: var(--ck-g900);
}
.ck-kpi-l {
font-size: 12px;
color: var(--ck-g500);
margin-top: 2px;
}
.ck-kpi--blue .ck-kpi-ic {
background: var(--ck-blue-l);
color: var(--ck-blue);
}
.ck-kpi--teal .ck-kpi-ic {
background: var(--ck-teal-l);
color: var(--ck-teal);
}
.ck-kpi--pink .ck-kpi-ic {
background: var(--ck-pink-l);
color: var(--ck-pink);
}
.ck-kpi--amber .ck-kpi-ic {
background: var(--ck-amber-l);
color: var(--ck-amber);
}
.ck-kpi--orange .ck-kpi-ic {
background: var(--ck-orange-l);
color: var(--ck-orange);
}
.ck-kpi--red .ck-kpi-ic {
background: var(--ck-red-l);
color: var(--ck-red);
}
/* CHARTS */
.ck-charts {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 320px 320px;
gap: 14px;
}
.ck-chart-card {
background: #fff;
border: 1px solid var(--ck-g200);
border-radius: 12px;
display: flex;
flex-direction: column;
box-shadow: 0 1px 3px rgb(0 0 0 / 5%);
}
.ck-chart-1 {
grid-column: 1;
grid-row: 1;
}
.ck-chart-2 {
grid-column: 2;
grid-row: 1;
}
.ck-chart-3 {
grid-column: 1 / span 2;
grid-row: 2;
}
.ck-chart-h {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
border-bottom: 1px solid var(--ck-g100);
h4 {
margin: 0;
font-size: 13px;
font-weight: 600;
color: var(--ck-g700);
}
}
.ck-chart-ic {
color: var(--ck-teal);
font-size: 14px;
}
.ck-chart-body {
padding: 14px;
flex: 1;
position: relative;
min-height: 0;
}
/* BUTTONS */
.ck-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border: 1px solid var(--ck-g300);
background: #fff;
border-radius: 8px;
font-size: 13px;
font-weight: 500;
color: var(--ck-g700);
cursor: pointer;
font-family: inherit;
transition: all 0.15s;
&:hover:not(:disabled) {
background: var(--ck-g50);
border-color: var(--ck-g500);
}
&:disabled {
opacity: 0.45;
cursor: not-allowed;
}
}
.ck-btn-t {
background: var(--ck-teal);
border-color: var(--ck-teal);
color: #fff;
&:hover:not(:disabled) {
background: #0a6d5c;
border-color: #0a6d5c;
}
}
.ck-btn-g {
/* default style — already grey */
}
.ck-btn-csv,
.ck-btn-xls,
.ck-btn-pdf {
color: #fff;
border: none;
}
.ck-btn-csv {
background: #16a34a;
&:hover:not(:disabled) {
background: #15803d;
}
}
.ck-btn-xls {
background: #1d6f42;
&:hover:not(:disabled) {
background: #155f37;
}
}
.ck-btn-pdf {
background: #b91c1c;
&:hover:not(:disabled) {
background: #991b1b;
}
}
/* INPUTS */
.ck-inp {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--ck-g200);
border-radius: 8px;
font-size: 13px;
color: var(--ck-g900);
background: #fff;
font-family: inherit;
transition:
border-color 0.15s,
box-shadow 0.15s;
&:focus {
outline: none;
border-color: var(--ck-teal);
box-shadow: 0 0 0 3px rgb(14 126 107 / 15%);
}
}
/* UPLOAD */
.ck-up-card,
.ck-info-card {
background: #fff;
border: 1px solid var(--ck-g200);
border-radius: 12px;
padding: 22px;
box-shadow: 0 1px 3px rgb(0 0 0 / 5%);
}
.ck-up-drop {
border: 2px dashed var(--ck-g300);
border-radius: 12px;
padding: 40px 24px;
text-align: center;
cursor: pointer;
transition: all 0.15s;
background: var(--ck-g50);
&:hover,
&.dragging {
border-color: var(--ck-teal);
background: var(--ck-teal-l);
}
}
.ck-up-ic {
font-size: 38px;
color: var(--ck-teal);
margin-bottom: 8px;
}
.ck-up-t {
margin: 6px 0 4px;
font-size: 16px;
font-weight: 600;
color: var(--ck-g900);
}
.ck-up-s {
margin: 0;
font-size: 13px;
color: var(--ck-g500);
}
.ck-up-progress {
margin-top: 18px;
}
.ck-up-progress-h {
display: flex;
justify-content: space-between;
font-size: 12px;
color: var(--ck-g500);
margin-bottom: 6px;
}
.ck-up-progress-name {
font-weight: 500;
color: var(--ck-g700);
}
.ck-up-progress-bar {
width: 100%;
height: 8px;
background: var(--ck-g100);
border-radius: 4px;
overflow: hidden;
}
.ck-up-progress-fill {
height: 100%;
background: var(--ck-teal);
transition: width 0.2s ease-out;
&.success {
background: var(--ck-green);
}
&.error {
background: var(--ck-red);
}
}
.ck-up-report {
margin-top: 18px;
border-radius: 10px;
padding: 14px 18px;
&--ok {
background: var(--ck-green-l);
color: #14532d;
}
&--err {
background: var(--ck-red-l);
color: #7f1d1d;
}
}
.ck-up-report-h {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 600;
margin-bottom: 10px;
}
.ck-up-report-ic {
font-size: 16px;
}
.ck-up-stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.ck-up-stat {
background: #fff;
border-radius: 8px;
padding: 10px 12px;
text-align: center;
}
.ck-up-stat-n {
font-size: 20px;
font-weight: 700;
color: var(--ck-g900);
line-height: 1.1;
}
.ck-up-stat-l {
font-size: 11px;
color: var(--ck-g500);
margin-top: 2px;
}
.ck-up-err {
font-size: 13px;
margin-top: 4px;
font-family: 'DM Mono', monospace;
}
.ck-info-card {
h4 {
margin: 0 0 12px;
font-size: 14px;
font-weight: 600;
color: var(--ck-g700);
}
}
.ck-schema {
display: flex;
flex-direction: column;
gap: 8px;
}
.ck-schema-row {
display: flex;
align-items: center;
gap: 14px;
font-size: 13px;
code {
background: var(--ck-g100);
padding: 3px 9px;
border-radius: 5px;
font-family: 'DM Mono', monospace;
font-size: 12px;
color: var(--ck-blue);
min-width: 170px;
display: inline-block;
}
span {
color: var(--ck-g700);
}
}
/* FILTERS */
.ck-filters {
background: #fff;
border: 1px solid var(--ck-g200);
border-radius: 12px;
padding: 14px;
display: grid;
grid-template-columns: repeat(6, 1fr) auto;
gap: 12px;
align-items: end;
}
.ck-filter {
display: flex;
flex-direction: column;
gap: 4px;
label {
font-size: 11px;
font-weight: 600;
color: var(--ck-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
}
.ck-filter-actions {
align-self: end;
}
/* EXPORT BAR */
.ck-export-bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 8px 0;
}
.ck-export-meta {
font-size: 13px;
color: var(--ck-g500);
}
.ck-export-btns {
display: flex;
gap: 8px;
}
/* TABLE */
.ck-table-wrap {
background: #fff;
border: 1px solid var(--ck-g200);
border-radius: 12px;
overflow: hidden;
box-shadow: 0 1px 3px rgb(0 0 0 / 5%);
}
.ck-table {
width: 100%;
border-collapse: collapse;
th {
text-align: left;
padding: 11px 14px;
font-size: 11px;
font-weight: 600;
color: var(--ck-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
background: var(--ck-g50);
border-bottom: 1px solid var(--ck-g200);
}
td {
padding: 11px 14px;
border-bottom: 1px solid var(--ck-g100);
font-size: 13px;
color: var(--ck-g700);
}
tr:hover td {
background: var(--ck-g50);
}
tr:last-child td {
border-bottom: none;
}
}
.mono {
font-family: 'DM Mono', monospace;
font-size: 12px;
letter-spacing: 0.02em;
}
.ck-empty {
text-align: center;
padding: 32px;
color: var(--ck-g500);
font-style: italic;
}
.ck-gen {
display: inline-block;
width: 22px;
height: 22px;
line-height: 22px;
text-align: center;
font-size: 11px;
font-weight: 700;
border-radius: 50%;
background: var(--ck-g100);
color: var(--ck-g500);
&.m {
background: var(--ck-blue-l);
color: var(--ck-blue);
}
&.f {
background: var(--ck-pink-l);
color: var(--ck-pink);
}
}
.ck-pager {
display: flex;
justify-content: center;
align-items: center;
gap: 14px;
margin-top: 8px;
font-size: 13px;
color: var(--ck-g700);
}
/* SEARCH */
.ck-search-bar {
background: #fff;
border: 1px solid var(--ck-g200);
border-radius: 12px;
padding: 12px;
display: flex;
gap: 8px;
align-items: center;
}
.ck-search-type {
width: 200px;
flex-shrink: 0;
}
.ck-search-inp {
flex: 1;
}
.ck-search-results {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.ck-search-card {
background: #fff;
border: 1px solid var(--ck-g200);
border-radius: 12px;
padding: 14px;
box-shadow: 0 1px 3px rgb(0 0 0 / 5%);
}
.ck-search-card-head {
display: flex;
align-items: center;
gap: 12px;
padding-bottom: 12px;
border-bottom: 1px solid var(--ck-g100);
margin-bottom: 12px;
}
.ck-search-card-av {
width: 38px;
height: 38px;
border-radius: 50%;
background: linear-gradient(135deg, var(--ck-teal), var(--ck-blue));
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 600;
flex-shrink: 0;
}
.ck-search-card-name {
font-size: 14px;
font-weight: 600;
color: var(--ck-g900);
}
.ck-search-card-cif {
font-size: 11px;
color: var(--ck-g500);
}
.ck-search-card-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px 14px;
font-size: 12px;
> div {
display: flex;
flex-direction: column;
label {
font-size: 10px;
font-weight: 600;
color: var(--ck-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 1px;
}
span {
color: var(--ck-g900);
}
}
}
.ck-search-card-addr {
grid-column: 1 / -1;
}
.ck-empty-card {
background: #fff;
border: 1px solid var(--ck-g200);
border-radius: 12px;
padding: 50px;
text-align: center;
color: var(--ck-g500);
p {
margin: 8px 0 0;
font-size: 13px;
}
}
.ck-empty-ic {
font-size: 32px;
color: var(--ck-g300);
}
/* TOAST */
.ck-toast {
position: fixed;
bottom: 32px;
left: 50%;
transform: translateX(-50%) translateY(20px);
background: var(--ck-navy);
color: #fff;
padding: 12px 22px;
border-radius: 10px;
font-size: 13px;
font-weight: 500;
box-shadow: 0 6px 20px rgb(0 0 0 / 25%);
opacity: 0;
pointer-events: none;
transition: all 0.25s;
z-index: 1500;
&.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
/* RESPONSIVE */
@media (width <= 1100px) {
.ck-kpis {
grid-template-columns: repeat(3, 1fr);
}
.ck-charts {
grid-template-columns: 1fr;
grid-template-rows: 280px 280px 280px;
}
.ck-chart-1,
.ck-chart-2 {
grid-column: 1;
}
.ck-chart-1 {
grid-row: 1;
}
.ck-chart-2 {
grid-row: 2;
}
.ck-chart-3 {
grid-column: 1;
grid-row: 3;
}
.ck-filters {
grid-template-columns: repeat(3, 1fr);
}
.ck-search-results {
grid-template-columns: 1fr;
}
}
@media (width <= 720px) {
.ck-kpis {
grid-template-columns: repeat(2, 1fr);
}
.ck-filters {
grid-template-columns: 1fr 1fr;
}
.ck-up-stats {
grid-template-columns: repeat(2, 1fr);
}
}
/* PRINT — for PDF export */
@media print {
:host {
background: #fff;
}
.ck-tabs,
.ck-filters,
.ck-search-bar,
.ck-export-bar,
.ck-pager,
.ck-toast {
display: none !important;
}
.ck-table-wrap,
.ck-search-card {
box-shadow: none;
border: 1px solid #ccc;
}
.ck-table th {
background: #f0f0f0 !important;
color: #000 !important;
}
}
@@ -6,16 +6,644 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { Component } from '@angular/core';
import { AfterViewInit, Component, ElementRef, ViewChild, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { Chart, registerables } from 'chart.js';
import { SAMPLE_CKYC_FILE } from './ckyc-sample-data';
Chart.register(...registerables);
interface CkycCustomer {
id: string;
seq: number;
title: string;
firstName: string;
middleName: string;
lastName: string;
fullName: string;
fatherName: string;
motherName: string;
gender: 'M' | 'F' | '';
dob: string;
occupationCode: string;
address1: string;
address2: string;
address3: string;
city: string;
district: string;
state: string;
country: string;
pin: string;
mobile: string;
uploadDate: string;
branch: string;
officer: string;
pan: string;
aadhaarMasked: string;
passport: string;
voterId: string;
drivingLicense: string;
documents: string[];
}
interface UploadResult {
status: 'idle' | 'parsing' | 'success' | 'error';
fileName: string;
totalRecords: number;
customers: number;
ids: number;
documents: number;
errors: string[];
startedAt?: number;
finishedAt?: number;
}
@Component({
selector: 'mifosx-ckyc-reports',
templateUrl: './ckyc-reports.component.html',
styleUrls: ['./ckyc-reports.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
FaIconComponent
FaIconComponent,
FormsModule
]
})
export class CkycReportsComponent {}
export class CkycReportsComponent implements OnInit, AfterViewInit {
@ViewChild('stateChartCanvas') stateChartCanvas!: ElementRef<HTMLCanvasElement>;
@ViewChild('branchChartCanvas') branchChartCanvas!: ElementRef<HTMLCanvasElement>;
@ViewChild('trendChartCanvas') trendChartCanvas!: ElementRef<HTMLCanvasElement>;
/** Active tab */
activeTab: 'dashboard' | 'upload' | 'customers' | 'search' = 'dashboard';
/** All customers (parsed + seed) */
customers: CkycCustomer[] = [];
/** Upload state */
upload: UploadResult = {
status: 'idle',
fileName: '',
totalRecords: 0,
customers: 0,
ids: 0,
documents: 0,
errors: []
};
uploadProgress = 0;
isDragging = false;
/** Customer list filters */
filterName = '';
filterBranch = '';
filterCity = '';
filterGender: '' | 'M' | 'F' = '';
filterDateFrom = '';
filterDateTo = '';
/** Search panel */
searchType: 'name' | 'mobile' | 'pan' | 'cif' = 'name';
searchQuery = '';
searchResults: CkycCustomer[] = [];
searchPerformed = false;
/** Pagination */
pageSize = 25;
pageIndex = 0;
/** Charts */
private stateChart: Chart | null = null;
private branchChart: Chart | null = null;
private trendChart: Chart | null = null;
/** Toast */
toastMsg = '';
toastVisible = false;
private toastTimer: any;
ngOnInit(): void {
// Seed data so the dashboard isn't empty on first load
this.parseAndStore(SAMPLE_CKYC_FILE, 'ckyc-sample-IN4711.txt');
}
ngAfterViewInit(): void {
// Render charts once view is ready (next tick so the canvases are in DOM)
setTimeout(() => this.renderCharts(), 0);
}
// -------------------- Tabs --------------------
showTab(tab: 'dashboard' | 'upload' | 'customers' | 'search'): void {
this.activeTab = tab;
if (tab === 'dashboard') {
setTimeout(() => this.renderCharts(), 0);
}
}
// -------------------- KPIs --------------------
get totalCustomers(): number {
return this.customers.length;
}
get maleCount(): number {
return this.customers.filter((c) => c.gender === 'M').length;
}
get femaleCount(): number {
return this.customers.filter((c) => c.gender === 'F').length;
}
get missingPanCount(): number {
return this.customers.filter((c) => !c.pan).length;
}
get missingAadhaarCount(): number {
return this.customers.filter((c) => !c.aadhaarMasked).length;
}
get duplicateMobileCount(): number {
const counts = new Map<string, number>();
for (const c of this.customers) {
if (!c.mobile) continue;
counts.set(c.mobile, (counts.get(c.mobile) || 0) + 1);
}
let dups = 0;
counts.forEach((n) => {
if (n > 1) dups += n;
});
return dups;
}
// -------------------- Filtered list --------------------
get filteredCustomers(): CkycCustomer[] {
const name = this.filterName.trim().toLowerCase();
const branch = this.filterBranch.trim().toLowerCase();
const city = this.filterCity.trim().toLowerCase();
const from = this.filterDateFrom ? new Date(this.filterDateFrom).getTime() : null;
const to = this.filterDateTo ? new Date(this.filterDateTo).getTime() : null;
return this.customers.filter((c) => {
if (name && !c.fullName.toLowerCase().includes(name)) return false;
if (branch && !c.branch.toLowerCase().includes(branch)) return false;
if (city && !c.city.toLowerCase().includes(city)) return false;
if (this.filterGender && c.gender !== this.filterGender) return false;
if (from || to) {
const t = this.parseDdMmYyyy(c.uploadDate);
if (!t) return false;
if (from && t < from) return false;
if (to && t > to) return false;
}
return true;
});
}
get pagedCustomers(): CkycCustomer[] {
const start = this.pageIndex * this.pageSize;
return this.filteredCustomers.slice(start, start + this.pageSize);
}
get pageCount(): number {
return Math.max(1, Math.ceil(this.filteredCustomers.length / this.pageSize));
}
changePage(delta: number): void {
this.pageIndex = Math.max(0, Math.min(this.pageCount - 1, this.pageIndex + delta));
}
resetFilters(): void {
this.filterName = '';
this.filterBranch = '';
this.filterCity = '';
this.filterGender = '';
this.filterDateFrom = '';
this.filterDateTo = '';
this.pageIndex = 0;
}
// -------------------- Search --------------------
performSearch(): void {
const q = this.searchQuery.trim().toLowerCase();
this.searchPerformed = true;
if (!q) {
this.searchResults = [];
return;
}
this.searchResults = this.customers.filter((c) => {
switch (this.searchType) {
case 'name':
return c.fullName.toLowerCase().includes(q);
case 'mobile':
return c.mobile.includes(q);
case 'pan':
return c.pan.toLowerCase().includes(q);
case 'cif':
return c.id.toLowerCase().includes(q);
}
});
}
// -------------------- Upload --------------------
onDragOver(e: DragEvent): void {
e.preventDefault();
e.stopPropagation();
this.isDragging = true;
}
onDragLeave(e: DragEvent): void {
e.preventDefault();
e.stopPropagation();
this.isDragging = false;
}
onDrop(e: DragEvent): void {
e.preventDefault();
e.stopPropagation();
this.isDragging = false;
const files = e.dataTransfer?.files;
if (files && files.length) this.handleFile(files[0]);
}
onFilePicked(e: Event): void {
const input = e.target as HTMLInputElement;
if (input.files && input.files.length) this.handleFile(input.files[0]);
input.value = '';
}
private handleFile(file: File): void {
this.upload = {
status: 'parsing',
fileName: file.name,
totalRecords: 0,
customers: 0,
ids: 0,
documents: 0,
errors: [],
startedAt: Date.now()
};
this.uploadProgress = 0;
// Animate progress while reading
const progressTimer = setInterval(() => {
if (this.uploadProgress < 90) this.uploadProgress += Math.random() * 12;
if (this.uploadProgress > 90) this.uploadProgress = 90;
}, 120);
const reader = new FileReader();
reader.onload = () => {
clearInterval(progressTimer);
try {
this.parseAndStore(reader.result as string, file.name);
this.uploadProgress = 100;
this.upload.status = 'success';
this.upload.finishedAt = Date.now();
this.toast(`Parsed ${this.upload.customers} customers from ${file.name}`);
// Re-render dashboard charts with new data
if (this.activeTab === 'dashboard') setTimeout(() => this.renderCharts(), 0);
} catch (err: any) {
this.upload.status = 'error';
this.upload.errors.push(err?.message || 'Failed to parse file');
this.uploadProgress = 0;
}
};
reader.onerror = () => {
clearInterval(progressTimer);
this.upload.status = 'error';
this.upload.errors.push('Failed to read file');
this.uploadProgress = 0;
};
reader.readAsText(file);
}
// -------------------- CKYC Parser --------------------
/**
* Parses a pipe-delimited CKYC file. Records:
* 10 = file header
* 20 = customer demographic (positional fields)
* 30 = KYC ID record (linked by sequence)
* 70 = document attachment (linked by sequence)
*/
private parseAndStore(text: string, fileName: string): void {
const lines = text.split(/\r?\n/).filter((l) => l.trim().length > 0);
const newCustomers = new Map<number, CkycCustomer>();
let recordCount = 0;
let idCount = 0;
let docCount = 0;
for (const line of lines) {
const f = line.split('|');
const type = f[0];
recordCount++;
if (type === '20') {
const seq = parseInt(f[1], 10);
const cust: CkycCustomer = {
id: 'CKYC-' + (f[15] || seq.toString()).padStart(8, '0'),
seq,
title: (f[16] || '').trim(),
firstName: (f[17] || '').trim(),
middleName: (f[18] || '').trim(),
lastName: (f[19] || '').trim(),
fullName: '',
fatherName: [
f[26],
f[27],
f[28]
]
.map((s) => (s || '').trim())
.filter(Boolean)
.join(' '),
motherName: [
f[31],
f[32],
f[33]
]
.map((s) => (s || '').trim())
.filter(Boolean)
.join(' '),
gender: ((f[35] || '').trim() as 'M' | 'F') || '',
dob: (f[39] || '').trim(),
occupationCode: (f[38] || '').trim(),
address1: (f[51] || '').trim(),
address2: (f[52] || '').trim(),
address3: (f[53] || '').trim(),
city: (f[54] || '').trim(),
district: (f[55] || '').trim(),
state: (f[56] || '').trim(),
country: (f[57] || '').trim(),
pin: (f[58] || '').trim(),
mobile: this.firstNonEmpty([
f[80],
f[78],
f[83]
]),
uploadDate: (f[88] || '').trim(),
branch: (f[89] || '').trim(),
officer: (f[92] || '').trim(),
pan: '',
aadhaarMasked: '',
passport: '',
voterId: '',
drivingLicense: '',
documents: []
};
cust.fullName = [
cust.title,
cust.firstName,
cust.middleName,
cust.lastName
]
.map((s) => s.trim())
.filter(Boolean)
.join(' ');
newCustomers.set(seq, cust);
} else if (type === '30') {
const seq = parseInt(f[1], 10);
const idType = (f[2] || '').trim();
const idValue = (f[3] || '').trim();
const cust = newCustomers.get(seq);
if (cust) {
if (idType === 'C') cust.pan = idValue;
else if (idType === 'E') cust.aadhaarMasked = idValue;
else if (idType === 'B') cust.passport = idValue;
else if (idType === 'V') cust.voterId = idValue;
else if (idType === 'D') cust.drivingLicense = idValue;
}
idCount++;
} else if (type === '70') {
const seq = parseInt(f[1], 10);
const file = (f[2] || '').trim();
const cust = newCustomers.get(seq);
if (cust && file) cust.documents.push(file);
docCount++;
}
}
const list = Array.from(newCustomers.values());
// Replace store on each parse so KPIs reflect the latest file
this.customers = list;
this.upload.fileName = fileName;
this.upload.totalRecords = recordCount;
this.upload.customers = list.length;
this.upload.ids = idCount;
this.upload.documents = docCount;
this.pageIndex = 0;
}
private firstNonEmpty(values: (string | undefined)[]): string {
for (const v of values) {
if (v && v.trim()) return v.trim();
}
return '';
}
/** DD-MM-YYYY → epoch ms */
private parseDdMmYyyy(s: string): number | null {
if (!s) return null;
const m = s.match(/^(\d{2})-(\d{2})-(\d{4})$/);
if (!m) return null;
const dt = new Date(parseInt(m[3], 10), parseInt(m[2], 10) - 1, parseInt(m[1], 10));
return dt.getTime();
}
// -------------------- Charts --------------------
private renderCharts(): void {
this.renderStateChart();
this.renderBranchChart();
this.renderTrendChart();
}
private renderStateChart(): void {
if (!this.stateChartCanvas) return;
const counts = this.groupBy(this.customers, (c) => c.state || 'Unknown');
const labels = Array.from(counts.keys());
const data = Array.from(counts.values());
const palette = [
'#0e7e6b',
'#1d4ed8',
'#d97706',
'#7c3aed',
'#dc2626',
'#0891b2',
'#65a30d'
];
if (this.stateChart) this.stateChart.destroy();
this.stateChart = new Chart(this.stateChartCanvas.nativeElement, {
type: 'doughnut',
data: {
labels,
datasets: [{ data, backgroundColor: labels.map((_, i) => palette[i % palette.length]), borderWidth: 0 }]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { position: 'bottom', labels: { font: { size: 11 } } } }
}
});
}
private renderBranchChart(): void {
if (!this.branchChartCanvas) return;
const counts = this.groupBy(this.customers, (c) => c.branch || 'Unknown');
// Top 10 branches
const sorted = Array.from(counts.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 10);
const labels = sorted.map((e) => e[0]);
const data = sorted.map((e) => e[1]);
if (this.branchChart) this.branchChart.destroy();
this.branchChart = new Chart(this.branchChartCanvas.nativeElement, {
type: 'bar',
data: {
labels,
datasets: [{ label: 'Customers', data, backgroundColor: '#1d4ed8', borderRadius: 6 }]
},
options: {
responsive: true,
maintainAspectRatio: false,
indexAxis: 'y',
plugins: { legend: { display: false } },
scales: { x: { beginAtZero: true, ticks: { precision: 0 } } }
}
});
}
private renderTrendChart(): void {
if (!this.trendChartCanvas) return;
const counts = this.groupBy(this.customers, (c) => c.uploadDate || 'Unknown');
const sorted = Array.from(counts.entries())
.filter((e) => e[0] !== 'Unknown')
.sort((a, b) => (this.parseDdMmYyyy(a[0]) || 0) - (this.parseDdMmYyyy(b[0]) || 0));
const labels = sorted.map((e) => e[0]);
const data = sorted.map((e) => e[1]);
if (this.trendChart) this.trendChart.destroy();
this.trendChart = new Chart(this.trendChartCanvas.nativeElement, {
type: 'line',
data: {
labels,
datasets: [
{
label: 'Uploads',
data,
borderColor: '#0e7e6b',
backgroundColor: 'rgba(14, 126, 107, 0.12)',
fill: true,
tension: 0.35,
pointRadius: 4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: { y: { beginAtZero: true, ticks: { precision: 0 } } }
}
});
}
private groupBy<T>(items: T[], keyFn: (t: T) => string): Map<string, number> {
const m = new Map<string, number>();
for (const item of items) {
const k = keyFn(item) || 'Unknown';
m.set(k, (m.get(k) || 0) + 1);
}
return m;
}
// -------------------- Exports --------------------
exportCsv(rows?: CkycCustomer[]): void {
this.downloadDelimited(rows || this.filteredCustomers, ',', 'csv');
}
exportExcel(rows?: CkycCustomer[]): void {
// Tab-delimited .xls — Excel opens it natively. Avoids adding xlsx dep.
this.downloadDelimited(rows || this.filteredCustomers, '\t', 'xls');
}
exportPdf(): void {
// Native print-to-PDF — uses the print stylesheet defined in the SCSS.
window.print();
}
private downloadDelimited(rows: CkycCustomer[], sep: string, ext: 'csv' | 'xls'): void {
const headers = [
'CIF',
'Title',
'First Name',
'Middle Name',
'Last Name',
'Father Name',
'Mother Name',
'Gender',
'DOB',
'Mobile',
'PAN',
'Aadhaar (Masked)',
'Address',
'City',
'District',
'State',
'PIN',
'Branch',
'Officer',
'Upload Date'
];
const escape = (v: string): string => {
const s = (v || '').replace(/"/g, '""');
if (sep === ',' && (s.includes(',') || s.includes('"') || s.includes('\n'))) return `"${s}"`;
if (sep === '\t' && /[\t\n]/.test(s)) return `"${s}"`;
return s;
};
const lines = [headers.join(sep)];
for (const c of rows) {
lines.push(
[
c.id,
c.title,
c.firstName,
c.middleName,
c.lastName,
c.fatherName,
c.motherName,
c.gender,
c.dob,
c.mobile,
c.pan,
c.aadhaarMasked,
[
c.address1,
c.address2,
c.address3
]
.filter(Boolean)
.join(' '),
c.city,
c.district,
c.state,
c.pin,
c.branch,
c.officer,
c.uploadDate
]
.map(escape)
.join(sep)
);
}
const blob = new Blob([lines.join('\n')], {
type: ext === 'csv' ? 'text/csv;charset=utf-8' : 'application/vnd.ms-excel'
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `ckyc-customers-${new Date().toISOString().slice(0, 10)}.${ext}`;
a.click();
URL.revokeObjectURL(url);
this.toast(`Exported ${rows.length} records as ${ext.toUpperCase()}`);
}
// -------------------- Toast --------------------
toast(msg: string): void {
this.toastMsg = msg;
this.toastVisible = true;
if (this.toastTimer) clearTimeout(this.toastTimer);
this.toastTimer = setTimeout(() => (this.toastVisible = false), 2800);
}
}
@@ -0,0 +1,86 @@
/**
* 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/.
*/
/**
* Sample CKYC pipe-delimited file (NAG BANK / GANJ BASODA branch).
* Used to seed the reports dashboard so the page is meaningful before any upload.
*/
export const SAMPLE_CKYC_FILE = `10|24401|IN4711|MP|70|31-08-2020|V1.1|||||
20|1|01|01|||||||||||01|||01|78|MR|BRAHAM |PRAKASH |JAIN |||||||01|MR|MISHRI|LAL|JAIN||MRS|A|B|C||M|02|IN|B-01|10-08-1968|||||||||01|02|||||02|WARD N.11|BRAHAM KIRANA STORE|PATHASHALA ROAD BASODA|BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD N.11|BRAHAM KIRANA STORE|PATHASHALA ROAD BASODA|BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9827612655|||||31-08-2020|GANJ BASODA|31-08-2020|01|GYANENDRA SINGH GAUTAM|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|4|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|1|C|AAQPJ8560J||01|02|||||
70|1|78_Sign.jpeg|09|01|01||||
70|1|78_Photo.jpeg|02|01|01||||
20|2|01|01|||||||||||01|||01|2181|MR|DEVI |SHING |KUSHWA H|||||||01|MR|CHATAR SINGH||||MRS|BUDA|BAI|||M|01|IN|B-01|01-01-1973|||||||||01|02|||||02|WARD NO 13|JIWAJIPUR|BASODA|BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 13|JIWAJIPUR|BASODA|BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||8602462283|91|8602462283|||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|2|E|XXXXXXXX6509||01|02|||||
20|3|01|01|||||||||||01|||01|6576|MR|RAM |MOHAN |SINGH |||||||01|MR|MEHTAB||DANGI||MRS|A|B|C||M|01|IN|B-01|01-01-1963|||||||||01|02|||||02|WARD NO 18|PACHMA ROAD|BASODA|BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 18|PACHMA ROAD|BASODA|BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9826628312|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|3|C|AQPPR9351Q||01|02|||||
20|4|01|01|||||||||||01|||01|33502|MRS|RUKSHAR |BEE||||||||01|MR|HASIN|KHAN|||MRS|A|B|C||F|01|IN|O-04|01-01-1991|||||||||01|02|||||02|PACHAMA|GAMAKHAR VIDISHA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|PACHAMA|GAMAKHAR VIDISHA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9009372818|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|4|E|XXXXXXXX3695||01|02|||||
20|5|01|01|||||||||||01|||01|33720|MR|ANKUR||PINGLE|||||||01|MR|SANTOSH KUMAR||||MRS|A|B|C||M|02|IN|B-01|18-06-1982|||||||||01|02|||||02|WARD NO 16|PACHMA ROAD|HOUSE NO 7|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 16|PACHMA ROAD|HOUSE NO 7|GANJ BASODA|VIDISHA|MP|IN|464221|01|||||||||||91|9993262850||||9993262850|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|5|C|BDYPP8413Q||01|02|||||
20|6|01|01|||||||||||01|||01|33847|MR|RAGHUVEER||VISHWAKARMA|||||||01|MR|SHAYAMLAL ||||MRS|A|B|C||M|01|IN|X-01|01-01-1965|||||||||01|02|||||02|WARD NO 6|LADDHA AGENCY VALI GALI BARETH ROAD|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 6|LADDHA AGENCY VALI GALI BARETH ROAD|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||7869252915|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|6|E|XXXXXXXX2861||01|02|||||
20|7|01|01|||||||||||01|||01|34081|MR|ROHIT ||JAIN|||||||01|MR|MAHESH JAIN||||MRS|A|B|C||M|02|IN|O-05|01-01-1988|||||||||01|02|||||02|HOUSE NO 411 BAHADURPUR|DIST ASHOKNAGAR GUNA||BAHADURPUR|ASHOK NAGAR|MP|IN|473443|01||Y|02|HOUSE NO 411 BAHADURPUR|DIST ASHOKNAGAR GUNA||BAHADURPUR|ASHOK NAGAR|MP|IN|473443|01||||||||||||||||9713583272|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|7|E|XXXXXXXX6260||01|02|||||
20|8|01|01|||||||||||01|||01|34177|MR|MAHENDRA||SINGH|||||||01|MR|MOHAN |SINGH|||MRS|A|B|C||M|02|IN|B-01|12-08-1998|||||||||01|02|||||02|WARD 21|SARDA NAGAR RAJODA ROAD|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD 21|SARDA NAGAR RAJODA ROAD|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01|||||||||||||||||||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|8|E|XXXXXXXX3543||01|02|||||
20|9|01|01|||||||||||01|||01|34178|MRS|SHREYA||SHARMA|||||||01|MR|PRAVEEN SHARMA||||MRS|A|B|C||F|02|IN|O-05|27-09-1997|||||||||01|02|||||02|WARD NO 24 HOUSE 5 KRISHNAPURA GALI|HANUMAN CHOWK|BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 24 HOUSE 5 KRISHNAPURA GALI|HANUMAN CHOWK|BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||8962639307|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|9|C|GZCPS9037N||01|02|||||
20|10|01|01|||||||||||01|||01|34181|MR|SORABH||JAIN|||||||01|MR|SURESH JAIN||||MRS|A|B|C||M|02|IN|B-01|24-11-1986|||||||||01|02|||||02|WARD NO 8 PARASARI NADI KE PUL KE |PASS GANJ BASODA|VIDISHA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 8 PARASARI NADI KE PUL KE |PASS GANJ BASODA|VIDISHA|GANJ BASODA|VIDISHA|MP|IN|464221|01|||||||||||91|9131156740||||9131156740|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|10|C|APUPJ8338R||01|02|||||
20|11|01|01|||||||||||01|||01|34185|MR|DARMENDRA||KUSHWAH|||||||01|MR|GULAB||||MRS|A|B|C||M|02|IN|B-01|01-01-1991|||||||||01|02|||||02|WARD NO 13 CHAK SWROOP NAGAR|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 13 CHAK SWROOP NAGAR|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9907260317|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|11|E|XXXXXXXX7722||01|02|||||
20|12|01|01|||||||||||01|||01|34186|MRS|MANESHA||RAJPUT|||||||01|MR|VEER SINGH||||MRS|A|B|C||F|02|IN|O-04|01-01-1979|||||||||01|02|||||02|WARD NO 21|LOHA MEEL ROAD BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 21|LOHA MEEL ROAD BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||7987077923|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|12|E|XXXXXXXX5640||01|02|||||
20|13|01|01|||||||||||01|||01|34188|MR|YOGESH||DANGI|||||||01|MR|BIHARI SINGH||||MRS|A|B|C||M|02|IN|B-01|01-01-1994|||||||||01|02|||||02|SEMRA POST PABAI|BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|SEMRA POST PABAI|BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9009571392|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|13|E|XXXXXXXX8016||01|02|||||
20|14|01|01|||||||||||01|||01|34190|MRS|RAJNI||SEN|||||||01|MR|SACHIN SEN||||MRS|A|B|C||F|01|IN|O-04|01-01-1990|||||||||01|02|||||02|WARD NO 16|PANJABI DHARAM SHALA GALI|BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 16|PANJABI DHARAM SHALA GALI|BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9584363875|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|14|B|WHU2321339||01|02|||||
20|15|01|01|||||||||||01|||01|34191|MR|SACHIN||SEN|||||||01|MR|GANESH RAM||||MRS|A|B|C||M|01|IN|B-01|13-08-1984|||||||||01|02|||||02|WARD NO 16 PANJABI DHARMSHALA |GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 16 PANJABI DHARMSHALA |GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9584363875|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|15|C|HONPS2337E||01|02|||||
20|16|01|01|||||||||||01|||01|34194|MRS|JAMNA||MALVIYA|||||||01|MR|BABLU||MALVIYA||MRS|A|B|C||F|01|IN|O-04|01-01-1990|||||||||01|02|||||02|MIRJAPUR|WARD NO 15|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|MIRJAPUR|WARD NO 15|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9977516599|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|16|E|XXXXXXXX6332||01|02|||||
20|17|01|01|||||||||||01|||01|34195|MR|LAKY||AHIRWAR|||||||01|MR|MOHAN LAL||||MRS|A|B|C||M|02|IN|O-05|01-01-1996|||||||||01|02|||||02|WARD NO 10|GOUSHALA|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 10|GOUSHALA|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9131915301|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|17|E|XXXXXXXX2402||01|02|||||
20|18|01|01|||||||||||01|||01|34197|MRS|PRAVESHIKA ||JAIN|||||||01|MR|RISHI JAIN||||MRS|MAMTA||||F|01|IN|O-04|07-08-1989|||||||||01|02|||||02|WARD NO 21 GANDHI MARG SIRONJ CHORA|BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 21 GANDHI MARG SIRONJ CHORA|BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9893971160|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|18|E|XXXXXXXX0211||01|02|||||
20|19|01|01|||||||||||01|||01|34201|MR|HEERA LAL||JAIN HUF|||||||01|MR|PURAN CHAND||||MRS|A|B|C||M|01|IN|B-01|01-01-1939|||||||||01|02|||||02|WARD NO 12|JAIN MANDIR GALI NEHRU CHOWK|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 12|JAIN MANDIR GALI NEHRU CHOWK|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||8962445182|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|19|E|XXXXXXXX1723||01|02|||||
20|20|01|01|||||||||||01|||01|34202|MR|RAJESH|KUMAR|JAIN HUF|||||||01|MR|HEERA LAL||||MRS|A|B|C||M|01|IN|B-01|12-05-1962|||||||||01|02|||||02|WARD NO 12 JAIN MANDIR GALI|NEHRU CHOWK BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 12 JAIN MANDIR GALI|NEHRU CHOWK BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9408510555|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|20|E|XXXXXXXX9147||01|02|||||
20|21|01|01|||||||||||01|||01|34203|MR|SHIV|KUMAR|JAIN HUF|||||||01|MR|HEERA LAL||||MRS|A|B|C||M|01|IN|B-01|10-05-1976|||||||||01|02|||||02|WARD NO 12|JAIN MANDIR GALI NEHRU CHOWK|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 12|JAIN MANDIR GALI NEHRU CHOWK|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9425432076|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|21|E|XXXXXXXX9363||01|02|||||
20|22|01|01|||||||||||01|||01|34205|MRS|KIRTI||BAIRAGIE|||||||01|MR|MUKESH||||MRS|A|B|C||F|02|IN|O-05|01-01-2000|||||||||01|02|||||02|WARD NO 7 LODHI COLONY|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 7 LODHI COLONY|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9644750547|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|22|E|XXXXXXXX6035||01|02|||||
20|23|01|01|||||||||||01|||01|34208|MR|SANJIV| |RAIKWAR|||||||01|MR|SUNDAR|LAL|RAIKWAR||MRS|A|B|C||M|01|IN|B-01|03-05-1986|||||||||01|02|||||02|WARD NO. 1|FREE GANJ MACHCHLI MARKET|BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO. 1|FREE GANJ MACHCHLI MARKET|BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||6260405821|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|23|C|DMJPR6517R||01|02|||||
20|24|01|01|||||||||||01|||01|34209|MRS|NEETU| |RAIKWAR|||||||01|MR|SANJIV| |RAIKWAR||MRS|A|B|C||F|01|IN|O-04|06-10-1990|||||||||01|02|||||02|FREE GANJ|WARD NO.1 MACHLI MARKET|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|FREE GANJ|WARD NO.1 MACHLI MARKET|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||6260405821|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|24|C|DMHPR0471Q||01|02|||||
20|25|01|01|||||||||||01|||01|34210|MRS|LATA| |BANGAD|||||||01|MR|SATYA|NARAYAN|BANGAD||MRS|A|B|C||F|01|IN|O-04|25-02-1975|||||||||01|02|||||02|WARD NO.12 |JAIN MANDIR GALI|BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO.12 |JAIN MANDIR GALI|BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01|||||||||||91|7987163771||||7987163771|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|25|C|AJVPB1142J||01|02|||||
20|26|01|01|||||||||||01|||01|34227|MR|SEETA RAM|SINGH||||||||01|MR|UMRAV SINGH||||MRS|A|B|C||M|01|IN|S-01|01-01-1982|||||||||01|02|||||02|BALRAMPUR|PANCHAYAT SAKRAI POST FUFER NATERAN|VIDISHA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|BALRAMPUR|PANCHAYAT SAKRAI POST FUFER NATERAN|VIDISHA|GANJ BASODA|VIDISHA|MP|IN|464221|01|||||||||||||||||||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|26|B|BBK1122340||01|02|||||
20|27|01|01|||||||||||01|||01|34232|MRS|JANKE|DANGI||||||||01|MR|MEHTAB SINGH||||MRS|A|B|C||F|01|IN|O-04|01-01-1936|||||||||01|02|||||02|MAHAGAUR|VIDISHA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|MAHAGAUR|VIDISHA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9406522221|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|27|E|XXXXXXXX5383||01|02|||||
20|28|01|01|||||||||||01|||01|34245|MR|KESHAV|PRAJAPATI||||||||01|MR|KARAN|SINGH|||MRS|A|B|C||M|01|IN|B-01|01-01-1991|||||||||01|02|||||02|SARASWATI NAGAR SWROOP NAGAR|GIRAJ DHAM MANDIR|LAPATHER BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|SARASWATI NAGAR SWROOP NAGAR|GIRAJ DHAM MANDIR|LAPATHER BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||7440844734|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|28|E|XXXXXXXX0585||01|02|||||
20|29|01|01|||||||||||01|||01|34246|MRS|VANDANA|JAIN||||||||01|MR|NEELESH|JAIN|||MRS|A|B|C||F|01|IN|O-04|10-07-1979|||||||||01|02|||||02|ONLINE CYBER CAFE NAYA BAZAR CITY|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|ONLINE CYBER CAFE NAYA BAZAR CITY|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9406522223|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|29|E|XXXXXXXX5997||01|02|||||
20|30|01|01|||||||||||01|||01|34255|MRS|NENASHI|NAMDEV||||||||01|MR|RAM SEVAK|NAMDEV|||MRS|A|B|C||F|02|IN|S-01|27-12-1997|||||||||01|02|||||02|WARD NO 24 KRISHNA PURA|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 24 KRISHNA PURA|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9685768714|||||30-08-2020|GANJ BASODA|30-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|30|E|XXXXXXXX6697||01|02|||||
20|31|01|01|||||||||||01|||01|34258|MRS|RANI|AGRAWAL||||||||01|MR|MILIND|AGRAWAL|||MRS|A|B|C||F|01|IN|O-04|22-12-1996|||||||||01|02|||||02|WARD NO 12 MILL ROAD|BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 12 MILL ROAD|BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9752979179|||||30-08-2020|GANJ BASODA|30-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|31|C|CVUPA9262K||01|02|||||
20|32|01|01|||||||||||01|||01|34260|MR|DEVI |PRASHAD||||||||01|MR|PRABHU |LAL|||MRS|A|B|C||M|01|IN|S-01|10-06-1965|||||||||01|02|||||02|WARD NO 7|KAMDHENU COLONY BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 7|KAMDHENU COLONY BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||8889089308|||||29-08-2020|GANJ BASODA|29-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|32|B|BBK0131060||01|02|||||
20|33|01|01|||||||||||01|||01|34275|MR|ANKIT|PINGLE||||||||01|MR|SHIRISH|PINGLE|||MRS|A|B|C||M|01|IN|B-01|28-05-1988|||||||||01|02|||||02|WARD NO 16 PACHMA ROAD|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 16 PACHMA ROAD|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||7723075050|||||29-08-2020|GANJ BASODA|29-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|33|C|BRAPP2466F||01|02|||||
20|34|01|01|||||||||||01|||01|34277|MR|SANJAY|PINGLE||||||||01|MR|NARAYAN|RAO|||MRS|A|B|C||M|01|IN|B-01|05-09-1957|||||||||01|02|||||02|M N 7|PACHAMA BALI GALI|WARD NO 16 GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|M N 7|PACHAMA BALI GALI|WARD NO 16 GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9893485050|||||29-08-2020|GANJ BASODA|29-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|34|B|MP13247207128||01|02|||||
20|35|01|01|||||||||||01|||01|34885|MR|SHALENDRA|RAGHUWANSHI||||||||01|MR|MALAM|SINGH|||MRS|PUSPA||||M|01|IN|X-01|03-01-1983|||||||||01|02|||||02|GRAM MEVLI PANCHAYAT|MUDRA POSTKAKRAWDA|BASODA|BASODA|VIDISHA|MP|IN|464221|01||Y|02|GRAM MEVLI PANCHAYAT|MUDRA POSTKAKRAWDA|BASODA|BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||6266886495|||||28-08-2020|GANJ BASODA|28-08-2020|01|BRIJESHKUMAR SHRIVASTVA|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|3|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|35|E|XXXXXXXX7677||01|02|||||
`;