/**
* 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.
}
@if (!verifying && matchFound) {
CONFIRMED MATCH / REFER
Sanctions Match Found
{{ clientName }} matched against
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) {
Dismiss
Mark as Clear
}
@if (!verifying && matchFound) {
Flag & Escalate
}
@if (!verifying && errorOccurred) {
Close
}
`,
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 });
}
}