From 846b2484d7e78d87784a10795c375c20c4734764 Mon Sep 17 00:00:00 2001 From: Jaanu423 Date: Thu, 21 May 2026 13:53:58 +0530 Subject: [PATCH] =?UTF-8?q?feat:=20accounts=20list=20=E2=80=94=20active-on?= =?UTF-8?q?ly=20filter,=20date=20range=20&=20as-on-date=20search,=20modern?= =?UTF-8?q?=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Show only active accounts (status=300) for SB/CA/FD/RD lists; status sent as query param - Add FROM/TO date range filter (API fires only when both are set) and independent AS ON DATE filter - Restrict date pickers to today max (no future date selection) - Default sort: latest accounts on top (ORDER BY id DESC) - Full UI redesign: accent header, monospace account pill, client avatar, modern badges - AccountsService: added status, fromDate, toDate, asOnDate to AccountListParams and all 4 methods Co-Authored-By: Claude Sonnet 4.6 --- .../accounts-list.component.html | 134 +++-- .../accounts-list.component.scss | 503 ++++++++++++------ .../accounts-list/accounts-list.component.ts | 259 +++++---- src/app/accounts/accounts.service.ts | 22 + 4 files changed, 570 insertions(+), 348 deletions(-) diff --git a/src/app/accounts/accounts-list/accounts-list.component.html b/src/app/accounts/accounts-list/accounts-list.component.html index c90880af5..47a178301 100644 --- a/src/app/accounts/accounts-list/accounts-list.component.html +++ b/src/app/accounts/accounts-list/accounts-list.component.html @@ -9,71 +9,105 @@
-
-
- -
-
-

{{ config.label }}

- - @if (!isLoading) { - {{ totalRecords }} total records - } @else { - Loading... - } - -
+
+ +
+
+

{{ config.label }}

+ + @if (isLoading) { + Loading... + } @else { + {{ totalRecords }} active accounts + } +
- -
-
- + +
+ -
- -
+ +
+
+
+ From + +
+
+ +
+
+ To + +
+
+ +
+ +
+ + + As On Date + + +
+ + @if (hasDateFilters) { + + } +
+ + +
@if (isLoading) { -
- - Loading accounts... +
+ + Loading accounts…
} @if (!isLoading && dataSource.data.length === 0) { -
- -

No accounts found

+
+
+ +
+

No active accounts found

+

Try adjusting your search

} @if (!isLoading && dataSource.data.length > 0) { -
+
- - - - - + - - - - +
Account No @@ -81,33 +115,30 @@ Type - - {{ getDepositTypeLabel(row) }} - + {{ config.typeLabel }} Client - {{ row.clientName }} +
+
{{ (row.clientName || '?')[0] }}
+ {{ row.clientName }} +
ProductProduct {{ row.savingsProductName || row.depositProductName || row.productName }} Balance @@ -115,37 +146,24 @@ Status - - {{ getStatusLabel(row) }} - + {{ getStatusLabel(row) }} Actions -
diff --git a/src/app/accounts/accounts-list/accounts-list.component.scss b/src/app/accounts/accounts-list/accounts-list.component.scss index 278974fce..858cf563b 100644 --- a/src/app/accounts/accounts-list/accounts-list.component.scss +++ b/src/app/accounts/accounts-list/accounts-list.component.scss @@ -6,18 +6,18 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// ─── Tokens ─────────────────────────────────────────────────────────────────── -$radius-sm: 8px; -$radius-md: 12px; -$radius-lg: 16px; -$shadow-card: - 0 1px 3px rgb(0 0 0 / 6%), - 0 4px 16px rgb(0 0 0 / 6%); -$transition: 0.15s ease; +$t: 0.18s ease; +$r-sm: 10px; +$r-md: 14px; +$r-lg: 18px; +$shadow: + 0 1px 3px rgb(0 0 0 / 5%), + 0 4px 20px rgb(0 0 0 / 6%); +$shadow-sm: 0 1px 3px rgb(0 0 0 / 8%); // ─── Page ───────────────────────────────────────────────────────────────────── .acl-page { - padding: 1.25rem 1.5rem; + padding: 1.5rem; display: flex; flex-direction: column; gap: 1rem; @@ -29,114 +29,106 @@ $transition: 0.15s ease; .acl-header { display: flex; align-items: center; - justify-content: space-between; + gap: 1rem; padding: 1.25rem 1.5rem; - border-radius: $radius-lg; background: #fff; - box-shadow: $shadow-card; + border-radius: $r-lg; + box-shadow: $shadow; position: relative; overflow: hidden; - &::before { + &::after { content: ''; position: absolute; inset: 0 auto 0 0; - width: 5px; - background: var(--accent-color, #1976d2); - border-radius: $radius-lg 0 0 $radius-lg; + width: 4px; + background: var(--ac, #1976d2); + border-radius: $r-lg 0 0 $r-lg; } - &__left { - display: flex; - align-items: center; - gap: 1rem; - } - - &__icon-wrap { + &__icon { width: 48px; height: 48px; - border-radius: $radius-md; - background: var(--accent-bg, #e3f2fd); + border-radius: $r-md; + background: var(--ac-bg, #e3f2fd); + color: var(--ac, #1976d2); display: flex; align-items: center; justify-content: center; - font-size: 1.35rem; - color: var(--accent-color, #1976d2); + font-size: 1.3rem; flex-shrink: 0; } &__title { - margin: 0; - font-size: 1.1rem; + margin: 0 0 2px; + font-size: 1.05rem; font-weight: 700; - color: #1a2332; + color: #0f172a; letter-spacing: -0.01em; - line-height: 1.3; } - &__subtitle { - font-size: 0.73rem; + &__count { + font-size: 0.72rem; color: #94a3b8; - margin-top: 3px; - display: block; font-weight: 500; } - // ── Accent variants ──────────────────────────────────────────────────────── + // accent variants &.accent--saving { - --accent-color: #1565c0; - --accent-bg: #dbeafe; + --ac: #1565c0; + --ac-bg: #dbeafe; } &.accent--current { - --accent-color: #065f46; - --accent-bg: #d1fae5; + --ac: #065f46; + --ac-bg: #d1fae5; } &.accent--fd { - --accent-color: #6b21a8; - --accent-bg: #f3e8ff; + --ac: #6b21a8; + --ac-bg: #f3e8ff; } &.accent--rd { - --accent-color: #c2410c; - --accent-bg: #ffedd5; + --ac: #c2410c; + --ac-bg: #ffedd5; } } -// ─── Toolbar (Search + Actions) ─────────────────────────────────────────────── -.acl-toolbar { +// ─── Search Bar ─────────────────────────────────────────────────────────────── +.acl-search-wrap { display: flex; align-items: center; - gap: 0.75rem; - padding: 0.75rem 1rem; + gap: 0.6rem; background: #fff; - border-radius: $radius-md; - box-shadow: $shadow-card; + border-radius: $r-md; + box-shadow: $shadow; + padding: 0.6rem 0.75rem; } -.acl-search-field { +.acl-search { flex: 1; display: flex; align-items: center; gap: 0.6rem; background: #f8fafc; border: 1.5px solid #e2e8f0; - border-radius: $radius-sm; - padding: 0.45rem 0.9rem; + border-radius: $r-sm; + padding: 0.42rem 0.85rem; transition: - border-color $transition, - box-shadow $transition; + border-color $t, + box-shadow $t, + background $t; &:focus-within { border-color: #3b82f6; - box-shadow: 0 0 0 3px rgb(59 130 246 / 12%); + box-shadow: 0 0 0 3px rgb(59 130 246 / 10%); background: #fff; } &__icon { color: #94a3b8; - font-size: 0.8rem; + font-size: 0.78rem; flex-shrink: 0; } @@ -159,9 +151,9 @@ $transition: 0.15s ease; color: #cbd5e1; cursor: pointer; padding: 0; - font-size: 0.75rem; + font-size: 0.72rem; + transition: color $t; line-height: 1; - transition: color $transition; &:hover { color: #64748b; @@ -169,183 +161,343 @@ $transition: 0.15s ease; } } -.acl-toggle-btn, -.acl-refresh-btn { - white-space: nowrap; - padding: 0.45rem 0.9rem; - border-radius: $radius-sm; - border: 1.5px solid #e2e8f0; - background: #f8fafc; - font-size: 0.78rem; - font-weight: 600; - color: #475569; - cursor: pointer; +.acl-btn { display: inline-flex; align-items: center; gap: 0.35rem; + padding: 0.42rem 0.9rem; + border-radius: $r-sm; + border: 1.5px solid #e2e8f0; + background: #f8fafc; + font-size: 0.77rem; + font-weight: 600; + color: #475569; + cursor: pointer; + white-space: nowrap; transition: - background $transition, - color $transition, - border-color $transition; + background $t, + color $t, + border-color $t; &:hover { background: #eff6ff; color: #1d4ed8; border-color: #bfdbfe; } + + &--icon { + width: 38px; + height: 38px; + padding: 0; + justify-content: center; + flex-shrink: 0; + } } -.acl-refresh-btn { - width: 38px; - height: 38px; - padding: 0; - justify-content: center; - flex-shrink: 0; -} - -// ─── Table Card ─────────────────────────────────────────────────────────────── -.acl-table-card { - background: #fff; - border-radius: $radius-lg; - box-shadow: $shadow-card; - border: 1px solid #f1f5f9; - overflow: hidden; - min-height: 280px; - position: relative; -} - -.acl-loading { +// ─── Date Filters ───────────────────────────────────────────────────────────── +.acl-filters { display: flex; - flex-direction: column; align-items: center; - justify-content: center; - gap: 0.75rem; - padding: 4rem; - color: #94a3b8; - font-size: 0.83rem; - font-weight: 500; -} - -.acl-empty { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; gap: 0.5rem; - padding: 4rem; - color: #cbd5e1; + background: #fff; + border-radius: $r-md; + box-shadow: $shadow; + padding: 0.65rem 1rem; + flex-wrap: wrap; + + &__group { + display: flex; + align-items: center; + gap: 0.4rem; + } + + &__arrow { + color: #cbd5e1; + font-size: 0.72rem; + flex-shrink: 0; + } + + &__sep { + width: 1px; + height: 26px; + background: #e2e8f0; + margin: 0 0.4rem; + flex-shrink: 0; + } + + &__clear { + margin-left: auto; + display: inline-flex; + align-items: center; + gap: 0.35rem; + padding: 0.34rem 0.85rem; + border-radius: 20px; + border: 1.5px solid #fecdd3; + background: #fff1f2; + color: #be123c; + font-size: 0.72rem; + font-weight: 700; + cursor: pointer; + transition: + background $t, + border-color $t, + box-shadow $t; + white-space: nowrap; + + &:hover { + background: #ffe4e6; + border-color: #fda4af; + box-shadow: 0 0 0 3px rgb(253 164 175 / 15%); + } + } +} + +.acl-datepick { + display: flex; + flex-direction: column; + gap: 0.15rem; + + &__label { + font-size: 0.59rem; + font-weight: 800; + color: #94a3b8; + text-transform: uppercase; + letter-spacing: 0.08em; + display: flex; + align-items: center; + gap: 0.2rem; + padding-left: 2px; + + fa-icon { + font-size: 0.57rem; + } + } + + &__input { + border: 1.5px solid #e2e8f0; + border-radius: $r-sm; + background: #f8fafc; + padding: 0.3rem 0.6rem; + font-size: 0.77rem; + color: #1e293b; + outline: none; + cursor: pointer; + min-width: 128px; + transition: + border-color $t, + background $t, + box-shadow $t; + + &:focus { + border-color: #3b82f6; + box-shadow: 0 0 0 3px rgb(59 130 246 / 10%); + background: #fff; + } + + &::-webkit-calendar-picker-indicator { + opacity: 0.35; + cursor: pointer; + font-size: 0.72rem; + + &:hover { + opacity: 0.75; + } + } + } + + &--ason { + .acl-datepick__label { + color: #6366f1; + } + + .acl-datepick__input { + border-color: #c7d2fe; + background: #eef2ff; + + &:focus { + border-color: #6366f1; + box-shadow: 0 0 0 3px rgb(99 102 241 / 10%); + background: #fff; + } + } + } +} + +// ─── Card ───────────────────────────────────────────────────────────────────── +.acl-card { + background: #fff; + border-radius: $r-lg; + box-shadow: $shadow; + overflow: hidden; + min-height: 260px; +} + +// ─── States ─────────────────────────────────────────────────────────────────── +.acl-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.6rem; + padding: 4rem 2rem; + + &--loading { + color: #94a3b8; + font-size: 0.83rem; + font-weight: 500; + flex-direction: row; + gap: 0.75rem; + } &__icon { - color: #e2e8f0; + width: 56px; + height: 56px; + border-radius: 50%; + background: #f1f5f9; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.4rem; + color: #cbd5e1; } - p { - font-size: 0.88rem; - font-weight: 500; - color: #94a3b8; + &__title { margin: 0; + font-size: 0.92rem; + font-weight: 600; + color: #475569; } -} -.acl-table-wrapper { - overflow-x: auto; + &__sub { + margin: 0; + font-size: 0.78rem; + color: #94a3b8; + } } // ─── Table ──────────────────────────────────────────────────────────────────── +.acl-table-wrap { + overflow-x: auto; +} + .acl-table { width: 100%; .acl-header-row { background: #f8fafc; - border-bottom: 2px solid #e2e8f0; + border-bottom: 1.5px solid #f1f5f9; } .acl-th { - font-size: 0.65rem; - font-weight: 700; + font-size: 0.63rem; + font-weight: 800; text-transform: uppercase; - letter-spacing: 0.07em; - color: #64748b; - padding: 0.75rem 1rem; + letter-spacing: 0.08em; + color: #94a3b8; + padding: 0.85rem 1rem; white-space: nowrap; - - &--right { - text-align: right !important; - } + border-bottom: none; &--center { text-align: center !important; } + + &--right { + text-align: right !important; + } } - .acl-data-row { + .acl-row { cursor: pointer; border-bottom: 1px solid #f8fafc; - transition: background $transition; - - &:hover { - background: #f0f7ff !important; - - .acl-acct-no { - color: #1d4ed8; - background: #dbeafe; - } - } + transition: background $t; &:last-child { border-bottom: none; } + + &:hover { + background: #fafbff !important; + + .acl-acct-no { + background: #dbeafe; + color: #1d4ed8; + } + } } .acl-td { - padding: 0.7rem 1rem; + padding: 0.75rem 1rem; font-size: 0.82rem; vertical-align: middle; - - &--right { - text-align: right !important; - } + border-bottom: none; &--center { text-align: center !important; } + + &--right { + text-align: right !important; + } } } // ─── Cell Atoms ─────────────────────────────────────────────────────────────── .acl-acct-no { - font-family: 'Courier New', monospace; - font-size: 0.77rem; + font-family: 'JetBrains Mono', 'Courier New', monospace; + font-size: 0.76rem; font-weight: 700; - color: #1565c0; + color: #1d4ed8; background: #eff6ff; - padding: 3px 10px; + padding: 4px 12px; border-radius: 20px; white-space: nowrap; transition: - background $transition, - color $transition; + background $t, + color $t; + letter-spacing: 0.3px; } -.acl-client-name { - font-weight: 600; - color: #1e293b; - font-size: 0.82rem; +.acl-client { + display: flex; + align-items: center; + gap: 0.6rem; + + &__avatar { + width: 30px; + height: 30px; + border-radius: 50%; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: #fff; + font-size: 0.7rem; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + text-transform: uppercase; + } + + &__name { + font-weight: 600; + color: #1e293b; + font-size: 0.83rem; + } } .acl-product { - font-size: 0.75rem; + font-size: 0.74rem; color: #64748b; background: #f1f5f9; - padding: 3px 9px; - border-radius: $radius-sm; + border: 1px solid #e2e8f0; + padding: 3px 10px; + border-radius: $r-sm; display: inline-block; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; - border: 1px solid #e2e8f0; } .acl-balance { @@ -356,16 +508,16 @@ $transition: 0.15s ease; font-variant-numeric: tabular-nums; } -// ─── Deposit Type Badge ─────────────────────────────────────────────────────── -.acl-dtype-badge { +// ─── Type Badge ─────────────────────────────────────────────────────────────── +.acl-type-badge { display: inline-flex; align-items: center; justify-content: center; - font-size: 0.62rem; - font-weight: 800; + font-size: 0.6rem; + font-weight: 900; padding: 3px 10px; border-radius: 20px; - letter-spacing: 0.6px; + letter-spacing: 0.8px; text-transform: uppercase; &.dtype--sb { @@ -394,14 +546,14 @@ $transition: 0.15s ease; } // ─── Status Badge ───────────────────────────────────────────────────────────── -.acl-status-badge { +.acl-status { display: inline-block; - font-size: 0.65rem; - font-weight: 700; - padding: 3px 10px; + font-size: 0.63rem; + font-weight: 800; + padding: 3px 11px; border-radius: 20px; text-transform: uppercase; - letter-spacing: 0.5px; + letter-spacing: 0.6px; white-space: nowrap; &.status--submitted { @@ -447,11 +599,11 @@ $transition: 0.15s ease; } } -// ─── Action Button ──────────────────────────────────────────────────────────── +// ─── View Button ────────────────────────────────────────────────────────────── .acl-view-btn { width: 32px; height: 32px; - border-radius: $radius-sm; + border-radius: $r-sm; border: 1.5px solid #e2e8f0; background: #f8fafc; color: #64748b; @@ -461,10 +613,10 @@ $transition: 0.15s ease; justify-content: center; font-size: 0.78rem; transition: - background $transition, - color $transition, - border-color $transition, - box-shadow $transition; + background $t, + color $t, + border-color $t, + box-shadow $t; &:hover { background: #eff6ff; @@ -479,4 +631,9 @@ $transition: 0.15s ease; border-top: 1px solid #f1f5f9; background: #fafbfe; font-size: 0.8rem; + + ::ng-deep .mat-mdc-paginator-container { + padding: 0.4rem 1rem; + min-height: 52px; + } } diff --git a/src/app/accounts/accounts-list/accounts-list.component.ts b/src/app/accounts/accounts-list/accounts-list.component.ts index 2930757bf..d1857bb71 100644 --- a/src/app/accounts/accounts-list/accounts-list.component.ts +++ b/src/app/accounts/accounts-list/accounts-list.component.ts @@ -24,14 +24,12 @@ import { MatRowDef, MatRow } from '@angular/material/table'; -import { Subject } from 'rxjs'; +import { Subject, merge } from 'rxjs'; import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators'; -import { NgClass, NgIf } from '@angular/common'; +import { NgClass } from '@angular/common'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; -import { MatCardModule } from '@angular/material/card'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; -import { MatButtonModule } from '@angular/material/button'; import { FormatNumberPipe } from '../../pipes/format-number.pipe'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; import { AccountsService } from '../accounts.service'; @@ -41,8 +39,9 @@ export type AccountType = 'savings' | 'current' | 'fixed-deposits' | 'recurring- interface AccountConfig { label: string; icon: string; - color: string; accentClass: string; + typeLabel: string; + typeBadgeClass: string; } @Component({ @@ -68,11 +67,8 @@ interface AccountConfig { MatRow, MatPaginator, NgClass, - NgIf, - MatCardModule, MatTooltipModule, MatProgressSpinnerModule, - MatButtonModule, FormatNumberPipe ] }) @@ -80,7 +76,6 @@ export class AccountsListComponent implements OnInit, OnDestroy { private route = inject(ActivatedRoute); private router = inject(Router); private accountsService = inject(AccountsService); - private destroy$ = new Subject(); accountType: AccountType = 'savings'; @@ -102,24 +97,41 @@ export class AccountsListComponent implements OnInit, OnDestroy { searchControl = new FormControl(''); searchType: 'accountNo' | 'clientName' = 'accountNo'; + fromDateControl = new FormControl(''); + toDateControl = new FormControl(''); + asOnDateControl = new FormControl(''); @ViewChild(MatPaginator) paginator!: MatPaginator; @ViewChild(MatSort) sort!: MatSort; readonly configs: Record = { - savings: { label: 'Savings Accounts', icon: 'piggy-bank', color: '#1976d2', accentClass: 'accent--saving' }, - current: { label: 'Current Accounts', icon: 'building', color: '#00796b', accentClass: 'accent--current' }, + savings: { + label: 'Savings Accounts', + icon: 'piggy-bank', + accentClass: 'accent--saving', + typeLabel: 'SB', + typeBadgeClass: 'dtype--sb' + }, + current: { + label: 'Current Accounts', + icon: 'building', + accentClass: 'accent--current', + typeLabel: 'CA', + typeBadgeClass: 'dtype--ca' + }, 'fixed-deposits': { label: 'Fixed Deposit Accounts', icon: 'lock', - color: '#6a1b9a', - accentClass: 'accent--fd' + accentClass: 'accent--fd', + typeLabel: 'FD', + typeBadgeClass: 'dtype--fd' }, 'recurring-deposits': { label: 'Recurring Deposit Accounts', icon: 'sync', - color: '#e65100', - accentClass: 'accent--rd' + accentClass: 'accent--rd', + typeLabel: 'RD', + typeBadgeClass: 'dtype--rd' } }; @@ -141,6 +153,22 @@ export class AccountsListComponent implements OnInit, OnDestroy { this.pageIndex = 0; this.loadAccounts(); }); + + merge(this.fromDateControl.valueChanges, this.toDateControl.valueChanges) + .pipe(debounceTime(400), takeUntil(this.destroy$)) + .subscribe(() => { + const from = this.fromDateControl.value; + const to = this.toDateControl.value; + if ((from && to) || (!from && !to)) { + this.pageIndex = 0; + this.loadAccounts(); + } + }); + + this.asOnDateControl.valueChanges.pipe(debounceTime(300), takeUntil(this.destroy$)).subscribe(() => { + this.pageIndex = 0; + this.loadAccounts(); + }); } ngOnDestroy() { @@ -151,11 +179,21 @@ export class AccountsListComponent implements OnInit, OnDestroy { loadAccounts() { this.isLoading = true; const search = (this.searchControl.value || '').trim(); - const params = { + const fromVal = this.fromDateControl.value; + const toVal = this.toDateControl.value; + const bothDatesSet = !!(fromVal && toVal); + + const params: import('../accounts.service').AccountListParams = { offset: this.pageIndex * this.pageSize, limit: this.pageSize, - accountNo: this.searchType === 'accountNo' ? search : undefined, - clientName: this.searchType === 'clientName' ? search : undefined + accountNo: this.searchType === 'accountNo' ? search || undefined : undefined, + clientName: this.searchType === 'clientName' ? search || undefined : undefined, + orderBy: 'id', + sortOrder: 'DESC', + status: 'active', + ...(bothDatesSet && { fromDate: fromVal! }), + ...(bothDatesSet && { toDate: toVal! }), + ...(this.asOnDateControl.value && { asOnDate: this.asOnDateControl.value }) }; const obs$ = @@ -171,20 +209,33 @@ export class AccountsListComponent implements OnInit, OnDestroy { next: (res) => { let items: any[] = Array.isArray(res) ? res : (res?.pageItems ?? []); - // Client-side deposit type filter (safety net until backend rebuild) - const depositTypeFilter = this.getDepositTypeId(); - if (depositTypeFilter !== null) { - items = items.filter((a: any) => a?.depositType?.id === depositTypeFilter); + if (this.accountType === 'savings') { + items = items.filter((a: any) => a?.depositType?.id === 100); } - // Client-side search fallback (for when backend search isn't rebuilt yet) - const searchTerm = (this.searchControl.value || '').trim().toLowerCase(); + items = items.filter((a: any) => (a?.status?.id ?? a?.statusId) === 300); + + const searchTerm = search.toLowerCase(); + if (searchTerm) { + items = items.filter((a: any) => + this.searchType === 'accountNo' + ? (a?.accountNo || '').toLowerCase().includes(searchTerm) + : (a?.clientName || '').toLowerCase().includes(searchTerm) + ); + } + + if (bothDatesSet || this.asOnDateControl.value) { + const fromDate = bothDatesSet ? new Date(fromVal!) : null; + const toDate = bothDatesSet ? new Date(toVal! + 'T23:59:59') : null; + const asOnDate = this.asOnDateControl.value ? new Date(this.asOnDateControl.value + 'T23:59:59') : null; items = items.filter((a: any) => { - if (this.searchType === 'accountNo') { - return (a?.accountNo || '').toLowerCase().includes(searchTerm); - } - return (a?.clientName || '').toLowerCase().includes(searchTerm); + const d = this.parseFinDate(a?.activationDate ?? a?.submittedOnDate); + if (!d) return true; + if (fromDate && d < fromDate) return false; + if (toDate && d > toDate) return false; + if (asOnDate && d > asOnDate) return false; + return true; }); } @@ -204,93 +255,74 @@ export class AccountsListComponent implements OnInit, OnDestroy { this.loadAccounts(); } - onSortChange(sort: Sort) { + private parseFinDate(d: any): Date | null { + if (!d) return null; + if (Array.isArray(d) && d.length >= 3) return new Date(d[0], d[1] - 1, d[2]); + if (typeof d === 'string') return new Date(d); + return null; + } + + readonly today = new Date().toISOString().split('T')[0]; + + get hasDateFilters(): boolean { + return !!((this.fromDateControl.value && this.toDateControl.value) || this.asOnDateControl.value); + } + + clearDateFilters() { + this.fromDateControl.setValue(null, { emitEvent: false }); + this.toDateControl.setValue(null, { emitEvent: false }); + this.asOnDateControl.setValue(null, { emitEvent: false }); + this.pageIndex = 0; + this.loadAccounts(); + } + + onSortChange(_sort: Sort) { this.pageIndex = 0; this.loadAccounts(); } toggleSearchType() { this.searchType = this.searchType === 'accountNo' ? 'clientName' : 'accountNo'; - if (this.searchControl.value) { - this.loadAccounts(); - } + if (this.searchControl.value) this.loadAccounts(); } clearSearch() { this.searchControl.setValue(''); } - getDepositTypeId(): number | null { - // Only filter on savings page — FD/RD endpoints already return correct type - return this.accountType === 'savings' ? 100 : null; - } - - getDepositTypeBadgeClass(_account: any): string { - switch (this.accountType) { - case 'savings': - return 'dtype--sb'; - case 'fixed-deposits': - return 'dtype--fd'; - case 'recurring-deposits': - return 'dtype--rd'; - default: - return 'dtype--ca'; - } - } - - getDepositTypeLabel(_account: any): string { - switch (this.accountType) { - case 'savings': - return 'SB'; - case 'fixed-deposits': - return 'FD'; - case 'recurring-deposits': - return 'RD'; - default: - return 'CA'; - } - } - viewAccount(account: any) { const id = account.id; - switch (this.accountType) { - case 'savings': - this.router.navigate([ - '/clients', - account.clientId, - 'savings-accounts', - id, - 'general' - ]); - break; - case 'current': - this.router.navigate([ - '/clients', - account.clientId, - 'current-accounts', - id, - 'general' - ]); - break; - case 'fixed-deposits': - this.router.navigate([ - '/clients', - account.clientId, - 'fixed-deposits-accounts', - id, - 'general' - ]); - break; - case 'recurring-deposits': - this.router.navigate([ - '/clients', - account.clientId, - 'recurring-deposits-accounts', - id, - 'general' - ]); - break; - } + const routes: Record = { + savings: [ + '/clients', + account.clientId, + 'savings-accounts', + id, + 'general' + ], + current: [ + '/clients', + account.clientId, + 'current-accounts', + id, + 'general' + ], + 'fixed-deposits': [ + '/clients', + account.clientId, + 'fixed-deposits-accounts', + id, + 'general' + ], + 'recurring-deposits': [ + '/clients', + account.clientId, + 'recurring-deposits-accounts', + id, + 'general' + ] + }; + this.router.navigate(routes[this.accountType]); } getBalance(account: any): number { @@ -303,22 +335,15 @@ export class AccountsListComponent implements OnInit, OnDestroy { getStatusClass(account: any): string { const id = account?.status?.id ?? account?.statusId; - switch (id) { - case 100: - return 'status--submitted'; - case 200: - return 'status--approved'; - case 300: - return 'status--active'; - case 400: - return 'status--withdrawn'; - case 500: - return 'status--rejected'; - case 600: - return 'status--closed'; - default: - return 'status--default'; - } + const map: Record = { + 100: 'status--submitted', + 200: 'status--approved', + 300: 'status--active', + 400: 'status--withdrawn', + 500: 'status--rejected', + 600: 'status--closed' + }; + return map[id] ?? 'status--default'; } getStatusLabel(account: any): string { diff --git a/src/app/accounts/accounts.service.ts b/src/app/accounts/accounts.service.ts index d48f2b0e9..fb262c0bc 100644 --- a/src/app/accounts/accounts.service.ts +++ b/src/app/accounts/accounts.service.ts @@ -17,6 +17,10 @@ export interface AccountListParams { clientName?: string; orderBy?: string; sortOrder?: string; + status?: string; + fromDate?: string; + toDate?: string; + asOnDate?: string; } @Injectable({ providedIn: 'root' }) @@ -32,6 +36,10 @@ export class AccountsService { if (params.clientName) httpParams = httpParams.set('clientName', params.clientName); if (params.orderBy) httpParams = httpParams.set('orderBy', params.orderBy); if (params.sortOrder) httpParams = httpParams.set('sortOrder', params.sortOrder); + if (params.status) httpParams = httpParams.set('status', params.status); + if (params.fromDate) httpParams = httpParams.set('fromDate', params.fromDate); + if (params.toDate) httpParams = httpParams.set('toDate', params.toDate); + if (params.asOnDate) httpParams = httpParams.set('asOnDate', params.asOnDate); return this.http.get('/savingsaccounts', { params: httpParams }); } @@ -41,6 +49,12 @@ export class AccountsService { .set('limit', (params.limit ?? 20).toString()); if (params.accountNo) httpParams = httpParams.set('accountNo', params.accountNo); if (params.clientName) httpParams = httpParams.set('clientName', params.clientName); + if (params.orderBy) httpParams = httpParams.set('orderBy', params.orderBy); + if (params.sortOrder) httpParams = httpParams.set('sortOrder', params.sortOrder); + if (params.status) httpParams = httpParams.set('status', params.status); + if (params.fromDate) httpParams = httpParams.set('fromDate', params.fromDate); + if (params.toDate) httpParams = httpParams.set('toDate', params.toDate); + if (params.asOnDate) httpParams = httpParams.set('asOnDate', params.asOnDate); return this.http.get('/currentaccounts', { params: httpParams }); } @@ -53,6 +67,10 @@ export class AccountsService { if (params.clientName) httpParams = httpParams.set('clientName', params.clientName); if (params.orderBy) httpParams = httpParams.set('orderBy', params.orderBy); if (params.sortOrder) httpParams = httpParams.set('sortOrder', params.sortOrder); + if (params.status) httpParams = httpParams.set('status', params.status); + if (params.fromDate) httpParams = httpParams.set('fromDate', params.fromDate); + if (params.toDate) httpParams = httpParams.set('toDate', params.toDate); + if (params.asOnDate) httpParams = httpParams.set('asOnDate', params.asOnDate); return this.http.get('/fixeddepositaccounts', { params: httpParams }); } @@ -65,6 +83,10 @@ export class AccountsService { if (params.clientName) httpParams = httpParams.set('clientName', params.clientName); if (params.orderBy) httpParams = httpParams.set('orderBy', params.orderBy); if (params.sortOrder) httpParams = httpParams.set('sortOrder', params.sortOrder); + if (params.status) httpParams = httpParams.set('status', params.status); + if (params.fromDate) httpParams = httpParams.set('fromDate', params.fromDate); + if (params.toDate) httpParams = httpParams.set('toDate', params.toDate); + if (params.asOnDate) httpParams = httpParams.set('asOnDate', params.asOnDate); return this.http.get('/recurringdepositaccounts', { params: httpParams }); } }