Merge branch 'feature/merge-updates' from cbsnexora into feature/ui-reports-product
Conflict resolutions: - src/main.scss: kept both new dialog backdrops (CIC/PSL/ALM/CIMS/CKYC/Acct) and incoming dialog panel styles (.abd-dialog-panel, .arc-dialog-panel) - client-general-step.component.html/.ts: kept HEAD's agriculture/non-agri income fields, education/degree/religion/social-category dropdowns, and inject() refactor matching the incoming pattern - kyc-management.component.html/.scss/.ts: kept HEAD's Home Visit terminology, takeAction() method, hv-* and vkyc-* dialog styles; accepted incoming .km-warn-d style addition Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+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')) {
|
||||
|
||||
+528
-204
File diff suppressed because it is too large
Load Diff
+144
-28
@@ -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: rgb(255 255 255 / 50%); 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 {
|
||||
@@ -139,8 +214,14 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,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;
|
||||
@@ -173,7 +265,10 @@
|
||||
font-size: 12px;
|
||||
color: #0c4a6e;
|
||||
line-height: 1.6;
|
||||
strong { font-weight: 600; }
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.ec-card-actions {
|
||||
@@ -182,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 {
|
||||
@@ -198,11 +298,27 @@
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
@media (width <= 900px) { .ec-grid-3 { grid-template-columns: repeat(2, 1fr); } }
|
||||
@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; }
|
||||
.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>
|
||||
}
|
||||
|
||||
+123
-25
@@ -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;
|
||||
@@ -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,13 @@
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&__left { flex-shrink: 0; }
|
||||
&__icon { color: #1074b9; }
|
||||
&__left {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: #1074b9;
|
||||
}
|
||||
|
||||
&__photo {
|
||||
width: 40px;
|
||||
@@ -121,9 +169,24 @@
|
||||
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;
|
||||
@@ -149,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 {
|
||||
@@ -204,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 {
|
||||
@@ -266,7 +344,9 @@
|
||||
|
||||
.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);
|
||||
@@ -275,7 +355,11 @@
|
||||
}
|
||||
|
||||
::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 {
|
||||
@@ -294,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 {
|
||||
@@ -312,7 +400,10 @@
|
||||
border-radius: 6px;
|
||||
width: 100%;
|
||||
|
||||
&__icon { color: #1074b9; font-size: 20px; }
|
||||
&__icon {
|
||||
color: #1074b9;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
&__name {
|
||||
flex: 1;
|
||||
@@ -330,7 +421,14 @@
|
||||
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>
|
||||
|
||||
+395
-80
@@ -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 rgb(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 {
|
||||
@@ -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,8 +301,13 @@
|
||||
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 (width <= 768px) {
|
||||
@@ -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,24 +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;
|
||||
@@ -392,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;
|
||||
@@ -404,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 */
|
||||
@@ -421,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 {
|
||||
@@ -444,20 +596,30 @@
|
||||
}
|
||||
|
||||
/* 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;
|
||||
@@ -469,9 +631,12 @@
|
||||
}
|
||||
|
||||
.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 {
|
||||
@@ -480,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 {
|
||||
@@ -507,31 +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 {
|
||||
@@ -542,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 {
|
||||
@@ -556,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 {
|
||||
@@ -573,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 {
|
||||
@@ -584,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 {
|
||||
@@ -599,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 (width <= 768px) {
|
||||
.nom-form-fields { grid-template-columns: 1fr; }
|
||||
.nom-guardian-card { flex-wrap: wrap; }
|
||||
.nom-guardian-rel { width: 100%; }
|
||||
.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: () => {} });
|
||||
}
|
||||
|
||||
|
||||
@@ -2020,11 +2020,12 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
export class SignatoryDetailDialogComponent {
|
||||
private clientService = inject(ClientsService);
|
||||
private sanitizer = inject(DomSanitizer);
|
||||
private inputData = inject<any>(MAT_DIALOG_DATA);
|
||||
data: any;
|
||||
loading = false;
|
||||
|
||||
constructor() {
|
||||
const inputData = inject<any>(MAT_DIALOG_DATA);
|
||||
const inputData = this.inputData;
|
||||
this.data = { ...inputData };
|
||||
const cid = inputData.id?.toString();
|
||||
if (!cid) return;
|
||||
|
||||
+356
-85
@@ -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 { FormsModule } from '@angular/forms';
|
||||
@@ -39,13 +47,23 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
}
|
||||
<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>
|
||||
<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" />
|
||||
<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>
|
||||
@@ -59,14 +77,17 @@ 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>
|
||||
}
|
||||
</div>
|
||||
<p class="verify-msg">
|
||||
Verifying DIN <strong>{{ dinNumber }}</strong> for <strong>{{ signatoryName }}</strong>
|
||||
against <strong>MCA (Ministry of Corporate Affairs)</strong> records.
|
||||
Verifying DIN <strong>{{ dinNumber }}</strong> for <strong>{{ signatoryName }}</strong> against
|
||||
<strong>MCA (Ministry of Corporate Affairs)</strong> records.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
@@ -80,9 +101,8 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
<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.
|
||||
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">
|
||||
@@ -124,7 +144,9 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
<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>
|
||||
<p>
|
||||
<strong>{{ entityName }}</strong> matches <strong>{{ matchedCompany }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
@@ -132,7 +154,9 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
<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>
|
||||
<p>
|
||||
<strong>{{ entityName }}</strong> does not match any of the director's associated companies.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -148,11 +172,21 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
<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 }} }
|
||||
@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>
|
||||
@@ -201,15 +235,11 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
</button>
|
||||
}
|
||||
@if (!verifying && verified) {
|
||||
<button mat-raised-button color="primary" (click)="onClose(true)">
|
||||
<fa-icon icon="check"></fa-icon> Done
|
||||
</button>
|
||||
<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>
|
||||
<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>
|
||||
@@ -235,7 +265,9 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
color: #1074b9;
|
||||
}
|
||||
|
||||
.dialog-title-icon { font-size: 20px; }
|
||||
.dialog-title-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
mat-dialog-content {
|
||||
padding: 16px 24px;
|
||||
@@ -255,10 +287,24 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
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; }
|
||||
.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 {
|
||||
@@ -272,10 +318,26 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
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; }
|
||||
.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 {
|
||||
@@ -291,49 +353,180 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.info-icon { font-size: 16px; color: #1074b9; flex-shrink: 0; margin-top: 2px; }
|
||||
.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; }
|
||||
.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-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; }
|
||||
.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; }
|
||||
.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-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-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-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; }
|
||||
.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 {
|
||||
@@ -351,7 +544,10 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.company-title-icon { font-size: 14px; color: #64748b; }
|
||||
.company-title-icon {
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* Match banner */
|
||||
.match-banner {
|
||||
@@ -365,7 +561,10 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.match-banner p { margin: 2px 0 0; font-size: 12px; }
|
||||
.match-banner p {
|
||||
margin: 2px 0 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.match-success {
|
||||
background: #ecfdf5;
|
||||
@@ -373,7 +572,12 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
.match-success .match-banner-icon { color: #059669; font-size: 18px; margin-top: 1px; flex-shrink: 0; }
|
||||
.match-success .match-banner-icon {
|
||||
color: #059669;
|
||||
font-size: 18px;
|
||||
margin-top: 1px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.match-warning {
|
||||
background: #fffbeb;
|
||||
@@ -381,7 +585,12 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.match-warning .match-banner-icon { color: #d97706; font-size: 18px; margin-top: 1px; flex-shrink: 0; }
|
||||
.match-warning .match-banner-icon {
|
||||
color: #d97706;
|
||||
font-size: 18px;
|
||||
margin-top: 1px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Company list */
|
||||
.company-list {
|
||||
@@ -399,9 +608,13 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.company-item:last-child { border-bottom: none; }
|
||||
.company-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.company-item:hover { background: #f8fafc; }
|
||||
.company-item:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.company-matched {
|
||||
background: #f0fdf4 !important;
|
||||
@@ -415,28 +628,78 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.company-match-icon.match { color: #22c55e; }
|
||||
.company-match-icon.no-match { color: #d1d5db; }
|
||||
.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; }
|
||||
.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;
|
||||
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;
|
||||
}
|
||||
.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; }
|
||||
.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; }
|
||||
mat-dialog-actions {
|
||||
padding: 8px 24px 16px;
|
||||
}
|
||||
mat-dialog-actions button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
`
|
||||
],
|
||||
imports: [
|
||||
@@ -561,7 +824,11 @@ export class DinVerifyDialogComponent implements OnInit, OnDestroy {
|
||||
|
||||
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.';
|
||||
this.errorMessage =
|
||||
this.apiError?.error?.defaultUserMessage ||
|
||||
this.apiError?.error?.message ||
|
||||
this.apiError?.message ||
|
||||
'Unable to verify DIN. Please try again.';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -590,7 +857,11 @@ export class DinVerifyDialogComponent implements OnInit, OnDestroy {
|
||||
this.matchCompanyName();
|
||||
} else {
|
||||
this.failed = true;
|
||||
this.failReason = changes?.dinFailReason || res?.failReason || res?.defaultUserMessage || 'DIN could not be verified against MCA records.';
|
||||
this.failReason =
|
||||
changes?.dinFailReason ||
|
||||
res?.failReason ||
|
||||
res?.defaultUserMessage ||
|
||||
'DIN could not be verified against MCA records.';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+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>
|
||||
}
|
||||
|
||||
+23
-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/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*/
|
||||
@@ -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;
|
||||
@@ -174,7 +191,8 @@ $muted: #64748b;
|
||||
|
||||
// Responsive
|
||||
@media (width <= 768px) {
|
||||
.addr-grid--2, .addr-grid--3 {
|
||||
.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()
|
||||
};
|
||||
|
||||
@@ -832,7 +832,8 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
} else {
|
||||
this.gstinVerifyStatus = 'FAILED';
|
||||
this.showPanErrorAlert(
|
||||
changes?.gstinVerificationMessage || 'GSTIN verification failed. Please check the GSTIN number and try again.'
|
||||
changes?.gstinVerificationMessage ||
|
||||
'GSTIN verification failed. Please check the GSTIN number and try again.'
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -840,7 +841,9 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.gstinVerifying = false;
|
||||
this.gstinVerifyStatus = 'FAILED';
|
||||
this.showPanErrorAlert(
|
||||
err?.error?.errors?.[0]?.defaultUserMessage || err?.error?.defaultUserMessage || 'GSTIN verification failed.'
|
||||
err?.error?.errors?.[0]?.defaultUserMessage ||
|
||||
err?.error?.defaultUserMessage ||
|
||||
'GSTIN verification failed.'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
+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>
|
||||
|
||||
+8
@@ -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
|
||||
*/
|
||||
|
||||
+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) => {
|
||||
|
||||
+14
-4
@@ -405,8 +405,13 @@
|
||||
}
|
||||
|
||||
.preview-field {
|
||||
&:nth-child(3n) { border-right: 1px solid rgb(0 0 0 / 4%); }
|
||||
&:nth-child(2n) { border-right: none; }
|
||||
&:nth-child(3n) {
|
||||
border-right: 1px solid rgb(0 0 0 / 4%);
|
||||
}
|
||||
|
||||
&:nth-child(2n) {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -34,12 +34,18 @@
|
||||
<mat-panel-title class="addr-panel__title">
|
||||
<i class="fa fa-home addr-panel__type-icon"></i>
|
||||
<span class="addr-panel__type">{{ address.addressType }}</span>
|
||||
<span class="addr-panel__status" [class.addr-panel__status--active]="address.isActive" [class.addr-panel__status--inactive]="!address.isActive">
|
||||
<span
|
||||
class="addr-panel__status"
|
||||
[class.addr-panel__status--active]="address.isActive"
|
||||
[class.addr-panel__status--inactive]="!address.isActive"
|
||||
>
|
||||
{{ address.isActive ? 'Active' : 'Inactive' }}
|
||||
</span>
|
||||
</mat-panel-title>
|
||||
<mat-panel-description class="addr-panel__desc">
|
||||
{{ address.city || '' }}{{ address.city && (address.countyDistrict || address.addressLine3) ? ', ' : '' }}{{ address.countyDistrict || address.addressLine3 || '' }}{{ address.postalCode ? ' - ' + address.postalCode : '' }}
|
||||
{{ address.city || '' }}{{ address.city && (address.countyDistrict || address.addressLine3) ? ', ' : ''
|
||||
}}{{ address.countyDistrict || address.addressLine3 || ''
|
||||
}}{{ address.postalCode ? ' - ' + address.postalCode : '' }}
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
@@ -101,12 +107,16 @@
|
||||
@if (address.stateProvinceId) {
|
||||
<div class="addr-field">
|
||||
<div class="addr-field__label">State</div>
|
||||
<div class="addr-field__value">{{ getSelectedValue('stateProvinceIdOptions', address.stateProvinceId)?.name || '-' }}</div>
|
||||
<div class="addr-field__value">
|
||||
{{ getSelectedValue('stateProvinceIdOptions', address.stateProvinceId)?.name || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="addr-field">
|
||||
<div class="addr-field__label">Country</div>
|
||||
<div class="addr-field__value">{{ getSelectedValue('countryIdOptions', address.countryId)?.name || 'India' }}</div>
|
||||
<div class="addr-field__value">
|
||||
{{ getSelectedValue('countryIdOptions', address.countryId)?.name || 'India' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
|
||||
@@ -80,23 +80,17 @@
|
||||
<!-- Client basic info -->
|
||||
<div class="ckyc-info-grid">
|
||||
<div class="ckyc-info-item">
|
||||
<span class="ckyc-info-label">
|
||||
<fa-icon icon="hashtag"></fa-icon> CIF Number
|
||||
</span>
|
||||
<span class="ckyc-info-label"> <fa-icon icon="hashtag"></fa-icon> CIF Number </span>
|
||||
<span class="ckyc-info-value ckyc-info-value--mono">{{ cifNumber || clientId }}</span>
|
||||
</div>
|
||||
|
||||
<div class="ckyc-info-item">
|
||||
<span class="ckyc-info-label">
|
||||
<fa-icon icon="user"></fa-icon> Full Name
|
||||
</span>
|
||||
<span class="ckyc-info-label"> <fa-icon icon="user"></fa-icon> Full Name </span>
|
||||
<span class="ckyc-info-value">{{ clientName || '—' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="ckyc-info-item">
|
||||
<span class="ckyc-info-label">
|
||||
<fa-icon icon="envelope"></fa-icon> Email
|
||||
</span>
|
||||
<span class="ckyc-info-label"> <fa-icon icon="envelope"></fa-icon> Email </span>
|
||||
@if (clientEmail) {
|
||||
<span class="ckyc-info-value">{{ clientEmail }}</span>
|
||||
} @else {
|
||||
@@ -105,9 +99,7 @@
|
||||
</div>
|
||||
|
||||
<div class="ckyc-info-item">
|
||||
<span class="ckyc-info-label">
|
||||
<fa-icon icon="phone"></fa-icon> Mobile
|
||||
</span>
|
||||
<span class="ckyc-info-label"> <fa-icon icon="phone"></fa-icon> Mobile </span>
|
||||
@if (clientMobile) {
|
||||
<span class="ckyc-info-value">{{ clientMobile }}</span>
|
||||
} @else {
|
||||
@@ -152,7 +144,8 @@
|
||||
}
|
||||
@if (existing.ADDRESS_PROOF.fileCount && existing.ADDRESS_PROOF.fileCount > 0) {
|
||||
<span class="ckyc-existing__count">
|
||||
{{ existing.ADDRESS_PROOF.fileCount }} {{ existing.ADDRESS_PROOF.fileCount === 1 ? 'file' : 'files' }}
|
||||
{{ existing.ADDRESS_PROOF.fileCount }}
|
||||
{{ existing.ADDRESS_PROOF.fileCount === 1 ? 'file' : 'files' }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
@@ -169,65 +162,65 @@
|
||||
}
|
||||
|
||||
@if (!existing.ADDRESS_PROOF.present || existing.ADDRESS_PROOF.expanded) {
|
||||
<p class="ckyc-upload-hint">{{ 'labels.inputs.Accepted formats' | translate }}: PNG, JPEG, JPG, PDF</p>
|
||||
<div class="ckyc-file-row">
|
||||
<input
|
||||
type="file"
|
||||
#addressProofInput
|
||||
accept=".png,.jpeg,.jpg,.pdf"
|
||||
(change)="onFileSelect($event, 'ADDRESS_PROOF')"
|
||||
[style.display]="'none'"
|
||||
/>
|
||||
<mat-form-field appearance="outline" class="ckyc-file-name-field">
|
||||
<p class="ckyc-upload-hint">{{ 'labels.inputs.Accepted formats' | translate }}: PNG, JPEG, JPG, PDF</p>
|
||||
<div class="ckyc-file-row">
|
||||
<input
|
||||
matInput
|
||||
[readonly]="true"
|
||||
[value]="addressProofFile?.name || ''"
|
||||
[placeholder]="'labels.text.NoFileSelected' | translate"
|
||||
type="file"
|
||||
#addressProofInput
|
||||
accept=".png,.jpeg,.jpg,.pdf"
|
||||
(change)="onFileSelect($event, 'ADDRESS_PROOF')"
|
||||
[style.display]="'none'"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<button mat-stroked-button type="button" (click)="addressProofInput.click()">
|
||||
<fa-icon icon="folder-open" size="lg"></fa-icon>
|
||||
{{ 'labels.buttons.Browse' | translate }}
|
||||
<mat-form-field appearance="outline" class="ckyc-file-name-field">
|
||||
<input
|
||||
matInput
|
||||
[readonly]="true"
|
||||
[value]="addressProofFile?.name || ''"
|
||||
[placeholder]="'labels.text.NoFileSelected' | translate"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<button mat-stroked-button type="button" (click)="addressProofInput.click()">
|
||||
<fa-icon icon="folder-open" size="lg"></fa-icon>
|
||||
{{ 'labels.buttons.Browse' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
type="button"
|
||||
[disabled]="!addressProofFile || uploading['ADDRESS_PROOF']"
|
||||
(click)="uploadDocument('ADDRESS_PROOF')"
|
||||
>
|
||||
@if (uploading['ADDRESS_PROOF']) {
|
||||
<mat-progress-spinner diameter="18" mode="indeterminate"></mat-progress-spinner>
|
||||
} @else {
|
||||
{{ 'labels.buttons.Upload' | translate }}
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
type="button"
|
||||
[disabled]="!addressProofFile || uploading['ADDRESS_PROOF']"
|
||||
(click)="uploadDocument('ADDRESS_PROOF')"
|
||||
>
|
||||
@if (uploading['ADDRESS_PROOF']) {
|
||||
<mat-progress-spinner diameter="18" mode="indeterminate"></mat-progress-spinner>
|
||||
} @else {
|
||||
{{ 'labels.buttons.Upload' | translate }}
|
||||
@if (uploadErrors['ADDRESS_PROOF']) {
|
||||
<div class="ckyc-upload-error">
|
||||
<mat-icon>error_outline</mat-icon>
|
||||
<span>{{ uploadErrors['ADDRESS_PROOF'] }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (uploadResponses['ADDRESS_PROOF']) {
|
||||
<div class="ckyc-upload-response">
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Status' | translate }}:</b> {{ uploadResponses['ADDRESS_PROOF'].status }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Request ID' | translate }}:</b> {{ uploadResponses['ADDRESS_PROOF'].requestId }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Acknowledgement Number' | translate }}:</b>
|
||||
{{ uploadResponses['ADDRESS_PROOF'].acknowledgementNumber }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Processing Status' | translate }}:</b>
|
||||
{{ uploadResponses['ADDRESS_PROOF'].processingStatus }}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</button>
|
||||
@if (uploadErrors['ADDRESS_PROOF']) {
|
||||
<div class="ckyc-upload-error">
|
||||
<mat-icon>error_outline</mat-icon>
|
||||
<span>{{ uploadErrors['ADDRESS_PROOF'] }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (uploadResponses['ADDRESS_PROOF']) {
|
||||
<div class="ckyc-upload-response">
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Status' | translate }}:</b> {{ uploadResponses['ADDRESS_PROOF'].status }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Request ID' | translate }}:</b> {{ uploadResponses['ADDRESS_PROOF'].requestId }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Acknowledgement Number' | translate }}:</b>
|
||||
{{ uploadResponses['ADDRESS_PROOF'].acknowledgementNumber }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Processing Status' | translate }}:</b>
|
||||
{{ uploadResponses['ADDRESS_PROOF'].processingStatus }}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
@@ -274,65 +267,65 @@
|
||||
}
|
||||
|
||||
@if (!existing.PHOTO_PROOF.present || existing.PHOTO_PROOF.expanded) {
|
||||
<p class="ckyc-upload-hint">{{ 'labels.inputs.Accepted formats' | translate }}: PNG, JPEG, JPG, PDF</p>
|
||||
<div class="ckyc-file-row">
|
||||
<input
|
||||
type="file"
|
||||
#photoProofInput
|
||||
accept=".png,.jpeg,.jpg,.pdf"
|
||||
(change)="onFileSelect($event, 'PHOTO_PROOF')"
|
||||
[style.display]="'none'"
|
||||
/>
|
||||
<mat-form-field appearance="outline" class="ckyc-file-name-field">
|
||||
<p class="ckyc-upload-hint">{{ 'labels.inputs.Accepted formats' | translate }}: PNG, JPEG, JPG, PDF</p>
|
||||
<div class="ckyc-file-row">
|
||||
<input
|
||||
matInput
|
||||
[readonly]="true"
|
||||
[value]="photoProofFile?.name || ''"
|
||||
[placeholder]="'labels.text.NoFileSelected' | translate"
|
||||
type="file"
|
||||
#photoProofInput
|
||||
accept=".png,.jpeg,.jpg,.pdf"
|
||||
(change)="onFileSelect($event, 'PHOTO_PROOF')"
|
||||
[style.display]="'none'"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<button mat-stroked-button type="button" (click)="photoProofInput.click()">
|
||||
<fa-icon icon="folder-open" size="lg"></fa-icon>
|
||||
{{ 'labels.buttons.Browse' | translate }}
|
||||
<mat-form-field appearance="outline" class="ckyc-file-name-field">
|
||||
<input
|
||||
matInput
|
||||
[readonly]="true"
|
||||
[value]="photoProofFile?.name || ''"
|
||||
[placeholder]="'labels.text.NoFileSelected' | translate"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<button mat-stroked-button type="button" (click)="photoProofInput.click()">
|
||||
<fa-icon icon="folder-open" size="lg"></fa-icon>
|
||||
{{ 'labels.buttons.Browse' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
type="button"
|
||||
[disabled]="!photoProofFile || uploading['PHOTO_PROOF']"
|
||||
(click)="uploadDocument('PHOTO_PROOF')"
|
||||
>
|
||||
@if (uploading['PHOTO_PROOF']) {
|
||||
<mat-progress-spinner diameter="18" mode="indeterminate"></mat-progress-spinner>
|
||||
} @else {
|
||||
{{ 'labels.buttons.Upload' | translate }}
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
type="button"
|
||||
[disabled]="!photoProofFile || uploading['PHOTO_PROOF']"
|
||||
(click)="uploadDocument('PHOTO_PROOF')"
|
||||
>
|
||||
@if (uploading['PHOTO_PROOF']) {
|
||||
<mat-progress-spinner diameter="18" mode="indeterminate"></mat-progress-spinner>
|
||||
} @else {
|
||||
{{ 'labels.buttons.Upload' | translate }}
|
||||
@if (uploadErrors['PHOTO_PROOF']) {
|
||||
<div class="ckyc-upload-error">
|
||||
<mat-icon>error_outline</mat-icon>
|
||||
<span>{{ uploadErrors['PHOTO_PROOF'] }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (uploadResponses['PHOTO_PROOF']) {
|
||||
<div class="ckyc-upload-response">
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Status' | translate }}:</b> {{ uploadResponses['PHOTO_PROOF'].status }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Request ID' | translate }}:</b> {{ uploadResponses['PHOTO_PROOF'].requestId }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Acknowledgement Number' | translate }}:</b>
|
||||
{{ uploadResponses['PHOTO_PROOF'].acknowledgementNumber }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Processing Status' | translate }}:</b>
|
||||
{{ uploadResponses['PHOTO_PROOF'].processingStatus }}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</button>
|
||||
@if (uploadErrors['PHOTO_PROOF']) {
|
||||
<div class="ckyc-upload-error">
|
||||
<mat-icon>error_outline</mat-icon>
|
||||
<span>{{ uploadErrors['PHOTO_PROOF'] }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (uploadResponses['PHOTO_PROOF']) {
|
||||
<div class="ckyc-upload-response">
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Status' | translate }}:</b> {{ uploadResponses['PHOTO_PROOF'].status }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Request ID' | translate }}:</b> {{ uploadResponses['PHOTO_PROOF'].requestId }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Acknowledgement Number' | translate }}:</b>
|
||||
{{ uploadResponses['PHOTO_PROOF'].acknowledgementNumber }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Processing Status' | translate }}:</b>
|
||||
{{ uploadResponses['PHOTO_PROOF'].processingStatus }}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
@@ -370,65 +363,65 @@
|
||||
}
|
||||
|
||||
@if (!existing.CLIENT_IMAGE.present || existing.CLIENT_IMAGE.expanded) {
|
||||
<p class="ckyc-upload-hint">{{ 'labels.inputs.Accepted formats' | translate }}: PNG, JPEG, JPG</p>
|
||||
<div class="ckyc-file-row">
|
||||
<input
|
||||
type="file"
|
||||
#clientImageInput
|
||||
accept=".png,.jpeg,.jpg"
|
||||
(change)="onFileSelect($event, 'CLIENT_IMAGE')"
|
||||
[style.display]="'none'"
|
||||
/>
|
||||
<mat-form-field appearance="outline" class="ckyc-file-name-field">
|
||||
<p class="ckyc-upload-hint">{{ 'labels.inputs.Accepted formats' | translate }}: PNG, JPEG, JPG</p>
|
||||
<div class="ckyc-file-row">
|
||||
<input
|
||||
matInput
|
||||
[readonly]="true"
|
||||
[value]="clientImageFile?.name || ''"
|
||||
[placeholder]="'labels.text.NoFileSelected' | translate"
|
||||
type="file"
|
||||
#clientImageInput
|
||||
accept=".png,.jpeg,.jpg"
|
||||
(change)="onFileSelect($event, 'CLIENT_IMAGE')"
|
||||
[style.display]="'none'"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<button mat-stroked-button type="button" (click)="clientImageInput.click()">
|
||||
<fa-icon icon="folder-open" size="lg"></fa-icon>
|
||||
{{ 'labels.buttons.Browse' | translate }}
|
||||
<mat-form-field appearance="outline" class="ckyc-file-name-field">
|
||||
<input
|
||||
matInput
|
||||
[readonly]="true"
|
||||
[value]="clientImageFile?.name || ''"
|
||||
[placeholder]="'labels.text.NoFileSelected' | translate"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<button mat-stroked-button type="button" (click)="clientImageInput.click()">
|
||||
<fa-icon icon="folder-open" size="lg"></fa-icon>
|
||||
{{ 'labels.buttons.Browse' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
type="button"
|
||||
[disabled]="!clientImageFile || uploading['CLIENT_IMAGE']"
|
||||
(click)="uploadDocument('CLIENT_IMAGE')"
|
||||
>
|
||||
@if (uploading['CLIENT_IMAGE']) {
|
||||
<mat-progress-spinner diameter="18" mode="indeterminate"></mat-progress-spinner>
|
||||
} @else {
|
||||
{{ 'labels.buttons.Upload' | translate }}
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
type="button"
|
||||
[disabled]="!clientImageFile || uploading['CLIENT_IMAGE']"
|
||||
(click)="uploadDocument('CLIENT_IMAGE')"
|
||||
>
|
||||
@if (uploading['CLIENT_IMAGE']) {
|
||||
<mat-progress-spinner diameter="18" mode="indeterminate"></mat-progress-spinner>
|
||||
} @else {
|
||||
{{ 'labels.buttons.Upload' | translate }}
|
||||
@if (uploadErrors['CLIENT_IMAGE']) {
|
||||
<div class="ckyc-upload-error">
|
||||
<mat-icon>error_outline</mat-icon>
|
||||
<span>{{ uploadErrors['CLIENT_IMAGE'] }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (uploadResponses['CLIENT_IMAGE']) {
|
||||
<div class="ckyc-upload-response">
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Status' | translate }}:</b> {{ uploadResponses['CLIENT_IMAGE'].status }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Request ID' | translate }}:</b> {{ uploadResponses['CLIENT_IMAGE'].requestId }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Acknowledgement Number' | translate }}:</b>
|
||||
{{ uploadResponses['CLIENT_IMAGE'].acknowledgementNumber }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Processing Status' | translate }}:</b>
|
||||
{{ uploadResponses['CLIENT_IMAGE'].processingStatus }}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</button>
|
||||
@if (uploadErrors['CLIENT_IMAGE']) {
|
||||
<div class="ckyc-upload-error">
|
||||
<mat-icon>error_outline</mat-icon>
|
||||
<span>{{ uploadErrors['CLIENT_IMAGE'] }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (uploadResponses['CLIENT_IMAGE']) {
|
||||
<div class="ckyc-upload-response">
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Status' | translate }}:</b> {{ uploadResponses['CLIENT_IMAGE'].status }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Request ID' | translate }}:</b> {{ uploadResponses['CLIENT_IMAGE'].requestId }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Acknowledgement Number' | translate }}:</b>
|
||||
{{ uploadResponses['CLIENT_IMAGE'].acknowledgementNumber }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Processing Status' | translate }}:</b>
|
||||
{{ uploadResponses['CLIENT_IMAGE'].processingStatus }}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
@@ -451,7 +444,11 @@
|
||||
</div>
|
||||
</div>
|
||||
@if (existing.CLIENT_SIGNATURE.previewUrl) {
|
||||
<img [src]="existing.CLIENT_SIGNATURE.previewUrl" alt="Client signature" class="ckyc-existing__thumb ckyc-existing__thumb--wide" />
|
||||
<img
|
||||
[src]="existing.CLIENT_SIGNATURE.previewUrl"
|
||||
alt="Client signature"
|
||||
class="ckyc-existing__thumb ckyc-existing__thumb--wide"
|
||||
/>
|
||||
}
|
||||
<div class="ckyc-existing__meta">
|
||||
<fa-icon icon="file-signature"></fa-icon>
|
||||
@@ -470,65 +467,66 @@
|
||||
}
|
||||
|
||||
@if (!existing.CLIENT_SIGNATURE.present || existing.CLIENT_SIGNATURE.expanded) {
|
||||
<p class="ckyc-upload-hint">{{ 'labels.inputs.Accepted formats' | translate }}: PNG, JPEG, JPG</p>
|
||||
<div class="ckyc-file-row">
|
||||
<input
|
||||
type="file"
|
||||
#clientSignatureInput
|
||||
accept=".png,.jpeg,.jpg"
|
||||
(change)="onFileSelect($event, 'CLIENT_SIGNATURE')"
|
||||
[style.display]="'none'"
|
||||
/>
|
||||
<mat-form-field appearance="outline" class="ckyc-file-name-field">
|
||||
<p class="ckyc-upload-hint">{{ 'labels.inputs.Accepted formats' | translate }}: PNG, JPEG, JPG</p>
|
||||
<div class="ckyc-file-row">
|
||||
<input
|
||||
matInput
|
||||
[readonly]="true"
|
||||
[value]="clientSignatureFile?.name || ''"
|
||||
[placeholder]="'labels.text.NoFileSelected' | translate"
|
||||
type="file"
|
||||
#clientSignatureInput
|
||||
accept=".png,.jpeg,.jpg"
|
||||
(change)="onFileSelect($event, 'CLIENT_SIGNATURE')"
|
||||
[style.display]="'none'"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<button mat-stroked-button type="button" (click)="clientSignatureInput.click()">
|
||||
<fa-icon icon="folder-open" size="lg"></fa-icon>
|
||||
{{ 'labels.buttons.Browse' | translate }}
|
||||
<mat-form-field appearance="outline" class="ckyc-file-name-field">
|
||||
<input
|
||||
matInput
|
||||
[readonly]="true"
|
||||
[value]="clientSignatureFile?.name || ''"
|
||||
[placeholder]="'labels.text.NoFileSelected' | translate"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<button mat-stroked-button type="button" (click)="clientSignatureInput.click()">
|
||||
<fa-icon icon="folder-open" size="lg"></fa-icon>
|
||||
{{ 'labels.buttons.Browse' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
type="button"
|
||||
[disabled]="!clientSignatureFile || uploading['CLIENT_SIGNATURE']"
|
||||
(click)="uploadDocument('CLIENT_SIGNATURE')"
|
||||
>
|
||||
@if (uploading['CLIENT_SIGNATURE']) {
|
||||
<mat-progress-spinner diameter="18" mode="indeterminate"></mat-progress-spinner>
|
||||
} @else {
|
||||
{{ 'labels.buttons.Upload' | translate }}
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
type="button"
|
||||
[disabled]="!clientSignatureFile || uploading['CLIENT_SIGNATURE']"
|
||||
(click)="uploadDocument('CLIENT_SIGNATURE')"
|
||||
>
|
||||
@if (uploading['CLIENT_SIGNATURE']) {
|
||||
<mat-progress-spinner diameter="18" mode="indeterminate"></mat-progress-spinner>
|
||||
} @else {
|
||||
{{ 'labels.buttons.Upload' | translate }}
|
||||
@if (uploadErrors['CLIENT_SIGNATURE']) {
|
||||
<div class="ckyc-upload-error">
|
||||
<mat-icon>error_outline</mat-icon>
|
||||
<span>{{ uploadErrors['CLIENT_SIGNATURE'] }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (uploadResponses['CLIENT_SIGNATURE']) {
|
||||
<div class="ckyc-upload-response">
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Status' | translate }}:</b> {{ uploadResponses['CLIENT_SIGNATURE'].status }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Request ID' | translate }}:</b>
|
||||
{{ uploadResponses['CLIENT_SIGNATURE'].requestId }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Acknowledgement Number' | translate }}:</b>
|
||||
{{ uploadResponses['CLIENT_SIGNATURE'].acknowledgementNumber }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Processing Status' | translate }}:</b>
|
||||
{{ uploadResponses['CLIENT_SIGNATURE'].processingStatus }}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</button>
|
||||
@if (uploadErrors['CLIENT_SIGNATURE']) {
|
||||
<div class="ckyc-upload-error">
|
||||
<mat-icon>error_outline</mat-icon>
|
||||
<span>{{ uploadErrors['CLIENT_SIGNATURE'] }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (uploadResponses['CLIENT_SIGNATURE']) {
|
||||
<div class="ckyc-upload-response">
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Status' | translate }}:</b> {{ uploadResponses['CLIENT_SIGNATURE'].status }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Request ID' | translate }}:</b> {{ uploadResponses['CLIENT_SIGNATURE'].requestId }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Acknowledgement Number' | translate }}:</b>
|
||||
{{ uploadResponses['CLIENT_SIGNATURE'].acknowledgementNumber }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ 'labels.inputs.Processing Status' | translate }}:</b>
|
||||
{{ uploadResponses['CLIENT_SIGNATURE'].processingStatus }}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
overflow: hidden;
|
||||
margin-bottom: 18px;
|
||||
color: #fff;
|
||||
box-shadow: 0 6px 22px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 6px 22px rgb(0 0 0 / 10%);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
@@ -71,7 +71,7 @@
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.18), transparent 70%);
|
||||
background: radial-gradient(circle, rgb(255 255 255 / 18%), transparent 70%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -96,8 +96,8 @@
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
background: rgb(255 255 255 / 20%);
|
||||
border: 1px solid rgb(255 255 255 / 35%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -115,8 +115,8 @@
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
background: rgba(255, 255, 255, 0.22);
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
background: rgb(255 255 255 / 22%);
|
||||
border: 1px solid rgb(255 255 255 / 40%);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.8px;
|
||||
@@ -150,11 +150,20 @@
|
||||
opacity: 0.95;
|
||||
flex-wrap: wrap;
|
||||
|
||||
fa-icon { font-size: 11px; opacity: 0.9; margin-right: 3px; }
|
||||
strong { font-weight: 700; }
|
||||
fa-icon {
|
||||
font-size: 11px;
|
||||
opacity: 0.9;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
&__sep { opacity: 0.5; }
|
||||
&__sep {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&__hint {
|
||||
font-size: 12px;
|
||||
@@ -169,8 +178,8 @@
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 7px 14px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
background: rgb(255 255 255 / 20%);
|
||||
border: 1px solid rgb(255 255 255 / 35%);
|
||||
border-radius: 20px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
@@ -186,15 +195,23 @@
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #9fe1cb;
|
||||
box-shadow: 0 0 0 0 rgba(159, 225, 203, 0.7);
|
||||
box-shadow: 0 0 0 0 rgb(159 225 203 / 70%);
|
||||
animation: cersai-pulse 2s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes cersai-pulse {
|
||||
0% { box-shadow: 0 0 0 0 rgba(159, 225, 203, 0.7); }
|
||||
70% { box-shadow: 0 0 0 10px rgba(159, 225, 203, 0); }
|
||||
100% { box-shadow: 0 0 0 0 rgba(159, 225, 203, 0); }
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgb(159 225 203 / 70%);
|
||||
}
|
||||
|
||||
70% {
|
||||
box-shadow: 0 0 0 10px rgb(159 225 203 / 0%);
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgb(159 225 203 / 0%);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===================== Client info grid ===================== */
|
||||
@@ -218,7 +235,7 @@
|
||||
|
||||
&:hover {
|
||||
border-color: #c6dafc;
|
||||
box-shadow: 0 2px 8px rgba(26, 115, 232, 0.06);
|
||||
box-shadow: 0 2px 8px rgb(26 115 232 / 6%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,9 +248,19 @@
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
&__left { width: 100%; }
|
||||
&__icon { width: 52px; height: 52px; font-size: 22px; }
|
||||
&__ckyc-number { font-size: 20px; }
|
||||
&__left {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
&__ckyc-number {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +280,10 @@
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
fa-icon { color: #1a73e8; font-size: 11px; }
|
||||
fa-icon {
|
||||
color: #1a73e8;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
.ckyc-info-value {
|
||||
@@ -277,7 +307,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.ckyc-info-hint { display: none; }
|
||||
.ckyc-info-hint {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ckyc-section-title {
|
||||
margin: 24px 0 12px;
|
||||
@@ -296,14 +328,16 @@
|
||||
border-radius: 12px !important;
|
||||
border: 1px solid #e8eaed;
|
||||
box-shadow: none !important;
|
||||
transition: border-color 0.15s, box-shadow 0.15s;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
box-shadow 0.15s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 240px;
|
||||
|
||||
&:hover {
|
||||
border-color: #c6dafc;
|
||||
box-shadow: 0 2px 10px rgba(26, 115, 232, 0.06) !important;
|
||||
box-shadow: 0 2px 10px rgb(26 115 232 / 6%) !important;
|
||||
}
|
||||
|
||||
.mat-mdc-card-content {
|
||||
@@ -336,7 +370,7 @@
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 6px rgba(26, 115, 232, 0.25);
|
||||
box-shadow: 0 2px 6px rgb(26 115 232 / 25%);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -426,7 +460,9 @@
|
||||
font-size: 12px;
|
||||
margin-bottom: 14px;
|
||||
|
||||
mat-progress-spinner { display: inline-block; }
|
||||
mat-progress-spinner {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.ckyc-existing {
|
||||
@@ -494,7 +530,10 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
fa-icon { color: #1d9e75; font-size: 12px; }
|
||||
fa-icon {
|
||||
color: #1d9e75;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&__key {
|
||||
@@ -528,7 +567,7 @@
|
||||
background: #fff;
|
||||
object-fit: cover;
|
||||
align-self: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 6%);
|
||||
|
||||
&--wide {
|
||||
width: 200px;
|
||||
@@ -550,7 +589,10 @@
|
||||
padding: 0 12px !important;
|
||||
letter-spacing: 0.2px;
|
||||
|
||||
fa-icon { margin-right: 6px; font-size: 11px; }
|
||||
fa-icon {
|
||||
margin-right: 6px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #e1f5ee !important;
|
||||
|
||||
@@ -176,7 +176,11 @@ export class CkycTabComponent implements OnInit, OnDestroy {
|
||||
private formatJavaDate(value: any): string {
|
||||
if (!value) return '';
|
||||
if (Array.isArray(value) && value.length >= 3) {
|
||||
const [y, m, d] = value;
|
||||
const [
|
||||
y,
|
||||
m,
|
||||
d
|
||||
] = value;
|
||||
return `${String(d).padStart(2, '0')}-${String(m).padStart(2, '0')}-${y}`;
|
||||
}
|
||||
if (typeof value === 'string') return value;
|
||||
@@ -236,13 +240,9 @@ export class CkycTabComponent implements OnInit, OnDestroy {
|
||||
|
||||
// 3 & 4. ID proof + Address proof — client identifiers classified via proof-types
|
||||
const idProofIds = new Set<string>((proofTypes?.idProofs || []).map((p: any) => String(p.id)));
|
||||
const addressProofIds = new Set<string>(
|
||||
(proofTypes?.addressProofs || []).map((p: any) => String(p.id))
|
||||
);
|
||||
const addressProofIds = new Set<string>((proofTypes?.addressProofs || []).map((p: any) => String(p.id)));
|
||||
|
||||
const idList = Array.isArray(identifiers)
|
||||
? identifiers
|
||||
: (identifiers as any)?.pageItems || [];
|
||||
const idList = Array.isArray(identifiers) ? identifiers : (identifiers as any)?.pageItems || [];
|
||||
|
||||
for (const identity of idList) {
|
||||
const docTypeId = String(identity?.documentType?.id ?? '');
|
||||
|
||||
@@ -10,29 +10,36 @@
|
||||
<div class="cv-top">
|
||||
<!-- Avatar -->
|
||||
<div class="cv-avatar">
|
||||
<img class="cv-avatar__img"
|
||||
[src]="clientImage ? clientImage : 'assets/images/user_placeholder.png'" />
|
||||
<img class="cv-avatar__img" [src]="clientImage ? clientImage : 'assets/images/user_placeholder.png'" />
|
||||
<div class="cv-avatar__actions">
|
||||
<button class="cv-avatar__btn"
|
||||
<button
|
||||
class="cv-avatar__btn"
|
||||
(click)="doAction('Upload Image')"
|
||||
matTooltip="{{ 'tooltips.Upload Image' | translate }}"
|
||||
*mifosxHasPermission="'CREATE_CLIENTIMAGE'">
|
||||
*mifosxHasPermission="'CREATE_CLIENTIMAGE'"
|
||||
>
|
||||
<i class="fa fa-upload"></i>
|
||||
</button>
|
||||
<button class="cv-avatar__btn"
|
||||
<button
|
||||
class="cv-avatar__btn"
|
||||
(click)="doAction('Capture Image')"
|
||||
matTooltip="{{ 'tooltips.Capture Image' | translate }}"
|
||||
*mifosxHasPermission="'CREATE_CLIENTIMAGE'">
|
||||
*mifosxHasPermission="'CREATE_CLIENTIMAGE'"
|
||||
>
|
||||
<i class="fa fa-camera"></i>
|
||||
</button>
|
||||
<button class="cv-avatar__btn cv-avatar__btn--danger"
|
||||
<button
|
||||
class="cv-avatar__btn cv-avatar__btn--danger"
|
||||
(click)="doAction('Delete Image')"
|
||||
matTooltip="{{ 'tooltips.Delete Image' | translate }}"
|
||||
*mifosxHasPermission="'DELETE_CLIENTIMAGE'">
|
||||
*mifosxHasPermission="'DELETE_CLIENTIMAGE'"
|
||||
>
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
<span class="cv-avatar__sig" (click)="doAction('View Signature')">{{ 'labels.inputs.View Signature' | translate }}</span>
|
||||
<span class="cv-avatar__sig" (click)="doAction('View Signature')">{{
|
||||
'labels.inputs.View Signature' | translate
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<!-- Info -->
|
||||
@@ -40,12 +47,19 @@
|
||||
<!-- Name + Status + Menu -->
|
||||
<div class="cv-name-row">
|
||||
<div class="cv-name">
|
||||
<i class="fa fa-stop cv-name__dot" [ngClass]="clientViewData.status.code | statusLookup"
|
||||
matTooltip="{{ 'labels.status.' + clientViewData.status.value | translate }}"></i>
|
||||
<i
|
||||
class="fa fa-stop cv-name__dot"
|
||||
[ngClass]="clientViewData.status.code | statusLookup"
|
||||
matTooltip="{{ 'labels.status.' + clientViewData.status.value | translate }}"
|
||||
></i>
|
||||
<span class="cv-name__text">
|
||||
<mifosx-entity-name
|
||||
[entityName]="complianceHideClientData ? maskName(clientViewData.displayName) : clientViewData.displayName"
|
||||
[display]="'right'" [hideCopy]="complianceHideClientData"></mifosx-entity-name>
|
||||
[entityName]="
|
||||
complianceHideClientData ? maskName(clientViewData.displayName) : clientViewData.displayName
|
||||
"
|
||||
[display]="'right'"
|
||||
[hideCopy]="complianceHideClientData"
|
||||
></mifosx-entity-name>
|
||||
</span>
|
||||
<span class="cv-name__status">
|
||||
{{ 'labels.status.' + clientViewData.status.value | translate }}
|
||||
@@ -77,8 +91,12 @@
|
||||
<span class="cv-id__label">{{ 'labels.inputs.External Id' | translate }}</span>
|
||||
<span class="cv-id__val">
|
||||
<mifosx-external-identifier
|
||||
[externalId]="complianceHideClientData ? maskValue(clientViewData.externalId) : clientViewData.externalId"
|
||||
[completed]="true" [hideCopy]="complianceHideClientData"></mifosx-external-identifier>
|
||||
[externalId]="
|
||||
complianceHideClientData ? maskValue(clientViewData.externalId) : clientViewData.externalId
|
||||
"
|
||||
[completed]="true"
|
||||
[hideCopy]="complianceHideClientData"
|
||||
></mifosx-external-identifier>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@@ -118,7 +136,9 @@
|
||||
<div class="cv-cell__value">
|
||||
<mifosx-external-identifier
|
||||
[externalId]="complianceHideClientData ? maskValue(clientViewData.mobileNo) : clientViewData.mobileNo"
|
||||
[completed]="true" [hideCopy]="complianceHideClientData"></mifosx-external-identifier>
|
||||
[completed]="true"
|
||||
[hideCopy]="complianceHideClientData"
|
||||
></mifosx-external-identifier>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -127,8 +147,12 @@
|
||||
<div class="cv-cell__label">{{ 'labels.inputs.Email' | translate }}</div>
|
||||
<div class="cv-cell__value">
|
||||
<mifosx-external-identifier
|
||||
[externalId]="complianceHideClientData ? maskEmail(clientViewData.emailAddress) : clientViewData.emailAddress"
|
||||
[completed]="true" [hideCopy]="complianceHideClientData"></mifosx-external-identifier>
|
||||
[externalId]="
|
||||
complianceHideClientData ? maskEmail(clientViewData.emailAddress) : clientViewData.emailAddress
|
||||
"
|
||||
[completed]="true"
|
||||
[hideCopy]="complianceHideClientData"
|
||||
></mifosx-external-identifier>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -148,10 +172,26 @@
|
||||
<div class="cv-cell">
|
||||
<div class="cv-cell__label">DIP Status</div>
|
||||
<div class="cv-cell__value">
|
||||
<span class="cv-badge"
|
||||
[class.cv-badge--green]="clientViewData.dipVerified === true || clientViewData.dipVerified === 'true' || clientViewData.dipVerified === 1"
|
||||
[class.cv-badge--yellow]="clientViewData.dipVerified !== true && clientViewData.dipVerified !== 'true' && clientViewData.dipVerified !== 1">
|
||||
{{ (clientViewData.dipVerified === true || clientViewData.dipVerified === 'true' || clientViewData.dipVerified === 1) ? 'Verified' : 'Pending' }}
|
||||
<span
|
||||
class="cv-badge"
|
||||
[class.cv-badge--green]="
|
||||
clientViewData.dipVerified === true ||
|
||||
clientViewData.dipVerified === 'true' ||
|
||||
clientViewData.dipVerified === 1
|
||||
"
|
||||
[class.cv-badge--yellow]="
|
||||
clientViewData.dipVerified !== true &&
|
||||
clientViewData.dipVerified !== 'true' &&
|
||||
clientViewData.dipVerified !== 1
|
||||
"
|
||||
>
|
||||
{{
|
||||
clientViewData.dipVerified === true ||
|
||||
clientViewData.dipVerified === 'true' ||
|
||||
clientViewData.dipVerified === 1
|
||||
? 'Verified'
|
||||
: 'Pending'
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -160,10 +200,26 @@
|
||||
<div class="cv-cell">
|
||||
<div class="cv-cell__label">Sanctions Screening</div>
|
||||
<div class="cv-cell__value">
|
||||
<span class="cv-badge"
|
||||
[class.cv-badge--green]="clientViewData.nameVerified === true || clientViewData.nameVerified === 'true' || clientViewData.nameVerified === 1"
|
||||
[class.cv-badge--red]="clientViewData.nameVerified === false || clientViewData.nameVerified === 'false' || clientViewData.nameVerified === 0">
|
||||
{{ (clientViewData.nameVerified === true || clientViewData.nameVerified === 'true' || clientViewData.nameVerified === 1) ? 'Clear' : 'Pending' }}
|
||||
<span
|
||||
class="cv-badge"
|
||||
[class.cv-badge--green]="
|
||||
clientViewData.nameVerified === true ||
|
||||
clientViewData.nameVerified === 'true' ||
|
||||
clientViewData.nameVerified === 1
|
||||
"
|
||||
[class.cv-badge--red]="
|
||||
clientViewData.nameVerified === false ||
|
||||
clientViewData.nameVerified === 'false' ||
|
||||
clientViewData.nameVerified === 0
|
||||
"
|
||||
>
|
||||
{{
|
||||
clientViewData.nameVerified === true ||
|
||||
clientViewData.nameVerified === 'true' ||
|
||||
clientViewData.nameVerified === 1
|
||||
? 'Clear'
|
||||
: 'Pending'
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -206,244 +262,299 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-menu #clientMenu="matMenu">
|
||||
<button mat-menu-item [routerLink]="['edit']" *mifosxHasPermission="'UPDATE_CLIENT'">
|
||||
<mat-menu #clientMenu="matMenu">
|
||||
<button mat-menu-item [routerLink]="['edit']" *mifosxHasPermission="'UPDATE_CLIENT'">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="edit" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<span>{{ 'labels.buttons.Edit' | translate }}</span>
|
||||
</button>
|
||||
|
||||
@if (isActive()) {
|
||||
<button mat-menu-item [matMenuTriggerFor]="accountApplications">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="money-bill-alt" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<span>{{ 'labels.buttons.Applications' | translate }}</span>
|
||||
</button>
|
||||
}
|
||||
|
||||
<button mat-menu-item [matMenuTriggerFor]="Actions">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="tasks" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<span>{{ 'labels.buttons.Actions' | translate }}</span>
|
||||
</button>
|
||||
|
||||
@if (!clientViewData.staffId) {
|
||||
<button mat-menu-item (click)="doAction('Assign Staff')">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="user-tie" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<span>{{ 'labels.buttons.Assign Staff' | translate }}</span>
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (clientViewData.staffId) {
|
||||
<span>
|
||||
<button mat-menu-item *mifosxHasPermission="'UNASSIGNSTAFF_CLIENT'" (click)="doAction('Unassign Staff')">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="edit" size="sm"></fa-icon>
|
||||
<fa-icon icon="user-tie" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<span>{{ 'labels.buttons.Edit' | translate }}</span>
|
||||
<span>{{ 'labels.buttons.Unassign Staff' | translate }}</span>
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
|
||||
@if (isActive()) {
|
||||
<button mat-menu-item [matMenuTriggerFor]="accountApplications">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="money-bill-alt" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<span>{{ 'labels.buttons.Applications' | translate }}</span>
|
||||
</button>
|
||||
}
|
||||
<button mat-menu-item [matMenuTriggerFor]="More">{{ 'labels.buttons.More' | translate }}</button>
|
||||
</mat-menu>
|
||||
|
||||
<button mat-menu-item [matMenuTriggerFor]="Actions">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="tasks" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<span>{{ 'labels.buttons.Actions' | translate }}</span>
|
||||
</button>
|
||||
<mat-menu #accountApplications="matMenu">
|
||||
<button mat-menu-item *mifosxHasPermission="'CREATE_LOAN'" [routerLink]="['loans-accounts', 'create']">
|
||||
{{ 'labels.buttons.New Loan Account' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item *mifosxHasPermission="'CREATE_SAVINGSACCOUNT'" [routerLink]="['savings-accounts', 'create']">
|
||||
{{ 'labels.buttons.New Savings Account' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item *mifosxHasPermission="'CREATE_SHAREACCOUNT'" [routerLink]="['shares-accounts', 'create']">
|
||||
{{ 'labels.buttons.New Share Account' | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
*mifosxHasPermission="'CREATE_RECURRINGDEPOSITACCOUNT'"
|
||||
[routerLink]="['recurring-deposits-accounts', 'create-recurring-deposits-account']"
|
||||
>
|
||||
{{ 'labels.buttons.New Recurring Deposit Account' | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
*mifosxHasPermission="'CREATE_FIXEDDEPOSITACCOUNT'"
|
||||
[routerLink]="['fixed-deposits-accounts', 'create']"
|
||||
>
|
||||
{{ 'labels.buttons.New Fixed Deposits Account' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item *mifosxHasPermission="'CREATE_CURRENTACCOUNT'" [routerLink]="['current-accounts', 'create']">
|
||||
{{ 'labels.buttons.New Current Account' | translate }}
|
||||
</button>
|
||||
</mat-menu>
|
||||
|
||||
@if (!clientViewData.staffId) {
|
||||
<button mat-menu-item (click)="doAction('Assign Staff')">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="user-tie" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<span>{{ 'labels.buttons.Assign Staff' | translate }}</span>
|
||||
</button>
|
||||
}
|
||||
<mat-menu #Actions="matMenu">
|
||||
<button mat-menu-item (click)="doAction('Close')">{{ 'labels.buttons.Close' | translate }}</button>
|
||||
<button mat-menu-item (click)="doAction('Transfer Client')">
|
||||
{{ 'labels.buttons.Transfer Client' | translate }}
|
||||
</button>
|
||||
@if (clientViewData.status.value === 'Pending') {
|
||||
<span
|
||||
><button mat-menu-item (click)="doAction('Activate')">
|
||||
{{ 'labels.buttons.Activate' | translate }}
|
||||
</button></span
|
||||
>
|
||||
}
|
||||
@if (clientViewData.status.value === 'Pending') {
|
||||
<span
|
||||
><button mat-menu-item (click)="doAction('Withdraw')">
|
||||
{{ 'labels.buttons.Withdraw' | translate }}
|
||||
</button></span
|
||||
>
|
||||
}
|
||||
@if (clientViewData.status.value === 'Pending') {
|
||||
<span
|
||||
><button mat-menu-item (click)="doAction('Reject')">{{ 'labels.buttons.Reject' | translate }}</button></span
|
||||
>
|
||||
}
|
||||
@if (clientViewData.status.value === 'Pending') {
|
||||
<span
|
||||
><button mat-menu-item *mifosxHasPermission="'DELETE_CLIENT'" (click)="doAction('Delete')">
|
||||
{{ 'labels.buttons.Delete' | translate }}
|
||||
</button></span
|
||||
>
|
||||
}
|
||||
@if (clientViewData.status.value === 'Closed') {
|
||||
<span
|
||||
><button mat-menu-item (click)="doAction('Reactivate')">
|
||||
{{ 'labels.buttons.Reactivate' | translate }}
|
||||
</button></span
|
||||
>
|
||||
}
|
||||
@if (clientViewData.status.value === 'Rejected') {
|
||||
<span
|
||||
><button mat-menu-item (click)="doAction('Undo Rejection')">
|
||||
{{ 'labels.buttons.Undo Rejection' | translate }}
|
||||
</button></span
|
||||
>
|
||||
}
|
||||
@if (clientViewData.status.value === 'Transfer in progress') {
|
||||
<span
|
||||
><button mat-menu-item (click)="doAction('Undo Transfer')">
|
||||
{{ 'labels.buttons.Undo Transfer' | translate }}
|
||||
</button></span
|
||||
>
|
||||
}
|
||||
@if (clientViewData.status.value === 'Transfer in progress') {
|
||||
<span
|
||||
><button mat-menu-item (click)="doAction('Accept Transfer')">
|
||||
{{ 'labels.buttons.Accept Transfer' | translate }}
|
||||
</button></span
|
||||
>
|
||||
}
|
||||
@if (clientViewData.status.value === 'Transfer in progress') {
|
||||
<span
|
||||
><button mat-menu-item (click)="doAction('Reject Transfer')">
|
||||
{{ 'labels.buttons.Reject transfer' | translate }}
|
||||
</button></span
|
||||
>
|
||||
}
|
||||
</mat-menu>
|
||||
|
||||
@if (clientViewData.staffId) {
|
||||
<span>
|
||||
<button mat-menu-item *mifosxHasPermission="'UNASSIGNSTAFF_CLIENT'" (click)="doAction('Unassign Staff')">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="user-tie" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<span>{{ 'labels.buttons.Unassign Staff' | translate }}</span>
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
|
||||
<button mat-menu-item [matMenuTriggerFor]="More">{{ 'labels.buttons.More' | translate }}</button>
|
||||
</mat-menu>
|
||||
|
||||
<mat-menu #accountApplications="matMenu">
|
||||
<button mat-menu-item *mifosxHasPermission="'CREATE_LOAN'" [routerLink]="['loans-accounts', 'create']">
|
||||
{{ 'labels.buttons.New Loan Account' | translate }}
|
||||
</button>
|
||||
<mat-menu #More="matMenu">
|
||||
<button mat-menu-item (click)="doAction('Add Charge')" *mifosxHasPermission="'CREATE_CLIENTCHARGE'">
|
||||
{{ 'labels.buttons.Add Charge' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item (click)="doAction('Create Collateral')">
|
||||
{{ 'labels.buttons.Create Collateral' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item (click)="doAction('Survey')" [disabled]="true">
|
||||
{{ 'labels.buttons.Survey' | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="doAction('Update Default Savings')"
|
||||
*mifosxHasPermission="'UPDATESAVINGSACCOUNT_CLIENT'"
|
||||
>
|
||||
{{ 'labels.buttons.Update Default Savings' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item (click)="doAction('Upload Signature')" *mifosxHasPermission="'CREATE_CLIENTIMAGE'">
|
||||
{{ 'labels.buttons.Upload Signature' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item (click)="doAction('Delete Signature')" *mifosxHasPermission="'DELETE_CLIENTIMAGE'">
|
||||
{{ 'labels.buttons.Delete Signature' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item (click)="doAction('Client Screen Reports')">
|
||||
{{ 'labels.buttons.Client Screen Reports' | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="doAction('Create Standing Instructions')"
|
||||
*mifosxHasPermission="'CREATE_STANDINGINSTRUCTION'"
|
||||
>
|
||||
{{ 'labels.buttons.Create Standing Instructions' | translate }}
|
||||
</button>
|
||||
@if (clientViewData.status.value !== 'Transfer on hold') {
|
||||
<span>
|
||||
<button
|
||||
mat-menu-item
|
||||
*mifosxHasPermission="'CREATE_SAVINGSACCOUNT'"
|
||||
[routerLink]="['savings-accounts', 'create']"
|
||||
(click)="doAction('View Standing Instructions')"
|
||||
*mifosxHasPermission="'READ_STANDINGINSTRUCTION'"
|
||||
>
|
||||
{{ 'labels.buttons.New Savings Account' | translate }}
|
||||
{{ 'labels.buttons.View Standing Instructions' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item *mifosxHasPermission="'CREATE_SHAREACCOUNT'" [routerLink]="['shares-accounts', 'create']">
|
||||
{{ 'labels.buttons.New Share Account' | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
*mifosxHasPermission="'CREATE_RECURRINGDEPOSITACCOUNT'"
|
||||
[routerLink]="['recurring-deposits-accounts', 'create-recurring-deposits-account']"
|
||||
>
|
||||
{{ 'labels.buttons.New Recurring Deposit Account' | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
*mifosxHasPermission="'CREATE_FIXEDDEPOSITACCOUNT'"
|
||||
[routerLink]="['fixed-deposits-accounts', 'create']"
|
||||
>
|
||||
{{ 'labels.buttons.New Fixed Deposits Account' | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
*mifosxHasPermission="'CREATE_CURRENTACCOUNT'"
|
||||
[routerLink]="['current-accounts', 'create']"
|
||||
>
|
||||
{{ 'labels.buttons.New Current Account' | translate }}
|
||||
</button>
|
||||
</mat-menu>
|
||||
|
||||
<mat-menu #Actions="matMenu">
|
||||
<button mat-menu-item (click)="doAction('Close')">{{ 'labels.buttons.Close' | translate }}</button>
|
||||
<button mat-menu-item (click)="doAction('Transfer Client')">
|
||||
{{ 'labels.buttons.Transfer Client' | translate }}
|
||||
</button>
|
||||
@if (clientViewData.status.value === 'Pending') {
|
||||
<span
|
||||
><button mat-menu-item (click)="doAction('Activate')">
|
||||
{{ 'labels.buttons.Activate' | translate }}
|
||||
</button></span
|
||||
>
|
||||
}
|
||||
@if (clientViewData.status.value === 'Pending') {
|
||||
<span
|
||||
><button mat-menu-item (click)="doAction('Withdraw')">
|
||||
{{ 'labels.buttons.Withdraw' | translate }}
|
||||
</button></span
|
||||
>
|
||||
}
|
||||
@if (clientViewData.status.value === 'Pending') {
|
||||
<span
|
||||
><button mat-menu-item (click)="doAction('Reject')">{{ 'labels.buttons.Reject' | translate }}</button></span
|
||||
>
|
||||
}
|
||||
@if (clientViewData.status.value === 'Pending') {
|
||||
<span
|
||||
><button mat-menu-item *mifosxHasPermission="'DELETE_CLIENT'" (click)="doAction('Delete')">
|
||||
{{ 'labels.buttons.Delete' | translate }}
|
||||
</button></span
|
||||
>
|
||||
}
|
||||
@if (clientViewData.status.value === 'Closed') {
|
||||
<span
|
||||
><button mat-menu-item (click)="doAction('Reactivate')">
|
||||
{{ 'labels.buttons.Reactivate' | translate }}
|
||||
</button></span
|
||||
>
|
||||
}
|
||||
@if (clientViewData.status.value === 'Rejected') {
|
||||
<span
|
||||
><button mat-menu-item (click)="doAction('Undo Rejection')">
|
||||
{{ 'labels.buttons.Undo Rejection' | translate }}
|
||||
</button></span
|
||||
>
|
||||
}
|
||||
@if (clientViewData.status.value === 'Transfer in progress') {
|
||||
<span
|
||||
><button mat-menu-item (click)="doAction('Undo Transfer')">
|
||||
{{ 'labels.buttons.Undo Transfer' | translate }}
|
||||
</button></span
|
||||
>
|
||||
}
|
||||
@if (clientViewData.status.value === 'Transfer in progress') {
|
||||
<span
|
||||
><button mat-menu-item (click)="doAction('Accept Transfer')">
|
||||
{{ 'labels.buttons.Accept Transfer' | translate }}
|
||||
</button></span
|
||||
>
|
||||
}
|
||||
@if (clientViewData.status.value === 'Transfer in progress') {
|
||||
<span
|
||||
><button mat-menu-item (click)="doAction('Reject Transfer')">
|
||||
{{ 'labels.buttons.Reject transfer' | translate }}
|
||||
</button></span
|
||||
>
|
||||
}
|
||||
</mat-menu>
|
||||
|
||||
<mat-menu #More="matMenu">
|
||||
<button mat-menu-item (click)="doAction('Add Charge')" *mifosxHasPermission="'CREATE_CLIENTCHARGE'">
|
||||
{{ 'labels.buttons.Add Charge' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item (click)="doAction('Create Collateral')">
|
||||
{{ 'labels.buttons.Create Collateral' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item (click)="doAction('Survey')" [disabled]="true">
|
||||
{{ 'labels.buttons.Survey' | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="doAction('Update Default Savings')"
|
||||
*mifosxHasPermission="'UPDATESAVINGSACCOUNT_CLIENT'"
|
||||
>
|
||||
{{ 'labels.buttons.Update Default Savings' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item (click)="doAction('Upload Signature')" *mifosxHasPermission="'CREATE_CLIENTIMAGE'">
|
||||
{{ 'labels.buttons.Upload Signature' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item (click)="doAction('Delete Signature')" *mifosxHasPermission="'DELETE_CLIENTIMAGE'">
|
||||
{{ 'labels.buttons.Delete Signature' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item (click)="doAction('Client Screen Reports')">
|
||||
{{ 'labels.buttons.Client Screen Reports' | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="doAction('Create Standing Instructions')"
|
||||
*mifosxHasPermission="'CREATE_STANDINGINSTRUCTION'"
|
||||
>
|
||||
{{ 'labels.buttons.Create Standing Instructions' | translate }}
|
||||
</button>
|
||||
@if (clientViewData.status.value !== 'Transfer on hold') {
|
||||
<span>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="doAction('View Standing Instructions')"
|
||||
*mifosxHasPermission="'READ_STANDINGINSTRUCTION'"
|
||||
>
|
||||
{{ 'labels.buttons.View Standing Instructions' | translate }}
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
</mat-menu>
|
||||
</span>
|
||||
}
|
||||
</mat-menu>
|
||||
|
||||
<div>
|
||||
<nav mat-tab-nav-bar class="cv-tabs" [tabPanel]="tabPanel">
|
||||
<a mat-tab-link [routerLink]="['./general']" routerLinkActive #general="routerLinkActive" [active]="general.isActive">
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./general']"
|
||||
routerLinkActive
|
||||
#general="routerLinkActive"
|
||||
[active]="general.isActive"
|
||||
>
|
||||
<i class="fa fa-tachometer cv-tab-icon"></i> {{ 'labels.inputs.General' | translate }}
|
||||
</a>
|
||||
<a mat-tab-link [routerLink]="['./personal-data']" routerLinkActive #personalData="routerLinkActive" [active]="personalData.isActive">
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./personal-data']"
|
||||
routerLinkActive
|
||||
#personalData="routerLinkActive"
|
||||
[active]="personalData.isActive"
|
||||
>
|
||||
<i class="fa fa-user cv-tab-icon"></i> {{ 'labels.inputs.Personal Data' | translate }}
|
||||
</a>
|
||||
<a mat-tab-link [routerLink]="['./address']" routerLinkActive #address="routerLinkActive" [active]="address.isActive">
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./address']"
|
||||
routerLinkActive
|
||||
#address="routerLinkActive"
|
||||
[active]="address.isActive"
|
||||
>
|
||||
<i class="fa fa-map-marker cv-tab-icon"></i> {{ 'labels.inputs.Address' | translate }}
|
||||
</a>
|
||||
@if (clientViewData?.legalForm?.id !== 2) {
|
||||
<a mat-tab-link [routerLink]="['./family-members']" routerLinkActive #familyMembers="routerLinkActive" [active]="familyMembers.isActive">
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./family-members']"
|
||||
routerLinkActive
|
||||
#familyMembers="routerLinkActive"
|
||||
[active]="familyMembers.isActive"
|
||||
>
|
||||
<i class="fa fa-users cv-tab-icon"></i> Family Members
|
||||
</a>
|
||||
}
|
||||
<a mat-tab-link [routerLink]="['./nominees']" routerLinkActive #nomineesTab="routerLinkActive" [active]="nomineesTab.isActive">
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./nominees']"
|
||||
routerLinkActive
|
||||
#nomineesTab="routerLinkActive"
|
||||
[active]="nomineesTab.isActive"
|
||||
>
|
||||
<i class="fa fa-shield cv-tab-icon"></i> Nominee
|
||||
</a>
|
||||
<a mat-tab-link [routerLink]="['./identities']" routerLinkActive #identities="routerLinkActive" [active]="identities.isActive" *mifosxHasPermission="'READ_CLIENTIDENTIFIER'">
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./identities']"
|
||||
routerLinkActive
|
||||
#identities="routerLinkActive"
|
||||
[active]="identities.isActive"
|
||||
*mifosxHasPermission="'READ_CLIENTIDENTIFIER'"
|
||||
>
|
||||
<i class="fa fa-id-card cv-tab-icon"></i> {{ 'labels.inputs.Identities' | translate }}
|
||||
</a>
|
||||
<a mat-tab-link [routerLink]="['./kyc']" routerLinkActive #kyc="routerLinkActive" [active]="kyc.isActive">
|
||||
<i class="fa fa-check-circle cv-tab-icon"></i> {{ 'labels.inputs.KYC' | translate }}
|
||||
</a>
|
||||
@if (clientViewData.legalForm?.id !== 1) {
|
||||
<a mat-tab-link [routerLink]="['./entity-classification']" routerLinkActive #ecTab="routerLinkActive" [active]="ecTab.isActive">
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./entity-classification']"
|
||||
routerLinkActive
|
||||
#ecTab="routerLinkActive"
|
||||
[active]="ecTab.isActive"
|
||||
>
|
||||
<i class="fa fa-building cv-tab-icon"></i> Scheme & Sector
|
||||
</a>
|
||||
}
|
||||
<a mat-tab-link [routerLink]="['./ckyc']" routerLinkActive #ckyc="routerLinkActive" [active]="ckyc.isActive">
|
||||
<i class="fa fa-shield cv-tab-icon"></i> {{ 'labels.inputs.CKYC' | translate }}
|
||||
</a>
|
||||
<a mat-tab-link [routerLink]="['./form60']" routerLinkActive #form60="routerLinkActive" [active]="form60.isActive">
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./form60']"
|
||||
routerLinkActive
|
||||
#form60="routerLinkActive"
|
||||
[active]="form60.isActive"
|
||||
>
|
||||
<i class="fa fa-file-text cv-tab-icon"></i> {{ 'labels.inputs.Form 60' | translate }}
|
||||
</a>
|
||||
<a mat-tab-link [routerLink]="['./documents']" routerLinkActive #documents="routerLinkActive" [active]="documents.isActive" *mifosxHasPermission="'READ_DOCUMENT'">
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./documents']"
|
||||
routerLinkActive
|
||||
#documents="routerLinkActive"
|
||||
[active]="documents.isActive"
|
||||
*mifosxHasPermission="'READ_DOCUMENT'"
|
||||
>
|
||||
<i class="fa fa-folder-open cv-tab-icon"></i> {{ 'labels.inputs.Documents' | translate }}
|
||||
</a>
|
||||
<a mat-tab-link [routerLink]="['./notes']" routerLinkActive #notes="routerLinkActive" [active]="notes.isActive" *mifosxHasPermission="'READ_CLIENTNOTE'">
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./notes']"
|
||||
routerLinkActive
|
||||
#notes="routerLinkActive"
|
||||
[active]="notes.isActive"
|
||||
*mifosxHasPermission="'READ_CLIENTNOTE'"
|
||||
>
|
||||
<i class="fa fa-sticky-note cv-tab-icon"></i> {{ 'labels.inputs.Notes' | translate }}
|
||||
</a>
|
||||
@for (clientDatatable of clientDatatables; track clientDatatable) {
|
||||
|
||||
@@ -43,10 +43,17 @@ $accent: #0ea5e9;
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: repeating-linear-gradient(108deg, transparent 0, transparent 80px, rgb(255 255 255 / 2.5%) 80px, rgb(255 255 255 / 2.5%) 160px);
|
||||
background: repeating-linear-gradient(
|
||||
108deg,
|
||||
transparent 0,
|
||||
transparent 80px,
|
||||
rgb(255 255 255 / 2.5%) 80px,
|
||||
rgb(255 255 255 / 2.5%) 160px
|
||||
);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line selector-max-universal */
|
||||
> * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
@@ -90,7 +97,9 @@ $accent: #0ea5e9;
|
||||
cursor: pointer;
|
||||
transition: all 0.12s;
|
||||
|
||||
i { font-size: 10px; }
|
||||
i {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgb(255 255 255 / 20%) !important;
|
||||
@@ -109,12 +118,18 @@ $accent: #0ea5e9;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover { color: #fff; text-decoration: underline; }
|
||||
&:hover {
|
||||
color: #fff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Info column ──
|
||||
.cv-info { flex: 1; min-width: 0; }
|
||||
.cv-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
// ── Name row ──
|
||||
.cv-name-row {
|
||||
@@ -129,7 +144,9 @@ $accent: #0ea5e9;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
&__dot { font-size: 10px; }
|
||||
&__dot {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-size: 20px;
|
||||
@@ -161,7 +178,10 @@ $accent: #0ea5e9;
|
||||
border-radius: 5px !important;
|
||||
line-height: 28px !important;
|
||||
|
||||
&:hover { background: rgb(255 255 255 / 10%); color: #fff !important; }
|
||||
&:hover {
|
||||
background: rgb(255 255 255 / 10%);
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.actions-menu {
|
||||
font-size: 14px !important;
|
||||
@@ -250,12 +270,25 @@ $accent: #0ea5e9;
|
||||
border-radius: 10px;
|
||||
letter-spacing: 0.3px;
|
||||
|
||||
&--green { background: rgb(16 185 129 / 20%); color: #6ee7b7; }
|
||||
&--yellow { background: rgb(245 158 11 / 20%); color: #fcd34d; }
|
||||
&--red { background: rgb(239 68 68 / 20%); color: #fca5a5; }
|
||||
&--green {
|
||||
background: rgb(16 185 129 / 20%);
|
||||
color: #6ee7b7;
|
||||
}
|
||||
|
||||
&--yellow {
|
||||
background: rgb(245 158 11 / 20%);
|
||||
color: #fcd34d;
|
||||
}
|
||||
|
||||
&--red {
|
||||
background: rgb(239 68 68 / 20%);
|
||||
color: #fca5a5;
|
||||
}
|
||||
}
|
||||
|
||||
.actions-menu { color: #fff !important; }
|
||||
.actions-menu {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
// ── Tab bar ──
|
||||
.cv-tabs {
|
||||
@@ -279,7 +312,8 @@ $accent: #0ea5e9;
|
||||
}
|
||||
|
||||
// Fix copy icons on dark background
|
||||
.cv-top, .cv-grid {
|
||||
.cv-top,
|
||||
.cv-grid {
|
||||
::ng-deep {
|
||||
mifosx-external-identifier,
|
||||
mifosx-account-number {
|
||||
@@ -350,16 +384,33 @@ $accent: #0ea5e9;
|
||||
|
||||
// ── Responsive ──
|
||||
@media (width <= 1024px) {
|
||||
.cv-grid { grid-template-columns: repeat(3, 1fr); }
|
||||
.cv-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 768px) {
|
||||
.cv-top { flex-direction: column; align-items: center; text-align: center; }
|
||||
.cv-name-row { justify-content: center; }
|
||||
.cv-ids { justify-content: center; }
|
||||
.cv-grid { grid-template-columns: repeat(2, 1fr); }
|
||||
.cv-top {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cv-name-row {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.cv-ids {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.cv-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 480px) {
|
||||
.cv-grid { grid-template-columns: 1fr; }
|
||||
.cv-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -24,5 +24,7 @@
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions class="icd-actions">
|
||||
<button mat-button type="button" (click)="cancel()">Cancel</button>
|
||||
<button mat-flat-button color="primary" type="button" (click)="save()" [disabled]="!croppedBlob">Use cropped image</button>
|
||||
<button mat-flat-button color="primary" type="button" (click)="save()" [disabled]="!croppedBlob">
|
||||
Use cropped image
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
|
||||
+1
@@ -38,6 +38,7 @@
|
||||
max-height: 60vh;
|
||||
overflow: hidden;
|
||||
|
||||
/* stylelint-disable-next-line selector-type-no-unknown */
|
||||
::ng-deep image-cropper {
|
||||
display: block;
|
||||
max-height: 56vh;
|
||||
|
||||
+33
-80
@@ -30,17 +30,14 @@
|
||||
<strong>KYC Document Upload</strong>
|
||||
<p>
|
||||
Upload KYC documents as per RBI guidelines. Accepted:
|
||||
<i class="fa fa-file-image-o"></i> JPG,
|
||||
<i class="fa fa-file-image-o"></i> PNG,
|
||||
<i class="fa fa-file-image-o"></i> JPG, <i class="fa fa-file-image-o"></i> PNG,
|
||||
<i class="fa fa-file-pdf-o"></i> PDF — max 5MB each.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (loadingProofs) {
|
||||
<div class="kyc-loading">
|
||||
<i class="fa fa-spinner fa-spin"></i> Loading document types…
|
||||
</div>
|
||||
<div class="kyc-loading"><i class="fa fa-spinner fa-spin"></i> Loading document types…</div>
|
||||
}
|
||||
|
||||
<form [formGroup]="uploadDocumentForm" class="kyc-form">
|
||||
@@ -51,12 +48,8 @@
|
||||
<i class="fa fa-user-circle-o"></i>
|
||||
</div>
|
||||
<div class="kyc-section__titles">
|
||||
<p class="kyc-section__title">
|
||||
<i class="fa fa-id-badge"></i> Identity proof
|
||||
</p>
|
||||
<p class="kyc-section__sub">
|
||||
Select document type — upload requirements will update automatically
|
||||
</p>
|
||||
<p class="kyc-section__title"><i class="fa fa-id-badge"></i> Identity proof</p>
|
||||
<p class="kyc-section__sub">Select document type — upload requirements will update automatically</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -73,9 +66,7 @@
|
||||
}
|
||||
|
||||
<div class="kyc-field kyc-mb-sm">
|
||||
<label class="kyc-label">
|
||||
<i class="fa fa-file-text-o"></i> Document type
|
||||
</label>
|
||||
<label class="kyc-label"> <i class="fa fa-file-text-o"></i> Document type </label>
|
||||
<div class="kyc-select-wrap">
|
||||
<i class="fa fa-id-card kyc-select-wrap__icon"></i>
|
||||
<select
|
||||
@@ -94,9 +85,7 @@
|
||||
|
||||
@if (selectedIdProof) {
|
||||
<div class="kyc-field kyc-mb-sm">
|
||||
<label class="kyc-label">
|
||||
<i class="fa fa-hashtag"></i> {{ selectedIdProof.documentKeyLabel }}
|
||||
</label>
|
||||
<label class="kyc-label"> <i class="fa fa-hashtag"></i> {{ selectedIdProof.documentKeyLabel }} </label>
|
||||
<div class="kyc-input-wrap">
|
||||
<i class="fa fa-pencil kyc-input-wrap__icon"></i>
|
||||
<input
|
||||
@@ -122,11 +111,9 @@
|
||||
<div class="kyc-upload-label">
|
||||
<span class="kyc-label">
|
||||
<i class="fa fa-file-image-o"></i>
|
||||
{{ selectedIdProof.sides === 2 ? 'Front side' : (selectedIdProof.name + ' (single copy)') }}
|
||||
</span>
|
||||
<span class="kyc-tag kyc-tag--req">
|
||||
<i class="fa fa-asterisk"></i> Required
|
||||
{{ selectedIdProof.sides === 2 ? 'Front side' : selectedIdProof.name + ' (single copy)' }}
|
||||
</span>
|
||||
<span class="kyc-tag kyc-tag--req"> <i class="fa fa-asterisk"></i> Required </span>
|
||||
</div>
|
||||
<label
|
||||
class="kyc-dropzone"
|
||||
@@ -142,15 +129,12 @@
|
||||
@if (idFiles.front) {
|
||||
<i class="fa fa-check-circle kyc-dropzone__icon"></i>
|
||||
<p class="kyc-dropzone__text">File selected</p>
|
||||
<p class="kyc-dropzone__name">
|
||||
<i class="fa fa-paperclip"></i> {{ getFileName('id', 'front') }}
|
||||
</p>
|
||||
<p class="kyc-dropzone__name"><i class="fa fa-paperclip"></i> {{ getFileName('id', 'front') }}</p>
|
||||
} @else {
|
||||
<i class="fa fa-cloud-upload kyc-dropzone__icon"></i>
|
||||
<p class="kyc-dropzone__text">Click to upload</p>
|
||||
<p class="kyc-dropzone__hint">
|
||||
<i class="fa fa-file-image-o"></i> JPG ·
|
||||
<i class="fa fa-file-image-o"></i> PNG ·
|
||||
<i class="fa fa-file-image-o"></i> JPG · <i class="fa fa-file-image-o"></i> PNG ·
|
||||
<i class="fa fa-file-pdf-o"></i> PDF · max 5MB
|
||||
</p>
|
||||
}
|
||||
@@ -160,12 +144,8 @@
|
||||
@if (selectedIdProof.sides === 2) {
|
||||
<div class="kyc-field">
|
||||
<div class="kyc-upload-label">
|
||||
<span class="kyc-label">
|
||||
<i class="fa fa-file-image-o"></i> Back side
|
||||
</span>
|
||||
<span class="kyc-tag kyc-tag--req">
|
||||
<i class="fa fa-asterisk"></i> Required
|
||||
</span>
|
||||
<span class="kyc-label"> <i class="fa fa-file-image-o"></i> Back side </span>
|
||||
<span class="kyc-tag kyc-tag--req"> <i class="fa fa-asterisk"></i> Required </span>
|
||||
</div>
|
||||
<label
|
||||
class="kyc-dropzone"
|
||||
@@ -181,15 +161,12 @@
|
||||
@if (idFiles.back) {
|
||||
<i class="fa fa-check-circle kyc-dropzone__icon"></i>
|
||||
<p class="kyc-dropzone__text">File selected</p>
|
||||
<p class="kyc-dropzone__name">
|
||||
<i class="fa fa-paperclip"></i> {{ getFileName('id', 'back') }}
|
||||
</p>
|
||||
<p class="kyc-dropzone__name"><i class="fa fa-paperclip"></i> {{ getFileName('id', 'back') }}</p>
|
||||
} @else {
|
||||
<i class="fa fa-cloud-upload kyc-dropzone__icon"></i>
|
||||
<p class="kyc-dropzone__text">Click to upload</p>
|
||||
<p class="kyc-dropzone__hint">
|
||||
<i class="fa fa-file-image-o"></i> JPG ·
|
||||
<i class="fa fa-file-image-o"></i> PNG ·
|
||||
<i class="fa fa-file-image-o"></i> JPG · <i class="fa fa-file-image-o"></i> PNG ·
|
||||
<i class="fa fa-file-pdf-o"></i> PDF · max 5MB
|
||||
</p>
|
||||
}
|
||||
@@ -231,12 +208,8 @@
|
||||
<i class="fa fa-map-marker"></i>
|
||||
</div>
|
||||
<div class="kyc-section__titles">
|
||||
<p class="kyc-section__title">
|
||||
<i class="fa fa-home"></i> Address proof
|
||||
</p>
|
||||
<p class="kyc-section__sub">
|
||||
Select document type — upload requirements will update automatically
|
||||
</p>
|
||||
<p class="kyc-section__title"><i class="fa fa-home"></i> Address proof</p>
|
||||
<p class="kyc-section__sub">Select document type — upload requirements will update automatically</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -258,14 +231,10 @@
|
||||
}
|
||||
|
||||
<div class="kyc-field">
|
||||
<label class="kyc-label">
|
||||
<i class="fa fa-file-text-o"></i> Document type
|
||||
</label>
|
||||
<label class="kyc-label"> <i class="fa fa-file-text-o"></i> Document type </label>
|
||||
<div class="kyc-chip-wrap">
|
||||
@for (group of addressGroups; track group.label; let last = $last) {
|
||||
<div class="kyc-chip-group-label">
|
||||
<i class="fa fa-folder-open-o"></i> {{ group.label }}
|
||||
</div>
|
||||
<div class="kyc-chip-group-label"><i class="fa fa-folder-open-o"></i> {{ group.label }}</div>
|
||||
<div class="kyc-chip-grid">
|
||||
@for (p of group.items; track p.id) {
|
||||
<span
|
||||
@@ -305,11 +274,9 @@
|
||||
<div class="kyc-upload-label">
|
||||
<span class="kyc-label">
|
||||
<i class="fa fa-file-image-o"></i>
|
||||
{{ selectedAddrProof.sides === 2 ? 'Front side' : (selectedAddrProof.name + ' (single copy)') }}
|
||||
</span>
|
||||
<span class="kyc-tag kyc-tag--req">
|
||||
<i class="fa fa-asterisk"></i> Required
|
||||
{{ selectedAddrProof.sides === 2 ? 'Front side' : selectedAddrProof.name + ' (single copy)' }}
|
||||
</span>
|
||||
<span class="kyc-tag kyc-tag--req"> <i class="fa fa-asterisk"></i> Required </span>
|
||||
</div>
|
||||
<label
|
||||
class="kyc-dropzone"
|
||||
@@ -332,8 +299,7 @@
|
||||
<i class="fa fa-cloud-upload kyc-dropzone__icon"></i>
|
||||
<p class="kyc-dropzone__text">Click to upload</p>
|
||||
<p class="kyc-dropzone__hint">
|
||||
<i class="fa fa-file-image-o"></i> JPG ·
|
||||
<i class="fa fa-file-image-o"></i> PNG ·
|
||||
<i class="fa fa-file-image-o"></i> JPG · <i class="fa fa-file-image-o"></i> PNG ·
|
||||
<i class="fa fa-file-pdf-o"></i> PDF · max 5MB
|
||||
</p>
|
||||
}
|
||||
@@ -343,12 +309,8 @@
|
||||
@if (selectedAddrProof.sides === 2) {
|
||||
<div class="kyc-field">
|
||||
<div class="kyc-upload-label">
|
||||
<span class="kyc-label">
|
||||
<i class="fa fa-file-image-o"></i> Back side
|
||||
</span>
|
||||
<span class="kyc-tag kyc-tag--req">
|
||||
<i class="fa fa-asterisk"></i> Required
|
||||
</span>
|
||||
<span class="kyc-label"> <i class="fa fa-file-image-o"></i> Back side </span>
|
||||
<span class="kyc-tag kyc-tag--req"> <i class="fa fa-asterisk"></i> Required </span>
|
||||
</div>
|
||||
<label
|
||||
class="kyc-dropzone"
|
||||
@@ -371,8 +333,7 @@
|
||||
<i class="fa fa-cloud-upload kyc-dropzone__icon"></i>
|
||||
<p class="kyc-dropzone__text">Click to upload</p>
|
||||
<p class="kyc-dropzone__hint">
|
||||
<i class="fa fa-file-image-o"></i> JPG ·
|
||||
<i class="fa fa-file-image-o"></i> PNG ·
|
||||
<i class="fa fa-file-image-o"></i> JPG · <i class="fa fa-file-image-o"></i> PNG ·
|
||||
<i class="fa fa-file-pdf-o"></i> PDF · max 5MB
|
||||
</p>
|
||||
}
|
||||
@@ -388,11 +349,7 @@
|
||||
</label>
|
||||
<div class="kyc-input-wrap kyc-input-wrap--date">
|
||||
<i class="fa fa-calendar-o kyc-input-wrap__icon"></i>
|
||||
<input
|
||||
type="date"
|
||||
class="kyc-input kyc-input--date"
|
||||
formControlName="issueDate"
|
||||
/>
|
||||
<input type="date" class="kyc-input kyc-input--date" formControlName="issueDate" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -439,19 +396,21 @@
|
||||
<div class="kyc-status-bar">
|
||||
<span>
|
||||
<span class="kyc-dot" [class.kyc-dot--done]="isIdComplete()">
|
||||
@if (isIdComplete()) { <i class="fa fa-check"></i> }
|
||||
@if (isIdComplete()) {
|
||||
<i class="fa fa-check"></i>
|
||||
}
|
||||
</span>
|
||||
<i class="fa fa-id-badge"></i> Identity proof
|
||||
</span>
|
||||
<span>
|
||||
<span class="kyc-dot" [class.kyc-dot--done]="isAddressComplete()">
|
||||
@if (isAddressComplete()) { <i class="fa fa-check"></i> }
|
||||
@if (isAddressComplete()) {
|
||||
<i class="fa fa-check"></i>
|
||||
}
|
||||
</span>
|
||||
<i class="fa fa-home"></i> Address proof
|
||||
</span>
|
||||
<span class="kyc-status-hint">
|
||||
<i class="fa fa-info-circle"></i> Fill in at least one section
|
||||
</span>
|
||||
<span class="kyc-status-hint"> <i class="fa fa-info-circle"></i> Fill in at least one section </span>
|
||||
</div>
|
||||
</form>
|
||||
} @else {
|
||||
@@ -488,13 +447,7 @@
|
||||
<i class="fa fa-times fd-btn__icon"></i>
|
||||
{{ 'labels.buttons.Cancel' | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
[disabled]="!canSubmit"
|
||||
(click)="submit()"
|
||||
class="fd-btn fd-btn--submit"
|
||||
>
|
||||
<button mat-flat-button color="primary" [disabled]="!canSubmit" (click)="submit()" class="fd-btn fd-btn--submit">
|
||||
<i class="fa fa-check fd-btn__icon"></i>
|
||||
@if (documentIdentifier) {
|
||||
{{ 'labels.buttons.Submit' | translate }}
|
||||
|
||||
+85
-29
@@ -304,8 +304,13 @@ $red-900: #993c1d;
|
||||
border-color: $teal-50;
|
||||
border-left-color: $teal-700;
|
||||
|
||||
> i:first-child { color: $teal-700; }
|
||||
.kyc-prefill__value { color: $teal-900; }
|
||||
> i:first-child {
|
||||
color: $teal-700;
|
||||
}
|
||||
|
||||
.kyc-prefill__value {
|
||||
color: $teal-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,15 +368,23 @@ $red-900: #993c1d;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
&:hover::-webkit-calendar-picker-indicator { opacity: 0.9; }
|
||||
&:hover::-webkit-calendar-picker-indicator {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
&::-webkit-datetime-edit {
|
||||
color: $gray-900;
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
&::-webkit-datetime-edit-fields-wrapper { color: $gray-900; }
|
||||
&::-webkit-datetime-edit-text { color: $gray-500; padding: 0 2px; }
|
||||
&::-webkit-datetime-edit-fields-wrapper {
|
||||
color: $gray-900;
|
||||
}
|
||||
|
||||
&::-webkit-datetime-edit-text {
|
||||
color: $gray-500;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
&:invalid::-webkit-datetime-edit,
|
||||
&:not(:focus):placeholder-shown::-webkit-datetime-edit {
|
||||
@@ -398,9 +411,13 @@ $red-900: #993c1d;
|
||||
font-family: inherit;
|
||||
resize: vertical;
|
||||
min-height: 60px;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
background 0.15s;
|
||||
|
||||
&::placeholder { color: $gray-500; }
|
||||
&::placeholder {
|
||||
color: $gray-500;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
@@ -414,7 +431,10 @@ $red-900: #993c1d;
|
||||
color: $gray-500;
|
||||
padding: 8px 0 14px;
|
||||
|
||||
i { margin-right: 6px; color: $primary; }
|
||||
i {
|
||||
margin-right: 6px;
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
.kyc-form {
|
||||
@@ -452,7 +472,10 @@ $red-900: #993c1d;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
i { color: $primary; font-size: 14px; }
|
||||
i {
|
||||
color: $primary;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
&__sub {
|
||||
@@ -496,7 +519,9 @@ $red-900: #993c1d;
|
||||
}
|
||||
}
|
||||
|
||||
.kyc-mb-sm { margin-bottom: 12px; }
|
||||
.kyc-mb-sm {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.kyc-field {
|
||||
display: flex;
|
||||
@@ -534,9 +559,13 @@ $red-900: #993c1d;
|
||||
color: $gray-900;
|
||||
font-size: 13px;
|
||||
font-family: inherit;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
background 0.15s;
|
||||
|
||||
&::placeholder { color: $gray-500; }
|
||||
&::placeholder {
|
||||
color: $gray-500;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
@@ -553,7 +582,6 @@ $red-900: #993c1d;
|
||||
|
||||
.kyc-select {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='%235f6368' d='M0 0l5 6 5-6z'/></svg>");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 12px center;
|
||||
@@ -570,7 +598,11 @@ $red-900: #993c1d;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 12px;
|
||||
|
||||
i { font-size: 14px; flex-shrink: 0; margin-top: 1px; }
|
||||
i {
|
||||
font-size: 14px;
|
||||
flex-shrink: 0;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
&--blue {
|
||||
background: $blue-50;
|
||||
@@ -670,8 +702,13 @@ $red-900: #993c1d;
|
||||
border-style: solid;
|
||||
background: $teal-50;
|
||||
|
||||
.kyc-dropzone__icon { color: $teal-accent; }
|
||||
.kyc-dropzone__text { color: $teal-900; }
|
||||
.kyc-dropzone__icon {
|
||||
color: $teal-accent;
|
||||
}
|
||||
|
||||
.kyc-dropzone__text {
|
||||
color: $teal-900;
|
||||
}
|
||||
}
|
||||
|
||||
&--req {
|
||||
@@ -682,7 +719,9 @@ $red-900: #993c1d;
|
||||
background: $red-100;
|
||||
}
|
||||
|
||||
.kyc-dropzone__icon { color: $red-700; }
|
||||
.kyc-dropzone__icon {
|
||||
color: $red-700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -704,7 +743,10 @@ $red-900: #993c1d;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
i { color: $gray-500; font-size: 11px; }
|
||||
i {
|
||||
color: $gray-500;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
.kyc-chip-grid {
|
||||
@@ -735,13 +777,18 @@ $red-900: #993c1d;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
|
||||
i { font-size: 11px; color: $gray-500; }
|
||||
i {
|
||||
font-size: 11px;
|
||||
color: $gray-500;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: $teal-700;
|
||||
color: $teal-900;
|
||||
|
||||
i { color: $teal-700; }
|
||||
i {
|
||||
color: $teal-700;
|
||||
}
|
||||
}
|
||||
|
||||
&--sel {
|
||||
@@ -750,7 +797,9 @@ $red-900: #993c1d;
|
||||
color: $teal-900;
|
||||
font-weight: 600;
|
||||
|
||||
i { color: $teal-accent; }
|
||||
i {
|
||||
color: $teal-accent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -765,6 +814,15 @@ $red-900: #993c1d;
|
||||
border-top: 1px solid $gray-200;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
span i {
|
||||
color: $gray-500;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.kyc-status-hint i {
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
.kyc-status-hint {
|
||||
@@ -789,12 +847,6 @@ $red-900: #993c1d;
|
||||
}
|
||||
}
|
||||
|
||||
.kyc-status-bar {
|
||||
span i { color: $gray-500; margin: 0 2px; }
|
||||
|
||||
.kyc-status-hint i { color: $primary; }
|
||||
}
|
||||
|
||||
.kyc-merged {
|
||||
margin-top: 14px;
|
||||
padding: 12px 14px;
|
||||
@@ -815,7 +867,9 @@ $red-900: #993c1d;
|
||||
font-weight: 700;
|
||||
color: #0c4a6e;
|
||||
|
||||
i { margin-right: 6px; }
|
||||
i {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&__hint {
|
||||
@@ -863,7 +917,9 @@ $red-900: #993c1d;
|
||||
font-family: inherit;
|
||||
transition: all 0.15s;
|
||||
|
||||
i { font-size: 11px; }
|
||||
i {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #0284c7;
|
||||
|
||||
+46
-35
@@ -246,9 +246,7 @@ export class UploadDocumentDialogComponent implements OnInit {
|
||||
next: (res: any) => {
|
||||
const activeFilter = (p: any) => p?.active !== false;
|
||||
this.idProofs = (res?.idProofs || []).filter(activeFilter).map((p: any) => this.normalizeProof(p));
|
||||
this.addressProofs = (res?.addressProofs || [])
|
||||
.filter(activeFilter)
|
||||
.map((p: any) => this.normalizeProof(p));
|
||||
this.addressProofs = (res?.addressProofs || []).filter(activeFilter).map((p: any) => this.normalizeProof(p));
|
||||
this.buildAddressGroups();
|
||||
this.loadingProofs = false;
|
||||
},
|
||||
@@ -281,7 +279,12 @@ export class UploadDocumentDialogComponent implements OnInit {
|
||||
if (!map.has(key)) map.set(key, []);
|
||||
map.get(key).push(p);
|
||||
});
|
||||
this.addressGroups = Array.from(map.entries()).map(([label, items]) => ({ label, items }));
|
||||
this.addressGroups = Array.from(map.entries()).map(
|
||||
([
|
||||
label,
|
||||
items
|
||||
]) => ({ label, items })
|
||||
);
|
||||
}
|
||||
|
||||
onIdProofChange(proofId: any): void {
|
||||
@@ -371,40 +374,48 @@ export class UploadDocumentDialogComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
Promise.all([this.fileToImage(front), this.fileToImage(back)])
|
||||
.then(([fImg, bImg]) => {
|
||||
const targetWidth = Math.max(fImg.width, bImg.width);
|
||||
const fScale = targetWidth / fImg.width;
|
||||
const bScale = targetWidth / bImg.width;
|
||||
const fH = fImg.height * fScale;
|
||||
const bH = bImg.height * bScale;
|
||||
const gap = 20;
|
||||
const totalH = Math.round(fH + bH + gap);
|
||||
Promise.all([
|
||||
this.fileToImage(front),
|
||||
this.fileToImage(back)
|
||||
])
|
||||
.then(
|
||||
([
|
||||
fImg,
|
||||
bImg
|
||||
]) => {
|
||||
const targetWidth = Math.max(fImg.width, bImg.width);
|
||||
const fScale = targetWidth / fImg.width;
|
||||
const bScale = targetWidth / bImg.width;
|
||||
const fH = fImg.height * fScale;
|
||||
const bH = bImg.height * bScale;
|
||||
const gap = 20;
|
||||
const totalH = Math.round(fH + bH + gap);
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = targetWidth;
|
||||
canvas.height = totalH;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = targetWidth;
|
||||
canvas.height = totalH;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(fImg, 0, 0, targetWidth, fH);
|
||||
ctx.drawImage(bImg, 0, fH + gap, targetWidth, bH);
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(fImg, 0, 0, targetWidth, fH);
|
||||
ctx.drawImage(bImg, 0, fH + gap, targetWidth, bH);
|
||||
|
||||
canvas.toBlob((blob) => {
|
||||
if (!blob) return;
|
||||
const merged = new File([blob], `${proof.name || 'document'}-combined.png`, { type: 'image/png' });
|
||||
const preview = canvas.toDataURL('image/png');
|
||||
if (section === 'id') {
|
||||
this.idMergedFile = merged;
|
||||
this.idMergedPreview = preview;
|
||||
} else {
|
||||
this.addrMergedFile = merged;
|
||||
this.addrMergedPreview = preview;
|
||||
}
|
||||
}, 'image/png');
|
||||
})
|
||||
canvas.toBlob((blob) => {
|
||||
if (!blob) return;
|
||||
const merged = new File([blob], `${proof.name || 'document'}-combined.png`, { type: 'image/png' });
|
||||
const preview = canvas.toDataURL('image/png');
|
||||
if (section === 'id') {
|
||||
this.idMergedFile = merged;
|
||||
this.idMergedPreview = preview;
|
||||
} else {
|
||||
this.addrMergedFile = merged;
|
||||
this.addrMergedPreview = preview;
|
||||
}
|
||||
}, 'image/png');
|
||||
}
|
||||
)
|
||||
.catch(() => this.resetMerged(section));
|
||||
}
|
||||
|
||||
|
||||
+108
-25
@@ -1,61 +1,144 @@
|
||||
.ec-view { padding: 16px 0; }
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
.ec-view {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.ec-loading {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 12px;
|
||||
padding: 48px; color: #64748b;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 48px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.ec-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
|
||||
h3 { font-size: 16px; font-weight: 600; color: #1e293b; display: flex; align-items: center; gap: 8px;
|
||||
fa-icon { color: #1565c0; }
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
fa-icon {
|
||||
color: #1565c0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ec-count {
|
||||
font-size: 12px; font-weight: 600; color: #059669;
|
||||
background: #ecfdf5; padding: 3px 10px; border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #059669;
|
||||
background: #ecfdf5;
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.ec-empty {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 8px;
|
||||
padding: 40px; color: #94a3b8; font-size: 14px;
|
||||
background: #f8fafc; border: 2px dashed #e2e8f0; border-radius: 10px;
|
||||
mat-icon { font-size: 36px; width: 36px; height: 36px; }
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 40px;
|
||||
color: #94a3b8;
|
||||
font-size: 14px;
|
||||
background: #f8fafc;
|
||||
border: 2px dashed #e2e8f0;
|
||||
border-radius: 10px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 36px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.ec-card {
|
||||
border: 1px solid #e2e8f0; border-radius: 10px; margin-bottom: 12px; overflow: hidden;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ec-card__header {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 12px 16px; background: #f8fafc; border-bottom: 1px solid #f1f5f9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
}
|
||||
|
||||
.ec-card__tag {
|
||||
font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.ec-card__title { font-size: 14px; font-weight: 600; color: #1e293b; flex: 1; }
|
||||
.ec-card__title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.ec-card__badge {
|
||||
font-size: 10px; font-weight: 700; color: #059669;
|
||||
background: #ecfdf5; padding: 2px 8px; border-radius: 10px; border: 1px solid #a7f3d0;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: #059669;
|
||||
background: #ecfdf5;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #a7f3d0;
|
||||
}
|
||||
|
||||
.ec-card__body { padding: 14px 16px; }
|
||||
.ec-card__body {
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.ec-card__grid {
|
||||
display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px 16px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px 16px;
|
||||
}
|
||||
|
||||
.ec-card__field { display: flex; flex-direction: column; gap: 2px; }
|
||||
.ec-card__label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; color: #94a3b8; font-weight: 500; }
|
||||
.ec-card__value { font-size: 13px; font-weight: 500; color: #1e293b; }
|
||||
.ec-card__field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.ec-card__label {
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: #94a3b8;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ec-card__value {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
@media (width <= 768px) {
|
||||
.ec-card__grid { grid-template-columns: repeat(2, 1fr); }
|
||||
.ec-card__grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
+51
-11
@@ -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, inject } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
@@ -29,19 +37,45 @@ export class EntityClassificationTabComponent implements OnInit {
|
||||
categories = [
|
||||
{ key: 'msme', label: 'MSME', tag: 'Udyam / MSME', tagColor: '#0A5C6B', tagBg: '#E6F4F6', icon: 'cogs' },
|
||||
{ key: 'startup', label: 'Startup India', tag: 'DPIIT', tagColor: '#1A5FAB', tagBg: '#EAF2FB', icon: 'rocket' },
|
||||
{ key: 'mudra', label: 'Mudra Borrower', tag: 'PMMY', tagColor: '#8A5A00', tagBg: '#FEF3DC', icon: 'hand-holding-usd' },
|
||||
{
|
||||
key: 'mudra',
|
||||
label: 'Mudra Borrower',
|
||||
tag: 'PMMY',
|
||||
tagColor: '#8A5A00',
|
||||
tagBg: '#FEF3DC',
|
||||
icon: 'hand-holding-usd'
|
||||
},
|
||||
{ key: 'eou', label: 'Export Oriented Unit', tag: 'EOU', tagColor: '#1B7A4A', tagBg: '#E8F5EE', icon: 'ship' },
|
||||
{ key: 'importexport', label: 'Import Export', tag: 'DGFT / IEC', tagColor: '#5B3A8A', tagBg: '#F0EBF8', icon: 'exchange-alt' },
|
||||
{
|
||||
key: 'importexport',
|
||||
label: 'Import Export',
|
||||
tag: 'DGFT / IEC',
|
||||
tagColor: '#5B3A8A',
|
||||
tagBg: '#F0EBF8',
|
||||
icon: 'exchange-alt'
|
||||
},
|
||||
{ key: 'psl', label: 'Priority Sector', tag: 'RBI PSL', tagColor: '#7B1F1F', tagBg: '#FCEAEA', icon: 'bullseye' },
|
||||
{ key: 'nps', label: 'Non-Priority Sector', tag: 'General', tagColor: '#444441', tagBg: '#F1EFE8', icon: 'building' }
|
||||
{
|
||||
key: 'nps',
|
||||
label: 'Non-Priority Sector',
|
||||
tag: 'General',
|
||||
tagColor: '#444441',
|
||||
tagBg: '#F1EFE8',
|
||||
icon: 'building'
|
||||
}
|
||||
];
|
||||
|
||||
ngOnInit() {
|
||||
this.clientId = this.route.parent?.snapshot.paramMap.get('clientId') || '';
|
||||
if (this.clientId) {
|
||||
this.clientsService.getEntityClassifications(this.clientId).subscribe({
|
||||
next: (res: any) => { this.data = res; this.loading = false; },
|
||||
error: () => { this.loading = false; }
|
||||
next: (res: any) => {
|
||||
this.data = res;
|
||||
this.loading = false;
|
||||
},
|
||||
error: () => {
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.loading = false;
|
||||
@@ -50,19 +84,25 @@ export class EntityClassificationTabComponent implements OnInit {
|
||||
|
||||
get enabledCategories(): any[] {
|
||||
if (!this.data) return [];
|
||||
return this.categories.filter(c => this.data[c.key]?.enabled);
|
||||
return this.categories.filter((c) => this.data[c.key]?.enabled);
|
||||
}
|
||||
|
||||
getCategoryData(key: string): any {
|
||||
return this.data?.[key] || {};
|
||||
}
|
||||
|
||||
getFieldPairs(catData: any): { label: string, value: any }[] {
|
||||
getFieldPairs(catData: any): { label: string; value: any }[] {
|
||||
if (!catData) return [];
|
||||
const skip = ['id', 'clientId', 'enabled', 'locale', 'dateFormat'];
|
||||
const skip = [
|
||||
'id',
|
||||
'clientId',
|
||||
'enabled',
|
||||
'locale',
|
||||
'dateFormat'
|
||||
];
|
||||
return Object.keys(catData)
|
||||
.filter(k => !skip.includes(k) && catData[k] != null && catData[k] !== '' && catData[k] !== false)
|
||||
.map(k => ({
|
||||
.filter((k) => !skip.includes(k) && catData[k] != null && catData[k] !== '' && catData[k] !== false)
|
||||
.map((k) => ({
|
||||
label: this.formatLabel(k),
|
||||
value: this.formatValue(catData[k])
|
||||
}));
|
||||
@@ -71,7 +111,7 @@ export class EntityClassificationTabComponent implements OnInit {
|
||||
private formatLabel(key: string): string {
|
||||
return key
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
.replace(/^./, s => s.toUpperCase())
|
||||
.replace(/^./, (s) => s.toUpperCase())
|
||||
.replace(/Id$/, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
-->
|
||||
|
||||
<div class="nom-container mat-typography">
|
||||
|
||||
<!-- ═══ FAMILY MEMBERS SECTION ═══ -->
|
||||
<div class="nom-header">
|
||||
<div class="nom-header__left">
|
||||
@@ -50,7 +49,12 @@
|
||||
<button mat-stroked-button color="primary" class="nom-actions__edit" (click)="editFamilyMember(member, i)">
|
||||
<fa-icon icon="edit" class="m-r-10"></fa-icon> Edit
|
||||
</button>
|
||||
<button mat-stroked-button color="warn" class="nom-actions__delete" (click)="deleteMember(member, 'Family Member')">
|
||||
<button
|
||||
mat-stroked-button
|
||||
color="warn"
|
||||
class="nom-actions__delete"
|
||||
(click)="deleteMember(member, 'Family Member')"
|
||||
>
|
||||
<fa-icon icon="trash" class="m-r-10"></fa-icon> Delete
|
||||
</button>
|
||||
</div>
|
||||
@@ -91,5 +95,4 @@
|
||||
</mat-expansion-panel>
|
||||
}
|
||||
</mat-accordion>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -80,12 +80,12 @@ export class FamilyMembersTabComponent {
|
||||
|
||||
/** Family members (non-nominee entries) */
|
||||
get familyMembers(): any[] {
|
||||
return this.clientFamilyMembers.filter(m => !m.nominationType || m.nominationType === '');
|
||||
return this.clientFamilyMembers.filter((m) => !m.nominationType || m.nominationType === '');
|
||||
}
|
||||
|
||||
/** Nominees (entries with nominationType set) */
|
||||
get nominees(): any[] {
|
||||
return this.clientFamilyMembers.filter(m => m.nominationType && m.nominationType !== '');
|
||||
return this.clientFamilyMembers.filter((m) => m.nominationType && m.nominationType !== '');
|
||||
}
|
||||
|
||||
/** Get relationship display name */
|
||||
|
||||
+3
-1
@@ -44,7 +44,9 @@ $white: #fff;
|
||||
height: 32px !important;
|
||||
line-height: 32px !important;
|
||||
color: $gray-500;
|
||||
transition: color 0.15s ease, background 0.15s ease;
|
||||
transition:
|
||||
color 0.15s ease,
|
||||
background 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
color: $gray-900;
|
||||
|
||||
+8
-2
@@ -48,8 +48,14 @@ export class NomineeDialogComponent implements OnInit {
|
||||
const member = this.data.member;
|
||||
this.nomineeForm = this.formBuilder.group({
|
||||
nominationType: [member?.nominationType || 'DA1'],
|
||||
firstName: [member?.firstName || '', Validators.required],
|
||||
relationshipId: [member?.relationshipId || '', Validators.required],
|
||||
firstName: [
|
||||
member?.firstName || '',
|
||||
Validators.required
|
||||
],
|
||||
relationshipId: [
|
||||
member?.relationshipId || '',
|
||||
Validators.required
|
||||
],
|
||||
dateOfBirth: [member?.dateOfBirth ? this.dateUtils.formatDate(member.dateOfBirth, 'yyyy-MM-dd') : null],
|
||||
percentageShare: [member?.percentageShare ?? 100],
|
||||
isDependent: [member?.isDependent || false],
|
||||
|
||||
@@ -61,13 +61,17 @@
|
||||
}
|
||||
@if (form60Data.dateOfBirth) {
|
||||
<div class="f60-detail">
|
||||
<span class="f60-detail__label"><mat-icon class="f60-detail__icon">calendar_today</mat-icon> Date of Birth</span>
|
||||
<span class="f60-detail__label"
|
||||
><mat-icon class="f60-detail__icon">calendar_today</mat-icon> Date of Birth</span
|
||||
>
|
||||
<span class="f60-detail__value">{{ form60Data.dateOfBirth | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (form60Data.fatherName) {
|
||||
<div class="f60-detail">
|
||||
<span class="f60-detail__label"><mat-icon class="f60-detail__icon">family_restroom</mat-icon> Father's Name</span>
|
||||
<span class="f60-detail__label"
|
||||
><mat-icon class="f60-detail__icon">family_restroom</mat-icon> Father's Name</span
|
||||
>
|
||||
<span class="f60-detail__value">{{ form60Data.fatherName }}</span>
|
||||
</div>
|
||||
}
|
||||
@@ -79,25 +83,33 @@
|
||||
}
|
||||
@if (form60Data.transactionType) {
|
||||
<div class="f60-detail">
|
||||
<span class="f60-detail__label"><mat-icon class="f60-detail__icon">receipt_long</mat-icon> Transaction Type</span>
|
||||
<span class="f60-detail__label"
|
||||
><mat-icon class="f60-detail__icon">receipt_long</mat-icon> Transaction Type</span
|
||||
>
|
||||
<span class="f60-detail__value">{{ form60Data.transactionType }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (form60Data.transactionAmount) {
|
||||
<div class="f60-detail">
|
||||
<span class="f60-detail__label"><mat-icon class="f60-detail__icon">currency_rupee</mat-icon> Transaction Amount</span>
|
||||
<span class="f60-detail__label"
|
||||
><mat-icon class="f60-detail__icon">currency_rupee</mat-icon> Transaction Amount</span
|
||||
>
|
||||
<span class="f60-detail__value">₹ {{ form60Data.transactionAmount }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (form60Data.estimatedTotalIncome) {
|
||||
<div class="f60-detail">
|
||||
<span class="f60-detail__label"><mat-icon class="f60-detail__icon">account_balance_wallet</mat-icon> Estimated Total Income</span>
|
||||
<span class="f60-detail__label"
|
||||
><mat-icon class="f60-detail__icon">account_balance_wallet</mat-icon> Estimated Total Income</span
|
||||
>
|
||||
<span class="f60-detail__value">₹ {{ form60Data.estimatedTotalIncome }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (form60Data.reasonForNoPan) {
|
||||
<div class="f60-detail">
|
||||
<span class="f60-detail__label"><mat-icon class="f60-detail__icon">help_outline</mat-icon> Reason for No PAN</span>
|
||||
<span class="f60-detail__label"
|
||||
><mat-icon class="f60-detail__icon">help_outline</mat-icon> Reason for No PAN</span
|
||||
>
|
||||
<span class="f60-detail__value">{{ form60Data.reasonForNoPan }}</span>
|
||||
</div>
|
||||
}
|
||||
@@ -181,7 +193,8 @@
|
||||
<!-- Declaration -->
|
||||
<div class="f60-declaration">
|
||||
<mat-checkbox formControlName="declaration">
|
||||
I hereby declare that I do not have a Permanent Account Number (PAN) and the information provided above is true and correct to the best of my knowledge and belief.
|
||||
I hereby declare that I do not have a Permanent Account Number (PAN) and the information provided above
|
||||
is true and correct to the best of my knowledge and belief.
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,19 +6,26 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$primary: #1E6BB8;
|
||||
$primary-light: #EBF3FF;
|
||||
$primary: #1e6bb8;
|
||||
$primary-light: #ebf3ff;
|
||||
$green: #16a34a;
|
||||
$green-light: #dcfce7;
|
||||
$amber: #f59e0b;
|
||||
$amber-light: #fef3c7;
|
||||
$border: #E2E8F0;
|
||||
$text: #1A2332;
|
||||
$muted: #64748B;
|
||||
$bg: #F0F4F8;
|
||||
$border: #e2e8f0;
|
||||
$text: #1a2332;
|
||||
$muted: #64748b;
|
||||
$bg: #f0f4f8;
|
||||
|
||||
.f60-container { padding: 20px 24px; }
|
||||
.f60-loading { display: flex; justify-content: center; padding: 40px; }
|
||||
.f60-container {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
.f60-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
/* ── PAN OK — Not Required ── */
|
||||
.f60-pan-ok {
|
||||
@@ -29,103 +36,260 @@ $bg: #F0F4F8;
|
||||
border-radius: 12px;
|
||||
|
||||
&__icon-wrap {
|
||||
width: 72px; height: 72px; border-radius: 50%;
|
||||
background: #fff; display: inline-flex; align-items: center;
|
||||
justify-content: center; margin-bottom: 16px;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba($green, 0.15);
|
||||
}
|
||||
&__icon { font-size: 40px; width: 40px; height: 40px; color: $green; }
|
||||
&__title { font-size: 20px; font-weight: 700; color: $text; margin: 0 0 8px; }
|
||||
|
||||
&__icon {
|
||||
font-size: 40px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
color: $green;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: $text;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
font-size: 15px; color: $text; margin: 0 0 6px;
|
||||
display: flex; align-items: center; justify-content: center; gap: 6px; flex-wrap: wrap;
|
||||
strong { font-family: 'JetBrains Mono', monospace; letter-spacing: 0.5px; }
|
||||
font-size: 15px;
|
||||
color: $text;
|
||||
margin: 0 0 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
strong {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
}
|
||||
|
||||
&__tick {
|
||||
font-size: 18px !important;
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
color: $green;
|
||||
}
|
||||
|
||||
&__verified {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $green;
|
||||
}
|
||||
|
||||
&__rule {
|
||||
font-size: 12px;
|
||||
color: $muted;
|
||||
margin: 12px 0 0;
|
||||
}
|
||||
&__tick { font-size: 18px !important; width: 18px !important; height: 18px !important; color: $green; }
|
||||
&__verified { font-size: 13px; font-weight: 600; color: $green; }
|
||||
&__rule { font-size: 12px; color: $muted; margin: 12px 0 0; }
|
||||
}
|
||||
|
||||
/* ── Warning Banner ── */
|
||||
.f60-warning {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
background: $amber-light; padding: 14px 20px;
|
||||
border-left: 4px solid $amber; border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
background: $amber-light;
|
||||
padding: 14px 20px;
|
||||
border-left: 4px solid $amber;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&__icon { color: $amber; font-size: 24px; flex-shrink: 0; }
|
||||
&__text { font-size: 13px; color: #92400e; line-height: 1.5; strong { font-weight: 700; } }
|
||||
&__icon {
|
||||
color: $amber;
|
||||
font-size: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-size: 13px;
|
||||
color: #92400e;
|
||||
line-height: 1.5;
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Card ── */
|
||||
.f60-card {
|
||||
background: #fff; border: 1px solid $border; border-radius: 12px; overflow: hidden;
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
|
||||
&__header {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 14px 20px; background: $bg; border-bottom: 1px solid $border;
|
||||
h3 { margin: 0; font-size: 15px; font-weight: 600; color: $text; flex: 1; }
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 14px 20px;
|
||||
background: $bg;
|
||||
border-bottom: 1px solid $border;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__header-icon {
|
||||
color: $primary;
|
||||
font-size: 22px;
|
||||
}
|
||||
&__header-icon { color: $primary; font-size: 22px; }
|
||||
|
||||
&__status {
|
||||
font-size: 11px; font-weight: 600; color: $green; background: $green-light;
|
||||
padding: 3px 10px; border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: $green;
|
||||
background: $green-light;
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
&__edit {
|
||||
font-size: 12px !important; height: 30px !important; border-radius: 6px !important;
|
||||
mat-icon { font-size: 16px; width: 16px; height: 16px; margin-right: 4px; }
|
||||
font-size: 12px !important;
|
||||
height: 30px !important;
|
||||
border-radius: 6px !important;
|
||||
|
||||
mat-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 20px;
|
||||
}
|
||||
&__body { padding: 20px; }
|
||||
}
|
||||
|
||||
/* ── Detail Grid (read-only) ── */
|
||||
.f60-detail-grid {
|
||||
display: grid; grid-template-columns: 1fr 1fr; gap: 16px 24px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px 24px;
|
||||
|
||||
@media (width <= 600px) { grid-template-columns: 1fr; }
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.f60-detail {
|
||||
display: flex; flex-direction: column;
|
||||
&--full { grid-column: 1 / -1; }
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 11px; font-weight: 600; text-transform: uppercase;
|
||||
color: $muted; letter-spacing: 0.5px; margin-bottom: 4px;
|
||||
display: flex; align-items: center; gap: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
color: $muted;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 15px !important;
|
||||
width: 15px !important;
|
||||
height: 15px !important;
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
}
|
||||
&__icon { font-size: 15px !important; width: 15px !important; height: 15px !important; color: $primary; }
|
||||
&__value { font-size: 15px; font-weight: 600; color: $text; }
|
||||
}
|
||||
|
||||
/* ── Form Grid ── */
|
||||
.f60-form-grid {
|
||||
display: grid; grid-template-columns: 1fr 1fr; gap: 4px 16px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4px 16px;
|
||||
|
||||
@media (width <= 600px) { grid-template-columns: 1fr; }
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.f60-ff {
|
||||
width: 100%;
|
||||
|
||||
&--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
.f60-ff { width: 100%; &--full { grid-column: 1 / -1; } }
|
||||
|
||||
.f60-prefix {
|
||||
display: inline-flex; align-items: center;
|
||||
padding-left: 4px; padding-right: 8px; margin-right: 8px;
|
||||
border-right: 1.5px solid #cbd5e1; line-height: 1;
|
||||
mat-icon { font-size: 20px; width: 20px; height: 20px; color: $primary; }
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding-left: 4px;
|
||||
padding-right: 8px;
|
||||
margin-right: 8px;
|
||||
border-right: 1.5px solid #cbd5e1;
|
||||
line-height: 1;
|
||||
|
||||
mat-icon {
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Declaration ── */
|
||||
.f60-declaration {
|
||||
margin: 12px 0 20px; padding: 14px 16px;
|
||||
background: $bg; border-radius: 8px; border: 1px solid $border;
|
||||
font-size: 13px; line-height: 1.5; color: $muted;
|
||||
margin: 12px 0 20px;
|
||||
padding: 14px 16px;
|
||||
background: $bg;
|
||||
border-radius: 8px;
|
||||
border: 1px solid $border;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: $muted;
|
||||
}
|
||||
|
||||
/* ── Actions ── */
|
||||
.f60-actions {
|
||||
display: flex; justify-content: flex-end; gap: 10px;
|
||||
button { border-radius: 8px !important; font-weight: 600 !important; }
|
||||
mat-icon { font-size: 18px; width: 18px; height: 18px; margin-right: 4px; vertical-align: middle; }
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
|
||||
button {
|
||||
border-radius: 8px !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-right: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,33 +90,37 @@ export class Form60TabComponent implements OnInit, OnDestroy {
|
||||
|
||||
private loadForm60Purposes(): void {
|
||||
this.loadingPurposes = true;
|
||||
this.clientsService.getCodeValuesByName('FORM_60_PURPOSE').pipe(
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe((values: any[]) => {
|
||||
this.transactionTypes = (values || [])
|
||||
.sort((a: any, b: any) => (a.position || 0) - (b.position || 0))
|
||||
.map((v: any) => ({ value: v.name, label: v.name }));
|
||||
this.loadingPurposes = false;
|
||||
});
|
||||
this.clientsService
|
||||
.getCodeValuesByName('FORM_60_PURPOSE')
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((values: any[]) => {
|
||||
this.transactionTypes = (values || [])
|
||||
.sort((a: any, b: any) => (a.position || 0) - (b.position || 0))
|
||||
.map((v: any) => ({ value: v.name, label: v.name }));
|
||||
this.loadingPurposes = false;
|
||||
});
|
||||
}
|
||||
|
||||
private checkPanStatus(): void {
|
||||
this.checkingPan = true;
|
||||
this.clientsService.getClientKyc(this.clientId).pipe(
|
||||
takeUntil(this.destroy$),
|
||||
catchError(() => of(null))
|
||||
).subscribe((kyc: any) => {
|
||||
this.checkingPan = false;
|
||||
if (kyc?.panNumber) {
|
||||
this.hasPan = true;
|
||||
this.panNumber = kyc.panNumber;
|
||||
this.panVerified = kyc.panVerified || false;
|
||||
this.loading = false;
|
||||
} else {
|
||||
this.hasPan = false;
|
||||
this.loadForm60Data();
|
||||
}
|
||||
});
|
||||
this.clientsService
|
||||
.getClientKyc(this.clientId)
|
||||
.pipe(
|
||||
takeUntil(this.destroy$),
|
||||
catchError(() => of(null))
|
||||
)
|
||||
.subscribe((kyc: any) => {
|
||||
this.checkingPan = false;
|
||||
if (kyc?.panNumber) {
|
||||
this.hasPan = true;
|
||||
this.panNumber = kyc.panNumber;
|
||||
this.panVerified = kyc.panVerified || false;
|
||||
this.loading = false;
|
||||
} else {
|
||||
this.hasPan = false;
|
||||
this.loadForm60Data();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private createForm(): void {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
-->
|
||||
|
||||
<div class="general-tab-container mat-typography">
|
||||
|
||||
<!-- Performance History - KPI Stat Cards -->
|
||||
<section class="performance-section">
|
||||
<h3 class="performance-title">{{ 'labels.heading.Performance History' | translate }}</h3>
|
||||
@@ -164,7 +163,9 @@
|
||||
<td mat-footer-cell *matFooterCellDef [attr.colspan]="openSavingsColumns.length">
|
||||
<div class="table-empty-state">
|
||||
<i class="fa fa-folder-open-o table-empty-state__icon"></i>
|
||||
<p class="table-empty-state__text">{{ 'labels.messages.No Active Saving Accounts Available' | translate }}</p>
|
||||
<p class="table-empty-state__text">
|
||||
{{ 'labels.messages.No Active Saving Accounts Available' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
@@ -211,7 +212,9 @@
|
||||
<td mat-footer-cell *matFooterCellDef [attr.colspan]="closedSavingsColumns.length">
|
||||
<div class="table-empty-state">
|
||||
<i class="fa fa-folder-open-o table-empty-state__icon"></i>
|
||||
<p class="table-empty-state__text">{{ 'labels.messages.No Closed Saving Accounts Available' | translate }}</p>
|
||||
<p class="table-empty-state__text">
|
||||
{{ 'labels.messages.No Closed Saving Accounts Available' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
@@ -233,6 +236,89 @@
|
||||
}
|
||||
</section>
|
||||
|
||||
<!-- Current Accounts -->
|
||||
@if (currentAccounts?.length > 0) {
|
||||
<section class="account-section">
|
||||
<div class="section-header">
|
||||
<div class="section-header__left">
|
||||
<i class="fa fa-credit-card section-header__icon"></i>
|
||||
<h3 class="section-header__title">Current Accounts</h3>
|
||||
<span class="section-header__badge">{{ currentAccounts?.length || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<table mat-table [dataSource]="currentAccounts">
|
||||
<ng-container matColumnDef="Account No">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Account No' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<i class="fa fa-stop" [ngClass]="element.status.code | statusLookup"></i>
|
||||
<mifosx-account-number accountNo="{{ element.accountNo }}"></mifosx-account-number>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="Saving Account">
|
||||
<th mat-header-cell *matHeaderCellDef>Current Account Product</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<mifosx-long-text textValue="{{ element.productName }}" chars="35"></mifosx-long-text>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="Last Active">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Last Active' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.lastActiveTransactionDate | dateFormat }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="Balance">
|
||||
<th mat-header-cell class="r-amount" *matHeaderCellDef>{{ 'labels.inputs.Balance' | translate }}</th>
|
||||
<td mat-cell class="r-amount" *matCellDef="let element">
|
||||
{{ element.accountBalance | currency: element.currency?.code || 'INR' : 'symbol-narrow' : '1.2-2' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="Actions">
|
||||
<th mat-header-cell class="center" *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
||||
<td mat-cell class="center" *matCellDef="let element">
|
||||
@if (element.status.submittedAndPendingApproval) {
|
||||
<button
|
||||
class="account-action-button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
(click)="routeEdit($event)"
|
||||
[routerLink]="['../', 'current-accounts', element.id, 'actions', 'Approve']"
|
||||
>
|
||||
<i class="fa fa-check"></i>
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
class="account-action-button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
(click)="routeEdit($event)"
|
||||
[routerLink]="['../', 'current-accounts', element.id, 'general']"
|
||||
>
|
||||
<i class="fa fa-eye"></i>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="no-data">
|
||||
<td mat-footer-cell *matFooterCellDef [attr.colspan]="openSavingsColumns.length">
|
||||
<div class="table-empty-state">
|
||||
<i class="fa fa-folder-open-o table-empty-state__icon"></i>
|
||||
<p class="table-empty-state__text">No Active Current Accounts Available</p>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="openSavingsColumns"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; columns: openSavingsColumns"
|
||||
[routerLink]="['../', 'current-accounts', row.id, 'general']"
|
||||
></tr>
|
||||
<tr mat-footer-row *matFooterRowDef="currentAccounts?.length === 0 ? ['no-data'] : []"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
<!-- Upcoming Charges -->
|
||||
<section class="account-section">
|
||||
<div class="section-header">
|
||||
@@ -258,7 +344,8 @@
|
||||
<ng-container matColumnDef="Name">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<i class="fa fa-stop" [ngClass]="!(element.isWaived || element.isPaid) | statusLookup"></i> {{ element.name }}
|
||||
<i class="fa fa-stop" [ngClass]="!(element.isWaived || element.isPaid) | statusLookup"></i>
|
||||
{{ element.name }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
@@ -322,7 +409,11 @@
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="upcomingChargesColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: upcomingChargesColumns" [routerLink]="['../', 'charges', row.id]"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; columns: upcomingChargesColumns"
|
||||
[routerLink]="['../', 'charges', row.id]"
|
||||
></tr>
|
||||
<tr mat-footer-row *matFooterRowDef="upcomingCharges?.length === 0 ? ['no-data'] : []"></tr>
|
||||
</table>
|
||||
</div>
|
||||
@@ -407,7 +498,9 @@
|
||||
<td mat-footer-cell *matFooterCellDef [attr.colspan]="openSavingsColumns.length">
|
||||
<div class="table-empty-state">
|
||||
<i class="fa fa-folder-open-o table-empty-state__icon"></i>
|
||||
<p class="table-empty-state__text">{{ 'labels.messages.No Active Fixed Deposit Accounts Available' | translate }}</p>
|
||||
<p class="table-empty-state__text">
|
||||
{{ 'labels.messages.No Active Fixed Deposit Accounts Available' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
@@ -454,7 +547,9 @@
|
||||
<td mat-footer-cell *matFooterCellDef [attr.colspan]="closedSavingsColumns.length">
|
||||
<div class="table-empty-state">
|
||||
<i class="fa fa-folder-open-o table-empty-state__icon"></i>
|
||||
<p class="table-empty-state__text">{{ 'labels.messages.No Closed Fixed Deposit Accounts Available' | translate }}</p>
|
||||
<p class="table-empty-state__text">
|
||||
{{ 'labels.messages.No Closed Fixed Deposit Accounts Available' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
@@ -559,7 +654,9 @@
|
||||
<td mat-footer-cell *matFooterCellDef [attr.colspan]="openSavingsColumns.length">
|
||||
<div class="table-empty-state">
|
||||
<i class="fa fa-folder-open-o table-empty-state__icon"></i>
|
||||
<p class="table-empty-state__text">{{ 'labels.messages.No Active Recurring Deposit Accounts Available' | translate }}</p>
|
||||
<p class="table-empty-state__text">
|
||||
{{ 'labels.messages.No Active Recurring Deposit Accounts Available' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
@@ -607,7 +704,9 @@
|
||||
<td mat-footer-cell *matFooterCellDef [attr.colspan]="closedSavingsColumns.length">
|
||||
<div class="table-empty-state">
|
||||
<i class="fa fa-folder-open-o table-empty-state__icon"></i>
|
||||
<p class="table-empty-state__text">{{ 'labels.messages.No Closed Recurring Deposit Accounts Available' | translate }}</p>
|
||||
<p class="table-empty-state__text">
|
||||
{{ 'labels.messages.No Closed Recurring Deposit Accounts Available' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
@@ -775,7 +874,9 @@
|
||||
<td mat-footer-cell *matFooterCellDef [attr.colspan]="openLoansColumns.length">
|
||||
<div class="table-empty-state">
|
||||
<i class="fa fa-folder-open-o table-empty-state__icon"></i>
|
||||
<p class="table-empty-state__text">{{ 'labels.messages.No Active Loan Accounts Available' | translate }}</p>
|
||||
<p class="table-empty-state__text">
|
||||
{{ 'labels.messages.No Active Loan Accounts Available' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
@@ -867,7 +968,9 @@
|
||||
<td mat-footer-cell *matFooterCellDef [attr.colspan]="closedLoansColumns.length">
|
||||
<div class="table-empty-state">
|
||||
<i class="fa fa-folder-open-o table-empty-state__icon"></i>
|
||||
<p class="table-empty-state__text">{{ 'labels.messages.No Closed Loan Accounts Available' | translate }}</p>
|
||||
<p class="table-empty-state__text">
|
||||
{{ 'labels.messages.No Closed Loan Accounts Available' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
@@ -924,7 +1027,8 @@
|
||||
<th mat-header-cell class="r-amount" *matHeaderCellDef>{{ 'labels.inputs.Total Value' | translate }}</th>
|
||||
<td mat-cell class="r-amount" *matCellDef="let element">
|
||||
{{
|
||||
element.basePrice * element.quantity | currency: element.currency?.code || 'USD' : 'symbol-narrow' : '1.2-2'
|
||||
element.basePrice * element.quantity
|
||||
| currency: element.currency?.code || 'USD' : 'symbol-narrow' : '1.2-2'
|
||||
}}
|
||||
</td>
|
||||
</ng-container>
|
||||
@@ -1043,7 +1147,9 @@
|
||||
<td mat-footer-cell *matFooterCellDef [attr.colspan]="openSharesColumns.length">
|
||||
<div class="table-empty-state">
|
||||
<i class="fa fa-folder-open-o table-empty-state__icon"></i>
|
||||
<p class="table-empty-state__text">{{ 'labels.messages.No Active Share Accounts Available' | translate }}</p>
|
||||
<p class="table-empty-state__text">
|
||||
{{ 'labels.messages.No Active Share Accounts Available' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
@@ -1094,7 +1200,9 @@
|
||||
<td mat-footer-cell *matFooterCellDef [attr.colspan]="closedSharesColumns.length">
|
||||
<div class="table-empty-state">
|
||||
<i class="fa fa-folder-open-o table-empty-state__icon"></i>
|
||||
<p class="table-empty-state__text">{{ 'labels.messages.No Closed Share Accounts Available' | translate }}</p>
|
||||
<p class="table-empty-state__text">
|
||||
{{ 'labels.messages.No Closed Share Accounts Available' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
@@ -1123,4 +1231,4 @@
|
||||
<embed [src]="pdfUrl" type="application/pdf" width="100%" height="600px" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -58,9 +58,13 @@ $red: #d93025;
|
||||
background: $white;
|
||||
border-radius: 10px;
|
||||
padding: 18px 20px;
|
||||
box-shadow: 0 1px 3px rgb(0 0 0 / 8%), 0 1px 2px rgb(0 0 0 / 6%);
|
||||
box-shadow:
|
||||
0 1px 3px rgb(0 0 0 / 8%),
|
||||
0 1px 2px rgb(0 0 0 / 6%);
|
||||
border-left: 4px solid transparent;
|
||||
transition: box-shadow 0.2s ease, transform 0.2s ease;
|
||||
transition:
|
||||
box-shadow 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgb(0 0 0 / 10%);
|
||||
@@ -219,6 +223,7 @@ $red: #d93025;
|
||||
}
|
||||
|
||||
// ── Mat Table Styling ──
|
||||
/* stylelint-disable selector-no-qualifying-type */
|
||||
table[mat-table] {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
@@ -312,6 +317,7 @@ table[mat-table] {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
/* stylelint-enable selector-no-qualifying-type */
|
||||
|
||||
// Status color classes (for the fa-stop dots)
|
||||
:host ::ng-deep {
|
||||
|
||||
@@ -228,6 +228,8 @@ export class GeneralTabComponent implements OnDestroy {
|
||||
loanAccounts: any[] = [];
|
||||
/** Savings Accounts Data */
|
||||
savingAccounts: any[] = [];
|
||||
/** Current Accounts Data */
|
||||
currentAccounts: any[] = [];
|
||||
/** Shares Accounts Data */
|
||||
shareAccounts: any[] = [];
|
||||
/** Upcoming Charges Data */
|
||||
@@ -273,6 +275,7 @@ export class GeneralTabComponent implements OnDestroy {
|
||||
(data: { clientAccountsData: any; clientChargesData: any; clientSummary: any; clientCollateralData: any }) => {
|
||||
this.clientAccountData = data.clientAccountsData;
|
||||
this.savingAccounts = data.clientAccountsData?.savingsAccounts ?? [];
|
||||
this.currentAccounts = data.clientAccountsData?.currentAccounts ?? [];
|
||||
this.loanAccounts = data.clientAccountsData?.loanAccounts ?? [];
|
||||
this.shareAccounts = data.clientAccountsData?.shareAccounts ?? [];
|
||||
|
||||
|
||||
@@ -14,7 +14,13 @@
|
||||
<h3 class="idt-header__title">{{ 'labels.heading.Identities' | translate }}</h3>
|
||||
<span class="idt-header__badge">{{ clientIdentities?.length || 0 }}</span>
|
||||
</div>
|
||||
<button mat-flat-button color="primary" class="idt-header__btn" (click)="addIdentifier()" *mifosxHasPermission="'CREATE_CLIENTIDENTIFIER'">
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
class="idt-header__btn"
|
||||
(click)="addIdentifier()"
|
||||
*mifosxHasPermission="'CREATE_CLIENTIDENTIFIER'"
|
||||
>
|
||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>{{ 'labels.buttons.Add' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -35,11 +41,21 @@
|
||||
<div class="idt-card__title">
|
||||
<i class="fa fa-file-text idt-card__type-icon"></i>
|
||||
<span class="idt-card__type">{{ identity.documentType?.name }}</span>
|
||||
<span class="idt-card__status" [class.idt-card__status--active]="identity.status === 'clientIdentifierStatusType.active'" [class.idt-card__status--inactive]="identity.status !== 'clientIdentifierStatusType.active'">
|
||||
<span
|
||||
class="idt-card__status"
|
||||
[class.idt-card__status--active]="identity.status === 'clientIdentifierStatusType.active'"
|
||||
[class.idt-card__status--inactive]="identity.status !== 'clientIdentifierStatusType.active'"
|
||||
>
|
||||
{{ identity.status === 'clientIdentifierStatusType.active' ? 'Active' : 'Inactive' }}
|
||||
</span>
|
||||
</div>
|
||||
<button mat-stroked-button color="warn" class="idt-card__delete" (click)="deleteIdentifier(identity.clientId, identity.id, i)" *mifosxHasPermission="'DELETE_CLIENTIDENTIFIER'">
|
||||
<button
|
||||
mat-stroked-button
|
||||
color="warn"
|
||||
class="idt-card__delete"
|
||||
(click)="deleteIdentifier(identity.clientId, identity.id, i)"
|
||||
*mifosxHasPermission="'DELETE_CLIENTIDENTIFIER'"
|
||||
>
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -70,13 +86,22 @@
|
||||
</div>
|
||||
<div class="idt-docs__grid">
|
||||
@for (document of identity.documents; track trackByDocumentId($index, document)) {
|
||||
<div class="idt-doc" [class.idt-doc--clickable]="isPreviewable(document)"
|
||||
<div
|
||||
class="idt-doc"
|
||||
[class.idt-doc--clickable]="isPreviewable(document)"
|
||||
(click)="isPreviewable(document) && openDocumentPreview(identity, document)"
|
||||
(keydown.enter)="isPreviewable(document) && openDocumentPreview(identity, document)"
|
||||
tabindex="0" role="button"
|
||||
[attr.aria-label]="('labels.buttons.Preview' | translate) + ': ' + document.name">
|
||||
tabindex="0"
|
||||
role="button"
|
||||
[attr.aria-label]="('labels.buttons.Preview' | translate) + ': ' + document.name"
|
||||
>
|
||||
@if (previewThumbnails[document.id]) {
|
||||
<img [src]="previewThumbnails[document.id]" [alt]="document.name" class="idt-doc__thumb" loading="lazy" />
|
||||
<img
|
||||
[src]="previewThumbnails[document.id]"
|
||||
[alt]="document.name"
|
||||
class="idt-doc__thumb"
|
||||
loading="lazy"
|
||||
/>
|
||||
} @else {
|
||||
<div class="idt-doc__placeholder">
|
||||
<i class="fa fa-file-text-o"></i>
|
||||
@@ -85,9 +110,7 @@
|
||||
<div class="idt-doc__info">
|
||||
<span class="idt-doc__name">{{ document.name }}</span>
|
||||
@if (document.fileName) {
|
||||
<span class="idt-doc__file">
|
||||
<i class="fa fa-paperclip"></i> {{ document.fileName }}
|
||||
</span>
|
||||
<span class="idt-doc__file"> <i class="fa fa-paperclip"></i> {{ document.fileName }} </span>
|
||||
}
|
||||
</div>
|
||||
@if (isPreviewable(document)) {
|
||||
|
||||
@@ -37,7 +37,10 @@ $red-light: #fce8e6;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
&__icon { font-size: 16px; color: $primary; }
|
||||
&__icon {
|
||||
font-size: 16px;
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
@@ -71,8 +74,17 @@ $red-light: #fce8e6;
|
||||
border-radius: 10px;
|
||||
border: 1px dashed $gray-200;
|
||||
|
||||
&__icon { font-size: 32px; color: $gray-500; margin-bottom: 8px; }
|
||||
&__text { color: $gray-500; font-size: 13px; margin: 0; }
|
||||
&__icon {
|
||||
font-size: 32px;
|
||||
color: $gray-500;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__text {
|
||||
color: $gray-500;
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Identity Card
|
||||
@@ -82,7 +94,9 @@ $red-light: #fce8e6;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 14px;
|
||||
transition: box-shadow 0.15s, border-color 0.15s;
|
||||
transition:
|
||||
box-shadow 0.15s,
|
||||
border-color 0.15s;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
@@ -96,7 +110,7 @@ $red-light: #fce8e6;
|
||||
|
||||
&:hover {
|
||||
border-color: $primary-light;
|
||||
box-shadow: 0 2px 8px rgba(26, 115, 232, 0.08);
|
||||
box-shadow: 0 2px 8px rgb(26 115 232 / 8%);
|
||||
}
|
||||
|
||||
&__header {
|
||||
@@ -126,7 +140,7 @@ $red-light: #fce8e6;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, $primary, #4285f4);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 6px rgba(26, 115, 232, 0.25);
|
||||
box-shadow: 0 2px 6px rgb(26 115 232 / 25%);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -162,14 +176,18 @@ $red-light: #fce8e6;
|
||||
background: $green-light;
|
||||
color: $green;
|
||||
|
||||
&::before { background: $green; }
|
||||
&::before {
|
||||
background: $green;
|
||||
}
|
||||
}
|
||||
|
||||
&--inactive {
|
||||
background: $red-light;
|
||||
color: $red;
|
||||
|
||||
&::before { background: $red; }
|
||||
&::before {
|
||||
background: $red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +207,9 @@ $red-light: #fce8e6;
|
||||
background: $red-light !important;
|
||||
}
|
||||
|
||||
i { font-size: 13px; }
|
||||
i {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
@@ -214,7 +234,9 @@ $red-light: #fce8e6;
|
||||
padding: 8px 14px;
|
||||
border-bottom: 1px dashed $gray-200;
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
@@ -257,8 +279,13 @@ $red-light: #fce8e6;
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
&__row { flex-wrap: wrap; }
|
||||
&__label { min-width: 0; }
|
||||
&__row {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&__label {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +304,9 @@ $red-light: #fce8e6;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
i { color: $primary; }
|
||||
i {
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
&__count {
|
||||
@@ -314,7 +343,7 @@ $red-light: #fce8e6;
|
||||
|
||||
&:hover {
|
||||
border-color: $primary;
|
||||
box-shadow: 0 2px 8px rgba(26, 115, 232, 0.1);
|
||||
box-shadow: 0 2px 8px rgb(26 115 232 / 10%);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
@@ -369,7 +398,9 @@ $red-light: #fce8e6;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
i { font-size: 9px; }
|
||||
i {
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
&__preview {
|
||||
@@ -388,7 +419,11 @@ $red-light: #fce8e6;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&:hover .idt-doc__preview { opacity: 1; }
|
||||
&:hover .idt-doc__preview {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.document-lightbox-host { display: none; }
|
||||
.document-lightbox-host {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -132,18 +132,15 @@ export class IdentitiesTabComponent implements OnDestroy {
|
||||
});
|
||||
|
||||
// Pull display name from parent route's clientViewData
|
||||
this.route.parent.data
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((data: any) => {
|
||||
this.clientName = data?.clientViewData?.displayName || '';
|
||||
});
|
||||
this.route.parent.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: any) => {
|
||||
this.clientName = data?.clientViewData?.displayName || '';
|
||||
});
|
||||
|
||||
// Pull permanent address (falls back to first available address)
|
||||
this.clientService.getClientAddressData(this.clientId).subscribe({
|
||||
next: (addresses: any[]) => {
|
||||
if (!addresses?.length) return;
|
||||
const permanent =
|
||||
addresses.find((a) => /permanent/i.test(a?.addressType || '')) || addresses[0];
|
||||
const permanent = addresses.find((a) => /permanent/i.test(a?.addressType || '')) || addresses[0];
|
||||
this.clientAddress = this.formatAddress(permanent);
|
||||
},
|
||||
error: () => undefined
|
||||
@@ -214,10 +211,7 @@ export class IdentitiesTabComponent implements OnDestroy {
|
||||
const identifierData = {
|
||||
documentTypeId: section.proofId,
|
||||
status: 'Active',
|
||||
documentKey:
|
||||
kind === 'id'
|
||||
? section.documentKey
|
||||
: `${section.proof?.name || 'Address Proof'} - ${this.clientId}`,
|
||||
documentKey: kind === 'id' ? section.documentKey : `${section.proof?.name || 'Address Proof'} - ${this.clientId}`,
|
||||
description: kind === 'address' && section.address ? section.address : description
|
||||
};
|
||||
|
||||
|
||||
@@ -65,12 +65,22 @@
|
||||
<span class="kv-status kv-status--pending"><mat-icon>radio_button_unchecked</mat-icon> Pending</span>
|
||||
}
|
||||
</span>
|
||||
<span class="kv-table__col kv-table__col--date">{{ kycData.panVerifiedOn ? formatDate(kycData.panVerifiedOn) : '—' }}</span>
|
||||
<span class="kv-table__col kv-table__col--date">{{
|
||||
kycData.panVerifiedOn ? formatDate(kycData.panVerifiedOn) : '—'
|
||||
}}</span>
|
||||
<span class="kv-table__col kv-table__col--action">
|
||||
<button mat-stroked-button class="kv-action-btn" type="button"
|
||||
[disabled]="verifying.pan || !kycData.panNumber" (click)="verifyPAN()">
|
||||
@if (verifying.pan) { <mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner> }
|
||||
@else { Re-Verify }
|
||||
<button
|
||||
mat-stroked-button
|
||||
class="kv-action-btn"
|
||||
type="button"
|
||||
[disabled]="verifying.pan || !kycData.panNumber"
|
||||
(click)="verifyPAN()"
|
||||
>
|
||||
@if (verifying.pan) {
|
||||
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner>
|
||||
} @else {
|
||||
Re-Verify
|
||||
}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
@@ -88,11 +98,18 @@
|
||||
<span class="kv-status kv-status--pending"><mat-icon>radio_button_unchecked</mat-icon> Pending</span>
|
||||
}
|
||||
</span>
|
||||
<span class="kv-table__col kv-table__col--date">{{ kycData.aadhaarVerifiedOn ? formatDate(kycData.aadhaarVerifiedOn) : '—' }}</span>
|
||||
<span class="kv-table__col kv-table__col--date">{{
|
||||
kycData.aadhaarVerifiedOn ? formatDate(kycData.aadhaarVerifiedOn) : '—'
|
||||
}}</span>
|
||||
<span class="kv-table__col kv-table__col--action">
|
||||
@if (!showAadhaarReVerify && !aadhaarOtpSent) {
|
||||
<button mat-stroked-button class="kv-action-btn" type="button"
|
||||
[disabled]="verifying.aadhaar || !kycData.aadhaarNumberMasked" (click)="startAadhaarReVerify()">
|
||||
<button
|
||||
mat-stroked-button
|
||||
class="kv-action-btn"
|
||||
type="button"
|
||||
[disabled]="verifying.aadhaar || !kycData.aadhaarNumberMasked"
|
||||
(click)="startAadhaarReVerify()"
|
||||
>
|
||||
Re-Verify
|
||||
</button>
|
||||
}
|
||||
@@ -113,12 +130,22 @@
|
||||
<span class="kv-status kv-status--pending"><mat-icon>radio_button_unchecked</mat-icon> Pending</span>
|
||||
}
|
||||
</span>
|
||||
<span class="kv-table__col kv-table__col--date">{{ kycData.gstinVerifiedOn ? formatDate(kycData.gstinVerifiedOn) : '—' }}</span>
|
||||
<span class="kv-table__col kv-table__col--date">{{
|
||||
kycData.gstinVerifiedOn ? formatDate(kycData.gstinVerifiedOn) : '—'
|
||||
}}</span>
|
||||
<span class="kv-table__col kv-table__col--action">
|
||||
<button mat-stroked-button class="kv-action-btn" type="button"
|
||||
[disabled]="verifying.gstin || !kycData.gstinNumber" (click)="verifyGSTIN()">
|
||||
@if (verifying.gstin) { <mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner> }
|
||||
@else { Re-Verify }
|
||||
<button
|
||||
mat-stroked-button
|
||||
class="kv-action-btn"
|
||||
type="button"
|
||||
[disabled]="verifying.gstin || !kycData.gstinNumber"
|
||||
(click)="verifyGSTIN()"
|
||||
>
|
||||
@if (verifying.gstin) {
|
||||
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner>
|
||||
} @else {
|
||||
Re-Verify
|
||||
}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
@@ -138,7 +165,9 @@
|
||||
<span class="kv-status kv-status--pending"><mat-icon>radio_button_unchecked</mat-icon> Pending</span>
|
||||
}
|
||||
</span>
|
||||
<span class="kv-table__col kv-table__col--date">{{ kycData.enterprisePanVerifiedOn ? formatDate(kycData.enterprisePanVerifiedOn) : '—' }}</span>
|
||||
<span class="kv-table__col kv-table__col--date">{{
|
||||
kycData.enterprisePanVerifiedOn ? formatDate(kycData.enterprisePanVerifiedOn) : '—'
|
||||
}}</span>
|
||||
<span class="kv-table__col kv-table__col--action"></span>
|
||||
</div>
|
||||
}
|
||||
@@ -202,12 +231,27 @@
|
||||
<div class="kv-otp-bar">
|
||||
<mat-form-field appearance="outline" class="kv-otp-input">
|
||||
<mat-label>Enter 12-digit Aadhaar</mat-label>
|
||||
<input matInput [(ngModel)]="aadhaarFullNumber" [ngModelOptions]="{standalone: true}" maxlength="12" type="password" />
|
||||
<input
|
||||
matInput
|
||||
[(ngModel)]="aadhaarFullNumber"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
maxlength="12"
|
||||
type="password"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="primary" type="button" class="kv-otp-btn"
|
||||
[disabled]="!aadhaarFullNumber || aadhaarFullNumber.length !== 12 || verifying.aadhaar" (click)="sendAadhaarOtp()">
|
||||
@if (verifying.aadhaar) { <mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner> }
|
||||
@else { Send OTP }
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
type="button"
|
||||
class="kv-otp-btn"
|
||||
[disabled]="!aadhaarFullNumber || aadhaarFullNumber.length !== 12 || verifying.aadhaar"
|
||||
(click)="sendAadhaarOtp()"
|
||||
>
|
||||
@if (verifying.aadhaar) {
|
||||
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner>
|
||||
} @else {
|
||||
Send OTP
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
@@ -216,32 +260,69 @@
|
||||
<span class="kv-otp-label"><mat-icon>sms</mat-icon> OTP sent</span>
|
||||
<mat-form-field appearance="outline" class="kv-otp-input">
|
||||
<mat-label>Enter OTP</mat-label>
|
||||
<input matInput [(ngModel)]="aadhaarOtp" [ngModelOptions]="{standalone: true}" maxlength="6" autocomplete="one-time-code" />
|
||||
<input
|
||||
matInput
|
||||
[(ngModel)]="aadhaarOtp"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
maxlength="6"
|
||||
autocomplete="one-time-code"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="primary" type="button" class="kv-otp-btn"
|
||||
[disabled]="!aadhaarOtp || aadhaarOtp.length < 4 || aadhaarOtpVerifying" (click)="verifyAadhaarOtp()">
|
||||
@if (aadhaarOtpVerifying) { <mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner> }
|
||||
@else { Verify }
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
type="button"
|
||||
class="kv-otp-btn"
|
||||
[disabled]="!aadhaarOtp || aadhaarOtp.length < 4 || aadhaarOtpVerifying"
|
||||
(click)="verifyAadhaarOtp()"
|
||||
>
|
||||
@if (aadhaarOtpVerifying) {
|
||||
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner>
|
||||
} @else {
|
||||
Verify
|
||||
}
|
||||
</button>
|
||||
<button
|
||||
mat-stroked-button
|
||||
type="button"
|
||||
class="kv-otp-btn"
|
||||
[disabled]="verifying.aadhaar"
|
||||
(click)="sendAadhaarOtp()"
|
||||
>
|
||||
Resend
|
||||
</button>
|
||||
<button mat-stroked-button type="button" class="kv-otp-btn" [disabled]="verifying.aadhaar" (click)="sendAadhaarOtp()">Resend</button>
|
||||
</div>
|
||||
}
|
||||
@if (verificationError.aadhaar) {
|
||||
<div class="kyc-error-msg"><mat-icon class="kyc-error-icon">error_outline</mat-icon> {{ verificationError.aadhaar }}</div>
|
||||
<div class="kyc-error-msg">
|
||||
<mat-icon class="kyc-error-icon">error_outline</mat-icon> {{ verificationError.aadhaar }}
|
||||
</div>
|
||||
}
|
||||
@if (verificationError.pan) {
|
||||
<div class="kyc-error-msg"><mat-icon class="kyc-error-icon">error_outline</mat-icon> {{ verificationError.pan }}</div>
|
||||
<div class="kyc-error-msg">
|
||||
<mat-icon class="kyc-error-icon">error_outline</mat-icon> {{ verificationError.pan }}
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- KYC Meta Grid -->
|
||||
<div class="kv-meta-grid">
|
||||
<div class="kv-meta-item">
|
||||
<span class="kv-meta-label">KYC Type</span>
|
||||
<span class="kv-meta-value">{{ kycData.kycType === 1 ? 'Individual' : kycData.kycType === 2 ? 'Non-Individual' : '—' }}</span>
|
||||
<span class="kv-meta-value">{{
|
||||
kycData.kycType === 1 ? 'Individual' : kycData.kycType === 2 ? 'Non-Individual' : '—'
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="kv-meta-item">
|
||||
<span class="kv-meta-label">Risk Category</span>
|
||||
<span class="kv-meta-value">{{ kycData.riskCategory === 1 ? 'Low' : kycData.riskCategory === 2 ? 'Medium' : kycData.riskCategory === 3 ? 'High' : '—' }}</span>
|
||||
<span class="kv-meta-value">{{
|
||||
kycData.riskCategory === 1
|
||||
? 'Low'
|
||||
: kycData.riskCategory === 2
|
||||
? 'Medium'
|
||||
: kycData.riskCategory === 3
|
||||
? 'High'
|
||||
: '—'
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="kv-meta-item">
|
||||
<span class="kv-meta-label">Initiated On</span>
|
||||
@@ -323,9 +404,14 @@
|
||||
<div class="rekyc-tl-content">
|
||||
<div class="rekyc-tl-label">Complete Re-KYC</div>
|
||||
<div class="rekyc-tl-sub">Updates Last Verified date and sets new Expiry Date as per RBI norms</div>
|
||||
<button mat-flat-button color="primary" class="rekyc-btn-complete" type="button"
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
class="rekyc-btn-complete"
|
||||
type="button"
|
||||
[disabled]="!selectedDeclarationFile || reKycCompleting || !kycId"
|
||||
(click)="completeReKyc()">
|
||||
(click)="completeReKyc()"
|
||||
>
|
||||
@if (reKycCompleting) {
|
||||
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner>
|
||||
Completing...
|
||||
|
||||
@@ -164,6 +164,24 @@
|
||||
color: #757575;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
&.linked {
|
||||
background: #ecfdf5;
|
||||
color: #059669;
|
||||
border: 1px solid #a7f3d0;
|
||||
}
|
||||
|
||||
&.not-linked {
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
&.exempt {
|
||||
background: #eff6ff;
|
||||
color: #2563eb;
|
||||
border: 1px solid #bfdbfe;
|
||||
}
|
||||
}
|
||||
|
||||
.status-chip-icon {
|
||||
@@ -375,26 +393,6 @@
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.status-chip {
|
||||
&.linked {
|
||||
background: #ecfdf5;
|
||||
color: #059669;
|
||||
border: 1px solid #a7f3d0;
|
||||
}
|
||||
|
||||
&.not-linked {
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
&.exempt {
|
||||
background: #eff6ff;
|
||||
color: #2563eb;
|
||||
border: 1px solid #bfdbfe;
|
||||
}
|
||||
}
|
||||
|
||||
.pan-aadhaar-detail {
|
||||
margin: 12px 0;
|
||||
display: flex;
|
||||
@@ -468,10 +466,17 @@
|
||||
padding: 0;
|
||||
transition: background 0.1s;
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
&:hover { background: #f8fafc; }
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&--link { background: #fafbfc; }
|
||||
&:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
&--link {
|
||||
background: #fafbfc;
|
||||
}
|
||||
}
|
||||
|
||||
.kv-table__col {
|
||||
@@ -486,10 +491,25 @@
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
&--num { flex: 1; }
|
||||
&--status { flex: 0 0 150px; }
|
||||
&--date { flex: 0 0 150px; color: #64748b; font-size: 12px; }
|
||||
&--action { flex: 0 0 110px; text-align: right; }
|
||||
|
||||
&--num {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&--status {
|
||||
flex: 0 0 150px;
|
||||
}
|
||||
|
||||
&--date {
|
||||
flex: 0 0 150px;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&--action {
|
||||
flex: 0 0 110px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.kv-doc-icon {
|
||||
@@ -520,10 +540,25 @@
|
||||
height: 14px !important;
|
||||
}
|
||||
|
||||
&--ok { background: #ecfdf5; color: #059669; }
|
||||
&--pending { background: #f8fafc; color: #94a3b8; }
|
||||
&--fail { background: #fef2f2; color: #dc2626; }
|
||||
&--info { background: #eff6ff; color: #2563eb; }
|
||||
&--ok {
|
||||
background: #ecfdf5;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
&--pending {
|
||||
background: #f8fafc;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
&--fail {
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
&--info {
|
||||
background: #eff6ff;
|
||||
color: #2563eb;
|
||||
}
|
||||
}
|
||||
|
||||
.kv-action-btn {
|
||||
@@ -558,14 +593,21 @@
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
|
||||
mat-icon { font-size: 16px !important; width: 16px !important; height: 16px !important; }
|
||||
mat-icon {
|
||||
font-size: 16px !important;
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.kv-otp-input {
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
|
||||
::ng-deep .mat-mdc-form-field-infix { padding: 6px 0 !important; min-height: 36px !important; }
|
||||
::ng-deep .mat-mdc-form-field-infix {
|
||||
padding: 6px 0 !important;
|
||||
min-height: 36px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.kv-otp-btn {
|
||||
@@ -594,8 +636,14 @@
|
||||
border-right: 1px solid #f1f5f9;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
|
||||
&:nth-child(5n) { border-right: none; }
|
||||
&--full { grid-column: 1 / -1; border-right: none; }
|
||||
&:nth-child(5n) {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
&--full {
|
||||
grid-column: 1 / -1;
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
.kv-meta-label {
|
||||
@@ -614,20 +662,51 @@
|
||||
|
||||
@media (width <= 960px) {
|
||||
.kv-table__col {
|
||||
&--doc { flex: 0 0 120px; }
|
||||
&--date { display: none; }
|
||||
&--action { flex: 0 0 90px; }
|
||||
&--doc {
|
||||
flex: 0 0 120px;
|
||||
}
|
||||
|
||||
&--date {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&--action {
|
||||
flex: 0 0 90px;
|
||||
}
|
||||
}
|
||||
|
||||
.kv-meta-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.kv-meta-item:nth-child(5n) {
|
||||
border-right: 1px solid #f1f5f9;
|
||||
}
|
||||
|
||||
.kv-meta-item:nth-child(3n) {
|
||||
border-right: none;
|
||||
}
|
||||
.kv-meta-grid { grid-template-columns: repeat(3, 1fr); }
|
||||
.kv-meta-item:nth-child(5n) { border-right: 1px solid #f1f5f9; }
|
||||
.kv-meta-item:nth-child(3n) { border-right: none; }
|
||||
}
|
||||
|
||||
@media (width <= 640px) {
|
||||
.kv-table__row { flex-wrap: wrap; }
|
||||
.kv-table__col { &--status, &--action { flex: 0 0 50%; } }
|
||||
.kv-meta-grid { grid-template-columns: 1fr 1fr; }
|
||||
.kv-otp-bar { flex-wrap: wrap; }
|
||||
.kv-table__row {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.kv-table__col {
|
||||
&--status,
|
||||
&--action {
|
||||
flex: 0 0 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.kv-meta-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.kv-otp-bar {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Header Actions ─── */
|
||||
@@ -645,7 +724,9 @@
|
||||
border-color: #93c5fd !important;
|
||||
}
|
||||
|
||||
.m-r-6 { margin-right: 6px; }
|
||||
.m-r-6 {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
/* ─── Re-KYC Section ─── */
|
||||
.rekyc-section {
|
||||
@@ -741,7 +822,9 @@
|
||||
}
|
||||
|
||||
.rekyc-tl-item--last .rekyc-tl-dot {
|
||||
.rekyc-tl-line { display: none; }
|
||||
.rekyc-tl-line {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.rekyc-tl-content {
|
||||
@@ -881,7 +964,10 @@
|
||||
border: 1px solid #a7f3d0;
|
||||
color: #065f46;
|
||||
|
||||
.rekyc-result-icon { color: #059669; font-size: 20px; }
|
||||
.rekyc-result-icon {
|
||||
color: #059669;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&--error {
|
||||
@@ -899,5 +985,7 @@
|
||||
}
|
||||
|
||||
@media (width <= 640px) {
|
||||
.rekyc-upload-zone { max-width: 100%; }
|
||||
.rekyc-upload-zone {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,10 +447,14 @@ export class KycTabComponent implements OnInit, OnDestroy {
|
||||
|
||||
getPanAadhaarStatusLabel(status: string): string {
|
||||
switch (status) {
|
||||
case 'Y': return 'Linked (Seeded)';
|
||||
case 'N': return 'Not Linked';
|
||||
case 'E': return 'Exempt';
|
||||
default: return 'Pending Verification';
|
||||
case 'Y':
|
||||
return 'Linked (Seeded)';
|
||||
case 'N':
|
||||
return 'Not Linked';
|
||||
case 'E':
|
||||
return 'Exempt';
|
||||
default:
|
||||
return 'Pending Verification';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,7 +548,14 @@ export class KycTabComponent implements OnInit, OnDestroy {
|
||||
if (kyc.aadhaarVerifiedOn) y = addLine('Aadhaar Verified On:', this.formatDate(kyc.aadhaarVerifiedOn), y);
|
||||
|
||||
// PAN-Aadhaar Link
|
||||
const seedingStatus = kyc.panAadhaarSeedingStatus === 'Y' ? 'Linked' : kyc.panAadhaarSeedingStatus === 'N' ? 'Not Linked' : kyc.panAadhaarSeedingStatus === 'E' ? 'Exempt' : 'Pending';
|
||||
const seedingStatus =
|
||||
kyc.panAadhaarSeedingStatus === 'Y'
|
||||
? 'Linked'
|
||||
: kyc.panAadhaarSeedingStatus === 'N'
|
||||
? 'Not Linked'
|
||||
: kyc.panAadhaarSeedingStatus === 'E'
|
||||
? 'Exempt'
|
||||
: 'Pending';
|
||||
y = addLine('PAN-Aadhaar Link:', seedingStatus, y);
|
||||
|
||||
// GSTIN (Non-Individual)
|
||||
@@ -573,7 +584,11 @@ export class KycTabComponent implements OnInit, OnDestroy {
|
||||
y += 8;
|
||||
|
||||
y = addLine('KYC Type:', kyc.kycType === 1 ? 'Individual' : kyc.kycType === 2 ? 'Non-Individual' : '—', y);
|
||||
y = addLine('Risk Category:', kyc.riskCategory === 1 ? 'Low' : kyc.riskCategory === 2 ? 'Medium' : kyc.riskCategory === 3 ? 'High' : '—', y);
|
||||
y = addLine(
|
||||
'Risk Category:',
|
||||
kyc.riskCategory === 1 ? 'Low' : kyc.riskCategory === 2 ? 'Medium' : kyc.riskCategory === 3 ? 'High' : '—',
|
||||
y
|
||||
);
|
||||
y = addLine('Initiated On:', this.formatDate(kyc.initiatedOn), y);
|
||||
y = addLine('Last Verified:', this.formatDate(kyc.verifiedOn), y);
|
||||
y = addLine('Expiry Date:', this.formatDate(kyc.expiryDate), y);
|
||||
@@ -627,7 +642,9 @@ I further confirm that:
|
||||
const footerY = doc.internal.pageSize.getHeight() - 10;
|
||||
doc.setFontSize(7);
|
||||
doc.setTextColor(150, 150, 150);
|
||||
doc.text('This is a system-generated document. Generated from CBS Nexora — KYC Module.', pageWidth / 2, footerY, { align: 'center' });
|
||||
doc.text('This is a system-generated document. Generated from CBS Nexora — KYC Module.', pageWidth / 2, footerY, {
|
||||
align: 'center'
|
||||
});
|
||||
|
||||
// Save
|
||||
doc.save(`Self_Declaration_${cifNumber}_${clientName.replace(/\s/g, '_')}.pdf`);
|
||||
@@ -654,7 +671,10 @@ I further confirm that:
|
||||
const formData = new FormData();
|
||||
formData.append('file', this.selectedDeclarationFile);
|
||||
formData.append('name', 'KYC Self Declaration');
|
||||
formData.append('description', `Signed self-declaration form uploaded on ${new Date().toLocaleDateString('en-IN')}`);
|
||||
formData.append(
|
||||
'description',
|
||||
`Signed self-declaration form uploaded on ${new Date().toLocaleDateString('en-IN')}`
|
||||
);
|
||||
|
||||
this.clientsService.uploadClientDocument(this.clientId, formData).subscribe({
|
||||
next: () => {
|
||||
@@ -668,13 +688,19 @@ I further confirm that:
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.reKycCompleting = false;
|
||||
this.reKycError = err?.error?.defaultUserMessage || err?.error?.developerMessage || 'Failed to complete re-KYC. Please try again.';
|
||||
this.reKycError =
|
||||
err?.error?.defaultUserMessage ||
|
||||
err?.error?.developerMessage ||
|
||||
'Failed to complete re-KYC. Please try again.';
|
||||
}
|
||||
});
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.reKycCompleting = false;
|
||||
this.reKycError = err?.error?.defaultUserMessage || err?.error?.developerMessage || 'Failed to upload declaration. Please try again.';
|
||||
this.reKycError =
|
||||
err?.error?.defaultUserMessage ||
|
||||
err?.error?.developerMessage ||
|
||||
'Failed to upload declaration. Please try again.';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
<div class="nom-container mat-typography">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="nom-page-header">
|
||||
<div class="nom-page-header__left">
|
||||
@@ -39,9 +38,12 @@
|
||||
<div class="nom-type-chips">
|
||||
@for (group of accountGroups; track group.type) {
|
||||
@if (group.accounts.length > 0) {
|
||||
<button type="button" class="nom-type-chip"
|
||||
<button
|
||||
type="button"
|
||||
class="nom-type-chip"
|
||||
[class.active]="selectedType === group.type"
|
||||
(click)="selectedType = group.type; onTypeChange()">
|
||||
(click)="selectedType = group.type; onTypeChange()"
|
||||
>
|
||||
<mat-icon>{{ group.icon }}</mat-icon>
|
||||
{{ group.label }}
|
||||
<span class="nom-type-chip__count">{{ group.accounts.length }}</span>
|
||||
@@ -55,13 +57,23 @@
|
||||
<mat-label>Account</mat-label>
|
||||
<mat-select [(ngModel)]="selectedAccountId" (ngModelChange)="onAccountChange()">
|
||||
@for (acc of selectedGroup.accounts; track acc.id) {
|
||||
<mat-option [value]="acc.id">{{ acc.productName }} — {{ acc.accountNo }}@if (acc.accountBalance != null) { ({{ acc.accountBalance | number:'1.2-2' }}) }</mat-option>
|
||||
<mat-option [value]="acc.id"
|
||||
>{{ acc.productName }} — {{ acc.accountNo }}
|
||||
@if (acc.accountBalance != null) {
|
||||
({{ acc.accountBalance | number: '1.2-2' }})
|
||||
}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
@if (selectedAccountId && !showAddForm) {
|
||||
<button mat-flat-button color="primary" class="nom-add-btn"
|
||||
[disabled]="totalShare >= 100" (click)="showAddForm = true">
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
class="nom-add-btn"
|
||||
[disabled]="totalShare >= 100"
|
||||
(click)="showAddForm = true"
|
||||
>
|
||||
<mat-icon>add</mat-icon> Add Nominee
|
||||
</button>
|
||||
}
|
||||
@@ -83,10 +95,12 @@
|
||||
<div class="nom-share-bar">
|
||||
<span class="nom-share-bar__label">Share</span>
|
||||
<div class="nom-share-bar__track">
|
||||
<div class="nom-share-bar__fill"
|
||||
<div
|
||||
class="nom-share-bar__fill"
|
||||
[style.width.%]="totalShare > 100 ? 100 : totalShare"
|
||||
[class.over]="totalShare > 100"
|
||||
[class.complete]="totalShare === 100"></div>
|
||||
[class.complete]="totalShare === 100"
|
||||
></div>
|
||||
</div>
|
||||
<span class="nom-share-bar__value" [class.over]="totalShare > 100">{{ totalShare }}%</span>
|
||||
</div>
|
||||
@@ -97,8 +111,12 @@
|
||||
<div class="nom-card__main">
|
||||
<div class="nom-card__avatar">
|
||||
@if (nominee.profileImageUrl) {
|
||||
<img [src]="nominee.profileImageUrl" alt="" class="nom-card__photo zoomable"
|
||||
(click)="zoomImage(nominee.profileImageUrl, 'Nominee Photo')" />
|
||||
<img
|
||||
[src]="nominee.profileImageUrl"
|
||||
alt=""
|
||||
class="nom-card__photo zoomable"
|
||||
(click)="zoomImage(nominee.profileImageUrl, 'Nominee Photo')"
|
||||
/>
|
||||
} @else {
|
||||
<mat-icon>person</mat-icon>
|
||||
}
|
||||
@@ -107,14 +125,22 @@
|
||||
<span class="nom-card__name">{{ nominee.nomineeDisplayName || nominee.firstName || '—' }}</span>
|
||||
<span class="nom-card__meta">
|
||||
{{ getRelationship(nominee) }}
|
||||
@if (nominee.nomineeDateOfBirth || nominee.dateOfBirth) { · DOB: {{ (nominee.nomineeDateOfBirth || nominee.dateOfBirth) | dateFormat }} }
|
||||
@if (nominee.nomineeMobileNo || nominee.mobileNo) { · {{ nominee.nomineeMobileNo || nominee.mobileNo }} }
|
||||
@if (nominee.nomineeDateOfBirth || nominee.dateOfBirth) {
|
||||
· DOB: {{ nominee.nomineeDateOfBirth || nominee.dateOfBirth | dateFormat }}
|
||||
}
|
||||
@if (nominee.nomineeMobileNo || nominee.mobileNo) {
|
||||
· {{ nominee.nomineeMobileNo || nominee.mobileNo }}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
@if (nominee.signatureUrl) {
|
||||
<div class="nom-card__sig">
|
||||
<img [src]="nominee.signatureUrl" alt="Signature" class="zoomable"
|
||||
(click)="zoomImage(nominee.signatureUrl, 'Nominee Signature')" />
|
||||
<img
|
||||
[src]="nominee.signatureUrl"
|
||||
alt="Signature"
|
||||
class="zoomable"
|
||||
(click)="zoomImage(nominee.signatureUrl, 'Nominee Signature')"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
@if (nominee.nomineeIdentifier) {
|
||||
@@ -124,16 +150,30 @@
|
||||
<span class="nom-card__share-value">{{ nominee.percentageShare || 0 }}%</span>
|
||||
</div>
|
||||
<div class="nom-card__actions">
|
||||
<button mat-icon-button type="button" matTooltip="View" (click)="viewNominee(nominee)"><mat-icon>visibility</mat-icon></button>
|
||||
<button mat-icon-button type="button" matTooltip="Edit" (click)="editNominee(nominee, i)"><mat-icon>edit</mat-icon></button>
|
||||
<button mat-icon-button type="button" matTooltip="Delete" class="nom-card__delete" (click)="deleteNominee(nominee, i)"><mat-icon>delete</mat-icon></button>
|
||||
<button mat-icon-button type="button" matTooltip="View" (click)="viewNominee(nominee)">
|
||||
<mat-icon>visibility</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button type="button" matTooltip="Edit" (click)="editNominee(nominee, i)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
matTooltip="Delete"
|
||||
class="nom-card__delete"
|
||||
(click)="deleteNominee(nominee, i)"
|
||||
>
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@if (nominee.nomineeIsMinor && (nominee.guardianDisplayName || nominee.guardianName)) {
|
||||
<div class="nom-card__guardian">
|
||||
<mat-icon>shield</mat-icon>
|
||||
Guardian: <strong>{{ nominee.guardianDisplayName || nominee.guardianName }}</strong>
|
||||
@if (nominee.guardianRelationshipName) { ({{ nominee.guardianRelationshipName }}) }
|
||||
@if (nominee.guardianRelationshipName) {
|
||||
({{ nominee.guardianRelationshipName }})
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<!-- Inline Edit -->
|
||||
@@ -143,24 +183,39 @@
|
||||
<mat-form-field appearance="outline" class="nom-edit-field">
|
||||
<mat-label>Type</mat-label>
|
||||
<mat-select [(ngModel)]="editNomType">
|
||||
@for (opt of nominationTypeOptions; track opt.value) { <mat-option [value]="opt.value">{{ opt.label }}</mat-option> }
|
||||
@for (opt of nominationTypeOptions; track opt.value) {
|
||||
<mat-option [value]="opt.value">{{ opt.label }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="nom-edit-field">
|
||||
<mat-label>Relationship</mat-label>
|
||||
<mat-select [(ngModel)]="editRelId">
|
||||
@for (rel of relationshipOptions; track rel) { <mat-option [value]="rel.id">{{ rel.name }}</mat-option> }
|
||||
@for (rel of relationshipOptions; track rel) {
|
||||
<mat-option [value]="rel.id">{{ rel.name }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="nom-edit-field nom-edit-field--sm">
|
||||
<mat-label>Share %</mat-label>
|
||||
<input matInput type="number" [(ngModel)]="editShare" min="1" [max]="maxShareForEdit(i)" />
|
||||
@if (editShare && editShare > maxShareForEdit(i)) { <mat-error>Max {{ maxShareForEdit(i) }}%</mat-error> }
|
||||
@if (editShare && editShare > maxShareForEdit(i)) {
|
||||
<mat-error>Max {{ maxShareForEdit(i) }}%</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<button mat-flat-button color="primary" type="button" class="nom-edit-save"
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
type="button"
|
||||
class="nom-edit-save"
|
||||
[disabled]="!editRelId || !editShare || editShare > maxShareForEdit(i)"
|
||||
(click)="saveEdit(nominee, i)"><mat-icon>check</mat-icon></button>
|
||||
<button mat-stroked-button type="button" class="nom-edit-cancel" (click)="cancelEdit()"><mat-icon>close</mat-icon></button>
|
||||
(click)="saveEdit(nominee, i)"
|
||||
>
|
||||
<mat-icon>check</mat-icon>
|
||||
</button>
|
||||
<button mat-stroked-button type="button" class="nom-edit-cancel" (click)="cancelEdit()">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -185,17 +240,39 @@
|
||||
<mat-form-field appearance="outline" class="nom-search-field">
|
||||
<mat-label>Search by Name, Client ID or Mobile</mat-label>
|
||||
<span matPrefix class="nom-search-prefix"><mat-icon>search</mat-icon></span>
|
||||
<input matInput [value]="searchText" (input)="onSearch($any($event.target).value)" (blur)="dismissSearch()" placeholder="Type at least 2 characters..." autocomplete="off" />
|
||||
<input
|
||||
matInput
|
||||
[value]="searchText"
|
||||
(input)="onSearch($any($event.target).value)"
|
||||
(blur)="dismissSearch()"
|
||||
placeholder="Type at least 2 characters..."
|
||||
autocomplete="off"
|
||||
/>
|
||||
</mat-form-field>
|
||||
@if (searchResults.length > 0 || searching) {
|
||||
<div class="nom-search-results">
|
||||
@if (searching) { <div class="nom-search-loading"><mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner> Searching...</div> }
|
||||
@if (searching) {
|
||||
<div class="nom-search-loading">
|
||||
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner> Searching...
|
||||
</div>
|
||||
}
|
||||
@for (client of searchResults; track client.id) {
|
||||
<div class="nom-search-item" (mousedown)="selectClient(client); $event.preventDefault()">
|
||||
<mat-icon class="nom-search-item__icon">person</mat-icon>
|
||||
<div class="nom-search-item__info">
|
||||
<span class="nom-search-item__name">{{ client.displayName }}</span>
|
||||
<span class="nom-search-item__meta">ID: {{ client.id }} · {{ client.officeName }}@if (client.dateOfBirth) { · DOB: {{ client.dateOfBirth | dateFormat }} }@if (client.fatherSpouseName) { · {{ client.fatherSpouseName }} }@if (client.mobileNo) { · {{ client.mobileNo }} }</span>
|
||||
<span class="nom-search-item__meta"
|
||||
>ID: {{ client.id }} · {{ client.officeName }}
|
||||
@if (client.dateOfBirth) {
|
||||
· DOB: {{ client.dateOfBirth | dateFormat }}
|
||||
}
|
||||
@if (client.fatherSpouseName) {
|
||||
· {{ client.fatherSpouseName }}
|
||||
}
|
||||
@if (client.mobileNo) {
|
||||
· {{ client.mobileNo }}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -212,32 +289,68 @@
|
||||
<div class="nom-linked-card">
|
||||
<div class="nom-linked-card__left">
|
||||
@if (linkedProfileImage) {
|
||||
<img [src]="linkedProfileImage" class="nom-linked-card__photo zoomable" (click)="zoomImage(linkedProfileImage, 'Photo')" />
|
||||
} @else { <mat-icon class="nom-linked-card__icon">person</mat-icon> }
|
||||
<img
|
||||
[src]="linkedProfileImage"
|
||||
class="nom-linked-card__photo zoomable"
|
||||
(click)="zoomImage(linkedProfileImage, 'Photo')"
|
||||
/>
|
||||
} @else {
|
||||
<mat-icon class="nom-linked-card__icon">person</mat-icon>
|
||||
}
|
||||
</div>
|
||||
<div class="nom-linked-card__info">
|
||||
<span class="nom-linked-card__name">{{ selectedClient.displayName }}</span>
|
||||
<span class="nom-linked-card__meta">ID: {{ selectedClient.id }}@if (selectedClient.mobileNo) { · {{ selectedClient.mobileNo }} }</span>
|
||||
<span class="nom-linked-card__meta"
|
||||
>ID: {{ selectedClient.id }}
|
||||
@if (selectedClient.mobileNo) {
|
||||
· {{ selectedClient.mobileNo }}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
@if (linkedSignatureImage) {
|
||||
<div class="nom-linked-card__sig">
|
||||
<img [src]="linkedSignatureImage" alt="Signature" class="zoomable" (click)="zoomImage(linkedSignatureImage, 'Signature')" />
|
||||
<img
|
||||
[src]="linkedSignatureImage"
|
||||
alt="Signature"
|
||||
class="zoomable"
|
||||
(click)="zoomImage(linkedSignatureImage, 'Signature')"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
<button mat-icon-button type="button" (click)="removeSelectedClient()"><mat-icon>close</mat-icon></button>
|
||||
</div>
|
||||
|
||||
@if (fetchingDetails) {
|
||||
<div class="nom-fetching-hint"><mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner> Fetching details...</div>
|
||||
<div class="nom-fetching-hint">
|
||||
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner> Fetching details...
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!fetchingDetails) {
|
||||
<!-- KYC details -->
|
||||
@if (selectedClient.dateOfBirth || selectedClient.panNumber || selectedClient.aadhaarMasked) {
|
||||
<div class="nom-readonly-row">
|
||||
@if (selectedClient.dateOfBirth) { <span class="nom-ro-item"><span class="nom-ro-label">DOB</span> {{ selectedClient.dateOfBirth | dateFormat }}</span> }
|
||||
@if (selectedClient.panNumber) { <span class="nom-ro-item"><span class="nom-ro-label">PAN</span> {{ selectedClient.panNumber }} @if (linkedKycStatus?.panVerified) { <mat-icon class="nom-ro-ok">check_circle</mat-icon> }</span> }
|
||||
@if (selectedClient.aadhaarMasked) { <span class="nom-ro-item"><span class="nom-ro-label">Aadhaar</span> {{ selectedClient.aadhaarMasked }} @if (linkedKycStatus?.aadhaarVerified) { <mat-icon class="nom-ro-ok">check_circle</mat-icon> }</span> }
|
||||
@if (selectedClient.dateOfBirth) {
|
||||
<span class="nom-ro-item"
|
||||
><span class="nom-ro-label">DOB</span> {{ selectedClient.dateOfBirth | dateFormat }}</span
|
||||
>
|
||||
}
|
||||
@if (selectedClient.panNumber) {
|
||||
<span class="nom-ro-item"
|
||||
><span class="nom-ro-label">PAN</span> {{ selectedClient.panNumber }}
|
||||
@if (linkedKycStatus?.panVerified) {
|
||||
<mat-icon class="nom-ro-ok">check_circle</mat-icon>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
@if (selectedClient.aadhaarMasked) {
|
||||
<span class="nom-ro-item"
|
||||
><span class="nom-ro-label">Aadhaar</span> {{ selectedClient.aadhaarMasked }}
|
||||
@if (linkedKycStatus?.aadhaarVerified) {
|
||||
<mat-icon class="nom-ro-ok">check_circle</mat-icon>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -246,19 +359,25 @@
|
||||
<mat-form-field appearance="outline" class="nom-field-item">
|
||||
<mat-label>Nomination Type</mat-label>
|
||||
<mat-select [(ngModel)]="nomineeNomType">
|
||||
@for (opt of nominationTypeOptions; track opt.value) { <mat-option [value]="opt.value">{{ opt.label }}</mat-option> }
|
||||
@for (opt of nominationTypeOptions; track opt.value) {
|
||||
<mat-option [value]="opt.value">{{ opt.label }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="nom-field-item">
|
||||
<mat-label>Relationship</mat-label>
|
||||
<mat-select [(ngModel)]="nomineeRelationshipId">
|
||||
@for (rel of relationshipOptions; track rel) { <mat-option [value]="rel.id">{{ rel.name }}</mat-option> }
|
||||
@for (rel of relationshipOptions; track rel) {
|
||||
<mat-option [value]="rel.id">{{ rel.name }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="nom-field-item nom-field-item--sm">
|
||||
<mat-label>Share %</mat-label>
|
||||
<input matInput type="number" [(ngModel)]="nomineeShare" min="1" [max]="maxAllowedShare" />
|
||||
@if (nomineeShare && nomineeShare > maxAllowedShare) { <mat-error>Max {{ maxAllowedShare }}%</mat-error> }
|
||||
@if (nomineeShare && nomineeShare > maxAllowedShare) {
|
||||
<mat-error>Max {{ maxAllowedShare }}%</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<div class="nom-field-item nom-field-item--toggle">
|
||||
<mat-slide-toggle [(ngModel)]="nomineeIsMinor">Minor</mat-slide-toggle>
|
||||
@@ -270,11 +389,21 @@
|
||||
<span class="nom-guardian-label"><mat-icon>shield</mat-icon> Guardian</span>
|
||||
@if (!selectedGuardian) {
|
||||
<mat-form-field appearance="outline" class="nom-search-field">
|
||||
<input matInput [ngModel]="guardianSearchText" (ngModelChange)="onGuardianSearch($event)" placeholder="Search guardian..." autocomplete="off" />
|
||||
<input
|
||||
matInput
|
||||
[ngModel]="guardianSearchText"
|
||||
(ngModelChange)="onGuardianSearch($event)"
|
||||
placeholder="Search guardian..."
|
||||
autocomplete="off"
|
||||
/>
|
||||
</mat-form-field>
|
||||
@if (guardianSearchResults.length > 0 || guardianSearching) {
|
||||
<div class="nom-search-results nom-search-results--inline">
|
||||
@if (guardianSearching) { <div class="nom-search-loading"><mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner></div> }
|
||||
@if (guardianSearching) {
|
||||
<div class="nom-search-loading">
|
||||
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner>
|
||||
</div>
|
||||
}
|
||||
@for (client of guardianSearchResults; track client.id) {
|
||||
<div class="nom-search-item" (click)="selectGuardian(client)">
|
||||
<mat-icon class="nom-search-item__icon">person</mat-icon>
|
||||
@@ -284,7 +413,12 @@
|
||||
</div>
|
||||
}
|
||||
} @else {
|
||||
<span class="nom-guardian-selected">{{ selectedGuardian.displayName }} <button mat-icon-button (click)="selectedGuardian = null"><mat-icon>close</mat-icon></button></span>
|
||||
<span class="nom-guardian-selected"
|
||||
>{{ selectedGuardian.displayName }}
|
||||
<button mat-icon-button (click)="selectedGuardian = null">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button></span
|
||||
>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -296,11 +430,24 @@
|
||||
|
||||
<div class="nom-form-actions">
|
||||
<button mat-stroked-button type="button" (click)="resetAddForm()">Cancel</button>
|
||||
<button mat-raised-button color="primary" type="button"
|
||||
[disabled]="!nomineeRelationshipId || !nomineeShare || nomineeSaving || (nomineeShare > maxAllowedShare) || (nomineeIsMinor && !selectedGuardian)"
|
||||
(click)="saveNominee()">
|
||||
@if (nomineeSaving) { <mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner> }
|
||||
@else { <mat-icon>check</mat-icon> Save }
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
type="button"
|
||||
[disabled]="
|
||||
!nomineeRelationshipId ||
|
||||
!nomineeShare ||
|
||||
nomineeSaving ||
|
||||
nomineeShare > maxAllowedShare ||
|
||||
(nomineeIsMinor && !selectedGuardian)
|
||||
"
|
||||
(click)="saveNominee()"
|
||||
>
|
||||
@if (nomineeSaving) {
|
||||
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner>
|
||||
} @else {
|
||||
<mat-icon>check</mat-icon> Save
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -8,189 +8,708 @@
|
||||
$primary: #1074b9;
|
||||
$primary-light: #e8f0fe;
|
||||
|
||||
.nom-container { padding: 16px 24px; }
|
||||
.nom-container {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.nom-page-header {
|
||||
background: #0f172a; padding: 14px 20px; border-radius: 10px; margin-bottom: 16px;
|
||||
&__left { display: flex; align-items: center; gap: 10px; }
|
||||
&__icon { color: #60a5fa; }
|
||||
&__title { font-size: 15px; font-weight: 600; color: #fff; margin: 0; }
|
||||
&__sub { font-size: 10px; color: rgb(255 255 255 / 45%); margin: 1px 0 0; }
|
||||
background: #0f172a;
|
||||
padding: 14px 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__sub {
|
||||
font-size: 10px;
|
||||
color: rgb(255 255 255 / 45%);
|
||||
margin: 1px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-loading { display: flex; align-items: center; gap: 8px; padding: 24px 0; justify-content: center; font-size: 13px; color: #64748b;
|
||||
&--sm { padding: 12px 0; }
|
||||
.nom-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 24px 0;
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
|
||||
&--sm {
|
||||
padding: 12px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-no-accounts {
|
||||
text-align: center; padding: 36px 20px; background: #f8fafc; border-radius: 8px; border: 1px dashed #e2e8f0;
|
||||
&__icon { font-size: 40px; width: 40px; height: 40px; color: #94a3b8; margin-bottom: 8px; }
|
||||
p { color: #64748b; font-size: 13px; margin: 2px 0; }
|
||||
&__sub { font-size: 11px; color: #94a3b8; }
|
||||
text-align: center;
|
||||
padding: 36px 20px;
|
||||
background: #f8fafc;
|
||||
border-radius: 8px;
|
||||
border: 1px dashed #e2e8f0;
|
||||
|
||||
&__icon {
|
||||
font-size: 40px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
color: #94a3b8;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
&__sub {
|
||||
font-size: 11px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
|
||||
/* Toolbar: chips on top, account + add below */
|
||||
.nom-toolbar {
|
||||
display: flex; flex-direction: column; gap: 10px; margin-bottom: 12px;
|
||||
&__right { display: flex; align-items: center; gap: 10px; }
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-type-chips {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.nom-type-chips { display: flex; gap: 6px; flex-wrap: wrap; }
|
||||
|
||||
.nom-type-chip {
|
||||
display: inline-flex; align-items: center; gap: 5px; padding: 6px 12px;
|
||||
border: 1.5px solid #e2e8f0; border-radius: 6px; background: #fff;
|
||||
font-size: 12px; font-weight: 500; color: #64748b; cursor: pointer; transition: all 0.15s;
|
||||
mat-icon { font-size: 16px; width: 16px; height: 16px; }
|
||||
&__count { font-size: 9px; font-weight: 700; background: #f1f5f9; color: #64748b; padding: 0 5px; border-radius: 6px; }
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 6px 12px;
|
||||
border: 1.5px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #64748b;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
|
||||
&.active { border-color: $primary; background: $primary-light; color: $primary;
|
||||
.nom-type-chip__count { background: $primary; color: #fff; }
|
||||
mat-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&__count {
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
background: #f1f5f9;
|
||||
color: #64748b;
|
||||
padding: 0 5px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: $primary;
|
||||
background: $primary-light;
|
||||
color: $primary;
|
||||
|
||||
.nom-type-chip__count {
|
||||
background: $primary;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover:not(.active) {
|
||||
border-color: #94a3b8;
|
||||
}
|
||||
&:hover:not(.active) { border-color: #94a3b8; }
|
||||
}
|
||||
.nom-account-select { flex: 1; min-width: 280px; }
|
||||
.nom-add-btn { border-radius: 6px !important; font-size: 12px !important; height: 36px !important; white-space: nowrap; }
|
||||
|
||||
.nom-account-select {
|
||||
flex: 1;
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
.nom-add-btn {
|
||||
border-radius: 6px !important;
|
||||
font-size: 12px !important;
|
||||
height: 36px !important;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Share bar */
|
||||
.nom-share-bar {
|
||||
display: flex; align-items: center; gap: 8px; padding: 6px 12px;
|
||||
background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; margin-bottom: 8px;
|
||||
&__label { font-size: 11px; font-weight: 600; color: #64748b; white-space: nowrap; }
|
||||
&__track { flex: 1; height: 6px; background: #e2e8f0; border-radius: 3px; overflow: hidden; }
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 12px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
&__label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #64748b;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__track {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background: #e2e8f0;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__fill {
|
||||
height: 100%; border-radius: 3px; transition: width 0.3s;
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
transition: width 0.3s;
|
||||
background: linear-gradient(90deg, #3b82f6, #10b981);
|
||||
&.over { background: #ef4444; } &.complete { background: linear-gradient(90deg, #10b981, #059669); }
|
||||
|
||||
&.over {
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
&.complete {
|
||||
background: linear-gradient(90deg, #10b981, #059669);
|
||||
}
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #10b981;
|
||||
min-width: 35px;
|
||||
text-align: right;
|
||||
|
||||
&.over {
|
||||
color: #ef4444;
|
||||
}
|
||||
}
|
||||
&__value { font-size: 13px; font-weight: 700; color: #10b981; min-width: 35px; text-align: right; &.over { color: #ef4444; } }
|
||||
}
|
||||
|
||||
/* Nominee card — compact */
|
||||
.nom-card { border: 1px solid #e2e8f0; border-radius: 6px; margin-bottom: 6px; overflow: hidden; }
|
||||
.nom-card__main { display: flex; align-items: center; gap: 10px; padding: 8px 12px; }
|
||||
.nom-card {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nom-card__main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.nom-card__avatar {
|
||||
width: 32px; height: 32px; border-radius: 50%; background: #dbeafe;
|
||||
display: flex; align-items: center; justify-content: center; color: #1565c0; flex-shrink: 0;
|
||||
mat-icon { font-size: 18px; }
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: #dbeafe;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #1565c0;
|
||||
flex-shrink: 0;
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-card__photo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.nom-card__photo { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
|
||||
|
||||
.nom-card__sig {
|
||||
flex-shrink: 0;
|
||||
img { height: 24px; max-width: 60px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 3px; background: #fff; padding: 1px 3px; }
|
||||
}
|
||||
.nom-card__info { flex: 1; min-width: 0; }
|
||||
.nom-card__name { font-weight: 600; font-size: 13px; color: #1e293b; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.nom-card__meta { font-size: 11px; color: #64748b; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
|
||||
img {
|
||||
height: 24px;
|
||||
max-width: 60px;
|
||||
object-fit: contain;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
padding: 1px 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-card__info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.nom-card__name {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: #1e293b;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.nom-card__meta {
|
||||
font-size: 11px;
|
||||
color: #64748b;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.nom-card__id {
|
||||
font-size: 10px; font-weight: 600; color: #1d4ed8; background: #eff6ff;
|
||||
padding: 2px 8px; border-radius: 4px; white-space: nowrap;
|
||||
font-family: 'Roboto Mono', monospace; letter-spacing: 0.3px; flex-shrink: 0;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: #1d4ed8;
|
||||
background: #eff6ff;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
letter-spacing: 0.3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nom-card__share {
|
||||
text-align: center;
|
||||
min-width: 40px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nom-card__share-value {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #1565c0;
|
||||
}
|
||||
|
||||
.nom-card__actions {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
button {
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
line-height: 32px !important;
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.nom-card__share { text-align: center; min-width: 40px; flex-shrink: 0; }
|
||||
.nom-card__share-value { font-size: 15px; font-weight: 700; color: #1565c0; }
|
||||
|
||||
.nom-card__actions { display: flex; gap: 0; flex-shrink: 0;
|
||||
.nom-card__delete {
|
||||
color: #94a3b8 !important;
|
||||
|
||||
button { width: 32px !important; height: 32px !important; line-height: 32px !important;
|
||||
mat-icon { font-size: 18px; }
|
||||
&:hover {
|
||||
color: #ef4444 !important;
|
||||
}
|
||||
}
|
||||
.nom-card__delete { color: #94a3b8 !important; &:hover { color: #ef4444 !important; } }
|
||||
|
||||
.nom-card__guardian {
|
||||
display: flex; align-items: center; gap: 5px; padding: 5px 12px; background: #fff7ed;
|
||||
font-size: 11px; color: #92400e; border-top: 1px solid #fde68a;
|
||||
mat-icon { font-size: 14px; width: 14px; height: 14px; }
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 5px 12px;
|
||||
background: #fff7ed;
|
||||
font-size: 11px;
|
||||
color: #92400e;
|
||||
border-top: 1px solid #fde68a;
|
||||
|
||||
mat-icon {
|
||||
font-size: 14px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Inline edit */
|
||||
.nom-card__edit { padding: 8px 12px; border-top: 1px solid #e2e8f0; background: #f8fafc; }
|
||||
.nom-edit-row { display: flex; align-items: center; gap: 8px; }
|
||||
.nom-edit-field { flex: 1; &--sm { max-width: 100px; } }
|
||||
.nom-edit-save, .nom-edit-cancel { min-width: 36px !important; height: 36px !important; padding: 0 !important; }
|
||||
.nom-card__edit {
|
||||
padding: 8px 12px;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.nom-edit-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.nom-edit-field {
|
||||
flex: 1;
|
||||
|
||||
&--sm {
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-edit-save,
|
||||
.nom-edit-cancel {
|
||||
min-width: 36px !important;
|
||||
height: 36px !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Empty */
|
||||
.nom-empty {
|
||||
text-align: center; padding: 24px; background: #f8fafc; border-radius: 6px; border: 1px dashed #e2e8f0;
|
||||
mat-icon { font-size: 28px; width: 28px; height: 28px; color: #94a3b8; margin-bottom: 4px; display: block; margin: 0 auto 4px; }
|
||||
span { font-size: 12px; color: #94a3b8; }
|
||||
text-align: center;
|
||||
padding: 24px;
|
||||
background: #f8fafc;
|
||||
border-radius: 6px;
|
||||
border: 1px dashed #e2e8f0;
|
||||
|
||||
mat-icon {
|
||||
font-size: 28px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
color: #94a3b8;
|
||||
display: block;
|
||||
margin: 0 auto 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══ Add Form ═══ */
|
||||
.nom-add-form {
|
||||
border: 1px solid #e2e8f0; border-radius: 8px; padding: 14px; margin-top: 8px; background: #fafbfc;
|
||||
&__title { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; color: #1e293b; margin-bottom: 12px; }
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
margin-top: 8px;
|
||||
background: #fafbfc;
|
||||
|
||||
&__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Search */
|
||||
.nom-search-wrapper { position: relative; margin-bottom: 8px; }
|
||||
.nom-search-field { width: 100%; }
|
||||
.nom-search-prefix { color: #94a3b8; }
|
||||
.nom-search-wrapper {
|
||||
position: relative;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.nom-search-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nom-search-prefix {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.nom-search-results {
|
||||
position: absolute; top: 56px; left: 0; right: 0; z-index: 100;
|
||||
background: #fff; border: 1px solid #e2e8f0; border-radius: 6px;
|
||||
box-shadow: 0 6px 20px rgb(0 0 0 / 10%); max-height: 220px; overflow-y: auto;
|
||||
&--inline { position: relative; top: 0; margin-top: 4px; }
|
||||
position: absolute;
|
||||
top: 56px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 6px 20px rgb(0 0 0 / 10%);
|
||||
max-height: 220px;
|
||||
overflow-y: auto;
|
||||
|
||||
&--inline {
|
||||
position: relative;
|
||||
top: 0;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-search-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 10px 12px;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
.nom-search-loading { display: flex; align-items: center; gap: 6px; padding: 10px 12px; font-size: 12px; color: #64748b; }
|
||||
|
||||
.nom-search-item {
|
||||
display: flex; align-items: center; gap: 8px; padding: 8px 12px; cursor: pointer; border-bottom: 1px solid #f1f5f9;
|
||||
&:last-child { border-bottom: none; } &:hover { background: #f0f7ff; }
|
||||
&__icon { color: $primary; font-size: 18px; }
|
||||
&__info { display: flex; flex-direction: column; flex: 1; }
|
||||
&__name { font-size: 13px; font-weight: 600; color: #1e293b; }
|
||||
&__meta { font-size: 10px; color: #64748b; }
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #f0f7ff;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: $primary;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
font-size: 10px;
|
||||
color: #64748b;
|
||||
}
|
||||
}
|
||||
|
||||
.search-no-results-hint {
|
||||
font-size: 11px;
|
||||
color: #94a3b8;
|
||||
text-align: center;
|
||||
padding: 4px 0;
|
||||
}
|
||||
.search-no-results-hint { font-size: 11px; color: #94a3b8; text-align: center; padding: 4px 0; }
|
||||
|
||||
/* Linked card */
|
||||
.nom-linked-card {
|
||||
display: flex; align-items: center; gap: 8px; padding: 8px 12px;
|
||||
background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; margin-bottom: 8px;
|
||||
&__left { flex-shrink: 0; }
|
||||
&__icon { color: $primary; }
|
||||
&__photo { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; border: 2px solid #e2e8f0; }
|
||||
&__info { flex: 1; display: flex; flex-direction: column; }
|
||||
&__name { font-size: 13px; font-weight: 600; color: #1e293b; }
|
||||
&__meta { font-size: 11px; color: #64748b; font-family: 'Roboto Mono', monospace; }
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
&__left {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
&__photo {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
border: 2px solid #e2e8f0;
|
||||
}
|
||||
|
||||
&__info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
font-size: 11px;
|
||||
color: #64748b;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
}
|
||||
|
||||
&__sig {
|
||||
flex-shrink: 0;
|
||||
img { height: 24px; max-width: 60px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 3px; background: #fff; padding: 1px 3px; }
|
||||
|
||||
img {
|
||||
height: 24px;
|
||||
max-width: 60px;
|
||||
object-fit: contain;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
padding: 1px 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.nom-fetching-hint { display: flex; align-items: center; gap: 6px; font-size: 11px; color: #64748b; margin: -4px 0 8px; }
|
||||
|
||||
.nom-fetching-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 11px;
|
||||
color: #64748b;
|
||||
margin: -4px 0 8px;
|
||||
}
|
||||
|
||||
/* Read-only KYC row */
|
||||
.nom-readonly-row {
|
||||
display: flex; flex-wrap: wrap; gap: 16px; padding: 8px 12px;
|
||||
background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; margin-bottom: 10px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
padding: 8px 12px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.nom-ro-item {
|
||||
font-size: 12px;
|
||||
color: #1e293b;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.nom-ro-label {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: #94a3b8;
|
||||
text-transform: uppercase;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.nom-ro-ok {
|
||||
font-size: 14px !important;
|
||||
width: 14px !important;
|
||||
height: 14px !important;
|
||||
color: #16a34a;
|
||||
}
|
||||
.nom-ro-item { font-size: 12px; color: #1e293b; display: inline-flex; align-items: center; gap: 4px; }
|
||||
.nom-ro-label { font-size: 10px; font-weight: 600; color: #94a3b8; text-transform: uppercase; margin-right: 2px; }
|
||||
.nom-ro-ok { font-size: 14px !important; width: 14px !important; height: 14px !important; color: #16a34a; }
|
||||
|
||||
/* Nominee fields */
|
||||
.nom-fields-row { display: flex; flex-wrap: wrap; gap: 0 12px; align-items: center; margin-bottom: 4px; }
|
||||
.nom-field-item { flex: 1; min-width: 160px; &--sm { max-width: 100px; min-width: 80px; } &--toggle { padding-top: 8px; } }
|
||||
.nom-remarks-field { width: 100%; }
|
||||
.nom-form-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
|
||||
.nom-fields-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 12px;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.nom-field-item {
|
||||
flex: 1;
|
||||
min-width: 160px;
|
||||
|
||||
&--sm {
|
||||
max-width: 100px;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
&--toggle {
|
||||
padding-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-remarks-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nom-form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Guardian */
|
||||
.nom-guardian-section { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
|
||||
.nom-guardian-label { display: flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 600; color: #92400e; mat-icon { font-size: 16px; } }
|
||||
.nom-guardian-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.nom-guardian-selected { display: flex; align-items: center; gap: 4px; font-size: 13px; font-weight: 500; color: #1e293b;
|
||||
button { width: 24px !important; height: 24px !important; line-height: 24px !important; mat-icon { font-size: 14px; } }
|
||||
.nom-guardian-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #92400e;
|
||||
|
||||
mat-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-guardian-selected {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #1e293b;
|
||||
|
||||
button {
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
line-height: 24px !important;
|
||||
|
||||
mat-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Zoom */
|
||||
.zoomable { cursor: zoom-in; transition: transform 0.15s; &:hover { transform: scale(1.08); box-shadow: 0 4px 12px rgb(0 0 0 / 15%); } }
|
||||
::ng-deep .image-zoom-panel { .mat-mdc-dialog-container { padding: 0 !important; border-radius: 8px !important; overflow: hidden; } }
|
||||
.zoomable {
|
||||
cursor: zoom-in;
|
||||
transition: transform 0.15s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.08);
|
||||
box-shadow: 0 4px 12px rgb(0 0 0 / 15%);
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .image-zoom-panel {
|
||||
.mat-mdc-dialog-container {
|
||||
padding: 0 !important;
|
||||
border-radius: 8px !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,11 +109,11 @@ export class NomineeTabComponent implements OnInit {
|
||||
editNomType: string = 'DA1';
|
||||
|
||||
get hasAccounts(): boolean {
|
||||
return this.accountGroups.some(g => g.accounts.length > 0);
|
||||
return this.accountGroups.some((g) => g.accounts.length > 0);
|
||||
}
|
||||
|
||||
get selectedGroup(): AccountGroup | undefined {
|
||||
return this.accountGroups.find(g => g.type === this.selectedType);
|
||||
return this.accountGroups.find((g) => g.type === this.selectedType);
|
||||
}
|
||||
|
||||
get selectedAccount(): any {
|
||||
@@ -154,13 +154,43 @@ export class NomineeTabComponent implements OnInit {
|
||||
next: (data: any) => {
|
||||
this.loading = false;
|
||||
this.accountGroups = [
|
||||
{ type: 'savings', apiType: 'SB', label: 'Savings Account', icon: 'savings', accounts: this.filterActive(data.savingsAccounts) },
|
||||
{ type: 'current', apiType: 'CB', label: 'Current Account', icon: 'account_balance', accounts: this.filterActive(data.currentAccounts) },
|
||||
{ type: 'fd', apiType: 'FD', label: 'Fixed Deposit', icon: 'lock', accounts: this.filterActive(data.fixedDepositAccounts) },
|
||||
{ type: 'rd', apiType: 'RD', label: 'Recurring Deposit', icon: 'autorenew', accounts: this.filterActive(data.recurringDepositAccounts) },
|
||||
{ type: 'loan', apiType: 'LA', label: 'Loan Account', icon: 'credit_score', accounts: this.filterActive(data.loanAccounts) }
|
||||
{
|
||||
type: 'savings',
|
||||
apiType: 'SB',
|
||||
label: 'Savings Account',
|
||||
icon: 'savings',
|
||||
accounts: this.filterActive(data.savingsAccounts)
|
||||
},
|
||||
{
|
||||
type: 'current',
|
||||
apiType: 'CB',
|
||||
label: 'Current Account',
|
||||
icon: 'account_balance',
|
||||
accounts: this.filterActive(data.currentAccounts)
|
||||
},
|
||||
{
|
||||
type: 'fd',
|
||||
apiType: 'FD',
|
||||
label: 'Fixed Deposit',
|
||||
icon: 'lock',
|
||||
accounts: this.filterActive(data.fixedDepositAccounts)
|
||||
},
|
||||
{
|
||||
type: 'rd',
|
||||
apiType: 'RD',
|
||||
label: 'Recurring Deposit',
|
||||
icon: 'autorenew',
|
||||
accounts: this.filterActive(data.recurringDepositAccounts)
|
||||
},
|
||||
{
|
||||
type: 'loan',
|
||||
apiType: 'LA',
|
||||
label: 'Loan Account',
|
||||
icon: 'credit_score',
|
||||
accounts: this.filterActive(data.loanAccounts)
|
||||
}
|
||||
];
|
||||
const firstWithAccounts = this.accountGroups.find(g => g.accounts.length > 0);
|
||||
const firstWithAccounts = this.accountGroups.find((g) => g.accounts.length > 0);
|
||||
if (firstWithAccounts) {
|
||||
this.selectedType = firstWithAccounts.type;
|
||||
// Auto-select if only 1 account in the group
|
||||
@@ -170,18 +200,16 @@ export class NomineeTabComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
},
|
||||
error: () => { this.loading = false; }
|
||||
error: () => {
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private filterActive(accounts: any[]): any[] {
|
||||
if (!accounts) return [];
|
||||
// Include all non-closed accounts (active, disbursed, pending, etc.)
|
||||
return accounts.filter((a: any) =>
|
||||
a.status?.active ||
|
||||
a.status?.value === 'Active' ||
|
||||
!a.status?.closed
|
||||
);
|
||||
return accounts.filter((a: any) => a.status?.active || a.status?.value === 'Active' || !a.status?.closed);
|
||||
}
|
||||
|
||||
onTypeChange(): void {
|
||||
@@ -198,7 +226,9 @@ export class NomineeTabComponent implements OnInit {
|
||||
onAccountChange(): void {
|
||||
this.nominees = [];
|
||||
this.resetAddForm();
|
||||
if (this.selectedAccountId) { this.loadNominees(); }
|
||||
if (this.selectedAccountId) {
|
||||
this.loadNominees();
|
||||
}
|
||||
}
|
||||
|
||||
private loadNominees(): void {
|
||||
@@ -210,9 +240,12 @@ export class NomineeTabComponent implements OnInit {
|
||||
this.nomineesLoading = false;
|
||||
this.nominees = Array.isArray(data) ? data : [];
|
||||
// Enrich each nominee with profile image & signature
|
||||
this.nominees.forEach(n => this.enrichNomineeImages(n));
|
||||
this.nominees.forEach((n) => this.enrichNomineeImages(n));
|
||||
},
|
||||
error: () => { this.nomineesLoading = false; this.nominees = []; }
|
||||
error: () => {
|
||||
this.nomineesLoading = false;
|
||||
this.nominees = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -224,7 +257,9 @@ export class NomineeTabComponent implements OnInit {
|
||||
// Profile image
|
||||
this.clientsService.getClientProfileImage(cid).subscribe({
|
||||
next: (img: any) => {
|
||||
if (img) { nominee.profileImageUrl = this.sanitizer.bypassSecurityTrustResourceUrl(img); }
|
||||
if (img) {
|
||||
nominee.profileImageUrl = this.sanitizer.bypassSecurityTrustResourceUrl(img);
|
||||
}
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
@@ -232,8 +267,8 @@ export class NomineeTabComponent implements OnInit {
|
||||
// Signature
|
||||
this.clientsService.getClientDocuments(cid).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(cid, sigDoc.id.toString()).subscribe({
|
||||
@@ -253,7 +288,10 @@ export class NomineeTabComponent implements OnInit {
|
||||
onSearch(text: string): void {
|
||||
this.searchText = text;
|
||||
clearTimeout(this.searchTimer);
|
||||
if (!text || text.length < 2) { this.searchResults = []; return; }
|
||||
if (!text || text.length < 2) {
|
||||
this.searchResults = [];
|
||||
return;
|
||||
}
|
||||
this.searching = true;
|
||||
this.searchTimer = setTimeout(() => {
|
||||
this.clientsService.searchByText(text, 0, 8).subscribe({
|
||||
@@ -262,13 +300,19 @@ export class NomineeTabComponent implements OnInit {
|
||||
this.searching = false;
|
||||
this.clientsService.enrichSearchResults(this.searchResults);
|
||||
},
|
||||
error: () => { this.searchResults = []; this.searching = false; }
|
||||
error: () => {
|
||||
this.searchResults = [];
|
||||
this.searching = false;
|
||||
}
|
||||
});
|
||||
}, 400);
|
||||
}
|
||||
|
||||
dismissSearch(): void {
|
||||
setTimeout(() => { this.searchResults = []; this.searching = false; }, 200);
|
||||
setTimeout(() => {
|
||||
this.searchResults = [];
|
||||
this.searching = false;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
selectClient(client: any): void {
|
||||
@@ -291,11 +335,18 @@ export class NomineeTabComponent implements OnInit {
|
||||
this.linkedSignatureImage = null;
|
||||
this.linkedKycStatus = null;
|
||||
let pending = 4;
|
||||
const done = () => { pending--; if (pending <= 0) { this.fetchingDetails = false; } };
|
||||
const done = () => {
|
||||
pending--;
|
||||
if (pending <= 0) {
|
||||
this.fetchingDetails = false;
|
||||
}
|
||||
};
|
||||
|
||||
this.clientsService.getClientData(clientId.toString()).subscribe({
|
||||
next: (data: any) => {
|
||||
if (data?.dateOfBirth) { this.selectedClient = { ...this.selectedClient, dateOfBirth: data.dateOfBirth }; }
|
||||
if (data?.dateOfBirth) {
|
||||
this.selectedClient = { ...this.selectedClient, dateOfBirth: data.dateOfBirth };
|
||||
}
|
||||
done();
|
||||
},
|
||||
error: () => done()
|
||||
@@ -304,8 +355,15 @@ export class NomineeTabComponent implements OnInit {
|
||||
this.clientsService.getClientKyc(clientId.toString()).subscribe({
|
||||
next: (kyc: any) => {
|
||||
if (kyc) {
|
||||
this.selectedClient = { ...this.selectedClient, panNumber: kyc.panNumber, aadhaarMasked: kyc.aadhaarNumberMasked };
|
||||
this.linkedKycStatus = { panVerified: kyc.panVerified || false, aadhaarVerified: kyc.aadhaarVerified || false };
|
||||
this.selectedClient = {
|
||||
...this.selectedClient,
|
||||
panNumber: kyc.panNumber,
|
||||
aadhaarMasked: kyc.aadhaarNumberMasked
|
||||
};
|
||||
this.linkedKycStatus = {
|
||||
panVerified: kyc.panVerified || false,
|
||||
aadhaarVerified: kyc.aadhaarVerified || false
|
||||
};
|
||||
}
|
||||
done();
|
||||
},
|
||||
@@ -313,19 +371,31 @@ export class NomineeTabComponent implements OnInit {
|
||||
});
|
||||
|
||||
this.clientsService.getClientProfileImage(clientId.toString()).subscribe({
|
||||
next: (img: any) => { if (img) { this.linkedProfileImage = this.sanitizer.bypassSecurityTrustResourceUrl(img); } done(); },
|
||||
next: (img: any) => {
|
||||
if (img) {
|
||||
this.linkedProfileImage = this.sanitizer.bypassSecurityTrustResourceUrl(img);
|
||||
}
|
||||
done();
|
||||
},
|
||||
error: () => done()
|
||||
});
|
||||
|
||||
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({
|
||||
next: (blob: Blob) => { this.linkedSignatureImage = this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(blob)); done(); },
|
||||
next: (blob: Blob) => {
|
||||
this.linkedSignatureImage = this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(blob));
|
||||
done();
|
||||
},
|
||||
error: () => done()
|
||||
});
|
||||
} else { done(); }
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
},
|
||||
error: () => done()
|
||||
});
|
||||
@@ -336,12 +406,22 @@ export class NomineeTabComponent 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, 8).subscribe({
|
||||
next: (data: any) => { this.guardianSearchResults = data?.content || []; this.guardianSearching = false; this.clientsService.enrichSearchResults(this.guardianSearchResults); },
|
||||
error: () => { this.guardianSearchResults = []; this.guardianSearching = false; }
|
||||
next: (data: any) => {
|
||||
this.guardianSearchResults = data?.content || [];
|
||||
this.guardianSearching = false;
|
||||
this.clientsService.enrichSearchResults(this.guardianSearchResults);
|
||||
},
|
||||
error: () => {
|
||||
this.guardianSearchResults = [];
|
||||
this.guardianSearching = false;
|
||||
}
|
||||
});
|
||||
}, 400);
|
||||
}
|
||||
@@ -385,7 +465,9 @@ export class NomineeTabComponent implements OnInit {
|
||||
this.loadNominees();
|
||||
this.resetAddForm();
|
||||
},
|
||||
error: () => { this.nomineeSaving = false; }
|
||||
error: () => {
|
||||
this.nomineeSaving = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -395,26 +477,30 @@ export class NomineeTabComponent implements OnInit {
|
||||
// Resolve the linked client ID — could be nomineeClientId or nested
|
||||
const clientId = nominee.nomineeClientId || nominee.linkedClientId || nominee.clientId;
|
||||
const displayName = nominee.nomineeDisplayName || nominee.firstName || '';
|
||||
import('../../client-stepper/client-family-members-step/client-family-member-dialog/client-family-member-dialog.component').then(m => {
|
||||
this.dialog.open(m.ClientFamilyMemberDialogComponent, {
|
||||
data: {
|
||||
context: 'View',
|
||||
member: {
|
||||
firstName: displayName,
|
||||
relationshipId: nominee.relationshipId,
|
||||
dateOfBirth: nominee.nomineeDateOfBirth || nominee.dateOfBirth,
|
||||
linkedClientId: clientId,
|
||||
linkedClientName: displayName,
|
||||
isAlive: true,
|
||||
isDependent: nominee.nomineeIsMinor,
|
||||
panNumber: nominee.panNumber || '',
|
||||
aadhaarNumber: nominee.aadhaarMasked || ''
|
||||
import('../../client-stepper/client-family-members-step/client-family-member-dialog/client-family-member-dialog.component').then(
|
||||
(m) => {
|
||||
this.dialog.open(m.ClientFamilyMemberDialogComponent, {
|
||||
data: {
|
||||
context: 'View',
|
||||
member: {
|
||||
firstName: displayName,
|
||||
relationshipId: nominee.relationshipId,
|
||||
dateOfBirth: nominee.nomineeDateOfBirth || nominee.dateOfBirth,
|
||||
linkedClientId: clientId,
|
||||
linkedClientName: displayName,
|
||||
isAlive: true,
|
||||
isDependent: nominee.nomineeIsMinor,
|
||||
panNumber: nominee.panNumber || '',
|
||||
aadhaarNumber: nominee.aadhaarMasked || ''
|
||||
},
|
||||
options: { relationshipIdOptions: this.relationshipOptions }
|
||||
},
|
||||
options: { relationshipIdOptions: this.relationshipOptions }
|
||||
},
|
||||
width: '640px', maxWidth: '95vw', panelClass: 'fm-dialog-panel'
|
||||
});
|
||||
});
|
||||
width: '640px',
|
||||
maxWidth: '95vw',
|
||||
panelClass: 'fm-dialog-panel'
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
editNominee(nominee: any, index: number): void {
|
||||
@@ -424,10 +510,12 @@ export class NomineeTabComponent implements OnInit {
|
||||
this.editNomType = nominee.nominationType || 'DA1';
|
||||
}
|
||||
|
||||
cancelEdit(): void { this.editingIndex = null; }
|
||||
cancelEdit(): void {
|
||||
this.editingIndex = null;
|
||||
}
|
||||
|
||||
maxShareForEdit(index: number): number {
|
||||
return 100 - this.nominees.reduce((sum, n, i) => i === index ? sum : sum + (n.percentageShare || 0), 0);
|
||||
return 100 - this.nominees.reduce((sum, n, i) => (i === index ? sum : sum + (n.percentageShare || 0)), 0);
|
||||
}
|
||||
|
||||
saveEdit(nominee: any, index: number): void {
|
||||
@@ -468,7 +556,8 @@ export class NomineeTabComponent implements OnInit {
|
||||
this.dialog.open(ImageZoomDialogComponent, {
|
||||
data: { imageSrc, title },
|
||||
panelClass: 'image-zoom-panel',
|
||||
maxWidth: '90vw', maxHeight: '90vh'
|
||||
maxWidth: '90vw',
|
||||
maxHeight: '90vh'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
@use 'sass:color';
|
||||
|
||||
$primary: #1a73e8;
|
||||
$primary-light: #e8f0fe;
|
||||
$gray-50: #f8f9fa;
|
||||
@@ -165,7 +167,7 @@ $teal: #00897b;
|
||||
|
||||
&.status-active {
|
||||
background: rgba($green, 0.1);
|
||||
color: darken($green, 10%);
|
||||
color: color.adjust($green, $lightness: -10%);
|
||||
|
||||
&::before {
|
||||
background: $green;
|
||||
@@ -183,7 +185,7 @@ $teal: #00897b;
|
||||
|
||||
&.status-pending {
|
||||
background: rgba($orange, 0.1);
|
||||
color: darken($orange, 15%);
|
||||
color: color.adjust($orange, $lightness: -15%);
|
||||
|
||||
&::before {
|
||||
background: $orange;
|
||||
@@ -230,7 +232,9 @@ $teal: #00897b;
|
||||
padding: 3px 12px;
|
||||
border-radius: 12px;
|
||||
|
||||
i { font-size: 12px; }
|
||||
i {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&--verified {
|
||||
background: #ecfdf5;
|
||||
|
||||
@@ -329,21 +329,28 @@ export class ClientsService {
|
||||
* Phase 1 – Record Master: fetch clients missing CKYC registration
|
||||
* or artefacts. Backed by GET /ckyc/record-master.
|
||||
*/
|
||||
getCkycRecordMaster(filters: {
|
||||
officeId?: number | string;
|
||||
productId?: number | string;
|
||||
fromDate?: string;
|
||||
toDate?: string;
|
||||
missing?: 'photo' | 'signature' | 'id' | 'address' | 'any';
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
} = {}): Observable<any> {
|
||||
getCkycRecordMaster(
|
||||
filters: {
|
||||
officeId?: number | string;
|
||||
productId?: number | string;
|
||||
fromDate?: string;
|
||||
toDate?: string;
|
||||
missing?: 'photo' | 'signature' | 'id' | 'address' | 'any';
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
} = {}
|
||||
): Observable<any> {
|
||||
let params = new HttpParams();
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value !== null && value !== undefined && value !== '') {
|
||||
params = params.set(key, String(value));
|
||||
Object.entries(filters).forEach(
|
||||
([
|
||||
key,
|
||||
value
|
||||
]) => {
|
||||
if (value !== null && value !== undefined && value !== '') {
|
||||
params = params.set(key, String(value));
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
return this.http.get(`/ckyc/record-master`, { params });
|
||||
}
|
||||
|
||||
@@ -359,18 +366,25 @@ export class ClientsService {
|
||||
* Phase 2/3 – List CKYC batches. Supports filters and an optional
|
||||
* groupByDate=true to bucket results by created_on for the UI.
|
||||
*/
|
||||
getCkycBatches(filters: {
|
||||
fromDate?: string;
|
||||
toDate?: string;
|
||||
uploadedToCersai?: boolean;
|
||||
groupByDate?: boolean;
|
||||
} = {}): Observable<any> {
|
||||
getCkycBatches(
|
||||
filters: {
|
||||
fromDate?: string;
|
||||
toDate?: string;
|
||||
uploadedToCersai?: boolean;
|
||||
groupByDate?: boolean;
|
||||
} = {}
|
||||
): Observable<any> {
|
||||
let params = new HttpParams();
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value !== null && value !== undefined && value !== '') {
|
||||
params = params.set(key, String(value));
|
||||
Object.entries(filters).forEach(
|
||||
([
|
||||
key,
|
||||
value
|
||||
]) => {
|
||||
if (value !== null && value !== undefined && value !== '') {
|
||||
params = params.set(key, String(value));
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
return this.http.get(`/ckyc/batches`, { params });
|
||||
}
|
||||
|
||||
|
||||
@@ -223,7 +223,9 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
span { flex: 1; }
|
||||
span {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
flex-shrink: 0;
|
||||
|
||||
Reference in New Issue
Block a user