Files
cbsnexora/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.html
T
niteshandClaude Opus 5 e8097bc2cb feat(tasks): open a checker inbox entry in a popup instead of a page
Clicking a row navigated away to its own page, costing the checker the list and
whatever search they had set up. It now opens in a dialog over the list, the
same pattern the Disbursement and Closure Requests tabs already use.

ViewCheckerInboxComponent gained the optional MatDialogRef/MAT_DIALOG_DATA pair
rather than being duplicated, so the direct route still works for a bookmarked
link. Two differences follow from being a dialog:

 - the list rows carry only summary fields, so the detail (which is what has
   commandAsJson) is fetched here; by route it stays resolver-provided. The
   template reads checkerInboxDetail.* unguarded, so it is now behind an @if
   with a loading state -- otherwise a dialog would render against undefined.
 - commandAsJson is absent on some entries (a DELETE carries no payload) and
   JSON.parse(undefined) throws. By route that broke the page; in a dialog it
   would have left an empty shell open. Parsing is now guarded.

The list only reloads when something was actually actioned, since reload()
re-navigates the route and would otherwise discard the checker's search on a
plain close.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 18:30:24 +05:30

181 lines
7.9 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">
<!-- Action bar (search + advanced toggle + Approve/Delete/Reject) — only
rendered when there's actual data to work with. An empty inbox shows
the friendly empty-state block below without these controls. -->
@if (checkerData) {
<div class="cv-action-bar">
<mat-form-field appearance="outline" class="cv-search-field">
<mat-icon matPrefix>search</mat-icon>
<input
matInput
placeholder="{{ 'labels.text.Search by user' | translate }}"
(keyup)="applyFilter($event.target.value)"
/>
</mat-form-field>
<button
mat-icon-button
(click)="toggleAdvancedSearch()"
class="cv-toggle-btn"
[attr.aria-label]="
showAdvancedSearch
? ('labels.buttons.Hide advanced search' | translate)
: ('labels.buttons.Show advanced search' | translate)
"
[attr.aria-expanded]="showAdvancedSearch"
>
<mat-icon>{{ showAdvancedSearch ? 'arrow_drop_up' : 'arrow_drop_down' }}</mat-icon>
</button>
<div class="cv-action-buttons">
<button mat-stroked-button class="cv-btn cv-btn-approve" (click)="approveChecker()">
<fa-icon icon="check" class="m-r-10"></fa-icon>{{ 'labels.buttons.Approve' | translate }}
</button>
<button mat-stroked-button class="cv-btn cv-btn-delete" (click)="deleteChecker()">
<fa-icon icon="trash" class="m-r-10"></fa-icon>{{ 'labels.buttons.Delete' | translate }}
</button>
<button mat-stroked-button class="cv-btn cv-btn-reject" (click)="rejectChecker()">
<fa-icon icon="times" class="m-r-10"></fa-icon>{{ 'labels.buttons.Reject' | translate }}
</button>
</div>
</div>
}
@if (showAdvancedSearch) {
<form [formGroup]="makerCheckerSearchForm">
<div class="cv-advanced-search">
<mat-form-field appearance="outline" class="cv-adv-field" (click)="makerDateTimeFromPicker.open()">
<mat-label>{{ 'labels.inputs.From date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="makerDateTimeFromPicker"
formControlName="makerDateTimeFrom"
/>
<mat-datepicker-toggle matSuffix [for]="makerDateTimeFromPicker"></mat-datepicker-toggle>
<mat-datepicker #makerDateTimeFromPicker></mat-datepicker>
</mat-form-field>
<mat-form-field appearance="outline" class="cv-adv-field" (click)="makerDateTimetoPicker.open()">
<mat-label>{{ 'labels.inputs.To date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="makerDateTimetoPicker"
formControlName="makerDateTimeto"
/>
<mat-datepicker-toggle matSuffix [for]="makerDateTimetoPicker"></mat-datepicker-toggle>
<mat-datepicker #makerDateTimetoPicker></mat-datepicker>
</mat-form-field>
<mat-form-field appearance="outline" class="cv-adv-field">
<mat-label>{{ 'labels.inputs.Select action' | translate }}</mat-label>
<mat-select formControlName="actionName">
@for (action of makerCheckerTemplate.actionNames; track action) {
<mat-option [value]="action">
{{ action }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="cv-adv-field">
<mat-label>{{ 'labels.inputs.Select entity' | translate }}</mat-label>
<mat-select formControlName="entityName">
@for (entity of makerCheckerTemplate.entityNames; track entity) {
<mat-option [value]="entity">
{{ entity }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="cv-adv-field">
<mat-label>{{ 'labels.inputs.Resource id' | translate }}</mat-label>
<input matInput formControlName="resourceId" />
</mat-form-field>
<button mat-flat-button color="primary" class="cv-btn cv-btn-search" type="button" (click)="search()">
<fa-icon icon="search" class="m-r-10"></fa-icon>{{ 'labels.buttons.Advanced Search' | translate }}
</button>
</div>
</form>
}
@if (checkerData && !noSearchedData) {
<div class="table-container">
<table mat-table [dataSource]="dataSource" class="modern-table">
<ng-container matColumnDef="select">
<th mat-header-cell *matHeaderCellDef>
<mat-checkbox
(change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()"
[aria-label]="checkboxLabel()"
>
</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="id">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.ID' | translate }}</th>
<td mat-cell *matCellDef="let makerChecker" class="cell-mono">{{ makerChecker.id }}</td>
</ng-container>
<ng-container matColumnDef="madeOnDate">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Made on Date' | translate }}</th>
<td mat-cell *matCellDef="let makerChecker">{{ makerChecker.madeOnDate | dateFormat }}</td>
</ng-container>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Status' | translate }}</th>
<td mat-cell *matCellDef="let makerChecker">
<span class="status-badge">{{ makerChecker.processingResult }}</span>
</td>
</ng-container>
<ng-container matColumnDef="user">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.User' | translate }}</th>
<td mat-cell *matCellDef="let makerChecker">{{ makerChecker.maker }}</td>
</ng-container>
<ng-container matColumnDef="action">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Action' | translate }}</th>
<td mat-cell *matCellDef="let makerChecker">{{ makerChecker.actionName }}</td>
</ng-container>
<ng-container matColumnDef="entity">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Entity' | translate }}</th>
<td mat-cell *matCellDef="let makerChecker">{{ makerChecker.entityName }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" (click)="viewEntry(row)" class="table-row"></tr>
</table>
</div>
}
@if (checkerData && noSearchedData) {
<div class="empty-state empty-state--filter">
<i class="fas fa-inbox empty-icon"></i>
<p class="empty-title">No matches</p>
<p class="empty-text">No checker inbox items match the current search. Try clearing the filters.</p>
</div>
}
@if (!checkerData) {
<div class="empty-state">
<i class="fas fa-inbox empty-icon"></i>
<p class="empty-title">Inbox is empty</p>
<p class="empty-text">
Currently no items in the Checker Inbox. New maker actions awaiting review will appear here.
</p>
</div>
}
</div>