diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.html b/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.html index 2a1f02ef2..cad6af83e 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.html +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.html @@ -155,12 +155,7 @@ {{ makerChecker.entityName }} - + } diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.ts b/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.ts index 6800f4e61..7ed20e06d 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.ts +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.ts @@ -33,6 +33,7 @@ import { SettingsService } from 'app/settings/settings.service'; /** Dialog Components */ import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/confirmation-dialog.component'; +import { ViewCheckerInboxComponent } from '../../view-checker-inbox/view-checker-inbox.component'; import { Dates } from 'app/core/utils/dates'; import { TranslateService } from '@ngx-translate/core'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; @@ -288,6 +289,27 @@ export class CheckerInboxComponent implements OnInit { this.dataSource.filter = filterValue.trim().toLowerCase(); } + /** + * Opens one inbox entry in a popup rather than navigating to its own page, so the checker keeps + * the list (and any search/filter they set up) behind the dialog. The direct route stays available + * for a bookmarked link — ViewCheckerInboxComponent supports both entry points. + */ + viewEntry(row: any): void { + this.dialog + .open(ViewCheckerInboxComponent, { + data: { checkerId: row.id }, + autoFocus: false + }) + .afterClosed() + .subscribe((result: any) => { + // Only reload when something was actually actioned — a plain close must not throw away the + // checker's current search, which reload() does by re-navigating the route. + if (result?.refreshed) { + this.reload(); + } + }); + } + /** * Refetches data for the component * TODO: Replace by a custom reload component instead of hard-coded back-routing. diff --git a/src/app/tasks/view-checker-inbox/view-checker-inbox.component.html b/src/app/tasks/view-checker-inbox/view-checker-inbox.component.html index 3ed785f19..ee89646b8 100644 --- a/src/app/tasks/view-checker-inbox/view-checker-inbox.component.html +++ b/src/app/tasks/view-checker-inbox/view-checker-inbox.component.html @@ -6,108 +6,125 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -@if (isProductEditEntry) { -
-
- This is a product edit awaiting approval. Approve or reject it from - Approvals > Pending Product Edits instead — actioning it here would not release its lock. +
+ @if (isDialog) { +
+ {{ 'labels.heading.View Checker Inbox' | translate }} + ✕ {{ 'labels.buttons.Close' | translate }}
-
-} @else { -
- - - -
-} + } -
- - -
-

{{ 'labels.heading.View Checker Inbox' | translate }}

+ + @if (loading) { +
{{ 'labels.heading.Loading' | translate }}…
+ } - - -

{{ 'labels.heading.Details' | translate }}

- - - -
- {{ 'labels.inputs.ID' | translate }}: - {{ checkerInboxDetail.id }} + @if (checkerInboxDetail) { + @if (isProductEditEntry) { +
+
+ This is a product edit awaiting approval. Approve or reject it from + Approvals > Pending Product Edits instead — actioning it here would not release its lock.
- -
- {{ 'labels.inputs.Status' | translate }}: - {{ checkerInboxDetail.processingResult }} -
- -
- {{ 'labels.inputs.User' | translate }}: - {{ checkerInboxDetail.maker }} -
- -
- {{ 'labels.inputs.Action' | translate }}: - {{ checkerInboxDetail.actionName }} -
- -
- {{ 'labels.inputs.Entity' | translate }}: - {{ checkerInboxDetail.entityName }} -
- - @if (checkerInboxDetail.resourceId) { -
- {{ 'labels.inputs.Resource ID' | translate }}: - {{ checkerInboxDetail.resourceId }} -
- } - -
- {{ 'labels.inputs.Date' | translate }}: - {{ checkerInboxDetail.madeOnDate | dateFormat }} -
- - @if (checkerInboxDetail.officeName) { -
- {{ 'labels.inputs.Office' | translate }}: - {{ checkerInboxDetail.officeName }} -
- } - - @if (checkerInboxDetail.savingsAccountNo) { -
- {{ 'labels.inputs.Savings Account Number' | translate }}: - {{ checkerInboxDetail.savingsAccountNo }} -
- } - - @if (checkerInboxDetail.groupLevelName) { -
- {{ checkerInboxDetail.groupLevelName }} - {{ checkerInboxDetail.groupName }} -
- } - - @if (displayJSONData) { -

{{ 'labels.heading.Command' | translate }}

- - @for (json of jsondata | keyvalue; track json) { -
- {{ json.key }}: - {{ json.value }} -
- } - }
- - + } @else { +
+ + + +
+ } + +
+ + +
+

{{ 'labels.heading.View Checker Inbox' | translate }}

+ + + +

{{ 'labels.heading.Details' | translate }}

+ + + +
+ {{ 'labels.inputs.ID' | translate }}: + {{ checkerInboxDetail.id }} +
+ +
+ {{ 'labels.inputs.Status' | translate }}: + {{ checkerInboxDetail.processingResult }} +
+ +
+ {{ 'labels.inputs.User' | translate }}: + {{ checkerInboxDetail.maker }} +
+ +
+ {{ 'labels.inputs.Action' | translate }}: + {{ checkerInboxDetail.actionName }} +
+ +
+ {{ 'labels.inputs.Entity' | translate }}: + {{ checkerInboxDetail.entityName }} +
+ + @if (checkerInboxDetail.resourceId) { +
+ {{ 'labels.inputs.Resource ID' | translate }}: + {{ checkerInboxDetail.resourceId }} +
+ } + +
+ {{ 'labels.inputs.Date' | translate }}: + {{ checkerInboxDetail.madeOnDate | dateFormat }} +
+ + @if (checkerInboxDetail.officeName) { +
+ {{ 'labels.inputs.Office' | translate }}: + {{ checkerInboxDetail.officeName }} +
+ } + + @if (checkerInboxDetail.savingsAccountNo) { +
+ {{ 'labels.inputs.Savings Account Number' | translate }}: + {{ checkerInboxDetail.savingsAccountNo }} +
+ } + + @if (checkerInboxDetail.groupLevelName) { +
+ {{ checkerInboxDetail.groupLevelName }} + {{ checkerInboxDetail.groupName }} +
+ } + + @if (displayJSONData) { +

{{ 'labels.heading.Command' | translate }}

+ + @for (json of jsondata | keyvalue; track json) { +
+ {{ json.key }}: + {{ json.value }} +
+ } + } +
+
+
+
+ }
diff --git a/src/app/tasks/view-checker-inbox/view-checker-inbox.component.scss b/src/app/tasks/view-checker-inbox/view-checker-inbox.component.scss index 3d1ba1746..5bf7407cd 100644 --- a/src/app/tasks/view-checker-inbox/view-checker-inbox.component.scss +++ b/src/app/tasks/view-checker-inbox/view-checker-inbox.component.scss @@ -10,6 +10,60 @@ margin: 1em 0 1.5em; } +/* 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; + + .container { + max-width: none; + padding: 0; + } +} + +.vci-dialog-bar { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 16px; + background: #fff; + border-bottom: 1px solid #e2e8f0; + position: sticky; + top: 0; + z-index: 1; + + &__title { + font-size: 14px; + font-weight: 700; + color: #475569; + } + + &__close { + margin-left: auto; + font-size: 12px; + font-weight: 500; + color: #94a3b8; + cursor: pointer; + padding: 6px 12px; + border-radius: 6px; + transition: all 0.15s; + + &:hover { + background: #f1f5f9; + color: #475569; + } + } +} + +.vci-loading { + padding: 48px 24px; + text-align: center; + font-size: 14px; + color: #94a3b8; +} + h2, h3, h4 { diff --git a/src/app/tasks/view-checker-inbox/view-checker-inbox.component.ts b/src/app/tasks/view-checker-inbox/view-checker-inbox.component.ts index 5bef1b3e1..967a0e661 100644 --- a/src/app/tasks/view-checker-inbox/view-checker-inbox.component.ts +++ b/src/app/tasks/view-checker-inbox/view-checker-inbox.component.ts @@ -10,7 +10,7 @@ import { Component, inject } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import * as _ from 'lodash'; -import { MatDialog } from '@angular/material/dialog'; +import { MatDialog, MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; /** Custom Services */ import { TasksService } from '../tasks.service'; @@ -33,7 +33,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; FaIconComponent, MatDivider, KeyValuePipe, - DateFormatPipe + DateFormatPipe, + MatDialogModule ] }) export class ViewCheckerInboxComponent { @@ -42,6 +43,17 @@ export class ViewCheckerInboxComponent { private router = inject(Router); private translateService = inject(TranslateService); private tasksService = inject(TasksService); + /** Present only when opened as a popup from the Checker Inbox list; absent when reached by direct + * route (a bookmarked or shared link). Both entry points stay supported — this only changes where + * the detail comes from and how "done" is signalled: closing the dialog vs. navigating back. */ + private dialogRef = inject(MatDialogRef, { optional: true }); + private dialogData = inject<{ checkerId: number | string } | null>(MAT_DIALOG_DATA, { optional: true }); + + loading = false; + + get isDialog(): boolean { + return !!this.dialogRef; + } /** Checker Inbox Details Data */ checkerInboxDetail: any; @@ -79,13 +91,51 @@ export class ViewCheckerInboxComponent { * @param {TasksService} tasksService Tasks Service. */ constructor() { + // As a popup the list only has summary rows, so the detail (which carries commandAsJson) is + // fetched here; as a route it is already resolved by GetCheckerInboxDetailResolver. + if (this.dialogData?.checkerId != null) { + this.loading = true; + this.tasksService.getCheckerInboxDetail(this.dialogData.checkerId).subscribe((detail: any) => { + this.loading = false; + this.applyDetail(detail); + }); + return; + } this.route.data.subscribe((data: { checkerInboxDetail: any }) => { - this.checkerInboxDetail = data.checkerInboxDetail; - this.jsondata = JSON.parse(this.checkerInboxDetail.commandAsJson); - this.displayJSONData = !_.isEmpty(this.jsondata); + this.applyDetail(data.checkerInboxDetail); }); } + private applyDetail(detail: any): void { + this.checkerInboxDetail = detail; + // commandAsJson is absent on some entries (a DELETE carries no payload), and JSON.parse(undefined) + // throws — which as a route only broke the page, but in a dialog would leave an empty shell open. + try { + this.jsondata = detail?.commandAsJson ? JSON.parse(detail.commandAsJson) : {}; + } catch { + this.jsondata = {}; + } + this.displayJSONData = !_.isEmpty(this.jsondata); + } + + /** "Done" for this screen — closes the popup (telling the list to refresh) when opened as a + * dialog, or returns to the inbox route otherwise. */ + private finish(): void { + if (this.dialogRef) { + this.dialogRef.close({ refreshed: true }); + } else { + this.router.navigate(['../../'], { relativeTo: this.route }); + } + } + + close(): void { + if (this.dialogRef) { + this.dialogRef.close(); + } else { + this.router.navigate(['../../'], { relativeTo: this.route }); + } + } + /** * Approve Checker */ @@ -99,7 +149,7 @@ export class ViewCheckerInboxComponent { approveCheckerDialogRef.afterClosed().subscribe((response: { confirm: any }) => { if (response.confirm) { this.tasksService.executeMakerCheckerAction(this.checkerInboxDetail.id, 'approve').subscribe((res: any) => { - this.router.navigate(['../../'], { relativeTo: this.route }); + this.finish(); }); } }); @@ -118,7 +168,7 @@ export class ViewCheckerInboxComponent { rejectCheckerDialogRef.afterClosed().subscribe((response: { confirm: any }) => { if (response.confirm) { this.tasksService.executeMakerCheckerAction(this.checkerInboxDetail.id, 'reject').subscribe((res: any) => { - this.router.navigate(['../../'], { relativeTo: this.route }); + this.finish(); }); } }); @@ -137,7 +187,7 @@ export class ViewCheckerInboxComponent { deleteCheckerDialogRef.afterClosed().subscribe((response: { confirm: any }) => { if (response.confirm) { this.tasksService.deleteMakerChecker(this.checkerInboxDetail.id).subscribe((res: any) => { - this.router.navigate(['../../'], { relativeTo: this.route }); + this.finish(); }); } });