update code current account ,saving account and rd account
This commit is contained in:
+43
-56
@@ -35,7 +35,7 @@
|
||||
</ng-template>
|
||||
|
||||
<!-- Step 1: Details -->
|
||||
<mat-step [stepControl]="currentAccountDetailsForm">
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.DETAILS' | translate }}</ng-template>
|
||||
|
||||
<mifosx-current-account-details-step
|
||||
@@ -45,64 +45,48 @@
|
||||
</mifosx-current-account-details-step>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 2: Currency -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>CURRENCY</ng-template>
|
||||
@if (currentAccountProductTemplate) {
|
||||
<!-- Step 2: Currency -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>CURRENCY</ng-template>
|
||||
|
||||
<mifosx-current-account-currency-step
|
||||
[currentAccountProductTemplate]="currentAccountProductTemplate"
|
||||
></mifosx-current-account-currency-step>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 3: Term (Interest Rate) -->
|
||||
<mat-step [stepControl]="currentAccountTermForm">
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.TERMS' | translate }}</ng-template>
|
||||
|
||||
<mifosx-current-account-term-step
|
||||
[currentAccountProductTemplate]="currentAccountProductTemplate"
|
||||
></mifosx-current-account-term-step>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 4: Settings -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>SETTINGS</ng-template>
|
||||
|
||||
<mifosx-current-account-settings-step
|
||||
[currentAccountProductTemplate]="currentAccountProductTemplate"
|
||||
></mifosx-current-account-settings-step>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 5: Digital -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>DIGITAL</ng-template>
|
||||
|
||||
<mifosx-current-account-digital-step
|
||||
[currentAccountProductTemplate]="currentAccountProductTemplate"
|
||||
></mifosx-current-account-digital-step>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 6: Charges -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.CHARGES' | translate }}</ng-template>
|
||||
|
||||
<mifosx-current-account-charges-step
|
||||
[currentAccountProductTemplate]="currentAccountProductTemplate"
|
||||
[currentAccountTemplate]="currentAccountTemplate"
|
||||
[currencyCode]="currentAccountTermsForm.get('currencyCode')"
|
||||
>
|
||||
</mifosx-current-account-charges-step>
|
||||
|
||||
<!-- Hidden terms step to keep currencyCode form populated -->
|
||||
<div style="display: none">
|
||||
<mifosx-current-account-terms-step
|
||||
<mifosx-current-account-currency-step
|
||||
[currentAccountProductTemplate]="currentAccountProductTemplate"
|
||||
[currentAccountTemplate]="currentAccountTemplate"
|
||||
></mifosx-current-account-terms-step>
|
||||
</div>
|
||||
</mat-step>
|
||||
></mifosx-current-account-currency-step>
|
||||
|
||||
<!-- Step 7: Preview -->
|
||||
@if (currentAccountFormValid) {
|
||||
<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: Digital -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>DIGITAL</ng-template>
|
||||
|
||||
<mifosx-current-account-digital-step
|
||||
[currentAccountProductTemplate]="currentAccountProductTemplate"
|
||||
></mifosx-current-account-digital-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 4: Preview -->
|
||||
<mat-step completed>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.PREVIEW' | translate }}</ng-template>
|
||||
<mifosx-current-account-preview-step
|
||||
@@ -112,6 +96,9 @@
|
||||
[currentAccountSettingsForm]="currentAccountSettingsForm"
|
||||
[currentAccountTermsForm]="currentAccountTermsForm"
|
||||
[currentAccount]="currentAccount"
|
||||
[currentAccountDigital]="currentAccountDigital"
|
||||
[currentAccountNominee]="currentAccountNominee"
|
||||
[currentAccountCurrencyFlags]="currentAccountCurrencyFlags"
|
||||
(submitEvent)="submit()"
|
||||
>
|
||||
</mifosx-current-account-preview-step>
|
||||
|
||||
@@ -27,11 +27,14 @@
|
||||
.mat-step-content {
|
||||
padding: 1.5rem;
|
||||
margin: 1rem 0;
|
||||
overflow: visible !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Form field spacing within steps
|
||||
:host ::ng-deep mat-step-content {
|
||||
overflow: visible !important;
|
||||
|
||||
mat-form-field {
|
||||
margin-bottom: 1rem;
|
||||
width: 100%;
|
||||
|
||||
+41
-60
@@ -12,10 +12,6 @@ import { Router, ActivatedRoute } from '@angular/router';
|
||||
|
||||
/** Custom Components */
|
||||
import { CurrentAccountDetailsStepComponent } from '../current-account-stepper/current-account-details-step/current-account-details-step.component';
|
||||
import { CurrentAccountTermsStepComponent } from '../current-account-stepper/current-account-terms-step/current-account-terms-step.component';
|
||||
import { CurrentAccountTermStepComponent } from '../current-account-stepper/current-account-term-step/current-account-term-step.component';
|
||||
import { CurrentAccountSettingsStepComponent } from '../current-account-stepper/current-account-settings-step/current-account-settings-step.component';
|
||||
import { CurrentAccountChargesStepComponent } from '../current-account-stepper/current-account-charges-step/current-account-charges-step.component';
|
||||
import { CurrentAccountCurrencyStepComponent } from '../current-account-stepper/current-account-currency-step/current-account-currency-step.component';
|
||||
import { CurrentAccountDigitalStepComponent } from '../current-account-stepper/current-account-digital-step/current-account-digital-step.component';
|
||||
|
||||
@@ -23,10 +19,18 @@ import { CurrentAccountDigitalStepComponent } from '../current-account-stepper/c
|
||||
import { CurrentAccountsService } from '../current-accounts.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper';
|
||||
import {
|
||||
MatStepper,
|
||||
MatStepperIcon,
|
||||
MatStep,
|
||||
MatStepLabel,
|
||||
MatStepperNext,
|
||||
MatStepperPrevious
|
||||
} from '@angular/material/stepper';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { CurrentAccountPreviewStepComponent } from '../current-account-stepper/current-account-preview-step/current-account-preview-step.component';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
/**
|
||||
* Create Current Account Component
|
||||
@@ -39,16 +43,14 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatStepper,
|
||||
MatStepperIcon,
|
||||
MatStepperNext,
|
||||
MatStepperPrevious,
|
||||
FaIconComponent,
|
||||
MatStep,
|
||||
MatStepLabel,
|
||||
CurrentAccountDetailsStepComponent,
|
||||
CurrentAccountCurrencyStepComponent,
|
||||
CurrentAccountTermStepComponent,
|
||||
CurrentAccountSettingsStepComponent,
|
||||
CurrentAccountDigitalStepComponent,
|
||||
CurrentAccountTermsStepComponent,
|
||||
CurrentAccountChargesStepComponent,
|
||||
CurrentAccountPreviewStepComponent
|
||||
]
|
||||
})
|
||||
@@ -64,21 +66,14 @@ export class CreateCurrentAccountComponent {
|
||||
/** Current Account Product Template */
|
||||
currentAccountProductTemplate: any;
|
||||
|
||||
/** Current Account Details Step */
|
||||
@ViewChild(CurrentAccountDetailsStepComponent, { static: true })
|
||||
currentAccountDetailsStep: CurrentAccountDetailsStepComponent;
|
||||
/** Current Account Term Step (new) */
|
||||
@ViewChild(CurrentAccountTermStepComponent, { static: true })
|
||||
currentAccountTermStep: CurrentAccountTermStepComponent;
|
||||
/** Current Account Settings Step */
|
||||
@ViewChild(CurrentAccountSettingsStepComponent, { static: true })
|
||||
currentAccountSettingsStep: CurrentAccountSettingsStepComponent;
|
||||
/** Current Account Terms Step (old — kept for currencyCode access) */
|
||||
@ViewChild(CurrentAccountTermsStepComponent, { static: true })
|
||||
currentAccountTermsStep: CurrentAccountTermsStepComponent;
|
||||
/** Current Account Charges Step */
|
||||
@ViewChild(CurrentAccountChargesStepComponent, { static: true })
|
||||
currentAccountChargesStep: CurrentAccountChargesStepComponent;
|
||||
|
||||
@ViewChild(CurrentAccountCurrencyStepComponent)
|
||||
currentAccountCurrencyStep: CurrentAccountCurrencyStepComponent;
|
||||
|
||||
@ViewChild(CurrentAccountDigitalStepComponent)
|
||||
currentAccountDigitalStep: CurrentAccountDigitalStepComponent;
|
||||
|
||||
constructor() {
|
||||
this.route.data.subscribe((data: { currentAccountTemplate: any }) => {
|
||||
@@ -100,43 +95,36 @@ export class CreateCurrentAccountComponent {
|
||||
return this.currentAccountDetailsStep.currentAccountDetailsForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves new term form (interest rate).
|
||||
*/
|
||||
get currentAccountTermForm() {
|
||||
return this.currentAccountTermStep.currentAccountTermForm;
|
||||
/** Stub forms needed by preview step inputs */
|
||||
get currentAccountTermForm(): FormGroup {
|
||||
return new FormGroup({});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves settings form.
|
||||
*/
|
||||
get currentAccountSettingsForm() {
|
||||
return this.currentAccountSettingsStep.currentAccountSettingsForm;
|
||||
get currentAccountSettingsForm(): FormGroup {
|
||||
return new FormGroup({});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves old terms form — still used for currencyCode by charges step.
|
||||
*/
|
||||
get currentAccountTermsForm() {
|
||||
return this.currentAccountTermsStep.currentAccountTermsForm;
|
||||
get currentAccountTermsForm(): FormGroup {
|
||||
return new FormGroup({});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks validity of overall current account form.
|
||||
*/
|
||||
get currentAccountFormValid() {
|
||||
return this.currentAccountDetailsForm.valid && this.currentAccountTermForm.valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves current account object.
|
||||
*/
|
||||
get currentAccount() {
|
||||
return { ...this.currentAccountDetailsStep.currentAccountDetails };
|
||||
}
|
||||
|
||||
get currentAccountDigital() {
|
||||
return this.currentAccountDigitalStep?.currentAccountDigital ?? null;
|
||||
}
|
||||
|
||||
get currentAccountNominee() {
|
||||
return this.currentAccountCurrencyStep?.nomineeData ?? null;
|
||||
}
|
||||
|
||||
get currentAccountCurrencyFlags() {
|
||||
return {
|
||||
...this.currentAccountDetailsStep.currentAccountDetails,
|
||||
...this.currentAccountTermStep.currentAccountTerms,
|
||||
...this.currentAccountSettingsStep.currentAccountSettings,
|
||||
...this.currentAccountChargesStep.currentAccountCharges
|
||||
balanceSwapOption: this.currentAccountCurrencyStep?.balanceSwapOption ?? false,
|
||||
msme: this.currentAccountCurrencyStep?.msme ?? false,
|
||||
autoDebitFacility: this.currentAccountCurrencyStep?.autoDebitFacility ?? false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -149,15 +137,8 @@ export class CreateCurrentAccountComponent {
|
||||
const monthDayFormat = 'dd MMMM';
|
||||
const currentAccount = {
|
||||
...this.currentAccount,
|
||||
charges: this.currentAccount.charges.map((charge: any) => ({
|
||||
chargeId: charge.id,
|
||||
amount: charge.amount,
|
||||
dueDate: charge.dueDate ? this.dateUtils.formatDate(charge.dueDate, dateFormat) : charge.dueDate,
|
||||
feeOnMonthDay: charge.feeOnMonthDay
|
||||
? this.dateUtils.formatDate(charge.feeOnMonthDay, monthDayFormat)
|
||||
: charge.feeOnMonthDay,
|
||||
feeInterval: charge.feeInterval
|
||||
})),
|
||||
...this.currentAccountCurrencyStep.currencyStepData,
|
||||
charges: [],
|
||||
submittedOnDate: this.dateUtils.formatDate(this.currentAccount.submittedOnDate, dateFormat),
|
||||
dateFormat,
|
||||
monthDayFormat,
|
||||
|
||||
+30
-30
@@ -26,6 +26,30 @@
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="sac-alert sac-alert--editable">
|
||||
<div class="sac-alert__content">
|
||||
<h4>Balance Swap Option</h4>
|
||||
<p>Allows balance to be swapped or transferred between linked accounts as per product configuration.</p>
|
||||
</div>
|
||||
<mat-slide-toggle [(ngModel)]="balanceSwapOption" color="primary"></mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<div class="sac-alert sac-alert--editable">
|
||||
<div class="sac-alert__content">
|
||||
<h4>MSME</h4>
|
||||
<p>Account is categorized under the Micro, Small and Medium Enterprises segment.</p>
|
||||
</div>
|
||||
<mat-slide-toggle [(ngModel)]="msme" color="primary"></mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<div class="sac-alert sac-alert--editable">
|
||||
<div class="sac-alert__content">
|
||||
<h4>Auto Debit Facility</h4>
|
||||
<p>Enable automatic debit instructions for recurring payments linked to this account.</p>
|
||||
</div>
|
||||
<mat-slide-toggle [(ngModel)]="autoDebitFacility" color="primary"></mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<div class="sac-alert">
|
||||
<div class="sac-alert__content">
|
||||
<h4>Dormancy Alert</h4>
|
||||
@@ -35,26 +59,9 @@
|
||||
{{ currentAccountProductTemplate?.dormancyAlert ? 'Yes' : 'No' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="sac-alert">
|
||||
<div class="sac-alert__content">
|
||||
<h4>Balance Swap Option</h4>
|
||||
<p>Allows balance to be swapped or transferred between linked accounts as per product configuration.</p>
|
||||
</div>
|
||||
<span class="sac-badge" [class.sac-badge--yes]="currentAccountProductTemplate?.balanceSwapOption">
|
||||
{{ currentAccountProductTemplate?.balanceSwapOption ? 'Yes' : 'No' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="sac-alert">
|
||||
<div class="sac-alert__content">
|
||||
<h4>MSME</h4>
|
||||
<p>Account is categorized under the Micro, Small and Medium Enterprises segment.</p>
|
||||
</div>
|
||||
<span class="sac-badge" [class.sac-badge--yes]="currentAccountProductTemplate?.msme">
|
||||
{{ currentAccountProductTemplate?.msme ? 'Yes' : 'No' }}
|
||||
</span>
|
||||
</div>
|
||||
@if (currentAccountProductTemplate?.dormancyAlert) {
|
||||
<mifosx-dormancy-alert-panel [productTemplate]="currentAccountProductTemplate"></mifosx-dormancy-alert-panel>
|
||||
}
|
||||
|
||||
<div class="sac-alert">
|
||||
<div class="sac-alert__content">
|
||||
@@ -65,15 +72,8 @@
|
||||
{{ currentAccountProductTemplate?.nominationFacility ? 'Yes' : 'No' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="sac-alert">
|
||||
<div class="sac-alert__content">
|
||||
<h4>Auto Debit Facility</h4>
|
||||
<p>Enable automatic debit instructions for recurring payments linked to this account.</p>
|
||||
</div>
|
||||
<span class="sac-badge" [class.sac-badge--yes]="currentAccountProductTemplate?.autoDebitFacility">
|
||||
{{ currentAccountProductTemplate?.autoDebitFacility ? 'Yes' : 'No' }}
|
||||
</span>
|
||||
</div>
|
||||
@if (currentAccountProductTemplate?.nominationFacility) {
|
||||
<mifosx-nominee-form (nomineeChange)="onNomineeChange($event)"></mifosx-nominee-form>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+14
@@ -114,3 +114,17 @@ $gray-900: #202124;
|
||||
border-color: #bbf7d0;
|
||||
}
|
||||
}
|
||||
|
||||
.sac-alert--editable {
|
||||
border-color: #bae6fd;
|
||||
background: #f0f9ff;
|
||||
|
||||
&:hover {
|
||||
border-color: #7dd3fc;
|
||||
box-shadow: 0 2px 8px rgb(2 132 199 / 10%);
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: #0369a1;
|
||||
}
|
||||
}
|
||||
|
||||
+43
-3
@@ -6,15 +6,55 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, Input } from '@angular/core';
|
||||
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-current-account-currency-step',
|
||||
templateUrl: './current-account-currency-step.component.html',
|
||||
styleUrls: ['./current-account-currency-step.component.scss'],
|
||||
imports: [...STANDALONE_SHARED_IMPORTS]
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
NomineeFormComponent,
|
||||
DormancyAlertPanelComponent,
|
||||
MatSlideToggle,
|
||||
FormsModule
|
||||
]
|
||||
})
|
||||
export class CurrentAccountCurrencyStepComponent {
|
||||
export class CurrentAccountCurrencyStepComponent implements OnChanges {
|
||||
@Input() currentAccountProductTemplate: any;
|
||||
|
||||
nomineeData: any = null;
|
||||
|
||||
// Editable feature toggles (pre-filled from product, overridable by user)
|
||||
balanceSwapOption = false;
|
||||
msme = false;
|
||||
autoDebitFacility = false;
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes['currentAccountProductTemplate']?.currentValue) {
|
||||
const pt = changes['currentAccountProductTemplate'].currentValue;
|
||||
this.balanceSwapOption = !!pt.balanceSwapOption;
|
||||
this.msme = !!pt.msme;
|
||||
this.autoDebitFacility = !!pt.autoDebitFacility;
|
||||
}
|
||||
}
|
||||
|
||||
onNomineeChange(event: any): void {
|
||||
this.nomineeData = event;
|
||||
}
|
||||
|
||||
/** Called by parent to collect currency step values */
|
||||
get currencyStepData() {
|
||||
return {
|
||||
balanceSwapOption: this.balanceSwapOption,
|
||||
msme: this.msme,
|
||||
autoDebitFacility: this.autoDebitFacility,
|
||||
...(this.nomineeData ? { nominee: this.nomineeData } : {})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
<!--
|
||||
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/.
|
||||
-->
|
||||
|
||||
<!-- Header -->
|
||||
<div class="abd-header">
|
||||
<div class="abd-header__icon">
|
||||
<mat-icon>person_pin</mat-icon>
|
||||
</div>
|
||||
<div class="abd-header__text">
|
||||
<h2 class="abd-header__title">Add Beneficiary / Nominee</h2>
|
||||
<p class="abd-header__sub">Register a nominee for this account as per Indian banking norms</p>
|
||||
</div>
|
||||
<button mat-icon-button class="abd-header__close" type="button" (click)="cancel()">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<mat-dialog-content>
|
||||
<form [formGroup]="form" class="abd-form">
|
||||
<!-- Mode Toggle -->
|
||||
<div class="abd-mode-toggle">
|
||||
<button
|
||||
type="button"
|
||||
class="abd-mode-btn"
|
||||
[class.abd-mode-btn--active]="mode === 'search'"
|
||||
(click)="switchMode('search')"
|
||||
>
|
||||
<mat-icon>manage_search</mat-icon> Linked Client
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="abd-mode-btn"
|
||||
[class.abd-mode-btn--active]="mode === 'manual'"
|
||||
(click)="switchMode('manual')"
|
||||
>
|
||||
<mat-icon>edit_note</mat-icon> Manual Entry
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Search Mode -->
|
||||
@if (mode === 'search') {
|
||||
<div class="abd-search-section">
|
||||
<mat-form-field appearance="outline" class="abd-full-width">
|
||||
<mat-label>Search Client by Name / CIF</mat-label>
|
||||
<mat-icon matPrefix class="abd-prefix-icon">search</mat-icon>
|
||||
<input
|
||||
matInput
|
||||
formControlName="searchText"
|
||||
(input)="onSearchInput($event.target.value)"
|
||||
autocomplete="off"
|
||||
placeholder="Type at least 2 characters..."
|
||||
/>
|
||||
@if (isSearching) {
|
||||
<mat-progress-spinner matSuffix diameter="18" mode="indeterminate"></mat-progress-spinner>
|
||||
}
|
||||
@if (selectedClient) {
|
||||
<button matSuffix mat-icon-button type="button" (click)="clearSelection()">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
@if (searchResults.length > 0) {
|
||||
<div class="abd-search-results">
|
||||
@for (client of searchResults; track client) {
|
||||
<div class="abd-result-item" (click)="selectClient(client)">
|
||||
<div class="abd-result-avatar">{{ (client.displayName || '?')[0].toUpperCase() }}</div>
|
||||
<div class="abd-result-body">
|
||||
<span class="abd-result-name">{{ client.displayName }}</span>
|
||||
<span class="abd-result-meta">
|
||||
@if (client.cifNumber) {
|
||||
<span class="abd-chip">{{ client.cifNumber }}</span>
|
||||
}
|
||||
@if (client.officeName) {
|
||||
<span class="abd-chip">{{ client.officeName }}</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<mat-icon class="abd-result-arrow">chevron_right</mat-icon>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (selectedClient) {
|
||||
<div class="abd-selected-card">
|
||||
<div class="abd-selected-avatar">{{ (selectedClient.displayName || '?')[0].toUpperCase() }}</div>
|
||||
<div class="abd-selected-info">
|
||||
<span class="abd-selected-name">{{ selectedClient.displayName }}</span>
|
||||
<span class="abd-selected-sub">{{ selectedClient.cifNumber || selectedClient.accountNumber }}</span>
|
||||
</div>
|
||||
<span class="abd-badge abd-badge--linked">Linked</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Name field (manual or auto-filled) -->
|
||||
<mat-form-field appearance="outline" class="abd-full-width abd-mt">
|
||||
<mat-label>Nominee / Beneficiary Name <span class="abd-required">*</span></mat-label>
|
||||
<input matInput formControlName="name" [readonly]="mode === 'search' && !!selectedClient" />
|
||||
<mat-error>Name is required</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<!-- Row: Relationship + DOB -->
|
||||
<div class="abd-row">
|
||||
<mat-form-field appearance="outline" class="abd-flex">
|
||||
<mat-label>Relationship <span class="abd-required">*</span></mat-label>
|
||||
<mat-select formControlName="relationship">
|
||||
@for (r of relationships; track r) {
|
||||
<mat-option [value]="r">{{ r }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>Relationship is required</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="abd-flex" (click)="dobPicker.open()">
|
||||
<mat-label>Date of Birth</mat-label>
|
||||
<input matInput [matDatepicker]="dobPicker" formControlName="dateOfBirth" readonly />
|
||||
<mat-datepicker-toggle matSuffix [for]="dobPicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #dobPicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Share % -->
|
||||
<div class="abd-share-row">
|
||||
<mat-form-field appearance="outline" class="abd-share-field">
|
||||
<mat-label>Share % <span class="abd-required">*</span></mat-label>
|
||||
<input matInput type="number" formControlName="sharePercentage" min="1" max="100" />
|
||||
<span matSuffix class="abd-pct">%</span>
|
||||
<mat-error>Enter 1–100</mat-error>
|
||||
</mat-form-field>
|
||||
<div class="abd-quick-pct">
|
||||
@for (p of [25, 33, 50, 100]; track p) {
|
||||
<button type="button" class="abd-pct-chip" (click)="form.get('sharePercentage').setValue(p)">{{ p }}%</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Minor Toggle -->
|
||||
<div class="abd-minor-toggle">
|
||||
<mat-slide-toggle formControlName="isMinor" color="primary">
|
||||
Nominee is a Minor (below 18 years)
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<!-- Guardian fields (if minor) -->
|
||||
@if (isMinor) {
|
||||
<div class="abd-guardian-box">
|
||||
<div class="abd-guardian-label">
|
||||
<mat-icon>supervisor_account</mat-icon>
|
||||
Guardian Details
|
||||
</div>
|
||||
<div class="abd-row">
|
||||
<mat-form-field appearance="outline" class="abd-flex">
|
||||
<mat-label>Guardian Name</mat-label>
|
||||
<input matInput formControlName="guardianName" />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="abd-flex">
|
||||
<mat-label>Guardian Relationship</mat-label>
|
||||
<mat-select formControlName="guardianRelationship">
|
||||
@for (r of guardianRelationships; track r) {
|
||||
<mat-option [value]="r">{{ r }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions class="abd-actions">
|
||||
<button mat-stroked-button type="button" (click)="cancel()">Cancel</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
type="button"
|
||||
class="abd-btn-add"
|
||||
[disabled]="form.get('name').invalid || form.get('relationship').invalid || form.get('sharePercentage').invalid"
|
||||
(click)="confirm()"
|
||||
>
|
||||
<mat-icon>check</mat-icon> Add Nominee
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
+415
@@ -0,0 +1,415 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
$primary: #1a56db;
|
||||
$primary-light: #eff6ff;
|
||||
$primary-border: #bfdbfe;
|
||||
$success: #057a55;
|
||||
$success-light: #f3faf7;
|
||||
$success-border: #84e1bc;
|
||||
$orange: #d03801;
|
||||
$orange-light: #fff8f1;
|
||||
$orange-border: #fcd9bd;
|
||||
$border: #e5e7eb;
|
||||
$text: #111827;
|
||||
$muted: #6b7280;
|
||||
$radius: 10px;
|
||||
|
||||
mifosx-add-beneficiary-dialog {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// Header
|
||||
.abd-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 18px 20px 14px;
|
||||
border-bottom: 1px solid $border;
|
||||
background: linear-gradient(135deg, #f0f7ff 0%, #fff 60%);
|
||||
|
||||
&__icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 10px;
|
||||
background: $primary;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
mat-icon {
|
||||
color: #fff;
|
||||
font-size: 22px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: $text;
|
||||
}
|
||||
|
||||
&__sub {
|
||||
margin: 3px 0 0;
|
||||
font-size: 11.5px;
|
||||
color: $muted;
|
||||
}
|
||||
|
||||
&__close {
|
||||
color: $muted;
|
||||
margin: -4px -8px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Form
|
||||
.abd-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
width: 500px;
|
||||
max-width: 100%;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.abd-full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.abd-mt {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
// Mode toggle
|
||||
.abd-mode-toggle {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 14px 0 10px;
|
||||
background: #f9fafb;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.abd-mode-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 8px 14px;
|
||||
border-radius: 8px;
|
||||
border: 1.5px solid transparent;
|
||||
background: transparent;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $muted;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
|
||||
mat-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: #fff;
|
||||
border-color: $primary-border;
|
||||
color: $primary;
|
||||
box-shadow: 0 1px 4px rgb(0 0 0 / 8%);
|
||||
}
|
||||
}
|
||||
|
||||
// Search
|
||||
.abd-search-section {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.abd-prefix-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: $muted;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.abd-search-results {
|
||||
position: absolute;
|
||||
z-index: 200;
|
||||
top: calc(100% - 8px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: $radius;
|
||||
box-shadow: 0 8px 24px rgb(0 0 0 / 12%);
|
||||
max-height: 220px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.abd-result-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
transition: background 0.1s;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $primary-light;
|
||||
}
|
||||
}
|
||||
|
||||
.abd-result-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #6366f1, $primary);
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.abd-result-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.abd-result-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
}
|
||||
|
||||
.abd-result-meta {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.abd-chip {
|
||||
font-size: 10px;
|
||||
padding: 1px 7px;
|
||||
border-radius: 999px;
|
||||
background: #f3f4f6;
|
||||
color: $muted;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.abd-result-arrow {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: $primary;
|
||||
opacity: 0.4;
|
||||
transition: all 0.1s;
|
||||
|
||||
.abd-result-item:hover & {
|
||||
opacity: 1;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
}
|
||||
|
||||
// Selected card
|
||||
.abd-selected-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
background: $success-light;
|
||||
border: 1.5px solid $success-border;
|
||||
border-radius: $radius;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.abd-selected-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #10b981, $success);
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.abd-selected-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.abd-selected-name {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
.abd-selected-sub {
|
||||
font-size: 11px;
|
||||
color: #047857;
|
||||
}
|
||||
|
||||
.abd-badge {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
padding: 3px 10px;
|
||||
border-radius: 999px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
|
||||
&--linked {
|
||||
background: $success;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
// Row layout
|
||||
.abd-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 8px;
|
||||
|
||||
@media (width <= 500px) {
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.abd-flex {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
// Share %
|
||||
.abd-share-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.abd-share-field {
|
||||
flex: 0 0 150px;
|
||||
}
|
||||
|
||||
.abd-pct {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: $muted;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.abd-quick-pct {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.abd-pct-chip {
|
||||
padding: 5px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1.5px solid $border;
|
||||
background: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: $muted;
|
||||
cursor: pointer;
|
||||
transition: all 0.12s;
|
||||
|
||||
&:hover {
|
||||
border-color: $primary;
|
||||
background: $primary-light;
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
// Required star
|
||||
.abd-required {
|
||||
color: #e02424;
|
||||
}
|
||||
|
||||
// Minor toggle
|
||||
.abd-minor-toggle {
|
||||
margin-top: 14px;
|
||||
padding: 12px 14px;
|
||||
background: #fffbeb;
|
||||
border: 1px solid #fde68a;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
// Guardian box
|
||||
.abd-guardian-box {
|
||||
margin-top: 10px;
|
||||
padding: 14px;
|
||||
background: $orange-light;
|
||||
border: 1.5px solid $orange-border;
|
||||
border-radius: $radius;
|
||||
}
|
||||
|
||||
.abd-guardian-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: $orange;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
margin-bottom: 10px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: $orange;
|
||||
}
|
||||
}
|
||||
|
||||
// Actions
|
||||
.abd-actions {
|
||||
padding: 12px 20px 16px !important;
|
||||
border-top: 1px solid $border;
|
||||
gap: 10px;
|
||||
justify-content: flex-end !important;
|
||||
}
|
||||
|
||||
.abd-btn-add {
|
||||
border-radius: 8px !important;
|
||||
padding: 0 18px !important;
|
||||
gap: 4px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, inject, OnDestroy, ViewEncapsulation } from '@angular/core';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatDialogRef, MatDialogContent, MatDialogActions } from '@angular/material/dialog';
|
||||
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatSlideToggle } from '@angular/material/slide-toggle';
|
||||
import { ClientsService } from 'app/clients/clients.service';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-add-beneficiary-dialog',
|
||||
templateUrl: './add-beneficiary-dialog.component.html',
|
||||
styleUrl: './add-beneficiary-dialog.component.scss',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatProgressSpinner,
|
||||
MatIcon,
|
||||
MatSlideToggle,
|
||||
ReactiveFormsModule
|
||||
]
|
||||
})
|
||||
export class AddBeneficiaryDialogComponent implements OnDestroy {
|
||||
private fb = inject(UntypedFormBuilder);
|
||||
private clientsService = inject(ClientsService);
|
||||
dialogRef = inject(MatDialogRef<AddBeneficiaryDialogComponent>);
|
||||
|
||||
/** Toggle between linked client and manual entry */
|
||||
mode: 'search' | 'manual' = 'search';
|
||||
|
||||
form: UntypedFormGroup = this.fb.group({
|
||||
searchText: [''],
|
||||
name: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
relationship: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
dateOfBirth: [null],
|
||||
sharePercentage: [
|
||||
null,
|
||||
[
|
||||
Validators.required,
|
||||
Validators.min(1),
|
||||
Validators.max(100)
|
||||
]
|
||||
],
|
||||
isMinor: [false],
|
||||
guardianName: [''],
|
||||
guardianRelationship: ['']
|
||||
});
|
||||
|
||||
searchResults: any[] = [];
|
||||
selectedClient: any = null;
|
||||
isSearching = false;
|
||||
private searchTimer: any = null;
|
||||
|
||||
readonly relationships = [
|
||||
'Spouse',
|
||||
'Son',
|
||||
'Daughter',
|
||||
'Father',
|
||||
'Mother',
|
||||
'Brother',
|
||||
'Sister',
|
||||
'Grandson',
|
||||
'Granddaughter',
|
||||
'Father-in-law',
|
||||
'Mother-in-law',
|
||||
'Other'
|
||||
];
|
||||
|
||||
readonly guardianRelationships = [
|
||||
'Father',
|
||||
'Mother',
|
||||
'Guardian',
|
||||
'Natural Guardian'
|
||||
];
|
||||
|
||||
get isMinor(): boolean {
|
||||
return this.form.get('isMinor').value;
|
||||
}
|
||||
|
||||
switchMode(m: 'search' | 'manual'): void {
|
||||
this.mode = m;
|
||||
this.selectedClient = null;
|
||||
this.searchResults = [];
|
||||
this.form.patchValue({ searchText: '', name: '' });
|
||||
}
|
||||
|
||||
onSearchInput(value: string): void {
|
||||
if (this.searchTimer) clearTimeout(this.searchTimer);
|
||||
this.selectedClient = null;
|
||||
if (!value || value.trim().length < 2) {
|
||||
this.searchResults = [];
|
||||
return;
|
||||
}
|
||||
this.isSearching = true;
|
||||
this.searchTimer = setTimeout(() => {
|
||||
this.clientsService.searchByText(value.trim(), 0, 10).subscribe({
|
||||
next: (res: any) => {
|
||||
this.searchResults = res?.content || [];
|
||||
this.isSearching = false;
|
||||
},
|
||||
error: () => {
|
||||
this.searchResults = [];
|
||||
this.isSearching = false;
|
||||
}
|
||||
});
|
||||
}, 400);
|
||||
}
|
||||
|
||||
selectClient(client: any): void {
|
||||
this.selectedClient = client;
|
||||
this.form.patchValue({
|
||||
searchText: client.displayName,
|
||||
name: client.displayName
|
||||
});
|
||||
this.searchResults = [];
|
||||
}
|
||||
|
||||
clearSelection(): void {
|
||||
this.selectedClient = null;
|
||||
this.form.patchValue({ searchText: '', name: '' });
|
||||
this.searchResults = [];
|
||||
}
|
||||
|
||||
confirm(): void {
|
||||
if (
|
||||
this.form.get('name').invalid ||
|
||||
this.form.get('relationship').invalid ||
|
||||
this.form.get('sharePercentage').invalid
|
||||
)
|
||||
return;
|
||||
const v = this.form.getRawValue();
|
||||
this.dialogRef.close({
|
||||
clientId: this.selectedClient?.id || null,
|
||||
name: v.name,
|
||||
relationship: v.relationship,
|
||||
dateOfBirth: v.dateOfBirth,
|
||||
sharePercentage: v.sharePercentage,
|
||||
isMinor: v.isMinor,
|
||||
guardianName: v.isMinor ? v.guardianName : null,
|
||||
guardianRelationship: v.isMinor ? v.guardianRelationship : null
|
||||
});
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.dialogRef.close(null);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.searchTimer) clearTimeout(this.searchTimer);
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
<!--
|
||||
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/.
|
||||
-->
|
||||
|
||||
<!-- Dialog Header -->
|
||||
<div class="arc-header">
|
||||
<div class="arc-header__icon">
|
||||
<mat-icon>person_add</mat-icon>
|
||||
</div>
|
||||
<div class="arc-header__text">
|
||||
<h2 class="arc-header__title">Add Related Client / Partner</h2>
|
||||
<p class="arc-header__sub">Search and link an existing client with their ownership share</p>
|
||||
</div>
|
||||
<button mat-icon-button class="arc-header__close" (click)="cancel()">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<mat-dialog-content>
|
||||
<form [formGroup]="form" class="arc-dialog-form">
|
||||
<!-- Step 1: Client Search -->
|
||||
<div class="arc-step-label">
|
||||
<span class="arc-step-badge">1</span>
|
||||
<span>Select Client</span>
|
||||
</div>
|
||||
|
||||
<div class="arc-search-section">
|
||||
<mat-form-field appearance="outline" class="arc-full-width">
|
||||
<mat-label>Search by Name / CIF / Account No</mat-label>
|
||||
<mat-icon matPrefix class="arc-search-icon">search</mat-icon>
|
||||
<input
|
||||
matInput
|
||||
formControlName="searchText"
|
||||
(input)="onSearchInput($event.target.value)"
|
||||
autocomplete="off"
|
||||
placeholder="Type at least 2 characters..."
|
||||
/>
|
||||
@if (isSearching) {
|
||||
<mat-progress-spinner matSuffix diameter="18" mode="indeterminate"></mat-progress-spinner>
|
||||
}
|
||||
@if (selectedClient) {
|
||||
<button matSuffix mat-icon-button type="button" (click)="clearSelection()">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<!-- Search Results -->
|
||||
@if (searchResults.length > 0) {
|
||||
<div class="arc-search-results">
|
||||
@for (client of searchResults; track client) {
|
||||
<div class="arc-search-result-item" (click)="selectClient(client)">
|
||||
<div class="arc-result-avatar">
|
||||
{{ (client.displayName || '?')[0].toUpperCase() }}
|
||||
</div>
|
||||
<div class="arc-result-body">
|
||||
<span class="arc-result-name">{{ client.displayName }}</span>
|
||||
<span class="arc-result-meta">
|
||||
@if (client.cifNumber) {
|
||||
<span class="arc-chip">{{ client.cifNumber }}</span>
|
||||
}
|
||||
@if (client.officeName) {
|
||||
<span class="arc-chip">{{ client.officeName }}</span>
|
||||
}
|
||||
@if (client.status?.value) {
|
||||
<span class="arc-chip arc-chip--status" [class.arc-chip--active]="client.status?.id === 300">
|
||||
{{ client.status.value }}
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<mat-icon class="arc-result-arrow">chevron_right</mat-icon>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Selected Client Card -->
|
||||
@if (selectedClient) {
|
||||
<div class="arc-selected-client">
|
||||
<div class="arc-selected-avatar">
|
||||
{{ (selectedClient.displayName || '?')[0].toUpperCase() }}
|
||||
</div>
|
||||
<div class="arc-client-info">
|
||||
<span class="arc-client-name">{{ selectedClient.displayName }}</span>
|
||||
<span class="arc-client-sub">{{ selectedClient.cifNumber || selectedClient.accountNumber || 'N/A' }}</span>
|
||||
</div>
|
||||
<span class="arc-selected-badge">Selected</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Step 2: Share Percentage -->
|
||||
<div class="arc-step-label arc-step-label--mt">
|
||||
<span class="arc-step-badge">2</span>
|
||||
<span>Ownership Share</span>
|
||||
</div>
|
||||
|
||||
<div class="arc-share-row">
|
||||
<mat-form-field appearance="outline" class="arc-share-field">
|
||||
<mat-label>Share Percentage</mat-label>
|
||||
<input matInput type="number" formControlName="sharePercentage" min="1" max="100" />
|
||||
<span matSuffix class="arc-percent-suffix">%</span>
|
||||
<mat-error>Enter a value between 1 and 100</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="arc-share-hints">
|
||||
@for (hint of [25, 33, 50, 100]; track hint) {
|
||||
<button type="button" class="arc-hint-chip" (click)="form.get('sharePercentage').setValue(hint)">
|
||||
{{ hint }}%
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions class="arc-actions">
|
||||
<button mat-stroked-button type="button" (click)="cancel()" class="arc-btn-cancel">Cancel</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
type="button"
|
||||
class="arc-btn-add"
|
||||
[disabled]="!selectedClient || form.get('sharePercentage').invalid"
|
||||
(click)="confirm()"
|
||||
>
|
||||
<mat-icon>add</mat-icon>
|
||||
Add Partner
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
+359
@@ -0,0 +1,359 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
// ── Tokens ───────────────────────────────────────────────────────────────────
|
||||
$primary: #2563eb;
|
||||
$primary-light: #eff6ff;
|
||||
$primary-border: #bfdbfe;
|
||||
$success: #16a34a;
|
||||
$success-light: #f0fdf4;
|
||||
$success-border: #bbf7d0;
|
||||
$border: #e5e7eb;
|
||||
$text: #111827;
|
||||
$text-muted: #6b7280;
|
||||
$radius: 10px;
|
||||
|
||||
// ── Dialog wrapper ────────────────────────────────────────────────────────────
|
||||
mifosx-add-related-client-dialog {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// ── Header ────────────────────────────────────────────────────────────────────
|
||||
.arc-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 20px 20px 14px;
|
||||
border-bottom: 1px solid $border;
|
||||
|
||||
&__icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
background: $primary-light;
|
||||
border: 1px solid $primary-border;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
mat-icon {
|
||||
color: $primary;
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: $text;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
&__sub {
|
||||
margin: 3px 0 0;
|
||||
font-size: 12px;
|
||||
color: $text-muted;
|
||||
}
|
||||
|
||||
&__close {
|
||||
margin-top: -4px;
|
||||
margin-right: -8px;
|
||||
color: $text-muted;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Dialog content ────────────────────────────────────────────────────────────
|
||||
.arc-dialog-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
width: 440px;
|
||||
max-width: 100%;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
// ── Step labels ───────────────────────────────────────────────────────────────
|
||||
.arc-step-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 16px 0 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: $text-muted;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
|
||||
&--mt {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.arc-step-badge {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: $primary;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
// ── Search ────────────────────────────────────────────────────────────────────
|
||||
.arc-full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.arc-search-section {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.arc-search-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: $text-muted;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.arc-search-results {
|
||||
position: absolute;
|
||||
z-index: 200;
|
||||
top: calc(100% - 8px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: $radius;
|
||||
box-shadow: 0 8px 24px rgb(0 0 0 / 12%);
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.arc-search-result-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
transition: background 0.12s;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $primary-light;
|
||||
|
||||
.arc-result-arrow {
|
||||
opacity: 1;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.arc-result-avatar {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #6366f1, #2563eb);
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.arc-result-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.arc-result-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
}
|
||||
|
||||
.arc-result-meta {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.arc-chip {
|
||||
font-size: 10px;
|
||||
padding: 1px 7px;
|
||||
border-radius: 999px;
|
||||
background: #f3f4f6;
|
||||
color: $text-muted;
|
||||
font-weight: 500;
|
||||
|
||||
&--status {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
}
|
||||
}
|
||||
|
||||
.arc-result-arrow {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: $primary;
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity 0.12s,
|
||||
transform 0.12s;
|
||||
}
|
||||
|
||||
// ── Selected client ───────────────────────────────────────────────────────────
|
||||
.arc-selected-client {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 14px;
|
||||
background: $success-light;
|
||||
border: 1.5px solid $success-border;
|
||||
border-radius: $radius;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.arc-selected-avatar {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #22c55e, #16a34a);
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.arc-client-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.arc-client-name {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.arc-client-sub {
|
||||
font-size: 11px;
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
.arc-selected-badge {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
padding: 3px 10px;
|
||||
border-radius: 999px;
|
||||
background: $success;
|
||||
color: #fff;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
// ── Share percentage ──────────────────────────────────────────────────────────
|
||||
.arc-share-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.arc-share-field {
|
||||
flex: 0 0 160px;
|
||||
}
|
||||
|
||||
.arc-percent-suffix {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: $text-muted;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.arc-share-hints {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.arc-hint-chip {
|
||||
padding: 5px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1.5px solid $border;
|
||||
background: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: $text-muted;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 0.12s,
|
||||
background 0.12s,
|
||||
color 0.12s;
|
||||
|
||||
&:hover {
|
||||
border-color: $primary;
|
||||
background: $primary-light;
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Actions ───────────────────────────────────────────────────────────────────
|
||||
.arc-actions {
|
||||
padding: 12px 20px 16px !important;
|
||||
border-top: 1px solid $border;
|
||||
gap: 10px;
|
||||
justify-content: flex-end !important;
|
||||
}
|
||||
|
||||
.arc-btn-cancel {
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
|
||||
.arc-btn-add {
|
||||
border-radius: 8px !important;
|
||||
padding: 0 18px !important;
|
||||
gap: 4px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, inject, OnDestroy, ViewEncapsulation } from '@angular/core';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatDialogRef, MatDialogContent, MatDialogActions } from '@angular/material/dialog';
|
||||
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { ClientsService } from 'app/clients/clients.service';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-add-related-client-dialog',
|
||||
templateUrl: './add-related-client-dialog.component.html',
|
||||
styleUrl: './add-related-client-dialog.component.scss',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatProgressSpinner,
|
||||
MatIcon,
|
||||
ReactiveFormsModule
|
||||
]
|
||||
})
|
||||
export class AddRelatedClientDialogComponent implements OnDestroy {
|
||||
private fb = inject(UntypedFormBuilder);
|
||||
private clientsService = inject(ClientsService);
|
||||
dialogRef = inject(MatDialogRef<AddRelatedClientDialogComponent>);
|
||||
|
||||
form: UntypedFormGroup = this.fb.group({
|
||||
searchText: [''],
|
||||
sharePercentage: [
|
||||
null,
|
||||
[
|
||||
Validators.required,
|
||||
Validators.min(1),
|
||||
Validators.max(100)
|
||||
]
|
||||
]
|
||||
});
|
||||
|
||||
searchResults: any[] = [];
|
||||
selectedClient: any = null;
|
||||
isSearching = false;
|
||||
private searchTimer: any = null;
|
||||
|
||||
onSearchInput(value: string): void {
|
||||
if (this.searchTimer) clearTimeout(this.searchTimer);
|
||||
this.selectedClient = null;
|
||||
if (!value || value.trim().length < 2) {
|
||||
this.searchResults = [];
|
||||
return;
|
||||
}
|
||||
this.isSearching = true;
|
||||
this.searchTimer = setTimeout(() => {
|
||||
this.clientsService.searchByText(value.trim(), 0, 10).subscribe({
|
||||
next: (res: any) => {
|
||||
this.searchResults = res?.content || [];
|
||||
this.isSearching = false;
|
||||
},
|
||||
error: () => {
|
||||
this.searchResults = [];
|
||||
this.isSearching = false;
|
||||
}
|
||||
});
|
||||
}, 400);
|
||||
}
|
||||
|
||||
selectClient(client: any): void {
|
||||
this.selectedClient = client;
|
||||
this.form.patchValue({ searchText: client.displayName });
|
||||
this.searchResults = [];
|
||||
}
|
||||
|
||||
clearSelection(): void {
|
||||
this.selectedClient = null;
|
||||
this.form.patchValue({ searchText: '' });
|
||||
this.searchResults = [];
|
||||
}
|
||||
|
||||
confirm(): void {
|
||||
if (!this.selectedClient || this.form.get('sharePercentage').invalid) return;
|
||||
this.dialogRef.close({
|
||||
clientId: this.selectedClient.id,
|
||||
clientName: this.selectedClient.displayName,
|
||||
clientCif: this.selectedClient.cifNumber || this.selectedClient.accountNumber || '',
|
||||
sharePercentage: this.form.get('sharePercentage').value
|
||||
});
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.dialogRef.close(null);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.searchTimer) clearTimeout(this.searchTimer);
|
||||
}
|
||||
}
|
||||
+214
-8
@@ -7,7 +7,7 @@
|
||||
-->
|
||||
|
||||
<form [formGroup]="currentAccountDetailsForm">
|
||||
<!-- Account Details -->
|
||||
<!-- ══ Account Details ══════════════════════════════════════════ -->
|
||||
<div class="sad-section">
|
||||
<div class="sad-section__header">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
@@ -45,9 +45,7 @@
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
@if (currentAccountProductSelected) {
|
||||
<mat-form-field appearance="outline" class="sad-field">
|
||||
<mat-label>{{ 'labels.inputs.Field Officer' | translate }}</mat-label>
|
||||
<mat-select formControlName="fieldOfficerId">
|
||||
@@ -56,9 +54,7 @@
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
@if (currentAccountProductSelected) {
|
||||
<mat-form-field appearance="outline" class="sad-field">
|
||||
<mat-label>{{ 'labels.inputs.External ID' | translate }}</mat-label>
|
||||
<input matInput formControlName="externalId" />
|
||||
@@ -67,8 +63,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product Classification (read-only, shown after product is selected) -->
|
||||
@if (currentAccountProductSelected && productTemplate) {
|
||||
<!-- ══ Product Classification (read-only) ══════════════════════ -->
|
||||
<div class="sad-section sad-section--mt">
|
||||
<div class="sad-section__header">
|
||||
<i class="fas fa-tags"></i>
|
||||
@@ -87,8 +83,12 @@
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="sad-field">
|
||||
<mat-label>Product Code</mat-label>
|
||||
<input matInput [value]="productTemplate.productCode || ''" readonly />
|
||||
<mat-label>Account No</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[value]="productTemplate?.previewAccountNo || currentAccountTemplate?.accountNo || 'Auto-generated'"
|
||||
readonly
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
@if (productTemplate.effectiveDate) {
|
||||
@@ -125,6 +125,212 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (productTemplate?.introducerRequired) {
|
||||
<mifosx-introducer-search (introducerChange)="onIntroducerChange($event)"></mifosx-introducer-search>
|
||||
}
|
||||
}
|
||||
|
||||
<!-- ══ Non-Individual Account Details (Indian Banking Style) ═════ -->
|
||||
@if (isNonIndividualClient && currentAccountProductSelected) {
|
||||
<div class="ni-card">
|
||||
<!-- Card Header -->
|
||||
<div class="ni-card__header">
|
||||
<div class="ni-card__header-left">
|
||||
<div class="ni-card__header-icon">
|
||||
<i class="fas fa-building"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="ni-card__title">Non-Individual Account Details</h3>
|
||||
<span class="ni-card__sub">As per RBI / Indian banking norms for non-individual entities</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="ni-pill ni-pill--entity">Entity Account</span>
|
||||
</div>
|
||||
|
||||
<!-- ── A. Partners / Members ─────────────────────────────── -->
|
||||
<div class="ni-block">
|
||||
<div class="ni-block__head">
|
||||
<div class="ni-block__head-left">
|
||||
<span class="ni-block__letter">A</span>
|
||||
<div>
|
||||
<span class="ni-block__title">Partners / Members</span>
|
||||
<span class="ni-block__hint">Authorised persons linked to this entity account</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
type="button"
|
||||
class="ni-add-btn"
|
||||
(click)="openAddRelatedClientDialog()"
|
||||
>
|
||||
<mat-icon>person_add</mat-icon> Add Partner
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (relatedClients.length > 0) {
|
||||
<div class="ni-table-wrap">
|
||||
<table class="ni-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="ni-th ni-th--sr">Sr.</th>
|
||||
<th class="ni-th">Partner Name</th>
|
||||
<th class="ni-th">CIF No.</th>
|
||||
<th class="ni-th ni-th--center">Ownership %</th>
|
||||
<th class="ni-th ni-th--center">Signing Authority</th>
|
||||
<th class="ni-th ni-th--center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (c of relatedClients; track c; let i = $index) {
|
||||
<tr class="ni-tr" [class.ni-tr--mandatory]="c.isMandatory">
|
||||
<td class="ni-td ni-td--sr">{{ i + 1 }}</td>
|
||||
<td class="ni-td">
|
||||
<div class="ni-client-cell">
|
||||
<div class="ni-avatar" [class.ni-avatar--mandatory]="c.isMandatory">
|
||||
{{ c.clientName[0].toUpperCase() }}
|
||||
</div>
|
||||
<div>
|
||||
<span class="ni-client-name">{{ c.clientName }}</span>
|
||||
@if (c.isMandatory) {
|
||||
<span class="ni-mandatory-tag">Mandatory Signatory</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="ni-td">
|
||||
<span class="ni-mono">{{ c.clientCif || '—' }}</span>
|
||||
</td>
|
||||
<td class="ni-td ni-td--center">
|
||||
<span class="ni-pct-badge" [class.ni-pct-badge--full]="c.sharePercentage === 100">
|
||||
{{ c.sharePercentage }}%
|
||||
</span>
|
||||
</td>
|
||||
<td class="ni-td ni-td--center">
|
||||
<button
|
||||
type="button"
|
||||
class="ni-sign-toggle"
|
||||
[class.ni-sign-toggle--mandatory]="c.isMandatory"
|
||||
[class.ni-sign-toggle--optional]="!c.isMandatory"
|
||||
(click)="toggleMandatory(c)"
|
||||
[title]="c.isMandatory ? 'Click to set Optional' : 'Click to set Mandatory'"
|
||||
>
|
||||
<mat-icon>{{ c.isMandatory ? 'lock' : 'lock_open' }}</mat-icon>
|
||||
{{ c.isMandatory ? 'Mandatory' : 'Optional' }}
|
||||
</button>
|
||||
</td>
|
||||
<td class="ni-td ni-td--center">
|
||||
<button
|
||||
mat-icon-button
|
||||
color="warn"
|
||||
type="button"
|
||||
(click)="removeRelatedClient(i)"
|
||||
class="ni-del-btn"
|
||||
>
|
||||
<mat-icon>delete_outline</mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="ni-tr-total">
|
||||
<td colspan="3" class="ni-td ni-td--total-label">Total Ownership</td>
|
||||
<td class="ni-td ni-td--center">
|
||||
<span
|
||||
class="ni-pct-badge"
|
||||
[class.ni-pct-badge--ok]="totalSharePercentage === 100"
|
||||
[class.ni-pct-badge--warn]="totalSharePercentage !== 100"
|
||||
>
|
||||
{{ totalSharePercentage }}%
|
||||
</span>
|
||||
</td>
|
||||
<td class="ni-td ni-td--center">
|
||||
<span class="ni-mandatory-count-badge">
|
||||
<mat-icon>lock</mat-icon> {{ mandatoryCount }} Mandatory
|
||||
</span>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
@if (totalSharePercentage !== 100) {
|
||||
<div class="ni-alert ni-alert--warn">
|
||||
<mat-icon>warning_amber</mat-icon>
|
||||
Total ownership is {{ totalSharePercentage }}%. It must equal 100%.
|
||||
</div>
|
||||
} @else {
|
||||
<div class="ni-alert ni-alert--ok">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
Ownership allocation is complete (100%).
|
||||
</div>
|
||||
}
|
||||
} @else {
|
||||
<div class="ni-empty-state">
|
||||
<mat-icon>group_add</mat-icon>
|
||||
<span>No partners added yet. Click <strong>Add Partner</strong> to begin.</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- ── B. Mode of Operation / Signatory Rights (auto from partners) ── -->
|
||||
<div class="ni-block ni-block--sep">
|
||||
<div class="ni-block__head">
|
||||
<div class="ni-block__head-left">
|
||||
<span class="ni-block__letter ni-block__letter--b">B</span>
|
||||
<div>
|
||||
<span class="ni-block__title">Mode of Operation / Signatory Rights</span>
|
||||
<span class="ni-block__hint">Auto-computed from Mandatory partners — you can override manually</span>
|
||||
</div>
|
||||
</div>
|
||||
@if (signatoryRights) {
|
||||
<span class="ni-auto-badge"> <mat-icon>auto_awesome</mat-icon> Auto </span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (relatedClients.length === 0) {
|
||||
<div class="ni-empty-state">
|
||||
<mat-icon>how_to_vote</mat-icon>
|
||||
<span>Add partners in Section A and set Mandatory/Optional to auto-compute the mode.</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="ni-signatory-grid">
|
||||
@for (opt of signatoryRightsOptions; track opt.value) {
|
||||
<button
|
||||
type="button"
|
||||
class="ni-sig-card"
|
||||
[class.ni-sig-card--selected]="signatoryRights === opt.value"
|
||||
(click)="signatoryRights = opt.value"
|
||||
>
|
||||
<mat-icon class="ni-sig-card__icon">{{ opt.icon }}</mat-icon>
|
||||
<div class="ni-sig-card__code">{{ opt.label }}</div>
|
||||
<div class="ni-sig-card__desc">{{ opt.sublabel }}</div>
|
||||
@if (signatoryRights === opt.value) {
|
||||
<mat-icon class="ni-sig-card__check">check_circle</mat-icon>
|
||||
}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="ni-mode-summary" [class.ni-mode-summary--set]="signatoryRights">
|
||||
@if (signatoryRights) {
|
||||
<mat-icon>how_to_vote</mat-icon>
|
||||
<span
|
||||
>Mode of Operation: <strong>{{ selectedSignatoryLabel }}</strong></span
|
||||
>
|
||||
<span class="ni-mode-summary__from">
|
||||
({{ mandatoryCount }} Mandatory + {{ relatedClients.length - mandatoryCount }} Optional partner(s))
|
||||
</span>
|
||||
} @else {
|
||||
<mat-icon>info</mat-icon>
|
||||
<span>Set Mandatory/Optional for partners above to auto-select mode.</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
||||
|
||||
+667
-1
@@ -15,7 +15,7 @@ $gray-900: #202124;
|
||||
.sad-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
background: #fff;
|
||||
|
||||
&--mt {
|
||||
@@ -103,3 +103,669 @@ $gray-900: #202124;
|
||||
border-color: #bbf7d0;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Non-Individual Section (Indian Banking Style) ─────────────────────────
|
||||
|
||||
$ni-primary: #1a56db;
|
||||
$ni-green: #057a55;
|
||||
$ni-orange: #d03801;
|
||||
$ni-border: #e5e7eb;
|
||||
$ni-bg: #f9fafb;
|
||||
|
||||
.ni-card {
|
||||
margin-top: 16px;
|
||||
border: 1.5px solid #c3dafe;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 20px;
|
||||
background: linear-gradient(135deg, #1e3a8a 0%, #1a56db 100%);
|
||||
}
|
||||
|
||||
&__header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
&__header-icon {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 8px;
|
||||
background: rgba(255 255 255 / 20%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
i {
|
||||
color: #fff;
|
||||
font-size: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&__sub {
|
||||
margin: 2px 0 0;
|
||||
font-size: 11px;
|
||||
color: rgba(255 255 255 / 75%);
|
||||
}
|
||||
}
|
||||
|
||||
.ni-pill {
|
||||
padding: 4px 12px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
|
||||
&--entity {
|
||||
background: rgba(255 255 255 / 20%);
|
||||
color: #fff;
|
||||
border: 1px solid rgba(255 255 255 / 30%);
|
||||
}
|
||||
}
|
||||
|
||||
// Block
|
||||
.ni-block {
|
||||
padding: 16px 20px 4px;
|
||||
|
||||
&--sep {
|
||||
border-top: 1.5px dashed #e5e7eb;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
&__head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
&__head-left {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
&--alone {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
&__letter {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 6px;
|
||||
background: $ni-primary;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
margin-top: 1px;
|
||||
|
||||
&--b {
|
||||
background: $ni-green;
|
||||
}
|
||||
|
||||
&--c {
|
||||
background: #7c3aed;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
&__hint {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: #6b7280;
|
||||
margin-top: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.ni-add-btn {
|
||||
border-radius: 8px !important;
|
||||
font-size: 12px !important;
|
||||
padding: 0 14px !important;
|
||||
height: 34px !important;
|
||||
gap: 4px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&--accent {
|
||||
background: $ni-green !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Table
|
||||
.ni-table-wrap {
|
||||
overflow-x: auto;
|
||||
border: 1px solid $ni-border;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.ni-tbl {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
|
||||
thead {
|
||||
background: #f8faff;
|
||||
}
|
||||
}
|
||||
|
||||
.ni-th {
|
||||
padding: 9px 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: #374151;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
border-bottom: 1.5px solid $ni-border;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
|
||||
&--sr {
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&--center {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.ni-tr {
|
||||
transition: background 0.1s;
|
||||
|
||||
&:hover {
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
&--mandatory {
|
||||
background: #fffbf0;
|
||||
|
||||
&:hover {
|
||||
background: #fff8e6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ni-tr-total {
|
||||
background: #f0f4ff;
|
||||
border-top: 1.5px solid #c3dafe;
|
||||
}
|
||||
|
||||
.ni-td {
|
||||
padding: 10px 12px;
|
||||
color: #111827;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
|
||||
&--sr {
|
||||
text-align: center;
|
||||
color: #6b7280;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&--center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&--total-label {
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
color: #374151;
|
||||
}
|
||||
}
|
||||
|
||||
.ni-client-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ni-avatar {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #6366f1, $ni-primary);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
&--green {
|
||||
background: linear-gradient(135deg, #10b981, $ni-green);
|
||||
}
|
||||
|
||||
&--mandatory {
|
||||
background: linear-gradient(135deg, #f59e0b, #d97706);
|
||||
}
|
||||
}
|
||||
|
||||
.ni-client-name {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.ni-mandatory-tag {
|
||||
display: inline-block;
|
||||
font-size: 9.5px;
|
||||
font-weight: 700;
|
||||
color: #b45309;
|
||||
background: #fef3c7;
|
||||
border: 1px solid #fde68a;
|
||||
border-radius: 4px;
|
||||
padding: 1px 6px;
|
||||
margin-top: 2px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.ni-mandatory-count-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: #92400e;
|
||||
background: #fef3c7;
|
||||
border: 1px solid #fde68a;
|
||||
border-radius: 6px;
|
||||
padding: 3px 8px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 12px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
// Signing authority toggle in table
|
||||
.ni-sign-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
border: 1.5px solid;
|
||||
font-size: 11.5px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
white-space: nowrap;
|
||||
|
||||
mat-icon {
|
||||
font-size: 13px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
&--mandatory {
|
||||
border-color: #d97706;
|
||||
background: #fffbeb;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
&--optional {
|
||||
border-color: #d1d5db;
|
||||
background: #f9fafb;
|
||||
color: #6b7280;
|
||||
|
||||
&:hover {
|
||||
border-color: #d97706;
|
||||
color: #92400e;
|
||||
background: #fffbeb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ni-nom-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.ni-guardian-hint {
|
||||
font-size: 10.5px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.ni-mono {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.ni-pct-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
background: #e0e7ff;
|
||||
color: $ni-primary;
|
||||
min-width: 52px;
|
||||
|
||||
&--ok {
|
||||
background: #d1fae5;
|
||||
color: $ni-green;
|
||||
}
|
||||
|
||||
&--warn {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
&--full {
|
||||
background: #d1fae5;
|
||||
color: $ni-green;
|
||||
}
|
||||
}
|
||||
|
||||
.ni-rel-badge {
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 6px;
|
||||
background: #f3f4f6;
|
||||
color: #374151;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ni-minor-badge {
|
||||
font-size: 10px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.ni-td-dash {
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
.ni-del-btn {
|
||||
width: 28px !important;
|
||||
height: 28px !important;
|
||||
line-height: 28px !important;
|
||||
|
||||
mat-icon {
|
||||
font-size: 17px;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
// Alerts
|
||||
.ni-alert {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&--warn {
|
||||
background: #fffbeb;
|
||||
color: #92400e;
|
||||
border: 1px solid #fde68a;
|
||||
}
|
||||
|
||||
&--ok {
|
||||
background: #f0fdf4;
|
||||
color: $ni-green;
|
||||
border: 1px solid #bbf7d0;
|
||||
}
|
||||
|
||||
&--sm {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
// Empty state
|
||||
.ni-empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 16px 12px;
|
||||
font-size: 13px;
|
||||
color: #9ca3af;
|
||||
font-style: italic;
|
||||
margin-bottom: 10px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: #d1d5db;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto badge on section header
|
||||
.ni-auto-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 3px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
background: #fffbeb;
|
||||
color: #92400e;
|
||||
border: 1px solid #fde68a;
|
||||
|
||||
mat-icon {
|
||||
font-size: 12px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
// Mode summary bar below signatory cards
|
||||
.ni-mode-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 9px 14px;
|
||||
border-radius: 8px;
|
||||
font-size: 12.5px;
|
||||
font-weight: 500;
|
||||
margin-top: 4px;
|
||||
background: #f3f4f6;
|
||||
color: #6b7280;
|
||||
border: 1px solid #e5e7eb;
|
||||
|
||||
mat-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&--set {
|
||||
background: #f0fdf4;
|
||||
color: $ni-green;
|
||||
border-color: #bbf7d0;
|
||||
}
|
||||
|
||||
&__from {
|
||||
font-size: 11px;
|
||||
color: #9ca3af;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Signatory / Mode of Operation cards ──────────────────────────────────
|
||||
.ni-signatory-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
@media (width <= 700px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.ni-sig-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 18px 10px 14px;
|
||||
border-radius: 12px;
|
||||
border: 1.5px solid $ni-border;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
text-align: center;
|
||||
|
||||
&:hover:not(.ni-sig-card--selected) {
|
||||
border-color: #93c5fd;
|
||||
background: #f0f9ff;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&--selected {
|
||||
border-color: $ni-primary;
|
||||
background: #eff6ff;
|
||||
box-shadow: 0 0 0 3px rgba(37 99 235 / 12%);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 22px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
color: #6b7280;
|
||||
|
||||
.ni-sig-card--selected & {
|
||||
color: $ni-primary;
|
||||
}
|
||||
}
|
||||
|
||||
&__code {
|
||||
font-size: 15px;
|
||||
font-weight: 800;
|
||||
color: #1e3a8a;
|
||||
font-family: monospace;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
font-size: 10.5px;
|
||||
font-weight: 500;
|
||||
color: #6b7280;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
&__check {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 7px;
|
||||
font-size: 15px;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
color: $ni-primary;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Account Feature Toggles ────────────────────────────────────────────────
|
||||
.sad-toggles {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.sad-toggle-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 18px;
|
||||
border: 1.5px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
transition:
|
||||
border-color 0.2s,
|
||||
box-shadow 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #93c5fd;
|
||||
box-shadow: 0 1px 6px rgb(59 130 246 / 10%);
|
||||
}
|
||||
|
||||
&__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
> i {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
background: #f0f9ff;
|
||||
border: 1px solid #bae6fd;
|
||||
color: #0284c7;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 15px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 13.5px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
&__hint {
|
||||
font-size: 11.5px;
|
||||
color: #64748b;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
+151
-3
@@ -14,9 +14,27 @@ import { Dates } from 'app/core/utils/dates';
|
||||
/** Custom Services */
|
||||
import { CurrentAccountsService } from 'app/current-accounts/current-accounts.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { ClientsService } from 'app/clients/clients.service';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
|
||||
import {
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow
|
||||
} from '@angular/material/table';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { IntroducerSearchComponent } from 'app/shared/introducer-search/introducer-search.component';
|
||||
import { AddRelatedClientDialogComponent } from './add-related-client-dialog/add-related-client-dialog.component';
|
||||
|
||||
/**
|
||||
* Current Account Details Step
|
||||
@@ -29,13 +47,28 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatStepperPrevious,
|
||||
FaIconComponent,
|
||||
MatStepperNext
|
||||
MatStepperNext,
|
||||
IntroducerSearchComponent,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
MatIcon,
|
||||
FormsModule
|
||||
]
|
||||
})
|
||||
export class CurrentAccountDetailsStepComponent implements OnInit {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private currentAccountsService = inject(CurrentAccountsService);
|
||||
private settingsService = inject(SettingsService);
|
||||
private clientsService = inject(ClientsService);
|
||||
private dialog = inject(MatDialog);
|
||||
private dateUtils = inject(Dates);
|
||||
private cdr = inject(ChangeDetectorRef);
|
||||
|
||||
@@ -56,6 +89,27 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
|
||||
currentAccountDetailsForm: UntypedFormGroup;
|
||||
|
||||
currentAccountProductSelected = false;
|
||||
introducerId: number | null = null;
|
||||
introducerName: string | null = null;
|
||||
introducerCif: string | null = null;
|
||||
|
||||
/** Non-Individual section */
|
||||
isNonIndividualClient = false;
|
||||
relatedClients: Array<{
|
||||
clientId: number;
|
||||
clientName: string;
|
||||
clientCif: string;
|
||||
sharePercentage: number;
|
||||
isMandatory: boolean;
|
||||
}> = [];
|
||||
signatoryRights = '';
|
||||
|
||||
readonly signatoryRightsOptions = [
|
||||
{ value: 'ANYONE', label: 'E or S', sublabel: 'Either or Survivor', icon: 'how_to_vote' },
|
||||
{ value: 'ONE_MANDATORY', label: 'J/One', sublabel: 'Joint – One Mandatory', icon: 'group' },
|
||||
{ value: 'TWO_MANDATORY', label: 'J/Two', sublabel: 'Joint – Two Mandatory', icon: 'groups' },
|
||||
{ value: 'ONLY_ONE', label: 'Single', sublabel: 'Only 1 Person', icon: 'person' }
|
||||
];
|
||||
|
||||
/** Current Account Template with product data */
|
||||
@Output() currentAccountProductTemplate = new EventEmitter();
|
||||
@@ -70,6 +124,13 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
this.maxDate = this.settingsService.businessDate;
|
||||
this.buildDependencies();
|
||||
if (this.currentAccountTemplate?.clientId) {
|
||||
this.clientsService.getClientData(String(this.currentAccountTemplate.clientId)).subscribe((client: any) => {
|
||||
this.isNonIndividualClient = client?.legalForm?.id === 2;
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
if (this.currentAccountTemplate) {
|
||||
this.productData = this.currentAccountTemplate.productOptions;
|
||||
if (this.currentAccountTemplate.currentAccountProductId) {
|
||||
@@ -102,7 +163,10 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
|
||||
Validators.required
|
||||
],
|
||||
fieldOfficerId: [''],
|
||||
externalId: ['']
|
||||
externalId: [''],
|
||||
balanceSwapOption: [false],
|
||||
msme: [false],
|
||||
autoDebitFacility: [false]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -131,6 +195,12 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
|
||||
this.currentAccountProductTemplate.emit(response);
|
||||
this.fieldOfficerData = response.fieldOfficerOptions;
|
||||
this.currentAccountProductSelected = true;
|
||||
// Pre-populate toggles from product defaults (user can override)
|
||||
this.currentAccountDetailsForm.patchValue({
|
||||
balanceSwapOption: !!response.balanceSwapOption,
|
||||
msme: !!response.msme,
|
||||
autoDebitFacility: !!response.autoDebitFacility
|
||||
});
|
||||
this.cdr.detectChanges();
|
||||
if (!this.isFieldOfficerPatched && this.currentAccountTemplate.fieldOfficerId) {
|
||||
this.currentAccountDetailsForm.get('fieldOfficerId').patchValue(this.currentAccountTemplate.fieldOfficerId);
|
||||
@@ -166,7 +236,85 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
|
||||
/**
|
||||
* Returns current account form value.
|
||||
*/
|
||||
onIntroducerChange(event: { clientId: number; displayName: string; cifNumber?: string } | null): void {
|
||||
this.introducerId = event?.clientId ?? null;
|
||||
this.introducerName = event?.displayName ?? null;
|
||||
this.introducerCif = event?.cifNumber ?? null;
|
||||
}
|
||||
|
||||
openAddRelatedClientDialog(): void {
|
||||
const ref = this.dialog.open(AddRelatedClientDialogComponent, {
|
||||
width: '500px',
|
||||
panelClass: 'arc-dialog-panel',
|
||||
disableClose: true
|
||||
});
|
||||
ref.afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
const duplicate = this.relatedClients.some((c) => c.clientCif && c.clientCif === result.clientCif);
|
||||
if (duplicate) {
|
||||
alert(`Partner with CIF ${result.clientCif} is already added.`);
|
||||
return;
|
||||
}
|
||||
this.relatedClients = [
|
||||
...this.relatedClients,
|
||||
{ ...result, isMandatory: false }];
|
||||
this.autoSetSignatoryRights();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeRelatedClient(index: number): void {
|
||||
this.relatedClients = this.relatedClients.filter((_, i) => i !== index);
|
||||
this.autoSetSignatoryRights();
|
||||
}
|
||||
|
||||
toggleMandatory(client: any): void {
|
||||
client.isMandatory = !client.isMandatory;
|
||||
this.autoSetSignatoryRights();
|
||||
}
|
||||
|
||||
/** Auto-compute Mode of Operation based on mandatory count */
|
||||
autoSetSignatoryRights(): void {
|
||||
const mandatory = this.relatedClients.filter((c) => c.isMandatory).length;
|
||||
if (mandatory === 0) {
|
||||
this.signatoryRights = 'ANYONE';
|
||||
} else if (mandatory === 1) {
|
||||
this.signatoryRights = 'ONE_MANDATORY';
|
||||
} else {
|
||||
this.signatoryRights = 'TWO_MANDATORY';
|
||||
}
|
||||
}
|
||||
|
||||
get totalSharePercentage(): number {
|
||||
return this.relatedClients.reduce((sum, c) => sum + (c.sharePercentage || 0), 0);
|
||||
}
|
||||
|
||||
get mandatoryCount(): number {
|
||||
return this.relatedClients.filter((c) => c.isMandatory).length;
|
||||
}
|
||||
|
||||
get selectedSignatoryLabel(): string {
|
||||
const opt = this.signatoryRightsOptions.find((x) => x.value === this.signatoryRights);
|
||||
return opt ? opt.sublabel : '';
|
||||
}
|
||||
|
||||
get currentAccountDetails() {
|
||||
return this.currentAccountDetailsForm.getRawValue();
|
||||
const value = this.currentAccountDetailsForm.getRawValue();
|
||||
if (this.introducerId) {
|
||||
value.introducerId = this.introducerId;
|
||||
value.introducerName = this.introducerName;
|
||||
value.introducerCif = this.introducerCif;
|
||||
}
|
||||
if (this.isNonIndividualClient) {
|
||||
value.signatoryRights = this.signatoryRights || null;
|
||||
value.partners = this.relatedClients.map((c) => ({
|
||||
clientId: c.clientId,
|
||||
clientName: c.clientName,
|
||||
clientCif: c.clientCif,
|
||||
sharePercentage: c.sharePercentage,
|
||||
isMandatory: c.isMandatory
|
||||
}));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
+194
-214
@@ -6,229 +6,209 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="spl-section">
|
||||
<div class="spl-section__header">
|
||||
<span class="spl-section__icon">
|
||||
<i class="fas fa-chart-bar"></i>
|
||||
</span>
|
||||
<div class="spl-section__title">Transaction & Product Limits</div>
|
||||
<div class="spl-section__badge">FROM PRODUCT</div>
|
||||
<div class="dig-wrapper">
|
||||
<div class="dig-header">
|
||||
<div class="dig-header__icon">
|
||||
<i class="fas fa-mobile-alt"></i>
|
||||
</div>
|
||||
<div class="dig-header__text">
|
||||
<h3 class="dig-header__title">Digital & Account Features</h3>
|
||||
<p class="dig-header__sub">Toggle the facilities available for this account (as per RBI guidelines)</p>
|
||||
</div>
|
||||
<span class="dig-header__badge">FROM PRODUCT</span>
|
||||
</div>
|
||||
|
||||
<div class="spl-section__body">
|
||||
<div class="spl-features">
|
||||
<div class="spl-features__header">Account Features (as per RBI guidelines)</div>
|
||||
|
||||
<div class="spl-features__grid">
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-credit-card"></i> ATM Facility</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.atmFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-wallet"></i> ATM Withdrawal</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.atmWithdrawalFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-mobile-alt"></i> UPI Facility</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.upiFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-exchange-alt"></i> POS Transactions</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.posTransactionsFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-book"></i> Cheque Book</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.chequeBook" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-bolt"></i> IMPS Facility</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.impsFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-sms"></i> SMS Alerts</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.smsAlerts" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-bell"></i> BBPS Facility</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.bbpsFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-shield-alt"></i> Insurance Cover</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.insuranceCover" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-bolt"></i> BEPG Facility</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.bepgFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-money-check-alt"></i> NEFT Facility</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.neftFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-money-check"></i> RTGS Facility</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.rtgsFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-balance-scale"></i> Inter-Bank Total Cap</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.interBankTotalCapFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-arrow-right-arrow-left"></i> Withdrawal Facility</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.withdrawalFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-id-card"></i> Deposit Facility (KYC)</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.depositFacilityKyc" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-credit-card"></i> Virtual Debit Card</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.virtualDebitCard" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="dig-grid">
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.atmFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-credit-card"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">ATM Facility</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.atmFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.atmFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@if (accountFacilityLimits.length > 0) {
|
||||
<div class="spl-table-wrap">
|
||||
<table class="spl-table">
|
||||
<colgroup>
|
||||
<col class="spl-table__col--label" />
|
||||
<col class="spl-table__col--value" />
|
||||
<col class="spl-table__col--value" />
|
||||
<col class="spl-table__col--value" />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Limit Type</th>
|
||||
<th>General Limit (Daily)</th>
|
||||
<th>Minor Account Limit</th>
|
||||
<th>Small Saving Limit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (row of accountFacilityLimits; track row.limitType) {
|
||||
<tr>
|
||||
<td>{{ row.limitType }}</td>
|
||||
<td><input type="text" class="spl-input" [value]="row.generalLimit" disabled /></td>
|
||||
<td><input type="text" class="spl-input" [value]="row.minorAccountLimit" disabled /></td>
|
||||
<td><input type="text" class="spl-input" [value]="row.smallSavingLimit" disabled /></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.atmWithdrawalFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-wallet"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">ATM Withdrawal</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.atmWithdrawalFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
}
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.atmWithdrawalFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@if (depositLimitKyc?.general) {
|
||||
<div class="spl-table-wrap spl-table-wrap--kyc">
|
||||
<h4 class="mat-h4">Deposit Limit (KYC)</h4>
|
||||
<table class="spl-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account Type</th>
|
||||
<th>KYC Type</th>
|
||||
<th>Limit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>General</td>
|
||||
<td>
|
||||
<select class="spl-select" [ngModel]="depositLimitKyc.general.kycType" disabled>
|
||||
<option>{{ depositLimitKyc.general.kycType }}</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" class="spl-input" [value]="depositLimitKyc.general.limit" disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Minor</td>
|
||||
<td>
|
||||
<select class="spl-select" [ngModel]="depositLimitKyc.minor.kycType" disabled>
|
||||
<option>{{ depositLimitKyc.minor.kycType }}</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="number" class="spl-input" [value]="depositLimitKyc.minor.limit" disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Small Saving</td>
|
||||
<td>
|
||||
<select class="spl-select" [ngModel]="depositLimitKyc.smallSaving.kycType" disabled>
|
||||
<option>{{ depositLimitKyc.smallSaving.kycType }}</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="number" class="spl-input" [value]="depositLimitKyc.smallSaving.limit" disabled /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.upiFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-mobile-alt"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">UPI Facility</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.upiFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
}
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.upiFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="spl-note">
|
||||
<strong>Note:</strong>
|
||||
These facilities are defined at product level and applied to this account. Small Saving Account limits are
|
||||
strictly capped at Rs.1,00,000 maximum balance per RBI Master Circular.
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.posTransactionsFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-exchange-alt"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">POS Transactions</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.posTransactionsFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.posTransactionsFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.chequeBook">
|
||||
<div class="dig-card__icon"><i class="fas fa-book"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">Cheque Book</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.chequeBook ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.chequeBook" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.impsFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-bolt"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">IMPS Facility</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.impsFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.impsFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.smsAlerts">
|
||||
<div class="dig-card__icon"><i class="fas fa-sms"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">SMS Alerts</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.smsAlerts ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.smsAlerts" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.bbpsFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-bell"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">BBPS Facility</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.bbpsFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.bbpsFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.insuranceCover">
|
||||
<div class="dig-card__icon"><i class="fas fa-shield-alt"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">Insurance Cover</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.insuranceCover ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.insuranceCover" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.bepgFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-plug"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">BEPG Facility</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.bepgFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.bepgFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.neftFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-money-check-alt"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">NEFT Facility</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.neftFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.neftFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.rtgsFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-money-check"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">RTGS Facility</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.rtgsFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.rtgsFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.interBankTotalCapFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-balance-scale"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">Inter-Bank Total Cap</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.interBankTotalCapFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.interBankTotalCapFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.withdrawalFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-arrow-right-arrow-left"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">Withdrawal Facility</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.withdrawalFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.withdrawalFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.depositFacilityKyc">
|
||||
<div class="dig-card__icon"><i class="fas fa-id-card"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">Deposit Facility (KYC)</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.depositFacilityKyc ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.depositFacilityKyc" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.virtualDebitCard">
|
||||
<div class="dig-card__icon"><i class="fas fa-credit-card"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">Virtual Debit Card</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.virtualDebitCard ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.virtualDebitCard" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+165
-154
@@ -6,194 +6,205 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$border: #d9e2ec;
|
||||
$border-strong: #c7d2de;
|
||||
$text: #24364b;
|
||||
$blue: #2563eb;
|
||||
$blue-light: #eff6ff;
|
||||
$green: #16a34a;
|
||||
$green-light: #f0fdf4;
|
||||
$green-border: #bbf7d0;
|
||||
$border: #e2e8f0;
|
||||
$text: #1e293b;
|
||||
$muted: #64748b;
|
||||
$header-bg: #f8fafc;
|
||||
$gray-bg: #f8fafc;
|
||||
|
||||
.spl-section {
|
||||
// ── Wrapper ──────────────────────────────────────────────
|
||||
.dig-wrapper {
|
||||
border: 1px solid $border;
|
||||
border-radius: 12px;
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 4px rgb(0 0 0 / 6%);
|
||||
}
|
||||
|
||||
.spl-section__header {
|
||||
// ── Header ───────────────────────────────────────────────
|
||||
.dig-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px 20px;
|
||||
background: $header-bg;
|
||||
gap: 14px;
|
||||
padding: 18px 24px;
|
||||
background: $gray-bg;
|
||||
border-bottom: 1px solid $border;
|
||||
}
|
||||
|
||||
.spl-section__icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
background: #eff6ff;
|
||||
color: #2563eb;
|
||||
font-size: 14px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.spl-section__title {
|
||||
flex: 1 1 auto;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
}
|
||||
|
||||
.spl-section__badge {
|
||||
flex: 0 0 auto;
|
||||
padding: 5px 10px;
|
||||
border-radius: 999px;
|
||||
background: #dbeafe;
|
||||
color: #1d4ed8;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.spl-section__body {
|
||||
padding: 18px 20px 20px;
|
||||
}
|
||||
|
||||
.spl-table-wrap {
|
||||
overflow-x: auto;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.spl-table {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid $border;
|
||||
|
||||
col.spl-table__col--label {
|
||||
width: 22%;
|
||||
&__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
background: $blue-light;
|
||||
color: $blue;
|
||||
font-size: 16px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
col.spl-table__col--value {
|
||||
width: 26%;
|
||||
&__text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid $border;
|
||||
padding: 6px 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f8fafc;
|
||||
color: #64748b;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
&__title {
|
||||
margin: 0 0 2px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
background: #f8fafc;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td {
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
&__sub {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: $muted;
|
||||
}
|
||||
|
||||
&__badge {
|
||||
flex: 0 0 auto;
|
||||
padding: 4px 12px;
|
||||
border-radius: 999px;
|
||||
background: #dbeafe;
|
||||
color: $blue;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
}
|
||||
|
||||
.spl-input,
|
||||
.spl-select {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.spl-input {
|
||||
height: 30px;
|
||||
border: 1px solid $border-strong;
|
||||
border-radius: 2px;
|
||||
padding: 0 8px;
|
||||
background: #f8fafc;
|
||||
color: $muted;
|
||||
}
|
||||
|
||||
.spl-select {
|
||||
height: 30px;
|
||||
border: 1px solid $border-strong;
|
||||
border-radius: 2px;
|
||||
padding: 0 6px;
|
||||
background: #f8fafc;
|
||||
color: $muted;
|
||||
}
|
||||
|
||||
.spl-features {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.spl-features__header {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $muted;
|
||||
margin-bottom: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.spl-features__grid {
|
||||
// ── Grid ─────────────────────────────────────────────────
|
||||
.dig-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px 16px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0;
|
||||
|
||||
@media (width <= 900px) {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
@media (width <= 960px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media (width <= 640px) {
|
||||
@media (width <= 580px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.spl-feature {
|
||||
// ── Card ─────────────────────────────────────────────────
|
||||
.dig-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: $text;
|
||||
}
|
||||
|
||||
.spl-feature__label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
color: #334155;
|
||||
gap: 12px;
|
||||
padding: 14px 18px;
|
||||
border-right: 1px solid $border;
|
||||
border-bottom: 1px solid $border;
|
||||
background: #fff;
|
||||
transition: background 0.18s ease;
|
||||
cursor: pointer;
|
||||
|
||||
i {
|
||||
color: #2563eb;
|
||||
font-size: 12px;
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
&:nth-child(3n) {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
&--on {
|
||||
background: $green-light;
|
||||
|
||||
.dig-card__icon {
|
||||
background: $green-border;
|
||||
color: $green;
|
||||
}
|
||||
|
||||
.dig-card__status {
|
||||
color: $green;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 9px;
|
||||
background: $blue-light;
|
||||
color: $blue;
|
||||
font-size: 14px;
|
||||
flex: 0 0 auto;
|
||||
transition:
|
||||
background 0.18s,
|
||||
color 0.18s;
|
||||
}
|
||||
|
||||
&__body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__status {
|
||||
font-size: 11px;
|
||||
color: $muted;
|
||||
transition: color 0.18s;
|
||||
}
|
||||
}
|
||||
|
||||
.spl-note {
|
||||
margin-top: 16px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
color: $muted;
|
||||
font-size: 11px;
|
||||
line-height: 1.6;
|
||||
// ── Toggle Switch ─────────────────────────────────────────
|
||||
.dig-toggle {
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
cursor: pointer;
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
&:checked + .dig-toggle__track {
|
||||
background: $green;
|
||||
border-color: $green;
|
||||
}
|
||||
|
||||
&:checked + .dig-toggle__track .dig-toggle__thumb {
|
||||
transform: translateX(18px);
|
||||
}
|
||||
}
|
||||
|
||||
&__track {
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 22px;
|
||||
border-radius: 999px;
|
||||
background: #cbd5e1;
|
||||
border: 1.5px solid #94a3b8;
|
||||
position: relative;
|
||||
transition:
|
||||
background 0.2s,
|
||||
border-color 0.2s;
|
||||
}
|
||||
|
||||
&__thumb {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 3px rgb(0 0 0 / 20%);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-21
@@ -39,31 +39,16 @@ const DEFAULT_FACILITY_CONFIG = {
|
||||
]
|
||||
})
|
||||
export class CurrentAccountDigitalStepComponent {
|
||||
private _currentAccountProductTemplate: any;
|
||||
facilityConfig = { ...DEFAULT_FACILITY_CONFIG };
|
||||
|
||||
@Input()
|
||||
set currentAccountProductTemplate(value: any) {
|
||||
this._currentAccountProductTemplate = value;
|
||||
}
|
||||
get currentAccountProductTemplate(): any {
|
||||
return this._currentAccountProductTemplate;
|
||||
if (value?.accountFacilityConfig) {
|
||||
this.facilityConfig = { ...DEFAULT_FACILITY_CONFIG, ...value.accountFacilityConfig };
|
||||
}
|
||||
}
|
||||
|
||||
get accountFacilityConfig() {
|
||||
return this._currentAccountProductTemplate?.accountFacilityConfig || DEFAULT_FACILITY_CONFIG;
|
||||
}
|
||||
|
||||
get accountFacilityLimits(): any[] {
|
||||
return this._currentAccountProductTemplate?.accountFacilityLimits || [];
|
||||
}
|
||||
|
||||
get depositLimitKyc() {
|
||||
return (
|
||||
this._currentAccountProductTemplate?.depositLimitKyc || {
|
||||
general: { kycType: '', limit: '' },
|
||||
minor: { kycType: '', limit: '' },
|
||||
smallSaving: { kycType: '', limit: '' }
|
||||
}
|
||||
);
|
||||
get currentAccountDigital() {
|
||||
return { accountFacilityConfig: { ...this.facilityConfig } };
|
||||
}
|
||||
}
|
||||
|
||||
+328
-386
@@ -7,10 +7,11 @@
|
||||
-->
|
||||
|
||||
<div class="preview-wrapper">
|
||||
<!-- Details -->
|
||||
<!-- ══ 1. Account Details ════════════════════════════════════════ -->
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<span class="preview-section__title">{{ 'labels.heading.Details' | translate }}</span>
|
||||
<i class="fas fa-info-circle preview-section__icon"></i>
|
||||
<span class="preview-section__title">Account Details</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
@@ -38,27 +39,28 @@
|
||||
<mifosx-external-identifier externalId="{{ currentAccount.externalId }}"></mifosx-external-identifier>
|
||||
</span>
|
||||
} @else {
|
||||
<span class="preview-value">{{ 'labels.inputs.Unassigned' | translate }}</span>
|
||||
<span class="preview-value preview-muted">Unassigned</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product Classification -->
|
||||
<!-- ══ 2. Product Classification ════════════════════════════════ -->
|
||||
@if (
|
||||
currentAccountProductTemplate?.accountType ||
|
||||
currentAccountProductTemplate?.customerSegment ||
|
||||
currentAccountProductTemplate?.productCode ||
|
||||
currentAccountProductTemplate?.previewAccountNo ||
|
||||
currentAccountProductTemplate?.effectiveDate ||
|
||||
currentAccountProductTemplate?.effectiveTo ||
|
||||
currentAccountProductTemplate?.endDate ||
|
||||
currentAccountProductTemplate?.introducerRequired !== undefined ||
|
||||
currentAccountProductTemplate?.shareLinkingEnabled !== undefined
|
||||
currentAccountProductTemplate?.endDate
|
||||
) {
|
||||
<div class="preview-section">
|
||||
<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">From Product</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
@@ -77,9 +79,15 @@
|
||||
@if (currentAccountProductTemplate?.productCode) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Product Code</span>
|
||||
<span class="preview-value">{{ currentAccountProductTemplate?.productCode }}</span>
|
||||
<span class="preview-value preview-mono">{{ currentAccountProductTemplate?.productCode }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Account No</span>
|
||||
<span class="preview-value preview-mono">{{
|
||||
currentAccountProductTemplate?.previewAccountNo || currentAccountTemplate?.accountNo || 'Auto-generated'
|
||||
}}</span>
|
||||
</div>
|
||||
@if (currentAccountProductTemplate?.effectiveDate) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Effective Date</span>
|
||||
@@ -98,418 +106,352 @@
|
||||
<span class="preview-value">{{ currentAccountProductTemplate?.endDate | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (
|
||||
currentAccountProductTemplate?.introducerRequired !== undefined &&
|
||||
currentAccountProductTemplate?.introducerRequired !== null
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Introducer Required</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="currentAccountProductTemplate?.introducerRequired"
|
||||
>
|
||||
{{ currentAccountProductTemplate?.introducerRequired | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Share Linking Enabled</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="currentAccountProductTemplate?.shareLinkingEnabled"
|
||||
>
|
||||
{{ currentAccountProductTemplate?.shareLinkingEnabled | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ══ 3. Introducer Details (if selected) ══════════════════════ -->
|
||||
@if (currentAccount?.introducerId) {
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-user-tie preview-section__icon"></i>
|
||||
<span class="preview-section__title">Introducer Details</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Name</span>
|
||||
<span class="preview-value">{{ currentAccount?.introducerName || '—' }}</span>
|
||||
</div>
|
||||
@if (currentAccount?.introducerCif) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Introducer Required</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="currentAccountProductTemplate?.introducerRequired"
|
||||
>
|
||||
{{ currentAccountProductTemplate?.introducerRequired | yesNo }}
|
||||
</span>
|
||||
<span class="preview-label">CIF No</span>
|
||||
<span class="preview-value preview-mono">{{ currentAccount.introducerCif }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (
|
||||
currentAccountProductTemplate?.shareLinkingEnabled !== undefined &&
|
||||
currentAccountProductTemplate?.shareLinkingEnabled !== null
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Share Linking Enabled</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="currentAccountProductTemplate?.shareLinkingEnabled"
|
||||
>
|
||||
{{ currentAccountProductTemplate?.shareLinkingEnabled | yesNo }}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Client ID</span>
|
||||
<span class="preview-value preview-mono">{{ currentAccount.introducerId }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ══ 4. Currency & Account Flags ══════════════════════════════ -->
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-coins preview-section__icon"></i>
|
||||
<span class="preview-section__title">Currency & Account Flags</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Currency</span>
|
||||
<span class="preview-value">{{ currencyName }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Decimal Places</span>
|
||||
<span class="preview-value">{{ decimalPlaces }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Currency In Multiples Of</span>
|
||||
<span class="preview-value">{{ inMultiplesOf }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Balance Swap Option</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="currentAccountCurrencyFlags?.balanceSwapOption"
|
||||
>
|
||||
{{ currentAccountCurrencyFlags?.balanceSwapOption | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">MSME</span>
|
||||
<span class="preview-value preview-badge" [class.preview-badge--yes]="currentAccountCurrencyFlags?.msme">
|
||||
{{ currentAccountCurrencyFlags?.msme | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Auto Debit Facility</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="currentAccountCurrencyFlags?.autoDebitFacility"
|
||||
>
|
||||
{{ currentAccountCurrencyFlags?.autoDebitFacility | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Dormancy Alert</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="currentAccountProductTemplate?.dormancyAlert"
|
||||
>
|
||||
{{ currentAccountProductTemplate?.dormancyAlert | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Nomination Facility</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="currentAccountProductTemplate?.nominationFacility"
|
||||
>
|
||||
{{ currentAccountProductTemplate?.nominationFacility | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ 5. Dormancy Monitoring ════════════════════════════════════ -->
|
||||
@if (currentAccountProductTemplate?.dormancyAlert || currentAccountProductTemplate?.isDormancyTrackingActive) {
|
||||
<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"
|
||||
[class.preview-section__badge--active]="currentAccountProductTemplate?.isDormancyTrackingActive"
|
||||
>
|
||||
{{ currentAccountProductTemplate?.isDormancyTrackingActive ? 'Active' : 'Inactive' }}
|
||||
</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">
|
||||
@if (currentAccountProductTemplate?.daysToInactive != null) {
|
||||
After {{ currentAccountProductTemplate.daysToInactive }} days of no transaction
|
||||
} @else {
|
||||
Not configured
|
||||
}
|
||||
</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">
|
||||
@if (currentAccountProductTemplate?.daysToDormancy != null) {
|
||||
{{ currentAccountProductTemplate.daysToDormancy }} days after inactive
|
||||
} @else {
|
||||
Not configured
|
||||
}
|
||||
</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">
|
||||
@if (currentAccountProductTemplate?.daysToEscheat != null) {
|
||||
{{ currentAccountProductTemplate.daysToEscheat }} days after dormant
|
||||
} @else {
|
||||
Not configured
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-dorm-flags">
|
||||
<span class="preview-dorm-flag" [class.preview-dorm-flag--on]="currentAccountProductTemplate?.dormancyAlert">
|
||||
<i class="fas fa-bell"></i> Alert
|
||||
{{ currentAccountProductTemplate?.dormancyAlert ? 'Enabled' : 'Disabled' }}
|
||||
</span>
|
||||
<span
|
||||
class="preview-dorm-flag"
|
||||
[class.preview-dorm-flag--on]="currentAccountProductTemplate?.isDormancyTrackingActive"
|
||||
>
|
||||
<i class="fas fa-eye"></i> Tracking
|
||||
{{ currentAccountProductTemplate?.isDormancyTrackingActive ? 'Active' : 'Inactive' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ══ 6. Nominee Details (if filled) ═══════════════════════════ -->
|
||||
@if (currentAccountNominee) {
|
||||
<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">
|
||||
{{ currentAccountNominee?.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">Name</span>
|
||||
<span class="preview-value">{{
|
||||
currentAccountNominee?.fullName || currentAccountNominee?.displayName || '—'
|
||||
}}</span>
|
||||
</div>
|
||||
@if (currentAccountNominee?.cifNumber) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">CIF No</span>
|
||||
<span class="preview-value preview-mono">{{ currentAccountNominee.cifNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Relationship</span>
|
||||
<span class="preview-value">{{ currentAccountNominee?.relationship || '—' }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Share %</span>
|
||||
<span class="preview-value">{{ currentAccountNominee?.nomineeShare ?? '—' }}%</span>
|
||||
</div>
|
||||
@if (currentAccountNominee?.dateOfBirth) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Date of Birth</span>
|
||||
<span class="preview-value">{{ currentAccountNominee?.dateOfBirth | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (currentAccountNominee?.nationality) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Nationality</span>
|
||||
<span class="preview-value">{{ currentAccountNominee?.nationality }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (currentAccountNominee?.mobileNo) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Mobile No</span>
|
||||
<span class="preview-value preview-mono">{{ currentAccountNominee?.mobileNo }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (currentAccountNominee?.aadhaarNumber) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Aadhaar No</span>
|
||||
<span class="preview-value preview-mono">{{ currentAccountNominee?.aadhaarNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (currentAccountNominee?.panNumber) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">PAN No</span>
|
||||
<span class="preview-value preview-mono">{{ currentAccountNominee?.panNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (currentAccountNominee?.guardianName) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Guardian Name</span>
|
||||
<span class="preview-value">{{ currentAccountNominee?.guardianName }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (currentAccountNominee?.guardianRelationship) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Guardian Relationship</span>
|
||||
<span class="preview-value">{{ currentAccountNominee?.guardianRelationship }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (currentAccountNominee?.remarks) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Remarks</span>
|
||||
<span class="preview-value">{{ currentAccountNominee?.remarks }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Currency -->
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<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">{{ currentAccountTermsForm?.get('currencyCode')?.value }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Decimal Places' | translate }}</span>
|
||||
<span class="preview-value">{{ currentAccountProductTemplate?.currency?.decimalPlaces }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Currency In Multiples Of' | translate }}</span>
|
||||
<span class="preview-value">{{ currentAccountProductTemplate?.currency?.inMultiplesOf }}</span>
|
||||
</div>
|
||||
@if (
|
||||
currentAccountProductTemplate?.dormancyAlert !== undefined &&
|
||||
currentAccountProductTemplate?.dormancyAlert !== null
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Dormancy Alert</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="currentAccountProductTemplate?.dormancyAlert"
|
||||
>
|
||||
{{ currentAccountProductTemplate?.dormancyAlert | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@if (
|
||||
currentAccountProductTemplate?.balanceSwapOption !== undefined &&
|
||||
currentAccountProductTemplate?.balanceSwapOption !== null
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Balance Swap Option</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="currentAccountProductTemplate?.balanceSwapOption"
|
||||
>
|
||||
{{ currentAccountProductTemplate?.balanceSwapOption | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@if (currentAccountProductTemplate?.msme !== undefined && currentAccountProductTemplate?.msme !== null) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">MSME</span>
|
||||
<span class="preview-value preview-badge" [class.preview-badge--yes]="currentAccountProductTemplate?.msme">
|
||||
{{ currentAccountProductTemplate?.msme | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@if (
|
||||
currentAccountProductTemplate?.nominationFacility !== undefined &&
|
||||
currentAccountProductTemplate?.nominationFacility !== null
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Nomination Facility</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="currentAccountProductTemplate?.nominationFacility"
|
||||
>
|
||||
{{ currentAccountProductTemplate?.nominationFacility | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@if (
|
||||
currentAccountProductTemplate?.autoDebitFacility !== undefined &&
|
||||
currentAccountProductTemplate?.autoDebitFacility !== null
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Auto Debit Facility</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="currentAccountProductTemplate?.autoDebitFacility"
|
||||
>
|
||||
{{ currentAccountProductTemplate?.autoDebitFacility | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Terms -->
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<span class="preview-section__title">{{ 'labels.heading.Terms' | translate }}</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Interest Posting Frequency</span>
|
||||
<span class="preview-value">{{
|
||||
termPreview?.interestPostingPeriodType
|
||||
| find: currentAccountProductTemplate?.interestPostingPeriodTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Auto Interest Post</span>
|
||||
<span class="preview-value preview-badge" [class.preview-badge--yes]="termPreview?.autoPost">
|
||||
{{ termPreview?.autoPost | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
@if (termPreview?.frequencyFixDay !== undefined && termPreview?.frequencyFixDay !== null) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Frequency Fix Day</span>
|
||||
<span class="preview-value">{{ termPreview?.frequencyFixDay }} Days</span>
|
||||
</div>
|
||||
}
|
||||
@if (termPreview?.lastInterestDate) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Last Interest Date</span>
|
||||
<span class="preview-value">{{ termPreview?.lastInterestDate | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings -->
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<span class="preview-section__title">{{ 'labels.heading.Settings' | translate }}</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
@if (
|
||||
settingsPreview?.minRequiredOpeningBalance !== undefined &&
|
||||
settingsPreview?.minRequiredOpeningBalance !== null &&
|
||||
settingsPreview?.minRequiredOpeningBalance !== ''
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Minimum Opening Balance' | translate }}</span>
|
||||
<span class="preview-value">{{ settingsPreview?.minRequiredOpeningBalance | formatNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Enforce Minimum Balance' | translate }}</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="settingsPreview?.enforceMinRequiredBalance"
|
||||
>
|
||||
{{ settingsPreview?.enforceMinRequiredBalance | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
@if (
|
||||
settingsPreview?.minRequiredBalance !== undefined &&
|
||||
settingsPreview?.minRequiredBalance !== null &&
|
||||
settingsPreview?.minRequiredBalance !== ''
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Minimum Balance' | translate }}</span>
|
||||
<span class="preview-value">{{ settingsPreview?.minRequiredBalance | formatNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Balance Frequency</span>
|
||||
<span class="preview-value">{{ settingsPreview?.balanceFrequency || 'Unassigned' }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Apply Withdrawal Fee for Transfers' | translate }}</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="settingsPreview?.withdrawalFeeForTransfers"
|
||||
>
|
||||
{{ settingsPreview?.withdrawalFeeForTransfers | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Enable Lock-in Period</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="settingsPreview?.enableLockinPeriod || !!settingsPreview?.lockinPeriodFrequency"
|
||||
>
|
||||
{{ settingsPreview?.enableLockinPeriod || !!settingsPreview?.lockinPeriodFrequency | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
@if (settingsPreview?.lockinPeriodFrequency) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Lock-in Period' | translate }}</span>
|
||||
<span class="preview-value">
|
||||
{{ settingsPreview?.lockinPeriodFrequency }}
|
||||
{{
|
||||
settingsPreview?.lockinPeriodFrequencyType
|
||||
| find: currentAccountProductTemplate?.lockinPeriodFrequencyTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Account Closed Zero Balance In Day Closing</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="settingsPreview?.accountClosedZeroBalanceInDayClosing"
|
||||
>
|
||||
{{ settingsPreview?.accountClosedZeroBalanceInDayClosing | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Is Overdraft Allowed' | translate }}</span>
|
||||
<span class="preview-value preview-badge" [class.preview-badge--yes]="settingsPreview?.allowOverdraft">
|
||||
{{ settingsPreview?.allowOverdraft | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
@if (settingsPreview?.allowOverdraft) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Avg Balance Calc Period</span>
|
||||
<span class="preview-value">{{ settingsPreview?.avgBalanceCalcPeriod || 'Unassigned' }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Valid For Period</span>
|
||||
<span class="preview-value">{{ settingsPreview?.validForPeriod || 'Unassigned' }}</span>
|
||||
</div>
|
||||
@if (
|
||||
settingsPreview?.overdraftLimit !== undefined &&
|
||||
settingsPreview?.overdraftLimit !== null &&
|
||||
settingsPreview?.overdraftLimit !== ''
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">OD Limit %</span>
|
||||
<span class="preview-value">{{ settingsPreview?.overdraftLimit | formatNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (
|
||||
settingsPreview?.nominalAnnualInterestRateOverdraft !== undefined &&
|
||||
settingsPreview?.nominalAnnualInterestRateOverdraft !== null &&
|
||||
settingsPreview?.nominalAnnualInterestRateOverdraft !== ''
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Interest on OD %</span>
|
||||
<span class="preview-value">{{
|
||||
settingsPreview?.nominalAnnualInterestRateOverdraft | formatNumber
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">OD Account Closed Zero Balance In Day Closing</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="settingsPreview?.odAccountClosedZeroBalanceInDayClosing"
|
||||
>
|
||||
{{ settingsPreview?.odAccountClosedZeroBalanceInDayClosing | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (hasDigitalSection) {
|
||||
<!-- ══ 7. Non-Individual Details (if applicable) ════════════════ -->
|
||||
@if (currentAccount?.partners?.length > 0) {
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<span class="preview-section__title">Digital</span>
|
||||
<i class="fas fa-building preview-section__icon"></i>
|
||||
<span class="preview-section__title">Non-Individual Account Details</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
@for (facility of digitalFacilityLabels; track facility.key) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ facility.label }}</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="digitalPreview?.accountFacilityConfig?.[facility.key]"
|
||||
>
|
||||
{{ digitalPreview?.accountFacilityConfig?.[facility.key] | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (digitalPreview?.accountFacilityLimits?.length) {
|
||||
<table class="mat-elevation-z1 preview-table">
|
||||
<!-- Partners -->
|
||||
<div class="preview-ni-label">Partners / Members</div>
|
||||
<div class="preview-ni-table-wrap">
|
||||
<table class="preview-ni-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Limit Type</th>
|
||||
<th>General Limit</th>
|
||||
<th>Minor Account Limit</th>
|
||||
<th>Small Saving Limit</th>
|
||||
<th>Partner Name</th>
|
||||
<th>CIF No</th>
|
||||
<th>Ownership %</th>
|
||||
<th>Signing Authority</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (row of digitalPreview.accountFacilityLimits; track row.limitType) {
|
||||
@for (p of currentAccount.partners; track p.clientId) {
|
||||
<tr>
|
||||
<td>{{ row.limitType }}</td>
|
||||
<td>{{ row.generalLimit || 'Unassigned' }}</td>
|
||||
<td>{{ row.minorAccountLimit || 'Unassigned' }}</td>
|
||||
<td>{{ row.smallSavingLimit || 'Unassigned' }}</td>
|
||||
<td>{{ p.clientName || '—' }}</td>
|
||||
<td class="preview-mono">{{ p.clientCif || '—' }}</td>
|
||||
<td>{{ p.sharePercentage }}%</td>
|
||||
<td>
|
||||
<span class="preview-badge" [class.preview-badge--yes]="p.isMandatory">
|
||||
{{ p.isMandatory ? 'Mandatory' : 'Optional' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@if (digitalPreview?.depositLimitKyc?.general) {
|
||||
<table class="mat-elevation-z1 preview-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account Type</th>
|
||||
<th>KYC Type</th>
|
||||
<th>Limit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>General</td>
|
||||
<td>{{ digitalPreview.depositLimitKyc.general.kycType || 'Unassigned' }}</td>
|
||||
<td>{{ digitalPreview.depositLimitKyc.general.limit || 'Unassigned' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Minor</td>
|
||||
<td>{{ digitalPreview.depositLimitKyc.minor?.kycType || 'Unassigned' }}</td>
|
||||
<td>{{ digitalPreview.depositLimitKyc.minor?.limit || 'Unassigned' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Small Saving</td>
|
||||
<td>{{ digitalPreview.depositLimitKyc.smallSaving?.kycType || 'Unassigned' }}</td>
|
||||
<td>{{ digitalPreview.depositLimitKyc.smallSaving?.limit || 'Unassigned' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Signatory Rights -->
|
||||
@if (currentAccount?.signatoryRights) {
|
||||
<div class="preview-ni-mode">
|
||||
<i class="fas fa-how_to_vote"></i>
|
||||
<span class="preview-ni-mode__label">Mode of Operation:</span>
|
||||
<strong>{{ currentAccount.signatoryRights }}</strong>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Charges -->
|
||||
@if (currentAccount?.charges?.length) {
|
||||
<!-- ══ 8. Digital Facilities ════════════════════════════════════ -->
|
||||
@if (hasDigitalSection) {
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<span class="preview-section__title">{{ 'labels.inputs.Charges' | translate }}</span>
|
||||
<i class="fas fa-mobile-alt preview-section__icon"></i>
|
||||
<span class="preview-section__title">Digital Facilities</span>
|
||||
<span class="preview-section__badge preview-section__badge--digital">User Configured</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<table class="mat-elevation-z1 preview-table" mat-table [dataSource]="currentAccount.charges">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.name + ', ' + charge.currency?.displaySymbol }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="chargeCalculationType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.chargeCalculationType?.value | translateKey: 'catalogs' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.amount | formatNumber }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="chargeTimeType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.chargeTimeType?.value }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="date">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Date' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
@if (
|
||||
charge.chargeTimeType?.value === 'Specified due date' || charge.chargeTimeType?.value === 'Weekly Fee'
|
||||
) {
|
||||
<span>{{ (charge.dueDate | dateFormat) || 'Unassigned' }}</span>
|
||||
} @else if (
|
||||
charge.chargeTimeType?.value === 'Monthly Fee' || charge.chargeTimeType?.value === 'Annual Fee'
|
||||
) {
|
||||
<span>{{ (charge.feeOnMonthDay | dateFormat) || 'Unassigned' }}</span>
|
||||
} @else {
|
||||
<span>N/A</span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="repaymentsEvery">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Repayments Every' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.feeInterval || 'Not Provided' }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="chargesDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: chargesDisplayedColumns"></tr>
|
||||
</table>
|
||||
<div class="preview-digital-grid">
|
||||
@for (facility of digitalFacilityLabels; track facility.key) {
|
||||
<div class="preview-digital-card" [class.preview-digital-card--on]="digitalFacilityConfig?.[facility.key]">
|
||||
<span class="preview-digital-label">{{ facility.label }}</span>
|
||||
<span
|
||||
class="preview-digital-status"
|
||||
[class.preview-digital-status--on]="digitalFacilityConfig?.[facility.key]"
|
||||
>
|
||||
{{ digitalFacilityConfig?.[facility.key] ? 'Enabled' : 'Disabled' }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
+300
-24
@@ -9,29 +9,65 @@
|
||||
.preview-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
gap: 1rem;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.preview-section {
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 3px rgb(0 0 0 / 6%);
|
||||
box-shadow: 0 1px 4px rgb(0 0 0 / 5%);
|
||||
|
||||
&__header {
|
||||
background: #f5f5f5;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
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 {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
letter-spacing: 0.06em;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__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;
|
||||
|
||||
&--digital {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
border-color: #fde68a;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: #dcfce7 !important;
|
||||
color: #15803d !important;
|
||||
border-color: #bbf7d0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +77,7 @@
|
||||
|
||||
.preview-grid {
|
||||
display: grid;
|
||||
gap: 0.75rem 1.5rem;
|
||||
gap: 0.85rem 1.5rem;
|
||||
|
||||
&--three {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
@@ -59,36 +95,276 @@
|
||||
.preview-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.preview-label {
|
||||
font-size: 0.72rem;
|
||||
color: #888;
|
||||
font-weight: 500;
|
||||
font-size: 0.68rem;
|
||||
color: #94a3b8;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.preview-value {
|
||||
font-size: 0.92rem;
|
||||
color: #222;
|
||||
font-size: 0.9rem;
|
||||
color: #1e293b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.preview-muted {
|
||||
color: #94a3b8;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.preview-mono {
|
||||
font-family: monospace;
|
||||
font-size: 0.88rem;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.preview-badge {
|
||||
display: inline-block;
|
||||
padding: 0.1rem 0.55rem;
|
||||
padding: 0.15rem 0.6rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.78rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
background: #ffebee;
|
||||
color: #c62828;
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
width: fit-content;
|
||||
|
||||
&--yes {
|
||||
background: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
background: #f0fdf4;
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
&--warn {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
}
|
||||
|
||||
// Digital facilities grid
|
||||
.preview-digital-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 8px;
|
||||
|
||||
@media (width <= 900px) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.preview-digital-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 10px 12px;
|
||||
border: 1.5px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
|
||||
&--on {
|
||||
border-color: #86efac;
|
||||
background: #f0fdf4;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-digital-label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.preview-digital-status {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: #dc2626;
|
||||
|
||||
&--on {
|
||||
color: #15803d;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Dormancy Monitoring ───────────────────────────────────────────
|
||||
.preview-dormancy-timeline {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.preview-dorm-step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
|
||||
&__dot {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
&--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;
|
||||
}
|
||||
|
||||
.preview-dorm-flags {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.preview-dorm-flag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 11.5px;
|
||||
font-weight: 600;
|
||||
background: #f1f5f9;
|
||||
color: #94a3b8;
|
||||
border: 1px solid #e2e8f0;
|
||||
|
||||
i {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
&--on {
|
||||
background: #f0fdf4;
|
||||
color: #15803d;
|
||||
border-color: #bbf7d0;
|
||||
}
|
||||
}
|
||||
|
||||
// Non-Individual section
|
||||
.preview-ni-label {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: #64748b;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.preview-ni-table-wrap {
|
||||
overflow-x: auto;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.preview-ni-tbl {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
|
||||
th {
|
||||
padding: 8px 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: #374151;
|
||||
text-transform: uppercase;
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 9px 12px;
|
||||
color: #1e293b;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-ni-mode {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: #374151;
|
||||
padding: 8px 12px;
|
||||
background: #f0fdf4;
|
||||
border: 1px solid #bbf7d0;
|
||||
border-radius: 8px;
|
||||
|
||||
&__label {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+25
-12
@@ -83,18 +83,15 @@ export class CurrentAccountPreviewStepComponent implements OnChanges {
|
||||
|
||||
private translateService = inject(TranslateService);
|
||||
|
||||
/** Current Account Product Template */
|
||||
@Input() currentAccountProductTemplate: any;
|
||||
/** Current Account Template */
|
||||
@Input() currentAccountTemplate: any;
|
||||
/** Current Account Terms Form */
|
||||
@Input() currentAccountTermsForm: any;
|
||||
/** Current Account Term Form */
|
||||
@Input() currentAccountTermForm: any;
|
||||
/** Current Account Settings Form */
|
||||
@Input() currentAccountSettingsForm: any;
|
||||
/** Current Account */
|
||||
@Input() currentAccount: any;
|
||||
@Input() currentAccountDigital: any;
|
||||
@Input() currentAccountNominee: any;
|
||||
@Input() currentAccountCurrencyFlags: any;
|
||||
@Input() activeClientMembers?: any;
|
||||
|
||||
/** Table Data Source */
|
||||
@@ -136,15 +133,31 @@ export class CurrentAccountPreviewStepComponent implements OnChanges {
|
||||
return this.currentAccountSettingsForm?.getRawValue?.() || this.currentAccount || {};
|
||||
}
|
||||
|
||||
get digitalPreview(): any {
|
||||
return this.currentAccountProductTemplate || {};
|
||||
get digitalFacilityConfig(): any {
|
||||
return (
|
||||
this.currentAccountDigital?.accountFacilityConfig ??
|
||||
this.currentAccountProductTemplate?.accountFacilityConfig ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
get hasDigitalSection(): boolean {
|
||||
return !!(
|
||||
this.digitalPreview?.accountFacilityConfig ||
|
||||
this.digitalPreview?.accountFacilityLimits?.length ||
|
||||
this.digitalPreview?.depositLimitKyc
|
||||
return !!this.digitalFacilityConfig;
|
||||
}
|
||||
|
||||
get currencyName(): string {
|
||||
return (
|
||||
this.currentAccountProductTemplate?.currency?.name ||
|
||||
this.currentAccountTermsForm?.get?.('currencyCode')?.value ||
|
||||
'—'
|
||||
);
|
||||
}
|
||||
|
||||
get decimalPlaces(): string | number {
|
||||
return this.currentAccountProductTemplate?.currency?.decimalPlaces ?? '—';
|
||||
}
|
||||
|
||||
get inMultiplesOf(): string | number {
|
||||
return this.currentAccountProductTemplate?.currency?.inMultiplesOf ?? '—';
|
||||
}
|
||||
}
|
||||
|
||||
+1
-7
@@ -49,13 +49,7 @@
|
||||
|
||||
<mat-form-field appearance="outline" class="sat-field" (click)="lastInterestDatePicker.open()">
|
||||
<mat-label>Last Interest Date</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[matDatepicker]="lastInterestDatePicker"
|
||||
formControlName="lastInterestDate"
|
||||
[min]="minDate"
|
||||
[max]="maxDate"
|
||||
/>
|
||||
<input matInput [matDatepicker]="lastInterestDatePicker" formControlName="lastInterestDate" />
|
||||
<mat-datepicker-toggle matSuffix [for]="lastInterestDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #lastInterestDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
+46
-38
@@ -34,7 +34,8 @@
|
||||
<fa-icon icon="eye" size="sm"></fa-icon>
|
||||
</ng-template>
|
||||
|
||||
<mat-step [stepControl]="recurringDepositAccountDetailsForm">
|
||||
<!-- Step 1: Details -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.DETAILS' | translate }}</ng-template>
|
||||
|
||||
<mifosx-recurring-deposits-account-details-step
|
||||
@@ -44,49 +45,52 @@
|
||||
</mifosx-recurring-deposits-account-details-step>
|
||||
</mat-step>
|
||||
|
||||
<mat-step [stepControl]="recurringDepositAccountTermsForm">
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.TERMS' | translate }}</ng-template>
|
||||
<!-- Step 2: Currency -->
|
||||
@if (recurringDepositsAccountProductTemplate) {
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>Currency</ng-template>
|
||||
|
||||
<mifosx-recurring-deposits-account-terms-step
|
||||
[recurringDepositsAccountTemplate]="recurringDepositsAccountTemplate"
|
||||
[recurringDepositsAccountProductTemplate]="recurringDepositsAccountProductTemplate"
|
||||
>
|
||||
</mifosx-recurring-deposits-account-terms-step>
|
||||
</mat-step>
|
||||
<mifosx-recurring-deposits-account-currency-step
|
||||
[recurringDepositsAccountProductTemplate]="recurringDepositsAccountProductTemplate"
|
||||
>
|
||||
</mifosx-recurring-deposits-account-currency-step>
|
||||
|
||||
<mat-step [stepControl]="recurringDepositAccountSettingsForm">
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.SETTINGS' | translate }}</ng-template>
|
||||
<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>
|
||||
|
||||
<mifosx-recurring-deposits-account-settings-step
|
||||
[recurringDepositsAccountTemplate]="recurringDepositsAccountTemplate"
|
||||
[recurringDepositsAccountProductTemplate]="recurringDepositsAccountProductTemplate"
|
||||
>
|
||||
</mifosx-recurring-deposits-account-settings-step>
|
||||
</mat-step>
|
||||
<!-- Step 3: Interest Rate Chart -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.INTEREST RATE CHART' | translate }}</ng-template>
|
||||
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.INTEREST RATE CHART' | translate }}</ng-template>
|
||||
<mifosx-recurring-deposits-account-interest-rate-chart-step
|
||||
[recurringDepositsAccountTemplate]="recurringDepositsAccountTemplate"
|
||||
[recurringDepositsAccountProductTemplate]="recurringDepositsAccountProductTemplate"
|
||||
[rdInterestRates]="rdInterestRates"
|
||||
>
|
||||
</mifosx-recurring-deposits-account-interest-rate-chart-step>
|
||||
|
||||
<mifosx-recurring-deposits-account-interest-rate-chart-step
|
||||
[recurringDepositsAccountTemplate]="recurringDepositsAccountTemplate"
|
||||
[recurringDepositsAccountProductTemplate]="recurringDepositsAccountProductTemplate"
|
||||
[rdInterestRates]="rdInterestRates"
|
||||
>
|
||||
</mifosx-recurring-deposits-account-interest-rate-chart-step>
|
||||
</mat-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>
|
||||
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.CHARGES' | translate }}</ng-template>
|
||||
|
||||
<mifosx-recurring-deposits-account-charges-step
|
||||
[recurringDepositsAccountTemplate]="recurringDepositsAccountTemplate"
|
||||
[recurringDepositsAccountProductTemplate]="recurringDepositsAccountProductTemplate"
|
||||
[recurringDepositAccountFormValid]="recurringDepositAccountFormValid"
|
||||
>
|
||||
</mifosx-recurring-deposits-account-charges-step>
|
||||
</mat-step>
|
||||
|
||||
@if (recurringDepositAccountFormValid) {
|
||||
<!-- Step 4: Preview -->
|
||||
<mat-step state="preview" completed>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.PREVIEW' | translate }}</ng-template>
|
||||
<mifosx-recurring-deposits-account-preview-step
|
||||
@@ -94,6 +98,10 @@
|
||||
[recurringDepositsAccountProductTemplate]="recurringDepositsAccountProductTemplate"
|
||||
[recurringDepositAccountData]="recurringDepositAccountData"
|
||||
[rdInterestRates]="rdInterestRates"
|
||||
[rdNominee]="rdNominee"
|
||||
[rdCurrencyFlags]="rdCurrencyFlags"
|
||||
[rdTenureDays]="rdTenureDays"
|
||||
[rdApplicableSlab]="rdApplicableSlab"
|
||||
(submitEvent)="submit()"
|
||||
>
|
||||
</mifosx-recurring-deposits-account-preview-step>
|
||||
|
||||
+12
@@ -5,3 +5,15 @@
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
:host ::ng-deep mat-stepper {
|
||||
background: transparent;
|
||||
|
||||
.mat-step-content {
|
||||
overflow: visible !important;
|
||||
}
|
||||
}
|
||||
|
||||
:host ::ng-deep mat-step-content {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
+44
-54
@@ -17,12 +17,17 @@ import { ProductsService } from 'app/products/products.service';
|
||||
|
||||
/** Step Components */
|
||||
import { RecurringDepositsAccountDetailsStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component';
|
||||
import { RecurringDepositsAccountTermsStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-terms-step/recurring-deposits-account-terms-step.component';
|
||||
import { RecurringDepositsAccountSettingsStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-settings-step/recurring-deposits-account-settings-step.component';
|
||||
import { RecurringDepositsAccountChargesStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-charges-step/recurring-deposits-account-charges-step.component';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper';
|
||||
import {
|
||||
MatStepper,
|
||||
MatStepperIcon,
|
||||
MatStep,
|
||||
MatStepLabel,
|
||||
MatStepperNext,
|
||||
MatStepperPrevious
|
||||
} from '@angular/material/stepper';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { RecurringDepositsAccountCurrencyStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component';
|
||||
import { RecurringDepositsAccountInterestRateChartStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-interest-rate-chart-step/recurring-deposits-account-interest-rate-chart-step.component';
|
||||
import { RecurringDepositsAccountPreviewStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
@@ -38,14 +43,14 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatStepper,
|
||||
MatStepperIcon,
|
||||
MatStepperNext,
|
||||
MatStepperPrevious,
|
||||
FaIconComponent,
|
||||
MatStep,
|
||||
MatStepLabel,
|
||||
RecurringDepositsAccountDetailsStepComponent,
|
||||
RecurringDepositsAccountTermsStepComponent,
|
||||
RecurringDepositsAccountSettingsStepComponent,
|
||||
RecurringDepositsAccountCurrencyStepComponent,
|
||||
RecurringDepositsAccountInterestRateChartStepComponent,
|
||||
RecurringDepositsAccountChargesStepComponent,
|
||||
RecurringDepositsAccountPreviewStepComponent
|
||||
]
|
||||
})
|
||||
@@ -57,15 +62,14 @@ export class CreateRecurringDepositsAccountComponent {
|
||||
private settingsService = inject(SettingsService);
|
||||
private productsService = inject(ProductsService);
|
||||
|
||||
/** Imports all the step component */
|
||||
@ViewChild(RecurringDepositsAccountDetailsStepComponent, { static: true })
|
||||
recurringDepositsAccountDetailsStep: RecurringDepositsAccountDetailsStepComponent;
|
||||
@ViewChild(RecurringDepositsAccountTermsStepComponent, { static: true })
|
||||
recurringDepositAccountTermsStep: RecurringDepositsAccountTermsStepComponent;
|
||||
@ViewChild(RecurringDepositsAccountSettingsStepComponent, { static: true })
|
||||
recurringDepositAccountSettingsStep: RecurringDepositsAccountSettingsStepComponent;
|
||||
@ViewChild(RecurringDepositsAccountChargesStepComponent, { static: true })
|
||||
recurringDepositAccountChargesStep: RecurringDepositsAccountChargesStepComponent;
|
||||
|
||||
@ViewChild(RecurringDepositsAccountCurrencyStepComponent)
|
||||
recurringDepositsAccountCurrencyStep: RecurringDepositsAccountCurrencyStepComponent;
|
||||
|
||||
@ViewChild(RecurringDepositsAccountInterestRateChartStepComponent)
|
||||
recurringDepositsAccountChartStep: RecurringDepositsAccountInterestRateChartStepComponent;
|
||||
|
||||
/** Recurring Deposits Account Template */
|
||||
recurringDepositsAccountTemplate: any;
|
||||
@@ -83,7 +87,6 @@ export class CreateRecurringDepositsAccountComponent {
|
||||
/**
|
||||
* Sets recurring deposits account product template.
|
||||
* Also fetches the active CBS base interest rate chart.
|
||||
* @param {any} $event API response (includes productId)
|
||||
*/
|
||||
setTemplate($event: any) {
|
||||
this.recurringDepositsAccountProductTemplate = $event;
|
||||
@@ -102,42 +105,36 @@ export class CreateRecurringDepositsAccountComponent {
|
||||
return this.recurringDepositsAccountDetailsStep.recurringDepositAccountDetailsForm;
|
||||
}
|
||||
|
||||
/** Get Recurring Deposit Account Terms Form Data */
|
||||
get recurringDepositAccountTermsForm() {
|
||||
return this.recurringDepositAccountTermsStep.recurringDepositAccountTermsForm;
|
||||
}
|
||||
|
||||
/** Get Recurring Deposit Account Settings Form Data */
|
||||
get recurringDepositAccountSettingsForm() {
|
||||
return this.recurringDepositAccountSettingsStep.recurringDepositAccountSettingsForm;
|
||||
}
|
||||
|
||||
/** Checks wheter all the forms in different steps are valid or not */
|
||||
get recurringDepositAccountFormValid() {
|
||||
return (
|
||||
this.recurringDepositAccountDetailsForm.valid &&
|
||||
this.recurringDepositAccountTermsForm.valid &&
|
||||
this.recurringDepositAccountSettingsForm.valid
|
||||
);
|
||||
}
|
||||
|
||||
/** Retrieves Data of all the forms */
|
||||
/** Retrieves Data of all forms */
|
||||
get recurringDepositAccountData() {
|
||||
return {
|
||||
...this.recurringDepositsAccountDetailsStep.recurringDepositAccountDetails,
|
||||
...this.recurringDepositAccountTermsStep.recurringDepositAccountTerms,
|
||||
...this.recurringDepositAccountSettingsStep.recurringDepositAccountSettings,
|
||||
...this.recurringDepositAccountChargesStep.recurringDepositAccountCharges
|
||||
...this.recurringDepositsAccountDetailsStep.recurringDepositAccountDetails
|
||||
};
|
||||
}
|
||||
|
||||
/** Retrieves Data of all forms except Currency to submit the data */
|
||||
get recurringDepositAccount() {
|
||||
return {
|
||||
...this.recurringDepositsAccountDetailsStep.recurringDepositAccountDetails,
|
||||
...this.recurringDepositAccountTermsStep.recurringDepositAccountTerms,
|
||||
...this.recurringDepositAccountSettingsStep.recurringDepositAccountSettings,
|
||||
...this.recurringDepositAccountChargesStep.recurringDepositAccountCharges
|
||||
...this.recurringDepositsAccountDetailsStep.recurringDepositAccountDetails
|
||||
};
|
||||
}
|
||||
|
||||
get rdNominee() {
|
||||
return this.recurringDepositsAccountCurrencyStep?.nomineeData ?? null;
|
||||
}
|
||||
|
||||
get rdTenureDays(): number | null {
|
||||
return this.recurringDepositsAccountChartStep?.tenureDays ?? null;
|
||||
}
|
||||
|
||||
get rdApplicableSlab(): any | null {
|
||||
return this.recurringDepositsAccountChartStep?.applicableSlab ?? null;
|
||||
}
|
||||
|
||||
get rdCurrencyFlags() {
|
||||
return {
|
||||
balanceSwapOption: this.recurringDepositsAccountCurrencyStep?.balanceSwapOption ?? false,
|
||||
msme: this.recurringDepositsAccountCurrencyStep?.msme ?? false,
|
||||
autoDebitFacility: this.recurringDepositsAccountCurrencyStep?.autoDebitFacility ?? false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -150,17 +147,10 @@ export class CreateRecurringDepositsAccountComponent {
|
||||
const monthDayFormat = 'dd MMMM';
|
||||
const recurringDepositAccount = {
|
||||
...this.recurringDepositAccount,
|
||||
...this.recurringDepositsAccountCurrencyStep.currencyStepData,
|
||||
clientId: this.recurringDepositsAccountTemplate.clientId,
|
||||
charges: this.recurringDepositAccount.charges.map((charge: any) => ({
|
||||
chargeId: charge.id,
|
||||
amount: charge.amount,
|
||||
dueDate: charge.dueDate && this.dateUtils.formatDate(charge.dueDate, dateFormat),
|
||||
feeOnMonthDay: charge.feeOnMonthDay,
|
||||
feeInterval: charge.feeInterval
|
||||
})),
|
||||
isCalendarInherited: this.recurringDepositAccount.recurringDepositAccount
|
||||
? this.recurringDepositAccount.recurringDepositAccount
|
||||
: false,
|
||||
charges: [],
|
||||
isCalendarInherited: false,
|
||||
submittedOnDate: this.dateUtils.formatDate(this.recurringDepositAccount.submittedOnDate, dateFormat),
|
||||
expectedFirstDepositOnDate: this.dateUtils.formatDate(
|
||||
this.recurringDepositAccount.expectedFirstDepositOnDate,
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<!--
|
||||
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="racc-section">
|
||||
<div class="racc-section__header">
|
||||
<i class="fas fa-coins"></i>
|
||||
<span>Currency & Account Status Configuration</span>
|
||||
<span class="racc-badge racc-badge--from">From Product</span>
|
||||
</div>
|
||||
<div class="racc-body">
|
||||
<div class="racc-grid">
|
||||
<mat-form-field appearance="outline" class="racc-field">
|
||||
<mat-label>Currency</mat-label>
|
||||
<input matInput [value]="recurringDepositsAccountProductTemplate?.currency?.name || ''" readonly />
|
||||
<mat-hint>Inherited from selected product</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="racc-field">
|
||||
<mat-label>Decimal Places</mat-label>
|
||||
<input matInput [value]="recurringDepositsAccountProductTemplate?.currency?.decimalPlaces ?? ''" readonly />
|
||||
<mat-hint>Inherited from selected product</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="racc-alert racc-alert--editable">
|
||||
<div class="racc-alert__content">
|
||||
<h4>Balance Swap Option</h4>
|
||||
<p>Enable automated transfer of excess funds to linked deposits or vice-versa to maintain liquidity.</p>
|
||||
</div>
|
||||
<mat-slide-toggle [(ngModel)]="balanceSwapOption" color="primary"></mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<div class="racc-alert racc-alert--editable">
|
||||
<div class="racc-alert__content">
|
||||
<h4>MSME</h4>
|
||||
<p>Micro, Small and Medium Enterprises — mark this account under MSME category.</p>
|
||||
</div>
|
||||
<mat-slide-toggle [(ngModel)]="msme" color="primary"></mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<div class="racc-alert racc-alert--editable">
|
||||
<div class="racc-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="racc-alert">
|
||||
<div class="racc-alert__content">
|
||||
<h4>Dormancy Alert</h4>
|
||||
<p>Enable automated notifications when an account becomes inactive based on product rules.</p>
|
||||
</div>
|
||||
<span class="racc-yn" [class.racc-yn--yes]="recurringDepositsAccountProductTemplate?.dormancyAlert">
|
||||
{{ recurringDepositsAccountProductTemplate?.dormancyAlert ? 'Yes' : 'No' }}
|
||||
</span>
|
||||
</div>
|
||||
@if (recurringDepositsAccountProductTemplate?.dormancyAlert) {
|
||||
<mifosx-dormancy-alert-panel
|
||||
[productTemplate]="recurringDepositsAccountProductTemplate"
|
||||
></mifosx-dormancy-alert-panel>
|
||||
}
|
||||
|
||||
<div class="racc-alert">
|
||||
<div class="racc-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="racc-yn" [class.racc-yn--yes]="recurringDepositsAccountProductTemplate?.nominationFacility">
|
||||
{{ recurringDepositsAccountProductTemplate?.nominationFacility ? 'Yes' : 'No' }}
|
||||
</span>
|
||||
</div>
|
||||
@if (recurringDepositsAccountProductTemplate?.nominationFacility) {
|
||||
<mifosx-nominee-form (nomineeChange)="onNomineeChange($event)"></mifosx-nominee-form>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-900: #202124;
|
||||
|
||||
.racc-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
span {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.racc-body {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.racc-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.racc-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.racc-alert {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
padding: 18px 24px;
|
||||
border-top: 1px solid $border;
|
||||
background: #f8fafc;
|
||||
|
||||
@media (width <= 760px) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&__content {
|
||||
h4 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: #64748b;
|
||||
max-width: 540px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.racc-yn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 48px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
background: #fee2e2;
|
||||
color: #b91c1c;
|
||||
border: 1px solid #fecaca;
|
||||
|
||||
&--yes {
|
||||
background: #dcfce7;
|
||||
color: #15803d;
|
||||
border-color: #bbf7d0;
|
||||
}
|
||||
}
|
||||
|
||||
.racc-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
|
||||
&--from {
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
border: 1px solid #bfdbfe;
|
||||
}
|
||||
}
|
||||
|
||||
.racc-alert--editable {
|
||||
border-color: #bae6fd;
|
||||
background: #f0f9ff;
|
||||
|
||||
&:hover {
|
||||
border-color: #7dd3fc;
|
||||
box-shadow: 0 2px 8px rgb(2 132 199 / 10%);
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: #0369a1;
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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-recurring-deposits-account-currency-step',
|
||||
templateUrl: './recurring-deposits-account-currency-step.component.html',
|
||||
styleUrls: ['./recurring-deposits-account-currency-step.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
NomineeFormComponent,
|
||||
DormancyAlertPanelComponent,
|
||||
MatSlideToggle,
|
||||
FormsModule
|
||||
]
|
||||
})
|
||||
export class RecurringDepositsAccountCurrencyStepComponent implements OnChanges {
|
||||
@Input() recurringDepositsAccountProductTemplate: any;
|
||||
|
||||
nomineeData: any = null;
|
||||
|
||||
// Editable feature toggles (pre-filled from product, overridable by user)
|
||||
balanceSwapOption = false;
|
||||
msme = false;
|
||||
autoDebitFacility = false;
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes['recurringDepositsAccountProductTemplate']?.currentValue) {
|
||||
const pt = changes['recurringDepositsAccountProductTemplate'].currentValue;
|
||||
this.balanceSwapOption = !!pt.balanceSwapOption;
|
||||
this.msme = !!pt.msme;
|
||||
this.autoDebitFacility = !!pt.autoDebitFacility;
|
||||
}
|
||||
}
|
||||
|
||||
onNomineeChange(event: any): void {
|
||||
this.nomineeData = event;
|
||||
}
|
||||
|
||||
/** Called by parent to collect currency step values */
|
||||
get currencyStepData() {
|
||||
return {
|
||||
balanceSwapOption: this.balanceSwapOption,
|
||||
msme: this.msme,
|
||||
autoDebitFacility: this.autoDebitFacility,
|
||||
...(this.nomineeData ? { nominee: this.nomineeData } : {})
|
||||
};
|
||||
}
|
||||
}
|
||||
+13
-100
@@ -7,7 +7,7 @@
|
||||
-->
|
||||
|
||||
<form [formGroup]="recurringDepositAccountDetailsForm">
|
||||
<!-- ===================== Account Details ===================== -->
|
||||
<!-- ══ Account Details ══════════════════════════════════════════ -->
|
||||
<div class="rad-section">
|
||||
<div class="rad-section__header">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
@@ -64,7 +64,7 @@
|
||||
</div>
|
||||
|
||||
@if (isProductSelected && productTemplate) {
|
||||
<!-- ===================== Product Classification ===================== -->
|
||||
<!-- ══ Product Classification (read-only) ══════════════════════ -->
|
||||
<div class="rad-section rad-section--mt">
|
||||
<div class="rad-section__header">
|
||||
<i class="fas fa-tags"></i>
|
||||
@@ -83,8 +83,14 @@
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="rad-field">
|
||||
<mat-label>Product Code</mat-label>
|
||||
<input matInput [value]="productTemplate.productCode || ''" readonly />
|
||||
<mat-label>Account No</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[value]="
|
||||
productTemplate?.previewAccountNo || recurringDepositsAccountTemplate?.accountNo || 'Auto-generated'
|
||||
"
|
||||
readonly
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
@if (productTemplate.effectiveDate) {
|
||||
@@ -115,101 +121,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===================== Currency & Account Status Configuration ===================== -->
|
||||
<div class="rad-section rad-section--mt">
|
||||
<div class="rad-section__header">
|
||||
<i class="fas fa-coins"></i>
|
||||
<h3>Currency & Account Status Configuration</h3>
|
||||
<span class="rad-badge">From Product</span>
|
||||
</div>
|
||||
<div class="rad-body">
|
||||
@if (productTemplate.currency) {
|
||||
<div class="rad-grid">
|
||||
<mat-form-field appearance="outline" class="rad-field">
|
||||
<mat-label>Currency</mat-label>
|
||||
<input matInput [value]="productTemplate.currency.name" readonly />
|
||||
<mat-hint>Default: Indian Rupee - auto-set for domestic products</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
@if (
|
||||
productTemplate.currency.decimalPlaces !== undefined && productTemplate.currency.decimalPlaces !== null
|
||||
) {
|
||||
<mat-form-field appearance="outline" class="rad-field">
|
||||
<mat-label>Decimal Places</mat-label>
|
||||
<input matInput [value]="productTemplate.currency.decimalPlaces" readonly />
|
||||
</mat-form-field>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="rad-alert">
|
||||
<div class="rad-alert__content">
|
||||
<h4>Dormancy Alert</h4>
|
||||
<p>Enable automated notifications when an account becomes inactive based on product rules.</p>
|
||||
</div>
|
||||
<div class="rad-alert__choices">
|
||||
<span class="rad-yn" [class.rad-yn--active]="productTemplate.dormancyAlert === true">yes</span>
|
||||
<span class="rad-yn" [class.rad-yn--active]="productTemplate.dormancyAlert === false">no</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rad-alert">
|
||||
<div class="rad-alert__content">
|
||||
<h4>Balance Swap Option</h4>
|
||||
<p>Enable automated transfer of excess funds to linked deposits or vice-versa to maintain liquidity.</p>
|
||||
</div>
|
||||
<div class="rad-alert__choices">
|
||||
<span class="rad-yn" [class.rad-yn--active]="productTemplate.balanceSwapOption === true">yes</span>
|
||||
<span class="rad-yn" [class.rad-yn--active]="productTemplate.balanceSwapOption === false">no</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rad-alert">
|
||||
<div class="rad-alert__content">
|
||||
<h4>MSME</h4>
|
||||
<p>Micro, Small and Medium Enterprises</p>
|
||||
</div>
|
||||
<div class="rad-alert__choices">
|
||||
<span class="rad-yn" [class.rad-yn--active]="productTemplate.msme === true">yes</span>
|
||||
<span class="rad-yn" [class.rad-yn--active]="productTemplate.msme === false">no</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rad-alert">
|
||||
<div class="rad-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>
|
||||
<div class="rad-alert__choices">
|
||||
<span class="rad-yn" [class.rad-yn--active]="productTemplate.nominationFacility === true">yes</span>
|
||||
<span class="rad-yn" [class.rad-yn--active]="productTemplate.nominationFacility === false">no</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rad-alert">
|
||||
<div class="rad-alert__content">
|
||||
<h4>Auto Debit Facility</h4>
|
||||
<p>Enable automatic deduction of funds for recurring payments, utility bills, or EMI obligations.</p>
|
||||
</div>
|
||||
<div class="rad-alert__choices">
|
||||
<span class="rad-yn" [class.rad-yn--active]="productTemplate.autoDebitFacility === true">yes</span>
|
||||
<span class="rad-yn" [class.rad-yn--active]="productTemplate.autoDebitFacility === false">no</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (productTemplate?.introducerRequired) {
|
||||
<mifosx-introducer-search (introducerChange)="onIntroducerChange($event)"></mifosx-introducer-search>
|
||||
}
|
||||
}
|
||||
|
||||
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
||||
<button mat-raised-button matStepperPrevious disabled>
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button matStepperNext [disabled]="recurringDepositAccountDetailsForm.invalid">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+59
-1
@@ -16,7 +16,7 @@ $gray-900: #202124;
|
||||
.rad-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
background: #fff;
|
||||
|
||||
&--mt {
|
||||
@@ -183,3 +183,61 @@ $gray-900: #202124;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Account Feature Toggles ────────────────────────────────────────────────
|
||||
.rad-toggles {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.rad-toggle-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 18px;
|
||||
border: 1.5px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
transition:
|
||||
border-color 0.2s,
|
||||
box-shadow 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #93c5fd;
|
||||
box-shadow: 0 1px 6px rgb(59 130 246 / 10%);
|
||||
}
|
||||
|
||||
&__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
> i {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
background: #f0f9ff;
|
||||
border: 1px solid #bae6fd;
|
||||
color: #0284c7;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 15px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 13.5px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
&__hint {
|
||||
font-size: 11.5px;
|
||||
color: #64748b;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
+29
-3
@@ -18,6 +18,7 @@ import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { IntroducerSearchComponent } from 'app/shared/introducer-search/introducer-search.component';
|
||||
|
||||
/**
|
||||
* Recurring Deposits Account Details Step
|
||||
@@ -31,7 +32,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
MatTooltip,
|
||||
MatStepperPrevious,
|
||||
FaIconComponent,
|
||||
MatStepperNext
|
||||
MatStepperNext,
|
||||
IntroducerSearchComponent
|
||||
]
|
||||
})
|
||||
export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
|
||||
@@ -58,6 +60,9 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
|
||||
recurringDepositAccountDetailsForm!: UntypedFormGroup;
|
||||
|
||||
isProductSelected = false;
|
||||
introducerId: number | null = null;
|
||||
introducerName: string | null = null;
|
||||
introducerCif: string | null = null;
|
||||
|
||||
/** Recurring Deposits Account Template with product data */
|
||||
@Output() recurringDepositsAccountProductTemplate = new EventEmitter();
|
||||
@@ -106,7 +111,10 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
|
||||
Validators.required
|
||||
],
|
||||
fieldOfficerId: [''],
|
||||
externalId: ['']
|
||||
externalId: [''],
|
||||
balanceSwapOption: [false],
|
||||
msme: [false],
|
||||
autoDebitFacility: [false]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -135,6 +143,12 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
|
||||
this.recurringDepositsAccountProductTemplate.emit(response);
|
||||
this.fieldOfficerData = response.fieldOfficerOptions;
|
||||
this.isProductSelected = true;
|
||||
// Pre-populate toggles from product defaults (user can override)
|
||||
this.recurringDepositAccountDetailsForm.patchValue({
|
||||
balanceSwapOption: !!response.balanceSwapOption,
|
||||
msme: !!response.msme,
|
||||
autoDebitFacility: !!response.autoDebitFacility
|
||||
});
|
||||
this.cdr.detectChanges();
|
||||
if (!this.isFieldOfficerPatched && this.recurringDepositsAccountTemplate.fieldOfficerId) {
|
||||
this.recurringDepositAccountDetailsForm
|
||||
@@ -184,7 +198,19 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
|
||||
/**
|
||||
* Returns recurring deposits account details form value.
|
||||
*/
|
||||
onIntroducerChange(event: { clientId: number; displayName: string; cifNumber?: string } | null): void {
|
||||
this.introducerId = event?.clientId ?? null;
|
||||
this.introducerName = event?.displayName ?? null;
|
||||
this.introducerCif = event?.cifNumber ?? null;
|
||||
}
|
||||
|
||||
get recurringDepositAccountDetails() {
|
||||
return this.recurringDepositAccountDetailsForm.value;
|
||||
const value = this.recurringDepositAccountDetailsForm.value;
|
||||
if (this.introducerId) {
|
||||
value.introducerId = this.introducerId;
|
||||
value.introducerName = this.introducerName;
|
||||
value.introducerCif = this.introducerCif;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
+39
-207
@@ -7,214 +7,46 @@
|
||||
-->
|
||||
|
||||
<div class="layout-column gap-2px">
|
||||
<!-- ── Custom RD Interest Rate Chart (from m_rd_interest_rate_slab_history) ── -->
|
||||
<!-- ── Tenure Calculator ───────────────────────────────────────── -->
|
||||
@if (_activeRdVersion) {
|
||||
<div class="rd-rate-header">
|
||||
<strong>{{ _activeRdVersion.versionLabel }}</strong>
|
||||
<span class="rd-rate-active-badge">Active</span>
|
||||
<span class="rd-rate-from">Effective from: {{ _activeRdVersion.effectiveFromDate }}</span>
|
||||
</div>
|
||||
|
||||
<table class="mat-elevation-z1 rd-rate-table" mat-table [dataSource]="_activeRdVersion.slabs">
|
||||
<ng-container matColumnDef="srNo">
|
||||
<th mat-header-cell *matHeaderCellDef>Sr.</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.srNo }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef>Particular</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.description }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="fromPeriod">
|
||||
<th mat-header-cell *matHeaderCellDef>From</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.fromPeriod }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="toPeriod">
|
||||
<th mat-header-cell *matHeaderCellDef>To</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.toPeriod ?? '—' }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="periodTypeLabel">
|
||||
<th mat-header-cell *matHeaderCellDef>Period Type</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.periodTypeLabel }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="annualInterestRate">
|
||||
<th mat-header-cell *matHeaderCellDef>Annual Rate (%)</th>
|
||||
<td mat-cell *matCellDef="let slab">
|
||||
<strong>{{ slab.annualInterestRate | number: '1.2-2' }}</strong>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="rdSlabColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: rdSlabColumns"></tr>
|
||||
</table>
|
||||
}
|
||||
|
||||
<!-- ── Legacy chart (shown only when no custom rate chart available) ── -->
|
||||
@if (!_activeRdVersion) {
|
||||
<div class="flex-fill" *ngIf="recurringDepositsAccountProductTemplate?.accountChart?.name">
|
||||
<span class="flex-40">{{ 'labels.inputs.name' | translate }}:</span>
|
||||
<span class="flex-60">{{ recurringDepositsAccountProductTemplate?.accountChart?.name }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-fill">
|
||||
<span class="flex-40">{{ 'labels.inputs.Valid from Date' | translate }}:</span>
|
||||
<span class="flex-60">{{ recurringDepositsAccountProductTemplate?.accountChart?.fromDate | dateFormat }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-fill" *ngIf="recurringDepositsAccountProductTemplate?.accountChart?.endDate">
|
||||
<span class="flex-40">{{ 'labels.inputs.End Date' | translate }}:</span>
|
||||
<span class="flex-60">{{ recurringDepositsAccountProductTemplate?.accountChart?.endDate | dateFormat }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-fill" *ngIf="recurringDepositsAccountProductTemplate?.accountChart?.description">
|
||||
<span class="flex-40">{{ 'labels.inputs.Description' | translate }}:</span>
|
||||
<span class="flex-60">{{ recurringDepositsAccountProductTemplate?.accountChart?.description }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-fill">
|
||||
<span class="flex-40">{{ 'labels.inputs.Primary Grouping by Amount' | translate }}:</span>
|
||||
<span class="flex-60">{{
|
||||
recurringDepositsAccountProductTemplate?.accountChart?.isPrimaryGroupingByAmount | yesNo
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<table
|
||||
class="mat-elevation-z1"
|
||||
[class.hidden]="_activeRdVersion"
|
||||
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 | 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="incentives flex-fill layout-row flex-wrap"
|
||||
[@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="mat-elevation-z1 flex-fill"
|
||||
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 class="tenure-calc">
|
||||
<div class="tenure-calc__header">
|
||||
<i class="fas fa-calculator"></i>
|
||||
<span>Tenure & Interest Calculator</span>
|
||||
</div>
|
||||
<div class="tenure-calc__body">
|
||||
<div class="tenure-calc__input-row">
|
||||
<mat-form-field appearance="outline" class="tenure-calc__field">
|
||||
<mat-label>Enter Tenure (Days)</mat-label>
|
||||
<input matInput type="number" [(ngModel)]="tenureDays" min="1" placeholder="e.g. 365" />
|
||||
<mat-hint>Enter the number of days for your RD</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: chartSlabsDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: chartSlabsIncentivesDisplayedColumns" class="incentives-row"></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="layout-row responsive-column align-center gap-2px margin-t">
|
||||
<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>
|
||||
@if (tenureDays && tenureDays > 0) {
|
||||
@if (applicableSlab) {
|
||||
<div class="tenure-calc__result">
|
||||
<div class="tenure-calc__result-row">
|
||||
<span class="tenure-calc__result-label">Tenure</span>
|
||||
<span class="tenure-calc__result-value">{{ tenureDays }} Days</span>
|
||||
</div>
|
||||
<div class="tenure-calc__result-row">
|
||||
<span class="tenure-calc__result-label">Applicable Slab</span>
|
||||
<span class="tenure-calc__result-value">{{ applicableSlab.description }}</span>
|
||||
</div>
|
||||
<div class="tenure-calc__result-row tenure-calc__result-row--highlight">
|
||||
<span class="tenure-calc__result-label">Annual Interest Rate</span>
|
||||
<span class="tenure-calc__result-rate">{{ applicableSlab.annualInterestRate | number: '1.2-2' }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="tenure-calc__no-match">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
No matching slab found for {{ tenureDays }} days
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
+99
@@ -6,6 +6,105 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// ── Tenure Calculator ─────────────────────────────────────────────────────────
|
||||
.tenure-calc {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 4px rgb(0 0 0 / 5%);
|
||||
margin-bottom: 1.25rem;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: #f0f9ff;
|
||||
border-bottom: 1px solid #bae6fd;
|
||||
padding: 0.65rem 1rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
color: #0369a1;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
|
||||
i {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 1rem 1.25rem;
|
||||
}
|
||||
|
||||
&__input-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
&__field {
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
&__result {
|
||||
margin-top: 1rem;
|
||||
border: 1px solid #e0f2fe;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__result-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.55rem 1rem;
|
||||
border-bottom: 1px solid #f0f9ff;
|
||||
font-size: 0.88rem;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&--highlight {
|
||||
background: #f0f9ff;
|
||||
}
|
||||
}
|
||||
|
||||
&__result-label {
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&__result-value {
|
||||
color: #1e293b;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__result-rate {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 800;
|
||||
color: #0369a1;
|
||||
}
|
||||
|
||||
&__no-match {
|
||||
margin-top: 0.75rem;
|
||||
padding: 0.6rem 1rem;
|
||||
border-radius: 8px;
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
i {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.margin-t {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
+19
-1
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, OnInit, Input, OnChanges, ViewChild } from '@angular/core';
|
||||
import { Component, OnInit, Input, OnChanges, ViewChild, Output, EventEmitter } from '@angular/core';
|
||||
import {
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
@@ -28,6 +28,7 @@ import { DateFormatPipe } from '../../../../pipes/date-format.pipe';
|
||||
import { FormatNumberPipe } from '../../../../pipes/format-number.pipe';
|
||||
import { YesnoPipe } from '../../../../pipes/yesno.pipe';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
/**
|
||||
* Recurring Deposits Account Interest Rate Chart Step
|
||||
@@ -45,6 +46,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FormsModule,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
@@ -114,10 +116,26 @@ export class RecurringDepositsAccountInterestRateChartStepComponent implements O
|
||||
/** Expand Chart Slab Index used in the view */
|
||||
expandChartSlabIndex: number;
|
||||
|
||||
/** Tenure entered by user (in days) */
|
||||
tenureDays: number | null = null;
|
||||
|
||||
@ViewChild('chartsTable', { static: true }) chartsTableRef: MatTable<Element>;
|
||||
|
||||
constructor() {}
|
||||
|
||||
/** Find the matching slab for the entered tenure */
|
||||
get applicableSlab(): any | null {
|
||||
if (!this.tenureDays || !this._activeRdVersion?.slabs?.length) return null;
|
||||
const days = this.tenureDays;
|
||||
return (
|
||||
this._activeRdVersion.slabs.find((s: any) => {
|
||||
const from = s.fromPeriod ?? 0;
|
||||
const to = s.toPeriod ?? Infinity;
|
||||
return days >= from && days <= to;
|
||||
}) ?? null
|
||||
);
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
if (this.recurringDepositsAccountProductTemplate) {
|
||||
this.interestRateChartData = this.recurringDepositsAccountProductTemplate.accountChart?.chartSlabs ?? [];
|
||||
|
||||
+243
-501
@@ -7,9 +7,10 @@
|
||||
-->
|
||||
|
||||
<div class="preview-wrapper">
|
||||
<!-- ===================== Details ===================== -->
|
||||
<!-- ══ 1. Account Details ════════════════════════════════════════ -->
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-info-circle preview-section__icon"></i>
|
||||
<span class="preview-section__title">{{ 'labels.heading.Details' | translate }}</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
@@ -21,12 +22,10 @@
|
||||
| find: recurringDepositsAccountTemplate?.productOptions : 'id' : 'name'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Submitted On' | translate }}</span>
|
||||
<span class="preview-value">{{ recurringDepositAccountData?.submittedOnDate | dateFormat }}</span>
|
||||
</div>
|
||||
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Field Officer' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
@@ -34,18 +33,17 @@
|
||||
| find: recurringDepositsAccountProductTemplate?.fieldOfficerOptions : 'id' : 'displayName'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
@if (recurringDepositAccountData?.externalId) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.External Id' | translate }}</span>
|
||||
<span class="preview-value">{{ recurringDepositAccountData?.externalId }}</span>
|
||||
<span class="preview-value">{{ recurringDepositAccountData.externalId }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===================== Product Classification ===================== -->
|
||||
<!-- ══ 2. Product Classification ════════════════════════════════ -->
|
||||
@if (
|
||||
recurringDepositsAccountProductTemplate?.accountType ||
|
||||
recurringDepositsAccountProductTemplate?.customerSegment ||
|
||||
@@ -57,40 +55,42 @@
|
||||
) {
|
||||
<div class="preview-section">
|
||||
<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">From Product</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
@if (recurringDepositsAccountProductTemplate?.accountType) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Account Type</span>
|
||||
<span class="preview-value">{{ recurringDepositsAccountProductTemplate?.accountType }}</span>
|
||||
<span class="preview-value">{{ recurringDepositsAccountProductTemplate.accountType }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (recurringDepositsAccountProductTemplate?.customerSegment) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Customer Segment</span>
|
||||
<span class="preview-value">{{ recurringDepositsAccountProductTemplate?.customerSegment }}</span>
|
||||
<span class="preview-value">{{ recurringDepositsAccountProductTemplate.customerSegment }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (recurringDepositsAccountProductTemplate?.productCode) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Product Code</span>
|
||||
<span class="preview-value">{{ recurringDepositsAccountProductTemplate?.productCode }}</span>
|
||||
<span class="preview-value preview-mono">{{ recurringDepositsAccountProductTemplate.productCode }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (recurringDepositsAccountProductTemplate?.effectiveDate) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Effective Date</span>
|
||||
<span class="preview-value">{{
|
||||
recurringDepositsAccountProductTemplate?.effectiveDate | dateFormat
|
||||
recurringDepositsAccountProductTemplate.effectiveDate | dateFormat
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
@if (recurringDepositsAccountProductTemplate?.endDate) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">End Date</span>
|
||||
<span class="preview-value">{{ recurringDepositsAccountProductTemplate?.endDate | dateFormat }}</span>
|
||||
<span class="preview-value">{{ recurringDepositsAccountProductTemplate.endDate | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (
|
||||
@@ -101,9 +101,9 @@
|
||||
<span class="preview-label">Introducer Required</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate?.introducerRequired"
|
||||
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate.introducerRequired"
|
||||
>
|
||||
{{ recurringDepositsAccountProductTemplate?.introducerRequired | yesNo }}
|
||||
{{ recurringDepositsAccountProductTemplate.introducerRequired | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@@ -115,9 +115,9 @@
|
||||
<span class="preview-label">Share Linking Enabled</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate?.shareLinkingEnabled"
|
||||
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate.shareLinkingEnabled"
|
||||
>
|
||||
{{ recurringDepositsAccountProductTemplate?.shareLinkingEnabled | yesNo }}
|
||||
{{ recurringDepositsAccountProductTemplate.shareLinkingEnabled | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@@ -126,32 +126,54 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ===================== Currency ===================== -->
|
||||
<!-- ══ 3. Introducer Details (if selected) ══════════════════════ -->
|
||||
@if (recurringDepositAccountData?.introducerId) {
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-user-tie preview-section__icon"></i>
|
||||
<span class="preview-section__title">Introducer Details</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Name</span>
|
||||
<span class="preview-value">{{ recurringDepositAccountData?.introducerName || '—' }}</span>
|
||||
</div>
|
||||
@if (recurringDepositAccountData?.introducerCif) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">CIF No</span>
|
||||
<span class="preview-value preview-mono">{{ recurringDepositAccountData.introducerCif }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Client ID</span>
|
||||
<span class="preview-value preview-mono">{{ recurringDepositAccountData.introducerId }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ══ 4. Currency & Account Flags ══════════════════════════════ -->
|
||||
<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">{{
|
||||
recurringDepositsAccountProductTemplate?.currency?.code ||
|
||||
recurringDepositsAccountProductTemplate?.currency?.name ||
|
||||
'—'
|
||||
}}</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">{{ recurringDepositsAccountProductTemplate?.currency?.decimalPlaces }}</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">{{ recurringDepositsAccountProductTemplate?.currency?.inMultiplesOf }}</span>
|
||||
<span class="preview-value">{{ inMultiplesOf }}</span>
|
||||
</div>
|
||||
|
||||
@if (recurringDepositsAccountProductTemplate?.withHoldTax) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Is Withhold Tax Applicable' | translate }}</span>
|
||||
@@ -163,14 +185,30 @@
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (recurringDepositAccountData?.withHoldTax && recurringDepositsAccountProductTemplate?.taxGroup?.name) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Tax Group' | translate }}</span>
|
||||
<span class="preview-value">{{ recurringDepositsAccountProductTemplate?.taxGroup?.name }}</span>
|
||||
<span class="preview-value">{{ recurringDepositsAccountProductTemplate.taxGroup.name }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Balance Swap Option</span>
|
||||
<span class="preview-value preview-badge" [class.preview-badge--yes]="rdCurrencyFlags?.balanceSwapOption">
|
||||
{{ rdCurrencyFlags?.balanceSwapOption | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">MSME</span>
|
||||
<span class="preview-value preview-badge" [class.preview-badge--yes]="rdCurrencyFlags?.msme">
|
||||
{{ rdCurrencyFlags?.msme | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Auto Debit Facility</span>
|
||||
<span class="preview-value preview-badge" [class.preview-badge--yes]="rdCurrencyFlags?.autoDebitFacility">
|
||||
{{ rdCurrencyFlags?.autoDebitFacility | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
@if (
|
||||
recurringDepositsAccountProductTemplate?.dormancyAlert !== undefined &&
|
||||
recurringDepositsAccountProductTemplate?.dormancyAlert !== null
|
||||
@@ -179,43 +217,12 @@
|
||||
<span class="preview-label">Dormancy Alert</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate?.dormancyAlert"
|
||||
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate.dormancyAlert"
|
||||
>
|
||||
{{ recurringDepositsAccountProductTemplate?.dormancyAlert | yesNo }}
|
||||
{{ recurringDepositsAccountProductTemplate.dormancyAlert | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (
|
||||
recurringDepositsAccountProductTemplate?.balanceSwapOption !== undefined &&
|
||||
recurringDepositsAccountProductTemplate?.balanceSwapOption !== null
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Balance Swap Option</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate?.balanceSwapOption"
|
||||
>
|
||||
{{ recurringDepositsAccountProductTemplate?.balanceSwapOption | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (
|
||||
recurringDepositsAccountProductTemplate?.msme !== undefined &&
|
||||
recurringDepositsAccountProductTemplate?.msme !== null
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">MSME</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate?.msme"
|
||||
>
|
||||
{{ recurringDepositsAccountProductTemplate?.msme | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (
|
||||
recurringDepositsAccountProductTemplate?.nominationFacility !== undefined &&
|
||||
recurringDepositsAccountProductTemplate?.nominationFacility !== null
|
||||
@@ -224,24 +231,9 @@
|
||||
<span class="preview-label">Nomination Facility</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate?.nominationFacility"
|
||||
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate.nominationFacility"
|
||||
>
|
||||
{{ recurringDepositsAccountProductTemplate?.nominationFacility | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (
|
||||
recurringDepositsAccountProductTemplate?.autoDebitFacility !== undefined &&
|
||||
recurringDepositsAccountProductTemplate?.autoDebitFacility !== null
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Auto Debit Facility</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate?.autoDebitFacility"
|
||||
>
|
||||
{{ recurringDepositsAccountProductTemplate?.autoDebitFacility | yesNo }}
|
||||
{{ recurringDepositsAccountProductTemplate.nominationFacility | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@@ -249,403 +241,235 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===================== Terms ===================== -->
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<span class="preview-section__title">{{ 'labels.heading.Terms' | translate }}</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Nominal Annual Interest' | translate }}</span>
|
||||
<span class="preview-value">
|
||||
{{ recurringDepositsAccountProductTemplate?.nominalAnnualInterestRate ?? '—' }}
|
||||
@if (recurringDepositsAccountProductTemplate?.nominalAnnualInterestRate != null) {
|
||||
%
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if (recurringDepositsAccountProductTemplate?.effectiveDate) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Effective Date</span>
|
||||
<span class="preview-value">{{ recurringDepositsAccountProductTemplate?.effectiveDate | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Interest Compounding Period' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
recurringDepositAccountData?.interestCompoundingPeriodType
|
||||
| find: recurringDepositsAccountProductTemplate?.interestCompoundingPeriodTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Interest Posting Period' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
recurringDepositAccountData?.interestPostingPeriodType
|
||||
| find: recurringDepositsAccountProductTemplate?.interestPostingPeriodTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
@if (recurringDepositsAccountProductTemplate?.averagePeriod) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Average Period</span>
|
||||
<span class="preview-value">{{ recurringDepositsAccountProductTemplate?.averagePeriod }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Interest Calculated using' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
recurringDepositAccountData?.interestCalculationType
|
||||
| find: recurringDepositsAccountProductTemplate?.interestCalculationTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Days in Year' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
recurringDepositAccountData?.interestCalculationDaysInYearType
|
||||
| find: recurringDepositsAccountProductTemplate?.interestCalculationDaysInYearTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
@if (
|
||||
recurringDepositsAccountProductTemplate?.autoPost !== undefined &&
|
||||
recurringDepositsAccountProductTemplate?.autoPost !== null
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Auto Interest Post</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate?.autoPost"
|
||||
>
|
||||
{{ recurringDepositsAccountProductTemplate?.autoPost | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (
|
||||
recurringDepositsAccountProductTemplate?.frequencyFixDay !== undefined &&
|
||||
recurringDepositsAccountProductTemplate?.frequencyFixDay !== null
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Frequency (Fix Day)</span>
|
||||
<span class="preview-value">{{ recurringDepositsAccountProductTemplate?.frequencyFixDay }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (recurringDepositsAccountProductTemplate?.lastInterestDate) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Last Interest Date</span>
|
||||
<span class="preview-value">{{
|
||||
recurringDepositsAccountProductTemplate?.lastInterestDate | dateFormat
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (recurringDepositsAccountProductTemplate?.interestCalculationTypeName) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Interest Calculation Type</span>
|
||||
<span class="preview-value">{{
|
||||
recurringDepositsAccountProductTemplate?.interestCalculationTypeName
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
<!-- ══ 5. Dormancy Monitoring ════════════════════════════════════ -->
|
||||
@if (
|
||||
recurringDepositsAccountProductTemplate?.dormancyAlert ||
|
||||
recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct
|
||||
) {
|
||||
<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"
|
||||
[class.preview-section__badge--active]="
|
||||
recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct
|
||||
"
|
||||
>
|
||||
{{ recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct ? 'Active' : 'Inactive' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===================== Settings ===================== -->
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<span class="preview-section__title">{{ 'labels.heading.Settings' | translate }}</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<!-- Premature Option -->
|
||||
<div class="preview-subsection">
|
||||
<div class="preview-subsection__title">Premature Option</div>
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Loan Facility</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositAccountData?.loanFacility"
|
||||
>
|
||||
{{ recurringDepositAccountData?.loanFacility | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Premature Withdrawal Option</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositAccountData?.prematureWithdrawalOption"
|
||||
>
|
||||
{{ recurringDepositAccountData?.prematureWithdrawalOption | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Tenure Flexibility</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositAccountData?.tenureFlexibility"
|
||||
>
|
||||
{{ recurringDepositAccountData?.tenureFlexibility | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Lien Marked</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositAccountData?.lienMarked"
|
||||
>
|
||||
{{ recurringDepositAccountData?.lienMarked | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Withdrawal Fee for Transfers</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositAccountData?.withdrawalFeeForTransfers"
|
||||
>
|
||||
{{ recurringDepositAccountData?.withdrawalFeeForTransfers | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="preview-divider"></mat-divider>
|
||||
|
||||
<!-- Lock-in Period -->
|
||||
<div class="preview-subsection">
|
||||
<div class="preview-subsection__title">{{ 'labels.inputs.Lock-in Period' | translate }}</div>
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Enable Lock-in Period</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositAccountData?.enableLockinPeriod"
|
||||
>
|
||||
{{ recurringDepositAccountData?.enableLockinPeriod | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
@if (recurringDepositAccountData?.enableLockinPeriod) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Lock-in Frequency</span>
|
||||
<span class="preview-value">
|
||||
{{ recurringDepositAccountData?.lockinPeriodFrequency }}
|
||||
{{
|
||||
recurringDepositAccountData?.lockinPeriodFrequencyType
|
||||
| find: recurringDepositsAccountProductTemplate?.lockinPeriodFrequencyTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}
|
||||
<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">
|
||||
@if (recurringDepositsAccountProductTemplate?.daysToInactiveFromProduct != null) {
|
||||
After {{ recurringDepositsAccountProductTemplate.daysToInactiveFromProduct }} days of no transaction
|
||||
} @else {
|
||||
Not configured
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-divider class="preview-divider"></mat-divider>
|
||||
|
||||
<!-- Maturity Instructions -->
|
||||
<div class="preview-subsection">
|
||||
<div class="preview-subsection__title">Maturity Instructions</div>
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Maturity Instructions</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositAccountData?.maturityInstructions"
|
||||
>
|
||||
{{ recurringDepositAccountData?.maturityInstructions | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Credit Maturity Proceeds</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositAccountData?.creditMaturityProceeds"
|
||||
>
|
||||
{{ recurringDepositAccountData?.creditMaturityProceeds | yesNo }}
|
||||
</span>
|
||||
<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">
|
||||
@if (recurringDepositsAccountProductTemplate?.daysToDormancyFromProduct != null) {
|
||||
{{ recurringDepositsAccountProductTemplate.daysToDormancyFromProduct }} days after inactive
|
||||
} @else {
|
||||
Not configured
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Penalty / Grace Period for Missed Installments</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositAccountData?.penaltyGracePeriodMissedInstallments"
|
||||
>
|
||||
{{ recurringDepositAccountData?.penaltyGracePeriodMissedInstallments | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Panel Request Applicable</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositAccountData?.panelRequestApplicable"
|
||||
>
|
||||
{{ recurringDepositAccountData?.panelRequestApplicable | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">No Penalty for Nominee / Legal Heir Closure</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositAccountData?.noPenaltyNomineeHeirClosure"
|
||||
>
|
||||
{{ recurringDepositAccountData?.noPenaltyNomineeHeirClosure | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Deceased Settlement</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositAccountData?.deceasedSettlement"
|
||||
>
|
||||
{{ recurringDepositAccountData?.deceasedSettlement | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Allow Debit Txn after Due Date</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositAccountData?.allowDebitTxnAfterDueDate"
|
||||
>
|
||||
{{ recurringDepositAccountData?.allowDebitTxnAfterDueDate | yesNo }}
|
||||
</span>
|
||||
<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">
|
||||
@if (recurringDepositsAccountProductTemplate?.daysToEscheatFromProduct != null) {
|
||||
{{ recurringDepositsAccountProductTemplate.daysToEscheatFromProduct }} days after dormant
|
||||
} @else {
|
||||
Not configured
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-dorm-flags">
|
||||
<span
|
||||
class="preview-dorm-flag"
|
||||
[class.preview-dorm-flag--on]="recurringDepositsAccountProductTemplate?.dormancyAlert"
|
||||
>
|
||||
<i class="fas fa-bell"></i> Alert
|
||||
{{ recurringDepositsAccountProductTemplate?.dormancyAlert ? 'Enabled' : 'Disabled' }}
|
||||
</span>
|
||||
<span
|
||||
class="preview-dorm-flag"
|
||||
[class.preview-dorm-flag--on]="recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct"
|
||||
>
|
||||
<i class="fas fa-eye"></i> Tracking
|
||||
{{ recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct ? 'Active' : 'Inactive' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<mat-divider class="preview-divider"></mat-divider>
|
||||
|
||||
<!-- Additional Rules -->
|
||||
<div class="preview-subsection">
|
||||
<div class="preview-subsection__title">Additional Rules</div>
|
||||
<!-- ══ 6. Nominee Details (if filled) ═══════════════════════════ -->
|
||||
@if (rdNominee) {
|
||||
<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">
|
||||
{{ rdNominee?.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">Account Closed with Zero Balance in Day Closing</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="recurringDepositAccountData?.accountClosedZeroBalanceInDayClosing"
|
||||
>
|
||||
{{ recurringDepositAccountData?.accountClosedZeroBalanceInDayClosing | yesNo }}
|
||||
</span>
|
||||
<span class="preview-label">Name</span>
|
||||
<span class="preview-value">{{ rdNominee?.fullName || rdNominee?.displayName || '—' }}</span>
|
||||
</div>
|
||||
@if (recurringDepositAccountData?.minBalanceForInterestCalculation) {
|
||||
@if (rdNominee?.cifNumber) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Min. Balance for Interest Calculation</span>
|
||||
<span class="preview-value">{{
|
||||
recurringDepositAccountData?.minBalanceForInterestCalculation | formatNumber
|
||||
}}</span>
|
||||
<span class="preview-label">CIF No</span>
|
||||
<span class="preview-value preview-mono">{{ rdNominee.cifNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (recurringDepositAccountData?.mandatoryRecommendedDepositAmount) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Relationship</span>
|
||||
<span class="preview-value">{{ rdNominee?.relationship || '—' }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Share %</span>
|
||||
<span class="preview-value">{{ rdNominee?.nomineeShare ?? '—' }}%</span>
|
||||
</div>
|
||||
@if (rdNominee?.dateOfBirth) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Recurring Deposit Amount' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
recurringDepositAccountData?.mandatoryRecommendedDepositAmount | formatNumber
|
||||
}}</span>
|
||||
<span class="preview-label">Date of Birth</span>
|
||||
<span class="preview-value">{{ rdNominee?.dateOfBirth | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (rdNominee?.nationality) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Nationality</span>
|
||||
<span class="preview-value">{{ rdNominee.nationality }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (rdNominee?.mobileNo) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Mobile No</span>
|
||||
<span class="preview-value preview-mono">{{ rdNominee.mobileNo }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (rdNominee?.aadhaarNumber) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Aadhaar No</span>
|
||||
<span class="preview-value preview-mono">{{ rdNominee.aadhaarNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (rdNominee?.panNumber) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">PAN No</span>
|
||||
<span class="preview-value preview-mono">{{ rdNominee.panNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (rdNominee?.guardianName) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Guardian Name</span>
|
||||
<span class="preview-value">{{ rdNominee.guardianName }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (rdNominee?.guardianRelationship) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Guardian Relationship</span>
|
||||
<span class="preview-value">{{ rdNominee.guardianRelationship }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (rdNominee?.remarks) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Remarks</span>
|
||||
<span class="preview-value">{{ rdNominee.remarks }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ===================== Interest Rate Chart ===================== -->
|
||||
<!-- ══ 7. Interest Rate (Tenure-based result) ════════════════════ -->
|
||||
@if (_activeRdVersion || recurringDepositsAccountProductTemplate?.accountChart) {
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<span class="preview-section__title">{{ 'labels.inputs.Interest Rate Chart' | translate }}</span>
|
||||
<i class="fas fa-chart-line preview-section__icon"></i>
|
||||
<span class="preview-section__title">Interest Rate</span>
|
||||
@if (_activeRdVersion) {
|
||||
<span class="preview-section__badge">{{ _activeRdVersion.versionLabel }}</span>
|
||||
}
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
@if (_activeRdVersion) {
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Version</span>
|
||||
<span class="preview-value">{{ _activeRdVersion.versionLabel }}</span>
|
||||
@if (rdTenureDays && rdApplicableSlab) {
|
||||
<div class="preview-tenure-result">
|
||||
<div class="preview-tenure-result__row">
|
||||
<span class="preview-label">Tenure</span>
|
||||
<span class="preview-value">{{ rdTenureDays }} Days</span>
|
||||
</div>
|
||||
<div class="preview-tenure-result__row">
|
||||
<span class="preview-label">Applicable Slab</span>
|
||||
<span class="preview-value">{{ rdApplicableSlab.description }}</span>
|
||||
</div>
|
||||
<div class="preview-tenure-result__row preview-tenure-result__row--highlight">
|
||||
<span class="preview-label">Annual Interest Rate</span>
|
||||
<span class="preview-tenure-result__rate"
|
||||
>{{ rdApplicableSlab.annualInterestRate | formatNumber }}%</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Active</span>
|
||||
<span class="preview-value preview-badge preview-badge--yes">Yes</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Effective From</span>
|
||||
<span class="preview-value">{{ _activeRdVersion.effectiveFromDate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="mat-elevation-z1 irc-table preview-table" mat-table [dataSource]="_activeRdVersion.slabs">
|
||||
<ng-container matColumnDef="srNo">
|
||||
<th mat-header-cell *matHeaderCellDef>Sr.</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.srNo }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef>Particular</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.description }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="fromPeriod">
|
||||
<th mat-header-cell *matHeaderCellDef>From</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.fromPeriod }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="toPeriod">
|
||||
<th mat-header-cell *matHeaderCellDef>To</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.toPeriod ?? 'Unassigned' }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="periodTypeLabel">
|
||||
<th mat-header-cell *matHeaderCellDef>Period Type</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.periodTypeLabel }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="annualInterestRate">
|
||||
<th mat-header-cell *matHeaderCellDef>Annual Rate (%)</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.annualInterestRate | formatNumber }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="rdSlabColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: rdSlabColumns"></tr>
|
||||
</table>
|
||||
} @else {
|
||||
<span class="preview-muted">No tenure entered — interest rate not calculated</span>
|
||||
}
|
||||
} @else {
|
||||
<div class="preview-grid preview-grid--three">
|
||||
@if (recurringDepositsAccountProductTemplate?.accountChart?.name) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.name' | translate }}</span>
|
||||
<span class="preview-value">{{ recurringDepositsAccountProductTemplate?.accountChart?.name }}</span>
|
||||
<span class="preview-value">{{ recurringDepositsAccountProductTemplate.accountChart.name }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (recurringDepositsAccountProductTemplate?.accountChart?.fromDate) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Valid from Date' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
recurringDepositsAccountProductTemplate?.accountChart?.fromDate | dateFormat
|
||||
recurringDepositsAccountProductTemplate.accountChart.fromDate | dateFormat
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (recurringDepositsAccountProductTemplate?.accountChart?.endDate) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.End Date' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
recurringDepositsAccountProductTemplate?.accountChart?.endDate | dateFormat
|
||||
recurringDepositsAccountProductTemplate.accountChart.endDate | dateFormat
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (recurringDepositsAccountProductTemplate?.accountChart?.description) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Description' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
recurringDepositsAccountProductTemplate?.accountChart?.description
|
||||
recurringDepositsAccountProductTemplate.accountChart.description
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Primary Grouping by Amount' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
@@ -669,24 +493,20 @@
|
||||
}}
|
||||
</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">
|
||||
@@ -706,7 +526,6 @@
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="incentives">
|
||||
<td
|
||||
mat-cell
|
||||
@@ -756,7 +575,6 @@
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: chartSlabsDisplayedColumns"></tr>
|
||||
<tr
|
||||
@@ -770,82 +588,6 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ===================== Charges ===================== -->
|
||||
@if (recurringDepositAccountData?.charges?.length) {
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<span class="preview-section__title">{{ 'labels.heading.Charges' | translate }}</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<table
|
||||
class="flex-100 mat-elevation-z1 preview-table"
|
||||
mat-table
|
||||
[dataSource]="recurringDepositAccountData.charges"
|
||||
>
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.name + ', ' + charge.currency?.displaySymbol }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="chargeCalculationType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.chargeCalculationType?.value | translateKey: 'catalogs' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.amount | formatNumber }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="chargeTimeType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.chargeTimeType?.value }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="date">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Date' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
<span
|
||||
*ngIf="
|
||||
charge.chargeTimeType?.value === 'Specified due date' || charge.chargeTimeType?.value === 'Weekly Fee'
|
||||
"
|
||||
>
|
||||
{{ (charge.dueDate | dateFormat) || 'Unassigned' }}
|
||||
</span>
|
||||
<span
|
||||
*ngIf="charge.chargeTimeType?.value === 'Monthly Fee' || charge.chargeTimeType?.value === 'Annual Fee'"
|
||||
>
|
||||
{{ (charge.feeOnMonthDay | dateFormat) || 'Unassigned' }}
|
||||
</span>
|
||||
<span
|
||||
*ngIf="
|
||||
!(
|
||||
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 }}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="repaymentsEvery">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Repayments Every' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.feeInterval || 'Not Provided' }}</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="chargesDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: chargesDisplayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="layout-row responsive-column align-center gap-2px margin-t">
|
||||
|
||||
+193
@@ -23,18 +23,48 @@
|
||||
box-shadow: 0 1px 3px rgb(0 0 0 / 6%);
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: #f5f5f5;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
padding: 0.6rem 1rem;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 13px;
|
||||
color: #0ea5e9;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__title {
|
||||
flex: 1;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
&__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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Body ─────────────────────────────────────────────────────────────────────
|
||||
@@ -146,6 +176,169 @@
|
||||
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;
|
||||
}
|
||||
|
||||
// ── Dormancy timeline ─────────────────────────────────────────────────────────
|
||||
.preview-dormancy-timeline {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.preview-dorm-step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
|
||||
&__dot {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
&--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;
|
||||
}
|
||||
|
||||
.preview-dorm-flags {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.preview-dorm-flag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 11.5px;
|
||||
font-weight: 600;
|
||||
background: #f1f5f9;
|
||||
color: #94a3b8;
|
||||
border: 1px solid #e2e8f0;
|
||||
|
||||
i {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
&--on {
|
||||
background: #f0fdf4;
|
||||
color: #15803d;
|
||||
border-color: #bbf7d0;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Product feature flag rows (same style as details step) ────────────────────
|
||||
.preview-feature-list {
|
||||
margin-top: 1rem;
|
||||
|
||||
+16
@@ -73,6 +73,10 @@ export class RecurringDepositsAccountPreviewStepComponent implements OnChanges {
|
||||
@Input() recurringDepositsAccountTemplate: any;
|
||||
@Input() recurringDepositsAccountProductTemplate: any;
|
||||
@Input() recurringDepositAccountData: any;
|
||||
@Input() rdNominee: any = null;
|
||||
@Input() rdCurrencyFlags: any = null;
|
||||
@Input() rdTenureDays: number | null = null;
|
||||
@Input() rdApplicableSlab: any = null;
|
||||
@Input() set rdInterestRates(rates: any[]) {
|
||||
this._rdInterestRates = rates || [];
|
||||
this._activeRdVersion = this._rdInterestRates.find((v: any) => v.isActive) ?? this._rdInterestRates[0] ?? null;
|
||||
@@ -80,6 +84,18 @@ export class RecurringDepositsAccountPreviewStepComponent implements OnChanges {
|
||||
/** Output the submit action */
|
||||
@Output() submitEvent = new EventEmitter();
|
||||
|
||||
get currencyName(): string {
|
||||
return this.recurringDepositsAccountProductTemplate?.currency?.name || '—';
|
||||
}
|
||||
|
||||
get decimalPlaces(): string | number {
|
||||
return this.recurringDepositsAccountProductTemplate?.currency?.decimalPlaces ?? '—';
|
||||
}
|
||||
|
||||
get inMultiplesOf(): string | number {
|
||||
return this.recurringDepositsAccountProductTemplate?.currency?.inMultiplesOf ?? '—';
|
||||
}
|
||||
|
||||
_rdInterestRates: any[] = [];
|
||||
_activeRdVersion: any = null;
|
||||
|
||||
|
||||
+6
-2
@@ -82,8 +82,12 @@
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="spd-field">
|
||||
<mat-label>Product Code</mat-label>
|
||||
<input matInput formControlName="productCode" readonly />
|
||||
<mat-label>GL Account (Liability)</mat-label>
|
||||
<mat-select formControlName="glAccountId" [compareWith]="compareById">
|
||||
@for (account of liabilityAccountOptions; track account.id) {
|
||||
<mat-option [value]="account.id">{{ account.name }} ({{ account.glCode }})</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="spd-field" (click)="effectiveToPicker.open()">
|
||||
|
||||
+11
-3
@@ -6,7 +6,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, OnInit, OnChanges, Input, SimpleChanges, inject } from '@angular/core';
|
||||
import { Component, OnInit, OnChanges, Input, SimpleChanges, inject, ChangeDetectorRef } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||
@@ -30,12 +30,14 @@ export class CurrentProductDetailsStepComponent implements OnInit, OnChanges {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private dateUtils = inject(Dates);
|
||||
private settingsService = inject(SettingsService);
|
||||
private cdr = inject(ChangeDetectorRef);
|
||||
|
||||
@Input() CurrentProductsTemplate: any;
|
||||
|
||||
CurrentProductDetailsForm: UntypedFormGroup;
|
||||
accountTypeData: string[] = [];
|
||||
customerSegmentData: string[] = [];
|
||||
liabilityAccountOptions: any[] = [];
|
||||
yesNoOptions: Array<{
|
||||
value: boolean;
|
||||
label: string;
|
||||
@@ -82,7 +84,7 @@ export class CurrentProductDetailsStepComponent implements OnInit, OnChanges {
|
||||
Validators.required
|
||||
],
|
||||
customerSegment: [null],
|
||||
productCode: [''],
|
||||
glAccountId: [null],
|
||||
effectiveTo: [''],
|
||||
endDate: [''],
|
||||
introducerRequired: [false],
|
||||
@@ -124,6 +126,8 @@ export class CurrentProductDetailsStepComponent implements OnInit, OnChanges {
|
||||
|
||||
this.accountTypeData = accountTypeOptions;
|
||||
this.customerSegmentData = customerSegmentOptions;
|
||||
this.liabilityAccountOptions = template.accountingMappingOptions?.liabilityAccountOptions ?? [];
|
||||
this.cdr.detectChanges();
|
||||
if (yesNoOptions.length) {
|
||||
this.yesNoOptions = yesNoOptions;
|
||||
}
|
||||
@@ -136,7 +140,7 @@ export class CurrentProductDetailsStepComponent implements OnInit, OnChanges {
|
||||
description: template.description,
|
||||
accountType: template.accountType ?? this.accountTypeData[0] ?? null,
|
||||
customerSegment: template.customerSegment ?? this.customerSegmentData[0] ?? null,
|
||||
productCode: template.productCode ?? '',
|
||||
glAccountId: template.glAccountId ?? null,
|
||||
effectiveTo: this.parseDate(template.effectiveTo),
|
||||
endDate: this.parseDate(template.endDate),
|
||||
introducerRequired: template.introducerRequired === true || template.introducerRequired === 'Yes',
|
||||
@@ -173,6 +177,10 @@ export class CurrentProductDetailsStepComponent implements OnInit, OnChanges {
|
||||
.filter((value) => !!value);
|
||||
}
|
||||
|
||||
compareById(o1: any, o2: any): boolean {
|
||||
return o1 != null && o2 != null && Number(o1) === Number(o2);
|
||||
}
|
||||
|
||||
get CurrentProductDetails() {
|
||||
const formValue = { ...this.CurrentProductDetailsForm.value };
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
|
||||
+4
-4
@@ -28,7 +28,7 @@
|
||||
@if (
|
||||
CurrentProduct.accountType ||
|
||||
CurrentProduct.customerSegment ||
|
||||
CurrentProduct.productCode ||
|
||||
CurrentProduct.glAccountId ||
|
||||
CurrentProduct.effectiveTo ||
|
||||
CurrentProduct.introducerRequired !== undefined ||
|
||||
CurrentProduct.shareLinkingEnabled !== undefined
|
||||
@@ -51,10 +51,10 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.productCode) {
|
||||
@if (CurrentProduct.glAccountId) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Product Code:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.productCode }}</span>
|
||||
<span class="flex-40">GL Account (Liability):</span>
|
||||
<span class="flex-60">{{ getGlAccountName(CurrentProduct.glAccountId) }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -205,6 +205,13 @@ export class CurrentProductPreviewStepComponent implements OnInit, OnChanges {
|
||||
return value === true || value === 'Yes' ? 'Yes' : 'No';
|
||||
}
|
||||
|
||||
getGlAccountName(id: any): string {
|
||||
if (!id) return '';
|
||||
const accounts = this.CurrentProductsTemplate?.accountingMappingOptions?.liabilityAccountOptions ?? [];
|
||||
const found = accounts.find((a: any) => a.id === id || a.id === +id);
|
||||
return found ? `${found.name} (${found.glCode})` : String(id);
|
||||
}
|
||||
|
||||
getLockinPeriodFrequencyTypeLabel(value: any): string {
|
||||
switch (value) {
|
||||
case 'DAYS':
|
||||
|
||||
+4
-4
@@ -40,7 +40,7 @@
|
||||
@if (
|
||||
currentProduct.accountType ||
|
||||
currentProduct.customerSegment ||
|
||||
currentProduct.productCode ||
|
||||
currentProduct.glAccountId ||
|
||||
currentProduct.effectiveDate ||
|
||||
currentProduct.effectiveTo ||
|
||||
currentProduct.endDate ||
|
||||
@@ -65,10 +65,10 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.productCode) {
|
||||
@if (currentProduct.glAccountId) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Product Code:</span>
|
||||
<span class="flex-60">{{ currentProduct.productCode }}</span>
|
||||
<span class="flex-40">GL Account (Liability):</span>
|
||||
<span class="flex-60">{{ currentProduct.glAccountName || currentProduct.glAccountId }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
+15
-1
@@ -51,6 +51,10 @@ export class RecurringDepositProductCurrencyStepComponent implements OnInit {
|
||||
this.recurringDepositProductsTemplate.currency?.decimalPlaces ??
|
||||
2,
|
||||
dormancyAlert: this.recurringDepositProductsTemplate.dormancyAlert ?? false,
|
||||
isDormancyTrackingActive: this.recurringDepositProductsTemplate.isDormancyTrackingActive ?? false,
|
||||
daysToInactive: this.recurringDepositProductsTemplate.daysToInactive ?? 30,
|
||||
daysToDormancy: this.recurringDepositProductsTemplate.daysToDormancy ?? 60,
|
||||
daysToEscheat: this.recurringDepositProductsTemplate.daysToEscheat ?? 90,
|
||||
balanceSwapOption: this.recurringDepositProductsTemplate.balanceSwapOption ?? false,
|
||||
msme: this.recurringDepositProductsTemplate.msme ?? false,
|
||||
nominationFacility: this.recurringDepositProductsTemplate.nominationFacility ?? false,
|
||||
@@ -72,6 +76,10 @@ export class RecurringDepositProductCurrencyStepComponent implements OnInit {
|
||||
]
|
||||
],
|
||||
dormancyAlert: [false],
|
||||
isDormancyTrackingActive: [false],
|
||||
daysToInactive: [30],
|
||||
daysToDormancy: [60],
|
||||
daysToEscheat: [90],
|
||||
balanceSwapOption: [false],
|
||||
msme: [false],
|
||||
nominationFacility: [false],
|
||||
@@ -81,7 +89,7 @@ export class RecurringDepositProductCurrencyStepComponent implements OnInit {
|
||||
|
||||
get recurringDepositProductCurrency() {
|
||||
const formValue = this.recurringDepositProductCurrencyForm.value;
|
||||
return {
|
||||
const result: any = {
|
||||
currencyCode: formValue.currencyCode,
|
||||
digitsAfterDecimal: formValue.digitsAfterDecimal,
|
||||
dormancyAlert: formValue.dormancyAlert,
|
||||
@@ -90,5 +98,11 @@ export class RecurringDepositProductCurrencyStepComponent implements OnInit {
|
||||
nominationFacility: formValue.nominationFacility,
|
||||
autoDebitFacility: formValue.autoDebitFacility
|
||||
};
|
||||
if (formValue.dormancyAlert) {
|
||||
result.daysToInactive = formValue.daysToInactive;
|
||||
result.daysToDormancy = formValue.daysToDormancy;
|
||||
result.daysToEscheat = formValue.daysToEscheat;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -88,8 +88,12 @@
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="rdpd-field">
|
||||
<mat-label>Product Code</mat-label>
|
||||
<input matInput formControlName="productCode" readonly />
|
||||
<mat-label>GL Account (Liability)</mat-label>
|
||||
<mat-select formControlName="glAccountId" [compareWith]="compareById">
|
||||
@for (account of liabilityAccountOptions; track account.id) {
|
||||
<mat-option [value]="account.id">{{ account.name }} ({{ account.glCode }})</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="rdpd-field" (click)="effectiveToPicker.open()">
|
||||
|
||||
+11
-3
@@ -6,7 +6,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, OnInit, OnChanges, Input, SimpleChanges, inject } from '@angular/core';
|
||||
import { Component, OnInit, OnChanges, Input, SimpleChanges, inject, ChangeDetectorRef } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||
@@ -28,12 +28,14 @@ export class RecurringDepositProductDetailsStepComponent implements OnInit, OnCh
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private dateUtils = inject(Dates);
|
||||
private settingsService = inject(SettingsService);
|
||||
private cdr = inject(ChangeDetectorRef);
|
||||
|
||||
@Input() recurringDepositProductsTemplate: any;
|
||||
|
||||
recurringDepositProductDetailsForm: UntypedFormGroup;
|
||||
accountTypeData: string[] = [];
|
||||
customerSegmentData: string[] = [];
|
||||
liabilityAccountOptions: any[] = [];
|
||||
yesNoOptions: Array<{ value: boolean; label: string }> = [
|
||||
{ value: true, label: 'Yes' },
|
||||
{ value: false, label: 'No' }
|
||||
@@ -71,7 +73,7 @@ export class RecurringDepositProductDetailsStepComponent implements OnInit, OnCh
|
||||
Validators.required
|
||||
],
|
||||
customerSegment: [null],
|
||||
productCode: [''],
|
||||
glAccountId: [null],
|
||||
effectiveTo: [''],
|
||||
endDate: [''],
|
||||
introducerRequired: [false],
|
||||
@@ -99,9 +101,15 @@ export class RecurringDepositProductDetailsStepComponent implements OnInit, OnCh
|
||||
return new Date(dateValue);
|
||||
}
|
||||
|
||||
compareById(o1: any, o2: any): boolean {
|
||||
return o1 != null && o2 != null && Number(o1) === Number(o2);
|
||||
}
|
||||
|
||||
private applyTemplate(template: any) {
|
||||
this.accountTypeData = this.getOptionNames(template.accountTypeOptions);
|
||||
this.customerSegmentData = this.getOptionNames(template.customerSegmentOptions);
|
||||
this.liabilityAccountOptions = template.accountingMappingOptions?.liabilityAccountOptions ?? [];
|
||||
this.cdr.detectChanges();
|
||||
|
||||
const isEditing = !!template.id;
|
||||
this.recurringDepositProductDetailsForm.patchValue({
|
||||
@@ -110,7 +118,7 @@ export class RecurringDepositProductDetailsStepComponent implements OnInit, OnCh
|
||||
description: template.description || '',
|
||||
accountType: template.accountType ?? this.accountTypeData[0] ?? null,
|
||||
customerSegment: template.customerSegment ?? this.customerSegmentData[0] ?? null,
|
||||
productCode: template.productCode ?? '',
|
||||
glAccountId: template.glAccountId ?? null,
|
||||
effectiveTo: this.parseDate(template.effectiveTo),
|
||||
endDate: this.parseDate(template.endDate),
|
||||
introducerRequired: template.introducerRequired === true || template.introducerRequired === 'Yes',
|
||||
|
||||
+4
-4
@@ -31,7 +31,7 @@
|
||||
@if (
|
||||
recurringDepositProduct.accountType ||
|
||||
recurringDepositProduct.customerSegment ||
|
||||
recurringDepositProduct.productCode ||
|
||||
recurringDepositProduct.glAccountId ||
|
||||
recurringDepositProduct.effectiveTo ||
|
||||
recurringDepositProduct.introducerRequired !== undefined ||
|
||||
recurringDepositProduct.shareLinkingEnabled !== undefined
|
||||
@@ -53,10 +53,10 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (recurringDepositProduct.productCode) {
|
||||
@if (recurringDepositProduct.glAccountId) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Product Code:</span>
|
||||
<span class="flex-60">{{ recurringDepositProduct.productCode }}</span>
|
||||
<span class="flex-40">GL Account (Liability):</span>
|
||||
<span class="flex-60">{{ getGlAccountName(recurringDepositProduct.glAccountId) }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -121,6 +121,13 @@ export class RecurringDepositProductPreviewStepComponent implements OnInit, OnCh
|
||||
return found?.value || String(value);
|
||||
}
|
||||
|
||||
getGlAccountName(id: any): string {
|
||||
if (!id) return '';
|
||||
const accounts = this.recurringDepositProductsTemplate?.accountingMappingOptions?.liabilityAccountOptions ?? [];
|
||||
const found = accounts.find((a: any) => a.id === id || a.id === +id);
|
||||
return found ? `${found.name} (${found.glCode})` : String(id);
|
||||
}
|
||||
|
||||
formatGlAccountById(glAccountId: number, options: any[]): string {
|
||||
if (!glAccountId || !Array.isArray(options)) return '-';
|
||||
const account = this.accounting.glAccountLookUp(glAccountId, options as any);
|
||||
|
||||
+3
-3
@@ -54,10 +54,10 @@
|
||||
<span class="flex-60">{{ recurringDepositProduct.customerSegment }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (recurringDepositProduct.productCode) {
|
||||
@if (recurringDepositProduct.glAccountId) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Product Code:</span>
|
||||
<span class="flex-60">{{ recurringDepositProduct.productCode }}</span>
|
||||
<span class="flex-40">GL Account (Liability):</span>
|
||||
<span class="flex-60">{{ recurringDepositProduct.glAccountName || recurringDepositProduct.glAccountId }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (recurringDepositProduct.effectiveTo) {
|
||||
|
||||
+6
-2
@@ -82,8 +82,12 @@
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="spd-field">
|
||||
<mat-label>Product Code</mat-label>
|
||||
<input matInput formControlName="productCode" readonly />
|
||||
<mat-label>GL Account (Liability)</mat-label>
|
||||
<mat-select formControlName="glAccountId" [compareWith]="compareById">
|
||||
@for (account of liabilityAccountOptions; track account.id) {
|
||||
<mat-option [value]="account.id">{{ account.name }} ({{ account.glCode }})</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="spd-field" (click)="effectiveToPicker.open()">
|
||||
|
||||
+11
-3
@@ -6,7 +6,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, OnInit, OnChanges, Input, SimpleChanges, inject } from '@angular/core';
|
||||
import { Component, OnInit, OnChanges, Input, SimpleChanges, inject, ChangeDetectorRef } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||
@@ -30,12 +30,14 @@ export class SavingProductDetailsStepComponent implements OnInit, OnChanges {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private dateUtils = inject(Dates);
|
||||
private settingsService = inject(SettingsService);
|
||||
private cdr = inject(ChangeDetectorRef);
|
||||
|
||||
@Input() savingProductsTemplate: any;
|
||||
|
||||
savingProductDetailsForm: UntypedFormGroup;
|
||||
accountTypeData: string[] = [];
|
||||
customerSegmentData: string[] = [];
|
||||
liabilityAccountOptions: any[] = [];
|
||||
yesNoOptions: Array<{
|
||||
value: boolean;
|
||||
label: string;
|
||||
@@ -82,7 +84,7 @@ export class SavingProductDetailsStepComponent implements OnInit, OnChanges {
|
||||
Validators.required
|
||||
],
|
||||
customerSegment: [null],
|
||||
productCode: [''],
|
||||
glAccountId: [null],
|
||||
effectiveDate: [''],
|
||||
effectiveTo: [''],
|
||||
endDate: [''],
|
||||
@@ -110,6 +112,8 @@ export class SavingProductDetailsStepComponent implements OnInit, OnChanges {
|
||||
|
||||
this.accountTypeData = accountTypeOptions;
|
||||
this.customerSegmentData = customerSegmentOptions;
|
||||
this.liabilityAccountOptions = template.accountingMappingOptions?.liabilityAccountOptions ?? [];
|
||||
this.cdr.detectChanges();
|
||||
if (yesNoOptions.length) {
|
||||
this.yesNoOptions = yesNoOptions;
|
||||
}
|
||||
@@ -122,7 +126,7 @@ export class SavingProductDetailsStepComponent implements OnInit, OnChanges {
|
||||
description: template.description,
|
||||
accountType: template.accountType ?? this.accountTypeData[0] ?? null,
|
||||
customerSegment: template.customerSegment ?? this.customerSegmentData[0] ?? null,
|
||||
productCode: template.productCode ?? '',
|
||||
glAccountId: template.glAccountId ?? null,
|
||||
effectiveDate: template.effectiveDate ? new Date(template.effectiveDate) : '',
|
||||
effectiveTo: template.effectiveTo ? new Date(template.effectiveTo) : '',
|
||||
endDate: template.endDate ? new Date(template.endDate) : '',
|
||||
@@ -162,6 +166,10 @@ export class SavingProductDetailsStepComponent implements OnInit, OnChanges {
|
||||
.filter((value) => !!value);
|
||||
}
|
||||
|
||||
compareById(o1: any, o2: any): boolean {
|
||||
return o1 != null && o2 != null && Number(o1) === Number(o2);
|
||||
}
|
||||
|
||||
get savingProductDetails() {
|
||||
const formValue = { ...this.savingProductDetailsForm.value };
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
|
||||
+4
-4
@@ -28,7 +28,7 @@
|
||||
@if (
|
||||
savingProduct.accountType ||
|
||||
savingProduct.customerSegment ||
|
||||
savingProduct.productCode ||
|
||||
savingProduct.glAccountId ||
|
||||
savingProduct.effectiveTo ||
|
||||
savingProduct.introducerRequired !== undefined ||
|
||||
savingProduct.shareLinkingEnabled !== undefined
|
||||
@@ -51,10 +51,10 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (savingProduct.productCode) {
|
||||
@if (savingProduct.glAccountId) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Product Code:</span>
|
||||
<span class="flex-60">{{ savingProduct.productCode }}</span>
|
||||
<span class="flex-40">GL Account (Liability):</span>
|
||||
<span class="flex-60">{{ getGlAccountName(savingProduct.glAccountId) }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -201,6 +201,13 @@ export class SavingProductPreviewStepComponent implements OnInit, OnChanges {
|
||||
return this.accounting.isAccrualAccounting(this.accountingRule);
|
||||
}
|
||||
|
||||
getGlAccountName(id: any): string {
|
||||
if (!id) return '';
|
||||
const accounts = this.savingProductsTemplate?.accountingMappingOptions?.liabilityAccountOptions ?? [];
|
||||
const found = accounts.find((a: any) => a.id === id || a.id === +id);
|
||||
return found ? `${found.name} (${found.glCode})` : String(id);
|
||||
}
|
||||
|
||||
getBooleanLabel(value: any): string {
|
||||
return value === true || value === 'Yes' ? 'Yes' : 'No';
|
||||
}
|
||||
|
||||
+4
-4
@@ -40,7 +40,7 @@
|
||||
@if (
|
||||
savingProduct.accountType ||
|
||||
savingProduct.customerSegment ||
|
||||
savingProduct.productCode ||
|
||||
savingProduct.glAccountId ||
|
||||
savingProduct.effectiveDate ||
|
||||
savingProduct.effectiveTo ||
|
||||
savingProduct.endDate ||
|
||||
@@ -65,10 +65,10 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (savingProduct.productCode) {
|
||||
@if (savingProduct.glAccountId) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Product Code:</span>
|
||||
<span class="flex-60">{{ savingProduct.productCode }}</span>
|
||||
<span class="flex-40">GL Account (Liability):</span>
|
||||
<span class="flex-60">{{ savingProduct.glAccountName || savingProduct.glAccountId }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
</ng-template>
|
||||
|
||||
<!-- Step 1: Product Classification + Details -->
|
||||
<mat-step [stepControl]="savingsAccountDetailsForm">
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>Product Classification</ng-template>
|
||||
|
||||
<mifosx-savings-account-details-step
|
||||
@@ -45,96 +45,46 @@
|
||||
</mifosx-savings-account-details-step>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 2: Currency -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>Currency</ng-template>
|
||||
@if (savingsAccountProductTemplate) {
|
||||
<!-- Step 2: Currency -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>Currency</ng-template>
|
||||
|
||||
<mifosx-savings-account-currency-step [savingsAccountProductTemplate]="savingsAccountProductTemplate">
|
||||
</mifosx-savings-account-currency-step>
|
||||
<mifosx-savings-account-currency-step [savingsAccountProductTemplate]="savingsAccountProductTemplate">
|
||||
</mifosx-savings-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>
|
||||
<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: Term -->
|
||||
<mat-step [stepControl]="savingsAccountTermsForm">
|
||||
<ng-template matStepLabel>Term</ng-template>
|
||||
<!-- Step 3: Digital -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>Digital</ng-template>
|
||||
|
||||
<mifosx-savings-account-term-step [savingsAccountProductTemplate]="savingsAccountProductTemplate">
|
||||
</mifosx-savings-account-term-step>
|
||||
<mifosx-savings-account-digital-step [savingsAccountProductTemplate]="savingsAccountProductTemplate">
|
||||
</mifosx-savings-account-digital-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 [disabled]="savingsAccountTermsForm.invalid">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-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 4: Settings -->
|
||||
<mat-step [stepControl]="savingsAccountSettingsForm">
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.SETTINGS' | translate }}</ng-template>
|
||||
|
||||
<mifosx-savings-account-settings-step [savingsAccountProductTemplate]="savingsAccountProductTemplate">
|
||||
</mifosx-savings-account-settings-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 5: Digital -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>Digital</ng-template>
|
||||
|
||||
<mifosx-savings-account-digital-step [savingsAccountProductTemplate]="savingsAccountProductTemplate">
|
||||
</mifosx-savings-account-digital-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: Charges -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.CHARGES' | translate }}</ng-template>
|
||||
|
||||
<mifosx-savings-account-charges-step
|
||||
[savingsAccountProductTemplate]="savingsAccountProductTemplate"
|
||||
[savingsAccountTemplate]="savingsAccountTemplate"
|
||||
[currencyCode]="savingsAccountTermsForm.get('currencyCode')"
|
||||
>
|
||||
</mifosx-savings-account-charges-step>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 7: Preview -->
|
||||
@if (savingsAccountFormValid) {
|
||||
<!-- Step 4: Preview -->
|
||||
<mat-step completed>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.PREVIEW' | translate }}</ng-template>
|
||||
<mifosx-savings-account-preview-step
|
||||
@@ -142,6 +92,8 @@
|
||||
[savingsAccountTemplate]="savingsAccountTemplate"
|
||||
[savingsAccountTermsForm]="savingsAccountTermsForm"
|
||||
[savingsAccount]="savingsAccount"
|
||||
[savingsAccountDigital]="savingsAccountDigital"
|
||||
[savingsAccountNominee]="savingsAccountNominee"
|
||||
(submitEvent)="submit()"
|
||||
>
|
||||
</mifosx-savings-account-preview-step>
|
||||
|
||||
@@ -27,11 +27,14 @@
|
||||
.mat-step-content {
|
||||
padding: 1.5rem;
|
||||
margin: 1rem 0;
|
||||
overflow: visible !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Form field spacing within steps
|
||||
:host ::ng-deep mat-step-content {
|
||||
overflow: visible !important;
|
||||
|
||||
mat-form-field {
|
||||
margin-bottom: 1rem;
|
||||
width: 100%;
|
||||
|
||||
@@ -9,20 +9,26 @@
|
||||
/** Angular Imports */
|
||||
import { Component, ViewChild, inject } from '@angular/core';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
/** Custom Components */
|
||||
import { SavingsAccountDetailsStepComponent } from '../savings-account-stepper/savings-account-details-step/savings-account-details-step.component';
|
||||
import { SavingsAccountTermStepComponent } from '../savings-account-stepper/savings-account-term-step/savings-account-term-step.component';
|
||||
import { SavingsAccountSettingsStepComponent } from '../savings-account-stepper/savings-account-settings-step/savings-account-settings-step.component';
|
||||
import { SavingsAccountChargesStepComponent } from '../savings-account-stepper/savings-account-charges-step/savings-account-charges-step.component';
|
||||
import { SavingsAccountCurrencyStepComponent } from '../savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component';
|
||||
import { SavingsAccountDigitalStepComponent } from '../savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component';
|
||||
import { SavingsAccountSettingsStepComponent } from '../savings-account-stepper/savings-account-settings-step/savings-account-settings-step.component';
|
||||
|
||||
/** Custom Services */
|
||||
import { SavingsService } from '../savings.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper';
|
||||
import {
|
||||
MatStepper,
|
||||
MatStepperIcon,
|
||||
MatStep,
|
||||
MatStepLabel,
|
||||
MatStepperNext,
|
||||
MatStepperPrevious
|
||||
} from '@angular/material/stepper';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { SavingsAccountPreviewStepComponent } from '../savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
@@ -38,14 +44,13 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatStepper,
|
||||
MatStepperIcon,
|
||||
MatStepperNext,
|
||||
MatStepperPrevious,
|
||||
FaIconComponent,
|
||||
MatStep,
|
||||
MatStepLabel,
|
||||
SavingsAccountDetailsStepComponent,
|
||||
SavingsAccountCurrencyStepComponent,
|
||||
SavingsAccountTermStepComponent,
|
||||
SavingsAccountSettingsStepComponent,
|
||||
SavingsAccountChargesStepComponent,
|
||||
SavingsAccountDigitalStepComponent,
|
||||
SavingsAccountPreviewStepComponent
|
||||
]
|
||||
@@ -65,14 +70,11 @@ export class CreateSavingsAccountComponent {
|
||||
@ViewChild(SavingsAccountDetailsStepComponent, { static: true })
|
||||
savingsAccountDetailsStep: SavingsAccountDetailsStepComponent;
|
||||
|
||||
@ViewChild(SavingsAccountTermStepComponent, { static: true })
|
||||
savingsAccountTermStep: SavingsAccountTermStepComponent;
|
||||
@ViewChild(SavingsAccountDigitalStepComponent)
|
||||
savingsAccountDigitalStep: SavingsAccountDigitalStepComponent;
|
||||
|
||||
@ViewChild(SavingsAccountSettingsStepComponent, { static: true })
|
||||
savingsAccountSettingsStep: SavingsAccountSettingsStepComponent;
|
||||
|
||||
@ViewChild(SavingsAccountChargesStepComponent, { static: true })
|
||||
savingsAccountChargesStep: SavingsAccountChargesStepComponent;
|
||||
@ViewChild(SavingsAccountCurrencyStepComponent)
|
||||
savingsAccountCurrencyStep: SavingsAccountCurrencyStepComponent;
|
||||
|
||||
constructor() {
|
||||
this.route.data.subscribe((data: { savingsAccountTemplate: any }) => {
|
||||
@@ -88,46 +90,32 @@ export class CreateSavingsAccountComponent {
|
||||
return this.savingsAccountDetailsStep.savingsAccountDetailsForm;
|
||||
}
|
||||
|
||||
get savingsAccountTermsForm() {
|
||||
return this.savingsAccountTermStep.savingsAccountTermForm;
|
||||
}
|
||||
|
||||
get savingsAccountSettingsForm() {
|
||||
return this.savingsAccountSettingsStep.savingsAccountSettingsForm;
|
||||
}
|
||||
|
||||
get savingsAccountFormValid() {
|
||||
return (
|
||||
this.savingsAccountDetailsForm.valid &&
|
||||
this.savingsAccountTermsForm.valid &&
|
||||
this.savingsAccountSettingsForm.valid
|
||||
);
|
||||
/** Stub — no term step in this flow */
|
||||
get savingsAccountTermsForm(): FormGroup {
|
||||
return new FormGroup({});
|
||||
}
|
||||
|
||||
get savingsAccount() {
|
||||
return {
|
||||
...this.savingsAccountDetailsStep.savingsAccountDetails,
|
||||
...this.savingsAccountTermStep.savingsAccountTerms,
|
||||
...this.savingsAccountSettingsStep.savingsAccountSettings,
|
||||
...this.savingsAccountChargesStep.savingsAccountCharges
|
||||
...this.savingsAccountDetailsStep.savingsAccountDetails
|
||||
};
|
||||
}
|
||||
|
||||
get savingsAccountDigital() {
|
||||
return this.savingsAccountDigitalStep?.savingsAccountDigital ?? null;
|
||||
}
|
||||
|
||||
get savingsAccountNominee() {
|
||||
return this.savingsAccountCurrencyStep?.nomineeData ?? null;
|
||||
}
|
||||
|
||||
submit() {
|
||||
const locale = this.settingsService.language.code;
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
const monthDayFormat = 'dd MMMM';
|
||||
const savingsAccount = {
|
||||
...this.savingsAccount,
|
||||
charges: this.savingsAccount.charges.map((charge: any) => ({
|
||||
chargeId: charge.id,
|
||||
amount: charge.amount,
|
||||
dueDate: charge.dueDate ? this.dateUtils.formatDate(charge.dueDate, dateFormat) : charge.dueDate,
|
||||
feeOnMonthDay: charge.feeOnMonthDay
|
||||
? this.dateUtils.formatDate(charge.feeOnMonthDay, monthDayFormat)
|
||||
: charge.feeOnMonthDay,
|
||||
feeInterval: charge.feeInterval
|
||||
})),
|
||||
charges: [],
|
||||
submittedOnDate: this.dateUtils.formatDate(this.savingsAccount.submittedOnDate, dateFormat),
|
||||
dateFormat,
|
||||
monthDayFormat,
|
||||
|
||||
+6
@@ -35,6 +35,9 @@
|
||||
{{ savingsAccountProductTemplate?.dormancyAlert ? 'Yes' : 'No' }}
|
||||
</span>
|
||||
</div>
|
||||
@if (savingsAccountProductTemplate?.dormancyAlert) {
|
||||
<mifosx-dormancy-alert-panel [productTemplate]="savingsAccountProductTemplate"></mifosx-dormancy-alert-panel>
|
||||
}
|
||||
|
||||
<div class="sac-alert">
|
||||
<div class="sac-alert__content">
|
||||
@@ -45,5 +48,8 @@
|
||||
{{ savingsAccountProductTemplate?.nominationFacility ? 'Yes' : 'No' }}
|
||||
</span>
|
||||
</div>
|
||||
@if (savingsAccountProductTemplate?.nominationFacility) {
|
||||
<mifosx-nominee-form (nomineeChange)="onNomineeChange($event)"></mifosx-nominee-form>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+13
-1
@@ -8,13 +8,25 @@
|
||||
|
||||
import { Component, Input } 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';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-savings-account-currency-step',
|
||||
templateUrl: './savings-account-currency-step.component.html',
|
||||
styleUrls: ['./savings-account-currency-step.component.scss'],
|
||||
imports: [...STANDALONE_SHARED_IMPORTS]
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
NomineeFormComponent,
|
||||
DormancyAlertPanelComponent
|
||||
]
|
||||
})
|
||||
export class SavingsAccountCurrencyStepComponent {
|
||||
@Input() savingsAccountProductTemplate: any;
|
||||
|
||||
nomineeData: any = null;
|
||||
|
||||
onNomineeChange(event: any): void {
|
||||
this.nomineeData = event;
|
||||
}
|
||||
}
|
||||
|
||||
+11
-14
@@ -67,7 +67,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product Classification (read-only, shown after product is selected) -->
|
||||
<!-- Product Classification + Introducer (shown after product is selected) -->
|
||||
@if (savingsProductSelected) {
|
||||
<div class="sad-section sad-section--mt">
|
||||
<div class="sad-section__header">
|
||||
@@ -87,8 +87,12 @@
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="sad-field">
|
||||
<mat-label>Product Code</mat-label>
|
||||
<input matInput [value]="productTemplate.productCode || ''" readonly />
|
||||
<mat-label>Account No</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[value]="productTemplate?.previewAccountNo || savingsAccountTemplate?.accountNo || 'Auto-generated'"
|
||||
readonly
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
@if (productTemplate.effectiveDate) {
|
||||
@@ -118,16 +122,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
||||
<button mat-raised-button matStepperPrevious disabled>
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button matStepperNext [disabled]="savingsAccountDetailsForm.invalid">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
@if (productTemplate?.introducerRequired) {
|
||||
<mifosx-introducer-search (introducerChange)="onIntroducerChange($event)"></mifosx-introducer-search>
|
||||
}
|
||||
}
|
||||
</form>
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ $gray-900: #202124;
|
||||
.sad-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
background: #fff;
|
||||
|
||||
&--mt {
|
||||
|
||||
+19
-2
@@ -17,6 +17,7 @@ import { SavingsService } from 'app/savings/savings.service';
|
||||
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { IntroducerSearchComponent } from 'app/shared/introducer-search/introducer-search.component';
|
||||
|
||||
/**
|
||||
* Savings Account Details Step
|
||||
@@ -29,7 +30,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatStepperPrevious,
|
||||
FaIconComponent,
|
||||
MatStepperNext
|
||||
MatStepperNext,
|
||||
IntroducerSearchComponent
|
||||
]
|
||||
})
|
||||
export class SavingsAccountDetailsStepComponent implements OnInit {
|
||||
@@ -56,6 +58,9 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
||||
savingsAccountDetailsForm: UntypedFormGroup;
|
||||
|
||||
savingsProductSelected = false;
|
||||
introducerId: number | null = null;
|
||||
introducerName: string | null = null;
|
||||
introducerCif: string | null = null;
|
||||
|
||||
/** Savings Account Template with product data */
|
||||
@Output() savingsAccountProductTemplate = new EventEmitter();
|
||||
@@ -165,7 +170,19 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
||||
/**
|
||||
* Returns savings account form value.
|
||||
*/
|
||||
onIntroducerChange(event: { clientId: number; displayName: string; cifNumber?: string } | null): void {
|
||||
this.introducerId = event?.clientId ?? null;
|
||||
this.introducerName = event?.displayName ?? null;
|
||||
this.introducerCif = event?.cifNumber ?? null;
|
||||
}
|
||||
|
||||
get savingsAccountDetails() {
|
||||
return this.savingsAccountDetailsForm.getRawValue();
|
||||
const value = this.savingsAccountDetailsForm.getRawValue();
|
||||
if (this.introducerId) {
|
||||
value.introducerId = this.introducerId;
|
||||
value.introducerName = this.introducerName;
|
||||
value.introducerCif = this.introducerCif;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
+194
-214
@@ -6,229 +6,209 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="spl-section">
|
||||
<div class="spl-section__header">
|
||||
<span class="spl-section__icon">
|
||||
<i class="fas fa-chart-bar"></i>
|
||||
</span>
|
||||
<div class="spl-section__title">Transaction & Product Limits</div>
|
||||
<div class="spl-section__badge">FROM PRODUCT</div>
|
||||
<div class="dig-wrapper">
|
||||
<div class="dig-header">
|
||||
<div class="dig-header__icon">
|
||||
<i class="fas fa-mobile-alt"></i>
|
||||
</div>
|
||||
<div class="dig-header__text">
|
||||
<h3 class="dig-header__title">Digital & Account Features</h3>
|
||||
<p class="dig-header__sub">Toggle the facilities available for this account (as per RBI guidelines)</p>
|
||||
</div>
|
||||
<span class="dig-header__badge">FROM PRODUCT</span>
|
||||
</div>
|
||||
|
||||
<div class="spl-section__body">
|
||||
<div class="spl-features">
|
||||
<div class="spl-features__header">Account Features (as per RBI guidelines)</div>
|
||||
|
||||
<div class="spl-features__grid">
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-automobile"></i> ATM Facility</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.atmFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-wallet"></i> ATM Withdrawal</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.atmWithdrawalFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-mobile-alt"></i> UPI Facility</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.upiFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-exchange-alt"></i> POS Transactions</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.posTransactionsFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-book"></i> Cheque Book</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.chequeBook" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-bolt"></i> IMPS Facility</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.impsFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-sms"></i> SMS Alerts</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.smsAlerts" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-bell"></i> BBPS Facility</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.bbpsFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-shield-alt"></i> Insurance Cover</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.insuranceCover" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-bolt"></i> BEPG Facility</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.bepgFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-money-check-alt"></i> NEFT Facility</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.neftFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-money-check"></i> RTGS Facility</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.rtgsFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-balance-scale"></i> Inter-Bank Total Cap</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.interBankTotalCapFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-arrow-right-arrow-left"></i> Withdrawal Facility</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.withdrawalFacility" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-id-card"></i> Deposit Facility (KYC)</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.depositFacilityKyc" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="spl-feature">
|
||||
<span class="spl-feature__label"><i class="fas fa-credit-card"></i> Virtual Debit Card</span>
|
||||
<select class="spl-select" [ngModel]="accountFacilityConfig.virtualDebitCard" disabled>
|
||||
<option [ngValue]="false">No</option>
|
||||
<option [ngValue]="true">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="dig-grid">
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.atmFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-credit-card"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">ATM Facility</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.atmFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.atmFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@if (accountFacilityLimits.length > 0) {
|
||||
<div class="spl-table-wrap">
|
||||
<table class="spl-table">
|
||||
<colgroup>
|
||||
<col class="spl-table__col--label" />
|
||||
<col class="spl-table__col--value" />
|
||||
<col class="spl-table__col--value" />
|
||||
<col class="spl-table__col--value" />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Limit Type</th>
|
||||
<th>General Limit (Daily)</th>
|
||||
<th>Minor Account Limit</th>
|
||||
<th>Small Saving Limit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (row of accountFacilityLimits; track row.limitType) {
|
||||
<tr>
|
||||
<td>{{ row.limitType }}</td>
|
||||
<td><input type="text" class="spl-input" [value]="row.generalLimit" disabled /></td>
|
||||
<td><input type="text" class="spl-input" [value]="row.minorAccountLimit" disabled /></td>
|
||||
<td><input type="text" class="spl-input" [value]="row.smallSavingLimit" disabled /></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.atmWithdrawalFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-wallet"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">ATM Withdrawal</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.atmWithdrawalFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
}
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.atmWithdrawalFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@if (depositLimitKyc?.general) {
|
||||
<div class="spl-table-wrap spl-table-wrap--kyc">
|
||||
<h4 class="mat-h4">Deposit Limit (KYC)</h4>
|
||||
<table class="spl-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account Type</th>
|
||||
<th>KYC Type</th>
|
||||
<th>Limit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>General</td>
|
||||
<td>
|
||||
<select class="spl-select" [ngModel]="depositLimitKyc.general.kycType" disabled>
|
||||
<option>{{ depositLimitKyc.general.kycType }}</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" class="spl-input" [value]="depositLimitKyc.general.limit" disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Minor</td>
|
||||
<td>
|
||||
<select class="spl-select" [ngModel]="depositLimitKyc.minor.kycType" disabled>
|
||||
<option>{{ depositLimitKyc.minor.kycType }}</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="number" class="spl-input" [value]="depositLimitKyc.minor.limit" disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Small Saving</td>
|
||||
<td>
|
||||
<select class="spl-select" [ngModel]="depositLimitKyc.smallSaving.kycType" disabled>
|
||||
<option>{{ depositLimitKyc.smallSaving.kycType }}</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="number" class="spl-input" [value]="depositLimitKyc.smallSaving.limit" disabled /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.upiFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-mobile-alt"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">UPI Facility</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.upiFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
}
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.upiFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="spl-note">
|
||||
<strong>Note:</strong>
|
||||
These facilities are defined at product level and applied to this account. Small Saving Account limits are
|
||||
strictly capped at Rs.1,00,000 maximum balance per RBI Master Circular.
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.posTransactionsFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-exchange-alt"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">POS Transactions</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.posTransactionsFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.posTransactionsFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.chequeBook">
|
||||
<div class="dig-card__icon"><i class="fas fa-book"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">Cheque Book</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.chequeBook ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.chequeBook" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.impsFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-bolt"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">IMPS Facility</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.impsFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.impsFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.smsAlerts">
|
||||
<div class="dig-card__icon"><i class="fas fa-sms"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">SMS Alerts</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.smsAlerts ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.smsAlerts" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.bbpsFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-bell"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">BBPS Facility</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.bbpsFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.bbpsFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.insuranceCover">
|
||||
<div class="dig-card__icon"><i class="fas fa-shield-alt"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">Insurance Cover</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.insuranceCover ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.insuranceCover" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.bepgFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-plug"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">BEPG Facility</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.bepgFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.bepgFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.neftFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-money-check-alt"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">NEFT Facility</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.neftFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.neftFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.rtgsFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-money-check"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">RTGS Facility</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.rtgsFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.rtgsFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.interBankTotalCapFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-balance-scale"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">Inter-Bank Total Cap</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.interBankTotalCapFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.interBankTotalCapFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.withdrawalFacility">
|
||||
<div class="dig-card__icon"><i class="fas fa-arrow-right-arrow-left"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">Withdrawal Facility</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.withdrawalFacility ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.withdrawalFacility" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.depositFacilityKyc">
|
||||
<div class="dig-card__icon"><i class="fas fa-id-card"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">Deposit Facility (KYC)</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.depositFacilityKyc ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.depositFacilityKyc" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="dig-card" [class.dig-card--on]="facilityConfig.virtualDebitCard">
|
||||
<div class="dig-card__icon"><i class="fas fa-credit-card"></i></div>
|
||||
<div class="dig-card__body">
|
||||
<span class="dig-card__label">Virtual Debit Card</span>
|
||||
<span class="dig-card__status">{{ facilityConfig.virtualDebitCard ? 'Enabled' : 'Disabled' }}</span>
|
||||
</div>
|
||||
<label class="dig-toggle">
|
||||
<input type="checkbox" [(ngModel)]="facilityConfig.virtualDebitCard" />
|
||||
<span class="dig-toggle__track"><span class="dig-toggle__thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+165
-154
@@ -6,194 +6,205 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$border: #d9e2ec;
|
||||
$border-strong: #c7d2de;
|
||||
$text: #24364b;
|
||||
$blue: #2563eb;
|
||||
$blue-light: #eff6ff;
|
||||
$green: #16a34a;
|
||||
$green-light: #f0fdf4;
|
||||
$green-border: #bbf7d0;
|
||||
$border: #e2e8f0;
|
||||
$text: #1e293b;
|
||||
$muted: #64748b;
|
||||
$header-bg: #f8fafc;
|
||||
$gray-bg: #f8fafc;
|
||||
|
||||
.spl-section {
|
||||
// ── Wrapper ──────────────────────────────────────────────
|
||||
.dig-wrapper {
|
||||
border: 1px solid $border;
|
||||
border-radius: 12px;
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 4px rgb(0 0 0 / 6%);
|
||||
}
|
||||
|
||||
.spl-section__header {
|
||||
// ── Header ───────────────────────────────────────────────
|
||||
.dig-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px 20px;
|
||||
background: $header-bg;
|
||||
gap: 14px;
|
||||
padding: 18px 24px;
|
||||
background: $gray-bg;
|
||||
border-bottom: 1px solid $border;
|
||||
}
|
||||
|
||||
.spl-section__icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
background: #eff6ff;
|
||||
color: #2563eb;
|
||||
font-size: 14px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.spl-section__title {
|
||||
flex: 1 1 auto;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
}
|
||||
|
||||
.spl-section__badge {
|
||||
flex: 0 0 auto;
|
||||
padding: 5px 10px;
|
||||
border-radius: 999px;
|
||||
background: #dbeafe;
|
||||
color: #1d4ed8;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.spl-section__body {
|
||||
padding: 18px 20px 20px;
|
||||
}
|
||||
|
||||
.spl-table-wrap {
|
||||
overflow-x: auto;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.spl-table {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid $border;
|
||||
|
||||
col.spl-table__col--label {
|
||||
width: 22%;
|
||||
&__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
background: $blue-light;
|
||||
color: $blue;
|
||||
font-size: 16px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
col.spl-table__col--value {
|
||||
width: 26%;
|
||||
&__text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid $border;
|
||||
padding: 6px 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f8fafc;
|
||||
color: #64748b;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
&__title {
|
||||
margin: 0 0 2px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
background: #f8fafc;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td {
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
&__sub {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: $muted;
|
||||
}
|
||||
|
||||
&__badge {
|
||||
flex: 0 0 auto;
|
||||
padding: 4px 12px;
|
||||
border-radius: 999px;
|
||||
background: #dbeafe;
|
||||
color: $blue;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
}
|
||||
|
||||
.spl-input,
|
||||
.spl-select {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.spl-input {
|
||||
height: 30px;
|
||||
border: 1px solid $border-strong;
|
||||
border-radius: 2px;
|
||||
padding: 0 8px;
|
||||
background: #f8fafc;
|
||||
color: $muted;
|
||||
}
|
||||
|
||||
.spl-select {
|
||||
height: 30px;
|
||||
border: 1px solid $border-strong;
|
||||
border-radius: 2px;
|
||||
padding: 0 6px;
|
||||
background: #f8fafc;
|
||||
color: $muted;
|
||||
}
|
||||
|
||||
.spl-features {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.spl-features__header {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $muted;
|
||||
margin-bottom: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.spl-features__grid {
|
||||
// ── Grid ─────────────────────────────────────────────────
|
||||
.dig-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px 16px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0;
|
||||
|
||||
@media (width <= 900px) {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
@media (width <= 960px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media (width <= 640px) {
|
||||
@media (width <= 580px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.spl-feature {
|
||||
// ── Card ─────────────────────────────────────────────────
|
||||
.dig-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: $text;
|
||||
}
|
||||
|
||||
.spl-feature__label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
color: #334155;
|
||||
gap: 12px;
|
||||
padding: 14px 18px;
|
||||
border-right: 1px solid $border;
|
||||
border-bottom: 1px solid $border;
|
||||
background: #fff;
|
||||
transition: background 0.18s ease;
|
||||
cursor: pointer;
|
||||
|
||||
i {
|
||||
color: #2563eb;
|
||||
font-size: 12px;
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
&:nth-child(3n) {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
&--on {
|
||||
background: $green-light;
|
||||
|
||||
.dig-card__icon {
|
||||
background: $green-border;
|
||||
color: $green;
|
||||
}
|
||||
|
||||
.dig-card__status {
|
||||
color: $green;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 9px;
|
||||
background: $blue-light;
|
||||
color: $blue;
|
||||
font-size: 14px;
|
||||
flex: 0 0 auto;
|
||||
transition:
|
||||
background 0.18s,
|
||||
color 0.18s;
|
||||
}
|
||||
|
||||
&__body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__status {
|
||||
font-size: 11px;
|
||||
color: $muted;
|
||||
transition: color 0.18s;
|
||||
}
|
||||
}
|
||||
|
||||
.spl-note {
|
||||
margin-top: 16px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
color: $muted;
|
||||
font-size: 11px;
|
||||
line-height: 1.6;
|
||||
// ── Toggle Switch ─────────────────────────────────────────
|
||||
.dig-toggle {
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
cursor: pointer;
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
&:checked + .dig-toggle__track {
|
||||
background: $green;
|
||||
border-color: $green;
|
||||
}
|
||||
|
||||
&:checked + .dig-toggle__track .dig-toggle__thumb {
|
||||
transform: translateX(18px);
|
||||
}
|
||||
}
|
||||
|
||||
&__track {
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 22px;
|
||||
border-radius: 999px;
|
||||
background: #cbd5e1;
|
||||
border: 1.5px solid #94a3b8;
|
||||
position: relative;
|
||||
transition:
|
||||
background 0.2s,
|
||||
border-color 0.2s;
|
||||
}
|
||||
|
||||
&__thumb {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 3px rgb(0 0 0 / 20%);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-21
@@ -39,31 +39,16 @@ const DEFAULT_FACILITY_CONFIG = {
|
||||
]
|
||||
})
|
||||
export class SavingsAccountDigitalStepComponent {
|
||||
private _savingsAccountProductTemplate: any;
|
||||
facilityConfig = { ...DEFAULT_FACILITY_CONFIG };
|
||||
|
||||
@Input()
|
||||
set savingsAccountProductTemplate(value: any) {
|
||||
this._savingsAccountProductTemplate = value;
|
||||
}
|
||||
get savingsAccountProductTemplate(): any {
|
||||
return this._savingsAccountProductTemplate;
|
||||
if (value?.accountFacilityConfig) {
|
||||
this.facilityConfig = { ...DEFAULT_FACILITY_CONFIG, ...value.accountFacilityConfig };
|
||||
}
|
||||
}
|
||||
|
||||
get accountFacilityConfig() {
|
||||
return this._savingsAccountProductTemplate?.accountFacilityConfig || DEFAULT_FACILITY_CONFIG;
|
||||
}
|
||||
|
||||
get accountFacilityLimits(): any[] {
|
||||
return this._savingsAccountProductTemplate?.accountFacilityLimits || [];
|
||||
}
|
||||
|
||||
get depositLimitKyc() {
|
||||
return (
|
||||
this._savingsAccountProductTemplate?.depositLimitKyc || {
|
||||
general: { kycType: '', limit: '' },
|
||||
minor: { kycType: '', limit: '' },
|
||||
smallSaving: { kycType: '', limit: '' }
|
||||
}
|
||||
);
|
||||
get savingsAccountDigital() {
|
||||
return { accountFacilityConfig: { ...this.facilityConfig } };
|
||||
}
|
||||
}
|
||||
|
||||
+287
-438
@@ -7,9 +7,11 @@
|
||||
-->
|
||||
|
||||
<div class="preview-wrapper">
|
||||
<!-- ══ 1. Account Details ════════════════════════════════════════ -->
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<span class="preview-section__title">{{ 'labels.heading.Details' | translate }}</span>
|
||||
<i class="fas fa-info-circle preview-section__icon"></i>
|
||||
<span class="preview-section__title">Account Details</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
@@ -37,25 +39,27 @@
|
||||
<mifosx-external-identifier externalId="{{ savingsAccount.externalId }}"></mifosx-external-identifier>
|
||||
</span>
|
||||
} @else {
|
||||
<span class="preview-value">{{ 'labels.inputs.Unassigned' | translate }}</span>
|
||||
<span class="preview-value preview-muted">Unassigned</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ 2. Product Classification ════════════════════════════════ -->
|
||||
@if (
|
||||
savingsAccountProductTemplate?.accountType ||
|
||||
savingsAccountProductTemplate?.customerSegment ||
|
||||
savingsAccountProductTemplate?.productCode ||
|
||||
savingsAccountProductTemplate?.previewAccountNo ||
|
||||
savingsAccountProductTemplate?.effectiveDate ||
|
||||
savingsAccountProductTemplate?.endDate ||
|
||||
savingsAccountProductTemplate?.introducerRequired !== undefined ||
|
||||
savingsAccountProductTemplate?.shareLinkingEnabled !== undefined
|
||||
savingsAccountProductTemplate?.endDate
|
||||
) {
|
||||
<div class="preview-section">
|
||||
<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">From Product</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
@@ -74,9 +78,15 @@
|
||||
@if (savingsAccountProductTemplate?.productCode) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Product Code</span>
|
||||
<span class="preview-value">{{ savingsAccountProductTemplate?.productCode }}</span>
|
||||
<span class="preview-value preview-mono">{{ savingsAccountProductTemplate?.productCode }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Account No</span>
|
||||
<span class="preview-value preview-mono">{{
|
||||
savingsAccountProductTemplate?.previewAccountNo || savingsAccountTemplate?.accountNo || 'Auto-generated'
|
||||
}}</span>
|
||||
</div>
|
||||
@if (savingsAccountProductTemplate?.effectiveDate) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Effective Date</span>
|
||||
@@ -89,400 +99,298 @@
|
||||
<span class="preview-value">{{ savingsAccountProductTemplate?.endDate | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (
|
||||
savingsAccountProductTemplate?.introducerRequired !== undefined &&
|
||||
savingsAccountProductTemplate?.introducerRequired !== null
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Introducer Required</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="savingsAccountProductTemplate?.introducerRequired"
|
||||
>
|
||||
{{ savingsAccountProductTemplate?.introducerRequired | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@if (
|
||||
savingsAccountProductTemplate?.shareLinkingEnabled !== undefined &&
|
||||
savingsAccountProductTemplate?.shareLinkingEnabled !== null
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Share Linking Enabled</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="savingsAccountProductTemplate?.shareLinkingEnabled"
|
||||
>
|
||||
{{ savingsAccountProductTemplate?.shareLinkingEnabled | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Introducer Required</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="savingsAccountProductTemplate?.introducerRequired"
|
||||
>
|
||||
{{ savingsAccountProductTemplate?.introducerRequired | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Share Linking Enabled</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="savingsAccountProductTemplate?.shareLinkingEnabled"
|
||||
>
|
||||
{{ savingsAccountProductTemplate?.shareLinkingEnabled | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<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">{{ savingsAccountTermsForm?.get('currencyCode')?.value }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Decimal Places' | translate }}</span>
|
||||
<span class="preview-value">{{ savingsAccountProductTemplate?.currency?.decimalPlaces }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Currency In Multiples Of' | translate }}</span>
|
||||
<span class="preview-value">{{ savingsAccountProductTemplate?.currency?.inMultiplesOf }}</span>
|
||||
</div>
|
||||
|
||||
@if (
|
||||
savingsAccountProductTemplate?.dormancyAlert !== undefined &&
|
||||
savingsAccountProductTemplate?.dormancyAlert !== null
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Dormancy Alert</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="savingsAccountProductTemplate?.dormancyAlert"
|
||||
>
|
||||
{{ savingsAccountProductTemplate?.dormancyAlert | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (
|
||||
savingsAccountProductTemplate?.balanceSwapOption !== undefined &&
|
||||
savingsAccountProductTemplate?.balanceSwapOption !== null
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Balance Swap Option</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="savingsAccountProductTemplate?.balanceSwapOption"
|
||||
>
|
||||
{{ savingsAccountProductTemplate?.balanceSwapOption | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (savingsAccountProductTemplate?.msme !== undefined && savingsAccountProductTemplate?.msme !== null) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">MSME</span>
|
||||
<span class="preview-value preview-badge" [class.preview-badge--yes]="savingsAccountProductTemplate?.msme">
|
||||
{{ savingsAccountProductTemplate?.msme | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (
|
||||
savingsAccountProductTemplate?.nominationFacility !== undefined &&
|
||||
savingsAccountProductTemplate?.nominationFacility !== null
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Nomination Facility</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="savingsAccountProductTemplate?.nominationFacility"
|
||||
>
|
||||
{{ savingsAccountProductTemplate?.nominationFacility | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (
|
||||
savingsAccountProductTemplate?.autoDebitFacility !== undefined &&
|
||||
savingsAccountProductTemplate?.autoDebitFacility !== null
|
||||
) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Auto Debit Facility</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="savingsAccountProductTemplate?.autoDebitFacility"
|
||||
>
|
||||
{{ savingsAccountProductTemplate?.autoDebitFacility | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<span class="preview-section__title">{{ 'labels.heading.Terms' | translate }}</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Nominal Annual Interest' | translate }}</span>
|
||||
<span class="preview-value">{{ savingsAccount?.nominalAnnualInterestRate | formatNumber }} %</span>
|
||||
</div>
|
||||
@if (savingsAccountProductTemplate?.interestCalculationTypeName) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Interest Calculation Mode</span>
|
||||
<span class="preview-value">{{ savingsAccountProductTemplate?.interestCalculationTypeName }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Interest Compounding Period' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
savingsAccount?.interestCompoundingPeriodType
|
||||
| find: savingsAccountProductTemplate?.interestCompoundingPeriodTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Interest Posting Period' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
savingsAccount?.interestPostingPeriodType
|
||||
| find: savingsAccountProductTemplate?.interestPostingPeriodTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Interest Calculated using' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
savingsAccount?.interestCalculationType
|
||||
| find: savingsAccountProductTemplate?.interestCalculationTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Days in Year' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
savingsAccount?.interestCalculationDaysInYearType
|
||||
| find: savingsAccountProductTemplate?.interestCalculationDaysInYearTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Interest Calculation Selector</span>
|
||||
<span class="preview-value">{{
|
||||
savingsAccount?.interestCalculateSelector
|
||||
| find: savingsAccountProductTemplate?.interestCalculationTypeOption : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span>
|
||||
</div>
|
||||
@if (savingsAccount?.averagePeriod) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Average Period</span>
|
||||
<span class="preview-value">{{ savingsAccount?.averagePeriod }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Auto Post</span>
|
||||
<span class="preview-value preview-badge" [class.preview-badge--yes]="savingsAccount?.autoPost">
|
||||
{{ savingsAccount?.autoPost | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
@if (savingsAccountProductTemplate?.lastInterestDate) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Last Interest Date</span>
|
||||
<span class="preview-value">{{ savingsAccountProductTemplate?.lastInterestDate | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (savingsAccount?.frequencyFixDay !== undefined && savingsAccount?.frequencyFixDay !== null) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Frequency Fix Day</span>
|
||||
<span class="preview-value">{{ savingsAccount?.frequencyFixDay }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<span class="preview-section__title">{{ 'labels.heading.Settings' | translate }}</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
@if (savingsAccount?.minRequiredOpeningBalance) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Minimum Opening Balance' | translate }}</span>
|
||||
<span class="preview-value">{{ savingsAccount?.minRequiredOpeningBalance | formatNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Apply Withdrawal Fee for Transfers' | translate }}</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="savingsAccount?.withdrawalFeeForTransfers"
|
||||
>
|
||||
{{ savingsAccount?.withdrawalFeeForTransfers | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
@if (savingsAccount?.lockinPeriodFrequency) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Lock-in Period' | translate }}</span>
|
||||
<span class="preview-value">
|
||||
{{ savingsAccount?.lockinPeriodFrequency }}
|
||||
{{
|
||||
savingsAccount?.lockinPeriodFrequencyType
|
||||
| find: savingsAccountProductTemplate?.lockinPeriodFrequencyTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Is Overdraft Allowed' | translate }}</span>
|
||||
<span class="preview-value preview-badge" [class.preview-badge--yes]="savingsAccount?.allowOverdraft">
|
||||
{{ savingsAccount?.allowOverdraft | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
@if (savingsAccount?.minOverdraftForInterestCalculation) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{
|
||||
'labels.inputs.Minimum Overdraft Required for Interest Calculation' | translate
|
||||
}}</span>
|
||||
<span class="preview-value">{{ savingsAccount?.minOverdraftForInterestCalculation | formatNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (savingsAccount?.nominalAnnualInterestRateOverdraft) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Nominal Annual Interest for Overdraft' | translate }}</span>
|
||||
<span class="preview-value">{{ savingsAccount?.nominalAnnualInterestRateOverdraft | formatNumber }} %</span>
|
||||
</div>
|
||||
}
|
||||
@if (savingsAccount?.overdraftLimit) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Maximum Overdraft Amount Limit' | translate }}</span>
|
||||
<span class="preview-value">{{ savingsAccount?.overdraftLimit | formatNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Enforce Minimum Balance' | translate }}</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="savingsAccount?.enforceMinRequiredBalance"
|
||||
>
|
||||
{{ savingsAccount?.enforceMinRequiredBalance | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
@if (savingsAccountProductTemplate?.enableLockinPeriod !== undefined) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Enable Lock-in Period</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="savingsAccountProductTemplate?.enableLockinPeriod"
|
||||
>
|
||||
{{ savingsAccountProductTemplate?.enableLockinPeriod | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@if (savingsAccountProductTemplate?.balanceFrequency) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Balance Frequency</span>
|
||||
<span class="preview-value">{{ savingsAccountProductTemplate?.balanceFrequency }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (savingsAccountProductTemplate?.accountClosedZeroBalanceInDayClosing !== undefined) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Account Closed Zero Balance In Day Closing</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="savingsAccountProductTemplate?.accountClosedZeroBalanceInDayClosing"
|
||||
>
|
||||
{{ savingsAccountProductTemplate?.accountClosedZeroBalanceInDayClosing | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@if (savingsAccount?.minRequiredBalance) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Minimum Balance' | translate }}</span>
|
||||
<span class="preview-value">{{ savingsAccount?.minRequiredBalance }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (hasDigitalSection) {
|
||||
<!-- ══ 3. Introducer Details (if selected) ══════════════════════ -->
|
||||
@if (savingsAccount?.introducerId) {
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<span class="preview-section__title">Digital</span>
|
||||
<i class="fas fa-user-tie preview-section__icon"></i>
|
||||
<span class="preview-section__title">Introducer Details</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
@for (facility of digitalFacilityLabels; track facility.key) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Name</span>
|
||||
<span class="preview-value">{{ savingsAccount?.introducerName || '—' }}</span>
|
||||
</div>
|
||||
@if (savingsAccount?.introducerCif) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ facility.label }}</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="digitalPreview?.accountFacilityConfig?.[facility.key]"
|
||||
>
|
||||
{{ digitalPreview?.accountFacilityConfig?.[facility.key] | yesNo }}
|
||||
</span>
|
||||
<span class="preview-label">CIF No</span>
|
||||
<span class="preview-value preview-mono">{{ savingsAccount.introducerCif }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Client ID</span>
|
||||
<span class="preview-value preview-mono">{{ savingsAccount.introducerId }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (digitalPreview?.accountFacilityLimits?.length) {
|
||||
<table class="mat-elevation-z1 preview-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Limit Type</th>
|
||||
<th>General Limit</th>
|
||||
<th>Minor Account Limit</th>
|
||||
<th>Small Saving Limit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (row of digitalPreview.accountFacilityLimits; track row.limitType) {
|
||||
<tr>
|
||||
<td>{{ row.limitType }}</td>
|
||||
<td>{{ row.generalLimit || 'Unassigned' }}</td>
|
||||
<td>{{ row.minorAccountLimit || 'Unassigned' }}</td>
|
||||
<td>{{ row.smallSavingLimit || 'Unassigned' }}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@if (digitalPreview?.depositLimitKyc?.general) {
|
||||
<table class="mat-elevation-z1 preview-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account Type</th>
|
||||
<th>KYC Type</th>
|
||||
<th>Limit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>General</td>
|
||||
<td>{{ digitalPreview.depositLimitKyc.general.kycType || 'Unassigned' }}</td>
|
||||
<td>{{ digitalPreview.depositLimitKyc.general.limit || 'Unassigned' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Minor</td>
|
||||
<td>{{ digitalPreview.depositLimitKyc.minor?.kycType || 'Unassigned' }}</td>
|
||||
<td>{{ digitalPreview.depositLimitKyc.minor?.limit || 'Unassigned' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Small Saving</td>
|
||||
<td>{{ digitalPreview.depositLimitKyc.smallSaving?.kycType || 'Unassigned' }}</td>
|
||||
<td>{{ digitalPreview.depositLimitKyc.smallSaving?.limit || 'Unassigned' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ══ 4. Currency & Account Flags ══════════════════════════════ -->
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-coins preview-section__icon"></i>
|
||||
<span class="preview-section__title">Currency & Account Flags</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Currency</span>
|
||||
<span class="preview-value">{{ currencyName }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Decimal Places</span>
|
||||
<span class="preview-value">{{ decimalPlaces }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Currency In Multiples Of</span>
|
||||
<span class="preview-value">{{ inMultiplesOf }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Dormancy Alert</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="savingsAccountProductTemplate?.dormancyAlert"
|
||||
>
|
||||
{{ savingsAccountProductTemplate?.dormancyAlert | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Nomination Facility</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="savingsAccountProductTemplate?.nominationFacility"
|
||||
>
|
||||
{{ savingsAccountProductTemplate?.nominationFacility | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ 5. Dormancy Monitoring ════════════════════════════════════ -->
|
||||
@if (
|
||||
savingsAccountProductTemplate?.dormancyAlert || savingsAccountProductTemplate?.isDormancyTrackingActiveFromProduct
|
||||
) {
|
||||
<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"
|
||||
[class.preview-section__badge--active]="savingsAccountProductTemplate?.isDormancyTrackingActiveFromProduct"
|
||||
>
|
||||
{{ savingsAccountProductTemplate?.isDormancyTrackingActiveFromProduct ? 'Active' : 'Inactive' }}
|
||||
</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">
|
||||
@if (savingsAccountProductTemplate?.daysToInactiveFromProduct != null) {
|
||||
After {{ savingsAccountProductTemplate.daysToInactiveFromProduct }} days of no transaction
|
||||
} @else {
|
||||
Not configured
|
||||
}
|
||||
</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">
|
||||
@if (savingsAccountProductTemplate?.daysToDormancyFromProduct != null) {
|
||||
{{ savingsAccountProductTemplate.daysToDormancyFromProduct }} days after inactive
|
||||
} @else {
|
||||
Not configured
|
||||
}
|
||||
</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">
|
||||
@if (savingsAccountProductTemplate?.daysToEscheatFromProduct != null) {
|
||||
{{ savingsAccountProductTemplate.daysToEscheatFromProduct }} days after dormant
|
||||
} @else {
|
||||
Not configured
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-dorm-flags">
|
||||
<span class="preview-dorm-flag" [class.preview-dorm-flag--on]="savingsAccountProductTemplate?.dormancyAlert">
|
||||
<i class="fas fa-bell"></i> Alert
|
||||
{{ savingsAccountProductTemplate?.dormancyAlert ? 'Enabled' : 'Disabled' }}
|
||||
</span>
|
||||
<span
|
||||
class="preview-dorm-flag"
|
||||
[class.preview-dorm-flag--on]="savingsAccountProductTemplate?.isDormancyTrackingActiveFromProduct"
|
||||
>
|
||||
<i class="fas fa-eye"></i> Tracking
|
||||
{{ savingsAccountProductTemplate?.isDormancyTrackingActiveFromProduct ? 'Active' : 'Inactive' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ══ 7. Nominee Details (if filled) ═══════════════════════════ -->
|
||||
@if (savingsAccountNominee) {
|
||||
<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">
|
||||
{{ savingsAccountNominee?.mode === 'linked' ? 'Linked Client' : 'Manual Entry' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<!-- Name: fullName for manual, displayName for linked -->
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Name</span>
|
||||
<span class="preview-value">{{
|
||||
savingsAccountNominee?.fullName || savingsAccountNominee?.displayName || '—'
|
||||
}}</span>
|
||||
</div>
|
||||
@if (savingsAccountNominee?.cifNumber) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">CIF No</span>
|
||||
<span class="preview-value preview-mono">{{ savingsAccountNominee.cifNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Relationship</span>
|
||||
<span class="preview-value">{{ savingsAccountNominee?.relationship || '—' }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Share %</span>
|
||||
<span class="preview-value">{{ savingsAccountNominee?.nomineeShare ?? '—' }}%</span>
|
||||
</div>
|
||||
@if (savingsAccountNominee?.dateOfBirth) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Date of Birth</span>
|
||||
<span class="preview-value">{{ savingsAccountNominee?.dateOfBirth | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (savingsAccountNominee?.nationality) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Nationality</span>
|
||||
<span class="preview-value">{{ savingsAccountNominee?.nationality }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (savingsAccountNominee?.mobileNo) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Mobile No</span>
|
||||
<span class="preview-value preview-mono">{{ savingsAccountNominee?.mobileNo }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (savingsAccountNominee?.aadhaarNumber) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Aadhaar No</span>
|
||||
<span class="preview-value preview-mono">{{ savingsAccountNominee?.aadhaarNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (savingsAccountNominee?.panNumber) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">PAN No</span>
|
||||
<span class="preview-value preview-mono">{{ savingsAccountNominee?.panNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (savingsAccountNominee?.isDependent) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Dependent</span>
|
||||
<span class="preview-value preview-badge preview-badge--warn">Yes</span>
|
||||
</div>
|
||||
}
|
||||
@if (savingsAccountNominee?.guardianName) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Guardian Name</span>
|
||||
<span class="preview-value">{{ savingsAccountNominee?.guardianName }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (savingsAccountNominee?.guardianRelationship) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Guardian Relationship</span>
|
||||
<span class="preview-value">{{ savingsAccountNominee?.guardianRelationship }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (savingsAccountNominee?.remarks) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Remarks</span>
|
||||
<span class="preview-value">{{ savingsAccountNominee?.remarks }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ══ 5. Digital Facilities ════════════════════════════════════ -->
|
||||
@if (hasDigitalSection) {
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-mobile-alt preview-section__icon"></i>
|
||||
<span class="preview-section__title">Digital Facilities</span>
|
||||
<span class="preview-section__badge preview-section__badge--digital">User Configured</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-digital-grid">
|
||||
@for (facility of digitalFacilityLabels; track facility.key) {
|
||||
<div class="preview-digital-card" [class.preview-digital-card--on]="digitalFacilityConfig?.[facility.key]">
|
||||
<span class="preview-digital-label">{{ facility.label }}</span>
|
||||
<span
|
||||
class="preview-digital-status"
|
||||
[class.preview-digital-status--on]="digitalFacilityConfig?.[facility.key]"
|
||||
>
|
||||
{{ digitalFacilityConfig?.[facility.key] ? 'Enabled' : 'Disabled' }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ══ 6. Client Members (GSIM) ═════════════════════════════════ -->
|
||||
@if (activeClientMembers) {
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-users preview-section__icon"></i>
|
||||
<span class="preview-section__title">{{ 'labels.heading.Client Members' | translate }}</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
@@ -501,65 +409,6 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (savingsAccount?.charges?.length) {
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<span class="preview-section__title">{{ 'labels.inputs.Charges' | translate }}</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<table class="mat-elevation-z1 preview-table" mat-table [dataSource]="savingsAccount.charges">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.name + ', ' + charge.currency.displaySymbol }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="chargeCalculationType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.chargeCalculationType.value | translateKey: 'catalogs' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.amount | formatNumber }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="date">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Date' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
@if (
|
||||
charge.chargeTimeType.value === 'Specified due date' || charge.chargeTimeType.value === 'Weekly Fee'
|
||||
) {
|
||||
<span>{{ (charge.dueDate | dateFormat) || 'Unassigned' }}</span>
|
||||
}
|
||||
@if (charge.chargeTimeType.value === 'Monthly Fee' || charge.chargeTimeType.value === 'Annual Fee') {
|
||||
<span>{{ (charge.feeOnMonthDay | dateFormat) || 'Unassigned' }}</span>
|
||||
}
|
||||
@if (
|
||||
!(
|
||||
charge.chargeTimeType.value === 'Monthly Fee' ||
|
||||
charge.chargeTimeType.value === 'Annual Fee' ||
|
||||
charge.chargeTimeType.value === 'Specified due date' ||
|
||||
charge.chargeTimeType.value === 'Weekly Fee'
|
||||
)
|
||||
) {
|
||||
<span>N/A</span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="repaymentsEvery">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Repayments Every' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.feeInterval || 'Not Provided' }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="chargeTimeType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.chargeTimeType.value }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="chargesDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: chargesDisplayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="layout-row responsive-column align-center gap-2px margin-t">
|
||||
|
||||
+247
-24
@@ -9,29 +9,59 @@
|
||||
.preview-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
gap: 1rem;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.preview-section {
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 3px rgb(0 0 0 / 6%);
|
||||
box-shadow: 0 1px 4px rgb(0 0 0 / 5%);
|
||||
|
||||
&__header {
|
||||
background: #f5f5f5;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
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 {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
letter-spacing: 0.06em;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__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;
|
||||
|
||||
&--digital {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
border-color: #fde68a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +71,7 @@
|
||||
|
||||
.preview-grid {
|
||||
display: grid;
|
||||
gap: 0.75rem 1.5rem;
|
||||
gap: 0.85rem 1.5rem;
|
||||
|
||||
&--three {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
@@ -59,36 +89,229 @@
|
||||
.preview-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.preview-label {
|
||||
font-size: 0.72rem;
|
||||
color: #888;
|
||||
font-weight: 500;
|
||||
font-size: 0.68rem;
|
||||
color: #94a3b8;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.preview-value {
|
||||
font-size: 0.92rem;
|
||||
color: #222;
|
||||
font-size: 0.9rem;
|
||||
color: #1e293b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.preview-muted {
|
||||
color: #94a3b8;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.preview-mono {
|
||||
font-family: monospace;
|
||||
font-size: 0.88rem;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.preview-badge {
|
||||
display: inline-block;
|
||||
padding: 0.1rem 0.55rem;
|
||||
padding: 0.15rem 0.6rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.78rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
background: #ffebee;
|
||||
color: #c62828;
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
width: fit-content;
|
||||
|
||||
&--yes {
|
||||
background: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
background: #f0fdf4;
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
&--warn {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
}
|
||||
|
||||
// Digital facilities grid
|
||||
.preview-digital-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 8px;
|
||||
|
||||
@media (width <= 900px) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.preview-digital-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 10px 12px;
|
||||
border: 1.5px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
transition: all 0.12s;
|
||||
|
||||
&--on {
|
||||
border-color: #86efac;
|
||||
background: #f0fdf4;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-digital-label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.preview-digital-status {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: #dc2626;
|
||||
|
||||
&--on {
|
||||
color: #15803d;
|
||||
}
|
||||
}
|
||||
|
||||
// Section badge variants
|
||||
.preview-section__badge--active {
|
||||
background: #dcfce7 !important;
|
||||
color: #15803d !important;
|
||||
border-color: #bbf7d0 !important;
|
||||
}
|
||||
|
||||
// ── Dormancy Monitoring ───────────────────────────────────────────
|
||||
.preview-dormancy-timeline {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.preview-dorm-step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
|
||||
&__dot {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
&--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;
|
||||
}
|
||||
|
||||
.preview-dorm-flags {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.preview-dorm-flag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 11.5px;
|
||||
font-weight: 600;
|
||||
background: #f1f5f9;
|
||||
color: #94a3b8;
|
||||
border: 1px solid #e2e8f0;
|
||||
|
||||
i {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
&--on {
|
||||
background: #f0fdf4;
|
||||
color: #15803d;
|
||||
border-color: #bbf7d0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+28
-6
@@ -88,6 +88,10 @@ export class SavingsAccountPreviewStepComponent implements OnChanges {
|
||||
@Input() savingsAccountTermsForm: any;
|
||||
/** Savings Account */
|
||||
@Input() savingsAccount: any;
|
||||
/** Digital facilities — user-modified values from digital step */
|
||||
@Input() savingsAccountDigital: any;
|
||||
/** Nominee data from currency step */
|
||||
@Input() savingsAccountNominee: any;
|
||||
/** active Client Members in case of GSIM Account */
|
||||
@Input() activeClientMembers?: any;
|
||||
|
||||
@@ -118,15 +122,33 @@ export class SavingsAccountPreviewStepComponent implements OnChanges {
|
||||
}
|
||||
}
|
||||
|
||||
get digitalPreview(): any {
|
||||
return this.savingsAccountProductTemplate || {};
|
||||
/** Returns user-modified digital config (from digital step), falling back to product template */
|
||||
get digitalFacilityConfig(): any {
|
||||
return (
|
||||
this.savingsAccountDigital?.accountFacilityConfig ??
|
||||
this.savingsAccountProductTemplate?.accountFacilityConfig ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
get hasDigitalSection(): boolean {
|
||||
return !!(
|
||||
this.digitalPreview?.accountFacilityConfig ||
|
||||
this.digitalPreview?.accountFacilityLimits?.length ||
|
||||
this.digitalPreview?.depositLimitKyc
|
||||
return !!this.digitalFacilityConfig;
|
||||
}
|
||||
|
||||
/** Currency info from product template */
|
||||
get currencyName(): string {
|
||||
return (
|
||||
this.savingsAccountProductTemplate?.currency?.name ||
|
||||
this.savingsAccountTermsForm?.get?.('currencyCode')?.value ||
|
||||
'—'
|
||||
);
|
||||
}
|
||||
|
||||
get decimalPlaces(): string | number {
|
||||
return this.savingsAccountProductTemplate?.currency?.decimalPlaces ?? '—';
|
||||
}
|
||||
|
||||
get inMultiplesOf(): string | number {
|
||||
return this.savingsAccountProductTemplate?.currency?.inMultiplesOf ?? '—';
|
||||
}
|
||||
}
|
||||
|
||||
+1
-7
@@ -144,13 +144,7 @@
|
||||
|
||||
<mat-form-field appearance="outline" class="sat-field" (click)="lastInterestDatePicker.open()">
|
||||
<mat-label>Last Interest Date</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[matDatepicker]="lastInterestDatePicker"
|
||||
formControlName="lastInterestDate"
|
||||
[min]="minDate"
|
||||
[max]="maxDate"
|
||||
/>
|
||||
<input matInput [matDatepicker]="lastInterestDatePicker" formControlName="lastInterestDate" />
|
||||
<mat-datepicker-toggle matSuffix [for]="lastInterestDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #lastInterestDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
<!--
|
||||
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/.
|
||||
-->
|
||||
|
||||
@if (shouldShow) {
|
||||
<div class="dap" [class]="'dap--' + risk">
|
||||
<!-- ══ HEADER ════════════════════════════════════════════════════ -->
|
||||
<div class="dap__header">
|
||||
<div class="dap__header-left">
|
||||
<div class="dap__icon-wrap">
|
||||
<i [class]="isProductMode ? 'fas fa-shield-alt' : 'fas fa-exclamation-triangle'"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="dap__title">
|
||||
{{ isProductMode ? 'Dormancy Monitoring Configuration' : 'Dormancy Risk Monitor' }}
|
||||
</div>
|
||||
<div class="dap__subtitle">
|
||||
@if (isProductMode) {
|
||||
Product-level dormancy thresholds applied to this account
|
||||
} @else {
|
||||
Real-time dormancy tracking · Based on last customer transaction
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Risk severity badge -->
|
||||
<div class="dap__badges">
|
||||
<span
|
||||
class="dap__risk-badge"
|
||||
[class]="'dap__risk-badge--' + risk"
|
||||
title="Dormancy risk level based on transaction inactivity"
|
||||
>
|
||||
<span class="dap__risk-dot"></span>
|
||||
@if (isProductMode) {
|
||||
Monitoring: {{ riskSeverity }}
|
||||
} @else {
|
||||
Dormancy Risk: {{ riskSeverity }}
|
||||
}
|
||||
</span>
|
||||
@if (!isProductMode && isMonitoringActive) {
|
||||
<span class="dap__monitor-badge"> <i class="fas fa-satellite-dish"></i> Monitoring Active </span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ METRIC CARDS ════════════════════════════════════════════ -->
|
||||
<div class="dap__cards" [class.dap__cards--product]="isProductMode">
|
||||
@if (isProductMode) {
|
||||
<!-- ── PRODUCT MODE CARDS ─────────────────────────────────── -->
|
||||
|
||||
<!-- Account Status -->
|
||||
<div class="dap__card">
|
||||
<span class="dap__card-label">Account Status</span>
|
||||
<span class="dap__pill dap__pill--green"> <i class="fas fa-check-circle"></i> New Account </span>
|
||||
<span class="dap__card-hint">No transactions yet</span>
|
||||
</div>
|
||||
|
||||
<!-- Monitoring Status -->
|
||||
<div class="dap__card">
|
||||
<span class="dap__card-label">
|
||||
Dormancy Monitoring
|
||||
<i
|
||||
class="fas fa-info-circle dap__help"
|
||||
title="When enabled, the system tracks transaction activity and alerts on inactivity"
|
||||
></i>
|
||||
</span>
|
||||
<span class="dap__pill" [class]="isMonitoringActive ? 'dap__pill--green' : 'dap__pill--gray'">
|
||||
<i [class]="isMonitoringActive ? 'fas fa-satellite-dish' : 'fas fa-ban'"></i>
|
||||
{{ isMonitoringActive ? 'Enabled' : 'Disabled' }}
|
||||
</span>
|
||||
<span class="dap__card-hint">{{
|
||||
isMonitoringActive ? 'Transaction activity tracked' : 'Not configured'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<!-- Alert Notification -->
|
||||
<div class="dap__card">
|
||||
<span class="dap__card-label">
|
||||
Alert Notifications
|
||||
<i
|
||||
class="fas fa-info-circle dap__help"
|
||||
title="Sends alerts to branch officers when account approaches dormancy threshold"
|
||||
></i>
|
||||
</span>
|
||||
<span class="dap__pill" [class]="isAlertEnabled ? 'dap__pill--green' : 'dap__pill--gray'">
|
||||
<i [class]="isAlertEnabled ? 'fas fa-bell' : 'fas fa-bell-slash'"></i>
|
||||
{{ isAlertEnabled ? 'Enabled' : 'Disabled' }}
|
||||
</span>
|
||||
<span class="dap__card-hint">{{ isAlertEnabled ? 'Branch will be notified' : 'No alerts configured' }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Inactive Threshold -->
|
||||
<div class="dap__card">
|
||||
<span class="dap__card-label">
|
||||
Inactive Threshold
|
||||
<i
|
||||
class="fas fa-info-circle dap__help"
|
||||
title="Account is flagged inactive after this many days of no customer-initiated transaction"
|
||||
></i>
|
||||
</span>
|
||||
@if (daysToInactive !== null) {
|
||||
<span class="dap__number"> {{ daysToInactive }}<small>days</small> </span>
|
||||
<span class="dap__card-hint">No transaction for {{ daysToInactive }} days</span>
|
||||
} @else {
|
||||
<span class="dap__na">Not configured</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Dormancy Threshold -->
|
||||
<div class="dap__card">
|
||||
<span class="dap__card-label">
|
||||
Dormancy Threshold
|
||||
<i
|
||||
class="fas fa-info-circle dap__help"
|
||||
title="Account is classified dormant after this many additional days following inactive status"
|
||||
></i>
|
||||
</span>
|
||||
@if (daysToDormancy !== null) {
|
||||
<span class="dap__number">{{ daysToDormancy }}<small>days</small></span>
|
||||
<span class="dap__card-hint">{{ daysToDormancy }} days after inactive</span>
|
||||
} @else {
|
||||
<span class="dap__na">Not configured</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Escheat Threshold -->
|
||||
<div class="dap__card">
|
||||
<span class="dap__card-label">
|
||||
Escheat Threshold
|
||||
<i
|
||||
class="fas fa-info-circle dap__help"
|
||||
title="Unclaimed funds transferred to government/regulator after this many days of dormancy"
|
||||
></i>
|
||||
</span>
|
||||
@if (daysToEscheat !== null) {
|
||||
<span class="dap__number">{{ daysToEscheat }}<small>days</small></span>
|
||||
<span class="dap__card-hint">{{ daysToEscheat }} days after dormant</span>
|
||||
} @else {
|
||||
<span class="dap__na">Not configured</span>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<!-- ── ACCOUNT VIEW MODE CARDS ────────────────────────────── -->
|
||||
|
||||
<!-- Account Status -->
|
||||
<div class="dap__card">
|
||||
<span class="dap__card-label">Account Status</span>
|
||||
<span class="dap__pill" [class]="'dap__pill--' + risk">
|
||||
<i class="fas fa-circle"></i> {{ statusLabel }}
|
||||
</span>
|
||||
<span class="dap__card-hint">
|
||||
@if (statusId === 0) {
|
||||
Currently active
|
||||
}
|
||||
@if (statusId === 100) {
|
||||
No recent transactions
|
||||
}
|
||||
@if (statusId === 200) {
|
||||
Classified as dormant
|
||||
}
|
||||
@if (statusId === 300) {
|
||||
Pending regulatory transfer
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Last Transaction -->
|
||||
<div class="dap__card">
|
||||
<span class="dap__card-label">Last Transaction</span>
|
||||
@if (lastTxnDate) {
|
||||
<span class="dap__date"><i class="fas fa-calendar-check"></i> {{ lastTxnDate }}</span>
|
||||
<span class="dap__card-hint">Most recent customer activity</span>
|
||||
} @else if (accountOpenedDate) {
|
||||
<span class="dap__date dap__date--muted"><i class="fas fa-calendar-plus"></i> {{ accountOpenedDate }}</span>
|
||||
<span class="dap__card-hint">Account opened · No transactions yet</span>
|
||||
} @else {
|
||||
<span class="dap__na">No transactions yet</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Days to Inactive -->
|
||||
<div class="dap__card">
|
||||
<span class="dap__card-label">
|
||||
Days to Inactive
|
||||
<i
|
||||
class="fas fa-info-circle dap__help"
|
||||
title="Account will be flagged inactive after no customer-initiated transaction for this duration"
|
||||
></i>
|
||||
</span>
|
||||
@if (statusId >= 100) {
|
||||
<span class="dap__reached"><i class="fas fa-flag"></i> Threshold Reached</span>
|
||||
<span class="dap__card-hint">Account is inactive</span>
|
||||
} @else if (daysToInactive !== null) {
|
||||
<span class="dap__number" [class.dap__number--warn]="daysToInactive <= 30">
|
||||
{{ daysToInactive }}<small>days</small>
|
||||
</span>
|
||||
<span class="dap__card-hint">{{ inactiveSubtext() }}</span>
|
||||
} @else {
|
||||
<span class="dap__na">—</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Days to Dormant -->
|
||||
<div class="dap__card">
|
||||
<span class="dap__card-label">
|
||||
Days to Dormant
|
||||
<i
|
||||
class="fas fa-info-circle dap__help"
|
||||
title="Dormant classification per RBI guidelines after prolonged inactivity"
|
||||
></i>
|
||||
</span>
|
||||
@if (statusId >= 200) {
|
||||
<span class="dap__reached"><i class="fas fa-flag"></i> Threshold Reached</span>
|
||||
<span class="dap__card-hint">Account is dormant</span>
|
||||
} @else if (daysToDormancy !== null) {
|
||||
<span class="dap__number" [class.dap__number--warn]="daysToDormancy <= 30">
|
||||
{{ daysToDormancy }}<small>days</small>
|
||||
</span>
|
||||
<span class="dap__card-hint">{{ dormancySubtext() }}</span>
|
||||
} @else {
|
||||
<span class="dap__na">—</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Days to Escheat -->
|
||||
<div class="dap__card">
|
||||
<span class="dap__card-label">
|
||||
Days to Escheat
|
||||
<i
|
||||
class="fas fa-info-circle dap__help"
|
||||
title="Unclaimed funds transferred to government if account remains dormant beyond this period"
|
||||
></i>
|
||||
</span>
|
||||
@if (statusId >= 300) {
|
||||
<span class="dap__reached"><i class="fas fa-flag"></i> Threshold Reached</span>
|
||||
<span class="dap__card-hint">Pending regulatory transfer</span>
|
||||
} @else if (daysToEscheat !== null) {
|
||||
<span class="dap__number" [class.dap__number--warn]="daysToEscheat <= 30">
|
||||
{{ daysToEscheat }}<small>days</small>
|
||||
</span>
|
||||
<span class="dap__card-hint">{{ escheatSubtext() }}</span>
|
||||
} @else {
|
||||
<span class="dap__na">—</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- ══ DORMANCY PROGRESSION TIMELINE ══════════════════════════ -->
|
||||
<div class="dap__timeline">
|
||||
<div class="dap__tl-header">
|
||||
<span class="dap__tl-label">Dormancy Progression</span>
|
||||
@if (!isProductMode) {
|
||||
<span class="dap__tl-note">Timeline based on last customer transaction</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="dap__tl-track">
|
||||
<!-- Active node (always reached) -->
|
||||
<div class="dap__tl-node" [class.dap__tl-node--current]="statusId === 0" [class.dap__tl-node--reached]="true">
|
||||
<div class="dap__tl-circle dap__tl-circle--done">
|
||||
<i class="fas fa-check"></i>
|
||||
</div>
|
||||
<span class="dap__tl-name">Active</span>
|
||||
<span class="dap__tl-sub">Normal</span>
|
||||
</div>
|
||||
|
||||
@for (step of timelineSteps; track step.label) {
|
||||
<div class="dap__tl-line" [class.dap__tl-line--done]="step.reached"></div>
|
||||
<div
|
||||
class="dap__tl-node"
|
||||
[class.dap__tl-node--current]="step.current"
|
||||
[class.dap__tl-node--reached]="step.reached"
|
||||
>
|
||||
<div
|
||||
class="dap__tl-circle"
|
||||
[class.dap__tl-circle--done]="step.reached && !step.current"
|
||||
[class.dap__tl-circle--current]="step.current"
|
||||
[class.dap__tl-circle--pending]="!step.reached"
|
||||
>
|
||||
@if (step.reached && !step.current) {
|
||||
<i class="fas fa-check"></i>
|
||||
} @else if (step.current) {
|
||||
<i class="fas fa-circle"></i>
|
||||
} @else {
|
||||
<i class="far fa-circle"></i>
|
||||
}
|
||||
</div>
|
||||
<span class="dap__tl-name" [title]="step.tooltip">
|
||||
{{ step.label }}
|
||||
<i class="fas fa-info-circle dap__tl-info"></i>
|
||||
</span>
|
||||
@if (!step.reached && step.days !== null) {
|
||||
<span class="dap__tl-days">{{ step.days }}d</span>
|
||||
} @else if (step.reached) {
|
||||
<span class="dap__tl-reached-tag">Reached</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,684 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
@use 'sass:color';
|
||||
|
||||
// ── Design Tokens ──────────────────────────────────────────────────────────
|
||||
// Risk colours
|
||||
$c-safe: #16a34a;
|
||||
$bg-safe: #f0fdf4;
|
||||
$bd-safe: #bbf7d0;
|
||||
$c-low: #0284c7;
|
||||
$bg-low: #f0f9ff;
|
||||
$bd-low: #bae6fd;
|
||||
$c-medium: #d97706;
|
||||
$bg-medium: #fffbeb;
|
||||
$bd-medium: #fde68a;
|
||||
$c-high: #dc2626;
|
||||
$bg-high: #fff5f5;
|
||||
$bd-high: #fecaca;
|
||||
$c-critical: #b91c1c;
|
||||
$bg-critical: #fef2f2;
|
||||
$bd-critical: #fca5a5;
|
||||
$c-escheat: #7c3aed;
|
||||
$bg-escheat: #f5f3ff;
|
||||
$bd-escheat: #ddd6fe;
|
||||
|
||||
// Neutral
|
||||
$text: #1e293b;
|
||||
$muted: #64748b;
|
||||
$subtle: #94a3b8;
|
||||
$border: #e2e8f0;
|
||||
$surface: #f8fafc;
|
||||
|
||||
// ── Host ───────────────────────────────────────────────────────────────────
|
||||
:host {
|
||||
display: block;
|
||||
margin: 14px 0 18px;
|
||||
}
|
||||
|
||||
// ── Root wrapper ───────────────────────────────────────────────────────────
|
||||
.dap {
|
||||
border-radius: 14px;
|
||||
border: 1.5px solid $border;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 4px rgb(0 0 0 / 6%);
|
||||
transition: box-shadow 0.2s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 3px 12px rgb(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
// Risk-tinted border
|
||||
&--safe {
|
||||
border-color: $bd-safe;
|
||||
}
|
||||
|
||||
&--low {
|
||||
border-color: $bd-low;
|
||||
}
|
||||
|
||||
&--medium {
|
||||
border-color: $bd-medium;
|
||||
}
|
||||
|
||||
&--high {
|
||||
border-color: $bd-high;
|
||||
}
|
||||
|
||||
&--critical {
|
||||
border-color: $bd-critical;
|
||||
}
|
||||
|
||||
&--escheat {
|
||||
border-color: $bd-escheat;
|
||||
}
|
||||
|
||||
// ── HEADER ───────────────────────────────────────────────────────
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
padding: 14px 18px;
|
||||
background: $surface;
|
||||
border-bottom: 1px solid $border;
|
||||
}
|
||||
|
||||
&__header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
&__icon-wrap {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
border: 1px solid $border;
|
||||
|
||||
i {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.dap--safe & {
|
||||
background: $bg-safe;
|
||||
border-color: $bd-safe;
|
||||
|
||||
i {
|
||||
color: $c-safe;
|
||||
}
|
||||
}
|
||||
|
||||
.dap--low & {
|
||||
background: $bg-low;
|
||||
border-color: $bd-low;
|
||||
|
||||
i {
|
||||
color: $c-low;
|
||||
}
|
||||
}
|
||||
|
||||
.dap--medium & {
|
||||
background: $bg-medium;
|
||||
border-color: $bd-medium;
|
||||
|
||||
i {
|
||||
color: $c-medium;
|
||||
}
|
||||
}
|
||||
|
||||
.dap--high & {
|
||||
background: $bg-high;
|
||||
border-color: $bd-high;
|
||||
|
||||
i {
|
||||
color: $c-high;
|
||||
}
|
||||
}
|
||||
|
||||
.dap--critical & {
|
||||
background: $bg-critical;
|
||||
border-color: $bd-critical;
|
||||
|
||||
i {
|
||||
color: $c-critical;
|
||||
}
|
||||
}
|
||||
|
||||
.dap--escheat & {
|
||||
background: $bg-escheat;
|
||||
border-color: $bd-escheat;
|
||||
|
||||
i {
|
||||
color: $c-escheat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 13.5px;
|
||||
font-weight: 700;
|
||||
color: $text;
|
||||
letter-spacing: -0.01em;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
font-size: 11.5px;
|
||||
color: $muted;
|
||||
margin-top: 2px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
// ── Badge group ──────────────────────────────────────────────────
|
||||
&__badges {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&__risk-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 5px 13px;
|
||||
border-radius: 999px;
|
||||
font-size: 11.5px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.01em;
|
||||
cursor: default;
|
||||
|
||||
.dap--safe & {
|
||||
background: $bg-safe;
|
||||
color: $c-safe;
|
||||
border: 1.5px solid $bd-safe;
|
||||
}
|
||||
|
||||
.dap--low & {
|
||||
background: $bg-low;
|
||||
color: $c-low;
|
||||
border: 1.5px solid $bd-low;
|
||||
}
|
||||
|
||||
.dap--medium & {
|
||||
background: $bg-medium;
|
||||
color: $c-medium;
|
||||
border: 1.5px solid $bd-medium;
|
||||
}
|
||||
|
||||
.dap--high & {
|
||||
background: $bg-high;
|
||||
color: $c-high;
|
||||
border: 1.5px solid $bd-high;
|
||||
}
|
||||
|
||||
.dap--critical & {
|
||||
background: $bg-critical;
|
||||
color: $c-critical;
|
||||
border: 1.5px solid $bd-critical;
|
||||
}
|
||||
|
||||
.dap--escheat & {
|
||||
background: $bg-escheat;
|
||||
color: $c-escheat;
|
||||
border: 1.5px solid $bd-escheat;
|
||||
}
|
||||
}
|
||||
|
||||
&__risk-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
animation: pulse-dot 2s ease-in-out infinite;
|
||||
|
||||
.dap--safe & {
|
||||
background: $c-safe;
|
||||
}
|
||||
|
||||
.dap--low & {
|
||||
background: $c-low;
|
||||
}
|
||||
|
||||
.dap--medium & {
|
||||
background: $c-medium;
|
||||
}
|
||||
|
||||
.dap--high & {
|
||||
background: $c-high;
|
||||
}
|
||||
|
||||
.dap--critical & {
|
||||
background: $c-critical;
|
||||
}
|
||||
|
||||
.dap--escheat & {
|
||||
background: $c-escheat;
|
||||
}
|
||||
}
|
||||
|
||||
&__monitor-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 5px 11px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
background: $bg-low;
|
||||
color: $c-low;
|
||||
border: 1.5px solid $bd-low;
|
||||
|
||||
i {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Metric Cards ─────────────────────────────────────────────────
|
||||
&__cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
border-bottom: 1px solid $border;
|
||||
|
||||
&--product {
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
}
|
||||
|
||||
@media (width <= 1024px) {
|
||||
grid-template-columns: repeat(3, 1fr) !important;
|
||||
|
||||
.dap__card:nth-child(3n) {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: repeat(2, 1fr) !important;
|
||||
|
||||
.dap__card:nth-child(2n) {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__card {
|
||||
padding: 14px 16px;
|
||||
border-right: 1px solid $border;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
transition: background 0.15s;
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $surface;
|
||||
}
|
||||
}
|
||||
|
||||
&__card-label {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: $muted;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
&__card-hint {
|
||||
font-size: 10.5px;
|
||||
color: $subtle;
|
||||
margin-top: 2px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
&__help {
|
||||
font-size: 9px;
|
||||
color: $subtle;
|
||||
cursor: help;
|
||||
|
||||
&:hover {
|
||||
color: $c-low;
|
||||
}
|
||||
}
|
||||
|
||||
// Status pill
|
||||
&__pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 4px 11px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
width: fit-content;
|
||||
|
||||
i {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
&--green {
|
||||
background: $bg-safe;
|
||||
color: $c-safe;
|
||||
border: 1px solid $bd-safe;
|
||||
}
|
||||
|
||||
&--gray {
|
||||
background: #f1f5f9;
|
||||
color: $muted;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
&--safe {
|
||||
background: $bg-safe;
|
||||
color: $c-safe;
|
||||
border: 1px solid $bd-safe;
|
||||
}
|
||||
|
||||
&--low {
|
||||
background: $bg-low;
|
||||
color: $c-low;
|
||||
border: 1px solid $bd-low;
|
||||
}
|
||||
|
||||
&--medium {
|
||||
background: $bg-medium;
|
||||
color: $c-medium;
|
||||
border: 1px solid $bd-medium;
|
||||
}
|
||||
|
||||
&--high {
|
||||
background: $bg-high;
|
||||
color: $c-high;
|
||||
border: 1px solid $bd-high;
|
||||
}
|
||||
|
||||
&--critical {
|
||||
background: $bg-critical;
|
||||
color: $c-critical;
|
||||
border: 1px solid $bd-critical;
|
||||
}
|
||||
|
||||
&--escheat {
|
||||
background: $bg-escheat;
|
||||
color: $c-escheat;
|
||||
border: 1px solid $bd-escheat;
|
||||
}
|
||||
}
|
||||
|
||||
// Number display
|
||||
&__number {
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
color: $text;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
|
||||
small {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: $muted;
|
||||
}
|
||||
|
||||
&--warn {
|
||||
color: $c-high;
|
||||
|
||||
small {
|
||||
color: color.adjust($c-high, $lightness: 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__date {
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
i {
|
||||
color: $muted;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&--muted {
|
||||
color: $muted;
|
||||
}
|
||||
}
|
||||
|
||||
&__reached {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: $c-high;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
i {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
&__na {
|
||||
font-size: 12px;
|
||||
color: $subtle;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
// ── Timeline ─────────────────────────────────────────────────────
|
||||
&__timeline {
|
||||
padding: 16px 18px 18px;
|
||||
background: $surface;
|
||||
border-top: 1px solid $border;
|
||||
}
|
||||
|
||||
&__tl-header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__tl-label {
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.07em;
|
||||
color: $muted;
|
||||
}
|
||||
|
||||
&__tl-note {
|
||||
font-size: 10px;
|
||||
color: $subtle;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
&__tl-track {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
&__tl-node {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
flex: 0 0 auto;
|
||||
min-width: 68px;
|
||||
}
|
||||
|
||||
&__tl-circle {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid $border;
|
||||
background: #fff;
|
||||
transition: all 0.2s;
|
||||
|
||||
i {
|
||||
font-size: 12px;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
&--done {
|
||||
background: $c-safe;
|
||||
border-color: $c-safe;
|
||||
box-shadow: 0 0 0 3px rgba($c-safe, 0.15);
|
||||
|
||||
i {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&--current {
|
||||
background: #fff;
|
||||
box-shadow: 0 0 0 3px rgb(0 0 0 / 8%);
|
||||
|
||||
.dap--safe &,
|
||||
.dap--low & {
|
||||
border-color: $c-safe;
|
||||
box-shadow: 0 0 0 3px rgba($c-safe, 0.15);
|
||||
|
||||
i {
|
||||
color: $c-safe;
|
||||
}
|
||||
}
|
||||
|
||||
.dap--medium & {
|
||||
border-color: $c-medium;
|
||||
box-shadow: 0 0 0 3px rgba($c-medium, 0.15);
|
||||
|
||||
i {
|
||||
color: $c-medium;
|
||||
}
|
||||
}
|
||||
|
||||
.dap--high &,
|
||||
.dap--critical & {
|
||||
border-color: $c-high;
|
||||
box-shadow: 0 0 0 3px rgba($c-high, 0.15);
|
||||
|
||||
i {
|
||||
color: $c-high;
|
||||
}
|
||||
}
|
||||
|
||||
.dap--escheat & {
|
||||
border-color: $c-escheat;
|
||||
box-shadow: 0 0 0 3px rgba($c-escheat, 0.15);
|
||||
|
||||
i {
|
||||
color: $c-escheat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--pending {
|
||||
background: $surface;
|
||||
|
||||
i {
|
||||
color: #cbd5e1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__tl-name {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: $muted;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
cursor: help;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
&__tl-info {
|
||||
font-size: 8px;
|
||||
color: $subtle;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
&__tl-sub {
|
||||
font-size: 9px;
|
||||
color: $subtle;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__tl-days {
|
||||
font-size: 10px;
|
||||
color: $muted;
|
||||
background: #e9eef5;
|
||||
border-radius: 999px;
|
||||
padding: 2px 7px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__tl-reached-tag {
|
||||
font-size: 9px;
|
||||
color: $c-high;
|
||||
background: $bg-high;
|
||||
border-radius: 999px;
|
||||
padding: 2px 7px;
|
||||
font-weight: 700;
|
||||
border: 1px solid $bd-high;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__tl-line {
|
||||
flex: 1;
|
||||
height: 2px;
|
||||
background: $border;
|
||||
margin: 14px 2px 0;
|
||||
min-width: 20px;
|
||||
border-radius: 2px;
|
||||
transition: background 0.3s;
|
||||
|
||||
&--done {
|
||||
background: $c-safe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Pulse animation for risk dot ───────────────────────────────────────────
|
||||
@keyframes pulse-dot {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.45;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
export type DormancyRisk = 'safe' | 'low' | 'medium' | 'high' | 'critical' | 'escheat';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-dormancy-alert-panel',
|
||||
templateUrl: './dormancy-alert-panel.component.html',
|
||||
styleUrls: ['./dormancy-alert-panel.component.scss'],
|
||||
imports: [...STANDALONE_SHARED_IMPORTS]
|
||||
})
|
||||
export class DormancyAlertPanelComponent implements OnInit {
|
||||
/** Pass full account data — for ACCOUNT VIEW (general tab) */
|
||||
@Input() accountData: any;
|
||||
/** Pass product template — for ACCOUNT CREATION (currency step) */
|
||||
@Input() productTemplate: any;
|
||||
|
||||
risk: DormancyRisk = 'safe';
|
||||
riskLabel = '';
|
||||
riskSeverity = ''; // e.g. "Low", "Medium", "High"
|
||||
|
||||
statusLabel = '';
|
||||
statusId = 0;
|
||||
|
||||
lastTxnDate: string | null = null;
|
||||
accountOpenedDate: string | null = null;
|
||||
|
||||
daysToInactive: number | null = null;
|
||||
daysToDormancy: number | null = null;
|
||||
daysToEscheat: number | null = null;
|
||||
|
||||
isMonitoringActive = false; // isDormancyTrackingActive
|
||||
isAlertEnabled = false; // dormancyAlert
|
||||
|
||||
shouldShow = false;
|
||||
|
||||
/** true = product template mode (creation stepper), false = account view mode */
|
||||
get isProductMode(): boolean {
|
||||
return !!this.productTemplate && !this.accountData;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.productTemplate && !this.accountData) {
|
||||
this.initFromProductTemplate();
|
||||
} else if (this.accountData) {
|
||||
this.initFromAccountData();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Product Template Mode (Currency Step) ─────────────────────
|
||||
private initFromProductTemplate(): void {
|
||||
const pt = this.productTemplate;
|
||||
|
||||
// Savings: fields come with "FromProduct" suffix
|
||||
// RD/Current: fields come directly (isDormancyTrackingActive, daysToInactive, etc.)
|
||||
this.isMonitoringActive = !!(pt.isDormancyTrackingActiveFromProduct ?? pt.isDormancyTrackingActive ?? false);
|
||||
this.isAlertEnabled = !!pt.dormancyAlert;
|
||||
|
||||
this.daysToInactive = pt.daysToInactiveFromProduct ?? pt.daysToInactive ?? null;
|
||||
this.daysToDormancy = pt.daysToDormancyFromProduct ?? pt.daysToDormancy ?? null;
|
||||
this.daysToEscheat = pt.daysToEscheatFromProduct ?? pt.daysToEscheat ?? null;
|
||||
|
||||
this.statusId = 0;
|
||||
this.statusLabel = 'New Account';
|
||||
|
||||
this.computeRiskFromProduct();
|
||||
this.shouldShow = this.isMonitoringActive || this.isAlertEnabled;
|
||||
}
|
||||
|
||||
private computeRiskFromProduct(): void {
|
||||
// Product mode — always show safe/neutral, no risk colouring
|
||||
this.risk = 'safe';
|
||||
this.riskLabel = 'Configured';
|
||||
this.riskSeverity = this.isMonitoringActive ? 'Active' : 'Inactive';
|
||||
}
|
||||
|
||||
// ── Account Data Mode (General Tab) ───────────────────────────
|
||||
private initFromAccountData(): void {
|
||||
const a = this.accountData;
|
||||
|
||||
this.statusId = a.subStatus?.id ?? 0;
|
||||
this.statusLabel = a.subStatus?.value ?? 'Active';
|
||||
this.isAlertEnabled = !!a.dormancyAlert;
|
||||
this.isMonitoringActive = !!a.isDormancyTrackingActive;
|
||||
|
||||
this.daysToInactive = a.daysToInactive ?? null;
|
||||
this.daysToDormancy = a.daysToDormancy ?? null;
|
||||
this.daysToEscheat = a.daysToEscheat ?? null;
|
||||
|
||||
// Last active transaction date
|
||||
const raw = a.lastActiveTransactionDate;
|
||||
if (raw) {
|
||||
const d = Array.isArray(raw) && raw.length >= 3 ? new Date(raw[0], raw[1] - 1, raw[2]) : new Date(raw);
|
||||
this.lastTxnDate = d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
}
|
||||
|
||||
// Account opened / activated date
|
||||
const tl = a.timeline;
|
||||
if (tl?.activatedOnDate) {
|
||||
const ad =
|
||||
Array.isArray(tl.activatedOnDate) && tl.activatedOnDate.length >= 3
|
||||
? new Date(tl.activatedOnDate[0], tl.activatedOnDate[1] - 1, tl.activatedOnDate[2])
|
||||
: new Date(tl.activatedOnDate);
|
||||
this.accountOpenedDate = ad.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
}
|
||||
|
||||
this.computeRiskFromAccount();
|
||||
this.shouldShow =
|
||||
this.isAlertEnabled ||
|
||||
this.isMonitoringActive ||
|
||||
this.statusId > 0 ||
|
||||
(this.daysToInactive !== null && this.daysToInactive <= 90) ||
|
||||
!!this.lastTxnDate;
|
||||
}
|
||||
|
||||
private computeRiskFromAccount(): void {
|
||||
if (this.statusId === 300) {
|
||||
this.risk = 'escheat';
|
||||
this.riskLabel = 'Escheat';
|
||||
this.riskSeverity = 'Critical';
|
||||
} else if (this.statusId === 200) {
|
||||
this.risk = 'critical';
|
||||
this.riskLabel = 'Dormant';
|
||||
this.riskSeverity = 'Critical';
|
||||
} else if (this.statusId === 100) {
|
||||
this.risk = 'high';
|
||||
this.riskLabel = 'Inactive';
|
||||
this.riskSeverity = 'High';
|
||||
} else if (this.daysToInactive !== null && this.daysToInactive <= 30) {
|
||||
this.risk = 'high';
|
||||
this.riskLabel = 'High';
|
||||
this.riskSeverity = 'High';
|
||||
} else if (this.daysToInactive !== null && this.daysToInactive <= 60) {
|
||||
this.risk = 'medium';
|
||||
this.riskLabel = 'Medium';
|
||||
this.riskSeverity = 'Medium';
|
||||
} else if (this.daysToInactive !== null && this.daysToInactive <= 90) {
|
||||
this.risk = 'low';
|
||||
this.riskLabel = 'Low';
|
||||
this.riskSeverity = 'Low';
|
||||
} else {
|
||||
this.risk = 'safe';
|
||||
this.riskLabel = 'Active';
|
||||
this.riskSeverity = 'Safe';
|
||||
}
|
||||
}
|
||||
|
||||
get timelineSteps() {
|
||||
return [
|
||||
{
|
||||
label: 'Inactive',
|
||||
tooltip: 'Account flagged as inactive after no customer-initiated transaction',
|
||||
days: this.daysToInactive,
|
||||
reached: this.statusId >= 100,
|
||||
current: this.statusId === 100
|
||||
},
|
||||
{
|
||||
label: 'Dormant',
|
||||
tooltip: 'Account classified as dormant per RBI/regulatory guidelines',
|
||||
days: this.daysToDormancy,
|
||||
reached: this.statusId >= 200,
|
||||
current: this.statusId === 200
|
||||
},
|
||||
{
|
||||
label: 'Escheat',
|
||||
tooltip: 'Unclaimed funds transferred to government/regulator as per law',
|
||||
days: this.daysToEscheat,
|
||||
reached: this.statusId >= 300,
|
||||
current: this.statusId === 300
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/** e.g. "Inactive after 30 days of no transaction" */
|
||||
inactiveSubtext(): string {
|
||||
return this.daysToInactive !== null ? `No transaction for ${this.daysToInactive} days` : 'Threshold not configured';
|
||||
}
|
||||
|
||||
dormancySubtext(): string {
|
||||
const total = (this.daysToInactive ?? 0) + (this.daysToDormancy ?? 0);
|
||||
return this.daysToDormancy !== null ? `${this.daysToDormancy} days after inactive` : 'Threshold not configured';
|
||||
}
|
||||
|
||||
escheatSubtext(): string {
|
||||
return this.daysToEscheat !== null ? `${this.daysToEscheat} days after dormant` : 'Threshold not configured';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<!--
|
||||
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="intro-section">
|
||||
<div class="intro-section__header">
|
||||
<i class="fas fa-user-check"></i>
|
||||
<span>Introducer</span>
|
||||
<span class="intro-badge intro-badge--required">Required</span>
|
||||
</div>
|
||||
|
||||
<div class="intro-body">
|
||||
@if (!selectedClient) {
|
||||
<!-- Search Box -->
|
||||
<div class="intro-search-wrap">
|
||||
<mat-form-field appearance="outline" class="intro-search-field">
|
||||
<mat-label>Search by Name or Client ID</mat-label>
|
||||
<input matInput [value]="searchText" (input)="onSearch($any($event.target).value)" autocomplete="off" />
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
</mat-form-field>
|
||||
|
||||
@if (searching) {
|
||||
<div class="intro-results">
|
||||
<div class="intro-results__loading">
|
||||
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner>
|
||||
<span>Searching...</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!searching && searchResults.length > 0) {
|
||||
<div class="intro-results-inline">
|
||||
@for (r of searchResults; track r.id) {
|
||||
<div class="intro-result-item" (click)="selectClient(r)">
|
||||
<mat-icon class="intro-result-item__icon">person</mat-icon>
|
||||
<div class="intro-result-item__info">
|
||||
<span class="intro-result-item__name">{{ r.displayName }}</span>
|
||||
<span class="intro-result-item__meta">
|
||||
ID: {{ r.id }}
|
||||
@if (r.officeName) {
|
||||
· {{ r.officeName }}
|
||||
}
|
||||
@if (r.mobileNo) {
|
||||
· {{ r.mobileNo }}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!searching && searchText.length >= 2 && searchResults.length === 0) {
|
||||
<div class="intro-no-results">No clients found</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (selectedClient) {
|
||||
<!-- Selected Client Card -->
|
||||
<div class="intro-card">
|
||||
<div class="intro-card__avatar">
|
||||
@if (linkedProfileImage) {
|
||||
<img [src]="linkedProfileImage" alt="Profile" class="intro-card__photo" />
|
||||
} @else {
|
||||
<mat-icon class="intro-card__avatar-icon">person</mat-icon>
|
||||
}
|
||||
</div>
|
||||
<div class="intro-card__info">
|
||||
<span class="intro-card__name">{{ selectedClient.displayName }}</span>
|
||||
<span class="intro-card__meta">
|
||||
Client ID: {{ selectedClient.id }}
|
||||
@if (selectedClient.accountNumber) {
|
||||
· A/C: {{ selectedClient.accountNumber }}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<button mat-icon-button type="button" (click)="removeClient()" class="intro-card__remove">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (fetchingLinkedData) {
|
||||
<div class="intro-fetching">
|
||||
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner>
|
||||
<span>Fetching client details...</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!fetchingLinkedData) {
|
||||
<div class="intro-details-grid">
|
||||
<div class="intro-detail">
|
||||
<span class="intro-detail__label">FULL NAME</span>
|
||||
<span class="intro-detail__value">{{ selectedClient.displayName }}</span>
|
||||
</div>
|
||||
<div class="intro-detail">
|
||||
<span class="intro-detail__label">DATE OF BIRTH</span>
|
||||
<span class="intro-detail__value">{{ clientDob || '—' }}</span>
|
||||
</div>
|
||||
@if (clientAadhaar) {
|
||||
<div class="intro-detail">
|
||||
<span class="intro-detail__label">AADHAAR</span>
|
||||
<span class="intro-detail__value">
|
||||
{{ clientAadhaar }}
|
||||
@if (linkedKycStatus?.aadhaarVerified) {
|
||||
<span class="intro-verified"><mat-icon>verified</mat-icon> Verified</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@if (clientPan) {
|
||||
<div class="intro-detail">
|
||||
<span class="intro-detail__label">PAN</span>
|
||||
<span class="intro-detail__value">
|
||||
{{ clientPan }}
|
||||
@if (linkedKycStatus?.panVerified) {
|
||||
<span class="intro-verified"><mat-icon>verified</mat-icon> Verified</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
$blue: #2563eb;
|
||||
$blue-light: #eff6ff;
|
||||
$green: #16a34a;
|
||||
$green-light: #dcfce7;
|
||||
$border: #e2e8f0;
|
||||
$text: #1e293b;
|
||||
$muted: #64748b;
|
||||
$gray-bg: #f8fafc;
|
||||
|
||||
:host {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.intro-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 12px;
|
||||
overflow: visible;
|
||||
background: #fff;
|
||||
margin-top: 16px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 20px;
|
||||
background: $gray-bg;
|
||||
border-bottom: 1px solid $border;
|
||||
border-radius: 12px 12px 0 0;
|
||||
|
||||
i {
|
||||
color: $blue;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
span:nth-child(2) {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.intro-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
|
||||
&--required {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
border: 1px solid #fde68a;
|
||||
}
|
||||
}
|
||||
|
||||
.intro-body {
|
||||
padding: 16px 20px;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
// Search
|
||||
.intro-search-wrap {
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.intro-search-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.intro-results-inline {
|
||||
margin-top: 4px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
box-shadow: 0 4px 16px rgb(0 0 0 / 8%);
|
||||
overflow: hidden;
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.intro-results {
|
||||
position: absolute !important;
|
||||
z-index: 99999 !important;
|
||||
top: calc(100% + 4px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgb(0 0 0 / 10%);
|
||||
max-height: 260px;
|
||||
overflow: hidden auto;
|
||||
|
||||
&__loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 14px 16px;
|
||||
font-size: 13px;
|
||||
color: $muted;
|
||||
}
|
||||
}
|
||||
|
||||
.intro-result-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
border-bottom: 1px solid $border;
|
||||
min-width: 0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $blue-light;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: $muted;
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
font-size: 11px;
|
||||
color: $muted;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.intro-no-results {
|
||||
padding: 10px 0;
|
||||
font-size: 13px;
|
||||
color: $muted;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// Selected client card
|
||||
.intro-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
background: $gray-bg;
|
||||
margin-bottom: 14px;
|
||||
min-width: 0;
|
||||
|
||||
&__avatar {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
background: $blue-light;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__photo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
&__avatar-icon {
|
||||
color: $blue;
|
||||
font-size: 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
&__info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
font-size: 12px;
|
||||
color: $muted;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
&__remove {
|
||||
flex: 0 0 auto;
|
||||
color: $muted;
|
||||
}
|
||||
}
|
||||
|
||||
.intro-fetching {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
color: $muted;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
// Details grid
|
||||
.intro-details-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 14px 24px;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.intro-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
|
||||
&__label {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
color: $muted;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.intro-verified {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: $green;
|
||||
background: $green-light;
|
||||
border-radius: 999px;
|
||||
padding: 2px 8px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 12px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 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, Output, EventEmitter, inject } from '@angular/core';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { ClientsService } from 'app/clients/clients.service';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-introducer-search',
|
||||
templateUrl: './introducer-search.component.html',
|
||||
styleUrls: ['./introducer-search.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FormsModule,
|
||||
MatProgressSpinner,
|
||||
MatIconButton,
|
||||
MatIcon
|
||||
]
|
||||
})
|
||||
export class IntroducerSearchComponent {
|
||||
private clientsService = inject(ClientsService);
|
||||
private sanitizer = inject(DomSanitizer);
|
||||
|
||||
@Output() introducerChange = new EventEmitter<{ clientId: number; displayName: string; cifNumber?: string } | null>();
|
||||
|
||||
entryMode: 'linked' = 'linked';
|
||||
searchText = '';
|
||||
searching = false;
|
||||
searchResults: any[] = [];
|
||||
selectedClient: any = null;
|
||||
fetchingLinkedData = false;
|
||||
linkedProfileImage: any = null;
|
||||
linkedKycStatus: any = null;
|
||||
|
||||
// Fetched client details
|
||||
clientDob = '';
|
||||
clientAadhaar = '';
|
||||
clientPan = '';
|
||||
|
||||
private searchTimer: any;
|
||||
|
||||
onSearch(text: string): void {
|
||||
this.searchText = text;
|
||||
clearTimeout(this.searchTimer);
|
||||
if (!text || text.length < 2) {
|
||||
this.searchResults = [];
|
||||
return;
|
||||
}
|
||||
this.searching = true;
|
||||
this.searchTimer = setTimeout(() => {
|
||||
this.clientsService.searchByText(text, 0, 8).subscribe({
|
||||
next: (data: any) => {
|
||||
this.searchResults = data?.content || [];
|
||||
this.searching = false;
|
||||
},
|
||||
error: () => {
|
||||
this.searchResults = [];
|
||||
this.searching = false;
|
||||
}
|
||||
});
|
||||
}, 400);
|
||||
}
|
||||
|
||||
selectClient(client: any): void {
|
||||
this.selectedClient = client;
|
||||
this.searchResults = [];
|
||||
this.searchText = '';
|
||||
this.clientDob = '';
|
||||
this.clientAadhaar = '';
|
||||
this.clientPan = '';
|
||||
this.linkedProfileImage = null;
|
||||
this.linkedKycStatus = null;
|
||||
this.fetchLinkedClientData(client.id);
|
||||
this.introducerChange.emit({
|
||||
clientId: client.id,
|
||||
displayName: client.displayName,
|
||||
cifNumber: client.cifNumber || client.accountNumber || ''
|
||||
});
|
||||
}
|
||||
|
||||
removeClient(): void {
|
||||
this.selectedClient = null;
|
||||
this.linkedProfileImage = null;
|
||||
this.linkedKycStatus = null;
|
||||
this.clientDob = '';
|
||||
this.clientAadhaar = '';
|
||||
this.clientPan = '';
|
||||
this.introducerChange.emit(null);
|
||||
}
|
||||
|
||||
private fetchLinkedClientData(clientId: string): void {
|
||||
this.fetchingLinkedData = true;
|
||||
|
||||
this.clientsService.getClientData(clientId).subscribe({
|
||||
next: (data: any) => {
|
||||
if (data.dateOfBirth) {
|
||||
const d = Array.isArray(data.dateOfBirth)
|
||||
? new Date(data.dateOfBirth[0], data.dateOfBirth[1] - 1, data.dateOfBirth[2])
|
||||
: new Date(data.dateOfBirth);
|
||||
this.clientDob = d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
}
|
||||
this.fetchingLinkedData = false;
|
||||
},
|
||||
error: () => {
|
||||
this.fetchingLinkedData = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.clientsService.getClientKyc(clientId).subscribe({
|
||||
next: (kyc: any) => {
|
||||
if (kyc) {
|
||||
this.clientAadhaar = kyc.aadhaarNumberMasked || '';
|
||||
this.clientPan = kyc.panNumber || '';
|
||||
this.linkedKycStatus = {
|
||||
panVerified: kyc.panVerified || false,
|
||||
aadhaarVerified: kyc.aadhaarVerified || false
|
||||
};
|
||||
}
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
|
||||
this.clientsService.getClientProfileImage(clientId).subscribe({
|
||||
next: (imageData: any) => {
|
||||
if (imageData) {
|
||||
this.linkedProfileImage = this.sanitizer.bypassSecurityTrustResourceUrl(imageData);
|
||||
}
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
<!--
|
||||
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="nom-section">
|
||||
<!-- Header -->
|
||||
<div class="nom-header">
|
||||
<div class="nom-header__left">
|
||||
<i class="fas fa-user-shield"></i>
|
||||
<span class="nom-header__title">Nomination Facility</span>
|
||||
</div>
|
||||
<div class="nom-mode-tabs">
|
||||
<button type="button" class="nom-tab" [class.nom-tab--active]="mode === 'manual'" (click)="switchMode('manual')">
|
||||
<i class="fas fa-pencil-alt"></i> Enter Manually
|
||||
</button>
|
||||
<button type="button" class="nom-tab" [class.nom-tab--active]="mode === 'linked'" (click)="switchMode('linked')">
|
||||
<i class="fas fa-search"></i> Link Existing Client
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nom-body">
|
||||
<!-- ══════════════ MANUAL MODE ══════════════ -->
|
||||
@if (mode === 'manual') {
|
||||
<form [formGroup]="manualForm" class="nom-form">
|
||||
<!-- Basic Info -->
|
||||
<div class="nom-row">
|
||||
<mat-form-field appearance="outline" class="nom-field">
|
||||
<mat-label>Full Name *</mat-label>
|
||||
<input matInput formControlName="fullName" placeholder="Nominee full name" />
|
||||
<mat-error>Full name is required</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="nom-field">
|
||||
<mat-label>Relationship *</mat-label>
|
||||
<mat-select formControlName="relationship">
|
||||
@for (rel of relationships; track rel) {
|
||||
<mat-option [value]="rel">{{ rel }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>Relationship is required</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="nom-row">
|
||||
<mat-form-field appearance="outline" class="nom-field" (click)="dobPicker.open()">
|
||||
<mat-label>Date of Birth</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[matDatepicker]="dobPicker"
|
||||
[min]="minDate"
|
||||
[max]="maxDate"
|
||||
formControlName="dateOfBirth"
|
||||
readonly
|
||||
/>
|
||||
<mat-datepicker-toggle matSuffix [for]="dobPicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #dobPicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="nom-field">
|
||||
<mat-label>Nationality</mat-label>
|
||||
<mat-select formControlName="nationality">
|
||||
<mat-option value="Indian">Indian</mat-option>
|
||||
<mat-option value="NRI">NRI</mat-option>
|
||||
<mat-option value="Foreign National">Foreign National</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- ID Verification -->
|
||||
<div class="nom-section-label"><i class="fas fa-id-card"></i> ID Verification</div>
|
||||
|
||||
<div class="nom-row">
|
||||
<mat-form-field appearance="outline" class="nom-field">
|
||||
<mat-label>Aadhaar Number</mat-label>
|
||||
<input matInput formControlName="aadhaarNumber" maxlength="12" placeholder="12-digit Aadhaar" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="nom-field">
|
||||
<mat-label>PAN Number</mat-label>
|
||||
<input
|
||||
matInput
|
||||
formControlName="panNumber"
|
||||
maxlength="10"
|
||||
placeholder="ABCDE1234F"
|
||||
style="text-transform: uppercase"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="nom-row">
|
||||
<mat-form-field appearance="outline" class="nom-field">
|
||||
<mat-label>Mobile Number</mat-label>
|
||||
<input matInput formControlName="mobileNo" maxlength="10" placeholder="10-digit mobile" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="nom-field">
|
||||
<mat-label>Nominee Share (%)</mat-label>
|
||||
<input matInput type="number" formControlName="nomineeShare" readonly />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Guardian (shown if minor / not alive) -->
|
||||
<div class="nom-section-label">
|
||||
<i class="fas fa-user-friends"></i> Guardian Details
|
||||
<span class="nom-hint">(if nominee is a minor)</span>
|
||||
</div>
|
||||
|
||||
<div class="nom-row">
|
||||
<mat-form-field appearance="outline" class="nom-field">
|
||||
<mat-label>Guardian Name</mat-label>
|
||||
<input matInput formControlName="guardianName" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="nom-field">
|
||||
<mat-label>Guardian Relationship</mat-label>
|
||||
<mat-select formControlName="guardianRelationship">
|
||||
@for (rel of relationships; track rel) {
|
||||
<mat-option [value]="rel">{{ rel }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Flags -->
|
||||
<div class="nom-flags">
|
||||
<mat-checkbox formControlName="isAlive" color="primary">Is Alive</mat-checkbox>
|
||||
<mat-checkbox formControlName="isDependent" color="primary">Is Dependent</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<!-- Remarks -->
|
||||
<mat-form-field appearance="outline" class="nom-field nom-field--full">
|
||||
<mat-label>Remarks</mat-label>
|
||||
<textarea matInput formControlName="remarks" rows="2"></textarea>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
}
|
||||
|
||||
<!-- ══════════════ LINK EXISTING CLIENT MODE ══════════════ -->
|
||||
@if (mode === 'linked') {
|
||||
@if (!selectedClient) {
|
||||
<!-- Search -->
|
||||
<div class="nom-search-wrap">
|
||||
<mat-form-field appearance="outline" class="nom-field--full">
|
||||
<mat-label>Search by Name or Client ID</mat-label>
|
||||
<input matInput [value]="searchText" (input)="onSearch($any($event.target).value)" autocomplete="off" />
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
</mat-form-field>
|
||||
|
||||
@if (searching) {
|
||||
<div class="nom-results">
|
||||
<div class="nom-results__loading">
|
||||
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner>
|
||||
<span>Searching...</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!searching && searchResults.length > 0) {
|
||||
<div class="nom-results">
|
||||
@for (r of searchResults; track r.id) {
|
||||
<div class="nom-result-item" (click)="selectClient(r)">
|
||||
<mat-icon class="nom-result-item__icon">person</mat-icon>
|
||||
<div class="nom-result-item__info">
|
||||
<span class="nom-result-item__name">{{ r.displayName }}</span>
|
||||
<span class="nom-result-item__meta">
|
||||
ID: {{ r.id }}
|
||||
@if (r.officeName) {
|
||||
· {{ r.officeName }}
|
||||
}
|
||||
@if (r.mobileNo) {
|
||||
· {{ r.mobileNo }}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!searching && searchText.length >= 2 && searchResults.length === 0) {
|
||||
<div class="nom-no-results">No clients found</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (selectedClient) {
|
||||
<!-- Selected Client Card -->
|
||||
<div class="nom-client-card">
|
||||
<div class="nom-client-card__avatar">
|
||||
@if (linkedProfileImage) {
|
||||
<img [src]="linkedProfileImage" alt="Profile" class="nom-client-card__photo" />
|
||||
} @else {
|
||||
<mat-icon class="nom-client-card__avatar-icon">person</mat-icon>
|
||||
}
|
||||
</div>
|
||||
<div class="nom-client-card__info">
|
||||
<span class="nom-client-card__name">{{ selectedClient.displayName }}</span>
|
||||
<span class="nom-client-card__meta">
|
||||
Client ID: {{ selectedClient.id }}
|
||||
@if (selectedClient.accountNumber) {
|
||||
· A/C: {{ selectedClient.accountNumber }}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
(click)="removeLinked()"
|
||||
class="nom-client-card__remove"
|
||||
matTooltip="Remove"
|
||||
>
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (fetchingLinkedData) {
|
||||
<div class="nom-fetching">
|
||||
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner>
|
||||
<span>Fetching nominee details...</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!fetchingLinkedData) {
|
||||
<div class="nom-details-grid">
|
||||
<div class="nom-detail">
|
||||
<span class="nom-detail__label">Full Name</span>
|
||||
<span class="nom-detail__value">{{ selectedClient.displayName }}</span>
|
||||
</div>
|
||||
<div class="nom-detail">
|
||||
<span class="nom-detail__label">Date of Birth</span>
|
||||
<span class="nom-detail__value">{{ clientDob || '—' }}</span>
|
||||
</div>
|
||||
@if (clientAadhaar) {
|
||||
<div class="nom-detail">
|
||||
<span class="nom-detail__label">Aadhaar</span>
|
||||
<span class="nom-detail__value">
|
||||
{{ clientAadhaar }}
|
||||
@if (linkedKycStatus?.aadhaarVerified) {
|
||||
<span class="nom-verified"><mat-icon>verified</mat-icon> Verified</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@if (clientPan) {
|
||||
<div class="nom-detail">
|
||||
<span class="nom-detail__label">PAN</span>
|
||||
<span class="nom-detail__value">
|
||||
{{ clientPan }}
|
||||
@if (linkedKycStatus?.panVerified) {
|
||||
<span class="nom-verified"><mat-icon>verified</mat-icon> Verified</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Relationship picker for linked client -->
|
||||
<div class="nom-linked-extras">
|
||||
<mat-form-field appearance="outline" class="nom-field">
|
||||
<mat-label>Relationship *</mat-label>
|
||||
<mat-select [(ngModel)]="linkedRelationship" (ngModelChange)="onLinkedRelationshipChange($event)">
|
||||
@for (rel of relationships; track rel) {
|
||||
<mat-option [value]="rel">{{ rel }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="nom-field">
|
||||
<mat-label>Nominee Share (%)</mat-label>
|
||||
<input matInput type="number" [value]="100" readonly />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,387 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
@use 'sass:color';
|
||||
|
||||
$blue: #2563eb;
|
||||
$blue-light: #eff6ff;
|
||||
$green: #16a34a;
|
||||
$green-light: #dcfce7;
|
||||
$border: #e2e8f0;
|
||||
$text: #1e293b;
|
||||
$muted: #64748b;
|
||||
$gray-bg: #f8fafc;
|
||||
$purple: #7c3aed;
|
||||
$purple-light: #f5f3ff;
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// ─── Wrapper ───────────────────────────────────────────────────
|
||||
.nom-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
margin-top: 20px;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
// ─── Header ────────────────────────────────────────────────────
|
||||
.nom-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
padding: 14px 20px;
|
||||
background: $gray-bg;
|
||||
border-bottom: 1px solid $border;
|
||||
border-radius: 14px 14px 0 0;
|
||||
|
||||
&__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
i {
|
||||
color: $purple;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: $text;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Mode toggle tabs ──────────────────────────────────────────
|
||||
.nom-mode-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.nom-tab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 14px;
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border: 1.5px solid $border;
|
||||
background: #fff;
|
||||
color: $muted;
|
||||
transition: all 0.18s;
|
||||
|
||||
i {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: $blue;
|
||||
color: $blue;
|
||||
background: $blue-light;
|
||||
}
|
||||
|
||||
&--active {
|
||||
border-color: $blue;
|
||||
background: $blue;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background: color.adjust(#2563eb, $lightness: -5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Body ──────────────────────────────────────────────────────
|
||||
.nom-body {
|
||||
padding: 20px;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
// ─── Manual form grid ──────────────────────────────────────────
|
||||
.nom-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.nom-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-field {
|
||||
width: 100%;
|
||||
|
||||
&--full {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-section-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.07em;
|
||||
text-transform: uppercase;
|
||||
color: $blue;
|
||||
margin: 8px 0 4px;
|
||||
|
||||
i {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-hint {
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
color: $muted;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.nom-flags {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
margin: 8px 0 12px;
|
||||
}
|
||||
|
||||
// ─── Search (link mode) ────────────────────────────────────────
|
||||
.nom-search-wrap {
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.nom-results {
|
||||
margin-top: 4px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
box-shadow: 0 4px 20px rgb(0 0 0 / 10%);
|
||||
overflow: hidden;
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
|
||||
&__loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 14px 16px;
|
||||
font-size: 13px;
|
||||
color: $muted;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-result-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
border-bottom: 1px solid $border;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $blue-light;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: $muted;
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
font-size: 11px;
|
||||
color: $muted;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-no-results {
|
||||
padding: 12px 0;
|
||||
font-size: 13px;
|
||||
color: $muted;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// ─── Selected client card ──────────────────────────────────────
|
||||
.nom-client-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
background: $gray-bg;
|
||||
margin-bottom: 14px;
|
||||
|
||||
&__avatar {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border-radius: 50%;
|
||||
background: $blue-light;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__photo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
&__avatar-icon {
|
||||
color: $blue;
|
||||
font-size: 26px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
&__info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: $text;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
font-size: 12px;
|
||||
color: $muted;
|
||||
}
|
||||
|
||||
&__remove {
|
||||
flex: 0 0 auto;
|
||||
color: $muted;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-fetching {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
color: $muted;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
// ─── Linked client details grid ────────────────────────────────
|
||||
.nom-details-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 14px 24px;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
margin-bottom: 14px;
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
&__label {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
color: $muted;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.nom-verified {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: $green;
|
||||
background: $green-light;
|
||||
border-radius: 999px;
|
||||
padding: 2px 8px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 12px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Extras for linked (relationship + share) ──────────────────
|
||||
.nom-linked-extras {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin-top: 4px;
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* 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, Output, EventEmitter, inject, OnInit } from '@angular/core';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators, FormsModule } from '@angular/forms';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { ClientsService } from 'app/clients/clients.service';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatCheckbox } from '@angular/material/checkbox';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
|
||||
export interface NomineeData {
|
||||
mode: 'manual' | 'linked';
|
||||
// Manual fields
|
||||
fullName?: string;
|
||||
relationship?: string;
|
||||
dateOfBirth?: string;
|
||||
nationality?: string;
|
||||
aadhaarNumber?: string;
|
||||
panNumber?: string;
|
||||
mobileNo?: string;
|
||||
nomineeShare?: number;
|
||||
isAlive?: boolean;
|
||||
isDependent?: boolean;
|
||||
guardianName?: string;
|
||||
guardianRelationship?: string;
|
||||
remarks?: string;
|
||||
// Linked client fields
|
||||
clientId?: number;
|
||||
displayName?: string;
|
||||
cifNumber?: string;
|
||||
}
|
||||
|
||||
const RELATIONSHIPS = [
|
||||
'Spouse',
|
||||
'Son',
|
||||
'Daughter',
|
||||
'Father',
|
||||
'Mother',
|
||||
'Brother',
|
||||
'Sister',
|
||||
'Grandfather',
|
||||
'Grandmother',
|
||||
'Grandson',
|
||||
'Granddaughter',
|
||||
'Uncle',
|
||||
'Aunt',
|
||||
'Nephew',
|
||||
'Niece',
|
||||
'Friend',
|
||||
'Other'
|
||||
];
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-nominee-form',
|
||||
templateUrl: './nominee-form.component.html',
|
||||
styleUrls: ['./nominee-form.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FormsModule,
|
||||
MatProgressSpinner,
|
||||
MatIconButton,
|
||||
MatIcon,
|
||||
MatCheckbox,
|
||||
MatDatepickerModule
|
||||
]
|
||||
})
|
||||
export class NomineeFormComponent implements OnInit {
|
||||
private fb = inject(UntypedFormBuilder);
|
||||
private clientsService = inject(ClientsService);
|
||||
private sanitizer = inject(DomSanitizer);
|
||||
|
||||
@Output() nomineeChange = new EventEmitter<NomineeData | null>();
|
||||
|
||||
mode: 'manual' | 'linked' = 'manual';
|
||||
relationships = RELATIONSHIPS;
|
||||
|
||||
// Manual form
|
||||
manualForm!: UntypedFormGroup;
|
||||
minDate = new Date(1900, 0, 1);
|
||||
maxDate = new Date();
|
||||
|
||||
// Link mode
|
||||
searchText = '';
|
||||
searching = false;
|
||||
searchResults: any[] = [];
|
||||
selectedClient: any = null;
|
||||
fetchingLinkedData = false;
|
||||
linkedProfileImage: any = null;
|
||||
linkedKycStatus: any = null;
|
||||
clientDob = '';
|
||||
clientAadhaar = '';
|
||||
clientPan = '';
|
||||
clientCifNumber = '';
|
||||
linkedRelationship = '';
|
||||
linkedShare = 100;
|
||||
|
||||
private searchTimer: any;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.manualForm = this.fb.group({
|
||||
fullName: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
relationship: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
dateOfBirth: [''],
|
||||
nationality: ['Indian'],
|
||||
aadhaarNumber: [''],
|
||||
panNumber: [''],
|
||||
mobileNo: [''],
|
||||
nomineeShare: [100],
|
||||
isAlive: [true],
|
||||
isDependent: [false],
|
||||
guardianName: [''],
|
||||
guardianRelationship: [''],
|
||||
remarks: ['']
|
||||
});
|
||||
|
||||
this.manualForm.valueChanges.subscribe(() => {
|
||||
if (this.mode === 'manual' && this.manualForm.valid) {
|
||||
this.emitManual();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
switchMode(m: 'manual' | 'linked'): void {
|
||||
this.mode = m;
|
||||
if (m === 'manual') {
|
||||
this.clearLinked();
|
||||
} else {
|
||||
this.manualForm.reset({ nationality: 'Indian', isAlive: true, isDependent: false, nomineeShare: 100 });
|
||||
this.nomineeChange.emit(null);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Manual ──────────────────────────────────────
|
||||
private emitManual(): void {
|
||||
const v = this.manualForm.value;
|
||||
this.nomineeChange.emit({ mode: 'manual', ...v });
|
||||
}
|
||||
|
||||
// ─── Link existing client ─────────────────────────
|
||||
onSearch(text: string): void {
|
||||
this.searchText = text;
|
||||
clearTimeout(this.searchTimer);
|
||||
if (!text || text.length < 2) {
|
||||
this.searchResults = [];
|
||||
return;
|
||||
}
|
||||
this.searching = true;
|
||||
this.searchTimer = setTimeout(() => {
|
||||
this.clientsService.searchByText(text, 0, 8).subscribe({
|
||||
next: (data: any) => {
|
||||
this.searchResults = data?.content || [];
|
||||
this.searching = false;
|
||||
},
|
||||
error: () => {
|
||||
this.searchResults = [];
|
||||
this.searching = false;
|
||||
}
|
||||
});
|
||||
}, 400);
|
||||
}
|
||||
|
||||
selectClient(client: any): void {
|
||||
this.selectedClient = client;
|
||||
this.searchResults = [];
|
||||
this.searchText = '';
|
||||
this.fetchLinkedClientData(client.id);
|
||||
this.nomineeChange.emit({
|
||||
mode: 'linked',
|
||||
clientId: client.id,
|
||||
displayName: client.displayName,
|
||||
cifNumber: client.accountNo || client.cifNumber || ''
|
||||
});
|
||||
}
|
||||
|
||||
removeLinked(): void {
|
||||
this.clearLinked();
|
||||
this.nomineeChange.emit(null);
|
||||
}
|
||||
|
||||
onLinkedRelationshipChange(rel: string): void {
|
||||
if (this.selectedClient) {
|
||||
this.nomineeChange.emit({
|
||||
mode: 'linked',
|
||||
clientId: this.selectedClient.id,
|
||||
displayName: this.selectedClient.displayName,
|
||||
cifNumber: this.selectedClient.accountNo || this.selectedClient.cifNumber || '',
|
||||
relationship: rel,
|
||||
nomineeShare: this.linkedShare
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private clearLinked(): void {
|
||||
this.selectedClient = null;
|
||||
this.linkedProfileImage = null;
|
||||
this.linkedKycStatus = null;
|
||||
this.clientDob = '';
|
||||
this.clientAadhaar = '';
|
||||
this.clientPan = '';
|
||||
this.clientCifNumber = '';
|
||||
this.searchText = '';
|
||||
this.searchResults = [];
|
||||
this.linkedRelationship = '';
|
||||
this.linkedShare = 100;
|
||||
}
|
||||
|
||||
private fetchLinkedClientData(clientId: string): void {
|
||||
this.fetchingLinkedData = true;
|
||||
|
||||
this.clientsService.getClientData(clientId).subscribe({
|
||||
next: (data: any) => {
|
||||
if (data.dateOfBirth) {
|
||||
const d = Array.isArray(data.dateOfBirth)
|
||||
? new Date(data.dateOfBirth[0], data.dateOfBirth[1] - 1, data.dateOfBirth[2])
|
||||
: new Date(data.dateOfBirth);
|
||||
this.clientDob = d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
}
|
||||
this.fetchingLinkedData = false;
|
||||
},
|
||||
error: () => {
|
||||
this.fetchingLinkedData = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.clientsService.getClientKyc(clientId).subscribe({
|
||||
next: (kyc: any) => {
|
||||
if (kyc) {
|
||||
this.clientAadhaar = kyc.aadhaarNumberMasked || '';
|
||||
this.clientPan = kyc.panNumber || '';
|
||||
this.linkedKycStatus = {
|
||||
panVerified: kyc.panVerified || false,
|
||||
aadhaarVerified: kyc.aadhaarVerified || false
|
||||
};
|
||||
}
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
|
||||
this.clientsService.getClientProfileImage(clientId).subscribe({
|
||||
next: (imageData: any) => {
|
||||
if (imageData) {
|
||||
this.linkedProfileImage = this.sanitizer.bypassSecurityTrustResourceUrl(imageData);
|
||||
}
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@
|
||||
/* tslint:disable */
|
||||
export default {
|
||||
'mifos_x': {
|
||||
'version': '260422',
|
||||
'hash': 'e9a93771'
|
||||
'version': '260427',
|
||||
'hash': '5fb25edc'
|
||||
},
|
||||
'allow_switching_backend_instance': true
|
||||
};
|
||||
|
||||
@@ -957,3 +957,51 @@ mat-card {
|
||||
border-radius: 16px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Add Beneficiary Dialog Panel ─── */
|
||||
.abd-dialog-panel {
|
||||
.mat-mdc-dialog-container {
|
||||
padding: 0;
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mat-mdc-dialog-surface {
|
||||
padding: 0 !important;
|
||||
border-radius: 14px !important;
|
||||
}
|
||||
|
||||
.mat-mdc-dialog-content {
|
||||
padding: 0 20px !important;
|
||||
max-height: 72vh;
|
||||
}
|
||||
|
||||
.mat-mdc-dialog-actions {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Add Related Client Dialog Panel ─── */
|
||||
.arc-dialog-panel {
|
||||
.mat-mdc-dialog-container {
|
||||
padding: 0;
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mat-mdc-dialog-surface {
|
||||
padding: 0 !important;
|
||||
border-radius: 14px !important;
|
||||
}
|
||||
|
||||
.mat-mdc-dialog-content {
|
||||
padding: 0 20px !important;
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
.mat-mdc-dialog-actions {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user