feat: add RBI circulars, CKYC, KYC, Superset analytics, passbook modules and UI updates

Add new feature modules (RBI feeds, CKYC, KYC tab, Form60, Superset analytics, passbook),
update client family members, dashboard, login page, sidenav, images, translations,
environment config, and fix RBI feeds API auth by using relative URLs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Aditya
2026-03-10 15:13:35 +05:30
co-authored by Claude Opus 4.6
parent 07f701f654
commit 73aa69ee02
84 changed files with 6256 additions and 700 deletions
@@ -0,0 +1,29 @@
/**
* Copyright since 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { Route } from '../core/route/route.service';
import { SupersetDashboardComponent } from './superset-dashboard.component';
const routes: Routes = [
Route.withShell([
{
path: 'analytics',
component: SupersetDashboardComponent,
data: { title: 'Analytics', breadcrumb: 'Analytics' }
}
])
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class SupersetAnalyticsRoutingModule {}
@@ -0,0 +1,22 @@
/**
* Copyright since 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { SupersetAnalyticsRoutingModule } from './superset-analytics-routing.module';
import { SupersetDashboardComponent } from './superset-dashboard.component';
@NgModule({
imports: [
SharedModule,
SupersetAnalyticsRoutingModule,
SupersetDashboardComponent
]
})
export class SupersetAnalyticsModule {}
@@ -0,0 +1,74 @@
<!--
Copyright since 2025 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<div class="header">
<h2>
<fa-icon icon="chart-bar" class="mr-05"></fa-icon>
{{ 'labels.heading.Analytics Dashboard' | translate }}
</h2>
<button mat-raised-button color="primary" (click)="openSupersetFullScreen()">
<fa-icon icon="external-link-alt" class="mr-05"></fa-icon>
{{ 'labels.buttons.Open Full Dashboard' | translate }}
</button>
</div>
<mat-card *ngIf="isLoading" class="loading-card">
<mat-card-content class="loading-content">
<mat-spinner diameter="40"></mat-spinner>
<p>{{ 'labels.text.Loading analytics dashboard...' | translate }}</p>
</mat-card-content>
</mat-card>
<mat-card *ngIf="errorMessage" class="error-card">
<mat-card-content>
<p class="error-message">
<fa-icon icon="exclamation-triangle"></fa-icon>
{{ errorMessage }}
</p>
<button mat-raised-button color="primary" (click)="loadDashboard()">
{{ 'labels.buttons.Retry' | translate }}
</button>
</mat-card-content>
</mat-card>
<ng-container *ngIf="!isLoading && !errorMessage">
<!-- Dashboard ID configured: show embedded iframe -->
<div *ngIf="dashboardUrl" class="dashboard-container">
<iframe [src]="dashboardUrl" class="superset-iframe" frameBorder="0" allowfullscreen> </iframe>
</div>
<!-- No Dashboard ID: show setup instructions -->
<mat-card *ngIf="!dashboardUrl" class="setup-card">
<mat-card-content>
<div class="setup-content">
<fa-icon icon="chart-bar" size="3x" class="setup-icon"></fa-icon>
<h3>{{ 'labels.heading.Apache Superset Analytics' | translate }}</h3>
<p>{{ 'labels.text.Superset is connected but no dashboard ID is configured.' | translate }}</p>
<div class="setup-steps">
<h4>{{ 'labels.heading.Quick Setup' | translate }}</h4>
<ol>
<li>
{{ 'labels.text.Open Superset and create a dashboard' | translate }}:
<a [href]="supersetBaseUrl" target="_blank">{{ supersetBaseUrl }}</a>
</li>
<li>{{ 'labels.text.Copy the dashboard ID from the URL' | translate }}</li>
<li>{{ 'labels.text.Set supersetDashboardId in your environment config' | translate }}</li>
</ol>
</div>
<button mat-raised-button color="primary" (click)="openSupersetFullScreen()">
<fa-icon icon="external-link-alt" class="mr-05"></fa-icon>
{{ 'labels.buttons.Open Superset' | translate }}
</button>
</div>
</mat-card-content>
</mat-card>
</ng-container>
</div>
@@ -0,0 +1,129 @@
/**
* 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/.
*/
.container {
padding: 16px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
h2 {
margin: 0;
font-size: 1.4rem;
font-weight: 500;
}
}
.mr-05 {
margin-right: 5px;
}
.dashboard-container {
width: 100%;
height: calc(100vh - 180px);
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
}
.superset-iframe {
width: 100%;
height: 100%;
border: none;
}
.loading-card {
margin-top: 24px;
}
.loading-content {
display: flex;
flex-direction: column;
align-items: center;
padding: 48px;
gap: 16px;
p {
color: rgb(0 0 0 / 54%);
}
}
.error-card {
margin-top: 24px;
.error-message {
color: #f44336;
margin-bottom: 16px;
fa-icon {
margin-right: 8px;
}
}
}
.setup-card {
margin-top: 24px;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
.setup-content {
text-align: center;
padding: 32px 16px;
.setup-icon {
color: rgb(0 0 0 / 26%);
margin-bottom: 16px;
}
h3 {
margin: 16px 0 8px;
font-size: 1.2rem;
}
p {
color: rgb(0 0 0 / 54%);
margin-bottom: 24px;
}
}
.setup-steps {
text-align: left;
background: #f5f5f5;
border-radius: 8px;
padding: 16px 24px;
margin-bottom: 24px;
h4 {
margin: 0 0 12px;
}
ol {
margin: 0;
padding-left: 20px;
li {
margin-bottom: 8px;
line-height: 1.5;
a {
color: #1976d2;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
}
@@ -0,0 +1,57 @@
/**
* Copyright since 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { Component, OnInit, inject } from '@angular/core';
import { SafeResourceUrl } from '@angular/platform-browser';
import { SupersetService } from './superset.service';
import { environment } from '../../environments/environment';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { MatProgressSpinner } from '@angular/material/progress-spinner';
@Component({
selector: 'mifosx-superset-dashboard',
standalone: true,
templateUrl: './superset-dashboard.component.html',
styleUrls: ['./superset-dashboard.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
FaIconComponent,
MatProgressSpinner
]
})
export class SupersetDashboardComponent implements OnInit {
private supersetService = inject(SupersetService);
dashboardUrl: SafeResourceUrl | null = null;
isLoading = true;
errorMessage: string | null = null;
supersetBaseUrl: string;
ngOnInit() {
this.supersetBaseUrl = environment.supersetUrl;
this.loadDashboard();
}
loadDashboard() {
const dashboardId = environment.supersetDashboardId;
if (!dashboardId) {
this.isLoading = false;
this.errorMessage = null;
return;
}
this.isLoading = false;
this.dashboardUrl = this.supersetService.getEmbeddedDashboardUrl(dashboardId);
}
openSupersetFullScreen() {
window.open(this.supersetBaseUrl, '_blank');
}
}
@@ -0,0 +1,88 @@
/**
* Copyright since 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { Injectable, inject } from '@angular/core';
import { HttpClient, HttpBackend } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { environment } from '../../environments/environment';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
@Injectable({
providedIn: 'root'
})
export class SupersetService {
private sanitizer = inject(DomSanitizer);
private supersetHttp: HttpClient;
constructor(httpBackend: HttpBackend) {
// Bypass Fineract interceptors for Superset API calls
this.supersetHttp = new HttpClient(httpBackend);
}
get supersetUrl(): string {
return environment.supersetUrl;
}
get isEnabled(): boolean {
return environment.supersetEnabled;
}
/**
* Login to Superset and get access token
*/
login(username: string, password: string): Observable<string> {
return this.supersetHttp
.post<any>(`${this.supersetUrl}/api/v1/security/login`, {
username,
password,
provider: 'db',
refresh: true
})
.pipe(map((response) => response.access_token));
}
/**
* Fetch guest token for embedded dashboard
*/
fetchGuestToken(accessToken: string, dashboardId: string): Observable<string> {
return this.supersetHttp
.post<any>(
`${this.supersetUrl}/api/v1/security/guest_token/`,
{
resources: [{ type: 'dashboard', id: dashboardId }],
user: { username: 'guest', first_name: 'Guest', last_name: 'User' },
rls: []
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
}
)
.pipe(map((response) => response.token));
}
/**
* Get the embedded dashboard URL (standalone mode via iframe)
*/
getEmbeddedDashboardUrl(dashboardId: string): SafeResourceUrl {
const url = `${this.supersetUrl}/superset/dashboard/${dashboardId}/?standalone=true`;
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
}
/**
* Get full login + guest token flow for embedding
*/
getGuestTokenForDashboard(dashboardId: string, username: string, password: string): Observable<string> {
return this.login(username, password).pipe(
switchMap((accessToken) => this.fetchGuestToken(accessToken, dashboardId))
);
}
}