- New "Pending Client Edits" screen under Checker Inbox & Tasks: server-driven pagination,
date range + Today/Yesterday/All presets, status filter (Pending/Approved/Rejected), search —
mirrors ChequebookRequestsComponent's architecture.
- Compare dialog diffs the maker's proposed values against a point-in-time "before" snapshot
(client-edit-diff.util.ts) captured when the edit page was opened, so only genuinely changed
fields show — works correctly for Pending, Approved, and Rejected edits alike, since it no
longer depends on comparing against the live (possibly already-updated) client record.
- edit-client.component.ts: General tab save now submits for approval instead of applying
immediately, shows a pending-approval banner, and blocks a second submission while one's
outstanding.
- error-handler.interceptor.ts: added the new /clients/{id}/edit/pending check to the existing
"expected 403/404, don't alert" allowlist (same pattern already used for client images/KYC).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
158 lines
5.4 KiB
HTML
158 lines
5.4 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="pce-tab-content">
|
|
<div class="pce-action-bar">
|
|
<div class="pce-search-box">
|
|
<mat-icon>search</mat-icon>
|
|
<input
|
|
#filterInput
|
|
type="text"
|
|
placeholder="Search by client name"
|
|
(keyup.enter)="applyFilter(filterInput.value)"
|
|
/>
|
|
</div>
|
|
|
|
<select class="pce-filter-select" [(ngModel)]="filterStatus" (change)="onFilterChange()">
|
|
@for (opt of statusOptions; track opt.value) {
|
|
<option [value]="opt.value">{{ opt.label }}</option>
|
|
}
|
|
</select>
|
|
|
|
<span class="pce-fb-sep"></span>
|
|
|
|
<div class="pce-presets">
|
|
@for (p of datePresets; track p.key) {
|
|
<button
|
|
type="button"
|
|
class="pce-preset-chip"
|
|
[class.pce-preset-chip--active]="activePreset === p.key"
|
|
(click)="applyPreset(p.key)"
|
|
>
|
|
{{ p.label }}
|
|
</button>
|
|
}
|
|
</div>
|
|
|
|
<span class="pce-fb-sep"></span>
|
|
|
|
<div class="pce-date-range">
|
|
<div class="pce-date-box">
|
|
<span class="pce-date-box__lbl">From</span>
|
|
<input
|
|
class="pce-date-box__input"
|
|
[matDatepicker]="fromPicker"
|
|
[(ngModel)]="fromDate"
|
|
[max]="maxDate"
|
|
(dateChange)="onFromDateChange()"
|
|
readonly
|
|
/>
|
|
<button type="button" class="pce-date-box__btn" (click)="fromPicker.open()">
|
|
<mat-icon>event</mat-icon>
|
|
</button>
|
|
<mat-datepicker #fromPicker></mat-datepicker>
|
|
</div>
|
|
<span class="pce-date-range__sep">—</span>
|
|
<div class="pce-date-box">
|
|
<span class="pce-date-box__lbl">To</span>
|
|
<input
|
|
class="pce-date-box__input"
|
|
[matDatepicker]="toPicker"
|
|
[(ngModel)]="toDate"
|
|
[min]="fromDate"
|
|
[max]="maxDate"
|
|
(dateChange)="onToDateChange()"
|
|
readonly
|
|
/>
|
|
<button type="button" class="pce-date-box__btn" (click)="toPicker.open()">
|
|
<mat-icon>event</mat-icon>
|
|
</button>
|
|
<mat-datepicker #toPicker></mat-datepicker>
|
|
</div>
|
|
</div>
|
|
|
|
<button type="button" class="pce-btn pce-btn-refresh" (click)="load()" [disabled]="loading">
|
|
<fa-icon icon="sync-alt"></fa-icon>
|
|
</button>
|
|
</div>
|
|
|
|
@if (loading) {
|
|
<div class="empty-state">
|
|
<p class="empty-title">Loading…</p>
|
|
</div>
|
|
} @else if (loaded && edits.length === 0) {
|
|
<div class="empty-state">
|
|
<fa-icon icon="hourglass-half" class="empty-icon"></fa-icon>
|
|
<p class="empty-title">No {{ filterStatus | lowercase }} client edits</p>
|
|
<p class="empty-text">
|
|
No Client details edits found for this filter. Try
|
|
<a (click)="applyPreset('all')" class="pce-client-link">All</a> dates, or a different status above.
|
|
</p>
|
|
</div>
|
|
} @else {
|
|
<div class="table-container">
|
|
<table mat-table [dataSource]="edits" class="modern-table">
|
|
<ng-container matColumnDef="clientDisplayName">
|
|
<th mat-header-cell *matHeaderCellDef>Client</th>
|
|
<td mat-cell *matCellDef="let edit">
|
|
<a [routerLink]="['/clients', edit.clientId, 'general']" class="pce-client-link">{{
|
|
edit.clientDisplayName
|
|
}}</a>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="makerName">
|
|
<th mat-header-cell *matHeaderCellDef>Submitted By</th>
|
|
<td mat-cell *matCellDef="let edit">{{ edit.makerName }}</td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="submittedDate">
|
|
<th mat-header-cell *matHeaderCellDef>Submitted On</th>
|
|
<td mat-cell *matCellDef="let edit">{{ formatDate(edit.submittedDate) }}</td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="status">
|
|
<th mat-header-cell *matHeaderCellDef>Status</th>
|
|
<td mat-cell *matCellDef="let edit">
|
|
<span class="status-badge" [ngClass]="statusClass(edit.status)">{{ edit.status }}</span>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<ng-container matColumnDef="actions">
|
|
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
|
<td mat-cell *matCellDef="let edit">
|
|
<button mat-stroked-button class="pce-btn pce-btn-view" (click)="viewCompare(edit)">
|
|
<fa-icon icon="eye" class="m-r-10"></fa-icon>Compare
|
|
</button>
|
|
@if (edit.status === 'PENDING') {
|
|
<button mat-stroked-button class="pce-btn pce-btn-approve" (click)="approve(edit)">
|
|
<fa-icon icon="check" class="m-r-10"></fa-icon>Approve
|
|
</button>
|
|
<button mat-stroked-button class="pce-btn pce-btn-reject" (click)="reject(edit)">
|
|
<fa-icon icon="ban" class="m-r-10"></fa-icon>Reject
|
|
</button>
|
|
}
|
|
</td>
|
|
</ng-container>
|
|
|
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
|
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="table-row"></tr>
|
|
</table>
|
|
</div>
|
|
|
|
<mat-paginator
|
|
[length]="totalRecords"
|
|
[pageSize]="pageSize"
|
|
[pageSizeOptions]="pageSizeOptions"
|
|
[pageIndex]="pageIndex"
|
|
showFirstLastButtons
|
|
(page)="onPageChange($event)"
|
|
></mat-paginator>
|
|
}
|
|
</div>
|