update fd account
This commit is contained in:
+6
-50
@@ -45,38 +45,15 @@
|
|||||||
</mifosx-fixed-deposit-account-details-step>
|
</mifosx-fixed-deposit-account-details-step>
|
||||||
</mat-step>
|
</mat-step>
|
||||||
|
|
||||||
|
<!-- Step 2: Currency -->
|
||||||
@if (fixedDepositsAccountProductTemplate) {
|
@if (fixedDepositsAccountProductTemplate) {
|
||||||
<!-- Step 2: Terms -->
|
|
||||||
<mat-step>
|
<mat-step>
|
||||||
<ng-template matStepLabel>{{ 'labels.inputs.TERMS' | translate }}</ng-template>
|
<ng-template matStepLabel>Currency</ng-template>
|
||||||
|
|
||||||
<mifosx-fixed-deposit-account-terms-step
|
<mifosx-fixed-deposit-account-currency-step
|
||||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
|
||||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||||
>
|
>
|
||||||
</mifosx-fixed-deposit-account-terms-step>
|
</mifosx-fixed-deposit-account-currency-step>
|
||||||
|
|
||||||
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
|
||||||
<button mat-raised-button matStepperPrevious>
|
|
||||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
|
||||||
{{ 'labels.buttons.Previous' | translate }}
|
|
||||||
</button>
|
|
||||||
<button mat-raised-button matStepperNext>
|
|
||||||
{{ 'labels.buttons.Next' | translate }}
|
|
||||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</mat-step>
|
|
||||||
|
|
||||||
<!-- Step 3: Settings -->
|
|
||||||
<mat-step>
|
|
||||||
<ng-template matStepLabel>{{ 'labels.inputs.SETTINGS' | translate }}</ng-template>
|
|
||||||
|
|
||||||
<mifosx-fixed-deposit-account-settings-step
|
|
||||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
|
||||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
|
||||||
>
|
|
||||||
</mifosx-fixed-deposit-account-settings-step>
|
|
||||||
|
|
||||||
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
||||||
<button mat-raised-button matStepperPrevious>
|
<button mat-raised-button matStepperPrevious>
|
||||||
@@ -97,6 +74,7 @@
|
|||||||
<mifosx-fixed-deposit-account-interest-rate-chart-step
|
<mifosx-fixed-deposit-account-interest-rate-chart-step
|
||||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
||||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||||
|
(tenureSelected)="onTenureSelected($event)"
|
||||||
>
|
>
|
||||||
</mifosx-fixed-deposit-account-interest-rate-chart-step>
|
</mifosx-fixed-deposit-account-interest-rate-chart-step>
|
||||||
|
|
||||||
@@ -112,29 +90,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</mat-step>
|
</mat-step>
|
||||||
|
|
||||||
<!-- Step 5: Charges -->
|
|
||||||
<mat-step>
|
|
||||||
<ng-template matStepLabel>{{ 'labels.inputs.CHARGES' | translate }}</ng-template>
|
|
||||||
|
|
||||||
<mifosx-fixed-deposit-account-charges-step
|
|
||||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
|
||||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
|
||||||
[fixedDepositAccountFormValid]="fixedDepositAccountFormValid"
|
|
||||||
>
|
|
||||||
</mifosx-fixed-deposit-account-charges-step>
|
|
||||||
|
|
||||||
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
|
||||||
<button mat-raised-button matStepperPrevious>
|
|
||||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
|
||||||
{{ 'labels.buttons.Previous' | translate }}
|
|
||||||
</button>
|
|
||||||
<button mat-raised-button matStepperNext>
|
|
||||||
{{ 'labels.buttons.Next' | translate }}
|
|
||||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</mat-step>
|
|
||||||
|
|
||||||
<!-- Step 6: Preview -->
|
<!-- Step 6: Preview -->
|
||||||
<mat-step state="preview" completed>
|
<mat-step state="preview" completed>
|
||||||
<ng-template matStepLabel>{{ 'labels.inputs.PREVIEW' | translate }}</ng-template>
|
<ng-template matStepLabel>{{ 'labels.inputs.PREVIEW' | translate }}</ng-template>
|
||||||
@@ -142,6 +97,7 @@
|
|||||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
||||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||||
[fixedDepositAccountData]="fixedDepositAccount"
|
[fixedDepositAccountData]="fixedDepositAccount"
|
||||||
|
[selectedTenure]="selectedTenure"
|
||||||
(submitEvent)="submit()"
|
(submitEvent)="submit()"
|
||||||
>
|
>
|
||||||
</mifosx-fixed-deposit-account-preview-step>
|
</mifosx-fixed-deposit-account-preview-step>
|
||||||
|
|||||||
+16
-37
@@ -16,9 +16,7 @@ import { SettingsService } from 'app/settings/settings.service';
|
|||||||
|
|
||||||
/** Custom Components */
|
/** Custom Components */
|
||||||
import { FixedDepositAccountDetailsStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-details-step/fixed-deposit-account-details-step.component';
|
import { FixedDepositAccountDetailsStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-details-step/fixed-deposit-account-details-step.component';
|
||||||
import { FixedDepositAccountTermsStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-terms-step/fixed-deposit-account-terms-step.component';
|
import { FixedDepositAccountCurrencyStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-currency-step/fixed-deposit-account-currency-step.component';
|
||||||
import { FixedDepositAccountSettingsStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-settings-step/fixed-deposit-account-settings-step.component';
|
|
||||||
import { FixedDepositAccountChargesStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-charges-step/fixed-deposit-account-charges-step.component';
|
|
||||||
import { Dates } from 'app/core/utils/dates';
|
import { Dates } from 'app/core/utils/dates';
|
||||||
import {
|
import {
|
||||||
MatStepper,
|
MatStepper,
|
||||||
@@ -50,10 +48,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
MatStep,
|
MatStep,
|
||||||
MatStepLabel,
|
MatStepLabel,
|
||||||
FixedDepositAccountDetailsStepComponent,
|
FixedDepositAccountDetailsStepComponent,
|
||||||
FixedDepositAccountTermsStepComponent,
|
|
||||||
FixedDepositAccountSettingsStepComponent,
|
|
||||||
FixedDepositAccountInterestRateChartStepComponent,
|
FixedDepositAccountInterestRateChartStepComponent,
|
||||||
FixedDepositAccountChargesStepComponent,
|
FixedDepositAccountCurrencyStepComponent,
|
||||||
FixedDepositAccountPreviewStepComponent
|
FixedDepositAccountPreviewStepComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
@@ -66,17 +62,15 @@ export class CreateFixedDepositAccountComponent {
|
|||||||
|
|
||||||
@ViewChild(FixedDepositAccountDetailsStepComponent, { static: true })
|
@ViewChild(FixedDepositAccountDetailsStepComponent, { static: true })
|
||||||
fixedDepositsAccountDetailsStep: FixedDepositAccountDetailsStepComponent;
|
fixedDepositsAccountDetailsStep: FixedDepositAccountDetailsStepComponent;
|
||||||
@ViewChild(FixedDepositAccountTermsStepComponent)
|
@ViewChild(FixedDepositAccountCurrencyStepComponent)
|
||||||
fixedDepositAccountTermsStep: FixedDepositAccountTermsStepComponent;
|
fixedDepositAccountCurrencyStep: FixedDepositAccountCurrencyStepComponent;
|
||||||
@ViewChild(FixedDepositAccountSettingsStepComponent)
|
|
||||||
fixedDepositAccountSettingsStep: FixedDepositAccountSettingsStepComponent;
|
|
||||||
@ViewChild(FixedDepositAccountChargesStepComponent)
|
|
||||||
fixedDepositAccountChargesStep: FixedDepositAccountChargesStepComponent;
|
|
||||||
|
|
||||||
/** Fixed Deposits Account Template */
|
/** Fixed Deposits Account Template */
|
||||||
fixedDepositsAccountTemplate: any;
|
fixedDepositsAccountTemplate: any;
|
||||||
/** Fixed Deposit Account Product Template */
|
/** Fixed Deposit Account Product Template */
|
||||||
fixedDepositsAccountProductTemplate: any;
|
fixedDepositsAccountProductTemplate: any;
|
||||||
|
/** Tenure selected from the interest rate chart step */
|
||||||
|
selectedTenure: { depositPeriod: number; depositPeriodFrequencyId: number } | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches FD account template from `resolve`
|
* Fetches FD account template from `resolve`
|
||||||
@@ -100,36 +94,22 @@ export class CreateFixedDepositAccountComponent {
|
|||||||
this.fixedDepositsAccountProductTemplate = $event;
|
this.fixedDepositsAccountProductTemplate = $event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onTenureSelected($event: { depositPeriod: number; depositPeriodFrequencyId: number }) {
|
||||||
|
this.selectedTenure = $event;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves Fixed Deposit Account Details Form Data
|
* Retrieves Fixed Deposit Account Details Form Data
|
||||||
*/
|
*/
|
||||||
get fixedDepositAccountDetailsForm() {
|
get fixedDepositAccountDetailsForm() {
|
||||||
return this.fixedDepositsAccountDetailsStep.fixedDepositAccountDetailsForm;
|
return this.fixedDepositsAccountDetailsStep?.fixedDepositAccountDetailsForm;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves Fixed Deposit Account Terms Form Data
|
|
||||||
*/
|
|
||||||
get fixedDepositAccountTermsForm() {
|
|
||||||
return this.fixedDepositAccountTermsStep.fixedDepositAccountTermsForm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves Fixed Deposit Account Settings Form Data
|
|
||||||
*/
|
|
||||||
get fixedDepositAccountSettingsForm() {
|
|
||||||
return this.fixedDepositAccountSettingsStep.fixedDepositAccountSettingsForm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks stepper validity.
|
* Checks stepper validity.
|
||||||
*/
|
*/
|
||||||
get fixedDepositAccountFormValid() {
|
get fixedDepositAccountFormValid() {
|
||||||
return (
|
return !!this.fixedDepositAccountDetailsForm?.valid;
|
||||||
this.fixedDepositAccountDetailsForm.valid &&
|
|
||||||
this.fixedDepositAccountTermsForm.valid &&
|
|
||||||
this.fixedDepositAccountSettingsForm.valid
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,10 +117,8 @@ export class CreateFixedDepositAccountComponent {
|
|||||||
*/
|
*/
|
||||||
get fixedDepositAccount() {
|
get fixedDepositAccount() {
|
||||||
return {
|
return {
|
||||||
...this.fixedDepositsAccountDetailsStep.fixedDepositAccountDetails,
|
...this.fixedDepositsAccountDetailsStep?.fixedDepositAccountDetails,
|
||||||
...this.fixedDepositAccountTermsStep.fixedDepositAccountTerms,
|
...this.fixedDepositAccountCurrencyStep?.currencyStepData
|
||||||
...this.fixedDepositAccountSettingsStep.fixedDepositAccountSettings,
|
|
||||||
...this.fixedDepositAccountChargesStep.fixedDepositAccountCharges
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +131,7 @@ export class CreateFixedDepositAccountComponent {
|
|||||||
const monthDayFormat = 'dd MMMM';
|
const monthDayFormat = 'dd MMMM';
|
||||||
const fixedDepositAccount = {
|
const fixedDepositAccount = {
|
||||||
...this.fixedDepositAccount,
|
...this.fixedDepositAccount,
|
||||||
|
...(this.selectedTenure ?? {}),
|
||||||
clientId: this.fixedDepositsAccountTemplate.clientId,
|
clientId: this.fixedDepositsAccountTemplate.clientId,
|
||||||
charges: this.fixedDepositAccount.charges.map((charge: any) => ({
|
charges: this.fixedDepositAccount.charges.map((charge: any) => ({
|
||||||
chargeId: charge.id,
|
chargeId: charge.id,
|
||||||
@@ -163,7 +142,7 @@ export class CreateFixedDepositAccountComponent {
|
|||||||
feeInterval: charge.feeInterval
|
feeInterval: charge.feeInterval
|
||||||
})),
|
})),
|
||||||
submittedOnDate: this.dateUtils.formatDate(this.fixedDepositAccount.submittedOnDate, dateFormat),
|
submittedOnDate: this.dateUtils.formatDate(this.fixedDepositAccount.submittedOnDate, dateFormat),
|
||||||
charts: [{ chartSlabs: this.fixedDepositsAccountProductTemplate.accountChart.chartSlabs }],
|
charts: [{ chartSlabs: this.fixedDepositsAccountProductTemplate.accountChart?.chartSlabs ?? [] }],
|
||||||
dateFormat,
|
dateFormat,
|
||||||
monthDayFormat,
|
monthDayFormat,
|
||||||
locale
|
locale
|
||||||
|
|||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
<!--
|
||||||
|
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/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<div class="facc-section">
|
||||||
|
<div class="facc-section__header">
|
||||||
|
<i class="fas fa-coins"></i>
|
||||||
|
<span>Currency & Account Status Configuration</span>
|
||||||
|
<span class="facc-badge facc-badge--from">From Product</span>
|
||||||
|
</div>
|
||||||
|
<div class="facc-body">
|
||||||
|
<div class="facc-grid">
|
||||||
|
<mat-form-field appearance="outline" class="facc-field">
|
||||||
|
<mat-label>Currency</mat-label>
|
||||||
|
<input matInput [value]="fixedDepositsAccountProductTemplate?.currency?.name || ''" readonly />
|
||||||
|
<mat-hint>Inherited from selected product</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="facc-field">
|
||||||
|
<mat-label>Decimal Places</mat-label>
|
||||||
|
<input matInput [value]="fixedDepositsAccountProductTemplate?.currency?.decimalPlaces ?? ''" readonly />
|
||||||
|
<mat-hint>Inherited from selected product</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="facc-alert">
|
||||||
|
<div class="facc-alert__content">
|
||||||
|
<h4>Auto Debit Facility</h4>
|
||||||
|
<p>Enable automatic deduction of funds for recurring payments, utility bills, or EMI obligations.</p>
|
||||||
|
</div>
|
||||||
|
<mat-slide-toggle [(ngModel)]="autoDebitFacility" color="primary"></mat-slide-toggle>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="facc-alert">
|
||||||
|
<div class="facc-alert__content">
|
||||||
|
<h4>Dormancy Alert</h4>
|
||||||
|
<p>Enable automated notifications when an account becomes inactive based on product rules.</p>
|
||||||
|
</div>
|
||||||
|
<span class="facc-yn" [class.facc-yn--yes]="fixedDepositsAccountProductTemplate?.dormancyAlert">
|
||||||
|
{{ fixedDepositsAccountProductTemplate?.dormancyAlert ? 'Yes' : 'No' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
@if (fixedDepositsAccountProductTemplate?.dormancyAlert) {
|
||||||
|
<mifosx-dormancy-alert-panel
|
||||||
|
[productTemplate]="fixedDepositsAccountProductTemplate"
|
||||||
|
></mifosx-dormancy-alert-panel>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="facc-alert">
|
||||||
|
<div class="facc-alert__content">
|
||||||
|
<h4>Nomination Facility</h4>
|
||||||
|
<p>Allow account holders to designate individuals to receive funds in the event of account holder's death.</p>
|
||||||
|
</div>
|
||||||
|
<span class="facc-yn" [class.facc-yn--yes]="fixedDepositsAccountProductTemplate?.nominationFacility">
|
||||||
|
{{ fixedDepositsAccountProductTemplate?.nominationFacility ? 'Yes' : 'No' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
@if (fixedDepositsAccountProductTemplate?.nominationFacility) {
|
||||||
|
<mifosx-nominee-form (nomineeChange)="onNomineeChange($event)"></mifosx-nominee-form>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
+124
@@ -0,0 +1,124 @@
|
|||||||
|
/**
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.facc-section {
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
background-color: #f8fafc;
|
||||||
|
padding: 0.75rem 1.25rem;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: #1976d2;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.facc-body {
|
||||||
|
padding: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facc-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 1.25rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
@media (width <= 600px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.facc-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facc-alert {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
background-color: #fff;
|
||||||
|
gap: 1.5rem;
|
||||||
|
|
||||||
|
&--editable {
|
||||||
|
border-color: #bfdbfe;
|
||||||
|
background-color: #eff6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin: 0 0 0.25rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #64748b;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.facc-badge {
|
||||||
|
padding: 0.2rem 0.6rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
&--from {
|
||||||
|
background-color: #dbeafe;
|
||||||
|
color: #1e40af;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.facc-yn {
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
background-color: #fee2e2;
|
||||||
|
color: #991b1b;
|
||||||
|
min-width: 60px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&--yes {
|
||||||
|
background-color: #dcfce7;
|
||||||
|
color: #166534;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host ::ng-deep {
|
||||||
|
.mat-mdc-form-field-subscript-wrapper {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* 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, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
import { NomineeFormComponent } from 'app/shared/nominee-form/nominee-form.component';
|
||||||
|
import { DormancyAlertPanelComponent } from 'app/shared/dormancy-alert-panel/dormancy-alert-panel.component';
|
||||||
|
import { MatSlideToggle } from '@angular/material/slide-toggle';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-fixed-deposit-account-currency-step',
|
||||||
|
templateUrl: './fixed-deposit-account-currency-step.component.html',
|
||||||
|
styleUrls: ['./fixed-deposit-account-currency-step.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
NomineeFormComponent,
|
||||||
|
DormancyAlertPanelComponent,
|
||||||
|
MatSlideToggle,
|
||||||
|
FormsModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class FixedDepositAccountCurrencyStepComponent implements OnChanges {
|
||||||
|
@Input() fixedDepositsAccountProductTemplate: any;
|
||||||
|
|
||||||
|
nomineeData: any = null;
|
||||||
|
|
||||||
|
autoDebitFacility = false;
|
||||||
|
|
||||||
|
// Editable feature toggles (pre-filled from product, overridable by user)
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
if (changes['fixedDepositsAccountProductTemplate']?.currentValue) {
|
||||||
|
const pt = changes['fixedDepositsAccountProductTemplate'].currentValue;
|
||||||
|
this.autoDebitFacility = !!pt.autoDebitFacility;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onNomineeChange(event: any): void {
|
||||||
|
this.nomineeData = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called by parent to collect currency step values */
|
||||||
|
get currencyStepData() {
|
||||||
|
return {
|
||||||
|
autoDebitFacility: this.autoDebitFacility,
|
||||||
|
...(this.nomineeData ? { nominee: this.nomineeData } : {})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
+869
-53
@@ -7,67 +7,883 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<form [formGroup]="fixedDepositAccountDetailsForm">
|
<form [formGroup]="fixedDepositAccountDetailsForm">
|
||||||
<div class="layout-row-wrap gap-2px responsive-column">
|
<!-- ══ Account Details ══════════════════════════════════════════ -->
|
||||||
<mat-form-field class="flex-48">
|
<div class="rad-section">
|
||||||
<mat-label>{{ 'labels.inputs.Product Name' | translate }}</mat-label>
|
<div class="rad-section__header">
|
||||||
<mat-select formControlName="productId" required>
|
<i class="fas fa-info-circle"></i>
|
||||||
@for (product of productData; track product) {
|
<h3>Account Details</h3>
|
||||||
<mat-option [value]="product.id">
|
</div>
|
||||||
{{ product.name }}
|
<div class="rad-grid">
|
||||||
</mat-option>
|
<mat-form-field appearance="outline" class="rad-field">
|
||||||
}
|
<mat-label>{{ 'labels.inputs.Product Name' | translate }}</mat-label>
|
||||||
</mat-select>
|
<mat-select formControlName="productId" required>
|
||||||
<mat-error>
|
@for (product of productData; track product) {
|
||||||
{{ 'labels.inputs.Product Name' | translate }} {{ 'labels.commons.is' | translate }}
|
<mat-option [value]="product.id">{{ product.name }}</mat-option>
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
}
|
||||||
</mat-error>
|
</mat-select>
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
@if (isProductSelected) {
|
|
||||||
<mat-form-field class="flex-48" (click)="submittedOnDatePicker.open()">
|
|
||||||
<mat-label>{{ 'labels.inputs.Submitted On' | translate }}</mat-label>
|
|
||||||
<input
|
|
||||||
matInput
|
|
||||||
[min]="minDate"
|
|
||||||
[max]="maxDate"
|
|
||||||
[matDatepicker]="submittedOnDatePicker"
|
|
||||||
formControlName="submittedOnDate"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<mat-datepicker-toggle matSuffix [for]="submittedOnDatePicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #submittedOnDatePicker></mat-datepicker>
|
|
||||||
<mat-error>
|
<mat-error>
|
||||||
{{ 'labels.inputs.Submission Date' | translate }} {{ 'labels.commons.is' | translate }}
|
{{ 'labels.inputs.Product Name' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
}
|
|
||||||
|
|
||||||
@if (isProductSelected) {
|
@if (isProductSelected) {
|
||||||
<mat-form-field class="flex-48">
|
<mat-form-field appearance="outline" class="rad-field" (click)="submittedOnDatePicker.open()">
|
||||||
<mat-label>{{ 'labels.inputs.Field Officer' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Submitted On' | translate }}</mat-label>
|
||||||
<mat-select formControlName="fieldOfficerId">
|
<input
|
||||||
@for (officer of fieldOfficerData; track officer) {
|
matInput
|
||||||
<mat-option [value]="officer.id">
|
[min]="minDate"
|
||||||
{{ officer.displayName }}
|
[max]="maxDate"
|
||||||
</mat-option>
|
[matDatepicker]="submittedOnDatePicker"
|
||||||
}
|
formControlName="submittedOnDate"
|
||||||
</mat-select>
|
required
|
||||||
</mat-form-field>
|
/>
|
||||||
}
|
<mat-datepicker-toggle matSuffix [for]="submittedOnDatePicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #submittedOnDatePicker></mat-datepicker>
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Submitted On' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
@if (isProductSelected) {
|
<mat-form-field appearance="outline" class="rad-field">
|
||||||
<mat-form-field class="flex-48">
|
<mat-label>{{ 'labels.inputs.Field Officer' | translate }}</mat-label>
|
||||||
<mat-label>{{ 'labels.inputs.External id' | translate }}</mat-label>
|
<mat-select formControlName="fieldOfficerId">
|
||||||
<input
|
@for (officer of fieldOfficerData; track officer) {
|
||||||
matInput
|
<mat-option [value]="officer.id">{{ officer.displayName }}</mat-option>
|
||||||
formControlName="externalId"
|
}
|
||||||
matTooltip="{{ 'tooltips.Provides an external id' | translate }}"
|
</mat-select>
|
||||||
/>
|
</mat-form-field>
|
||||||
</mat-form-field>
|
|
||||||
}
|
<mat-form-field appearance="outline" class="rad-field">
|
||||||
|
<mat-label>{{ 'labels.inputs.External id' | translate }}</mat-label>
|
||||||
|
<input matInput formControlName="externalId" />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (isProductSelected && productTemplate) {
|
||||||
|
<!-- ══ Product Classification (read-only) ══════════════════════ -->
|
||||||
|
<div class="rad-section rad-section--mt">
|
||||||
|
<div class="rad-section__header">
|
||||||
|
<i class="fas fa-tags"></i>
|
||||||
|
<h3>Product Classification</h3>
|
||||||
|
<span class="rad-badge">From Product</span>
|
||||||
|
</div>
|
||||||
|
<div class="rad-grid rad-grid--classification">
|
||||||
|
<mat-form-field appearance="outline" class="rad-field">
|
||||||
|
<mat-label>Account Type</mat-label>
|
||||||
|
<mat-select [(ngModel)]="selectedAccountType" [ngModelOptions]="{ standalone: true }">
|
||||||
|
@for (opt of accountTypeOptions; track opt) {
|
||||||
|
<mat-option [value]="opt">{{ opt }}</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
@if (productTemplate?.fdType) {
|
||||||
|
<mat-form-field appearance="outline" class="rad-field">
|
||||||
|
<mat-label>FD Type</mat-label>
|
||||||
|
<input matInput [value]="getFdTypeLabel(productTemplate.fdType)" readonly />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="rad-field">
|
||||||
|
<mat-label>Customer Segment</mat-label>
|
||||||
|
<mat-select [(ngModel)]="selectedCustomerSegment" [ngModelOptions]="{ standalone: true }">
|
||||||
|
@for (opt of customerSegmentOptions; track opt) {
|
||||||
|
<mat-option [value]="opt">{{ opt }}</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="rad-field">
|
||||||
|
<mat-label>Account No</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[value]="productTemplate?.previewAccountNo || fixedDepositsAccountTemplate?.accountNo || 'Auto-generated'"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
@if (productTemplate.effectiveDate) {
|
||||||
|
<mat-form-field appearance="outline" class="rad-field">
|
||||||
|
<mat-label>Effective Date</mat-label>
|
||||||
|
<input matInput [value]="formatTemplateDate(productTemplate.effectiveDate)" readonly />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (productTemplate.endDate) {
|
||||||
|
<mat-form-field appearance="outline" class="rad-field">
|
||||||
|
<mat-label>End Date</mat-label>
|
||||||
|
<input matInput [value]="formatTemplateDate(productTemplate.endDate)" readonly />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="rad-field rad-info-row">
|
||||||
|
<span class="rad-info-label">Deposit Account Link</span>
|
||||||
|
<span class="rad-badge" [class.rad-badge--yes]="productTemplate.shareLinkingEnabled">
|
||||||
|
{{ productTemplate.shareLinkingEnabled ? 'Yes' : 'No' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ══ Currency (read-only) ════════════════════════════════════ -->
|
||||||
|
<div class="rad-section rad-section--mt">
|
||||||
|
<div class="rad-section__header">
|
||||||
|
<i class="fas fa-coins"></i>
|
||||||
|
<h3>Currency</h3>
|
||||||
|
<span class="rad-badge">From Product</span>
|
||||||
|
</div>
|
||||||
|
<div class="rad-grid rad-grid--classification">
|
||||||
|
<mat-form-field appearance="outline" class="rad-field">
|
||||||
|
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
|
||||||
|
<input matInput [value]="currencyName" readonly />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="rad-field">
|
||||||
|
<mat-label>{{ 'labels.inputs.Decimal Places' | translate }}</mat-label>
|
||||||
|
<input matInput [value]="decimalPlaces" readonly />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="rad-field">
|
||||||
|
<mat-label>{{ 'labels.inputs.Currency In Multiples Of' | translate }}</mat-label>
|
||||||
|
<input matInput [value]="inMultiplesOf" readonly />
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ══ Deposit Account Link ══════════════════════════════════════════ -->
|
||||||
|
@if (productTemplate?.shareLinkingEnabled) {
|
||||||
|
<div class="dal-card">
|
||||||
|
<div class="dal-card__header">
|
||||||
|
<div class="dal-card__header-left">
|
||||||
|
<div class="dal-card__icon">
|
||||||
|
<mat-icon>link</mat-icon>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="dal-card__title">Deposit Account Link</h3>
|
||||||
|
<span class="dal-card__sub">Link an existing Savings or Current account to this FD</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dal-card__body">
|
||||||
|
@if (dalLoading) {
|
||||||
|
<div class="dal-loading">
|
||||||
|
<mat-icon class="dal-loading__icon">hourglass_top</mat-icon>
|
||||||
|
<span>Loading accounts…</span>
|
||||||
|
</div>
|
||||||
|
} @else if (selectedLinkedAccount) {
|
||||||
|
<!-- ── Selected Account Card ── -->
|
||||||
|
<div class="dal-selected">
|
||||||
|
<div class="dal-selected__info">
|
||||||
|
<span
|
||||||
|
class="dal-type-badge"
|
||||||
|
[class.dal-type-badge--savings]="selectedLinkedAccount.accountType === 'SAVINGS'"
|
||||||
|
[class.dal-type-badge--current]="selectedLinkedAccount.accountType === 'CURRENT'"
|
||||||
|
>
|
||||||
|
<mat-icon>{{
|
||||||
|
selectedLinkedAccount.accountType === 'SAVINGS' ? 'savings' : 'account_balance'
|
||||||
|
}}</mat-icon>
|
||||||
|
{{ selectedLinkedAccount.accountType === 'SAVINGS' ? 'Savings' : 'Current' }}
|
||||||
|
</span>
|
||||||
|
<div class="dal-selected__details">
|
||||||
|
<span class="dal-selected__accno">{{ selectedLinkedAccount.accountNo }}</span>
|
||||||
|
<span class="dal-selected__product">{{ selectedLinkedAccount.productName }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="dal-selected__balance">
|
||||||
|
<span class="dal-selected__bal-label">Balance</span>
|
||||||
|
<span class="dal-selected__bal-amount"
|
||||||
|
>{{ selectedLinkedAccount.currencyCode }}
|
||||||
|
{{ selectedLinkedAccount.balance | number: '1.2-2' }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
mat-stroked-button
|
||||||
|
color="warn"
|
||||||
|
type="button"
|
||||||
|
class="dal-change-btn"
|
||||||
|
(click)="clearLinkedAccount()"
|
||||||
|
>
|
||||||
|
<mat-icon>swap_horiz</mat-icon> Change
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<!-- ── Step 1: Choose account type ── -->
|
||||||
|
@if (!dalSelectedType) {
|
||||||
|
<div class="dal-type-step">
|
||||||
|
<p class="dal-type-step__label">Select account type to link:</p>
|
||||||
|
<div class="dal-type-grid">
|
||||||
|
<button type="button" class="dal-type-btn dal-type-btn--savings" (click)="selectDalType('SAVINGS')">
|
||||||
|
<mat-icon>savings</mat-icon>
|
||||||
|
<span class="dal-type-btn__name">Savings Account</span>
|
||||||
|
<span class="dal-type-btn__count"
|
||||||
|
>{{ dalAllSavings.length }} account{{ dalAllSavings.length !== 1 ? 's' : '' }}</span
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="dal-type-btn dal-type-btn--current" (click)="selectDalType('CURRENT')">
|
||||||
|
<mat-icon>account_balance</mat-icon>
|
||||||
|
<span class="dal-type-btn__name">Current Account</span>
|
||||||
|
<span class="dal-type-btn__count"
|
||||||
|
>{{ dalAllCurrent.length }} account{{ dalAllCurrent.length !== 1 ? 's' : '' }}</span
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<!-- ── Step 2: Search & pick from accounts ── -->
|
||||||
|
<div class="dal-picker">
|
||||||
|
<div class="dal-picker__topbar">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
mat-icon-button
|
||||||
|
class="dal-back-btn"
|
||||||
|
matTooltip="Back"
|
||||||
|
(click)="dalSelectedType = null"
|
||||||
|
>
|
||||||
|
<mat-icon>arrow_back</mat-icon>
|
||||||
|
</button>
|
||||||
|
<span class="dal-picker__title">
|
||||||
|
<mat-icon>{{ dalSelectedType === 'SAVINGS' ? 'savings' : 'account_balance' }}</mat-icon>
|
||||||
|
{{ dalSelectedType === 'SAVINGS' ? 'Savings Accounts' : 'Current Accounts' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="dal-search-field">
|
||||||
|
<mat-label>Search by Account No. or Product</mat-label>
|
||||||
|
<mat-icon matPrefix>search</mat-icon>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[(ngModel)]="dalSearch"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
placeholder="Type to filter…"
|
||||||
|
/>
|
||||||
|
@if (dalSearch) {
|
||||||
|
<button matSuffix mat-icon-button type="button" (click)="dalSearch = ''">
|
||||||
|
<mat-icon>clear</mat-icon>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
@if (dalFilteredAccounts.length === 0) {
|
||||||
|
<div class="dal-empty">
|
||||||
|
<mat-icon>search_off</mat-icon>
|
||||||
|
<span>No {{ dalSelectedType === 'SAVINGS' ? 'Savings' : 'Current' }} accounts found.</span>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<div class="dal-list">
|
||||||
|
@for (account of dalFilteredAccounts; track account.id) {
|
||||||
|
<div class="dal-item" (click)="selectLinkedAccount(account)">
|
||||||
|
<div class="dal-item__details">
|
||||||
|
<span class="dal-item__accno">{{ account.accountNo }}</span>
|
||||||
|
<span class="dal-item__product">{{ account.productName }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="dal-item__right">
|
||||||
|
<span
|
||||||
|
class="dal-item__status"
|
||||||
|
[class.dal-item__status--active]="account.status === 'Active'"
|
||||||
|
>{{ account.status }}</span
|
||||||
|
>
|
||||||
|
<span class="dal-item__balance"
|
||||||
|
>{{ account.currencyCode }} {{ account.balance | number: '1.2-2' }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<mat-icon class="dal-item__arrow">chevron_right</mat-icon>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- ══ Status of Account (RBI) ═══════════════════════════════════════ -->
|
||||||
|
@if (isProductSelected) {
|
||||||
|
<div class="soa-card">
|
||||||
|
<div class="soa-card__header">
|
||||||
|
<div class="soa-card__header-left">
|
||||||
|
<div class="soa-card__icon">
|
||||||
|
<i class="fas fa-id-card"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="soa-card__title">Status of Account</h3>
|
||||||
|
<span class="soa-card__sub">As per RBI guidelines — select account holder type</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="soa-pill">RBI Compliant</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Auto-detected client info bar -->
|
||||||
|
@if (clientDob) {
|
||||||
|
<div class="soa-client-info-bar">
|
||||||
|
<div class="soa-client-info-bar__item">
|
||||||
|
<mat-icon>cake</mat-icon>
|
||||||
|
<span
|
||||||
|
>Date of Birth: <strong>{{ formatTemplateDate(clientDob) }}</strong></span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="soa-client-info-bar__item">
|
||||||
|
<mat-icon>person_pin</mat-icon>
|
||||||
|
<span
|
||||||
|
>Age: <strong>{{ clientAge }} years</strong></span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
@if (clientAge !== null && clientAge < 18) {
|
||||||
|
<span class="soa-auto-pill soa-auto-pill--minor">
|
||||||
|
<mat-icon>child_care</mat-icon> Auto-detected: Minor
|
||||||
|
</span>
|
||||||
|
} @else if (clientAge !== null && clientAge >= 60) {
|
||||||
|
<span class="soa-auto-pill soa-auto-pill--senior">
|
||||||
|
<mat-icon>elderly</mat-icon> Auto-detected: Senior Citizen
|
||||||
|
</span>
|
||||||
|
} @else {
|
||||||
|
<span class="soa-auto-pill soa-auto-pill--ok"><mat-icon>check_circle</mat-icon> Age Verified</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Status selector tiles -->
|
||||||
|
<div class="soa-tiles">
|
||||||
|
@for (opt of accountStatusOptions; track opt.value) {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="soa-tile"
|
||||||
|
[class.soa-tile--selected]="selectedAccountStatus === opt.value"
|
||||||
|
[class.soa-tile--disabled]="isTileDisabled(opt.value)"
|
||||||
|
[disabled]="isTileDisabled(opt.value)"
|
||||||
|
(click)="selectAccountStatus(opt.value)"
|
||||||
|
[title]="getTileTooltip(opt.value)"
|
||||||
|
>
|
||||||
|
<mat-icon class="soa-tile__icon" [style.color]="isTileDisabled(opt.value) ? '#cbd5e1' : opt.color">
|
||||||
|
{{ opt.icon }}
|
||||||
|
</mat-icon>
|
||||||
|
<span class="soa-tile__label">{{ opt.label }}</span>
|
||||||
|
@if (isTileDisabled(opt.value)) {
|
||||||
|
<span class="soa-tile__lock"><mat-icon>lock</mat-icon></span>
|
||||||
|
}
|
||||||
|
@if (selectedAccountStatus === opt.value) {
|
||||||
|
<mat-icon class="soa-tile__check">check_circle</mat-icon>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ── JOINT ACCOUNT ─────────────────────────────────────────── -->
|
||||||
|
@if (selectedAccountStatus === 'JOINT') {
|
||||||
|
<div class="soa-block">
|
||||||
|
<div class="soa-block__head">
|
||||||
|
<div class="soa-block__head-left">
|
||||||
|
<span class="soa-block__letter" style="background: #8b5cf6">A</span>
|
||||||
|
<div>
|
||||||
|
<span class="soa-block__title">Joint Account Holders</span>
|
||||||
|
<span class="soa-block__hint">Add all co-applicants linked to this joint FD account</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
mat-flat-button
|
||||||
|
color="primary"
|
||||||
|
type="button"
|
||||||
|
class="soa-add-btn"
|
||||||
|
(click)="openAddJointHolderDialog()"
|
||||||
|
>
|
||||||
|
<mat-icon>person_add</mat-icon> Add Holder
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="soa-table-wrap">
|
||||||
|
<table class="soa-tbl">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="soa-th soa-th--sr">Sr.</th>
|
||||||
|
<th class="soa-th">Holder Name</th>
|
||||||
|
<th class="soa-th">CIF No.</th>
|
||||||
|
<th class="soa-th soa-th--center">Ownership %</th>
|
||||||
|
<th class="soa-th soa-th--center">Signing Authority</th>
|
||||||
|
<th class="soa-th soa-th--center">Role</th>
|
||||||
|
<th class="soa-th soa-th--center">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="soa-tr soa-tr--primary">
|
||||||
|
<td class="soa-td soa-td--sr">1</td>
|
||||||
|
<td class="soa-td">
|
||||||
|
<div class="soa-client-cell">
|
||||||
|
<div class="soa-avatar soa-avatar--primary">
|
||||||
|
{{ primaryClientName ? primaryClientName[0].toUpperCase() : '?' }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="soa-client-name">{{ primaryClientName || 'Primary Holder' }}</span>
|
||||||
|
<span class="soa-primary-tag">Primary Account Holder</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="soa-td">
|
||||||
|
<span class="soa-mono">{{ primaryClientCif || '—' }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="soa-td soa-td--center">
|
||||||
|
<input
|
||||||
|
class="soa-pct-input"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
[(ngModel)]="primaryHolderShare"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
<span class="soa-pct-unit">%</span>
|
||||||
|
</td>
|
||||||
|
<td class="soa-td soa-td--center">
|
||||||
|
<span class="soa-sign-toggle soa-sign-toggle--mandatory">
|
||||||
|
<mat-icon>lock</mat-icon> Mandatory
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="soa-td soa-td--center">Primary Account Holder</td>
|
||||||
|
<td class="soa-td soa-td--center"><span class="soa-no-action">—</span></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
@for (h of jointHolders; track h; let i = $index) {
|
||||||
|
<tr class="soa-tr" [class.soa-tr--mandatory]="h.isMandatory">
|
||||||
|
<td class="soa-td soa-td--sr">{{ i + 2 }}</td>
|
||||||
|
<td class="soa-td">
|
||||||
|
<div class="soa-client-cell">
|
||||||
|
<div class="soa-avatar" [class.soa-avatar--mandatory]="h.isMandatory">
|
||||||
|
{{ h.clientName[0].toUpperCase() }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="soa-client-name">{{ h.clientName }}</span>
|
||||||
|
@if (h.isMandatory) {
|
||||||
|
<span class="soa-mandatory-tag">Mandatory Signatory</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="soa-td">
|
||||||
|
<span class="soa-mono">{{ h.clientCif || '—' }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="soa-td soa-td--center">
|
||||||
|
<span class="soa-pct-badge" [class.soa-pct-badge--full]="h.sharePercentage === 100">
|
||||||
|
{{ h.sharePercentage }}%
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="soa-td soa-td--center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="soa-sign-toggle"
|
||||||
|
[class.soa-sign-toggle--mandatory]="h.isMandatory"
|
||||||
|
[class.soa-sign-toggle--optional]="!h.isMandatory"
|
||||||
|
(click)="toggleJointHolderMandatory(h)"
|
||||||
|
>
|
||||||
|
<mat-icon>{{ h.isMandatory ? 'lock' : 'lock_open' }}</mat-icon>
|
||||||
|
{{ h.isMandatory ? 'Mandatory' : 'Optional' }}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="soa-td soa-td--center">Co-Holder</td>
|
||||||
|
<td class="soa-td soa-td--center">
|
||||||
|
<button mat-icon-button color="warn" type="button" (click)="removeJointHolder(i)">
|
||||||
|
<mat-icon>delete_outline</mat-icon>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr class="soa-tr-total">
|
||||||
|
<td colspan="3" class="soa-td soa-td--total-label">Total Ownership</td>
|
||||||
|
<td class="soa-td soa-td--center">
|
||||||
|
<span
|
||||||
|
class="soa-pct-badge"
|
||||||
|
[class.soa-pct-badge--ok]="totalJointSharePercentage === 100"
|
||||||
|
[class.soa-pct-badge--warn]="totalJointSharePercentage !== 100"
|
||||||
|
>
|
||||||
|
{{ totalJointSharePercentage }}%
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="soa-td soa-td--center">
|
||||||
|
<span class="soa-mandatory-count">
|
||||||
|
<mat-icon>lock</mat-icon> {{ mandatoryJointCount + 1 }} Mandatory
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (jointHolders.length === 0) {
|
||||||
|
<div class="soa-empty" style="margin-top: 8px">
|
||||||
|
<mat-icon>group_add</mat-icon>
|
||||||
|
<span>Click <strong>Add Holder</strong> to add joint account holders.</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (totalJointSharePercentage !== 100) {
|
||||||
|
<div class="soa-alert soa-alert--warn" style="margin-top: 8px">
|
||||||
|
<mat-icon>warning_amber</mat-icon>
|
||||||
|
Total ownership is {{ totalJointSharePercentage }}%. It must equal 100%.
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<div class="soa-alert soa-alert--ok" style="margin-top: 8px">
|
||||||
|
<mat-icon>check_circle</mat-icon>
|
||||||
|
Ownership allocation is complete (100%).
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="soa-block soa-block--sep">
|
||||||
|
<div class="soa-block__head">
|
||||||
|
<div class="soa-block__head-left">
|
||||||
|
<span class="soa-block__letter" style="background: #7c3aed">B</span>
|
||||||
|
<div>
|
||||||
|
<span class="soa-block__title">Mode of Operation</span>
|
||||||
|
<span class="soa-block__hint">Auto-set from mandatory holders — override manually if required</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (modeOfOperation) {
|
||||||
|
<span class="soa-auto-badge"><mat-icon>auto_awesome</mat-icon> Auto</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (jointHolders.length === 0) {
|
||||||
|
<div class="soa-empty">
|
||||||
|
<mat-icon>how_to_vote</mat-icon>
|
||||||
|
<span>Add joint holders in Section A first.</span>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<div class="soa-mode-grid">
|
||||||
|
@for (opt of modeOfOperationOptions; track opt.value) {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="soa-mode-card"
|
||||||
|
[class.soa-mode-card--selected]="modeOfOperation === opt.value"
|
||||||
|
(click)="modeOfOperation = opt.value"
|
||||||
|
>
|
||||||
|
<mat-icon class="soa-mode-card__icon">{{ opt.icon }}</mat-icon>
|
||||||
|
<div class="soa-mode-card__code">{{ opt.label }}</div>
|
||||||
|
<div class="soa-mode-card__desc">{{ opt.sublabel }}</div>
|
||||||
|
@if (modeOfOperation === opt.value) {
|
||||||
|
<mat-icon class="soa-mode-card__check">check_circle</mat-icon>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="soa-mode-summary" [class.soa-mode-summary--set]="modeOfOperation">
|
||||||
|
@if (modeOfOperation) {
|
||||||
|
<mat-icon>how_to_vote</mat-icon>
|
||||||
|
<span
|
||||||
|
>Mode of Operation: <strong>{{ selectedModeLabel }}</strong></span
|
||||||
|
>
|
||||||
|
<span class="soa-mode-summary__from">
|
||||||
|
({{ mandatoryJointCount }} Mandatory + {{ jointHolders.length - mandatoryJointCount }} Optional)
|
||||||
|
</span>
|
||||||
|
} @else {
|
||||||
|
<mat-icon>info</mat-icon>
|
||||||
|
<span>Set Mandatory/Optional for holders above to auto-select mode.</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- ── MINOR ACCOUNT ─────────────────────────────────────────── -->
|
||||||
|
@if (selectedAccountStatus === 'MINOR') {
|
||||||
|
<div class="soa-block">
|
||||||
|
<div class="soa-block__head">
|
||||||
|
<div class="soa-block__head-left">
|
||||||
|
<span class="soa-block__letter" style="background: #f59e0b">
|
||||||
|
<mat-icon style="font-size: 14px; height: 14px; width: 14px">child_care</mat-icon>
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<span class="soa-block__title">Minor Account Details</span>
|
||||||
|
<span class="soa-block__hint">As per RBI — guardian must be an existing registered client</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="soa-pill soa-pill--minor">Guardian Required</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="soa-minor-grid">
|
||||||
|
<mat-form-field appearance="outline" class="soa-field" (click)="minorDobPicker.open()">
|
||||||
|
<mat-label>Minor's Date of Birth</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[matDatepicker]="minorDobPicker"
|
||||||
|
[max]="maxDate"
|
||||||
|
[(ngModel)]="minorDob"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
/>
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="minorDobPicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #minorDobPicker></mat-datepicker>
|
||||||
|
@if (clientDob) {
|
||||||
|
<mat-hint>Auto-filled from client profile</mat-hint>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="soa-field">
|
||||||
|
<mat-label>Age</mat-label>
|
||||||
|
<input matInput [value]="clientAge !== null ? clientAge + ' years' : '—'" readonly />
|
||||||
|
<mat-hint>Must be below 18 years</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="soa-field">
|
||||||
|
<mat-label>Date of Majority (Auto)</mat-label>
|
||||||
|
<input matInput [value]="majorityDate" readonly />
|
||||||
|
<mat-hint>Auto-calculated: DOB + 18 years</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="soa-field">
|
||||||
|
<mat-label>Guardian Relationship</mat-label>
|
||||||
|
<mat-select [(ngModel)]="guardianRelationship" [ngModelOptions]="{ standalone: true }">
|
||||||
|
@for (rel of guardianRelationshipOptions; track rel) {
|
||||||
|
<mat-option [value]="rel">{{ rel }}</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="soa-guardian-search">
|
||||||
|
<div class="soa-guardian-search__label">
|
||||||
|
<mat-icon>manage_accounts</mat-icon>
|
||||||
|
<span>Search & Link Guardian (existing client)</span>
|
||||||
|
</div>
|
||||||
|
<mifosx-introducer-search (introducerChange)="onGuardianChange($event)"></mifosx-introducer-search>
|
||||||
|
|
||||||
|
@if (guardianName) {
|
||||||
|
<div class="soa-guardian-selected">
|
||||||
|
<mat-icon class="soa-guardian-selected__icon">verified_user</mat-icon>
|
||||||
|
<div>
|
||||||
|
<div class="soa-guardian-selected__name">{{ guardianName }}</div>
|
||||||
|
<div class="soa-guardian-selected__cif">CIF: {{ guardianCif || 'N/A' }}</div>
|
||||||
|
</div>
|
||||||
|
<span class="soa-pill soa-pill--ok">Guardian Linked</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="soa-info-banner soa-info-banner--minor">
|
||||||
|
<mat-icon>info</mat-icon>
|
||||||
|
<ul>
|
||||||
|
<li>Minor below 10 years — Guardian must operate the account.</li>
|
||||||
|
<li>Minor above 10 years — Can operate independently (bank discretion).</li>
|
||||||
|
<li>Account must be converted to regular savings on attaining majority.</li>
|
||||||
|
<li>Fresh KYC of the minor must be done on majority.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- ── SENIOR CITIZEN ────────────────────────────────────────── -->
|
||||||
|
@if (selectedAccountStatus === 'SENIOR_CITIZEN') {
|
||||||
|
<div class="soa-block">
|
||||||
|
<div class="soa-block__head">
|
||||||
|
<div class="soa-block__head-left">
|
||||||
|
<span class="soa-block__letter" style="background: #10b981">
|
||||||
|
<mat-icon style="font-size: 14px; height: 14px; width: 14px">elderly</mat-icon>
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<span class="soa-block__title">Senior Citizen Account</span>
|
||||||
|
<span class="soa-block__hint">Age ≥ 60 years — eligible for preferential banking services</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="soa-pill soa-pill--senior">Age ≥ 60</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="soa-minor-grid" style="margin-bottom: 16px">
|
||||||
|
<mat-form-field appearance="outline" class="soa-field">
|
||||||
|
<mat-label>Date of Birth</mat-label>
|
||||||
|
<input matInput [value]="formatTemplateDate(clientDob)" readonly />
|
||||||
|
<mat-hint>From client profile</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="soa-field">
|
||||||
|
<mat-label>Age</mat-label>
|
||||||
|
<input matInput [value]="clientAge !== null ? clientAge + ' years' : '—'" readonly />
|
||||||
|
<mat-hint>Age ≥ 60 required for Senior Citizen</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="soa-sc-benefits">
|
||||||
|
<div class="soa-sc-benefit">
|
||||||
|
<mat-icon>priority_high</mat-icon>
|
||||||
|
<div>
|
||||||
|
<strong>Priority Service</strong>
|
||||||
|
<span>Dedicated counters / assisted banking at branches</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="soa-sc-benefit">
|
||||||
|
<mat-icon>percent</mat-icon>
|
||||||
|
<div>
|
||||||
|
<strong>Preferential Interest Rate</strong>
|
||||||
|
<span>Additional 0.25%–0.50% on deposits (as per product)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="soa-sc-benefit">
|
||||||
|
<mat-icon>health_and_safety</mat-icon>
|
||||||
|
<div>
|
||||||
|
<strong>Doorstep Banking</strong>
|
||||||
|
<span>Eligible for home banking services (RBI circular)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="soa-sc-benefit">
|
||||||
|
<mat-icon>gavel</mat-icon>
|
||||||
|
<div>
|
||||||
|
<strong>Nomination Mandatory</strong>
|
||||||
|
<span>RBI mandate — ensure nomination is registered</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="soa-info-banner soa-info-banner--senior">
|
||||||
|
<mat-icon>info</mat-icon>
|
||||||
|
<ul>
|
||||||
|
<li>Age proof (DOB document) mandatory at time of account opening.</li>
|
||||||
|
<li>Nomination registration is compulsory (Form DA-1).</li>
|
||||||
|
<li>Bank must offer dedicated helpline and priority grievance redressal.</li>
|
||||||
|
<li>Door-step banking services must be offered (RBI Master Direction 2023).</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- ── INDIVIDUAL MALE ───────────────────────────────────────── -->
|
||||||
|
@if (selectedAccountStatus === 'INDIVIDUAL_MALE') {
|
||||||
|
<div class="soa-block">
|
||||||
|
<div class="soa-block__head">
|
||||||
|
<div class="soa-block__head-left">
|
||||||
|
<span class="soa-block__letter" style="background: #3b82f6">
|
||||||
|
<mat-icon style="font-size: 14px; height: 14px; width: 14px">person</mat-icon>
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<span class="soa-block__title">Individual Account (Male)</span>
|
||||||
|
<span class="soa-block__hint"
|
||||||
|
>Select special category if applicable — governs applicable schemes & benefits</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="soa-pill" style="background: #dbeafe; color: #1d4ed8; border-color: #bfdbfe"
|
||||||
|
>Special Category</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="soa-category-grid">
|
||||||
|
@for (opt of specialCategoryOptions; track opt.value) {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="soa-cat-tile"
|
||||||
|
[class.soa-cat-tile--selected]="selectedSpecialCategory === opt.value"
|
||||||
|
[class.soa-cat-tile--male]="true"
|
||||||
|
(click)="selectSpecialCategory(opt.value)"
|
||||||
|
>
|
||||||
|
<mat-icon class="soa-cat-tile__icon">{{ opt.icon }}</mat-icon>
|
||||||
|
<span class="soa-cat-tile__label">{{ opt.label }}</span>
|
||||||
|
@if (selectedSpecialCategory === opt.value) {
|
||||||
|
<mat-icon class="soa-cat-tile__check">check_circle</mat-icon>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="soa-info-banner soa-info-banner--male">
|
||||||
|
<mat-icon>info</mat-icon>
|
||||||
|
<ul>
|
||||||
|
<li>Valid KYC documents required (Aadhaar / PAN / Passport).</li>
|
||||||
|
<li>Nomination registration is mandatory (Form DA-1).</li>
|
||||||
|
<li>Defence / Ex-Serviceman — eligible for special savings schemes (DSSP).</li>
|
||||||
|
<li>NRI — FCNR / NRE / NRO account rules apply as per FEMA guidelines.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- ── INDIVIDUAL FEMALE ──────────────────────────────────────── -->
|
||||||
|
@if (selectedAccountStatus === 'INDIVIDUAL_FEMALE') {
|
||||||
|
<div class="soa-block">
|
||||||
|
<div class="soa-block__head">
|
||||||
|
<div class="soa-block__head-left">
|
||||||
|
<span class="soa-block__letter" style="background: #ec4899">
|
||||||
|
<mat-icon style="font-size: 14px; height: 14px; width: 14px">person_outline</mat-icon>
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<span class="soa-block__title">Individual Account (Female)</span>
|
||||||
|
<span class="soa-block__hint"
|
||||||
|
>Select weaker section category if applicable — governs PMJDY & priority schemes</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="soa-pill" style="background: #fce7f3; color: #be185d; border-color: #f9a8d4"
|
||||||
|
>Weaker Section</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="soa-category-grid">
|
||||||
|
@for (opt of weakerSectionOptions; track opt.value) {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="soa-cat-tile"
|
||||||
|
[class.soa-cat-tile--selected]="selectedWeakerSection === opt.value"
|
||||||
|
[class.soa-cat-tile--female]="true"
|
||||||
|
(click)="selectWeakerSection(opt.value)"
|
||||||
|
>
|
||||||
|
<mat-icon class="soa-cat-tile__icon">{{ opt.icon }}</mat-icon>
|
||||||
|
<span class="soa-cat-tile__label">{{ opt.label }}</span>
|
||||||
|
@if (selectedWeakerSection === opt.value) {
|
||||||
|
<mat-icon class="soa-cat-tile__check">check_circle</mat-icon>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="soa-info-banner soa-info-banner--female">
|
||||||
|
<mat-icon>info</mat-icon>
|
||||||
|
<ul>
|
||||||
|
<li>Valid KYC documents required (Aadhaar / PAN / Passport).</li>
|
||||||
|
<li>Nomination registration is mandatory (Form DA-1).</li>
|
||||||
|
<li>Eligible for PMJDY overdraft facilities (subject to rules).</li>
|
||||||
|
<li>Financial literacy & priority processing for women applicants.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- ── SINGLE ACCOUNT ────────────────────────────────────────── -->
|
||||||
|
@if (selectedAccountStatus === 'SINGLE') {
|
||||||
|
<div class="soa-block">
|
||||||
|
<div class="soa-block__head">
|
||||||
|
<div class="soa-block__head-left">
|
||||||
|
<span class="soa-block__letter" style="background: #0891b2">
|
||||||
|
<mat-icon style="font-size: 14px; height: 14px; width: 14px">account_circle</mat-icon>
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<span class="soa-block__title">Single Account</span>
|
||||||
|
<span class="soa-block__hint">Standard individual account operated by a single person</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="soa-pill" style="background: #ecfeff; color: #0e7490; border-color: #cffafe"
|
||||||
|
>Single Holder</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="soa-info-banner soa-info-banner--single">
|
||||||
|
<mat-icon>info</mat-icon>
|
||||||
|
<ul>
|
||||||
|
<li>Only one primary holder allowed.</li>
|
||||||
|
<li>Nomination is highly recommended.</li>
|
||||||
|
<li>Full independent operation of account.</li>
|
||||||
|
<li>Convertible to joint account later if required.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
||||||
<button mat-raised-button matStepperPrevious disabled>
|
<button mat-raised-button matStepperPrevious disabled>
|
||||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||||
|
|||||||
+1547
-2
File diff suppressed because it is too large
Load Diff
+479
-24
@@ -7,16 +7,21 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/** Angular Imports */
|
/** Angular Imports */
|
||||||
import { Component, OnInit, Input, Output, EventEmitter, inject } from '@angular/core';
|
import { Component, OnInit, Input, Output, EventEmitter, inject, ChangeDetectorRef } from '@angular/core';
|
||||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
import { UntypedFormGroup, UntypedFormBuilder, Validators, FormsModule } from '@angular/forms';
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
|
import { Dates } from 'app/core/utils/dates';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
|
||||||
/** Custom Services */
|
/** Custom Services */
|
||||||
import { FixedDepositsService } from '../../fixed-deposits.service';
|
import { FixedDepositsService } from '../../fixed-deposits.service';
|
||||||
|
import { ClientsService } from 'app/clients/clients.service';
|
||||||
import { MatTooltip } from '@angular/material/tooltip';
|
import { MatTooltip } from '@angular/material/tooltip';
|
||||||
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
|
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
import { MatIcon } from '@angular/material/icon';
|
||||||
|
import { AddRelatedClientDialogComponent } from 'app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fixed Deposits Account Details Step
|
* Fixed Deposits Account Details Step
|
||||||
@@ -30,13 +35,19 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
MatTooltip,
|
MatTooltip,
|
||||||
MatStepperPrevious,
|
MatStepperPrevious,
|
||||||
FaIconComponent,
|
FaIconComponent,
|
||||||
MatStepperNext
|
MatStepperNext,
|
||||||
|
FormsModule,
|
||||||
|
MatIcon
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
private fixedDepositsService = inject(FixedDepositsService);
|
private fixedDepositsService = inject(FixedDepositsService);
|
||||||
private settingsService = inject(SettingsService);
|
private settingsService = inject(SettingsService);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
|
private cdr = inject(ChangeDetectorRef);
|
||||||
|
private clientsService = inject(ClientsService);
|
||||||
|
private dialog = inject(MatDialog);
|
||||||
|
|
||||||
/** Fixed Deposits Account Template */
|
/** Fixed Deposits Account Template */
|
||||||
@Input() fixedDepositsAccountTemplate: any;
|
@Input() fixedDepositsAccountTemplate: any;
|
||||||
@@ -49,22 +60,194 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
|||||||
productData: any;
|
productData: any;
|
||||||
/** Field Officer Data */
|
/** Field Officer Data */
|
||||||
fieldOfficerData: any;
|
fieldOfficerData: any;
|
||||||
/** For edit savings form */
|
/** For edit fixed deposits form */
|
||||||
isFieldOfficerPatched = false;
|
isFieldOfficerPatched = false;
|
||||||
/** Fixed Deposits Account Details Form */
|
/** Fixed Deposits Account Details Form */
|
||||||
fixedDepositAccountDetailsForm: UntypedFormGroup;
|
fixedDepositAccountDetailsForm!: UntypedFormGroup;
|
||||||
|
|
||||||
isProductSelected = false;
|
isProductSelected = false;
|
||||||
|
|
||||||
/** Fixed Deposits Account Template with product data */
|
/** Fixed Deposits Account Template with product data */
|
||||||
@Output() fixedDepositsAccountProductTemplate = new EventEmitter();
|
@Output() fixedDepositsAccountProductTemplate = new EventEmitter();
|
||||||
|
|
||||||
/**
|
/** Local copy of product template for displaying classification info */
|
||||||
* Sets fixed deposits account details form.
|
productTemplate: any = null;
|
||||||
* @param {FormBuilder} formBuilder Form Builder.
|
accountTypeOptions: string[] = [];
|
||||||
* @param {FixedDepositsService} fixedDepositsService Fixed Deposits Service.
|
|
||||||
* @param {SettingsService} settingsService Settings Service
|
readonly fdTypeLabels: Record<string, string> = {
|
||||||
*/
|
SINGLE: 'FD Single (Days < 1 Year)',
|
||||||
|
DOUBLE: 'FD Double (≥ 1 Year)',
|
||||||
|
SPECIAL: 'FD Special (Monthly Interest Payout)'
|
||||||
|
};
|
||||||
|
|
||||||
|
getFdTypeLabel(fdType: string): string {
|
||||||
|
return this.fdTypeLabels[fdType] || fdType;
|
||||||
|
}
|
||||||
|
customerSegmentOptions: string[] = [];
|
||||||
|
selectedAccountType: string | null = null;
|
||||||
|
selectedCustomerSegment: string | null = null;
|
||||||
|
|
||||||
|
// ── Client info (auto-fetched) ─────────────────────────────────────────
|
||||||
|
clientDob: Date | null = null;
|
||||||
|
clientAge: number | null = null;
|
||||||
|
clientGender: string | null = null;
|
||||||
|
primaryClientName = '';
|
||||||
|
primaryClientCif = '';
|
||||||
|
primaryClientId: number | null = null;
|
||||||
|
primaryHolderShare = 0;
|
||||||
|
|
||||||
|
// ── Status of Account (RBI) ────────────────────────────────────────────
|
||||||
|
readonly accountStatusOptions = [
|
||||||
|
{ value: 'INDIVIDUAL_MALE', label: 'Individual (Male)', icon: 'person', color: '#3b82f6' },
|
||||||
|
{ value: 'INDIVIDUAL_FEMALE', label: 'Individual (Female)', icon: 'person_outline', color: '#ec4899' },
|
||||||
|
{ value: 'SINGLE', label: 'Single Account', icon: 'account_circle', color: '#0891b2' },
|
||||||
|
{ value: 'JOINT', label: 'Joint Account', icon: 'group', color: '#8b5cf6' },
|
||||||
|
{ value: 'MINOR', label: 'Minor Account', icon: 'child_care', color: '#f59e0b' },
|
||||||
|
{ value: 'SENIOR_CITIZEN', label: 'Senior Citizen', icon: 'elderly', color: '#10b981' }
|
||||||
|
];
|
||||||
|
|
||||||
|
selectedAccountStatus = '';
|
||||||
|
|
||||||
|
// ── Joint Account ──────────────────────────────────────────────────────
|
||||||
|
jointHolders: Array<{
|
||||||
|
clientId: number;
|
||||||
|
clientName: string;
|
||||||
|
clientCif: string;
|
||||||
|
sharePercentage: number;
|
||||||
|
isMandatory: boolean;
|
||||||
|
signingAuthority?: string;
|
||||||
|
role?: string;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
modeOfOperation = '';
|
||||||
|
|
||||||
|
readonly modeOfOperationOptions = [
|
||||||
|
{ value: 'EITHER_OR_SURVIVOR', label: 'E or S', sublabel: 'Either or Survivor', icon: 'how_to_vote' },
|
||||||
|
{ value: 'FORMER_OR_SURVIVOR', label: 'F or S', sublabel: 'Former or Survivor', icon: 'person' },
|
||||||
|
{ value: 'JOINTLY', label: 'Jointly', sublabel: 'All Must Sign', icon: 'groups' },
|
||||||
|
{ value: 'ANYONE_OR_SURVIVOR', label: 'Anyone', sublabel: 'Anyone or Survivor', icon: 'group' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// ── Individual Male — Special Category ────────────────────────────────
|
||||||
|
selectedSpecialCategory = '';
|
||||||
|
|
||||||
|
readonly specialCategoryOptions = [
|
||||||
|
{ value: 'DEFENCE', label: 'Defence Personnel', icon: 'military_tech' },
|
||||||
|
{ value: 'EX_SERVICEMAN', label: 'Ex-Serviceman', icon: 'shield' },
|
||||||
|
{ value: 'GOVT_EMPLOYEE', label: 'Government Employee', icon: 'account_balance' },
|
||||||
|
{ value: 'PHYSICALLY_CHALLENGED', label: 'Physically Challenged', icon: 'accessible' },
|
||||||
|
{ value: 'NRI', label: 'NRI (Non-Resident Indian)', icon: 'flight' },
|
||||||
|
{ value: 'NONE', label: 'None / General', icon: 'person' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// ── Individual Female — Weaker Section ────────────────────────────────
|
||||||
|
selectedWeakerSection = '';
|
||||||
|
|
||||||
|
readonly weakerSectionOptions = [
|
||||||
|
{ value: 'BPL', label: 'BPL (Below Poverty Line)', icon: 'volunteer_activism' },
|
||||||
|
{ value: 'SC_ST', label: 'SC / ST Category', icon: 'groups' },
|
||||||
|
{ value: 'MINORITY', label: 'Minority Community', icon: 'diversity_3' },
|
||||||
|
{ value: 'SHG', label: 'Self Help Group Member', icon: 'handshake' },
|
||||||
|
{ value: 'PMJDY', label: 'PMJDY Beneficiary', icon: 'savings' },
|
||||||
|
{ value: 'NONE', label: 'None / General', icon: 'person_outline' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// ── Minor Account ──────────────────────────────────────────────────────
|
||||||
|
guardianClientId: number | null = null;
|
||||||
|
guardianName = '';
|
||||||
|
guardianCif = '';
|
||||||
|
guardianRelationship = '';
|
||||||
|
minorDob: Date | null = null;
|
||||||
|
|
||||||
|
readonly guardianRelationshipOptions = [
|
||||||
|
'Father',
|
||||||
|
'Mother',
|
||||||
|
'Legal Guardian',
|
||||||
|
'Court Appointed Guardian'
|
||||||
|
];
|
||||||
|
|
||||||
|
// ── Deposit Account Link ───────────────────────────────────────────────
|
||||||
|
dalSelectedType: 'SAVINGS' | 'CURRENT' | null = null;
|
||||||
|
dalAllSavings: any[] = [];
|
||||||
|
dalAllCurrent: any[] = [];
|
||||||
|
dalSearch = '';
|
||||||
|
dalLoading = false;
|
||||||
|
selectedLinkedAccount: any = null;
|
||||||
|
currencyFlags: any = null;
|
||||||
|
|
||||||
|
get currencyName(): string {
|
||||||
|
return this.productTemplate?.currency?.name || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
get decimalPlaces(): number {
|
||||||
|
return this.productTemplate?.currency?.decimalPlaces ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get inMultiplesOf(): number {
|
||||||
|
return this.productTemplate?.currency?.inMultiplesOf ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get dalAccounts(): any[] {
|
||||||
|
return this.dalSelectedType === 'SAVINGS' ? this.dalAllSavings : this.dalAllCurrent;
|
||||||
|
}
|
||||||
|
|
||||||
|
get dalFilteredAccounts(): any[] {
|
||||||
|
const q = this.dalSearch.toLowerCase().trim();
|
||||||
|
if (!q) return this.dalAccounts;
|
||||||
|
return this.dalAccounts.filter(
|
||||||
|
(a) => a.accountNo.toLowerCase().includes(q) || a.productName.toLowerCase().includes(q)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectDalType(type: 'SAVINGS' | 'CURRENT'): void {
|
||||||
|
this.dalSelectedType = type;
|
||||||
|
this.dalSearch = '';
|
||||||
|
this.selectedLinkedAccount = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectLinkedAccount(account: any): void {
|
||||||
|
this.selectedLinkedAccount = account;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLinkedAccount(): void {
|
||||||
|
this.selectedLinkedAccount = null;
|
||||||
|
this.dalSelectedType = null;
|
||||||
|
this.dalSearch = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadClientAccounts(): void {
|
||||||
|
const clientId = String(this.fixedDepositsAccountTemplate?.clientId);
|
||||||
|
if (!clientId) return;
|
||||||
|
this.dalLoading = true;
|
||||||
|
this.clientsService.getClientAccountData(clientId).subscribe({
|
||||||
|
next: (data: any) => {
|
||||||
|
this.dalAllSavings = (data?.savingsAccounts ?? []).map((a: any) => ({
|
||||||
|
id: a.id,
|
||||||
|
accountNo: a.accountNo,
|
||||||
|
productName: a.productName ?? '',
|
||||||
|
accountType: 'SAVINGS' as const,
|
||||||
|
status: a.status?.value ?? '',
|
||||||
|
balance: a.accountBalance ?? 0,
|
||||||
|
currencyCode: a.currency?.code ?? ''
|
||||||
|
}));
|
||||||
|
this.dalAllCurrent = (data?.currentAccounts ?? []).map((a: any) => ({
|
||||||
|
id: a.id,
|
||||||
|
accountNo: a.accountNo,
|
||||||
|
productName: a.productName ?? '',
|
||||||
|
accountType: 'CURRENT' as const,
|
||||||
|
status: a.status?.value ?? '',
|
||||||
|
balance: a.accountBalance ?? 0,
|
||||||
|
currencyCode: a.currency?.code ?? ''
|
||||||
|
}));
|
||||||
|
this.dalLoading = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.dalLoading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.createFixedDepositsAccountDetailsForm();
|
this.createFixedDepositsAccountDetailsForm();
|
||||||
}
|
}
|
||||||
@@ -78,12 +261,109 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
|||||||
this.fixedDepositAccountDetailsForm.patchValue({
|
this.fixedDepositAccountDetailsForm.patchValue({
|
||||||
productId: this.fixedDepositsAccountTemplate.depositProductId,
|
productId: this.fixedDepositsAccountTemplate.depositProductId,
|
||||||
submittedOnDate:
|
submittedOnDate:
|
||||||
this.fixedDepositsAccountTemplate.timeline.submittedOnDate &&
|
this.fixedDepositsAccountTemplate.timeline?.submittedOnDate &&
|
||||||
new Date(this.fixedDepositsAccountTemplate.timeline.submittedOnDate),
|
new Date(this.fixedDepositsAccountTemplate.timeline.submittedOnDate),
|
||||||
externalId: this.fixedDepositsAccountTemplate.externalId || ''
|
externalId: this.fixedDepositsAccountTemplate.externalId
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.fixedDepositAccountDetailsForm.patchValue({
|
||||||
|
submittedOnDate: this.settingsService.businessDate
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-detect account status from client DOB + capture primary holder info
|
||||||
|
const clientId = this.fixedDepositsAccountTemplate?.clientId || this.fixedDepositsAccountTemplate?.id;
|
||||||
|
if (clientId) {
|
||||||
|
this.primaryClientId = clientId;
|
||||||
|
this.clientsService.getClientData(String(clientId)).subscribe((client: any) => {
|
||||||
|
this.primaryClientName = client?.displayName ?? client?.fullname ?? '';
|
||||||
|
this.primaryClientCif = client?.cifNumber ?? client?.cifNo ?? '';
|
||||||
|
if (client?.dateOfBirth) {
|
||||||
|
const dob = this.parseClientDob(client.dateOfBirth);
|
||||||
|
if (dob) {
|
||||||
|
this.clientDob = dob;
|
||||||
|
this.clientAge = this.calculateAge(dob);
|
||||||
|
this.clientGender = client.gender?.name?.toLowerCase() ?? null;
|
||||||
|
this.autoDetectAccountStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parse DOB from backend — handles array [yyyy, mm, dd] or string */
|
||||||
|
private parseClientDob(dob: any): Date | null {
|
||||||
|
if (!dob) return null;
|
||||||
|
if (Array.isArray(dob) && dob.length >= 3) {
|
||||||
|
return new Date(dob[0], dob[1] - 1, dob[2]);
|
||||||
|
}
|
||||||
|
const d = new Date(dob);
|
||||||
|
return isNaN(d.getTime()) ? null : d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calculate full years from a date to today */
|
||||||
|
private calculateAge(dob: Date): number {
|
||||||
|
const today = this.settingsService.businessDate ?? new Date();
|
||||||
|
let age = today.getFullYear() - dob.getFullYear();
|
||||||
|
const monthDiff = today.getMonth() - dob.getMonth();
|
||||||
|
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < dob.getDate())) {
|
||||||
|
age--;
|
||||||
|
}
|
||||||
|
return age;
|
||||||
|
}
|
||||||
|
|
||||||
|
isTileDisabled(value: string): boolean {
|
||||||
|
if (value === 'MINOR') return this.clientAge === null || this.clientAge >= 18;
|
||||||
|
if (value === 'SENIOR_CITIZEN') return this.clientAge === null || this.clientAge < 60;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Handle Account Status selection */
|
||||||
|
selectAccountStatus(status: string): void {
|
||||||
|
if (!this.isTileDisabled(status)) {
|
||||||
|
this.selectedAccountStatus = status;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Handle Special Category selection */
|
||||||
|
selectSpecialCategory(category: string): void {
|
||||||
|
this.selectedSpecialCategory = category;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Handle Weaker Section selection */
|
||||||
|
selectWeakerSection(section: string): void {
|
||||||
|
this.selectedWeakerSection = section;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
getTileTooltip(value: string): string {
|
||||||
|
if (value === 'MINOR' && this.clientAge !== null && this.clientAge >= 18) {
|
||||||
|
return `Age ${this.clientAge} yrs — Minor only allowed below 18 years`;
|
||||||
|
}
|
||||||
|
if (value === 'SENIOR_CITIZEN' && this.clientAge !== null && this.clientAge < 60) {
|
||||||
|
return `Age ${this.clientAge} yrs — Senior Citizen only allowed at 60+ years`;
|
||||||
|
}
|
||||||
|
if (value === 'MINOR' && this.clientAge === null) return 'DOB not available — cannot verify age';
|
||||||
|
if (value === 'SENIOR_CITIZEN' && this.clientAge === null) return 'DOB not available — cannot verify age';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private autoDetectAccountStatus(): void {
|
||||||
|
if (this.clientAge === null) return;
|
||||||
|
if (this.clientAge < 18) {
|
||||||
|
this.selectedAccountStatus = 'MINOR';
|
||||||
|
this.minorDob = this.clientDob;
|
||||||
|
} else if (this.clientAge >= 60) {
|
||||||
|
this.selectedAccountStatus = 'SENIOR_CITIZEN';
|
||||||
|
} else if (this.clientGender === 'female') {
|
||||||
|
this.selectedAccountStatus = 'INDIVIDUAL_FEMALE';
|
||||||
|
} else {
|
||||||
|
this.selectedAccountStatus = 'INDIVIDUAL_MALE';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -108,34 +388,209 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
|||||||
* Fetches fixed deposits account product template on productId value changes
|
* Fetches fixed deposits account product template on productId value changes
|
||||||
*/
|
*/
|
||||||
buildDependencies() {
|
buildDependencies() {
|
||||||
const clientId = this.fixedDepositsAccountTemplate.clientId;
|
if (!this.fixedDepositsAccountTemplate) return;
|
||||||
this.fixedDepositAccountDetailsForm.get('productId').valueChanges.subscribe((productId: string) => {
|
const clientId = this.fixedDepositsAccountTemplate.clientId || this.fixedDepositsAccountTemplate.id;
|
||||||
|
this.fixedDepositAccountDetailsForm.get('productId')!.valueChanges.subscribe((productId: string) => {
|
||||||
this.fixedDepositsService.getFixedDepositsAccountTemplate(clientId, productId).subscribe((response: any) => {
|
this.fixedDepositsService.getFixedDepositsAccountTemplate(clientId, productId).subscribe((response: any) => {
|
||||||
|
if (!response.currency) {
|
||||||
|
const selectedProduct = this.fixedDepositsAccountTemplate.productOptions?.find(
|
||||||
|
(product: any) => product.id == productId
|
||||||
|
);
|
||||||
|
if (selectedProduct?.currency) {
|
||||||
|
response.currency = selectedProduct.currency;
|
||||||
|
} else if (this.fixedDepositsAccountTemplate.currency) {
|
||||||
|
response.currency = this.fixedDepositsAccountTemplate.currency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.productTemplate = response;
|
||||||
|
this.accountTypeOptions = this.extractOptions(response.accountTypeOptions, response.accountType);
|
||||||
|
this.customerSegmentOptions = this.extractOptions(response.customerSegmentOptions, response.customerSegment);
|
||||||
|
this.selectedAccountType = response.accountType ?? null;
|
||||||
|
this.selectedCustomerSegment = response.customerSegment ?? null;
|
||||||
this.fixedDepositsAccountProductTemplate.emit(response);
|
this.fixedDepositsAccountProductTemplate.emit(response);
|
||||||
this.isProductSelected = true;
|
|
||||||
this.fieldOfficerData = response.fieldOfficerOptions;
|
this.fieldOfficerData = response.fieldOfficerOptions;
|
||||||
|
this.isProductSelected = true;
|
||||||
|
if (response.shareLinkingEnabled) {
|
||||||
|
this.loadClientAccounts();
|
||||||
|
}
|
||||||
|
this.cdr.detectChanges();
|
||||||
if (!this.isFieldOfficerPatched && this.fixedDepositsAccountTemplate.fieldOfficerId) {
|
if (!this.isFieldOfficerPatched && this.fixedDepositsAccountTemplate.fieldOfficerId) {
|
||||||
this.fixedDepositAccountDetailsForm
|
this.fixedDepositAccountDetailsForm
|
||||||
.get('fieldOfficerId')
|
.get('fieldOfficerId')!
|
||||||
.patchValue(this.fixedDepositsAccountTemplate.fieldOfficerId);
|
.patchValue(this.fixedDepositsAccountTemplate.fieldOfficerId);
|
||||||
this.isFieldOfficerPatched = true;
|
this.isFieldOfficerPatched = true;
|
||||||
} else {
|
} else {
|
||||||
this.fixedDepositAccountDetailsForm.get('fieldOfficerId').patchValue('');
|
this.fixedDepositAccountDetailsForm.get('fieldOfficerId')!.patchValue('');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasValue(val: any): boolean {
|
||||||
|
return val !== null && val !== undefined && val !== '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractOptions(options: any[], fallback?: string): string[] {
|
||||||
|
if (Array.isArray(options) && options.length > 0) {
|
||||||
|
return options.map((o: any) => o.value ?? o.name ?? o);
|
||||||
|
}
|
||||||
|
return fallback ? [fallback] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
formatTemplateDate(value: any): string {
|
||||||
|
if (!value) return '';
|
||||||
|
let dateValue: Date | null = null;
|
||||||
|
if (value instanceof Date) {
|
||||||
|
dateValue = value;
|
||||||
|
} else if (Array.isArray(value) && value.length >= 3) {
|
||||||
|
dateValue = new Date(value[0], value[1] - 1, value[2]);
|
||||||
|
} else {
|
||||||
|
dateValue = new Date(value);
|
||||||
|
}
|
||||||
|
if (!dateValue || Number.isNaN(dateValue.getTime())) {
|
||||||
|
return Array.isArray(value) ? value.join('/') : String(value);
|
||||||
|
}
|
||||||
|
return this.dateUtils.formatDate(dateValue, 'M/d/yyyy') || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
onGuardianChange(event: { clientId: number; displayName: string; cifNumber?: string } | null): void {
|
||||||
|
this.guardianClientId = event?.clientId ?? null;
|
||||||
|
this.guardianName = event?.displayName ?? '';
|
||||||
|
this.guardianCif = event?.cifNumber ?? '';
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Joint Account helpers ──────────────────────────────────────────────
|
||||||
|
openAddJointHolderDialog(): void {
|
||||||
|
const ref = this.dialog.open(AddRelatedClientDialogComponent, {
|
||||||
|
width: '500px',
|
||||||
|
panelClass: 'arc-dialog-panel',
|
||||||
|
disableClose: true
|
||||||
|
});
|
||||||
|
ref.afterClosed().subscribe((result) => {
|
||||||
|
if (result) {
|
||||||
|
const duplicate = this.jointHolders.some((h) => h.clientCif && h.clientCif === result.clientCif);
|
||||||
|
if (duplicate) {
|
||||||
|
alert(`Account holder with CIF ${result.clientCif} is already added.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.jointHolders = [
|
||||||
|
...this.jointHolders,
|
||||||
|
{ ...result, isMandatory: false }];
|
||||||
|
this.autoSetModeOfOperation();
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeJointHolder(index: number): void {
|
||||||
|
this.jointHolders = this.jointHolders.filter((_, i) => i !== index);
|
||||||
|
this.autoSetModeOfOperation();
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleJointHolderMandatory(holder: any): void {
|
||||||
|
holder.isMandatory = !holder.isMandatory;
|
||||||
|
this.autoSetModeOfOperation();
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
autoSetModeOfOperation(): void {
|
||||||
|
const mandatory = this.jointHolders.filter((h) => h.isMandatory).length;
|
||||||
|
if (mandatory === 0) {
|
||||||
|
this.modeOfOperation = 'EITHER_OR_SURVIVOR';
|
||||||
|
} else if (mandatory === 1) {
|
||||||
|
this.modeOfOperation = 'FORMER_OR_SURVIVOR';
|
||||||
|
} else {
|
||||||
|
this.modeOfOperation = 'JOINTLY';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get totalJointSharePercentage(): number {
|
||||||
|
return (this.primaryHolderShare || 0) + this.jointHolders.reduce((sum, h) => sum + (h.sharePercentage || 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get mandatoryJointCount(): number {
|
||||||
|
return this.jointHolders.filter((h) => h.isMandatory).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
get selectedModeLabel(): string {
|
||||||
|
return this.modeOfOperationOptions.find((o) => o.value === this.modeOfOperation)?.sublabel ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
get majorityDate(): string {
|
||||||
|
const dob = this.minorDob ?? this.clientDob;
|
||||||
|
if (!dob) return '';
|
||||||
|
const d = new Date(dob);
|
||||||
|
d.setFullYear(d.getFullYear() + 18);
|
||||||
|
return this.dateUtils.formatDate(d, 'M/d/yyyy') || '';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns fixed deposits account details form value.
|
* Returns fixed deposits account details form value.
|
||||||
*/
|
*/
|
||||||
get fixedDepositAccountDetails() {
|
get fixedDepositAccountDetails() {
|
||||||
const fixedDepositAccountDetails = this.fixedDepositAccountDetailsForm.value;
|
const value = this.fixedDepositAccountDetailsForm.value;
|
||||||
for (const key in fixedDepositAccountDetails) {
|
|
||||||
if (fixedDepositAccountDetails[key] === '') {
|
if (this.selectedAccountType) {
|
||||||
delete fixedDepositAccountDetails[key];
|
value.accountType = this.selectedAccountType;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return fixedDepositAccountDetails;
|
if (this.selectedCustomerSegment) {
|
||||||
|
value.customerSegment = this.selectedCustomerSegment;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedAccountStatus) {
|
||||||
|
value.accountStatus = this.selectedAccountStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedAccountStatus === 'JOINT') {
|
||||||
|
value.modeOfOperation = this.modeOfOperation || null;
|
||||||
|
value.jointHolders = [
|
||||||
|
{
|
||||||
|
clientId: this.primaryClientId,
|
||||||
|
displayName: this.primaryClientName,
|
||||||
|
cifNumber: this.primaryClientCif,
|
||||||
|
sharePercentage: this.primaryHolderShare,
|
||||||
|
isMandatory: true,
|
||||||
|
isPrimary: true,
|
||||||
|
signingAuthority: 'Mandatory',
|
||||||
|
role: 'Primary Account Holder'
|
||||||
|
},
|
||||||
|
...this.jointHolders.map((h) => ({
|
||||||
|
clientId: h.clientId,
|
||||||
|
displayName: h.clientName,
|
||||||
|
cifNumber: h.clientCif,
|
||||||
|
sharePercentage: h.sharePercentage,
|
||||||
|
isMandatory: h.isMandatory,
|
||||||
|
isPrimary: false,
|
||||||
|
signingAuthority: h.signingAuthority || (h.isMandatory ? 'Mandatory' : 'Optional'),
|
||||||
|
role: h.role || 'Co-Holder'
|
||||||
|
}))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedAccountStatus === 'INDIVIDUAL_MALE' && this.selectedSpecialCategory) {
|
||||||
|
value.specialCategory = this.selectedSpecialCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedAccountStatus === 'INDIVIDUAL_FEMALE' && this.selectedWeakerSection) {
|
||||||
|
value.weakerSection = this.selectedWeakerSection;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedAccountStatus === 'MINOR') {
|
||||||
|
value.guardianClientId = this.guardianClientId;
|
||||||
|
value.guardianName = this.guardianName;
|
||||||
|
value.guardianCifNumber = this.guardianCif;
|
||||||
|
value.guardianRelationship = this.guardianRelationship;
|
||||||
|
value.minorDateOfBirth = this.minorDob ? this.dateUtils.formatDate(this.minorDob, 'yyyy-MM-dd') : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedLinkedAccount) {
|
||||||
|
value.linkedAccountId = this.selectedLinkedAccount.id;
|
||||||
|
value.linkedAccountType = this.selectedLinkedAccount.accountType;
|
||||||
|
value.linkedAccountNo = this.selectedLinkedAccount.accountNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+412
-154
@@ -6,161 +6,419 @@
|
|||||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<div class="layout-column gap-2px">
|
<div class="fdrc-wrapper">
|
||||||
<div class="flex-fill" *ngIf="fixedDepositsAccountProductTemplate?.accountChart.name">
|
<!-- ══ Chart Header Card ════════════════════════════════════════════════ -->
|
||||||
<span class="flex-40">{{ 'labels.inputs.name' | translate }}:</span>
|
@if (fixedDepositsAccountProductTemplate?.accountChart) {
|
||||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate.accountChart.name }}</span>
|
<div class="fdrc-header-card">
|
||||||
</div>
|
<div class="fdrc-header-card__left">
|
||||||
|
<div class="fdrc-header-card__icon">
|
||||||
<div class="flex-fill">
|
<i class="fas fa-chart-line"></i>
|
||||||
<span class="flex-40">{{ 'labels.inputs.Valid from Date' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate?.accountChart.fromDate | dateFormat }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-fill" *ngIf="fixedDepositsAccountProductTemplate?.accountChart.endDate">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.End Date' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate?.accountChart.endDate | dateFormat }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-fill" *ngIf="fixedDepositsAccountProductTemplate?.accountChart.description">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Description' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate?.accountChart.description }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-fill">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Primary Grouping by Amount' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
fixedDepositsAccountProductTemplate?.accountChart.isPrimaryGroupingByAmount ? 'Yes' : 'No'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table class="mat-elevation-z1" mat-table [dataSource]="interestRateChartData" #chartsTable multiTemplateDataRows>
|
|
||||||
<ng-container matColumnDef="period">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Period' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let chartSlab">
|
|
||||||
{{ chartSlab.fromPeriod }}-{{ chartSlab.toPeriod }} {{ chartSlab.periodType.value }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="amountRange">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount Range' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.amountRangeFrom }}-{{ chartSlab.amountRangeTo }}</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="interest">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.annualInterestRate }}</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="description">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Description' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.description }}</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="actions">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let chartSlab; let chartSlabIndex = dataIndex">
|
|
||||||
<button
|
|
||||||
mat-button
|
|
||||||
color="primary"
|
|
||||||
(click)="expandChartSlabIndex = expandChartSlabIndex === chartSlabIndex ? null : chartSlabIndex"
|
|
||||||
>
|
|
||||||
<span *ngIf="expandChartSlabIndex !== chartSlabIndex">
|
|
||||||
<fa-icon icon="eye" class="m-r-10"></fa-icon>
|
|
||||||
{{ 'labels.buttons.View Incentives' | translate }}
|
|
||||||
</span>
|
|
||||||
<span *ngIf="expandChartSlabIndex === chartSlabIndex">
|
|
||||||
<fa-icon icon="eye-slash" class="m-r-10"></fa-icon>
|
|
||||||
{{ 'labels.buttons.Hide Incentives' | translate }}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="incentives">
|
|
||||||
<td
|
|
||||||
mat-cell
|
|
||||||
*matCellDef="let chartSlab; let chartSlabIndex = dataIndex"
|
|
||||||
[attr.colspan]="chartSlabsDisplayedColumns.length"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="incentives layout-row-wrap flex-fill"
|
|
||||||
[@expandChartSlab]="chartSlabIndex === expandChartSlabIndex ? 'expanded' : 'collapsed'"
|
|
||||||
>
|
|
||||||
<mat-card class="layout-row-wrap flex-fill">
|
|
||||||
<h4 class="m-b-10 flex-13">
|
|
||||||
{{ 'labels.heading.Incentives' | translate }}
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<table
|
|
||||||
class="flex-fill mat-elevation-z1"
|
|
||||||
mat-table
|
|
||||||
[dataSource]="chartSlab.incentives"
|
|
||||||
*ngIf="chartSlab.incentives.length"
|
|
||||||
>
|
|
||||||
<ng-container matColumnDef="entityType">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Entity Type' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive">
|
|
||||||
{{ incentive.entityType.value }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="attributeName">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Name' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive">
|
|
||||||
{{ incentive.attributeName.value }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="conditionType">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Condition Type' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive">
|
|
||||||
{{ incentive.conditionType.value | titlecase }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="attributeValue">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Value' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive" [ngSwitch]="incentive.attributeName">
|
|
||||||
{{ incentive.attributeValueDesc }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="incentiveType">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Incentive Type' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive">
|
|
||||||
{{ incentive.incentiveType.value }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="amount">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive">
|
|
||||||
{{ incentive.amount }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<tr mat-header-row *matHeaderRowDef="incentivesDisplayedColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: incentivesDisplayedColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
<div>
|
||||||
</ng-container>
|
<div class="fdrc-header-card__title">{{ chartName || 'Interest Rate Chart' }}</div>
|
||||||
|
@if (chartDescription) {
|
||||||
|
<div class="fdrc-header-card__desc">{{ chartDescription }}</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="fdrc-header-card__meta">
|
||||||
|
@if (fdType) {
|
||||||
|
<span
|
||||||
|
class="fdrc-pill fdrc-pill--type"
|
||||||
|
[class.fdrc-pill--single]="fdType === 'SINGLE'"
|
||||||
|
[class.fdrc-pill--double]="fdType === 'DOUBLE'"
|
||||||
|
[class.fdrc-pill--special]="fdType === 'SPECIAL'"
|
||||||
|
>
|
||||||
|
<i class="fas fa-tag"></i>
|
||||||
|
{{ fdTypeLabel }}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
<div class="fdrc-header-card__dates">
|
||||||
|
<span class="fdrc-meta-item">
|
||||||
|
<i class="fas fa-calendar-check"></i>
|
||||||
|
From: <strong>{{ chartFromDate | dateFormat }}</strong>
|
||||||
|
</span>
|
||||||
|
@if (chartEndDate) {
|
||||||
|
<span class="fdrc-meta-item">
|
||||||
|
<i class="fas fa-calendar-times"></i>
|
||||||
|
To: <strong>{{ chartEndDate | dateFormat }}</strong>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@if (isPrimaryGroupingByAmount) {
|
||||||
|
<span class="fdrc-pill fdrc-pill--amount"> <i class="fas fa-layer-group"></i> Grouped by Amount </span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns"></tr>
|
<!-- ══ FD Type Rule Banner ══════════════════════════════════════════════ -->
|
||||||
<tr mat-row *matRowDef="let row; columns: chartSlabsDisplayedColumns"></tr>
|
@if (fdType) {
|
||||||
<tr mat-row *matRowDef="let row; columns: chartSlabsIncentivesDisplayedColumns" class="incentives-row"></tr>
|
<div
|
||||||
</table>
|
class="fdrc-rule-banner"
|
||||||
</div>
|
[class.fdrc-rule-banner--single]="fdType === 'SINGLE'"
|
||||||
|
[class.fdrc-rule-banner--double]="fdType === 'DOUBLE'"
|
||||||
|
[class.fdrc-rule-banner--special]="fdType === 'SPECIAL'"
|
||||||
|
>
|
||||||
|
<div class="fdrc-rule-banner__icon">
|
||||||
|
@if (fdType === 'SINGLE') {
|
||||||
|
<i class="fas fa-hourglass-start"></i>
|
||||||
|
}
|
||||||
|
@if (fdType === 'DOUBLE') {
|
||||||
|
<i class="fas fa-hourglass-end"></i>
|
||||||
|
}
|
||||||
|
@if (fdType === 'SPECIAL') {
|
||||||
|
<i class="fas fa-star"></i>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="fdrc-rule-banner__content">
|
||||||
|
@if (fdType === 'SINGLE') {
|
||||||
|
<strong>FD Single — Short Term</strong>
|
||||||
|
<span>Tenure must be less than 1 year (1 to 364 days). Enter tenure in Days only.</span>
|
||||||
|
}
|
||||||
|
@if (fdType === 'DOUBLE') {
|
||||||
|
<strong>FD Double — Long Term</strong>
|
||||||
|
<span>Tenure must be 1 year or more. Enter tenure in Months or Years (minimum 12 months).</span>
|
||||||
|
}
|
||||||
|
@if (fdType === 'SPECIAL') {
|
||||||
|
<strong>FD Special — Monthly Interest Payout</strong>
|
||||||
|
<span
|
||||||
|
>Interest is paid out monthly. Enter your principal to estimate monthly income. Any tenure is allowed.</span
|
||||||
|
>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="layout-row responsive-column align-center gap-2px margin-t">
|
<!-- ══ Tenure Calculator ════════════════════════════════════════════════ -->
|
||||||
<button mat-raised-button matStepperPrevious>
|
<div class="fdrc-calc-card">
|
||||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
<div class="fdrc-calc-card__header">
|
||||||
{{ 'labels.buttons.Previous' | translate }}
|
<div class="fdrc-calc-card__header-left">
|
||||||
</button>
|
<div class="fdrc-calc-icon">
|
||||||
<button mat-raised-button matStepperNext>
|
<i class="fas fa-calculator"></i>
|
||||||
{{ 'labels.buttons.Next' | translate }}
|
</div>
|
||||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
<div>
|
||||||
</button>
|
<span class="fdrc-calc-card__title">Rate Calculator</span>
|
||||||
|
<span class="fdrc-calc-card__sub">Enter your deposit tenure to find the applicable interest rate</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="fdrc-calc-card__body">
|
||||||
|
<div class="fdrc-calc-inputs">
|
||||||
|
<!-- Tenure Value -->
|
||||||
|
<div class="fdrc-calc-field">
|
||||||
|
<label class="fdrc-calc-label">Deposit Tenure</label>
|
||||||
|
<div class="fdrc-tenure-row">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="fdrc-tenure-input"
|
||||||
|
[(ngModel)]="tenureValue"
|
||||||
|
[min]="1"
|
||||||
|
placeholder="Enter value"
|
||||||
|
(keyup.enter)="calculateRate()"
|
||||||
|
/>
|
||||||
|
<!-- Period Unit Selector -->
|
||||||
|
@if (allowedPeriodUnits.length === 1) {
|
||||||
|
<span class="fdrc-period-badge">{{ allowedPeriodUnits[0] }}</span>
|
||||||
|
} @else {
|
||||||
|
<div class="fdrc-period-tabs">
|
||||||
|
@for (unit of allowedPeriodUnits; track unit) {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="fdrc-period-tab"
|
||||||
|
[class.fdrc-period-tab--active]="tenurePeriodUnit === unit"
|
||||||
|
(click)="tenurePeriodUnit = unit; onPeriodUnitChange()"
|
||||||
|
>
|
||||||
|
{{ unit }}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<!-- FD type constraint hint -->
|
||||||
|
@if (fdType === 'SINGLE') {
|
||||||
|
<span class="fdrc-calc-hint"><i class="fas fa-info-circle"></i> Max 364 days allowed</span>
|
||||||
|
}
|
||||||
|
@if (fdType === 'DOUBLE') {
|
||||||
|
<span class="fdrc-calc-hint"><i class="fas fa-info-circle"></i> Minimum 12 months required</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Principal Amount (SPECIAL only) -->
|
||||||
|
@if (isSpecial) {
|
||||||
|
<div class="fdrc-calc-field">
|
||||||
|
<label class="fdrc-calc-label"
|
||||||
|
>Principal Amount <span class="fdrc-calc-optional">(optional — for monthly payout estimate)</span></label
|
||||||
|
>
|
||||||
|
<div class="fdrc-amount-row">
|
||||||
|
<span class="fdrc-amount-prefix">₹</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="fdrc-tenure-input fdrc-tenure-input--amount"
|
||||||
|
[(ngModel)]="principalAmount"
|
||||||
|
[min]="1"
|
||||||
|
placeholder="e.g. 100000"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="fdrc-calc-actions">
|
||||||
|
<button type="button" class="fdrc-calc-btn" (click)="calculateRate()">
|
||||||
|
<i class="fas fa-search"></i> Find Rate
|
||||||
|
</button>
|
||||||
|
@if (tenureValue || matchResult) {
|
||||||
|
<button type="button" class="fdrc-calc-reset" (click)="resetCalculator()">
|
||||||
|
<i class="fas fa-times"></i> Reset
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Validation Warning -->
|
||||||
|
@if (validationWarning) {
|
||||||
|
<div class="fdrc-alert fdrc-alert--warn">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
<span>{{ validationWarning }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Match Result -->
|
||||||
|
@if (matchResult) {
|
||||||
|
<div class="fdrc-result-card">
|
||||||
|
<div class="fdrc-result-card__check">
|
||||||
|
<i class="fas fa-check-circle"></i>
|
||||||
|
</div>
|
||||||
|
<div class="fdrc-result-card__body">
|
||||||
|
<!-- Selected Tenure Banner -->
|
||||||
|
<div class="fdrc-tenure-banner">
|
||||||
|
<i class="fas fa-calendar-check"></i>
|
||||||
|
<div class="fdrc-tenure-banner__content">
|
||||||
|
<span class="fdrc-tenure-banner__label">Your Selected FD Tenure</span>
|
||||||
|
<span class="fdrc-tenure-banner__value">{{ tenureValue }} {{ tenurePeriodUnit }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="fdrc-tenure-banner__saved"> <i class="fas fa-save"></i> Will be saved </span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="fdrc-result-rate">
|
||||||
|
<span class="fdrc-result-rate__value">{{ matchResult.appliedRate | number: '1.2-2' }}%</span>
|
||||||
|
<span class="fdrc-result-rate__label">Annual Interest Rate</span>
|
||||||
|
</div>
|
||||||
|
<div class="fdrc-result-meta">
|
||||||
|
<div class="fdrc-result-meta__item">
|
||||||
|
<i class="fas fa-layer-group"></i>
|
||||||
|
<span
|
||||||
|
>Applicable Slab:
|
||||||
|
<strong>
|
||||||
|
{{ matchResult.slab.fromPeriod }}–{{ matchResult.slab.toPeriod ?? '∞' }}
|
||||||
|
{{ matchResult.slab.periodType?.value }}
|
||||||
|
</strong>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
@if (matchResult.slab.description) {
|
||||||
|
<div class="fdrc-result-meta__item">
|
||||||
|
<i class="fas fa-tag"></i>
|
||||||
|
<span>{{ matchResult.slab.description }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (matchResult.slab.amountRangeFrom || matchResult.slab.amountRangeTo) {
|
||||||
|
<div class="fdrc-result-meta__item">
|
||||||
|
<i class="fas fa-coins"></i>
|
||||||
|
<span
|
||||||
|
>Amount Range:
|
||||||
|
<strong
|
||||||
|
>{{ matchResult.slab.amountRangeFrom | number }}–{{
|
||||||
|
matchResult.slab.amountRangeTo | number
|
||||||
|
}}</strong
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Monthly Payout (SPECIAL) -->
|
||||||
|
@if (isSpecial && matchResult.estimatedMonthlyPayout != null) {
|
||||||
|
<div class="fdrc-payout-card">
|
||||||
|
<i class="fas fa-wallet"></i>
|
||||||
|
<div class="fdrc-payout-card__content">
|
||||||
|
<span class="fdrc-payout-card__label">Estimated Monthly Payout</span>
|
||||||
|
<span class="fdrc-payout-card__amount"
|
||||||
|
>₹ {{ matchResult.estimatedMonthlyPayout | number: '1.2-2' }}</span
|
||||||
|
>
|
||||||
|
<span class="fdrc-payout-card__formula"
|
||||||
|
>= ₹{{ principalAmount | number }} × {{ matchResult.appliedRate }}% ÷ 12</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (isSpecial && !matchResult.estimatedMonthlyPayout) {
|
||||||
|
<div class="fdrc-payout-hint">
|
||||||
|
<i class="fas fa-lightbulb"></i>
|
||||||
|
Enter a principal amount above to see estimated monthly interest payout.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ══ Rate Chart Table ══════════════════════════════════════════════════ -->
|
||||||
|
<div class="fdrc-table-card">
|
||||||
|
<div class="fdrc-table-card__header">
|
||||||
|
<div class="fdrc-table-card__title-row">
|
||||||
|
<i class="fas fa-table"></i>
|
||||||
|
<span class="fdrc-table-card__title">Interest Rate Slabs</span>
|
||||||
|
@if (interestRateChartData.length) {
|
||||||
|
<span class="fdrc-slab-count"
|
||||||
|
>{{ interestRateChartData.length }} slab{{ interestRateChartData.length !== 1 ? 's' : '' }}</span
|
||||||
|
>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@if (matchResult) {
|
||||||
|
<span class="fdrc-highlight-note"> <i class="fas fa-highlighter"></i> Matched slab is highlighted </span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="fdrc-table-wrap">
|
||||||
|
<table mat-table [dataSource]="interestRateChartData" #chartsTable multiTemplateDataRows class="fdrc-table">
|
||||||
|
<!-- Period Column -->
|
||||||
|
<ng-container matColumnDef="period">
|
||||||
|
<th mat-header-cell *matHeaderCellDef class="fdrc-th">Period</th>
|
||||||
|
<td mat-cell *matCellDef="let slab" class="fdrc-td fdrc-td--period">
|
||||||
|
<span class="fdrc-period-range">
|
||||||
|
{{ slab.fromPeriod }}
|
||||||
|
@if (slab.toPeriod != null) {
|
||||||
|
– {{ slab.toPeriod }}
|
||||||
|
} @else {
|
||||||
|
+
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<span class="fdrc-period-type">{{ slab.periodType?.value }}</span>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Amount Range Column -->
|
||||||
|
<ng-container matColumnDef="amountRange">
|
||||||
|
<th mat-header-cell *matHeaderCellDef class="fdrc-th">Amount Range</th>
|
||||||
|
<td mat-cell *matCellDef="let slab" class="fdrc-td">
|
||||||
|
@if (slab.amountRangeFrom || slab.amountRangeTo) {
|
||||||
|
<span>{{ slab.amountRangeFrom | number }} – {{ slab.amountRangeTo | number }}</span>
|
||||||
|
} @else {
|
||||||
|
<span class="fdrc-td--na">—</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Interest Column -->
|
||||||
|
<ng-container matColumnDef="interest">
|
||||||
|
<th mat-header-cell *matHeaderCellDef class="fdrc-th">Interest Rate</th>
|
||||||
|
<td mat-cell *matCellDef="let slab" class="fdrc-td">
|
||||||
|
<span class="fdrc-rate-badge">{{ slab.annualInterestRate | number: '1.2-2' }}%</span>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Description Column -->
|
||||||
|
<ng-container matColumnDef="description">
|
||||||
|
<th mat-header-cell *matHeaderCellDef class="fdrc-th">Description</th>
|
||||||
|
<td mat-cell *matCellDef="let slab" class="fdrc-td">
|
||||||
|
{{ slab.description || '—' }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Actions Column -->
|
||||||
|
<ng-container matColumnDef="actions">
|
||||||
|
<th mat-header-cell *matHeaderCellDef class="fdrc-th">Incentives</th>
|
||||||
|
<td mat-cell *matCellDef="let slab; let i = dataIndex" class="fdrc-td">
|
||||||
|
@if (slab.incentives?.length) {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="fdrc-incentive-btn"
|
||||||
|
(click)="expandChartSlabIndex = expandChartSlabIndex === i ? null : i"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fas"
|
||||||
|
[class.fa-eye]="expandChartSlabIndex !== i"
|
||||||
|
[class.fa-eye-slash]="expandChartSlabIndex === i"
|
||||||
|
></i>
|
||||||
|
{{ expandChartSlabIndex === i ? 'Hide' : 'View' }} ({{ slab.incentives.length }})
|
||||||
|
</button>
|
||||||
|
} @else {
|
||||||
|
<span class="fdrc-td--na">None</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Incentives Expanded Row -->
|
||||||
|
<ng-container matColumnDef="incentives">
|
||||||
|
<td
|
||||||
|
mat-cell
|
||||||
|
*matCellDef="let slab; let i = dataIndex"
|
||||||
|
[attr.colspan]="chartSlabsDisplayedColumns.length"
|
||||||
|
class="fdrc-incentives-cell"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="fdrc-incentives-wrap"
|
||||||
|
[@expandChartSlab]="i === expandChartSlabIndex ? 'expanded' : 'collapsed'"
|
||||||
|
>
|
||||||
|
@if (slab.incentives?.length) {
|
||||||
|
<div class="fdrc-incentives-inner">
|
||||||
|
<div class="fdrc-incentives-title"><i class="fas fa-gift"></i> Incentives for this slab</div>
|
||||||
|
<table mat-table [dataSource]="slab.incentives" class="fdrc-incentives-table">
|
||||||
|
<ng-container matColumnDef="entityType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Entity Type</th>
|
||||||
|
<td mat-cell *matCellDef="let inc">{{ inc.entityType?.value }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="attributeName">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Attribute</th>
|
||||||
|
<td mat-cell *matCellDef="let inc">{{ inc.attributeName?.value }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="conditionType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Condition</th>
|
||||||
|
<td mat-cell *matCellDef="let inc">{{ inc.conditionType?.value | titlecase }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="attributeValue">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Value</th>
|
||||||
|
<td mat-cell *matCellDef="let inc">{{ inc.attributeValueDesc }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="incentiveType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Type</th>
|
||||||
|
<td mat-cell *matCellDef="let inc">{{ inc.incentiveType?.value }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="amount">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Amount</th>
|
||||||
|
<td mat-cell *matCellDef="let inc">{{ inc.amount }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr mat-header-row *matHeaderRowDef="incentivesDisplayedColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: incentivesDisplayedColumns"></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns" class="fdrc-header-row"></tr>
|
||||||
|
<tr
|
||||||
|
mat-row
|
||||||
|
*matRowDef="let row; columns: chartSlabsDisplayedColumns; let i = dataIndex"
|
||||||
|
class="fdrc-data-row"
|
||||||
|
[class.fdrc-data-row--matched]="isMatchedSlab(i)"
|
||||||
|
></tr>
|
||||||
|
<tr
|
||||||
|
mat-row
|
||||||
|
*matRowDef="let row; columns: chartSlabsIncentivesDisplayedColumns"
|
||||||
|
class="fdrc-incentives-row"
|
||||||
|
></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
@if (!interestRateChartData.length) {
|
||||||
|
<div class="fdrc-empty">
|
||||||
|
<i class="fas fa-chart-bar"></i>
|
||||||
|
<span>No interest rate slabs defined for this product.</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+854
-14
@@ -6,26 +6,866 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.margin-t {
|
$border: #e2e8f0;
|
||||||
margin-top: 2em;
|
$gray-50: #f8f9fa;
|
||||||
|
$gray-100: #f1f5f9;
|
||||||
|
$gray-200: #e8eaed;
|
||||||
|
$gray-600: #64748b;
|
||||||
|
$gray-700: #334155;
|
||||||
|
$gray-900: #0f172a;
|
||||||
|
$blue: #2563eb;
|
||||||
|
$blue-light: #dbeafe;
|
||||||
|
$green: #16a34a;
|
||||||
|
$green-light: #dcfce7;
|
||||||
|
$amber: #d97706;
|
||||||
|
$amber-light: #fef3c7;
|
||||||
|
$purple: #7c3aed;
|
||||||
|
$purple-light: #ede9fe;
|
||||||
|
$red: #dc2626;
|
||||||
|
$red-light: #fee2e2;
|
||||||
|
|
||||||
|
.fdrc-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
// ── Chart Header Card ──────────────────────────────────────────────────────
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
tr.incentives-row {
|
.fdrc-header-card {
|
||||||
height: 0;
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid $border;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
> td {
|
&__left {
|
||||||
padding: 0;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: $blue-light;
|
||||||
|
color: $blue;
|
||||||
|
font-size: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $gray-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__desc {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $gray-600;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__dates {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: $gray-700;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: $gray-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: $gray-900;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Pills ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
.fdrc-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&--type {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: $gray-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--single {
|
||||||
|
background: $amber-light;
|
||||||
|
color: $amber;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--double {
|
||||||
|
background: $purple-light;
|
||||||
|
color: $purple;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--special {
|
||||||
|
background: $green-light;
|
||||||
|
color: $green;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--amount {
|
||||||
|
background: $blue-light;
|
||||||
|
color: $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── FD Type Rule Banner ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
.fdrc-rule-banner {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border-left: 4px solid;
|
||||||
|
|
||||||
|
&--single {
|
||||||
|
background: $amber-light;
|
||||||
|
border-color: $amber;
|
||||||
|
color: #b45309;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--double {
|
||||||
|
background: $purple-light;
|
||||||
|
border-color: $purple;
|
||||||
|
color: #6d28d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--special {
|
||||||
|
background: $green-light;
|
||||||
|
border-color: $green;
|
||||||
|
color: #15803d;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-top: 2px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.incentives {
|
span {
|
||||||
overflow: hidden;
|
font-size: 12px;
|
||||||
}
|
|
||||||
|
|
||||||
.mat-card {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Calculator Card ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
.fdrc-calc-card {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid $border;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 14px 20px;
|
||||||
|
background: $gray-50;
|
||||||
|
border-bottom: 1px solid $gray-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $gray-900;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__sub {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $gray-600;
|
||||||
|
display: block;
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-calc-icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: $blue-light;
|
||||||
|
color: $blue;
|
||||||
|
font-size: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-calc-inputs {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-calc-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
flex: 1 1 220px;
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-calc-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $gray-700;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-calc-optional {
|
||||||
|
font-weight: 400;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: 0;
|
||||||
|
color: $gray-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-tenure-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-tenure-input {
|
||||||
|
height: 42px;
|
||||||
|
border: 1.5px solid $border;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0 12px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $gray-900;
|
||||||
|
background: #fff;
|
||||||
|
width: 130px;
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: $blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--amount {
|
||||||
|
width: 180px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-period-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 14px;
|
||||||
|
height: 42px;
|
||||||
|
background: $gray-100;
|
||||||
|
border: 1.5px solid $border;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $gray-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-period-tabs {
|
||||||
|
display: flex;
|
||||||
|
border: 1.5px solid $border;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-period-tab {
|
||||||
|
height: 42px;
|
||||||
|
padding: 0 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
background: #fff;
|
||||||
|
color: $gray-600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
background 0.15s,
|
||||||
|
color 0.15s;
|
||||||
|
|
||||||
|
& + & {
|
||||||
|
border-left: 1px solid $border;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
background: $blue;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-amount-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-amount-prefix {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 42px;
|
||||||
|
background: $gray-100;
|
||||||
|
border: 1.5px solid $border;
|
||||||
|
border-right: none;
|
||||||
|
border-radius: 8px 0 0 8px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $gray-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-amount-row .fdrc-tenure-input {
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-calc-hint {
|
||||||
|
font-size: 11px;
|
||||||
|
color: $amber;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-calc-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-calc-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
height: 42px;
|
||||||
|
padding: 0 22px;
|
||||||
|
background: $blue;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #1d4ed8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-calc-reset {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
height: 42px;
|
||||||
|
padding: 0 16px;
|
||||||
|
background: transparent;
|
||||||
|
color: $gray-600;
|
||||||
|
border: 1.5px solid $border;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
border-color 0.15s,
|
||||||
|
color 0.15s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: $red;
|
||||||
|
color: $red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Alert ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
.fdrc-alert {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 16px;
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-top: 1px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--warn {
|
||||||
|
background: $amber-light;
|
||||||
|
color: #b45309;
|
||||||
|
border: 1px solid rgba($amber, 0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Tenure Banner (inside result card) ───────────────────────────────────
|
||||||
|
|
||||||
|
.fdrc-tenure-banner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1.5px solid rgba($green, 0.4);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
|
||||||
|
> i {
|
||||||
|
font-size: 18px;
|
||||||
|
color: $green;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
color: $gray-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__value {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: $gray-900;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__saved {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
background: $green-light;
|
||||||
|
color: $green;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Result Card ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
.fdrc-result-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 18px 20px;
|
||||||
|
background: $green-light;
|
||||||
|
border: 1.5px solid rgba($green, 0.35);
|
||||||
|
border-radius: 12px;
|
||||||
|
|
||||||
|
&__check {
|
||||||
|
font-size: 26px;
|
||||||
|
color: $green;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-result-rate {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
&__value {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: $green;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #166534;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-result-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: $gray-700;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: $green;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: $gray-900;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Monthly Payout (SPECIAL) ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
.fdrc-payout-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
margin-top: 14px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1.5px solid rgba($green, 0.3);
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 22px;
|
||||||
|
color: $green;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $gray-600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__amount {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: $green;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__formula {
|
||||||
|
font-size: 11px;
|
||||||
|
color: $gray-600;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-payout-hint {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: $gray-600;
|
||||||
|
font-style: italic;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: $amber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Rate Chart Table Card ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
.fdrc-table-card {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid $border;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 14px 20px;
|
||||||
|
background: $gray-50;
|
||||||
|
border-bottom: 1px solid $gray-200;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $gray-900;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-slab-count {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 8px;
|
||||||
|
background: $blue-light;
|
||||||
|
color: $blue;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-highlight-note {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $green;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-table-wrap {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-table {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-th {
|
||||||
|
font-size: 11px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
color: $gray-700 !important;
|
||||||
|
background: $gray-50;
|
||||||
|
padding: 10px 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-td {
|
||||||
|
padding: 12px 16px !important;
|
||||||
|
font-size: 13px;
|
||||||
|
color: $gray-700;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
&--period {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--na {
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-period-range {
|
||||||
|
font-weight: 700;
|
||||||
|
color: $gray-900;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-period-type {
|
||||||
|
font-size: 11px;
|
||||||
|
color: $gray-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-rate-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 3px 10px;
|
||||||
|
background: $blue-light;
|
||||||
|
color: $blue;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-incentive-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
height: 32px;
|
||||||
|
background: transparent;
|
||||||
|
border: 1.5px solid $border;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $gray-700;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
border-color 0.15s,
|
||||||
|
color 0.15s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: $blue;
|
||||||
|
color: $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-header-row {
|
||||||
|
background: $gray-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-data-row {
|
||||||
|
transition: background 0.15s;
|
||||||
|
|
||||||
|
&:hover td {
|
||||||
|
background: $gray-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--matched td {
|
||||||
|
background: $green-light !important;
|
||||||
|
border-left: 3px solid $green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-incentives-row {
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
> td {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-incentives-cell {
|
||||||
|
padding: 0 !important;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-incentives-wrap {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-incentives-inner {
|
||||||
|
padding: 12px 20px 16px;
|
||||||
|
background: $gray-50;
|
||||||
|
border-top: 1px dashed $border;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-incentives-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $gray-700;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: $purple;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-incentives-table {
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid $border;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-empty {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 48px 20px;
|
||||||
|
color: #94a3b8;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Stepper Actions ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
.fdrc-stepper-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|||||||
+203
-26
@@ -6,8 +6,7 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** Angular Imports */
|
import { Component, Input, OnChanges, Output, EventEmitter, ViewChild } from '@angular/core';
|
||||||
import { Component, Input, OnChanges, ViewChild } from '@angular/core';
|
|
||||||
import {
|
import {
|
||||||
MatTableDataSource,
|
MatTableDataSource,
|
||||||
MatTable,
|
MatTable,
|
||||||
@@ -22,15 +21,20 @@ import {
|
|||||||
MatRow
|
MatRow
|
||||||
} from '@angular/material/table';
|
} from '@angular/material/table';
|
||||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||||
import { NgIf, NgSwitch, TitleCasePipe } from '@angular/common';
|
import { NgIf, NgSwitch, TitleCasePipe, DecimalPipe } from '@angular/common';
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
|
|
||||||
import { DateFormatPipe } from '../../../../pipes/date-format.pipe';
|
import { DateFormatPipe } from '../../../../pipes/date-format.pipe';
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
/**
|
export type FdPeriodUnit = 'Days' | 'Months' | 'Years';
|
||||||
* Fixed Deposits Account Interest Rate Chart Step
|
|
||||||
*/
|
export interface SlabMatchResult {
|
||||||
|
slab: any;
|
||||||
|
slabIndex: number;
|
||||||
|
appliedRate: number;
|
||||||
|
estimatedMonthlyPayout?: number;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'mifosx-fixed-deposit-account-interest-rate-chart-step',
|
selector: 'mifosx-fixed-deposit-account-interest-rate-chart-step',
|
||||||
templateUrl: './fixed-deposit-account-interest-rate-chart-step.component.html',
|
templateUrl: './fixed-deposit-account-interest-rate-chart-step.component.html',
|
||||||
@@ -44,41 +48,38 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
FormsModule,
|
||||||
MatTable,
|
MatTable,
|
||||||
MatColumnDef,
|
MatColumnDef,
|
||||||
MatHeaderCellDef,
|
MatHeaderCellDef,
|
||||||
MatHeaderCell,
|
MatHeaderCell,
|
||||||
MatCellDef,
|
MatCellDef,
|
||||||
MatCell,
|
MatCell,
|
||||||
FaIconComponent,
|
|
||||||
NgSwitch,
|
NgSwitch,
|
||||||
MatHeaderRowDef,
|
MatHeaderRowDef,
|
||||||
MatHeaderRow,
|
MatHeaderRow,
|
||||||
MatRowDef,
|
MatRowDef,
|
||||||
MatRow,
|
MatRow,
|
||||||
MatStepperPrevious,
|
|
||||||
MatStepperNext,
|
|
||||||
TitleCasePipe,
|
TitleCasePipe,
|
||||||
DateFormatPipe
|
DateFormatPipe,
|
||||||
|
DecimalPipe
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class FixedDepositAccountInterestRateChartStepComponent implements OnChanges {
|
export class FixedDepositAccountInterestRateChartStepComponent implements OnChanges {
|
||||||
/** Fixed deposits account template */
|
|
||||||
@Input() fixedDepositsAccountTemplate: any;
|
@Input() fixedDepositsAccountTemplate: any;
|
||||||
/** Fixed deposits account and product template */
|
|
||||||
@Input() fixedDepositsAccountProductTemplate: any;
|
@Input() fixedDepositsAccountProductTemplate: any;
|
||||||
|
|
||||||
/** Interest Rate Chart Data */
|
@Output() tenureSelected = new EventEmitter<{ depositPeriod: number; depositPeriodFrequencyId: number }>();
|
||||||
interestRateChartData: any = [];
|
|
||||||
/** Columns to be displayed in interest rate chart table. */
|
interestRateChartData: any[] = [];
|
||||||
chartSlabsDisplayedColumns: any[] = [
|
|
||||||
|
chartSlabsDisplayedColumns: string[] = [
|
||||||
'period',
|
'period',
|
||||||
'amountRange',
|
'amountRange',
|
||||||
'interest',
|
'interest',
|
||||||
'description',
|
'description',
|
||||||
'actions'
|
'actions'
|
||||||
];
|
];
|
||||||
/** Columns to be displayed in incentives sub-table. */
|
|
||||||
incentivesDisplayedColumns: string[] = [
|
incentivesDisplayedColumns: string[] = [
|
||||||
'entityType',
|
'entityType',
|
||||||
'attributeName',
|
'attributeName',
|
||||||
@@ -87,20 +88,196 @@ export class FixedDepositAccountInterestRateChartStepComponent implements OnChan
|
|||||||
'incentiveType',
|
'incentiveType',
|
||||||
'amount'
|
'amount'
|
||||||
];
|
];
|
||||||
/** Additional Column to disblac incentives table */
|
|
||||||
chartSlabsIncentivesDisplayedColumns: string[] = ['incentives'];
|
chartSlabsIncentivesDisplayedColumns: string[] = ['incentives'];
|
||||||
/** Expand Chart Slab Index used in the view */
|
|
||||||
expandChartSlabIndex: number;
|
|
||||||
|
|
||||||
/** Interest Rate charts table reference */
|
expandChartSlabIndex: number | null = null;
|
||||||
|
|
||||||
@ViewChild('chartsTable', { static: true }) chartsTableRef: MatTable<Element>;
|
@ViewChild('chartsTable', { static: true }) chartsTableRef: MatTable<Element>;
|
||||||
|
|
||||||
constructor() {}
|
// ── Tenure Calculator ───────────────────────────────────────────────────
|
||||||
|
tenureValue: number | null = null;
|
||||||
|
tenurePeriodUnit: FdPeriodUnit = 'Days';
|
||||||
|
principalAmount: number | null = null;
|
||||||
|
|
||||||
|
matchResult: SlabMatchResult | null = null;
|
||||||
|
validationWarning: string | null = null;
|
||||||
|
|
||||||
|
// ── FD Type labels ──────────────────────────────────────────────────────
|
||||||
|
readonly fdTypeLabels: Record<string, string> = {
|
||||||
|
SINGLE: 'FD Single (Days < 1 Year)',
|
||||||
|
DOUBLE: 'FD Double (≥ 1 Year)',
|
||||||
|
SPECIAL: 'FD Special (Monthly Interest Payout)'
|
||||||
|
};
|
||||||
|
|
||||||
|
get fdType(): string {
|
||||||
|
return this.fixedDepositsAccountProductTemplate?.fdType || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
get fdTypeLabel(): string {
|
||||||
|
return this.fdTypeLabels[this.fdType] || this.fdType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Period unit options driven by fdType */
|
||||||
|
get allowedPeriodUnits(): FdPeriodUnit[] {
|
||||||
|
if (this.fdType === 'SINGLE') return ['Days'];
|
||||||
|
if (this.fdType === 'DOUBLE') return [
|
||||||
|
'Months',
|
||||||
|
'Years'
|
||||||
|
];
|
||||||
|
return [
|
||||||
|
'Days',
|
||||||
|
'Months',
|
||||||
|
'Years'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSpecial(): boolean {
|
||||||
|
return this.fdType === 'SPECIAL';
|
||||||
|
}
|
||||||
|
|
||||||
ngOnChanges() {
|
ngOnChanges() {
|
||||||
if (this.fixedDepositsAccountProductTemplate) {
|
if (this.fixedDepositsAccountProductTemplate) {
|
||||||
this.interestRateChartData = this.fixedDepositsAccountProductTemplate.accountChart.chartSlabs;
|
this.interestRateChartData = this.fixedDepositsAccountProductTemplate.accountChart?.chartSlabs ?? [];
|
||||||
this.chartsTableRef.renderRows();
|
if (this.chartsTableRef) {
|
||||||
|
this.chartsTableRef.renderRows();
|
||||||
|
}
|
||||||
|
// Reset calculator when product changes
|
||||||
|
this.resetCalculator();
|
||||||
|
// Set default period unit based on FD type
|
||||||
|
this.tenurePeriodUnit = this.allowedPeriodUnits[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert entered tenure to Days for slab matching
|
||||||
|
private toDays(value: number, unit: FdPeriodUnit): number {
|
||||||
|
if (unit === 'Days') return value;
|
||||||
|
if (unit === 'Months') return value * 30;
|
||||||
|
return value * 365;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert slab period (which is stored in its own periodType) to days
|
||||||
|
private slabPeriodToDays(period: number, periodTypeValue: string): number {
|
||||||
|
const p = (periodTypeValue || '').toLowerCase();
|
||||||
|
if (p.includes('month')) return period * 30;
|
||||||
|
if (p.includes('year')) return period * 365;
|
||||||
|
return period; // days
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateRate() {
|
||||||
|
this.validationWarning = null;
|
||||||
|
this.matchResult = null;
|
||||||
|
|
||||||
|
if (!this.tenureValue || this.tenureValue <= 0) {
|
||||||
|
this.validationWarning = 'Please enter a valid tenure value greater than 0.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FD Type specific validation
|
||||||
|
const enteredDays = this.toDays(this.tenureValue, this.tenurePeriodUnit);
|
||||||
|
|
||||||
|
if (this.fdType === 'SINGLE' && enteredDays >= 365) {
|
||||||
|
this.validationWarning =
|
||||||
|
'FD Single allows tenure less than 1 year (max 364 days). Please enter a shorter tenure.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.fdType === 'DOUBLE' && enteredDays < 365) {
|
||||||
|
this.validationWarning =
|
||||||
|
'FD Double requires tenure of at least 1 year (minimum 12 months). Please enter a longer tenure.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const slabs: any[] = this.interestRateChartData || [];
|
||||||
|
if (!slabs.length) {
|
||||||
|
this.validationWarning = 'No interest rate slabs are defined for this product.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let matchedSlab: any = null;
|
||||||
|
let matchedIndex = -1;
|
||||||
|
|
||||||
|
for (let i = 0; i < slabs.length; i++) {
|
||||||
|
const slab = slabs[i];
|
||||||
|
const periodTypeValue = slab.periodType?.value || 'Days';
|
||||||
|
const fromDays = this.slabPeriodToDays(slab.fromPeriod ?? 0, periodTypeValue);
|
||||||
|
const toDays = slab.toPeriod != null ? this.slabPeriodToDays(slab.toPeriod, periodTypeValue) : Infinity;
|
||||||
|
|
||||||
|
if (enteredDays >= fromDays && enteredDays <= toDays) {
|
||||||
|
matchedSlab = slab;
|
||||||
|
matchedIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matchedSlab) {
|
||||||
|
this.validationWarning =
|
||||||
|
'No applicable interest rate slab found for the entered tenure. Please check the rate chart below.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rate = matchedSlab.annualInterestRate ?? 0;
|
||||||
|
const result: SlabMatchResult = {
|
||||||
|
slab: matchedSlab,
|
||||||
|
slabIndex: matchedIndex,
|
||||||
|
appliedRate: rate
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.isSpecial && this.principalAmount && this.principalAmount > 0) {
|
||||||
|
result.estimatedMonthlyPayout = (this.principalAmount * rate) / 1200;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.matchResult = result;
|
||||||
|
|
||||||
|
// Emit selected tenure to parent for backend submission
|
||||||
|
const frequencyId = this.resolveFrequencyId(this.tenurePeriodUnit);
|
||||||
|
if (frequencyId !== null) {
|
||||||
|
this.tenureSelected.emit({ depositPeriod: this.tenureValue, depositPeriodFrequencyId: frequencyId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolveFrequencyId(unit: FdPeriodUnit): number | null {
|
||||||
|
const options: any[] = this.fixedDepositsAccountProductTemplate?.periodFrequencyTypeOptions ?? [];
|
||||||
|
const unitLower = unit.toLowerCase();
|
||||||
|
const match = options.find((o: any) => (o.value || '').toLowerCase().includes(unitLower));
|
||||||
|
if (match) return match.id;
|
||||||
|
// Fineract standard fallback: Days=0, Months=2, Years=3
|
||||||
|
const fallback: Record<FdPeriodUnit, number> = { Days: 0, Months: 2, Years: 3 };
|
||||||
|
return fallback[unit] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetCalculator() {
|
||||||
|
this.tenureValue = null;
|
||||||
|
this.principalAmount = null;
|
||||||
|
this.matchResult = null;
|
||||||
|
this.validationWarning = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
onPeriodUnitChange() {
|
||||||
|
this.matchResult = null;
|
||||||
|
this.validationWarning = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
isMatchedSlab(index: number): boolean {
|
||||||
|
return this.matchResult?.slabIndex === index;
|
||||||
|
}
|
||||||
|
|
||||||
|
get chartName(): string {
|
||||||
|
return this.fixedDepositsAccountProductTemplate?.accountChart?.name || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
get chartFromDate(): any {
|
||||||
|
return this.fixedDepositsAccountProductTemplate?.accountChart?.fromDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
get chartEndDate(): any {
|
||||||
|
return this.fixedDepositsAccountProductTemplate?.accountChart?.endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
get chartDescription(): string {
|
||||||
|
return this.fixedDepositsAccountProductTemplate?.accountChart?.description || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
get isPrimaryGroupingByAmount(): boolean {
|
||||||
|
return !!this.fixedDepositsAccountProductTemplate?.accountChart?.isPrimaryGroupingByAmount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+565
-394
@@ -6,416 +6,587 @@
|
|||||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<div class="layout-row-wrap responsive-column">
|
<div class="preview-wrapper">
|
||||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Details' | translate }}</h3>
|
<!-- ══ 1. Account Details ════════════════════════════════════════ -->
|
||||||
|
<div class="preview-section">
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
<div class="preview-section__header">
|
||||||
<div class="layout-row flex-100">
|
<i class="fas fa-info-circle preview-section__icon"></i>
|
||||||
<span class="flex-40">{{ 'labels.inputs.Product' | translate }}</span>
|
<span class="preview-section__title">Account Details</span>
|
||||||
<span class="flex-60">{{
|
</div>
|
||||||
fixedDepositAccountData.productId | find: fixedDepositsAccountTemplate.productOptions : 'id' : 'name'
|
<div class="preview-body">
|
||||||
}}</span>
|
<div class="preview-grid preview-grid--three">
|
||||||
</div>
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Product</span>
|
||||||
<div class="layout-row flex-100">
|
<span class="preview-value">{{
|
||||||
<span class="flex-40">{{ 'labels.inputs.Submitted On' | translate }}</span>
|
fixedDepositAccountData?.productId | find: fixedDepositsAccountTemplate?.productOptions : 'id' : 'name'
|
||||||
<span class="flex-60">{{ fixedDepositAccountData.submittedOnDate | dateFormat }}</span>
|
}}</span>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100" *ngIf="fixedDepositAccountData.externalId">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.External Id' | translate }}</span>
|
|
||||||
<span class="flex-60">{{ fixedDepositAccountData.externalId }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Field Officer' | translate }}</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
(fixedDepositAccountData.fieldOfficerId
|
|
||||||
| find: fixedDepositsAccountProductTemplate.fieldOfficerOptions : 'id' : 'displayName') || 'Unassigned'
|
|
||||||
| translateKey: 'inputs'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Currency' | translate }}</h3>
|
|
||||||
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
|
||||||
<div class="layout-row flex-100">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Currency' | translate }}</span>
|
|
||||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate.currency.code }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Currency Multiple' | translate }}</span>
|
|
||||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate.currency.inMultiplesOf }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Decimal Places' | translate }}</span>
|
|
||||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate.currency.decimalPlaces }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Terms' | translate }}</h3>
|
|
||||||
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
|
||||||
<div class="layout-row flex-100">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Fixed Deposit Amount' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
fixedDepositAccountData.depositAmount
|
|
||||||
| currency: fixedDepositsAccountProductTemplate.currency.code : 'symbol-narrow' : '1.2-2'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Deposit Period' | translate }}:</span>
|
|
||||||
<span class="flex-60"
|
|
||||||
>{{ fixedDepositAccountData.depositPeriod }}
|
|
||||||
{{
|
|
||||||
fixedDepositAccountData.depositPeriodFrequencyId
|
|
||||||
| find: fixedDepositsAccountProductTemplate.periodFrequencyTypeOptions : 'id' : 'value'
|
|
||||||
| translateKey: 'catalogs'
|
|
||||||
}}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Interest Compounding Period' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
fixedDepositAccountData.interestCompoundingPeriodType
|
|
||||||
| find: fixedDepositsAccountProductTemplate.interestCompoundingPeriodTypeOptions : 'id' : 'value'
|
|
||||||
| translateKey: 'catalogs'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Interest Posting Period' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
fixedDepositAccountData.interestPostingPeriodType
|
|
||||||
| find: fixedDepositsAccountProductTemplate.interestPostingPeriodTypeOptions : 'id' : 'value'
|
|
||||||
| translateKey: 'catalogs'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Interest Calculated using' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
fixedDepositAccountData.interestCalculationType
|
|
||||||
| find: fixedDepositsAccountProductTemplate.interestCalculationTypeOptions : 'id' : 'value'
|
|
||||||
| translateKey: 'catalogs'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Days in Year' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
fixedDepositAccountData.interestCalculationDaysInYearType
|
|
||||||
| find: fixedDepositsAccountProductTemplate.interestCalculationDaysInYearTypeOptions : 'id' : 'value'
|
|
||||||
| translateKey: 'catalogs'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Settings' | translate }}</h3>
|
|
||||||
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
|
||||||
<div class="layout-row flex-100" *ngIf="fixedDepositAccountData.lockinPeriodFrequency">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Lock-in Period' | translate }}:</span>
|
|
||||||
<span class="flex-60"
|
|
||||||
>{{ fixedDepositAccountData.lockinPeriodFrequency }}
|
|
||||||
{{
|
|
||||||
fixedDepositAccountData.lockinPeriodFrequencyType
|
|
||||||
| find: fixedDepositsAccountProductTemplate.lockinPeriodFrequencyTypeOptions : 'id' : 'value'
|
|
||||||
| translateKey: 'catalogs'
|
|
||||||
}}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100" *ngIf="fixedDepositsAccountProductTemplate.minDepositTerm">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Minimum Deposit Term' | translate }}:</span>
|
|
||||||
<span class="flex-60"
|
|
||||||
>{{ fixedDepositsAccountProductTemplate.minDepositTerm }}
|
|
||||||
{{ fixedDepositsAccountProductTemplate.minDepositTermType?.value | translateKey: 'catalogs' }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100" *ngIf="fixedDepositsAccountProductTemplate.inMultiplesOfDepositTerm">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.In Multiples Of' | translate }}:</span>
|
|
||||||
<span class="flex-60"
|
|
||||||
>{{ fixedDepositsAccountProductTemplate.inMultiplesOfDepositTerm }}
|
|
||||||
{{ fixedDepositsAccountProductTemplate.inMultiplesOfDepositTermType?.value | translateKey: 'catalogs' }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100" *ngIf="fixedDepositsAccountProductTemplate.maxDepositTerm">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Maximum Deposit Term' | translate }}</span>
|
|
||||||
<span class="flex-60"
|
|
||||||
>{{ fixedDepositsAccountProductTemplate.maxDepositTerm }}
|
|
||||||
{{ fixedDepositsAccountProductTemplate.maxDepositTermType?.value | translateKey: 'catalogs' }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Transfer Interest to Savings Account' | translate }}?</span>
|
|
||||||
<span class="flex-60">{{ fixedDepositAccountData.transferInterestToSavings | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100" *ngIf="fixedDepositAccountData.transferInterestToSavings">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Linked Savings Account' | translate }}</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
fixedDepositAccountData.linkAccountId
|
|
||||||
| find: fixedDepositsAccountProductTemplate.savingsAccounts : 'id' : 'accountNo'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100" *ngIf="fixedDepositAccountData.maturityInstructionId">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Maturity Instructions' | translate }}</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
fixedDepositAccountData.maturityInstructionId
|
|
||||||
| find: fixedDepositsAccountProductTemplate.maturityInstructionOptions : 'id' : 'value'
|
|
||||||
| translateKey: 'catalogs'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Apply Penal Interest (less)' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate.preClosurePenalApplicable | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100" *ngIf="fixedDepositsAccountProductTemplate.preClosurePenalApplicable">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Penal Interest' | translate }} (%):</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
fixedDepositsAccountProductTemplate.preClosurePenalInterest +
|
|
||||||
'% (' +
|
|
||||||
fixedDepositsAccountProductTemplate.preClosurePenalInterestOnType?.id +
|
|
||||||
')'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100" *ngIf="fixedDepositsAccountProductTemplate.withHoldTax">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Withhold Tax is Applicable' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ fixedDepositAccountData.withHoldTax | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Interest Rate Chart' | translate }}</h3>
|
|
||||||
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
|
||||||
<div class="layout-row flex-100" *ngIf="fixedDepositsAccountProductTemplate?.accountChart.name">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.name' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate?.accountChart.name }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Valid from Date' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate?.accountChart.fromDate | dateFormat }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100" *ngIf="fixedDepositsAccountProductTemplate?.accountChart.endDate">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.End Date' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate?.accountChart.endDate | dateFormat }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100" *ngIf="fixedDepositsAccountProductTemplate?.accountChart.description">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Description' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate?.accountChart.description }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row flex-100">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Primary Grouping by Amount' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
fixedDepositsAccountProductTemplate?.accountChart.isPrimaryGroupingByAmount | yesNo
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table class="mat-elevation-z1 irc-table" mat-table [dataSource]="interestRateChartData" multiTemplateDataRows>
|
|
||||||
<ng-container matColumnDef="period">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Period' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let chartSlab">
|
|
||||||
{{ chartSlab.fromPeriod }} - {{ chartSlab.toPeriod }} {{
|
|
||||||
chartSlab.periodType.value | translateKey: 'catalogs'
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="amountRange">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount Range' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let chartSlab">
|
|
||||||
{{ chartSlab.amountRangeFrom | formatNumber }} - {{ chartSlab.amountRangeTo | formatNumber }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="interest">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.annualInterestRate | formatNumber }} %</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="description">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Description' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.description }}</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="actions">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let chartSlab; let chartSlabIndex = dataIndex">
|
|
||||||
<button
|
|
||||||
mat-button
|
|
||||||
color="primary"
|
|
||||||
(click)="expandChartSlabIndex = expandChartSlabIndex === chartSlabIndex ? null : chartSlabIndex"
|
|
||||||
>
|
|
||||||
<span *ngIf="expandChartSlabIndex !== chartSlabIndex">
|
|
||||||
<fa-icon icon="eye" class="m-r-10"></fa-icon>
|
|
||||||
{{ 'labels.buttons.View Incentives' | translate }}
|
|
||||||
</span>
|
|
||||||
<span *ngIf="expandChartSlabIndex === chartSlabIndex">
|
|
||||||
<fa-icon icon="eye-slash" class="m-r-10"></fa-icon>
|
|
||||||
{{ 'labels.buttons.Hide Incentives' | translate }}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="incentives">
|
|
||||||
<td
|
|
||||||
mat-cell
|
|
||||||
*matCellDef="let chartSlab; let chartSlabIndex = dataIndex"
|
|
||||||
[attr.colspan]="chartSlabsDisplayedColumns.length"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="layout-row-wrap flex-100 incentives"
|
|
||||||
[@expandChartSlab]="chartSlabIndex === expandChartSlabIndex ? 'expanded' : 'collapsed'"
|
|
||||||
>
|
|
||||||
<mat-card class="layout-row-wrap flex-100">
|
|
||||||
<h4 class="m-b-10 flex-13">
|
|
||||||
{{ 'labels.heading.Incentives' | translate }}
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<table
|
|
||||||
class="flex-100 mat-elevation-z1"
|
|
||||||
mat-table
|
|
||||||
[dataSource]="chartSlab.incentives"
|
|
||||||
*ngIf="chartSlab.incentives.length"
|
|
||||||
>
|
|
||||||
<ng-container matColumnDef="entityType">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Entity Type' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive">
|
|
||||||
{{ incentive.entityType.value }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="attributeName">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Name' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive">
|
|
||||||
{{ incentive.attributeName.value }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="conditionType">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Condition Type' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive">
|
|
||||||
{{ incentive.conditionType.value | titlecase }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="attributeValue">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Value' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive" [ngSwitch]="incentive.attributeName">
|
|
||||||
{{ incentive.attributeValueDesc }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="incentiveType">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Incentive Type' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive">
|
|
||||||
{{ incentive.incentiveType.value }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container matColumnDef="amount">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let incentive">
|
|
||||||
{{ incentive.amount | formatNumber }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<tr mat-header-row *matHeaderRowDef="incentivesDisplayedColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: incentivesDisplayedColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
<div class="preview-field">
|
||||||
</ng-container>
|
<span class="preview-label">Submitted On</span>
|
||||||
|
<span class="preview-value">{{ fixedDepositAccountData?.submittedOnDate | dateFormat }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Field Officer</span>
|
||||||
|
<span class="preview-value">{{
|
||||||
|
(fixedDepositAccountData?.fieldOfficerId
|
||||||
|
| find: fixedDepositsAccountProductTemplate?.fieldOfficerOptions : 'id' : 'displayName') || 'Unassigned'
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">External ID</span>
|
||||||
|
<span class="preview-value" [class.preview-muted]="!fixedDepositAccountData?.externalId">
|
||||||
|
{{ fixedDepositAccountData?.externalId || 'Unassigned' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns"></tr>
|
<!-- ══ 1a. Product Classification ════════════════════════════════ -->
|
||||||
<tr mat-row *matRowDef="let row; columns: chartSlabsDisplayedColumns"></tr>
|
@if (fixedDepositsAccountProductTemplate) {
|
||||||
<tr mat-row *matRowDef="let row; columns: chartSlabsIncentivesDisplayedColumns" class="incentives-row"></tr>
|
<div class="preview-section">
|
||||||
</table>
|
<div class="preview-section__header">
|
||||||
|
<i class="fas fa-tags preview-section__icon"></i>
|
||||||
|
<span class="preview-section__title">Product Classification</span>
|
||||||
|
<span class="preview-section__badge preview-section__badge--active" style="margin-left: auto"
|
||||||
|
>From Product</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="preview-body">
|
||||||
|
<div class="preview-grid preview-grid--three">
|
||||||
|
@if (fixedDepositAccountData?.accountType) {
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Account Type</span>
|
||||||
|
<span class="preview-value">{{ fixedDepositAccountData.accountType }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (fixedDepositAccountData?.customerSegment) {
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Customer Segment</span>
|
||||||
|
<span class="preview-value">{{ fixedDepositAccountData.customerSegment }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (fixedDepositsAccountProductTemplate?.fdType) {
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">FD Type</span>
|
||||||
|
<span class="preview-value">{{ getFdTypeLabel(fixedDepositsAccountProductTemplate.fdType) }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Account No</span>
|
||||||
|
<span class="preview-value preview-mono">
|
||||||
|
{{
|
||||||
|
fixedDepositsAccountProductTemplate?.previewAccountNo ||
|
||||||
|
fixedDepositsAccountTemplate?.accountNo ||
|
||||||
|
'Auto-generated'
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
@if (fixedDepositsAccountProductTemplate?.effectiveDate) {
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Effective To</span>
|
||||||
|
<span class="preview-value">{{ fixedDepositsAccountProductTemplate.effectiveDate | dateFormat }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (fixedDepositsAccountProductTemplate?.endDate) {
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">End Date</span>
|
||||||
|
<span class="preview-value">{{ fixedDepositsAccountProductTemplate.endDate | dateFormat }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Share Linking Enabled</span>
|
||||||
|
<span
|
||||||
|
class="preview-value preview-badge"
|
||||||
|
[class.preview-badge--yes]="fixedDepositsAccountProductTemplate?.shareLinkingEnabled"
|
||||||
|
>
|
||||||
|
{{ fixedDepositsAccountProductTemplate?.shareLinkingEnabled ? 'Yes' : 'No' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
@if (fixedDepositsAccountProductTemplate?.introducerRequired !== undefined) {
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Introducer Required</span>
|
||||||
|
<span
|
||||||
|
class="preview-value preview-badge"
|
||||||
|
[class.preview-badge--yes]="fixedDepositsAccountProductTemplate?.introducerRequired"
|
||||||
|
>
|
||||||
|
{{ fixedDepositsAccountProductTemplate?.introducerRequired ? 'Yes' : 'No' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div *ngIf="fixedDepositAccountData.charges.length" class="layout-row-wrap responsive-column flex-100">
|
<!-- ══ 1b. Status of Account (RBI) ══════════════════════════════ -->
|
||||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Charges' | translate }}</h3>
|
@if (fixedDepositAccountData?.accountStatus) {
|
||||||
|
<div class="preview-section">
|
||||||
|
<div class="preview-section__header">
|
||||||
|
<i class="fas fa-id-card preview-section__icon"></i>
|
||||||
|
<span class="preview-section__title">Status of Account</span>
|
||||||
|
<span class="preview-section__badge">RBI Guidelines</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-body">
|
||||||
|
<div class="preview-soa-status-row">
|
||||||
|
<span class="preview-soa-tile">
|
||||||
|
@switch (fixedDepositAccountData.accountStatus) {
|
||||||
|
@case ('INDIVIDUAL_MALE') {
|
||||||
|
<i class="fas fa-male"></i> Individual (Male)
|
||||||
|
}
|
||||||
|
@case ('INDIVIDUAL_FEMALE') {
|
||||||
|
<i class="fas fa-female"></i> Individual (Female)
|
||||||
|
}
|
||||||
|
@case ('SINGLE') {
|
||||||
|
<i class="fas fa-user-circle"></i> Single Account
|
||||||
|
}
|
||||||
|
@case ('JOINT') {
|
||||||
|
<i class="fas fa-users"></i> Joint Account
|
||||||
|
}
|
||||||
|
@case ('MINOR') {
|
||||||
|
<i class="fas fa-child"></i> Minor Account
|
||||||
|
}
|
||||||
|
@case ('SENIOR_CITIZEN') {
|
||||||
|
<i class="fas fa-user-clock"></i> Senior Citizen
|
||||||
|
}
|
||||||
|
@default {
|
||||||
|
{{ fixedDepositAccountData.accountStatus }}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
@if (fixedDepositAccountData.accountStatus === 'JOINT') {
|
||||||
<table class="flex-100 mat-elevation-z1" mat-table [dataSource]="fixedDepositAccountData.charges">
|
<div class="preview-grid preview-grid--three" style="margin-top: 12px">
|
||||||
<ng-container matColumnDef="name">
|
<div class="preview-field">
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.name' | translate }}</th>
|
<span class="preview-label">Mode of Operation</span>
|
||||||
<td mat-cell *matCellDef="let charge">
|
<span class="preview-value">
|
||||||
{{ charge.name + ', ' + charge.currency.displaySymbol }}
|
@switch (fixedDepositAccountData.modeOfOperation) {
|
||||||
</td>
|
@case ('EITHER_OR_SURVIVOR') {
|
||||||
</ng-container>
|
Either or Survivor
|
||||||
|
}
|
||||||
|
@case ('FORMER_OR_SURVIVOR') {
|
||||||
|
Former or Survivor
|
||||||
|
}
|
||||||
|
@case ('JOINTLY') {
|
||||||
|
Jointly
|
||||||
|
}
|
||||||
|
@case ('ANYONE_OR_SURVIVOR') {
|
||||||
|
Anyone or Survivor
|
||||||
|
}
|
||||||
|
@default {
|
||||||
|
{{ fixedDepositAccountData.modeOfOperation || '—' }}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (fixedDepositAccountData.jointHolders?.length) {
|
||||||
|
<div class="preview-soa-table-wrap">
|
||||||
|
<table class="preview-soa-tbl">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="preview-soa-th">Sr.</th>
|
||||||
|
<th class="preview-soa-th">Holder Name</th>
|
||||||
|
<th class="preview-soa-th">CIF No.</th>
|
||||||
|
<th class="preview-soa-th">Ownership %</th>
|
||||||
|
<th class="preview-soa-th">Signing</th>
|
||||||
|
<th class="preview-soa-th">Role</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@for (holder of fixedDepositAccountData.jointHolders; track holder.clientId; let i = $index) {
|
||||||
|
<tr [class.preview-soa-tr--primary]="holder.isPrimary">
|
||||||
|
<td class="preview-soa-td">{{ i + 1 }}</td>
|
||||||
|
<td class="preview-soa-td">
|
||||||
|
{{ holder.displayName || holder.clientName || '—' }}
|
||||||
|
@if (holder.isPrimary) {
|
||||||
|
<span class="preview-soa-primary-tag">Primary</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td class="preview-soa-td preview-mono">{{ holder.cifNumber || holder.clientCif || '—' }}</td>
|
||||||
|
<td class="preview-soa-td">{{ holder.sharePercentage ?? '—' }}%</td>
|
||||||
|
<td class="preview-soa-td">{{ holder.isMandatory ? 'Mandatory' : 'Optional' }}</td>
|
||||||
|
<td class="preview-soa-td">{{ holder.isPrimary ? 'Primary Account Holder' : 'Co-Holder' }}</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
<ng-container matColumnDef="chargeCalculationType">
|
@if (fixedDepositAccountData.accountStatus === 'INDIVIDUAL_MALE' && fixedDepositAccountData.specialCategory) {
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
|
<div class="preview-grid preview-grid--three" style="margin-top: 12px">
|
||||||
<td mat-cell *matCellDef="let charge">
|
<div class="preview-field">
|
||||||
{{ charge.chargeCalculationType.value }}
|
<span class="preview-label">Special Category</span>
|
||||||
</td>
|
<span class="preview-value">{{ fixedDepositAccountData.specialCategory }}</span>
|
||||||
</ng-container>
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<ng-container matColumnDef="amount">
|
@if (fixedDepositAccountData.accountStatus === 'INDIVIDUAL_FEMALE' && fixedDepositAccountData.weakerSection) {
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
<div class="preview-grid preview-grid--three" style="margin-top: 12px">
|
||||||
<td mat-cell *matCellDef="let charge">
|
<div class="preview-field">
|
||||||
{{ charge.amount }}
|
<span class="preview-label">Weaker Section</span>
|
||||||
</td>
|
<span class="preview-value">{{ fixedDepositAccountData.weakerSection }}</span>
|
||||||
</ng-container>
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<ng-container matColumnDef="date">
|
@if (fixedDepositAccountData.accountStatus === 'MINOR') {
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Date' | translate }}</th>
|
<div class="preview-grid preview-grid--three" style="margin-top: 12px">
|
||||||
<td mat-cell *matCellDef="let charge">
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Guardian Name</span>
|
||||||
|
<span class="preview-value">{{ fixedDepositAccountData.guardianName || '—' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Guardian CIF</span>
|
||||||
|
<span class="preview-value preview-mono">{{ fixedDepositAccountData.guardianCifNumber || '—' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Relationship</span>
|
||||||
|
<span class="preview-value">{{ fixedDepositAccountData.guardianRelationship || '—' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- ══ 1c. Linked Account ════════════════════════════════════════ -->
|
||||||
|
@if (fixedDepositAccountData?.linkedAccountId) {
|
||||||
|
<div class="preview-section">
|
||||||
|
<div class="preview-section__header">
|
||||||
|
<i class="fas fa-link preview-section__icon"></i>
|
||||||
|
<span class="preview-section__title">Linked Deposit Account</span>
|
||||||
|
<span class="preview-section__badge">{{ fixedDepositAccountData.linkedAccountType }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-body">
|
||||||
|
<div class="preview-grid preview-grid--three">
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Account No</span>
|
||||||
|
<span class="preview-value preview-mono">{{ fixedDepositAccountData.linkedAccountNo }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Account Type</span>
|
||||||
|
<span class="preview-value">{{ fixedDepositAccountData.linkedAccountType }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Account ID</span>
|
||||||
|
<span class="preview-value preview-mono">{{ fixedDepositAccountData.linkedAccountId }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- ══ 2. Currency & Configuration ══════════════════════════════ -->
|
||||||
|
<div class="preview-section">
|
||||||
|
<div class="preview-section__header">
|
||||||
|
<i class="fas fa-coins preview-section__icon"></i>
|
||||||
|
<span class="preview-section__title">{{ 'labels.heading.Currency' | translate }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-body">
|
||||||
|
<div class="preview-grid preview-grid--three">
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">{{ 'labels.inputs.Currency' | translate }}</span>
|
||||||
|
<span class="preview-value">{{ currencyName }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">{{ 'labels.inputs.Decimal Places' | translate }}</span>
|
||||||
|
<span class="preview-value">{{ decimalPlaces }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">{{ 'labels.inputs.Currency In Multiples Of' | translate }}</span>
|
||||||
|
<span class="preview-value">{{ inMultiplesOf }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Auto Debit Facility</span>
|
||||||
<span
|
<span
|
||||||
*ngIf="charge.chargeTimeType.value === 'Specified due date' || charge.chargeTimeType.value === 'Weekly Fee'"
|
class="preview-value preview-badge"
|
||||||
|
[class.preview-badge--yes]="fixedDepositAccountData?.autoDebitFacility"
|
||||||
>
|
>
|
||||||
{{ (charge.dueDate | dateFormat) || 'Unassigned' }}
|
{{ fixedDepositAccountData?.autoDebitFacility | yesNo }}
|
||||||
</span>
|
|
||||||
<span *ngIf="charge.chargeTimeType.value === 'Monthly Fee' || charge.chargeTimeType.value === 'Annual Fee'">
|
|
||||||
{{ charge.feeOnMonthDay ? ([2000].concat(charge.feeOnMonthDay) | dateFormat: 'dd MMMM') : 'Unassigned' }}
|
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ══ 2b. Dormancy Monitoring ═══════════════════════════════════ -->
|
||||||
|
@if (fixedDepositsAccountProductTemplate?.dormancyAlert) {
|
||||||
|
<div class="preview-section">
|
||||||
|
<div class="preview-section__header">
|
||||||
|
<i class="fas fa-moon preview-section__icon"></i>
|
||||||
|
<span class="preview-section__title">Dormancy Monitoring</span>
|
||||||
|
<span class="preview-section__badge preview-section__badge--active">Monitoring: Active</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-body">
|
||||||
|
<div class="preview-dormancy-timeline">
|
||||||
|
<div class="preview-dorm-step">
|
||||||
|
<div class="preview-dorm-step__dot preview-dorm-step__dot--inactive"></div>
|
||||||
|
<div class="preview-dorm-step__body">
|
||||||
|
<span class="preview-dorm-step__label">Inactive</span>
|
||||||
|
<span class="preview-dorm-step__days">
|
||||||
|
After {{ fixedDepositsAccountProductTemplate?.daysToInactiveFromProduct || 30 }} days of no transaction
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="preview-dorm-connector"></div>
|
||||||
|
<div class="preview-dorm-step">
|
||||||
|
<div class="preview-dorm-step__dot preview-dorm-step__dot--dormant"></div>
|
||||||
|
<div class="preview-dorm-step__body">
|
||||||
|
<span class="preview-dorm-step__label">Dormant</span>
|
||||||
|
<span class="preview-dorm-step__days">
|
||||||
|
{{ fixedDepositsAccountProductTemplate?.daysToDormancyFromProduct || 60 }} days after inactive
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="preview-dorm-connector"></div>
|
||||||
|
<div class="preview-dorm-step">
|
||||||
|
<div class="preview-dorm-step__dot preview-dorm-step__dot--escheat"></div>
|
||||||
|
<div class="preview-dorm-step__body">
|
||||||
|
<span class="preview-dorm-step__label">Escheat</span>
|
||||||
|
<span class="preview-dorm-step__days">
|
||||||
|
{{ fixedDepositsAccountProductTemplate?.daysToEscheatFromProduct || 90 }} days after dormant
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- ══ 2c. Nominee Details ══════════════════════════════════════ -->
|
||||||
|
@if (fixedDepositAccountData?.nominee) {
|
||||||
|
<div class="preview-section">
|
||||||
|
<div class="preview-section__header">
|
||||||
|
<i class="fas fa-user-shield preview-section__icon"></i>
|
||||||
|
<span class="preview-section__title">Nominee Details</span>
|
||||||
|
<span class="preview-section__badge">{{
|
||||||
|
fixedDepositAccountData.nominee.mode === 'linked' ? 'Linked Client' : 'Manual Entry'
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-body">
|
||||||
|
<div class="preview-grid preview-grid--three">
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Full Name</span>
|
||||||
|
<span class="preview-value">{{
|
||||||
|
fixedDepositAccountData.nominee.fullName || fixedDepositAccountData.nominee.displayName
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Relationship</span>
|
||||||
|
<span class="preview-value">{{ fixedDepositAccountData.nominee.relationship }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Nominee Share (%)</span>
|
||||||
|
<span class="preview-value">{{ fixedDepositAccountData.nominee.nomineeShare }}%</span>
|
||||||
|
</div>
|
||||||
|
@if (fixedDepositAccountData.nominee.dateOfBirth) {
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Date of Birth</span>
|
||||||
|
<span class="preview-value">{{ fixedDepositAccountData.nominee.dateOfBirth | dateFormat }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (fixedDepositAccountData.nominee.nationality) {
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Nationality</span>
|
||||||
|
<span class="preview-value">{{ fixedDepositAccountData.nominee.nationality }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (fixedDepositAccountData.nominee.mobileNo) {
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Mobile Number</span>
|
||||||
|
<span class="preview-value preview-mono">{{ fixedDepositAccountData.nominee.mobileNo }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- ══ 5. Interest Rate Chart ═══════════════════════════════════ -->
|
||||||
|
<div class="preview-section">
|
||||||
|
<div class="preview-section__header">
|
||||||
|
<i class="fas fa-chart-line preview-section__icon"></i>
|
||||||
|
<span class="preview-section__title">{{ 'labels.heading.Interest Rate Chart' | translate }}</span>
|
||||||
|
@if (selectedTenure) {
|
||||||
|
<span class="preview-section__badge preview-section__badge--tenure">
|
||||||
|
<i class="fas fa-calendar-check"></i> Selected Tenure: {{ selectedTenureLabel }}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="preview-body">
|
||||||
|
<!-- Selected Tenure + Rate Banner -->
|
||||||
|
@if (selectedTenure && matchedSlabIndex >= 0) {
|
||||||
|
<div class="preview-tenure-banner">
|
||||||
|
<div class="preview-tenure-banner__left">
|
||||||
|
<i class="fas fa-calendar-check"></i>
|
||||||
|
<div>
|
||||||
|
<span class="preview-tenure-banner__label">Your FD Tenure</span>
|
||||||
|
<span class="preview-tenure-banner__value">{{ selectedTenureLabel }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="preview-tenure-banner__divider"></div>
|
||||||
|
<div class="preview-tenure-banner__right">
|
||||||
|
<span class="preview-tenure-banner__rate-label">Applicable Interest Rate</span>
|
||||||
|
<span class="preview-tenure-banner__rate"
|
||||||
|
>{{ interestRateChartData[matchedSlabIndex]?.annualInterestRate | formatNumber }}%</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="preview-tenure-banner__slab">
|
||||||
|
<i class="fas fa-layer-group"></i>
|
||||||
|
Slab: {{ interestRateChartData[matchedSlabIndex]?.fromPeriod }}–{{
|
||||||
|
interestRateChartData[matchedSlabIndex]?.toPeriod ?? '∞'
|
||||||
|
}}
|
||||||
|
{{ interestRateChartData[matchedSlabIndex]?.periodType?.value }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="preview-grid preview-grid--three" style="margin-bottom: 1rem">
|
||||||
|
@if (fixedDepositsAccountProductTemplate?.accountChart?.name) {
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Name</span>
|
||||||
|
<span class="preview-value">{{ fixedDepositsAccountProductTemplate.accountChart.name }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Valid From</span>
|
||||||
|
<span class="preview-value">{{
|
||||||
|
fixedDepositsAccountProductTemplate?.accountChart?.fromDate | dateFormat
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Primary Grouping by Amount</span>
|
||||||
<span
|
<span
|
||||||
*ngIf="
|
class="preview-value preview-badge"
|
||||||
!(
|
[class.preview-badge--yes]="fixedDepositsAccountProductTemplate?.accountChart?.isPrimaryGroupingByAmount"
|
||||||
charge.chargeTimeType.value === 'Monthly Fee' ||
|
|
||||||
charge.chargeTimeType.value === 'Annual Fee' ||
|
|
||||||
charge.chargeTimeType.value === 'Specified due date' ||
|
|
||||||
charge.chargeTimeType.value === 'Weekly Fee'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
{{ 'labels.inputs.N/A' | translate }}
|
{{ fixedDepositsAccountProductTemplate?.accountChart?.isPrimaryGroupingByAmount | yesNo }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</div>
|
||||||
</ng-container>
|
</div>
|
||||||
|
|
||||||
<ng-container matColumnDef="repaymentsEvery">
|
<table
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Repayments Every' | translate }}</th>
|
class="mat-elevation-z1 irc-table preview-table"
|
||||||
<td mat-cell *matCellDef="let charge">
|
mat-table
|
||||||
{{ charge.feeInterval || 'Not Provided' }}
|
[dataSource]="interestRateChartData"
|
||||||
</td>
|
multiTemplateDataRows
|
||||||
</ng-container>
|
>
|
||||||
|
<ng-container matColumnDef="period">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Period' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let chartSlab">
|
||||||
|
{{ chartSlab.fromPeriod }} - {{ chartSlab.toPeriod }} {{
|
||||||
|
chartSlab.periodType?.value | translateKey: 'catalogs'
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="chargeTimeType">
|
<ng-container matColumnDef="amountRange">
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount Range' | translate }}</th>
|
||||||
<td mat-cell *matCellDef="let charge">
|
<td mat-cell *matCellDef="let chartSlab">
|
||||||
{{ charge.chargeTimeType.value }}
|
{{ chartSlab.amountRangeFrom | formatNumber }} - {{ chartSlab.amountRangeTo | formatNumber }}
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<tr mat-header-row *matHeaderRowDef="chargesDisplayedColumns"></tr>
|
<ng-container matColumnDef="interest">
|
||||||
<tr mat-row *matRowDef="let row; columns: chargesDisplayedColumns"></tr>
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
|
||||||
</table>
|
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.annualInterestRate | formatNumber }} %</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="description">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Description' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.description }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="actions">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let chartSlab; let chartSlabIndex = dataIndex">
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
color="primary"
|
||||||
|
(click)="expandChartSlabIndex = expandChartSlabIndex === chartSlabIndex ? null : chartSlabIndex"
|
||||||
|
>
|
||||||
|
<fa-icon [icon]="expandChartSlabIndex === chartSlabIndex ? 'eye-slash' : 'eye'" class="m-r-10"></fa-icon>
|
||||||
|
{{
|
||||||
|
(expandChartSlabIndex === chartSlabIndex
|
||||||
|
? 'labels.buttons.Hide Incentives'
|
||||||
|
: 'labels.buttons.View Incentives'
|
||||||
|
) | translate
|
||||||
|
}}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="incentives">
|
||||||
|
<td
|
||||||
|
mat-cell
|
||||||
|
*matCellDef="let chartSlab; let chartSlabIndex = dataIndex"
|
||||||
|
[attr.colspan]="chartSlabsDisplayedColumns?.length"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="layout-row-wrap flex-100 incentives"
|
||||||
|
[@expandChartSlab]="chartSlabIndex === expandChartSlabIndex ? 'expanded' : 'collapsed'"
|
||||||
|
>
|
||||||
|
<mat-card class="layout-row-wrap flex-100">
|
||||||
|
<h4 class="m-b-10 flex-13">{{ 'labels.heading.Incentives' | translate }}</h4>
|
||||||
|
<table
|
||||||
|
class="flex-100 mat-elevation-z1"
|
||||||
|
mat-table
|
||||||
|
[dataSource]="chartSlab.incentives"
|
||||||
|
*ngIf="chartSlab.incentives?.length"
|
||||||
|
>
|
||||||
|
<ng-container matColumnDef="entityType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Entity Type' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let incentive">{{ incentive.entityType?.value }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="attributeName">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Name' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let incentive">{{ incentive.attributeName?.value }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="conditionType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Condition Type' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let incentive">{{ incentive.conditionType?.value | titlecase }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="attributeValue">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Value' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let incentive">{{ incentive.attributeValueDesc }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="incentiveType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Incentive Type' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let incentive">{{ incentive.incentiveType?.value }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="amount">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let incentive">{{ incentive.amount | formatNumber }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr mat-header-row *matHeaderRowDef="incentivesDisplayedColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: incentivesDisplayedColumns"></tr>
|
||||||
|
</table>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns"></tr>
|
||||||
|
<tr
|
||||||
|
mat-row
|
||||||
|
*matRowDef="let row; columns: chartSlabsDisplayedColumns; let i = dataIndex"
|
||||||
|
[class.preview-slab-row--matched]="i === matchedSlabIndex"
|
||||||
|
></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: chartSlabsIncentivesDisplayedColumns" class="incentives-row"></tr>
|
||||||
|
</table>
|
||||||
|
@if (selectedTenure && matchedSlabIndex >= 0) {
|
||||||
|
<div class="preview-slab-legend">
|
||||||
|
<span class="preview-slab-legend__dot"></span>
|
||||||
|
Highlighted row is the slab applicable for your selected tenure of <strong>{{ selectedTenureLabel }}</strong>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+477
-16
@@ -6,35 +6,277 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
table {
|
// ── Wrapper ──────────────────────────────────────────────────────────────────
|
||||||
width: 100%;
|
.preview-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.25rem;
|
||||||
|
padding: 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-elevation-z1 {
|
// ── Section card ─────────────────────────────────────────────────────────────
|
||||||
margin: 1em 0 1.5em;
|
.preview-section {
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 1px 4px rgb(0 0 0 / 5%);
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
padding: 0.6rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #0ea5e9;
|
||||||
|
width: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1e293b;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
background: #dbeafe;
|
||||||
|
color: #1d4ed8;
|
||||||
|
border: 1px solid #bfdbfe;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
background: #dcfce7 !important;
|
||||||
|
color: #15803d !important;
|
||||||
|
border-color: #bbf7d0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h2,
|
// ── Body ─────────────────────────────────────────────────────────────────────
|
||||||
h3,
|
.preview-body {
|
||||||
h4 {
|
padding: 1rem 1.25rem;
|
||||||
margin: 0;
|
}
|
||||||
|
|
||||||
|
// ── Subsection (inside Settings) ─────────────────────────────────────────────
|
||||||
|
.preview-subsection {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 0.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-divider {
|
||||||
|
margin: 0.75rem 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Grid ─────────────────────────────────────────────────────────────────────
|
||||||
|
.preview-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.85rem 1.5rem;
|
||||||
|
|
||||||
|
&--three {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
|
||||||
|
@media (width <= 768px) {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width <= 480px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Field ─────────────────────────────────────────────────────────────────────
|
||||||
|
.preview-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-label {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-value {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #1e293b;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
// ── Yes/No badge ─────────────────────────────────────────────────────────────
|
||||||
margin: 0.5em 0;
|
.preview-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.15rem 0.6rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
background: #fef2f2;
|
||||||
|
color: #dc2626;
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
&--yes {
|
||||||
|
background: #f0fdf4;
|
||||||
|
color: #15803d;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--warn {
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #92400e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mat-divider {
|
// ── Section badge variant for tenure ─────────────────────────────────────────
|
||||||
margin: 0 0 0.5em;
|
.preview-section__badge--tenure {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
background: #dcfce7;
|
||||||
|
color: #16a34a;
|
||||||
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.margin-t {
|
// ── Tenure + Rate banner ──────────────────────────────────────────────────────
|
||||||
margin-top: 1em;
|
.preview-tenure-banner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
background: #f0fdf4;
|
||||||
|
border: 1.5px solid #86efac;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
&__left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
> i {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #16a34a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
display: block;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__value {
|
||||||
|
display: block;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #0f172a;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__divider {
|
||||||
|
width: 1px;
|
||||||
|
height: 40px;
|
||||||
|
background: #86efac;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rate-label {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rate {
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #16a34a;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__slab {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #374151;
|
||||||
|
margin-left: auto;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: #16a34a;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.margin-b {
|
// ── Matched slab row highlight ────────────────────────────────────────────────
|
||||||
margin-bottom: 0.25em;
|
.preview-slab-row--matched td {
|
||||||
|
background: #dcfce7 !important;
|
||||||
|
border-left: 3px solid #16a34a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Legend below table ────────────────────────────────────────────────────────
|
||||||
|
.preview-slab-legend {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #4b5563;
|
||||||
|
|
||||||
|
&__dot {
|
||||||
|
display: inline-block;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: #dcfce7;
|
||||||
|
border: 2px solid #16a34a;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Tables ───────────────────────────────────────────────────────────────────
|
||||||
|
.preview-table {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.irc-table {
|
.irc-table {
|
||||||
@@ -56,3 +298,222 @@ mat-divider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.margin-t {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Preview Tenure Result Card ─────────────────────────────────────────────────
|
||||||
|
.preview-tenure-result {
|
||||||
|
border: 1px solid #e0f2fe;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&__row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.65rem 1rem;
|
||||||
|
border-bottom: 1px solid #f0f9ff;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--highlight {
|
||||||
|
background: #f0f9ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rate {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #0369a1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Misc ──────────────────────────────────────────────────────────────────────
|
||||||
|
.preview-muted {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-mono {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Status of Account (SOA) ───────────────────────────────────────────────────
|
||||||
|
.preview-soa-status-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-soa-tile {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 20px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #1d4ed8;
|
||||||
|
border: 2px solid #bfdbfe;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-soa-table-wrap {
|
||||||
|
margin-top: 14px;
|
||||||
|
overflow-x: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-soa-tbl {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-soa-th {
|
||||||
|
background: #f8fafc;
|
||||||
|
padding: 8px 12px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #475569;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-soa-td {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-bottom: 1px solid #f1f5f9;
|
||||||
|
color: #0f172a;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-soa-tr--primary {
|
||||||
|
background: #f0f9ff;
|
||||||
|
|
||||||
|
.preview-soa-td {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-soa-primary-tag {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 6px;
|
||||||
|
padding: 1px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
background: #dbeafe;
|
||||||
|
color: #1d4ed8;
|
||||||
|
border: 1px solid #bfdbfe;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Dormancy Monitoring Timeline ──────────────────────────────────────────────
|
||||||
|
.preview-dormancy-timeline {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
margin: 1.25rem 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-dorm-step {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
flex: 0 0 140px;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
&__dot {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 3px solid #e2e8f0;
|
||||||
|
background: #f8fafc;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 0 0 4px #fff;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
&--inactive {
|
||||||
|
border-color: #f59e0b;
|
||||||
|
background: #fffbeb;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #f59e0b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--dormant {
|
||||||
|
border-color: #ef4444;
|
||||||
|
background: #fef2f2;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #ef4444;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--escheat {
|
||||||
|
border-color: #7c3aed;
|
||||||
|
background: #f5f3ff;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #7c3aed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__days {
|
||||||
|
font-size: 10.5px;
|
||||||
|
color: #64748b;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-dorm-connector {
|
||||||
|
flex: 0 0 40px;
|
||||||
|
height: 3px;
|
||||||
|
background: linear-gradient(90deg, #e2e8f0, #cbd5e1);
|
||||||
|
margin-top: 16px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|||||||
+52
-1
@@ -76,6 +76,8 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
|
|||||||
@Input() fixedDepositsAccountProductTemplate: any;
|
@Input() fixedDepositsAccountProductTemplate: any;
|
||||||
/** Fixed Deposits Account Data */
|
/** Fixed Deposits Account Data */
|
||||||
@Input() fixedDepositAccountData: any;
|
@Input() fixedDepositAccountData: any;
|
||||||
|
/** Tenure selected from the interest rate chart step */
|
||||||
|
@Input() selectedTenure: { depositPeriod: number; depositPeriodFrequencyId: number } | null = null;
|
||||||
|
|
||||||
/** Charges Displayed Columns */
|
/** Charges Displayed Columns */
|
||||||
chargesDisplayedColumns: string[] = [
|
chargesDisplayedColumns: string[] = [
|
||||||
@@ -110,6 +112,28 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
|
|||||||
/** Expand Chart Slab Index used in the view */
|
/** Expand Chart Slab Index used in the view */
|
||||||
expandChartSlabIndex: number;
|
expandChartSlabIndex: number;
|
||||||
|
|
||||||
|
readonly fdTypeLabels: Record<string, string> = {
|
||||||
|
SINGLE: 'FD Single (Days < 1 Year)',
|
||||||
|
DOUBLE: 'FD Double (≥ 1 Year)',
|
||||||
|
SPECIAL: 'FD Special (Monthly Interest Payout)'
|
||||||
|
};
|
||||||
|
|
||||||
|
getFdTypeLabel(fdType: string): string {
|
||||||
|
return this.fdTypeLabels[fdType] || fdType;
|
||||||
|
}
|
||||||
|
|
||||||
|
get currencyName(): string {
|
||||||
|
return this.fixedDepositsAccountProductTemplate?.currency?.name || '—';
|
||||||
|
}
|
||||||
|
|
||||||
|
get decimalPlaces(): string | number {
|
||||||
|
return this.fixedDepositsAccountProductTemplate?.currency?.decimalPlaces ?? '—';
|
||||||
|
}
|
||||||
|
|
||||||
|
get inMultiplesOf(): string | number {
|
||||||
|
return this.fixedDepositsAccountProductTemplate?.currency?.inMultiplesOf ?? '—';
|
||||||
|
}
|
||||||
|
|
||||||
/** Form submission event */
|
/** Form submission event */
|
||||||
@Output() submitEvent = new EventEmitter();
|
@Output() submitEvent = new EventEmitter();
|
||||||
|
|
||||||
@@ -117,7 +141,34 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
|
|||||||
|
|
||||||
ngOnChanges() {
|
ngOnChanges() {
|
||||||
if (this.fixedDepositsAccountProductTemplate) {
|
if (this.fixedDepositsAccountProductTemplate) {
|
||||||
this.interestRateChartData = this.fixedDepositsAccountProductTemplate.accountChart.chartSlabs;
|
this.interestRateChartData = this.fixedDepositsAccountProductTemplate.accountChart?.chartSlabs ?? [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get matchedSlabIndex(): number {
|
||||||
|
if (!this.selectedTenure || !this.interestRateChartData?.length) return -1;
|
||||||
|
const { depositPeriod, depositPeriodFrequencyId } = this.selectedTenure;
|
||||||
|
// Convert selected tenure to days for comparison
|
||||||
|
const toDays = (v: number, id: number) => {
|
||||||
|
if (id === 2) return v * 30; // Months
|
||||||
|
if (id === 3) return v * 365; // Years
|
||||||
|
return v; // Days (id=0)
|
||||||
|
};
|
||||||
|
const enteredDays = toDays(depositPeriod, depositPeriodFrequencyId);
|
||||||
|
|
||||||
|
return this.interestRateChartData.findIndex((slab: any) => {
|
||||||
|
const periodTypeValue = (slab.periodType?.value || '').toLowerCase();
|
||||||
|
const slabId = periodTypeValue.includes('month') ? 2 : periodTypeValue.includes('year') ? 3 : 0;
|
||||||
|
const fromDays = toDays(slab.fromPeriod ?? 0, slabId);
|
||||||
|
const toDaysVal = slab.toPeriod != null ? toDays(slab.toPeriod, slabId) : Infinity;
|
||||||
|
return enteredDays >= fromDays && enteredDays <= toDaysVal;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get selectedTenureLabel(): string {
|
||||||
|
if (!this.selectedTenure) return '';
|
||||||
|
const { depositPeriod, depositPeriodFrequencyId } = this.selectedTenure;
|
||||||
|
const unitMap: Record<number, string> = { 0: 'Days', 2: 'Months', 3: 'Years' };
|
||||||
|
return `${depositPeriod} ${unitMap[depositPeriodFrequencyId] ?? ''}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,12 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// IMPORTANT: THIS FILE IS AUTO GENERATED! DO NOT MANUALLY EDIT OR CHECKIN!
|
// IMPORTANT: THIS FILE IS AUTO GENERATED! DO NOT MANUALLY EDIT OR CHECKIN!
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
export default {
|
export default {
|
||||||
'mifos_x': {
|
'mifos_x': {
|
||||||
'version': '260504',
|
'version': '260507',
|
||||||
'hash': 'e9d2f4b5'
|
'hash': 'b0cffd38'
|
||||||
},
|
},
|
||||||
'allow_switching_backend_instance': true
|
'allow_switching_backend_instance': true
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user