Files
cbsnexora/src/app/tasks/checker-inbox-and-tasks-tabs/withdrawal-requests/withdrawal-requests.component.ts
T

236 lines
6.5 KiB
TypeScript

/**
* 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, OnInit, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Router, RouterModule } from '@angular/router';
import { MatIconModule } from '@angular/material/icon';
import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { MatNativeDateModule } from '@angular/material/core';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { catchError } from 'rxjs/operators';
import { of } from 'rxjs';
import { ClerkWithdrawalService } from '../../../cashier/clerk-withdrawal.service';
import { SettingsService } from '../../../settings/settings.service';
@Component({
selector: 'mifosx-withdrawal-requests',
templateUrl: './withdrawal-requests.component.html',
styleUrls: ['./withdrawal-requests.component.scss'],
standalone: true,
imports: [
CommonModule,
FormsModule,
RouterModule,
MatIconModule,
MatPaginatorModule,
MatSnackBarModule,
MatNativeDateModule,
MatFormFieldModule,
MatInputModule,
MatDatepickerModule
]
})
export class WithdrawalRequestsComponent implements OnInit {
private service = inject(ClerkWithdrawalService);
private router = inject(Router);
private snackBar = inject(MatSnackBar);
private settingsService = inject(SettingsService);
requests: any[] = [];
totalRecords = 0;
pageSize = 20;
pageIndex = 0;
pageSizeOptions = [
10,
20,
50
];
filterStatus = 'PENDING';
fromDate: Date | null = null;
toDate: Date | null = null;
maxDate!: Date;
activePreset: string = 'today';
loading = false;
loaded = false;
searchTerm = '';
private searchDebounce: ReturnType<typeof setTimeout> | null = null;
readonly datePresets = [
{ key: 'today', label: 'Today' },
{ key: 'week', label: 'This Week' },
{ key: 'month', label: 'This Month' },
{ key: 'all', label: 'All' }
];
get statusOptions(): { value: string; label: string }[] {
return [
{ value: 'PENDING', label: 'Pending' },
{ value: 'APPROVED', label: 'Approved' },
{ value: 'REJECTED', label: 'Rejected' },
{ value: 'WITHDRAWN', label: 'Withdrawn' }
];
}
ngOnInit(): void {
const today = new Date(this.settingsService.businessDate);
today.setHours(0, 0, 0, 0);
this.maxDate = today;
// A pending request must never be hidden by date: submitted_at is wall-clock time, and the business
// date "today" can lag it, so opening on Today showed an empty queue. Open on All instead.
this.applyPreset('all');
}
load(): void {
this.loading = true;
this.service
.getPendingRequests(
this.filterStatus,
this.pageIndex * this.pageSize,
this.pageSize,
this.isoDate(this.fromDate),
this.isoDate(this.toDate),
this.searchTerm.trim() || undefined
)
.pipe(catchError(() => of({ pageItems: [], totalFilteredRecords: 0 })))
.subscribe((res: any) => {
this.requests = res?.pageItems ?? [];
this.totalRecords = Number(res?.totalFilteredRecords ?? 0);
this.loading = false;
this.loaded = true;
});
}
onSearchChange(): void {
if (this.searchDebounce) {
clearTimeout(this.searchDebounce);
}
this.searchDebounce = setTimeout(() => {
this.pageIndex = 0;
this.load();
}, 300);
}
onPageChange(event: PageEvent): void {
this.pageSize = event.pageSize;
this.pageIndex = event.pageIndex;
this.load();
}
onFilterChange(): void {
this.pageIndex = 0;
this.load();
}
onFromDateChange(): void {
this.activePreset = '';
if (this.fromDate && this.toDate && this.fromDate > this.toDate) {
this.toDate = new Date(this.fromDate);
}
this.pageIndex = 0;
this.load();
}
onToDateChange(): void {
this.activePreset = '';
this.pageIndex = 0;
this.load();
}
applyPreset(key: string): void {
this.activePreset = key;
const today = new Date(this.settingsService.businessDate);
today.setHours(0, 0, 0, 0);
if (key === 'today') {
this.fromDate = new Date(today);
this.toDate = new Date(today);
} else if (key === 'week') {
const start = new Date(today);
start.setDate(today.getDate() - today.getDay());
this.fromDate = start;
this.toDate = new Date(today);
} else if (key === 'month') {
this.fromDate = new Date(today.getFullYear(), today.getMonth(), 1);
this.toDate = new Date(today);
} else {
this.fromDate = null;
this.toDate = null;
}
this.pageIndex = 0;
this.load();
}
private isoDate(d: Date | null): string | undefined {
if (!d) return undefined;
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${y}-${m}-${day}`;
}
review(req: any): void {
this.router.navigate([
'/checker-inbox-and-tasks/withdrawal-requests',
req.id,
'review'
]);
}
statusClass(status: string): string {
const map: Record<string, string> = {
PENDING: 'wri-status--pending',
APPROVED: 'wri-status--approved',
REJECTED: 'wri-status--rejected',
WITHDRAWN: 'wri-status--disbursed'
};
return map[status] ?? '';
}
statusLabel(status: string): string {
const map: Record<string, string> = {
PENDING: 'Pending',
APPROVED: 'Approved',
REJECTED: 'Rejected',
WITHDRAWN: 'Withdrawn'
};
return map[status] ?? status;
}
formatDate(val: any): string {
if (!val) return '—';
try {
const d = new Date(val);
return d.toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' });
} catch {
return String(val);
}
}
formatDateTime(val: any): string {
if (!val) return '—';
try {
const d = new Date(val);
return d.toLocaleString('en-IN', {
day: '2-digit',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
} catch {
return String(val);
}
}
}