feat(election): add guarantor status section to nomination-apply page
Shows whether the selected nominee is a guarantor on any loan, with loan health indicator, borrower details, savings account, guaranteed/released/remaining amounts, and loan timeline. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
3d9b0480c6
commit
71552d6875
@@ -8,8 +8,6 @@
|
||||
|
||||
.candidate-board {
|
||||
padding: 24px;
|
||||
max-width: 1300px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
|
||||
@@ -30,8 +30,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%);
|
||||
}
|
||||
|
||||
.config-inner {
|
||||
max-width: 860px;
|
||||
margin: 0 auto;
|
||||
// layout wrapper
|
||||
}
|
||||
|
||||
// ── Page header ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
.election-dashboard {
|
||||
padding: 24px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
.election-results {
|
||||
padding: 24px;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
|
||||
@@ -28,10 +28,17 @@
|
||||
<mat-stepper orientation="horizontal" #stepper linear>
|
||||
<!-- ── Step 1: Personal Profile ── -->
|
||||
<mat-step [stepControl]="profileForm" label="Personal Profile">
|
||||
<form [formGroup]="profileForm" class="step-form">
|
||||
<form [formGroup]="profileForm" class="step-form step-form--split">
|
||||
<div class="step1-layout" #layoutRef [style.gridTemplateColumns]="leftPanelWidth + '% 8px 1fr'">
|
||||
<!-- ── LEFT: Lookup panel ── -->
|
||||
<div class="step1-left">
|
||||
<div class="step1-left-scroll">
|
||||
<!-- Member Search -->
|
||||
<div class="member-search-box">
|
||||
<div class="section-label"><mat-icon>manage_search</mat-icon> Search Account Holder</div>
|
||||
|
||||
@if (!selectedMemberId) {
|
||||
<!-- Search input — only shown when no member selected -->
|
||||
<div class="search-row">
|
||||
<mat-form-field appearance="outline" class="search-field">
|
||||
<mat-label>Type member name or account number</mat-label>
|
||||
@@ -49,13 +56,17 @@
|
||||
>
|
||||
@if (searchLoading) {
|
||||
<mat-option disabled>
|
||||
<mat-spinner diameter="18" style="display: inline-block; margin-right: 8px"></mat-spinner>
|
||||
<mat-spinner
|
||||
diameter="18"
|
||||
style="display: inline-block; margin-right: 8px"
|
||||
></mat-spinner>
|
||||
Searching...
|
||||
</mat-option>
|
||||
}
|
||||
@for (client of filteredClients; track client.id) {
|
||||
<mat-option [value]="client">
|
||||
<mat-icon style="font-size: 15px; vertical-align: middle; color: #4a6cf7; margin-right: 4px"
|
||||
<mat-icon
|
||||
style="font-size: 15px; vertical-align: middle; color: #4a6cf7; margin-right: 4px"
|
||||
>person</mat-icon
|
||||
>
|
||||
<strong>{{ client.displayName }}</strong>
|
||||
@@ -76,8 +87,12 @@
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@if (selectedMemberId) {
|
||||
<div class="member-hint">
|
||||
<mat-icon>info</mat-icon>
|
||||
Search and select a member to auto-fill their personal details below.
|
||||
</div>
|
||||
} @else {
|
||||
<!-- Selected member badge with remove button -->
|
||||
<div class="member-selected-badge">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
<span
|
||||
@@ -94,11 +109,9 @@
|
||||
<span style="color: #c53030"> · No active share account found</span>
|
||||
}
|
||||
<span> · Details auto-filled below</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="member-hint">
|
||||
<mat-icon>info</mat-icon>
|
||||
Search and select a member to auto-fill their personal details below.
|
||||
<button type="button" class="member-remove-btn" (click)="clearMember()" title="Remove member">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -106,8 +119,18 @@
|
||||
<!-- Family Members (shown after a member is selected) -->
|
||||
@if (selectedMemberId) {
|
||||
<div class="family-section">
|
||||
<div class="section-label"><mat-icon>group</mat-icon> Family Members</div>
|
||||
<div class="section-label section-label--toggle" (click)="familyExpanded = !familyExpanded">
|
||||
<mat-icon>group</mat-icon>
|
||||
Family Members
|
||||
@if (familyMembers.length > 0) {
|
||||
<span class="section-count">{{ familyMembers.length }}</span>
|
||||
}
|
||||
<mat-icon class="section-chevron" [class.section-chevron--open]="familyExpanded"
|
||||
>expand_more</mat-icon
|
||||
>
|
||||
</div>
|
||||
|
||||
@if (familyExpanded) {
|
||||
@if (familyLoading) {
|
||||
<div class="family-loading">
|
||||
<mat-spinner diameter="18"></mat-spinner>
|
||||
@@ -132,7 +155,9 @@
|
||||
<span class="family-name">{{ familyMemberDisplayName(member) }}</span>
|
||||
|
||||
@if (member.relationship || member.relationshipId?.name) {
|
||||
<span class="family-rel">{{ member.relationship || member.relationshipId?.name }}</span>
|
||||
<span class="family-rel">{{
|
||||
member.relationship || member.relationshipId?.name
|
||||
}}</span>
|
||||
}
|
||||
|
||||
@if (isLinkedClient(member)) {
|
||||
@@ -174,7 +199,12 @@
|
||||
<!-- Expanded: detail row + KYC -->
|
||||
@if (expandedFamilyCards.has(member.id)) {
|
||||
<!-- Detail row: PAN, Aadhaar, Nationality, Mobile -->
|
||||
@if (member.panNumber || member.aadhaarNumber || member.nationality || member.mobileNumber) {
|
||||
@if (
|
||||
member.panNumber ||
|
||||
member.aadhaarNumber ||
|
||||
member.nationality ||
|
||||
member.mobileNumber
|
||||
) {
|
||||
<div class="family-detail-row">
|
||||
@if (member.panNumber) {
|
||||
<div class="family-detail-item">
|
||||
@@ -301,7 +331,9 @@
|
||||
<div
|
||||
class="kyc-expiry-block"
|
||||
[class.kyc-expiry-block--expired]="fmExp.isExpired"
|
||||
[class.kyc-expiry-block--soon]="fmExp.isExpiringSoon && !fmExp.isExpired"
|
||||
[class.kyc-expiry-block--soon]="
|
||||
fmExp.isExpiringSoon && !fmExp.isExpired
|
||||
"
|
||||
>
|
||||
<mat-icon>{{ fmExp.isExpired ? 'event_busy' : 'event' }}</mat-icon>
|
||||
<div>
|
||||
@@ -338,7 +370,9 @@
|
||||
[class.kyc-vbadge--ok]="fmKyc.panVerified"
|
||||
[class.kyc-vbadge--no]="!fmKyc.panVerified"
|
||||
>
|
||||
<mat-icon>{{ fmKyc.panVerified ? 'check_circle' : 'cancel' }}</mat-icon>
|
||||
<mat-icon>{{
|
||||
fmKyc.panVerified ? 'check_circle' : 'cancel'
|
||||
}}</mat-icon>
|
||||
{{ fmKyc.panVerified ? 'Verified' : 'Not Verified' }}
|
||||
</span>
|
||||
}
|
||||
@@ -357,7 +391,9 @@
|
||||
[class.kyc-vbadge--ok]="fmKyc.aadhaarVerified"
|
||||
[class.kyc-vbadge--no]="!fmKyc.aadhaarVerified"
|
||||
>
|
||||
<mat-icon>{{ fmKyc.aadhaarVerified ? 'check_circle' : 'cancel' }}</mat-icon>
|
||||
<mat-icon>{{
|
||||
fmKyc.aadhaarVerified ? 'check_circle' : 'cancel'
|
||||
}}</mat-icon>
|
||||
{{ fmKyc.aadhaarVerified ? 'Verified' : 'Not Verified' }}
|
||||
</span>
|
||||
}
|
||||
@@ -394,7 +430,8 @@
|
||||
}
|
||||
@if (fmKyc.kycDocExpiryDate) {
|
||||
<div class="kyc-item-sub">
|
||||
Expires: {{ parseDob(fmKyc.kycDocExpiryDate) | date: 'dd MMM yyyy' }}
|
||||
Expires:
|
||||
{{ parseDob(fmKyc.kycDocExpiryDate) | date: 'dd MMM yyyy' }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -410,22 +447,107 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ═══ MANUAL ENTRY WARNING ═══ -->
|
||||
@if (!familyLoading && manualEntryMembers.length > 0) {
|
||||
<div class="manual-entry-warning">
|
||||
<mat-icon>warning_amber</mat-icon>
|
||||
<div class="manual-entry-warning__body">
|
||||
<strong>Submission blocked</strong> —
|
||||
@for (m of manualEntryMembers; track m.id; let last = $last) {
|
||||
<span>
|
||||
{{ familyMemberDisplayName(m)
|
||||
}}{{
|
||||
m.relationship || m.relationshipId?.name
|
||||
? ' (' + (m.relationship || m.relationshipId?.name) + ')'
|
||||
: ''
|
||||
}}{{ last ? '' : ', ' }}
|
||||
</span>
|
||||
}
|
||||
{{ manualEntryMembers.length === 1 ? 'is' : 'are' }} not registered as a CBS client. Use
|
||||
<strong>Create as Client</strong> below before submitting.
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ═══ ADD AS CLIENT PANEL ═══ -->
|
||||
@if (!familyLoading) {
|
||||
<div class="aac-panel">
|
||||
<button class="aac-panel__toggle" (click)="toggleAddAsClientPanel()">
|
||||
<mat-icon>person_add</mat-icon>
|
||||
<span>Create New Family Member as Client</span>
|
||||
<mat-icon
|
||||
class="aac-panel__chevron"
|
||||
[class.aac-panel__chevron--open]="showAddAsClientPanel"
|
||||
>
|
||||
expand_more
|
||||
</mat-icon>
|
||||
</button>
|
||||
@if (showAddAsClientPanel) {
|
||||
<div class="aac-body">
|
||||
@if (addAsClientLoading) {
|
||||
<div class="aac-loading">
|
||||
<mat-icon>hourglass_empty</mat-icon> Loading relationships…
|
||||
</div>
|
||||
} @else {
|
||||
<p class="aac-hint">Select the relationship you want to create as a client:</p>
|
||||
<div class="aac-chips">
|
||||
@for (rel of availableRelationships; track rel.id) {
|
||||
<button
|
||||
class="aac-chip"
|
||||
[class.aac-chip--selected]="selectedNewRelationship?.id === rel.id"
|
||||
(click)="selectNewRelationship(rel)"
|
||||
>
|
||||
{{ rel.name }}
|
||||
</button>
|
||||
}
|
||||
@for (rel of takenRelationships; track rel.id) {
|
||||
<button class="aac-chip aac-chip--taken" disabled>
|
||||
<mat-icon>check_circle</mat-icon> {{ rel.name }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@if (selectedNewRelationship) {
|
||||
<div class="aac-create-bar">
|
||||
<span class="aac-create-bar__label">
|
||||
<mat-icon>person</mat-icon>
|
||||
Creating a
|
||||
<strong>{{ selectedNewRelationship.name }}</strong> as CBS client
|
||||
</span>
|
||||
<button class="aac-create-bar__btn" (click)="createClientForRelationship()">
|
||||
<mat-icon>person_add</mat-icon> Create as Client
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<!-- /familyExpanded -->
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- KYC Status -->
|
||||
@if (selectedMemberId) {
|
||||
<div class="kyc-section">
|
||||
<div class="section-label">
|
||||
<div class="section-label section-label--toggle" (click)="kycExpanded = !kycExpanded">
|
||||
<mat-icon>verified_user</mat-icon>
|
||||
KYC Details
|
||||
@if (profileForm.value.applicantName) {
|
||||
<span class="kyc-owner-chip">{{ profileForm.value.applicantName }}</span>
|
||||
}
|
||||
<mat-icon class="section-chevron" [class.section-chevron--open]="kycExpanded"
|
||||
>expand_more</mat-icon
|
||||
>
|
||||
</div>
|
||||
|
||||
@if (kycExpanded) {
|
||||
@if (familyLoading) {
|
||||
<div class="family-loading"><mat-spinner diameter="18"></mat-spinner> Loading KYC details...</div>
|
||||
<div class="family-loading">
|
||||
<mat-spinner diameter="18"></mat-spinner> Loading KYC details...
|
||||
</div>
|
||||
} @else if (!kycData) {
|
||||
<div class="kyc-not-done">
|
||||
<mat-icon>warning</mat-icon>
|
||||
@@ -459,7 +581,9 @@
|
||||
{{ kycData.kycType === 2 ? 'Non-Individual' : 'Individual' }}
|
||||
</span>
|
||||
@if (kycData.ckycNumber) {
|
||||
<span class="ckyc-badge"> <mat-icon>task_alt</mat-icon> CKYC: {{ kycData.ckycNumber }} </span>
|
||||
<span class="ckyc-badge">
|
||||
<mat-icon>task_alt</mat-icon> CKYC: {{ kycData.ckycNumber }}
|
||||
</span>
|
||||
}
|
||||
@if (kycData.verifiedOn) {
|
||||
<span class="kyc-date-chip">
|
||||
@@ -471,7 +595,10 @@
|
||||
|
||||
<!-- ── Risk + Expiry banner ── -->
|
||||
<div class="kyc-risk-row">
|
||||
<div class="kyc-risk-block" [class]="'kyc-risk-block--' + riskClass(kycData.riskCategory)">
|
||||
<div
|
||||
class="kyc-risk-block"
|
||||
[class]="'kyc-risk-block--' + riskClass(kycData.riskCategory)"
|
||||
>
|
||||
<mat-icon>{{
|
||||
kycData.riskCategory === 3
|
||||
? 'priority_high'
|
||||
@@ -532,7 +659,9 @@
|
||||
</span>
|
||||
}
|
||||
@if (kycData.panVerifiedOn) {
|
||||
<div class="kyc-item-sub">on {{ parseDob(kycData.panVerifiedOn) | date: 'dd MMM yyyy' }}</div>
|
||||
<div class="kyc-item-sub">
|
||||
on {{ parseDob(kycData.panVerifiedOn) | date: 'dd MMM yyyy' }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -563,7 +692,9 @@
|
||||
@if (kycData.panAadhaarSeedingStatus === 'Y') {
|
||||
<span class="kyc-vbadge kyc-vbadge--ok"><mat-icon>link</mat-icon> Linked</span>
|
||||
} @else if (kycData.panAadhaarSeedingStatus === 'N') {
|
||||
<span class="kyc-vbadge kyc-vbadge--no"><mat-icon>link_off</mat-icon> Not Linked</span>
|
||||
<span class="kyc-vbadge kyc-vbadge--no"
|
||||
><mat-icon>link_off</mat-icon> Not Linked</span
|
||||
>
|
||||
} @else if (kycData.panAadhaarSeedingStatus === 'E') {
|
||||
<span class="kyc-vbadge kyc-vbadge--na"><mat-icon>block</mat-icon> Exempt</span>
|
||||
} @else {
|
||||
@@ -644,9 +775,438 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<!-- /kycExpanded -->
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Eligibility Verification -->
|
||||
@if (selectedMemberId && !familyLoading) {
|
||||
<div class="elig-section">
|
||||
<div class="section-label section-label--toggle" (click)="eligExpanded = !eligExpanded">
|
||||
<mat-icon>fact_check</mat-icon>
|
||||
Eligibility Verification
|
||||
<span
|
||||
class="section-elig-badge"
|
||||
[class.section-elig-badge--ok]="isEligible"
|
||||
[class.section-elig-badge--fail]="!isEligible"
|
||||
>
|
||||
<mat-icon>{{ isEligible ? 'check_circle' : 'warning' }}</mat-icon>
|
||||
{{ isEligible ? 'Eligible' : 'Not Eligible' }}
|
||||
</span>
|
||||
<mat-icon class="section-chevron" [class.section-chevron--open]="eligExpanded"
|
||||
>expand_more</mat-icon
|
||||
>
|
||||
</div>
|
||||
|
||||
@if (eligExpanded) {
|
||||
<!-- No Due Certificate -->
|
||||
<div
|
||||
class="ndc-banner"
|
||||
[class.ndc-banner--eligible]="isEligible"
|
||||
[class.ndc-banner--not]="!isEligible"
|
||||
>
|
||||
<mat-icon>{{ isEligible ? 'verified' : 'gpp_bad' }}</mat-icon>
|
||||
<div class="ndc-banner__body">
|
||||
<strong>No Due Certificate</strong>
|
||||
<span>{{
|
||||
isEligible
|
||||
? 'ELIGIBLE — No overdue loans found for nominee or family members'
|
||||
: 'NOT ELIGIBLE — One or more overdue loans exist'
|
||||
}}</span>
|
||||
</div>
|
||||
@if (familyAccountsLoading) {
|
||||
<mat-spinner diameter="16"></mat-spinner>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Nominee Loan Accounts -->
|
||||
@if (nomineeLoanAccounts.length > 0) {
|
||||
<div class="elig-sub-label"><mat-icon>account_balance</mat-icon> Nominee — Loan Accounts</div>
|
||||
<div class="elig-table-wrap">
|
||||
<table class="elig-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account No</th>
|
||||
<th>Product</th>
|
||||
<th>Principal</th>
|
||||
<th>Outstanding</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (loan of nomineeLoanAccounts; track loan.id) {
|
||||
<tr>
|
||||
<td class="mono">{{ loan.accountNo }}</td>
|
||||
<td>{{ loan.productName }}</td>
|
||||
<td class="amt">
|
||||
₹{{
|
||||
loan.originalLoan ?? loan.summary?.principalDisbursed ?? 0 | number: '1.0-0'
|
||||
}}
|
||||
</td>
|
||||
<td class="amt">₹{{ loan.summary?.totalOutstanding ?? 0 | number: '1.0-0' }}</td>
|
||||
<td>
|
||||
<span class="loan-status" [class]="'loan-status--' + loanStatusClass(loan)">{{
|
||||
loanStatusLabel(loan)
|
||||
}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="elig-empty-row">
|
||||
<mat-icon>info</mat-icon> No loan accounts found for nominee.
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Nominee Savings Accounts -->
|
||||
@if (nomineeSavingsAccounts.length > 0) {
|
||||
<div class="elig-sub-label"><mat-icon>savings</mat-icon> Nominee — Savings Accounts</div>
|
||||
<div class="elig-table-wrap">
|
||||
<table class="elig-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account No</th>
|
||||
<th>Product</th>
|
||||
<th>Balance</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (sav of nomineeSavingsAccounts; track sav.id) {
|
||||
<tr>
|
||||
<td class="mono">{{ sav.accountNo }}</td>
|
||||
<td>{{ sav.productName }}</td>
|
||||
<td class="amt">₹{{ sav.accountBalance ?? sav.balance ?? 0 | number: '1.0-0' }}</td>
|
||||
<td>
|
||||
<span
|
||||
class="loan-status"
|
||||
[class]="
|
||||
(sav.status?.value ?? '').toLowerCase().includes('active')
|
||||
? 'loan-status--active'
|
||||
: 'loan-status--closed'
|
||||
"
|
||||
>{{ sav.status?.value ?? '—' }}</span
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Family Members Accounts -->
|
||||
@for (member of familyMembers; track member.id) {
|
||||
@if (member.linkedClientId) {
|
||||
@let fmAcc = familyMemberAccounts(member.linkedClientId);
|
||||
<div class="elig-fm-header">
|
||||
<mat-icon>group</mat-icon>
|
||||
{{ familyMemberDisplayName(member) }}
|
||||
@if (member.relationship || member.relationshipId?.name) {
|
||||
<span class="elig-fm-rel">{{
|
||||
member.relationship || member.relationshipId?.name
|
||||
}}</span>
|
||||
}
|
||||
</div>
|
||||
@if (familyAccountsLoading && !fmAcc) {
|
||||
<div class="elig-loading">
|
||||
<mat-spinner diameter="14"></mat-spinner> Loading accounts...
|
||||
</div>
|
||||
} @else if (fmAcc) {
|
||||
@if ((fmAcc.loanAccounts?.length ?? 0) > 0) {
|
||||
<div class="elig-sub-label elig-sub-label--fm">
|
||||
<mat-icon>account_balance</mat-icon> Loan Accounts
|
||||
</div>
|
||||
<div class="elig-table-wrap">
|
||||
<table class="elig-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account No</th>
|
||||
<th>Product</th>
|
||||
<th>Principal</th>
|
||||
<th>Outstanding</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (loan of fmAcc.loanAccounts; track loan.id) {
|
||||
<tr>
|
||||
<td class="mono">{{ loan.accountNo }}</td>
|
||||
<td>{{ loan.productName }}</td>
|
||||
<td class="amt">
|
||||
₹{{
|
||||
loan.originalLoan ?? loan.summary?.principalDisbursed ?? 0
|
||||
| number: '1.0-0'
|
||||
}}
|
||||
</td>
|
||||
<td class="amt">
|
||||
₹{{ loan.summary?.totalOutstanding ?? 0 | number: '1.0-0' }}
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
class="loan-status"
|
||||
[class]="'loan-status--' + loanStatusClass(loan)"
|
||||
>{{ loanStatusLabel(loan) }}</span
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="elig-empty-row"><mat-icon>info</mat-icon> No loan accounts.</div>
|
||||
}
|
||||
@if ((fmAcc.savingsAccounts?.length ?? 0) > 0) {
|
||||
<div class="elig-sub-label elig-sub-label--fm">
|
||||
<mat-icon>savings</mat-icon> Savings Accounts
|
||||
</div>
|
||||
<div class="elig-table-wrap">
|
||||
<table class="elig-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account No</th>
|
||||
<th>Product</th>
|
||||
<th>Balance</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (sav of fmAcc.savingsAccounts; track sav.id) {
|
||||
<tr>
|
||||
<td class="mono">{{ sav.accountNo }}</td>
|
||||
<td>{{ sav.productName }}</td>
|
||||
<td class="amt">
|
||||
₹{{ sav.accountBalance ?? sav.balance ?? 0 | number: '1.0-0' }}
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
class="loan-status"
|
||||
[class]="
|
||||
(sav.status?.value ?? '').toLowerCase().includes('active')
|
||||
? 'loan-status--active'
|
||||
: 'loan-status--closed'
|
||||
"
|
||||
>{{ sav.status?.value ?? '—' }}</span
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
} @else {
|
||||
<div class="elig-empty-row"><mat-icon>info</mat-icon> No account data available.</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
<!-- /eligExpanded -->
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Guarantor Status (Is this nominee a guarantor on any loan?) -->
|
||||
@if (selectedMemberId && nomineeAccounts) {
|
||||
<div class="guarantors-section">
|
||||
<div
|
||||
class="section-label section-label--toggle"
|
||||
(click)="guarantorsExpanded = !guarantorsExpanded"
|
||||
>
|
||||
<mat-icon>handshake</mat-icon>
|
||||
Guarantor Status
|
||||
@if (nomineeGuarantorAccounts.length > 0) {
|
||||
<span class="section-count">{{ nomineeGuarantorAccounts.length }}</span>
|
||||
}
|
||||
<mat-icon class="section-chevron" [class.section-chevron--open]="guarantorsExpanded"
|
||||
>expand_more</mat-icon
|
||||
>
|
||||
</div>
|
||||
|
||||
@if (guarantorsExpanded) {
|
||||
@if (nomineeGuarantorAccounts.length === 0) {
|
||||
<div class="family-empty">
|
||||
<mat-icon>info_outline</mat-icon>
|
||||
This member is not a guarantor on any loan.
|
||||
</div>
|
||||
} @else {
|
||||
<div class="guarantor-summary-banner">
|
||||
<mat-icon>verified_user</mat-icon>
|
||||
This member is acting as guarantor on
|
||||
<strong
|
||||
>{{ nomineeGuarantorAccounts.length }} loan{{
|
||||
nomineeGuarantorAccounts.length > 1 ? 's' : ''
|
||||
}}</strong
|
||||
>
|
||||
</div>
|
||||
<div class="family-list">
|
||||
@for (g of nomineeGuarantorAccounts; track g.id) {
|
||||
<div class="guarantor-card">
|
||||
<!-- Header: Loan No + badges -->
|
||||
<div class="guarantor-card-top">
|
||||
<div class="guarantor-card-left">
|
||||
<mat-icon>credit_score</mat-icon>
|
||||
<span class="guarantor-name">Loan #{{ g.accountNo }}</span>
|
||||
@if (g.relationship) {
|
||||
<span class="guarantor-rel">{{ g.relationship }}</span>
|
||||
}
|
||||
</div>
|
||||
<div class="guarantor-card-right">
|
||||
<span class="guarantor-type-badge">{{ g.productName }}</span>
|
||||
@if (g.inArrears) {
|
||||
<span class="guarantor-status-badge guarantor-status-badge--deleted"
|
||||
>In Arrears</span
|
||||
>
|
||||
} @else {
|
||||
<span class="guarantor-status-badge guarantor-status-badge--active">{{
|
||||
g.status?.value
|
||||
}}</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Borrower name row -->
|
||||
@if (g.borrowerName) {
|
||||
<div class="guarantor-borrower-row">
|
||||
<mat-icon>person</mat-icon>
|
||||
Borrower: <strong>{{ g.borrowerName }}</strong>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Savings A/C row -->
|
||||
@if (g.savingsAccountNo) {
|
||||
<div class="guarantor-savings-row">
|
||||
<mat-icon>account_balance_wallet</mat-icon>
|
||||
Pledged Savings A/C: <strong class="mono">{{ g.savingsAccountNo }}</strong>
|
||||
@if (g.savingsBalance != null) {
|
||||
<span class="guarantor-savings-balance">
|
||||
· Balance: <strong>{{ g.savingsBalance | number: '1.2-2' }}</strong>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Loan health indicator -->
|
||||
@if (g.inArrears && g.totalOverdue > 0) {
|
||||
<div class="guarantor-health-row guarantor-health-row--problem">
|
||||
<mat-icon>warning</mat-icon>
|
||||
Overdue: <strong>{{ g.totalOverdue | number: '1.2-2' }}</strong>
|
||||
@if (g.overdueSinceDate) {
|
||||
· Since {{ g.overdueSinceDate | date: 'dd MMM yyyy' }}
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="guarantor-health-row guarantor-health-row--ok">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
No overdue — loan is in good standing
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Amounts row -->
|
||||
<div class="guarantor-funds-row">
|
||||
<div class="guarantor-fund-item">
|
||||
<span class="guarantor-fund-label">Loan Amount</span>
|
||||
<span class="guarantor-fund-value">{{ g.originalLoan | number: '1.2-2' }}</span>
|
||||
</div>
|
||||
<div class="guarantor-fund-item">
|
||||
<span class="guarantor-fund-label">Guaranteed</span>
|
||||
<span class="guarantor-fund-value">{{ g.onHoldAmount | number: '1.2-2' }}</span>
|
||||
</div>
|
||||
<div class="guarantor-fund-item">
|
||||
<span class="guarantor-fund-label">Released</span>
|
||||
<span class="guarantor-fund-value">{{ g.amountReleased | number: '1.2-2' }}</span>
|
||||
</div>
|
||||
<div class="guarantor-fund-item">
|
||||
<span class="guarantor-fund-label">Remaining</span>
|
||||
<span
|
||||
class="guarantor-fund-value"
|
||||
[class.guarantor-fund-value--active]="g.amountRemaining > 0"
|
||||
>
|
||||
{{ g.amountRemaining | number: '1.2-2' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="guarantor-fund-item">
|
||||
<span class="guarantor-fund-label">Balance</span>
|
||||
<span class="guarantor-fund-value">{{ g.loanBalance | number: '1.2-2' }}</span>
|
||||
</div>
|
||||
<div class="guarantor-fund-item">
|
||||
<span class="guarantor-fund-label">Paid</span>
|
||||
<span class="guarantor-fund-value">{{ g.amountPaid | number: '1.2-2' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loan timeline -->
|
||||
<div class="guarantor-timeline">
|
||||
<div class="guarantor-timeline-item">
|
||||
<span class="guarantor-fund-label">Submitted</span>
|
||||
<span class="guarantor-fund-value">{{
|
||||
g.submittedOnDate ? (g.submittedOnDate | date: 'dd MMM yyyy') : '—'
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="guarantor-timeline-item">
|
||||
<span class="guarantor-fund-label">Approved</span>
|
||||
<span class="guarantor-fund-value">{{
|
||||
g.approvedOnDate ? (g.approvedOnDate | date: 'dd MMM yyyy') : '—'
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="guarantor-timeline-item">
|
||||
<span class="guarantor-fund-label">Disbursed</span>
|
||||
<span class="guarantor-fund-value">{{
|
||||
g.disbursedOnDate ? (g.disbursedOnDate | date: 'dd MMM yyyy') : '—'
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="guarantor-timeline-item">
|
||||
<span class="guarantor-fund-label">Matures</span>
|
||||
<span class="guarantor-fund-value">{{
|
||||
g.expectedMaturityDate ? (g.expectedMaturityDate | date: 'dd MMM yyyy') : '—'
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Spacer pushes summary card to the bottom -->
|
||||
<div class="step1-left-spacer"></div>
|
||||
|
||||
<!-- Member summary pinned at bottom -->
|
||||
@if (selectedMemberId && profileForm.value.applicantName) {
|
||||
<div class="member-summary-card">
|
||||
<div class="msc-avatar">{{ profileForm.value.applicantName?.charAt(0) | uppercase }}</div>
|
||||
<div class="msc-info">
|
||||
<div class="msc-name">{{ profileForm.value.applicantName }}</div>
|
||||
<div class="msc-meta">
|
||||
ID: {{ selectedMemberId }}
|
||||
@if (selectedShareCount) {
|
||||
· {{ selectedShareCount }} shares
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<span class="msc-elig" [class.msc-elig--ok]="isEligible" [class.msc-elig--fail]="!isEligible">
|
||||
<mat-icon>{{ isEligible ? 'check_circle' : 'warning' }}</mat-icon>
|
||||
{{ isEligible ? 'Eligible' : 'Not Eligible' }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<!-- /step1-left-scroll -->
|
||||
</div>
|
||||
<!-- /step1-left -->
|
||||
|
||||
<!-- ── Drag resize handle ── -->
|
||||
<div class="step1-drag-handle" [class.dragging]="isDragging" (mousedown)="startDrag($event, layoutRef)">
|
||||
<span class="drag-dots"></span>
|
||||
</div>
|
||||
|
||||
<!-- ── RIGHT: Form panel ── -->
|
||||
<div class="step1-right">
|
||||
<!-- Applicant Profile -->
|
||||
<div class="section-label"><mat-icon>person</mat-icon> Applicant Profile</div>
|
||||
|
||||
@@ -725,6 +1285,10 @@
|
||||
Next <mat-icon>arrow_forward</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /step1-right -->
|
||||
</div>
|
||||
<!-- /step1-layout -->
|
||||
</form>
|
||||
</mat-step>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -71,7 +71,43 @@ export class NominationApplyComponent implements OnInit {
|
||||
kycData: any = null;
|
||||
familyMemberKycMap: Record<string, any> = {};
|
||||
familyKycLoading = false;
|
||||
nomineeAccounts: any = null;
|
||||
familyMemberAccountsMap: Record<string, any> = {};
|
||||
familyAccountsLoading = false;
|
||||
expandedFamilyCards = new Set<number>();
|
||||
familyExpanded = true;
|
||||
kycExpanded = true;
|
||||
eligExpanded = true;
|
||||
guarantorsExpanded = true;
|
||||
|
||||
// ── Resizable split panel ─────────────────────────────────────────────────
|
||||
leftPanelWidth = 44;
|
||||
isDragging = false;
|
||||
private _dragLayout: HTMLElement | null = null;
|
||||
private _onMouseMove = (e: MouseEvent) => {
|
||||
if (!this._dragLayout) return;
|
||||
const rect = this._dragLayout.getBoundingClientRect();
|
||||
const raw = ((e.clientX - rect.left) / rect.width) * 100;
|
||||
this.leftPanelWidth = Math.min(70, Math.max(25, raw));
|
||||
};
|
||||
private _onMouseUp = () => {
|
||||
this.isDragging = false;
|
||||
document.removeEventListener('mousemove', this._onMouseMove);
|
||||
document.removeEventListener('mouseup', this._onMouseUp);
|
||||
};
|
||||
|
||||
startDrag(event: MouseEvent, layoutEl: HTMLElement): void {
|
||||
event.preventDefault();
|
||||
this.isDragging = true;
|
||||
this._dragLayout = layoutEl;
|
||||
document.addEventListener('mousemove', this._onMouseMove);
|
||||
document.addEventListener('mouseup', this._onMouseUp);
|
||||
}
|
||||
|
||||
familyMemberOptions: any = null;
|
||||
showAddAsClientPanel = false;
|
||||
selectedNewRelationship: any = null;
|
||||
addAsClientLoading = false;
|
||||
|
||||
profileForm!: FormGroup;
|
||||
postForm!: FormGroup;
|
||||
@@ -121,6 +157,59 @@ export class NominationApplyComponent implements OnInit {
|
||||
return !!member.linkedClientId;
|
||||
}
|
||||
|
||||
private get takenRelationshipIds(): Set<number> {
|
||||
return new Set(
|
||||
this.familyMembers
|
||||
.map((m) => (typeof m.relationshipId === 'object' ? m.relationshipId?.id : m.relationshipId))
|
||||
.filter((id) => !!id)
|
||||
);
|
||||
}
|
||||
|
||||
get availableRelationships(): any[] {
|
||||
const taken = this.takenRelationshipIds;
|
||||
return (this.familyMemberOptions?.relationshipIdOptions || []).filter((r: any) => !taken.has(r.id));
|
||||
}
|
||||
|
||||
get takenRelationships(): any[] {
|
||||
const taken = this.takenRelationshipIds;
|
||||
return (this.familyMemberOptions?.relationshipIdOptions || []).filter((r: any) => taken.has(r.id));
|
||||
}
|
||||
|
||||
toggleAddAsClientPanel(): void {
|
||||
this.showAddAsClientPanel = !this.showAddAsClientPanel;
|
||||
this.selectedNewRelationship = null;
|
||||
if (this.showAddAsClientPanel && !this.familyMemberOptions) {
|
||||
this.addAsClientLoading = true;
|
||||
this.clientsService.getClientTemplate().subscribe({
|
||||
next: (template: any) => {
|
||||
this.familyMemberOptions = template.familyMemberOptions;
|
||||
this.addAsClientLoading = false;
|
||||
},
|
||||
error: () => (this.addAsClientLoading = false)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
selectNewRelationship(rel: any): void {
|
||||
this.selectedNewRelationship = this.selectedNewRelationship?.id === rel.id ? null : rel;
|
||||
}
|
||||
|
||||
createClientForRelationship(): void {
|
||||
if (!this.selectedNewRelationship) return;
|
||||
const prefill = {
|
||||
relationshipId: this.selectedNewRelationship.id,
|
||||
relationship: this.selectedNewRelationship.name,
|
||||
parentClientId: this.selectedMemberId
|
||||
};
|
||||
this.router.navigate(
|
||||
[
|
||||
'/clients',
|
||||
'create'
|
||||
],
|
||||
{ state: { prefill } }
|
||||
);
|
||||
}
|
||||
|
||||
toggleFamilyCard(memberId: number): void {
|
||||
if (this.expandedFamilyCards.has(memberId)) {
|
||||
this.expandedFamilyCards.delete(memberId);
|
||||
@@ -200,6 +289,55 @@ export class NominationApplyComponent implements OnInit {
|
||||
return risk === 1 ? 'low' : risk === 2 ? 'medium' : risk === 3 ? 'high' : '';
|
||||
}
|
||||
|
||||
isLoanOverdue(loan: any): boolean {
|
||||
const isClosed = loan.status?.id === 600 || (loan.status?.value ?? '').toString().toLowerCase().includes('closed');
|
||||
if (isClosed) return false;
|
||||
return (loan.summary?.totalOverdue ?? 0) > 0;
|
||||
}
|
||||
|
||||
loanStatusLabel(loan: any): string {
|
||||
const statusVal = (loan.status?.value ?? '').toString().toLowerCase();
|
||||
if (statusVal.includes('closed')) return 'Closed';
|
||||
if ((loan.summary?.totalOverdue ?? 0) > 0) return 'Overdue';
|
||||
if (statusVal.includes('active')) return 'Active';
|
||||
return loan.status?.value ?? '—';
|
||||
}
|
||||
|
||||
loanStatusClass(loan: any): string {
|
||||
const statusVal = (loan.status?.value ?? '').toString().toLowerCase();
|
||||
if (statusVal.includes('closed')) return 'closed';
|
||||
if ((loan.summary?.totalOverdue ?? 0) > 0) return 'overdue';
|
||||
return 'active';
|
||||
}
|
||||
|
||||
familyMemberAccounts(linkedClientId: number): any {
|
||||
return this.familyMemberAccountsMap['c' + linkedClientId] ?? null;
|
||||
}
|
||||
|
||||
get nomineeLoanAccounts(): any[] {
|
||||
return this.nomineeAccounts?.loanAccounts ?? [];
|
||||
}
|
||||
|
||||
get nomineeSavingsAccounts(): any[] {
|
||||
return this.nomineeAccounts?.savingsAccounts ?? [];
|
||||
}
|
||||
|
||||
get nomineeGuarantorAccounts(): any[] {
|
||||
return this.nomineeAccounts?.guarantorAccounts ?? [];
|
||||
}
|
||||
|
||||
get isEligible(): boolean {
|
||||
if (!this.nomineeAccounts) return true;
|
||||
const nomineeOverdue = (this.nomineeAccounts?.loanAccounts ?? []).some((l: any) => this.isLoanOverdue(l));
|
||||
if (nomineeOverdue) return false;
|
||||
for (const key of Object.keys(this.familyMemberAccountsMap)) {
|
||||
const fmAccounts = this.familyMemberAccountsMap[key];
|
||||
const overdue = (fmAccounts?.loanAccounts ?? []).some((l: any) => this.isLoanOverdue(l));
|
||||
if (overdue) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
kycExpiryInfo(expiryDate: any): { date: Date | null; isExpired: boolean; isExpiringSoon: boolean; daysLeft: number } {
|
||||
const date = this.parseDob(expiryDate);
|
||||
if (!date) return { date: null, isExpired: false, isExpiringSoon: false, daysLeft: 0 };
|
||||
@@ -208,6 +346,22 @@ export class NominationApplyComponent implements OnInit {
|
||||
return { date, isExpired: daysLeft < 0, isExpiringSoon: daysLeft >= 0 && daysLeft <= 180, daysLeft };
|
||||
}
|
||||
|
||||
clearMember(): void {
|
||||
this.selectedMemberId = null;
|
||||
this.memberSearchCtrl.setValue('', { emitEvent: false });
|
||||
this.filteredClients = [];
|
||||
this.selectedShareAccountNo = null;
|
||||
this.selectedShareCount = null;
|
||||
this.familyMembers = [];
|
||||
this.kycData = null;
|
||||
this.familyMemberKycMap = {};
|
||||
this.nomineeAccounts = null;
|
||||
this.familyMemberAccountsMap = {};
|
||||
this.familyAccountsLoading = false;
|
||||
this.familyLoading = false;
|
||||
this.profileForm.reset();
|
||||
}
|
||||
|
||||
onMemberSelect(client: any): void {
|
||||
this.selectedMemberId = client.id;
|
||||
this.memberSearchCtrl.setValue(client.displayName, { emitEvent: false });
|
||||
@@ -218,6 +372,14 @@ export class NominationApplyComponent implements OnInit {
|
||||
this.familyMemberKycMap = {};
|
||||
this.familyKycLoading = false;
|
||||
this.expandedFamilyCards = new Set<number>();
|
||||
this.showAddAsClientPanel = false;
|
||||
this.selectedNewRelationship = null;
|
||||
this.familyExpanded = true;
|
||||
this.kycExpanded = true;
|
||||
this.eligExpanded = true;
|
||||
this.nomineeAccounts = null;
|
||||
this.familyMemberAccountsMap = {};
|
||||
this.familyAccountsLoading = false;
|
||||
|
||||
forkJoin({
|
||||
detail: this.clientsService.getClientData(client.id.toString()),
|
||||
@@ -281,6 +443,7 @@ export class NominationApplyComponent implements OnInit {
|
||||
this.familyLoading = false;
|
||||
this.familyMembers = Array.isArray(family) ? family : [];
|
||||
this.kycData = kyc;
|
||||
this.nomineeAccounts = accounts;
|
||||
|
||||
const linkedMembers = this.familyMembers.filter((m) => !!m.linkedClientId);
|
||||
if (linkedMembers.length > 0) {
|
||||
@@ -296,6 +459,20 @@ export class NominationApplyComponent implements OnInit {
|
||||
this.familyMemberKycMap = kycMap;
|
||||
});
|
||||
}
|
||||
|
||||
if (linkedMembers.length > 0) {
|
||||
this.familyAccountsLoading = true;
|
||||
const accountCalls: { [k: string]: any } = {};
|
||||
linkedMembers.forEach((m) => {
|
||||
accountCalls['c' + m.linkedClientId] = this.clientsService
|
||||
.getClientAccountData(m.linkedClientId.toString())
|
||||
.pipe(catchError(() => of(null)));
|
||||
});
|
||||
forkJoin(accountCalls).subscribe((accountsMap: any) => {
|
||||
this.familyAccountsLoading = false;
|
||||
this.familyMemberAccountsMap = accountsMap;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -371,6 +548,10 @@ export class NominationApplyComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
get manualEntryMembers(): any[] {
|
||||
return this.familyMembers.filter((m) => !m.linkedClientId);
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
if (!this.selectedMemberId) {
|
||||
this.snackBar.open('Please search and select a member first', 'Close', { duration: 3000 });
|
||||
@@ -382,6 +563,33 @@ export class NominationApplyComponent implements OnInit {
|
||||
this.declarationForm.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
const manualMembers = this.manualEntryMembers;
|
||||
if (manualMembers.length > 0) {
|
||||
const names = manualMembers
|
||||
.map((m) => {
|
||||
const name = this.familyMemberDisplayName(m);
|
||||
const rel = m.relationship || m.relationshipId?.name || '';
|
||||
return rel ? `${name} (${rel})` : name;
|
||||
})
|
||||
.join(', ');
|
||||
this.snackBar.open(
|
||||
`Cannot submit: ${names} ${manualMembers.length === 1 ? 'is' : 'are'} not registered as CBS client${manualMembers.length === 1 ? '' : 's'}. Please use "Create as Client" to register them first.`,
|
||||
'Close',
|
||||
{ duration: 8000, panelClass: 'snack-error' }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isEligible) {
|
||||
this.snackBar.open(
|
||||
'Cannot submit: Nominee or a family member has overdue loans. No Due Certificate is not eligible.',
|
||||
'Close',
|
||||
{ duration: 8000, panelClass: 'snack-error' }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitting = true;
|
||||
|
||||
const bv = this.profileForm.value;
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
.nomination-detail {
|
||||
padding: 24px;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
.nomination-list {
|
||||
padding: 24px;
|
||||
max-width: 1300px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
.voting-interface {
|
||||
padding: 24px;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
|
||||
+11
-37
@@ -104,86 +104,63 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table
|
||||
class="flex-fill"
|
||||
mat-table
|
||||
[dataSource]="guarantorDetails | accountsFilter: 'guarantor' : showDeletedGuarantorsAccounts"
|
||||
>
|
||||
<table mat-table [dataSource]="guarantorDetails | accountsFilter: 'guarantor' : showDeletedGuarantorsAccounts">
|
||||
<ng-container matColumnDef="fullname">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let guarantor">
|
||||
@if (!guarantor.guarantorFundingDetails) {
|
||||
<span> {{ guarantor.firstname }} {{ guarantor.lastname }} </span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="relationship">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Relationship' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let guarantor">
|
||||
@if (!guarantor.guarantorFundingDetails) {
|
||||
<span>
|
||||
{{ guarantor.clientRelationshipType?.name }}
|
||||
</span>
|
||||
}
|
||||
<span>{{ guarantor.clientRelationshipType?.name }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="guarantortype">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Guarantor Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let guarantor">
|
||||
@if (!guarantor.guarantorFundingDetails) {
|
||||
<span>
|
||||
{{ guarantor.guarantorType.value }}
|
||||
</span>
|
||||
}
|
||||
<span>{{ guarantor.guarantorType.value }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="depositAccount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Deposit Account' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let guarantor">
|
||||
@if (!guarantor.guarantorFundingDetails) {
|
||||
<span> </span>
|
||||
@if (guarantor.guarantorFundingDetails?.length) {
|
||||
<span>{{ guarantor.guarantorFundingDetails[0].savingsAccount?.accountNo }}</span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let guarantor">
|
||||
@if (!guarantor.guarantorFundingDetails) {
|
||||
<span> </span>
|
||||
@if (guarantor.guarantorFundingDetails?.length) {
|
||||
<span>{{ guarantor.guarantorFundingDetails[0].amount | formatNumber }}</span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="remainingAmount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Remaining Amount' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let guarantor">
|
||||
@if (!guarantor.guarantorFundingDetails) {
|
||||
<span> </span>
|
||||
@if (guarantor.guarantorFundingDetails?.length) {
|
||||
<span>{{ guarantor.guarantorFundingDetails[0].amountRemaining | formatNumber }}</span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="status">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Status' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let guarantor">
|
||||
@if (!guarantor.guarantorFundingDetails) {
|
||||
<span>
|
||||
@if (guarantor.status) {
|
||||
<span>
|
||||
{{ 'labels.inputs.Active' | translate }}
|
||||
</span>
|
||||
<span>{{ 'labels.inputs.Active' | translate }}</span>
|
||||
}
|
||||
@if (!guarantor.status) {
|
||||
<span>
|
||||
{{ 'labels.inputs.Deleted' | translate }}
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
<span>{{ 'labels.inputs.Deleted' | translate }}</span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="action">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Action' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let guarantor; let id = index">
|
||||
@if (!guarantor.guarantorFundingDetails) {
|
||||
<span>
|
||||
<button
|
||||
mat-raised-button
|
||||
@@ -200,7 +177,6 @@
|
||||
</button>
|
||||
}
|
||||
@if (guarantor.status) {
|
||||
<span>
|
||||
<button
|
||||
mat-raised-button
|
||||
class="account-action-button"
|
||||
@@ -210,10 +186,8 @@
|
||||
>
|
||||
<fa-icon icon="trash" class="m-r-10"></fa-icon>
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="guarantorsDisplayedColumns"></tr>
|
||||
|
||||
@@ -800,4 +800,8 @@ export class LoansService {
|
||||
const httpParams = new HttpParams().set('offset', offset.toString()).set('limit', limit.toString());
|
||||
return this.http.get('/entityDatatableChecks', { params: httpParams });
|
||||
}
|
||||
|
||||
getLoanGuarantors(loanId: string): Observable<any> {
|
||||
return this.http.get(`/loans/${loanId}/guarantors`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
/* tslint:disable */
|
||||
export default {
|
||||
'mifos_x': {
|
||||
'version': '260619',
|
||||
'hash': '822fa4c6'
|
||||
'version': '260623',
|
||||
'hash': '3d9b0480'
|
||||
},
|
||||
'allow_switching_backend_instance': true
|
||||
};
|
||||
|
||||
@@ -33,6 +33,19 @@
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.snack-error .mdc-snackbar__surface {
|
||||
background-color: #dc2626 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.snack-error .mat-mdc-snack-bar-label {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.snack-error .mat-mdc-button {
|
||||
color: #fecaca !important;
|
||||
}
|
||||
|
||||
// ===== Sub-menu Mega Panels (rendered in CDK overlay) =====
|
||||
.deposits-mega-panel,
|
||||
.hrm-mega-panel {
|
||||
|
||||
Reference in New Issue
Block a user