Client Approval previously fetched up to 1000 pending clients via a route resolver and did name-search/sorting client-side (the search box was actually dead — it set a MatTableDataSource filter that the grouped-table view never read). Reworked to mirror PendingClientEditsComponent: real mat-paginator against /clients (offset/limit), server-side displayName search, orderBy=submittedOnDate for latest-first, and All/Today/Yesterday presets backed by the new fromDate/toDate params. Dropped the now-unused GetGroupedClientsData resolver. Also: Account Number column -> Client ID (element.id), disabled the Approve button with a "N selected" indicator when nothing's checked, and a redesigned action bar / empty-state (context-aware message for zero-results-from-search vs. zero-results-from-date-filter vs. truly nothing pending) — plus the plain-div search box and matched heights across chips/search/button, replacing an earlier mat-form-field version that Material's own outlined-field box model kept misaligning against the pill buttons.
153 lines
5.6 KiB
HTML
153 lines
5.6 KiB
HTML
<!--
|
|
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/.
|
|
-->
|
|
|
|
<div class="cv-tab-content">
|
|
<div class="cv-action-bar">
|
|
<div class="cv-search-box">
|
|
<fa-icon icon="search"></fa-icon>
|
|
<input
|
|
#filterInput
|
|
type="text"
|
|
placeholder="{{ 'labels.text.Filter by name' | translate }}"
|
|
(keyup.enter)="applyFilter(filterInput.value)"
|
|
/>
|
|
</div>
|
|
<div class="cv-quick-filters">
|
|
@for (p of datePresets; track p.key) {
|
|
<button
|
|
type="button"
|
|
class="cv-chip"
|
|
[class.cv-chip--active]="activePreset === p.key"
|
|
(click)="applyPreset(p.key)"
|
|
>
|
|
{{ p.label }}
|
|
</button>
|
|
}
|
|
</div>
|
|
<div class="cv-action-buttons">
|
|
@if (selection.selected.length > 0) {
|
|
<span class="cv-selected-count">{{ selection.selected.length }} selected</span>
|
|
}
|
|
<button
|
|
mat-stroked-button
|
|
class="cv-btn cv-btn-approve"
|
|
*mifosxHasPermission="'ACTIVATE_CLIENT'"
|
|
[disabled]="!selection.hasValue()"
|
|
(click)="approveClients()"
|
|
>
|
|
<fa-icon icon="check" class="m-r-10"></fa-icon>{{ 'labels.buttons.Approve' | translate }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
@if (loading) {
|
|
<div class="cv-empty">
|
|
<div class="cv-spinner"></div>
|
|
<p class="cv-empty__text">Loading pending clients…</p>
|
|
</div>
|
|
} @else if (loaded && !showData) {
|
|
<div class="cv-empty">
|
|
<div class="cv-empty__badge">
|
|
<fa-icon icon="user-check"></fa-icon>
|
|
</div>
|
|
@if (searchTerm) {
|
|
<p class="cv-empty__title">No matching clients</p>
|
|
<p class="cv-empty__text">
|
|
No pending client matches “{{ searchTerm }}”. Try
|
|
<a class="cv-empty__link" (click)="applyFilter(''); filterInput.value = ''">clearing the search</a>.
|
|
</p>
|
|
} @else if (activePreset !== 'all') {
|
|
<p class="cv-empty__title">No requests in this range</p>
|
|
<p class="cv-empty__text">
|
|
No client requests were submitted in this range. Try
|
|
<a class="cv-empty__link" (click)="applyPreset('all')">All requests</a> instead.
|
|
</p>
|
|
} @else {
|
|
<p class="cv-empty__title">{{ 'labels.text.No pending loans for approval' | translate }}</p>
|
|
<p class="cv-empty__text">Every client request has been reviewed — nothing waiting on you right now.</p>
|
|
}
|
|
</div>
|
|
} @else {
|
|
@for (client of groupedClients | keyvalue; track client) {
|
|
<h3 class="cv-group-title">{{ client.key }}</h3>
|
|
@if (client.value.length > 0) {
|
|
<div class="table-container cv-group-table">
|
|
<table mat-table [dataSource]="client.value | accountsFilter: 'clientApproval'" class="modern-table">
|
|
<ng-container matColumnDef="select">
|
|
<th mat-header-cell *matHeaderCellDef>
|
|
<mat-checkbox
|
|
(change)="$event ? masterToggle(client.value) : null"
|
|
[checked]="selection.hasValue() && isAllSelected(client.value)"
|
|
[indeterminate]="selection.hasValue() && !isAllSelected(client.value)"
|
|
[aria-label]="checkboxLabel(client.value)"
|
|
>
|
|
</mat-checkbox>
|
|
</th>
|
|
<td mat-cell *matCellDef="let row">
|
|
<mat-checkbox
|
|
(click)="$event.stopPropagation()"
|
|
(change)="$event ? selection.toggle(row) : null"
|
|
[checked]="selection.isSelected(row)"
|
|
[aria-label]="checkboxLabel(row)"
|
|
>
|
|
</mat-checkbox>
|
|
</td>
|
|
</ng-container>
|
|
<ng-container matColumnDef="name">
|
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.name' | translate }}</th>
|
|
<td mat-cell *matCellDef="let element" class="cell-link" [routerLink]="['../../clients', element.id]">
|
|
{{ element.displayName }}
|
|
</td>
|
|
</ng-container>
|
|
<ng-container matColumnDef="clientId">
|
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Client ID' | translate }}</th>
|
|
<td
|
|
mat-cell
|
|
*matCellDef="let element"
|
|
class="cell-mono"
|
|
[routerLink]="['../../clients', element.id]"
|
|
style="cursor: pointer"
|
|
>
|
|
{{ element.id }}
|
|
</td>
|
|
</ng-container>
|
|
<ng-container matColumnDef="staff">
|
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Staff' | translate }}</th>
|
|
<td
|
|
mat-cell
|
|
*matCellDef="let element"
|
|
[routerLink]="['../../clients', element.id]"
|
|
style="cursor: pointer"
|
|
>
|
|
{{ element.staffName }}
|
|
</td>
|
|
</ng-container>
|
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
|
<tr
|
|
mat-row
|
|
*matRowDef="let row; columns: displayedColumns"
|
|
(click)="selection.toggle(row)"
|
|
class="table-row"
|
|
></tr>
|
|
</table>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
<mat-paginator
|
|
class="cv-paginator"
|
|
[length]="totalRecords"
|
|
[pageSize]="pageSize"
|
|
[pageSizeOptions]="pageSizeOptions"
|
|
[pageIndex]="pageIndex"
|
|
showFirstLastButtons
|
|
(page)="onPageChange($event)"
|
|
></mat-paginator>
|
|
}
|
|
</div>
|