Files
cbsnexora/src/app/tasks/checker-inbox-and-tasks-tabs/client-approval/client-approval.component.html
T
nitesh 1efb520009 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.
2026-07-30 10:31:55 +05:30

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&hellip;</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 &ldquo;{{ searchTerm }}&rdquo;. 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>