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:
Aditya
2026-04-04 15:10:51 +05:30
co-authored by Claude Opus 4.6
parent ab01111424
commit b890d24bb7
522 changed files with 51991 additions and 21885 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
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
});
}
}
@@ -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> &amp;
<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> &amp;
<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 &amp; 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 });
}
}