/** * 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/. */ /** Angular Imports */ import { Component, OnInit, OnDestroy, inject } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Subject, takeUntil, catchError, of } from 'rxjs'; /** Custom Services */ import { ClientsService } from '../../clients.service'; import { AlertService } from 'app/core/alert/alert.service'; import { MatIcon } from '@angular/material/icon'; import { MatProgressSpinner } from '@angular/material/progress-spinner'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; @Component({ selector: 'mifosx-ckyc-tab', templateUrl: './ckyc-tab.component.html', styleUrls: ['./ckyc-tab.component.scss'], standalone: true, imports: [ ...STANDALONE_SHARED_IMPORTS, MatIcon, MatProgressSpinner, FaIconComponent ] }) export class CkycTabComponent implements OnInit, OnDestroy { private route = inject(ActivatedRoute); private clientsService = inject(ClientsService); private alertService = inject(AlertService); private destroy$ = new Subject(); /** Top section - read-only client info */ clientId: string; clientName: string = ''; allAccounts: any[] = []; selectedAccount: any = null; selectedAccountType: string = ''; /** State */ loading = true; ckycData: any = null; /** File references */ addressProofFile: File | null = null; photoProofFile: File | null = null; clientImageFile: File | null = null; clientSignatureFile: File | null = null; /** Per-document upload state */ uploading: { [key: string]: boolean } = { ADDRESS_PROOF: false, PHOTO_PROOF: false, CLIENT_IMAGE: false, CLIENT_SIGNATURE: false }; /** Per-document upload response */ uploadResponses: { [key: string]: any } = {}; /** Per-document error messages */ uploadErrors: { [key: string]: string | null } = { ADDRESS_PROOF: null, PHOTO_PROOF: null, CLIENT_IMAGE: null, CLIENT_SIGNATURE: null }; ngOnInit(): void { this.route.parent.params.subscribe((params: any) => { this.clientId = params['clientId']; this.loadClientInfo(); this.loadAccounts(); this.loadCkycData(); }); } private loadClientInfo(): void { this.route.parent.data.subscribe((data: any) => { if (data?.clientViewData) { this.clientName = data.clientViewData.displayName || ''; } }); } private loadAccounts(): void { this.clientsService .getClientAccountData(this.clientId) .pipe( takeUntil(this.destroy$), catchError(() => of(null)) ) .subscribe((data: any) => { if (data) { const savings = (data.savingsAccounts || []).map((a: any) => ({ accountNo: a.accountNo, accountType: 'Savings', productName: a.productName || '', id: a.id })); const loans = (data.loanAccounts || []).map((a: any) => ({ accountNo: a.accountNo, accountType: 'Loan', productName: a.productName || '', id: a.id })); this.allAccounts = [ ...savings, ...loans ]; } }); } private loadCkycData(): void { this.loading = true; this.clientsService .getClientCkyc(this.clientId) .pipe( takeUntil(this.destroy$), catchError(() => { this.loading = false; return of(null); }) ) .subscribe((data: any) => { this.loading = false; this.ckycData = data; }); } onAccountSelect(accountNo: string): void { this.selectedAccount = this.allAccounts.find((a) => a.accountNo === accountNo) || null; this.selectedAccountType = this.selectedAccount?.accountType || ''; } onFileSelect(event: any, documentType: string): void { const file = event.target?.files?.[0]; if (!file) return; switch (documentType) { case 'ADDRESS_PROOF': this.addressProofFile = file; break; case 'PHOTO_PROOF': this.photoProofFile = file; break; case 'CLIENT_IMAGE': this.clientImageFile = file; break; case 'CLIENT_SIGNATURE': this.clientSignatureFile = file; break; } this.uploadErrors[documentType] = null; } getFile(documentType: string): File | null { switch (documentType) { case 'ADDRESS_PROOF': return this.addressProofFile; case 'PHOTO_PROOF': return this.photoProofFile; case 'CLIENT_IMAGE': return this.clientImageFile; case 'CLIENT_SIGNATURE': return this.clientSignatureFile; default: return null; } } uploadDocument(documentType: string): void { const file = this.getFile(documentType); if (!file) return; this.uploading[documentType] = true; this.uploadErrors[documentType] = null; const formData = new FormData(); formData.append('file', file); formData.append('documentType', documentType); this.clientsService .uploadCkycDocument(this.clientId, formData) .pipe(takeUntil(this.destroy$)) .subscribe({ next: (response: any) => { this.uploading[documentType] = false; this.uploadResponses[documentType] = response; this.alertService.alert({ type: 'success', message: response?.message || 'Document uploaded successfully.' }); }, error: (err: any) => { this.uploading[documentType] = false; this.uploadErrors[documentType] = err?.error?.developerMessage || err?.error?.defaultUserMessage || err?.message || 'Failed to upload document.'; } }); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } }