From e4516934a7d3f4eb7485e4a91d98f681cf2d0b00 Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Mon, 27 Apr 2026 10:55:03 +0530 Subject: [PATCH] saving account,current account and rd account modify based on new update --- .../create-current-account.component.html | 3 + .../create-current-account.component.ts | 26 +- ...rrent-account-currency-step.component.html | 6 +- .../add-beneficiary-dialog.component.html | 190 ++++++ .../add-beneficiary-dialog.component.scss | 415 ++++++++++++ .../add-beneficiary-dialog.component.ts | 167 +++++ .../add-related-client-dialog.component.html | 135 ++++ .../add-related-client-dialog.component.scss | 359 +++++++++++ .../add-related-client-dialog.component.ts | 104 +++ ...urrent-account-details-step.component.html | 202 ++++++ ...urrent-account-details-step.component.scss | 608 ++++++++++++++++++ .../current-account-details-step.component.ts | 132 +++- ...urrent-account-preview-step.component.html | 451 +++++++++---- ...urrent-account-preview-step.component.scss | 324 +++++++++- .../current-account-preview-step.component.ts | 37 +- ...-recurring-deposits-account.component.html | 4 + ...te-recurring-deposits-account.component.ts | 25 +- ...osits-account-currency-step.component.html | 10 +- ...deposits-account-details-step.component.ts | 8 +- ...nt-interest-rate-chart-step.component.html | 235 ++----- ...nt-interest-rate-chart-step.component.scss | 99 +++ ...ount-interest-rate-chart-step.component.ts | 20 +- ...posits-account-preview-step.component.html | 402 ++++++++---- ...posits-account-preview-step.component.scss | 193 ++++++ ...deposits-account-preview-step.component.ts | 16 + .../create-savings-account.component.html | 2 + .../create-savings-account.component.ts | 15 + ...vings-account-currency-step.component.html | 6 +- .../savings-account-details-step.component.ts | 8 +- ...avings-account-preview-step.component.html | 386 +++++++---- ...avings-account-preview-step.component.scss | 271 +++++++- .../savings-account-preview-step.component.ts | 34 +- .../introducer-search.component.ts | 8 +- .../nominee-form/nominee-form.component.ts | 11 +- src/environments/.env.ts | 5 +- src/main.scss | 48 ++ 36 files changed, 4302 insertions(+), 663 deletions(-) create mode 100644 src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.html create mode 100644 src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.scss create mode 100644 src/app/current-accounts/current-account-stepper/current-account-details-step/add-beneficiary-dialog/add-beneficiary-dialog.component.ts create mode 100644 src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.html create mode 100644 src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.scss create mode 100644 src/app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component.ts diff --git a/src/app/current-accounts/create-current-account/create-current-account.component.html b/src/app/current-accounts/create-current-account/create-current-account.component.html index 1f07c596b..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 @@ -96,6 +96,9 @@ [currentAccountSettingsForm]="currentAccountSettingsForm" [currentAccountTermsForm]="currentAccountTermsForm" [currentAccount]="currentAccount" + [currentAccountDigital]="currentAccountDigital" + [currentAccountNominee]="currentAccountNominee" + [currentAccountCurrencyFlags]="currentAccountCurrencyFlags" (submitEvent)="submit()" > 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 9e0cad657..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 @@ -66,14 +66,15 @@ export class CreateCurrentAccountComponent { /** Current Account Product Template */ currentAccountProductTemplate: any; - /** Current Account Details Step */ @ViewChild(CurrentAccountDetailsStepComponent, { static: true }) currentAccountDetailsStep: CurrentAccountDetailsStepComponent; - /** Current Account Currency Step */ - @ViewChild(CurrentAccountCurrencyStepComponent, { static: true }) + @ViewChild(CurrentAccountCurrencyStepComponent) currentAccountCurrencyStep: CurrentAccountCurrencyStepComponent; + @ViewChild(CurrentAccountDigitalStepComponent) + currentAccountDigitalStep: CurrentAccountDigitalStepComponent; + constructor() { this.route.data.subscribe((data: { currentAccountTemplate: any }) => { this.currentAccountTemplate = data.currentAccountTemplate; @@ -107,12 +108,23 @@ export class CreateCurrentAccountComponent { return new FormGroup({}); } - /** - * 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 + balanceSwapOption: this.currentAccountCurrencyStep?.balanceSwapOption ?? false, + msme: this.currentAccountCurrencyStep?.msme ?? false, + autoDebitFacility: this.currentAccountCurrencyStep?.autoDebitFacility ?? false }; } 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 e790fd695..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 @@ -59,8 +59,9 @@ {{ currentAccountProductTemplate?.dormancyAlert ? 'Yes' : 'No' }} - - + @if (currentAccountProductTemplate?.dormancyAlert) { + + }
@@ -71,7 +72,6 @@ {{ currentAccountProductTemplate?.nominationFacility ? 'Yes' : 'No' }}
- @if (currentAccountProductTemplate?.nominationFacility) { } 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 46882eb22..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 @@ -131,6 +131,208 @@ } } + + @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. + } +
+ } +
+
+ } +
- - 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/savings/create-savings-account/create-savings-account.component.html b/src/app/savings/create-savings-account/create-savings-account.component.html index 23f9f93aa..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 @@ -92,6 +92,8 @@ [savingsAccountTemplate]="savingsAccountTemplate" [savingsAccountTermsForm]="savingsAccountTermsForm" [savingsAccount]="savingsAccount" + [savingsAccountDigital]="savingsAccountDigital" + [savingsAccountNominee]="savingsAccountNominee" (submitEvent)="submit()" > 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 fd733bbae..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 @@ -15,6 +15,7 @@ import { FormGroup } from '@angular/forms'; import { SavingsAccountDetailsStepComponent } from '../savings-account-stepper/savings-account-details-step/savings-account-details-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'; @@ -69,6 +70,12 @@ export class CreateSavingsAccountComponent { @ViewChild(SavingsAccountDetailsStepComponent, { static: true }) savingsAccountDetailsStep: SavingsAccountDetailsStepComponent; + @ViewChild(SavingsAccountDigitalStepComponent) + savingsAccountDigitalStep: SavingsAccountDigitalStepComponent; + + @ViewChild(SavingsAccountCurrencyStepComponent) + savingsAccountCurrencyStep: SavingsAccountCurrencyStepComponent; + constructor() { this.route.data.subscribe((data: { savingsAccountTemplate: any }) => { this.savingsAccountTemplate = data.savingsAccountTemplate; @@ -94,6 +101,14 @@ export class CreateSavingsAccountComponent { }; } + 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; 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 9dd007828..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,8 +35,9 @@ {{ savingsAccountProductTemplate?.dormancyAlert ? 'Yes' : 'No' }}
- - + @if (savingsAccountProductTemplate?.dormancyAlert) { + + }
@@ -47,7 +48,6 @@ {{ savingsAccountProductTemplate?.nominationFacility ? 'Yes' : 'No' }}
- @if (savingsAccountProductTemplate?.nominationFacility) { } 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 8585f3dac..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 @@ -59,6 +59,8 @@ export class SavingsAccountDetailsStepComponent implements OnInit { savingsProductSelected = false; introducerId: number | null = null; + introducerName: string | null = null; + introducerCif: string | null = null; /** Savings Account Template with product data */ @Output() savingsAccountProductTemplate = new EventEmitter(); @@ -168,14 +170,18 @@ export class SavingsAccountDetailsStepComponent implements OnInit { /** * Returns savings account form value. */ - onIntroducerChange(event: { clientId: number; displayName: string } | null): void { + 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() { 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-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 70337bb64..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,145 +99,285 @@ {{ 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 }} + +
} + + @if (savingsAccount?.introducerId) { +
+
+ + Introducer Details +
+
+
+
+ Name + {{ savingsAccount?.introducerName || '—' }} +
+ @if (savingsAccount?.introducerCif) { +
+ CIF No + {{ savingsAccount.introducerCif }} +
+ } +
+ Client ID + {{ savingsAccount.introducerId }} +
+
+
+
+ } + +
- {{ 'labels.heading.Currency' | translate }} + + Currency & Account Flags
- {{ 'labels.inputs.Currency' | translate }} - {{ savingsAccountTermsForm?.get('currencyCode')?.value }} + Currency + {{ currencyName }}
- {{ 'labels.inputs.Decimal Places' | translate }} - {{ savingsAccountProductTemplate?.currency?.decimalPlaces }} + Decimal Places + {{ decimalPlaces }}
- {{ 'labels.inputs.Currency In Multiples Of' | translate }} - {{ savingsAccountProductTemplate?.currency?.inMultiplesOf }} + Currency In Multiples Of + {{ inMultiplesOf }} +
+
+ Dormancy Alert + + {{ savingsAccountProductTemplate?.dormancyAlert | yesNo }} + +
+
+ Nomination Facility + + {{ savingsAccountProductTemplate?.nominationFacility | yesNo }} +
- - @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 }} - -
- }
- @if (hasDigitalSection) { + + @if ( + savingsAccountProductTemplate?.dormancyAlert || savingsAccountProductTemplate?.isDormancyTrackingActiveFromProduct + ) {
- Digital + + 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' }} +
- @for (facility of digitalFacilityLabels; track facility.key) { + +
+ Name + {{ + savingsAccountNominee?.fullName || savingsAccountNominee?.displayName || '—' + }} +
+ @if (savingsAccountNominee?.cifNumber) {
- {{ facility.label }} + 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 }} - {{ digitalPreview?.accountFacilityConfig?.[facility.key] | yesNo }} + {{ digitalFacilityConfig?.[facility.key] ? 'Enabled' : 'Disabled' }}
} @@ -236,9 +386,11 @@
} + @if (activeClientMembers) {
+ {{ 'labels.heading.Client Members' | translate }}
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/shared/introducer-search/introducer-search.component.ts b/src/app/shared/introducer-search/introducer-search.component.ts index fa0410d78..703b691fa 100644 --- a/src/app/shared/introducer-search/introducer-search.component.ts +++ b/src/app/shared/introducer-search/introducer-search.component.ts @@ -31,7 +31,7 @@ export class IntroducerSearchComponent { private clientsService = inject(ClientsService); private sanitizer = inject(DomSanitizer); - @Output() introducerChange = new EventEmitter<{ clientId: number; displayName: string } | null>(); + @Output() introducerChange = new EventEmitter<{ clientId: number; displayName: string; cifNumber?: string } | null>(); entryMode: 'linked' = 'linked'; searchText = ''; @@ -81,7 +81,11 @@ export class IntroducerSearchComponent { this.linkedProfileImage = null; this.linkedKycStatus = null; this.fetchLinkedClientData(client.id); - this.introducerChange.emit({ clientId: client.id, displayName: client.displayName }); + this.introducerChange.emit({ + clientId: client.id, + displayName: client.displayName, + cifNumber: client.cifNumber || client.accountNumber || '' + }); } removeClient(): void { diff --git a/src/app/shared/nominee-form/nominee-form.component.ts b/src/app/shared/nominee-form/nominee-form.component.ts index 594692183..d016d8a6b 100644 --- a/src/app/shared/nominee-form/nominee-form.component.ts +++ b/src/app/shared/nominee-form/nominee-form.component.ts @@ -36,6 +36,7 @@ export interface NomineeData { // Linked client fields clientId?: number; displayName?: string; + cifNumber?: string; } const RELATIONSHIPS = [ @@ -98,6 +99,7 @@ export class NomineeFormComponent implements OnInit { clientDob = ''; clientAadhaar = ''; clientPan = ''; + clientCifNumber = ''; linkedRelationship = ''; linkedShare = 100; @@ -177,7 +179,12 @@ export class NomineeFormComponent implements OnInit { this.searchResults = []; this.searchText = ''; this.fetchLinkedClientData(client.id); - this.nomineeChange.emit({ mode: 'linked', clientId: client.id, displayName: client.displayName }); + this.nomineeChange.emit({ + mode: 'linked', + clientId: client.id, + displayName: client.displayName, + cifNumber: client.accountNo || client.cifNumber || '' + }); } removeLinked(): void { @@ -191,6 +198,7 @@ export class NomineeFormComponent implements OnInit { mode: 'linked', clientId: this.selectedClient.id, displayName: this.selectedClient.displayName, + cifNumber: this.selectedClient.accountNo || this.selectedClient.cifNumber || '', relationship: rel, nomineeShare: this.linkedShare }); @@ -204,6 +212,7 @@ export class NomineeFormComponent implements OnInit { this.clientDob = ''; this.clientAadhaar = ''; this.clientPan = ''; + this.clientCifNumber = ''; this.searchText = ''; this.searchResults = []; this.linkedRelationship = ''; diff --git a/src/environments/.env.ts b/src/environments/.env.ts index d1086eaaa..77fd662d8 100644 --- a/src/environments/.env.ts +++ b/src/environments/.env.ts @@ -5,12 +5,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + // IMPORTANT: THIS FILE IS AUTO GENERATED! DO NOT MANUALLY EDIT OR CHECKIN! /* tslint:disable */ export default { 'mifos_x': { - 'version': '260425', - 'hash': 'fbaec185' + '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; + } +}