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>
168 lines
6.7 KiB
HTML
168 lines
6.7 KiB
HTML
<!--
|
|
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="cud-header">
|
|
<div class="cud-header__badge">
|
|
<fa-icon icon="folder-open"></fa-icon>
|
|
</div>
|
|
<div class="cud-header__text">
|
|
<h2 mat-dialog-title class="cud-title">{{ data.title }}</h2>
|
|
<p class="cud-subtitle">Fill in the document details, then attach the scan.</p>
|
|
</div>
|
|
<button mat-icon-button mat-dialog-close class="cud-close" aria-label="Close">
|
|
<fa-icon icon="times"></fa-icon>
|
|
</button>
|
|
</div>
|
|
|
|
<mat-dialog-content class="cud-body">
|
|
@if (data.isProofKind) {
|
|
<div class="cud-section">
|
|
<span class="cud-section__label"><fa-icon icon="id-card"></fa-icon> Document type</span>
|
|
<mat-form-field appearance="outline" class="cud-field" subscriptSizing="dynamic">
|
|
<mat-select
|
|
[value]="selectedProofType"
|
|
[compareWith]="compareProofType"
|
|
placeholder="Select document type"
|
|
(selectionChange)="onProofTypeChange($event.value)"
|
|
>
|
|
@for (p of data.proofOptions; track p.id) {
|
|
<mat-option [value]="p">{{ p.name }}</mat-option>
|
|
}
|
|
</mat-select>
|
|
</mat-form-field>
|
|
<p class="cud-hint">Upload requirements will update automatically based on the type you pick.</p>
|
|
</div>
|
|
|
|
@if (selectedProofType) {
|
|
@if (selectedProofType.info) {
|
|
<div class="cud-banner">
|
|
<fa-icon icon="check-circle"></fa-icon>
|
|
<span>{{ selectedProofType.info }}</span>
|
|
</div>
|
|
}
|
|
|
|
<div class="cud-section">
|
|
<span class="cud-section__label">
|
|
<fa-icon icon="hashtag"></fa-icon>
|
|
{{ selectedProofType.documentKeyLabel || 'Document number' }}
|
|
</span>
|
|
<mat-form-field appearance="outline" class="cud-field" subscriptSizing="dynamic">
|
|
<input
|
|
matInput
|
|
[(ngModel)]="documentKey"
|
|
[ngModelOptions]="{ standalone: true }"
|
|
[placeholder]="selectedProofType.documentKeyPlaceholder || 'Enter document number'"
|
|
/>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="cud-section cud-section--dates">
|
|
<div class="cud-date-field">
|
|
<span class="cud-section__label"><fa-icon icon="calendar-check"></fa-icon> Issue date</span>
|
|
<mat-form-field appearance="outline" class="cud-field" subscriptSizing="dynamic">
|
|
<input
|
|
matInput
|
|
readonly
|
|
[matDatepicker]="issuePicker"
|
|
[(ngModel)]="issueDate"
|
|
[ngModelOptions]="{ standalone: true }"
|
|
placeholder="Optional"
|
|
(click)="issuePicker.open()"
|
|
/>
|
|
<mat-datepicker-toggle matSuffix [for]="issuePicker"></mat-datepicker-toggle>
|
|
<mat-datepicker #issuePicker></mat-datepicker>
|
|
</mat-form-field>
|
|
</div>
|
|
<div class="cud-date-field">
|
|
<span class="cud-section__label"><fa-icon icon="calendar-check"></fa-icon> Expiry date</span>
|
|
<mat-form-field appearance="outline" class="cud-field" subscriptSizing="dynamic">
|
|
<input
|
|
matInput
|
|
readonly
|
|
[matDatepicker]="expiryPicker"
|
|
[(ngModel)]="expiryDate"
|
|
[ngModelOptions]="{ standalone: true }"
|
|
placeholder="Optional"
|
|
(click)="expiryPicker.open()"
|
|
/>
|
|
<mat-datepicker-toggle matSuffix [for]="expiryPicker"></mat-datepicker-toggle>
|
|
<mat-datepicker #expiryPicker></mat-datepicker>
|
|
</mat-form-field>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="cud-divider"><span>Scan</span></div>
|
|
|
|
<div class="cud-grid" [class.cud-grid--two]="sides === 2">
|
|
<div class="cud-slot">
|
|
<span class="cud-slot__label">{{ sides === 2 ? 'Front side' : 'Document' }}</span>
|
|
<label class="cud-dropzone" [class.cud-dropzone--has]="front">
|
|
<input type="file" [attr.accept]="data.accept" hidden (change)="onFilePick('front', $event)" />
|
|
@if (frontPreview) {
|
|
<img [src]="frontPreview" alt="Front side preview" class="cud-dropzone__img" />
|
|
} @else if (front) {
|
|
<fa-icon icon="file-alt" class="cud-dropzone__icon"></fa-icon>
|
|
<p class="cud-dropzone__name">{{ front.name }}</p>
|
|
} @else {
|
|
<fa-icon icon="cloud-upload-alt" class="cud-dropzone__icon"></fa-icon>
|
|
<p class="cud-dropzone__text">Click to choose file</p>
|
|
}
|
|
</label>
|
|
</div>
|
|
|
|
@if (sides === 2) {
|
|
<div class="cud-slot">
|
|
<span class="cud-slot__label">Back side</span>
|
|
<label class="cud-dropzone" [class.cud-dropzone--has]="back">
|
|
<input type="file" [attr.accept]="data.accept" hidden (change)="onFilePick('back', $event)" />
|
|
@if (backPreview) {
|
|
<img [src]="backPreview" alt="Back side preview" class="cud-dropzone__img" />
|
|
} @else if (back) {
|
|
<fa-icon icon="file-alt" class="cud-dropzone__icon"></fa-icon>
|
|
<p class="cud-dropzone__name">{{ back.name }}</p>
|
|
} @else {
|
|
<fa-icon icon="cloud-upload-alt" class="cud-dropzone__icon"></fa-icon>
|
|
<p class="cud-dropzone__text">Click to choose file</p>
|
|
}
|
|
</label>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
} @else {
|
|
<div class="cud-grid">
|
|
<div class="cud-slot">
|
|
<span class="cud-slot__label">Document</span>
|
|
<label class="cud-dropzone" [class.cud-dropzone--has]="front">
|
|
<input type="file" [attr.accept]="data.accept" hidden (change)="onFilePick('front', $event)" />
|
|
@if (frontPreview) {
|
|
<img [src]="frontPreview" alt="Preview" class="cud-dropzone__img" />
|
|
} @else if (front) {
|
|
<fa-icon icon="file-alt" class="cud-dropzone__icon"></fa-icon>
|
|
<p class="cud-dropzone__name">{{ front.name }}</p>
|
|
} @else {
|
|
<fa-icon icon="cloud-upload-alt" class="cud-dropzone__icon"></fa-icon>
|
|
<p class="cud-dropzone__text">Click to choose file</p>
|
|
}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
}
|
|
</mat-dialog-content>
|
|
|
|
<mat-dialog-actions class="cud-actions" align="end">
|
|
<button mat-stroked-button type="button" (click)="cancel()">
|
|
<fa-icon icon="times"></fa-icon>
|
|
Cancel
|
|
</button>
|
|
<button mat-flat-button color="primary" type="button" [disabled]="!canSubmit" (click)="submit()">
|
|
<fa-icon icon="upload"></fa-icon>
|
|
Upload
|
|
</button>
|
|
</mat-dialog-actions>
|