diff --git a/.commit b/.commit index 1039435b5..0865abed9 100644 --- a/.commit +++ b/.commit @@ -1 +1 @@ -b32f3a9288588262ff652ef1fe639551d0cce92a +cd633f4378e2f09003301521206faa759b7dff9f diff --git a/.sync-history b/.sync-history index 46b50e5b8..4366e364e 100644 --- a/.sync-history +++ b/.sync-history @@ -1,2 +1 @@ -8a5d78f15 2026-07-13 Merged PR 93367: 713184 - Web Portal: Optimize request history loading, optimize Pending Requests loading #713184 -793ca3335 2026-07-08 Merged PR 94402: #724361 - ATT: Fix attestation grouping crash on undefined groupedData \ No newline at end of file +cd633f437 2026-07-21 Merged PR 94742: #713400 - QER/ATT/CPL/POL: remove shared dashboard busy-state coupling in por... \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a0fdd31a8..c66c89c59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +### July 22, 2026 +- 713400: Optimalized Web Portal Dashboard component widgets coupling + ### July 13, 2026 - 713184: Optimizing request history and pending request loading (Need API update). - 724361: Fixing pending attestation grouping. diff --git a/imxweb/imx-modules/imx-api-qer.tgz b/imxweb/imx-modules/imx-api-qer.tgz index c920ead9e..f2d87ebfa 100644 Binary files a/imxweb/imx-modules/imx-api-qer.tgz and b/imxweb/imx-modules/imx-api-qer.tgz differ diff --git a/imxweb/projects/att/src/lib/dashboard-plugin/dashboard-plugin.component.ts b/imxweb/projects/att/src/lib/dashboard-plugin/dashboard-plugin.component.ts index eac2a020d..f978d1568 100644 --- a/imxweb/projects/att/src/lib/dashboard-plugin/dashboard-plugin.component.ts +++ b/imxweb/projects/att/src/lib/dashboard-plugin/dashboard-plugin.component.ts @@ -27,7 +27,7 @@ import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; -import { DashboardService, PendingItemsType, UserModelService } from 'qer'; +import { PendingItemsType, UserModelService } from 'qer'; import { AttestationFeatureGuardService } from '../attestation-feature-guard.service'; @Component({ @@ -40,24 +40,16 @@ export class DashboardPluginComponent implements OnInit { constructor( public readonly router: Router, - private readonly dashboardSvc: DashboardService, private readonly userModelSvc: UserModelService, private readonly attFeatureGuard: AttestationFeatureGuardService ) { } public async ngOnInit(): Promise { - - const busy = this.dashboardSvc.beginBusy(); - - try { - this.pendingItems = await this.userModelSvc.getPendingItems(); - this.attEnabled = (await this.attFeatureGuard.getAttestationConfig()).IsAttestationEnabled; - } finally { - busy.endBusy(); - } + this.pendingItems = await this.userModelSvc.getPendingItems(); + this.attEnabled = (await this.attFeatureGuard.getAttestationConfig()).IsAttestationEnabled; } public goToAttestationInquiries(): void { - this.router.navigate(['attestation', 'decision'], {queryParams: {inquiries:true}}); + this.router.navigate(['attestation', 'decision'], { queryParams: { inquiries: true } }); } } diff --git a/imxweb/projects/cpl/src/lib/dashboard-plugin/dashboard-plugin.component.ts b/imxweb/projects/cpl/src/lib/dashboard-plugin/dashboard-plugin.component.ts index 9610327ad..be67139a3 100644 --- a/imxweb/projects/cpl/src/lib/dashboard-plugin/dashboard-plugin.component.ts +++ b/imxweb/projects/cpl/src/lib/dashboard-plugin/dashboard-plugin.component.ts @@ -27,7 +27,7 @@ import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; -import { DashboardService, PendingItemsType, UserModelService } from 'qer'; +import { PendingItemsType, UserModelService } from 'qer'; import { CplPermissionsService } from '../rules/admin/cpl-permissions.service'; @Component({ @@ -41,22 +41,14 @@ export class DashboardPluginComponent implements OnInit { constructor( public readonly router: Router, - private readonly dashboardService: DashboardService, private readonly permissionService: CplPermissionsService, private readonly userModelSvc: UserModelService ) { } public async ngOnInit(): Promise { - - const busy = this.dashboardService.beginBusy(); - - try { - this.isExceptionAdmin = await this.permissionService.isExceptionAdmin(); - if (this.isExceptionAdmin) { - this.pendingItems = await this.userModelSvc.getPendingItems(); - } - } finally { - busy.endBusy(); + this.isExceptionAdmin = await this.permissionService.isExceptionAdmin(); + if (this.isExceptionAdmin) { + this.pendingItems = await this.userModelSvc.getPendingItems(); } } } diff --git a/imxweb/projects/pol/src/lib/dashboard-plugin/dashboard-plugin.component.ts b/imxweb/projects/pol/src/lib/dashboard-plugin/dashboard-plugin.component.ts index e773b2292..788cd74df 100644 --- a/imxweb/projects/pol/src/lib/dashboard-plugin/dashboard-plugin.component.ts +++ b/imxweb/projects/pol/src/lib/dashboard-plugin/dashboard-plugin.component.ts @@ -27,7 +27,7 @@ import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; -import { DashboardService, PendingItemsType, UserModelService } from 'qer'; +import { PendingItemsType, UserModelService } from 'qer'; @Component({ templateUrl: './dashboard-plugin.component.html' @@ -38,18 +38,10 @@ export class DashboardPluginComponent implements OnInit { constructor( public readonly router: Router, - private readonly dashboardService: DashboardService, private readonly userModelSvc: UserModelService ) { } public async ngOnInit(): Promise { - - const busy = this.dashboardService.beginBusy(); - - try { - this.pendingItems = await this.userModelSvc.getPendingItems(); - } finally { - busy.endBusy(); - } + this.pendingItems = await this.userModelSvc.getPendingItems(); } } diff --git a/imxweb/projects/qer/src/lib/team-responsibilities/team-responsibilities.service.ts b/imxweb/projects/qer/src/lib/team-responsibilities/team-responsibilities.service.ts index bcd6dd532..3f6ff0686 100644 --- a/imxweb/projects/qer/src/lib/team-responsibilities/team-responsibilities.service.ts +++ b/imxweb/projects/qer/src/lib/team-responsibilities/team-responsibilities.service.ts @@ -28,8 +28,8 @@ import { OverlayRef } from '@angular/cdk/overlay'; import { Injectable } from '@angular/core'; import { QerApiService } from '../qer-api-client.service'; import { EuiLoadingService } from '@elemental-ui/core'; -import { CollectionLoadParameters, DataModel, DisplayColumns, EntitySchema, ExtendedTypedEntityCollection, TypedEntityBuilder, ValType } from 'imx-qbm-dbts'; -import { PortalDevices, PortalRespTeamResponsibilities, ResponsibilitiesExtendedData } from 'imx-api-qer'; +import { CollectionLoadParameters, DataModel, EntitySchema, ExtendedTypedEntityCollection, MethodDescriptor, TimeZoneInfo } from 'imx-qbm-dbts'; +import { PortalRespTeamResponsibilities, ResponsibilitiesExtendedData } from 'imx-api-qer'; @Injectable({ providedIn: 'root', @@ -40,7 +40,7 @@ export class TeamResponsibilitiesService { constructor( private readonly qerClient: QerApiService, private readonly busyService: EuiLoadingService - ) {} + ) { } public get responsibilitySchema(): EntitySchema { return this.qerClient.typedClient.PortalRespTeamResponsibilities.GetSchema(); @@ -53,7 +53,7 @@ export class TeamResponsibilitiesService { } public handleCloseLoader(): void { - if(this.busyIndicator) { + if (this.busyIndicator) { setTimeout(() => { this.busyService.hide(this.busyIndicator); this.busyIndicator = undefined; @@ -69,8 +69,21 @@ export class TeamResponsibilitiesService { return this.qerClient.client.portal_resp_team_responsibilities_datamodel_get(); } - public async countInactiveIdentity(): Promise{ - const responsibilities = await this.get({forinactive: '1', PageSize: -1}) - return responsibilities.totalCount; + public async countInactiveIdentity(): Promise { + return this.qerClient.apiClient.processRequest(this.getInactiveResponsibilitiesCountDescriptor()); + } + + private getInactiveResponsibilitiesCountDescriptor(): MethodDescriptor { + return { + path: '/portal/resp/team/responsibilities/count', + method: 'GET', + parameters: [], + headers: { + 'imx-timezone': TimeZoneInfo.get() + }, + credentials: 'include', + observe: 'response', + responseType: 'json', + }; } } diff --git a/imxweb/projects/qer/src/lib/team-responsibilities/team-responsibility-tile/team-responsibility-tile.component.ts b/imxweb/projects/qer/src/lib/team-responsibilities/team-responsibility-tile/team-responsibility-tile.component.ts index ab28a6a37..4f23afa37 100644 --- a/imxweb/projects/qer/src/lib/team-responsibilities/team-responsibility-tile/team-responsibility-tile.component.ts +++ b/imxweb/projects/qer/src/lib/team-responsibilities/team-responsibility-tile/team-responsibility-tile.component.ts @@ -28,7 +28,6 @@ import { Component, OnInit } from '@angular/core'; import { TeamResponsibilitiesService } from '../team-responsibilities.service'; import { Router } from '@angular/router'; import { QerPermissionsService } from '../../admin/qer-permissions.service'; -import { DashboardService } from '../../wport/start/dashboard.service'; @Component({ selector: 'imx-team-responsibility-tile', @@ -36,22 +35,17 @@ import { DashboardService } from '../../wport/start/dashboard.service'; }) export class TeamResponsibilityTileComponent implements OnInit { public inactiveResponsibilitiesCount: number; + constructor( private readonly teamResponsibilitiesService: TeamResponsibilitiesService, public readonly router: Router, - private readonly qerPermissionsService: QerPermissionsService, - private readonly dashboardService: DashboardService - ) {} + private readonly qerPermissionsService: QerPermissionsService + ) { } async ngOnInit(): Promise { - const busy = this.dashboardService.beginBusy(); - try{ - const permission = await this.qerPermissionsService.isPersonManager(); - if(permission){ - this.inactiveResponsibilitiesCount = await this.teamResponsibilitiesService.countInactiveIdentity(); - } - }finally{ - busy.endBusy(); + const permission = await this.qerPermissionsService.isPersonManager(); + if (permission) { + this.inactiveResponsibilitiesCount = await this.teamResponsibilitiesService.countInactiveIdentity(); } } } diff --git a/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.ts b/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.ts index ecb9bbac3..64ab67cdb 100644 --- a/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.ts +++ b/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.ts @@ -35,7 +35,6 @@ import { IdentitySidesheetComponent } from '../../identities/identity-sidesheet/ import { ProjectConfigurationService } from '../../project-configuration/project-configuration.service'; import { QerApiService } from '../../qer-api-client.service'; import { UserModelService } from '../../user/user-model.service'; -import { DashboardService } from '../start/dashboard.service'; import { CreateNewIdentityComponent } from '../../identities/create-new-identity/create-new-identity.component'; import { IdentitiesService } from '../../identities/identities.service'; @@ -47,9 +46,7 @@ import { IdentitiesService } from '../../identities/identities.service'; export class BusinessOwnerChartSummaryComponent implements OnInit { public reports: PortalPersonReports[]; public ownerships: OwnershipInformation[]; - public get viewReady(): boolean { - return !this.dashboardService.isBusy; - } + public viewReady = false; public allReportsCount: number; private projectConfig: ProjectConfig; @@ -58,7 +55,6 @@ export class BusinessOwnerChartSummaryComponent implements OnInit { private readonly router: Router, private readonly qerClient: QerApiService, private readonly busyService: EuiLoadingService, - private readonly dashboardService: DashboardService, private readonly sideSheet: EuiSidesheetService, private readonly errorHandler: ErrorHandler, private readonly configService: ProjectConfigurationService, @@ -66,10 +62,10 @@ export class BusinessOwnerChartSummaryComponent implements OnInit { private readonly userModelService: UserModelService, public readonly qerPermissions: QerPermissionsService, public readonly translate: TranslateService - ) {} + ) { } public async ngOnInit(): Promise { - const busy = this.dashboardService.beginBusy(); + this.viewReady = false; try { const userConfig = await this.userModelService.getUserConfig(); this.ownerships = userConfig.Ownerships; @@ -78,7 +74,7 @@ export class BusinessOwnerChartSummaryComponent implements OnInit { await this.getData(); } finally { - busy.endBusy(); + this.viewReady = true; } } @@ -127,7 +123,7 @@ export class BusinessOwnerChartSummaryComponent implements OnInit { } public async openCreateNewIdentitySidesheet(): Promise { - const identityCreated = await this.sideSheet.open(CreateNewIdentityComponent, { + const identityCreated = await this.sideSheet.open(CreateNewIdentityComponent, { title: await this.translate.get('#LDS#Heading Create Identity').toPromise(), headerColour: 'iris-blue', padding: '0px', @@ -142,13 +138,14 @@ export class BusinessOwnerChartSummaryComponent implements OnInit { }).afterClosed().toPromise(); if (identityCreated) { - const busy = this.dashboardService.beginBusy(); + this.viewReady = false; try { await this.getData(); } finally { - busy.endBusy(); + this.viewReady = true; } - } } + } + } public openOwnership(ownerShip: OwnershipInformation): void { this.router.navigate(['myresponsibilities', ownerShip.TableName]); @@ -156,7 +153,7 @@ export class BusinessOwnerChartSummaryComponent implements OnInit { private async getData(): Promise { await this.loadIndirectOrDirectReports(); - if (this.allReportsCount > 0 ) { + if (this.allReportsCount > 0) { await this.loadDirectReports(); } } diff --git a/imxweb/projects/qer/src/lib/wport/start/dashboard.service.ts b/imxweb/projects/qer/src/lib/wport/start/dashboard.service.ts index 6bd4a5946..adcdb19c6 100644 --- a/imxweb/projects/qer/src/lib/wport/start/dashboard.service.ts +++ b/imxweb/projects/qer/src/lib/wport/start/dashboard.service.ts @@ -27,6 +27,7 @@ import { Injectable } from "@angular/core"; import { BusyService } from "qbm"; +/** @deprecated Use per-widget loading states instead of shared dashboard busy-state coupling. */ @Injectable({ providedIn: 'root' }) export class DashboardService extends BusyService { diff --git a/imxweb/projects/qer/src/lib/wport/start/start.component.html b/imxweb/projects/qer/src/lib/wport/start/start.component.html index b255a35cb..4d97a45cb 100644 --- a/imxweb/projects/qer/src/lib/wport/start/start.component.html +++ b/imxweb/projects/qer/src/lib/wport/start/start.component.html @@ -2,143 +2,110 @@

{{ '#LDS#Welcome' | translate }}

- -
- +
+ + [value]="GetCountProductsinShoppingCart()" [identifier]="'shopping-cart'" (actionClick)="GoToShoppingCart()"> - + *ngIf="userConfig?.IsITShopEnabled && GetCountPendingRequests() > 0" [value]="GetCountPendingRequests()" + [identifier]="'it-shop-approvals'" (actionClick)="GoToItshopApprovals()"> - + (actionClick)="router.navigate(['requesthistory'], { queryParams: { ShowMyPending: 1 } })"> - + *ngIf="userConfig?.IsITShopEnabled && GetCountRequestInquiries() > 0" [value]="GetCountRequestInquiries()" + [identifier]="'it-shop-approval-inquiries'" (actionClick)="GoToItShopApprovalInquiries()"> - - - + + + [value]="userConfig?.CountRequestsEndingSoon" [identifier]="'requests ending soon'" + (actionClick)="router.navigate(['requesthistory'], { queryParams: { ShowEndingSoon: 1 } })"> - +
-
- + + " [identifier]="'no-password-query-and-answer-set'"> - - + - + [identifier]="'password-manager-link'" [image]="'key'"> - - + [identifier]="'reset-password-web-link'" [image]="'key'"> - - + [subtitle]="'#LDS#Request products for yourself or for others.' | translate" [image]="'cart'" + [identifier]="'start-a-new-request'" *ngIf="viewReady && ShowNewRequestLink()"> - - +
-
+
diff --git a/imxweb/projects/qer/src/lib/wport/start/start.component.ts b/imxweb/projects/qer/src/lib/wport/start/start.component.ts index ca85c4a2a..6a0e144e6 100644 --- a/imxweb/projects/qer/src/lib/wport/start/start.component.ts +++ b/imxweb/projects/qer/src/lib/wport/start/start.component.ts @@ -24,7 +24,7 @@ * */ -import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { Component, NgZone, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { SystemInfo } from 'imx-api-qbm'; @@ -33,7 +33,6 @@ import { imx_SessionService, SplashService, SystemInfoService } from 'qbm'; import { ProjectConfigurationService } from '../../project-configuration/project-configuration.service'; import { PendingItemsType } from '../../user/pending-items-type.interface'; import { UserModelService } from '../../user/user-model.service'; -import { DashboardService } from './dashboard.service'; @Component({ templateUrl: './start.component.html', @@ -45,31 +44,23 @@ export class StartComponent implements OnInit { public projectConfig: QerProjectConfig & ProjectConfig; public pendingItems: PendingItemsType; public systemInfo: SystemInfo; - public viewReady: boolean; + public viewReady = false; + public renderDeferredDashboard = false; public userUid: string; constructor( public readonly router: Router, - private readonly dashboardService: DashboardService, private readonly userModelSvc: UserModelService, private readonly systemInfoService: SystemInfoService, private readonly sessionService: imx_SessionService, - private readonly detectRef: ChangeDetectorRef, private readonly projectConfigurationService: ProjectConfigurationService, private readonly splash: SplashService, + private readonly ngZone: NgZone, ) { } public async ngOnInit(): Promise { - this.dashboardService.busyStateChanged.subscribe((busy) => { - this.viewReady = !busy; - this.detectRef.detectChanges(); - }); - const busy = this.dashboardService.beginBusy(); try { - // The following calls are independent of each other and can be issued in parallel. - // This is important on installations where individual REST calls may be slow - // (e.g. read-only secondary SQL databases): with Promise.all, the splash-screen - // wall-clock time is bounded by the slowest single call instead of the sum. + // Core startup data for first page interactivity. const [userConfig, pendingItems, projectConfig, systemInfo, sessionState] = await Promise.all([ this.userModelSvc.getUserConfig(), this.userModelSvc.getPendingItems(), @@ -83,8 +74,12 @@ export class StartComponent implements OnInit { this.systemInfo = systemInfo; this.userUid = sessionState.UserUid; } finally { + this.viewReady = true; + // Defer dashboard widget instantiation until after the first render. + requestAnimationFrame(() => this.ngZone.run(() => { + this.renderDeferredDashboard = true; + })); this.splash.close(); - busy.endBusy(); } }