Files
cbsnexora/src/app/clients/clients-view/custom-dialogs/upload-document-dialog/upload-document-dialog.component.html
T
AdityaandClaude Opus 4.6 d72e969dc3 feat: Client & CKYC UI improvements — filters, KYC management, image crop+merge
- Clients list: CKYC icon, CKYC Number column, status/legal form/CKYC filters
- CKYC: SFTP upload logs, date formatting, modal z-index fix, non-dismissible dialogs
- KYC Management: new standalone module with alert dashboard, notification scheduler, audit inventory, account status
- Image cropper: ngx-image-cropper integration for front+back document merge into single PNG
- Sidebar: KYC Management menu entry after CKYC

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:51:11 +05:30

507 lines
20 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="fd-dialog" [class.fd-dialog--kyc]="documentIdentifier">
<div class="fd-header">
<h1 mat-dialog-title class="fd-title">
@if (documentIdentifier) {
<i class="fa fa-id-card fd-title__icon"></i>
{{ 'labels.heading.Add Client Identifier' | translate }}
} @else {
<i class="fa fa-upload fd-title__icon"></i>
{{ 'labels.heading.Upload' | translate }} {{ entityType }} {{ 'labels.heading.Documents' | translate }}
}
</h1>
<button mat-icon-button mat-dialog-close class="fd-close" aria-label="Close">
<i class="fa fa-times"></i>
</button>
</div>
<div class="fd-content">
@if (documentIdentifier) {
<div class="kyc-intro">
<i class="fa fa-shield kyc-intro__icon"></i>
<div>
<strong>KYC Document Upload</strong>
<p>
Upload KYC documents as per RBI guidelines. Accepted:
<i class="fa fa-file-image-o"></i> JPG,
<i class="fa fa-file-image-o"></i> PNG,
<i class="fa fa-file-pdf-o"></i> PDF — max 5MB each.
</p>
</div>
</div>
@if (loadingProofs) {
<div class="kyc-loading">
<i class="fa fa-spinner fa-spin"></i> Loading document types…
</div>
}
<form [formGroup]="uploadDocumentForm" class="kyc-form">
<!-- SECTION 1: IDENTITY PROOF -->
<div class="kyc-section" formGroupName="id">
<div class="kyc-section__header">
<div class="kyc-badge kyc-badge--blue">
<i class="fa fa-user-circle-o"></i>
</div>
<div class="kyc-section__titles">
<p class="kyc-section__title">
<i class="fa fa-id-badge"></i> Identity proof
</p>
<p class="kyc-section__sub">
Select document type — upload requirements will update automatically
</p>
</div>
</div>
<!-- Prefilled name (from client registration) -->
@if (clientName) {
<div class="kyc-prefill">
<i class="fa fa-user"></i>
<div>
<span class="kyc-prefill__label">Full name (from registration)</span>
<span class="kyc-prefill__value">{{ clientName }}</span>
</div>
<i class="fa fa-lock kyc-prefill__lock" title="Prefilled from client registration"></i>
</div>
}
<div class="kyc-field kyc-mb-sm">
<label class="kyc-label">
<i class="fa fa-file-text-o"></i> Document type
</label>
<div class="kyc-select-wrap">
<i class="fa fa-id-card kyc-select-wrap__icon"></i>
<select
class="kyc-input kyc-select"
formControlName="proofId"
(change)="onIdProofChange($any($event.target).value)"
>
<option value="">— Select document —</option>
@for (p of idProofs; track p.id) {
<option [value]="p.id">{{ p.name }}</option>
}
</select>
<i class="fa fa-chevron-down kyc-select-wrap__chevron"></i>
</div>
</div>
@if (selectedIdProof) {
<div class="kyc-field kyc-mb-sm">
<label class="kyc-label">
<i class="fa fa-hashtag"></i> {{ selectedIdProof.documentKeyLabel }}
</label>
<div class="kyc-input-wrap">
<i class="fa fa-pencil kyc-input-wrap__icon"></i>
<input
type="text"
class="kyc-input kyc-input--padded"
formControlName="documentKey"
[placeholder]="selectedIdProof.documentKeyPlaceholder || 'Enter document number'"
/>
</div>
</div>
}
@if (selectedIdProof?.info) {
<div class="kyc-banner kyc-banner--blue">
<i class="fa fa-info-circle"></i>
<span>{{ selectedIdProof.info }}</span>
</div>
}
@if (selectedIdProof) {
<div class="kyc-upload-grid" [class.kyc-upload-grid--two]="selectedIdProof.sides === 2">
<div class="kyc-field">
<div class="kyc-upload-label">
<span class="kyc-label">
<i class="fa fa-file-image-o"></i>
{{ selectedIdProof.sides === 2 ? 'Front side' : (selectedIdProof.name + ' (single copy)') }}
</span>
<span class="kyc-tag kyc-tag--req">
<i class="fa fa-asterisk"></i> Required
</span>
</div>
<label
class="kyc-dropzone"
[class.kyc-dropzone--has]="idFiles.front"
[class.kyc-dropzone--req]="!idFiles.front"
>
<input
type="file"
accept=".jpg,.jpeg,.png,.pdf"
hidden
(change)="onFilePick('id', 'front', $event)"
/>
@if (idFiles.front) {
<i class="fa fa-check-circle kyc-dropzone__icon"></i>
<p class="kyc-dropzone__text">File selected</p>
<p class="kyc-dropzone__name">
<i class="fa fa-paperclip"></i> {{ getFileName('id', 'front') }}
</p>
} @else {
<i class="fa fa-cloud-upload kyc-dropzone__icon"></i>
<p class="kyc-dropzone__text">Click to upload</p>
<p class="kyc-dropzone__hint">
<i class="fa fa-file-image-o"></i> JPG ·
<i class="fa fa-file-image-o"></i> PNG ·
<i class="fa fa-file-pdf-o"></i> PDF · max 5MB
</p>
}
</label>
</div>
@if (selectedIdProof.sides === 2) {
<div class="kyc-field">
<div class="kyc-upload-label">
<span class="kyc-label">
<i class="fa fa-file-image-o"></i> Back side
</span>
<span class="kyc-tag kyc-tag--req">
<i class="fa fa-asterisk"></i> Required
</span>
</div>
<label
class="kyc-dropzone"
[class.kyc-dropzone--has]="idFiles.back"
[class.kyc-dropzone--req]="!idFiles.back"
>
<input
type="file"
accept=".jpg,.jpeg,.png,.pdf"
hidden
(change)="onFilePick('id', 'back', $event)"
/>
@if (idFiles.back) {
<i class="fa fa-check-circle kyc-dropzone__icon"></i>
<p class="kyc-dropzone__text">File selected</p>
<p class="kyc-dropzone__name">
<i class="fa fa-paperclip"></i> {{ getFileName('id', 'back') }}
</p>
} @else {
<i class="fa fa-cloud-upload kyc-dropzone__icon"></i>
<p class="kyc-dropzone__text">Click to upload</p>
<p class="kyc-dropzone__hint">
<i class="fa fa-file-image-o"></i> JPG ·
<i class="fa fa-file-image-o"></i> PNG ·
<i class="fa fa-file-pdf-o"></i> PDF · max 5MB
</p>
}
</label>
</div>
}
</div>
@if (idMergedPreview && selectedIdProof?.sides === 2) {
<div class="kyc-merged">
<div class="kyc-merged__head">
<span class="kyc-merged__title">
<i class="fa fa-object-group"></i> Combined preview (front + back)
</span>
<span class="kyc-merged__hint">
Saved as a single PNG. Re-crop a side to update the merged file.
</span>
</div>
<div class="kyc-merged__preview">
<img [src]="idMergedPreview" alt="Merged ID document" />
</div>
<div class="kyc-merged__actions">
<button type="button" class="kyc-merged__btn" (click)="reCrop('id', 'front')">
<i class="fa fa-crop"></i> Re-crop front
</button>
<button type="button" class="kyc-merged__btn" (click)="reCrop('id', 'back')">
<i class="fa fa-crop"></i> Re-crop back
</button>
</div>
</div>
}
}
</div>
<!-- SECTION 2: ADDRESS PROOF -->
<div class="kyc-section" formGroupName="address">
<div class="kyc-section__header">
<div class="kyc-badge kyc-badge--teal">
<i class="fa fa-map-marker"></i>
</div>
<div class="kyc-section__titles">
<p class="kyc-section__title">
<i class="fa fa-home"></i> Address proof
</p>
<p class="kyc-section__sub">
Select document type — upload requirements will update automatically
</p>
</div>
</div>
<!-- Prefilled permanent address -->
@if (clientAddress) {
<div class="kyc-prefill kyc-prefill--teal">
<i class="fa fa-map-marker"></i>
<div>
<span class="kyc-prefill__label">Permanent address (from registration)</span>
<span class="kyc-prefill__value">{{ clientAddress }}</span>
</div>
<i class="fa fa-lock kyc-prefill__lock" title="Prefilled from client registration"></i>
</div>
} @else {
<div class="kyc-banner kyc-banner--amber">
<i class="fa fa-exclamation-triangle"></i>
<span>No permanent address found on file. Add one in the Address tab first.</span>
</div>
}
<div class="kyc-field">
<label class="kyc-label">
<i class="fa fa-file-text-o"></i> Document type
</label>
<div class="kyc-chip-wrap">
@for (group of addressGroups; track group.label; let last = $last) {
<div class="kyc-chip-group-label">
<i class="fa fa-folder-open-o"></i> {{ group.label }}
</div>
<div class="kyc-chip-grid">
@for (p of group.items; track p.id) {
<span
class="kyc-chip"
[class.kyc-chip--sel]="isAddrProofSelected(p)"
(click)="selectAddrProof(p)"
(keydown.enter)="selectAddrProof(p)"
tabindex="0"
role="button"
>
@if (isAddrProofSelected(p)) {
<i class="fa fa-check-circle"></i>
} @else {
<i class="fa fa-circle-o"></i>
}
{{ p.name }}
</span>
}
</div>
@if (!last) {
<div class="kyc-chip-divider"></div>
}
}
</div>
</div>
@if (selectedAddrProof?.info) {
<div class="kyc-banner kyc-banner--teal">
<i class="fa fa-check-circle"></i>
<span>{{ selectedAddrProof.info }}</span>
</div>
}
@if (selectedAddrProof) {
<div class="kyc-upload-grid" [class.kyc-upload-grid--two]="selectedAddrProof.sides === 2">
<div class="kyc-field">
<div class="kyc-upload-label">
<span class="kyc-label">
<i class="fa fa-file-image-o"></i>
{{ selectedAddrProof.sides === 2 ? 'Front side' : (selectedAddrProof.name + ' (single copy)') }}
</span>
<span class="kyc-tag kyc-tag--req">
<i class="fa fa-asterisk"></i> Required
</span>
</div>
<label
class="kyc-dropzone"
[class.kyc-dropzone--has]="addrFiles.front"
[class.kyc-dropzone--req]="!addrFiles.front"
>
<input
type="file"
accept=".jpg,.jpeg,.png,.pdf"
hidden
(change)="onFilePick('address', 'front', $event)"
/>
@if (addrFiles.front) {
<i class="fa fa-check-circle kyc-dropzone__icon"></i>
<p class="kyc-dropzone__text">File selected</p>
<p class="kyc-dropzone__name">
<i class="fa fa-paperclip"></i> {{ getFileName('address', 'front') }}
</p>
} @else {
<i class="fa fa-cloud-upload kyc-dropzone__icon"></i>
<p class="kyc-dropzone__text">Click to upload</p>
<p class="kyc-dropzone__hint">
<i class="fa fa-file-image-o"></i> JPG ·
<i class="fa fa-file-image-o"></i> PNG ·
<i class="fa fa-file-pdf-o"></i> PDF · max 5MB
</p>
}
</label>
</div>
@if (selectedAddrProof.sides === 2) {
<div class="kyc-field">
<div class="kyc-upload-label">
<span class="kyc-label">
<i class="fa fa-file-image-o"></i> Back side
</span>
<span class="kyc-tag kyc-tag--req">
<i class="fa fa-asterisk"></i> Required
</span>
</div>
<label
class="kyc-dropzone"
[class.kyc-dropzone--has]="addrFiles.back"
[class.kyc-dropzone--req]="!addrFiles.back"
>
<input
type="file"
accept=".jpg,.jpeg,.png,.pdf"
hidden
(change)="onFilePick('address', 'back', $event)"
/>
@if (addrFiles.back) {
<i class="fa fa-check-circle kyc-dropzone__icon"></i>
<p class="kyc-dropzone__text">File selected</p>
<p class="kyc-dropzone__name">
<i class="fa fa-paperclip"></i> {{ getFileName('address', 'back') }}
</p>
} @else {
<i class="fa fa-cloud-upload kyc-dropzone__icon"></i>
<p class="kyc-dropzone__text">Click to upload</p>
<p class="kyc-dropzone__hint">
<i class="fa fa-file-image-o"></i> JPG ·
<i class="fa fa-file-image-o"></i> PNG ·
<i class="fa fa-file-pdf-o"></i> PDF · max 5MB
</p>
}
</label>
</div>
}
@if (selectedAddrProof.requiresDate) {
<div class="kyc-field kyc-field--date">
<label class="kyc-label">
<i class="fa fa-calendar"></i> Issue / statement date
<span class="kyc-opt">(not older than 3 months)</span>
</label>
<div class="kyc-input-wrap kyc-input-wrap--date">
<i class="fa fa-calendar-o kyc-input-wrap__icon"></i>
<input
type="date"
class="kyc-input kyc-input--date"
formControlName="issueDate"
/>
</div>
</div>
}
</div>
@if (addrMergedPreview && selectedAddrProof?.sides === 2) {
<div class="kyc-merged">
<div class="kyc-merged__head">
<span class="kyc-merged__title">
<i class="fa fa-object-group"></i> Combined preview (front + back)
</span>
<span class="kyc-merged__hint">
Saved as a single PNG. Re-crop a side to update the merged file.
</span>
</div>
<div class="kyc-merged__preview">
<img [src]="addrMergedPreview" alt="Merged address document" />
</div>
<div class="kyc-merged__actions">
<button type="button" class="kyc-merged__btn" (click)="reCrop('address', 'front')">
<i class="fa fa-crop"></i> Re-crop front
</button>
<button type="button" class="kyc-merged__btn" (click)="reCrop('address', 'back')">
<i class="fa fa-crop"></i> Re-crop back
</button>
</div>
</div>
}
}
</div>
<div class="kyc-section kyc-section--notes">
<label class="kyc-label">
<i class="fa fa-sticky-note-o"></i> Description <span class="kyc-opt">(optional)</span>
</label>
<textarea
class="kyc-textarea"
formControlName="description"
placeholder="Add any additional notes about these documents…"
rows="2"
></textarea>
</div>
<div class="kyc-status-bar">
<span>
<span class="kyc-dot" [class.kyc-dot--done]="isIdComplete()">
@if (isIdComplete()) { <i class="fa fa-check"></i> }
</span>
<i class="fa fa-id-badge"></i> Identity proof
</span>
<span>
<span class="kyc-dot" [class.kyc-dot--done]="isAddressComplete()">
@if (isAddressComplete()) { <i class="fa fa-check"></i> }
</span>
<i class="fa fa-home"></i> Address proof
</span>
<span class="kyc-status-hint">
<i class="fa fa-info-circle"></i> Fill in at least one section
</span>
</div>
</form>
} @else {
<form [formGroup]="uploadDocumentForm" class="fd-form">
<div class="fd-grid fd-grid--cols-2">
<div class="fd-field">
<mat-form-field class="flex-fill" appearance="outline">
<mat-label>{{ 'labels.inputs.File Name' | translate }}</mat-label>
<input formControlName="fileName" required matInput />
@if (uploadDocumentForm.controls.fileName.hasError('required')) {
<mat-error>
{{ 'labels.inputs.File Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
<div class="fd-field">
<mat-form-field class="flex-fill" appearance="outline">
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<input formControlName="description" matInput />
</mat-form-field>
</div>
</div>
<div class="fd-field fd-field--file">
<mifosx-file-upload flex="100%" (change)="onFileSelect($event)"></mifosx-file-upload>
</div>
</form>
}
</div>
<mat-dialog-actions class="fd-actions" align="end">
<button mat-stroked-button mat-dialog-close class="fd-btn fd-btn--cancel">
<i class="fa fa-times fd-btn__icon"></i>
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-flat-button
color="primary"
[disabled]="!canSubmit"
(click)="submit()"
class="fd-btn fd-btn--submit"
>
<i class="fa fa-check fd-btn__icon"></i>
@if (documentIdentifier) {
{{ 'labels.buttons.Submit' | translate }}
} @else {
{{ 'labels.buttons.Upload' | translate }}
}
</button>
</mat-dialog-actions>
</div>