+3
-1
@@ -91,7 +91,9 @@
|
||||
@if (isFieldEnabled('stateProvinceId')) {
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">{{ 'labels.inputs.State / Province' | translate }}</span>
|
||||
<span class="detail-value">{{ getSelectedValue('stateProvinceIdOptions', address.stateProvinceId)?.name }}</span>
|
||||
<span class="detail-value">{{
|
||||
getSelectedValue('stateProvinceIdOptions', address.stateProvinceId)?.name
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
@if (isFieldEnabled('countryId')) {
|
||||
|
||||
+6
-6
@@ -41,11 +41,11 @@
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 24px 16px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
color: rgb(0 0 0 / 45%);
|
||||
font-size: 14px;
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
background: rgb(0 0 0 / 2%);
|
||||
border-radius: 6px;
|
||||
border: 1px dashed rgba(0, 0, 0, 0.12);
|
||||
border: 1px dashed rgb(0 0 0 / 12%);
|
||||
|
||||
.empty-icon {
|
||||
font-size: 18px;
|
||||
@@ -58,7 +58,7 @@
|
||||
.panel-icon {
|
||||
margin-right: 8px;
|
||||
font-size: 13px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
color: rgb(0 0 0 / 45%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
.detail-label {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
color: rgb(0 0 0 / 50%);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
color: #333;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@media (width <= 768px) {
|
||||
.section-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
+528
-204
File diff suppressed because it is too large
Load Diff
+147
-29
@@ -1,18 +1,48 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Entity Classification — Scheme & Sector Classification
|
||||
*/
|
||||
|
||||
.ec-page { padding: 0; }
|
||||
.ec-page {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.step-header {
|
||||
background: #0f172a;
|
||||
padding: 16px 24px;
|
||||
border-radius: 10px 10px 0 0;
|
||||
margin-bottom: 20px;
|
||||
&__left { display: flex; align-items: center; gap: 12px; }
|
||||
&__icon { font-size: 18px; color: #60a5fa; }
|
||||
&__title { font-size: 16px; font-weight: 600; color: #fff; margin: 0; }
|
||||
&__sub { font-size: 11px; color: rgba(255,255,255,0.5); margin: 2px 0 0; }
|
||||
|
||||
&__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 18px;
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__sub {
|
||||
font-size: 11px;
|
||||
color: rgb(255 255 255 / 50%);
|
||||
margin: 2px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ec-chips-label {
|
||||
@@ -32,7 +62,11 @@
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.ec-no-chip { font-size: 12px; color: #94a3b8; padding: 4px 0; }
|
||||
.ec-no-chip {
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.ec-chip {
|
||||
padding: 4px 10px;
|
||||
@@ -50,7 +84,10 @@
|
||||
height: 14px !important;
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
&:hover { opacity: 1; }
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.ec-card {
|
||||
@@ -60,7 +97,10 @@
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.2s;
|
||||
&--open { border-color: #93c5fd; }
|
||||
|
||||
&--open {
|
||||
border-color: #93c5fd;
|
||||
}
|
||||
}
|
||||
|
||||
.ec-card-head {
|
||||
@@ -71,10 +111,17 @@
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: background 0.15s;
|
||||
&:hover { background: #f8fafc; }
|
||||
|
||||
&:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
}
|
||||
|
||||
.ec-card-head-left { display: flex; align-items: center; gap: 12px; }
|
||||
.ec-card-head-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.ec-tag {
|
||||
padding: 3px 10px;
|
||||
@@ -85,14 +132,31 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ec-card-title { font-size: 14px; font-weight: 600; color: #1e293b; }
|
||||
.ec-card-desc { font-size: 12px; color: #64748b; margin-top: 2px; }
|
||||
.ec-card-toggle { display: flex; align-items: center; gap: 10px; }
|
||||
.ec-card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.ec-card-desc {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.ec-card-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.ec-chevron {
|
||||
transition: transform 0.25s;
|
||||
color: #94a3b8;
|
||||
&.open { transform: rotate(180deg); }
|
||||
|
||||
&.open {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.ec-card-body {
|
||||
@@ -105,7 +169,10 @@
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 4px 16px;
|
||||
margin-bottom: 8px;
|
||||
mat-form-field { width: 100%; }
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.ec-grid-2 {
|
||||
@@ -113,7 +180,10 @@
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
margin-bottom: 8px;
|
||||
mat-form-field { width: 100%; }
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.ec-sub-title {
|
||||
@@ -125,7 +195,12 @@
|
||||
margin: 16px 0 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px dashed #e2e8f0;
|
||||
&:first-child { margin-top: 0; padding-top: 0; border-top: none; }
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ec-check-grid {
|
||||
@@ -133,13 +208,20 @@
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
mat-checkbox {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
transition: background 0.12s, border-color 0.12s;
|
||||
&:hover { background: #f8fafc; border-color: #93c5fd; }
|
||||
transition:
|
||||
background 0.12s,
|
||||
border-color 0.12s;
|
||||
|
||||
&:hover {
|
||||
background: #f8fafc;
|
||||
border-color: #93c5fd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,8 +242,19 @@
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.ec-flag-label { font-size: 13px; font-weight: 500; color: #1e293b; display: block; }
|
||||
.ec-flag-sub { font-size: 11px; color: #94a3b8; display: block; margin-top: 1px; }
|
||||
.ec-flag-label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #1e293b;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ec-flag-sub {
|
||||
font-size: 11px;
|
||||
color: #94a3b8;
|
||||
display: block;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.ec-reg-note {
|
||||
margin-top: 16px;
|
||||
@@ -172,7 +265,10 @@
|
||||
font-size: 12px;
|
||||
color: #0c4a6e;
|
||||
line-height: 1.6;
|
||||
strong { font-weight: 600; }
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.ec-card-actions {
|
||||
@@ -181,7 +277,12 @@
|
||||
margin-top: 16px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
button { display: flex; align-items: center; gap: 6px; }
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.ec-info-banner {
|
||||
@@ -197,10 +298,27 @@
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) { .ec-grid-3 { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 600px) {
|
||||
.ec-grid-3, .ec-grid-2 { grid-template-columns: 1fr; }
|
||||
.ec-check-grid { grid-template-columns: 1fr; }
|
||||
.ec-card-head { padding: 12px 14px; }
|
||||
.ec-card-body { padding: 14px; }
|
||||
@media (width <= 900px) {
|
||||
.ec-grid-3 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
.ec-grid-3,
|
||||
.ec-grid-2 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.ec-check-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.ec-card-head {
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
.ec-card-body {
|
||||
padding: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
+100
-20
@@ -1,5 +1,9 @@
|
||||
/**
|
||||
* 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, Input, OnInit, OnChanges, SimpleChanges, inject } from '@angular/core';
|
||||
@@ -54,13 +58,69 @@ export class ClientEntityKycStepComponent implements OnInit, OnChanges {
|
||||
|
||||
/** Category metadata */
|
||||
categories: CategoryMeta[] = [
|
||||
{ id: 'msme', label: 'MSME', tag: 'Udyam / MSME', tagColor: '#0A5C6B', tagBg: '#E6F4F6', desc: 'Micro, Small & Medium Enterprise — Ministry of MSME / Udyam Registration', icon: 'cogs' },
|
||||
{ id: 'startup', label: 'Startup India Registered', tag: 'DPIIT', tagColor: '#1A5FAB', tagBg: '#EAF2FB', desc: 'DPIIT recognition under Startup India initiative', icon: 'rocket' },
|
||||
{ id: 'mudra', label: 'Mudra Borrower', tag: 'PMMY', tagColor: '#8A5A00', tagBg: '#FEF3DC', desc: 'Pradhan Mantri Mudra Yojana — SIDBI / Mudra scheme', icon: 'hand-holding-usd' },
|
||||
{ id: 'eou', label: 'Export Oriented Unit', tag: 'EOU', tagColor: '#1B7A4A', tagBg: '#E8F5EE', desc: 'Customs-registered EOU under DGFT / Ministry of Commerce', icon: 'ship' },
|
||||
{ id: 'importexport', label: 'Import Export Business', tag: 'DGFT / IEC', tagColor: '#5B3A8A', tagBg: '#F0EBF8', desc: 'Entity with valid DGFT Importer Exporter Code', icon: 'exchange-alt' },
|
||||
{ id: 'psl', label: 'Priority Sector Borrower', tag: 'RBI PSL', tagColor: '#7B1F1F', tagBg: '#FCEAEA', desc: 'Eligible under RBI Priority Sector Lending guidelines', icon: 'bullseye' },
|
||||
{ id: 'nps', label: 'Non-Priority Sector', tag: 'General', tagColor: '#444441', tagBg: '#F1EFE8', desc: 'Standard corporate — no special priority sector classification', icon: 'building' }
|
||||
{
|
||||
id: 'msme',
|
||||
label: 'MSME',
|
||||
tag: 'Udyam / MSME',
|
||||
tagColor: '#0A5C6B',
|
||||
tagBg: '#E6F4F6',
|
||||
desc: 'Micro, Small & Medium Enterprise — Ministry of MSME / Udyam Registration',
|
||||
icon: 'cogs'
|
||||
},
|
||||
{
|
||||
id: 'startup',
|
||||
label: 'Startup India Registered',
|
||||
tag: 'DPIIT',
|
||||
tagColor: '#1A5FAB',
|
||||
tagBg: '#EAF2FB',
|
||||
desc: 'DPIIT recognition under Startup India initiative',
|
||||
icon: 'rocket'
|
||||
},
|
||||
{
|
||||
id: 'mudra',
|
||||
label: 'Mudra Borrower',
|
||||
tag: 'PMMY',
|
||||
tagColor: '#8A5A00',
|
||||
tagBg: '#FEF3DC',
|
||||
desc: 'Pradhan Mantri Mudra Yojana — SIDBI / Mudra scheme',
|
||||
icon: 'hand-holding-usd'
|
||||
},
|
||||
{
|
||||
id: 'eou',
|
||||
label: 'Export Oriented Unit',
|
||||
tag: 'EOU',
|
||||
tagColor: '#1B7A4A',
|
||||
tagBg: '#E8F5EE',
|
||||
desc: 'Customs-registered EOU under DGFT / Ministry of Commerce',
|
||||
icon: 'ship'
|
||||
},
|
||||
{
|
||||
id: 'importexport',
|
||||
label: 'Import Export Business',
|
||||
tag: 'DGFT / IEC',
|
||||
tagColor: '#5B3A8A',
|
||||
tagBg: '#F0EBF8',
|
||||
desc: 'Entity with valid DGFT Importer Exporter Code',
|
||||
icon: 'exchange-alt'
|
||||
},
|
||||
{
|
||||
id: 'psl',
|
||||
label: 'Priority Sector Borrower',
|
||||
tag: 'RBI PSL',
|
||||
tagColor: '#7B1F1F',
|
||||
tagBg: '#FCEAEA',
|
||||
desc: 'Eligible under RBI Priority Sector Lending guidelines',
|
||||
icon: 'bullseye'
|
||||
},
|
||||
{
|
||||
id: 'nps',
|
||||
label: 'Non-Priority Sector',
|
||||
tag: 'General',
|
||||
tagColor: '#444441',
|
||||
tagBg: '#F1EFE8',
|
||||
desc: 'Standard corporate — no special priority sector classification',
|
||||
icon: 'building'
|
||||
}
|
||||
];
|
||||
|
||||
/** Toggle & expand state per category */
|
||||
@@ -89,7 +149,15 @@ export class ClientEntityKycStepComponent implements OnInit, OnChanges {
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes['readOnly']) {
|
||||
const forms = [this.msmeForm, this.startupForm, this.mudraForm, this.eouForm, this.importexportForm, this.pslForm, this.npsForm];
|
||||
const forms = [
|
||||
this.msmeForm,
|
||||
this.startupForm,
|
||||
this.mudraForm,
|
||||
this.eouForm,
|
||||
this.importexportForm,
|
||||
this.pslForm,
|
||||
this.npsForm
|
||||
];
|
||||
for (const form of forms) {
|
||||
if (!form) continue;
|
||||
if (this.readOnly) {
|
||||
@@ -213,14 +281,22 @@ export class ClientEntityKycStepComponent implements OnInit, OnChanges {
|
||||
|
||||
getForm(categoryId: string): UntypedFormGroup {
|
||||
switch (categoryId) {
|
||||
case 'msme': return this.msmeForm;
|
||||
case 'startup': return this.startupForm;
|
||||
case 'mudra': return this.mudraForm;
|
||||
case 'eou': return this.eouForm;
|
||||
case 'importexport': return this.importexportForm;
|
||||
case 'psl': return this.pslForm;
|
||||
case 'nps': return this.npsForm;
|
||||
default: return this.fb.group({});
|
||||
case 'msme':
|
||||
return this.msmeForm;
|
||||
case 'startup':
|
||||
return this.startupForm;
|
||||
case 'mudra':
|
||||
return this.mudraForm;
|
||||
case 'eou':
|
||||
return this.eouForm;
|
||||
case 'importexport':
|
||||
return this.importexportForm;
|
||||
case 'psl':
|
||||
return this.pslForm;
|
||||
case 'nps':
|
||||
return this.npsForm;
|
||||
default:
|
||||
return this.fb.group({});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +312,7 @@ export class ClientEntityKycStepComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
get enabledCategories(): CategoryMeta[] {
|
||||
return this.categories.filter(c => this.enabled[c.id]);
|
||||
return this.categories.filter((c) => this.enabled[c.id]);
|
||||
}
|
||||
|
||||
get enabledCount(): number {
|
||||
@@ -266,7 +342,7 @@ export class ClientEntityKycStepComponent implements OnInit, OnChanges {
|
||||
next: () => {
|
||||
this.saving[categoryId] = false;
|
||||
this.saved[categoryId] = true;
|
||||
setTimeout(() => this.saved[categoryId] = false, 3000);
|
||||
setTimeout(() => (this.saved[categoryId] = false), 3000);
|
||||
},
|
||||
error: () => {
|
||||
this.saving[categoryId] = false;
|
||||
@@ -306,6 +382,10 @@ export class ClientEntityKycStepComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
// Keep old getter for backward compatibility
|
||||
get entityKycData(): any { return this.classificationData; }
|
||||
get hasEntityKycData(): boolean { return this.hasClassificationData; }
|
||||
get entityKycData(): any {
|
||||
return this.classificationData;
|
||||
}
|
||||
get hasEntityKycData(): boolean {
|
||||
return this.hasClassificationData;
|
||||
}
|
||||
}
|
||||
|
||||
+112
-28
@@ -1,3 +1,11 @@
|
||||
<!--
|
||||
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/.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
-->
|
||||
@@ -10,7 +18,6 @@
|
||||
|
||||
<mat-dialog-content>
|
||||
<form [formGroup]="familyMemberForm">
|
||||
|
||||
<!-- ═══ VIEW MODE ═══ -->
|
||||
@if (isViewMode) {
|
||||
<!-- Linked client card -->
|
||||
@@ -18,20 +25,31 @@
|
||||
<div class="fm-linked-card">
|
||||
<div class="fm-linked-card__left">
|
||||
@if (linkedProfileImage) {
|
||||
<img [src]="linkedProfileImage" class="fm-linked-card__photo zoomable" alt="Profile"
|
||||
(click)="zoomImage(linkedProfileImage, 'Client Photograph')" />
|
||||
<img
|
||||
[src]="linkedProfileImage"
|
||||
class="fm-linked-card__photo zoomable"
|
||||
alt="Profile"
|
||||
(click)="zoomImage(linkedProfileImage, 'Client Photograph')"
|
||||
/>
|
||||
} @else {
|
||||
<mat-icon class="fm-linked-card__icon">person</mat-icon>
|
||||
}
|
||||
</div>
|
||||
<div class="fm-linked-card__info">
|
||||
<span class="fm-linked-card__name">{{ selectedClient.displayName }}</span>
|
||||
<span class="fm-linked-card__meta">Client ID: {{ selectedClient.id }}{{ selectedClient.accountNumber ? ' · A/C: ' + selectedClient.accountNumber : '' }}</span>
|
||||
<span class="fm-linked-card__meta"
|
||||
>Client ID: {{ selectedClient.id
|
||||
}}{{ selectedClient.accountNumber ? ' · A/C: ' + selectedClient.accountNumber : '' }}</span
|
||||
>
|
||||
</div>
|
||||
@if (linkedSignatureImage) {
|
||||
<div class="fm-linked-card__sig">
|
||||
<img [src]="linkedSignatureImage" alt="Signature" class="zoomable"
|
||||
(click)="zoomImage(linkedSignatureImage, 'Specimen Signature')" />
|
||||
<img
|
||||
[src]="linkedSignatureImage"
|
||||
alt="Signature"
|
||||
class="zoomable"
|
||||
(click)="zoomImage(linkedSignatureImage, 'Specimen Signature')"
|
||||
/>
|
||||
<span>Signature</span>
|
||||
</div>
|
||||
}
|
||||
@@ -57,7 +75,11 @@
|
||||
</div>
|
||||
<div class="fm-readonly-item">
|
||||
<span class="fm-readonly-label">Date of Birth</span>
|
||||
<span class="fm-readonly-value">{{ familyMemberForm.get('dateOfBirth')?.value ? (familyMemberForm.get('dateOfBirth')?.value | date:'dd MMM yyyy') : '—' }}</span>
|
||||
<span class="fm-readonly-value">{{
|
||||
familyMemberForm.get('dateOfBirth')?.value
|
||||
? (familyMemberForm.get('dateOfBirth')?.value | date: 'dd MMM yyyy')
|
||||
: '—'
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="fm-readonly-item">
|
||||
<span class="fm-readonly-label">Nationality</span>
|
||||
@@ -89,19 +111,25 @@
|
||||
<span class="fm-readonly-label">Status</span>
|
||||
<span class="fm-readonly-value">
|
||||
{{ familyMemberForm.get('isAlive')?.value !== false ? 'Alive' : 'Deceased' }}
|
||||
@if (familyMemberForm.get('isDependent')?.value) { · Dependent }
|
||||
@if (familyMemberForm.get('isDependent')?.value) {
|
||||
· Dependent
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
@if (isSpouse && familyMemberForm.get('marriageDate')?.value) {
|
||||
<div class="fm-readonly-item">
|
||||
<span class="fm-readonly-label">Marriage Date</span>
|
||||
<span class="fm-readonly-value">{{ familyMemberForm.get('marriageDate')?.value | date:'dd MMM yyyy' }}</span>
|
||||
<span class="fm-readonly-value">{{
|
||||
familyMemberForm.get('marriageDate')?.value | date: 'dd MMM yyyy'
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
@if (isSpouse && familyMemberForm.get('marriageAuthority')?.value) {
|
||||
<div class="fm-readonly-item">
|
||||
<span class="fm-readonly-label">Issuing Authority</span>
|
||||
<span class="fm-readonly-value">{{ getMarriageAuthorityLabel(familyMemberForm.get('marriageAuthority')?.value) }}</span>
|
||||
<span class="fm-readonly-value">{{
|
||||
getMarriageAuthorityLabel(familyMemberForm.get('marriageAuthority')?.value)
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -136,7 +164,9 @@
|
||||
</mat-form-field>
|
||||
@if (searching) {
|
||||
<div class="fm-search-results">
|
||||
<div class="fm-search-loading"><mat-progress-spinner diameter="18" mode="indeterminate"></mat-progress-spinner> Searching...</div>
|
||||
<div class="fm-search-loading">
|
||||
<mat-progress-spinner diameter="18" mode="indeterminate"></mat-progress-spinner> Searching...
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (!searching && searchResults.length > 0) {
|
||||
@@ -146,7 +176,18 @@
|
||||
<mat-icon class="fm-search-item__icon">person</mat-icon>
|
||||
<div class="fm-search-item__info">
|
||||
<span class="fm-search-item__name">{{ r.displayName }}</span>
|
||||
<span class="fm-search-item__meta">ID: {{ r.id }} · {{ r.officeName }}@if (r.dateOfBirth) { · DOB: {{ r.dateOfBirth | dateFormat }} }@if (r.fatherSpouseName) { · {{ r.fatherSpouseName }} }@if (r.mobileNo) { · {{ r.mobileNo }} }</span>
|
||||
<span class="fm-search-item__meta"
|
||||
>ID: {{ r.id }} · {{ r.officeName }}
|
||||
@if (r.dateOfBirth) {
|
||||
· DOB: {{ r.dateOfBirth | dateFormat }}
|
||||
}
|
||||
@if (r.fatherSpouseName) {
|
||||
· {{ r.fatherSpouseName }}
|
||||
}
|
||||
@if (r.mobileNo) {
|
||||
· {{ r.mobileNo }}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -162,24 +203,37 @@
|
||||
<div class="fm-linked-card">
|
||||
<div class="fm-linked-card__left">
|
||||
@if (linkedProfileImage) {
|
||||
<img [src]="linkedProfileImage" class="fm-linked-card__photo zoomable" alt="Profile"
|
||||
(click)="zoomImage(linkedProfileImage, 'Client Photograph')" />
|
||||
<img
|
||||
[src]="linkedProfileImage"
|
||||
class="fm-linked-card__photo zoomable"
|
||||
alt="Profile"
|
||||
(click)="zoomImage(linkedProfileImage, 'Client Photograph')"
|
||||
/>
|
||||
} @else {
|
||||
<mat-icon class="fm-linked-card__icon">person</mat-icon>
|
||||
}
|
||||
</div>
|
||||
<div class="fm-linked-card__info">
|
||||
<span class="fm-linked-card__name">{{ selectedClient.displayName }}</span>
|
||||
<span class="fm-linked-card__meta">Client ID: {{ selectedClient.id }}{{ selectedClient.accountNumber ? ' · A/C: ' + selectedClient.accountNumber : '' }}</span>
|
||||
<span class="fm-linked-card__meta"
|
||||
>Client ID: {{ selectedClient.id
|
||||
}}{{ selectedClient.accountNumber ? ' · A/C: ' + selectedClient.accountNumber : '' }}</span
|
||||
>
|
||||
</div>
|
||||
@if (linkedSignatureImage) {
|
||||
<div class="fm-linked-card__sig">
|
||||
<img [src]="linkedSignatureImage" alt="Signature" class="zoomable"
|
||||
(click)="zoomImage(linkedSignatureImage, 'Specimen Signature')" />
|
||||
<img
|
||||
[src]="linkedSignatureImage"
|
||||
alt="Signature"
|
||||
class="zoomable"
|
||||
(click)="zoomImage(linkedSignatureImage, 'Specimen Signature')"
|
||||
/>
|
||||
<span>Signature</span>
|
||||
</div>
|
||||
}
|
||||
<button mat-icon-button type="button" (click)="removeLinkedClient()" class="fm-linked-card__remove"><mat-icon>close</mat-icon></button>
|
||||
<button mat-icon-button type="button" (click)="removeLinkedClient()" class="fm-linked-card__remove">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (fetchingLinkedData) {
|
||||
@@ -198,7 +252,11 @@
|
||||
</div>
|
||||
<div class="fm-readonly-item">
|
||||
<span class="fm-readonly-label">Date of Birth</span>
|
||||
<span class="fm-readonly-value">{{ familyMemberForm.get('dateOfBirth')?.value ? (familyMemberForm.get('dateOfBirth')?.value | date:'dd MMM yyyy') : '—' }}</span>
|
||||
<span class="fm-readonly-value">{{
|
||||
familyMemberForm.get('dateOfBirth')?.value
|
||||
? (familyMemberForm.get('dateOfBirth')?.value | date: 'dd MMM yyyy')
|
||||
: '—'
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="fm-readonly-item">
|
||||
<span class="fm-readonly-label">Aadhaar</span>
|
||||
@@ -292,8 +350,13 @@
|
||||
<button type="button" mat-stroked-button class="fm-file-btn" (click)="certInput.click()">
|
||||
<mat-icon>upload_file</mat-icon> Upload Marriage Certificate
|
||||
</button>
|
||||
<input #certInput type="file" hidden accept=".pdf,.jpg,.jpeg,.png"
|
||||
(change)="onMarriageCertSelected($event)" />
|
||||
<input
|
||||
#certInput
|
||||
type="file"
|
||||
hidden
|
||||
accept=".pdf,.jpg,.jpeg,.png"
|
||||
(change)="onMarriageCertSelected($event)"
|
||||
/>
|
||||
<span class="fm-file-hint">PDF, JPG or PNG (max 5 MB)</span>
|
||||
} @else {
|
||||
<div class="fm-file-attached">
|
||||
@@ -313,15 +376,32 @@
|
||||
<div class="fm-grid">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Aadhaar Number</mat-label>
|
||||
<input matInput formControlName="aadhaarNumber" maxlength="12" placeholder="12-digit Aadhaar"
|
||||
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()" />
|
||||
<input
|
||||
matInput
|
||||
formControlName="aadhaarNumber"
|
||||
maxlength="12"
|
||||
placeholder="12-digit Aadhaar"
|
||||
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>PAN Number</mat-label>
|
||||
<input matInput formControlName="panNumber" maxlength="10" placeholder="ABCDE1234F" style="text-transform:uppercase"
|
||||
(input)="familyMemberForm.get('panNumber')?.setValue($any($event.target).value.toUpperCase(), {emitEvent:false})" />
|
||||
@if (familyMemberForm.get('panNumber')?.hasError('pattern') && familyMemberForm.get('panNumber')?.touched) {
|
||||
<input
|
||||
matInput
|
||||
formControlName="panNumber"
|
||||
maxlength="10"
|
||||
placeholder="ABCDE1234F"
|
||||
style="text-transform: uppercase"
|
||||
(input)="
|
||||
familyMemberForm
|
||||
.get('panNumber')
|
||||
?.setValue($any($event.target).value.toUpperCase(), { emitEvent: false })
|
||||
"
|
||||
/>
|
||||
@if (
|
||||
familyMemberForm.get('panNumber')?.hasError('pattern') && familyMemberForm.get('panNumber')?.touched
|
||||
) {
|
||||
<mat-error>Invalid PAN format</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
@@ -348,9 +428,13 @@
|
||||
<button mat-flat-button color="primary" mat-dialog-close type="button">Close</button>
|
||||
} @else {
|
||||
<button mat-stroked-button mat-dialog-close type="button">Cancel</button>
|
||||
<button mat-flat-button color="primary" type="button"
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
type="button"
|
||||
[disabled]="familyMemberForm.invalid"
|
||||
[mat-dialog-close]="{ member: familyMember }">
|
||||
[mat-dialog-close]="{ member: familyMember }"
|
||||
>
|
||||
{{ data.context === 'Edit' ? 'Update' : 'Add Member' }}
|
||||
</button>
|
||||
}
|
||||
|
||||
+131
-27
@@ -1,3 +1,11 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
.fm-dialog__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -9,7 +17,9 @@
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
|
||||
.fm-dialog__icon { color: #1074b9; }
|
||||
.fm-dialog__icon {
|
||||
color: #1074b9;
|
||||
}
|
||||
}
|
||||
|
||||
.fm-mode-toggle {
|
||||
@@ -32,7 +42,11 @@
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
|
||||
mat-icon { font-size: 18px; width: 18px; height: 18px; }
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #1074b9;
|
||||
@@ -40,11 +54,19 @@
|
||||
color: #1074b9;
|
||||
}
|
||||
|
||||
&:hover:not(.active) { border-color: #94a3b8; }
|
||||
&:hover:not(.active) {
|
||||
border-color: #94a3b8;
|
||||
}
|
||||
}
|
||||
|
||||
.fm-search-wrapper { position: relative; margin-bottom: 12px; }
|
||||
.fm-search-field { width: 100%; }
|
||||
.fm-search-wrapper {
|
||||
position: relative;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.fm-search-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fm-search-results {
|
||||
position: absolute;
|
||||
@@ -55,7 +77,7 @@
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
box-shadow: 0 8px 24px rgb(0 0 0 / 12%);
|
||||
max-height: 220px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@@ -92,13 +114,34 @@
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
&:hover { background: #f0f7ff; }
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&__icon { color: #1074b9; font-size: 20px; }
|
||||
&__info { display: flex; flex-direction: column; }
|
||||
&__name { font-size: 13px; font-weight: 600; color: #1e293b; }
|
||||
&__meta { font-size: 11px; color: #64748b; }
|
||||
&:hover {
|
||||
background: #f0f7ff;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: #1074b9;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
font-size: 11px;
|
||||
color: #64748b;
|
||||
}
|
||||
}
|
||||
|
||||
.fm-linked-card {
|
||||
@@ -111,8 +154,14 @@
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&__left { flex-shrink: 0; }
|
||||
&__icon { color: #1074b9; }
|
||||
&__left {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: #1074b9;
|
||||
}
|
||||
|
||||
&__photo {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
@@ -120,9 +169,25 @@
|
||||
object-fit: cover;
|
||||
border: 2px solid #e2e8f0;
|
||||
}
|
||||
&__info { flex: 1; display: flex; flex-direction: column; }
|
||||
&__name { font-size: 14px; font-weight: 600; color: #1e293b; }
|
||||
&__meta { font-size: 12px; color: #64748b; font-family: 'Roboto Mono', monospace; }
|
||||
|
||||
&__info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
}
|
||||
|
||||
&__sig {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -139,6 +204,7 @@
|
||||
background: #fff;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 9px;
|
||||
color: #94a3b8;
|
||||
@@ -146,7 +212,14 @@
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
}
|
||||
&__remove { color: #94a3b8 !important; &:hover { color: #ef4444 !important; } }
|
||||
|
||||
&__remove {
|
||||
color: #94a3b8 !important;
|
||||
|
||||
&:hover {
|
||||
color: #ef4444 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fm-fetching-hint {
|
||||
@@ -201,8 +274,16 @@
|
||||
padding: 10px 16px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.fm-readonly-label { display: block; margin-bottom: 4px; }
|
||||
.fm-readonly-value { font-size: 13px; color: #475569; line-height: 1.5; }
|
||||
.fm-readonly-label {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.fm-readonly-value {
|
||||
font-size: 13px;
|
||||
color: #475569;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.fm-kyc-badge {
|
||||
@@ -225,6 +306,7 @@
|
||||
background: #dcfce7;
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
&.not-verified {
|
||||
background: #fef3c7;
|
||||
color: #d97706;
|
||||
@@ -262,16 +344,22 @@
|
||||
|
||||
.zoomable {
|
||||
cursor: zoom-in;
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
transition:
|
||||
transform 0.15s ease,
|
||||
box-shadow 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.08);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
|
||||
box-shadow: 0 4px 16px rgb(0 0 0 / 18%);
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .image-zoom-panel {
|
||||
.mat-mdc-dialog-container { padding: 0 !important; border-radius: 10px !important; overflow: hidden; }
|
||||
.mat-mdc-dialog-container {
|
||||
padding: 0 !important;
|
||||
border-radius: 10px !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.fm-file-upload {
|
||||
@@ -290,7 +378,11 @@
|
||||
color: #1074b9;
|
||||
border-color: #cbd5e1;
|
||||
|
||||
mat-icon { font-size: 18px; width: 18px; height: 18px; }
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.fm-file-hint {
|
||||
@@ -308,7 +400,11 @@
|
||||
border-radius: 6px;
|
||||
width: 100%;
|
||||
|
||||
&__icon { color: #1074b9; font-size: 20px; }
|
||||
&__icon {
|
||||
color: #1074b9;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
&__name {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
@@ -318,13 +414,21 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__remove {
|
||||
color: #94a3b8 !important;
|
||||
width: 28px !important;
|
||||
height: 28px !important;
|
||||
line-height: 28px !important;
|
||||
|
||||
mat-icon { font-size: 16px; width: 16px; height: 16px; }
|
||||
&:hover { color: #ef4444 !important; }
|
||||
mat-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #ef4444 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+88
-33
@@ -87,7 +87,9 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
|
||||
}
|
||||
|
||||
/** Whether dialog is in View-only mode */
|
||||
get isViewMode(): boolean { return this.data.context === 'View'; }
|
||||
get isViewMode(): boolean {
|
||||
return this.data.context === 'View';
|
||||
}
|
||||
|
||||
/** Get relationship name from ID */
|
||||
get relationshipName(): string {
|
||||
@@ -109,7 +111,7 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
|
||||
|
||||
/** Get marriage authority display label */
|
||||
getMarriageAuthorityLabel(value: string): string {
|
||||
return this.marriageAuthorityOptions.find(a => a.value === value)?.label || value || '—';
|
||||
return this.marriageAuthorityOptions.find((a) => a.value === value)?.label || value || '—';
|
||||
}
|
||||
|
||||
/** Marriage certificate file */
|
||||
@@ -150,7 +152,10 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
|
||||
});
|
||||
if (m.linkedClientId) {
|
||||
this.entryMode = 'linked';
|
||||
this.selectedClient = { id: m.linkedClientId, displayName: m.linkedClientName || m.firstName || `Client #${m.linkedClientId}` };
|
||||
this.selectedClient = {
|
||||
id: m.linkedClientId,
|
||||
displayName: m.linkedClientName || m.firstName || `Client #${m.linkedClientId}`
|
||||
};
|
||||
// Re-fetch linked client details (profile, signature, KYC status)
|
||||
this.fetchLinkedClientData(m.linkedClientId.toString());
|
||||
}
|
||||
@@ -162,13 +167,22 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
|
||||
|
||||
createFamilyMemberForm() {
|
||||
this.familyMemberForm = this.formBuilder.group({
|
||||
firstName: ['', Validators.required],
|
||||
relationshipId: ['', Validators.required],
|
||||
firstName: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
relationshipId: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
dateOfBirth: [''],
|
||||
isDependent: [false],
|
||||
remarks: [''],
|
||||
aadhaarNumber: [''],
|
||||
panNumber: ['', Validators.pattern(/^[A-Z]{5}[0-9]{4}[A-Z]{1}$/)],
|
||||
panNumber: [
|
||||
'',
|
||||
Validators.pattern(/^[A-Z]{5}[0-9]{4}[A-Z]{1}$/)
|
||||
],
|
||||
nationality: ['INDIAN'],
|
||||
isAlive: [true],
|
||||
marriageDate: [''],
|
||||
@@ -208,7 +222,10 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
|
||||
this.searching = false;
|
||||
this.clientsService.enrichSearchResults(this.searchResults);
|
||||
},
|
||||
error: () => { this.searchResults = []; this.searching = false; }
|
||||
error: () => {
|
||||
this.searchResults = [];
|
||||
this.searching = false;
|
||||
}
|
||||
});
|
||||
}, 400);
|
||||
}
|
||||
@@ -242,7 +259,9 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
|
||||
this.setLinkedFieldsReadonly(true);
|
||||
this.fetchingLinkedData = false;
|
||||
},
|
||||
error: () => { this.fetchingLinkedData = false; }
|
||||
error: () => {
|
||||
this.fetchingLinkedData = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch KYC (PAN, Aadhaar, verification status)
|
||||
@@ -250,8 +269,12 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
|
||||
next: (kyc: any) => {
|
||||
if (kyc) {
|
||||
const patch: any = {};
|
||||
if (kyc.panNumber) { patch.panNumber = kyc.panNumber; }
|
||||
if (kyc.aadhaarNumberMasked) { patch.aadhaarNumber = kyc.aadhaarNumberMasked; }
|
||||
if (kyc.panNumber) {
|
||||
patch.panNumber = kyc.panNumber;
|
||||
}
|
||||
if (kyc.aadhaarNumberMasked) {
|
||||
patch.aadhaarNumber = kyc.aadhaarNumberMasked;
|
||||
}
|
||||
this.familyMemberForm.patchValue(patch);
|
||||
this.linkedKycStatus = {
|
||||
panVerified: kyc.panVerified || false,
|
||||
@@ -278,8 +301,8 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
|
||||
// Fetch signature image
|
||||
this.clientsService.getClientDocuments(clientId).subscribe({
|
||||
next: (docs: any[]) => {
|
||||
const sigDoc = docs?.find((d: any) =>
|
||||
d.name?.toLowerCase().includes('signature') || d.description?.toLowerCase().includes('signature')
|
||||
const sigDoc = docs?.find(
|
||||
(d: any) => d.name?.toLowerCase().includes('signature') || d.description?.toLowerCase().includes('signature')
|
||||
);
|
||||
if (sigDoc) {
|
||||
this.clientsService.getClientSignatureImage(clientId, sigDoc.id.toString()).subscribe({
|
||||
@@ -296,8 +319,13 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
|
||||
|
||||
/** Disable/enable fields that are auto-populated from linked client */
|
||||
private setLinkedFieldsReadonly(readonly: boolean): void {
|
||||
const fields = ['firstName', 'dateOfBirth', 'aadhaarNumber', 'panNumber'];
|
||||
fields.forEach(f => {
|
||||
const fields = [
|
||||
'firstName',
|
||||
'dateOfBirth',
|
||||
'aadhaarNumber',
|
||||
'panNumber'
|
||||
];
|
||||
fields.forEach((f) => {
|
||||
const ctrl = this.familyMemberForm.get(f);
|
||||
if (ctrl) {
|
||||
readonly ? ctrl.disable() : ctrl.enable();
|
||||
@@ -313,7 +341,11 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
|
||||
this.linkedKycStatus = null;
|
||||
this.setLinkedFieldsReadonly(false);
|
||||
this.familyMemberForm.patchValue({
|
||||
firstName: '', linkedClientId: '', dateOfBirth: '', aadhaarNumber: '', panNumber: ''
|
||||
firstName: '',
|
||||
linkedClientId: '',
|
||||
dateOfBirth: '',
|
||||
aadhaarNumber: '',
|
||||
panNumber: ''
|
||||
});
|
||||
}
|
||||
|
||||
@@ -385,26 +417,49 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
.img-zoom-dialog { display: flex; flex-direction: column; }
|
||||
.img-zoom-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 10px 16px; background: #f8fafc; border-bottom: 1px solid #e2e8f0;
|
||||
font-size: 14px; font-weight: 600; color: #1e293b;
|
||||
}
|
||||
.img-zoom-body {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
padding: 20px; background: #fff; min-height: 200px;
|
||||
}
|
||||
.img-zoom-body img {
|
||||
max-width: 100%; max-height: 70vh; object-fit: contain;
|
||||
border-radius: 6px; box-shadow: 0 2px 12px rgba(0,0,0,0.1);
|
||||
}
|
||||
`],
|
||||
imports: [MatIcon, STANDALONE_SHARED_IMPORTS]
|
||||
styles: [
|
||||
`
|
||||
.img-zoom-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.img-zoom-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 16px;
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
.img-zoom-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
min-height: 200px;
|
||||
}
|
||||
.img-zoom-body img {
|
||||
max-width: 100%;
|
||||
max-height: 70vh;
|
||||
object-fit: contain;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
`
|
||||
],
|
||||
imports: [
|
||||
MatIcon,
|
||||
STANDALONE_SHARED_IMPORTS
|
||||
]
|
||||
})
|
||||
export class ImageZoomDialogComponent {
|
||||
dialogRef = inject<MatDialogRef<ImageZoomDialogComponent>>(MatDialogRef);
|
||||
data = inject(MAT_DIALOG_DATA);
|
||||
close() { this.dialogRef.close(); }
|
||||
close() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
|
||||
+36
-7
@@ -1,3 +1,11 @@
|
||||
<!--
|
||||
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/.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
-->
|
||||
@@ -75,9 +83,13 @@
|
||||
{{ member.dateOfBirth ? (member.dateOfBirth | dateFormat) : '—' }}
|
||||
</span>
|
||||
<span class="fm-table__col fm-table__col--id fm-mono">
|
||||
@if (member.panNumber) { {{ member.panNumber }} }
|
||||
@else if (member.aadhaarNumber) { {{ member.aadhaarNumber }} }
|
||||
@else { — }
|
||||
@if (member.panNumber) {
|
||||
{{ member.panNumber }}
|
||||
} @else if (member.aadhaarNumber) {
|
||||
{{ member.aadhaarNumber }}
|
||||
} @else {
|
||||
—
|
||||
}
|
||||
</span>
|
||||
<span class="fm-table__col fm-table__col--status">
|
||||
@if (member.isAlive !== false) {
|
||||
@@ -87,14 +99,32 @@
|
||||
}
|
||||
</span>
|
||||
<span class="fm-table__col fm-table__col--actions">
|
||||
<button mat-icon-button type="button" (click)="viewFamilyMember(member)" class="fm-action-btn" matTooltip="View">
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
(click)="viewFamilyMember(member)"
|
||||
class="fm-action-btn"
|
||||
matTooltip="View"
|
||||
>
|
||||
<mat-icon>visibility</mat-icon>
|
||||
</button>
|
||||
@if (!readOnly) {
|
||||
<button mat-icon-button type="button" (click)="editFamilyMember(member, i)" class="fm-action-btn" matTooltip="Edit">
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
(click)="editFamilyMember(member, i)"
|
||||
class="fm-action-btn"
|
||||
matTooltip="Edit"
|
||||
>
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button type="button" (click)="deleteFamilyMember(member.firstName, i)" class="fm-action-btn fm-action-btn--delete" matTooltip="Delete">
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
(click)="deleteFamilyMember(member.firstName, i)"
|
||||
class="fm-action-btn fm-action-btn--delete"
|
||||
matTooltip="Delete"
|
||||
>
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
}
|
||||
@@ -103,5 +133,4 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
+402
-84
@@ -1,11 +1,29 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
.zoomable {
|
||||
cursor: zoom-in;
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
&:hover { transform: scale(1.08); box-shadow: 0 4px 16px rgba(0,0,0,0.18); }
|
||||
transition:
|
||||
transform 0.15s ease,
|
||||
box-shadow 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.08);
|
||||
box-shadow: 0 4px 16px rgb(0 0 0 / 18%);
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .image-zoom-panel {
|
||||
.mat-mdc-dialog-container { padding: 0 !important; border-radius: 10px !important; overflow: hidden; }
|
||||
.mat-mdc-dialog-container {
|
||||
padding: 0 !important;
|
||||
border-radius: 10px !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.search-no-results-hint {
|
||||
@@ -42,7 +60,7 @@
|
||||
|
||||
&__sub {
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
color: rgb(255 255 255 / 50%);
|
||||
margin: 2px 0 0;
|
||||
}
|
||||
}
|
||||
@@ -63,7 +81,10 @@
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.fm-step__icon { color: #1074b9; font-size: 14px; }
|
||||
.fm-step__icon {
|
||||
color: #1074b9;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.fm-step__count {
|
||||
background: #1074b9;
|
||||
@@ -170,8 +191,13 @@
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
transition: background 0.1s;
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
&:hover { background: #f8fafc; }
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
}
|
||||
|
||||
.fm-table__col {
|
||||
@@ -186,11 +212,31 @@
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
&--rel { flex: 0 0 130px; }
|
||||
&--dob { flex: 0 0 120px; color: #64748b; font-size: 12px; }
|
||||
&--id { flex: 0 0 140px; }
|
||||
&--status { flex: 0 0 60px; text-align: center; }
|
||||
&--actions { flex: 0 0 80px; display: flex; gap: 2px; }
|
||||
|
||||
&--rel {
|
||||
flex: 0 0 130px;
|
||||
}
|
||||
|
||||
&--dob {
|
||||
flex: 0 0 120px;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&--id {
|
||||
flex: 0 0 140px;
|
||||
}
|
||||
|
||||
&--status {
|
||||
flex: 0 0 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&--actions {
|
||||
flex: 0 0 80px;
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.fm-table__avatar {
|
||||
@@ -232,8 +278,15 @@
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
|
||||
&--yes { background: #ecfdf5; color: #059669; }
|
||||
&--no { background: #fef2f2; color: #dc2626; }
|
||||
&--yes {
|
||||
background: #ecfdf5;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
&--no {
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
}
|
||||
}
|
||||
|
||||
.fm-action-btn {
|
||||
@@ -248,11 +301,16 @@
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
&:hover mat-icon { color: #1074b9; }
|
||||
&--delete:hover mat-icon { color: #ef4444; }
|
||||
&:hover mat-icon {
|
||||
color: #1074b9;
|
||||
}
|
||||
|
||||
&--delete:hover mat-icon {
|
||||
color: #ef4444;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@media (width <= 768px) {
|
||||
.fm-step__header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
@@ -260,7 +318,10 @@
|
||||
}
|
||||
|
||||
.fm-table__col {
|
||||
&--type, &--id { display: none; }
|
||||
&--type,
|
||||
&--id {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,11 +345,27 @@
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.nom-header__icon { font-size: 16px; color: #1565c0; }
|
||||
.nom-header__title { font-size: 16px; font-weight: 600; color: #1e293b; display: block; }
|
||||
.nom-header__sub { font-size: 11px; color: #94a3b8; display: block; }
|
||||
.nom-header__icon {
|
||||
font-size: 16px;
|
||||
color: #1565c0;
|
||||
}
|
||||
|
||||
.nom-add-btn { font-size: 13px; }
|
||||
.nom-header__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nom-header__sub {
|
||||
font-size: 11px;
|
||||
color: #94a3b8;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nom-add-btn {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* Share bar */
|
||||
.nom-share-bar {
|
||||
@@ -302,7 +379,11 @@
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.nom-share-bar__label { font-size: 12px; color: #64748b; white-space: nowrap; }
|
||||
.nom-share-bar__label {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nom-share-bar__track {
|
||||
flex: 1;
|
||||
@@ -317,8 +398,14 @@
|
||||
background: #1565c0;
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s;
|
||||
&.complete { background: #059669; }
|
||||
&.over { background: #dc2626; }
|
||||
|
||||
&.complete {
|
||||
background: #059669;
|
||||
}
|
||||
|
||||
&.over {
|
||||
background: #dc2626;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-share-bar__value {
|
||||
@@ -327,7 +414,10 @@
|
||||
color: #1565c0;
|
||||
min-width: 36px;
|
||||
text-align: right;
|
||||
&.over { color: #dc2626; }
|
||||
|
||||
&.over {
|
||||
color: #dc2626;
|
||||
}
|
||||
}
|
||||
|
||||
/* Nominee card */
|
||||
@@ -346,23 +436,62 @@
|
||||
}
|
||||
|
||||
.nom-card__avatar {
|
||||
width: 36px; height: 36px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: #dbeafe;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #1565c0;
|
||||
mat-icon { font-size: 20px; }
|
||||
|
||||
mat-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-card__photo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.nom-card__info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.nom-card__name {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: #1e293b;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nom-card__meta {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.nom-card__photo { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
|
||||
.nom-card__info { flex: 1; }
|
||||
.nom-card__name { font-weight: 600; font-size: 14px; color: #1e293b; display: block; }
|
||||
.nom-card__meta { font-size: 12px; color: #64748b; display: block; margin-top: 2px; }
|
||||
.nom-card__sig {
|
||||
flex-shrink: 0;
|
||||
img { height: 24px; max-width: 60px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 4px; padding: 2px; }
|
||||
|
||||
img {
|
||||
height: 24px;
|
||||
max-width: 60px;
|
||||
object-fit: contain;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 4px;
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-card__actions {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
.nom-card__actions { display: flex; gap: 2px; }
|
||||
|
||||
.nom-card__edit {
|
||||
padding: 12px 16px;
|
||||
@@ -391,8 +520,18 @@
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.nom-card__share-value { font-size: 18px; font-weight: 700; color: #1565c0; display: block; }
|
||||
.nom-card__share-label { font-size: 10px; color: #94a3b8; text-transform: uppercase; }
|
||||
.nom-card__share-value {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1565c0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nom-card__share-label {
|
||||
font-size: 10px;
|
||||
color: #94a3b8;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.nom-card__guardian {
|
||||
display: flex;
|
||||
@@ -403,7 +542,13 @@
|
||||
border-top: 1px solid #e0f2fe;
|
||||
font-size: 12px;
|
||||
color: #0c4a6e;
|
||||
mat-icon { font-size: 16px; width: 16px; height: 16px; color: #0ea5e9; }
|
||||
|
||||
mat-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: #0ea5e9;
|
||||
}
|
||||
}
|
||||
|
||||
/* Empty */
|
||||
@@ -420,8 +565,16 @@
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.nom-empty__icon { font-size: 36px; width: 36px; height: 36px; color: #cbd5e1; }
|
||||
.nom-empty__sub { font-size: 12px; }
|
||||
.nom-empty__icon {
|
||||
font-size: 36px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
.nom-empty__sub {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Nominee form */
|
||||
.nom-form {
|
||||
@@ -443,33 +596,47 @@
|
||||
}
|
||||
|
||||
/* Search */
|
||||
.nom-search-wrapper { position: relative; }
|
||||
.nom-search-field { width: 100%; }
|
||||
.nom-search-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nom-search-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nom-search-prefix {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding-right: 8px;
|
||||
mat-icon { font-size: 20px; color: #94a3b8; }
|
||||
|
||||
mat-icon {
|
||||
font-size: 20px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-search-results {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0; right: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 4px 12px rgb(0 0 0 / 10%);
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
margin-top: -12px;
|
||||
}
|
||||
|
||||
.nom-search-loading {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
font-size: 13px; color: #64748b;
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.nom-search-item {
|
||||
@@ -478,14 +645,43 @@
|
||||
gap: 10px;
|
||||
padding: 10px 16px;
|
||||
cursor: pointer;
|
||||
&:hover { background: #f1f5f9; }
|
||||
|
||||
&:hover {
|
||||
background: #f1f5f9;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-search-item__icon { color: #94a3b8; font-size: 20px; }
|
||||
.nom-search-item__info { flex: 1; }
|
||||
.nom-search-item__name { font-weight: 500; font-size: 13px; color: #1e293b; display: block; }
|
||||
.nom-search-item__meta { font-size: 11px; color: #94a3b8; display: block; }
|
||||
.nom-search-item__status { font-size: 11px; font-weight: 600; color: #94a3b8; &.active { color: #22c55e; } }
|
||||
.nom-search-item__icon {
|
||||
color: #94a3b8;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.nom-search-item__info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.nom-search-item__name {
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
color: #1e293b;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nom-search-item__meta {
|
||||
font-size: 11px;
|
||||
color: #94a3b8;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nom-search-item__status {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #94a3b8;
|
||||
|
||||
&.active {
|
||||
color: #22c55e;
|
||||
}
|
||||
}
|
||||
|
||||
/* Selected nominee */
|
||||
.nom-selected {
|
||||
@@ -505,30 +701,92 @@
|
||||
}
|
||||
|
||||
.nom-selected__avatar {
|
||||
width: 40px; height: 40px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: #dbeafe;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #1565c0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nom-selected__photo { width: 100%; height: 100%; object-fit: cover; }
|
||||
.nom-selected__info { flex: 1; }
|
||||
.nom-selected__name { font-weight: 600; font-size: 14px; color: #1e293b; display: block; }
|
||||
.nom-selected__meta { font-size: 12px; color: #64748b; display: block; }
|
||||
.nom-selected__photo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.nom-selected__info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.nom-selected__name {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: #1e293b;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nom-selected__meta {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nom-selected__sig {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 2px; flex-shrink: 0;
|
||||
img { height: 28px; max-width: 70px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 4px; background: #fff; padding: 2px 4px; }
|
||||
span { font-size: 9px; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.04em; }
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
flex-shrink: 0;
|
||||
|
||||
img {
|
||||
height: 28px;
|
||||
max-width: 70px;
|
||||
object-fit: contain;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 9px;
|
||||
color: #94a3b8;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-selected__remove {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.nom-selected__loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
.nom-selected__remove { color: #94a3b8; }
|
||||
.nom-selected__loading { display: flex; align-items: center; gap: 8px; padding: 12px 16px; font-size: 12px; color: #64748b; }
|
||||
|
||||
.nom-kyc-badge {
|
||||
display: inline-flex; align-items: center; margin-left: 4px;
|
||||
mat-icon { font-size: 14px; width: 14px; height: 14px; }
|
||||
&.verified { color: #16a34a; }
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-left: 4px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 14px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
&.verified {
|
||||
color: #16a34a;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-selected__details {
|
||||
@@ -539,10 +797,29 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.nom-detail { display: flex; flex-direction: column; gap: 2px; }
|
||||
.nom-detail__label { font-size: 10px; text-transform: uppercase; color: #94a3b8; letter-spacing: 0.5px; }
|
||||
.nom-detail__value { font-size: 13px; font-weight: 500; color: #1e293b; }
|
||||
.nom-detail__value.mono { font-family: 'Roboto Mono', monospace; font-size: 12px; }
|
||||
.nom-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.nom-detail__label {
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
color: #94a3b8;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.nom-detail__value {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.nom-detail__value.mono {
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Form fields */
|
||||
.nom-form-fields {
|
||||
@@ -553,8 +830,13 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nom-form-field { width: 100%; }
|
||||
.nom-minor-toggle { padding-top: 8px; }
|
||||
.nom-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nom-minor-toggle {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
/* Guardian */
|
||||
.nom-guardian-section {
|
||||
@@ -570,7 +852,11 @@
|
||||
font-weight: 600;
|
||||
color: #0c4a6e;
|
||||
margin-bottom: 10px;
|
||||
mat-icon { font-size: 18px; color: #0ea5e9; }
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
color: #0ea5e9;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-guardian-card {
|
||||
@@ -581,13 +867,32 @@
|
||||
background: #f0f9ff;
|
||||
border: 1px solid #bae6fd;
|
||||
border-radius: 8px;
|
||||
mat-icon { color: #0ea5e9; }
|
||||
|
||||
mat-icon {
|
||||
color: #0ea5e9;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-guardian-card__info { flex: 1; }
|
||||
.nom-guardian-card__name { font-weight: 600; font-size: 13px; color: #1e293b; display: block; }
|
||||
.nom-guardian-card__meta { font-size: 11px; color: #64748b; display: block; }
|
||||
.nom-guardian-rel { width: 160px; }
|
||||
.nom-guardian-card__info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.nom-guardian-card__name {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: #1e293b;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nom-guardian-card__meta {
|
||||
font-size: 11px;
|
||||
color: #64748b;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nom-guardian-rel {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
/* Actions */
|
||||
.nom-form-actions {
|
||||
@@ -596,11 +901,24 @@
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
button { display: flex; align-items: center; gap: 6px; }
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.nom-form-fields { grid-template-columns: 1fr; }
|
||||
.nom-guardian-card { flex-wrap: wrap; }
|
||||
.nom-guardian-rel { width: 100%; }
|
||||
@media (width <= 768px) {
|
||||
.nom-form-fields {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.nom-guardian-card {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.nom-guardian-rel {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
+74
-25
@@ -1,5 +1,9 @@
|
||||
/**
|
||||
* 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, Input, OnInit, inject } from '@angular/core';
|
||||
@@ -88,9 +92,20 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
|
||||
/** Relationship options */
|
||||
relationshipOptions = [
|
||||
'Father', 'Mother', 'Spouse', 'Son', 'Daughter',
|
||||
'Brother', 'Sister', 'Uncle', 'Aunt', 'Grandfather',
|
||||
'Grandmother', 'Friend', 'Business Partner', 'Other'
|
||||
'Father',
|
||||
'Mother',
|
||||
'Spouse',
|
||||
'Son',
|
||||
'Daughter',
|
||||
'Brother',
|
||||
'Sister',
|
||||
'Uncle',
|
||||
'Aunt',
|
||||
'Grandfather',
|
||||
'Grandmother',
|
||||
'Friend',
|
||||
'Business Partner',
|
||||
'Other'
|
||||
];
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -109,14 +124,18 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
this.loaded = true;
|
||||
this.clientFamilyMembers = Array.isArray(data) ? data : [];
|
||||
},
|
||||
error: () => { this.loaded = true; }
|
||||
error: () => {
|
||||
this.loaded = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addFamilyMember() {
|
||||
const ref = this.dialog.open(ClientFamilyMemberDialogComponent, {
|
||||
data: { context: 'Add', options: this.clientTemplate.familyMemberOptions },
|
||||
width: '640px', maxWidth: '95vw', panelClass: 'fm-dialog-panel'
|
||||
width: '640px',
|
||||
maxWidth: '95vw',
|
||||
panelClass: 'fm-dialog-panel'
|
||||
});
|
||||
ref.afterClosed().subscribe((response: any) => {
|
||||
if (response?.member) {
|
||||
@@ -128,14 +147,18 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
viewFamilyMember(member: any) {
|
||||
this.dialog.open(ClientFamilyMemberDialogComponent, {
|
||||
data: { context: 'View', member, options: this.clientTemplate.familyMemberOptions },
|
||||
width: '640px', maxWidth: '95vw', panelClass: 'fm-dialog-panel'
|
||||
width: '640px',
|
||||
maxWidth: '95vw',
|
||||
panelClass: 'fm-dialog-panel'
|
||||
});
|
||||
}
|
||||
|
||||
editFamilyMember(member: any, index: number) {
|
||||
const ref = this.dialog.open(ClientFamilyMemberDialogComponent, {
|
||||
data: { context: 'Edit', member, options: this.clientTemplate.familyMemberOptions },
|
||||
width: '640px', maxWidth: '95vw', panelClass: 'fm-dialog-panel'
|
||||
width: '640px',
|
||||
maxWidth: '95vw',
|
||||
panelClass: 'fm-dialog-panel'
|
||||
});
|
||||
ref.afterClosed().subscribe((response: any) => {
|
||||
if (response?.member) {
|
||||
@@ -175,8 +198,7 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
return this.clientFamilyMembers.some((m: any) => {
|
||||
const rel = m.relationship || m.relationshipId;
|
||||
const relName = typeof rel === 'string' ? rel : '';
|
||||
return relName.toLowerCase() === 'spouse' ||
|
||||
m.firstName?.toLowerCase().includes('spouse');
|
||||
return relName.toLowerCase() === 'spouse' || m.firstName?.toLowerCase().includes('spouse');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -191,14 +213,19 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
const allMembers = Array.isArray(data) ? data : [];
|
||||
this.nominees = allMembers.filter((m: any) => m.nominationType && m.nominationType !== '');
|
||||
},
|
||||
error: () => { this.nomineesLoaded = true; }
|
||||
error: () => {
|
||||
this.nomineesLoaded = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onNomineeSearch(text: string): void {
|
||||
this.nomineeSearchText = text;
|
||||
clearTimeout(this.nomineeSearchTimer);
|
||||
if (!text || text.length < 2) { this.nomineeSearchResults = []; return; }
|
||||
if (!text || text.length < 2) {
|
||||
this.nomineeSearchResults = [];
|
||||
return;
|
||||
}
|
||||
this.nomineeSearching = true;
|
||||
this.nomineeSearchTimer = setTimeout(() => {
|
||||
this.clientsService.searchByText(text, 0, 10).subscribe({
|
||||
@@ -207,7 +234,10 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
this.nomineeSearching = false;
|
||||
this.clientsService.enrichSearchResults(this.nomineeSearchResults);
|
||||
},
|
||||
error: () => { this.nomineeSearchResults = []; this.nomineeSearching = false; }
|
||||
error: () => {
|
||||
this.nomineeSearchResults = [];
|
||||
this.nomineeSearching = false;
|
||||
}
|
||||
});
|
||||
}, 400);
|
||||
}
|
||||
@@ -232,7 +262,9 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
// Fetch client data for DOB
|
||||
this.clientsService.getClientData(clientId.toString()).subscribe({
|
||||
next: (data: any) => {
|
||||
if (data?.dateOfBirth) { details.dateOfBirth = data.dateOfBirth; }
|
||||
if (data?.dateOfBirth) {
|
||||
details.dateOfBirth = data.dateOfBirth;
|
||||
}
|
||||
done();
|
||||
},
|
||||
error: () => done()
|
||||
@@ -253,7 +285,9 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
// Fetch profile image
|
||||
this.clientsService.getClientProfileImage(clientId.toString()).subscribe({
|
||||
next: (img: any) => {
|
||||
if (img) { details.profileImageUrl = this.sanitizer.bypassSecurityTrustResourceUrl(img); }
|
||||
if (img) {
|
||||
details.profileImageUrl = this.sanitizer.bypassSecurityTrustResourceUrl(img);
|
||||
}
|
||||
done();
|
||||
},
|
||||
error: () => done()
|
||||
@@ -262,8 +296,8 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
// Fetch signature image
|
||||
this.clientsService.getClientDocuments(clientId.toString()).subscribe({
|
||||
next: (docs: any[]) => {
|
||||
const sigDoc = docs?.find((d: any) =>
|
||||
d.name?.toLowerCase().includes('signature') || d.description?.toLowerCase().includes('signature')
|
||||
const sigDoc = docs?.find(
|
||||
(d: any) => d.name?.toLowerCase().includes('signature') || d.description?.toLowerCase().includes('signature')
|
||||
);
|
||||
if (sigDoc) {
|
||||
this.clientsService.getClientSignatureImage(clientId.toString(), sigDoc.id.toString()).subscribe({
|
||||
@@ -273,7 +307,9 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
},
|
||||
error: () => done()
|
||||
});
|
||||
} else { done(); }
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
},
|
||||
error: () => done()
|
||||
});
|
||||
@@ -282,7 +318,10 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
onGuardianSearch(text: string): void {
|
||||
this.guardianSearchText = text;
|
||||
clearTimeout(this.guardianSearchTimer);
|
||||
if (!text || text.length < 2) { this.guardianSearchResults = []; return; }
|
||||
if (!text || text.length < 2) {
|
||||
this.guardianSearchResults = [];
|
||||
return;
|
||||
}
|
||||
this.guardianSearching = true;
|
||||
this.guardianSearchTimer = setTimeout(() => {
|
||||
this.clientsService.searchByText(text, 0, 10).subscribe({
|
||||
@@ -291,7 +330,10 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
this.guardianSearching = false;
|
||||
this.clientsService.enrichSearchResults(this.guardianSearchResults);
|
||||
},
|
||||
error: () => { this.guardianSearchResults = []; this.guardianSearching = false; }
|
||||
error: () => {
|
||||
this.guardianSearchResults = [];
|
||||
this.guardianSearching = false;
|
||||
}
|
||||
});
|
||||
}, 400);
|
||||
}
|
||||
@@ -315,7 +357,8 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
}
|
||||
|
||||
saveNominee(): void {
|
||||
if (!this.clientId || !this.selectedNomineeClient || !this.nomineeRelationshipId || !this.nomineePercentageShare) return;
|
||||
if (!this.clientId || !this.selectedNomineeClient || !this.nomineeRelationshipId || !this.nomineePercentageShare)
|
||||
return;
|
||||
if (this.totalSharePercentage > 100) return;
|
||||
|
||||
this.nomineeSaving = true;
|
||||
@@ -360,7 +403,9 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
});
|
||||
this.resetNomineeForm();
|
||||
},
|
||||
error: () => { this.nomineeSaving = false; }
|
||||
error: () => {
|
||||
this.nomineeSaving = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -383,13 +428,13 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
|
||||
/** Max share allowed for the nominee being edited (excludes its own share) */
|
||||
maxShareForEdit(index: number): number {
|
||||
const otherTotal = this.nominees.reduce((sum, n, i) => i === index ? sum : sum + (n.percentageShare || 0), 0);
|
||||
const otherTotal = this.nominees.reduce((sum, n, i) => (i === index ? sum : sum + (n.percentageShare || 0)), 0);
|
||||
return 100 - otherTotal;
|
||||
}
|
||||
|
||||
saveEditNominee(nominee: any, index: number): void {
|
||||
if (!this.editNomineeRelId || !this.editNomineeShare) return;
|
||||
const otherTotal = this.nominees.reduce((sum, n, i) => i === index ? sum : sum + (n.percentageShare || 0), 0);
|
||||
const otherTotal = this.nominees.reduce((sum, n, i) => (i === index ? sum : sum + (n.percentageShare || 0)), 0);
|
||||
if (otherTotal + this.editNomineeShare > 100) return;
|
||||
|
||||
const relOption = this.clientTemplate?.familyMemberOptions?.relationshipIdOptions?.find(
|
||||
@@ -413,8 +458,12 @@ export class ClientFamilyMembersStepComponent implements OnInit {
|
||||
dateFormat: 'dd MMMM yyyy',
|
||||
locale: 'en'
|
||||
};
|
||||
if (nominee.guardianName) { payload.guardianName = nominee.guardianName; }
|
||||
if (nominee.guardianRelationship) { payload.guardianRelationship = nominee.guardianRelationship; }
|
||||
if (nominee.guardianName) {
|
||||
payload.guardianName = nominee.guardianName;
|
||||
}
|
||||
if (nominee.guardianRelationship) {
|
||||
payload.guardianRelationship = nominee.guardianRelationship;
|
||||
}
|
||||
this.clientsService.editFamilyMember(this.clientId, nominee.id, payload).subscribe({ error: () => {} });
|
||||
}
|
||||
|
||||
|
||||
+348
-162
@@ -42,7 +42,9 @@
|
||||
<mat-select-trigger>
|
||||
@if (createClientForm.get('legalFormId')?.value) {
|
||||
<span class="legal-form-option">
|
||||
<mat-icon class="legal-form-option-icon">{{ createClientForm.get('legalFormId')?.value === 1 ? 'person' : 'business' }}</mat-icon>
|
||||
<mat-icon class="legal-form-option-icon">{{
|
||||
createClientForm.get('legalFormId')?.value === 1 ? 'person' : 'business'
|
||||
}}</mat-icon>
|
||||
{{ getSelectedLegalFormLabel() }}
|
||||
</span>
|
||||
}
|
||||
@@ -67,12 +69,12 @@
|
||||
@if (getSelectedNationality().flagType === 'india') {
|
||||
<span class="nat-flag">
|
||||
<svg viewBox="0 0 36 24" class="nat-flag-svg">
|
||||
<rect width="36" height="8" fill="#FF9933"/>
|
||||
<rect y="8" width="36" height="8" fill="#FFFFFF"/>
|
||||
<rect y="16" width="36" height="8" fill="#138808"/>
|
||||
<circle cx="18" cy="12" r="3" fill="#000080"/>
|
||||
<circle cx="18" cy="12" r="2.5" fill="#FFFFFF"/>
|
||||
<circle cx="18" cy="12" r="0.7" fill="#000080"/>
|
||||
<rect width="36" height="8" fill="#FF9933" />
|
||||
<rect y="8" width="36" height="8" fill="#FFFFFF" />
|
||||
<rect y="16" width="36" height="8" fill="#138808" />
|
||||
<circle cx="18" cy="12" r="3" fill="#000080" />
|
||||
<circle cx="18" cy="12" r="2.5" fill="#FFFFFF" />
|
||||
<circle cx="18" cy="12" r="0.7" fill="#000080" />
|
||||
</svg>
|
||||
</span>
|
||||
} @else {
|
||||
@@ -88,12 +90,12 @@
|
||||
@if (nat.flagType === 'india') {
|
||||
<span class="nat-flag">
|
||||
<svg viewBox="0 0 36 24" class="nat-flag-svg">
|
||||
<rect width="36" height="8" fill="#FF9933"/>
|
||||
<rect y="8" width="36" height="8" fill="#FFFFFF"/>
|
||||
<rect y="16" width="36" height="8" fill="#138808"/>
|
||||
<circle cx="18" cy="12" r="3" fill="#000080"/>
|
||||
<circle cx="18" cy="12" r="2.5" fill="#FFFFFF"/>
|
||||
<circle cx="18" cy="12" r="0.7" fill="#000080"/>
|
||||
<rect width="36" height="8" fill="#FF9933" />
|
||||
<rect y="8" width="36" height="8" fill="#FFFFFF" />
|
||||
<rect y="16" width="36" height="8" fill="#138808" />
|
||||
<circle cx="18" cy="12" r="3" fill="#000080" />
|
||||
<circle cx="18" cy="12" r="2.5" fill="#FFFFFF" />
|
||||
<circle cx="18" cy="12" r="0.7" fill="#000080" />
|
||||
</svg>
|
||||
</span>
|
||||
} @else {
|
||||
@@ -129,7 +131,10 @@
|
||||
@if (createClientForm.get('fullname')) {
|
||||
<mat-form-field appearance="outline" class="form-field full-width">
|
||||
<mat-label>
|
||||
{{ 'labels.inputs.' + getDateLabel(createClientForm.get('legalFormId')?.value, ['Name', 'Entity Name']) | translate }}
|
||||
{{
|
||||
'labels.inputs.' + getDateLabel(createClientForm.get('legalFormId')?.value, ['Name', 'Entity Name'])
|
||||
| translate
|
||||
}}
|
||||
</mat-label>
|
||||
<input matInput required formControlName="fullname" />
|
||||
@if (createClientForm.controls.fullname.hasError('required')) {
|
||||
@@ -150,30 +155,39 @@
|
||||
</div>
|
||||
<div class="verify-inline">
|
||||
@if (!sanctionsVerified && !sanctionsBlocked) {
|
||||
<button mat-stroked-button color="primary" type="button" class="verify-name-btn"
|
||||
<button
|
||||
mat-stroked-button
|
||||
color="primary"
|
||||
type="button"
|
||||
class="verify-name-btn"
|
||||
[disabled]="readOnly"
|
||||
(click)="verifySanctions()">
|
||||
(click)="verifySanctions()"
|
||||
>
|
||||
<fa-icon icon="shield-alt" class="verify-name-icon"></fa-icon>
|
||||
Verify Entity
|
||||
</button>
|
||||
}
|
||||
@if (sanctionsVerified) {
|
||||
<span class="sanctions-citation">RBI KYC Master Direction (2016) — Section 51 & PMLA 2002</span>
|
||||
<span class="verify-status verified">
|
||||
<fa-icon icon="check-circle"></fa-icon> Verified
|
||||
</span>
|
||||
<mat-icon class="sanctions-info-icon"
|
||||
<span class="verify-status verified"> <fa-icon icon="check-circle"></fa-icon> Verified </span>
|
||||
<mat-icon
|
||||
class="sanctions-info-icon"
|
||||
matTooltip="All customer names must be verified against the UN Security Council Consolidated List before account opening and periodically thereafter. Positive matches must be escalated to the Principal Officer immediately."
|
||||
matTooltipClass="sanctions-tooltip">info</mat-icon>
|
||||
matTooltipClass="sanctions-tooltip"
|
||||
>info</mat-icon
|
||||
>
|
||||
}
|
||||
@if (sanctionsBlocked) {
|
||||
<span class="sanctions-citation">RBI KYC Master Direction (2016) — Section 51 & PMLA 2002</span>
|
||||
<span class="verify-status blocked">
|
||||
<fa-icon icon="exclamation-triangle"></fa-icon> Sanctions Match
|
||||
</span>
|
||||
<mat-icon class="sanctions-info-icon blocked-info"
|
||||
<mat-icon
|
||||
class="sanctions-info-icon blocked-info"
|
||||
matTooltip="All customer names must be verified against the UN Security Council Consolidated List before account opening and periodically thereafter. Positive matches must be escalated to the Principal Officer immediately."
|
||||
matTooltipClass="sanctions-tooltip">info</mat-icon>
|
||||
matTooltipClass="sanctions-tooltip"
|
||||
>info</mat-icon
|
||||
>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -181,7 +195,9 @@
|
||||
}
|
||||
|
||||
<!-- Person: Salutation + First / Middle / Last Name -->
|
||||
@if (createClientForm.get('firstname') || createClientForm.get('middlename') || createClientForm.get('lastname')) {
|
||||
@if (
|
||||
createClientForm.get('firstname') || createClientForm.get('middlename') || createClientForm.get('lastname')
|
||||
) {
|
||||
<div class="name-fields-row">
|
||||
@if (createClientForm.get('salutationId') && salutationOptions?.length > 0) {
|
||||
<mat-form-field appearance="outline" class="name-field salutation-field-wide">
|
||||
@@ -213,7 +229,10 @@
|
||||
<mat-label>{{ 'labels.inputs.Middle Name' | translate }}</mat-label>
|
||||
<span matPrefix class="field-prefix-icon"><mat-icon>person_outline</mat-icon></span>
|
||||
<input matInput formControlName="middlename" />
|
||||
@if (createClientForm.controls.middlename?.hasError('pattern') && createClientForm.controls.middlename?.touched) {
|
||||
@if (
|
||||
createClientForm.controls.middlename?.hasError('pattern') &&
|
||||
createClientForm.controls.middlename?.touched
|
||||
) {
|
||||
<mat-error>Only letters, spaces, dots and hyphens allowed</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
@@ -243,30 +262,39 @@
|
||||
</div>
|
||||
<div class="verify-inline">
|
||||
@if (!sanctionsVerified && !sanctionsBlocked) {
|
||||
<button mat-stroked-button color="primary" type="button" class="verify-name-btn"
|
||||
<button
|
||||
mat-stroked-button
|
||||
color="primary"
|
||||
type="button"
|
||||
class="verify-name-btn"
|
||||
[disabled]="readOnly"
|
||||
(click)="verifySanctions()">
|
||||
(click)="verifySanctions()"
|
||||
>
|
||||
<fa-icon icon="shield-alt" class="verify-name-icon"></fa-icon>
|
||||
Verify Client
|
||||
</button>
|
||||
}
|
||||
@if (sanctionsVerified) {
|
||||
<span class="sanctions-citation">RBI KYC Master Direction (2016) — Section 51 & PMLA 2002</span>
|
||||
<span class="verify-status verified">
|
||||
<fa-icon icon="check-circle"></fa-icon> Verified
|
||||
</span>
|
||||
<mat-icon class="sanctions-info-icon"
|
||||
<span class="verify-status verified"> <fa-icon icon="check-circle"></fa-icon> Verified </span>
|
||||
<mat-icon
|
||||
class="sanctions-info-icon"
|
||||
matTooltip="All customer names must be verified against the UN Security Council Consolidated List before account opening and periodically thereafter. Positive matches must be escalated to the Principal Officer immediately."
|
||||
matTooltipClass="sanctions-tooltip">info</mat-icon>
|
||||
matTooltipClass="sanctions-tooltip"
|
||||
>info</mat-icon
|
||||
>
|
||||
}
|
||||
@if (sanctionsBlocked) {
|
||||
<span class="sanctions-citation">RBI KYC Master Direction (2016) — Section 51 & PMLA 2002</span>
|
||||
<span class="verify-status blocked">
|
||||
<fa-icon icon="exclamation-triangle"></fa-icon> Sanctions Match
|
||||
</span>
|
||||
<mat-icon class="sanctions-info-icon blocked-info"
|
||||
<mat-icon
|
||||
class="sanctions-info-icon blocked-info"
|
||||
matTooltip="All customer names must be verified against the UN Security Council Consolidated List before account opening and periodically thereafter. Positive matches must be escalated to the Principal Officer immediately."
|
||||
matTooltipClass="sanctions-tooltip">info</mat-icon>
|
||||
matTooltipClass="sanctions-tooltip"
|
||||
>info</mat-icon
|
||||
>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -278,10 +306,18 @@
|
||||
<mat-form-field appearance="outline" class="form-field" (click)="dateOfBirthDatePicker.open()">
|
||||
<mat-label>Date of Birth</mat-label>
|
||||
<span matPrefix class="field-prefix-icon"><mat-icon>calendar_today</mat-icon></span>
|
||||
<input matInput required [max]="maxDate" [matDatepicker]="dateOfBirthDatePicker" formControlName="dateOfBirth" />
|
||||
<input
|
||||
matInput
|
||||
required
|
||||
[max]="maxDate"
|
||||
[matDatepicker]="dateOfBirthDatePicker"
|
||||
formControlName="dateOfBirth"
|
||||
/>
|
||||
<mat-datepicker-toggle matSuffix [for]="dateOfBirthDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #dateOfBirthDatePicker></mat-datepicker>
|
||||
@if (createClientForm.get('dateOfBirth')?.hasError('required') && createClientForm.get('dateOfBirth')?.touched) {
|
||||
@if (
|
||||
createClientForm.get('dateOfBirth')?.hasError('required') && createClientForm.get('dateOfBirth')?.touched
|
||||
) {
|
||||
<mat-error>Date of Birth is <strong>required</strong></mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
@@ -324,7 +360,6 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Minor / Guardian Mapping (mandatory if age < 10) -->
|
||||
@@ -341,9 +376,15 @@
|
||||
</div>
|
||||
<div class="guardian-section__header-right">
|
||||
<div class="guardian-section__age-chips">
|
||||
<span class="guardian-age-chip"><strong>{{ ageBreakdown.years }}</strong> yrs</span>
|
||||
<span class="guardian-age-chip"><strong>{{ ageBreakdown.months }}</strong> mos</span>
|
||||
<span class="guardian-age-chip"><strong>{{ ageBreakdown.days }}</strong> days</span>
|
||||
<span class="guardian-age-chip"
|
||||
><strong>{{ ageBreakdown.years }}</strong> yrs</span
|
||||
>
|
||||
<span class="guardian-age-chip"
|
||||
><strong>{{ ageBreakdown.months }}</strong> mos</span
|
||||
>
|
||||
<span class="guardian-age-chip"
|
||||
><strong>{{ ageBreakdown.days }}</strong> days</span
|
||||
>
|
||||
<span class="guardian-minor-badge">MINOR</span>
|
||||
</div>
|
||||
@if (!selectedGuardian && !guardianLinking) {
|
||||
@@ -357,13 +398,14 @@
|
||||
|
||||
<!-- Body -->
|
||||
<div class="guardian-section__body" formGroupName="guardianDetails">
|
||||
|
||||
<!-- Empty state: no guardian linked, not linking -->
|
||||
@if (!selectedGuardian && !guardianLinking) {
|
||||
<div class="guardian-empty guardian-empty--required">
|
||||
<mat-icon class="guardian-empty__icon">person_off</mat-icon>
|
||||
<div class="guardian-empty__content">
|
||||
<span class="guardian-empty__text">No guardian linked yet. Click <strong>"Link Guardian"</strong> to search and add.</span>
|
||||
<span class="guardian-empty__text"
|
||||
>No guardian linked yet. Click <strong>"Link Guardian"</strong> to search and add.</span
|
||||
>
|
||||
<span class="guardian-empty__required">Guardian is mandatory for minor accounts</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -377,22 +419,33 @@
|
||||
<mat-select required formControlName="guardianRelation">
|
||||
<mat-option value="FATHER"><mat-icon class="icon-option-icon">man</mat-icon> Father</mat-option>
|
||||
<mat-option value="MOTHER"><mat-icon class="icon-option-icon">woman</mat-icon> Mother</mat-option>
|
||||
<mat-option value="LEGAL_GUARDIAN"><mat-icon class="icon-option-icon">gavel</mat-icon> Legal Guardian</mat-option>
|
||||
<mat-option value="GRANDPARENT"><mat-icon class="icon-option-icon">elderly</mat-icon> Grandparent</mat-option>
|
||||
<mat-option value="LEGAL_GUARDIAN"
|
||||
><mat-icon class="icon-option-icon">gavel</mat-icon> Legal Guardian</mat-option
|
||||
>
|
||||
<mat-option value="GRANDPARENT"
|
||||
><mat-icon class="icon-option-icon">elderly</mat-icon> Grandparent</mat-option
|
||||
>
|
||||
<mat-option value="OTHER"><mat-icon class="icon-option-icon">group</mat-icon> Other</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="guardian-search-wrapper guardian-search-col">
|
||||
<mat-form-field appearance="outline" class="guardian-search-field"
|
||||
[class.guardian-search-disabled]="!createClientForm.get('guardianDetails.guardianRelation')?.value">
|
||||
<mat-form-field
|
||||
appearance="outline"
|
||||
class="guardian-search-field"
|
||||
[class.guardian-search-disabled]="!createClientForm.get('guardianDetails.guardianRelation')?.value"
|
||||
>
|
||||
<mat-label>Search Guardian by Name or Client ID</mat-label>
|
||||
<span matPrefix class="field-prefix-icon"><mat-icon>person_search</mat-icon></span>
|
||||
<input matInput [value]="guardianSearchText"
|
||||
<input
|
||||
matInput
|
||||
[value]="guardianSearchText"
|
||||
(input)="onGuardianSearch($any($event.target).value)"
|
||||
(blur)="dismissGuardianSearch()"
|
||||
[disabled]="!createClientForm.get('guardianDetails.guardianRelation')?.value"
|
||||
placeholder="Select relationship first, then search..." autocomplete="off" />
|
||||
placeholder="Select relationship first, then search..."
|
||||
autocomplete="off"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
@if (guardianSearching) {
|
||||
@@ -413,11 +466,17 @@
|
||||
<div class="guardian-result-item__info">
|
||||
<span class="guardian-result-item__name">{{ result.displayName }}</span>
|
||||
<span class="guardian-result-item__meta">
|
||||
ID: {{ result.id }} · A/C: {{ result.accountNumber }} · {{ result.officeName }}@if (result.dateOfBirth) { · DOB: {{ result.dateOfBirth | dateFormat }} }@if (result.fatherSpouseName) { · {{ result.fatherSpouseName }} }
|
||||
ID: {{ result.id }} · A/C: {{ result.accountNumber }} ·
|
||||
{{ result.officeName }}
|
||||
@if (result.dateOfBirth) {
|
||||
· DOB: {{ result.dateOfBirth | dateFormat }}
|
||||
}
|
||||
@if (result.fatherSpouseName) {
|
||||
· {{ result.fatherSpouseName }}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<span class="guardian-result-item__status"
|
||||
[class.active]="result.status?.value === 'Active'">
|
||||
<span class="guardian-result-item__status" [class.active]="result.status?.value === 'Active'">
|
||||
{{ result.status?.value }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -429,7 +488,13 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
<button mat-icon-button type="button" class="guardian-cancel-link" (click)="guardianLinking = false" matTooltip="Cancel">
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
class="guardian-cancel-link"
|
||||
(click)="guardianLinking = false"
|
||||
matTooltip="Cancel"
|
||||
>
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
@@ -453,8 +518,12 @@
|
||||
<span class="guardian-row__col guardian-row__col--photo">
|
||||
<div class="guardian-row__avatar" [class.has-photo]="selectedGuardian.profileImageUrl">
|
||||
@if (selectedGuardian.profileImageUrl) {
|
||||
<img [src]="selectedGuardian.profileImageUrl" alt="Photo" class="avatar-photo guardian-row__zoomable"
|
||||
(click)="zoomGuardianImage(selectedGuardian.profileImageUrl, 'Guardian Photo')" />
|
||||
<img
|
||||
[src]="selectedGuardian.profileImageUrl"
|
||||
alt="Photo"
|
||||
class="avatar-photo guardian-row__zoomable"
|
||||
(click)="zoomGuardianImage(selectedGuardian.profileImageUrl, 'Guardian Photo')"
|
||||
/>
|
||||
} @else {
|
||||
<fa-icon icon="user-shield"></fa-icon>
|
||||
}
|
||||
@@ -470,26 +539,40 @@
|
||||
</div>
|
||||
</span>
|
||||
<span class="guardian-row__col guardian-row__col--rel">
|
||||
<span class="guardian-row__rel-badge">{{ createClientForm.get('guardianDetails.guardianRelation')?.value || '-' }}</span>
|
||||
<span class="guardian-row__rel-badge">{{
|
||||
createClientForm.get('guardianDetails.guardianRelation')?.value || '-'
|
||||
}}</span>
|
||||
</span>
|
||||
<span class="guardian-row__col guardian-row__col--cif">
|
||||
<span class="guardian-row__mono">{{ selectedGuardian.accountNo || selectedGuardian.accountNumber || '-' }}</span>
|
||||
<span class="guardian-row__mono">{{
|
||||
selectedGuardian.accountNo || selectedGuardian.accountNumber || '-'
|
||||
}}</span>
|
||||
</span>
|
||||
<span class="guardian-row__col guardian-row__col--status">
|
||||
<span class="guardian-row__status-badge"
|
||||
[class.guardian-row__status-badge--active]="selectedGuardian.status?.value === 'Active'">
|
||||
<span
|
||||
class="guardian-row__status-badge"
|
||||
[class.guardian-row__status-badge--active]="selectedGuardian.status?.value === 'Active'"
|
||||
>
|
||||
{{ selectedGuardian.status?.value || '-' }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="guardian-row__col guardian-row__col--actions">
|
||||
<button mat-icon-button type="button" class="guardian-action-btn"
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
class="guardian-action-btn"
|
||||
(click)="guardianExpanded = !guardianExpanded"
|
||||
[matTooltip]="guardianExpanded ? 'Collapse' : 'View Details'">
|
||||
[matTooltip]="guardianExpanded ? 'Collapse' : 'View Details'"
|
||||
>
|
||||
<mat-icon>{{ guardianExpanded ? 'expand_less' : 'visibility' }}</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button type="button" class="guardian-action-btn guardian-action-btn--delete"
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
class="guardian-action-btn guardian-action-btn--delete"
|
||||
(click)="removeGuardian()"
|
||||
matTooltip="Delete">
|
||||
matTooltip="Delete"
|
||||
>
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</span>
|
||||
@@ -506,16 +589,22 @@
|
||||
@if (selectedGuardian.panNumber) {
|
||||
<div class="guardian-expand__item">
|
||||
<span class="guardian-expand__label">PAN</span>
|
||||
<span class="guardian-expand__value">{{ selectedGuardian.panNumber }}
|
||||
@if (selectedGuardian.panVerified) { <mat-icon class="verify-tick-sm">check_circle</mat-icon> }
|
||||
<span class="guardian-expand__value"
|
||||
>{{ selectedGuardian.panNumber }}
|
||||
@if (selectedGuardian.panVerified) {
|
||||
<mat-icon class="verify-tick-sm">check_circle</mat-icon>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@if (selectedGuardian.aadhaarMasked) {
|
||||
<div class="guardian-expand__item">
|
||||
<span class="guardian-expand__label">Aadhaar</span>
|
||||
<span class="guardian-expand__value">{{ selectedGuardian.aadhaarMasked }}
|
||||
@if (selectedGuardian.aadhaarVerified) { <mat-icon class="verify-tick-sm">check_circle</mat-icon> }
|
||||
<span class="guardian-expand__value"
|
||||
>{{ selectedGuardian.aadhaarMasked }}
|
||||
@if (selectedGuardian.aadhaarVerified) {
|
||||
<mat-icon class="verify-tick-sm">check_circle</mat-icon>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@@ -535,8 +624,12 @@
|
||||
@if (selectedGuardian.signatureUrl) {
|
||||
<div class="guardian-expand__sig">
|
||||
<span class="guardian-expand__label">Specimen Signature</span>
|
||||
<img [src]="selectedGuardian.signatureUrl" alt="Signature" class="guardian-expand__sig-img guardian-row__zoomable"
|
||||
(click)="zoomGuardianImage(selectedGuardian.signatureUrl, 'Specimen Signature')" />
|
||||
<img
|
||||
[src]="selectedGuardian.signatureUrl"
|
||||
alt="Signature"
|
||||
class="guardian-expand__sig-img guardian-row__zoomable"
|
||||
(click)="zoomGuardianImage(selectedGuardian.signatureUrl, 'Specimen Signature')"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -549,7 +642,7 @@
|
||||
|
||||
<!-- Non-Individual Details -->
|
||||
@if (createClientForm.get('clientNonPersonDetails')) {
|
||||
<div class="form-grid" formGroupName="clientNonPersonDetails" style="margin-top: 16px;">
|
||||
<div class="form-grid" formGroupName="clientNonPersonDetails" style="margin-top: 16px">
|
||||
<mat-form-field appearance="outline" class="form-field">
|
||||
<mat-label>Constitution Type</mat-label>
|
||||
<mat-select formControlName="constitutionId" required>
|
||||
@@ -607,7 +700,10 @@
|
||||
</h4>
|
||||
<div class="minor-info-bar">
|
||||
<fa-icon icon="info-circle" class="minor-info-icon"></fa-icon>
|
||||
<span>Select registered clients as authorized signatories — as per Board Resolution / Partnership Deed. Max 10.</span>
|
||||
<span
|
||||
>Select registered clients as authorized signatories — as per Board Resolution / Partnership Deed. Max
|
||||
10.</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Signatory List Table -->
|
||||
@@ -619,6 +715,7 @@
|
||||
<span class="sig-table__col sig-table__col--name">Name</span>
|
||||
<span class="sig-table__col sig-table__col--id">Client ID</span>
|
||||
<span class="sig-table__col sig-table__col--pan">PAN</span>
|
||||
<span class="sig-table__col sig-table__col--din">DIN</span>
|
||||
<span class="sig-table__col sig-table__col--status">Status</span>
|
||||
<span class="sig-table__col sig-table__col--actions">Actions</span>
|
||||
</div>
|
||||
@@ -637,16 +734,45 @@
|
||||
<span class="sig-table__col sig-table__col--name">
|
||||
<strong>{{ sig.displayName }}</strong>
|
||||
@if (sig._loading) {
|
||||
<mat-progress-spinner diameter="14" mode="indeterminate" class="sig-inline-spinner"></mat-progress-spinner>
|
||||
<mat-progress-spinner
|
||||
diameter="14"
|
||||
mode="indeterminate"
|
||||
class="sig-inline-spinner"
|
||||
></mat-progress-spinner>
|
||||
}
|
||||
</span>
|
||||
<span class="sig-table__col sig-table__col--id">{{ sig.cifNumber || sig.id }}</span>
|
||||
<span class="sig-table__col sig-table__col--pan">
|
||||
{{ sig.panNumber || '-' }}
|
||||
@if (sig.panVerified) { <mat-icon class="verify-tick-sm">check_circle</mat-icon> }
|
||||
@if (sig.panVerified) {
|
||||
<mat-icon class="verify-tick-sm">check_circle</mat-icon>
|
||||
}
|
||||
</span>
|
||||
<span class="sig-table__col sig-table__col--din">
|
||||
@if (sig.dinNumber) {
|
||||
{{ sig.dinNumber }}
|
||||
@if (sig.dinVerified) {
|
||||
<mat-icon class="verify-tick-sm">check_circle</mat-icon>
|
||||
}
|
||||
@if (!sig.dinVerified) {
|
||||
<mat-icon class="verify-cross-sm">cancel</mat-icon>
|
||||
}
|
||||
} @else {
|
||||
<button
|
||||
mat-stroked-button
|
||||
class="din-verify-btn"
|
||||
type="button"
|
||||
matTooltip="Verify DIN"
|
||||
(click)="openDinVerifyDialog(sig)"
|
||||
>
|
||||
<mat-icon class="din-verify-icon">badge</mat-icon> Verify DIN
|
||||
</button>
|
||||
}
|
||||
</span>
|
||||
<span class="sig-table__col sig-table__col--status">
|
||||
<span class="sig-status-chip" [class.active]="sig.status?.value === 'Active'">{{ sig.status?.value || '-' }}</span>
|
||||
<span class="sig-status-chip" [class.active]="sig.status?.value === 'Active'">{{
|
||||
sig.status?.value || '-'
|
||||
}}</span>
|
||||
</span>
|
||||
<span class="sig-table__col sig-table__col--actions">
|
||||
<button mat-icon-button type="button" matTooltip="View Details" (click)="viewSignatoryDetails(sig)">
|
||||
@@ -668,10 +794,14 @@
|
||||
<mat-form-field appearance="outline" class="guardian-search-field">
|
||||
<mat-label>Add Signatory — Search by Name or Client ID</mat-label>
|
||||
<span matPrefix class="field-prefix-icon"><mat-icon>person_add</mat-icon></span>
|
||||
<input matInput [value]="signatorySearchText"
|
||||
<input
|
||||
matInput
|
||||
[value]="signatorySearchText"
|
||||
(input)="onSignatorySearch($any($event.target).value)"
|
||||
(blur)="dismissSignatorySearch()"
|
||||
placeholder="Type name or client ID..." autocomplete="off" />
|
||||
placeholder="Type name or client ID..."
|
||||
autocomplete="off"
|
||||
/>
|
||||
</mat-form-field>
|
||||
@if (signatorySearching) {
|
||||
<div class="guardian-results">
|
||||
@@ -688,9 +818,20 @@
|
||||
<div class="guardian-result-item__avatar"><fa-icon icon="user"></fa-icon></div>
|
||||
<div class="guardian-result-item__info">
|
||||
<span class="guardian-result-item__name">{{ result.displayName }}</span>
|
||||
<span class="guardian-result-item__meta">ID: {{ result.id }} · A/C: {{ result.accountNumber }} · {{ result.officeName }}@if (result.dateOfBirth) { · DOB: {{ result.dateOfBirth | dateFormat }} }@if (result.fatherSpouseName) { · {{ result.fatherSpouseName }} }</span>
|
||||
<span class="guardian-result-item__meta"
|
||||
>ID: {{ result.id }} · A/C: {{ result.accountNumber }} ·
|
||||
{{ result.officeName }}
|
||||
@if (result.dateOfBirth) {
|
||||
· DOB: {{ result.dateOfBirth | dateFormat }}
|
||||
}
|
||||
@if (result.fatherSpouseName) {
|
||||
· {{ result.fatherSpouseName }}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<span class="guardian-result-item__status" [class.active]="result.status?.value === 'Active'">{{ result.status?.value }}</span>
|
||||
<span class="guardian-result-item__status" [class.active]="result.status?.value === 'Active'">{{
|
||||
result.status?.value
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -716,7 +857,6 @@
|
||||
<textarea matInput formControlName="remarks" cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -725,9 +865,12 @@
|
||||
<h4 class="section-title">
|
||||
<fa-icon icon="mobile-alt" class="section-icon"></fa-icon>
|
||||
Contact Details
|
||||
<mat-icon class="dip-info-icon"
|
||||
<mat-icon
|
||||
class="dip-info-icon"
|
||||
matTooltip="Digital Intelligence Platform (DIP) refers to the fraud-monitoring and intelligence ecosystem created by the Department of Telecommunications (DoT)"
|
||||
matTooltipPosition="above">info</mat-icon>
|
||||
matTooltipPosition="above"
|
||||
>info</mat-icon
|
||||
>
|
||||
</h4>
|
||||
|
||||
<div class="form-grid">
|
||||
@@ -735,21 +878,35 @@
|
||||
<mat-form-field appearance="outline" class="form-field">
|
||||
<mat-label>{{ 'labels.inputs.Mobile No' | translate }}</mat-label>
|
||||
<span matPrefix class="field-prefix-icon"><mat-icon>phone_android</mat-icon></span>
|
||||
<input matInput required type="text" formControlName="mobileNo" placeholder="e.g. 9876543210" maxlength="10"
|
||||
<input
|
||||
matInput
|
||||
required
|
||||
type="text"
|
||||
formControlName="mobileNo"
|
||||
placeholder="e.g. 9876543210"
|
||||
maxlength="10"
|
||||
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()"
|
||||
(paste)="$event.preventDefault()" />
|
||||
(paste)="$event.preventDefault()"
|
||||
/>
|
||||
@if (createClientForm.get('mobileNo')?.hasError('required') && createClientForm.get('mobileNo')?.touched) {
|
||||
<mat-error>Mobile number is <strong>required</strong></mat-error>
|
||||
} @else if (createClientForm.get('mobileNo')?.hasError('pattern') && createClientForm.get('mobileNo')?.touched) {
|
||||
} @else if (
|
||||
createClientForm.get('mobileNo')?.hasError('pattern') && createClientForm.get('mobileNo')?.touched
|
||||
) {
|
||||
<mat-error>Enter valid 10-digit number (starts with 6-9)</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<div class="form-field mobile-verify-row">
|
||||
@if (!mobileVerified) {
|
||||
<button mat-stroked-button color="primary" type="button" class="verify-btn"
|
||||
<button
|
||||
mat-stroked-button
|
||||
color="primary"
|
||||
type="button"
|
||||
class="verify-btn"
|
||||
[disabled]="readOnly || createClientForm.get('mobileNo')?.value?.length !== 10"
|
||||
(click)="verifyWithDIP()">
|
||||
(click)="verifyWithDIP()"
|
||||
>
|
||||
<fa-icon icon="shield-alt" class="m-r-10"></fa-icon>
|
||||
Verify with DIP
|
||||
</button>
|
||||
@@ -762,19 +919,33 @@
|
||||
<mat-form-field appearance="outline" class="form-field">
|
||||
<mat-label>Alternate Mobile No</mat-label>
|
||||
<span matPrefix class="field-prefix-icon"><mat-icon>phone_android</mat-icon></span>
|
||||
<input matInput type="text" formControlName="alternateMobileNo" placeholder="e.g. 9876543210" maxlength="10"
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
formControlName="alternateMobileNo"
|
||||
placeholder="e.g. 9876543210"
|
||||
maxlength="10"
|
||||
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()"
|
||||
(paste)="$event.preventDefault()" />
|
||||
@if (createClientForm.get('alternateMobileNo')?.hasError('pattern') && createClientForm.get('alternateMobileNo')?.touched) {
|
||||
(paste)="$event.preventDefault()"
|
||||
/>
|
||||
@if (
|
||||
createClientForm.get('alternateMobileNo')?.hasError('pattern') &&
|
||||
createClientForm.get('alternateMobileNo')?.touched
|
||||
) {
|
||||
<mat-error>Enter valid 10-digit number (starts with 6-9)</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<div class="form-field mobile-verify-row">
|
||||
@if (!alternateMobileVerified) {
|
||||
<button mat-stroked-button color="primary" type="button" class="verify-btn"
|
||||
<button
|
||||
mat-stroked-button
|
||||
color="primary"
|
||||
type="button"
|
||||
class="verify-btn"
|
||||
[disabled]="readOnly || createClientForm.get('alternateMobileNo')?.value?.length !== 10"
|
||||
(click)="verifyAlternateDIP()">
|
||||
(click)="verifyAlternateDIP()"
|
||||
>
|
||||
<fa-icon icon="shield-alt" class="m-r-10"></fa-icon>
|
||||
Verify with DIP
|
||||
</button>
|
||||
@@ -788,21 +959,27 @@
|
||||
<mat-label>{{ 'labels.inputs.Email Address' | translate }}</mat-label>
|
||||
<span matPrefix class="field-prefix-icon"><mat-icon>email</mat-icon></span>
|
||||
<input matInput required formControlName="emailAddress" placeholder="e.g. name@example.com" />
|
||||
@if (createClientForm.get('emailAddress')?.hasError('required') && createClientForm.get('emailAddress')?.touched) {
|
||||
@if (
|
||||
createClientForm.get('emailAddress')?.hasError('required') && createClientForm.get('emailAddress')?.touched
|
||||
) {
|
||||
<mat-error>Email is <strong>required</strong></mat-error>
|
||||
} @else if (createClientForm.get('emailAddress')?.invalid && createClientForm.get('emailAddress')?.touched) {
|
||||
<mat-error>Enter a valid email (e.g. name@example.com)</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
@if (createClientForm.get('legalFormId')?.value === LegalFormId.PERSON && maritalStatusOptions.length > 0 && !isMinor) {
|
||||
@if (
|
||||
createClientForm.get('legalFormId')?.value === LegalFormId.PERSON && maritalStatusOptions.length > 0 && !isMinor
|
||||
) {
|
||||
<mat-form-field appearance="outline" class="form-field">
|
||||
<mat-label>Marital Status</mat-label>
|
||||
<mat-select formControlName="maritalStatusId">
|
||||
<mat-select-trigger>
|
||||
@if (getSelectedMaritalStatus()) {
|
||||
<span class="icon-option">
|
||||
<mat-icon class="icon-option-icon">{{ getMaritalStatusIcon(getSelectedMaritalStatus().name) }}</mat-icon>
|
||||
<mat-icon class="icon-option-icon">{{
|
||||
getMaritalStatusIcon(getSelectedMaritalStatus().name)
|
||||
}}</mat-icon>
|
||||
{{ getSelectedMaritalStatus().name }}
|
||||
</span>
|
||||
}
|
||||
@@ -823,81 +1000,90 @@
|
||||
|
||||
<!-- Section: Financial & Classification (Individual only) -->
|
||||
@if (createClientForm.get('legalFormId')?.value === LegalFormId.PERSON) {
|
||||
<div class="section-block">
|
||||
<h4 class="section-title">
|
||||
<fa-icon icon="chart-line" class="section-icon"></fa-icon>
|
||||
Financial & Classification
|
||||
</h4>
|
||||
<div class="form-grid">
|
||||
|
||||
|
||||
<mat-form-field appearance="outline" class="form-field">
|
||||
<mat-label>{{ 'labels.inputs.Occupation Category' | translate }}</mat-label>
|
||||
<mat-select formControlName="occupationCategoryId">
|
||||
<mat-select-trigger>
|
||||
@if (getSelectedOccupationCategory()) {
|
||||
<span class="occ-option">
|
||||
<mat-icon class="occ-option-icon">{{ getOccupationCategoryIcon(getSelectedOccupationCategory().name) }}</mat-icon>
|
||||
{{ getSelectedOccupationCategory().name }}
|
||||
</span>
|
||||
<div class="section-block">
|
||||
<h4 class="section-title">
|
||||
<fa-icon icon="chart-line" class="section-icon"></fa-icon>
|
||||
Financial & Classification
|
||||
</h4>
|
||||
<div class="form-grid">
|
||||
<mat-form-field appearance="outline" class="form-field">
|
||||
<mat-label>{{ 'labels.inputs.Occupation Category' | translate }}</mat-label>
|
||||
<mat-select formControlName="occupationCategoryId">
|
||||
<mat-select-trigger>
|
||||
@if (getSelectedOccupationCategory()) {
|
||||
<span class="occ-option">
|
||||
<mat-icon class="occ-option-icon">{{
|
||||
getOccupationCategoryIcon(getSelectedOccupationCategory().name)
|
||||
}}</mat-icon>
|
||||
{{ getSelectedOccupationCategory().name }}
|
||||
</span>
|
||||
}
|
||||
</mat-select-trigger>
|
||||
@for (cat of occupationCategoryOptions; track cat) {
|
||||
<mat-option [value]="cat.id">
|
||||
<span class="occ-option">
|
||||
<mat-icon class="occ-option-icon">{{ getOccupationCategoryIcon(cat.name) }}</mat-icon>
|
||||
{{ cat.name }}
|
||||
</span>
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select-trigger>
|
||||
@for (cat of occupationCategoryOptions; track cat) {
|
||||
<mat-option [value]="cat.id">
|
||||
<span class="occ-option">
|
||||
<mat-icon class="occ-option-icon">{{ getOccupationCategoryIcon(cat.name) }}</mat-icon>
|
||||
{{ cat.name }}
|
||||
</span>
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="form-field">
|
||||
<mat-label>{{ 'labels.inputs.Occupation' | translate }}</mat-label>
|
||||
<mat-select formControlName="occupationId">
|
||||
<mat-select-trigger>
|
||||
@if (getSelectedOccupation()) {
|
||||
<span class="occ-option">
|
||||
<mat-icon class="occ-option-icon">{{ getOccupationIcon(getSelectedOccupation().name) }}</mat-icon>
|
||||
{{ getSelectedOccupation().name }}
|
||||
</span>
|
||||
<mat-form-field appearance="outline" class="form-field">
|
||||
<mat-label>{{ 'labels.inputs.Occupation' | translate }}</mat-label>
|
||||
<mat-select formControlName="occupationId">
|
||||
<mat-select-trigger>
|
||||
@if (getSelectedOccupation()) {
|
||||
<span class="occ-option">
|
||||
<mat-icon class="occ-option-icon">{{ getOccupationIcon(getSelectedOccupation().name) }}</mat-icon>
|
||||
{{ getSelectedOccupation().name }}
|
||||
</span>
|
||||
}
|
||||
</mat-select-trigger>
|
||||
@for (occ of occupationOptions; track occ) {
|
||||
<mat-option [value]="occ.id">
|
||||
<span class="occ-option">
|
||||
<mat-icon class="occ-option-icon">{{ getOccupationIcon(occ.name) }}</mat-icon>
|
||||
{{ occ.name }}
|
||||
</span>
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select-trigger>
|
||||
@for (occ of occupationOptions; track occ) {
|
||||
<mat-option [value]="occ.id">
|
||||
<span class="occ-option">
|
||||
<mat-icon class="occ-option-icon">{{ getOccupationIcon(occ.name) }}</mat-icon>
|
||||
{{ occ.name }}
|
||||
</span>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="form-field">
|
||||
<mat-label>Monthly Income</mat-label>
|
||||
<span matPrefix class="rupee-prefix">₹</span>
|
||||
<input matInput type="number" formControlName="monthlyIncome" min="0" />
|
||||
@if (
|
||||
createClientForm.get('monthlyIncome')?.hasError('pattern') && createClientForm.get('monthlyIncome')?.touched
|
||||
) {
|
||||
<mat-error>Enter a valid amount</mat-error>
|
||||
} @else if (
|
||||
createClientForm.get('monthlyIncome')?.hasError('min') && createClientForm.get('monthlyIncome')?.touched
|
||||
) {
|
||||
<mat-error>Amount cannot be negative</mat-error>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="form-field">
|
||||
<mat-label>Monthly Income</mat-label>
|
||||
<span matPrefix class="rupee-prefix">₹</span>
|
||||
<input matInput type="number" formControlName="monthlyIncome" min="0" />
|
||||
@if (createClientForm.get('monthlyIncome')?.hasError('pattern') && createClientForm.get('monthlyIncome')?.touched) {
|
||||
<mat-error>Enter a valid amount</mat-error>
|
||||
} @else if (createClientForm.get('monthlyIncome')?.hasError('min') && createClientForm.get('monthlyIncome')?.touched) {
|
||||
<mat-error>Amount cannot be negative</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="form-field">
|
||||
<mat-label>Annual Turnover (Last FY)</mat-label>
|
||||
<span matPrefix class="rupee-prefix">₹</span>
|
||||
<input matInput type="number" formControlName="annualTurnover" min="0" />
|
||||
@if (createClientForm.get('annualTurnover')?.hasError('pattern') && createClientForm.get('annualTurnover')?.touched) {
|
||||
<mat-error>Enter a valid amount</mat-error>
|
||||
} @else if (createClientForm.get('annualTurnover')?.hasError('min') && createClientForm.get('annualTurnover')?.touched) {
|
||||
<mat-error>Amount cannot be negative</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="form-field">
|
||||
<mat-label>Annual Turnover (Last FY)</mat-label>
|
||||
<span matPrefix class="rupee-prefix">₹</span>
|
||||
<input matInput type="number" formControlName="annualTurnover" min="0" />
|
||||
@if (
|
||||
createClientForm.get('annualTurnover')?.hasError('pattern') &&
|
||||
createClientForm.get('annualTurnover')?.touched
|
||||
) {
|
||||
<mat-error>Enter a valid amount</mat-error>
|
||||
} @else if (
|
||||
createClientForm.get('annualTurnover')?.hasError('min') && createClientForm.get('annualTurnover')?.touched
|
||||
) {
|
||||
<mat-error>Amount cannot be negative</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- submittedOnDate is set automatically via form default (business date) -->
|
||||
|
||||
+252
-61
@@ -13,7 +13,7 @@ form {
|
||||
.section-block {
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-bottom: 1px solid rgb(0 0 0 / 6%);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
@@ -28,7 +28,7 @@ form {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin: 0 0 16px 0;
|
||||
margin: 0 0 16px;
|
||||
|
||||
.section-icon {
|
||||
color: var(--md-sys-color-primary, #1074b9);
|
||||
@@ -235,7 +235,7 @@ form {
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 4%);
|
||||
}
|
||||
|
||||
.guardian-section__header {
|
||||
@@ -268,7 +268,7 @@ form {
|
||||
|
||||
.guardian-section__sub {
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
color: rgb(255 255 255 / 55%);
|
||||
}
|
||||
|
||||
.guardian-section__age-chips {
|
||||
@@ -280,9 +280,9 @@ form {
|
||||
.guardian-age-chip {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
color: rgb(255 255 255 / 80%);
|
||||
padding: 3px 10px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: rgb(255 255 255 / 10%);
|
||||
border-radius: 10px;
|
||||
|
||||
strong {
|
||||
@@ -408,7 +408,10 @@ form {
|
||||
.guardian-cancel-link {
|
||||
color: #94a3b8 !important;
|
||||
margin-top: 8px;
|
||||
&:hover { color: #ef4444 !important; }
|
||||
|
||||
&:hover {
|
||||
color: #ef4444 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══ Guardian Row Table ═══ */
|
||||
@@ -437,7 +440,10 @@ form {
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
transition: background 0.1s;
|
||||
&:hover { background: #f8fafc; }
|
||||
|
||||
&:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
}
|
||||
|
||||
.guardian-row__col {
|
||||
@@ -445,12 +451,33 @@ form {
|
||||
font-size: 13px;
|
||||
color: #1e293b;
|
||||
|
||||
&--photo { flex: 0 0 50px; }
|
||||
&--name { flex: 1; min-width: 0; }
|
||||
&--rel { flex: 0 0 120px; }
|
||||
&--cif { flex: 0 0 130px; }
|
||||
&--status { flex: 0 0 80px; }
|
||||
&--actions { flex: 0 0 110px; display: flex; gap: 2px; justify-content: flex-end; }
|
||||
&--photo {
|
||||
flex: 0 0 50px;
|
||||
}
|
||||
|
||||
&--name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&--rel {
|
||||
flex: 0 0 120px;
|
||||
}
|
||||
|
||||
&--cif {
|
||||
flex: 0 0 130px;
|
||||
}
|
||||
|
||||
&--status {
|
||||
flex: 0 0 80px;
|
||||
}
|
||||
|
||||
&--actions {
|
||||
flex: 0 0 110px;
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.guardian-row__avatar {
|
||||
@@ -543,14 +570,22 @@ form {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
&:hover mat-icon { color: #1074b9; }
|
||||
&--delete:hover mat-icon { color: #ef4444; }
|
||||
&:hover mat-icon {
|
||||
color: #1074b9;
|
||||
}
|
||||
|
||||
&--delete:hover mat-icon {
|
||||
color: #ef4444;
|
||||
}
|
||||
}
|
||||
|
||||
.guardian-row__zoomable {
|
||||
cursor: zoom-in;
|
||||
transition: transform 0.15s;
|
||||
&:hover { transform: scale(1.05); }
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
/* Expandable detail panel */
|
||||
@@ -558,12 +593,19 @@ form {
|
||||
border-top: 1px solid #e2e8f0;
|
||||
background: #f8fafc;
|
||||
padding: 16px 20px;
|
||||
animation: guardianSlideDown 0.2s ease-out;
|
||||
animation: guardian-slide-down 0.2s ease-out;
|
||||
}
|
||||
|
||||
@keyframes guardianSlideDown {
|
||||
from { opacity: 0; max-height: 0; }
|
||||
to { opacity: 1; max-height: 500px; }
|
||||
@keyframes guardian-slide-down {
|
||||
from {
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
max-height: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
.guardian-expand__grid {
|
||||
@@ -638,12 +680,13 @@ form {
|
||||
padding: 0 !important;
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
|
||||
.mdc-dialog__surface {
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@media (width <= 768px) {
|
||||
.guardian-section__header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
@@ -665,7 +708,9 @@ form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.guardian-row__header { display: none; }
|
||||
.guardian-row__header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.guardian-row__data {
|
||||
flex-wrap: wrap;
|
||||
@@ -673,10 +718,23 @@ form {
|
||||
}
|
||||
|
||||
.guardian-row__col {
|
||||
&--photo { flex: 0 0 40px; }
|
||||
&--name { flex: 1 1 200px; }
|
||||
&--rel, &--cif, &--status { flex: 0 0 auto; }
|
||||
&--actions { flex: 0 0 auto; }
|
||||
&--photo {
|
||||
flex: 0 0 40px;
|
||||
}
|
||||
|
||||
&--name {
|
||||
flex: 1 1 200px;
|
||||
}
|
||||
|
||||
&--rel,
|
||||
&--cif,
|
||||
&--status {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&--actions {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.guardian-expand__grid {
|
||||
@@ -738,7 +796,7 @@ form {
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
box-shadow: 0 8px 24px rgb(0 0 0 / 12%);
|
||||
max-height: 280px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@@ -776,8 +834,13 @@ form {
|
||||
transition: background 0.1s;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
&:hover { background: #f0f7ff; }
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #f0f7ff;
|
||||
}
|
||||
}
|
||||
|
||||
.guardian-result-item__avatar {
|
||||
@@ -834,7 +897,7 @@ form {
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: 0 1px 4px rgb(0 0 0 / 6%);
|
||||
}
|
||||
|
||||
.guardian-card__header {
|
||||
@@ -881,7 +944,10 @@ form {
|
||||
|
||||
.guardian-card__remove {
|
||||
color: #94a3b8 !important;
|
||||
&:hover { color: #ef4444 !important; }
|
||||
|
||||
&:hover {
|
||||
color: #ef4444 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.guardian-card__body {
|
||||
@@ -898,8 +964,13 @@ form {
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
border-right: 1px solid #f1f5f9;
|
||||
|
||||
&:nth-child(2n) { border-right: none; }
|
||||
&:nth-last-child(-n+2) { border-bottom: none; }
|
||||
&:nth-child(2n) {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
&:nth-last-child(-n + 2) {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.guardian-card__label {
|
||||
@@ -945,7 +1016,7 @@ form {
|
||||
height: 16px;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 0 0 1px rgb(0 0 0 / 10%);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -1030,7 +1101,7 @@ form {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
background: rgb(255 255 255 / 15%);
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
@@ -1040,7 +1111,7 @@ form {
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
background: rgb(255 255 255 / 25%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1068,10 +1139,22 @@ form {
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.row-micro td { background: #ecfdf5; }
|
||||
.row-small td { background: #e0f2fe; }
|
||||
.row-medium td { background: #fffbeb; }
|
||||
.row-large td { background: #f8fafc; color: #94a3b8; }
|
||||
.row-micro td {
|
||||
background: #ecfdf5;
|
||||
}
|
||||
|
||||
.row-small td {
|
||||
background: #e0f2fe;
|
||||
}
|
||||
|
||||
.row-medium td {
|
||||
background: #fffbeb;
|
||||
}
|
||||
|
||||
.row-large td {
|
||||
background: #f8fafc;
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
|
||||
.msme-dot {
|
||||
@@ -1081,10 +1164,21 @@ form {
|
||||
border-radius: 50%;
|
||||
margin-right: 6px;
|
||||
|
||||
&.green { background: #10b981; }
|
||||
&.blue { background: #0ea5e9; }
|
||||
&.amber { background: #f59e0b; }
|
||||
&.gray { background: #cbd5e1; }
|
||||
&.green {
|
||||
background: #10b981;
|
||||
}
|
||||
|
||||
&.blue {
|
||||
background: #0ea5e9;
|
||||
}
|
||||
|
||||
&.amber {
|
||||
background: #f59e0b;
|
||||
}
|
||||
|
||||
&.gray {
|
||||
background: #cbd5e1;
|
||||
}
|
||||
}
|
||||
|
||||
.msme-class-table-footer {
|
||||
@@ -1201,6 +1295,7 @@ form {
|
||||
.mdc-notched-outline__trailing {
|
||||
border-color: #10b981 !important;
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
color: #059669 !important;
|
||||
-webkit-text-fill-color: #059669 !important;
|
||||
@@ -1213,6 +1308,7 @@ form {
|
||||
.mdc-notched-outline__trailing {
|
||||
border-color: #0ea5e9 !important;
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
color: #0284c7 !important;
|
||||
-webkit-text-fill-color: #0284c7 !important;
|
||||
@@ -1225,6 +1321,7 @@ form {
|
||||
.mdc-notched-outline__trailing {
|
||||
border-color: #f59e0b !important;
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
color: #d97706 !important;
|
||||
-webkit-text-fill-color: #d97706 !important;
|
||||
@@ -1390,7 +1487,7 @@ form {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@media (width <= 768px) {
|
||||
.form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@@ -1495,18 +1592,68 @@ form {
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
transition: background 0.15s;
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
&:hover { background: #f8fafc; }
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
}
|
||||
|
||||
.sig-table__col {
|
||||
&--sno { flex: 0 0 32px; text-align: center; color: #94a3b8; font-size: 12px; }
|
||||
&--photo { flex: 0 0 40px; }
|
||||
&--name { flex: 1; font-size: 13px; display: flex; align-items: center; gap: 6px; }
|
||||
&--id { flex: 0 0 100px; font-size: 12px; color: #64748b; }
|
||||
&--pan { flex: 0 0 120px; font-size: 12px; color: #1e293b; display: flex; align-items: center; gap: 3px; }
|
||||
&--status { flex: 0 0 80px; }
|
||||
&--actions { flex: 0 0 80px; display: flex; gap: 2px; }
|
||||
&--sno {
|
||||
flex: 0 0 32px;
|
||||
text-align: center;
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&--photo {
|
||||
flex: 0 0 40px;
|
||||
}
|
||||
|
||||
&--name {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&--id {
|
||||
flex: 0 0 100px;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
&--pan {
|
||||
flex: 0 0 120px;
|
||||
font-size: 12px;
|
||||
color: #1e293b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
&--din {
|
||||
flex: 0 0 140px;
|
||||
font-size: 12px;
|
||||
color: #1e293b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
&--status {
|
||||
flex: 0 0 80px;
|
||||
}
|
||||
|
||||
&--actions {
|
||||
flex: 0 0 80px;
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.sig-table__avatar {
|
||||
@@ -1521,11 +1668,21 @@ form {
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
|
||||
&.has-photo { padding: 0; }
|
||||
.avatar-photo { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
|
||||
&.has-photo {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.avatar-photo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.sig-inline-spinner { display: inline-block; }
|
||||
.sig-inline-spinner {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.verify-tick-sm {
|
||||
font-size: 14px !important;
|
||||
@@ -1534,11 +1691,40 @@ form {
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.verify-cross-sm {
|
||||
font-size: 14px !important;
|
||||
width: 14px !important;
|
||||
height: 14px !important;
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.din-verify-btn {
|
||||
font-size: 11px !important;
|
||||
padding: 2px 8px !important;
|
||||
min-height: 24px !important;
|
||||
line-height: 20px !important;
|
||||
border-radius: 6px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: #1074b9 !important;
|
||||
border-color: #93c5fd !important;
|
||||
|
||||
.din-verify-icon {
|
||||
font-size: 14px !important;
|
||||
width: 14px !important;
|
||||
height: 14px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.sig-status-chip {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #94a3b8;
|
||||
&.active { color: #22c55e; }
|
||||
|
||||
&.active {
|
||||
color: #22c55e;
|
||||
}
|
||||
}
|
||||
|
||||
.sig-action-icon {
|
||||
@@ -1546,7 +1732,10 @@ form {
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
color: #64748b;
|
||||
&.remove { color: #ef4444; }
|
||||
|
||||
&.remove {
|
||||
color: #ef4444;
|
||||
}
|
||||
}
|
||||
|
||||
.sig-add-wrapper {
|
||||
@@ -1561,11 +1750,13 @@ form {
|
||||
overflow: visible !important;
|
||||
max-height: none !important;
|
||||
}
|
||||
|
||||
.mdc-dialog__surface {
|
||||
border-radius: 12px !important;
|
||||
overflow: visible !important;
|
||||
max-height: none !important;
|
||||
}
|
||||
|
||||
.mat-mdc-dialog-content {
|
||||
max-height: none !important;
|
||||
overflow: visible !important;
|
||||
@@ -1625,7 +1816,7 @@ form {
|
||||
|
||||
&__sub {
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
color: rgb(255 255 255 / 50%);
|
||||
margin: 2px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
+639
-163
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,922 @@
|
||||
/**
|
||||
* 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 { FormsModule } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { ClientsService } from 'app/clients/clients.service';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-din-verify-dialog',
|
||||
template: `
|
||||
<h2 mat-dialog-title>
|
||||
<fa-icon icon="id-badge" class="dialog-title-icon"></fa-icon>
|
||||
DIN Verification
|
||||
</h2>
|
||||
<mat-dialog-content>
|
||||
<!-- Step 1: Enter DIN -->
|
||||
@if (!verifying && !verified && !failed && !errorOccurred) {
|
||||
<div class="din-entry">
|
||||
<div class="signatory-info">
|
||||
<fa-icon icon="user-shield" class="sig-icon"></fa-icon>
|
||||
<div class="sig-detail">
|
||||
<span class="sig-name">{{ signatoryName }}</span>
|
||||
<span class="sig-meta">CIF: {{ cifNumber }} · PAN: {{ panNumber }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@if (entityName) {
|
||||
<div class="entity-info">
|
||||
<fa-icon icon="building" class="entity-icon"></fa-icon>
|
||||
<div class="entity-detail">
|
||||
<span class="entity-label">Entity</span>
|
||||
<span class="entity-name">{{ entityName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="din-info-bar">
|
||||
<fa-icon icon="info-circle" class="info-icon"></fa-icon>
|
||||
<span
|
||||
>Enter the <strong>Director Identification Number (DIN)</strong> issued by the Ministry of Corporate
|
||||
Affairs (MCA) for this authorized signatory. The system will verify the DIN and match the entity name
|
||||
against the director's associated companies.</span
|
||||
>
|
||||
</div>
|
||||
<mat-form-field appearance="outline" class="din-input-field">
|
||||
<mat-label>DIN Number</mat-label>
|
||||
<mat-icon matPrefix class="din-prefix-icon">badge</mat-icon>
|
||||
<input
|
||||
matInput
|
||||
[(ngModel)]="dinNumber"
|
||||
placeholder="e.g. 01234567"
|
||||
maxlength="8"
|
||||
(keyup.enter)="onVerify()"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<mat-hint>8-digit DIN number from MCA</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Step 2: Verifying -->
|
||||
@if (verifying) {
|
||||
<div class="progress-section">
|
||||
<mat-progress-bar mode="determinate" [value]="progress" color="primary"></mat-progress-bar>
|
||||
<div class="progress-percent">{{ progress }}%</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="verify-msg">
|
||||
Verifying DIN <strong>{{ dinNumber }}</strong> for <strong>{{ signatoryName }}</strong> against
|
||||
<strong>MCA (Ministry of Corporate Affairs)</strong> records.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Step 3a: Verified -->
|
||||
@if (!verifying && verified) {
|
||||
<div class="result-card success-card">
|
||||
<div class="result-icon-ring success-ring">
|
||||
<fa-icon icon="check-circle" class="result-shield success-shield"></fa-icon>
|
||||
</div>
|
||||
<div class="result-badge success-badge">VERIFIED</div>
|
||||
<h3 class="result-title">DIN Verified Successfully</h3>
|
||||
<p class="result-detail">
|
||||
DIN <strong class="din-highlight">{{ dinNumber }}</strong> for <strong>{{ signatoryName }}</strong> has been
|
||||
verified against <strong>MCA</strong> records.
|
||||
</p>
|
||||
<div class="result-meta">
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">DIN</span>
|
||||
<span class="meta-value meta-mono">{{ dinNumber }}</span>
|
||||
</div>
|
||||
@if (directorName) {
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Director Name</span>
|
||||
<span class="meta-value">{{ directorName }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Status</span>
|
||||
<span class="meta-value meta-success">{{ dinStatus || 'Active' }}</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Verified On</span>
|
||||
<span class="meta-value">{{ verifiedDate }}</span>
|
||||
</div>
|
||||
@if (dinDesignation) {
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Designation</span>
|
||||
<span class="meta-value">{{ dinDesignation }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Company Match Section -->
|
||||
@if (companies.length > 0) {
|
||||
<div class="company-match-section">
|
||||
<h4 class="company-match-title">
|
||||
<fa-icon icon="building" class="company-title-icon"></fa-icon>
|
||||
Associated Companies ({{ companies.length }})
|
||||
</h4>
|
||||
|
||||
@if (companyMatch) {
|
||||
<div class="match-banner match-success">
|
||||
<fa-icon icon="check-circle" class="match-banner-icon"></fa-icon>
|
||||
<div>
|
||||
<strong>Entity Match Found</strong>
|
||||
<p>
|
||||
<strong>{{ entityName }}</strong> matches <strong>{{ matchedCompany }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="match-banner match-warning">
|
||||
<fa-icon icon="exclamation-triangle" class="match-banner-icon"></fa-icon>
|
||||
<div>
|
||||
<strong>No Entity Match</strong>
|
||||
<p>
|
||||
<strong>{{ entityName }}</strong> does not match any of the director's associated companies.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="company-list">
|
||||
@for (company of companies; track company.companyName || $index) {
|
||||
<div class="company-item" [class.company-matched]="company._matched">
|
||||
@if (company._matched) {
|
||||
<mat-icon class="company-match-icon match">check_circle</mat-icon>
|
||||
} @else {
|
||||
<mat-icon class="company-match-icon no-match">radio_button_unchecked</mat-icon>
|
||||
}
|
||||
<div class="company-info">
|
||||
<span class="company-name">{{ company.companyName }}</span>
|
||||
<span class="company-meta">
|
||||
@if (company.cin) {
|
||||
CIN: {{ company.cin }}
|
||||
}
|
||||
@if (company.cin && company.designation) {
|
||||
·
|
||||
}
|
||||
@if (company.designation) {
|
||||
{{ company.designation }}
|
||||
}
|
||||
@if ((company.cin || company.designation) && company.appointmentDate) {
|
||||
·
|
||||
}
|
||||
@if (company.appointmentDate) {
|
||||
Since: {{ company.appointmentDate }}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Step 3b: Verification Failed -->
|
||||
@if (!verifying && failed) {
|
||||
<div class="result-card fail-card">
|
||||
<div class="result-icon-ring fail-ring">
|
||||
<fa-icon icon="times-circle" class="result-shield fail-shield"></fa-icon>
|
||||
</div>
|
||||
<div class="result-badge fail-badge">FAILED</div>
|
||||
<h3 class="result-title">DIN Verification Failed</h3>
|
||||
<p class="result-detail">
|
||||
DIN <strong class="din-highlight-fail">{{ dinNumber }}</strong> could not be verified.
|
||||
</p>
|
||||
@if (failReason) {
|
||||
<div class="fail-reason">
|
||||
<fa-icon icon="exclamation-triangle" class="fail-reason-icon"></fa-icon>
|
||||
<span>{{ failReason }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Step 3c: Error -->
|
||||
@if (!verifying && errorOccurred) {
|
||||
<div class="result-card error-card">
|
||||
<fa-icon icon="exclamation-circle" class="error-icon-large"></fa-icon>
|
||||
<p class="error-msg">Verification failed. Please try again.</p>
|
||||
<p class="error-detail">{{ errorMessage }}</p>
|
||||
</div>
|
||||
}
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions align="end">
|
||||
@if (!verifying && !verified && !failed && !errorOccurred) {
|
||||
<button mat-stroked-button (click)="onSkip()">Skip</button>
|
||||
<button mat-raised-button color="primary" [disabled]="!dinNumber || dinNumber.length < 8" (click)="onVerify()">
|
||||
<fa-icon icon="search"></fa-icon> Verify DIN
|
||||
</button>
|
||||
}
|
||||
@if (!verifying && verified) {
|
||||
<button mat-raised-button color="primary" (click)="onClose(true)"><fa-icon icon="check"></fa-icon> Done</button>
|
||||
}
|
||||
@if (!verifying && failed) {
|
||||
<button mat-stroked-button (click)="onRetry()">Retry</button>
|
||||
<button mat-raised-button color="warn" (click)="onClose(false)"><fa-icon icon="times"></fa-icon> Close</button>
|
||||
}
|
||||
@if (!verifying && errorOccurred) {
|
||||
<button mat-stroked-button (click)="onRetry()">Retry</button>
|
||||
<button mat-raised-button (click)="onClose(false)">Close</button>
|
||||
}
|
||||
</mat-dialog-actions>
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
:host {
|
||||
display: block;
|
||||
min-width: 520px;
|
||||
}
|
||||
|
||||
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: 70vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* Signatory info */
|
||||
.signatory-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px 16px;
|
||||
background: #f0f4f8;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.sig-icon {
|
||||
font-size: 22px;
|
||||
color: #1074b9;
|
||||
}
|
||||
.sig-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.sig-name {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #1a2332;
|
||||
}
|
||||
.sig-meta {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* Entity info */
|
||||
.entity-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 16px;
|
||||
background: #fffbeb;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 14px;
|
||||
border: 1px solid #fde68a;
|
||||
}
|
||||
|
||||
.entity-icon {
|
||||
font-size: 18px;
|
||||
color: #b45309;
|
||||
}
|
||||
.entity-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.entity-label {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: #92400e;
|
||||
}
|
||||
.entity-name {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #78350f;
|
||||
}
|
||||
|
||||
/* DIN info bar */
|
||||
.din-info-bar {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
background: #e3f2fd;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
font-size: 16px;
|
||||
color: #1074b9;
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* DIN input */
|
||||
.din-input-field {
|
||||
width: 100%;
|
||||
}
|
||||
.din-prefix-icon {
|
||||
color: #64748b;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* Progress */
|
||||
.progress-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.progress-percent {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #1074b9;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.verify-msg {
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
line-height: 1.6;
|
||||
text-align: center;
|
||||
margin: 4px 0 0;
|
||||
}
|
||||
|
||||
/* Result cards */
|
||||
.result-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px 16px;
|
||||
gap: 12px;
|
||||
}
|
||||
.result-icon-ring {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.success-ring {
|
||||
background: linear-gradient(135deg, #ecfdf5, #d1fae5);
|
||||
border: 3px solid #a7f3d0;
|
||||
}
|
||||
.fail-ring {
|
||||
background: linear-gradient(135deg, #fef2f2, #fecaca);
|
||||
border: 3px solid #fca5a5;
|
||||
}
|
||||
.result-shield {
|
||||
font-size: 28px;
|
||||
}
|
||||
.success-shield {
|
||||
color: #059669;
|
||||
}
|
||||
.fail-shield {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.result-badge {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 1.5px;
|
||||
padding: 3px 14px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
.success-badge {
|
||||
color: #059669;
|
||||
background: #ecfdf5;
|
||||
border: 1px solid #a7f3d0;
|
||||
}
|
||||
.fail-badge {
|
||||
color: #dc2626;
|
||||
background: #fef2f2;
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
margin: 0;
|
||||
}
|
||||
.result-detail {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
max-width: 400px;
|
||||
}
|
||||
.din-highlight {
|
||||
color: #059669;
|
||||
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
}
|
||||
.din-highlight-fail {
|
||||
color: #dc2626;
|
||||
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
}
|
||||
|
||||
.result-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-mono {
|
||||
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.meta-success {
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
/* ═══ Company Match Section ═══ */
|
||||
.company-match-section {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.company-match-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.company-title-icon {
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* Match banner */
|
||||
.match-banner {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.match-banner p {
|
||||
margin: 2px 0 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.match-success {
|
||||
background: #ecfdf5;
|
||||
border: 1px solid #a7f3d0;
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
.match-success .match-banner-icon {
|
||||
color: #059669;
|
||||
font-size: 18px;
|
||||
margin-top: 1px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.match-warning {
|
||||
background: #fffbeb;
|
||||
border: 1px solid #fde68a;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.match-warning .match-banner-icon {
|
||||
color: #d97706;
|
||||
font-size: 18px;
|
||||
margin-top: 1px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Company list */
|
||||
.company-list {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.company-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.company-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.company-item:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.company-matched {
|
||||
background: #f0fdf4 !important;
|
||||
border-left: 3px solid #22c55e;
|
||||
}
|
||||
|
||||
.company-match-icon {
|
||||
font-size: 18px !important;
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.company-match-icon.match {
|
||||
color: #22c55e;
|
||||
}
|
||||
.company-match-icon.no-match {
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
.company-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
.company-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.company-meta {
|
||||
font-size: 11px;
|
||||
color: #94a3b8;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* Fail reason */
|
||||
.fail-reason {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
background: #fef2f2;
|
||||
border: 1px solid #fecaca;
|
||||
padding: 10px 14px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
color: #991b1b;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.fail-reason-icon {
|
||||
font-size: 16px;
|
||||
color: #dc2626;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Error */
|
||||
.error-icon-large {
|
||||
font-size: 48px;
|
||||
color: #f57c00;
|
||||
}
|
||||
.error-msg {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #f57c00;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
.error-detail {
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
mat-dialog-actions {
|
||||
padding: 8px 24px 16px;
|
||||
}
|
||||
mat-dialog-actions button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
`
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
MatDialogModule,
|
||||
MatProgressBarModule,
|
||||
MatButtonModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatIconModule,
|
||||
FaIconComponent
|
||||
]
|
||||
})
|
||||
export class DinVerifyDialogComponent implements OnInit, OnDestroy {
|
||||
private dialogRef = inject(MatDialogRef<DinVerifyDialogComponent>);
|
||||
private data: {
|
||||
clientId: string;
|
||||
kycId: string;
|
||||
signatoryName: string;
|
||||
cifNumber: string;
|
||||
panNumber: string;
|
||||
entityName: string;
|
||||
} = inject(MAT_DIALOG_DATA);
|
||||
private clientsService = inject(ClientsService);
|
||||
|
||||
signatoryName = '';
|
||||
cifNumber = '';
|
||||
panNumber = '';
|
||||
entityName = '';
|
||||
dinNumber = '';
|
||||
|
||||
verifying = false;
|
||||
verified = false;
|
||||
failed = false;
|
||||
errorOccurred = false;
|
||||
errorMessage = '';
|
||||
failReason = '';
|
||||
|
||||
directorName = '';
|
||||
dinStatus = '';
|
||||
dinDesignation = '';
|
||||
verifiedDate = new Date().toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
|
||||
/** Companies list from DIN verification response */
|
||||
companies: any[] = [];
|
||||
/** Whether entity name matched a company */
|
||||
companyMatch = false;
|
||||
/** The matched company name */
|
||||
matchedCompany = '';
|
||||
|
||||
progress = 0;
|
||||
currentStep = 0;
|
||||
progressSteps = [
|
||||
{ label: 'Connecting to MCA portal...', icon: 'globe' as any },
|
||||
{ label: 'Looking up DIN records...', icon: 'search' as any },
|
||||
{ label: 'Matching entity with companies...', icon: 'building' as any },
|
||||
{ label: 'Saving verification...', icon: 'database' as any }
|
||||
];
|
||||
|
||||
private intervalId: any;
|
||||
private apiResponse: any = null;
|
||||
private apiError: any = null;
|
||||
private apiDone = false;
|
||||
|
||||
ngOnInit() {
|
||||
this.signatoryName = this.data?.signatoryName || '';
|
||||
this.cifNumber = this.data?.cifNumber || '';
|
||||
this.panNumber = this.data?.panNumber || '-';
|
||||
this.entityName = this.data?.entityName || '';
|
||||
}
|
||||
|
||||
onVerify() {
|
||||
if (!this.dinNumber || this.dinNumber.length < 8) return;
|
||||
|
||||
this.verifying = true;
|
||||
this.verified = false;
|
||||
this.failed = false;
|
||||
this.errorOccurred = false;
|
||||
this.apiDone = false;
|
||||
this.apiResponse = null;
|
||||
this.apiError = null;
|
||||
this.progress = 0;
|
||||
this.currentStep = 0;
|
||||
this.companies = [];
|
||||
this.companyMatch = false;
|
||||
this.matchedCompany = '';
|
||||
|
||||
this.dialogRef.disableClose = true;
|
||||
|
||||
// Fire API call — pass clientId, kycId, dinNumber and entityName
|
||||
this.clientsService.verifyDIN(this.data.clientId, this.data.kycId, this.dinNumber, this.entityName).subscribe({
|
||||
next: (res: any) => {
|
||||
this.apiResponse = res;
|
||||
this.apiDone = true;
|
||||
this.tryShowResult();
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.apiError = err;
|
||||
this.apiDone = true;
|
||||
this.tryShowResult();
|
||||
}
|
||||
});
|
||||
|
||||
// Animate progress
|
||||
this.intervalId = setInterval(() => {
|
||||
this.progress += 2;
|
||||
this.currentStep = Math.min(Math.floor(this.progress / 25), this.progressSteps.length - 1);
|
||||
if (this.progress >= 100) {
|
||||
clearInterval(this.intervalId);
|
||||
this.intervalId = null;
|
||||
this.tryShowResult();
|
||||
}
|
||||
}, 60);
|
||||
}
|
||||
|
||||
private tryShowResult() {
|
||||
if (!this.apiDone || this.progress < 100) return;
|
||||
|
||||
this.verifying = false;
|
||||
this.dialogRef.disableClose = false;
|
||||
|
||||
if (this.apiError) {
|
||||
this.errorOccurred = true;
|
||||
this.errorMessage =
|
||||
this.apiError?.error?.defaultUserMessage ||
|
||||
this.apiError?.error?.message ||
|
||||
this.apiError?.message ||
|
||||
'Unable to verify DIN. Please try again.';
|
||||
return;
|
||||
}
|
||||
|
||||
const res = this.apiResponse;
|
||||
const changes = res?.changes || res;
|
||||
|
||||
if (changes?.dinVerified || res?.dinVerified) {
|
||||
this.verified = true;
|
||||
this.directorName = changes?.directorName || res?.directorName || '';
|
||||
this.dinStatus = changes?.dinStatus || res?.dinStatus || 'Active';
|
||||
this.dinDesignation = changes?.dinDesignation || res?.dinDesignation || '';
|
||||
this.verifiedDate = new Date().toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
|
||||
// Process company list from response
|
||||
const rawCompanies = changes?.companies || res?.companies || [];
|
||||
this.companies = rawCompanies.map((c: any) => ({
|
||||
...c,
|
||||
companyName: c.companyName || c.name || '',
|
||||
cin: c.cin || c.cinNumber || '',
|
||||
designation: c.designation || '',
|
||||
appointmentDate: c.appointmentDate || c.dateOfAppointment || '',
|
||||
_matched: false
|
||||
}));
|
||||
|
||||
// Match entity name against company list
|
||||
this.matchCompanyName();
|
||||
} else {
|
||||
this.failed = true;
|
||||
this.failReason =
|
||||
changes?.dinFailReason ||
|
||||
res?.failReason ||
|
||||
res?.defaultUserMessage ||
|
||||
'DIN could not be verified against MCA records.';
|
||||
}
|
||||
}
|
||||
|
||||
/** Match the entity name against the list of companies from the DIN response */
|
||||
private matchCompanyName() {
|
||||
if (!this.entityName || this.companies.length === 0) return;
|
||||
|
||||
const normalize = (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||
const entityNorm = normalize(this.entityName);
|
||||
|
||||
for (const company of this.companies) {
|
||||
const compNorm = normalize(company.companyName);
|
||||
// Exact match or substring containment
|
||||
if (compNorm === entityNorm || compNorm.includes(entityNorm) || entityNorm.includes(compNorm)) {
|
||||
company._matched = true;
|
||||
this.companyMatch = true;
|
||||
this.matchedCompany = company.companyName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onSkip() {
|
||||
this.dialogRef.close({ skipped: true });
|
||||
}
|
||||
|
||||
onRetry() {
|
||||
this.verified = false;
|
||||
this.failed = false;
|
||||
this.errorOccurred = false;
|
||||
this.progress = 0;
|
||||
this.currentStep = 0;
|
||||
this.companies = [];
|
||||
this.companyMatch = false;
|
||||
this.matchedCompany = '';
|
||||
}
|
||||
|
||||
onClose(verified: boolean) {
|
||||
this.dialogRef.close({
|
||||
verified,
|
||||
dinNumber: this.dinNumber,
|
||||
directorName: this.directorName,
|
||||
dinStatus: this.dinStatus,
|
||||
dinDesignation: this.dinDesignation,
|
||||
dinVerified: verified,
|
||||
companyMatch: this.companyMatch,
|
||||
matchedCompany: this.matchedCompany,
|
||||
companies: this.companies,
|
||||
response: this.apiResponse
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId);
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
-5
@@ -1,3 +1,11 @@
|
||||
/**
|
||||
* 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';
|
||||
@@ -21,7 +29,10 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
<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>
|
||||
<fa-icon
|
||||
[icon]="i < currentStep ? 'check-circle' : step.icon"
|
||||
[class.done-icon]="i < currentStep"
|
||||
></fa-icon>
|
||||
<span>{{ step.label }}</span>
|
||||
</div>
|
||||
}
|
||||
@@ -42,8 +53,8 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
<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.
|
||||
<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">
|
||||
@@ -550,7 +561,12 @@ export class SanctionsVerifyDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private runProgress() {
|
||||
const stepDurations = [1200, 1500, 1800, 1000]; // ms per step
|
||||
const stepDurations = [
|
||||
1200,
|
||||
1500,
|
||||
1800,
|
||||
1000
|
||||
]; // ms per step
|
||||
const totalDuration = stepDurations.reduce((a, b) => a + b, 0);
|
||||
let elapsed = 0;
|
||||
const interval = 50;
|
||||
@@ -602,7 +618,10 @@ export class SanctionsVerifyDialogComponent implements OnInit, OnDestroy {
|
||||
|
||||
if (this.apiError) {
|
||||
this.errorOccurred = true;
|
||||
this.errorMessage = this.apiError?.error?.message || this.apiError?.message || 'Unable to connect to sanctions screening service.';
|
||||
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) {
|
||||
|
||||
+56
-24
@@ -20,15 +20,18 @@
|
||||
<mat-tab-group class="addr-tabs" [(selectedIndex)]="activeTab" animationDuration="150ms">
|
||||
<!-- ═══ TAB 1: PERMANENT ═══ -->
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<span class="addr-tab-num">1</span> Permanent Address
|
||||
</ng-template>
|
||||
<ng-template mat-tab-label> <span class="addr-tab-num">1</span> Permanent Address </ng-template>
|
||||
<div class="addr-panel" [formGroup]="permanentForm">
|
||||
<div class="addr-grid addr-grid--1">
|
||||
<mat-form-field appearance="outline" class="addr-ff">
|
||||
<mat-label>Address Line 1</mat-label>
|
||||
<span matPrefix class="addr-prefix"><mat-icon>home</mat-icon></span>
|
||||
<input matInput required formControlName="addressLine1" placeholder="House / Flat No., Floor, Building Name" />
|
||||
<input
|
||||
matInput
|
||||
required
|
||||
formControlName="addressLine1"
|
||||
placeholder="House / Flat No., Floor, Building Name"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="addr-grid addr-grid--1">
|
||||
@@ -45,9 +48,15 @@
|
||||
<mat-form-field appearance="outline" class="addr-ff">
|
||||
<mat-label>PIN Code</mat-label>
|
||||
<span matPrefix class="addr-prefix"><mat-icon>pin_drop</mat-icon></span>
|
||||
<input matInput required formControlName="pinCode" maxlength="6" placeholder="6-digit PIN"
|
||||
<input
|
||||
matInput
|
||||
required
|
||||
formControlName="pinCode"
|
||||
maxlength="6"
|
||||
placeholder="6-digit PIN"
|
||||
(input)="onPinInput('permanent')"
|
||||
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()" />
|
||||
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()"
|
||||
/>
|
||||
@if (permanentForm.get('pinCode')?.hasError('pattern') && permanentForm.get('pinCode')?.touched) {
|
||||
<mat-error>Enter a valid 6-digit PIN code</mat-error>
|
||||
}
|
||||
@@ -126,9 +135,7 @@
|
||||
|
||||
<!-- ═══ TAB 2: CURRENT / RESIDENTIAL ═══ -->
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<span class="addr-tab-num">2</span> Current / Residential
|
||||
</ng-template>
|
||||
<ng-template mat-tab-label> <span class="addr-tab-num">2</span> Current / Residential </ng-template>
|
||||
<div class="addr-panel" [formGroup]="currentForm">
|
||||
<div class="addr-same-row">
|
||||
<mat-checkbox [checked]="sameAsPermanent" (change)="toggleSameAsPermanent($event.checked)">
|
||||
@@ -140,7 +147,12 @@
|
||||
<div class="addr-grid addr-grid--1">
|
||||
<mat-form-field appearance="outline" class="addr-ff">
|
||||
<mat-label>Address Line 1</mat-label>
|
||||
<input matInput required formControlName="addressLine1" placeholder="House / Flat No., Floor, Building Name" />
|
||||
<input
|
||||
matInput
|
||||
required
|
||||
formControlName="addressLine1"
|
||||
placeholder="House / Flat No., Floor, Building Name"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="addr-grid addr-grid--1">
|
||||
@@ -155,9 +167,15 @@
|
||||
<div class="addr-grid addr-grid--2">
|
||||
<mat-form-field appearance="outline" class="addr-ff">
|
||||
<mat-label>PIN Code</mat-label>
|
||||
<input matInput required formControlName="pinCode" maxlength="6" placeholder="6-digit PIN"
|
||||
<input
|
||||
matInput
|
||||
required
|
||||
formControlName="pinCode"
|
||||
maxlength="6"
|
||||
placeholder="6-digit PIN"
|
||||
(input)="onPinInput('current')"
|
||||
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()" />
|
||||
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()"
|
||||
/>
|
||||
@if (currentForm.get('pinCode')?.hasError('pattern') && currentForm.get('pinCode')?.touched) {
|
||||
<mat-error>Enter a valid 6-digit PIN code</mat-error>
|
||||
}
|
||||
@@ -230,9 +248,7 @@
|
||||
|
||||
<!-- ═══ TAB 3: OFFICE / WORK ═══ -->
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<span class="addr-tab-num">3</span> Office / Work
|
||||
</ng-template>
|
||||
<ng-template mat-tab-label> <span class="addr-tab-num">3</span> Office / Work </ng-template>
|
||||
<div class="addr-panel" [formGroup]="officeForm">
|
||||
<div class="addr-grid addr-grid--2">
|
||||
<mat-form-field appearance="outline" class="addr-ff">
|
||||
@@ -268,9 +284,14 @@
|
||||
<mat-form-field appearance="outline" class="addr-ff">
|
||||
<mat-label>PIN Code</mat-label>
|
||||
<span matPrefix class="addr-prefix"><mat-icon>pin_drop</mat-icon></span>
|
||||
<input matInput formControlName="pinCode" maxlength="6" placeholder="6-digit PIN"
|
||||
<input
|
||||
matInput
|
||||
formControlName="pinCode"
|
||||
maxlength="6"
|
||||
placeholder="6-digit PIN"
|
||||
(input)="onPinInput('office')"
|
||||
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()" />
|
||||
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()"
|
||||
/>
|
||||
@if (officeForm.get('pinCode')?.hasError('pattern') && officeForm.get('pinCode')?.touched) {
|
||||
<mat-error>Enter a valid 6-digit PIN code</mat-error>
|
||||
}
|
||||
@@ -348,13 +369,13 @@
|
||||
|
||||
<!-- ═══ TAB 4: CORRESPONDENCE ═══ -->
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<span class="addr-tab-num">4</span> Correspondence
|
||||
</ng-template>
|
||||
<ng-template mat-tab-label> <span class="addr-tab-num">4</span> Correspondence </ng-template>
|
||||
<div class="addr-panel" [formGroup]="correspondenceForm">
|
||||
<div class="addr-copy-row">
|
||||
<span class="addr-copy-label">Copy address from:</span>
|
||||
<button mat-stroked-button type="button" class="addr-copy-btn" (click)="copyFrom('permanent')">Permanent</button>
|
||||
<button mat-stroked-button type="button" class="addr-copy-btn" (click)="copyFrom('permanent')">
|
||||
Permanent
|
||||
</button>
|
||||
<button mat-stroked-button type="button" class="addr-copy-btn" (click)="copyFrom('current')">Current</button>
|
||||
<button mat-stroked-button type="button" class="addr-copy-btn" (click)="copyFrom('office')">Office</button>
|
||||
</div>
|
||||
@@ -363,7 +384,12 @@
|
||||
<mat-form-field appearance="outline" class="addr-ff">
|
||||
<mat-label>Address Line 1</mat-label>
|
||||
<span matPrefix class="addr-prefix"><mat-icon>home</mat-icon></span>
|
||||
<input matInput required formControlName="addressLine1" placeholder="House / Flat No., Floor, Building Name" />
|
||||
<input
|
||||
matInput
|
||||
required
|
||||
formControlName="addressLine1"
|
||||
placeholder="House / Flat No., Floor, Building Name"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="addr-grid addr-grid--1">
|
||||
@@ -380,9 +406,15 @@
|
||||
<mat-form-field appearance="outline" class="addr-ff">
|
||||
<mat-label>PIN Code</mat-label>
|
||||
<span matPrefix class="addr-prefix"><mat-icon>pin_drop</mat-icon></span>
|
||||
<input matInput required formControlName="pinCode" maxlength="6" placeholder="6-digit PIN"
|
||||
<input
|
||||
matInput
|
||||
required
|
||||
formControlName="pinCode"
|
||||
maxlength="6"
|
||||
placeholder="6-digit PIN"
|
||||
(input)="onPinInput('correspondence')"
|
||||
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()" />
|
||||
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()"
|
||||
/>
|
||||
@if (correspondenceForm.get('pinCode')?.hasError('pattern') && correspondenceForm.get('pinCode')?.touched) {
|
||||
<mat-error>Enter a valid 6-digit PIN code</mat-error>
|
||||
}
|
||||
|
||||
+25
-7
@@ -1,3 +1,11 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*/
|
||||
@@ -38,7 +46,7 @@ $muted: #64748b;
|
||||
|
||||
&__sub {
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
color: rgb(255 255 255 / 50%);
|
||||
margin: 2px 0 0;
|
||||
}
|
||||
}
|
||||
@@ -76,9 +84,17 @@ $muted: #64748b;
|
||||
gap: 4px 16px;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&--1 { grid-template-columns: 1fr; }
|
||||
&--2 { grid-template-columns: 1fr 1fr; }
|
||||
&--3 { grid-template-columns: 1fr 1fr 1fr; }
|
||||
&--1 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
&--2 {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
&--3 {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.addr-ff {
|
||||
@@ -101,7 +117,8 @@ $muted: #64748b;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&::before, &::after {
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
@@ -173,8 +190,9 @@ $muted: #64748b;
|
||||
}
|
||||
|
||||
// Responsive
|
||||
@media (max-width: 768px) {
|
||||
.addr-grid--2, .addr-grid--3 {
|
||||
@media (width <= 768px) {
|
||||
.addr-grid--2,
|
||||
.addr-grid--3 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
+131
-37
@@ -42,27 +42,60 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
sameAsPermanent = false;
|
||||
|
||||
permanentForm: UntypedFormGroup = this.fb.group({
|
||||
addressLine1: ['', Validators.required],
|
||||
addressLine1: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
addressLine2: [''],
|
||||
pinCode: ['', [Validators.required, Validators.pattern(/^\d{6}$/)]],
|
||||
locality: ['', Validators.required],
|
||||
pinCode: [
|
||||
'',
|
||||
[
|
||||
Validators.required,
|
||||
Validators.pattern(/^\d{6}$/)
|
||||
]
|
||||
],
|
||||
locality: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
city: [''],
|
||||
district: [''],
|
||||
state: ['', Validators.required],
|
||||
state: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
country: [{ value: 'India', disabled: true }],
|
||||
landmark: [''],
|
||||
proofType: ['', Validators.required],
|
||||
proofType: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
proofDocNumber: ['']
|
||||
});
|
||||
|
||||
currentForm: UntypedFormGroup = this.fb.group({
|
||||
addressLine1: ['', Validators.required],
|
||||
addressLine1: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
addressLine2: [''],
|
||||
pinCode: ['', [Validators.required, Validators.pattern(/^\d{6}$/)]],
|
||||
locality: ['', Validators.required],
|
||||
pinCode: [
|
||||
'',
|
||||
[
|
||||
Validators.required,
|
||||
Validators.pattern(/^\d{6}$/)
|
||||
]
|
||||
],
|
||||
locality: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
city: [''],
|
||||
district: [''],
|
||||
state: ['', Validators.required],
|
||||
state: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
country: [{ value: 'India', disabled: true }],
|
||||
landmark: [''],
|
||||
residenceType: [''],
|
||||
@@ -74,7 +107,10 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
department: [''],
|
||||
addressLine1: [''],
|
||||
addressLine2: [''],
|
||||
pinCode: ['', Validators.pattern(/^\d{6}$/)],
|
||||
pinCode: [
|
||||
'',
|
||||
Validators.pattern(/^\d{6}$/)
|
||||
],
|
||||
locality: [''],
|
||||
city: [''],
|
||||
district: [''],
|
||||
@@ -87,13 +123,28 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
});
|
||||
|
||||
correspondenceForm: UntypedFormGroup = this.fb.group({
|
||||
addressLine1: ['', Validators.required],
|
||||
addressLine1: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
addressLine2: [''],
|
||||
pinCode: ['', [Validators.required, Validators.pattern(/^\d{6}$/)]],
|
||||
locality: ['', Validators.required],
|
||||
pinCode: [
|
||||
'',
|
||||
[
|
||||
Validators.required,
|
||||
Validators.pattern(/^\d{6}$/)
|
||||
]
|
||||
],
|
||||
locality: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
city: [''],
|
||||
district: [''],
|
||||
state: ['', Validators.required],
|
||||
state: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
country: [{ value: 'India', disabled: true }],
|
||||
landmark: [''],
|
||||
commMode: [''],
|
||||
@@ -116,18 +167,36 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
};
|
||||
|
||||
proofTypes = [
|
||||
'Aadhaar Card', 'Passport', 'Voter ID', 'Driving Licence',
|
||||
'Utility Bill (not older than 3 months)', 'Bank Statement'
|
||||
'Aadhaar Card',
|
||||
'Passport',
|
||||
'Voter ID',
|
||||
'Driving Licence',
|
||||
'Utility Bill (not older than 3 months)',
|
||||
'Bank Statement'
|
||||
];
|
||||
|
||||
residenceTypes = ['Owned', 'Rented', 'Leased', 'Company Provided', 'Parental / Family Owned'];
|
||||
residenceTypes = [
|
||||
'Owned',
|
||||
'Rented',
|
||||
'Leased',
|
||||
'Company Provided',
|
||||
'Parental / Family Owned'
|
||||
];
|
||||
|
||||
employmentTypes = [
|
||||
'Salaried (Private)', 'Salaried (Government / PSU)',
|
||||
'Self-Employed Professional', 'Business Owner', 'Retired'
|
||||
'Salaried (Private)',
|
||||
'Salaried (Government / PSU)',
|
||||
'Self-Employed Professional',
|
||||
'Business Owner',
|
||||
'Retired'
|
||||
];
|
||||
|
||||
commModes = ['Post / Courier', 'Email', 'SMS', 'WhatsApp'];
|
||||
commModes = [
|
||||
'Post / Courier',
|
||||
'Email',
|
||||
'SMS',
|
||||
'WhatsApp'
|
||||
];
|
||||
|
||||
private pinTimers: Record<string, any> = {};
|
||||
private pinCache: Record<string, any[]> = {};
|
||||
@@ -144,7 +213,12 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes['readOnly']) {
|
||||
const forms = [this.permanentForm, this.currentForm, this.officeForm, this.correspondenceForm];
|
||||
const forms = [
|
||||
this.permanentForm,
|
||||
this.currentForm,
|
||||
this.officeForm,
|
||||
this.correspondenceForm
|
||||
];
|
||||
for (const form of forms) {
|
||||
if (this.readOnly) {
|
||||
form.disable({ emitEvent: false });
|
||||
@@ -168,10 +242,19 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
|
||||
// Build type-to-tab mapping from address type names
|
||||
const tabMap: Record<string, string> = {
|
||||
permanent: 'permanent', 'permanent address': 'permanent',
|
||||
residential: 'current', current: 'current', 'current / residential': 'current', communication: 'current',
|
||||
office: 'office', 'office / work': 'office', business: 'office', work: 'office',
|
||||
correspondence: 'correspondence', mailing: 'correspondence', postal: 'correspondence'
|
||||
permanent: 'permanent',
|
||||
'permanent address': 'permanent',
|
||||
residential: 'current',
|
||||
current: 'current',
|
||||
'current / residential': 'current',
|
||||
communication: 'current',
|
||||
office: 'office',
|
||||
'office / work': 'office',
|
||||
business: 'office',
|
||||
work: 'office',
|
||||
correspondence: 'correspondence',
|
||||
mailing: 'correspondence',
|
||||
postal: 'correspondence'
|
||||
};
|
||||
|
||||
for (const addr of addresses) {
|
||||
@@ -192,7 +275,9 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
});
|
||||
}
|
||||
},
|
||||
error: () => { this.addressLoaded = true; }
|
||||
error: () => {
|
||||
this.addressLoaded = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -235,11 +320,13 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
|
||||
private applyPinData(tab: string, offices: any[]) {
|
||||
const seen = new Set<string>();
|
||||
this.localityOptions[tab] = offices.filter(o => {
|
||||
if (seen.has(o.Name)) return false;
|
||||
seen.add(o.Name);
|
||||
return true;
|
||||
}).map(o => o.Name);
|
||||
this.localityOptions[tab] = offices
|
||||
.filter((o) => {
|
||||
if (seen.has(o.Name)) return false;
|
||||
seen.add(o.Name);
|
||||
return true;
|
||||
})
|
||||
.map((o) => o.Name);
|
||||
|
||||
const form = this.getForm(tab);
|
||||
const f = offices[0];
|
||||
@@ -259,11 +346,16 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
|
||||
getForm(tab: string): UntypedFormGroup {
|
||||
switch (tab) {
|
||||
case 'permanent': return this.permanentForm;
|
||||
case 'current': return this.currentForm;
|
||||
case 'office': return this.officeForm;
|
||||
case 'correspondence': return this.correspondenceForm;
|
||||
default: return this.permanentForm;
|
||||
case 'permanent':
|
||||
return this.permanentForm;
|
||||
case 'current':
|
||||
return this.currentForm;
|
||||
case 'office':
|
||||
return this.officeForm;
|
||||
case 'correspondence':
|
||||
return this.correspondenceForm;
|
||||
default:
|
||||
return this.permanentForm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,7 +399,9 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
get addressData(): any {
|
||||
return {
|
||||
permanent: this.permanentForm.getRawValue(),
|
||||
current: this.sameAsPermanent ? { ...this.permanentForm.getRawValue(), sameAsPermanent: true } : this.currentForm.getRawValue(),
|
||||
current: this.sameAsPermanent
|
||||
? { ...this.permanentForm.getRawValue(), sameAsPermanent: true }
|
||||
: this.currentForm.getRawValue(),
|
||||
office: this.officeForm.getRawValue(),
|
||||
correspondence: this.correspondenceForm.getRawValue()
|
||||
};
|
||||
|
||||
@@ -305,23 +305,85 @@
|
||||
<mat-datepicker #incorpDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<!-- GSTIN (all non-individual) -->
|
||||
<mat-form-field appearance="outline" class="form-field">
|
||||
<mat-label>GSTIN Number</mat-label>
|
||||
<input
|
||||
matInput
|
||||
formControlName="gstinNumber"
|
||||
placeholder="15-character GSTIN"
|
||||
maxlength="15"
|
||||
style="text-transform: uppercase"
|
||||
(input)="
|
||||
kycForm.get('gstinNumber')?.setValue($any($event.target).value.toUpperCase(), { emitEvent: false })
|
||||
"
|
||||
/>
|
||||
@if (kycForm.get('gstinNumber')?.hasError('pattern') && kycForm.get('gstinNumber')?.touched) {
|
||||
<mat-error>Invalid GSTIN format (e.g. 22AAAAA0000A1Z5)</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<!-- GSTIN (all non-individual) with Verify -->
|
||||
<div class="form-field verify-field">
|
||||
<mat-form-field appearance="outline" class="verify-input">
|
||||
<mat-label>GSTIN Number</mat-label>
|
||||
<span matPrefix class="field-prefix-icon"><mat-icon>receipt_long</mat-icon></span>
|
||||
<input
|
||||
matInput
|
||||
formControlName="gstinNumber"
|
||||
placeholder="15-character GSTIN"
|
||||
maxlength="15"
|
||||
style="text-transform: uppercase"
|
||||
(input)="
|
||||
kycForm.get('gstinNumber')?.setValue($any($event.target).value.toUpperCase(), { emitEvent: false })
|
||||
"
|
||||
/>
|
||||
@if (kycForm.get('gstinNumber')?.hasError('pattern') && kycForm.get('gstinNumber')?.touched) {
|
||||
<mat-error>Invalid GSTIN format (e.g. 22AAAAA0000A1Z5)</mat-error>
|
||||
}
|
||||
@if (gstinVerifyStatus === 'VERIFIED') {
|
||||
<mat-icon matSuffix class="verify-icon-ok">check_circle</mat-icon>
|
||||
}
|
||||
@if (gstinVerifyStatus === 'FAILED') {
|
||||
<mat-icon matSuffix class="verify-icon-fail">cancel</mat-icon>
|
||||
}
|
||||
</mat-form-field>
|
||||
<div class="verify-action">
|
||||
@if (gstinVerifyStatus === 'NONE' || gstinVerifyStatus === 'FAILED') {
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
type="button"
|
||||
class="verify-btn-inline"
|
||||
[disabled]="
|
||||
readOnly ||
|
||||
!kycForm.get('gstinNumber')?.value ||
|
||||
kycForm.get('gstinNumber')?.value.length < 15 ||
|
||||
gstinVerifying ||
|
||||
!clientId
|
||||
"
|
||||
(click)="verifyGstin()"
|
||||
>
|
||||
@if (gstinVerifying) {
|
||||
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner>
|
||||
} @else {
|
||||
<fa-icon icon="shield-alt"></fa-icon> Verify GSTIN
|
||||
}
|
||||
</button>
|
||||
}
|
||||
@if (gstinVerifyStatus === 'VERIFIED') {
|
||||
<span class="verify-chip verified"><fa-icon icon="check-circle"></fa-icon> Verified</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@if (gstinVerifyStatus === 'VERIFIED') {
|
||||
<div class="pan-result-card full-width">
|
||||
<div class="pan-result-header">
|
||||
<fa-icon icon="check-circle" class="pan-result-ok-icon"></fa-icon>
|
||||
<span>GSTIN Verified Successfully</span>
|
||||
</div>
|
||||
<div class="pan-result-body">
|
||||
@if (gstinVerifyName) {
|
||||
<div class="pan-result-row">
|
||||
<span class="pan-result-label">Trade Name / Legal Name</span>
|
||||
<span class="pan-result-value name">{{ gstinVerifyName }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (gstinVerifyState) {
|
||||
<div class="pan-result-row">
|
||||
<span class="pan-result-label">State</span>
|
||||
<span class="pan-result-value">{{ gstinVerifyState }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="pan-result-row">
|
||||
<span class="pan-result-label">Verified On</span>
|
||||
<span class="pan-result-value date">{{ gstinVerifiedOn }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- PAN — Proprietorship uses Individual PAN, others use Enterprise PAN -->
|
||||
@if (constitutionMeta?.value === 'PROPRIETORSHIP') {
|
||||
|
||||
@@ -263,6 +263,13 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
entPanVerifyName = '';
|
||||
entPanVerifiedOn = '';
|
||||
|
||||
/** GSTIN verification */
|
||||
gstinVerifyStatus: 'NONE' | 'VERIFIED' | 'FAILED' = 'NONE';
|
||||
gstinVerifying = false;
|
||||
gstinVerifyName = '';
|
||||
gstinVerifiedOn = '';
|
||||
gstinVerifyState = '';
|
||||
|
||||
/** CIN verification (dummy) */
|
||||
cinVerifyStatus: 'NONE' | 'VERIFIED' | 'FAILED' = 'NONE';
|
||||
cinVerifying = false;
|
||||
@@ -480,6 +487,17 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
if (data.panAadhaarSeedingStatus) {
|
||||
this.panAadhaarSeedingStatus = data.panAadhaarSeedingStatus;
|
||||
}
|
||||
if (data.gstinVerified) {
|
||||
this.gstinVerifyStatus = 'VERIFIED';
|
||||
this.gstinVerifyName = data.gstinTradeName || data.gstinLegalName || '';
|
||||
this.gstinVerifyState = data.gstinState || '';
|
||||
const rawGstinDate = data.gstinVerifiedOn;
|
||||
if (Array.isArray(rawGstinDate) && rawGstinDate.length >= 3) {
|
||||
this.gstinVerifiedOn = `${rawGstinDate[2]?.toString().padStart(2, '0')}/${rawGstinDate[1]?.toString().padStart(2, '0')}/${rawGstinDate[0]}`;
|
||||
} else if (rawGstinDate) {
|
||||
this.gstinVerifiedOn = rawGstinDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Load existing photo & signature
|
||||
this.loadExistingPhoto();
|
||||
@@ -772,6 +790,71 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
/** Verify GSTIN via API */
|
||||
verifyGstin() {
|
||||
if (!this.clientId) return;
|
||||
const gstin = this.kycForm.value.gstinNumber;
|
||||
if (!gstin || this.kycForm.get('gstinNumber')?.invalid) return;
|
||||
|
||||
this.gstinVerifying = true;
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
|
||||
const payload: any = {
|
||||
locale: 'en',
|
||||
kycType: 1,
|
||||
riskCategory: this.kycForm.value.riskCategory || 1,
|
||||
gstinNumber: gstin,
|
||||
dateFormat,
|
||||
panNumber: this.kycForm.value.enterprisePan || this.kycForm.value.panNumber || null,
|
||||
aadhaarNumberMasked: 'XXXXXXXX0000'
|
||||
};
|
||||
|
||||
this.saveKycAndDo(
|
||||
payload,
|
||||
() => {
|
||||
this.clientsService.verifyGSTIN(this.clientId, this.kycId).subscribe({
|
||||
next: (res: any) => {
|
||||
this.gstinVerifying = false;
|
||||
const changes = res?.changes || res;
|
||||
if (changes?.gstinVerified === true) {
|
||||
this.gstinVerifyStatus = 'VERIFIED';
|
||||
this.gstinVerifyName = changes?.gstinTradeName || changes?.gstinLegalName || '';
|
||||
this.gstinVerifyState = changes?.gstinState || '';
|
||||
const rawDate = changes?.gstinVerifiedOn;
|
||||
if (Array.isArray(rawDate)) {
|
||||
this.gstinVerifiedOn = `${rawDate[2]?.toString().padStart(2, '0')}/${rawDate[1]?.toString().padStart(2, '0')}/${rawDate[0]}`;
|
||||
} else {
|
||||
const now = new Date();
|
||||
this.gstinVerifiedOn =
|
||||
rawDate ||
|
||||
`${now.getDate().toString().padStart(2, '0')}/${(now.getMonth() + 1).toString().padStart(2, '0')}/${now.getFullYear()}`;
|
||||
}
|
||||
} else {
|
||||
this.gstinVerifyStatus = 'FAILED';
|
||||
this.showPanErrorAlert(
|
||||
changes?.gstinVerificationMessage ||
|
||||
'GSTIN verification failed. Please check the GSTIN number and try again.'
|
||||
);
|
||||
}
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.gstinVerifying = false;
|
||||
this.gstinVerifyStatus = 'FAILED';
|
||||
this.showPanErrorAlert(
|
||||
err?.error?.errors?.[0]?.defaultUserMessage ||
|
||||
err?.error?.defaultUserMessage ||
|
||||
'GSTIN verification failed.'
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
(msg) => {
|
||||
this.gstinVerifying = false;
|
||||
this.showPanErrorAlert(msg);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/** Verify CIN (dummy — no real API) */
|
||||
verifyCin() {
|
||||
const cin = this.kycForm.value.cinNumber;
|
||||
|
||||
+12
-2
@@ -1,3 +1,11 @@
|
||||
<!--
|
||||
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/.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
-->
|
||||
@@ -20,11 +28,13 @@
|
||||
|
||||
<div class="crop-workspace" [class.hidden]="!imageLoaded">
|
||||
<div class="crop-source">
|
||||
<canvas #sourceCanvas
|
||||
<canvas
|
||||
#sourceCanvas
|
||||
(mousedown)="onMouseDown($event)"
|
||||
(mousemove)="onMouseMove($event)"
|
||||
(mouseup)="onMouseUp()"
|
||||
(mouseleave)="onMouseUp()">
|
||||
(mouseleave)="onMouseUp()"
|
||||
>
|
||||
</canvas>
|
||||
<div class="crop-hint">Drag to move. Drag corner to resize.</div>
|
||||
</div>
|
||||
|
||||
+9
-1
@@ -1,3 +1,11 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*/
|
||||
@@ -158,7 +166,7 @@ $navy: #0f172a;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.crop-workspace {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
+55
-21
@@ -1,8 +1,12 @@
|
||||
/**
|
||||
* 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, Inject, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
|
||||
import { Component, inject, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogContent, MatDialogActions } from '@angular/material/dialog';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
@@ -59,10 +63,8 @@ export class ImageCropDialogComponent implements AfterViewInit {
|
||||
|
||||
zoom = 1;
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<ImageCropDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: ImageCropData
|
||||
) {}
|
||||
dialogRef = inject(MatDialogRef<ImageCropDialogComponent>);
|
||||
data = inject<ImageCropData>(MAT_DIALOG_DATA);
|
||||
|
||||
ngAfterViewInit() {
|
||||
const reader = new FileReader();
|
||||
@@ -127,9 +129,14 @@ export class ImageCropDialogComponent implements AfterViewInit {
|
||||
ctx.clearRect(this.cropX, this.cropY, this.cropW, this.cropH);
|
||||
ctx.drawImage(
|
||||
this.img,
|
||||
this.cropX * this.scaleX, this.cropY * this.scaleY,
|
||||
this.cropW * this.scaleX, this.cropH * this.scaleY,
|
||||
this.cropX, this.cropY, this.cropW, this.cropH
|
||||
this.cropX * this.scaleX,
|
||||
this.cropY * this.scaleY,
|
||||
this.cropW * this.scaleX,
|
||||
this.cropH * this.scaleY,
|
||||
this.cropX,
|
||||
this.cropY,
|
||||
this.cropW,
|
||||
this.cropH
|
||||
);
|
||||
|
||||
// Crop border
|
||||
@@ -141,14 +148,31 @@ export class ImageCropDialogComponent implements AfterViewInit {
|
||||
const hs = 8;
|
||||
ctx.fillStyle = '#1353a0';
|
||||
const corners = [
|
||||
[this.cropX, this.cropY],
|
||||
[this.cropX + this.cropW, this.cropY],
|
||||
[this.cropX, this.cropY + this.cropH],
|
||||
[this.cropX + this.cropW, this.cropY + this.cropH]
|
||||
[
|
||||
this.cropX,
|
||||
this.cropY
|
||||
],
|
||||
[
|
||||
this.cropX + this.cropW,
|
||||
this.cropY
|
||||
],
|
||||
[
|
||||
this.cropX,
|
||||
this.cropY + this.cropH
|
||||
],
|
||||
[
|
||||
this.cropX + this.cropW,
|
||||
this.cropY + this.cropH
|
||||
]
|
||||
];
|
||||
corners.forEach(([x, y]) => {
|
||||
ctx.fillRect(x - hs / 2, y - hs / 2, hs, hs);
|
||||
});
|
||||
corners.forEach(
|
||||
([
|
||||
x,
|
||||
y
|
||||
]) => {
|
||||
ctx.fillRect(x - hs / 2, y - hs / 2, hs, hs);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private updatePreview() {
|
||||
@@ -163,9 +187,14 @@ export class ImageCropDialogComponent implements AfterViewInit {
|
||||
pCtx.clearRect(0, 0, pw, ph);
|
||||
pCtx.drawImage(
|
||||
this.img,
|
||||
this.cropX * this.scaleX, this.cropY * this.scaleY,
|
||||
this.cropW * this.scaleX, this.cropH * this.scaleY,
|
||||
0, 0, pw, ph
|
||||
this.cropX * this.scaleX,
|
||||
this.cropY * this.scaleY,
|
||||
this.cropW * this.scaleX,
|
||||
this.cropH * this.scaleY,
|
||||
0,
|
||||
0,
|
||||
pw,
|
||||
ph
|
||||
);
|
||||
}
|
||||
|
||||
@@ -229,9 +258,14 @@ export class ImageCropDialogComponent implements AfterViewInit {
|
||||
const ctx = offscreen.getContext('2d')!;
|
||||
ctx.drawImage(
|
||||
this.img,
|
||||
this.cropX * this.scaleX, this.cropY * this.scaleY,
|
||||
this.cropW * this.scaleX, this.cropH * this.scaleY,
|
||||
0, 0, offscreen.width, offscreen.height
|
||||
this.cropX * this.scaleX,
|
||||
this.cropY * this.scaleY,
|
||||
this.cropW * this.scaleX,
|
||||
this.cropH * this.scaleY,
|
||||
0,
|
||||
0,
|
||||
offscreen.width,
|
||||
offscreen.height
|
||||
);
|
||||
|
||||
offscreen.toBlob((blob) => {
|
||||
|
||||
+387
-162
@@ -88,14 +88,18 @@
|
||||
}
|
||||
@if (client?.dateOfBirth) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ client.legalFormId === LegalFormId.PERSON ? 'Date of Birth' : 'Incorporation Date' }}</span>
|
||||
<span class="preview-label">{{
|
||||
client.legalFormId === LegalFormId.PERSON ? 'Date of Birth' : 'Incorporation Date'
|
||||
}}</span>
|
||||
<span class="preview-value">{{ client.dateOfBirth | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (client?.legalFormId === LegalFormId.PERSON && client?.genderId) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Gender</span>
|
||||
<span class="preview-value">{{ client.genderId | find: clientTemplate.genderOptions : 'id' : 'name' }}</span>
|
||||
<span class="preview-value">{{
|
||||
client.genderId | find: clientTemplate.genderOptions : 'id' : 'name'
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
@if (client?.legalFormId) {
|
||||
@@ -107,13 +111,17 @@
|
||||
@if (client?.officeId) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Office</span>
|
||||
<span class="preview-value">{{ client.officeId | find: clientTemplate.officeOptions : 'id' : 'name' }}</span>
|
||||
<span class="preview-value">{{
|
||||
client.officeId | find: clientTemplate.officeOptions : 'id' : 'name'
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
@if (client?.staffId) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Staff</span>
|
||||
<span class="preview-value">{{ client.staffId | find: clientTemplate.staffOptions : 'id' : 'displayName' }}</span>
|
||||
<span class="preview-value">{{
|
||||
client.staffId | find: clientTemplate.staffOptions : 'id' : 'displayName'
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
@if (client?.legalFormId === LegalFormId.PERSON) {
|
||||
@@ -137,7 +145,9 @@
|
||||
@if (client?.salutationId) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Salutation</span>
|
||||
<span class="preview-value">{{ client.salutationId | find: clientTemplate.salutationOptions : 'id' : 'name' }}</span>
|
||||
<span class="preview-value">{{
|
||||
client.salutationId | find: clientTemplate.salutationOptions : 'id' : 'name'
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
@if (client?.ageYears != null) {
|
||||
@@ -150,7 +160,11 @@
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Minor</span>
|
||||
<span class="preview-value">
|
||||
<span [class]="client.isMinor ? 'preview-badge preview-badge--danger' : 'preview-badge preview-badge--verified'">
|
||||
<span
|
||||
[class]="
|
||||
client.isMinor ? 'preview-badge preview-badge--danger' : 'preview-badge preview-badge--verified'
|
||||
"
|
||||
>
|
||||
{{ client.isMinor ? 'Yes (Age < 10)' : 'No' }}
|
||||
</span>
|
||||
</span>
|
||||
@@ -172,13 +186,19 @@
|
||||
@if (client.clientNonPersonDetails.constitutionId) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Constitution</span>
|
||||
<span class="preview-value">{{ client.clientNonPersonDetails.constitutionId | find: clientTemplate.clientNonPersonConstitutionOptions : 'id' : 'name' }}</span>
|
||||
<span class="preview-value">{{
|
||||
client.clientNonPersonDetails.constitutionId
|
||||
| find: clientTemplate.clientNonPersonConstitutionOptions : 'id' : 'name'
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
@if (client.clientNonPersonDetails.mainBusinessLineId) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Main Business Line</span>
|
||||
<span class="preview-value">{{ client.clientNonPersonDetails.mainBusinessLineId | find: clientTemplate.clientNonPersonMainBusinessLineOptions : 'id' : 'name' }}</span>
|
||||
<span class="preview-value">{{
|
||||
client.clientNonPersonDetails.mainBusinessLineId
|
||||
| find: clientTemplate.clientNonPersonMainBusinessLineOptions : 'id' : 'name'
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
@if (client.clientNonPersonDetails.incorpNumber) {
|
||||
@@ -215,7 +235,11 @@
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">DIP Verification</span>
|
||||
<span class="preview-value">
|
||||
<span [class]="client?.dipVerified ? 'preview-badge preview-badge--verified' : 'preview-badge preview-badge--pending'">
|
||||
<span
|
||||
[class]="
|
||||
client?.dipVerified ? 'preview-badge preview-badge--verified' : 'preview-badge preview-badge--pending'
|
||||
"
|
||||
>
|
||||
{{ client?.dipVerified ? 'Verified' : 'Pending' }}
|
||||
</span>
|
||||
</span>
|
||||
@@ -236,7 +260,13 @@
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Alternate DIP Verification</span>
|
||||
<span class="preview-value">
|
||||
<span [class]="client?.alternateDipVerified ? 'preview-badge preview-badge--verified' : 'preview-badge preview-badge--pending'">
|
||||
<span
|
||||
[class]="
|
||||
client?.alternateDipVerified
|
||||
? 'preview-badge preview-badge--verified'
|
||||
: 'preview-badge preview-badge--pending'
|
||||
"
|
||||
>
|
||||
{{ client?.alternateDipVerified ? 'Verified' : 'Pending' }}
|
||||
</span>
|
||||
</span>
|
||||
@@ -257,8 +287,18 @@
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Name Verification (Sanctions)</span>
|
||||
<span class="preview-value">
|
||||
<span [class]="client?.nameVerified ? 'preview-badge preview-badge--verified' : 'preview-badge preview-badge--danger'">
|
||||
{{ client?.nameVerified ? 'Verified — Clear' : (client?.nameVerified === false ? 'Sanctions Match' : 'Pending') }}
|
||||
<span
|
||||
[class]="
|
||||
client?.nameVerified ? 'preview-badge preview-badge--verified' : 'preview-badge preview-badge--danger'
|
||||
"
|
||||
>
|
||||
{{
|
||||
client?.nameVerified
|
||||
? 'Verified — Clear'
|
||||
: client?.nameVerified === false
|
||||
? 'Sanctions Match'
|
||||
: 'Pending'
|
||||
}}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -274,61 +314,82 @@
|
||||
|
||||
<!-- Financial & Classification (Individual only) -->
|
||||
@if (client?.legalFormId !== 2) {
|
||||
<div class="info-card">
|
||||
<div class="card-header">
|
||||
<fa-icon icon="chart-line" class="card-icon"></fa-icon>
|
||||
<span>Financial & Classification</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="preview-grid">
|
||||
@if (client?.occupationCategoryId) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Occupation Category</span>
|
||||
<span class="preview-value">{{ client.occupationCategoryId | find: clientTemplate.occupationCategoryOptions : 'id' : 'name' }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (client?.occupationId) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Occupation</span>
|
||||
<span class="preview-value">{{ client.occupationId | find: clientTemplate.occupationOptions : 'id' : 'name' }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (client?.clientTypeId) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Client Type</span>
|
||||
<span class="preview-value">{{ client.clientTypeId | find: clientTemplate.clientTypeOptions : 'id' : 'name' }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (client?.clientClassificationId) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Client Classification</span>
|
||||
<span class="preview-value">{{ client.clientClassificationId | find: clientTemplate.clientClassificationOptions : 'id' : 'name' }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (client?.monthlyIncome) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Monthly Income</span>
|
||||
<span class="preview-value">₹ {{ client.monthlyIncome | number }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (client?.annualTurnover) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Annual Turnover</span>
|
||||
<span class="preview-value">₹ {{ client.annualTurnover | number }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (!client?.occupationCategoryId && !client?.occupationId && !client?.clientTypeId && !client?.clientClassificationId && !client?.monthlyIncome && !client?.annualTurnover) {
|
||||
<div class="preview-field full-width">
|
||||
<span class="preview-value no-data">No classification data provided</span>
|
||||
</div>
|
||||
}
|
||||
<div class="info-card">
|
||||
<div class="card-header">
|
||||
<fa-icon icon="chart-line" class="card-icon"></fa-icon>
|
||||
<span>Financial & Classification</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="preview-grid">
|
||||
@if (client?.occupationCategoryId) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Occupation Category</span>
|
||||
<span class="preview-value">{{
|
||||
client.occupationCategoryId | find: clientTemplate.occupationCategoryOptions : 'id' : 'name'
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
@if (client?.occupationId) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Occupation</span>
|
||||
<span class="preview-value">{{
|
||||
client.occupationId | find: clientTemplate.occupationOptions : 'id' : 'name'
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
@if (client?.clientTypeId) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Client Type</span>
|
||||
<span class="preview-value">{{
|
||||
client.clientTypeId | find: clientTemplate.clientTypeOptions : 'id' : 'name'
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
@if (client?.clientClassificationId) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Client Classification</span>
|
||||
<span class="preview-value">{{
|
||||
client.clientClassificationId | find: clientTemplate.clientClassificationOptions : 'id' : 'name'
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
@if (client?.monthlyIncome) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Monthly Income</span>
|
||||
<span class="preview-value">₹ {{ client.monthlyIncome | number }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (client?.annualTurnover) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Annual Turnover</span>
|
||||
<span class="preview-value">₹ {{ client.annualTurnover | number }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (
|
||||
!client?.occupationCategoryId &&
|
||||
!client?.occupationId &&
|
||||
!client?.clientTypeId &&
|
||||
!client?.clientClassificationId &&
|
||||
!client?.monthlyIncome &&
|
||||
!client?.annualTurnover
|
||||
) {
|
||||
<div class="preview-field full-width">
|
||||
<span class="preview-value no-data">No classification data provided</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- KYC Details -->
|
||||
@if (kycData?.panNumber || kycData?.aadhaarNumberMasked || kycData?.gstinNumber || kycData?.enterprisePan || kycData?.cinNumber) {
|
||||
@if (
|
||||
kycData?.panNumber ||
|
||||
kycData?.aadhaarNumberMasked ||
|
||||
kycData?.gstinNumber ||
|
||||
kycData?.enterprisePan ||
|
||||
kycData?.cinNumber
|
||||
) {
|
||||
<div class="info-card">
|
||||
<div class="card-header">
|
||||
<fa-icon icon="fingerprint" class="card-icon"></fa-icon>
|
||||
@@ -391,9 +452,13 @@
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Risk Category</span>
|
||||
<span class="preview-value">
|
||||
@if (kycData.riskCategory === 1) { Low Risk }
|
||||
@else if (kycData.riskCategory === 2) { Medium Risk }
|
||||
@else if (kycData.riskCategory === 3) { High Risk }
|
||||
@if (kycData.riskCategory === 1) {
|
||||
Low Risk
|
||||
} @else if (kycData.riskCategory === 2) {
|
||||
Medium Risk
|
||||
} @else if (kycData.riskCategory === 3) {
|
||||
High Risk
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@@ -414,7 +479,7 @@
|
||||
<div class="card-header">
|
||||
<fa-icon icon="user-shield" class="card-icon"></fa-icon>
|
||||
<span>Guardian Details</span>
|
||||
<span class="preview-badge preview-badge--info" style="margin-left:auto">Minor</span>
|
||||
<span class="preview-badge preview-badge--info" style="margin-left: auto">Minor</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="preview-grid">
|
||||
@@ -450,7 +515,13 @@
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Status</span>
|
||||
<span class="preview-value">
|
||||
<span [class]="guardian.status?.value === 'Active' ? 'preview-badge preview-badge--verified' : 'preview-badge preview-badge--pending'">
|
||||
<span
|
||||
[class]="
|
||||
guardian.status?.value === 'Active'
|
||||
? 'preview-badge preview-badge--verified'
|
||||
: 'preview-badge preview-badge--pending'
|
||||
"
|
||||
>
|
||||
{{ guardian.status.value }}
|
||||
</span>
|
||||
</span>
|
||||
@@ -524,7 +595,12 @@
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if (!addressData.permanent?.addressLine1 && !addressData.current?.addressLine1 && !addressData.office?.addressLine1 && !addressData.correspondence?.addressLine1) {
|
||||
@if (
|
||||
!addressData.permanent?.addressLine1 &&
|
||||
!addressData.current?.addressLine1 &&
|
||||
!addressData.office?.addressLine1 &&
|
||||
!addressData.correspondence?.addressLine1
|
||||
) {
|
||||
<div class="preview-field full-width">
|
||||
<span class="preview-value no-data">No address data provided</span>
|
||||
</div>
|
||||
@@ -563,40 +639,76 @@
|
||||
<div class="card-body">
|
||||
<div class="preview-grid">
|
||||
@if (entityClassificationData.msme.udyamRegistrationNumber) {
|
||||
<div class="preview-field"><span class="preview-label">Udyam Registration No.</span><span class="preview-value mono">{{ entityClassificationData.msme.udyamRegistrationNumber }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Udyam Registration No.</span
|
||||
><span class="preview-value mono">{{ entityClassificationData.msme.udyamRegistrationNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.msme.msmeClassification) {
|
||||
<div class="preview-field"><span class="preview-label">MSME Classification</span><span class="preview-value">{{ entityClassificationData.msme.msmeClassification }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">MSME Classification</span
|
||||
><span class="preview-value">{{ entityClassificationData.msme.msmeClassification }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.msme.enterpriseType) {
|
||||
<div class="preview-field"><span class="preview-label">Enterprise Type</span><span class="preview-value">{{ entityClassificationData.msme.enterpriseType }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Enterprise Type</span
|
||||
><span class="preview-value">{{ entityClassificationData.msme.enterpriseType }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.msme.nicActivityCode) {
|
||||
<div class="preview-field"><span class="preview-label">NIC Activity Code</span><span class="preview-value mono">{{ entityClassificationData.msme.nicActivityCode }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">NIC Activity Code</span
|
||||
><span class="preview-value mono">{{ entityClassificationData.msme.nicActivityCode }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.msme.annualTurnover) {
|
||||
<div class="preview-field"><span class="preview-label">Annual Turnover</span><span class="preview-value">₹ {{ entityClassificationData.msme.annualTurnover }} Cr</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Annual Turnover</span
|
||||
><span class="preview-value">₹ {{ entityClassificationData.msme.annualTurnover }} Cr</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.msme.investmentPlantMachinery) {
|
||||
<div class="preview-field"><span class="preview-label">Investment (Plant & Machinery)</span><span class="preview-value">₹ {{ entityClassificationData.msme.investmentPlantMachinery }} Cr</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Investment (Plant & Machinery)</span
|
||||
><span class="preview-value">₹ {{ entityClassificationData.msme.investmentPlantMachinery }} Cr</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.msme.financialYearAssessment) {
|
||||
<div class="preview-field"><span class="preview-label">FY of Assessment</span><span class="preview-value">{{ entityClassificationData.msme.financialYearAssessment }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">FY of Assessment</span
|
||||
><span class="preview-value">{{ entityClassificationData.msme.financialYearAssessment }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.msme.socialCategoryOwner) {
|
||||
<div class="preview-field"><span class="preview-label">Social Category</span><span class="preview-value">{{ entityClassificationData.msme.socialCategoryOwner }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Social Category</span
|
||||
><span class="preview-value">{{ entityClassificationData.msme.socialCategoryOwner }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.msme.genderPrimaryOwner) {
|
||||
<div class="preview-field"><span class="preview-label">Gender of Owner</span><span class="preview-value">{{ entityClassificationData.msme.genderPrimaryOwner }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Gender of Owner</span
|
||||
><span class="preview-value">{{ entityClassificationData.msme.genderPrimaryOwner }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.msme.noOfEmployees) {
|
||||
<div class="preview-field"><span class="preview-label">No. of Employees</span><span class="preview-value">{{ entityClassificationData.msme.noOfEmployees }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">No. of Employees</span
|
||||
><span class="preview-value">{{ entityClassificationData.msme.noOfEmployees }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.msme.womenLedEnterprise) {
|
||||
<div class="preview-field"><span class="preview-label">Women-led Enterprise</span><span class="preview-value"><span class="preview-badge preview-badge--verified">Yes</span></span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Women-led Enterprise</span
|
||||
><span class="preview-value"><span class="preview-badge preview-badge--verified">Yes</span></span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.msme.divyangPwdOwned) {
|
||||
<div class="preview-field"><span class="preview-label">Divyang (PWD) Owned</span><span class="preview-value"><span class="preview-badge preview-badge--verified">Yes</span></span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Divyang (PWD) Owned</span
|
||||
><span class="preview-value"><span class="preview-badge preview-badge--verified">Yes</span></span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -613,19 +725,34 @@
|
||||
<div class="card-body">
|
||||
<div class="preview-grid">
|
||||
@if (entityClassificationData.startup.dpiitRecognitionNumber) {
|
||||
<div class="preview-field"><span class="preview-label">DPIIT Recognition No.</span><span class="preview-value mono">{{ entityClassificationData.startup.dpiitRecognitionNumber }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">DPIIT Recognition No.</span
|
||||
><span class="preview-value mono">{{ entityClassificationData.startup.dpiitRecognitionNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.startup.entityStage) {
|
||||
<div class="preview-field"><span class="preview-label">Entity Stage</span><span class="preview-value">{{ entityClassificationData.startup.entityStage }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Entity Stage</span
|
||||
><span class="preview-value">{{ entityClassificationData.startup.entityStage }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.startup.sectorAsDpiit) {
|
||||
<div class="preview-field"><span class="preview-label">Sector (DPIIT)</span><span class="preview-value">{{ entityClassificationData.startup.sectorAsDpiit }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Sector (DPIIT)</span
|
||||
><span class="preview-value">{{ entityClassificationData.startup.sectorAsDpiit }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.startup.taxExemption80iac) {
|
||||
<div class="preview-field"><span class="preview-label">80IAC Tax Exemption</span><span class="preview-value"><span class="preview-badge preview-badge--verified">Yes</span></span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">80IAC Tax Exemption</span
|
||||
><span class="preview-value"><span class="preview-badge preview-badge--verified">Yes</span></span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.startup.pslLoanLimit) {
|
||||
<div class="preview-field"><span class="preview-label">PSL Loan Limit</span><span class="preview-value">₹ {{ entityClassificationData.startup.pslLoanLimit | number }} Cr</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">PSL Loan Limit</span
|
||||
><span class="preview-value">₹ {{ entityClassificationData.startup.pslLoanLimit | number }} Cr</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -642,22 +769,40 @@
|
||||
<div class="card-body">
|
||||
<div class="preview-grid">
|
||||
@if (entityClassificationData.mudra.mudraCategory) {
|
||||
<div class="preview-field"><span class="preview-label">Mudra Category</span><span class="preview-value">{{ entityClassificationData.mudra.mudraCategory }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Mudra Category</span
|
||||
><span class="preview-value">{{ entityClassificationData.mudra.mudraCategory }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.mudra.mudraLoanAccountNo) {
|
||||
<div class="preview-field"><span class="preview-label">Loan Account No.</span><span class="preview-value mono">{{ entityClassificationData.mudra.mudraLoanAccountNo }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Loan Account No.</span
|
||||
><span class="preview-value mono">{{ entityClassificationData.mudra.mudraLoanAccountNo }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.mudra.sanctionedAmount) {
|
||||
<div class="preview-field"><span class="preview-label">Sanctioned Amount</span><span class="preview-value">₹ {{ entityClassificationData.mudra.sanctionedAmount | number }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Sanctioned Amount</span
|
||||
><span class="preview-value">₹ {{ entityClassificationData.mudra.sanctionedAmount | number }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.mudra.lendingInstitutionName) {
|
||||
<div class="preview-field"><span class="preview-label">Lending Institution</span><span class="preview-value">{{ entityClassificationData.mudra.lendingInstitutionName }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Lending Institution</span
|
||||
><span class="preview-value">{{ entityClassificationData.mudra.lendingInstitutionName }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.mudra.repaymentStatus) {
|
||||
<div class="preview-field"><span class="preview-label">Repayment Status</span><span class="preview-value">{{ entityClassificationData.mudra.repaymentStatus }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Repayment Status</span
|
||||
><span class="preview-value">{{ entityClassificationData.mudra.repaymentStatus }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.mudra.npaFlag) {
|
||||
<div class="preview-field"><span class="preview-label">NPA Flag</span><span class="preview-value"><span class="preview-badge preview-badge--danger">NPA</span></span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">NPA Flag</span
|
||||
><span class="preview-value"><span class="preview-badge preview-badge--danger">NPA</span></span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -674,19 +819,36 @@
|
||||
<div class="card-body">
|
||||
<div class="preview-grid">
|
||||
@if (entityClassificationData.eou.eouRegistrationNumber) {
|
||||
<div class="preview-field"><span class="preview-label">EOU Registration No.</span><span class="preview-value mono">{{ entityClassificationData.eou.eouRegistrationNumber }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">EOU Registration No.</span
|
||||
><span class="preview-value mono">{{ entityClassificationData.eou.eouRegistrationNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.eou.registeredDcZone) {
|
||||
<div class="preview-field"><span class="preview-label">Registered DC Zone</span><span class="preview-value">{{ entityClassificationData.eou.registeredDcZone }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Registered DC Zone</span
|
||||
><span class="preview-value">{{ entityClassificationData.eou.registeredDcZone }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.eou.gstinEou) {
|
||||
<div class="preview-field"><span class="preview-label">GSTIN (EOU)</span><span class="preview-value mono">{{ entityClassificationData.eou.gstinEou }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">GSTIN (EOU)</span
|
||||
><span class="preview-value mono">{{ entityClassificationData.eou.gstinEou }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.eou.annualExportObligation) {
|
||||
<div class="preview-field"><span class="preview-label">Export Obligation</span><span class="preview-value">₹ {{ entityClassificationData.eou.annualExportObligation | number }} Cr</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Export Obligation</span
|
||||
><span class="preview-value"
|
||||
>₹ {{ entityClassificationData.eou.annualExportObligation | number }} Cr</span
|
||||
>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.eou.adCategoryBankCode) {
|
||||
<div class="preview-field"><span class="preview-label">AD Bank Code</span><span class="preview-value mono">{{ entityClassificationData.eou.adCategoryBankCode }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">AD Bank Code</span
|
||||
><span class="preview-value mono">{{ entityClassificationData.eou.adCategoryBankCode }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -703,22 +865,40 @@
|
||||
<div class="card-body">
|
||||
<div class="preview-grid">
|
||||
@if (entityClassificationData.importexport.iecCode) {
|
||||
<div class="preview-field"><span class="preview-label">IEC Code</span><span class="preview-value mono">{{ entityClassificationData.importexport.iecCode }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">IEC Code</span
|
||||
><span class="preview-value mono">{{ entityClassificationData.importexport.iecCode }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.importexport.iecStatus) {
|
||||
<div class="preview-field"><span class="preview-label">IEC Status</span><span class="preview-value">{{ entityClassificationData.importexport.iecStatus }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">IEC Status</span
|
||||
><span class="preview-value">{{ entityClassificationData.importexport.iecStatus }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.importexport.tradeType) {
|
||||
<div class="preview-field"><span class="preview-label">Trade Type</span><span class="preview-value">{{ entityClassificationData.importexport.tradeType }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Trade Type</span
|
||||
><span class="preview-value">{{ entityClassificationData.importexport.tradeType }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.importexport.primaryHsCode) {
|
||||
<div class="preview-field"><span class="preview-label">Primary HS Code</span><span class="preview-value mono">{{ entityClassificationData.importexport.primaryHsCode }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Primary HS Code</span
|
||||
><span class="preview-value mono">{{ entityClassificationData.importexport.primaryHsCode }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.importexport.eefcAccountRequired) {
|
||||
<div class="preview-field"><span class="preview-label">EEFC Account</span><span class="preview-value"><span class="preview-badge preview-badge--verified">Required</span></span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">EEFC Account</span
|
||||
><span class="preview-value"><span class="preview-badge preview-badge--verified">Required</span></span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.importexport.femaDeclarationObtained) {
|
||||
<div class="preview-field"><span class="preview-label">FEMA Declaration</span><span class="preview-value"><span class="preview-badge preview-badge--verified">Obtained</span></span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">FEMA Declaration</span
|
||||
><span class="preview-value"><span class="preview-badge preview-badge--verified">Obtained</span></span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -735,22 +915,40 @@
|
||||
<div class="card-body">
|
||||
<div class="preview-grid">
|
||||
@if (entityClassificationData.psl.pslCategory) {
|
||||
<div class="preview-field"><span class="preview-label">PSL Category</span><span class="preview-value">{{ entityClassificationData.psl.pslCategory }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">PSL Category</span
|
||||
><span class="preview-value">{{ entityClassificationData.psl.pslCategory }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.psl.pslSubCategory) {
|
||||
<div class="preview-field"><span class="preview-label">PSL Sub-category</span><span class="preview-value">{{ entityClassificationData.psl.pslSubCategory }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">PSL Sub-category</span
|
||||
><span class="preview-value">{{ entityClassificationData.psl.pslSubCategory }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.psl.pslSanctionedLimit) {
|
||||
<div class="preview-field"><span class="preview-label">Sanctioned Limit</span><span class="preview-value">₹ {{ entityClassificationData.psl.pslSanctionedLimit | number }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Sanctioned Limit</span
|
||||
><span class="preview-value">₹ {{ entityClassificationData.psl.pslSanctionedLimit | number }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.psl.fiddReportingCode) {
|
||||
<div class="preview-field"><span class="preview-label">FIDD Reporting Code</span><span class="preview-value mono">{{ entityClassificationData.psl.fiddReportingCode }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">FIDD Reporting Code</span
|
||||
><span class="preview-value mono">{{ entityClassificationData.psl.fiddReportingCode }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.psl.weakerSectionFlag) {
|
||||
<div class="preview-field"><span class="preview-label">Weaker Section</span><span class="preview-value"><span class="preview-badge preview-badge--verified">Yes</span></span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Weaker Section</span
|
||||
><span class="preview-value"><span class="preview-badge preview-badge--verified">Yes</span></span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.psl.womenEntrepreneurFlag) {
|
||||
<div class="preview-field"><span class="preview-label">Women Entrepreneur</span><span class="preview-value"><span class="preview-badge preview-badge--verified">Yes</span></span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Women Entrepreneur</span
|
||||
><span class="preview-value"><span class="preview-badge preview-badge--verified">Yes</span></span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -767,25 +965,46 @@
|
||||
<div class="card-body">
|
||||
<div class="preview-grid">
|
||||
@if (entityClassificationData.nps.industrySector) {
|
||||
<div class="preview-field"><span class="preview-label">Industry Sector</span><span class="preview-value">{{ entityClassificationData.nps.industrySector }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Industry Sector</span
|
||||
><span class="preview-value">{{ entityClassificationData.nps.industrySector }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.nps.nicCode) {
|
||||
<div class="preview-field"><span class="preview-label">NIC Code</span><span class="preview-value mono">{{ entityClassificationData.nps.nicCode }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">NIC Code</span
|
||||
><span class="preview-value mono">{{ entityClassificationData.nps.nicCode }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.nps.annualRevenueBand) {
|
||||
<div class="preview-field"><span class="preview-label">Annual Revenue Band</span><span class="preview-value">{{ entityClassificationData.nps.annualRevenueBand }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Annual Revenue Band</span
|
||||
><span class="preview-value">{{ entityClassificationData.nps.annualRevenueBand }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.nps.riskCategory) {
|
||||
<div class="preview-field"><span class="preview-label">Risk Category</span><span class="preview-value">{{ entityClassificationData.nps.riskCategory }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Risk Category</span
|
||||
><span class="preview-value">{{ entityClassificationData.nps.riskCategory }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.nps.amlRiskClassification) {
|
||||
<div class="preview-field"><span class="preview-label">AML Risk</span><span class="preview-value">{{ entityClassificationData.nps.amlRiskClassification }}</span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">AML Risk</span
|
||||
><span class="preview-value">{{ entityClassificationData.nps.amlRiskClassification }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.nps.pepFlag) {
|
||||
<div class="preview-field"><span class="preview-label">PEP Flag</span><span class="preview-value"><span class="preview-badge preview-badge--danger">PEP</span></span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">PEP Flag</span
|
||||
><span class="preview-value"><span class="preview-badge preview-badge--danger">PEP</span></span>
|
||||
</div>
|
||||
}
|
||||
@if (entityClassificationData.nps.amlWatchlistMatch) {
|
||||
<div class="preview-field"><span class="preview-label">AML Watchlist</span><span class="preview-value"><span class="preview-badge preview-badge--danger">Match Found</span></span></div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">AML Watchlist</span
|
||||
><span class="preview-value"><span class="preview-badge preview-badge--danger">Match Found</span></span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -793,58 +1012,64 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<!-- Family Members (Individual only) -->
|
||||
@if (client?.legalFormId !== 2) {
|
||||
<div class="info-card">
|
||||
<div class="card-header">
|
||||
<fa-icon icon="users" class="card-icon"></fa-icon>
|
||||
<span>Family Members</span>
|
||||
@if (familyMembers.length > 0) {
|
||||
<span class="count-badge">{{ familyMembers.length }}</span>
|
||||
}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (familyMembers.length === 0) {
|
||||
<div class="preview-empty preview-empty--warn">
|
||||
<mat-icon class="preview-empty__icon">warning</mat-icon>
|
||||
<span>No family members added. At least one is required.</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="fm-preview-list">
|
||||
@for (member of familyMembers; track member; let i = $index) {
|
||||
<div class="fm-preview-row">
|
||||
<div class="fm-preview-row__sno">{{ i + 1 }}</div>
|
||||
<div class="fm-preview-row__avatar">
|
||||
<mat-icon>person</mat-icon>
|
||||
</div>
|
||||
<div class="fm-preview-row__info">
|
||||
<span class="fm-preview-row__name">{{ member.firstName || member.displayName }}</span>
|
||||
<span class="fm-preview-row__meta">
|
||||
@if (member.relationship) { {{ member.relationship }} }
|
||||
@if (member.dateOfBirth) { · DOB: {{ member.dateOfBirth }} }
|
||||
</span>
|
||||
</div>
|
||||
@if (member.aadhaarNumber || member.panNumber) {
|
||||
<div class="fm-preview-row__ids">
|
||||
@if (member.panNumber) { <span class="fm-preview-id">PAN: {{ member.panNumber }}</span> }
|
||||
@if (member.aadhaarNumber) { <span class="fm-preview-id">Aadhaar: {{ member.aadhaarNumber }}</span> }
|
||||
<div class="info-card">
|
||||
<div class="card-header">
|
||||
<fa-icon icon="users" class="card-icon"></fa-icon>
|
||||
<span>Family Members</span>
|
||||
@if (familyMembers.length > 0) {
|
||||
<span class="count-badge">{{ familyMembers.length }}</span>
|
||||
}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (familyMembers.length === 0) {
|
||||
<div class="preview-empty preview-empty--warn">
|
||||
<mat-icon class="preview-empty__icon">warning</mat-icon>
|
||||
<span>No family members added. At least one is required.</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="fm-preview-list">
|
||||
@for (member of familyMembers; track member; let i = $index) {
|
||||
<div class="fm-preview-row">
|
||||
<div class="fm-preview-row__sno">{{ i + 1 }}</div>
|
||||
<div class="fm-preview-row__avatar">
|
||||
<mat-icon>person</mat-icon>
|
||||
</div>
|
||||
}
|
||||
<div class="fm-preview-row__status">
|
||||
@if (member.isDependent) {
|
||||
<span class="preview-badge preview-badge--info">Dependent</span>
|
||||
}
|
||||
@if (member.linkedClientId) {
|
||||
<span class="preview-badge preview-badge--verified">Linked</span>
|
||||
<div class="fm-preview-row__info">
|
||||
<span class="fm-preview-row__name">{{ member.firstName || member.displayName }}</span>
|
||||
<span class="fm-preview-row__meta">
|
||||
@if (member.relationship) {
|
||||
{{ member.relationship }}
|
||||
}
|
||||
@if (member.dateOfBirth) {
|
||||
· DOB: {{ member.dateOfBirth }}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
@if (member.aadhaarNumber || member.panNumber) {
|
||||
<div class="fm-preview-row__ids">
|
||||
@if (member.panNumber) {
|
||||
<span class="fm-preview-id">PAN: {{ member.panNumber }}</span>
|
||||
}
|
||||
@if (member.aadhaarNumber) {
|
||||
<span class="fm-preview-id">Aadhaar: {{ member.aadhaarNumber }}</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="fm-preview-row__status">
|
||||
@if (member.isDependent) {
|
||||
<span class="preview-badge preview-badge--info">Dependent</span>
|
||||
}
|
||||
@if (member.linkedClientId) {
|
||||
<span class="preview-badge preview-badge--verified">Linked</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
+34
-24
@@ -32,7 +32,7 @@
|
||||
|
||||
&__sub {
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
color: rgb(255 255 255 / 50%);
|
||||
margin: 2px 0 0;
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@
|
||||
padding: 20px 24px;
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%);
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border: 1px solid rgb(0 0 0 / 6%);
|
||||
}
|
||||
|
||||
.profile-avatar {
|
||||
@@ -96,9 +96,9 @@
|
||||
font-weight: 500;
|
||||
padding: 3px 10px;
|
||||
border-radius: 20px;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
background: rgb(255 255 255 / 70%);
|
||||
color: #555;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid rgb(0 0 0 / 8%);
|
||||
|
||||
.chip-icon {
|
||||
font-size: 10px;
|
||||
@@ -120,7 +120,7 @@
|
||||
|
||||
/* ── Info Cards ── */
|
||||
.info-card {
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid rgb(0 0 0 / 8%);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
@@ -132,7 +132,7 @@
|
||||
gap: 8px;
|
||||
padding: 12px 20px;
|
||||
background: #fafafa;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-bottom: 1px solid rgb(0 0 0 / 6%);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
@@ -177,8 +177,8 @@
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.04);
|
||||
border-bottom: 1px solid rgb(0 0 0 / 4%);
|
||||
border-right: 1px solid rgb(0 0 0 / 4%);
|
||||
|
||||
&:nth-child(3n) {
|
||||
border-right: none;
|
||||
@@ -195,7 +195,7 @@
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
color: rgb(0 0 0 / 45%);
|
||||
}
|
||||
|
||||
.preview-value {
|
||||
@@ -204,7 +204,7 @@
|
||||
color: #1a1a1a;
|
||||
|
||||
&.no-data {
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
color: rgb(0 0 0 / 30%);
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
@@ -239,7 +239,7 @@
|
||||
border-collapse: collapse;
|
||||
|
||||
tr {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
|
||||
border-bottom: 1px solid rgb(0 0 0 / 4%);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
@@ -254,7 +254,7 @@
|
||||
.info-label {
|
||||
width: 40%;
|
||||
font-size: 13px;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
color: rgb(0 0 0 / 50%);
|
||||
font-weight: 500;
|
||||
padding-right: 16px;
|
||||
}
|
||||
@@ -278,17 +278,17 @@
|
||||
padding: 10px 16px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
color: rgb(0 0 0 / 55%);
|
||||
text-align: left;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-bottom: 1px solid rgb(0 0 0 / 8%);
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
|
||||
border-bottom: 1px solid rgb(0 0 0 / 4%);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
@@ -349,7 +349,7 @@
|
||||
.address-block {
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px dashed rgba(0, 0, 0, 0.08);
|
||||
border-bottom: 1px dashed rgb(0 0 0 / 8%);
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
@@ -381,7 +381,7 @@
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 20px 0 4px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
@@ -394,23 +394,28 @@
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
color: rgb(0 0 0 / 60%);
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
/* ── Responsive ── */
|
||||
@media (max-width: 960px) {
|
||||
@media (width <= 960px) {
|
||||
.preview-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.preview-field {
|
||||
&:nth-child(3n) { border-right: 1px solid rgba(0, 0, 0, 0.04); }
|
||||
&:nth-child(2n) { border-right: none; }
|
||||
&:nth-child(3n) {
|
||||
border-right: 1px solid rgb(0 0 0 / 4%);
|
||||
}
|
||||
|
||||
&:nth-child(2n) {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@media (width <= 768px) {
|
||||
.profile-card {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
@@ -486,8 +491,13 @@
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
transition: background 0.1s;
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
&:hover { background: #f8fafc; }
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
}
|
||||
|
||||
.fm-preview-row__sno {
|
||||
|
||||
@@ -75,7 +75,12 @@ export class ClientPreviewStepComponent {
|
||||
@Input() maritalStatusOptions: any[] = [];
|
||||
|
||||
/** Address tab labels */
|
||||
addressTabs = ['permanent', 'current', 'office', 'correspondence'];
|
||||
addressTabs = [
|
||||
'permanent',
|
||||
'current',
|
||||
'office',
|
||||
'correspondence'
|
||||
];
|
||||
addressTabLabels: Record<string, string> = {
|
||||
permanent: 'Permanent Address',
|
||||
current: 'Current / Residential',
|
||||
|
||||
Reference in New Issue
Block a user