fix(tasks): lay the checker inbox popup out properly
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:
@@ -6,125 +6,121 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div [class.vci--dialog]="isDialog">
|
||||
<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) {
|
||||
<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>
|
||||
<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) {
|
||||
<div class="vci-body">
|
||||
@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-notice">
|
||||
This is a product edit awaiting approval. Approve or reject it from
|
||||
<strong>Approvals > 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 }}
|
||||
<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 mat-raised-button color="warn" (click)="deleteChecker()">
|
||||
<fa-icon icon="trash" class="m-r-10"></fa-icon>{{ 'labels.buttons.Delete' | translate }}
|
||||
<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 mat-raised-button color="reject" (click)="rejectChecker()">
|
||||
<fa-icon icon="times" class="m-r-10"></fa-icon>{{ 'labels.buttons.Reject' | translate }}
|
||||
<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 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>
|
||||
|
||||
<div class="flex-fill">
|
||||
<span class="flex-40">{{ 'labels.inputs.Status' | translate }}:</span>
|
||||
<span class="flex-60">{{ checkerInboxDetail.processingResult }}</span>
|
||||
<div class="vci-kv">
|
||||
<dt>{{ 'labels.inputs.Status' | translate }}</dt>
|
||||
<dd>{{ checkerInboxDetail.processingResult || '—' }}</dd>
|
||||
</div>
|
||||
|
||||
<div class="flex-fill">
|
||||
<span class="flex-40">{{ 'labels.inputs.User' | translate }}:</span>
|
||||
<span class="flex-60">{{ checkerInboxDetail.maker }}</span>
|
||||
<div class="vci-kv">
|
||||
<dt>{{ 'labels.inputs.User' | translate }}</dt>
|
||||
<dd>{{ checkerInboxDetail.maker || '—' }}</dd>
|
||||
</div>
|
||||
|
||||
<div class="flex-fill">
|
||||
<span class="flex-40">{{ 'labels.inputs.Action' | translate }}:</span>
|
||||
<span class="flex-60">{{ checkerInboxDetail.actionName }}</span>
|
||||
<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="flex-fill">
|
||||
<span class="flex-40">{{ 'labels.inputs.Entity' | translate }}:</span>
|
||||
<span class="flex-60">{{ checkerInboxDetail.entityName }}</span>
|
||||
<div class="vci-kv">
|
||||
<dt>{{ 'labels.inputs.Entity' | translate }}</dt>
|
||||
<dd>{{ checkerInboxDetail.entityName }}</dd>
|
||||
</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 class="vci-kv">
|
||||
<dt>{{ 'labels.inputs.Resource ID' | translate }}</dt>
|
||||
<dd>{{ checkerInboxDetail.resourceId }}</dd>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="flex-fill">
|
||||
<span class="flex-40">{{ 'labels.inputs.Date' | translate }}:</span>
|
||||
<span class="flex-60">{{ checkerInboxDetail.madeOnDate | dateFormat }}</span>
|
||||
<div class="vci-kv">
|
||||
<dt>{{ 'labels.inputs.Date' | translate }}</dt>
|
||||
<dd>{{ checkerInboxDetail.madeOnDate | dateFormat }}</dd>
|
||||
</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 class="vci-kv">
|
||||
<dt>{{ 'labels.inputs.Office' | translate }}</dt>
|
||||
<dd>{{ checkerInboxDetail.officeName }}</dd>
|
||||
</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 class="vci-kv">
|
||||
<dt>{{ 'labels.inputs.Savings Account Number' | translate }}</dt>
|
||||
<dd>{{ checkerInboxDetail.savingsAccountNo }}</dd>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (checkerInboxDetail.groupLevelName) {
|
||||
<div class="flex-fill">
|
||||
<span class="flex-40"> {{ checkerInboxDetail.groupLevelName }}</span>
|
||||
<span class="flex-60">{{ checkerInboxDetail.groupName }}</span>
|
||||
<div class="vci-kv">
|
||||
<dt>{{ checkerInboxDetail.groupLevelName }}</dt>
|
||||
<dd>{{ checkerInboxDetail.groupName }}</dd>
|
||||
</div>
|
||||
}
|
||||
</dl>
|
||||
</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 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>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</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);
|
||||
.vci {
|
||||
background: #fff;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: none;
|
||||
padding: 0;
|
||||
&: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;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.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;
|
||||
|
||||
.margin-t {
|
||||
margin-top: 1em;
|
||||
&--danger {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user