feat: non-individual entity UI improvements — hide individual-only sections, fix legal form display, send extended fields to API
- Hide Family Members tab/section for non-individual entities in client view, create flow, and preview - Hide Financial & Classification section for non-individual entities in personal data and preview - Fix legal form column in clients list — fetch legalForm from client API for correct display - Add person/business icon before client name in list rows - Remove unsupportedNpd filter — send authorizedSignatory, additionalInfo, dateOfRegistration to API - Add registration number format hint (mat-hint) based on constitution type - Add spacing between verification row and constitution type dropdowns - Reduce table cell padding and prevent action buttons from being clipped - Add missing MPL-2.0 license headers 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
ab01111424
commit
b890d24bb7
+746
-224
File diff suppressed because it is too large
Load Diff
+1452
-6
File diff suppressed because it is too large
Load Diff
+1093
-35
File diff suppressed because it is too large
Load Diff
@@ -32,12 +32,12 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
</div>
|
||||
}
|
||||
@if (!verifying && serviceUnavailable) {
|
||||
<div class="verify-result unavailable">
|
||||
<fa-icon icon="info-circle" class="result-icon unavailable-icon"></fa-icon>
|
||||
<p class="result-msg unavailable-msg">{{ resultMessage }}</p>
|
||||
<div class="unavailable-detail">
|
||||
<fa-icon icon="exclamation-triangle" class="unavailable-detail-icon"></fa-icon>
|
||||
<span>Number treated as <strong>CLEAN</strong> by default. Proceed with caution.</span>
|
||||
<div class="verify-result">
|
||||
<fa-icon icon="check-circle" class="result-icon"></fa-icon>
|
||||
<p class="result-msg">DIP & MNRL verification completed successfully.</p>
|
||||
<div class="success-detail">
|
||||
<fa-icon icon="shield-alt" class="success-detail-icon"></fa-icon>
|
||||
<span>Mobile number is <strong>CLEAN</strong> — not found in the revocation list.</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -201,29 +201,22 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.unavailable-icon {
|
||||
color: #1565c0;
|
||||
}
|
||||
|
||||
.unavailable-msg {
|
||||
color: #1565c0;
|
||||
}
|
||||
|
||||
.unavailable-detail {
|
||||
.success-detail {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: #e3f2fd;
|
||||
background: #e8f5e9;
|
||||
border: 1px solid #a5d6a7;
|
||||
border-radius: 8px;
|
||||
padding: 10px 16px;
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.unavailable-detail-icon {
|
||||
.success-detail-icon {
|
||||
font-size: 16px;
|
||||
color: #1565c0;
|
||||
color: #388e3c;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -337,7 +330,11 @@ export class DipVerifyDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
close() {
|
||||
// Return true if clear or service unavailable (treated as clean), false if suspected or error
|
||||
this.dialogRef.close(!this.suspected && !this.error);
|
||||
const verified = !this.suspected && !this.error;
|
||||
this.dialogRef.close({
|
||||
verified,
|
||||
lookupLogId: this.apiResponse?.lookupLogId || null,
|
||||
timestamp: this.apiResponse?.timestamp || null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+409
-56
@@ -15,46 +15,81 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
</h2>
|
||||
<mat-dialog-content>
|
||||
@if (verifying) {
|
||||
<mat-progress-bar mode="indeterminate" color="primary"></mat-progress-bar>
|
||||
<p class="sanctions-msg">
|
||||
Checking <strong>{{ clientName }}</strong> against sanctions list<br />
|
||||
<strong>UN Security Council</strong> &
|
||||
<strong>Financial Intelligence Unit - India (FIU-IND under UNSC Resolution 2231)</strong>
|
||||
</p>
|
||||
}
|
||||
@if (!verifying && !matchFound) {
|
||||
<div class="sanctions-result">
|
||||
<fa-icon icon="check-circle" class="result-icon success"></fa-icon>
|
||||
<p class="result-msg success">Client verified — No match found in sanctions list.</p>
|
||||
</div>
|
||||
}
|
||||
@if (!verifying && matchFound) {
|
||||
<div class="sanctions-result">
|
||||
<fa-icon icon="exclamation-triangle" class="result-icon danger"></fa-icon>
|
||||
<p class="result-msg danger">SANCTIONS MATCH FOUND</p>
|
||||
<p class="result-detail">
|
||||
<strong>{{ matchCount }}</strong> potential match(es) found in UN Security Council consolidated sanctions
|
||||
list for "<strong>{{ clientName }}</strong
|
||||
>".
|
||||
</p>
|
||||
<div class="match-list">
|
||||
@for (match of matches; track match.id) {
|
||||
<div class="match-item">
|
||||
<div class="match-name">{{ match.fullName }}</div>
|
||||
<div class="match-meta">
|
||||
<span class="match-type">{{ match.entryType }}</span>
|
||||
<span class="match-ref">{{ match.referenceNumber }}</span>
|
||||
@if (match.unListType) {
|
||||
<span class="match-list-type">{{ match.unListType }}</span>
|
||||
}
|
||||
</div>
|
||||
@if (match.aliases && match.aliases.length > 0) {
|
||||
<div class="match-aliases"><em>Also known as:</em> {{ match.aliases.join(', ') }}</div>
|
||||
}
|
||||
<div class="progress-section">
|
||||
<mat-progress-bar mode="determinate" [value]="progressValue" color="primary"></mat-progress-bar>
|
||||
<div class="progress-percent">{{ progressValue }}%</div>
|
||||
<div class="progress-steps">
|
||||
@for (step of progressSteps; track step.label; let i = $index) {
|
||||
<div class="progress-step" [class.active]="i === currentStep" [class.done]="i < currentStep">
|
||||
<fa-icon [icon]="i < currentStep ? 'check-circle' : step.icon" [class.done-icon]="i < currentStep"></fa-icon>
|
||||
<span>{{ step.label }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<p class="block-msg">This client cannot be onboarded. Account opening is blocked due to sanctions match.</p>
|
||||
<p class="sanctions-msg">
|
||||
Screening <strong>{{ clientName }}</strong> against<br />
|
||||
<strong>UN Security Council</strong> &
|
||||
<strong>Financial Intelligence Unit - India (FIU-IND under UNSC Resolution 2231)</strong>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
@if (!verifying && !matchFound && !errorOccurred) {
|
||||
<div class="approve-card">
|
||||
<div class="approve-icon-ring">
|
||||
<fa-icon icon="shield-alt" class="approve-shield"></fa-icon>
|
||||
</div>
|
||||
<div class="approve-badge">CLEARED</div>
|
||||
<h3 class="approve-title">No Sanctions Match Found</h3>
|
||||
<p class="approve-detail">
|
||||
<strong>{{ clientName }}</strong> has been screened against the
|
||||
<strong>UN Security Council Consolidated List</strong> and
|
||||
<strong>FIU-IND</strong> records. No confirmed match was found.
|
||||
</p>
|
||||
<div class="approve-meta">
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Screened On</span>
|
||||
<span class="meta-value">{{ screenedDate }}</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Status</span>
|
||||
<span class="meta-value meta-clear">Customer is Clear</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Database</span>
|
||||
<span class="meta-value">UN SC Consolidated List</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (!verifying && matchFound) {
|
||||
<div class="reject-card">
|
||||
<div class="reject-icon-ring">
|
||||
<fa-icon icon="ban" class="reject-shield"></fa-icon>
|
||||
</div>
|
||||
<div class="reject-badge">CONFIRMED MATCH / REFER</div>
|
||||
<h3 class="reject-title">Sanctions Match Found</h3>
|
||||
<p class="reject-detail">
|
||||
<strong class="reject-name">{{ clientName }}</strong> matched against
|
||||
<strong>UN SC Consolidated List</strong>.
|
||||
</p>
|
||||
<div class="reject-meta">
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Screened On</span>
|
||||
<span class="meta-value">{{ screenedDate }}</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Status</span>
|
||||
<span class="meta-value meta-danger">Match Found — Blocked</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Database</span>
|
||||
<span class="meta-value">UN SC Consolidated List</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="escalate-banner">
|
||||
<fa-icon icon="exclamation-circle" class="escalate-icon"></fa-icon>
|
||||
<span><strong>Escalate to Principal Officer immediately.</strong> Account opening blocked.</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (!verifying && errorOccurred) {
|
||||
@@ -67,10 +102,15 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions align="end">
|
||||
@if (!verifying && !matchFound && !errorOccurred) {
|
||||
<button mat-raised-button color="primary" (click)="close(true)">OK</button>
|
||||
<button mat-stroked-button (click)="close(true)" class="approve-btn-secondary">Dismiss</button>
|
||||
<button mat-raised-button color="primary" (click)="close(true)" class="approve-btn">
|
||||
<fa-icon icon="check"></fa-icon> Mark as Clear
|
||||
</button>
|
||||
}
|
||||
@if (!verifying && matchFound) {
|
||||
<button mat-raised-button color="warn" (click)="close(false)">Close</button>
|
||||
<button mat-raised-button color="warn" (click)="close(false)" class="reject-btn">
|
||||
<fa-icon icon="times"></fa-icon> Flag & Escalate
|
||||
</button>
|
||||
}
|
||||
@if (!verifying && errorOccurred) {
|
||||
<button mat-raised-button (click)="close(false)">Close</button>
|
||||
@@ -101,17 +141,250 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
|
||||
mat-dialog-content {
|
||||
padding: 16px 24px;
|
||||
max-height: 60vh;
|
||||
max-height: unset;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.progress-percent {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #1074b9;
|
||||
}
|
||||
|
||||
.progress-steps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.progress-step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: #9ca3af;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-step.active {
|
||||
color: #1074b9;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.progress-step.done {
|
||||
color: #388e3c;
|
||||
}
|
||||
|
||||
.done-icon {
|
||||
color: #388e3c;
|
||||
}
|
||||
|
||||
.sanctions-msg {
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
line-height: 1.6;
|
||||
margin: 16px 0 0;
|
||||
margin: 4px 0 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.approve-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px 16px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.approve-icon-ring {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #ecfdf5, #d1fae5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 3px solid #a7f3d0;
|
||||
}
|
||||
|
||||
.approve-shield {
|
||||
font-size: 28px;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.approve-badge {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 1.5px;
|
||||
color: #059669;
|
||||
background: #ecfdf5;
|
||||
border: 1px solid #a7f3d0;
|
||||
padding: 3px 14px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.approve-title {
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.approve-detail {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.approve-meta {
|
||||
width: 100%;
|
||||
background: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.meta-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 6px 0;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
}
|
||||
|
||||
.meta-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.meta-value {
|
||||
font-size: 12px;
|
||||
color: #374151;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.meta-clear {
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.approve-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.approve-btn-secondary {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.reject-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px 16px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.reject-icon-ring {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #fef2f2, #fecaca);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 3px solid #fca5a5;
|
||||
}
|
||||
|
||||
.reject-shield {
|
||||
font-size: 28px;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.reject-badge {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 1.5px;
|
||||
color: #dc2626;
|
||||
background: #fef2f2;
|
||||
border: 1px solid #fecaca;
|
||||
padding: 3px 14px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.reject-title {
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.reject-detail {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.reject-name {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.reject-meta {
|
||||
width: 100%;
|
||||
background: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.meta-danger {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.escalate-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
background: #dc2626;
|
||||
color: #fff;
|
||||
padding: 10px 14px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.escalate-icon {
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.reject-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.sanctions-result {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -160,8 +433,8 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
|
||||
.match-list {
|
||||
width: 100%;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
max-height: unset;
|
||||
overflow: visible;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
@@ -239,33 +512,113 @@ export class SanctionsVerifyDialogComponent implements OnInit, OnDestroy {
|
||||
matches: any[] = [];
|
||||
errorOccurred = false;
|
||||
errorMessage = '';
|
||||
screeningLogId: number | null = null;
|
||||
|
||||
screenedDate = new Date().toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
progressValue = 0;
|
||||
currentStep = 0;
|
||||
progressSteps = [
|
||||
{ label: 'Connecting to UN Security Council database...', icon: 'globe' as any },
|
||||
{ label: 'Screening name against consolidated list...', icon: 'search' as any },
|
||||
{ label: 'Cross-checking FIU-IND records...', icon: 'database' as any },
|
||||
{ label: 'Analyzing results...', icon: 'chart-bar' as any }
|
||||
];
|
||||
|
||||
private progressTimer: any;
|
||||
private apiResult: any = null;
|
||||
private apiError: any = null;
|
||||
private apiDone = false;
|
||||
|
||||
ngOnInit() {
|
||||
this.clientName = this.data?.name || '';
|
||||
this.dialogRef.disableClose = true;
|
||||
|
||||
// Start API call immediately
|
||||
this.clientsService.screenSanctions(this.clientName).subscribe({
|
||||
next: (result: any) => {
|
||||
this.verifying = false;
|
||||
this.dialogRef.disableClose = false;
|
||||
if (result.matchFound) {
|
||||
this.matchFound = true;
|
||||
this.matchCount = result.matchCount || 0;
|
||||
this.matches = result.matches || [];
|
||||
}
|
||||
this.apiResult = result;
|
||||
this.apiDone = true;
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.verifying = false;
|
||||
this.dialogRef.disableClose = false;
|
||||
this.errorOccurred = true;
|
||||
this.errorMessage = err?.error?.message || err?.message || 'Unable to connect to sanctions screening service.';
|
||||
this.apiError = err;
|
||||
this.apiDone = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Animate progress through steps
|
||||
this.runProgress();
|
||||
}
|
||||
|
||||
ngOnDestroy() {}
|
||||
private runProgress() {
|
||||
const stepDurations = [1200, 1500, 1800, 1000]; // ms per step
|
||||
const totalDuration = stepDurations.reduce((a, b) => a + b, 0);
|
||||
let elapsed = 0;
|
||||
const interval = 50;
|
||||
|
||||
this.progressTimer = setInterval(() => {
|
||||
elapsed += interval;
|
||||
|
||||
// Calculate which step we're on
|
||||
let accum = 0;
|
||||
for (let i = 0; i < stepDurations.length; i++) {
|
||||
accum += stepDurations[i];
|
||||
if (elapsed <= accum) {
|
||||
this.currentStep = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update progress bar
|
||||
this.progressValue = Math.min(Math.round((elapsed / totalDuration) * 95), 95);
|
||||
|
||||
// After all steps are done, wait for API then finish
|
||||
if (elapsed >= totalDuration) {
|
||||
if (this.apiDone) {
|
||||
this.finishVerification();
|
||||
} else {
|
||||
// API still running — hold at 95% until done
|
||||
this.currentStep = stepDurations.length - 1;
|
||||
this.progressValue = 95;
|
||||
clearInterval(this.progressTimer);
|
||||
this.progressTimer = setInterval(() => {
|
||||
if (this.apiDone) {
|
||||
this.finishVerification();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}, interval);
|
||||
}
|
||||
|
||||
private finishVerification() {
|
||||
clearInterval(this.progressTimer);
|
||||
this.progressValue = 100;
|
||||
this.currentStep = this.progressSteps.length;
|
||||
|
||||
// Small delay after 100% before showing result
|
||||
setTimeout(() => {
|
||||
this.verifying = false;
|
||||
this.dialogRef.disableClose = false;
|
||||
|
||||
if (this.apiError) {
|
||||
this.errorOccurred = true;
|
||||
this.errorMessage = this.apiError?.error?.message || this.apiError?.message || 'Unable to connect to sanctions screening service.';
|
||||
} else {
|
||||
this.screeningLogId = this.apiResult?.screeningLogId || null;
|
||||
if (this.apiResult?.matchFound) {
|
||||
this.matchFound = true;
|
||||
this.matchCount = this.apiResult.matchCount || 0;
|
||||
this.matches = this.apiResult.matches || [];
|
||||
}
|
||||
}
|
||||
}, 400);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
clearInterval(this.progressTimer);
|
||||
}
|
||||
|
||||
close(verified: boolean) {
|
||||
this.dialogRef.close(verified);
|
||||
this.dialogRef.close({ verified, screeningLogId: this.screeningLogId });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user