fix(clients): minor-age threshold to 18, senior/PWD doc reload, in-app document preview dialog

- Raise isMinor threshold from age <10 to <18 (legal definition), cascading correctly to
  guardian requirement, KYC document filtering, and Education Level/Degree Qualification
  visibility (now shown for all ages, not hidden for minors).
- Load and display previously-uploaded Senior Citizen proof / PWD certificate documents on
  edit (view/replace/remove), instead of always showing an empty upload prompt.
- Fix seniorPwdDocsComplete to accept an existing certificate, not just a freshly-picked file.
- Add DocumentPreviewDialogComponent: a reusable in-app modal (image/PDF/download) used in
  place of window.open() for saved KYC/address/senior-citizen/PWD documents — avoids popup
  blockers (fired only after the async download resolved) and Chrome/Edge's failure to paint
  large base64 data: URLs on first open.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 15:49:53 +05:30
co-authored by Claude Sonnet 5
parent c1fc95d9b7
commit 79fedb66e5
10 changed files with 347 additions and 107 deletions
@@ -15,9 +15,11 @@ import { MatCheckbox } from '@angular/material/checkbox';
import { MatTabsModule } from '@angular/material/tabs';
import { MatIcon } from '@angular/material/icon';
import { MatIconButton, MatButton } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog';
import { ClientsService } from '../../clients.service';
import { ProofType, normalizeProofType, groupProofsByCategory } from '../../shared/kyc-proof-types';
import { KycUploadHelperService } from '../../shared/kyc-upload-helper.service';
import { DocumentPreviewDialogComponent } from '../../shared/document-preview-dialog/document-preview-dialog.component';
@Component({
selector: 'mifosx-client-india-address-step',
@@ -38,6 +40,7 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
private http = inject(HttpClient);
private clientsService = inject(ClientsService);
private kycUpload = inject(KycUploadHelperService);
private dialog = inject(MatDialog);
/** Client ID — when set, loads existing addresses for edit mode */
@Input() clientId: string = '';
@@ -633,33 +636,17 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
});
}
/**
* Open the saved address proof in a new browser tab.
*
* For images we already hold a base64 data URL in `previewUrl` from
* `loadExistingAddressProof`, so we can hand it straight to `window.open`.
* For PDFs / other types we need to fetch the binary blob and create a
* temporary object URL so the browser's PDF viewer can render it.
*/
/** Open the saved address proof in an in-app preview dialog. */
viewExistingAddressProof(): void {
if (!this.existingAddressProofIdentifierId || this.existingAddressProofDocuments.length === 0) {
return;
}
const doc = this.existingAddressProofDocuments[0];
if (doc.previewUrl) {
window.open(doc.previewUrl, '_blank', 'noopener');
return;
}
this.clientsService.downloadClientIdentificationDocument(this.existingAddressProofIdentifierId, doc.id).subscribe({
next: (blob: Blob) => {
const url = URL.createObjectURL(blob);
const w = window.open(url, '_blank', 'noopener');
// Revoke after the tab has had time to load — browsers vary, so
// err on the side of a generous wait.
if (w) {
setTimeout(() => URL.revokeObjectURL(url), 60_000);
}
},
next: (blob: Blob) =>
this.dialog.open(DocumentPreviewDialogComponent, {
data: { blob, title: doc.fileName || doc.name, fileName: doc.fileName || doc.name }
}),
error: () => {
// Silent fail — UI shows the saved name so user knows it exists.
}