fix(tasks): lay the checker inbox popup out properly
Tests / testRigor (push) Canceled after 0s
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Publish Image in Docker Hub / build (push) Canceled after 0s

The dialog rendered but read badly: the heading appeared twice, and every field
ran together onto one line ("ID: 125Status: User: mifosAction: DELETE").

Cause was the global flex-40/flex-60/flex-fill utilities the template used --
they are sized for a full-width page, so inside a 680px dialog the label and
value columns collapsed and wrapped into each other. Replaced them with a
self-contained label-above-value grid that reflows to one column when narrow,
so the same markup reads correctly as a page and as a popup.

Also: moved the heading into the shared header so it is rendered once in both
modes, gave the actions real buttons instead of Material raised buttons styled
by two global overrides, put DELETE in a red tag so a destructive entry is
visible at a glance, and dropped MatDivider/MatDialogModule from imports now
that the markup no longer uses them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-01 18:40:59 +05:30
co-authored by Claude Opus 5
parent e8097bc2cb
commit 63134f172a
3 changed files with 302 additions and 158 deletions
@@ -6,125 +6,121 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div [class.vci--dialog]="isDialog">
@if (isDialog) {
<div class="vci-dialog-bar">
<span class="vci-dialog-bar__title">{{ 'labels.heading.View Checker Inbox' | translate }}</span>
<span class="vci-dialog-bar__close" (click)="close()">✕ {{ 'labels.buttons.Close' | translate }}</span>
</div>
}
<div class="vci" [class.vci--dialog]="isDialog">
<!-- The heading lives here in both modes, so a dialog does not end up showing it twice. -->
<div class="vci-head">
<span class="vci-head__title">{{ 'labels.heading.View Checker Inbox' | translate }}</span>
@if (isDialog) {
<button type="button" class="vci-head__close" (click)="close()">
<fa-icon icon="times"></fa-icon>
<span>{{ 'labels.buttons.Close' | translate }}</span>
</button>
}
</div>
<!-- As a dialog the detail is fetched rather than resolved, so nothing below can be rendered
until it arrives — the template reads checkerInboxDetail.* unguarded. -->
<!-- As a dialog the detail is fetched rather than resolved, so nothing below can render until it
arrives — every field reads checkerInboxDetail.* directly. -->
@if (loading) {
<div class="vci-loading">{{ 'labels.heading.Loading' | translate }}…</div>
<div class="vci-loading">
<fa-icon icon="spinner" class="vci-loading__ic"></fa-icon>
<span>{{ 'labels.heading.Loading' | translate }}…</span>
</div>
}
@if (checkerInboxDetail) {
@if (isProductEditEntry) {
<div class="container m-b-20">
<div class="mat-elevation-z1" style="padding: 12px 16px; border-left: 4px solid #f59e0b; background: #fffbeb">
<div class="vci-body">
@if (isProductEditEntry) {
<div class="vci-notice">
This is a product edit awaiting approval. Approve or reject it from
<strong>Approvals &gt; Pending Product Edits</strong> instead — actioning it here would not release its lock.
</div>
</div>
} @else {
<div class="layout-row align-end gap-1percent layout-lt-md-column container m-b-20">
<button mat-raised-button color="success" (click)="approveChecker()">
<fa-icon icon="check" class="m-r-10"></fa-icon>{{ 'labels.buttons.Approve' | translate }}
</button>
<button mat-raised-button color="warn" (click)="deleteChecker()">
<fa-icon icon="trash" class="m-r-10"></fa-icon>{{ 'labels.buttons.Delete' | translate }}
</button>
<button mat-raised-button color="reject" (click)="rejectChecker()">
<fa-icon icon="times" class="m-r-10"></fa-icon>{{ 'labels.buttons.Reject' | translate }}
</button>
</div>
}
} @else {
<div class="vci-actions">
<button type="button" class="vci-btn vci-btn--approve" (click)="approveChecker()">
<fa-icon icon="check"></fa-icon><span>{{ 'labels.buttons.Approve' | translate }}</span>
</button>
<button type="button" class="vci-btn vci-btn--delete" (click)="deleteChecker()">
<fa-icon icon="trash"></fa-icon><span>{{ 'labels.buttons.Delete' | translate }}</span>
</button>
<button type="button" class="vci-btn vci-btn--reject" (click)="rejectChecker()">
<fa-icon icon="times"></fa-icon><span>{{ 'labels.buttons.Reject' | translate }}</span>
</button>
</div>
}
<div class="container">
<mat-card>
<mat-card-content>
<div class="layout-row-wrap responsive-column">
<h2 class="mat-h2 flex-fill">{{ 'labels.heading.View Checker Inbox' | translate }}</h2>
<mat-divider [inset]="true"></mat-divider>
<h3 class="mat-h3 flex-fill">{{ 'labels.heading.Details' | translate }}</h3>
<mat-divider [inset]="true"></mat-divider>
<div class="flex-fill">
<span class="flex-40">{{ 'labels.inputs.ID' | translate }}:</span>
<span class="flex-60">{{ checkerInboxDetail.id }}</span>
</div>
<div class="flex-fill">
<span class="flex-40">{{ 'labels.inputs.Status' | translate }}:</span>
<span class="flex-60">{{ checkerInboxDetail.processingResult }}</span>
</div>
<div class="flex-fill">
<span class="flex-40">{{ 'labels.inputs.User' | translate }}:</span>
<span class="flex-60">{{ checkerInboxDetail.maker }}</span>
</div>
<div class="flex-fill">
<span class="flex-40">{{ 'labels.inputs.Action' | translate }}:</span>
<span class="flex-60">{{ checkerInboxDetail.actionName }}</span>
</div>
<div class="flex-fill">
<span class="flex-40">{{ 'labels.inputs.Entity' | translate }}:</span>
<span class="flex-60">{{ checkerInboxDetail.entityName }}</span>
</div>
@if (checkerInboxDetail.resourceId) {
<div class="flex-fill">
<span class="flex-40">{{ 'labels.inputs.Resource ID' | translate }}:</span>
<span class="flex-60">{{ checkerInboxDetail.resourceId }}</span>
</div>
}
<div class="flex-fill">
<span class="flex-40">{{ 'labels.inputs.Date' | translate }}:</span>
<span class="flex-60">{{ checkerInboxDetail.madeOnDate | dateFormat }}</span>
</div>
@if (checkerInboxDetail.officeName) {
<div class="flex-fill">
<span class="flex-40">{{ 'labels.inputs.Office' | translate }}:</span>
<span class="flex-60">{{ checkerInboxDetail.officeName }}</span>
</div>
}
@if (checkerInboxDetail.savingsAccountNo) {
<div class="flex-fill">
<span class="flex-40">{{ 'labels.inputs.Savings Account Number' | translate }}:</span>
<span class="flex-60">{{ checkerInboxDetail.savingsAccountNo }}</span>
</div>
}
@if (checkerInboxDetail.groupLevelName) {
<div class="flex-fill">
<span class="flex-40"> {{ checkerInboxDetail.groupLevelName }}</span>
<span class="flex-60">{{ checkerInboxDetail.groupName }}</span>
</div>
}
@if (displayJSONData) {
<h3 class="mat-h3 flex-fill">{{ 'labels.heading.Command' | translate }}</h3>
<mat-divider [inset]="true"></mat-divider>
@for (json of jsondata | keyvalue; track json) {
<div class="flex-fill">
<span class="flex-40">{{ json.key }}:</span>
<span class="flex-60">{{ json.value }}</span>
</div>
}
}
<div class="vci-section">
<div class="vci-section__title">{{ 'labels.heading.Details' | translate }}</div>
<!-- A real label/value grid rather than the global flex-40/flex-60 utilities, which are sized
for a full-width page and collapse into one run-together line inside a dialog. -->
<dl class="vci-grid">
<div class="vci-kv">
<dt>{{ 'labels.inputs.ID' | translate }}</dt>
<dd>{{ checkerInboxDetail.id }}</dd>
</div>
</mat-card-content>
</mat-card>
<div class="vci-kv">
<dt>{{ 'labels.inputs.Status' | translate }}</dt>
<dd>{{ checkerInboxDetail.processingResult || '—' }}</dd>
</div>
<div class="vci-kv">
<dt>{{ 'labels.inputs.User' | translate }}</dt>
<dd>{{ checkerInboxDetail.maker || '—' }}</dd>
</div>
<div class="vci-kv">
<dt>{{ 'labels.inputs.Action' | translate }}</dt>
<dd>
<span class="vci-tag" [class.vci-tag--danger]="checkerInboxDetail.actionName === 'DELETE'">{{
checkerInboxDetail.actionName
}}</span>
</dd>
</div>
<div class="vci-kv">
<dt>{{ 'labels.inputs.Entity' | translate }}</dt>
<dd>{{ checkerInboxDetail.entityName }}</dd>
</div>
@if (checkerInboxDetail.resourceId) {
<div class="vci-kv">
<dt>{{ 'labels.inputs.Resource ID' | translate }}</dt>
<dd>{{ checkerInboxDetail.resourceId }}</dd>
</div>
}
<div class="vci-kv">
<dt>{{ 'labels.inputs.Date' | translate }}</dt>
<dd>{{ checkerInboxDetail.madeOnDate | dateFormat }}</dd>
</div>
@if (checkerInboxDetail.officeName) {
<div class="vci-kv">
<dt>{{ 'labels.inputs.Office' | translate }}</dt>
<dd>{{ checkerInboxDetail.officeName }}</dd>
</div>
}
@if (checkerInboxDetail.savingsAccountNo) {
<div class="vci-kv">
<dt>{{ 'labels.inputs.Savings Account Number' | translate }}</dt>
<dd>{{ checkerInboxDetail.savingsAccountNo }}</dd>
</div>
}
@if (checkerInboxDetail.groupLevelName) {
<div class="vci-kv">
<dt>{{ checkerInboxDetail.groupLevelName }}</dt>
<dd>{{ checkerInboxDetail.groupName }}</dd>
</div>
}
</dl>
</div>
@if (displayJSONData) {
<div class="vci-section">
<div class="vci-section__title">{{ 'labels.heading.Command' | translate }}</div>
<dl class="vci-grid">
@for (json of jsondata | keyvalue; track json.key) {
<div class="vci-kv">
<dt>{{ json.key }}</dt>
<dd>{{ json.value }}</dd>
</div>
}
</dl>
</div>
}
</div>
}
</div>
@@ -6,45 +6,70 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.mat-elevation-z1 {
margin: 1em 0 1.5em;
$success: #16a34a;
$danger: #dc2626;
$warning: #d97706;
$text-dark: #0d1b2a;
$text-mid: #475569;
$text-soft: #94a3b8;
$border: #e2e8f0;
$bg-page: #f8fafc;
:host {
display: block;
}
/* Opened as a popup rather than a full page — sizes to its own content (capped so a long command
payload still scrolls inside the dialog) instead of stretching to the viewport. */
.vci--dialog {
width: min(720px, 92vw);
max-height: 85vh;
overflow-y: auto;
.vci {
background: #fff;
.container {
max-width: none;
padding: 0;
/* Opened as a popup rather than a full page: sized to its own content, capped so a long command
payload scrolls inside the dialog instead of pushing it off screen. As a route it stays a
normal page-width card. */
&--dialog {
width: min(680px, 92vw);
max-height: 85vh;
overflow-y: auto;
border-radius: 10px;
}
&:not(&--dialog) {
max-width: 900px;
margin: 20px auto;
border: 1px solid $border;
border-radius: 12px;
overflow: hidden;
}
}
.vci-dialog-bar {
// ── Header ───────────────────────────────────────────────────────────────────
.vci-head {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
gap: 12px;
padding: 14px 20px;
border-bottom: 1px solid $border;
background: #fff;
border-bottom: 1px solid #e2e8f0;
position: sticky;
top: 0;
z-index: 1;
&__title {
font-size: 14px;
font-size: 15px;
font-weight: 700;
color: #475569;
color: $text-dark;
}
&__close {
margin-left: auto;
font-size: 12px;
font-weight: 500;
color: #94a3b8;
display: inline-flex;
align-items: center;
gap: 6px;
border: none;
background: none;
font-family: inherit;
font-size: 12.5px;
font-weight: 600;
color: $text-soft;
cursor: pointer;
padding: 6px 12px;
border-radius: 6px;
@@ -52,43 +77,169 @@
&:hover {
background: #f1f5f9;
color: #475569;
color: $text-mid;
}
}
}
.vci-loading {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
padding: 48px 24px;
text-align: center;
font-size: 14px;
color: #94a3b8;
color: $text-soft;
&__ic {
animation: vci-spin 1s linear infinite;
}
}
h2,
h3,
h4 {
@keyframes vci-spin {
to {
transform: rotate(360deg);
}
}
.vci-body {
padding: 16px 20px 20px;
display: flex;
flex-direction: column;
gap: 16px;
}
// ── Actions ──────────────────────────────────────────────────────────────────
.vci-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.vci-btn {
display: inline-flex;
align-items: center;
gap: 7px;
height: 36px;
padding: 0 18px;
border-radius: 8px;
font-family: inherit;
font-size: 13.5px;
font-weight: 600;
cursor: pointer;
border: 1.5px solid transparent;
transition: all 0.15s;
&--approve {
background: #f0fdf4;
color: $success;
border-color: #86efac;
&:hover {
background: $success;
color: #fff;
border-color: $success;
}
}
&--delete {
background: $danger;
color: #fff;
border-color: $danger;
&:hover {
filter: brightness(1.08);
}
}
&--reject {
background: #fff;
color: $text-mid;
border-color: $border;
&:hover {
background: #f1f5f9;
color: $text-dark;
}
}
}
.vci-notice {
padding: 12px 16px;
border-left: 4px solid $warning;
background: #fffbeb;
border-radius: 6px;
font-size: 13px;
line-height: 1.5;
color: #92400e;
}
// ── Detail sections ──────────────────────────────────────────────────────────
.vci-section {
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
&__title {
padding: 10px 16px;
background: $bg-page;
border-bottom: 1px solid $border;
font-size: 11px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.07em;
color: $text-mid;
}
}
/* One label above its value, two pairs per row where there is room. This replaces the global
flex-40/flex-60 utilities, which are sized for a full-width page and ran every field together
onto a single line inside the dialog. */
.vci-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 14px 24px;
margin: 0;
font-weight: 500;
padding: 16px;
}
span {
margin: 0.5em 0;
.vci-kv {
display: flex;
flex-direction: column;
gap: 3px;
min-width: 0;
dt {
font-size: 10.5px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: $text-soft;
}
dd {
margin: 0;
font-size: 14px;
font-weight: 500;
color: $text-dark;
line-height: 1.35;
overflow-wrap: anywhere;
}
}
.margin-t {
margin-top: 1em;
}
.vci-tag {
display: inline-flex;
align-items: center;
padding: 2px 10px;
border-radius: 20px;
background: #e0f2fe;
color: #0369a1;
font-size: 11.5px;
font-weight: 700;
letter-spacing: 0.04em;
mat-divider {
margin: 0 0 0.5em;
}
.mat-raised-button.mat-success {
color: #fff;
background-color: #008000ab;
}
.mat-raised-button.mat-reject {
color: #fff;
background-color: #ffca00;
&--danger {
background: #fee2e2;
color: #991b1b;
}
}
@@ -10,7 +10,7 @@
import { Component, inject } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import * as _ from 'lodash';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
/** Custom Services */
import { TasksService } from '../tasks.service';
@@ -19,7 +19,6 @@ import { TasksService } from '../tasks.service';
import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/confirmation-dialog.component';
import { TranslateService } from '@ngx-translate/core';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { MatDivider } from '@angular/material/divider';
import { KeyValuePipe } from '@angular/common';
import { DateFormatPipe } from '../../pipes/date-format.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -31,10 +30,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
imports: [
...STANDALONE_SHARED_IMPORTS,
FaIconComponent,
MatDivider,
KeyValuePipe,
DateFormatPipe,
MatDialogModule
DateFormatPipe
]
})
export class ViewCheckerInboxComponent {