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:
co-authored by
Claude Opus 4.7
parent
52e3395345
commit
634003a75c
@@ -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 & 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 & 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|||||
|
||||
`;
|
||||
Reference in New Issue
Block a user