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>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
79e455c4ed
commit
d72e969dc3
@@ -24,52 +24,108 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Top Section: Read-Only Client Info -->
|
||||
<mat-card class="ckyc-info-card">
|
||||
<mat-card-content>
|
||||
<div class="ckyc-info-grid">
|
||||
<!-- Account Number Dropdown -->
|
||||
<div class="ckyc-info-item">
|
||||
<span class="ckyc-info-label">{{ 'labels.inputs.Account Number' | translate }}</span>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-select
|
||||
[value]="selectedAccount?.accountNo || ''"
|
||||
(selectionChange)="onAccountSelect($event.value)"
|
||||
[placeholder]="'labels.inputs.Select Account' | translate"
|
||||
>
|
||||
@for (account of allAccounts; track account.accountNo) {
|
||||
<mat-option [value]="account.accountNo">
|
||||
{{ account.accountNo }} ({{ account.productName }})
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Client ID -->
|
||||
<div class="ckyc-info-item">
|
||||
<span class="ckyc-info-label">{{ 'labels.inputs.Client ID' | translate }}</span>
|
||||
<span class="ckyc-info-value">{{ clientId }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Client Name -->
|
||||
<div class="ckyc-info-item">
|
||||
<span class="ckyc-info-label">{{ 'labels.inputs.Client Name' | translate }}</span>
|
||||
<span class="ckyc-info-value">{{ clientName }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Account Type -->
|
||||
<div class="ckyc-info-item">
|
||||
<span class="ckyc-info-label">{{ 'labels.inputs.Account Type' | translate }}</span>
|
||||
<span class="ckyc-info-value">{{ selectedAccountType || '—' }}</span>
|
||||
</div>
|
||||
<!-- CERSAI Sync Status hero -->
|
||||
<div class="cersai-hero" [class.cersai-hero--synced]="ckycNumber" [class.cersai-hero--pending]="!ckycNumber">
|
||||
<div class="cersai-hero__left">
|
||||
<div class="cersai-hero__icon">
|
||||
@if (ckycNumber) {
|
||||
<fa-icon icon="check-double"></fa-icon>
|
||||
} @else {
|
||||
<fa-icon icon="hourglass-half"></fa-icon>
|
||||
}
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<div class="cersai-hero__text">
|
||||
<div class="cersai-hero__status">
|
||||
@if (ckycNumber) {
|
||||
<span class="cersai-hero__tag">SYNCED WITH CERSAI</span>
|
||||
} @else {
|
||||
<span class="cersai-hero__tag cersai-hero__tag--pending">PENDING CERSAI SYNC</span>
|
||||
}
|
||||
</div>
|
||||
@if (ckycNumber) {
|
||||
<div class="cersai-hero__ckyc-label">CKYC Registry Number</div>
|
||||
<div class="cersai-hero__ckyc-number">{{ ckycNumber }}</div>
|
||||
<div class="cersai-hero__meta">
|
||||
@if (ckycUploadedOn) {
|
||||
<span>
|
||||
<fa-icon icon="calendar-check"></fa-icon>
|
||||
Uploaded on <strong>{{ ckycUploadedOn }}</strong>
|
||||
</span>
|
||||
}
|
||||
@if (ckycBatchId) {
|
||||
<span class="cersai-hero__sep">·</span>
|
||||
<span>
|
||||
<fa-icon icon="file-archive"></fa-icon>
|
||||
Batch <strong>{{ ckycBatchId }}</strong>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="cersai-hero__ckyc-label">This client has not yet been synced to the CERSAI registry</div>
|
||||
<div class="cersai-hero__hint">
|
||||
Collect all 4 artefacts below, then create a batch from the Record Master to upload.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (ckycNumber) {
|
||||
<div class="cersai-hero__pulse">
|
||||
<span class="cersai-hero__pulse-dot"></span>
|
||||
Live
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Client basic info -->
|
||||
<div class="ckyc-info-grid">
|
||||
<div class="ckyc-info-item">
|
||||
<span class="ckyc-info-label">
|
||||
<fa-icon icon="hashtag"></fa-icon> CIF Number
|
||||
</span>
|
||||
<span class="ckyc-info-value ckyc-info-value--mono">{{ cifNumber || clientId }}</span>
|
||||
</div>
|
||||
|
||||
<div class="ckyc-info-item">
|
||||
<span class="ckyc-info-label">
|
||||
<fa-icon icon="user"></fa-icon> Full Name
|
||||
</span>
|
||||
<span class="ckyc-info-value">{{ clientName || '—' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="ckyc-info-item">
|
||||
<span class="ckyc-info-label">
|
||||
<fa-icon icon="envelope"></fa-icon> Email
|
||||
</span>
|
||||
@if (clientEmail) {
|
||||
<span class="ckyc-info-value">{{ clientEmail }}</span>
|
||||
} @else {
|
||||
<span class="ckyc-info-value ckyc-info-value--missing">No email on file</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="ckyc-info-item">
|
||||
<span class="ckyc-info-label">
|
||||
<fa-icon icon="phone"></fa-icon> Mobile
|
||||
</span>
|
||||
@if (clientMobile) {
|
||||
<span class="ckyc-info-value">{{ clientMobile }}</span>
|
||||
} @else {
|
||||
<span class="ckyc-info-value ckyc-info-value--missing">No mobile on file</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Document Upload Section -->
|
||||
<h4 class="ckyc-section-title">{{ 'labels.heading.CKYC Documents' | translate }}</h4>
|
||||
|
||||
@if (checkingExisting) {
|
||||
<div class="ckyc-existing-loading">
|
||||
<mat-progress-spinner diameter="18" mode="indeterminate"></mat-progress-spinner>
|
||||
<span>Checking for already-uploaded artefacts…</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="ckyc-upload-grid">
|
||||
<!-- Address Proof Document -->
|
||||
<mat-card class="ckyc-upload-card">
|
||||
@@ -78,6 +134,41 @@
|
||||
<mat-icon class="ckyc-upload-icon">home</mat-icon>
|
||||
<span>{{ 'labels.inputs.Address Proof Document' | translate }}</span>
|
||||
</div>
|
||||
|
||||
@if (existing.ADDRESS_PROOF.present) {
|
||||
<div class="ckyc-existing">
|
||||
<div class="ckyc-existing__head">
|
||||
<fa-icon icon="check-circle" class="ckyc-existing__ok"></fa-icon>
|
||||
<div>
|
||||
<strong>Already available</strong>
|
||||
<span class="ckyc-existing__source">from {{ existing.ADDRESS_PROOF.source }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ckyc-existing__meta">
|
||||
<fa-icon icon="file-alt"></fa-icon>
|
||||
<span>{{ existing.ADDRESS_PROOF.name }}</span>
|
||||
@if (existing.ADDRESS_PROOF.documentKey) {
|
||||
<span class="ckyc-existing__key">{{ existing.ADDRESS_PROOF.documentKey }}</span>
|
||||
}
|
||||
@if (existing.ADDRESS_PROOF.fileCount && existing.ADDRESS_PROOF.fileCount > 0) {
|
||||
<span class="ckyc-existing__count">
|
||||
{{ existing.ADDRESS_PROOF.fileCount }} {{ existing.ADDRESS_PROOF.fileCount === 1 ? 'file' : 'files' }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<button
|
||||
mat-stroked-button
|
||||
type="button"
|
||||
class="ckyc-existing__replace"
|
||||
(click)="toggleReplace('ADDRESS_PROOF')"
|
||||
>
|
||||
<fa-icon [icon]="existing.ADDRESS_PROOF.expanded ? 'times' : 'upload'"></fa-icon>
|
||||
{{ existing.ADDRESS_PROOF.expanded ? 'Cancel replace' : 'Upload new version' }}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!existing.ADDRESS_PROOF.present || existing.ADDRESS_PROOF.expanded) {
|
||||
<p class="ckyc-upload-hint">{{ 'labels.inputs.Accepted formats' | translate }}: PNG, JPEG, JPG, PDF</p>
|
||||
<div class="ckyc-file-row">
|
||||
<input
|
||||
@@ -137,6 +228,7 @@
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
@@ -147,6 +239,41 @@
|
||||
<mat-icon class="ckyc-upload-icon">photo_camera</mat-icon>
|
||||
<span>{{ 'labels.inputs.Photo Proof Document' | translate }}</span>
|
||||
</div>
|
||||
|
||||
@if (existing.PHOTO_PROOF.present) {
|
||||
<div class="ckyc-existing">
|
||||
<div class="ckyc-existing__head">
|
||||
<fa-icon icon="check-circle" class="ckyc-existing__ok"></fa-icon>
|
||||
<div>
|
||||
<strong>Already available</strong>
|
||||
<span class="ckyc-existing__source">from {{ existing.PHOTO_PROOF.source }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ckyc-existing__meta">
|
||||
<fa-icon icon="id-card"></fa-icon>
|
||||
<span>{{ existing.PHOTO_PROOF.name }}</span>
|
||||
@if (existing.PHOTO_PROOF.documentKey) {
|
||||
<span class="ckyc-existing__key">{{ existing.PHOTO_PROOF.documentKey }}</span>
|
||||
}
|
||||
@if (existing.PHOTO_PROOF.fileCount && existing.PHOTO_PROOF.fileCount > 0) {
|
||||
<span class="ckyc-existing__count">
|
||||
{{ existing.PHOTO_PROOF.fileCount }} {{ existing.PHOTO_PROOF.fileCount === 1 ? 'file' : 'files' }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<button
|
||||
mat-stroked-button
|
||||
type="button"
|
||||
class="ckyc-existing__replace"
|
||||
(click)="toggleReplace('PHOTO_PROOF')"
|
||||
>
|
||||
<fa-icon [icon]="existing.PHOTO_PROOF.expanded ? 'times' : 'upload'"></fa-icon>
|
||||
{{ existing.PHOTO_PROOF.expanded ? 'Cancel replace' : 'Upload new version' }}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!existing.PHOTO_PROOF.present || existing.PHOTO_PROOF.expanded) {
|
||||
<p class="ckyc-upload-hint">{{ 'labels.inputs.Accepted formats' | translate }}: PNG, JPEG, JPG, PDF</p>
|
||||
<div class="ckyc-file-row">
|
||||
<input
|
||||
@@ -206,6 +333,7 @@
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
@@ -216,6 +344,32 @@
|
||||
<mat-icon class="ckyc-upload-icon">person</mat-icon>
|
||||
<span>{{ 'labels.inputs.Image of Client' | translate }}</span>
|
||||
</div>
|
||||
|
||||
@if (existing.CLIENT_IMAGE.present) {
|
||||
<div class="ckyc-existing">
|
||||
<div class="ckyc-existing__head">
|
||||
<fa-icon icon="check-circle" class="ckyc-existing__ok"></fa-icon>
|
||||
<div>
|
||||
<strong>Already available</strong>
|
||||
<span class="ckyc-existing__source">from {{ existing.CLIENT_IMAGE.source }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@if (existing.CLIENT_IMAGE.previewUrl) {
|
||||
<img [src]="existing.CLIENT_IMAGE.previewUrl" alt="Client photo" class="ckyc-existing__thumb" />
|
||||
}
|
||||
<button
|
||||
mat-stroked-button
|
||||
type="button"
|
||||
class="ckyc-existing__replace"
|
||||
(click)="toggleReplace('CLIENT_IMAGE')"
|
||||
>
|
||||
<fa-icon [icon]="existing.CLIENT_IMAGE.expanded ? 'times' : 'upload'"></fa-icon>
|
||||
{{ existing.CLIENT_IMAGE.expanded ? 'Cancel replace' : 'Upload new version' }}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!existing.CLIENT_IMAGE.present || existing.CLIENT_IMAGE.expanded) {
|
||||
<p class="ckyc-upload-hint">{{ 'labels.inputs.Accepted formats' | translate }}: PNG, JPEG, JPG</p>
|
||||
<div class="ckyc-file-row">
|
||||
<input
|
||||
@@ -275,6 +429,7 @@
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
@@ -285,6 +440,36 @@
|
||||
<mat-icon class="ckyc-upload-icon">draw</mat-icon>
|
||||
<span>{{ 'labels.inputs.Signature of Client' | translate }}</span>
|
||||
</div>
|
||||
|
||||
@if (existing.CLIENT_SIGNATURE.present) {
|
||||
<div class="ckyc-existing">
|
||||
<div class="ckyc-existing__head">
|
||||
<fa-icon icon="check-circle" class="ckyc-existing__ok"></fa-icon>
|
||||
<div>
|
||||
<strong>Already available</strong>
|
||||
<span class="ckyc-existing__source">from {{ existing.CLIENT_SIGNATURE.source }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@if (existing.CLIENT_SIGNATURE.previewUrl) {
|
||||
<img [src]="existing.CLIENT_SIGNATURE.previewUrl" alt="Client signature" class="ckyc-existing__thumb ckyc-existing__thumb--wide" />
|
||||
}
|
||||
<div class="ckyc-existing__meta">
|
||||
<fa-icon icon="file-signature"></fa-icon>
|
||||
<span>{{ existing.CLIENT_SIGNATURE.name }}</span>
|
||||
</div>
|
||||
<button
|
||||
mat-stroked-button
|
||||
type="button"
|
||||
class="ckyc-existing__replace"
|
||||
(click)="toggleReplace('CLIENT_SIGNATURE')"
|
||||
>
|
||||
<fa-icon [icon]="existing.CLIENT_SIGNATURE.expanded ? 'times' : 'upload'"></fa-icon>
|
||||
{{ existing.CLIENT_SIGNATURE.expanded ? 'Cancel replace' : 'Upload new version' }}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!existing.CLIENT_SIGNATURE.present || existing.CLIENT_SIGNATURE.expanded) {
|
||||
<p class="ckyc-upload-hint">{{ 'labels.inputs.Accepted formats' | translate }}: PNG, JPEG, JPG</p>
|
||||
<div class="ckyc-file-row">
|
||||
<input
|
||||
@@ -344,6 +529,7 @@
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user