236 lines
6.5 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
}
|