From fbaec18552ef30de5f1ca5cc288f1ca45a74e74c Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Fri, 24 Apr 2026 16:24:28 +0530 Subject: [PATCH] update code account saving,current,RD --- .../create-current-account.component.html | 96 +-- .../create-current-account.component.scss | 3 + .../create-current-account.component.ts | 81 +-- ...rrent-account-currency-step.component.html | 56 +- ...rrent-account-currency-step.component.scss | 14 + ...current-account-currency-step.component.ts | 46 +- ...urrent-account-details-step.component.html | 12 +- ...urrent-account-details-step.component.scss | 60 +- .../current-account-details-step.component.ts | 26 +- ...urrent-account-digital-step.component.html | 408 +++++------ ...urrent-account-digital-step.component.scss | 319 ++++---- .../current-account-digital-step.component.ts | 27 +- ...urrent-account-preview-step.component.html | 273 ------- .../current-account-term-step.component.html | 8 +- ...-recurring-deposits-account.component.html | 80 +- ...-recurring-deposits-account.component.scss | 12 + ...te-recurring-deposits-account.component.ts | 75 +- ...osits-account-currency-step.component.html | 82 +++ ...osits-account-currency-step.component.scss | 141 ++++ ...eposits-account-currency-step.component.ts | 60 ++ ...posits-account-details-step.component.html | 103 +-- ...posits-account-details-step.component.scss | 60 +- ...deposits-account-details-step.component.ts | 26 +- ...nt-interest-rate-chart-step.component.html | 11 - ...posits-account-preview-step.component.html | 386 ---------- ...deposit-product-currency-step.component.ts | 16 +- .../create-savings-account.component.html | 120 +-- .../create-savings-account.component.scss | 3 + .../create-savings-account.component.ts | 59 +- ...vings-account-currency-step.component.html | 6 + ...savings-account-currency-step.component.ts | 14 +- ...avings-account-details-step.component.html | 17 +- ...avings-account-details-step.component.scss | 2 +- .../savings-account-details-step.component.ts | 15 +- ...avings-account-digital-step.component.html | 408 +++++------ ...avings-account-digital-step.component.scss | 319 ++++---- .../savings-account-digital-step.component.ts | 27 +- ...avings-account-preview-step.component.html | 303 -------- .../savings-account-term-step.component.html | 8 +- .../dormancy-alert-panel.component.html | 308 ++++++++ .../dormancy-alert-panel.component.scss | 684 ++++++++++++++++++ .../dormancy-alert-panel.component.ts | 195 +++++ .../introducer-search.component.html | 130 ++++ .../introducer-search.component.scss | 316 ++++++++ .../introducer-search.component.ts | 138 ++++ .../nominee-form/nominee-form.component.html | 282 ++++++++ .../nominee-form/nominee-form.component.scss | 387 ++++++++++ .../nominee-form/nominee-form.component.ts | 254 +++++++ src/environments/.env.ts | 5 +- 49 files changed, 4221 insertions(+), 2260 deletions(-) create mode 100644 src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.html create mode 100644 src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.scss create mode 100644 src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.ts create mode 100644 src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.html create mode 100644 src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.scss create mode 100644 src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.ts create mode 100644 src/app/shared/introducer-search/introducer-search.component.html create mode 100644 src/app/shared/introducer-search/introducer-search.component.scss create mode 100644 src/app/shared/introducer-search/introducer-search.component.ts create mode 100644 src/app/shared/nominee-form/nominee-form.component.html create mode 100644 src/app/shared/nominee-form/nominee-form.component.scss create mode 100644 src/app/shared/nominee-form/nominee-form.component.ts diff --git a/src/app/current-accounts/create-current-account/create-current-account.component.html b/src/app/current-accounts/create-current-account/create-current-account.component.html index 9b5c5211b..1f07c596b 100644 --- a/src/app/current-accounts/create-current-account/create-current-account.component.html +++ b/src/app/current-accounts/create-current-account/create-current-account.component.html @@ -35,7 +35,7 @@ - + {{ 'labels.inputs.DETAILS' | translate }} - - - CURRENCY + @if (currentAccountProductTemplate) { + + + CURRENCY - - - - - - {{ 'labels.inputs.TERMS' | translate }} - - - - - - - SETTINGS - - - - - - - DIGITAL - - - - - - - {{ 'labels.inputs.CHARGES' | translate }} - - - - - -
- -
-
+ > - - @if (currentAccountFormValid) { +
+ + +
+
+ + + + DIGITAL + + + +
+ + +
+
+ + {{ 'labels.inputs.PREVIEW' | translate }} { @@ -100,32 +94,17 @@ 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; - } - - /** - * Checks validity of overall current account form. - */ - get currentAccountFormValid() { - return this.currentAccountDetailsForm.valid && this.currentAccountTermForm.valid; + get currentAccountTermsForm(): FormGroup { + return new FormGroup({}); } /** @@ -133,10 +112,7 @@ export class CreateCurrentAccountComponent { */ get currentAccount() { return { - ...this.currentAccountDetailsStep.currentAccountDetails, - ...this.currentAccountTermStep.currentAccountTerms, - ...this.currentAccountSettingsStep.currentAccountSettings, - ...this.currentAccountChargesStep.currentAccountCharges + ...this.currentAccountDetailsStep.currentAccountDetails }; } @@ -149,15 +125,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, diff --git a/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.html b/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.html index e9ba49bac..e790fd695 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.html +++ b/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.html @@ -26,6 +26,30 @@ +
+
+

Balance Swap Option

+

Allows balance to be swapped or transferred between linked accounts as per product configuration.

+
+ +
+ +
+
+

MSME

+

Account is categorized under the Micro, Small and Medium Enterprises segment.

+
+ +
+ +
+
+

Auto Debit Facility

+

Enable automatic debit instructions for recurring payments linked to this account.

+
+ +
+

Dormancy Alert

@@ -36,25 +60,7 @@
-
-
-

Balance Swap Option

-

Allows balance to be swapped or transferred between linked accounts as per product configuration.

-
- - {{ currentAccountProductTemplate?.balanceSwapOption ? 'Yes' : 'No' }} - -
- -
-
-

MSME

-

Account is categorized under the Micro, Small and Medium Enterprises segment.

-
- - {{ currentAccountProductTemplate?.msme ? 'Yes' : 'No' }} - -
+
@@ -66,14 +72,8 @@
-
-
-

Auto Debit Facility

-

Enable automatic debit instructions for recurring payments linked to this account.

-
- - {{ currentAccountProductTemplate?.autoDebitFacility ? 'Yes' : 'No' }} - -
+ @if (currentAccountProductTemplate?.nominationFacility) { + + }
diff --git a/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.scss b/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.scss index 17712a898..68c9a94a2 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.scss +++ b/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.scss @@ -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; + } +} diff --git a/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.ts b/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.ts index 805c4336b..c3cae7dce 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.ts +++ b/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.ts @@ -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 } : {}) + }; + } } diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html index e5914fe18..440bf7b4f 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html @@ -7,7 +7,7 @@ -->
- +
@@ -45,9 +45,7 @@ {{ 'labels.commons.required' | translate }} - } - @if (currentAccountProductSelected) { {{ 'labels.inputs.Field Officer' | translate }} @@ -56,9 +54,7 @@ } - } - @if (currentAccountProductSelected) { {{ 'labels.inputs.External ID' | translate }} @@ -67,8 +63,8 @@
- @if (currentAccountProductSelected && productTemplate) { +
@@ -125,6 +121,10 @@
+ + @if (productTemplate?.introducerRequired) { + + } }
diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.scss b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.scss index 7c9847ae8..cd7492271 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.scss +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.scss @@ -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,61 @@ $gray-900: #202124; border-color: #bbf7d0; } } + +// ── 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; + } +} diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.ts b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.ts index c547fedcf..bacad5cdb 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.ts +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.ts @@ -17,6 +17,7 @@ import { SettingsService } from 'app/settings/settings.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'; /** * Current 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 CurrentAccountDetailsStepComponent implements OnInit { @@ -56,6 +58,7 @@ export class CurrentAccountDetailsStepComponent implements OnInit { currentAccountDetailsForm: UntypedFormGroup; currentAccountProductSelected = false; + introducerId: number | null = null; /** Current Account Template with product data */ @Output() currentAccountProductTemplate = new EventEmitter(); @@ -102,7 +105,10 @@ export class CurrentAccountDetailsStepComponent implements OnInit { Validators.required ], fieldOfficerId: [''], - externalId: [''] + externalId: [''], + balanceSwapOption: [false], + msme: [false], + autoDebitFacility: [false] }); } @@ -131,6 +137,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 +178,15 @@ export class CurrentAccountDetailsStepComponent implements OnInit { /** * Returns current account form value. */ + onIntroducerChange(event: { clientId: number; displayName: string } | null): void { + this.introducerId = event?.clientId ?? null; + } + get currentAccountDetails() { - return this.currentAccountDetailsForm.getRawValue(); + const value = this.currentAccountDetailsForm.getRawValue(); + if (this.introducerId) { + value.introducerId = this.introducerId; + } + return value; } } diff --git a/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.html b/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.html index 096e08a7b..5b0970996 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.html +++ b/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.html @@ -6,229 +6,209 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -
-
- - - -
Transaction & Product Limits
-
FROM PRODUCT
+
+
+
+ +
+
+

Digital & Account Features

+

Toggle the facilities available for this account (as per RBI guidelines)

+
+ FROM PRODUCT
-
-
-
Account Features (as per RBI guidelines)
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+
+
+
+ ATM Facility + {{ facilityConfig.atmFacility ? 'Enabled' : 'Disabled' }}
+
- @if (accountFacilityLimits.length > 0) { -
- - - - - - - - - - - - - - - - - @for (row of accountFacilityLimits; track row.limitType) { - - - - - - - } - -
Limit TypeGeneral Limit (Daily)Minor Account LimitSmall Saving Limit
{{ row.limitType }}
+
+
+
+ ATM Withdrawal + {{ facilityConfig.atmWithdrawalFacility ? 'Enabled' : 'Disabled' }}
- } + +
- @if (depositLimitKyc?.general) { -
-

Deposit Limit (KYC)

- - - - - - - - - - - - - - - - - - - - - - - - - -
Account TypeKYC TypeLimit
General - -
Minor - -
Small Saving - -
+
+
+
+ UPI Facility + {{ facilityConfig.upiFacility ? 'Enabled' : 'Disabled' }}
- } + +
-
- Note: - 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. +
+
+
+ POS Transactions + {{ facilityConfig.posTransactionsFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Cheque Book + {{ facilityConfig.chequeBook ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ IMPS Facility + {{ facilityConfig.impsFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ SMS Alerts + {{ facilityConfig.smsAlerts ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ BBPS Facility + {{ facilityConfig.bbpsFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Insurance Cover + {{ facilityConfig.insuranceCover ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ BEPG Facility + {{ facilityConfig.bepgFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ NEFT Facility + {{ facilityConfig.neftFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ RTGS Facility + {{ facilityConfig.rtgsFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Inter-Bank Total Cap + {{ facilityConfig.interBankTotalCapFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Withdrawal Facility + {{ facilityConfig.withdrawalFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Deposit Facility (KYC) + {{ facilityConfig.depositFacilityKyc ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Virtual Debit Card + {{ facilityConfig.virtualDebitCard ? 'Enabled' : 'Disabled' }} +
+
diff --git a/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.scss b/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.scss index 7b58a1abb..038aa7fc7 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.scss +++ b/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.scss @@ -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; + } } diff --git a/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.ts b/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.ts index 9ffb1d278..321d9984d 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.ts +++ b/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.ts @@ -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 } }; } } diff --git a/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.html b/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.html index 5cf37b020..3a893481d 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.html +++ b/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.html @@ -218,174 +218,6 @@
- -
-
- {{ 'labels.heading.Terms' | translate }} -
-
-
-
- Interest Posting Frequency - {{ - termPreview?.interestPostingPeriodType - | find: currentAccountProductTemplate?.interestPostingPeriodTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
-
- Auto Interest Post - - {{ termPreview?.autoPost | yesNo }} - -
- @if (termPreview?.frequencyFixDay !== undefined && termPreview?.frequencyFixDay !== null) { -
- Frequency Fix Day - {{ termPreview?.frequencyFixDay }} Days -
- } - @if (termPreview?.lastInterestDate) { -
- Last Interest Date - {{ termPreview?.lastInterestDate | dateFormat }} -
- } -
-
-
- - -
-
- {{ 'labels.heading.Settings' | translate }} -
-
-
- @if ( - settingsPreview?.minRequiredOpeningBalance !== undefined && - settingsPreview?.minRequiredOpeningBalance !== null && - settingsPreview?.minRequiredOpeningBalance !== '' - ) { -
- {{ 'labels.inputs.Minimum Opening Balance' | translate }} - {{ settingsPreview?.minRequiredOpeningBalance | formatNumber }} -
- } -
- {{ 'labels.inputs.Enforce Minimum Balance' | translate }} - - {{ settingsPreview?.enforceMinRequiredBalance | yesNo }} - -
- @if ( - settingsPreview?.minRequiredBalance !== undefined && - settingsPreview?.minRequiredBalance !== null && - settingsPreview?.minRequiredBalance !== '' - ) { -
- {{ 'labels.inputs.Minimum Balance' | translate }} - {{ settingsPreview?.minRequiredBalance | formatNumber }} -
- } -
- Balance Frequency - {{ settingsPreview?.balanceFrequency || 'Unassigned' }} -
-
- {{ 'labels.inputs.Apply Withdrawal Fee for Transfers' | translate }} - - {{ settingsPreview?.withdrawalFeeForTransfers | yesNo }} - -
-
- Enable Lock-in Period - - {{ settingsPreview?.enableLockinPeriod || !!settingsPreview?.lockinPeriodFrequency | yesNo }} - -
- @if (settingsPreview?.lockinPeriodFrequency) { -
- {{ 'labels.inputs.Lock-in Period' | translate }} - - {{ settingsPreview?.lockinPeriodFrequency }} - {{ - settingsPreview?.lockinPeriodFrequencyType - | find: currentAccountProductTemplate?.lockinPeriodFrequencyTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} - -
- } -
- Account Closed Zero Balance In Day Closing - - {{ settingsPreview?.accountClosedZeroBalanceInDayClosing | yesNo }} - -
-
- {{ 'labels.inputs.Is Overdraft Allowed' | translate }} - - {{ settingsPreview?.allowOverdraft | yesNo }} - -
- @if (settingsPreview?.allowOverdraft) { -
- Avg Balance Calc Period - {{ settingsPreview?.avgBalanceCalcPeriod || 'Unassigned' }} -
-
- Valid For Period - {{ settingsPreview?.validForPeriod || 'Unassigned' }} -
- @if ( - settingsPreview?.overdraftLimit !== undefined && - settingsPreview?.overdraftLimit !== null && - settingsPreview?.overdraftLimit !== '' - ) { -
- OD Limit % - {{ settingsPreview?.overdraftLimit | formatNumber }} -
- } - @if ( - settingsPreview?.nominalAnnualInterestRateOverdraft !== undefined && - settingsPreview?.nominalAnnualInterestRateOverdraft !== null && - settingsPreview?.nominalAnnualInterestRateOverdraft !== '' - ) { -
- Interest on OD % - {{ - settingsPreview?.nominalAnnualInterestRateOverdraft | formatNumber - }} -
- } -
- OD Account Closed Zero Balance In Day Closing - - {{ settingsPreview?.odAccountClosedZeroBalanceInDayClosing | yesNo }} - -
- } -
-
-
- @if (hasDigitalSection) {
@@ -405,111 +237,6 @@
}
- - @if (digitalPreview?.accountFacilityLimits?.length) { - - - - - - - - - - - @for (row of digitalPreview.accountFacilityLimits; track row.limitType) { - - - - - - - } - -
Limit TypeGeneral LimitMinor Account LimitSmall Saving Limit
{{ row.limitType }}{{ row.generalLimit || 'Unassigned' }}{{ row.minorAccountLimit || 'Unassigned' }}{{ row.smallSavingLimit || 'Unassigned' }}
- } - - @if (digitalPreview?.depositLimitKyc?.general) { - - - - - - - - - - - - - - - - - - - - - - - - - -
Account TypeKYC TypeLimit
General{{ digitalPreview.depositLimitKyc.general.kycType || 'Unassigned' }}{{ digitalPreview.depositLimitKyc.general.limit || 'Unassigned' }}
Minor{{ digitalPreview.depositLimitKyc.minor?.kycType || 'Unassigned' }}{{ digitalPreview.depositLimitKyc.minor?.limit || 'Unassigned' }}
Small Saving{{ digitalPreview.depositLimitKyc.smallSaving?.kycType || 'Unassigned' }}{{ digitalPreview.depositLimitKyc.smallSaving?.limit || 'Unassigned' }}
- } -
-
- } - - - @if (currentAccount?.charges?.length) { -
-
- {{ 'labels.inputs.Charges' | translate }} -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ 'labels.inputs.Name' | translate }}{{ charge.name + ', ' + charge.currency?.displaySymbol }}{{ 'labels.inputs.Type' | translate }} - {{ charge.chargeCalculationType?.value | translateKey: 'catalogs' }} - {{ 'labels.inputs.Amount' | translate }}{{ charge.amount | formatNumber }}{{ 'labels.inputs.Collected On' | translate }}{{ charge.chargeTimeType?.value }}{{ 'labels.inputs.Date' | translate }} - @if ( - charge.chargeTimeType?.value === 'Specified due date' || charge.chargeTimeType?.value === 'Weekly Fee' - ) { - {{ (charge.dueDate | dateFormat) || 'Unassigned' }} - } @else if ( - charge.chargeTimeType?.value === 'Monthly Fee' || charge.chargeTimeType?.value === 'Annual Fee' - ) { - {{ (charge.feeOnMonthDay | dateFormat) || 'Unassigned' }} - } @else { - N/A - } - {{ 'labels.inputs.Repayments Every' | translate }}{{ charge.feeInterval || 'Not Provided' }}
} diff --git a/src/app/current-accounts/current-account-stepper/current-account-term-step/current-account-term-step.component.html b/src/app/current-accounts/current-account-stepper/current-account-term-step/current-account-term-step.component.html index 3dd3b12b0..b98fbacaf 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-term-step/current-account-term-step.component.html +++ b/src/app/current-accounts/current-account-stepper/current-account-term-step/current-account-term-step.component.html @@ -49,13 +49,7 @@ Last Interest Date - + diff --git a/src/app/deposits/recurring-deposits/create-recurring-deposits-account/create-recurring-deposits-account.component.html b/src/app/deposits/recurring-deposits/create-recurring-deposits-account/create-recurring-deposits-account.component.html index 889528426..7431eddc8 100644 --- a/src/app/deposits/recurring-deposits/create-recurring-deposits-account/create-recurring-deposits-account.component.html +++ b/src/app/deposits/recurring-deposits/create-recurring-deposits-account/create-recurring-deposits-account.component.html @@ -34,7 +34,8 @@ - + + {{ 'labels.inputs.DETAILS' | translate }} - - {{ 'labels.inputs.TERMS' | translate }} + + @if (recurringDepositsAccountProductTemplate) { + + Currency - - - + + - - {{ 'labels.inputs.SETTINGS' | translate }} +
+ + +
+
- - -
+ + + {{ 'labels.inputs.INTEREST RATE CHART' | translate }} - - {{ 'labels.inputs.INTEREST RATE CHART' | translate }} + + - - - +
+ + +
+
- - {{ 'labels.inputs.CHARGES' | translate }} - - - - - - @if (recurringDepositAccountFormValid) { + {{ 'labels.inputs.PREVIEW' | translate }} ({ - 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, diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.html b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.html new file mode 100644 index 000000000..568ebc0c2 --- /dev/null +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.html @@ -0,0 +1,82 @@ + + +
+
+ + Currency & Account Status Configuration + From Product +
+
+
+ + Currency + + Inherited from selected product + + + + Decimal Places + + Inherited from selected product + +
+ +
+
+

Balance Swap Option

+

Enable automated transfer of excess funds to linked deposits or vice-versa to maintain liquidity.

+
+ +
+ +
+
+

MSME

+

Micro, Small and Medium Enterprises — mark this account under MSME category.

+
+ +
+ +
+
+

Auto Debit Facility

+

Enable automatic deduction of funds for recurring payments, utility bills, or EMI obligations.

+
+ +
+ +
+
+

Dormancy Alert

+

Enable automated notifications when an account becomes inactive based on product rules.

+
+ + {{ recurringDepositsAccountProductTemplate?.dormancyAlert ? 'Yes' : 'No' }} + +
+ + + +
+
+

Nomination Facility

+

Allow account holders to designate individuals to receive funds in the event of account holder's death.

+
+ + {{ recurringDepositsAccountProductTemplate?.nominationFacility ? 'Yes' : 'No' }} + +
+ + @if (recurringDepositsAccountProductTemplate?.nominationFacility) { + + } +
+
diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.scss b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.scss new file mode 100644 index 000000000..3ed604577 --- /dev/null +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.scss @@ -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; + } +} diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.ts b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.ts new file mode 100644 index 000000000..d793ae8fe --- /dev/null +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.ts @@ -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 } : {}) + }; + } +} diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.html b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.html index 194eeefec..1b01888e6 100644 --- a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.html +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.html @@ -7,7 +7,7 @@ --> - +
@@ -64,7 +64,7 @@
@if (isProductSelected && productTemplate) { - +
@@ -115,101 +115,8 @@
- -
-
- -

Currency & Account Status Configuration

- From Product -
-
- @if (productTemplate.currency) { -
- - Currency - - Default: Indian Rupee - auto-set for domestic products - - - @if ( - productTemplate.currency.decimalPlaces !== undefined && productTemplate.currency.decimalPlaces !== null - ) { - - Decimal Places - - - } -
- } - -
-
-

Dormancy Alert

-

Enable automated notifications when an account becomes inactive based on product rules.

-
-
- yes - no -
-
- -
-
-

Balance Swap Option

-

Enable automated transfer of excess funds to linked deposits or vice-versa to maintain liquidity.

-
-
- yes - no -
-
- -
-
-

MSME

-

Micro, Small and Medium Enterprises

-
-
- yes - no -
-
- -
-
-

Nomination Facility

-

- Allow account holders to designate individuals to receive funds in the event of account holder's death. -

-
-
- yes - no -
-
- -
-
-

Auto Debit Facility

-

Enable automatic deduction of funds for recurring payments, utility bills, or EMI obligations.

-
-
- yes - no -
-
-
-
+ @if (productTemplate?.introducerRequired) { + + } } - -
- - -
diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.scss b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.scss index 45ea9fcab..cd6bfa982 100644 --- a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.scss +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.scss @@ -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; + } +} diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.ts b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.ts index a399f42da..8caea5ac7 100644 --- a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.ts +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.ts @@ -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,7 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { recurringDepositAccountDetailsForm!: UntypedFormGroup; isProductSelected = false; + introducerId: number | null = null; /** Recurring Deposits Account Template with product data */ @Output() recurringDepositsAccountProductTemplate = new EventEmitter(); @@ -106,7 +109,10 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { Validators.required ], fieldOfficerId: [''], - externalId: [''] + externalId: [''], + balanceSwapOption: [false], + msme: [false], + autoDebitFacility: [false] }); } @@ -135,6 +141,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 +196,15 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { /** * Returns recurring deposits account details form value. */ + onIntroducerChange(event: { clientId: number; displayName: string } | null): void { + this.introducerId = event?.clientId ?? null; + } + get recurringDepositAccountDetails() { - return this.recurringDepositAccountDetailsForm.value; + const value = this.recurringDepositAccountDetailsForm.value; + if (this.introducerId) { + value.introducerId = this.introducerId; + } + return value; } } diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-interest-rate-chart-step/recurring-deposits-account-interest-rate-chart-step.component.html b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-interest-rate-chart-step/recurring-deposits-account-interest-rate-chart-step.component.html index c38bb2cc0..28268e478 100644 --- a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-interest-rate-chart-step/recurring-deposits-account-interest-rate-chart-step.component.html +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-interest-rate-chart-step/recurring-deposits-account-interest-rate-chart-step.component.html @@ -207,14 +207,3 @@
- -
- - -
diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component.html b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component.html index 7b64f8833..e8058af74 100644 --- a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component.html +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component.html @@ -249,316 +249,6 @@
- -
-
- {{ 'labels.heading.Terms' | translate }} -
-
-
-
- {{ 'labels.inputs.Nominal Annual Interest' | translate }} - - {{ recurringDepositsAccountProductTemplate?.nominalAnnualInterestRate ?? '—' }} - @if (recurringDepositsAccountProductTemplate?.nominalAnnualInterestRate != null) { - % - } - -
- - @if (recurringDepositsAccountProductTemplate?.effectiveDate) { -
- Effective Date - {{ recurringDepositsAccountProductTemplate?.effectiveDate | dateFormat }} -
- } - -
- {{ 'labels.inputs.Interest Compounding Period' | translate }} - {{ - recurringDepositAccountData?.interestCompoundingPeriodType - | find: recurringDepositsAccountProductTemplate?.interestCompoundingPeriodTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
- -
- {{ 'labels.inputs.Interest Posting Period' | translate }} - {{ - recurringDepositAccountData?.interestPostingPeriodType - | find: recurringDepositsAccountProductTemplate?.interestPostingPeriodTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
- - @if (recurringDepositsAccountProductTemplate?.averagePeriod) { -
- Average Period - {{ recurringDepositsAccountProductTemplate?.averagePeriod }} -
- } - -
- {{ 'labels.inputs.Interest Calculated using' | translate }} - {{ - recurringDepositAccountData?.interestCalculationType - | find: recurringDepositsAccountProductTemplate?.interestCalculationTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
- -
- {{ 'labels.inputs.Days in Year' | translate }} - {{ - recurringDepositAccountData?.interestCalculationDaysInYearType - | find: recurringDepositsAccountProductTemplate?.interestCalculationDaysInYearTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
- - @if ( - recurringDepositsAccountProductTemplate?.autoPost !== undefined && - recurringDepositsAccountProductTemplate?.autoPost !== null - ) { -
- Auto Interest Post - - {{ recurringDepositsAccountProductTemplate?.autoPost | yesNo }} - -
- } - - @if ( - recurringDepositsAccountProductTemplate?.frequencyFixDay !== undefined && - recurringDepositsAccountProductTemplate?.frequencyFixDay !== null - ) { -
- Frequency (Fix Day) - {{ recurringDepositsAccountProductTemplate?.frequencyFixDay }} -
- } - - @if (recurringDepositsAccountProductTemplate?.lastInterestDate) { -
- Last Interest Date - {{ - recurringDepositsAccountProductTemplate?.lastInterestDate | dateFormat - }} -
- } - - @if (recurringDepositsAccountProductTemplate?.interestCalculationTypeName) { -
- Interest Calculation Type - {{ - recurringDepositsAccountProductTemplate?.interestCalculationTypeName - }} -
- } -
-
-
- - -
-
- {{ 'labels.heading.Settings' | translate }} -
-
- -
-
Premature Option
-
-
- Loan Facility - - {{ recurringDepositAccountData?.loanFacility | yesNo }} - -
-
- Premature Withdrawal Option - - {{ recurringDepositAccountData?.prematureWithdrawalOption | yesNo }} - -
-
- Tenure Flexibility - - {{ recurringDepositAccountData?.tenureFlexibility | yesNo }} - -
-
- Lien Marked - - {{ recurringDepositAccountData?.lienMarked | yesNo }} - -
-
- Withdrawal Fee for Transfers - - {{ recurringDepositAccountData?.withdrawalFeeForTransfers | yesNo }} - -
-
-
- - - - -
-
{{ 'labels.inputs.Lock-in Period' | translate }}
-
-
- Enable Lock-in Period - - {{ recurringDepositAccountData?.enableLockinPeriod | yesNo }} - -
- @if (recurringDepositAccountData?.enableLockinPeriod) { -
- Lock-in Frequency - - {{ recurringDepositAccountData?.lockinPeriodFrequency }} - {{ - recurringDepositAccountData?.lockinPeriodFrequencyType - | find: recurringDepositsAccountProductTemplate?.lockinPeriodFrequencyTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} - -
- } -
-
- - - - -
-
Maturity Instructions
-
-
- Maturity Instructions - - {{ recurringDepositAccountData?.maturityInstructions | yesNo }} - -
-
- Credit Maturity Proceeds - - {{ recurringDepositAccountData?.creditMaturityProceeds | yesNo }} - -
-
- Penalty / Grace Period for Missed Installments - - {{ recurringDepositAccountData?.penaltyGracePeriodMissedInstallments | yesNo }} - -
-
- Panel Request Applicable - - {{ recurringDepositAccountData?.panelRequestApplicable | yesNo }} - -
-
- No Penalty for Nominee / Legal Heir Closure - - {{ recurringDepositAccountData?.noPenaltyNomineeHeirClosure | yesNo }} - -
-
- Deceased Settlement - - {{ recurringDepositAccountData?.deceasedSettlement | yesNo }} - -
-
- Allow Debit Txn after Due Date - - {{ recurringDepositAccountData?.allowDebitTxnAfterDueDate | yesNo }} - -
-
-
- - - - -
-
Additional Rules
-
-
- Account Closed with Zero Balance in Day Closing - - {{ recurringDepositAccountData?.accountClosedZeroBalanceInDayClosing | yesNo }} - -
- @if (recurringDepositAccountData?.minBalanceForInterestCalculation) { -
- Min. Balance for Interest Calculation - {{ - recurringDepositAccountData?.minBalanceForInterestCalculation | formatNumber - }} -
- } - @if (recurringDepositAccountData?.mandatoryRecommendedDepositAmount) { -
- {{ 'labels.inputs.Recurring Deposit Amount' | translate }} - {{ - recurringDepositAccountData?.mandatoryRecommendedDepositAmount | formatNumber - }} -
- } -
-
-
-
- @if (_activeRdVersion || recurringDepositsAccountProductTemplate?.accountChart) {
@@ -770,82 +460,6 @@
} - - - @if (recurringDepositAccountData?.charges?.length) { -
-
- {{ 'labels.heading.Charges' | translate }} -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ 'labels.inputs.name' | translate }}{{ charge.name + ', ' + charge.currency?.displaySymbol }}{{ 'labels.inputs.Type' | translate }} - {{ charge.chargeCalculationType?.value | translateKey: 'catalogs' }} - {{ 'labels.inputs.Amount' | translate }}{{ charge.amount | formatNumber }}{{ 'labels.inputs.Collected On' | translate }}{{ charge.chargeTimeType?.value }}{{ 'labels.inputs.Date' | translate }} - - {{ (charge.dueDate | dateFormat) || 'Unassigned' }} - - - {{ (charge.feeOnMonthDay | dateFormat) || 'Unassigned' }} - - - {{ 'labels.inputs.N/A' | translate }} - - {{ 'labels.inputs.Repayments Every' | translate }}{{ charge.feeInterval || 'Not Provided' }}
-
-
- }
diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.ts b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.ts index 7beb38296..25e2a9c41 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.ts +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component.ts @@ -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; } } diff --git a/src/app/savings/create-savings-account/create-savings-account.component.html b/src/app/savings/create-savings-account/create-savings-account.component.html index ffe983360..23f9f93aa 100644 --- a/src/app/savings/create-savings-account/create-savings-account.component.html +++ b/src/app/savings/create-savings-account/create-savings-account.component.html @@ -35,7 +35,7 @@ - + Product Classification - - - Currency + @if (savingsAccountProductTemplate) { + + + Currency - - + + -
- - -
-
+
+ + +
+
- - - Term + + + Digital - - + + -
- - -
-
+
+ + +
+
- - - {{ 'labels.inputs.SETTINGS' | translate }} - - - - -
- - -
-
- - - - Digital - - - - -
- - -
-
- - - - {{ 'labels.inputs.CHARGES' | translate }} - - - - - - - @if (savingsAccountFormValid) { + {{ 'labels.inputs.PREVIEW' | translate }} { this.savingsAccountTemplate = data.savingsAccountTemplate; @@ -88,28 +83,14 @@ 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 }; } @@ -119,15 +100,7 @@ export class CreateSavingsAccountComponent { 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, diff --git a/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.html b/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.html index 10c787094..9dd007828 100644 --- a/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.html +++ b/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.html @@ -36,6 +36,8 @@
+ +

Nomination Facility

@@ -45,5 +47,9 @@ {{ savingsAccountProductTemplate?.nominationFacility ? 'Yes' : 'No' }}
+ + @if (savingsAccountProductTemplate?.nominationFacility) { + + }
diff --git a/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.ts b/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.ts index 553256ce0..31e029241 100644 --- a/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.ts +++ b/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.ts @@ -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; + } } diff --git a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html index df5db2234..60304a70d 100644 --- a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html +++ b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html @@ -67,7 +67,7 @@ - + @if (savingsProductSelected) {
@@ -118,16 +118,9 @@
- } -
- - -
+ @if (productTemplate?.introducerRequired) { + + } + } diff --git a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.scss b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.scss index 7c9847ae8..5744c0823 100644 --- a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.scss +++ b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.scss @@ -15,7 +15,7 @@ $gray-900: #202124; .sad-section { border: 1px solid $border; border-radius: 10px; - overflow: hidden; + overflow: visible; background: #fff; &--mt { diff --git a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.ts b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.ts index e30469624..8585f3dac 100644 --- a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.ts +++ b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.ts @@ -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,7 @@ export class SavingsAccountDetailsStepComponent implements OnInit { savingsAccountDetailsForm: UntypedFormGroup; savingsProductSelected = false; + introducerId: number | null = null; /** Savings Account Template with product data */ @Output() savingsAccountProductTemplate = new EventEmitter(); @@ -165,7 +168,15 @@ export class SavingsAccountDetailsStepComponent implements OnInit { /** * Returns savings account form value. */ + onIntroducerChange(event: { clientId: number; displayName: string } | null): void { + this.introducerId = event?.clientId ?? null; + } + get savingsAccountDetails() { - return this.savingsAccountDetailsForm.getRawValue(); + const value = this.savingsAccountDetailsForm.getRawValue(); + if (this.introducerId) { + value.introducerId = this.introducerId; + } + return value; } } diff --git a/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.html b/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.html index c8bddd8d6..5b0970996 100644 --- a/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.html +++ b/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.html @@ -6,229 +6,209 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -
-
- - - -
Transaction & Product Limits
-
FROM PRODUCT
+
+
+
+ +
+
+

Digital & Account Features

+

Toggle the facilities available for this account (as per RBI guidelines)

+
+ FROM PRODUCT
-
-
-
Account Features (as per RBI guidelines)
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+
+
+
+ ATM Facility + {{ facilityConfig.atmFacility ? 'Enabled' : 'Disabled' }}
+
- @if (accountFacilityLimits.length > 0) { -
- - - - - - - - - - - - - - - - - @for (row of accountFacilityLimits; track row.limitType) { - - - - - - - } - -
Limit TypeGeneral Limit (Daily)Minor Account LimitSmall Saving Limit
{{ row.limitType }}
+
+
+
+ ATM Withdrawal + {{ facilityConfig.atmWithdrawalFacility ? 'Enabled' : 'Disabled' }}
- } + +
- @if (depositLimitKyc?.general) { -
-

Deposit Limit (KYC)

- - - - - - - - - - - - - - - - - - - - - - - - - -
Account TypeKYC TypeLimit
General - -
Minor - -
Small Saving - -
+
+
+
+ UPI Facility + {{ facilityConfig.upiFacility ? 'Enabled' : 'Disabled' }}
- } + +
-
- Note: - 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. +
+
+
+ POS Transactions + {{ facilityConfig.posTransactionsFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Cheque Book + {{ facilityConfig.chequeBook ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ IMPS Facility + {{ facilityConfig.impsFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ SMS Alerts + {{ facilityConfig.smsAlerts ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ BBPS Facility + {{ facilityConfig.bbpsFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Insurance Cover + {{ facilityConfig.insuranceCover ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ BEPG Facility + {{ facilityConfig.bepgFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ NEFT Facility + {{ facilityConfig.neftFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ RTGS Facility + {{ facilityConfig.rtgsFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Inter-Bank Total Cap + {{ facilityConfig.interBankTotalCapFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Withdrawal Facility + {{ facilityConfig.withdrawalFacility ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Deposit Facility (KYC) + {{ facilityConfig.depositFacilityKyc ? 'Enabled' : 'Disabled' }} +
+ +
+ +
+
+
+ Virtual Debit Card + {{ facilityConfig.virtualDebitCard ? 'Enabled' : 'Disabled' }} +
+
diff --git a/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.scss b/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.scss index 7b58a1abb..038aa7fc7 100644 --- a/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.scss +++ b/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.scss @@ -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; + } } diff --git a/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.ts b/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.ts index ae9f82d8e..5c747b548 100644 --- a/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.ts +++ b/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.ts @@ -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 } }; } } diff --git a/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.html b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.html index 0a595f67f..70337bb64 100644 --- a/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.html +++ b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.html @@ -213,198 +213,6 @@
-
-
- {{ 'labels.heading.Terms' | translate }} -
-
-
-
- {{ 'labels.inputs.Nominal Annual Interest' | translate }} - {{ savingsAccount?.nominalAnnualInterestRate | formatNumber }} % -
- @if (savingsAccountProductTemplate?.interestCalculationTypeName) { -
- Interest Calculation Mode - {{ savingsAccountProductTemplate?.interestCalculationTypeName }} -
- } -
- {{ 'labels.inputs.Interest Compounding Period' | translate }} - {{ - savingsAccount?.interestCompoundingPeriodType - | find: savingsAccountProductTemplate?.interestCompoundingPeriodTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
-
- {{ 'labels.inputs.Interest Posting Period' | translate }} - {{ - savingsAccount?.interestPostingPeriodType - | find: savingsAccountProductTemplate?.interestPostingPeriodTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
-
- {{ 'labels.inputs.Interest Calculated using' | translate }} - {{ - savingsAccount?.interestCalculationType - | find: savingsAccountProductTemplate?.interestCalculationTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
-
- {{ 'labels.inputs.Days in Year' | translate }} - {{ - savingsAccount?.interestCalculationDaysInYearType - | find: savingsAccountProductTemplate?.interestCalculationDaysInYearTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} -
- -
- Interest Calculation Selector - {{ - savingsAccount?.interestCalculateSelector - | find: savingsAccountProductTemplate?.interestCalculationTypeOption : 'id' : 'value' - | translateKey: 'catalogs' - }} -
- @if (savingsAccount?.averagePeriod) { -
- Average Period - {{ savingsAccount?.averagePeriod }} -
- } -
- Auto Post - - {{ savingsAccount?.autoPost | yesNo }} - -
- @if (savingsAccountProductTemplate?.lastInterestDate) { -
- Last Interest Date - {{ savingsAccountProductTemplate?.lastInterestDate | dateFormat }} -
- } - @if (savingsAccount?.frequencyFixDay !== undefined && savingsAccount?.frequencyFixDay !== null) { -
- Frequency Fix Day - {{ savingsAccount?.frequencyFixDay }} -
- } -
-
-
- -
-
- {{ 'labels.heading.Settings' | translate }} -
-
-
- @if (savingsAccount?.minRequiredOpeningBalance) { -
- {{ 'labels.inputs.Minimum Opening Balance' | translate }} - {{ savingsAccount?.minRequiredOpeningBalance | formatNumber }} -
- } -
- {{ 'labels.inputs.Apply Withdrawal Fee for Transfers' | translate }} - - {{ savingsAccount?.withdrawalFeeForTransfers | yesNo }} - -
- @if (savingsAccount?.lockinPeriodFrequency) { -
- {{ 'labels.inputs.Lock-in Period' | translate }} - - {{ savingsAccount?.lockinPeriodFrequency }} - {{ - savingsAccount?.lockinPeriodFrequencyType - | find: savingsAccountProductTemplate?.lockinPeriodFrequencyTypeOptions : 'id' : 'value' - | translateKey: 'catalogs' - }} - -
- } -
- {{ 'labels.inputs.Is Overdraft Allowed' | translate }} - - {{ savingsAccount?.allowOverdraft | yesNo }} - -
- @if (savingsAccount?.minOverdraftForInterestCalculation) { -
- {{ - 'labels.inputs.Minimum Overdraft Required for Interest Calculation' | translate - }} - {{ savingsAccount?.minOverdraftForInterestCalculation | formatNumber }} -
- } - @if (savingsAccount?.nominalAnnualInterestRateOverdraft) { -
- {{ 'labels.inputs.Nominal Annual Interest for Overdraft' | translate }} - {{ savingsAccount?.nominalAnnualInterestRateOverdraft | formatNumber }} % -
- } - @if (savingsAccount?.overdraftLimit) { -
- {{ 'labels.inputs.Maximum Overdraft Amount Limit' | translate }} - {{ savingsAccount?.overdraftLimit | formatNumber }} -
- } -
- {{ 'labels.inputs.Enforce Minimum Balance' | translate }} - - {{ savingsAccount?.enforceMinRequiredBalance | yesNo }} - -
- @if (savingsAccountProductTemplate?.enableLockinPeriod !== undefined) { -
- Enable Lock-in Period - - {{ savingsAccountProductTemplate?.enableLockinPeriod | yesNo }} - -
- } - @if (savingsAccountProductTemplate?.balanceFrequency) { -
- Balance Frequency - {{ savingsAccountProductTemplate?.balanceFrequency }} -
- } - @if (savingsAccountProductTemplate?.accountClosedZeroBalanceInDayClosing !== undefined) { -
- Account Closed Zero Balance In Day Closing - - {{ savingsAccountProductTemplate?.accountClosedZeroBalanceInDayClosing | yesNo }} - -
- } - @if (savingsAccount?.minRequiredBalance) { -
- {{ 'labels.inputs.Minimum Balance' | translate }} - {{ savingsAccount?.minRequiredBalance }} -
- } -
-
-
- @if (hasDigitalSection) {
@@ -424,58 +232,6 @@
}
- - @if (digitalPreview?.accountFacilityLimits?.length) { - - - - - - - - - - - @for (row of digitalPreview.accountFacilityLimits; track row.limitType) { - - - - - - - } - -
Limit TypeGeneral LimitMinor Account LimitSmall Saving Limit
{{ row.limitType }}{{ row.generalLimit || 'Unassigned' }}{{ row.minorAccountLimit || 'Unassigned' }}{{ row.smallSavingLimit || 'Unassigned' }}
- } - - @if (digitalPreview?.depositLimitKyc?.general) { - - - - - - - - - - - - - - - - - - - - - - - - - -
Account TypeKYC TypeLimit
General{{ digitalPreview.depositLimitKyc.general.kycType || 'Unassigned' }}{{ digitalPreview.depositLimitKyc.general.limit || 'Unassigned' }}
Minor{{ digitalPreview.depositLimitKyc.minor?.kycType || 'Unassigned' }}{{ digitalPreview.depositLimitKyc.minor?.limit || 'Unassigned' }}
Small Saving{{ digitalPreview.depositLimitKyc.smallSaving?.kycType || 'Unassigned' }}{{ digitalPreview.depositLimitKyc.smallSaving?.limit || 'Unassigned' }}
- }
} @@ -501,65 +257,6 @@
} - - @if (savingsAccount?.charges?.length) { -
-
- {{ 'labels.inputs.Charges' | translate }} -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ 'labels.inputs.Name' | translate }}{{ charge.name + ', ' + charge.currency.displaySymbol }}{{ 'labels.inputs.Type' | translate }} - {{ charge.chargeCalculationType.value | translateKey: 'catalogs' }} - {{ 'labels.inputs.Amount' | translate }}{{ charge.amount | formatNumber }}{{ 'labels.inputs.Date' | translate }} - @if ( - charge.chargeTimeType.value === 'Specified due date' || charge.chargeTimeType.value === 'Weekly Fee' - ) { - {{ (charge.dueDate | dateFormat) || 'Unassigned' }} - } - @if (charge.chargeTimeType.value === 'Monthly Fee' || charge.chargeTimeType.value === 'Annual Fee') { - {{ (charge.feeOnMonthDay | dateFormat) || 'Unassigned' }} - } - @if ( - !( - charge.chargeTimeType.value === 'Monthly Fee' || - charge.chargeTimeType.value === 'Annual Fee' || - charge.chargeTimeType.value === 'Specified due date' || - charge.chargeTimeType.value === 'Weekly Fee' - ) - ) { - N/A - } - {{ 'labels.inputs.Repayments Every' | translate }}{{ charge.feeInterval || 'Not Provided' }}{{ 'labels.inputs.Collected On' | translate }}{{ charge.chargeTimeType.value }}
-
-
- }
diff --git a/src/app/savings/savings-account-stepper/savings-account-term-step/savings-account-term-step.component.html b/src/app/savings/savings-account-stepper/savings-account-term-step/savings-account-term-step.component.html index a90dfeba3..b7592f93b 100644 --- a/src/app/savings/savings-account-stepper/savings-account-term-step/savings-account-term-step.component.html +++ b/src/app/savings/savings-account-stepper/savings-account-term-step/savings-account-term-step.component.html @@ -144,13 +144,7 @@ Last Interest Date - + diff --git a/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.html b/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.html new file mode 100644 index 000000000..5aba5ae30 --- /dev/null +++ b/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.html @@ -0,0 +1,308 @@ + + +@if (shouldShow) { +
+ +
+
+
+ +
+
+
+ {{ isProductMode ? 'Dormancy Monitoring Configuration' : 'Dormancy Risk Monitor' }} +
+
+ @if (isProductMode) { + Product-level dormancy thresholds applied to this account + } @else { + Real-time dormancy tracking · Based on last customer transaction + } +
+
+
+ + +
+ + + @if (isProductMode) { + Monitoring: {{ riskSeverity }} + } @else { + Dormancy Risk: {{ riskSeverity }} + } + + @if (!isProductMode && isMonitoringActive) { + Monitoring Active + } +
+
+ + +
+ @if (isProductMode) { + + + +
+ Account Status + New Account + No transactions yet +
+ + +
+ + Dormancy Monitoring + + + + + {{ isMonitoringActive ? 'Enabled' : 'Disabled' }} + + {{ + isMonitoringActive ? 'Transaction activity tracked' : 'Not configured' + }} +
+ + +
+ + Alert Notifications + + + + + {{ isAlertEnabled ? 'Enabled' : 'Disabled' }} + + {{ isAlertEnabled ? 'Branch will be notified' : 'No alerts configured' }} +
+ + +
+ + Inactive Threshold + + + @if (daysToInactive !== null) { + {{ daysToInactive }}days + No transaction for {{ daysToInactive }} days + } @else { + Not configured + } +
+ + +
+ + Dormancy Threshold + + + @if (daysToDormancy !== null) { + {{ daysToDormancy }}days + {{ daysToDormancy }} days after inactive + } @else { + Not configured + } +
+ + +
+ + Escheat Threshold + + + @if (daysToEscheat !== null) { + {{ daysToEscheat }}days + {{ daysToEscheat }} days after dormant + } @else { + Not configured + } +
+ } @else { + + + +
+ Account Status + + {{ statusLabel }} + + + @if (statusId === 0) { + Currently active + } + @if (statusId === 100) { + No recent transactions + } + @if (statusId === 200) { + Classified as dormant + } + @if (statusId === 300) { + Pending regulatory transfer + } + +
+ + +
+ Last Transaction + @if (lastTxnDate) { + {{ lastTxnDate }} + Most recent customer activity + } @else if (accountOpenedDate) { + {{ accountOpenedDate }} + Account opened · No transactions yet + } @else { + No transactions yet + } +
+ + +
+ + Days to Inactive + + + @if (statusId >= 100) { + Threshold Reached + Account is inactive + } @else if (daysToInactive !== null) { + + {{ daysToInactive }}days + + {{ inactiveSubtext() }} + } @else { + — + } +
+ + +
+ + Days to Dormant + + + @if (statusId >= 200) { + Threshold Reached + Account is dormant + } @else if (daysToDormancy !== null) { + + {{ daysToDormancy }}days + + {{ dormancySubtext() }} + } @else { + — + } +
+ + +
+ + Days to Escheat + + + @if (statusId >= 300) { + Threshold Reached + Pending regulatory transfer + } @else if (daysToEscheat !== null) { + + {{ daysToEscheat }}days + + {{ escheatSubtext() }} + } @else { + — + } +
+ } +
+ + +
+
+ Dormancy Progression + @if (!isProductMode) { + Timeline based on last customer transaction + } +
+ +
+ +
+
+ +
+ Active + Normal +
+ + @for (step of timelineSteps; track step.label) { +
+
+
+ @if (step.reached && !step.current) { + + } @else if (step.current) { + + } @else { + + } +
+ + {{ step.label }} + + + @if (!step.reached && step.days !== null) { + {{ step.days }}d + } @else if (step.reached) { + Reached + } +
+ } +
+
+
+} diff --git a/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.scss b/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.scss new file mode 100644 index 000000000..4cb26e9bc --- /dev/null +++ b/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.scss @@ -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; + } +} diff --git a/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.ts b/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.ts new file mode 100644 index 000000000..2797494ad --- /dev/null +++ b/src/app/shared/dormancy-alert-panel/dormancy-alert-panel.component.ts @@ -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'; + } +} diff --git a/src/app/shared/introducer-search/introducer-search.component.html b/src/app/shared/introducer-search/introducer-search.component.html new file mode 100644 index 000000000..d7663ef85 --- /dev/null +++ b/src/app/shared/introducer-search/introducer-search.component.html @@ -0,0 +1,130 @@ + + +
+
+ + Introducer + Required +
+ +
+ @if (!selectedClient) { + +
+ + Search by Name or Client ID + + search + + + @if (searching) { +
+
+ + Searching... +
+
+ } + + @if (!searching && searchResults.length > 0) { +
+ @for (r of searchResults; track r.id) { +
+ person +
+ {{ r.displayName }} + + ID: {{ r.id }} + @if (r.officeName) { + · {{ r.officeName }} + } + @if (r.mobileNo) { + · {{ r.mobileNo }} + } + +
+
+ } +
+ } + + @if (!searching && searchText.length >= 2 && searchResults.length === 0) { +
No clients found
+ } +
+ } + + @if (selectedClient) { + +
+
+ @if (linkedProfileImage) { + Profile + } @else { + person + } +
+
+ {{ selectedClient.displayName }} + + Client ID: {{ selectedClient.id }} + @if (selectedClient.accountNumber) { + · A/C: {{ selectedClient.accountNumber }} + } + +
+ +
+ + @if (fetchingLinkedData) { +
+ + Fetching client details... +
+ } + + @if (!fetchingLinkedData) { +
+
+ FULL NAME + {{ selectedClient.displayName }} +
+
+ DATE OF BIRTH + {{ clientDob || '—' }} +
+ @if (clientAadhaar) { +
+ AADHAAR + + {{ clientAadhaar }} + @if (linkedKycStatus?.aadhaarVerified) { + verified Verified + } + +
+ } + @if (clientPan) { +
+ PAN + + {{ clientPan }} + @if (linkedKycStatus?.panVerified) { + verified Verified + } + +
+ } +
+ } + } +
+
diff --git a/src/app/shared/introducer-search/introducer-search.component.scss b/src/app/shared/introducer-search/introducer-search.component.scss new file mode 100644 index 000000000..4b882d3cd --- /dev/null +++ b/src/app/shared/introducer-search/introducer-search.component.scss @@ -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; + } +} diff --git a/src/app/shared/introducer-search/introducer-search.component.ts b/src/app/shared/introducer-search/introducer-search.component.ts new file mode 100644 index 000000000..fa0410d78 --- /dev/null +++ b/src/app/shared/introducer-search/introducer-search.component.ts @@ -0,0 +1,138 @@ +/** + * 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 } | 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 }); + } + + 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: () => {} + }); + } +} diff --git a/src/app/shared/nominee-form/nominee-form.component.html b/src/app/shared/nominee-form/nominee-form.component.html new file mode 100644 index 000000000..49bd32d8a --- /dev/null +++ b/src/app/shared/nominee-form/nominee-form.component.html @@ -0,0 +1,282 @@ + + +
+ +
+
+ + Nomination Facility +
+
+ + +
+
+ +
+ + @if (mode === 'manual') { +
+ +
+ + Full Name * + + Full name is required + + + + Relationship * + + @for (rel of relationships; track rel) { + {{ rel }} + } + + Relationship is required + +
+ +
+ + Date of Birth + + + + + + + Nationality + + Indian + NRI + Foreign National + + +
+ + + + +
+ + Aadhaar Number + + + + + PAN Number + + +
+ +
+ + Mobile Number + + + + + Nominee Share (%) + + +
+ + + + +
+ + Guardian Name + + + + + Guardian Relationship + + @for (rel of relationships; track rel) { + {{ rel }} + } + + +
+ + +
+ Is Alive + Is Dependent +
+ + + + Remarks + + +
+ } + + + @if (mode === 'linked') { + @if (!selectedClient) { + +
+ + Search by Name or Client ID + + search + + + @if (searching) { +
+
+ + Searching... +
+
+ } + + @if (!searching && searchResults.length > 0) { +
+ @for (r of searchResults; track r.id) { +
+ person +
+ {{ r.displayName }} + + ID: {{ r.id }} + @if (r.officeName) { + · {{ r.officeName }} + } + @if (r.mobileNo) { + · {{ r.mobileNo }} + } + +
+
+ } +
+ } + + @if (!searching && searchText.length >= 2 && searchResults.length === 0) { +
No clients found
+ } +
+ } + + @if (selectedClient) { + +
+
+ @if (linkedProfileImage) { + Profile + } @else { + person + } +
+
+ {{ selectedClient.displayName }} + + Client ID: {{ selectedClient.id }} + @if (selectedClient.accountNumber) { + · A/C: {{ selectedClient.accountNumber }} + } + +
+ +
+ + @if (fetchingLinkedData) { +
+ + Fetching nominee details... +
+ } + + @if (!fetchingLinkedData) { +
+
+ Full Name + {{ selectedClient.displayName }} +
+
+ Date of Birth + {{ clientDob || '—' }} +
+ @if (clientAadhaar) { +
+ Aadhaar + + {{ clientAadhaar }} + @if (linkedKycStatus?.aadhaarVerified) { + verified Verified + } + +
+ } + @if (clientPan) { +
+ PAN + + {{ clientPan }} + @if (linkedKycStatus?.panVerified) { + verified Verified + } + +
+ } +
+ + +
+ + Relationship * + + @for (rel of relationships; track rel) { + {{ rel }} + } + + + + + Nominee Share (%) + + +
+ } + } + } +
+
diff --git a/src/app/shared/nominee-form/nominee-form.component.scss b/src/app/shared/nominee-form/nominee-form.component.scss new file mode 100644 index 000000000..edfa9a96f --- /dev/null +++ b/src/app/shared/nominee-form/nominee-form.component.scss @@ -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; + } +} diff --git a/src/app/shared/nominee-form/nominee-form.component.ts b/src/app/shared/nominee-form/nominee-form.component.ts new file mode 100644 index 000000000..594692183 --- /dev/null +++ b/src/app/shared/nominee-form/nominee-form.component.ts @@ -0,0 +1,254 @@ +/** + * 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; +} + +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(); + + 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 = ''; + 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 }); + } + + 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, + relationship: rel, + nomineeShare: this.linkedShare + }); + } + } + + private clearLinked(): void { + this.selectedClient = null; + this.linkedProfileImage = null; + this.linkedKycStatus = null; + this.clientDob = ''; + this.clientAadhaar = ''; + this.clientPan = ''; + 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: () => {} + }); + } +} diff --git a/src/environments/.env.ts b/src/environments/.env.ts index c31d3d90c..9feeaaf07 100644 --- a/src/environments/.env.ts +++ b/src/environments/.env.ts @@ -5,13 +5,12 @@ * 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/. */ - // IMPORTANT: THIS FILE IS AUTO GENERATED! DO NOT MANUALLY EDIT OR CHECKIN! /* tslint:disable */ export default { 'mifos_x': { - 'version': '260422', - 'hash': 'e9a93771' + 'version': '260424', + 'hash': 'fc1c5126' }, 'allow_switching_backend_instance': true };