@masterteam/dashboard-builder 0.0.24 → 0.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19668,6 +19668,7 @@ class DashboardBuilder {
19668
19668
  cdr = inject(ChangeDetectorRef);
19669
19669
  dashboardService = inject(DashboardBuilderService);
19670
19670
  storeService = inject(DashboardStoreService);
19671
+ itemStoreService = inject(DashboardItemStoreService);
19671
19672
  transloco = inject(TranslocoService);
19672
19673
  modalService = inject(ModalService);
19673
19674
  // ============================================
@@ -19691,6 +19692,8 @@ class DashboardBuilder {
19691
19692
  readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
19692
19693
  /** Optional informative authoring context for context-aware selection */
19693
19694
  informativeContext = input(null, ...(ngDevMode ? [{ debugName: "informativeContext" }] : /* istanbul ignore next */ []));
19695
+ /** Dashboard-wide extra filters applied to every item request */
19696
+ extraFilters = input({}, ...(ngDevMode ? [{ debugName: "extraFilters" }] : /* istanbul ignore next */ []));
19694
19697
  // ============================================
19695
19698
  // Outputs
19696
19699
  // ============================================
@@ -19807,6 +19810,8 @@ class DashboardBuilder {
19807
19810
  dragStartY = 0;
19808
19811
  applyingDashboardData = false;
19809
19812
  emittingDashboardData = false;
19813
+ lastExtraFiltersSignature = null;
19814
+ lastStandaloneDashboardSignature = null;
19810
19815
  boundHandleDragOver = (event) => this.handleDragOver(event);
19811
19816
  boundHandleDrop = (event) => this.handleDrop(event);
19812
19817
  boundHandleDragEnd = (event) => this.handleDragEnd(event);
@@ -20785,6 +20790,14 @@ class DashboardBuilder {
20785
20790
  this.scrollInterval = null;
20786
20791
  }
20787
20792
  }
20793
+ refreshAllItems() {
20794
+ this.charts().forEach((chart) => {
20795
+ const dashboardId = chart.config?.serviceConfig?.dashboardId;
20796
+ if (dashboardId) {
20797
+ this.itemStoreService.triggerDataFetch(dashboardId);
20798
+ }
20799
+ });
20800
+ }
20788
20801
  constructor() {
20789
20802
  // Apply standalone data when provided
20790
20803
  effect(() => {
@@ -20793,6 +20806,10 @@ class DashboardBuilder {
20793
20806
  const data = this.dashboardData();
20794
20807
  if (!data)
20795
20808
  return;
20809
+ const signature = this.serializeDashboardData(data);
20810
+ if (this.lastStandaloneDashboardSignature === signature)
20811
+ return;
20812
+ this.lastStandaloneDashboardSignature = signature;
20796
20813
  this.applyDashboardData(data);
20797
20814
  });
20798
20815
  // Keep filters signal in sync
@@ -20809,7 +20826,23 @@ class DashboardBuilder {
20809
20826
  this.filtersConfig();
20810
20827
  if (this.applyingDashboardData)
20811
20828
  return;
20812
- this.emitDashboardDataChange();
20829
+ const snapshot = this.buildDashboardDataSnapshot();
20830
+ const signature = this.serializeDashboardData(snapshot);
20831
+ if (this.lastStandaloneDashboardSignature === signature)
20832
+ return;
20833
+ this.lastStandaloneDashboardSignature = signature;
20834
+ this.emitDashboardDataChange(snapshot);
20835
+ });
20836
+ effect(() => {
20837
+ const signature = JSON.stringify(this.extraFilters() ?? {});
20838
+ if (this.lastExtraFiltersSignature === null) {
20839
+ this.lastExtraFiltersSignature = signature;
20840
+ return;
20841
+ }
20842
+ if (this.lastExtraFiltersSignature !== signature) {
20843
+ this.lastExtraFiltersSignature = signature;
20844
+ this.refreshAllItems();
20845
+ }
20813
20846
  });
20814
20847
  }
20815
20848
  applyDashboardData(data) {
@@ -20822,13 +20855,17 @@ class DashboardBuilder {
20822
20855
  this.applyingDashboardData = false;
20823
20856
  this.cdr.detectChanges();
20824
20857
  }
20825
- emitDashboardDataChange() {
20858
+ emitDashboardDataChange(data) {
20826
20859
  if (!this.standalone())
20827
20860
  return;
20828
20861
  this.emittingDashboardData = true;
20862
+ this.dashboardData.set(data ?? this.buildDashboardDataSnapshot());
20863
+ this.emittingDashboardData = false;
20864
+ }
20865
+ buildDashboardDataSnapshot() {
20829
20866
  const filters = [...this.filtersConfig()];
20830
20867
  const page = this.pageConfig();
20831
- this.dashboardData.set({
20868
+ return {
20832
20869
  page: page
20833
20870
  ? {
20834
20871
  ...page,
@@ -20841,8 +20878,10 @@ class DashboardBuilder {
20841
20878
  charts: this.charts(),
20842
20879
  dialogs: this.dialogs(),
20843
20880
  filters,
20844
- });
20845
- this.emittingDashboardData = false;
20881
+ };
20882
+ }
20883
+ serializeDashboardData(data) {
20884
+ return JSON.stringify(data ?? null);
20846
20885
  }
20847
20886
  setFiltersConfig(filters) {
20848
20887
  const normalizedFilters = this.normalizeFiltersConfig(filters);
@@ -20889,7 +20928,7 @@ class DashboardBuilder {
20889
20928
  return current;
20890
20929
  }
20891
20930
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DashboardBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
20892
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DashboardBuilder, isStandalone: true, selector: "mt-dashboard-builder", inputs: { isPage: { classPropertyName: "isPage", publicName: "isPage", isSignal: true, isRequired: false, transformFunction: null }, pageTitle: { classPropertyName: "pageTitle", publicName: "pageTitle", isSignal: true, isRequired: false, transformFunction: null }, backButton: { classPropertyName: "backButton", publicName: "backButton", isSignal: true, isRequired: false, transformFunction: null }, pageId: { classPropertyName: "pageId", publicName: "pageId", isSignal: true, isRequired: false, transformFunction: null }, standalone: { classPropertyName: "standalone", publicName: "standalone", isSignal: true, isRequired: false, transformFunction: null }, services: { classPropertyName: "services", publicName: "services", isSignal: true, isRequired: false, transformFunction: null }, dashboardData: { classPropertyName: "dashboardData", publicName: "dashboardData", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, informativeContext: { classPropertyName: "informativeContext", publicName: "informativeContext", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dashboardData: "dashboardDataChange", pageChange: "pageChange", chartsChange: "chartsChange", onSave: "onSave", onBack: "onBack", onAddChart: "onAddChart", onEditChart: "onEditChart" }, host: { listeners: { "window:keydown": "onKeyDown($event)" } }, viewQueries: [{ propertyName: "contextMenu", first: true, predicate: ["contextMenu"], descendants: true, isSignal: true }, { propertyName: "gridsterContainer", first: true, predicate: ["gridsterContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'dashboardBuilder'\">\r\n <!-- Shared Actions Template -->\r\n <ng-template #actionsTemplate let-inPage=\"inPage\">\r\n <mt-button\r\n [label]=\"t('addWidget')\"\r\n icon=\"general.plus\"\r\n (onClick)=\"toggleWidgetPalette()\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n\r\n <mt-button\r\n [label]=\"t('save')\"\r\n icon=\"general.save-02\"\r\n [loading]=\"saving()\"\r\n [disabled]=\"saving()\"\r\n (onClick)=\"saveDash()\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n\r\n <span [class]=\"'w-px h-5 bg-surface-300'\"></span>\r\n\r\n <mt-button\r\n [icon]=\"stopActionsOnCards() ? 'media.play' : 'media.stop'\"\r\n [tooltip]=\"stopActionsOnCards() ? t('activateActions') : t('stopActions')\"\r\n (onClick)=\"stopAndActiveActions()\"\r\n [severity]=\"'warn'\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n\r\n <mt-button\r\n icon=\"general.edit-05\"\r\n [tooltip]=\"t('pageSettings')\"\r\n (onClick)=\"addOrEditPage(pageConfig()!)\"\r\n [severity]=\"'help'\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n\r\n <mt-button\r\n icon=\"general.filter-lines\"\r\n [tooltip]=\"t('manageFilter')\"\r\n (onClick)=\"openManageFilter()\"\r\n [severity]=\"'info'\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n </ng-template>\r\n\r\n <!-- Page mode -->\r\n @if (isPage()) {\r\n <mt-page\r\n [title]=\"\r\n pageTitle() ||\r\n pageConfig()?.name?.[languageCode()] ||\r\n pageConfig()?.name?.['en'] ||\r\n t('builder')\r\n \"\r\n [backButton]=\"backButton()\"\r\n (backButtonClick)=\"onBack.emit()\"\r\n >\r\n <!-- Header actions -->\r\n <ng-template #headerEnd>\r\n <div class=\"flex items-center gap-2\">\r\n <ng-container\r\n *ngTemplateOutlet=\"actionsTemplate; context: { inPage: true }\"\r\n />\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Content -->\r\n <ng-container *ngTemplateOutlet=\"builderContent\" />\r\n </mt-page>\r\n } @else {\r\n <!-- Non-page mode -->\r\n <ng-container *ngTemplateOutlet=\"builderContent\" />\r\n }\r\n\r\n <!-- Reusable builder content template -->\r\n <ng-template #builderContent>\r\n <div class=\"relative h-full min-h-[600px]\">\r\n <!-- Loading State -->\r\n @if (loading()) {\r\n <div class=\"flex items-center justify-center h-64\">\r\n <i class=\"mti mti-spinner-third mti-spin text-4xl text-primary\"></i>\r\n </div>\r\n }\r\n\r\n <!-- Main Content -->\r\n @if (!loading() && pageConfig()) {\r\n <!-- Fixed Actions Bar (only when NOT page mode) -->\r\n @if (!isPage()) {\r\n <div\r\n class=\"fixed bottom-4 left-1/2 -translate-x-1/2 z-50 flex items-center gap-2 bg-surface-0 rounded-full shadow-lg px-4 py-2 border border-surface-200\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"actionsTemplate; context: { inPage: false }\"\r\n />\r\n </div>\r\n }\r\n\r\n <!-- Gridster Container -->\r\n <gridster\r\n #gridsterContainer\r\n [options]=\"options\"\r\n class=\"h-full min-h-[500px] bg-surface-50!\"\r\n [class.is-page]=\"isPage()\"\r\n [class.pointer-events-none]=\"readonly()\"\r\n (dragover)=\"onGridDragOver($event)\"\r\n (drop)=\"onGridDrop($event)\"\r\n >\r\n @for (chart of charts() | filterByGroup: undefined; track $index) {\r\n <gridster-item\r\n [item]=\"chart\"\r\n class=\"bg-surface-0 rounded-lg shadow-sm border border-surface-200 overflow-hidden transition-all duration-200 hover:shadow-md hover:border-surface-300\"\r\n [class.ring-2]=\"isSelected(chart)\"\r\n [class.ring-primary]=\"isSelected(chart)\"\r\n [class.ring-offset-2]=\"isSelected(chart)\"\r\n (click)=\"onSelect(chart, $event)\"\r\n >\r\n <!-- Chart Content -->\r\n <div class=\"h-full w-full relative group\">\r\n <!-- Drag Handle -->\r\n @if (!readonly()) {\r\n <div\r\n class=\"drag-handler absolute top-0 left-0 right-0 h-6 cursor-move bg-gradient-to-b from-black/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center z-10\"\r\n >\r\n <i\r\n class=\"mti mti-grip-horizontal text-white/70 text-sm\"\r\n ></i>\r\n </div>\r\n }\r\n\r\n <!-- Action Buttons -->\r\n @if (!readonly()) {\r\n <div\r\n class=\"absolute top-1 right-1 flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity z-20\"\r\n >\r\n <!-- Quick Manage Button -->\r\n @if (showQuickManageButton(chart)) {\r\n <mt-button\r\n icon=\"editor.palette\"\r\n [tooltip]=\"t('quickManage')\"\r\n (onClick)=\"openQuickManage($event, chart)\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n }\r\n\r\n <!-- Breadcrumb Button (for topbar) -->\r\n @if (\r\n chart.config?.clientConfig?.componentName === \"topbar\"\r\n ) {\r\n <mt-button\r\n icon=\"general.slash-divider\"\r\n [tooltip]=\"t('manageBreadcrumb')\"\r\n (onClick)=\"openBreadcrumb(chart)\"\r\n severity=\"info\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n }\r\n\r\n <!-- Edit Button -->\r\n @if (\r\n chart.config?.clientConfig?.componentName !== \"header\" &&\r\n chart.config?.clientConfig?.componentName !== \"topbar\" &&\r\n chart.config?.clientConfig?.componentName !== \"Group\"\r\n ) {\r\n <mt-button\r\n icon=\"general.edit-05\"\r\n [tooltip]=\"t('edit')\"\r\n (onClick)=\"editItem(chart)\"\r\n severity=\"warn\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n }\r\n\r\n <!-- Delete Button -->\r\n @if (\r\n chart.config?.clientConfig?.componentName === \"Group\"\r\n ) {\r\n <mt-button\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"t('removeGroup')\"\r\n (onClick)=\"removeGroup(chart)\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n } @else {\r\n <mt-button\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"t('delete')\"\r\n (onClick)=\"deleteItem(chart)\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n }\r\n\r\n <!-- Advanced/Dialog Actions Button (pipe handles all conditions) -->\r\n @let dialogActions =\r\n chart | getChartActions: chartActionsContext;\r\n @if (dialogActions.length > 0) {\r\n <mt-button\r\n icon=\"general.dots-vertical\"\r\n [tooltip]=\"t('advanced')\"\r\n (onClick)=\"advancedPopover.toggle($event)\"\r\n severity=\"info\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n <p-popover #advancedPopover appendTo=\"body\">\r\n <div class=\"flex flex-col gap-1 min-w-[160px]\">\r\n @for (action of dialogActions; track action.label) {\r\n <mt-button\r\n [label]=\"action.label\"\r\n [icon]=\"action.icon\"\r\n severity=\"secondary\"\r\n [text]=\"true\"\r\n size=\"small\"\r\n (onClick)=\"\r\n action.command?.({\r\n originalEvent: $event,\r\n item: action,\r\n });\r\n advancedPopover.hide()\r\n \"\r\n />\r\n }\r\n </div>\r\n </p-popover>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Chart Rendering -->\r\n @if (!chart.loading) {\r\n <mt-dashboard-item\r\n [config]=\"chart.config\"\r\n [chartTypeId]=\"chart.chartTypeId\"\r\n [readonly]=\"readonly()\"\r\n [pageName]=\"\r\n pageConfig()?.name?.[languageCode()] ||\r\n pageConfig()?.name?.['en'] ||\r\n pageConfig()?.name?.['ar'] ||\r\n ''\r\n \"\r\n />\r\n } @else {\r\n <div class=\"flex items-center justify-center h-full\">\r\n <i\r\n class=\"mti mti-spinner-third mti-spin text-2xl text-primary\"\r\n ></i>\r\n </div>\r\n }\r\n\r\n <!-- Group Children -->\r\n @if (chart.config?.clientConfig?.componentName === \"Group\") {\r\n <div class=\"h-full flex flex-col p-2\">\r\n @for (\r\n childChart of charts()\r\n | filterByGroup\r\n : chart.config?.serviceConfig?.dashboardId;\r\n track childChart.config?.serviceConfig?.dashboardId;\r\n let idx = $index\r\n ) {\r\n @if (idx === chart.selectedGroupIndex) {\r\n <div class=\"flex-1 min-h-0\">\r\n <mt-dashboard-item\r\n [config]=\"childChart.config\"\r\n [chartTypeId]=\"childChart.chartTypeId\"\r\n [readonly]=\"readonly()\"\r\n [pageName]=\"\r\n pageConfig()?.name?.[languageCode()] ||\r\n pageConfig()?.name?.['en'] ||\r\n pageConfig()?.name?.['ar'] ||\r\n ''\r\n \"\r\n />\r\n </div>\r\n }\r\n }\r\n\r\n <!-- Group Tabs -->\r\n <div\r\n class=\"absolute bottom-0 left-0 right-0 flex gap-1 p-1 bg-surface-100 rounded-b-lg overflow-x-auto\"\r\n >\r\n @for (\r\n childChart of charts()\r\n | filterByGroup\r\n : chart.config?.serviceConfig?.dashboardId;\r\n track childChart.config?.serviceConfig?.dashboardId;\r\n let idx = $index\r\n ) {\r\n <mt-button\r\n [label]=\"\r\n childChart.config?.clientConfig?.title?.[\r\n languageCode()\r\n ] ||\r\n childChart.config?.clientConfig?.title?.['en'] ||\r\n 'Chart ' + (idx + 1)\r\n \"\r\n [severity]=\"\r\n idx === chart.selectedGroupIndex\r\n ? 'primary'\r\n : 'secondary'\r\n \"\r\n [text]=\"idx !== chart.selectedGroupIndex\"\r\n size=\"small\"\r\n (onClick)=\"chart.selectedGroupIndex = idx\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </gridster-item>\r\n }\r\n </gridster>\r\n\r\n <!-- Context Menu -->\r\n <p-contextMenu #contextMenu [model]=\"menuItems()\" appendTo=\"body\" />\r\n }\r\n\r\n <!-- Empty State -->\r\n @if (!loading() && !pageConfig()) {\r\n <div\r\n class=\"flex flex-col items-center justify-center h-64 text-muted-color\"\r\n >\r\n <i class=\"mti mti-layout text-6xl mb-4\"></i>\r\n <p class=\"text-lg\">{{ t(\"noPageSelected\") }}</p>\r\n <p class=\"text-sm\">{{ t(\"selectPageToStart\") }}</p>\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%}gridster{min-height:calc(100vh - 120px);height:100%}gridster .gridster-column{border-left:1px solid var(--p-primary-100)!important;border-right:1px solid var(--p-primary-100)!important}gridster .gridster-row{border-top:1px solid var(--p-primary-100)!important;border-bottom:1px solid var(--p-primary-100)!important}gridster.is-page{min-height:calc(100vh - 200px)}.mti-spin{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.gridster-drop-indicator{position:absolute;background:var(--p-primary-100);border:2px dashed var(--p-primary-400);border-radius:8px;pointer-events:none;z-index:100}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: GridsterModule }, { kind: "component", type: i2$2.GridsterComponent, selector: "gridster", inputs: ["options"] }, { kind: "component", type: i2$2.GridsterItemComponent, selector: "gridster-item", inputs: ["item"], outputs: ["itemInit", "itemChange", "itemResize"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { kind: "component", type: Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: ContextMenuModule }, { kind: "component", type: i3$1.ContextMenu, selector: "p-contextMenu, p-contextmenu, p-context-menu", inputs: ["model", "triggerEvent", "target", "global", "style", "styleClass", "autoZIndex", "baseZIndex", "id", "breakpoint", "ariaLabel", "ariaLabelledBy", "pressDelay", "appendTo", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "component", type: DashboardItem, selector: "mt-dashboard-item", inputs: ["config", "chartTypeId", "readonly", "pageName", "inGroup", "isDialog", "queryParams", "extraFilters", "ignoreQueryFilter"], outputs: ["actionTriggered"] }, { kind: "pipe", type: FilterByGroupPipe, name: "filterByGroup" }, { kind: "pipe", type: GetChartActionsPipe, name: "getChartActions" }], encapsulation: i0.ViewEncapsulation.None });
20931
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DashboardBuilder, isStandalone: true, selector: "mt-dashboard-builder", inputs: { isPage: { classPropertyName: "isPage", publicName: "isPage", isSignal: true, isRequired: false, transformFunction: null }, pageTitle: { classPropertyName: "pageTitle", publicName: "pageTitle", isSignal: true, isRequired: false, transformFunction: null }, backButton: { classPropertyName: "backButton", publicName: "backButton", isSignal: true, isRequired: false, transformFunction: null }, pageId: { classPropertyName: "pageId", publicName: "pageId", isSignal: true, isRequired: false, transformFunction: null }, standalone: { classPropertyName: "standalone", publicName: "standalone", isSignal: true, isRequired: false, transformFunction: null }, services: { classPropertyName: "services", publicName: "services", isSignal: true, isRequired: false, transformFunction: null }, dashboardData: { classPropertyName: "dashboardData", publicName: "dashboardData", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, informativeContext: { classPropertyName: "informativeContext", publicName: "informativeContext", isSignal: true, isRequired: false, transformFunction: null }, extraFilters: { classPropertyName: "extraFilters", publicName: "extraFilters", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dashboardData: "dashboardDataChange", pageChange: "pageChange", chartsChange: "chartsChange", onSave: "onSave", onBack: "onBack", onAddChart: "onAddChart", onEditChart: "onEditChart" }, host: { listeners: { "window:keydown": "onKeyDown($event)" } }, viewQueries: [{ propertyName: "contextMenu", first: true, predicate: ["contextMenu"], descendants: true, isSignal: true }, { propertyName: "gridsterContainer", first: true, predicate: ["gridsterContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'dashboardBuilder'\">\r\n <!-- Shared Actions Template -->\r\n <ng-template #actionsTemplate let-inPage=\"inPage\">\r\n <mt-button\r\n [label]=\"t('addWidget')\"\r\n icon=\"general.plus\"\r\n (onClick)=\"toggleWidgetPalette()\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n\r\n <mt-button\r\n [label]=\"t('save')\"\r\n icon=\"general.save-02\"\r\n [loading]=\"saving()\"\r\n [disabled]=\"saving()\"\r\n (onClick)=\"saveDash()\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n\r\n <span [class]=\"'w-px h-5 bg-surface-300'\"></span>\r\n\r\n <mt-button\r\n [icon]=\"stopActionsOnCards() ? 'media.play' : 'media.stop'\"\r\n [tooltip]=\"stopActionsOnCards() ? t('activateActions') : t('stopActions')\"\r\n (onClick)=\"stopAndActiveActions()\"\r\n [severity]=\"'warn'\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n\r\n <mt-button\r\n icon=\"general.edit-05\"\r\n [tooltip]=\"t('pageSettings')\"\r\n (onClick)=\"addOrEditPage(pageConfig()!)\"\r\n [severity]=\"'help'\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n\r\n <mt-button\r\n icon=\"general.filter-lines\"\r\n [tooltip]=\"t('manageFilter')\"\r\n (onClick)=\"openManageFilter()\"\r\n [severity]=\"'info'\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n </ng-template>\r\n\r\n <!-- Page mode -->\r\n @if (isPage()) {\r\n <mt-page\r\n [title]=\"\r\n pageTitle() ||\r\n pageConfig()?.name?.[languageCode()] ||\r\n pageConfig()?.name?.['en'] ||\r\n t('builder')\r\n \"\r\n [backButton]=\"backButton()\"\r\n (backButtonClick)=\"onBack.emit()\"\r\n >\r\n <!-- Header actions -->\r\n <ng-template #headerEnd>\r\n <div class=\"flex items-center gap-2\">\r\n <ng-container\r\n *ngTemplateOutlet=\"actionsTemplate; context: { inPage: true }\"\r\n />\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Content -->\r\n <ng-container *ngTemplateOutlet=\"builderContent\" />\r\n </mt-page>\r\n } @else {\r\n <!-- Non-page mode -->\r\n <ng-container *ngTemplateOutlet=\"builderContent\" />\r\n }\r\n\r\n <!-- Reusable builder content template -->\r\n <ng-template #builderContent>\r\n <div class=\"relative h-full min-h-[600px]\">\r\n <!-- Loading State -->\r\n @if (loading()) {\r\n <div class=\"flex items-center justify-center h-64\">\r\n <i class=\"mti mti-spinner-third mti-spin text-4xl text-primary\"></i>\r\n </div>\r\n }\r\n\r\n <!-- Main Content -->\r\n @if (!loading() && pageConfig()) {\r\n <!-- Fixed Actions Bar (only when NOT page mode) -->\r\n @if (!isPage()) {\r\n <div\r\n class=\"fixed bottom-4 left-1/2 -translate-x-1/2 z-50 flex items-center gap-2 bg-surface-0 rounded-full shadow-lg px-4 py-2 border border-surface-200\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"actionsTemplate; context: { inPage: false }\"\r\n />\r\n </div>\r\n }\r\n\r\n <!-- Gridster Container -->\r\n <gridster\r\n #gridsterContainer\r\n [options]=\"options\"\r\n class=\"h-full min-h-[500px] bg-surface-50!\"\r\n [class.is-page]=\"isPage()\"\r\n [class.pointer-events-none]=\"readonly()\"\r\n (dragover)=\"onGridDragOver($event)\"\r\n (drop)=\"onGridDrop($event)\"\r\n >\r\n @for (chart of charts() | filterByGroup: undefined; track $index) {\r\n <gridster-item\r\n [item]=\"chart\"\r\n class=\"bg-surface-0 rounded-lg shadow-sm border border-surface-200 overflow-hidden transition-all duration-200 hover:shadow-md hover:border-surface-300\"\r\n [class.ring-2]=\"isSelected(chart)\"\r\n [class.ring-primary]=\"isSelected(chart)\"\r\n [class.ring-offset-2]=\"isSelected(chart)\"\r\n (click)=\"onSelect(chart, $event)\"\r\n >\r\n <!-- Chart Content -->\r\n <div class=\"h-full w-full relative group\">\r\n <!-- Drag Handle -->\r\n @if (!readonly()) {\r\n <div\r\n class=\"drag-handler absolute top-0 left-0 right-0 h-6 cursor-move bg-gradient-to-b from-black/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center z-10\"\r\n >\r\n <i\r\n class=\"mti mti-grip-horizontal text-white/70 text-sm\"\r\n ></i>\r\n </div>\r\n }\r\n\r\n <!-- Action Buttons -->\r\n @if (!readonly()) {\r\n <div\r\n class=\"absolute top-1 right-1 flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity z-20\"\r\n >\r\n <!-- Quick Manage Button -->\r\n @if (showQuickManageButton(chart)) {\r\n <mt-button\r\n icon=\"editor.palette\"\r\n [tooltip]=\"t('quickManage')\"\r\n (onClick)=\"openQuickManage($event, chart)\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n }\r\n\r\n <!-- Breadcrumb Button (for topbar) -->\r\n @if (\r\n chart.config?.clientConfig?.componentName === \"topbar\"\r\n ) {\r\n <mt-button\r\n icon=\"general.slash-divider\"\r\n [tooltip]=\"t('manageBreadcrumb')\"\r\n (onClick)=\"openBreadcrumb(chart)\"\r\n severity=\"info\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n }\r\n\r\n <!-- Edit Button -->\r\n @if (\r\n chart.config?.clientConfig?.componentName !== \"header\" &&\r\n chart.config?.clientConfig?.componentName !== \"topbar\" &&\r\n chart.config?.clientConfig?.componentName !== \"Group\"\r\n ) {\r\n <mt-button\r\n icon=\"general.edit-05\"\r\n [tooltip]=\"t('edit')\"\r\n (onClick)=\"editItem(chart)\"\r\n severity=\"warn\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n }\r\n\r\n <!-- Delete Button -->\r\n @if (\r\n chart.config?.clientConfig?.componentName === \"Group\"\r\n ) {\r\n <mt-button\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"t('removeGroup')\"\r\n (onClick)=\"removeGroup(chart)\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n } @else {\r\n <mt-button\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"t('delete')\"\r\n (onClick)=\"deleteItem(chart)\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n }\r\n\r\n <!-- Advanced/Dialog Actions Button (pipe handles all conditions) -->\r\n @let dialogActions =\r\n chart | getChartActions: chartActionsContext;\r\n @if (dialogActions.length > 0) {\r\n <mt-button\r\n icon=\"general.dots-vertical\"\r\n [tooltip]=\"t('advanced')\"\r\n (onClick)=\"advancedPopover.toggle($event)\"\r\n severity=\"info\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n <p-popover #advancedPopover appendTo=\"body\">\r\n <div class=\"flex flex-col gap-1 min-w-[160px]\">\r\n @for (action of dialogActions; track action.label) {\r\n <mt-button\r\n [label]=\"action.label\"\r\n [icon]=\"action.icon\"\r\n severity=\"secondary\"\r\n [text]=\"true\"\r\n size=\"small\"\r\n (onClick)=\"\r\n action.command?.({\r\n originalEvent: $event,\r\n item: action,\r\n });\r\n advancedPopover.hide()\r\n \"\r\n />\r\n }\r\n </div>\r\n </p-popover>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Chart Rendering -->\r\n @if (!chart.loading) {\r\n <mt-dashboard-item\n [config]=\"chart.config\"\n [chartTypeId]=\"chart.chartTypeId\"\n [readonly]=\"readonly()\"\n [extraFilters]=\"extraFilters()\"\n [pageName]=\"\n pageConfig()?.name?.[languageCode()] ||\n pageConfig()?.name?.['en'] ||\n pageConfig()?.name?.['ar'] ||\r\n ''\r\n \"\r\n />\r\n } @else {\r\n <div class=\"flex items-center justify-center h-full\">\r\n <i\r\n class=\"mti mti-spinner-third mti-spin text-2xl text-primary\"\r\n ></i>\r\n </div>\r\n }\r\n\r\n <!-- Group Children -->\r\n @if (chart.config?.clientConfig?.componentName === \"Group\") {\r\n <div class=\"h-full flex flex-col p-2\">\r\n @for (\r\n childChart of charts()\r\n | filterByGroup\r\n : chart.config?.serviceConfig?.dashboardId;\r\n track childChart.config?.serviceConfig?.dashboardId;\r\n let idx = $index\r\n ) {\r\n @if (idx === chart.selectedGroupIndex) {\r\n <div class=\"flex-1 min-h-0\">\r\n <mt-dashboard-item\n [config]=\"childChart.config\"\n [chartTypeId]=\"childChart.chartTypeId\"\n [readonly]=\"readonly()\"\n [extraFilters]=\"extraFilters()\"\n [pageName]=\"\n pageConfig()?.name?.[languageCode()] ||\n pageConfig()?.name?.['en'] ||\n pageConfig()?.name?.['ar'] ||\r\n ''\r\n \"\r\n />\r\n </div>\r\n }\r\n }\r\n\r\n <!-- Group Tabs -->\r\n <div\r\n class=\"absolute bottom-0 left-0 right-0 flex gap-1 p-1 bg-surface-100 rounded-b-lg overflow-x-auto\"\r\n >\r\n @for (\r\n childChart of charts()\r\n | filterByGroup\r\n : chart.config?.serviceConfig?.dashboardId;\r\n track childChart.config?.serviceConfig?.dashboardId;\r\n let idx = $index\r\n ) {\r\n <mt-button\r\n [label]=\"\r\n childChart.config?.clientConfig?.title?.[\r\n languageCode()\r\n ] ||\r\n childChart.config?.clientConfig?.title?.['en'] ||\r\n 'Chart ' + (idx + 1)\r\n \"\r\n [severity]=\"\r\n idx === chart.selectedGroupIndex\r\n ? 'primary'\r\n : 'secondary'\r\n \"\r\n [text]=\"idx !== chart.selectedGroupIndex\"\r\n size=\"small\"\r\n (onClick)=\"chart.selectedGroupIndex = idx\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </gridster-item>\r\n }\r\n </gridster>\r\n\r\n <!-- Context Menu -->\r\n <p-contextMenu #contextMenu [model]=\"menuItems()\" appendTo=\"body\" />\r\n }\r\n\r\n <!-- Empty State -->\r\n @if (!loading() && !pageConfig()) {\r\n <div\r\n class=\"flex flex-col items-center justify-center h-64 text-muted-color\"\r\n >\r\n <i class=\"mti mti-layout text-6xl mb-4\"></i>\r\n <p class=\"text-lg\">{{ t(\"noPageSelected\") }}</p>\r\n <p class=\"text-sm\">{{ t(\"selectPageToStart\") }}</p>\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%}gridster{min-height:calc(100vh - 120px);height:100%}gridster .gridster-column{border-left:1px solid var(--p-primary-100)!important;border-right:1px solid var(--p-primary-100)!important}gridster .gridster-row{border-top:1px solid var(--p-primary-100)!important;border-bottom:1px solid var(--p-primary-100)!important}gridster.is-page{min-height:calc(100vh - 200px)}.mti-spin{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.gridster-drop-indicator{position:absolute;background:var(--p-primary-100);border:2px dashed var(--p-primary-400);border-radius:8px;pointer-events:none;z-index:100}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: GridsterModule }, { kind: "component", type: i2$2.GridsterComponent, selector: "gridster", inputs: ["options"] }, { kind: "component", type: i2$2.GridsterItemComponent, selector: "gridster-item", inputs: ["item"], outputs: ["itemInit", "itemChange", "itemResize"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { kind: "component", type: Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: ContextMenuModule }, { kind: "component", type: i3$1.ContextMenu, selector: "p-contextMenu, p-contextmenu, p-context-menu", inputs: ["model", "triggerEvent", "target", "global", "style", "styleClass", "autoZIndex", "baseZIndex", "id", "breakpoint", "ariaLabel", "ariaLabelledBy", "pressDelay", "appendTo", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "component", type: DashboardItem, selector: "mt-dashboard-item", inputs: ["config", "chartTypeId", "readonly", "pageName", "inGroup", "isDialog", "queryParams", "extraFilters", "ignoreQueryFilter"], outputs: ["actionTriggered"] }, { kind: "pipe", type: FilterByGroupPipe, name: "filterByGroup" }, { kind: "pipe", type: GetChartActionsPipe, name: "getChartActions" }], encapsulation: i0.ViewEncapsulation.None });
20893
20932
  }
20894
20933
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DashboardBuilder, decorators: [{
20895
20934
  type: Component,
@@ -20905,8 +20944,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
20905
20944
  FilterByGroupPipe,
20906
20945
  GetChartActionsPipe,
20907
20946
  DashboardItem,
20908
- ], encapsulation: ViewEncapsulation.None, template: "<ng-container *transloco=\"let t; prefix: 'dashboardBuilder'\">\r\n <!-- Shared Actions Template -->\r\n <ng-template #actionsTemplate let-inPage=\"inPage\">\r\n <mt-button\r\n [label]=\"t('addWidget')\"\r\n icon=\"general.plus\"\r\n (onClick)=\"toggleWidgetPalette()\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n\r\n <mt-button\r\n [label]=\"t('save')\"\r\n icon=\"general.save-02\"\r\n [loading]=\"saving()\"\r\n [disabled]=\"saving()\"\r\n (onClick)=\"saveDash()\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n\r\n <span [class]=\"'w-px h-5 bg-surface-300'\"></span>\r\n\r\n <mt-button\r\n [icon]=\"stopActionsOnCards() ? 'media.play' : 'media.stop'\"\r\n [tooltip]=\"stopActionsOnCards() ? t('activateActions') : t('stopActions')\"\r\n (onClick)=\"stopAndActiveActions()\"\r\n [severity]=\"'warn'\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n\r\n <mt-button\r\n icon=\"general.edit-05\"\r\n [tooltip]=\"t('pageSettings')\"\r\n (onClick)=\"addOrEditPage(pageConfig()!)\"\r\n [severity]=\"'help'\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n\r\n <mt-button\r\n icon=\"general.filter-lines\"\r\n [tooltip]=\"t('manageFilter')\"\r\n (onClick)=\"openManageFilter()\"\r\n [severity]=\"'info'\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n </ng-template>\r\n\r\n <!-- Page mode -->\r\n @if (isPage()) {\r\n <mt-page\r\n [title]=\"\r\n pageTitle() ||\r\n pageConfig()?.name?.[languageCode()] ||\r\n pageConfig()?.name?.['en'] ||\r\n t('builder')\r\n \"\r\n [backButton]=\"backButton()\"\r\n (backButtonClick)=\"onBack.emit()\"\r\n >\r\n <!-- Header actions -->\r\n <ng-template #headerEnd>\r\n <div class=\"flex items-center gap-2\">\r\n <ng-container\r\n *ngTemplateOutlet=\"actionsTemplate; context: { inPage: true }\"\r\n />\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Content -->\r\n <ng-container *ngTemplateOutlet=\"builderContent\" />\r\n </mt-page>\r\n } @else {\r\n <!-- Non-page mode -->\r\n <ng-container *ngTemplateOutlet=\"builderContent\" />\r\n }\r\n\r\n <!-- Reusable builder content template -->\r\n <ng-template #builderContent>\r\n <div class=\"relative h-full min-h-[600px]\">\r\n <!-- Loading State -->\r\n @if (loading()) {\r\n <div class=\"flex items-center justify-center h-64\">\r\n <i class=\"mti mti-spinner-third mti-spin text-4xl text-primary\"></i>\r\n </div>\r\n }\r\n\r\n <!-- Main Content -->\r\n @if (!loading() && pageConfig()) {\r\n <!-- Fixed Actions Bar (only when NOT page mode) -->\r\n @if (!isPage()) {\r\n <div\r\n class=\"fixed bottom-4 left-1/2 -translate-x-1/2 z-50 flex items-center gap-2 bg-surface-0 rounded-full shadow-lg px-4 py-2 border border-surface-200\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"actionsTemplate; context: { inPage: false }\"\r\n />\r\n </div>\r\n }\r\n\r\n <!-- Gridster Container -->\r\n <gridster\r\n #gridsterContainer\r\n [options]=\"options\"\r\n class=\"h-full min-h-[500px] bg-surface-50!\"\r\n [class.is-page]=\"isPage()\"\r\n [class.pointer-events-none]=\"readonly()\"\r\n (dragover)=\"onGridDragOver($event)\"\r\n (drop)=\"onGridDrop($event)\"\r\n >\r\n @for (chart of charts() | filterByGroup: undefined; track $index) {\r\n <gridster-item\r\n [item]=\"chart\"\r\n class=\"bg-surface-0 rounded-lg shadow-sm border border-surface-200 overflow-hidden transition-all duration-200 hover:shadow-md hover:border-surface-300\"\r\n [class.ring-2]=\"isSelected(chart)\"\r\n [class.ring-primary]=\"isSelected(chart)\"\r\n [class.ring-offset-2]=\"isSelected(chart)\"\r\n (click)=\"onSelect(chart, $event)\"\r\n >\r\n <!-- Chart Content -->\r\n <div class=\"h-full w-full relative group\">\r\n <!-- Drag Handle -->\r\n @if (!readonly()) {\r\n <div\r\n class=\"drag-handler absolute top-0 left-0 right-0 h-6 cursor-move bg-gradient-to-b from-black/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center z-10\"\r\n >\r\n <i\r\n class=\"mti mti-grip-horizontal text-white/70 text-sm\"\r\n ></i>\r\n </div>\r\n }\r\n\r\n <!-- Action Buttons -->\r\n @if (!readonly()) {\r\n <div\r\n class=\"absolute top-1 right-1 flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity z-20\"\r\n >\r\n <!-- Quick Manage Button -->\r\n @if (showQuickManageButton(chart)) {\r\n <mt-button\r\n icon=\"editor.palette\"\r\n [tooltip]=\"t('quickManage')\"\r\n (onClick)=\"openQuickManage($event, chart)\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n }\r\n\r\n <!-- Breadcrumb Button (for topbar) -->\r\n @if (\r\n chart.config?.clientConfig?.componentName === \"topbar\"\r\n ) {\r\n <mt-button\r\n icon=\"general.slash-divider\"\r\n [tooltip]=\"t('manageBreadcrumb')\"\r\n (onClick)=\"openBreadcrumb(chart)\"\r\n severity=\"info\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n }\r\n\r\n <!-- Edit Button -->\r\n @if (\r\n chart.config?.clientConfig?.componentName !== \"header\" &&\r\n chart.config?.clientConfig?.componentName !== \"topbar\" &&\r\n chart.config?.clientConfig?.componentName !== \"Group\"\r\n ) {\r\n <mt-button\r\n icon=\"general.edit-05\"\r\n [tooltip]=\"t('edit')\"\r\n (onClick)=\"editItem(chart)\"\r\n severity=\"warn\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n }\r\n\r\n <!-- Delete Button -->\r\n @if (\r\n chart.config?.clientConfig?.componentName === \"Group\"\r\n ) {\r\n <mt-button\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"t('removeGroup')\"\r\n (onClick)=\"removeGroup(chart)\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n } @else {\r\n <mt-button\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"t('delete')\"\r\n (onClick)=\"deleteItem(chart)\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n }\r\n\r\n <!-- Advanced/Dialog Actions Button (pipe handles all conditions) -->\r\n @let dialogActions =\r\n chart | getChartActions: chartActionsContext;\r\n @if (dialogActions.length > 0) {\r\n <mt-button\r\n icon=\"general.dots-vertical\"\r\n [tooltip]=\"t('advanced')\"\r\n (onClick)=\"advancedPopover.toggle($event)\"\r\n severity=\"info\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n <p-popover #advancedPopover appendTo=\"body\">\r\n <div class=\"flex flex-col gap-1 min-w-[160px]\">\r\n @for (action of dialogActions; track action.label) {\r\n <mt-button\r\n [label]=\"action.label\"\r\n [icon]=\"action.icon\"\r\n severity=\"secondary\"\r\n [text]=\"true\"\r\n size=\"small\"\r\n (onClick)=\"\r\n action.command?.({\r\n originalEvent: $event,\r\n item: action,\r\n });\r\n advancedPopover.hide()\r\n \"\r\n />\r\n }\r\n </div>\r\n </p-popover>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Chart Rendering -->\r\n @if (!chart.loading) {\r\n <mt-dashboard-item\r\n [config]=\"chart.config\"\r\n [chartTypeId]=\"chart.chartTypeId\"\r\n [readonly]=\"readonly()\"\r\n [pageName]=\"\r\n pageConfig()?.name?.[languageCode()] ||\r\n pageConfig()?.name?.['en'] ||\r\n pageConfig()?.name?.['ar'] ||\r\n ''\r\n \"\r\n />\r\n } @else {\r\n <div class=\"flex items-center justify-center h-full\">\r\n <i\r\n class=\"mti mti-spinner-third mti-spin text-2xl text-primary\"\r\n ></i>\r\n </div>\r\n }\r\n\r\n <!-- Group Children -->\r\n @if (chart.config?.clientConfig?.componentName === \"Group\") {\r\n <div class=\"h-full flex flex-col p-2\">\r\n @for (\r\n childChart of charts()\r\n | filterByGroup\r\n : chart.config?.serviceConfig?.dashboardId;\r\n track childChart.config?.serviceConfig?.dashboardId;\r\n let idx = $index\r\n ) {\r\n @if (idx === chart.selectedGroupIndex) {\r\n <div class=\"flex-1 min-h-0\">\r\n <mt-dashboard-item\r\n [config]=\"childChart.config\"\r\n [chartTypeId]=\"childChart.chartTypeId\"\r\n [readonly]=\"readonly()\"\r\n [pageName]=\"\r\n pageConfig()?.name?.[languageCode()] ||\r\n pageConfig()?.name?.['en'] ||\r\n pageConfig()?.name?.['ar'] ||\r\n ''\r\n \"\r\n />\r\n </div>\r\n }\r\n }\r\n\r\n <!-- Group Tabs -->\r\n <div\r\n class=\"absolute bottom-0 left-0 right-0 flex gap-1 p-1 bg-surface-100 rounded-b-lg overflow-x-auto\"\r\n >\r\n @for (\r\n childChart of charts()\r\n | filterByGroup\r\n : chart.config?.serviceConfig?.dashboardId;\r\n track childChart.config?.serviceConfig?.dashboardId;\r\n let idx = $index\r\n ) {\r\n <mt-button\r\n [label]=\"\r\n childChart.config?.clientConfig?.title?.[\r\n languageCode()\r\n ] ||\r\n childChart.config?.clientConfig?.title?.['en'] ||\r\n 'Chart ' + (idx + 1)\r\n \"\r\n [severity]=\"\r\n idx === chart.selectedGroupIndex\r\n ? 'primary'\r\n : 'secondary'\r\n \"\r\n [text]=\"idx !== chart.selectedGroupIndex\"\r\n size=\"small\"\r\n (onClick)=\"chart.selectedGroupIndex = idx\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </gridster-item>\r\n }\r\n </gridster>\r\n\r\n <!-- Context Menu -->\r\n <p-contextMenu #contextMenu [model]=\"menuItems()\" appendTo=\"body\" />\r\n }\r\n\r\n <!-- Empty State -->\r\n @if (!loading() && !pageConfig()) {\r\n <div\r\n class=\"flex flex-col items-center justify-center h-64 text-muted-color\"\r\n >\r\n <i class=\"mti mti-layout text-6xl mb-4\"></i>\r\n <p class=\"text-lg\">{{ t(\"noPageSelected\") }}</p>\r\n <p class=\"text-sm\">{{ t(\"selectPageToStart\") }}</p>\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%}gridster{min-height:calc(100vh - 120px);height:100%}gridster .gridster-column{border-left:1px solid var(--p-primary-100)!important;border-right:1px solid var(--p-primary-100)!important}gridster .gridster-row{border-top:1px solid var(--p-primary-100)!important;border-bottom:1px solid var(--p-primary-100)!important}gridster.is-page{min-height:calc(100vh - 200px)}.mti-spin{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.gridster-drop-indicator{position:absolute;background:var(--p-primary-100);border:2px dashed var(--p-primary-400);border-radius:8px;pointer-events:none;z-index:100}\n"] }]
20909
- }], ctorParameters: () => [], propDecorators: { isPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "isPage", required: false }] }], pageTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageTitle", required: false }] }], backButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "backButton", required: false }] }], pageId: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageId", required: false }] }], standalone: [{ type: i0.Input, args: [{ isSignal: true, alias: "standalone", required: false }] }], services: [{ type: i0.Input, args: [{ isSignal: true, alias: "services", required: false }] }], dashboardData: [{ type: i0.Input, args: [{ isSignal: true, alias: "dashboardData", required: false }] }, { type: i0.Output, args: ["dashboardDataChange"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], informativeContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "informativeContext", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], chartsChange: [{ type: i0.Output, args: ["chartsChange"] }], onSave: [{ type: i0.Output, args: ["onSave"] }], onBack: [{ type: i0.Output, args: ["onBack"] }], onAddChart: [{ type: i0.Output, args: ["onAddChart"] }], onEditChart: [{ type: i0.Output, args: ["onEditChart"] }], contextMenu: [{ type: i0.ViewChild, args: ['contextMenu', { isSignal: true }] }], gridsterContainer: [{ type: i0.ViewChild, args: ['gridsterContainer', { isSignal: true }] }], onKeyDown: [{
20947
+ ], encapsulation: ViewEncapsulation.None, template: "<ng-container *transloco=\"let t; prefix: 'dashboardBuilder'\">\r\n <!-- Shared Actions Template -->\r\n <ng-template #actionsTemplate let-inPage=\"inPage\">\r\n <mt-button\r\n [label]=\"t('addWidget')\"\r\n icon=\"general.plus\"\r\n (onClick)=\"toggleWidgetPalette()\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n\r\n <mt-button\r\n [label]=\"t('save')\"\r\n icon=\"general.save-02\"\r\n [loading]=\"saving()\"\r\n [disabled]=\"saving()\"\r\n (onClick)=\"saveDash()\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n\r\n <span [class]=\"'w-px h-5 bg-surface-300'\"></span>\r\n\r\n <mt-button\r\n [icon]=\"stopActionsOnCards() ? 'media.play' : 'media.stop'\"\r\n [tooltip]=\"stopActionsOnCards() ? t('activateActions') : t('stopActions')\"\r\n (onClick)=\"stopAndActiveActions()\"\r\n [severity]=\"'warn'\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n\r\n <mt-button\r\n icon=\"general.edit-05\"\r\n [tooltip]=\"t('pageSettings')\"\r\n (onClick)=\"addOrEditPage(pageConfig()!)\"\r\n [severity]=\"'help'\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n\r\n <mt-button\r\n icon=\"general.filter-lines\"\r\n [tooltip]=\"t('manageFilter')\"\r\n (onClick)=\"openManageFilter()\"\r\n [severity]=\"'info'\"\r\n [outlined]=\"true\"\r\n size=\"small\"\r\n [rounded]=\"!inPage\"\r\n />\r\n </ng-template>\r\n\r\n <!-- Page mode -->\r\n @if (isPage()) {\r\n <mt-page\r\n [title]=\"\r\n pageTitle() ||\r\n pageConfig()?.name?.[languageCode()] ||\r\n pageConfig()?.name?.['en'] ||\r\n t('builder')\r\n \"\r\n [backButton]=\"backButton()\"\r\n (backButtonClick)=\"onBack.emit()\"\r\n >\r\n <!-- Header actions -->\r\n <ng-template #headerEnd>\r\n <div class=\"flex items-center gap-2\">\r\n <ng-container\r\n *ngTemplateOutlet=\"actionsTemplate; context: { inPage: true }\"\r\n />\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Content -->\r\n <ng-container *ngTemplateOutlet=\"builderContent\" />\r\n </mt-page>\r\n } @else {\r\n <!-- Non-page mode -->\r\n <ng-container *ngTemplateOutlet=\"builderContent\" />\r\n }\r\n\r\n <!-- Reusable builder content template -->\r\n <ng-template #builderContent>\r\n <div class=\"relative h-full min-h-[600px]\">\r\n <!-- Loading State -->\r\n @if (loading()) {\r\n <div class=\"flex items-center justify-center h-64\">\r\n <i class=\"mti mti-spinner-third mti-spin text-4xl text-primary\"></i>\r\n </div>\r\n }\r\n\r\n <!-- Main Content -->\r\n @if (!loading() && pageConfig()) {\r\n <!-- Fixed Actions Bar (only when NOT page mode) -->\r\n @if (!isPage()) {\r\n <div\r\n class=\"fixed bottom-4 left-1/2 -translate-x-1/2 z-50 flex items-center gap-2 bg-surface-0 rounded-full shadow-lg px-4 py-2 border border-surface-200\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"actionsTemplate; context: { inPage: false }\"\r\n />\r\n </div>\r\n }\r\n\r\n <!-- Gridster Container -->\r\n <gridster\r\n #gridsterContainer\r\n [options]=\"options\"\r\n class=\"h-full min-h-[500px] bg-surface-50!\"\r\n [class.is-page]=\"isPage()\"\r\n [class.pointer-events-none]=\"readonly()\"\r\n (dragover)=\"onGridDragOver($event)\"\r\n (drop)=\"onGridDrop($event)\"\r\n >\r\n @for (chart of charts() | filterByGroup: undefined; track $index) {\r\n <gridster-item\r\n [item]=\"chart\"\r\n class=\"bg-surface-0 rounded-lg shadow-sm border border-surface-200 overflow-hidden transition-all duration-200 hover:shadow-md hover:border-surface-300\"\r\n [class.ring-2]=\"isSelected(chart)\"\r\n [class.ring-primary]=\"isSelected(chart)\"\r\n [class.ring-offset-2]=\"isSelected(chart)\"\r\n (click)=\"onSelect(chart, $event)\"\r\n >\r\n <!-- Chart Content -->\r\n <div class=\"h-full w-full relative group\">\r\n <!-- Drag Handle -->\r\n @if (!readonly()) {\r\n <div\r\n class=\"drag-handler absolute top-0 left-0 right-0 h-6 cursor-move bg-gradient-to-b from-black/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center z-10\"\r\n >\r\n <i\r\n class=\"mti mti-grip-horizontal text-white/70 text-sm\"\r\n ></i>\r\n </div>\r\n }\r\n\r\n <!-- Action Buttons -->\r\n @if (!readonly()) {\r\n <div\r\n class=\"absolute top-1 right-1 flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity z-20\"\r\n >\r\n <!-- Quick Manage Button -->\r\n @if (showQuickManageButton(chart)) {\r\n <mt-button\r\n icon=\"editor.palette\"\r\n [tooltip]=\"t('quickManage')\"\r\n (onClick)=\"openQuickManage($event, chart)\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n }\r\n\r\n <!-- Breadcrumb Button (for topbar) -->\r\n @if (\r\n chart.config?.clientConfig?.componentName === \"topbar\"\r\n ) {\r\n <mt-button\r\n icon=\"general.slash-divider\"\r\n [tooltip]=\"t('manageBreadcrumb')\"\r\n (onClick)=\"openBreadcrumb(chart)\"\r\n severity=\"info\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n }\r\n\r\n <!-- Edit Button -->\r\n @if (\r\n chart.config?.clientConfig?.componentName !== \"header\" &&\r\n chart.config?.clientConfig?.componentName !== \"topbar\" &&\r\n chart.config?.clientConfig?.componentName !== \"Group\"\r\n ) {\r\n <mt-button\r\n icon=\"general.edit-05\"\r\n [tooltip]=\"t('edit')\"\r\n (onClick)=\"editItem(chart)\"\r\n severity=\"warn\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n }\r\n\r\n <!-- Delete Button -->\r\n @if (\r\n chart.config?.clientConfig?.componentName === \"Group\"\r\n ) {\r\n <mt-button\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"t('removeGroup')\"\r\n (onClick)=\"removeGroup(chart)\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n } @else {\r\n <mt-button\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"t('delete')\"\r\n (onClick)=\"deleteItem(chart)\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n }\r\n\r\n <!-- Advanced/Dialog Actions Button (pipe handles all conditions) -->\r\n @let dialogActions =\r\n chart | getChartActions: chartActionsContext;\r\n @if (dialogActions.length > 0) {\r\n <mt-button\r\n icon=\"general.dots-vertical\"\r\n [tooltip]=\"t('advanced')\"\r\n (onClick)=\"advancedPopover.toggle($event)\"\r\n severity=\"info\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n />\r\n <p-popover #advancedPopover appendTo=\"body\">\r\n <div class=\"flex flex-col gap-1 min-w-[160px]\">\r\n @for (action of dialogActions; track action.label) {\r\n <mt-button\r\n [label]=\"action.label\"\r\n [icon]=\"action.icon\"\r\n severity=\"secondary\"\r\n [text]=\"true\"\r\n size=\"small\"\r\n (onClick)=\"\r\n action.command?.({\r\n originalEvent: $event,\r\n item: action,\r\n });\r\n advancedPopover.hide()\r\n \"\r\n />\r\n }\r\n </div>\r\n </p-popover>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Chart Rendering -->\r\n @if (!chart.loading) {\r\n <mt-dashboard-item\n [config]=\"chart.config\"\n [chartTypeId]=\"chart.chartTypeId\"\n [readonly]=\"readonly()\"\n [extraFilters]=\"extraFilters()\"\n [pageName]=\"\n pageConfig()?.name?.[languageCode()] ||\n pageConfig()?.name?.['en'] ||\n pageConfig()?.name?.['ar'] ||\r\n ''\r\n \"\r\n />\r\n } @else {\r\n <div class=\"flex items-center justify-center h-full\">\r\n <i\r\n class=\"mti mti-spinner-third mti-spin text-2xl text-primary\"\r\n ></i>\r\n </div>\r\n }\r\n\r\n <!-- Group Children -->\r\n @if (chart.config?.clientConfig?.componentName === \"Group\") {\r\n <div class=\"h-full flex flex-col p-2\">\r\n @for (\r\n childChart of charts()\r\n | filterByGroup\r\n : chart.config?.serviceConfig?.dashboardId;\r\n track childChart.config?.serviceConfig?.dashboardId;\r\n let idx = $index\r\n ) {\r\n @if (idx === chart.selectedGroupIndex) {\r\n <div class=\"flex-1 min-h-0\">\r\n <mt-dashboard-item\n [config]=\"childChart.config\"\n [chartTypeId]=\"childChart.chartTypeId\"\n [readonly]=\"readonly()\"\n [extraFilters]=\"extraFilters()\"\n [pageName]=\"\n pageConfig()?.name?.[languageCode()] ||\n pageConfig()?.name?.['en'] ||\n pageConfig()?.name?.['ar'] ||\r\n ''\r\n \"\r\n />\r\n </div>\r\n }\r\n }\r\n\r\n <!-- Group Tabs -->\r\n <div\r\n class=\"absolute bottom-0 left-0 right-0 flex gap-1 p-1 bg-surface-100 rounded-b-lg overflow-x-auto\"\r\n >\r\n @for (\r\n childChart of charts()\r\n | filterByGroup\r\n : chart.config?.serviceConfig?.dashboardId;\r\n track childChart.config?.serviceConfig?.dashboardId;\r\n let idx = $index\r\n ) {\r\n <mt-button\r\n [label]=\"\r\n childChart.config?.clientConfig?.title?.[\r\n languageCode()\r\n ] ||\r\n childChart.config?.clientConfig?.title?.['en'] ||\r\n 'Chart ' + (idx + 1)\r\n \"\r\n [severity]=\"\r\n idx === chart.selectedGroupIndex\r\n ? 'primary'\r\n : 'secondary'\r\n \"\r\n [text]=\"idx !== chart.selectedGroupIndex\"\r\n size=\"small\"\r\n (onClick)=\"chart.selectedGroupIndex = idx\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </gridster-item>\r\n }\r\n </gridster>\r\n\r\n <!-- Context Menu -->\r\n <p-contextMenu #contextMenu [model]=\"menuItems()\" appendTo=\"body\" />\r\n }\r\n\r\n <!-- Empty State -->\r\n @if (!loading() && !pageConfig()) {\r\n <div\r\n class=\"flex flex-col items-center justify-center h-64 text-muted-color\"\r\n >\r\n <i class=\"mti mti-layout text-6xl mb-4\"></i>\r\n <p class=\"text-lg\">{{ t(\"noPageSelected\") }}</p>\r\n <p class=\"text-sm\">{{ t(\"selectPageToStart\") }}</p>\r\n </div>\r\n }\r\n </div>\r\n </ng-template>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%}gridster{min-height:calc(100vh - 120px);height:100%}gridster .gridster-column{border-left:1px solid var(--p-primary-100)!important;border-right:1px solid var(--p-primary-100)!important}gridster .gridster-row{border-top:1px solid var(--p-primary-100)!important;border-bottom:1px solid var(--p-primary-100)!important}gridster.is-page{min-height:calc(100vh - 200px)}.mti-spin{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.gridster-drop-indicator{position:absolute;background:var(--p-primary-100);border:2px dashed var(--p-primary-400);border-radius:8px;pointer-events:none;z-index:100}\n"] }]
20948
+ }], ctorParameters: () => [], propDecorators: { isPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "isPage", required: false }] }], pageTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageTitle", required: false }] }], backButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "backButton", required: false }] }], pageId: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageId", required: false }] }], standalone: [{ type: i0.Input, args: [{ isSignal: true, alias: "standalone", required: false }] }], services: [{ type: i0.Input, args: [{ isSignal: true, alias: "services", required: false }] }], dashboardData: [{ type: i0.Input, args: [{ isSignal: true, alias: "dashboardData", required: false }] }, { type: i0.Output, args: ["dashboardDataChange"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], informativeContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "informativeContext", required: false }] }], extraFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "extraFilters", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], chartsChange: [{ type: i0.Output, args: ["chartsChange"] }], onSave: [{ type: i0.Output, args: ["onSave"] }], onBack: [{ type: i0.Output, args: ["onBack"] }], onAddChart: [{ type: i0.Output, args: ["onAddChart"] }], onEditChart: [{ type: i0.Output, args: ["onEditChart"] }], contextMenu: [{ type: i0.ViewChild, args: ['contextMenu', { isSignal: true }] }], gridsterContainer: [{ type: i0.ViewChild, args: ['gridsterContainer', { isSignal: true }] }], onKeyDown: [{
20910
20949
  type: HostListener,
20911
20950
  args: ['window:keydown', ['$event']]
20912
20951
  }] } });