feat: add KYC document renewal alerts, Aadhaar/PAN exceptions, and AML recurring alerts UI; remove old journal AML marking
This commit is contained in:
@@ -62,6 +62,30 @@ export class TdsInterestReportComponent implements OnInit {
|
||||
/** Which customer's quarter-wise detail is open. */
|
||||
expandedId: string | null = null;
|
||||
exporting = false;
|
||||
selectedTab: 'paying' | 'not-paying' = 'paying';
|
||||
|
||||
/** Rows per page; 0 shows the whole list. */
|
||||
readonly pageSizeOptions = [
|
||||
25,
|
||||
50,
|
||||
100,
|
||||
0
|
||||
];
|
||||
pageSize = 25;
|
||||
pageIndex = 0;
|
||||
|
||||
/**
|
||||
* Row and summary computation is memoised against the filter, because every getter below is
|
||||
* read many times per change-detection cycle (the KPI block alone reads `summary` a dozen
|
||||
* times) and recomputing the whole book on each read is what made this table slow enough to
|
||||
* need paging in the first place.
|
||||
*/
|
||||
private rowsCacheKey = '';
|
||||
private allRows: TdsInterestRow[] = [];
|
||||
private paying: TdsInterestRow[] = [];
|
||||
private notPaying: TdsInterestRow[] = [];
|
||||
private summaryCacheKey = '';
|
||||
private cachedSummary: TdsInterestSummary | null = null;
|
||||
|
||||
ngOnInit(): void {
|
||||
// A branch login sees only its own book and cannot widen the scope.
|
||||
@@ -94,12 +118,107 @@ export class TdsInterestReportComponent implements OnInit {
|
||||
return this.filter.branchCode ? glBranchName(this.filter.branchCode) : 'All branches';
|
||||
}
|
||||
|
||||
/** Recomputes the book only when the filter actually changed. Side-effect free. */
|
||||
private ensureRows(): void {
|
||||
const key = JSON.stringify(this.filter);
|
||||
if (key === this.rowsCacheKey) {
|
||||
return;
|
||||
}
|
||||
this.rowsCacheKey = key;
|
||||
this.allRows = tdsInterestRows(this.filter);
|
||||
this.paying = this.allRows.filter((r) => r.schedule.totalTds > 0);
|
||||
this.notPaying = this.allRows.filter((r) => r.schedule.totalTds === 0);
|
||||
}
|
||||
|
||||
get rows(): TdsInterestRow[] {
|
||||
return tdsInterestRows(this.filter);
|
||||
this.ensureRows();
|
||||
return this.allRows;
|
||||
}
|
||||
|
||||
get payingRows(): TdsInterestRow[] {
|
||||
this.ensureRows();
|
||||
return this.paying;
|
||||
}
|
||||
|
||||
get notPayingRows(): TdsInterestRow[] {
|
||||
this.ensureRows();
|
||||
return this.notPaying;
|
||||
}
|
||||
|
||||
get summary(): TdsInterestSummary {
|
||||
return tdsInterestSummary(this.filter);
|
||||
const key = JSON.stringify(this.filter);
|
||||
if (key !== this.summaryCacheKey || !this.cachedSummary) {
|
||||
this.summaryCacheKey = key;
|
||||
this.cachedSummary = tdsInterestSummary(this.filter);
|
||||
}
|
||||
return this.cachedSummary;
|
||||
}
|
||||
|
||||
/** Every row of the tab on screen, before paging. */
|
||||
get visibleRows(): TdsInterestRow[] {
|
||||
return this.selectedTab === 'paying' ? this.payingRows : this.notPayingRows;
|
||||
}
|
||||
|
||||
get pageCount(): number {
|
||||
if (!this.pageSize) {
|
||||
return 1;
|
||||
}
|
||||
return Math.max(1, Math.ceil(this.visibleRows.length / this.pageSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamped rather than assigned, so a filter that shrinks the list can never leave the table
|
||||
* rendering an out-of-range page, and nothing mutates state during change detection.
|
||||
*/
|
||||
get currentPage(): number {
|
||||
return Math.min(this.pageIndex, this.pageCount - 1);
|
||||
}
|
||||
|
||||
/** The slice actually rendered. */
|
||||
get pagedRows(): TdsInterestRow[] {
|
||||
const rows = this.visibleRows;
|
||||
if (!this.pageSize) {
|
||||
return rows;
|
||||
}
|
||||
const start = this.currentPage * this.pageSize;
|
||||
return rows.slice(start, start + this.pageSize);
|
||||
}
|
||||
|
||||
get rangeStart(): number {
|
||||
if (!this.visibleRows.length) {
|
||||
return 0;
|
||||
}
|
||||
return this.pageSize ? this.currentPage * this.pageSize + 1 : 1;
|
||||
}
|
||||
|
||||
get rangeEnd(): number {
|
||||
const total = this.visibleRows.length;
|
||||
return this.pageSize ? Math.min(total, (this.currentPage + 1) * this.pageSize) : total;
|
||||
}
|
||||
|
||||
goToPage(page: number): void {
|
||||
this.pageIndex = Math.max(0, Math.min(page, this.pageCount - 1));
|
||||
// The open quarter-wise detail belongs to a row that is no longer on screen.
|
||||
this.expandedId = null;
|
||||
}
|
||||
|
||||
/** Keeps the first row currently on screen in view when the page size changes. */
|
||||
onPageSizeChange(size: number): void {
|
||||
const anchor = this.currentPage * (this.pageSize || 1);
|
||||
this.pageSize = Number(size);
|
||||
this.pageIndex = this.pageSize ? Math.floor(anchor / this.pageSize) : 0;
|
||||
this.expandedId = null;
|
||||
}
|
||||
|
||||
/** A different filter is a different list, so paging starts over. */
|
||||
onFilterChanged(): void {
|
||||
this.pageIndex = 0;
|
||||
this.expandedId = null;
|
||||
}
|
||||
|
||||
selectTab(tab: 'paying' | 'not-paying'): void {
|
||||
this.selectedTab = tab;
|
||||
this.onFilterChanged();
|
||||
}
|
||||
|
||||
get hasFilters(): boolean {
|
||||
@@ -121,11 +240,13 @@ export class TdsInterestReportComponent implements OnInit {
|
||||
crossedOnly: false,
|
||||
nearThresholdOnly: false
|
||||
};
|
||||
this.onFilterChanged();
|
||||
}
|
||||
|
||||
/** Jump straight to the customers worth chasing a declaration from. */
|
||||
showNearThreshold(): void {
|
||||
this.filter = { ...this.filter, crossedOnly: false, nearThresholdOnly: true };
|
||||
this.onFilterChanged();
|
||||
}
|
||||
|
||||
toggleExpanded(row: TdsInterestRow): void {
|
||||
|
||||
Reference in New Issue
Block a user