feat(tasks): backend-driven pagination, search and date filters for Client Approval
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.
This commit is contained in:
+80
-33
@@ -6,29 +6,73 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
@if (showData) {
|
||||
<div class="cv-tab-content">
|
||||
<div class="cv-action-bar">
|
||||
<mat-form-field appearance="outline" class="cv-search-field">
|
||||
<mat-icon matPrefix>search</mat-icon>
|
||||
<input
|
||||
#filterInput
|
||||
matInput
|
||||
placeholder="{{ 'labels.text.Filter by name' | translate }}"
|
||||
(keyup)="applyFilter(filterInput.value)"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<div class="cv-action-buttons">
|
||||
<button
|
||||
mat-stroked-button
|
||||
class="cv-btn cv-btn-approve"
|
||||
*mifosxHasPermission="'ACTIVATE_CLIENT'"
|
||||
(click)="approveClients()"
|
||||
>
|
||||
<fa-icon icon="check" class="m-r-10"></fa-icon>{{ 'labels.buttons.Approve' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<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) {
|
||||
@@ -60,8 +104,8 @@
|
||||
{{ element.displayName }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="accountNumber">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Account Number' | translate }}</th>
|
||||
<ng-container matColumnDef="clientId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Client ID' | translate }}</th>
|
||||
<td
|
||||
mat-cell
|
||||
*matCellDef="let element"
|
||||
@@ -69,7 +113,7 @@
|
||||
[routerLink]="['../../clients', element.id]"
|
||||
style="cursor: pointer"
|
||||
>
|
||||
{{ element.accountNo }}
|
||||
{{ element.id }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="staff">
|
||||
@@ -94,12 +138,15 @@
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!showData) {
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-user-check empty-icon"></i>
|
||||
<p class="empty-text">{{ 'labels.text.No pending loans for approval' | translate }}</p>
|
||||
</div>
|
||||
}
|
||||
<mat-paginator
|
||||
class="cv-paginator"
|
||||
[length]="totalRecords"
|
||||
[pageSize]="pageSize"
|
||||
[pageSizeOptions]="pageSizeOptions"
|
||||
[pageIndex]="pageIndex"
|
||||
showFirstLastButtons
|
||||
(page)="onPageChange($event)"
|
||||
></mat-paginator>
|
||||
}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user