/** * 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 | 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 = { 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 = { 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); } } }