Files
cbsnexora/src/app/clients/clients-view/ckyc-tab/ckyc-upload-dialog/ckyc-upload-dialog.component.ts
T
niteshandClaude Sonnet 5 31e99b1363 fix(clients): rebuild CKYC document upload as a popup with full proof details
The CKYC tab's document cards now open a dedicated dialog (with client-side
preview) instead of expanding inline, collecting document type, number, and
issue/expiry dates the same way client creation does. Fixes several bugs
found along the way:

- Uploading a document type that's valid for both identity and address
  (Aadhaar, Passport, etc.) no longer 404s or 500s — the "no such endpoint"
  and case-sensitive ClientIdentifierStatus.valueOf("Active") crashes are
  both resolved.
- Edit Client's Identity-proof and Address-proof steps no longer show each
  other's document when both are dual-purpose OVD types, and no longer show
  stale/mismatched front-back scans after a document type change.
- Address-proof's own document preview never showed a document at all when
  no m_client_kyc record existed yet.

Document number and issue/expiry dates are persisted on the identifier
(documentKey + a shared "Issue date: ..., Expiry date: ..." description
convention) and read back by both the CKYC tab and Edit Client so the same
details appear everywhere.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 11:39:14 +05:30

156 lines
5.0 KiB
TypeScript

/**
* 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, OnDestroy, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
MatDialogRef,
MAT_DIALOG_DATA,
MatDialogTitle,
MatDialogContent,
MatDialogActions,
MatDialogClose
} from '@angular/material/dialog';
import { MatIconButton } from '@angular/material/button';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { Dates } from 'app/core/utils/dates';
import { ProofType } from '../../../shared/kyc-proof-types';
export interface CkycUploadDialogData {
title: string;
isProofKind: boolean;
accept: string;
proofOptions?: ProofType[];
initialProofType?: ProofType | null;
initialDocumentKey?: string;
initialIssueDate?: Date | null;
initialExpiryDate?: Date | null;
}
export interface CkycUploadDialogResult {
proofType?: ProofType | null;
front: File;
back?: File;
/** Document number (Aadhaar/PAN/Passport number, etc.) — same field client creation collects. */
documentKey?: string;
/** 'yyyy-MM-dd', same as the rest of this app's date fields. */
issueDate?: string | null;
expiryDate?: string | null;
}
/**
* A focused popup for replacing/uploading one CKYC artefact at a time — opened from each card on
* the CKYC tab instead of expanding a form inline underneath it. Collects the same details
* client creation does for an identity/address proof (document type, number, issue + expiry
* date) and shows a client-side preview of whatever image is picked before it's ever sent to the
* server; the actual upload still happens back in CkycTabComponent (this dialog only collects
* the type, number, dates and file(s), then hands them back).
*/
@Component({
selector: 'mifosx-ckyc-upload-dialog',
templateUrl: './ckyc-upload-dialog.component.html',
styleUrls: ['./ckyc-upload-dialog.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
FormsModule,
MatDialogTitle,
MatDialogContent,
MatDialogActions,
MatDialogClose,
MatIconButton,
FaIconComponent
]
})
export class CkycUploadDialogComponent implements OnDestroy {
private dates = inject(Dates);
dialogRef = inject<MatDialogRef<CkycUploadDialogComponent, CkycUploadDialogResult | null>>(MatDialogRef);
data = inject<CkycUploadDialogData>(MAT_DIALOG_DATA);
selectedProofType: ProofType | null = this.data.initialProofType || null;
documentKey = this.data.initialDocumentKey || '';
issueDate: Date | null = this.data.initialIssueDate || null;
expiryDate: Date | null = this.data.initialExpiryDate || null;
front: File | null = null;
back: File | null = null;
frontPreview: string | null = null;
backPreview: string | null = null;
get sides(): number {
return this.data.isProofKind ? (this.selectedProofType?.sides ?? 1) : 1;
}
get canSubmit(): boolean {
if (this.data.isProofKind) {
if (!this.selectedProofType) return false;
if (!this.documentKey.trim()) return false;
}
if (!this.front) return false;
if (this.sides === 2 && !this.back) return false;
return true;
}
compareProofType = (a: ProofType | null, b: ProofType | null): boolean => (a?.id ?? null) === (b?.id ?? null);
onProofTypeChange(proof: ProofType | null): void {
this.selectedProofType = proof;
this.clearSlot('front');
this.clearSlot('back');
}
onFilePick(slot: 'front' | 'back', event: any): void {
const file = event?.target?.files?.[0];
if (event?.target) event.target.value = '';
if (!file) return;
this.clearSlot(slot);
const preview = file.type?.startsWith('image/') ? URL.createObjectURL(file) : null;
if (slot === 'front') {
this.front = file;
this.frontPreview = preview;
} else {
this.back = file;
this.backPreview = preview;
}
}
private clearSlot(slot: 'front' | 'back'): void {
if (slot === 'front') {
if (this.frontPreview) URL.revokeObjectURL(this.frontPreview);
this.front = null;
this.frontPreview = null;
} else {
if (this.backPreview) URL.revokeObjectURL(this.backPreview);
this.back = null;
this.backPreview = null;
}
}
submit(): void {
if (!this.canSubmit) return;
this.dialogRef.close({
proofType: this.selectedProofType,
front: this.front!,
back: this.sides === 2 ? this.back! : undefined,
documentKey: this.data.isProofKind ? this.documentKey.trim() : undefined,
issueDate: this.issueDate ? this.dates.getDate(this.issueDate) : null,
expiryDate: this.expiryDate ? this.dates.getDate(this.expiryDate) : null
});
}
cancel(): void {
this.dialogRef.close(null);
}
ngOnDestroy(): void {
if (this.frontPreview) URL.revokeObjectURL(this.frontPreview);
if (this.backPreview) URL.revokeObjectURL(this.backPreview);
}
}