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..6a81d16b6 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 }} diff --git a/src/app/current-accounts/create-current-account/create-current-account.component.scss b/src/app/current-accounts/create-current-account/create-current-account.component.scss index 4d4789083..94c2d9d26 100644 --- a/src/app/current-accounts/create-current-account/create-current-account.component.scss +++ b/src/app/current-accounts/create-current-account/create-current-account.component.scss @@ -27,11 +27,14 @@ .mat-step-content { padding: 1.5rem; margin: 1rem 0; + overflow: visible !important; } } // Form field spacing within steps :host ::ng-deep mat-step-content { + overflow: visible !important; + mat-form-field { margin-bottom: 1rem; width: 100%; diff --git a/src/app/current-accounts/create-current-account/create-current-account.component.ts b/src/app/current-accounts/create-current-account/create-current-account.component.ts index dbb8867dc..1329e5312 100644 --- a/src/app/current-accounts/create-current-account/create-current-account.component.ts +++ b/src/app/current-accounts/create-current-account/create-current-account.component.ts @@ -12,10 +12,6 @@ import { Router, ActivatedRoute } from '@angular/router'; /** Custom Components */ import { CurrentAccountDetailsStepComponent } from '../current-account-stepper/current-account-details-step/current-account-details-step.component'; -import { CurrentAccountTermsStepComponent } from '../current-account-stepper/current-account-terms-step/current-account-terms-step.component'; -import { CurrentAccountTermStepComponent } from '../current-account-stepper/current-account-term-step/current-account-term-step.component'; -import { CurrentAccountSettingsStepComponent } from '../current-account-stepper/current-account-settings-step/current-account-settings-step.component'; -import { CurrentAccountChargesStepComponent } from '../current-account-stepper/current-account-charges-step/current-account-charges-step.component'; import { CurrentAccountCurrencyStepComponent } from '../current-account-stepper/current-account-currency-step/current-account-currency-step.component'; import { CurrentAccountDigitalStepComponent } from '../current-account-stepper/current-account-digital-step/current-account-digital-step.component'; @@ -23,10 +19,18 @@ import { CurrentAccountDigitalStepComponent } from '../current-account-stepper/c import { CurrentAccountsService } from '../current-accounts.service'; import { SettingsService } from 'app/settings/settings.service'; import { Dates } from 'app/core/utils/dates'; -import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper'; +import { + MatStepper, + MatStepperIcon, + MatStep, + MatStepLabel, + MatStepperNext, + MatStepperPrevious +} from '@angular/material/stepper'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { CurrentAccountPreviewStepComponent } from '../current-account-stepper/current-account-preview-step/current-account-preview-step.component'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { FormGroup } from '@angular/forms'; /** * Create Current Account Component @@ -39,16 +43,14 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; ...STANDALONE_SHARED_IMPORTS, MatStepper, MatStepperIcon, + MatStepperNext, + MatStepperPrevious, FaIconComponent, MatStep, MatStepLabel, CurrentAccountDetailsStepComponent, CurrentAccountCurrencyStepComponent, - CurrentAccountTermStepComponent, - CurrentAccountSettingsStepComponent, CurrentAccountDigitalStepComponent, - CurrentAccountTermsStepComponent, - CurrentAccountChargesStepComponent, CurrentAccountPreviewStepComponent ] }) @@ -64,21 +66,14 @@ export class CreateCurrentAccountComponent { /** Current Account Product Template */ currentAccountProductTemplate: any; - /** Current Account Details Step */ @ViewChild(CurrentAccountDetailsStepComponent, { static: true }) currentAccountDetailsStep: CurrentAccountDetailsStepComponent; - /** Current Account Term Step (new) */ - @ViewChild(CurrentAccountTermStepComponent, { static: true }) - currentAccountTermStep: CurrentAccountTermStepComponent; - /** Current Account Settings Step */ - @ViewChild(CurrentAccountSettingsStepComponent, { static: true }) - currentAccountSettingsStep: CurrentAccountSettingsStepComponent; - /** Current Account Terms Step (old — kept for currencyCode access) */ - @ViewChild(CurrentAccountTermsStepComponent, { static: true }) - currentAccountTermsStep: CurrentAccountTermsStepComponent; - /** Current Account Charges Step */ - @ViewChild(CurrentAccountChargesStepComponent, { static: true }) - currentAccountChargesStep: CurrentAccountChargesStepComponent; + + @ViewChild(CurrentAccountCurrencyStepComponent) + currentAccountCurrencyStep: CurrentAccountCurrencyStepComponent; + + @ViewChild(CurrentAccountDigitalStepComponent) + currentAccountDigitalStep: CurrentAccountDigitalStepComponent; constructor() { this.route.data.subscribe((data: { currentAccountTemplate: any }) => { @@ -100,43 +95,36 @@ export class CreateCurrentAccountComponent { return this.currentAccountDetailsStep.currentAccountDetailsForm; } - /** - * Retrieves new term form (interest rate). - */ - get currentAccountTermForm() { - return this.currentAccountTermStep.currentAccountTermForm; + /** Stub forms needed by preview step inputs */ + get currentAccountTermForm(): FormGroup { + return new FormGroup({}); } - /** - * Retrieves settings form. - */ - get currentAccountSettingsForm() { - return this.currentAccountSettingsStep.currentAccountSettingsForm; + get currentAccountSettingsForm(): FormGroup { + return new FormGroup({}); } - /** - * Retrieves old terms form — still used for currencyCode by charges step. - */ - get currentAccountTermsForm() { - return this.currentAccountTermsStep.currentAccountTermsForm; + get currentAccountTermsForm(): FormGroup { + return new FormGroup({}); } - /** - * Checks validity of overall current account form. - */ - get currentAccountFormValid() { - return this.currentAccountDetailsForm.valid && this.currentAccountTermForm.valid; - } - - /** - * Retrieves current account object. - */ get currentAccount() { + return { ...this.currentAccountDetailsStep.currentAccountDetails }; + } + + get currentAccountDigital() { + return this.currentAccountDigitalStep?.currentAccountDigital ?? null; + } + + get currentAccountNominee() { + return this.currentAccountCurrencyStep?.nomineeData ?? null; + } + + get currentAccountCurrencyFlags() { return { - ...this.currentAccountDetailsStep.currentAccountDetails, - ...this.currentAccountTermStep.currentAccountTerms, - ...this.currentAccountSettingsStep.currentAccountSettings, - ...this.currentAccountChargesStep.currentAccountCharges + balanceSwapOption: this.currentAccountCurrencyStep?.balanceSwapOption ?? false, + msme: this.currentAccountCurrencyStep?.msme ?? false, + autoDebitFacility: this.currentAccountCurrencyStep?.autoDebitFacility ?? false }; } @@ -149,15 +137,8 @@ export class CreateCurrentAccountComponent { const monthDayFormat = 'dd MMMM'; const currentAccount = { ...this.currentAccount, - charges: this.currentAccount.charges.map((charge: any) => ({ - chargeId: charge.id, - amount: charge.amount, - dueDate: charge.dueDate ? this.dateUtils.formatDate(charge.dueDate, dateFormat) : charge.dueDate, - feeOnMonthDay: charge.feeOnMonthDay - ? this.dateUtils.formatDate(charge.feeOnMonthDay, monthDayFormat) - : charge.feeOnMonthDay, - feeInterval: charge.feeInterval - })), + ...this.currentAccountCurrencyStep.currencyStepData, + charges: [], submittedOnDate: this.dateUtils.formatDate(this.currentAccount.submittedOnDate, dateFormat), dateFormat, monthDayFormat, 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..935e312dd 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

@@ -35,26 +59,9 @@ {{ currentAccountProductTemplate?.dormancyAlert ? 'Yes' : 'No' }}
- -
-
-

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' }} - -
+ @if (currentAccountProductTemplate?.dormancyAlert) { + + }
@@ -65,15 +72,8 @@ {{ currentAccountProductTemplate?.nominationFacility ? 'Yes' : 'No' }}
- -
-
-

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/add-beneficiary-dialog/add-beneficiary-dialog.component.html b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.html new file mode 100644 index 000000000..1b0c61fc7 --- /dev/null +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.html @@ -0,0 +1,190 @@ + + + +
+
+ person_pin +
+
+

Add Beneficiary / Nominee

+

Register a nominee for this account as per Indian banking norms

+
+ +
+ + +
+ +
+ + +
+ + + @if (mode === 'search') { +
+ + Search Client by Name / CIF + search + + @if (isSearching) { + + } + @if (selectedClient) { + + } + + + @if (searchResults.length > 0) { +
+ @for (client of searchResults; track client) { +
+
{{ (client.displayName || '?')[0].toUpperCase() }}
+
+ {{ client.displayName }} + + @if (client.cifNumber) { + {{ client.cifNumber }} + } + @if (client.officeName) { + {{ client.officeName }} + } + +
+ chevron_right +
+ } +
+ } + + @if (selectedClient) { +
+
{{ (selectedClient.displayName || '?')[0].toUpperCase() }}
+
+ {{ selectedClient.displayName }} + {{ selectedClient.cifNumber || selectedClient.accountNumber }} +
+ Linked +
+ } +
+ } + + + + Nominee / Beneficiary Name * + + Name is required + + + +
+ + Relationship * + + @for (r of relationships; track r) { + {{ r }} + } + + Relationship is required + + + + Date of Birth + + + + +
+ + +
+ + Share % * + + % + Enter 1–100 + +
+ @for (p of [25, 33, 50, 100]; track p) { + + } +
+
+ + +
+ + Nominee is a Minor (below 18 years) + +
+ + + @if (isMinor) { +
+
+ supervisor_account + Guardian Details +
+
+ + Guardian Name + + + + Guardian Relationship + + @for (r of guardianRelationships; track r) { + {{ r }} + } + + +
+
+ } +
+
+ + + + + diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.scss b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.scss new file mode 100644 index 000000000..4c9e71b0c --- /dev/null +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.scss @@ -0,0 +1,415 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +$primary: #1a56db; +$primary-light: #eff6ff; +$primary-border: #bfdbfe; +$success: #057a55; +$success-light: #f3faf7; +$success-border: #84e1bc; +$orange: #d03801; +$orange-light: #fff8f1; +$orange-border: #fcd9bd; +$border: #e5e7eb; +$text: #111827; +$muted: #6b7280; +$radius: 10px; + +mifosx-add-beneficiary-dialog { + display: block; +} + +// Header +.abd-header { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 18px 20px 14px; + border-bottom: 1px solid $border; + background: linear-gradient(135deg, #f0f7ff 0%, #fff 60%); + + &__icon { + width: 42px; + height: 42px; + border-radius: 10px; + background: $primary; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + mat-icon { + color: #fff; + font-size: 22px; + width: 22px; + height: 22px; + } + } + + &__text { + flex: 1; + } + + &__title { + margin: 0; + font-size: 15px; + font-weight: 700; + color: $text; + } + + &__sub { + margin: 3px 0 0; + font-size: 11.5px; + color: $muted; + } + + &__close { + color: $muted; + margin: -4px -8px 0 0; + } +} + +// Form +.abd-form { + display: flex; + flex-direction: column; + gap: 0; + width: 500px; + max-width: 100%; + padding: 4px 0; +} + +.abd-full-width { + width: 100%; +} + +.abd-mt { + margin-top: 8px; +} + +// Mode toggle +.abd-mode-toggle { + display: flex; + gap: 8px; + margin: 14px 0 10px; + background: #f9fafb; + border: 1px solid $border; + border-radius: 10px; + padding: 5px; +} + +.abd-mode-btn { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 8px 14px; + border-radius: 8px; + border: 1.5px solid transparent; + background: transparent; + font-size: 13px; + font-weight: 600; + color: $muted; + cursor: pointer; + transition: all 0.15s; + + mat-icon { + font-size: 16px; + width: 16px; + height: 16px; + } + + &--active { + background: #fff; + border-color: $primary-border; + color: $primary; + box-shadow: 0 1px 4px rgb(0 0 0 / 8%); + } +} + +// Search +.abd-search-section { + position: relative; +} + +.abd-prefix-icon { + font-size: 18px; + width: 18px; + height: 18px; + color: $muted; + margin-right: 4px; +} + +.abd-search-results { + position: absolute; + z-index: 200; + top: calc(100% - 8px); + left: 0; + right: 0; + background: #fff; + border: 1px solid $border; + border-radius: $radius; + box-shadow: 0 8px 24px rgb(0 0 0 / 12%); + max-height: 220px; + overflow-y: auto; +} + +.abd-result-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + cursor: pointer; + border-bottom: 1px solid #f3f4f6; + transition: background 0.1s; + + &:last-child { + border-bottom: none; + } + + &:hover { + background: $primary-light; + } +} + +.abd-result-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background: linear-gradient(135deg, #6366f1, $primary); + color: #fff; + font-size: 13px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.abd-result-body { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; +} + +.abd-result-name { + font-size: 13px; + font-weight: 600; + color: $text; +} + +.abd-result-meta { + display: flex; + gap: 5px; + flex-wrap: wrap; +} + +.abd-chip { + font-size: 10px; + padding: 1px 7px; + border-radius: 999px; + background: #f3f4f6; + color: $muted; + font-weight: 500; +} + +.abd-result-arrow { + font-size: 18px; + width: 18px; + height: 18px; + color: $primary; + opacity: 0.4; + transition: all 0.1s; + + .abd-result-item:hover & { + opacity: 1; + transform: translateX(2px); + } +} + +// Selected card +.abd-selected-card { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + background: $success-light; + border: 1.5px solid $success-border; + border-radius: $radius; + margin-top: 6px; +} + +.abd-selected-avatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: linear-gradient(135deg, #10b981, $success); + color: #fff; + font-size: 14px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.abd-selected-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; +} + +.abd-selected-name { + font-size: 13px; + font-weight: 700; + color: #065f46; +} + +.abd-selected-sub { + font-size: 11px; + color: #047857; +} + +.abd-badge { + font-size: 10px; + font-weight: 700; + padding: 3px 10px; + border-radius: 999px; + text-transform: uppercase; + letter-spacing: 0.04em; + + &--linked { + background: $success; + color: #fff; + } +} + +// Row layout +.abd-row { + display: flex; + gap: 12px; + margin-top: 8px; + + @media (width <= 500px) { + flex-direction: column; + gap: 0; + } +} + +.abd-flex { + flex: 1; +} + +// Share % +.abd-share-row { + display: flex; + align-items: flex-start; + gap: 12px; + margin-top: 8px; +} + +.abd-share-field { + flex: 0 0 150px; +} + +.abd-pct { + font-size: 15px; + font-weight: 600; + color: $muted; + margin-right: 4px; +} + +.abd-quick-pct { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding-top: 10px; +} + +.abd-pct-chip { + padding: 5px 12px; + border-radius: 8px; + border: 1.5px solid $border; + background: #fff; + font-size: 12px; + font-weight: 600; + color: $muted; + cursor: pointer; + transition: all 0.12s; + + &:hover { + border-color: $primary; + background: $primary-light; + color: $primary; + } +} + +// Required star +.abd-required { + color: #e02424; +} + +// Minor toggle +.abd-minor-toggle { + margin-top: 14px; + padding: 12px 14px; + background: #fffbeb; + border: 1px solid #fde68a; + border-radius: 8px; +} + +// Guardian box +.abd-guardian-box { + margin-top: 10px; + padding: 14px; + background: $orange-light; + border: 1.5px solid $orange-border; + border-radius: $radius; +} + +.abd-guardian-label { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + font-weight: 700; + color: $orange; + text-transform: uppercase; + letter-spacing: 0.04em; + margin-bottom: 10px; + + mat-icon { + font-size: 16px; + width: 16px; + height: 16px; + color: $orange; + } +} + +// Actions +.abd-actions { + padding: 12px 20px 16px !important; + border-top: 1px solid $border; + gap: 10px; + justify-content: flex-end !important; +} + +.abd-btn-add { + border-radius: 8px !important; + padding: 0 18px !important; + gap: 4px; + + mat-icon { + font-size: 18px; + width: 18px; + height: 18px; + } +} diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.ts b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.ts new file mode 100644 index 000000000..bd691bbdf --- /dev/null +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.ts @@ -0,0 +1,167 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, inject, OnDestroy, ViewEncapsulation } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; +import { MatDialogRef, MatDialogContent, MatDialogActions } from '@angular/material/dialog'; +import { MatProgressSpinner } from '@angular/material/progress-spinner'; +import { MatIcon } from '@angular/material/icon'; +import { MatSlideToggle } from '@angular/material/slide-toggle'; +import { ClientsService } from 'app/clients/clients.service'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; + +@Component({ + selector: 'mifosx-add-beneficiary-dialog', + templateUrl: './add-beneficiary-dialog.component.html', + styleUrl: './add-beneficiary-dialog.component.scss', + encapsulation: ViewEncapsulation.None, + imports: [ + ...STANDALONE_SHARED_IMPORTS, + MatDialogContent, + MatDialogActions, + MatProgressSpinner, + MatIcon, + MatSlideToggle, + ReactiveFormsModule + ] +}) +export class AddBeneficiaryDialogComponent implements OnDestroy { + private fb = inject(UntypedFormBuilder); + private clientsService = inject(ClientsService); + dialogRef = inject(MatDialogRef); + + /** Toggle between linked client and manual entry */ + mode: 'search' | 'manual' = 'search'; + + form: UntypedFormGroup = this.fb.group({ + searchText: [''], + name: [ + '', + Validators.required + ], + relationship: [ + '', + Validators.required + ], + dateOfBirth: [null], + sharePercentage: [ + null, + [ + Validators.required, + Validators.min(1), + Validators.max(100) + ] + ], + isMinor: [false], + guardianName: [''], + guardianRelationship: [''] + }); + + searchResults: any[] = []; + selectedClient: any = null; + isSearching = false; + private searchTimer: any = null; + + readonly relationships = [ + 'Spouse', + 'Son', + 'Daughter', + 'Father', + 'Mother', + 'Brother', + 'Sister', + 'Grandson', + 'Granddaughter', + 'Father-in-law', + 'Mother-in-law', + 'Other' + ]; + + readonly guardianRelationships = [ + 'Father', + 'Mother', + 'Guardian', + 'Natural Guardian' + ]; + + get isMinor(): boolean { + return this.form.get('isMinor').value; + } + + switchMode(m: 'search' | 'manual'): void { + this.mode = m; + this.selectedClient = null; + this.searchResults = []; + this.form.patchValue({ searchText: '', name: '' }); + } + + onSearchInput(value: string): void { + if (this.searchTimer) clearTimeout(this.searchTimer); + this.selectedClient = null; + if (!value || value.trim().length < 2) { + this.searchResults = []; + return; + } + this.isSearching = true; + this.searchTimer = setTimeout(() => { + this.clientsService.searchByText(value.trim(), 0, 10).subscribe({ + next: (res: any) => { + this.searchResults = res?.content || []; + this.isSearching = false; + }, + error: () => { + this.searchResults = []; + this.isSearching = false; + } + }); + }, 400); + } + + selectClient(client: any): void { + this.selectedClient = client; + this.form.patchValue({ + searchText: client.displayName, + name: client.displayName + }); + this.searchResults = []; + } + + clearSelection(): void { + this.selectedClient = null; + this.form.patchValue({ searchText: '', name: '' }); + this.searchResults = []; + } + + confirm(): void { + if ( + this.form.get('name').invalid || + this.form.get('relationship').invalid || + this.form.get('sharePercentage').invalid + ) + return; + const v = this.form.getRawValue(); + this.dialogRef.close({ + clientId: this.selectedClient?.id || null, + name: v.name, + relationship: v.relationship, + dateOfBirth: v.dateOfBirth, + sharePercentage: v.sharePercentage, + isMinor: v.isMinor, + guardianName: v.isMinor ? v.guardianName : null, + guardianRelationship: v.isMinor ? v.guardianRelationship : null + }); + } + + cancel(): void { + this.dialogRef.close(null); + } + + ngOnDestroy(): void { + if (this.searchTimer) clearTimeout(this.searchTimer); + } +} diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.html b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.html new file mode 100644 index 000000000..29ffc269e --- /dev/null +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.html @@ -0,0 +1,135 @@ + + + +
+
+ person_add +
+
+

Add Related Client / Partner

+

Search and link an existing client with their ownership share

+
+ +
+ + +
+ +
+ 1 + Select Client +
+ +
+ + Search by Name / CIF / Account No + search + + @if (isSearching) { + + } + @if (selectedClient) { + + } + + + + @if (searchResults.length > 0) { +
+ @for (client of searchResults; track client) { +
+
+ {{ (client.displayName || '?')[0].toUpperCase() }} +
+
+ {{ client.displayName }} + + @if (client.cifNumber) { + {{ client.cifNumber }} + } + @if (client.officeName) { + {{ client.officeName }} + } + @if (client.status?.value) { + + {{ client.status.value }} + + } + +
+ chevron_right +
+ } +
+ } +
+ + + @if (selectedClient) { +
+
+ {{ (selectedClient.displayName || '?')[0].toUpperCase() }} +
+
+ {{ selectedClient.displayName }} + {{ selectedClient.cifNumber || selectedClient.accountNumber || 'N/A' }} +
+ Selected +
+ } + + +
+ 2 + Ownership Share +
+ + +
+
+ + + + + diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.scss b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.scss new file mode 100644 index 000000000..3e9fcdc7e --- /dev/null +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.scss @@ -0,0 +1,359 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +// ── Tokens ─────────────────────────────────────────────────────────────────── +$primary: #2563eb; +$primary-light: #eff6ff; +$primary-border: #bfdbfe; +$success: #16a34a; +$success-light: #f0fdf4; +$success-border: #bbf7d0; +$border: #e5e7eb; +$text: #111827; +$text-muted: #6b7280; +$radius: 10px; + +// ── Dialog wrapper ──────────────────────────────────────────────────────────── +mifosx-add-related-client-dialog { + display: block; +} + +// ── Header ──────────────────────────────────────────────────────────────────── +.arc-header { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 20px 20px 14px; + border-bottom: 1px solid $border; + + &__icon { + width: 40px; + height: 40px; + border-radius: 10px; + background: $primary-light; + border: 1px solid $primary-border; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + mat-icon { + color: $primary; + font-size: 20px; + width: 20px; + height: 20px; + } + } + + &__text { + flex: 1; + } + + &__title { + margin: 0; + font-size: 15px; + font-weight: 700; + color: $text; + line-height: 1.3; + } + + &__sub { + margin: 3px 0 0; + font-size: 12px; + color: $text-muted; + } + + &__close { + margin-top: -4px; + margin-right: -8px; + color: $text-muted; + } +} + +// ── Dialog content ──────────────────────────────────────────────────────────── +.arc-dialog-form { + display: flex; + flex-direction: column; + gap: 0; + width: 440px; + max-width: 100%; + padding-top: 4px; +} + +// ── Step labels ─────────────────────────────────────────────────────────────── +.arc-step-label { + display: flex; + align-items: center; + gap: 8px; + margin: 16px 0 10px; + font-size: 12px; + font-weight: 700; + color: $text-muted; + text-transform: uppercase; + letter-spacing: 0.04em; + + &--mt { + margin-top: 20px; + } +} + +.arc-step-badge { + width: 20px; + height: 20px; + border-radius: 50%; + background: $primary; + color: #fff; + font-size: 11px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +// ── Search ──────────────────────────────────────────────────────────────────── +.arc-full-width { + width: 100%; +} + +.arc-search-section { + position: relative; +} + +.arc-search-icon { + font-size: 18px; + width: 18px; + height: 18px; + color: $text-muted; + margin-right: 4px; +} + +.arc-search-results { + position: absolute; + z-index: 200; + top: calc(100% - 8px); + left: 0; + right: 0; + background: #fff; + border: 1px solid $border; + border-radius: $radius; + box-shadow: 0 8px 24px rgb(0 0 0 / 12%); + max-height: 240px; + overflow-y: auto; +} + +.arc-search-result-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + cursor: pointer; + border-bottom: 1px solid #f3f4f6; + transition: background 0.12s; + + &:last-child { + border-bottom: none; + } + + &:hover { + background: $primary-light; + + .arc-result-arrow { + opacity: 1; + transform: translateX(2px); + } + } +} + +.arc-result-avatar { + width: 34px; + height: 34px; + border-radius: 50%; + background: linear-gradient(135deg, #6366f1, #2563eb); + color: #fff; + font-size: 13px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.arc-result-body { + flex: 1; + display: flex; + flex-direction: column; + gap: 3px; +} + +.arc-result-name { + font-size: 13px; + font-weight: 600; + color: $text; +} + +.arc-result-meta { + display: flex; + gap: 6px; + flex-wrap: wrap; +} + +.arc-chip { + font-size: 10px; + padding: 1px 7px; + border-radius: 999px; + background: #f3f4f6; + color: $text-muted; + font-weight: 500; + + &--status { + background: #fef3c7; + color: #92400e; + } + + &--active { + background: #dcfce7; + color: #166534; + } +} + +.arc-result-arrow { + font-size: 18px; + width: 18px; + height: 18px; + color: $primary; + opacity: 0; + transition: + opacity 0.12s, + transform 0.12s; +} + +// ── Selected client ─────────────────────────────────────────────────────────── +.arc-selected-client { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 14px; + background: $success-light; + border: 1.5px solid $success-border; + border-radius: $radius; + margin-top: 6px; +} + +.arc-selected-avatar { + width: 38px; + height: 38px; + border-radius: 50%; + background: linear-gradient(135deg, #22c55e, #16a34a); + color: #fff; + font-size: 14px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.arc-client-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; +} + +.arc-client-name { + font-size: 13px; + font-weight: 700; + color: #166534; +} + +.arc-client-sub { + font-size: 11px; + color: #15803d; +} + +.arc-selected-badge { + font-size: 10px; + font-weight: 700; + padding: 3px 10px; + border-radius: 999px; + background: $success; + color: #fff; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +// ── Share percentage ────────────────────────────────────────────────────────── +.arc-share-row { + display: flex; + align-items: flex-start; + gap: 12px; +} + +.arc-share-field { + flex: 0 0 160px; +} + +.arc-percent-suffix { + font-size: 15px; + font-weight: 600; + color: $text-muted; + margin-right: 4px; +} + +.arc-share-hints { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding-top: 10px; +} + +.arc-hint-chip { + padding: 5px 12px; + border-radius: 8px; + border: 1.5px solid $border; + background: #fff; + font-size: 12px; + font-weight: 600; + color: $text-muted; + cursor: pointer; + transition: + border-color 0.12s, + background 0.12s, + color 0.12s; + + &:hover { + border-color: $primary; + background: $primary-light; + color: $primary; + } +} + +// ── Actions ─────────────────────────────────────────────────────────────────── +.arc-actions { + padding: 12px 20px 16px !important; + border-top: 1px solid $border; + gap: 10px; + justify-content: flex-end !important; +} + +.arc-btn-cancel { + border-radius: 8px !important; +} + +.arc-btn-add { + border-radius: 8px !important; + padding: 0 18px !important; + gap: 4px; + + mat-icon { + font-size: 18px; + width: 18px; + height: 18px; + } +} diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.ts b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.ts new file mode 100644 index 000000000..60c96d69a --- /dev/null +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.ts @@ -0,0 +1,104 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, inject, OnDestroy, ViewEncapsulation } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; +import { MatDialogRef, MatDialogContent, MatDialogActions } from '@angular/material/dialog'; +import { MatProgressSpinner } from '@angular/material/progress-spinner'; +import { MatIcon } from '@angular/material/icon'; +import { ClientsService } from 'app/clients/clients.service'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; + +@Component({ + selector: 'mifosx-add-related-client-dialog', + templateUrl: './add-related-client-dialog.component.html', + styleUrl: './add-related-client-dialog.component.scss', + encapsulation: ViewEncapsulation.None, + imports: [ + ...STANDALONE_SHARED_IMPORTS, + MatDialogContent, + MatDialogActions, + MatProgressSpinner, + MatIcon, + ReactiveFormsModule + ] +}) +export class AddRelatedClientDialogComponent implements OnDestroy { + private fb = inject(UntypedFormBuilder); + private clientsService = inject(ClientsService); + dialogRef = inject(MatDialogRef); + + form: UntypedFormGroup = this.fb.group({ + searchText: [''], + sharePercentage: [ + null, + [ + Validators.required, + Validators.min(1), + Validators.max(100) + ] + ] + }); + + searchResults: any[] = []; + selectedClient: any = null; + isSearching = false; + private searchTimer: any = null; + + onSearchInput(value: string): void { + if (this.searchTimer) clearTimeout(this.searchTimer); + this.selectedClient = null; + if (!value || value.trim().length < 2) { + this.searchResults = []; + return; + } + this.isSearching = true; + this.searchTimer = setTimeout(() => { + this.clientsService.searchByText(value.trim(), 0, 10).subscribe({ + next: (res: any) => { + this.searchResults = res?.content || []; + this.isSearching = false; + }, + error: () => { + this.searchResults = []; + this.isSearching = false; + } + }); + }, 400); + } + + selectClient(client: any): void { + this.selectedClient = client; + this.form.patchValue({ searchText: client.displayName }); + this.searchResults = []; + } + + clearSelection(): void { + this.selectedClient = null; + this.form.patchValue({ searchText: '' }); + this.searchResults = []; + } + + confirm(): void { + if (!this.selectedClient || this.form.get('sharePercentage').invalid) return; + this.dialogRef.close({ + clientId: this.selectedClient.id, + clientName: this.selectedClient.displayName, + clientCif: this.selectedClient.cifNumber || this.selectedClient.accountNumber || '', + sharePercentage: this.form.get('sharePercentage').value + }); + } + + cancel(): void { + this.dialogRef.close(null); + } + + ngOnDestroy(): void { + if (this.searchTimer) clearTimeout(this.searchTimer); + } +} 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..68adf30a0 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) { +
@@ -87,8 +83,12 @@ - Product Code - + Account No + @if (productTemplate.effectiveDate) { @@ -125,6 +125,212 @@
+ + @if (productTemplate?.introducerRequired) { + + } + } + + + @if (isNonIndividualClient && currentAccountProductSelected) { +
+ +
+
+
+ +
+
+

Non-Individual Account Details

+ As per RBI / Indian banking norms for non-individual entities +
+
+ Entity Account +
+ + +
+
+
+ A +
+ Partners / Members + Authorised persons linked to this entity account +
+
+ +
+ + @if (relatedClients.length > 0) { +
+ + + + + + + + + + + + + @for (c of relatedClients; track c; let i = $index) { + + + + + + + + + } + + + + + + + + + +
Sr.Partner NameCIF No.Ownership %Signing AuthorityAction
{{ i + 1 }} +
+
+ {{ c.clientName[0].toUpperCase() }} +
+
+ {{ c.clientName }} + @if (c.isMandatory) { + Mandatory Signatory + } +
+
+
+ {{ c.clientCif || '—' }} + + + {{ c.sharePercentage }}% + + + + + +
Total Ownership + + {{ totalSharePercentage }}% + + + + lock {{ mandatoryCount }} Mandatory + +
+
+ @if (totalSharePercentage !== 100) { +
+ warning_amber + Total ownership is {{ totalSharePercentage }}%. It must equal 100%. +
+ } @else { +
+ check_circle + Ownership allocation is complete (100%). +
+ } + } @else { +
+ group_add + No partners added yet. Click Add Partner to begin. +
+ } +
+ + +
+
+
+ B +
+ Mode of Operation / Signatory Rights + Auto-computed from Mandatory partners — you can override manually +
+
+ @if (signatoryRights) { + auto_awesome Auto + } +
+ + @if (relatedClients.length === 0) { +
+ how_to_vote + Add partners in Section A and set Mandatory/Optional to auto-compute the mode. +
+ } @else { +
+ @for (opt of signatoryRightsOptions; track opt.value) { + + } +
+ +
+ @if (signatoryRights) { + how_to_vote + Mode of Operation: {{ selectedSignatoryLabel }} + + ({{ mandatoryCount }} Mandatory + {{ relatedClients.length - mandatoryCount }} Optional partner(s)) + + } @else { + info + Set Mandatory/Optional for partners above to auto-select mode. + } +
+ } +
+
}
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..bcad57a8c 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,669 @@ $gray-900: #202124; border-color: #bbf7d0; } } + +// ── Non-Individual Section (Indian Banking Style) ───────────────────────── + +$ni-primary: #1a56db; +$ni-green: #057a55; +$ni-orange: #d03801; +$ni-border: #e5e7eb; +$ni-bg: #f9fafb; + +.ni-card { + margin-top: 16px; + border: 1.5px solid #c3dafe; + border-radius: 12px; + overflow: hidden; + background: #fff; + + &__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 20px; + background: linear-gradient(135deg, #1e3a8a 0%, #1a56db 100%); + } + + &__header-left { + display: flex; + align-items: center; + gap: 12px; + } + + &__header-icon { + width: 38px; + height: 38px; + border-radius: 8px; + background: rgba(255 255 255 / 20%); + display: flex; + align-items: center; + justify-content: center; + + i { + color: #fff; + font-size: 17px; + } + } + + &__title { + margin: 0; + font-size: 14px; + font-weight: 700; + color: #fff; + } + + &__sub { + margin: 2px 0 0; + font-size: 11px; + color: rgba(255 255 255 / 75%); + } +} + +.ni-pill { + padding: 4px 12px; + border-radius: 999px; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.05em; + text-transform: uppercase; + + &--entity { + background: rgba(255 255 255 / 20%); + color: #fff; + border: 1px solid rgba(255 255 255 / 30%); + } +} + +// Block +.ni-block { + padding: 16px 20px 4px; + + &--sep { + border-top: 1.5px dashed #e5e7eb; + padding-top: 16px; + } + + &__head { + display: flex; + align-items: flex-start; + justify-content: space-between; + margin-bottom: 14px; + } + + &__head-left { + display: flex; + align-items: flex-start; + gap: 10px; + + &--alone { + margin-bottom: 14px; + } + } + + &__letter { + width: 26px; + height: 26px; + border-radius: 6px; + background: $ni-primary; + color: #fff; + font-size: 13px; + font-weight: 800; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + margin-top: 1px; + + &--b { + background: $ni-green; + } + + &--c { + background: #7c3aed; + } + } + + &__title { + display: block; + font-size: 13px; + font-weight: 700; + color: #111827; + } + + &__hint { + display: block; + font-size: 11px; + color: #6b7280; + margin-top: 1px; + } +} + +.ni-add-btn { + border-radius: 8px !important; + font-size: 12px !important; + padding: 0 14px !important; + height: 34px !important; + gap: 4px; + + mat-icon { + font-size: 16px; + width: 16px; + height: 16px; + } + + &--accent { + background: $ni-green !important; + color: #fff !important; + } +} + +// Table +.ni-table-wrap { + overflow-x: auto; + border: 1px solid $ni-border; + border-radius: 8px; + margin-bottom: 8px; +} + +.ni-tbl { + width: 100%; + border-collapse: collapse; + font-size: 13px; + + thead { + background: #f8faff; + } +} + +.ni-th { + padding: 9px 12px; + font-size: 11px; + font-weight: 700; + color: #374151; + text-transform: uppercase; + letter-spacing: 0.04em; + border-bottom: 1.5px solid $ni-border; + text-align: left; + white-space: nowrap; + + &--sr { + width: 40px; + text-align: center; + } + + &--center { + text-align: center; + } +} + +.ni-tr { + transition: background 0.1s; + + &:hover { + background: #f9fafb; + } + + &--mandatory { + background: #fffbf0; + + &:hover { + background: #fff8e6; + } + } +} + +.ni-tr-total { + background: #f0f4ff; + border-top: 1.5px solid #c3dafe; +} + +.ni-td { + padding: 10px 12px; + color: #111827; + border-bottom: 1px solid #f3f4f6; + + &--sr { + text-align: center; + color: #6b7280; + font-size: 12px; + } + + &--center { + text-align: center; + } + + &--total-label { + font-weight: 700; + font-size: 12px; + color: #374151; + } +} + +.ni-client-cell { + display: flex; + align-items: center; + gap: 8px; +} + +.ni-avatar { + width: 30px; + height: 30px; + border-radius: 50%; + background: linear-gradient(135deg, #6366f1, $ni-primary); + color: #fff; + font-size: 12px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + &--green { + background: linear-gradient(135deg, #10b981, $ni-green); + } + + &--mandatory { + background: linear-gradient(135deg, #f59e0b, #d97706); + } +} + +.ni-client-name { + display: block; + font-weight: 600; + color: #111827; +} + +.ni-mandatory-tag { + display: inline-block; + font-size: 9.5px; + font-weight: 700; + color: #b45309; + background: #fef3c7; + border: 1px solid #fde68a; + border-radius: 4px; + padding: 1px 6px; + margin-top: 2px; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.ni-mandatory-count-badge { + display: inline-flex; + align-items: center; + gap: 3px; + font-size: 11px; + font-weight: 700; + color: #92400e; + background: #fef3c7; + border: 1px solid #fde68a; + border-radius: 6px; + padding: 3px 8px; + + mat-icon { + font-size: 12px; + width: 12px; + height: 12px; + } +} + +// Signing authority toggle in table +.ni-sign-toggle { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 12px; + border-radius: 20px; + border: 1.5px solid; + font-size: 11.5px; + font-weight: 700; + cursor: pointer; + transition: all 0.15s; + white-space: nowrap; + + mat-icon { + font-size: 13px; + width: 13px; + height: 13px; + } + + &--mandatory { + border-color: #d97706; + background: #fffbeb; + color: #92400e; + } + + &--optional { + border-color: #d1d5db; + background: #f9fafb; + color: #6b7280; + + &:hover { + border-color: #d97706; + color: #92400e; + background: #fffbeb; + } + } +} + +.ni-nom-info { + display: flex; + flex-direction: column; + gap: 1px; +} + +.ni-guardian-hint { + font-size: 10.5px; + color: #6b7280; +} + +.ni-mono { + font-family: monospace; + font-size: 12px; + color: #374151; +} + +.ni-pct-badge { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 3px 10px; + border-radius: 999px; + font-size: 12px; + font-weight: 700; + background: #e0e7ff; + color: $ni-primary; + min-width: 52px; + + &--ok { + background: #d1fae5; + color: $ni-green; + } + + &--warn { + background: #fef3c7; + color: #92400e; + } + + &--full { + background: #d1fae5; + color: $ni-green; + } +} + +.ni-rel-badge { + font-size: 11px; + padding: 2px 8px; + border-radius: 6px; + background: #f3f4f6; + color: #374151; + font-weight: 500; +} + +.ni-minor-badge { + font-size: 10px; + padding: 2px 8px; + border-radius: 999px; + background: #fef3c7; + color: #92400e; + font-weight: 700; + text-transform: uppercase; +} + +.ni-td-dash { + color: #d1d5db; +} + +.ni-del-btn { + width: 28px !important; + height: 28px !important; + line-height: 28px !important; + + mat-icon { + font-size: 17px; + width: 17px; + height: 17px; + } +} + +// Alerts +.ni-alert { + display: flex; + align-items: center; + gap: 7px; + padding: 8px 12px; + border-radius: 8px; + font-size: 12px; + font-weight: 500; + margin-bottom: 12px; + + mat-icon { + font-size: 16px; + width: 16px; + height: 16px; + } + + &--warn { + background: #fffbeb; + color: #92400e; + border: 1px solid #fde68a; + } + + &--ok { + background: #f0fdf4; + color: $ni-green; + border: 1px solid #bbf7d0; + } + + &--sm { + margin-top: 8px; + margin-bottom: 14px; + } +} + +// Empty state +.ni-empty-state { + display: flex; + align-items: center; + gap: 8px; + padding: 16px 12px; + font-size: 13px; + color: #9ca3af; + font-style: italic; + margin-bottom: 10px; + + mat-icon { + font-size: 20px; + width: 20px; + height: 20px; + color: #d1d5db; + } +} + +// Auto badge on section header +.ni-auto-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 10px; + border-radius: 999px; + font-size: 11px; + font-weight: 700; + background: #fffbeb; + color: #92400e; + border: 1px solid #fde68a; + + mat-icon { + font-size: 12px; + width: 12px; + height: 12px; + } +} + +// Mode summary bar below signatory cards +.ni-mode-summary { + display: flex; + align-items: center; + gap: 8px; + padding: 9px 14px; + border-radius: 8px; + font-size: 12.5px; + font-weight: 500; + margin-top: 4px; + background: #f3f4f6; + color: #6b7280; + border: 1px solid #e5e7eb; + + mat-icon { + font-size: 16px; + width: 16px; + height: 16px; + } + + &--set { + background: #f0fdf4; + color: $ni-green; + border-color: #bbf7d0; + } + + &__from { + font-size: 11px; + color: #9ca3af; + font-style: italic; + } +} + +// ── Signatory / Mode of Operation cards ────────────────────────────────── +.ni-signatory-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 10px; + margin-bottom: 10px; + + @media (width <= 700px) { + grid-template-columns: repeat(2, 1fr); + } +} + +.ni-sig-card { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 6px; + padding: 18px 10px 14px; + border-radius: 12px; + border: 1.5px solid $ni-border; + background: #fff; + cursor: pointer; + transition: all 0.15s; + text-align: center; + + &:hover:not(.ni-sig-card--selected) { + border-color: #93c5fd; + background: #f0f9ff; + transform: translateY(-1px); + } + + &--selected { + border-color: $ni-primary; + background: #eff6ff; + box-shadow: 0 0 0 3px rgba(37 99 235 / 12%); + transform: translateY(-1px); + } + + &__icon { + font-size: 22px; + width: 22px; + height: 22px; + color: #6b7280; + + .ni-sig-card--selected & { + color: $ni-primary; + } + } + + &__code { + font-size: 15px; + font-weight: 800; + color: #1e3a8a; + font-family: monospace; + letter-spacing: 0.02em; + } + + &__desc { + font-size: 10.5px; + font-weight: 500; + color: #6b7280; + line-height: 1.3; + } + + &__check { + position: absolute; + top: 7px; + right: 7px; + font-size: 15px; + width: 15px; + height: 15px; + color: $ni-primary; + } +} + +// ── Account Feature Toggles ──────────────────────────────────────────────── +.sad-toggles { + display: flex; + flex-direction: column; + gap: 10px; + padding: 4px 0; +} + +.sad-toggle-card { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 18px; + border: 1.5px solid #e2e8f0; + border-radius: 10px; + background: #fff; + transition: + border-color 0.2s, + box-shadow 0.2s; + + &:hover { + border-color: #93c5fd; + box-shadow: 0 1px 6px rgb(59 130 246 / 10%); + } + + &__left { + display: flex; + align-items: center; + gap: 12px; + + > i { + width: 36px; + height: 36px; + border-radius: 8px; + background: #f0f9ff; + border: 1px solid #bae6fd; + color: #0284c7; + display: flex; + align-items: center; + justify-content: center; + font-size: 15px; + flex: 0 0 auto; + } + } + + &__label { + font-size: 13.5px; + font-weight: 600; + color: #1e293b; + } + + &__hint { + font-size: 11.5px; + color: #64748b; + margin-top: 2px; + } +} 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..8f1523d0e 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 @@ -14,9 +14,27 @@ import { Dates } from 'app/core/utils/dates'; /** Custom Services */ import { CurrentAccountsService } from 'app/current-accounts/current-accounts.service'; import { SettingsService } from 'app/settings/settings.service'; +import { ClientsService } from 'app/clients/clients.service'; +import { MatDialog } from '@angular/material/dialog'; import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper'; +import { + MatTable, + MatColumnDef, + MatHeaderCellDef, + MatHeaderCell, + MatCellDef, + MatCell, + MatHeaderRowDef, + MatHeaderRow, + MatRowDef, + MatRow +} from '@angular/material/table'; +import { MatIcon } from '@angular/material/icon'; +import { FormsModule } from '@angular/forms'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { IntroducerSearchComponent } from 'app/shared/introducer-search/introducer-search.component'; +import { AddRelatedClientDialogComponent } from './add-related-client-dialog/add-related-client-dialog.component'; /** * Current Account Details Step @@ -29,13 +47,28 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; ...STANDALONE_SHARED_IMPORTS, MatStepperPrevious, FaIconComponent, - MatStepperNext + MatStepperNext, + IntroducerSearchComponent, + MatTable, + MatColumnDef, + MatHeaderCellDef, + MatHeaderCell, + MatCellDef, + MatCell, + MatHeaderRowDef, + MatHeaderRow, + MatRowDef, + MatRow, + MatIcon, + FormsModule ] }) export class CurrentAccountDetailsStepComponent implements OnInit { private formBuilder = inject(UntypedFormBuilder); private currentAccountsService = inject(CurrentAccountsService); private settingsService = inject(SettingsService); + private clientsService = inject(ClientsService); + private dialog = inject(MatDialog); private dateUtils = inject(Dates); private cdr = inject(ChangeDetectorRef); @@ -56,6 +89,27 @@ export class CurrentAccountDetailsStepComponent implements OnInit { currentAccountDetailsForm: UntypedFormGroup; currentAccountProductSelected = false; + introducerId: number | null = null; + introducerName: string | null = null; + introducerCif: string | null = null; + + /** Non-Individual section */ + isNonIndividualClient = false; + relatedClients: Array<{ + clientId: number; + clientName: string; + clientCif: string; + sharePercentage: number; + isMandatory: boolean; + }> = []; + signatoryRights = ''; + + readonly signatoryRightsOptions = [ + { value: 'ANYONE', label: 'E or S', sublabel: 'Either or Survivor', icon: 'how_to_vote' }, + { value: 'ONE_MANDATORY', label: 'J/One', sublabel: 'Joint – One Mandatory', icon: 'group' }, + { value: 'TWO_MANDATORY', label: 'J/Two', sublabel: 'Joint – Two Mandatory', icon: 'groups' }, + { value: 'ONLY_ONE', label: 'Single', sublabel: 'Only 1 Person', icon: 'person' } + ]; /** Current Account Template with product data */ @Output() currentAccountProductTemplate = new EventEmitter(); @@ -70,6 +124,13 @@ export class CurrentAccountDetailsStepComponent implements OnInit { ngOnInit() { this.maxDate = this.settingsService.businessDate; this.buildDependencies(); + if (this.currentAccountTemplate?.clientId) { + this.clientsService.getClientData(String(this.currentAccountTemplate.clientId)).subscribe((client: any) => { + this.isNonIndividualClient = client?.legalForm?.id === 2; + this.cdr.detectChanges(); + }); + } + if (this.currentAccountTemplate) { this.productData = this.currentAccountTemplate.productOptions; if (this.currentAccountTemplate.currentAccountProductId) { @@ -102,7 +163,10 @@ export class CurrentAccountDetailsStepComponent implements OnInit { Validators.required ], fieldOfficerId: [''], - externalId: [''] + externalId: [''], + balanceSwapOption: [false], + msme: [false], + autoDebitFacility: [false] }); } @@ -131,6 +195,12 @@ export class CurrentAccountDetailsStepComponent implements OnInit { this.currentAccountProductTemplate.emit(response); this.fieldOfficerData = response.fieldOfficerOptions; this.currentAccountProductSelected = true; + // Pre-populate toggles from product defaults (user can override) + this.currentAccountDetailsForm.patchValue({ + balanceSwapOption: !!response.balanceSwapOption, + msme: !!response.msme, + autoDebitFacility: !!response.autoDebitFacility + }); this.cdr.detectChanges(); if (!this.isFieldOfficerPatched && this.currentAccountTemplate.fieldOfficerId) { this.currentAccountDetailsForm.get('fieldOfficerId').patchValue(this.currentAccountTemplate.fieldOfficerId); @@ -166,7 +236,85 @@ export class CurrentAccountDetailsStepComponent implements OnInit { /** * Returns current account form value. */ + onIntroducerChange(event: { clientId: number; displayName: string; cifNumber?: string } | null): void { + this.introducerId = event?.clientId ?? null; + this.introducerName = event?.displayName ?? null; + this.introducerCif = event?.cifNumber ?? null; + } + + openAddRelatedClientDialog(): void { + const ref = this.dialog.open(AddRelatedClientDialogComponent, { + width: '500px', + panelClass: 'arc-dialog-panel', + disableClose: true + }); + ref.afterClosed().subscribe((result) => { + if (result) { + const duplicate = this.relatedClients.some((c) => c.clientCif && c.clientCif === result.clientCif); + if (duplicate) { + alert(`Partner with CIF ${result.clientCif} is already added.`); + return; + } + this.relatedClients = [ + ...this.relatedClients, + { ...result, isMandatory: false }]; + this.autoSetSignatoryRights(); + } + }); + } + + removeRelatedClient(index: number): void { + this.relatedClients = this.relatedClients.filter((_, i) => i !== index); + this.autoSetSignatoryRights(); + } + + toggleMandatory(client: any): void { + client.isMandatory = !client.isMandatory; + this.autoSetSignatoryRights(); + } + + /** Auto-compute Mode of Operation based on mandatory count */ + autoSetSignatoryRights(): void { + const mandatory = this.relatedClients.filter((c) => c.isMandatory).length; + if (mandatory === 0) { + this.signatoryRights = 'ANYONE'; + } else if (mandatory === 1) { + this.signatoryRights = 'ONE_MANDATORY'; + } else { + this.signatoryRights = 'TWO_MANDATORY'; + } + } + + get totalSharePercentage(): number { + return this.relatedClients.reduce((sum, c) => sum + (c.sharePercentage || 0), 0); + } + + get mandatoryCount(): number { + return this.relatedClients.filter((c) => c.isMandatory).length; + } + + get selectedSignatoryLabel(): string { + const opt = this.signatoryRightsOptions.find((x) => x.value === this.signatoryRights); + return opt ? opt.sublabel : ''; + } + get currentAccountDetails() { - return this.currentAccountDetailsForm.getRawValue(); + const value = this.currentAccountDetailsForm.getRawValue(); + if (this.introducerId) { + value.introducerId = this.introducerId; + value.introducerName = this.introducerName; + value.introducerCif = this.introducerCif; + } + if (this.isNonIndividualClient) { + value.signatoryRights = this.signatoryRights || null; + value.partners = this.relatedClients.map((c) => ({ + clientId: c.clientId, + clientName: c.clientName, + clientCif: c.clientCif, + sharePercentage: c.sharePercentage, + isMandatory: c.isMandatory + })); + } + return value; } } 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..222c0c142 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 @@ -7,10 +7,11 @@ -->
- +
- {{ 'labels.heading.Details' | translate }} + + Account Details
@@ -38,27 +39,28 @@ } @else { - {{ 'labels.inputs.Unassigned' | translate }} + Unassigned }
- + @if ( currentAccountProductTemplate?.accountType || currentAccountProductTemplate?.customerSegment || currentAccountProductTemplate?.productCode || + currentAccountProductTemplate?.previewAccountNo || currentAccountProductTemplate?.effectiveDate || currentAccountProductTemplate?.effectiveTo || - currentAccountProductTemplate?.endDate || - currentAccountProductTemplate?.introducerRequired !== undefined || - currentAccountProductTemplate?.shareLinkingEnabled !== undefined + currentAccountProductTemplate?.endDate ) {
+ Product Classification + From Product
@@ -77,9 +79,15 @@ @if (currentAccountProductTemplate?.productCode) {
Product Code - {{ currentAccountProductTemplate?.productCode }} + {{ currentAccountProductTemplate?.productCode }}
} +
+ Account No + {{ + currentAccountProductTemplate?.previewAccountNo || currentAccountTemplate?.accountNo || 'Auto-generated' + }} +
@if (currentAccountProductTemplate?.effectiveDate) {
Effective Date @@ -98,418 +106,352 @@ {{ currentAccountProductTemplate?.endDate | dateFormat }}
} - @if ( - currentAccountProductTemplate?.introducerRequired !== undefined && - currentAccountProductTemplate?.introducerRequired !== null - ) { +
+ Introducer Required + + {{ currentAccountProductTemplate?.introducerRequired | yesNo }} + +
+
+ Share Linking Enabled + + {{ currentAccountProductTemplate?.shareLinkingEnabled | yesNo }} + +
+
+
+
+ } + + + @if (currentAccount?.introducerId) { +
+
+ + Introducer Details +
+
+
+
+ Name + {{ currentAccount?.introducerName || '—' }} +
+ @if (currentAccount?.introducerCif) {
- Introducer Required - - {{ currentAccountProductTemplate?.introducerRequired | yesNo }} - + CIF No + {{ currentAccount.introducerCif }}
} - @if ( - currentAccountProductTemplate?.shareLinkingEnabled !== undefined && - currentAccountProductTemplate?.shareLinkingEnabled !== null - ) { -
- Share Linking Enabled - - {{ currentAccountProductTemplate?.shareLinkingEnabled | yesNo }} +
+ Client ID + {{ currentAccount.introducerId }} +
+
+
+
+ } + + +
+
+ + Currency & Account Flags +
+
+
+
+ Currency + {{ currencyName }} +
+
+ Decimal Places + {{ decimalPlaces }} +
+
+ Currency In Multiples Of + {{ inMultiplesOf }} +
+
+ Balance Swap Option + + {{ currentAccountCurrencyFlags?.balanceSwapOption | yesNo }} + +
+
+ MSME + + {{ currentAccountCurrencyFlags?.msme | yesNo }} + +
+
+ Auto Debit Facility + + {{ currentAccountCurrencyFlags?.autoDebitFacility | yesNo }} + +
+
+ Dormancy Alert + + {{ currentAccountProductTemplate?.dormancyAlert | yesNo }} + +
+
+ Nomination Facility + + {{ currentAccountProductTemplate?.nominationFacility | yesNo }} + +
+
+
+
+ + + @if (currentAccountProductTemplate?.dormancyAlert || currentAccountProductTemplate?.isDormancyTrackingActive) { +
+
+ + Dormancy Monitoring + + {{ currentAccountProductTemplate?.isDormancyTrackingActive ? 'Active' : 'Inactive' }} + +
+
+
+
+
+
+ Inactive + + @if (currentAccountProductTemplate?.daysToInactive != null) { + After {{ currentAccountProductTemplate.daysToInactive }} days of no transaction + } @else { + Not configured + }
+
+
+
+
+
+ Dormant + + @if (currentAccountProductTemplate?.daysToDormancy != null) { + {{ currentAccountProductTemplate.daysToDormancy }} days after inactive + } @else { + Not configured + } + +
+
+
+
+
+
+ Escheat + + @if (currentAccountProductTemplate?.daysToEscheat != null) { + {{ currentAccountProductTemplate.daysToEscheat }} days after dormant + } @else { + Not configured + } + +
+
+
+
+ + Alert + {{ currentAccountProductTemplate?.dormancyAlert ? 'Enabled' : 'Disabled' }} + + + Tracking + {{ currentAccountProductTemplate?.isDormancyTrackingActive ? 'Active' : 'Inactive' }} + +
+
+
+ } + + + @if (currentAccountNominee) { +
+
+ + Nominee Details + + {{ currentAccountNominee?.mode === 'linked' ? 'Linked Client' : 'Manual Entry' }} + +
+
+
+
+ Name + {{ + currentAccountNominee?.fullName || currentAccountNominee?.displayName || '—' + }} +
+ @if (currentAccountNominee?.cifNumber) { +
+ CIF No + {{ currentAccountNominee.cifNumber }} +
+ } +
+ Relationship + {{ currentAccountNominee?.relationship || '—' }} +
+
+ Share % + {{ currentAccountNominee?.nomineeShare ?? '—' }}% +
+ @if (currentAccountNominee?.dateOfBirth) { +
+ Date of Birth + {{ currentAccountNominee?.dateOfBirth | dateFormat }} +
+ } + @if (currentAccountNominee?.nationality) { +
+ Nationality + {{ currentAccountNominee?.nationality }} +
+ } + @if (currentAccountNominee?.mobileNo) { +
+ Mobile No + {{ currentAccountNominee?.mobileNo }} +
+ } + @if (currentAccountNominee?.aadhaarNumber) { +
+ Aadhaar No + {{ currentAccountNominee?.aadhaarNumber }} +
+ } + @if (currentAccountNominee?.panNumber) { +
+ PAN No + {{ currentAccountNominee?.panNumber }} +
+ } + @if (currentAccountNominee?.guardianName) { +
+ Guardian Name + {{ currentAccountNominee?.guardianName }} +
+ } + @if (currentAccountNominee?.guardianRelationship) { +
+ Guardian Relationship + {{ currentAccountNominee?.guardianRelationship }} +
+ } + @if (currentAccountNominee?.remarks) { +
+ Remarks + {{ currentAccountNominee?.remarks }} +
}
} - -
-
- {{ 'labels.heading.Currency' | translate }} -
-
-
-
- {{ 'labels.inputs.Currency' | translate }} - {{ currentAccountTermsForm?.get('currencyCode')?.value }} -
-
- {{ 'labels.inputs.Decimal Places' | translate }} - {{ currentAccountProductTemplate?.currency?.decimalPlaces }} -
-
- {{ 'labels.inputs.Currency In Multiples Of' | translate }} - {{ currentAccountProductTemplate?.currency?.inMultiplesOf }} -
- @if ( - currentAccountProductTemplate?.dormancyAlert !== undefined && - currentAccountProductTemplate?.dormancyAlert !== null - ) { -
- Dormancy Alert - - {{ currentAccountProductTemplate?.dormancyAlert | yesNo }} - -
- } - @if ( - currentAccountProductTemplate?.balanceSwapOption !== undefined && - currentAccountProductTemplate?.balanceSwapOption !== null - ) { -
- Balance Swap Option - - {{ currentAccountProductTemplate?.balanceSwapOption | yesNo }} - -
- } - @if (currentAccountProductTemplate?.msme !== undefined && currentAccountProductTemplate?.msme !== null) { -
- MSME - - {{ currentAccountProductTemplate?.msme | yesNo }} - -
- } - @if ( - currentAccountProductTemplate?.nominationFacility !== undefined && - currentAccountProductTemplate?.nominationFacility !== null - ) { -
- Nomination Facility - - {{ currentAccountProductTemplate?.nominationFacility | yesNo }} - -
- } - @if ( - currentAccountProductTemplate?.autoDebitFacility !== undefined && - currentAccountProductTemplate?.autoDebitFacility !== null - ) { -
- Auto Debit Facility - - {{ currentAccountProductTemplate?.autoDebitFacility | yesNo }} - -
- } -
-
-
- - -
-
- {{ '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) { + + @if (currentAccount?.partners?.length > 0) {
- Digital + + Non-Individual Account Details
-
- @for (facility of digitalFacilityLabels; track facility.key) { -
- {{ facility.label }} - - {{ digitalPreview?.accountFacilityConfig?.[facility.key] | yesNo }} - -
- } -
- - @if (digitalPreview?.accountFacilityLimits?.length) { - + +
Partners / Members
+
+
- - - - + + + + - @for (row of digitalPreview.accountFacilityLimits; track row.limitType) { + @for (p of currentAccount.partners; track p.clientId) { - - - - + + + + }
Limit TypeGeneral LimitMinor Account LimitSmall Saving LimitPartner NameCIF NoOwnership %Signing Authority
{{ row.limitType }}{{ row.generalLimit || 'Unassigned' }}{{ row.minorAccountLimit || 'Unassigned' }}{{ row.smallSavingLimit || 'Unassigned' }}{{ p.clientName || '—' }}{{ p.clientCif || '—' }}{{ p.sharePercentage }}% + + {{ p.isMandatory ? 'Mandatory' : 'Optional' }} + +
- } - - @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?.signatoryRights) { +
+ + Mode of Operation: + {{ currentAccount.signatoryRights }} +
}
} - - @if (currentAccount?.charges?.length) { + + @if (hasDigitalSection) {
- {{ 'labels.inputs.Charges' | translate }} + + Digital Facilities + User Configured
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ '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' }}
+
+ @for (facility of digitalFacilityLabels; track facility.key) { +
+ {{ facility.label }} + + {{ digitalFacilityConfig?.[facility.key] ? 'Enabled' : 'Disabled' }} + +
+ } +
} diff --git a/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.scss b/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.scss index bec5645fb..fcfa95163 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.scss +++ b/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.scss @@ -9,29 +9,65 @@ .preview-wrapper { display: flex; flex-direction: column; - gap: 1.25rem; + gap: 1rem; padding: 0.5rem 0; } .preview-section { - border: 1px solid #e0e0e0; - border-radius: 8px; + border: 1px solid #e2e8f0; + border-radius: 10px; overflow: hidden; background: #fff; - box-shadow: 0 1px 3px rgb(0 0 0 / 6%); + box-shadow: 0 1px 4px rgb(0 0 0 / 5%); &__header { - background: #f5f5f5; - border-bottom: 1px solid #e0e0e0; + display: flex; + align-items: center; + gap: 8px; + background: #f8fafc; + border-bottom: 1px solid #e2e8f0; padding: 0.6rem 1rem; } + &__icon { + font-size: 13px; + color: #0ea5e9; + width: 16px; + text-align: center; + } + &__title { - font-size: 0.95rem; - font-weight: 600; - color: #333; + font-size: 0.82rem; + font-weight: 700; + color: #1e293b; text-transform: uppercase; - letter-spacing: 0.04em; + letter-spacing: 0.06em; + flex: 1; + } + + &__badge { + display: inline-flex; + align-items: center; + padding: 2px 10px; + border-radius: 999px; + font-size: 10px; + font-weight: 700; + background: #dbeafe; + color: #1d4ed8; + border: 1px solid #bfdbfe; + text-transform: uppercase; + + &--digital { + background: #fef3c7; + color: #92400e; + border-color: #fde68a; + } + + &--active { + background: #dcfce7 !important; + color: #15803d !important; + border-color: #bbf7d0 !important; + } } } @@ -41,7 +77,7 @@ .preview-grid { display: grid; - gap: 0.75rem 1.5rem; + gap: 0.85rem 1.5rem; &--three { grid-template-columns: repeat(3, 1fr); @@ -59,36 +95,276 @@ .preview-field { display: flex; flex-direction: column; - gap: 0.15rem; + gap: 0.2rem; } .preview-label { - font-size: 0.72rem; - color: #888; - font-weight: 500; + font-size: 0.68rem; + color: #94a3b8; + font-weight: 600; text-transform: uppercase; - letter-spacing: 0.04em; + letter-spacing: 0.05em; } .preview-value { - font-size: 0.92rem; - color: #222; + font-size: 0.9rem; + color: #1e293b; font-weight: 500; } +.preview-muted { + color: #94a3b8; + font-style: italic; +} + +.preview-mono { + font-family: monospace; + font-size: 0.88rem; + color: #334155; +} + .preview-badge { display: inline-block; - padding: 0.1rem 0.55rem; + padding: 0.15rem 0.6rem; border-radius: 12px; - font-size: 0.78rem; + font-size: 0.75rem; font-weight: 600; - background: #ffebee; - color: #c62828; + background: #fef2f2; + color: #dc2626; width: fit-content; &--yes { - background: #e8f5e9; - color: #2e7d32; + background: #f0fdf4; + color: #15803d; + } + + &--warn { + background: #fef3c7; + color: #92400e; + } +} + +// Digital facilities grid +.preview-digital-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 8px; + + @media (width <= 900px) { + grid-template-columns: repeat(3, 1fr); + } + + @media (width <= 600px) { + grid-template-columns: repeat(2, 1fr); + } +} + +.preview-digital-card { + display: flex; + flex-direction: column; + gap: 4px; + padding: 10px 12px; + border: 1.5px solid #e2e8f0; + border-radius: 8px; + background: #f8fafc; + + &--on { + border-color: #86efac; + background: #f0fdf4; + } +} + +.preview-digital-label { + font-size: 11px; + font-weight: 600; + color: #475569; +} + +.preview-digital-status { + font-size: 11px; + font-weight: 700; + color: #dc2626; + + &--on { + color: #15803d; + } +} + +// ── Dormancy Monitoring ─────────────────────────────────────────── +.preview-dormancy-timeline { + display: flex; + align-items: flex-start; + margin-bottom: 14px; +} + +.preview-dorm-step { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + flex: 1; + + &__dot { + width: 36px; + height: 36px; + border-radius: 50%; + border: 3px solid; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + &--inactive { + border-color: #f59e0b; + background: #fffbeb; + + &::after { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background: #f59e0b; + } + } + + &--dormant { + border-color: #ef4444; + background: #fef2f2; + + &::after { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background: #ef4444; + } + } + + &--escheat { + border-color: #7c3aed; + background: #f5f3ff; + + &::after { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background: #7c3aed; + } + } + } + + &__body { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: 2px; + } + + &__label { + font-size: 12px; + font-weight: 700; + color: #1e293b; + } + + &__days { + font-size: 10.5px; + color: #64748b; + line-height: 1.3; + } +} + +.preview-dorm-connector { + flex: 0 0 40px; + height: 3px; + background: linear-gradient(90deg, #e2e8f0, #cbd5e1); + margin-top: 16px; + border-radius: 2px; +} + +.preview-dorm-flags { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.preview-dorm-flag { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 4px 12px; + border-radius: 20px; + font-size: 11.5px; + font-weight: 600; + background: #f1f5f9; + color: #94a3b8; + border: 1px solid #e2e8f0; + + i { + font-size: 11px; + } + + &--on { + background: #f0fdf4; + color: #15803d; + border-color: #bbf7d0; + } +} + +// Non-Individual section +.preview-ni-label { + font-size: 11px; + font-weight: 700; + color: #64748b; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 8px; +} + +.preview-ni-table-wrap { + overflow-x: auto; + border: 1px solid #e2e8f0; + border-radius: 8px; + margin-bottom: 12px; +} + +.preview-ni-tbl { + width: 100%; + border-collapse: collapse; + font-size: 13px; + + th { + padding: 8px 12px; + font-size: 11px; + font-weight: 700; + color: #374151; + text-transform: uppercase; + background: #f8fafc; + border-bottom: 1px solid #e2e8f0; + text-align: left; + } + + td { + padding: 9px 12px; + color: #1e293b; + border-bottom: 1px solid #f3f4f6; + } +} + +.preview-ni-mode { + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; + color: #374151; + padding: 8px 12px; + background: #f0fdf4; + border: 1px solid #bbf7d0; + border-radius: 8px; + + &__label { + font-weight: 600; } } diff --git a/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.ts b/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.ts index 4daf9abfe..c5c139fd7 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.ts +++ b/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.ts @@ -83,18 +83,15 @@ export class CurrentAccountPreviewStepComponent implements OnChanges { private translateService = inject(TranslateService); - /** Current Account Product Template */ @Input() currentAccountProductTemplate: any; - /** Current Account Template */ @Input() currentAccountTemplate: any; - /** Current Account Terms Form */ @Input() currentAccountTermsForm: any; - /** Current Account Term Form */ @Input() currentAccountTermForm: any; - /** Current Account Settings Form */ @Input() currentAccountSettingsForm: any; - /** Current Account */ @Input() currentAccount: any; + @Input() currentAccountDigital: any; + @Input() currentAccountNominee: any; + @Input() currentAccountCurrencyFlags: any; @Input() activeClientMembers?: any; /** Table Data Source */ @@ -136,15 +133,31 @@ export class CurrentAccountPreviewStepComponent implements OnChanges { return this.currentAccountSettingsForm?.getRawValue?.() || this.currentAccount || {}; } - get digitalPreview(): any { - return this.currentAccountProductTemplate || {}; + get digitalFacilityConfig(): any { + return ( + this.currentAccountDigital?.accountFacilityConfig ?? + this.currentAccountProductTemplate?.accountFacilityConfig ?? + null + ); } get hasDigitalSection(): boolean { - return !!( - this.digitalPreview?.accountFacilityConfig || - this.digitalPreview?.accountFacilityLimits?.length || - this.digitalPreview?.depositLimitKyc + return !!this.digitalFacilityConfig; + } + + get currencyName(): string { + return ( + this.currentAccountProductTemplate?.currency?.name || + this.currentAccountTermsForm?.get?.('currencyCode')?.value || + '—' ); } + + get decimalPlaces(): string | number { + return this.currentAccountProductTemplate?.currency?.decimalPlaces ?? '—'; + } + + get inMultiplesOf(): string | number { + return this.currentAccountProductTemplate?.currency?.inMultiplesOf ?? '—'; + } } 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..b2f2a3d15 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 }} diff --git a/src/app/deposits/recurring-deposits/create-recurring-deposits-account/create-recurring-deposits-account.component.scss b/src/app/deposits/recurring-deposits/create-recurring-deposits-account/create-recurring-deposits-account.component.scss index af9f4c075..684642c73 100644 --- a/src/app/deposits/recurring-deposits/create-recurring-deposits-account/create-recurring-deposits-account.component.scss +++ b/src/app/deposits/recurring-deposits/create-recurring-deposits-account/create-recurring-deposits-account.component.scss @@ -5,3 +5,15 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +:host ::ng-deep mat-stepper { + background: transparent; + + .mat-step-content { + overflow: visible !important; + } +} + +:host ::ng-deep mat-step-content { + overflow: visible !important; +} diff --git a/src/app/deposits/recurring-deposits/create-recurring-deposits-account/create-recurring-deposits-account.component.ts b/src/app/deposits/recurring-deposits/create-recurring-deposits-account/create-recurring-deposits-account.component.ts index 0b5649391..46d217f0f 100644 --- a/src/app/deposits/recurring-deposits/create-recurring-deposits-account/create-recurring-deposits-account.component.ts +++ b/src/app/deposits/recurring-deposits/create-recurring-deposits-account/create-recurring-deposits-account.component.ts @@ -17,12 +17,17 @@ import { ProductsService } from 'app/products/products.service'; /** Step Components */ import { RecurringDepositsAccountDetailsStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component'; -import { RecurringDepositsAccountTermsStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-terms-step/recurring-deposits-account-terms-step.component'; -import { RecurringDepositsAccountSettingsStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-settings-step/recurring-deposits-account-settings-step.component'; -import { RecurringDepositsAccountChargesStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-charges-step/recurring-deposits-account-charges-step.component'; import { Dates } from 'app/core/utils/dates'; -import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper'; +import { + MatStepper, + MatStepperIcon, + MatStep, + MatStepLabel, + MatStepperNext, + MatStepperPrevious +} from '@angular/material/stepper'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { RecurringDepositsAccountCurrencyStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component'; import { RecurringDepositsAccountInterestRateChartStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-interest-rate-chart-step/recurring-deposits-account-interest-rate-chart-step.component'; import { RecurringDepositsAccountPreviewStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; @@ -38,14 +43,14 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; ...STANDALONE_SHARED_IMPORTS, MatStepper, MatStepperIcon, + MatStepperNext, + MatStepperPrevious, FaIconComponent, MatStep, MatStepLabel, RecurringDepositsAccountDetailsStepComponent, - RecurringDepositsAccountTermsStepComponent, - RecurringDepositsAccountSettingsStepComponent, + RecurringDepositsAccountCurrencyStepComponent, RecurringDepositsAccountInterestRateChartStepComponent, - RecurringDepositsAccountChargesStepComponent, RecurringDepositsAccountPreviewStepComponent ] }) @@ -57,15 +62,14 @@ export class CreateRecurringDepositsAccountComponent { private settingsService = inject(SettingsService); private productsService = inject(ProductsService); - /** Imports all the step component */ @ViewChild(RecurringDepositsAccountDetailsStepComponent, { static: true }) recurringDepositsAccountDetailsStep: RecurringDepositsAccountDetailsStepComponent; - @ViewChild(RecurringDepositsAccountTermsStepComponent, { static: true }) - recurringDepositAccountTermsStep: RecurringDepositsAccountTermsStepComponent; - @ViewChild(RecurringDepositsAccountSettingsStepComponent, { static: true }) - recurringDepositAccountSettingsStep: RecurringDepositsAccountSettingsStepComponent; - @ViewChild(RecurringDepositsAccountChargesStepComponent, { static: true }) - recurringDepositAccountChargesStep: RecurringDepositsAccountChargesStepComponent; + + @ViewChild(RecurringDepositsAccountCurrencyStepComponent) + recurringDepositsAccountCurrencyStep: RecurringDepositsAccountCurrencyStepComponent; + + @ViewChild(RecurringDepositsAccountInterestRateChartStepComponent) + recurringDepositsAccountChartStep: RecurringDepositsAccountInterestRateChartStepComponent; /** Recurring Deposits Account Template */ recurringDepositsAccountTemplate: any; @@ -83,7 +87,6 @@ export class CreateRecurringDepositsAccountComponent { /** * Sets recurring deposits account product template. * Also fetches the active CBS base interest rate chart. - * @param {any} $event API response (includes productId) */ setTemplate($event: any) { this.recurringDepositsAccountProductTemplate = $event; @@ -102,42 +105,36 @@ export class CreateRecurringDepositsAccountComponent { return this.recurringDepositsAccountDetailsStep.recurringDepositAccountDetailsForm; } - /** Get Recurring Deposit Account Terms Form Data */ - get recurringDepositAccountTermsForm() { - return this.recurringDepositAccountTermsStep.recurringDepositAccountTermsForm; - } - - /** Get Recurring Deposit Account Settings Form Data */ - get recurringDepositAccountSettingsForm() { - return this.recurringDepositAccountSettingsStep.recurringDepositAccountSettingsForm; - } - - /** Checks wheter all the forms in different steps are valid or not */ - get recurringDepositAccountFormValid() { - return ( - this.recurringDepositAccountDetailsForm.valid && - this.recurringDepositAccountTermsForm.valid && - this.recurringDepositAccountSettingsForm.valid - ); - } - - /** Retrieves Data of all the forms */ + /** Retrieves Data of all forms */ get recurringDepositAccountData() { return { - ...this.recurringDepositsAccountDetailsStep.recurringDepositAccountDetails, - ...this.recurringDepositAccountTermsStep.recurringDepositAccountTerms, - ...this.recurringDepositAccountSettingsStep.recurringDepositAccountSettings, - ...this.recurringDepositAccountChargesStep.recurringDepositAccountCharges + ...this.recurringDepositsAccountDetailsStep.recurringDepositAccountDetails }; } - /** Retrieves Data of all forms except Currency to submit the data */ get recurringDepositAccount() { return { - ...this.recurringDepositsAccountDetailsStep.recurringDepositAccountDetails, - ...this.recurringDepositAccountTermsStep.recurringDepositAccountTerms, - ...this.recurringDepositAccountSettingsStep.recurringDepositAccountSettings, - ...this.recurringDepositAccountChargesStep.recurringDepositAccountCharges + ...this.recurringDepositsAccountDetailsStep.recurringDepositAccountDetails + }; + } + + get rdNominee() { + return this.recurringDepositsAccountCurrencyStep?.nomineeData ?? null; + } + + get rdTenureDays(): number | null { + return this.recurringDepositsAccountChartStep?.tenureDays ?? null; + } + + get rdApplicableSlab(): any | null { + return this.recurringDepositsAccountChartStep?.applicableSlab ?? null; + } + + get rdCurrencyFlags() { + return { + balanceSwapOption: this.recurringDepositsAccountCurrencyStep?.balanceSwapOption ?? false, + msme: this.recurringDepositsAccountCurrencyStep?.msme ?? false, + autoDebitFacility: this.recurringDepositsAccountCurrencyStep?.autoDebitFacility ?? false }; } @@ -150,17 +147,10 @@ export class CreateRecurringDepositsAccountComponent { const monthDayFormat = 'dd MMMM'; const recurringDepositAccount = { ...this.recurringDepositAccount, + ...this.recurringDepositsAccountCurrencyStep.currencyStepData, clientId: this.recurringDepositsAccountTemplate.clientId, - charges: this.recurringDepositAccount.charges.map((charge: any) => ({ - chargeId: charge.id, - amount: charge.amount, - dueDate: charge.dueDate && this.dateUtils.formatDate(charge.dueDate, dateFormat), - feeOnMonthDay: charge.feeOnMonthDay, - feeInterval: charge.feeInterval - })), - isCalendarInherited: this.recurringDepositAccount.recurringDepositAccount - ? this.recurringDepositAccount.recurringDepositAccount - : false, + charges: [], + isCalendarInherited: false, submittedOnDate: this.dateUtils.formatDate(this.recurringDepositAccount.submittedOnDate, dateFormat), expectedFirstDepositOnDate: this.dateUtils.formatDate( this.recurringDepositAccount.expectedFirstDepositOnDate, 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..569e917ee --- /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' }} + +
+ @if (recurringDepositsAccountProductTemplate?.dormancyAlert) { + + } + +
+
+

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..186f2f076 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) { - +
@@ -83,8 +83,14 @@ - Product Code - + Account No + @if (productTemplate.effectiveDate) { @@ -115,101 +121,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..50fb89362 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,9 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { recurringDepositAccountDetailsForm!: UntypedFormGroup; isProductSelected = false; + introducerId: number | null = null; + introducerName: string | null = null; + introducerCif: string | null = null; /** Recurring Deposits Account Template with product data */ @Output() recurringDepositsAccountProductTemplate = new EventEmitter(); @@ -106,7 +111,10 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { Validators.required ], fieldOfficerId: [''], - externalId: [''] + externalId: [''], + balanceSwapOption: [false], + msme: [false], + autoDebitFacility: [false] }); } @@ -135,6 +143,12 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { this.recurringDepositsAccountProductTemplate.emit(response); this.fieldOfficerData = response.fieldOfficerOptions; this.isProductSelected = true; + // Pre-populate toggles from product defaults (user can override) + this.recurringDepositAccountDetailsForm.patchValue({ + balanceSwapOption: !!response.balanceSwapOption, + msme: !!response.msme, + autoDebitFacility: !!response.autoDebitFacility + }); this.cdr.detectChanges(); if (!this.isFieldOfficerPatched && this.recurringDepositsAccountTemplate.fieldOfficerId) { this.recurringDepositAccountDetailsForm @@ -184,7 +198,19 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit { /** * Returns recurring deposits account details form value. */ + onIntroducerChange(event: { clientId: number; displayName: string; cifNumber?: string } | null): void { + this.introducerId = event?.clientId ?? null; + this.introducerName = event?.displayName ?? null; + this.introducerCif = event?.cifNumber ?? null; + } + get recurringDepositAccountDetails() { - return this.recurringDepositAccountDetailsForm.value; + const value = this.recurringDepositAccountDetailsForm.value; + if (this.introducerId) { + value.introducerId = this.introducerId; + value.introducerName = this.introducerName; + value.introducerCif = this.introducerCif; + } + return value; } } 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..aeda181b5 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 @@ -7,214 +7,46 @@ -->
- + @if (_activeRdVersion) { -
- {{ _activeRdVersion.versionLabel }} - Active - Effective from: {{ _activeRdVersion.effectiveFromDate }} -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Sr.{{ slab.srNo }}Particular{{ slab.description }}From{{ slab.fromPeriod }}To{{ slab.toPeriod ?? '—' }}Period Type{{ slab.periodTypeLabel }}Annual Rate (%) - {{ slab.annualInterestRate | number: '1.2-2' }} -
- } - - - @if (!_activeRdVersion) { -
- {{ 'labels.inputs.name' | translate }}: - {{ recurringDepositsAccountProductTemplate?.accountChart?.name }} -
- -
- {{ 'labels.inputs.Valid from Date' | translate }}: - {{ recurringDepositsAccountProductTemplate?.accountChart?.fromDate | dateFormat }} -
- -
- {{ 'labels.inputs.End Date' | translate }}: - {{ recurringDepositsAccountProductTemplate?.accountChart?.endDate | dateFormat }} -
- -
- {{ 'labels.inputs.Description' | translate }}: - {{ recurringDepositsAccountProductTemplate?.accountChart?.description }} -
- -
- {{ 'labels.inputs.Primary Grouping by Amount' | translate }}: - {{ - recurringDepositsAccountProductTemplate?.accountChart?.isPrimaryGroupingByAmount | yesNo - }} -
- } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ 'labels.inputs.Period' | translate }} - {{ chartSlab.fromPeriod }} - {{ chartSlab.toPeriod }} {{ - chartSlab.periodType.value | translateKey: 'catalogs' - }} - {{ 'labels.inputs.Amount Range' | translate }} - {{ chartSlab.amountRangeFrom | formatNumber }} - {{ chartSlab.amountRangeTo | formatNumber }} - {{ 'labels.inputs.Interest' | translate }}{{ chartSlab.annualInterestRate | formatNumber }} %{{ 'labels.inputs.Description' | translate }}{{ chartSlab.description }}{{ 'labels.inputs.Actions' | translate }} - - -
- -

- {{ 'labels.heading.Incentives' | translate }} -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ 'labels.inputs.Entity Type' | translate }} - {{ incentive.entityType.value }} - {{ 'labels.inputs.Attribute Name' | translate }} - {{ incentive.attributeName.value }} - {{ 'labels.inputs.Condition Type' | translate }} - {{ incentive.conditionType.value | titlecase }} - {{ 'labels.inputs.Attribute Value' | translate }} - {{ incentive.attributeValueDesc }} - {{ 'labels.inputs.Incentive Type' | translate }} - {{ incentive.incentiveType.value }} - {{ 'labels.inputs.Interest' | translate }} - {{ incentive.amount | formatNumber }} -
-
+
+
+ + Tenure & Interest Calculator +
+
+
+ + Enter Tenure (Days) + + Enter the number of days for your RD +
-
-
- -
- - + @if (tenureDays && tenureDays > 0) { + @if (applicableSlab) { +
+
+ Tenure + {{ tenureDays }} Days +
+
+ Applicable Slab + {{ applicableSlab.description }} +
+
+ Annual Interest Rate + {{ applicableSlab.annualInterestRate | number: '1.2-2' }}% +
+
+ } @else { +
+ + No matching slab found for {{ tenureDays }} days +
+ } + } +
+
+ }
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.scss 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.scss index 03ea8d084..12e438941 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.scss +++ 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.scss @@ -6,6 +6,105 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +// ── Tenure Calculator ───────────────────────────────────────────────────────── +.tenure-calc { + border: 1px solid #e2e8f0; + border-radius: 10px; + overflow: hidden; + background: #fff; + box-shadow: 0 1px 4px rgb(0 0 0 / 5%); + margin-bottom: 1.25rem; + + &__header { + display: flex; + align-items: center; + gap: 8px; + background: #f0f9ff; + border-bottom: 1px solid #bae6fd; + padding: 0.65rem 1rem; + font-size: 0.85rem; + font-weight: 700; + color: #0369a1; + text-transform: uppercase; + letter-spacing: 0.05em; + + i { + font-size: 13px; + } + } + + &__body { + padding: 1rem 1.25rem; + } + + &__input-row { + display: flex; + align-items: flex-start; + gap: 1rem; + } + + &__field { + width: 260px; + } + + &__result { + margin-top: 1rem; + border: 1px solid #e0f2fe; + border-radius: 8px; + overflow: hidden; + } + + &__result-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.55rem 1rem; + border-bottom: 1px solid #f0f9ff; + font-size: 0.88rem; + + &:last-child { + border-bottom: none; + } + + &--highlight { + background: #f0f9ff; + } + } + + &__result-label { + color: #64748b; + font-weight: 500; + } + + &__result-value { + color: #1e293b; + font-weight: 600; + } + + &__result-rate { + font-size: 1.25rem; + font-weight: 800; + color: #0369a1; + } + + &__no-match { + margin-top: 0.75rem; + padding: 0.6rem 1rem; + border-radius: 8px; + background: #fef2f2; + color: #dc2626; + font-size: 0.85rem; + font-weight: 600; + display: flex; + align-items: center; + gap: 6px; + + i { + font-size: 13px; + } + } +} + .margin-t { margin-top: 2em; } 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.ts 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.ts index 0836f8502..07bc5a5e3 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.ts +++ 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.ts @@ -7,7 +7,7 @@ */ /** Angular Imports */ -import { Component, OnInit, Input, OnChanges, ViewChild } from '@angular/core'; +import { Component, OnInit, Input, OnChanges, ViewChild, Output, EventEmitter } from '@angular/core'; import { MatTable, MatColumnDef, @@ -28,6 +28,7 @@ import { DateFormatPipe } from '../../../../pipes/date-format.pipe'; import { FormatNumberPipe } from '../../../../pipes/format-number.pipe'; import { YesnoPipe } from '../../../../pipes/yesno.pipe'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { FormsModule } from '@angular/forms'; /** * Recurring Deposits Account Interest Rate Chart Step @@ -45,6 +46,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; ], imports: [ ...STANDALONE_SHARED_IMPORTS, + FormsModule, MatTable, MatColumnDef, MatHeaderCellDef, @@ -114,10 +116,26 @@ export class RecurringDepositsAccountInterestRateChartStepComponent implements O /** Expand Chart Slab Index used in the view */ expandChartSlabIndex: number; + /** Tenure entered by user (in days) */ + tenureDays: number | null = null; + @ViewChild('chartsTable', { static: true }) chartsTableRef: MatTable; constructor() {} + /** Find the matching slab for the entered tenure */ + get applicableSlab(): any | null { + if (!this.tenureDays || !this._activeRdVersion?.slabs?.length) return null; + const days = this.tenureDays; + return ( + this._activeRdVersion.slabs.find((s: any) => { + const from = s.fromPeriod ?? 0; + const to = s.toPeriod ?? Infinity; + return days >= from && days <= to; + }) ?? null + ); + } + ngOnChanges() { if (this.recurringDepositsAccountProductTemplate) { this.interestRateChartData = this.recurringDepositsAccountProductTemplate.accountChart?.chartSlabs ?? []; 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..60ca14d64 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 @@ -7,9 +7,10 @@ -->
- +
+ {{ 'labels.heading.Details' | translate }}
@@ -21,12 +22,10 @@ | find: recurringDepositsAccountTemplate?.productOptions : 'id' : 'name' }}
-
{{ 'labels.inputs.Submitted On' | translate }} {{ recurringDepositAccountData?.submittedOnDate | dateFormat }}
-
{{ 'labels.inputs.Field Officer' | translate }} {{ @@ -34,18 +33,17 @@ | find: recurringDepositsAccountProductTemplate?.fieldOfficerOptions : 'id' : 'displayName' }}
- @if (recurringDepositAccountData?.externalId) {
{{ 'labels.inputs.External Id' | translate }} - {{ recurringDepositAccountData?.externalId }} + {{ recurringDepositAccountData.externalId }}
}
- + @if ( recurringDepositsAccountProductTemplate?.accountType || recurringDepositsAccountProductTemplate?.customerSegment || @@ -57,40 +55,42 @@ ) {
+ Product Classification + From Product
@if (recurringDepositsAccountProductTemplate?.accountType) {
Account Type - {{ recurringDepositsAccountProductTemplate?.accountType }} + {{ recurringDepositsAccountProductTemplate.accountType }}
} @if (recurringDepositsAccountProductTemplate?.customerSegment) {
Customer Segment - {{ recurringDepositsAccountProductTemplate?.customerSegment }} + {{ recurringDepositsAccountProductTemplate.customerSegment }}
} @if (recurringDepositsAccountProductTemplate?.productCode) {
Product Code - {{ recurringDepositsAccountProductTemplate?.productCode }} + {{ recurringDepositsAccountProductTemplate.productCode }}
} @if (recurringDepositsAccountProductTemplate?.effectiveDate) {
Effective Date {{ - recurringDepositsAccountProductTemplate?.effectiveDate | dateFormat + recurringDepositsAccountProductTemplate.effectiveDate | dateFormat }}
} @if (recurringDepositsAccountProductTemplate?.endDate) {
End Date - {{ recurringDepositsAccountProductTemplate?.endDate | dateFormat }} + {{ recurringDepositsAccountProductTemplate.endDate | dateFormat }}
} @if ( @@ -101,9 +101,9 @@ Introducer Required - {{ recurringDepositsAccountProductTemplate?.introducerRequired | yesNo }} + {{ recurringDepositsAccountProductTemplate.introducerRequired | yesNo }}
} @@ -115,9 +115,9 @@ Share Linking Enabled - {{ recurringDepositsAccountProductTemplate?.shareLinkingEnabled | yesNo }} + {{ recurringDepositsAccountProductTemplate.shareLinkingEnabled | yesNo }}
} @@ -126,32 +126,54 @@
} - + + @if (recurringDepositAccountData?.introducerId) { +
+
+ + Introducer Details +
+
+
+
+ Name + {{ recurringDepositAccountData?.introducerName || '—' }} +
+ @if (recurringDepositAccountData?.introducerCif) { +
+ CIF No + {{ recurringDepositAccountData.introducerCif }} +
+ } +
+ Client ID + {{ recurringDepositAccountData.introducerId }} +
+
+
+
+ } + +
+ {{ 'labels.heading.Currency' | translate }}
{{ 'labels.inputs.Currency' | translate }} - {{ - recurringDepositsAccountProductTemplate?.currency?.code || - recurringDepositsAccountProductTemplate?.currency?.name || - '—' - }} + {{ currencyName }}
-
{{ 'labels.inputs.Decimal Places' | translate }} - {{ recurringDepositsAccountProductTemplate?.currency?.decimalPlaces }} + {{ decimalPlaces }}
-
{{ 'labels.inputs.Currency In Multiples Of' | translate }} - {{ recurringDepositsAccountProductTemplate?.currency?.inMultiplesOf }} + {{ inMultiplesOf }}
- @if (recurringDepositsAccountProductTemplate?.withHoldTax) {
{{ 'labels.inputs.Is Withhold Tax Applicable' | translate }} @@ -163,14 +185,30 @@
} - @if (recurringDepositAccountData?.withHoldTax && recurringDepositsAccountProductTemplate?.taxGroup?.name) {
{{ 'labels.inputs.Tax Group' | translate }} - {{ recurringDepositsAccountProductTemplate?.taxGroup?.name }} + {{ recurringDepositsAccountProductTemplate.taxGroup.name }}
} - +
+ Balance Swap Option + + {{ rdCurrencyFlags?.balanceSwapOption | yesNo }} + +
+
+ MSME + + {{ rdCurrencyFlags?.msme | yesNo }} + +
+
+ Auto Debit Facility + + {{ rdCurrencyFlags?.autoDebitFacility | yesNo }} + +
@if ( recurringDepositsAccountProductTemplate?.dormancyAlert !== undefined && recurringDepositsAccountProductTemplate?.dormancyAlert !== null @@ -179,43 +217,12 @@ Dormancy Alert - {{ recurringDepositsAccountProductTemplate?.dormancyAlert | yesNo }} + {{ recurringDepositsAccountProductTemplate.dormancyAlert | yesNo }}
} - - @if ( - recurringDepositsAccountProductTemplate?.balanceSwapOption !== undefined && - recurringDepositsAccountProductTemplate?.balanceSwapOption !== null - ) { -
- Balance Swap Option - - {{ recurringDepositsAccountProductTemplate?.balanceSwapOption | yesNo }} - -
- } - - @if ( - recurringDepositsAccountProductTemplate?.msme !== undefined && - recurringDepositsAccountProductTemplate?.msme !== null - ) { -
- MSME - - {{ recurringDepositsAccountProductTemplate?.msme | yesNo }} - -
- } - @if ( recurringDepositsAccountProductTemplate?.nominationFacility !== undefined && recurringDepositsAccountProductTemplate?.nominationFacility !== null @@ -224,24 +231,9 @@ Nomination Facility - {{ recurringDepositsAccountProductTemplate?.nominationFacility | yesNo }} - -
- } - - @if ( - recurringDepositsAccountProductTemplate?.autoDebitFacility !== undefined && - recurringDepositsAccountProductTemplate?.autoDebitFacility !== null - ) { -
- Auto Debit Facility - - {{ recurringDepositsAccountProductTemplate?.autoDebitFacility | yesNo }} + {{ recurringDepositsAccountProductTemplate.nominationFacility | yesNo }}
} @@ -249,403 +241,235 @@
- -
-
- {{ '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 - }} -
- } + + @if ( + recurringDepositsAccountProductTemplate?.dormancyAlert || + recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct + ) { +
+
+ + Dormancy Monitoring + + {{ recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct ? 'Active' : 'Inactive' }} +
-
-
- - -
-
- {{ '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' - }} +
+
+
+
+
+ Inactive + + @if (recurringDepositsAccountProductTemplate?.daysToInactiveFromProduct != null) { + After {{ recurringDepositsAccountProductTemplate.daysToInactiveFromProduct }} days of no transaction + } @else { + Not configured + }
- } -
-
- - - - -
-
Maturity Instructions
-
-
- Maturity Instructions - - {{ recurringDepositAccountData?.maturityInstructions | yesNo }} -
-
- Credit Maturity Proceeds - - {{ recurringDepositAccountData?.creditMaturityProceeds | yesNo }} - +
+
+
+
+ Dormant + + @if (recurringDepositsAccountProductTemplate?.daysToDormancyFromProduct != null) { + {{ recurringDepositsAccountProductTemplate.daysToDormancyFromProduct }} days after inactive + } @else { + Not configured + } + +
-
- 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 }} - +
+
+
+
+ Escheat + + @if (recurringDepositsAccountProductTemplate?.daysToEscheatFromProduct != null) { + {{ recurringDepositsAccountProductTemplate.daysToEscheatFromProduct }} days after dormant + } @else { + Not configured + } + +
+
+ + Alert + {{ recurringDepositsAccountProductTemplate?.dormancyAlert ? 'Enabled' : 'Disabled' }} + + + Tracking + {{ recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct ? 'Active' : 'Inactive' }} + +
+
+ } - - - -
-
Additional Rules
+ + @if (rdNominee) { +
+
+ + Nominee Details + + {{ rdNominee?.mode === 'linked' ? 'Linked Client' : 'Manual Entry' }} + +
+
- Account Closed with Zero Balance in Day Closing - - {{ recurringDepositAccountData?.accountClosedZeroBalanceInDayClosing | yesNo }} - + Name + {{ rdNominee?.fullName || rdNominee?.displayName || '—' }}
- @if (recurringDepositAccountData?.minBalanceForInterestCalculation) { + @if (rdNominee?.cifNumber) {
- Min. Balance for Interest Calculation - {{ - recurringDepositAccountData?.minBalanceForInterestCalculation | formatNumber - }} + CIF No + {{ rdNominee.cifNumber }}
} - @if (recurringDepositAccountData?.mandatoryRecommendedDepositAmount) { +
+ Relationship + {{ rdNominee?.relationship || '—' }} +
+
+ Share % + {{ rdNominee?.nomineeShare ?? '—' }}% +
+ @if (rdNominee?.dateOfBirth) {
- {{ 'labels.inputs.Recurring Deposit Amount' | translate }} - {{ - recurringDepositAccountData?.mandatoryRecommendedDepositAmount | formatNumber - }} + Date of Birth + {{ rdNominee?.dateOfBirth | dateFormat }} +
+ } + @if (rdNominee?.nationality) { +
+ Nationality + {{ rdNominee.nationality }} +
+ } + @if (rdNominee?.mobileNo) { +
+ Mobile No + {{ rdNominee.mobileNo }} +
+ } + @if (rdNominee?.aadhaarNumber) { +
+ Aadhaar No + {{ rdNominee.aadhaarNumber }} +
+ } + @if (rdNominee?.panNumber) { +
+ PAN No + {{ rdNominee.panNumber }} +
+ } + @if (rdNominee?.guardianName) { +
+ Guardian Name + {{ rdNominee.guardianName }} +
+ } + @if (rdNominee?.guardianRelationship) { +
+ Guardian Relationship + {{ rdNominee.guardianRelationship }} +
+ } + @if (rdNominee?.remarks) { +
+ Remarks + {{ rdNominee.remarks }}
}
-
+ } - + @if (_activeRdVersion || recurringDepositsAccountProductTemplate?.accountChart) {
- {{ 'labels.inputs.Interest Rate Chart' | translate }} + + Interest Rate + @if (_activeRdVersion) { + {{ _activeRdVersion.versionLabel }} + }
@if (_activeRdVersion) { -
-
- Version - {{ _activeRdVersion.versionLabel }} + @if (rdTenureDays && rdApplicableSlab) { +
+
+ Tenure + {{ rdTenureDays }} Days +
+
+ Applicable Slab + {{ rdApplicableSlab.description }} +
+
+ Annual Interest Rate + {{ rdApplicableSlab.annualInterestRate | formatNumber }}% +
-
- Active - Yes -
-
- Effective From - {{ _activeRdVersion.effectiveFromDate }} -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Sr.{{ slab.srNo }}Particular{{ slab.description }}From{{ slab.fromPeriod }}To{{ slab.toPeriod ?? 'Unassigned' }}Period Type{{ slab.periodTypeLabel }}Annual Rate (%){{ slab.annualInterestRate | formatNumber }}
+ } @else { + No tenure entered — interest rate not calculated + } } @else {
@if (recurringDepositsAccountProductTemplate?.accountChart?.name) {
{{ 'labels.inputs.name' | translate }} - {{ recurringDepositsAccountProductTemplate?.accountChart?.name }} + {{ recurringDepositsAccountProductTemplate.accountChart.name }}
} - @if (recurringDepositsAccountProductTemplate?.accountChart?.fromDate) {
{{ 'labels.inputs.Valid from Date' | translate }} {{ - recurringDepositsAccountProductTemplate?.accountChart?.fromDate | dateFormat + recurringDepositsAccountProductTemplate.accountChart.fromDate | dateFormat }}
} - @if (recurringDepositsAccountProductTemplate?.accountChart?.endDate) {
{{ 'labels.inputs.End Date' | translate }} {{ - recurringDepositsAccountProductTemplate?.accountChart?.endDate | dateFormat + recurringDepositsAccountProductTemplate.accountChart.endDate | dateFormat }}
} - @if (recurringDepositsAccountProductTemplate?.accountChart?.description) {
{{ 'labels.inputs.Description' | translate }} {{ - recurringDepositsAccountProductTemplate?.accountChart?.description + recurringDepositsAccountProductTemplate.accountChart.description }}
} -
{{ 'labels.inputs.Primary Grouping by Amount' | translate }} {{ @@ -669,24 +493,20 @@ }} - {{ 'labels.inputs.Amount Range' | translate }} {{ chartSlab.amountRangeFrom | formatNumber }} - {{ chartSlab.amountRangeTo | formatNumber }} - {{ 'labels.inputs.Interest' | translate }} {{ chartSlab.annualInterestRate | formatNumber }} % - {{ 'labels.inputs.Description' | translate }} {{ chartSlab.description }} - {{ 'labels.inputs.Actions' | translate }} @@ -706,7 +526,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/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component.scss b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component.scss index 433db21ac..99d36156b 100644 --- a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component.scss +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component.scss @@ -23,18 +23,48 @@ box-shadow: 0 1px 3px rgb(0 0 0 / 6%); &__header { + display: flex; + align-items: center; + gap: 8px; background: #f5f5f5; border-bottom: 1px solid #e0e0e0; padding: 0.6rem 1rem; } + &__icon { + font-size: 13px; + color: #0ea5e9; + width: 16px; + text-align: center; + } + &__title { + flex: 1; font-size: 0.95rem; font-weight: 600; color: #333; text-transform: uppercase; letter-spacing: 0.04em; } + + &__badge { + display: inline-flex; + align-items: center; + padding: 2px 10px; + border-radius: 999px; + font-size: 10px; + font-weight: 700; + background: #dbeafe; + color: #1d4ed8; + border: 1px solid #bfdbfe; + text-transform: uppercase; + + &--active { + background: #dcfce7 !important; + color: #15803d !important; + border-color: #bbf7d0 !important; + } + } } // ── Body ───────────────────────────────────────────────────────────────────── @@ -146,6 +176,169 @@ margin-top: 1em; } +// ── Preview Tenure Result Card ───────────────────────────────────────────────── +.preview-tenure-result { + border: 1px solid #e0f2fe; + border-radius: 10px; + overflow: hidden; + + &__row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.65rem 1rem; + border-bottom: 1px solid #f0f9ff; + + &:last-child { + border-bottom: none; + } + + &--highlight { + background: #f0f9ff; + } + } + + &__rate { + font-size: 1.5rem; + font-weight: 800; + color: #0369a1; + } +} + +// ── Misc ────────────────────────────────────────────────────────────────────── +.preview-muted { + color: #94a3b8; + font-style: italic; +} + +.preview-mono { + font-family: monospace; + font-size: 0.88rem; + color: #334155; +} + +// ── Dormancy timeline ───────────────────────────────────────────────────────── +.preview-dormancy-timeline { + display: flex; + align-items: flex-start; + margin-bottom: 14px; +} + +.preview-dorm-step { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + flex: 1; + + &__dot { + width: 36px; + height: 36px; + border-radius: 50%; + border: 3px solid; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + &--inactive { + border-color: #f59e0b; + background: #fffbeb; + + &::after { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background: #f59e0b; + } + } + + &--dormant { + border-color: #ef4444; + background: #fef2f2; + + &::after { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background: #ef4444; + } + } + + &--escheat { + border-color: #7c3aed; + background: #f5f3ff; + + &::after { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background: #7c3aed; + } + } + } + + &__body { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: 2px; + } + + &__label { + font-size: 12px; + font-weight: 700; + color: #1e293b; + } + + &__days { + font-size: 10.5px; + color: #64748b; + line-height: 1.3; + } +} + +.preview-dorm-connector { + flex: 0 0 40px; + height: 3px; + background: linear-gradient(90deg, #e2e8f0, #cbd5e1); + margin-top: 16px; + border-radius: 2px; +} + +.preview-dorm-flags { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.preview-dorm-flag { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 4px 12px; + border-radius: 20px; + font-size: 11.5px; + font-weight: 600; + background: #f1f5f9; + color: #94a3b8; + border: 1px solid #e2e8f0; + + i { + font-size: 11px; + } + + &--on { + background: #f0fdf4; + color: #15803d; + border-color: #bbf7d0; + } +} + // ── Product feature flag rows (same style as details step) ──────────────────── .preview-feature-list { margin-top: 1rem; diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component.ts b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component.ts index d3186524f..f67e5f035 100644 --- a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component.ts +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component.ts @@ -73,6 +73,10 @@ export class RecurringDepositsAccountPreviewStepComponent implements OnChanges { @Input() recurringDepositsAccountTemplate: any; @Input() recurringDepositsAccountProductTemplate: any; @Input() recurringDepositAccountData: any; + @Input() rdNominee: any = null; + @Input() rdCurrencyFlags: any = null; + @Input() rdTenureDays: number | null = null; + @Input() rdApplicableSlab: any = null; @Input() set rdInterestRates(rates: any[]) { this._rdInterestRates = rates || []; this._activeRdVersion = this._rdInterestRates.find((v: any) => v.isActive) ?? this._rdInterestRates[0] ?? null; @@ -80,6 +84,18 @@ export class RecurringDepositsAccountPreviewStepComponent implements OnChanges { /** Output the submit action */ @Output() submitEvent = new EventEmitter(); + get currencyName(): string { + return this.recurringDepositsAccountProductTemplate?.currency?.name || '—'; + } + + get decimalPlaces(): string | number { + return this.recurringDepositsAccountProductTemplate?.currency?.decimalPlaces ?? '—'; + } + + get inMultiplesOf(): string | number { + return this.recurringDepositsAccountProductTemplate?.currency?.inMultiplesOf ?? '—'; + } + _rdInterestRates: any[] = []; _activeRdVersion: any = null; diff --git a/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.html b/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.html index 7b619793d..65c350380 100644 --- a/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.html +++ b/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.html @@ -82,8 +82,12 @@ - Product Code - + GL Account (Liability) + + @for (account of liabilityAccountOptions; track account.id) { + {{ account.name }} ({{ account.glCode }}) + } + diff --git a/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.ts b/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.ts index 1aba2fbac..0c7125ca5 100644 --- a/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.ts +++ b/src/app/products/current-products/current-product-stepper/current-product-details-step/current-product-details-step.component.ts @@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { Component, OnInit, OnChanges, Input, SimpleChanges, inject } from '@angular/core'; +import { Component, OnInit, OnChanges, Input, SimpleChanges, inject, ChangeDetectorRef } from '@angular/core'; import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; import { MatTooltip } from '@angular/material/tooltip'; import { CdkTextareaAutosize } from '@angular/cdk/text-field'; @@ -30,12 +30,14 @@ export class CurrentProductDetailsStepComponent implements OnInit, OnChanges { private formBuilder = inject(UntypedFormBuilder); private dateUtils = inject(Dates); private settingsService = inject(SettingsService); + private cdr = inject(ChangeDetectorRef); @Input() CurrentProductsTemplate: any; CurrentProductDetailsForm: UntypedFormGroup; accountTypeData: string[] = []; customerSegmentData: string[] = []; + liabilityAccountOptions: any[] = []; yesNoOptions: Array<{ value: boolean; label: string; @@ -82,7 +84,7 @@ export class CurrentProductDetailsStepComponent implements OnInit, OnChanges { Validators.required ], customerSegment: [null], - productCode: [''], + glAccountId: [null], effectiveTo: [''], endDate: [''], introducerRequired: [false], @@ -124,6 +126,8 @@ export class CurrentProductDetailsStepComponent implements OnInit, OnChanges { this.accountTypeData = accountTypeOptions; this.customerSegmentData = customerSegmentOptions; + this.liabilityAccountOptions = template.accountingMappingOptions?.liabilityAccountOptions ?? []; + this.cdr.detectChanges(); if (yesNoOptions.length) { this.yesNoOptions = yesNoOptions; } @@ -136,7 +140,7 @@ export class CurrentProductDetailsStepComponent implements OnInit, OnChanges { description: template.description, accountType: template.accountType ?? this.accountTypeData[0] ?? null, customerSegment: template.customerSegment ?? this.customerSegmentData[0] ?? null, - productCode: template.productCode ?? '', + glAccountId: template.glAccountId ?? null, effectiveTo: this.parseDate(template.effectiveTo), endDate: this.parseDate(template.endDate), introducerRequired: template.introducerRequired === true || template.introducerRequired === 'Yes', @@ -173,6 +177,10 @@ export class CurrentProductDetailsStepComponent implements OnInit, OnChanges { .filter((value) => !!value); } + compareById(o1: any, o2: any): boolean { + return o1 != null && o2 != null && Number(o1) === Number(o2); + } + get CurrentProductDetails() { const formValue = { ...this.CurrentProductDetailsForm.value }; const dateFormat = this.settingsService.dateFormat; diff --git a/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.html b/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.html index 68e5bdde8..c74008227 100644 --- a/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.html +++ b/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.html @@ -28,7 +28,7 @@ @if ( CurrentProduct.accountType || CurrentProduct.customerSegment || - CurrentProduct.productCode || + CurrentProduct.glAccountId || CurrentProduct.effectiveTo || CurrentProduct.introducerRequired !== undefined || CurrentProduct.shareLinkingEnabled !== undefined @@ -51,10 +51,10 @@
} - @if (CurrentProduct.productCode) { + @if (CurrentProduct.glAccountId) {
- Product Code: - {{ CurrentProduct.productCode }} + GL Account (Liability): + {{ getGlAccountName(CurrentProduct.glAccountId) }}
} diff --git a/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.ts b/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.ts index 2ff2bd27c..8800b5b92 100644 --- a/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.ts +++ b/src/app/products/current-products/current-product-stepper/current-product-preview-step/current-product-preview-step.component.ts @@ -205,6 +205,13 @@ export class CurrentProductPreviewStepComponent implements OnInit, OnChanges { return value === true || value === 'Yes' ? 'Yes' : 'No'; } + getGlAccountName(id: any): string { + if (!id) return ''; + const accounts = this.CurrentProductsTemplate?.accountingMappingOptions?.liabilityAccountOptions ?? []; + const found = accounts.find((a: any) => a.id === id || a.id === +id); + return found ? `${found.name} (${found.glCode})` : String(id); + } + getLockinPeriodFrequencyTypeLabel(value: any): string { switch (value) { case 'DAYS': diff --git a/src/app/products/current-products/view-current-product/current-product-general-tab/current-product-general-tab.component.html b/src/app/products/current-products/view-current-product/current-product-general-tab/current-product-general-tab.component.html index c316d2003..0a68dd9d7 100644 --- a/src/app/products/current-products/view-current-product/current-product-general-tab/current-product-general-tab.component.html +++ b/src/app/products/current-products/view-current-product/current-product-general-tab/current-product-general-tab.component.html @@ -40,7 +40,7 @@ @if ( currentProduct.accountType || currentProduct.customerSegment || - currentProduct.productCode || + currentProduct.glAccountId || currentProduct.effectiveDate || currentProduct.effectiveTo || currentProduct.endDate || @@ -65,10 +65,10 @@
} - @if (currentProduct.productCode) { + @if (currentProduct.glAccountId) {
- Product Code: - {{ currentProduct.productCode }} + GL Account (Liability): + {{ currentProduct.glAccountName || currentProduct.glAccountId }}
} 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/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.html b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.html index 7e5f20a46..35a3129ac 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.html +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.html @@ -88,8 +88,12 @@ - Product Code - + GL Account (Liability) + + @for (account of liabilityAccountOptions; track account.id) { + {{ account.name }} ({{ account.glCode }}) + } + diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.ts b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.ts index 3f2cd558c..749ac8a3b 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.ts +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-details-step/recurring-deposit-product-details-step.component.ts @@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { Component, OnInit, OnChanges, Input, SimpleChanges, inject } from '@angular/core'; +import { Component, OnInit, OnChanges, Input, SimpleChanges, inject, ChangeDetectorRef } from '@angular/core'; import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; import { MatTooltip } from '@angular/material/tooltip'; import { CdkTextareaAutosize } from '@angular/cdk/text-field'; @@ -28,12 +28,14 @@ export class RecurringDepositProductDetailsStepComponent implements OnInit, OnCh private formBuilder = inject(UntypedFormBuilder); private dateUtils = inject(Dates); private settingsService = inject(SettingsService); + private cdr = inject(ChangeDetectorRef); @Input() recurringDepositProductsTemplate: any; recurringDepositProductDetailsForm: UntypedFormGroup; accountTypeData: string[] = []; customerSegmentData: string[] = []; + liabilityAccountOptions: any[] = []; yesNoOptions: Array<{ value: boolean; label: string }> = [ { value: true, label: 'Yes' }, { value: false, label: 'No' } @@ -71,7 +73,7 @@ export class RecurringDepositProductDetailsStepComponent implements OnInit, OnCh Validators.required ], customerSegment: [null], - productCode: [''], + glAccountId: [null], effectiveTo: [''], endDate: [''], introducerRequired: [false], @@ -99,9 +101,15 @@ export class RecurringDepositProductDetailsStepComponent implements OnInit, OnCh return new Date(dateValue); } + compareById(o1: any, o2: any): boolean { + return o1 != null && o2 != null && Number(o1) === Number(o2); + } + private applyTemplate(template: any) { this.accountTypeData = this.getOptionNames(template.accountTypeOptions); this.customerSegmentData = this.getOptionNames(template.customerSegmentOptions); + this.liabilityAccountOptions = template.accountingMappingOptions?.liabilityAccountOptions ?? []; + this.cdr.detectChanges(); const isEditing = !!template.id; this.recurringDepositProductDetailsForm.patchValue({ @@ -110,7 +118,7 @@ export class RecurringDepositProductDetailsStepComponent implements OnInit, OnCh description: template.description || '', accountType: template.accountType ?? this.accountTypeData[0] ?? null, customerSegment: template.customerSegment ?? this.customerSegmentData[0] ?? null, - productCode: template.productCode ?? '', + glAccountId: template.glAccountId ?? null, effectiveTo: this.parseDate(template.effectiveTo), endDate: this.parseDate(template.endDate), introducerRequired: template.introducerRequired === true || template.introducerRequired === 'Yes', diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.html b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.html index a421e9a2e..c32f8fbd1 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.html +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.html @@ -31,7 +31,7 @@ @if ( recurringDepositProduct.accountType || recurringDepositProduct.customerSegment || - recurringDepositProduct.productCode || + recurringDepositProduct.glAccountId || recurringDepositProduct.effectiveTo || recurringDepositProduct.introducerRequired !== undefined || recurringDepositProduct.shareLinkingEnabled !== undefined @@ -53,10 +53,10 @@
} - @if (recurringDepositProduct.productCode) { + @if (recurringDepositProduct.glAccountId) {
- Product Code: - {{ recurringDepositProduct.productCode }} + GL Account (Liability): + {{ getGlAccountName(recurringDepositProduct.glAccountId) }}
} diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.ts b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.ts index dadf5d011..9aa3f0276 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.ts +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component.ts @@ -121,6 +121,13 @@ export class RecurringDepositProductPreviewStepComponent implements OnInit, OnCh return found?.value || String(value); } + getGlAccountName(id: any): string { + if (!id) return ''; + const accounts = this.recurringDepositProductsTemplate?.accountingMappingOptions?.liabilityAccountOptions ?? []; + const found = accounts.find((a: any) => a.id === id || a.id === +id); + return found ? `${found.name} (${found.glCode})` : String(id); + } + formatGlAccountById(glAccountId: number, options: any[]): string { if (!glAccountId || !Array.isArray(options)) return '-'; const account = this.accounting.glAccountLookUp(glAccountId, options as any); diff --git a/src/app/products/recurring-deposit-products/view-recurring-deposit-product/recurring-deposit-general-tab/recurring-deposit-general-tab.component.html b/src/app/products/recurring-deposit-products/view-recurring-deposit-product/recurring-deposit-general-tab/recurring-deposit-general-tab.component.html index 09319e1dc..f673989be 100644 --- a/src/app/products/recurring-deposit-products/view-recurring-deposit-product/recurring-deposit-general-tab/recurring-deposit-general-tab.component.html +++ b/src/app/products/recurring-deposit-products/view-recurring-deposit-product/recurring-deposit-general-tab/recurring-deposit-general-tab.component.html @@ -54,10 +54,10 @@ {{ recurringDepositProduct.customerSegment }}
} - @if (recurringDepositProduct.productCode) { + @if (recurringDepositProduct.glAccountId) {
- Product Code: - {{ recurringDepositProduct.productCode }} + GL Account (Liability): + {{ recurringDepositProduct.glAccountName || recurringDepositProduct.glAccountId }}
} @if (recurringDepositProduct.effectiveTo) { diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.html b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.html index 93ae5be7c..7a8c5d3dc 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.html +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.html @@ -82,8 +82,12 @@ - Product Code - + GL Account (Liability) + + @for (account of liabilityAccountOptions; track account.id) { + {{ account.name }} ({{ account.glCode }}) + } + diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.ts index 2805dc866..c9d2ba566 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.ts +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component.ts @@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { Component, OnInit, OnChanges, Input, SimpleChanges, inject } from '@angular/core'; +import { Component, OnInit, OnChanges, Input, SimpleChanges, inject, ChangeDetectorRef } from '@angular/core'; import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms'; import { MatTooltip } from '@angular/material/tooltip'; import { CdkTextareaAutosize } from '@angular/cdk/text-field'; @@ -30,12 +30,14 @@ export class SavingProductDetailsStepComponent implements OnInit, OnChanges { private formBuilder = inject(UntypedFormBuilder); private dateUtils = inject(Dates); private settingsService = inject(SettingsService); + private cdr = inject(ChangeDetectorRef); @Input() savingProductsTemplate: any; savingProductDetailsForm: UntypedFormGroup; accountTypeData: string[] = []; customerSegmentData: string[] = []; + liabilityAccountOptions: any[] = []; yesNoOptions: Array<{ value: boolean; label: string; @@ -82,7 +84,7 @@ export class SavingProductDetailsStepComponent implements OnInit, OnChanges { Validators.required ], customerSegment: [null], - productCode: [''], + glAccountId: [null], effectiveDate: [''], effectiveTo: [''], endDate: [''], @@ -110,6 +112,8 @@ export class SavingProductDetailsStepComponent implements OnInit, OnChanges { this.accountTypeData = accountTypeOptions; this.customerSegmentData = customerSegmentOptions; + this.liabilityAccountOptions = template.accountingMappingOptions?.liabilityAccountOptions ?? []; + this.cdr.detectChanges(); if (yesNoOptions.length) { this.yesNoOptions = yesNoOptions; } @@ -122,7 +126,7 @@ export class SavingProductDetailsStepComponent implements OnInit, OnChanges { description: template.description, accountType: template.accountType ?? this.accountTypeData[0] ?? null, customerSegment: template.customerSegment ?? this.customerSegmentData[0] ?? null, - productCode: template.productCode ?? '', + glAccountId: template.glAccountId ?? null, effectiveDate: template.effectiveDate ? new Date(template.effectiveDate) : '', effectiveTo: template.effectiveTo ? new Date(template.effectiveTo) : '', endDate: template.endDate ? new Date(template.endDate) : '', @@ -162,6 +166,10 @@ export class SavingProductDetailsStepComponent implements OnInit, OnChanges { .filter((value) => !!value); } + compareById(o1: any, o2: any): boolean { + return o1 != null && o2 != null && Number(o1) === Number(o2); + } + get savingProductDetails() { const formValue = { ...this.savingProductDetailsForm.value }; const dateFormat = this.settingsService.dateFormat; diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.html b/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.html index 1fbae2866..8710a144e 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.html +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.html @@ -28,7 +28,7 @@ @if ( savingProduct.accountType || savingProduct.customerSegment || - savingProduct.productCode || + savingProduct.glAccountId || savingProduct.effectiveTo || savingProduct.introducerRequired !== undefined || savingProduct.shareLinkingEnabled !== undefined @@ -51,10 +51,10 @@
} - @if (savingProduct.productCode) { + @if (savingProduct.glAccountId) {
- Product Code: - {{ savingProduct.productCode }} + GL Account (Liability): + {{ getGlAccountName(savingProduct.glAccountId) }}
} diff --git a/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.ts b/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.ts index e1b544103..decfc645d 100644 --- a/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.ts +++ b/src/app/products/saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component.ts @@ -201,6 +201,13 @@ export class SavingProductPreviewStepComponent implements OnInit, OnChanges { return this.accounting.isAccrualAccounting(this.accountingRule); } + getGlAccountName(id: any): string { + if (!id) return ''; + const accounts = this.savingProductsTemplate?.accountingMappingOptions?.liabilityAccountOptions ?? []; + const found = accounts.find((a: any) => a.id === id || a.id === +id); + return found ? `${found.name} (${found.glCode})` : String(id); + } + getBooleanLabel(value: any): string { return value === true || value === 'Yes' ? 'Yes' : 'No'; } diff --git a/src/app/products/saving-products/view-saving-product/saving-product-general-tab/saving-product-general-tab.component.html b/src/app/products/saving-products/view-saving-product/saving-product-general-tab/saving-product-general-tab.component.html index cde61d82f..a36665ab5 100644 --- a/src/app/products/saving-products/view-saving-product/saving-product-general-tab/saving-product-general-tab.component.html +++ b/src/app/products/saving-products/view-saving-product/saving-product-general-tab/saving-product-general-tab.component.html @@ -40,7 +40,7 @@ @if ( savingProduct.accountType || savingProduct.customerSegment || - savingProduct.productCode || + savingProduct.glAccountId || savingProduct.effectiveDate || savingProduct.effectiveTo || savingProduct.endDate || @@ -65,10 +65,10 @@
} - @if (savingProduct.productCode) { + @if (savingProduct.glAccountId) {
- Product Code: - {{ savingProduct.productCode }} + GL Account (Liability): + {{ savingProduct.glAccountName || savingProduct.glAccountId }}
} 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..edd2aea77 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 }} diff --git a/src/app/savings/create-savings-account/create-savings-account.component.scss b/src/app/savings/create-savings-account/create-savings-account.component.scss index 4d4789083..94c2d9d26 100644 --- a/src/app/savings/create-savings-account/create-savings-account.component.scss +++ b/src/app/savings/create-savings-account/create-savings-account.component.scss @@ -27,11 +27,14 @@ .mat-step-content { padding: 1.5rem; margin: 1rem 0; + overflow: visible !important; } } // Form field spacing within steps :host ::ng-deep mat-step-content { + overflow: visible !important; + mat-form-field { margin-bottom: 1rem; width: 100%; diff --git a/src/app/savings/create-savings-account/create-savings-account.component.ts b/src/app/savings/create-savings-account/create-savings-account.component.ts index 134b23abe..a84dd5dcb 100644 --- a/src/app/savings/create-savings-account/create-savings-account.component.ts +++ b/src/app/savings/create-savings-account/create-savings-account.component.ts @@ -9,20 +9,26 @@ /** Angular Imports */ import { Component, ViewChild, inject } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; +import { FormGroup } from '@angular/forms'; /** Custom Components */ import { SavingsAccountDetailsStepComponent } from '../savings-account-stepper/savings-account-details-step/savings-account-details-step.component'; -import { SavingsAccountTermStepComponent } from '../savings-account-stepper/savings-account-term-step/savings-account-term-step.component'; -import { SavingsAccountSettingsStepComponent } from '../savings-account-stepper/savings-account-settings-step/savings-account-settings-step.component'; -import { SavingsAccountChargesStepComponent } from '../savings-account-stepper/savings-account-charges-step/savings-account-charges-step.component'; import { SavingsAccountCurrencyStepComponent } from '../savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component'; import { SavingsAccountDigitalStepComponent } from '../savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component'; +import { SavingsAccountSettingsStepComponent } from '../savings-account-stepper/savings-account-settings-step/savings-account-settings-step.component'; /** Custom Services */ import { SavingsService } from '../savings.service'; import { SettingsService } from 'app/settings/settings.service'; import { Dates } from 'app/core/utils/dates'; -import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper'; +import { + MatStepper, + MatStepperIcon, + MatStep, + MatStepLabel, + MatStepperNext, + MatStepperPrevious +} from '@angular/material/stepper'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { SavingsAccountPreviewStepComponent } from '../savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; @@ -38,14 +44,13 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; ...STANDALONE_SHARED_IMPORTS, MatStepper, MatStepperIcon, + MatStepperNext, + MatStepperPrevious, FaIconComponent, MatStep, MatStepLabel, SavingsAccountDetailsStepComponent, SavingsAccountCurrencyStepComponent, - SavingsAccountTermStepComponent, - SavingsAccountSettingsStepComponent, - SavingsAccountChargesStepComponent, SavingsAccountDigitalStepComponent, SavingsAccountPreviewStepComponent ] @@ -65,14 +70,11 @@ export class CreateSavingsAccountComponent { @ViewChild(SavingsAccountDetailsStepComponent, { static: true }) savingsAccountDetailsStep: SavingsAccountDetailsStepComponent; - @ViewChild(SavingsAccountTermStepComponent, { static: true }) - savingsAccountTermStep: SavingsAccountTermStepComponent; + @ViewChild(SavingsAccountDigitalStepComponent) + savingsAccountDigitalStep: SavingsAccountDigitalStepComponent; - @ViewChild(SavingsAccountSettingsStepComponent, { static: true }) - savingsAccountSettingsStep: SavingsAccountSettingsStepComponent; - - @ViewChild(SavingsAccountChargesStepComponent, { static: true }) - savingsAccountChargesStep: SavingsAccountChargesStepComponent; + @ViewChild(SavingsAccountCurrencyStepComponent) + savingsAccountCurrencyStep: SavingsAccountCurrencyStepComponent; constructor() { this.route.data.subscribe((data: { savingsAccountTemplate: any }) => { @@ -88,46 +90,32 @@ export class CreateSavingsAccountComponent { return this.savingsAccountDetailsStep.savingsAccountDetailsForm; } - get savingsAccountTermsForm() { - return this.savingsAccountTermStep.savingsAccountTermForm; - } - - get savingsAccountSettingsForm() { - return this.savingsAccountSettingsStep.savingsAccountSettingsForm; - } - - get savingsAccountFormValid() { - return ( - this.savingsAccountDetailsForm.valid && - this.savingsAccountTermsForm.valid && - this.savingsAccountSettingsForm.valid - ); + /** Stub — no term step in this flow */ + get savingsAccountTermsForm(): FormGroup { + return new FormGroup({}); } get savingsAccount() { return { - ...this.savingsAccountDetailsStep.savingsAccountDetails, - ...this.savingsAccountTermStep.savingsAccountTerms, - ...this.savingsAccountSettingsStep.savingsAccountSettings, - ...this.savingsAccountChargesStep.savingsAccountCharges + ...this.savingsAccountDetailsStep.savingsAccountDetails }; } + get savingsAccountDigital() { + return this.savingsAccountDigitalStep?.savingsAccountDigital ?? null; + } + + get savingsAccountNominee() { + return this.savingsAccountCurrencyStep?.nomineeData ?? null; + } + submit() { const locale = this.settingsService.language.code; const dateFormat = this.settingsService.dateFormat; const monthDayFormat = 'dd MMMM'; const savingsAccount = { ...this.savingsAccount, - charges: this.savingsAccount.charges.map((charge: any) => ({ - chargeId: charge.id, - amount: charge.amount, - dueDate: charge.dueDate ? this.dateUtils.formatDate(charge.dueDate, dateFormat) : charge.dueDate, - feeOnMonthDay: charge.feeOnMonthDay - ? this.dateUtils.formatDate(charge.feeOnMonthDay, monthDayFormat) - : charge.feeOnMonthDay, - feeInterval: charge.feeInterval - })), + charges: [], submittedOnDate: this.dateUtils.formatDate(this.savingsAccount.submittedOnDate, dateFormat), dateFormat, monthDayFormat, 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..29231e681 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 @@ -35,6 +35,9 @@ {{ savingsAccountProductTemplate?.dormancyAlert ? 'Yes' : 'No' }}
+ @if (savingsAccountProductTemplate?.dormancyAlert) { + + }
@@ -45,5 +48,8 @@ {{ 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..4bce9f33d 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) {
@@ -87,8 +87,12 @@ - Product Code - + Account No + @if (productTemplate.effectiveDate) { @@ -118,16 +122,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..7bdaefa68 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,9 @@ export class SavingsAccountDetailsStepComponent implements OnInit { savingsAccountDetailsForm: UntypedFormGroup; savingsProductSelected = false; + introducerId: number | null = null; + introducerName: string | null = null; + introducerCif: string | null = null; /** Savings Account Template with product data */ @Output() savingsAccountProductTemplate = new EventEmitter(); @@ -165,7 +170,19 @@ export class SavingsAccountDetailsStepComponent implements OnInit { /** * Returns savings account form value. */ + onIntroducerChange(event: { clientId: number; displayName: string; cifNumber?: string } | null): void { + this.introducerId = event?.clientId ?? null; + this.introducerName = event?.displayName ?? null; + this.introducerCif = event?.cifNumber ?? null; + } + get savingsAccountDetails() { - return this.savingsAccountDetailsForm.getRawValue(); + const value = this.savingsAccountDetailsForm.getRawValue(); + if (this.introducerId) { + value.introducerId = this.introducerId; + value.introducerName = this.introducerName; + value.introducerCif = this.introducerCif; + } + return value; } } 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..f523e4bd4 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 @@ -7,9 +7,11 @@ -->
+
- {{ 'labels.heading.Details' | translate }} + + Account Details
@@ -37,25 +39,27 @@ } @else { - {{ 'labels.inputs.Unassigned' | translate }} + Unassigned }
+ @if ( savingsAccountProductTemplate?.accountType || savingsAccountProductTemplate?.customerSegment || savingsAccountProductTemplate?.productCode || + savingsAccountProductTemplate?.previewAccountNo || savingsAccountProductTemplate?.effectiveDate || - savingsAccountProductTemplate?.endDate || - savingsAccountProductTemplate?.introducerRequired !== undefined || - savingsAccountProductTemplate?.shareLinkingEnabled !== undefined + savingsAccountProductTemplate?.endDate ) {
+ Product Classification + From Product
@@ -74,9 +78,15 @@ @if (savingsAccountProductTemplate?.productCode) {
Product Code - {{ savingsAccountProductTemplate?.productCode }} + {{ savingsAccountProductTemplate?.productCode }}
} +
+ Account No + {{ + savingsAccountProductTemplate?.previewAccountNo || savingsAccountTemplate?.accountNo || 'Auto-generated' + }} +
@if (savingsAccountProductTemplate?.effectiveDate) {
Effective Date @@ -89,400 +99,298 @@ {{ savingsAccountProductTemplate?.endDate | dateFormat }}
} - @if ( - savingsAccountProductTemplate?.introducerRequired !== undefined && - savingsAccountProductTemplate?.introducerRequired !== null - ) { -
- Introducer Required - - {{ savingsAccountProductTemplate?.introducerRequired | yesNo }} - -
- } - @if ( - savingsAccountProductTemplate?.shareLinkingEnabled !== undefined && - savingsAccountProductTemplate?.shareLinkingEnabled !== null - ) { -
- Share Linking Enabled - - {{ savingsAccountProductTemplate?.shareLinkingEnabled | yesNo }} - -
- } +
+ Introducer Required + + {{ savingsAccountProductTemplate?.introducerRequired | yesNo }} + +
+
+ Share Linking Enabled + + {{ savingsAccountProductTemplate?.shareLinkingEnabled | yesNo }} + +
} -
-
- {{ 'labels.heading.Currency' | translate }} -
-
-
-
- {{ 'labels.inputs.Currency' | translate }} - {{ savingsAccountTermsForm?.get('currencyCode')?.value }} -
-
- {{ 'labels.inputs.Decimal Places' | translate }} - {{ savingsAccountProductTemplate?.currency?.decimalPlaces }} -
-
- {{ 'labels.inputs.Currency In Multiples Of' | translate }} - {{ savingsAccountProductTemplate?.currency?.inMultiplesOf }} -
- - @if ( - savingsAccountProductTemplate?.dormancyAlert !== undefined && - savingsAccountProductTemplate?.dormancyAlert !== null - ) { -
- Dormancy Alert - - {{ savingsAccountProductTemplate?.dormancyAlert | yesNo }} - -
- } - - @if ( - savingsAccountProductTemplate?.balanceSwapOption !== undefined && - savingsAccountProductTemplate?.balanceSwapOption !== null - ) { -
- Balance Swap Option - - {{ savingsAccountProductTemplate?.balanceSwapOption | yesNo }} - -
- } - - @if (savingsAccountProductTemplate?.msme !== undefined && savingsAccountProductTemplate?.msme !== null) { -
- MSME - - {{ savingsAccountProductTemplate?.msme | yesNo }} - -
- } - - @if ( - savingsAccountProductTemplate?.nominationFacility !== undefined && - savingsAccountProductTemplate?.nominationFacility !== null - ) { -
- Nomination Facility - - {{ savingsAccountProductTemplate?.nominationFacility | yesNo }} - -
- } - - @if ( - savingsAccountProductTemplate?.autoDebitFacility !== undefined && - savingsAccountProductTemplate?.autoDebitFacility !== null - ) { -
- Auto Debit Facility - - {{ savingsAccountProductTemplate?.autoDebitFacility | yesNo }} - -
- } -
-
-
- -
-
- {{ '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) { + + @if (savingsAccount?.introducerId) {
- Digital + + Introducer Details
- @for (facility of digitalFacilityLabels; track facility.key) { +
+ Name + {{ savingsAccount?.introducerName || '—' }} +
+ @if (savingsAccount?.introducerCif) {
- {{ facility.label }} - - {{ digitalPreview?.accountFacilityConfig?.[facility.key] | yesNo }} - + CIF No + {{ savingsAccount.introducerCif }}
} +
+ Client ID + {{ savingsAccount.introducerId }} +
- - @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' }}
- }
} + +
+
+ + Currency & Account Flags +
+
+
+
+ Currency + {{ currencyName }} +
+
+ Decimal Places + {{ decimalPlaces }} +
+
+ Currency In Multiples Of + {{ inMultiplesOf }} +
+
+ Dormancy Alert + + {{ savingsAccountProductTemplate?.dormancyAlert | yesNo }} + +
+
+ Nomination Facility + + {{ savingsAccountProductTemplate?.nominationFacility | yesNo }} + +
+
+
+
+ + + @if ( + savingsAccountProductTemplate?.dormancyAlert || savingsAccountProductTemplate?.isDormancyTrackingActiveFromProduct + ) { +
+
+ + Dormancy Monitoring + + {{ savingsAccountProductTemplate?.isDormancyTrackingActiveFromProduct ? 'Active' : 'Inactive' }} + +
+
+
+
+
+
+ Inactive + + @if (savingsAccountProductTemplate?.daysToInactiveFromProduct != null) { + After {{ savingsAccountProductTemplate.daysToInactiveFromProduct }} days of no transaction + } @else { + Not configured + } + +
+
+
+
+
+
+ Dormant + + @if (savingsAccountProductTemplate?.daysToDormancyFromProduct != null) { + {{ savingsAccountProductTemplate.daysToDormancyFromProduct }} days after inactive + } @else { + Not configured + } + +
+
+
+
+
+
+ Escheat + + @if (savingsAccountProductTemplate?.daysToEscheatFromProduct != null) { + {{ savingsAccountProductTemplate.daysToEscheatFromProduct }} days after dormant + } @else { + Not configured + } + +
+
+
+
+ + Alert + {{ savingsAccountProductTemplate?.dormancyAlert ? 'Enabled' : 'Disabled' }} + + + Tracking + {{ savingsAccountProductTemplate?.isDormancyTrackingActiveFromProduct ? 'Active' : 'Inactive' }} + +
+
+
+ } + + + @if (savingsAccountNominee) { +
+
+ + Nominee Details + + {{ savingsAccountNominee?.mode === 'linked' ? 'Linked Client' : 'Manual Entry' }} + +
+
+
+ +
+ Name + {{ + savingsAccountNominee?.fullName || savingsAccountNominee?.displayName || '—' + }} +
+ @if (savingsAccountNominee?.cifNumber) { +
+ CIF No + {{ savingsAccountNominee.cifNumber }} +
+ } +
+ Relationship + {{ savingsAccountNominee?.relationship || '—' }} +
+
+ Share % + {{ savingsAccountNominee?.nomineeShare ?? '—' }}% +
+ @if (savingsAccountNominee?.dateOfBirth) { +
+ Date of Birth + {{ savingsAccountNominee?.dateOfBirth | dateFormat }} +
+ } + @if (savingsAccountNominee?.nationality) { +
+ Nationality + {{ savingsAccountNominee?.nationality }} +
+ } + @if (savingsAccountNominee?.mobileNo) { +
+ Mobile No + {{ savingsAccountNominee?.mobileNo }} +
+ } + @if (savingsAccountNominee?.aadhaarNumber) { +
+ Aadhaar No + {{ savingsAccountNominee?.aadhaarNumber }} +
+ } + @if (savingsAccountNominee?.panNumber) { +
+ PAN No + {{ savingsAccountNominee?.panNumber }} +
+ } + @if (savingsAccountNominee?.isDependent) { +
+ Dependent + Yes +
+ } + @if (savingsAccountNominee?.guardianName) { +
+ Guardian Name + {{ savingsAccountNominee?.guardianName }} +
+ } + @if (savingsAccountNominee?.guardianRelationship) { +
+ Guardian Relationship + {{ savingsAccountNominee?.guardianRelationship }} +
+ } + @if (savingsAccountNominee?.remarks) { +
+ Remarks + {{ savingsAccountNominee?.remarks }} +
+ } +
+
+
+ } + + + @if (hasDigitalSection) { +
+
+ + Digital Facilities + User Configured +
+
+
+ @for (facility of digitalFacilityLabels; track facility.key) { +
+ {{ facility.label }} + + {{ digitalFacilityConfig?.[facility.key] ? 'Enabled' : 'Disabled' }} + +
+ } +
+
+
+ } + + @if (activeClientMembers) {
+ {{ 'labels.heading.Client Members' | translate }}
@@ -501,65 +409,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-preview-step/savings-account-preview-step.component.scss b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.scss index bec5645fb..25e0a4e09 100644 --- a/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.scss +++ b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.scss @@ -9,29 +9,59 @@ .preview-wrapper { display: flex; flex-direction: column; - gap: 1.25rem; + gap: 1rem; padding: 0.5rem 0; } .preview-section { - border: 1px solid #e0e0e0; - border-radius: 8px; + border: 1px solid #e2e8f0; + border-radius: 10px; overflow: hidden; background: #fff; - box-shadow: 0 1px 3px rgb(0 0 0 / 6%); + box-shadow: 0 1px 4px rgb(0 0 0 / 5%); &__header { - background: #f5f5f5; - border-bottom: 1px solid #e0e0e0; + display: flex; + align-items: center; + gap: 8px; + background: #f8fafc; + border-bottom: 1px solid #e2e8f0; padding: 0.6rem 1rem; } + &__icon { + font-size: 13px; + color: #0ea5e9; + width: 16px; + text-align: center; + } + &__title { - font-size: 0.95rem; - font-weight: 600; - color: #333; + font-size: 0.82rem; + font-weight: 700; + color: #1e293b; text-transform: uppercase; - letter-spacing: 0.04em; + letter-spacing: 0.06em; + flex: 1; + } + + &__badge { + display: inline-flex; + align-items: center; + padding: 2px 10px; + border-radius: 999px; + font-size: 10px; + font-weight: 700; + background: #dbeafe; + color: #1d4ed8; + border: 1px solid #bfdbfe; + text-transform: uppercase; + + &--digital { + background: #fef3c7; + color: #92400e; + border-color: #fde68a; + } } } @@ -41,7 +71,7 @@ .preview-grid { display: grid; - gap: 0.75rem 1.5rem; + gap: 0.85rem 1.5rem; &--three { grid-template-columns: repeat(3, 1fr); @@ -59,36 +89,229 @@ .preview-field { display: flex; flex-direction: column; - gap: 0.15rem; + gap: 0.2rem; } .preview-label { - font-size: 0.72rem; - color: #888; - font-weight: 500; + font-size: 0.68rem; + color: #94a3b8; + font-weight: 600; text-transform: uppercase; - letter-spacing: 0.04em; + letter-spacing: 0.05em; } .preview-value { - font-size: 0.92rem; - color: #222; + font-size: 0.9rem; + color: #1e293b; font-weight: 500; } +.preview-muted { + color: #94a3b8; + font-style: italic; +} + +.preview-mono { + font-family: monospace; + font-size: 0.88rem; + color: #334155; +} + .preview-badge { display: inline-block; - padding: 0.1rem 0.55rem; + padding: 0.15rem 0.6rem; border-radius: 12px; - font-size: 0.78rem; + font-size: 0.75rem; font-weight: 600; - background: #ffebee; - color: #c62828; + background: #fef2f2; + color: #dc2626; width: fit-content; &--yes { - background: #e8f5e9; - color: #2e7d32; + background: #f0fdf4; + color: #15803d; + } + + &--warn { + background: #fef3c7; + color: #92400e; + } +} + +// Digital facilities grid +.preview-digital-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 8px; + + @media (width <= 900px) { + grid-template-columns: repeat(3, 1fr); + } + + @media (width <= 600px) { + grid-template-columns: repeat(2, 1fr); + } +} + +.preview-digital-card { + display: flex; + flex-direction: column; + gap: 4px; + padding: 10px 12px; + border: 1.5px solid #e2e8f0; + border-radius: 8px; + background: #f8fafc; + transition: all 0.12s; + + &--on { + border-color: #86efac; + background: #f0fdf4; + } +} + +.preview-digital-label { + font-size: 11px; + font-weight: 600; + color: #475569; +} + +.preview-digital-status { + font-size: 11px; + font-weight: 700; + color: #dc2626; + + &--on { + color: #15803d; + } +} + +// Section badge variants +.preview-section__badge--active { + background: #dcfce7 !important; + color: #15803d !important; + border-color: #bbf7d0 !important; +} + +// ── Dormancy Monitoring ─────────────────────────────────────────── +.preview-dormancy-timeline { + display: flex; + align-items: flex-start; + gap: 0; + margin-bottom: 14px; +} + +.preview-dorm-step { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + flex: 1; + + &__dot { + width: 36px; + height: 36px; + border-radius: 50%; + border: 3px solid; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + &--inactive { + border-color: #f59e0b; + background: #fffbeb; + + &::after { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background: #f59e0b; + } + } + + &--dormant { + border-color: #ef4444; + background: #fef2f2; + + &::after { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background: #ef4444; + } + } + + &--escheat { + border-color: #7c3aed; + background: #f5f3ff; + + &::after { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background: #7c3aed; + } + } + } + + &__body { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: 2px; + } + + &__label { + font-size: 12px; + font-weight: 700; + color: #1e293b; + } + + &__days { + font-size: 10.5px; + color: #64748b; + line-height: 1.3; + } +} + +.preview-dorm-connector { + flex: 0 0 40px; + height: 3px; + background: linear-gradient(90deg, #e2e8f0, #cbd5e1); + margin-top: 16px; + border-radius: 2px; +} + +.preview-dorm-flags { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.preview-dorm-flag { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 4px 12px; + border-radius: 20px; + font-size: 11.5px; + font-weight: 600; + background: #f1f5f9; + color: #94a3b8; + border: 1px solid #e2e8f0; + + i { + font-size: 11px; + } + + &--on { + background: #f0fdf4; + color: #15803d; + border-color: #bbf7d0; } } diff --git a/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.ts b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.ts index aae881819..f29663102 100644 --- a/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.ts +++ b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.ts @@ -88,6 +88,10 @@ export class SavingsAccountPreviewStepComponent implements OnChanges { @Input() savingsAccountTermsForm: any; /** Savings Account */ @Input() savingsAccount: any; + /** Digital facilities — user-modified values from digital step */ + @Input() savingsAccountDigital: any; + /** Nominee data from currency step */ + @Input() savingsAccountNominee: any; /** active Client Members in case of GSIM Account */ @Input() activeClientMembers?: any; @@ -118,15 +122,33 @@ export class SavingsAccountPreviewStepComponent implements OnChanges { } } - get digitalPreview(): any { - return this.savingsAccountProductTemplate || {}; + /** Returns user-modified digital config (from digital step), falling back to product template */ + get digitalFacilityConfig(): any { + return ( + this.savingsAccountDigital?.accountFacilityConfig ?? + this.savingsAccountProductTemplate?.accountFacilityConfig ?? + null + ); } get hasDigitalSection(): boolean { - return !!( - this.digitalPreview?.accountFacilityConfig || - this.digitalPreview?.accountFacilityLimits?.length || - this.digitalPreview?.depositLimitKyc + return !!this.digitalFacilityConfig; + } + + /** Currency info from product template */ + get currencyName(): string { + return ( + this.savingsAccountProductTemplate?.currency?.name || + this.savingsAccountTermsForm?.get?.('currencyCode')?.value || + '—' ); } + + get decimalPlaces(): string | number { + return this.savingsAccountProductTemplate?.currency?.decimalPlaces ?? '—'; + } + + get inMultiplesOf(): string | number { + return this.savingsAccountProductTemplate?.currency?.inMultiplesOf ?? '—'; + } } 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..703b691fa --- /dev/null +++ b/src/app/shared/introducer-search/introducer-search.component.ts @@ -0,0 +1,142 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, Output, EventEmitter, inject } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import { ClientsService } from 'app/clients/clients.service'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { MatProgressSpinner } from '@angular/material/progress-spinner'; +import { MatIconButton } from '@angular/material/button'; +import { MatIcon } from '@angular/material/icon'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'mifosx-introducer-search', + templateUrl: './introducer-search.component.html', + styleUrls: ['./introducer-search.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + FormsModule, + MatProgressSpinner, + MatIconButton, + MatIcon + ] +}) +export class IntroducerSearchComponent { + private clientsService = inject(ClientsService); + private sanitizer = inject(DomSanitizer); + + @Output() introducerChange = new EventEmitter<{ clientId: number; displayName: string; cifNumber?: string } | null>(); + + entryMode: 'linked' = 'linked'; + searchText = ''; + searching = false; + searchResults: any[] = []; + selectedClient: any = null; + fetchingLinkedData = false; + linkedProfileImage: any = null; + linkedKycStatus: any = null; + + // Fetched client details + clientDob = ''; + clientAadhaar = ''; + clientPan = ''; + + private searchTimer: any; + + onSearch(text: string): void { + this.searchText = text; + clearTimeout(this.searchTimer); + if (!text || text.length < 2) { + this.searchResults = []; + return; + } + this.searching = true; + this.searchTimer = setTimeout(() => { + this.clientsService.searchByText(text, 0, 8).subscribe({ + next: (data: any) => { + this.searchResults = data?.content || []; + this.searching = false; + }, + error: () => { + this.searchResults = []; + this.searching = false; + } + }); + }, 400); + } + + selectClient(client: any): void { + this.selectedClient = client; + this.searchResults = []; + this.searchText = ''; + this.clientDob = ''; + this.clientAadhaar = ''; + this.clientPan = ''; + this.linkedProfileImage = null; + this.linkedKycStatus = null; + this.fetchLinkedClientData(client.id); + this.introducerChange.emit({ + clientId: client.id, + displayName: client.displayName, + cifNumber: client.cifNumber || client.accountNumber || '' + }); + } + + removeClient(): void { + this.selectedClient = null; + this.linkedProfileImage = null; + this.linkedKycStatus = null; + this.clientDob = ''; + this.clientAadhaar = ''; + this.clientPan = ''; + this.introducerChange.emit(null); + } + + private fetchLinkedClientData(clientId: string): void { + this.fetchingLinkedData = true; + + this.clientsService.getClientData(clientId).subscribe({ + next: (data: any) => { + if (data.dateOfBirth) { + const d = Array.isArray(data.dateOfBirth) + ? new Date(data.dateOfBirth[0], data.dateOfBirth[1] - 1, data.dateOfBirth[2]) + : new Date(data.dateOfBirth); + this.clientDob = d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }); + } + this.fetchingLinkedData = false; + }, + error: () => { + this.fetchingLinkedData = false; + } + }); + + this.clientsService.getClientKyc(clientId).subscribe({ + next: (kyc: any) => { + if (kyc) { + this.clientAadhaar = kyc.aadhaarNumberMasked || ''; + this.clientPan = kyc.panNumber || ''; + this.linkedKycStatus = { + panVerified: kyc.panVerified || false, + aadhaarVerified: kyc.aadhaarVerified || false + }; + } + }, + error: () => {} + }); + + this.clientsService.getClientProfileImage(clientId).subscribe({ + next: (imageData: any) => { + if (imageData) { + this.linkedProfileImage = this.sanitizer.bypassSecurityTrustResourceUrl(imageData); + } + }, + error: () => {} + }); + } +} 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..d016d8a6b --- /dev/null +++ b/src/app/shared/nominee-form/nominee-form.component.ts @@ -0,0 +1,263 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, Output, EventEmitter, inject, OnInit } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators, FormsModule } from '@angular/forms'; +import { DomSanitizer } from '@angular/platform-browser'; +import { ClientsService } from 'app/clients/clients.service'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { MatProgressSpinner } from '@angular/material/progress-spinner'; +import { MatIconButton } from '@angular/material/button'; +import { MatIcon } from '@angular/material/icon'; +import { MatCheckbox } from '@angular/material/checkbox'; +import { MatDatepickerModule } from '@angular/material/datepicker'; + +export interface NomineeData { + mode: 'manual' | 'linked'; + // Manual fields + fullName?: string; + relationship?: string; + dateOfBirth?: string; + nationality?: string; + aadhaarNumber?: string; + panNumber?: string; + mobileNo?: string; + nomineeShare?: number; + isAlive?: boolean; + isDependent?: boolean; + guardianName?: string; + guardianRelationship?: string; + remarks?: string; + // Linked client fields + clientId?: number; + displayName?: string; + cifNumber?: string; +} + +const RELATIONSHIPS = [ + 'Spouse', + 'Son', + 'Daughter', + 'Father', + 'Mother', + 'Brother', + 'Sister', + 'Grandfather', + 'Grandmother', + 'Grandson', + 'Granddaughter', + 'Uncle', + 'Aunt', + 'Nephew', + 'Niece', + 'Friend', + 'Other' +]; + +@Component({ + selector: 'mifosx-nominee-form', + templateUrl: './nominee-form.component.html', + styleUrls: ['./nominee-form.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + FormsModule, + MatProgressSpinner, + MatIconButton, + MatIcon, + MatCheckbox, + MatDatepickerModule + ] +}) +export class NomineeFormComponent implements OnInit { + private fb = inject(UntypedFormBuilder); + private clientsService = inject(ClientsService); + private sanitizer = inject(DomSanitizer); + + @Output() nomineeChange = new EventEmitter(); + + mode: 'manual' | 'linked' = 'manual'; + relationships = RELATIONSHIPS; + + // Manual form + manualForm!: UntypedFormGroup; + minDate = new Date(1900, 0, 1); + maxDate = new Date(); + + // Link mode + searchText = ''; + searching = false; + searchResults: any[] = []; + selectedClient: any = null; + fetchingLinkedData = false; + linkedProfileImage: any = null; + linkedKycStatus: any = null; + clientDob = ''; + clientAadhaar = ''; + clientPan = ''; + clientCifNumber = ''; + linkedRelationship = ''; + linkedShare = 100; + + private searchTimer: any; + + ngOnInit(): void { + this.manualForm = this.fb.group({ + fullName: [ + '', + Validators.required + ], + relationship: [ + '', + Validators.required + ], + dateOfBirth: [''], + nationality: ['Indian'], + aadhaarNumber: [''], + panNumber: [''], + mobileNo: [''], + nomineeShare: [100], + isAlive: [true], + isDependent: [false], + guardianName: [''], + guardianRelationship: [''], + remarks: [''] + }); + + this.manualForm.valueChanges.subscribe(() => { + if (this.mode === 'manual' && this.manualForm.valid) { + this.emitManual(); + } + }); + } + + switchMode(m: 'manual' | 'linked'): void { + this.mode = m; + if (m === 'manual') { + this.clearLinked(); + } else { + this.manualForm.reset({ nationality: 'Indian', isAlive: true, isDependent: false, nomineeShare: 100 }); + this.nomineeChange.emit(null); + } + } + + // ─── Manual ────────────────────────────────────── + private emitManual(): void { + const v = this.manualForm.value; + this.nomineeChange.emit({ mode: 'manual', ...v }); + } + + // ─── Link existing client ───────────────────────── + onSearch(text: string): void { + this.searchText = text; + clearTimeout(this.searchTimer); + if (!text || text.length < 2) { + this.searchResults = []; + return; + } + this.searching = true; + this.searchTimer = setTimeout(() => { + this.clientsService.searchByText(text, 0, 8).subscribe({ + next: (data: any) => { + this.searchResults = data?.content || []; + this.searching = false; + }, + error: () => { + this.searchResults = []; + this.searching = false; + } + }); + }, 400); + } + + selectClient(client: any): void { + this.selectedClient = client; + this.searchResults = []; + this.searchText = ''; + this.fetchLinkedClientData(client.id); + this.nomineeChange.emit({ + mode: 'linked', + clientId: client.id, + displayName: client.displayName, + cifNumber: client.accountNo || client.cifNumber || '' + }); + } + + removeLinked(): void { + this.clearLinked(); + this.nomineeChange.emit(null); + } + + onLinkedRelationshipChange(rel: string): void { + if (this.selectedClient) { + this.nomineeChange.emit({ + mode: 'linked', + clientId: this.selectedClient.id, + displayName: this.selectedClient.displayName, + cifNumber: this.selectedClient.accountNo || this.selectedClient.cifNumber || '', + relationship: rel, + nomineeShare: this.linkedShare + }); + } + } + + private clearLinked(): void { + this.selectedClient = null; + this.linkedProfileImage = null; + this.linkedKycStatus = null; + this.clientDob = ''; + this.clientAadhaar = ''; + this.clientPan = ''; + this.clientCifNumber = ''; + this.searchText = ''; + this.searchResults = []; + this.linkedRelationship = ''; + this.linkedShare = 100; + } + + private fetchLinkedClientData(clientId: string): void { + this.fetchingLinkedData = true; + + this.clientsService.getClientData(clientId).subscribe({ + next: (data: any) => { + if (data.dateOfBirth) { + const d = Array.isArray(data.dateOfBirth) + ? new Date(data.dateOfBirth[0], data.dateOfBirth[1] - 1, data.dateOfBirth[2]) + : new Date(data.dateOfBirth); + this.clientDob = d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }); + } + this.fetchingLinkedData = false; + }, + error: () => { + this.fetchingLinkedData = false; + } + }); + + this.clientsService.getClientKyc(clientId).subscribe({ + next: (kyc: any) => { + if (kyc) { + this.clientAadhaar = kyc.aadhaarNumberMasked || ''; + this.clientPan = kyc.panNumber || ''; + this.linkedKycStatus = { + panVerified: kyc.panVerified || false, + aadhaarVerified: kyc.aadhaarVerified || false + }; + } + }, + error: () => {} + }); + + this.clientsService.getClientProfileImage(clientId).subscribe({ + next: (imageData: any) => { + if (imageData) { + this.linkedProfileImage = this.sanitizer.bypassSecurityTrustResourceUrl(imageData); + } + }, + error: () => {} + }); + } +} diff --git a/src/environments/.env.ts b/src/environments/.env.ts index c31d3d90c..77fd662d8 100644 --- a/src/environments/.env.ts +++ b/src/environments/.env.ts @@ -10,8 +10,8 @@ /* tslint:disable */ export default { 'mifos_x': { - 'version': '260422', - 'hash': 'e9a93771' + 'version': '260427', + 'hash': '5fb25edc' }, 'allow_switching_backend_instance': true }; diff --git a/src/main.scss b/src/main.scss index 9fda56e50..c10b82bd2 100644 --- a/src/main.scss +++ b/src/main.scss @@ -957,3 +957,51 @@ mat-card { border-radius: 16px !important; } } + +/* ─── Add Beneficiary Dialog Panel ─── */ +.abd-dialog-panel { + .mat-mdc-dialog-container { + padding: 0; + border-radius: 14px; + overflow: hidden; + } + + .mat-mdc-dialog-surface { + padding: 0 !important; + border-radius: 14px !important; + } + + .mat-mdc-dialog-content { + padding: 0 20px !important; + max-height: 72vh; + } + + .mat-mdc-dialog-actions { + padding: 0 !important; + margin: 0 !important; + } +} + +/* ─── Add Related Client Dialog Panel ─── */ +.arc-dialog-panel { + .mat-mdc-dialog-container { + padding: 0; + border-radius: 14px; + overflow: hidden; + } + + .mat-mdc-dialog-surface { + padding: 0 !important; + border-radius: 14px !important; + } + + .mat-mdc-dialog-content { + padding: 0 20px !important; + max-height: 70vh; + } + + .mat-mdc-dialog-actions { + padding: 0 !important; + margin: 0 !important; + } +}