From 55648817e395e333641d28be106e8a7ddb00dbf3 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Tue, 7 Jul 2026 11:53:03 +0200 Subject: [PATCH] Added show of warning toast when switching dashboard states reveals overlapping widgets in edit mode --- .../core/services/dashboard-utils.service.ts | 22 ++++++++++++++++++- .../dashboard-page.component.ts | 13 +++++++++++ .../assets/locale/locale.constant-en_US.json | 1 + 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/core/services/dashboard-utils.service.ts b/ui-ngx/src/app/core/services/dashboard-utils.service.ts index b185c77c2d..285078ba5b 100644 --- a/ui-ngx/src/app/core/services/dashboard-utils.service.ts +++ b/ui-ngx/src/app/core/services/dashboard-utils.service.ts @@ -33,7 +33,8 @@ import { DashboardStateLayouts, GridSettings, LayoutType, - WidgetLayout + WidgetLayout, + WidgetLayouts } from '@shared/models/dashboard.models'; import { deepClone, isDefined, isDefinedAndNotNull, isNotEmptyStr, isString, isUndefined } from '@core/utils'; import { @@ -772,6 +773,25 @@ export class DashboardUtilsService { return false; } + public hasCollidingWidgets(widgetLayouts: WidgetLayouts): boolean { + if (!widgetLayouts) { + return false; + } + const layouts = Object.values(widgetLayouts).map(widget => ({ + row: widget.row || 0, + col: widget.col || 0, + sizeX: widget.sizeX || 1, + sizeY: widget.sizeY || 1 + })); + for (let i = 0; i < layouts.length; i++) { + const widget = layouts[i]; + if (this.hasWidgetCollision(widget.row, widget.col, widget.sizeX, widget.sizeY, layouts.slice(i + 1))) { + return true; + } + } + return false; + } + public removeWidgetFromLayout(dashboard: Dashboard, targetState: string, targetLayout: DashboardLayoutId, diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index 5875afe1a1..e674a4f9f2 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -43,6 +43,7 @@ import { UtilsService } from '@core/services/utils.service'; import { BreakpointId, BreakpointInfo, + BreakpointLayoutInfo, Dashboard, DashboardConfiguration, DashboardLayoutId, @@ -149,6 +150,7 @@ import { MoveWidgetsDialogResult } from '@home/components/dashboard-page/layout/move-widgets-dialog.component'; import { HttpStatusCode } from '@angular/common/http'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; // @dynamic @Component({ @@ -1106,6 +1108,17 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC if (!this.destroyed) { const layoutsData = this.dashboardUtils.getStateLayoutsData(this.dashboard, state); if (layoutsData) { + if (this.isEdit && Object.keys(layoutsData).some(l => { + const layout: DashboardPageLayout = this.layouts[l]; + const breakpoint = (layout && layout.layoutCtx.breakpoint) || 'default'; + const layoutInfo: BreakpointLayoutInfo = layoutsData[l][breakpoint] || layoutsData[l].default; + return layoutInfo && this.dashboardUtils.hasCollidingWidgets(layoutInfo.widgetLayouts); + })) { + this.store.dispatch(new ActionNotificationShow({ + message: this.translate.instant('dashboard.state-widgets-overlap-warning'), + type: 'warn' + })); + } this.dashboardCtx.state = state; this.dashboardCtx.aliasController.dashboardStateChanged(); this.isRightLayoutOpened = openRightLayout ? true : false; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index b7a2d72e33..931ff0ec2a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1519,6 +1519,7 @@ "state-id": "State Id", "state-id-required": "Dashboard state id is required.", "state-id-exists": "Dashboard state with the same id is already exists.", + "state-widgets-overlap-warning": "Some widgets on this dashboard state overlap each other.", "is-root-state": "Root state", "delete-state-title": "Delete dashboard state", "delete-state-text": "Are you sure you want delete dashboard state with name '{{stateName}}'?",