/** * 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/. */ import { Component, OnInit, OnDestroy, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatDialogRef, MatDialogModule, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatButtonModule } from '@angular/material/button'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { ClientsService } from 'app/clients/clients.service'; @Component({ selector: 'mifosx-sanctions-verify-dialog', template: `

Sanctions Verification

@if (verifying) {
{{ progressValue }}%
@for (step of progressSteps; track step.label; let i = $index) {
{{ step.label }}
}

Screening {{ clientName }} against
UN Security Council & Financial Intelligence Unit - India (FIU-IND under UNSC Resolution 2231)

} @if (!verifying && !matchFound && !errorOccurred) {
CLEARED

No Sanctions Match Found

{{ clientName }} has been screened against the UN Security Council Consolidated List and FIU-IND records. No confirmed match was found.

Screened On {{ screenedDate }}
Status Customer is Clear
Database UN SC Consolidated List
} @if (!verifying && matchFound) {
CONFIRMED MATCH / REFER

Sanctions Match Found

{{ clientName }} matched against UN SC Consolidated List.

Screened On {{ screenedDate }}
Status Match Found — Blocked
Database UN SC Consolidated List
Escalate to Principal Officer immediately. Account opening blocked.
} @if (!verifying && errorOccurred) {

Verification failed. Please try again.

{{ errorMessage }}

}
@if (!verifying && !matchFound && !errorOccurred) { } @if (!verifying && matchFound) { } @if (!verifying && errorOccurred) { } `, styles: [ ` :host { display: block; min-width: 500px; } h2[mat-dialog-title] { display: flex; align-items: center; gap: 8px; margin: 0; padding: 16px 24px; font-size: 18px; font-weight: 600; color: #1074b9; } .dialog-title-icon { font-size: 20px; } mat-dialog-content { padding: 16px 24px; 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: 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; align-items: center; gap: 8px; padding: 16px 0; } .result-icon { font-size: 48px; } .result-icon.success { color: #388e3c; } .result-icon.danger { color: #d32f2f; } .result-icon.warning { color: #f57c00; } .result-msg { font-size: 16px; font-weight: 600; margin: 0; text-align: center; } .result-msg.success { color: #388e3c; } .result-msg.danger { color: #d32f2f; } .result-msg.warning { color: #f57c00; } .result-detail { font-size: 14px; color: #555; text-align: center; margin: 4px 0; } .match-list { width: 100%; max-height: unset; overflow: visible; margin: 8px 0; } .match-item { background: #fce4ec; border-left: 4px solid #d32f2f; padding: 8px 12px; margin-bottom: 6px; border-radius: 4px; } .match-name { font-weight: 600; font-size: 14px; color: #333; } .match-meta { display: flex; gap: 8px; margin-top: 4px; flex-wrap: wrap; } .match-type, .match-ref, .match-list-type { font-size: 11px; padding: 2px 6px; border-radius: 3px; background: #ffcdd2; color: #b71c1c; } .match-aliases { font-size: 12px; color: #666; margin-top: 4px; } .block-msg { font-size: 14px; font-weight: 600; color: #d32f2f; background: #ffebee; padding: 10px 16px; border-radius: 4px; text-align: center; margin-top: 8px; width: 100%; } mat-dialog-actions { padding: 8px 24px 16px; } ` ], imports: [ CommonModule, MatDialogModule, MatProgressBarModule, MatButtonModule, FaIconComponent ] }) export class SanctionsVerifyDialogComponent implements OnInit, OnDestroy { private dialogRef = inject(MatDialogRef); private data: { name: string } = inject(MAT_DIALOG_DATA); private clientsService = inject(ClientsService); clientName = ''; verifying = true; matchFound = false; matchCount = 0; 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.apiResult = result; this.apiDone = true; }, error: (err: any) => { this.apiError = err; this.apiDone = true; } }); // Animate progress through steps this.runProgress(); } 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, screeningLogId: this.screeningLogId }); } }