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:
Jaanu423
2026-06-24 13:12:08 +05:30
co-authored by Claude Sonnet 4.6
parent 3d9b0480c6
commit 71552d6875
14 changed files with 2532 additions and 749 deletions
@@ -8,8 +8,6 @@
.candidate-board { .candidate-board {
padding: 24px; padding: 24px;
max-width: 1300px;
margin: 0 auto;
} }
.page-header { .page-header {
@@ -30,8 +30,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%);
} }
.config-inner { .config-inner {
max-width: 860px; // layout wrapper
margin: 0 auto;
} }
// ── Page header ──────────────────────────────────────────────────────────── // ── Page header ────────────────────────────────────────────────────────────
@@ -8,8 +8,6 @@
.election-dashboard { .election-dashboard {
padding: 24px; padding: 24px;
max-width: 1400px;
margin: 0 auto;
} }
.page-header { .page-header {
@@ -8,8 +8,6 @@
.election-results { .election-results {
padding: 24px; padding: 24px;
max-width: 1000px;
margin: 0 auto;
} }
.page-header { .page-header {
@@ -28,10 +28,17 @@
<mat-stepper orientation="horizontal" #stepper linear> <mat-stepper orientation="horizontal" #stepper linear>
<!-- ── Step 1: Personal Profile ── --> <!-- ── Step 1: Personal Profile ── -->
<mat-step [stepControl]="profileForm" label="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 --> <!-- Member Search -->
<div class="member-search-box"> <div class="member-search-box">
<div class="section-label"><mat-icon>manage_search</mat-icon> Search Account Holder</div> <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"> <div class="search-row">
<mat-form-field appearance="outline" class="search-field"> <mat-form-field appearance="outline" class="search-field">
<mat-label>Type member name or account number</mat-label> <mat-label>Type member name or account number</mat-label>
@@ -49,13 +56,17 @@
> >
@if (searchLoading) { @if (searchLoading) {
<mat-option disabled> <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... Searching...
</mat-option> </mat-option>
} }
@for (client of filteredClients; track client.id) { @for (client of filteredClients; track client.id) {
<mat-option [value]="client"> <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 >person</mat-icon
> >
<strong>{{ client.displayName }}</strong> <strong>{{ client.displayName }}</strong>
@@ -76,8 +87,12 @@
</mat-autocomplete> </mat-autocomplete>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="member-hint">
@if (selectedMemberId) { <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"> <div class="member-selected-badge">
<mat-icon>check_circle</mat-icon> <mat-icon>check_circle</mat-icon>
<span <span
@@ -94,11 +109,9 @@
<span style="color: #c53030">&nbsp;·&nbsp; No active share account found</span> <span style="color: #c53030">&nbsp;·&nbsp; No active share account found</span>
} }
<span>&nbsp;·&nbsp; Details auto-filled below</span> <span>&nbsp;·&nbsp; Details auto-filled below</span>
</div> <button type="button" class="member-remove-btn" (click)="clearMember()" title="Remove member">
} @else { <mat-icon>close</mat-icon>
<div class="member-hint"> </button>
<mat-icon>info</mat-icon>
Search and select a member to auto-fill their personal details below.
</div> </div>
} }
</div> </div>
@@ -106,8 +119,18 @@
<!-- Family Members (shown after a member is selected) --> <!-- Family Members (shown after a member is selected) -->
@if (selectedMemberId) { @if (selectedMemberId) {
<div class="family-section"> <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) { @if (familyLoading) {
<div class="family-loading"> <div class="family-loading">
<mat-spinner diameter="18"></mat-spinner> <mat-spinner diameter="18"></mat-spinner>
@@ -132,7 +155,9 @@
<span class="family-name">{{ familyMemberDisplayName(member) }}</span> <span class="family-name">{{ familyMemberDisplayName(member) }}</span>
@if (member.relationship || member.relationshipId?.name) { @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)) { @if (isLinkedClient(member)) {
@@ -174,7 +199,12 @@
<!-- Expanded: detail row + KYC --> <!-- Expanded: detail row + KYC -->
@if (expandedFamilyCards.has(member.id)) { @if (expandedFamilyCards.has(member.id)) {
<!-- Detail row: PAN, Aadhaar, Nationality, Mobile --> <!-- 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"> <div class="family-detail-row">
@if (member.panNumber) { @if (member.panNumber) {
<div class="family-detail-item"> <div class="family-detail-item">
@@ -301,7 +331,9 @@
<div <div
class="kyc-expiry-block" class="kyc-expiry-block"
[class.kyc-expiry-block--expired]="fmExp.isExpired" [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> <mat-icon>{{ fmExp.isExpired ? 'event_busy' : 'event' }}</mat-icon>
<div> <div>
@@ -338,7 +370,9 @@
[class.kyc-vbadge--ok]="fmKyc.panVerified" [class.kyc-vbadge--ok]="fmKyc.panVerified"
[class.kyc-vbadge--no]="!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' }} {{ fmKyc.panVerified ? 'Verified' : 'Not Verified' }}
</span> </span>
} }
@@ -357,7 +391,9 @@
[class.kyc-vbadge--ok]="fmKyc.aadhaarVerified" [class.kyc-vbadge--ok]="fmKyc.aadhaarVerified"
[class.kyc-vbadge--no]="!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' }} {{ fmKyc.aadhaarVerified ? 'Verified' : 'Not Verified' }}
</span> </span>
} }
@@ -394,7 +430,8 @@
} }
@if (fmKyc.kycDocExpiryDate) { @if (fmKyc.kycDocExpiryDate) {
<div class="kyc-item-sub"> <div class="kyc-item-sub">
Expires: {{ parseDob(fmKyc.kycDocExpiryDate) | date: 'dd MMM yyyy' }} Expires:
{{ parseDob(fmKyc.kycDocExpiryDate) | date: 'dd MMM yyyy' }}
</div> </div>
} }
</div> </div>
@@ -410,22 +447,107 @@
} }
</div> </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> </div>
} }
<!-- KYC Status --> <!-- KYC Status -->
@if (selectedMemberId) { @if (selectedMemberId) {
<div class="kyc-section"> <div class="kyc-section">
<div class="section-label"> <div class="section-label section-label--toggle" (click)="kycExpanded = !kycExpanded">
<mat-icon>verified_user</mat-icon> <mat-icon>verified_user</mat-icon>
KYC Details KYC Details
@if (profileForm.value.applicantName) { @if (profileForm.value.applicantName) {
<span class="kyc-owner-chip">{{ profileForm.value.applicantName }}</span> <span class="kyc-owner-chip">{{ profileForm.value.applicantName }}</span>
} }
<mat-icon class="section-chevron" [class.section-chevron--open]="kycExpanded"
>expand_more</mat-icon
>
</div> </div>
@if (kycExpanded) {
@if (familyLoading) { @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) { } @else if (!kycData) {
<div class="kyc-not-done"> <div class="kyc-not-done">
<mat-icon>warning</mat-icon> <mat-icon>warning</mat-icon>
@@ -459,7 +581,9 @@
{{ kycData.kycType === 2 ? 'Non-Individual' : 'Individual' }} {{ kycData.kycType === 2 ? 'Non-Individual' : 'Individual' }}
</span> </span>
@if (kycData.ckycNumber) { @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) { @if (kycData.verifiedOn) {
<span class="kyc-date-chip"> <span class="kyc-date-chip">
@@ -471,7 +595,10 @@
<!-- ── Risk + Expiry banner ── --> <!-- ── Risk + Expiry banner ── -->
<div class="kyc-risk-row"> <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>{{ <mat-icon>{{
kycData.riskCategory === 3 kycData.riskCategory === 3
? 'priority_high' ? 'priority_high'
@@ -532,7 +659,9 @@
</span> </span>
} }
@if (kycData.panVerifiedOn) { @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> </div>
@@ -563,7 +692,9 @@
@if (kycData.panAadhaarSeedingStatus === 'Y') { @if (kycData.panAadhaarSeedingStatus === 'Y') {
<span class="kyc-vbadge kyc-vbadge--ok"><mat-icon>link</mat-icon> Linked</span> <span class="kyc-vbadge kyc-vbadge--ok"><mat-icon>link</mat-icon> Linked</span>
} @else if (kycData.panAadhaarSeedingStatus === 'N') { } @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') { } @else if (kycData.panAadhaarSeedingStatus === 'E') {
<span class="kyc-vbadge kyc-vbadge--na"><mat-icon>block</mat-icon> Exempt</span> <span class="kyc-vbadge kyc-vbadge--na"><mat-icon>block</mat-icon> Exempt</span>
} @else { } @else {
@@ -644,9 +775,438 @@
} }
</div> </div>
} }
}
<!-- /kycExpanded -->
</div> </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:&nbsp;<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:&nbsp;<strong class="mono">{{ g.savingsAccountNo }}</strong>
@if (g.savingsBalance != null) {
<span class="guarantor-savings-balance">
&nbsp;·&nbsp; 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) {
&nbsp;· 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) {
&nbsp;·&nbsp; {{ 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 --> <!-- Applicant Profile -->
<div class="section-label"><mat-icon>person</mat-icon> Applicant Profile</div> <div class="section-label"><mat-icon>person</mat-icon> Applicant Profile</div>
@@ -725,6 +1285,10 @@
Next <mat-icon>arrow_forward</mat-icon> Next <mat-icon>arrow_forward</mat-icon>
</button> </button>
</div> </div>
</div>
<!-- /step1-right -->
</div>
<!-- /step1-layout -->
</form> </form>
</mat-step> </mat-step>
File diff suppressed because it is too large Load Diff
@@ -71,7 +71,43 @@ export class NominationApplyComponent implements OnInit {
kycData: any = null; kycData: any = null;
familyMemberKycMap: Record<string, any> = {}; familyMemberKycMap: Record<string, any> = {};
familyKycLoading = false; familyKycLoading = false;
nomineeAccounts: any = null;
familyMemberAccountsMap: Record<string, any> = {};
familyAccountsLoading = false;
expandedFamilyCards = new Set<number>(); 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; profileForm!: FormGroup;
postForm!: FormGroup; postForm!: FormGroup;
@@ -121,6 +157,59 @@ export class NominationApplyComponent implements OnInit {
return !!member.linkedClientId; 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 { toggleFamilyCard(memberId: number): void {
if (this.expandedFamilyCards.has(memberId)) { if (this.expandedFamilyCards.has(memberId)) {
this.expandedFamilyCards.delete(memberId); this.expandedFamilyCards.delete(memberId);
@@ -200,6 +289,55 @@ export class NominationApplyComponent implements OnInit {
return risk === 1 ? 'low' : risk === 2 ? 'medium' : risk === 3 ? 'high' : ''; 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 } { kycExpiryInfo(expiryDate: any): { date: Date | null; isExpired: boolean; isExpiringSoon: boolean; daysLeft: number } {
const date = this.parseDob(expiryDate); const date = this.parseDob(expiryDate);
if (!date) return { date: null, isExpired: false, isExpiringSoon: false, daysLeft: 0 }; 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 }; 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 { onMemberSelect(client: any): void {
this.selectedMemberId = client.id; this.selectedMemberId = client.id;
this.memberSearchCtrl.setValue(client.displayName, { emitEvent: false }); this.memberSearchCtrl.setValue(client.displayName, { emitEvent: false });
@@ -218,6 +372,14 @@ export class NominationApplyComponent implements OnInit {
this.familyMemberKycMap = {}; this.familyMemberKycMap = {};
this.familyKycLoading = false; this.familyKycLoading = false;
this.expandedFamilyCards = new Set<number>(); 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({ forkJoin({
detail: this.clientsService.getClientData(client.id.toString()), detail: this.clientsService.getClientData(client.id.toString()),
@@ -281,6 +443,7 @@ export class NominationApplyComponent implements OnInit {
this.familyLoading = false; this.familyLoading = false;
this.familyMembers = Array.isArray(family) ? family : []; this.familyMembers = Array.isArray(family) ? family : [];
this.kycData = kyc; this.kycData = kyc;
this.nomineeAccounts = accounts;
const linkedMembers = this.familyMembers.filter((m) => !!m.linkedClientId); const linkedMembers = this.familyMembers.filter((m) => !!m.linkedClientId);
if (linkedMembers.length > 0) { if (linkedMembers.length > 0) {
@@ -296,6 +459,20 @@ export class NominationApplyComponent implements OnInit {
this.familyMemberKycMap = kycMap; 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 { submit(): void {
if (!this.selectedMemberId) { if (!this.selectedMemberId) {
this.snackBar.open('Please search and select a member first', 'Close', { duration: 3000 }); 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(); this.declarationForm.markAllAsTouched();
return; 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; this.submitting = true;
const bv = this.profileForm.value; const bv = this.profileForm.value;
@@ -8,8 +8,6 @@
.nomination-detail { .nomination-detail {
padding: 24px; padding: 24px;
max-width: 1100px;
margin: 0 auto;
} }
.page-header { .page-header {
@@ -8,8 +8,6 @@
.nomination-list { .nomination-list {
padding: 24px; padding: 24px;
max-width: 1300px;
margin: 0 auto;
} }
.page-header { .page-header {
@@ -8,8 +8,6 @@
.voting-interface { .voting-interface {
padding: 24px; padding: 24px;
max-width: 900px;
margin: 0 auto;
} }
.page-header { .page-header {
@@ -104,86 +104,63 @@
</div> </div>
</div> </div>
</div> </div>
<table <table mat-table [dataSource]="guarantorDetails | accountsFilter: 'guarantor' : showDeletedGuarantorsAccounts">
class="flex-fill"
mat-table
[dataSource]="guarantorDetails | accountsFilter: 'guarantor' : showDeletedGuarantorsAccounts"
>
<ng-container matColumnDef="fullname"> <ng-container matColumnDef="fullname">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.name' | translate }}</th> <th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.name' | translate }}</th>
<td mat-cell *matCellDef="let guarantor"> <td mat-cell *matCellDef="let guarantor">
@if (!guarantor.guarantorFundingDetails) {
<span> {{ guarantor.firstname }} &nbsp; {{ guarantor.lastname }} </span> <span> {{ guarantor.firstname }} &nbsp; {{ guarantor.lastname }} </span>
}
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="relationship"> <ng-container matColumnDef="relationship">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Relationship' | translate }}</th> <th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Relationship' | translate }}</th>
<td mat-cell *matCellDef="let guarantor"> <td mat-cell *matCellDef="let guarantor">
@if (!guarantor.guarantorFundingDetails) { <span>{{ guarantor.clientRelationshipType?.name }}</span>
<span>
{{ guarantor.clientRelationshipType?.name }}
</span>
}
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="guarantortype"> <ng-container matColumnDef="guarantortype">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Guarantor Type' | translate }}</th> <th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Guarantor Type' | translate }}</th>
<td mat-cell *matCellDef="let guarantor"> <td mat-cell *matCellDef="let guarantor">
@if (!guarantor.guarantorFundingDetails) { <span>{{ guarantor.guarantorType.value }}</span>
<span>
{{ guarantor.guarantorType.value }}
</span>
}
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="depositAccount"> <ng-container matColumnDef="depositAccount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Deposit Account' | translate }}</th> <th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Deposit Account' | translate }}</th>
<td mat-cell *matCellDef="let guarantor"> <td mat-cell *matCellDef="let guarantor">
@if (!guarantor.guarantorFundingDetails) { @if (guarantor.guarantorFundingDetails?.length) {
<span> </span> <span>{{ guarantor.guarantorFundingDetails[0].savingsAccount?.accountNo }}</span>
} }
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="amount"> <ng-container matColumnDef="amount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th> <th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
<td mat-cell *matCellDef="let guarantor"> <td mat-cell *matCellDef="let guarantor">
@if (!guarantor.guarantorFundingDetails) { @if (guarantor.guarantorFundingDetails?.length) {
<span> </span> <span>{{ guarantor.guarantorFundingDetails[0].amount | formatNumber }}</span>
} }
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="remainingAmount"> <ng-container matColumnDef="remainingAmount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Remaining Amount' | translate }}</th> <th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Remaining Amount' | translate }}</th>
<td mat-cell *matCellDef="let guarantor"> <td mat-cell *matCellDef="let guarantor">
@if (!guarantor.guarantorFundingDetails) { @if (guarantor.guarantorFundingDetails?.length) {
<span> </span> <span>{{ guarantor.guarantorFundingDetails[0].amountRemaining | formatNumber }}</span>
} }
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="status"> <ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Status' | translate }}</th> <th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Status' | translate }}</th>
<td mat-cell *matCellDef="let guarantor"> <td mat-cell *matCellDef="let guarantor">
@if (!guarantor.guarantorFundingDetails) {
<span>
@if (guarantor.status) { @if (guarantor.status) {
<span> <span>{{ 'labels.inputs.Active' | translate }}</span>
{{ 'labels.inputs.Active' | translate }}
</span>
} }
@if (!guarantor.status) { @if (!guarantor.status) {
<span> <span>{{ 'labels.inputs.Deleted' | translate }}</span>
{{ 'labels.inputs.Deleted' | translate }}
</span>
}
</span>
} }
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="action"> <ng-container matColumnDef="action">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Action' | translate }}</th> <th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Action' | translate }}</th>
<td mat-cell *matCellDef="let guarantor; let id = index"> <td mat-cell *matCellDef="let guarantor; let id = index">
@if (!guarantor.guarantorFundingDetails) {
<span> <span>
<button <button
mat-raised-button mat-raised-button
@@ -200,7 +177,6 @@
</button> </button>
} }
@if (guarantor.status) { @if (guarantor.status) {
<span>
<button <button
mat-raised-button mat-raised-button
class="account-action-button" class="account-action-button"
@@ -210,10 +186,8 @@
> >
<fa-icon icon="trash" class="m-r-10"></fa-icon> <fa-icon icon="trash" class="m-r-10"></fa-icon>
</button> </button>
</span>
} }
</span> </span>
}
</td> </td>
</ng-container> </ng-container>
<tr mat-header-row *matHeaderRowDef="guarantorsDisplayedColumns"></tr> <tr mat-header-row *matHeaderRowDef="guarantorsDisplayedColumns"></tr>
+4
View File
@@ -800,4 +800,8 @@ export class LoansService {
const httpParams = new HttpParams().set('offset', offset.toString()).set('limit', limit.toString()); const httpParams = new HttpParams().set('offset', offset.toString()).set('limit', limit.toString());
return this.http.get('/entityDatatableChecks', { params: httpParams }); return this.http.get('/entityDatatableChecks', { params: httpParams });
} }
getLoanGuarantors(loanId: string): Observable<any> {
return this.http.get(`/loans/${loanId}/guarantors`);
}
} }
+2 -2
View File
@@ -9,8 +9,8 @@
/* tslint:disable */ /* tslint:disable */
export default { export default {
'mifos_x': { 'mifos_x': {
'version': '260619', 'version': '260623',
'hash': '822fa4c6' 'hash': '3d9b0480'
}, },
'allow_switching_backend_instance': true 'allow_switching_backend_instance': true
}; };
+13
View File
@@ -33,6 +33,19 @@
z-index: 9999; 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) ===== // ===== Sub-menu Mega Panels (rendered in CDK overlay) =====
.deposits-mega-panel, .deposits-mega-panel,
.hrm-mega-panel { .hrm-mega-panel {