@memberjunction/ng-dashboards 2.121.0 → 2.122.0

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.
Files changed (203) hide show
  1. package/dist/AI/components/agents/agent-configuration.component.d.ts +23 -11
  2. package/dist/AI/components/agents/agent-configuration.component.d.ts.map +1 -1
  3. package/dist/AI/components/agents/agent-configuration.component.js +122 -95
  4. package/dist/AI/components/agents/agent-configuration.component.js.map +1 -1
  5. package/dist/AI/components/agents/agent-editor.component.js +88 -90
  6. package/dist/AI/components/agents/agent-editor.component.js.map +1 -1
  7. package/dist/AI/components/agents/agent-filter-panel.component.js +2 -2
  8. package/dist/AI/components/execution-monitoring.component.d.ts +23 -10
  9. package/dist/AI/components/execution-monitoring.component.d.ts.map +1 -1
  10. package/dist/AI/components/execution-monitoring.component.js +143 -124
  11. package/dist/AI/components/execution-monitoring.component.js.map +1 -1
  12. package/dist/AI/components/models/model-management-v2.component.d.ts +17 -13
  13. package/dist/AI/components/models/model-management-v2.component.d.ts.map +1 -1
  14. package/dist/AI/components/models/model-management-v2.component.js +248 -266
  15. package/dist/AI/components/models/model-management-v2.component.js.map +1 -1
  16. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js +76 -78
  17. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js.map +1 -1
  18. package/dist/AI/components/prompts/prompt-filter-panel.component.js +2 -2
  19. package/dist/AI/components/prompts/prompt-management-v2.component.d.ts +17 -15
  20. package/dist/AI/components/prompts/prompt-management-v2.component.d.ts.map +1 -1
  21. package/dist/AI/components/prompts/prompt-management-v2.component.js +372 -397
  22. package/dist/AI/components/prompts/prompt-management-v2.component.js.map +1 -1
  23. package/dist/AI/components/prompts/prompt-version-control.component.js +100 -102
  24. package/dist/AI/components/prompts/prompt-version-control.component.js.map +1 -1
  25. package/dist/AI/components/system/system-config-filter-panel.component.js +2 -2
  26. package/dist/AI/components/system/system-configuration.component.d.ts +17 -10
  27. package/dist/AI/components/system/system-configuration.component.d.ts.map +1 -1
  28. package/dist/AI/components/system/system-configuration.component.js +82 -61
  29. package/dist/AI/components/system/system-configuration.component.js.map +1 -1
  30. package/dist/AI/components/widgets/kpi-card.component.d.ts.map +1 -1
  31. package/dist/AI/components/widgets/kpi-card.component.js +11 -7
  32. package/dist/AI/components/widgets/kpi-card.component.js.map +1 -1
  33. package/dist/AI/index.d.ts +4 -0
  34. package/dist/AI/index.d.ts.map +1 -1
  35. package/dist/AI/index.js +6 -1
  36. package/dist/AI/index.js.map +1 -1
  37. package/dist/Actions/components/actions-list-view.component.js +9 -9
  38. package/dist/Actions/components/actions-list-view.component.js.map +1 -1
  39. package/dist/Actions/components/actions-overview.component.d.ts +16 -13
  40. package/dist/Actions/components/actions-overview.component.d.ts.map +1 -1
  41. package/dist/Actions/components/actions-overview.component.js +62 -48
  42. package/dist/Actions/components/actions-overview.component.js.map +1 -1
  43. package/dist/Actions/components/categories-list-view.component.js +9 -9
  44. package/dist/Actions/components/categories-list-view.component.js.map +1 -1
  45. package/dist/Actions/components/code-management.component.d.ts +17 -7
  46. package/dist/Actions/components/code-management.component.d.ts.map +1 -1
  47. package/dist/Actions/components/code-management.component.js +45 -12
  48. package/dist/Actions/components/code-management.component.js.map +1 -1
  49. package/dist/Actions/components/entity-integration.component.d.ts +17 -7
  50. package/dist/Actions/components/entity-integration.component.d.ts.map +1 -1
  51. package/dist/Actions/components/entity-integration.component.js +45 -12
  52. package/dist/Actions/components/entity-integration.component.js.map +1 -1
  53. package/dist/Actions/components/execution-monitoring.component.d.ts +16 -10
  54. package/dist/Actions/components/execution-monitoring.component.d.ts.map +1 -1
  55. package/dist/Actions/components/execution-monitoring.component.js +56 -30
  56. package/dist/Actions/components/execution-monitoring.component.js.map +1 -1
  57. package/dist/Actions/components/scheduled-actions.component.d.ts +17 -7
  58. package/dist/Actions/components/scheduled-actions.component.d.ts.map +1 -1
  59. package/dist/Actions/components/scheduled-actions.component.js +45 -12
  60. package/dist/Actions/components/scheduled-actions.component.js.map +1 -1
  61. package/dist/Actions/components/security-permissions.component.d.ts +17 -7
  62. package/dist/Actions/components/security-permissions.component.d.ts.map +1 -1
  63. package/dist/Actions/components/security-permissions.component.js +45 -12
  64. package/dist/Actions/components/security-permissions.component.js.map +1 -1
  65. package/dist/Actions/index.d.ts +6 -1
  66. package/dist/Actions/index.d.ts.map +1 -1
  67. package/dist/Actions/index.js +9 -1
  68. package/dist/Actions/index.js.map +1 -1
  69. package/dist/ComponentStudio/component-studio-dashboard.component.d.ts +1 -1
  70. package/dist/ComponentStudio/component-studio-dashboard.component.js +8 -8
  71. package/dist/ComponentStudio/component-studio-dashboard.component.js.map +1 -1
  72. package/dist/ComponentStudio/components/artifact-load-dialog.component.js +52 -57
  73. package/dist/ComponentStudio/components/artifact-load-dialog.component.js.map +1 -1
  74. package/dist/ComponentStudio/components/artifact-selection-dialog.component.js +8 -9
  75. package/dist/ComponentStudio/components/artifact-selection-dialog.component.js.map +1 -1
  76. package/dist/DataExplorer/components/navigation-panel/navigation-panel.component.d.ts +107 -0
  77. package/dist/DataExplorer/components/navigation-panel/navigation-panel.component.d.ts.map +1 -0
  78. package/dist/DataExplorer/components/navigation-panel/navigation-panel.component.js +553 -0
  79. package/dist/DataExplorer/components/navigation-panel/navigation-panel.component.js.map +1 -0
  80. package/dist/DataExplorer/components/view-config-panel/view-config-panel.component.d.ts +179 -0
  81. package/dist/DataExplorer/components/view-config-panel/view-config-panel.component.d.ts.map +1 -0
  82. package/dist/DataExplorer/components/view-config-panel/view-config-panel.component.js +814 -0
  83. package/dist/DataExplorer/components/view-config-panel/view-config-panel.component.js.map +1 -0
  84. package/dist/DataExplorer/components/view-selector/view-selector.component.d.ts +151 -0
  85. package/dist/DataExplorer/components/view-selector/view-selector.component.d.ts.map +1 -0
  86. package/dist/DataExplorer/components/view-selector/view-selector.component.js +480 -0
  87. package/dist/DataExplorer/components/view-selector/view-selector.component.js.map +1 -0
  88. package/dist/DataExplorer/data-explorer-dashboard.component.d.ts +439 -0
  89. package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -0
  90. package/dist/DataExplorer/data-explorer-dashboard.component.js +2129 -0
  91. package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -0
  92. package/dist/DataExplorer/index.d.ts +5 -0
  93. package/dist/DataExplorer/index.d.ts.map +1 -0
  94. package/dist/DataExplorer/index.js +10 -0
  95. package/dist/DataExplorer/index.js.map +1 -0
  96. package/dist/DataExplorer/models/explorer-state.interface.d.ts +183 -0
  97. package/dist/DataExplorer/models/explorer-state.interface.d.ts.map +1 -0
  98. package/dist/DataExplorer/models/explorer-state.interface.js +31 -0
  99. package/dist/DataExplorer/models/explorer-state.interface.js.map +1 -0
  100. package/dist/DataExplorer/services/explorer-state.service.d.ts +232 -0
  101. package/dist/DataExplorer/services/explorer-state.service.d.ts.map +1 -0
  102. package/dist/DataExplorer/services/explorer-state.service.js +912 -0
  103. package/dist/DataExplorer/services/explorer-state.service.js.map +1 -0
  104. package/dist/EntityAdmin/components/entity-details.component.d.ts.map +1 -1
  105. package/dist/EntityAdmin/components/entity-details.component.js +11 -13
  106. package/dist/EntityAdmin/components/entity-details.component.js.map +1 -1
  107. package/dist/EntityAdmin/components/entity-filter-panel.component.js +2 -2
  108. package/dist/EntityAdmin/components/erd-composite.component.js +2 -2
  109. package/dist/EntityAdmin/components/erd-diagram.component.js +2 -2
  110. package/dist/EntityAdmin/entity-admin-dashboard.component.d.ts +1 -1
  111. package/dist/EntityAdmin/entity-admin-dashboard.component.d.ts.map +1 -1
  112. package/dist/EntityAdmin/entity-admin-dashboard.component.js +14 -15
  113. package/dist/EntityAdmin/entity-admin-dashboard.component.js.map +1 -1
  114. package/dist/Home/home-dashboard.component.d.ts +122 -0
  115. package/dist/Home/home-dashboard.component.d.ts.map +1 -0
  116. package/dist/Home/home-dashboard.component.js +698 -0
  117. package/dist/Home/home-dashboard.component.js.map +1 -0
  118. package/dist/Scheduling/components/index.d.ts +11 -0
  119. package/dist/Scheduling/components/index.d.ts.map +1 -0
  120. package/dist/Scheduling/components/index.js +13 -0
  121. package/dist/Scheduling/components/index.js.map +1 -0
  122. package/dist/Scheduling/components/scheduling-health-resource.component.d.ts +20 -0
  123. package/dist/Scheduling/components/scheduling-health-resource.component.d.ts.map +1 -0
  124. package/dist/Scheduling/components/scheduling-health-resource.component.js +55 -0
  125. package/dist/Scheduling/components/scheduling-health-resource.component.js.map +1 -0
  126. package/dist/Scheduling/components/scheduling-health.component.js +7 -8
  127. package/dist/Scheduling/components/scheduling-health.component.js.map +1 -1
  128. package/dist/Scheduling/components/scheduling-history-resource.component.d.ts +20 -0
  129. package/dist/Scheduling/components/scheduling-history-resource.component.d.ts.map +1 -0
  130. package/dist/Scheduling/components/scheduling-history-resource.component.js +55 -0
  131. package/dist/Scheduling/components/scheduling-history-resource.component.js.map +1 -0
  132. package/dist/Scheduling/components/scheduling-history.component.js +7 -8
  133. package/dist/Scheduling/components/scheduling-history.component.js.map +1 -1
  134. package/dist/Scheduling/components/scheduling-jobs-resource.component.d.ts +20 -0
  135. package/dist/Scheduling/components/scheduling-jobs-resource.component.d.ts.map +1 -0
  136. package/dist/Scheduling/components/scheduling-jobs-resource.component.js +55 -0
  137. package/dist/Scheduling/components/scheduling-jobs-resource.component.js.map +1 -0
  138. package/dist/Scheduling/components/scheduling-jobs.component.js +7 -8
  139. package/dist/Scheduling/components/scheduling-jobs.component.js.map +1 -1
  140. package/dist/Scheduling/components/scheduling-monitor-resource.component.d.ts +20 -0
  141. package/dist/Scheduling/components/scheduling-monitor-resource.component.d.ts.map +1 -0
  142. package/dist/Scheduling/components/scheduling-monitor-resource.component.js +55 -0
  143. package/dist/Scheduling/components/scheduling-monitor-resource.component.js.map +1 -0
  144. package/dist/Scheduling/components/scheduling-monitoring.component.js +7 -8
  145. package/dist/Scheduling/components/scheduling-monitoring.component.js.map +1 -1
  146. package/dist/Scheduling/components/scheduling-types-resource.component.d.ts +20 -0
  147. package/dist/Scheduling/components/scheduling-types-resource.component.d.ts.map +1 -0
  148. package/dist/Scheduling/components/scheduling-types-resource.component.js +55 -0
  149. package/dist/Scheduling/components/scheduling-types-resource.component.js.map +1 -0
  150. package/dist/Scheduling/components/scheduling-types.component.js +7 -8
  151. package/dist/Scheduling/components/scheduling-types.component.js.map +1 -1
  152. package/dist/Scheduling/scheduling-dashboard.component.d.ts +1 -1
  153. package/dist/Scheduling/scheduling-dashboard.component.js +3 -3
  154. package/dist/Testing/components/index.d.ts +11 -0
  155. package/dist/Testing/components/index.d.ts.map +1 -0
  156. package/dist/Testing/components/index.js +13 -0
  157. package/dist/Testing/components/index.js.map +1 -0
  158. package/dist/Testing/components/testing-analytics-resource.component.d.ts +20 -0
  159. package/dist/Testing/components/testing-analytics-resource.component.d.ts.map +1 -0
  160. package/dist/Testing/components/testing-analytics-resource.component.js +55 -0
  161. package/dist/Testing/components/testing-analytics-resource.component.js.map +1 -0
  162. package/dist/Testing/components/testing-execution-resource.component.d.ts +20 -0
  163. package/dist/Testing/components/testing-execution-resource.component.d.ts.map +1 -0
  164. package/dist/Testing/components/testing-execution-resource.component.js +55 -0
  165. package/dist/Testing/components/testing-execution-resource.component.js.map +1 -0
  166. package/dist/Testing/components/testing-execution.component.js +3 -3
  167. package/dist/Testing/components/testing-execution.component.js.map +1 -1
  168. package/dist/Testing/components/testing-feedback-resource.component.d.ts +20 -0
  169. package/dist/Testing/components/testing-feedback-resource.component.d.ts.map +1 -0
  170. package/dist/Testing/components/testing-feedback-resource.component.js +55 -0
  171. package/dist/Testing/components/testing-feedback-resource.component.js.map +1 -0
  172. package/dist/Testing/components/testing-overview-resource.component.d.ts +20 -0
  173. package/dist/Testing/components/testing-overview-resource.component.d.ts.map +1 -0
  174. package/dist/Testing/components/testing-overview-resource.component.js +55 -0
  175. package/dist/Testing/components/testing-overview-resource.component.js.map +1 -0
  176. package/dist/Testing/components/testing-version-resource.component.d.ts +20 -0
  177. package/dist/Testing/components/testing-version-resource.component.d.ts.map +1 -0
  178. package/dist/Testing/components/testing-version-resource.component.js +55 -0
  179. package/dist/Testing/components/testing-version-resource.component.js.map +1 -0
  180. package/dist/Testing/testing-dashboard.component.d.ts +1 -1
  181. package/dist/Testing/testing-dashboard.component.js +23 -25
  182. package/dist/Testing/testing-dashboard.component.js.map +1 -1
  183. package/dist/module.d.ts +83 -66
  184. package/dist/module.d.ts.map +1 -1
  185. package/dist/module.js +137 -19
  186. package/dist/module.js.map +1 -1
  187. package/dist/public-api.d.ts +6 -4
  188. package/dist/public-api.d.ts.map +1 -1
  189. package/dist/public-api.js +41 -13
  190. package/dist/public-api.js.map +1 -1
  191. package/package.json +17 -14
  192. package/dist/AI/ai-dashboard.component.d.ts +0 -62
  193. package/dist/AI/ai-dashboard.component.d.ts.map +0 -1
  194. package/dist/AI/ai-dashboard.component.js +0 -338
  195. package/dist/AI/ai-dashboard.component.js.map +0 -1
  196. package/dist/Actions/actions-management-dashboard.component.d.ts +0 -52
  197. package/dist/Actions/actions-management-dashboard.component.d.ts.map +0 -1
  198. package/dist/Actions/actions-management-dashboard.component.js +0 -308
  199. package/dist/Actions/actions-management-dashboard.component.js.map +0 -1
  200. package/dist/generic/base-dashboard.d.ts +0 -65
  201. package/dist/generic/base-dashboard.d.ts.map +0 -1
  202. package/dist/generic/base-dashboard.js +0 -74
  203. package/dist/generic/base-dashboard.js.map +0 -1
@@ -0,0 +1,2129 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Component, Input, HostListener, ViewChild } from '@angular/core';
8
+ import { NavigationEnd } from '@angular/router';
9
+ import { trigger, transition, style, animate } from '@angular/animations';
10
+ import { Subject } from 'rxjs';
11
+ import { takeUntil, debounceTime, distinctUntilChanged, filter } from 'rxjs/operators';
12
+ import { BaseDashboard } from '@memberjunction/ng-shared';
13
+ import { RegisterClass } from '@memberjunction/global';
14
+ import { Metadata, RunView, CompositeKey } from '@memberjunction/core';
15
+ import { EntityViewerComponent } from '@memberjunction/ng-entity-viewer';
16
+ import { ViewSelectorComponent } from './components/view-selector/view-selector.component';
17
+ import * as i0 from "@angular/core";
18
+ import * as i1 from "./services/explorer-state.service";
19
+ import * as i2 from "@angular/router";
20
+ import * as i3 from "@memberjunction/ng-shared-generic";
21
+ import * as i4 from "@angular/forms";
22
+ import * as i5 from "@memberjunction/ng-entity-viewer";
23
+ import * as i6 from "./components/navigation-panel/navigation-panel.component";
24
+ import * as i7 from "./components/view-selector/view-selector.component";
25
+ import * as i8 from "./components/view-config-panel/view-config-panel.component";
26
+ import * as i9 from "@angular/common";
27
+ const _c0 = ["filterInput"];
28
+ const _forTrack0 = ($index, $item) => $item.label;
29
+ const _forTrack1 = ($index, $item) => $item.ID;
30
+ const _forTrack2 = ($index, $item) => $item.entityId + "|" + $item.recordId;
31
+ const _forTrack3 = ($index, $item) => $item.entityId;
32
+ const _forTrack4 = ($index, $item) => $item.userFavoriteId;
33
+ function DataExplorerDashboardComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
34
+ const _r1 = i0.ɵɵgetCurrentView();
35
+ i0.ɵɵelementStart(0, "div", 16)(1, "mj-explorer-navigation-panel", 17);
36
+ i0.ɵɵlistener("entitySelected", function DataExplorerDashboardComponent_Conditional_1_Template_mj_explorer_navigation_panel_entitySelected_1_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onEntitySelected($event)); })("toggleCollapse", function DataExplorerDashboardComponent_Conditional_1_Template_mj_explorer_navigation_panel_toggleCollapse_1_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.toggleNavigationPanel()); })("sectionToggled", function DataExplorerDashboardComponent_Conditional_1_Template_mj_explorer_navigation_panel_sectionToggled_1_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.stateService.toggleSection($event)); })("openRecord", function DataExplorerDashboardComponent_Conditional_1_Template_mj_explorer_navigation_panel_openRecord_1_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onOpenRecordFromNav($event)); })("selectRecord", function DataExplorerDashboardComponent_Conditional_1_Template_mj_explorer_navigation_panel_selectRecord_1_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onSelectRecordFromNav($event)); })("expandAndFocus", function DataExplorerDashboardComponent_Conditional_1_Template_mj_explorer_navigation_panel_expandAndFocus_1_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onExpandAndFocus($event)); });
37
+ i0.ɵɵelementEnd()();
38
+ } if (rf & 2) {
39
+ const ctx_r1 = i0.ɵɵnextContext();
40
+ i0.ɵɵstyleProp("width", ctx_r1.state.navigationPanelCollapsed ? 48 : ctx_r1.state.navigationPanelWidth, "px");
41
+ i0.ɵɵclassProp("collapsed", ctx_r1.state.navigationPanelCollapsed);
42
+ i0.ɵɵproperty("@slideInLeft", undefined);
43
+ i0.ɵɵadvance();
44
+ i0.ɵɵproperty("entities", ctx_r1.entities)("selectedEntityName", ctx_r1.state.selectedEntityName)("favorites", ctx_r1.state.favorites)("recentItems", ctx_r1.state.recentItems)("collapsed", ctx_r1.state.navigationPanelCollapsed)("allowedEntityNames", ctx_r1.allowedEntityNames)("favoritesSectionExpanded", ctx_r1.state.favoritesSectionExpanded)("recentSectionExpanded", ctx_r1.state.recentSectionExpanded)("entitiesSectionExpanded", ctx_r1.state.entitiesSectionExpanded)("viewsSectionExpanded", ctx_r1.state.viewsSectionExpanded);
45
+ } }
46
+ function DataExplorerDashboardComponent_Conditional_3_For_2_Conditional_1_Template(rf, ctx) { if (rf & 1) {
47
+ i0.ɵɵelement(0, "i", 22);
48
+ } if (rf & 2) {
49
+ const crumb_r5 = i0.ɵɵnextContext().$implicit;
50
+ i0.ɵɵclassMap(crumb_r5.icon);
51
+ } }
52
+ function DataExplorerDashboardComponent_Conditional_3_For_2_Conditional_4_Template(rf, ctx) { if (rf & 1) {
53
+ i0.ɵɵelement(0, "i", 21);
54
+ } }
55
+ function DataExplorerDashboardComponent_Conditional_3_For_2_Template(rf, ctx) { if (rf & 1) {
56
+ const _r3 = i0.ɵɵgetCurrentView();
57
+ i0.ɵɵelementStart(0, "span", 18);
58
+ i0.ɵɵlistener("click", function DataExplorerDashboardComponent_Conditional_3_For_2_Template_span_click_0_listener() { const ctx_r3 = i0.ɵɵrestoreView(_r3); const crumb_r5 = ctx_r3.$implicit; const ɵ$index_13_r6 = ctx_r3.$index; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onBreadcrumbClick(crumb_r5, ɵ$index_13_r6)); });
59
+ i0.ɵɵtemplate(1, DataExplorerDashboardComponent_Conditional_3_For_2_Conditional_1_Template, 1, 2, "i", 19);
60
+ i0.ɵɵelementStart(2, "span", 20);
61
+ i0.ɵɵtext(3);
62
+ i0.ɵɵelementEnd()();
63
+ i0.ɵɵtemplate(4, DataExplorerDashboardComponent_Conditional_3_For_2_Conditional_4_Template, 1, 0, "i", 21);
64
+ } if (rf & 2) {
65
+ const crumb_r5 = ctx.$implicit;
66
+ const ɵ$index_13_r6 = ctx.$index;
67
+ const ɵ$count_13_r7 = ctx.$count;
68
+ i0.ɵɵclassProp("clickable", !(ɵ$index_13_r6 === ɵ$count_13_r7 - 1))("current", ɵ$index_13_r6 === ɵ$count_13_r7 - 1);
69
+ i0.ɵɵproperty("title", crumb_r5.label);
70
+ i0.ɵɵadvance();
71
+ i0.ɵɵconditional(crumb_r5.icon ? 1 : -1);
72
+ i0.ɵɵadvance(2);
73
+ i0.ɵɵtextInterpolate(crumb_r5.label);
74
+ i0.ɵɵadvance();
75
+ i0.ɵɵconditional(!(ɵ$index_13_r6 === ɵ$count_13_r7 - 1) ? 4 : -1);
76
+ } }
77
+ function DataExplorerDashboardComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
78
+ i0.ɵɵelementStart(0, "div", 4);
79
+ i0.ɵɵrepeaterCreate(1, DataExplorerDashboardComponent_Conditional_3_For_2_Template, 5, 8, null, null, _forTrack0);
80
+ i0.ɵɵelementEnd();
81
+ } if (rf & 2) {
82
+ const ctx_r1 = i0.ɵɵnextContext();
83
+ i0.ɵɵadvance();
84
+ i0.ɵɵrepeater(ctx_r1.breadcrumbs);
85
+ } }
86
+ function DataExplorerDashboardComponent_Conditional_6_Conditional_3_Template(rf, ctx) { if (rf & 1) {
87
+ i0.ɵɵelementStart(0, "span", 25);
88
+ i0.ɵɵtext(1);
89
+ i0.ɵɵpipe(2, "number");
90
+ i0.ɵɵpipe(3, "number");
91
+ i0.ɵɵelementEnd();
92
+ } if (rf & 2) {
93
+ const ctx_r1 = i0.ɵɵnextContext(2);
94
+ i0.ɵɵadvance();
95
+ i0.ɵɵtextInterpolate2("", i0.ɵɵpipeBind1(2, 2, ctx_r1.filteredRecordCount), " of ", i0.ɵɵpipeBind1(3, 4, ctx_r1.totalRecordCount), " records");
96
+ } }
97
+ function DataExplorerDashboardComponent_Conditional_6_Conditional_4_Template(rf, ctx) { if (rf & 1) {
98
+ i0.ɵɵelementStart(0, "span", 25);
99
+ i0.ɵɵtext(1);
100
+ i0.ɵɵpipe(2, "number");
101
+ i0.ɵɵelementEnd();
102
+ } if (rf & 2) {
103
+ const ctx_r1 = i0.ɵɵnextContext(2);
104
+ i0.ɵɵadvance();
105
+ i0.ɵɵtextInterpolate1("", i0.ɵɵpipeBind1(2, 1, ctx_r1.totalRecordCount), " records");
106
+ } }
107
+ function DataExplorerDashboardComponent_Conditional_6_Template(rf, ctx) { if (rf & 1) {
108
+ const _r8 = i0.ɵɵgetCurrentView();
109
+ i0.ɵɵelement(0, "i", 23);
110
+ i0.ɵɵelementStart(1, "h2", 24);
111
+ i0.ɵɵtext(2);
112
+ i0.ɵɵelementEnd();
113
+ i0.ɵɵtemplate(3, DataExplorerDashboardComponent_Conditional_6_Conditional_3_Template, 4, 6, "span", 25)(4, DataExplorerDashboardComponent_Conditional_6_Conditional_4_Template, 3, 3, "span", 25);
114
+ i0.ɵɵelementStart(5, "mj-view-selector", 26);
115
+ i0.ɵɵlistener("viewSelected", function DataExplorerDashboardComponent_Conditional_6_Template_mj_view_selector_viewSelected_5_listener($event) { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onViewSelected($event)); })("saveViewRequested", function DataExplorerDashboardComponent_Conditional_6_Template_mj_view_selector_saveViewRequested_5_listener($event) { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onSaveViewRequested($event)); })("manageViewsRequested", function DataExplorerDashboardComponent_Conditional_6_Template_mj_view_selector_manageViewsRequested_5_listener() { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onManageViewsRequested()); })("openInTabRequested", function DataExplorerDashboardComponent_Conditional_6_Template_mj_view_selector_openInTabRequested_5_listener($event) { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onOpenInTabRequested($event)); })("configureViewRequested", function DataExplorerDashboardComponent_Conditional_6_Template_mj_view_selector_configureViewRequested_5_listener() { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onConfigureViewRequested()); });
116
+ i0.ɵɵelementEnd();
117
+ } if (rf & 2) {
118
+ const ctx_r1 = i0.ɵɵnextContext();
119
+ i0.ɵɵclassMap(ctx_r1.getEntityIcon(ctx_r1.selectedEntity));
120
+ i0.ɵɵadvance(2);
121
+ i0.ɵɵtextInterpolate(ctx_r1.selectedEntity.Name);
122
+ i0.ɵɵadvance();
123
+ i0.ɵɵconditional(ctx_r1.debouncedFilterText && ctx_r1.filteredRecordCount !== ctx_r1.totalRecordCount ? 3 : 4);
124
+ i0.ɵɵadvance(2);
125
+ i0.ɵɵproperty("entity", ctx_r1.selectedEntity)("selectedViewId", ctx_r1.state.selectedViewId)("viewModified", ctx_r1.state.viewModified);
126
+ } }
127
+ function DataExplorerDashboardComponent_Conditional_7_Conditional_0_Template(rf, ctx) { if (rf & 1) {
128
+ i0.ɵɵelement(0, "i", 23);
129
+ } if (rf & 2) {
130
+ const ctx_r1 = i0.ɵɵnextContext(2);
131
+ i0.ɵɵclassMap(ctx_r1.displayIcon);
132
+ } }
133
+ function DataExplorerDashboardComponent_Conditional_7_Template(rf, ctx) { if (rf & 1) {
134
+ i0.ɵɵtemplate(0, DataExplorerDashboardComponent_Conditional_7_Conditional_0_Template, 1, 2, "i", 27);
135
+ i0.ɵɵelementStart(1, "h2", 24);
136
+ i0.ɵɵtext(2);
137
+ i0.ɵɵelementEnd();
138
+ i0.ɵɵelementStart(3, "span", 25);
139
+ i0.ɵɵtext(4);
140
+ i0.ɵɵelementEnd();
141
+ } if (rf & 2) {
142
+ const ctx_r1 = i0.ɵɵnextContext();
143
+ i0.ɵɵconditional(ctx_r1.displayIcon ? 0 : -1);
144
+ i0.ɵɵadvance(2);
145
+ i0.ɵɵtextInterpolate(ctx_r1.displayTitle);
146
+ i0.ɵɵadvance(2);
147
+ i0.ɵɵtextInterpolate1("", ctx_r1.entities.length, " entities available");
148
+ } }
149
+ function DataExplorerDashboardComponent_Conditional_9_Conditional_4_Template(rf, ctx) { if (rf & 1) {
150
+ const _r10 = i0.ɵɵgetCurrentView();
151
+ i0.ɵɵelementStart(0, "button", 31);
152
+ i0.ɵɵlistener("click", function DataExplorerDashboardComponent_Conditional_9_Conditional_4_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r10); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onSmartFilterChanged("")); });
153
+ i0.ɵɵelement(1, "i", 32);
154
+ i0.ɵɵelementEnd();
155
+ } }
156
+ function DataExplorerDashboardComponent_Conditional_9_Template(rf, ctx) { if (rf & 1) {
157
+ const _r9 = i0.ɵɵgetCurrentView();
158
+ i0.ɵɵelementStart(0, "div", 8);
159
+ i0.ɵɵelement(1, "i", 28);
160
+ i0.ɵɵelementStart(2, "input", 29, 0);
161
+ i0.ɵɵlistener("ngModelChange", function DataExplorerDashboardComponent_Conditional_9_Template_input_ngModelChange_2_listener($event) { i0.ɵɵrestoreView(_r9); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onSmartFilterChanged($event)); });
162
+ i0.ɵɵelementEnd();
163
+ i0.ɵɵtemplate(4, DataExplorerDashboardComponent_Conditional_9_Conditional_4_Template, 2, 0, "button", 30);
164
+ i0.ɵɵelementEnd();
165
+ } if (rf & 2) {
166
+ const ctx_r1 = i0.ɵɵnextContext();
167
+ i0.ɵɵadvance(2);
168
+ i0.ɵɵproperty("ngModel", ctx_r1.state.smartFilterPrompt);
169
+ i0.ɵɵadvance(2);
170
+ i0.ɵɵconditional(ctx_r1.state.smartFilterPrompt ? 4 : -1);
171
+ } }
172
+ function DataExplorerDashboardComponent_Conditional_10_Conditional_4_Template(rf, ctx) { if (rf & 1) {
173
+ const _r12 = i0.ɵɵgetCurrentView();
174
+ i0.ɵɵelementStart(0, "button", 31);
175
+ i0.ɵɵlistener("click", function DataExplorerDashboardComponent_Conditional_10_Conditional_4_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r12); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.entityFilterText = ""); });
176
+ i0.ɵɵelement(1, "i", 32);
177
+ i0.ɵɵelementEnd();
178
+ } }
179
+ function DataExplorerDashboardComponent_Conditional_10_Template(rf, ctx) { if (rf & 1) {
180
+ const _r11 = i0.ɵɵgetCurrentView();
181
+ i0.ɵɵelementStart(0, "div", 8);
182
+ i0.ɵɵelement(1, "i", 28);
183
+ i0.ɵɵelementStart(2, "input", 33, 0);
184
+ i0.ɵɵtwoWayListener("ngModelChange", function DataExplorerDashboardComponent_Conditional_10_Template_input_ngModelChange_2_listener($event) { i0.ɵɵrestoreView(_r11); const ctx_r1 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r1.entityFilterText, $event) || (ctx_r1.entityFilterText = $event); return i0.ɵɵresetView($event); });
185
+ i0.ɵɵelementEnd();
186
+ i0.ɵɵtemplate(4, DataExplorerDashboardComponent_Conditional_10_Conditional_4_Template, 2, 0, "button", 30);
187
+ i0.ɵɵelementEnd();
188
+ } if (rf & 2) {
189
+ const ctx_r1 = i0.ɵɵnextContext();
190
+ i0.ɵɵadvance(2);
191
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.entityFilterText);
192
+ i0.ɵɵadvance(2);
193
+ i0.ɵɵconditional(ctx_r1.entityFilterText ? 4 : -1);
194
+ } }
195
+ function DataExplorerDashboardComponent_Conditional_12_Template(rf, ctx) { if (rf & 1) {
196
+ const _r13 = i0.ɵɵgetCurrentView();
197
+ i0.ɵɵelementStart(0, "div", 10)(1, "button", 34);
198
+ i0.ɵɵlistener("click", function DataExplorerDashboardComponent_Conditional_12_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r13); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onViewModeChanged("grid")); });
199
+ i0.ɵɵelement(2, "i", 35);
200
+ i0.ɵɵelementEnd();
201
+ i0.ɵɵelementStart(3, "button", 36);
202
+ i0.ɵɵlistener("click", function DataExplorerDashboardComponent_Conditional_12_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r13); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onViewModeChanged("cards")); });
203
+ i0.ɵɵelement(4, "i", 37);
204
+ i0.ɵɵelementEnd()();
205
+ } if (rf & 2) {
206
+ const ctx_r1 = i0.ɵɵnextContext();
207
+ i0.ɵɵadvance();
208
+ i0.ɵɵclassProp("active", ctx_r1.state.viewMode === "grid");
209
+ i0.ɵɵadvance(2);
210
+ i0.ɵɵclassProp("active", ctx_r1.state.viewMode === "cards");
211
+ } }
212
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_1_Template(rf, ctx) { if (rf & 1) {
213
+ i0.ɵɵelementStart(0, "div", 38);
214
+ i0.ɵɵelement(1, "mj-loading", 40);
215
+ i0.ɵɵelementEnd();
216
+ } }
217
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_2_Conditional_5_For_4_Template(rf, ctx) { if (rf & 1) {
218
+ const _r15 = i0.ɵɵgetCurrentView();
219
+ i0.ɵɵelementStart(0, "button", 61);
220
+ i0.ɵɵlistener("click", function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_2_Conditional_5_For_4_Template_button_click_0_listener() { const entity_r16 = i0.ɵɵrestoreView(_r15).$implicit; const ctx_r1 = i0.ɵɵnextContext(6); return i0.ɵɵresetView(ctx_r1.setRecentRecordsEntityFilter(entity_r16.entityId)); });
221
+ i0.ɵɵelement(1, "i");
222
+ i0.ɵɵelementEnd();
223
+ } if (rf & 2) {
224
+ const entity_r16 = ctx.$implicit;
225
+ const ctx_r1 = i0.ɵɵnextContext(6);
226
+ i0.ɵɵclassProp("active", ctx_r1.recentRecordsEntityFilter === entity_r16.entityId);
227
+ i0.ɵɵproperty("title", entity_r16.entityName);
228
+ i0.ɵɵadvance();
229
+ i0.ɵɵclassMap(entity_r16.icon);
230
+ } }
231
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_2_Conditional_5_Template(rf, ctx) { if (rf & 1) {
232
+ const _r14 = i0.ɵɵgetCurrentView();
233
+ i0.ɵɵelementStart(0, "div", 55)(1, "button", 58);
234
+ i0.ɵɵlistener("click", function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_2_Conditional_5_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r14); const ctx_r1 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r1.setRecentRecordsEntityFilter(null)); });
235
+ i0.ɵɵelement(2, "i", 59);
236
+ i0.ɵɵelementEnd();
237
+ i0.ɵɵrepeaterCreate(3, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_2_Conditional_5_For_4_Template, 2, 5, "button", 60, _forTrack3);
238
+ i0.ɵɵelementEnd();
239
+ } if (rf & 2) {
240
+ const ctx_r1 = i0.ɵɵnextContext(5);
241
+ i0.ɵɵadvance();
242
+ i0.ɵɵclassProp("active", ctx_r1.recentRecordsEntityFilter === null);
243
+ i0.ɵɵadvance(2);
244
+ i0.ɵɵrepeater(ctx_r1.uniqueRecentRecordEntities);
245
+ } }
246
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_2_For_8_Template(rf, ctx) { if (rf & 1) {
247
+ const _r17 = i0.ɵɵgetCurrentView();
248
+ i0.ɵɵelementStart(0, "div", 62);
249
+ i0.ɵɵlistener("click", function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_2_For_8_Template_div_click_0_listener() { const record_r18 = i0.ɵɵrestoreView(_r17).$implicit; const ctx_r1 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r1.onRecentRecordClick(record_r18)); });
250
+ i0.ɵɵelementStart(1, "div", 63);
251
+ i0.ɵɵelement(2, "i");
252
+ i0.ɵɵelementEnd();
253
+ i0.ɵɵelementStart(3, "div", 64)(4, "span", 65);
254
+ i0.ɵɵtext(5);
255
+ i0.ɵɵelementEnd();
256
+ i0.ɵɵelementStart(6, "span", 66);
257
+ i0.ɵɵtext(7);
258
+ i0.ɵɵelementEnd()();
259
+ i0.ɵɵelementStart(8, "span", 67);
260
+ i0.ɵɵtext(9);
261
+ i0.ɵɵelementEnd()();
262
+ } if (rf & 2) {
263
+ const record_r18 = ctx.$implicit;
264
+ const ctx_r1 = i0.ɵɵnextContext(5);
265
+ i0.ɵɵproperty("title", record_r18.entityName + " - " + record_r18.recordId);
266
+ i0.ɵɵadvance(2);
267
+ i0.ɵɵclassMap(ctx_r1.getEntityIconById(record_r18.entityId));
268
+ i0.ɵɵadvance(3);
269
+ i0.ɵɵtextInterpolate(record_r18.recordName || record_r18.recordId);
270
+ i0.ɵɵadvance(2);
271
+ i0.ɵɵtextInterpolate(record_r18.entityName);
272
+ i0.ɵɵadvance(2);
273
+ i0.ɵɵtextInterpolate(ctx_r1.formatRelativeTime(record_r18.latestAt));
274
+ } }
275
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_2_Template(rf, ctx) { if (rf & 1) {
276
+ i0.ɵɵelementStart(0, "div", 39)(1, "div", 53);
277
+ i0.ɵɵelement(2, "i", 54);
278
+ i0.ɵɵelementStart(3, "h3", 45);
279
+ i0.ɵɵtext(4, "Recent Records");
280
+ i0.ɵɵelementEnd();
281
+ i0.ɵɵtemplate(5, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_2_Conditional_5_Template, 5, 2, "div", 55);
282
+ i0.ɵɵelementEnd();
283
+ i0.ɵɵelementStart(6, "div", 56);
284
+ i0.ɵɵrepeaterCreate(7, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_2_For_8_Template, 10, 6, "div", 57, _forTrack2);
285
+ i0.ɵɵelementEnd()();
286
+ } if (rf & 2) {
287
+ const ctx_r1 = i0.ɵɵnextContext(4);
288
+ i0.ɵɵadvance(5);
289
+ i0.ɵɵconditional(ctx_r1.showRecentRecordsEntityFilter ? 5 : -1);
290
+ i0.ɵɵadvance(2);
291
+ i0.ɵɵrepeater(ctx_r1.filteredRecentRecords);
292
+ } }
293
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_3_For_7_Template(rf, ctx) { if (rf & 1) {
294
+ const _r19 = i0.ɵɵgetCurrentView();
295
+ i0.ɵɵelementStart(0, "div", 62);
296
+ i0.ɵɵlistener("click", function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_3_For_7_Template_div_click_0_listener() { const record_r20 = i0.ɵɵrestoreView(_r19).$implicit; const ctx_r1 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r1.onFavoriteRecordClick(record_r20)); });
297
+ i0.ɵɵelementStart(1, "div", 63);
298
+ i0.ɵɵelement(2, "i");
299
+ i0.ɵɵelementEnd();
300
+ i0.ɵɵelementStart(3, "div", 64)(4, "span", 65);
301
+ i0.ɵɵtext(5);
302
+ i0.ɵɵelementEnd();
303
+ i0.ɵɵelementStart(6, "span", 66);
304
+ i0.ɵɵtext(7);
305
+ i0.ɵɵelementEnd()()();
306
+ } if (rf & 2) {
307
+ const record_r20 = ctx.$implicit;
308
+ const ctx_r1 = i0.ɵɵnextContext(5);
309
+ i0.ɵɵproperty("title", record_r20.entityName + " - " + record_r20.recordId);
310
+ i0.ɵɵadvance(2);
311
+ i0.ɵɵclassMap(ctx_r1.getEntityIconById(record_r20.entityId));
312
+ i0.ɵɵadvance(3);
313
+ i0.ɵɵtextInterpolate(record_r20.recordName || record_r20.recordId);
314
+ i0.ɵɵadvance(2);
315
+ i0.ɵɵtextInterpolate(record_r20.entityName);
316
+ } }
317
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_3_Template(rf, ctx) { if (rf & 1) {
318
+ i0.ɵɵelementStart(0, "div", 39)(1, "div", 53);
319
+ i0.ɵɵelement(2, "i", 68);
320
+ i0.ɵɵelementStart(3, "h3", 45);
321
+ i0.ɵɵtext(4, "Favorite Records");
322
+ i0.ɵɵelementEnd()();
323
+ i0.ɵɵelementStart(5, "div", 56);
324
+ i0.ɵɵrepeaterCreate(6, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_3_For_7_Template, 8, 5, "div", 57, _forTrack4);
325
+ i0.ɵɵelementEnd()();
326
+ } if (rf & 2) {
327
+ const ctx_r1 = i0.ɵɵnextContext(4);
328
+ i0.ɵɵadvance(6);
329
+ i0.ɵɵrepeater(ctx_r1.favoriteRecords);
330
+ } }
331
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_5_For_7_Template(rf, ctx) { if (rf & 1) {
332
+ const _r21 = i0.ɵɵgetCurrentView();
333
+ i0.ɵɵelementStart(0, "div", 72);
334
+ i0.ɵɵlistener("click", function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_5_For_7_Template_div_click_0_listener() { const entity_r22 = i0.ɵɵrestoreView(_r21).$implicit; const ctx_r1 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r1.onEntitySelected(entity_r22)); });
335
+ i0.ɵɵelementStart(1, "div", 73);
336
+ i0.ɵɵelement(2, "i");
337
+ i0.ɵɵelementEnd();
338
+ i0.ɵɵelementStart(3, "div", 74)(4, "span", 75);
339
+ i0.ɵɵtext(5);
340
+ i0.ɵɵelementEnd()();
341
+ i0.ɵɵelementStart(6, "button", 76);
342
+ i0.ɵɵlistener("click", function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_5_For_7_Template_button_click_6_listener($event) { const entity_r22 = i0.ɵɵrestoreView(_r21).$implicit; const ctx_r1 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r1.toggleEntityFavorite(entity_r22, $event)); });
343
+ i0.ɵɵelement(7, "i");
344
+ i0.ɵɵelementEnd()();
345
+ } if (rf & 2) {
346
+ const entity_r22 = ctx.$implicit;
347
+ const ctx_r1 = i0.ɵɵnextContext(5);
348
+ i0.ɵɵproperty("title", entity_r22.Description || entity_r22.Name);
349
+ i0.ɵɵadvance(2);
350
+ i0.ɵɵclassMap(ctx_r1.getEntityIcon(entity_r22));
351
+ i0.ɵɵadvance(3);
352
+ i0.ɵɵtextInterpolate(entity_r22.Name);
353
+ i0.ɵɵadvance();
354
+ i0.ɵɵclassProp("favorited", ctx_r1.isEntityFavorited(entity_r22));
355
+ i0.ɵɵproperty("title", ctx_r1.isEntityFavorited(entity_r22) ? "Remove from favorites" : "Add to favorites");
356
+ i0.ɵɵadvance();
357
+ i0.ɵɵclassMap(ctx_r1.isEntityFavorited(entity_r22) ? "fa-solid fa-star" : "fa-regular fa-star");
358
+ } }
359
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_5_Template(rf, ctx) { if (rf & 1) {
360
+ i0.ɵɵelementStart(0, "div", 39)(1, "div", 53);
361
+ i0.ɵɵelement(2, "i", 69);
362
+ i0.ɵɵelementStart(3, "h3", 45);
363
+ i0.ɵɵtext(4, "Recent Entities");
364
+ i0.ɵɵelementEnd()();
365
+ i0.ɵɵelementStart(5, "div", 70);
366
+ i0.ɵɵrepeaterCreate(6, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_5_For_7_Template, 8, 9, "div", 71, _forTrack1);
367
+ i0.ɵɵelementEnd()();
368
+ } if (rf & 2) {
369
+ const ctx_r1 = i0.ɵɵnextContext(4);
370
+ i0.ɵɵadvance(6);
371
+ i0.ɵɵrepeater(ctx_r1.recentEntities);
372
+ } }
373
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_6_For_7_Template(rf, ctx) { if (rf & 1) {
374
+ const _r23 = i0.ɵɵgetCurrentView();
375
+ i0.ɵɵelementStart(0, "div", 72);
376
+ i0.ɵɵlistener("click", function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_6_For_7_Template_div_click_0_listener() { const entity_r24 = i0.ɵɵrestoreView(_r23).$implicit; const ctx_r1 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r1.onEntitySelected(entity_r24)); });
377
+ i0.ɵɵelementStart(1, "div", 73);
378
+ i0.ɵɵelement(2, "i");
379
+ i0.ɵɵelementEnd();
380
+ i0.ɵɵelementStart(3, "div", 74)(4, "span", 75);
381
+ i0.ɵɵtext(5);
382
+ i0.ɵɵelementEnd()();
383
+ i0.ɵɵelementStart(6, "button", 77);
384
+ i0.ɵɵlistener("click", function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_6_For_7_Template_button_click_6_listener($event) { const entity_r24 = i0.ɵɵrestoreView(_r23).$implicit; const ctx_r1 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r1.toggleEntityFavorite(entity_r24, $event)); });
385
+ i0.ɵɵelement(7, "i", 78);
386
+ i0.ɵɵelementEnd()();
387
+ } if (rf & 2) {
388
+ const entity_r24 = ctx.$implicit;
389
+ const ctx_r1 = i0.ɵɵnextContext(5);
390
+ i0.ɵɵproperty("title", entity_r24.Description || entity_r24.Name);
391
+ i0.ɵɵadvance(2);
392
+ i0.ɵɵclassMap(ctx_r1.getEntityIcon(entity_r24));
393
+ i0.ɵɵadvance(3);
394
+ i0.ɵɵtextInterpolate(entity_r24.Name);
395
+ } }
396
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_6_Template(rf, ctx) { if (rf & 1) {
397
+ i0.ɵɵelementStart(0, "div", 39)(1, "div", 53);
398
+ i0.ɵɵelement(2, "i", 68);
399
+ i0.ɵɵelementStart(3, "h3", 45);
400
+ i0.ɵɵtext(4, "Favorite Entities");
401
+ i0.ɵɵelementEnd()();
402
+ i0.ɵɵelementStart(5, "div", 70);
403
+ i0.ɵɵrepeaterCreate(6, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_6_For_7_Template, 8, 4, "div", 71, _forTrack1);
404
+ i0.ɵɵelementEnd()();
405
+ } if (rf & 2) {
406
+ const ctx_r1 = i0.ɵɵnextContext(4);
407
+ i0.ɵɵadvance(6);
408
+ i0.ɵɵrepeater(ctx_r1.favoriteEntities);
409
+ } }
410
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Template(rf, ctx) { if (rf & 1) {
411
+ i0.ɵɵelementStart(0, "div", 41)(1, "div", 52);
412
+ i0.ɵɵtemplate(2, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_2_Template, 9, 1, "div", 39)(3, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_3_Template, 8, 0, "div", 39);
413
+ i0.ɵɵelementEnd();
414
+ i0.ɵɵelementStart(4, "div", 52);
415
+ i0.ɵɵtemplate(5, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_5_Template, 8, 0, "div", 39)(6, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Conditional_6_Template, 8, 0, "div", 39);
416
+ i0.ɵɵelementEnd()();
417
+ } if (rf & 2) {
418
+ const ctx_r1 = i0.ɵɵnextContext(3);
419
+ i0.ɵɵadvance(2);
420
+ i0.ɵɵconditional(ctx_r1.recentRecords.length > 0 ? 2 : -1);
421
+ i0.ɵɵadvance();
422
+ i0.ɵɵconditional(ctx_r1.favoriteRecords.length > 0 ? 3 : -1);
423
+ i0.ɵɵadvance(2);
424
+ i0.ɵɵconditional(ctx_r1.recentEntities.length > 0 ? 5 : -1);
425
+ i0.ɵɵadvance();
426
+ i0.ɵɵconditional(ctx_r1.favoriteEntities.length > 0 ? 6 : -1);
427
+ } }
428
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_6_Template(rf, ctx) { if (rf & 1) {
429
+ i0.ɵɵtext(0, " All Entities ");
430
+ } }
431
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_7_Template(rf, ctx) { if (rf & 1) {
432
+ i0.ɵɵtext(0, " Common Entities ");
433
+ } }
434
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_8_Template(rf, ctx) { if (rf & 1) {
435
+ i0.ɵɵelementStart(0, "span", 46);
436
+ i0.ɵɵtext(1);
437
+ i0.ɵɵelementEnd();
438
+ } if (rf & 2) {
439
+ const ctx_r1 = i0.ɵɵnextContext(3);
440
+ i0.ɵɵadvance();
441
+ i0.ɵɵtextInterpolate1("", ctx_r1.filteredEntities.length, " matching");
442
+ } }
443
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_9_Template(rf, ctx) { if (rf & 1) {
444
+ const _r25 = i0.ɵɵgetCurrentView();
445
+ i0.ɵɵelementStart(0, "div", 47)(1, "button", 79);
446
+ i0.ɵɵlistener("click", function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_9_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r25); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.toggleShowAllEntities()); });
447
+ i0.ɵɵtext(2, " Common ");
448
+ i0.ɵɵelementEnd();
449
+ i0.ɵɵelementStart(3, "button", 79);
450
+ i0.ɵɵlistener("click", function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_9_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r25); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.toggleShowAllEntities()); });
451
+ i0.ɵɵtext(4, " All ");
452
+ i0.ɵɵelementEnd()();
453
+ } if (rf & 2) {
454
+ const ctx_r1 = i0.ɵɵnextContext(3);
455
+ i0.ɵɵadvance();
456
+ i0.ɵɵclassProp("active", !ctx_r1.state.showAllEntities);
457
+ i0.ɵɵpropertyInterpolate1("title", "Show common entities (", ctx_r1.commonEntitiesCount, ")");
458
+ i0.ɵɵadvance(2);
459
+ i0.ɵɵclassProp("active", ctx_r1.state.showAllEntities);
460
+ i0.ɵɵpropertyInterpolate1("title", "Show all entities (", ctx_r1.allEntitiesCount, ")");
461
+ } }
462
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_For_12_Conditional_6_Template(rf, ctx) { if (rf & 1) {
463
+ i0.ɵɵelementStart(0, "p", 84);
464
+ i0.ɵɵtext(1);
465
+ i0.ɵɵelementEnd();
466
+ } if (rf & 2) {
467
+ const entity_r27 = i0.ɵɵnextContext().$implicit;
468
+ i0.ɵɵadvance();
469
+ i0.ɵɵtextInterpolate(entity_r27.Description);
470
+ } }
471
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_For_12_Template(rf, ctx) { if (rf & 1) {
472
+ const _r26 = i0.ɵɵgetCurrentView();
473
+ i0.ɵɵelementStart(0, "div", 80);
474
+ i0.ɵɵlistener("click", function DataExplorerDashboardComponent_Conditional_14_Conditional_2_For_12_Template_div_click_0_listener() { const entity_r27 = i0.ɵɵrestoreView(_r26).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.onEntitySelected(entity_r27)); });
475
+ i0.ɵɵelementStart(1, "div", 81);
476
+ i0.ɵɵelement(2, "i");
477
+ i0.ɵɵelementEnd();
478
+ i0.ɵɵelementStart(3, "div", 82)(4, "h4", 83);
479
+ i0.ɵɵtext(5);
480
+ i0.ɵɵelementEnd();
481
+ i0.ɵɵtemplate(6, DataExplorerDashboardComponent_Conditional_14_Conditional_2_For_12_Conditional_6_Template, 2, 1, "p", 84);
482
+ i0.ɵɵelementEnd();
483
+ i0.ɵɵelementStart(7, "button", 76);
484
+ i0.ɵɵlistener("click", function DataExplorerDashboardComponent_Conditional_14_Conditional_2_For_12_Template_button_click_7_listener($event) { const entity_r27 = i0.ɵɵrestoreView(_r26).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.toggleEntityFavorite(entity_r27, $event)); });
485
+ i0.ɵɵelement(8, "i");
486
+ i0.ɵɵelementEnd()();
487
+ } if (rf & 2) {
488
+ const entity_r27 = ctx.$implicit;
489
+ const ctx_r1 = i0.ɵɵnextContext(3);
490
+ i0.ɵɵproperty("title", entity_r27.Description || entity_r27.Name);
491
+ i0.ɵɵadvance(2);
492
+ i0.ɵɵclassMap(ctx_r1.getEntityIcon(entity_r27));
493
+ i0.ɵɵadvance(3);
494
+ i0.ɵɵtextInterpolate(entity_r27.Name);
495
+ i0.ɵɵadvance();
496
+ i0.ɵɵconditional(entity_r27.Description ? 6 : -1);
497
+ i0.ɵɵadvance();
498
+ i0.ɵɵclassProp("favorited", ctx_r1.isEntityFavorited(entity_r27));
499
+ i0.ɵɵproperty("title", ctx_r1.isEntityFavorited(entity_r27) ? "Remove from favorites" : "Add to favorites");
500
+ i0.ɵɵadvance();
501
+ i0.ɵɵclassMap(ctx_r1.isEntityFavorited(entity_r27) ? "fa-solid fa-star" : "fa-regular fa-star");
502
+ } }
503
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_13_Template(rf, ctx) { if (rf & 1) {
504
+ i0.ɵɵelementStart(0, "div", 50);
505
+ i0.ɵɵelement(1, "i", 85);
506
+ i0.ɵɵelementStart(2, "h3");
507
+ i0.ɵɵtext(3, "No Matching Entities");
508
+ i0.ɵɵelementEnd();
509
+ i0.ɵɵelementStart(4, "p");
510
+ i0.ɵɵtext(5);
511
+ i0.ɵɵelementEnd()();
512
+ } if (rf & 2) {
513
+ const ctx_r1 = i0.ɵɵnextContext(3);
514
+ i0.ɵɵadvance(5);
515
+ i0.ɵɵtextInterpolate1("No entities match \"", ctx_r1.entityFilterText, "\"");
516
+ } }
517
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_14_Template(rf, ctx) { if (rf & 1) {
518
+ i0.ɵɵelementStart(0, "div", 51);
519
+ i0.ɵɵelement(1, "i", 86);
520
+ i0.ɵɵelementStart(2, "h3");
521
+ i0.ɵɵtext(3, "No Entities Available");
522
+ i0.ɵɵelementEnd();
523
+ i0.ɵɵelementStart(4, "p");
524
+ i0.ɵɵtext(5, "There are no entities configured for this application.");
525
+ i0.ɵɵelementEnd()();
526
+ } }
527
+ function DataExplorerDashboardComponent_Conditional_14_Conditional_2_Template(rf, ctx) { if (rf & 1) {
528
+ i0.ɵɵtemplate(0, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_0_Template, 7, 4, "div", 41);
529
+ i0.ɵɵelementStart(1, "div", 39)(2, "div", 42)(3, "div", 43);
530
+ i0.ɵɵelement(4, "i", 44);
531
+ i0.ɵɵelementStart(5, "h3", 45);
532
+ i0.ɵɵtemplate(6, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_6_Template, 1, 0)(7, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_7_Template, 1, 0);
533
+ i0.ɵɵelementEnd();
534
+ i0.ɵɵtemplate(8, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_8_Template, 2, 1, "span", 46);
535
+ i0.ɵɵelementEnd();
536
+ i0.ɵɵtemplate(9, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_9_Template, 5, 8, "div", 47);
537
+ i0.ɵɵelementEnd();
538
+ i0.ɵɵelementStart(10, "div", 48);
539
+ i0.ɵɵrepeaterCreate(11, DataExplorerDashboardComponent_Conditional_14_Conditional_2_For_12_Template, 9, 10, "div", 49, _forTrack1);
540
+ i0.ɵɵelementEnd();
541
+ i0.ɵɵtemplate(13, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_13_Template, 6, 1, "div", 50)(14, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Conditional_14_Template, 6, 0, "div", 51);
542
+ i0.ɵɵelementEnd();
543
+ } if (rf & 2) {
544
+ const ctx_r1 = i0.ɵɵnextContext(2);
545
+ i0.ɵɵconditional(ctx_r1.hasTopSectionContent ? 0 : -1);
546
+ i0.ɵɵadvance(6);
547
+ i0.ɵɵconditional(ctx_r1.state.showAllEntities || !ctx_r1.showCommonAllToggle ? 6 : 7);
548
+ i0.ɵɵadvance(2);
549
+ i0.ɵɵconditional(ctx_r1.entityFilterText && ctx_r1.filteredEntities.length !== ctx_r1.entities.length ? 8 : -1);
550
+ i0.ɵɵadvance();
551
+ i0.ɵɵconditional(ctx_r1.showCommonAllToggle ? 9 : -1);
552
+ i0.ɵɵadvance(2);
553
+ i0.ɵɵrepeater(ctx_r1.filteredEntities);
554
+ i0.ɵɵadvance(2);
555
+ i0.ɵɵconditional(ctx_r1.filteredEntities.length === 0 && ctx_r1.entities.length > 0 ? 13 : -1);
556
+ i0.ɵɵadvance();
557
+ i0.ɵɵconditional(ctx_r1.entities.length === 0 ? 14 : -1);
558
+ } }
559
+ function DataExplorerDashboardComponent_Conditional_14_Template(rf, ctx) { if (rf & 1) {
560
+ i0.ɵɵelementStart(0, "div", 12);
561
+ i0.ɵɵtemplate(1, DataExplorerDashboardComponent_Conditional_14_Conditional_1_Template, 2, 0, "div", 38)(2, DataExplorerDashboardComponent_Conditional_14_Conditional_2_Template, 15, 6, "div", 39);
562
+ i0.ɵɵelementEnd();
563
+ } if (rf & 2) {
564
+ const ctx_r1 = i0.ɵɵnextContext();
565
+ i0.ɵɵadvance();
566
+ i0.ɵɵconditional(ctx_r1.isLoadingEntities ? 1 : 2);
567
+ } }
568
+ function DataExplorerDashboardComponent_Conditional_15_Template(rf, ctx) { if (rf & 1) {
569
+ const _r28 = i0.ɵɵgetCurrentView();
570
+ i0.ɵɵelementStart(0, "mj-entity-viewer", 87);
571
+ i0.ɵɵlistener("viewModeChange", function DataExplorerDashboardComponent_Conditional_15_Template_mj_entity_viewer_viewModeChange_0_listener($event) { i0.ɵɵrestoreView(_r28); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onViewModeChanged($event)); })("filterTextChange", function DataExplorerDashboardComponent_Conditional_15_Template_mj_entity_viewer_filterTextChange_0_listener($event) { i0.ɵɵrestoreView(_r28); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onFilterTextChanged($event)); })("recordSelected", function DataExplorerDashboardComponent_Conditional_15_Template_mj_entity_viewer_recordSelected_0_listener($event) { i0.ɵɵrestoreView(_r28); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onViewerRecordSelected($event)); })("recordOpened", function DataExplorerDashboardComponent_Conditional_15_Template_mj_entity_viewer_recordOpened_0_listener($event) { i0.ɵɵrestoreView(_r28); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onViewerRecordOpened($event)); })("dataLoaded", function DataExplorerDashboardComponent_Conditional_15_Template_mj_entity_viewer_dataLoaded_0_listener($event) { i0.ɵɵrestoreView(_r28); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onDataLoaded($event)); })("filteredCountChanged", function DataExplorerDashboardComponent_Conditional_15_Template_mj_entity_viewer_filteredCountChanged_0_listener($event) { i0.ɵɵrestoreView(_r28); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onFilteredCountChanged($event)); })("gridStateChanged", function DataExplorerDashboardComponent_Conditional_15_Template_mj_entity_viewer_gridStateChanged_0_listener($event) { i0.ɵɵrestoreView(_r28); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onGridStateChanged($event)); });
572
+ i0.ɵɵelementEnd();
573
+ } if (rf & 2) {
574
+ const ctx_r1 = i0.ɵɵnextContext();
575
+ i0.ɵɵproperty("entity", ctx_r1.selectedEntity)("viewEntity", ctx_r1.selectedViewEntity)("viewMode", ctx_r1.state.viewMode)("filterText", ctx_r1.debouncedFilterText)("selectedRecordId", ctx_r1.state.selectedRecordId)("config", ctx_r1.viewerConfig)("gridState", ctx_r1.currentGridState);
576
+ } }
577
+ function DataExplorerDashboardComponent_Conditional_16_Template(rf, ctx) { if (rf & 1) {
578
+ const _r29 = i0.ɵɵgetCurrentView();
579
+ i0.ɵɵelementStart(0, "div", 88)(1, "mj-entity-record-detail-panel", 89);
580
+ i0.ɵɵlistener("close", function DataExplorerDashboardComponent_Conditional_16_Template_mj_entity_record_detail_panel_close_1_listener() { i0.ɵɵrestoreView(_r29); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onDetailPanelClosed()); })("openRecord", function DataExplorerDashboardComponent_Conditional_16_Template_mj_entity_record_detail_panel_openRecord_1_listener($event) { i0.ɵɵrestoreView(_r29); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onOpenRecord($event)); })("navigateToRelated", function DataExplorerDashboardComponent_Conditional_16_Template_mj_entity_record_detail_panel_navigateToRelated_1_listener($event) { i0.ɵɵrestoreView(_r29); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onNavigateToRelated($event)); })("openRelatedRecord", function DataExplorerDashboardComponent_Conditional_16_Template_mj_entity_record_detail_panel_openRelatedRecord_1_listener($event) { i0.ɵɵrestoreView(_r29); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onOpenRelatedRecord($event)); })("openForeignKeyRecord", function DataExplorerDashboardComponent_Conditional_16_Template_mj_entity_record_detail_panel_openForeignKeyRecord_1_listener($event) { i0.ɵɵrestoreView(_r29); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onOpenForeignKeyRecord($event)); });
581
+ i0.ɵɵelementEnd()();
582
+ } if (rf & 2) {
583
+ const ctx_r1 = i0.ɵɵnextContext();
584
+ i0.ɵɵstyleProp("width", ctx_r1.state.detailPanelWidth, "px");
585
+ i0.ɵɵadvance();
586
+ i0.ɵɵproperty("entity", ctx_r1.detailPanelEntity)("record", ctx_r1.selectedRecord);
587
+ } }
588
+ /**
589
+ * Data Explorer Dashboard - Power user interface for exploring data across entities
590
+ * Combines card-based browsing with grid views and relationship visualization
591
+ *
592
+ * Uses mj-entity-viewer composite component for the main content area,
593
+ * which handles data loading, filtering, and view mode switching.
594
+ */
595
+ let DataExplorerDashboardComponent = class DataExplorerDashboardComponent extends BaseDashboard {
596
+ stateService;
597
+ cdr;
598
+ router;
599
+ recentAccessService;
600
+ destroy$ = new Subject();
601
+ metadata = new Metadata();
602
+ /** Reference to the filter input for keyboard shortcuts */
603
+ filterInputRef;
604
+ /** Reference to the view selector for refreshing after save */
605
+ viewSelectorRef;
606
+ /** Reference to the entity viewer for refreshing after view save */
607
+ entityViewerRef;
608
+ /**
609
+ * Optional filter to constrain which entities are shown in the explorer.
610
+ * Can filter by applicationId, schemaNames, or explicit entityNames.
611
+ */
612
+ entityFilter = null;
613
+ /**
614
+ * Optional deep link to navigate to a specific entity/record on load.
615
+ * Parsed from URL query parameters (e.g., ?entity=Users&record=123)
616
+ */
617
+ deepLink = null;
618
+ /**
619
+ * Optional context name to display in the header instead of "Data Explorer".
620
+ * Use this to customize the explorer for specific applications (e.g., "CRM", "Association Demo").
621
+ */
622
+ contextName = null;
623
+ /**
624
+ * Optional context icon (Font Awesome class) to display in the header.
625
+ * Use this alongside contextName for a fully customized header (e.g., "fa-solid fa-users" for CRM).
626
+ */
627
+ contextIcon = null;
628
+ // State
629
+ state;
630
+ // Entity data - all entities available to the user
631
+ allEntities = [];
632
+ // Filtered entities based on entityFilter
633
+ entities = [];
634
+ // Entity IDs for the current application (loaded when applicationId filter is set)
635
+ applicationEntityIds = new Set();
636
+ selectedEntity = null;
637
+ // Record counts (updated by mj-entity-viewer)
638
+ totalRecordCount = 0;
639
+ filteredRecordCount = 0;
640
+ // Selected record for detail panel
641
+ selectedRecord = null;
642
+ // Entity info for the detail panel (may differ from selectedEntity when viewing FK/related records)
643
+ detailPanelEntity = null;
644
+ // Currently loaded records from mj-entity-viewer (for back/forward navigation lookup)
645
+ loadedRecords = [];
646
+ // Currently selected view entity (for view data loading)
647
+ selectedViewEntity = null;
648
+ // Debounced filter text (synced with mj-entity-viewer)
649
+ debouncedFilterText = '';
650
+ filterInput$ = new Subject();
651
+ // Entity filter text for home screen
652
+ entityFilterText = '';
653
+ // Breadcrumbs for navigation display
654
+ breadcrumbs = [];
655
+ // Loading state for entities
656
+ isLoadingEntities = true;
657
+ // Flag to skip URL updates during initialization (when applying deep link)
658
+ skipUrlUpdates = true;
659
+ // Track the last URL we navigated to, to avoid reacting to our own navigation
660
+ lastNavigatedUrl = '';
661
+ // Recent records from User Record Logs
662
+ recentRecords = [];
663
+ // Favorite records from User Favorites (non-entity favorites)
664
+ favoriteRecords = [];
665
+ // Loading state for home screen sections
666
+ isLoadingRecentRecords = true;
667
+ // Entity filter for recent records (null = show all, string = filter by entityId)
668
+ recentRecordsEntityFilter = null;
669
+ /**
670
+ * Filtered entities based on entityFilterText (for home screen)
671
+ * Excludes entities shown in recent or favorites sections
672
+ * Applies Common/All toggle filtering
673
+ */
674
+ get filteredEntities() {
675
+ // Get IDs of entities in recent and favorites to exclude
676
+ const recentEntityIds = new Set(this.state.recentEntityAccesses.map(r => r.entityId));
677
+ const favoriteEntityIds = new Set(this.state.favoriteEntities.map(f => f.entityId));
678
+ let result = this.entities.filter(e => {
679
+ // Exclude entities shown in recent or favorites sections
680
+ if (recentEntityIds.has(e.ID) || favoriteEntityIds.has(e.ID)) {
681
+ return false;
682
+ }
683
+ // Apply Common/All toggle filter (only if we have DefaultForNewUser info)
684
+ if (!this.state.showAllEntities && this.stateService.DefaultEntityIds.size > 0) {
685
+ if (!this.stateService.DefaultEntityIds.has(e.ID)) {
686
+ return false;
687
+ }
688
+ }
689
+ return true;
690
+ });
691
+ // Apply text filter
692
+ if (this.entityFilterText && this.entityFilterText.trim() !== '') {
693
+ const filter = this.entityFilterText.toLowerCase().trim();
694
+ result = result.filter(e => e.Name.toLowerCase().includes(filter) ||
695
+ (e.Description && e.Description.toLowerCase().includes(filter)));
696
+ }
697
+ return result;
698
+ }
699
+ /**
700
+ * Get recent entities for home screen display (max 5)
701
+ */
702
+ get recentEntities() {
703
+ return this.state.recentEntityAccesses
704
+ .slice(0, 5)
705
+ .map(r => this.entities.find(e => e.ID === r.entityId))
706
+ .filter((e) => e !== undefined);
707
+ }
708
+ /**
709
+ * Get favorite entities for home screen display
710
+ */
711
+ get favoriteEntities() {
712
+ return this.state.favoriteEntities
713
+ .map(f => this.entities.find(e => e.ID === f.entityId))
714
+ .filter((e) => e !== undefined);
715
+ }
716
+ /**
717
+ * Check if we should show the Common/All toggle
718
+ * Only show if we have DefaultForNewUser information
719
+ */
720
+ get showCommonAllToggle() {
721
+ return this.stateService.DefaultEntityIds.size > 0;
722
+ }
723
+ /**
724
+ * Total count of all entities (for display)
725
+ */
726
+ get allEntitiesCount() {
727
+ return this.entities.length;
728
+ }
729
+ /**
730
+ * Count of common (DefaultForNewUser) entities
731
+ */
732
+ get commonEntitiesCount() {
733
+ return this.entities.filter(e => this.stateService.DefaultEntityIds.has(e.ID)).length;
734
+ }
735
+ /**
736
+ * Check if we have any content for the top two-column section
737
+ * (recent/favorite entities OR recent/favorite records)
738
+ */
739
+ get hasTopSectionContent() {
740
+ return this.recentEntities.length > 0 ||
741
+ this.favoriteEntities.length > 0 ||
742
+ this.recentRecords.length > 0 ||
743
+ this.favoriteRecords.length > 0;
744
+ }
745
+ /**
746
+ * Get unique entities from recent records for the filter strip.
747
+ * Returns up to 5 entities, sorted by frequency in the recent records.
748
+ */
749
+ get uniqueRecentRecordEntities() {
750
+ const entityCounts = new Map();
751
+ for (const record of this.recentRecords) {
752
+ const existing = entityCounts.get(record.entityId);
753
+ if (existing) {
754
+ existing.count++;
755
+ }
756
+ else {
757
+ entityCounts.set(record.entityId, {
758
+ entityId: record.entityId,
759
+ entityName: record.entityName,
760
+ count: 1
761
+ });
762
+ }
763
+ }
764
+ // Convert to array, sort by count (descending), take first 5
765
+ return Array.from(entityCounts.values())
766
+ .sort((a, b) => b.count - a.count)
767
+ .slice(0, 5)
768
+ .map(e => ({
769
+ ...e,
770
+ icon: this.getEntityIconById(e.entityId)
771
+ }));
772
+ }
773
+ /**
774
+ * Check if we should show the entity filter strip for recent records.
775
+ * Only show when there are 2+ unique entities.
776
+ */
777
+ get showRecentRecordsEntityFilter() {
778
+ return this.uniqueRecentRecordEntities.length >= 2;
779
+ }
780
+ /**
781
+ * Get filtered recent records based on entity filter.
782
+ */
783
+ get filteredRecentRecords() {
784
+ if (!this.recentRecordsEntityFilter) {
785
+ return this.recentRecords;
786
+ }
787
+ return this.recentRecords.filter(r => r.entityId === this.recentRecordsEntityFilter);
788
+ }
789
+ /**
790
+ * Set the entity filter for recent records
791
+ */
792
+ setRecentRecordsEntityFilter(entityId) {
793
+ this.recentRecordsEntityFilter = entityId;
794
+ }
795
+ /**
796
+ * Get the display title for the header.
797
+ * Priority: contextName > entityFilter.applicationName > "Data Explorer"
798
+ */
799
+ get displayTitle() {
800
+ return this.contextName || this.entityFilter?.applicationName || 'Data Explorer';
801
+ }
802
+ /**
803
+ * Get the display icon for the header (when at home level).
804
+ * Returns contextIcon if provided, otherwise null.
805
+ */
806
+ get displayIcon() {
807
+ return this.contextIcon;
808
+ }
809
+ /**
810
+ * Configuration for mj-entity-viewer composite component
811
+ * Hides the built-in header since we have a custom header in the dashboard
812
+ * Uses server-side pagination with 100 records per page (default)
813
+ */
814
+ viewerConfig = {
815
+ showFilter: false, // We have our own filter in the dashboard header
816
+ showViewModeToggle: false, // We have our own toggle in the dashboard header
817
+ showRecordCount: false, // We show count in the dashboard header
818
+ showPagination: true, // Show the pagination UI with "Load More" button
819
+ serverSideFiltering: true, // Use RunView's UserSearchString for filtering
820
+ serverSideSorting: true, // Use RunView's OrderBy for sorting
821
+ height: '100%'
822
+ };
823
+ /**
824
+ * Current grid state (built from view entity or local state changes)
825
+ * This is passed to mj-entity-viewer to control column display
826
+ */
827
+ currentGridState = null;
828
+ constructor(stateService, cdr, router, recentAccessService) {
829
+ super();
830
+ this.stateService = stateService;
831
+ this.cdr = cdr;
832
+ this.router = router;
833
+ this.recentAccessService = recentAccessService;
834
+ this.state = this.stateService.CurrentState;
835
+ }
836
+ async ngOnInit() {
837
+ // Parse URL state FIRST - URL wins over persisted state
838
+ // This must happen before loading entities to prevent race conditions
839
+ const urlState = this.parseUrlState();
840
+ // Set context for state service (enables context-specific settings)
841
+ await this.stateService.setContext(this.entityFilter);
842
+ this.state = this.stateService.CurrentState;
843
+ // Initialize debounced filter from persisted state (only if no URL state)
844
+ if (!urlState && this.state.smartFilterPrompt) {
845
+ this.debouncedFilterText = this.state.smartFilterPrompt;
846
+ }
847
+ // Load available entities (async to support applicationId filter)
848
+ // Pass urlState so we don't restore persisted entity if URL specifies one
849
+ await this.loadEntities(urlState);
850
+ // Apply URL state after entities are loaded
851
+ if (urlState) {
852
+ // URL has state - apply it (overrides persisted state)
853
+ this.applyUrlState(urlState);
854
+ }
855
+ else if (this.deepLink) {
856
+ // No URL state but @Input deepLink provided - use that
857
+ await this.applyDeepLink(this.deepLink);
858
+ }
859
+ // Subscribe to state changes
860
+ this.stateService.State
861
+ .pipe(takeUntil(this.destroy$))
862
+ .subscribe(state => {
863
+ const entityChanged = state.selectedEntityName !== this.state.selectedEntityName;
864
+ this.state = state;
865
+ // When entity changes, immediately update the debounced filter text
866
+ if (entityChanged && state.smartFilterPrompt !== this.debouncedFilterText) {
867
+ this.debouncedFilterText = state.smartFilterPrompt;
868
+ }
869
+ this.onStateChanged();
870
+ // Update URL to reflect current state (for deep linking)
871
+ if (!this.skipUrlUpdates) {
872
+ this.updateUrl();
873
+ }
874
+ this.cdr.detectChanges();
875
+ });
876
+ // Subscribe to breadcrumb changes
877
+ this.stateService.Breadcrumbs
878
+ .pipe(takeUntil(this.destroy$))
879
+ .subscribe(breadcrumbs => {
880
+ this.breadcrumbs = breadcrumbs;
881
+ this.cdr.detectChanges();
882
+ });
883
+ // Setup debounced filter
884
+ this.filterInput$
885
+ .pipe(debounceTime(250), distinctUntilChanged(), takeUntil(this.destroy$))
886
+ .subscribe(filterText => {
887
+ this.debouncedFilterText = filterText;
888
+ this.cdr.detectChanges();
889
+ });
890
+ // Subscribe to recent records changes
891
+ this.stateService.RecentRecords
892
+ .pipe(takeUntil(this.destroy$))
893
+ .subscribe(records => {
894
+ this.recentRecords = records;
895
+ this.isLoadingRecentRecords = false;
896
+ this.cdr.detectChanges();
897
+ });
898
+ // Subscribe to favorite records changes
899
+ this.stateService.FavoriteRecords
900
+ .pipe(takeUntil(this.destroy$))
901
+ .subscribe(records => {
902
+ this.favoriteRecords = records;
903
+ this.cdr.detectChanges();
904
+ });
905
+ // Subscribe to router NavigationEnd events for back/forward button support
906
+ this.router.events
907
+ .pipe(filter((event) => event instanceof NavigationEnd), takeUntil(this.destroy$))
908
+ .subscribe(event => {
909
+ // Only react to navigation events that weren't triggered by us
910
+ // Normalize URLs by decoding to handle + vs %20 encoding differences
911
+ // Note: decodeURIComponent doesn't decode +, so we also replace + with space
912
+ const currentUrl = event.urlAfterRedirects || event.url;
913
+ const normalizedCurrentUrl = decodeURIComponent(currentUrl).replace(/\+/g, ' ');
914
+ const normalizedLastUrl = decodeURIComponent(this.lastNavigatedUrl).replace(/\+/g, ' ');
915
+ const isExternal = normalizedCurrentUrl !== normalizedLastUrl;
916
+ if (isExternal) {
917
+ this.onExternalNavigation(currentUrl);
918
+ }
919
+ });
920
+ // Enable URL updates now that initialization is complete
921
+ this.skipUrlUpdates = false;
922
+ // Update URL to reflect current state (whether from URL, deepLink, or persisted)
923
+ this.updateUrl();
924
+ }
925
+ /**
926
+ * Handle keyboard shortcuts
927
+ * "/" or Cmd+K focuses the filter input
928
+ */
929
+ handleKeyboardShortcut(event) {
930
+ // Skip if user is typing in an input field
931
+ const target = event.target;
932
+ if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
933
+ // Allow Escape to blur the input
934
+ if (event.key === 'Escape') {
935
+ target.blur();
936
+ }
937
+ return;
938
+ }
939
+ // "/" to focus filter
940
+ if (event.key === '/') {
941
+ event.preventDefault();
942
+ this.focusFilterInput();
943
+ }
944
+ // Cmd+K or Ctrl+K to focus filter
945
+ if ((event.metaKey || event.ctrlKey) && event.key === 'k') {
946
+ event.preventDefault();
947
+ this.focusFilterInput();
948
+ }
949
+ }
950
+ /**
951
+ * Focus the filter input
952
+ */
953
+ focusFilterInput() {
954
+ if (this.filterInputRef) {
955
+ this.filterInputRef.nativeElement.focus();
956
+ this.filterInputRef.nativeElement.select();
957
+ }
958
+ }
959
+ ngOnDestroy() {
960
+ this.destroy$.next();
961
+ this.destroy$.complete();
962
+ super.ngOnDestroy();
963
+ }
964
+ async ngOnChanges(changes) {
965
+ // Re-apply filter when entityFilter changes
966
+ if (changes['entityFilter'] && !changes['entityFilter'].firstChange) {
967
+ // Update context for new filter (loads context-specific state)
968
+ await this.stateService.setContext(this.entityFilter);
969
+ this.state = this.stateService.CurrentState;
970
+ await this.loadEntities();
971
+ }
972
+ }
973
+ initDashboard() {
974
+ // Called by BaseDashboard
975
+ }
976
+ loadData() {
977
+ // Data loading is handled by mj-entity-viewer
978
+ }
979
+ // ========================================
980
+ // ENTITY MANAGEMENT
981
+ // ========================================
982
+ /**
983
+ * Load all available entities the user can access, applying any configured filter
984
+ * @param urlState Optional URL state - if provided, skip restoring persisted entity to avoid race conditions
985
+ */
986
+ async loadEntities(urlState) {
987
+ console.log('[DataExplorer] loadEntities called, entityFilter:', this.entityFilter);
988
+ this.isLoadingEntities = true;
989
+ try {
990
+ // First, load all entities the user can access
991
+ this.allEntities = this.metadata.Entities
992
+ .filter(e => {
993
+ const perms = e.GetUserPermisions(this.metadata.CurrentUser);
994
+ return perms.CanRead && e.IncludeInAPI;
995
+ })
996
+ .sort((a, b) => a.Name.localeCompare(b.Name));
997
+ console.log('[DataExplorer] allEntities count:', this.allEntities.length);
998
+ // If we have an applicationId filter, load the application entities
999
+ if (this.entityFilter?.applicationId) {
1000
+ await this.loadApplicationEntityIds(this.entityFilter.applicationId);
1001
+ console.log('[DataExplorer] applicationEntityIds count:', this.applicationEntityIds.size);
1002
+ }
1003
+ // Apply filter to get the final entity list
1004
+ this.entities = this.applyEntityFilter(this.allEntities);
1005
+ console.log('[DataExplorer] filtered entities count:', this.entities.length);
1006
+ // Only restore entity from persisted state if there's no URL state
1007
+ // This prevents race conditions where persisted entity triggers data load
1008
+ // before URL state can override it
1009
+ if (!urlState && this.state.selectedEntityName) {
1010
+ this.selectedEntity = this.entities.find(e => e.Name === this.state.selectedEntityName) || null;
1011
+ }
1012
+ }
1013
+ finally {
1014
+ this.isLoadingEntities = false;
1015
+ this.cdr.detectChanges();
1016
+ }
1017
+ }
1018
+ /**
1019
+ * Load entity IDs associated with a specific application
1020
+ */
1021
+ async loadApplicationEntityIds(applicationId) {
1022
+ this.applicationEntityIds.clear();
1023
+ const rv = new RunView();
1024
+ const result = await rv.RunView({
1025
+ EntityName: 'Application Entities',
1026
+ ExtraFilter: `ApplicationID = '${applicationId}'`,
1027
+ ResultType: 'entity_object'
1028
+ });
1029
+ if (result.Success && result.Results) {
1030
+ for (const appEntity of result.Results) {
1031
+ this.applicationEntityIds.add(appEntity.EntityID);
1032
+ }
1033
+ }
1034
+ }
1035
+ /**
1036
+ * Apply the configured filter to the entity list
1037
+ */
1038
+ applyEntityFilter(entities) {
1039
+ if (!this.entityFilter) {
1040
+ return entities;
1041
+ }
1042
+ return entities.filter(entity => {
1043
+ // Filter by application (via ApplicationEntities)
1044
+ if (this.entityFilter.applicationId) {
1045
+ if (!this.applicationEntityIds.has(entity.ID)) {
1046
+ return false;
1047
+ }
1048
+ }
1049
+ // Filter by schema names
1050
+ if (this.entityFilter.schemaNames && this.entityFilter.schemaNames.length > 0) {
1051
+ if (!this.entityFilter.schemaNames.includes(entity.SchemaName)) {
1052
+ return false;
1053
+ }
1054
+ }
1055
+ // Filter by explicit entity names
1056
+ if (this.entityFilter.entityNames && this.entityFilter.entityNames.length > 0) {
1057
+ if (!this.entityFilter.entityNames.includes(entity.Name)) {
1058
+ return false;
1059
+ }
1060
+ }
1061
+ // Filter out system entities unless explicitly included
1062
+ if (!this.entityFilter.includeSystemEntities) {
1063
+ // Skip entities with names starting with __ (MJ system entities)
1064
+ if (entity.Name.startsWith('__')) {
1065
+ return false;
1066
+ }
1067
+ // Could add more system schema checks here if needed
1068
+ }
1069
+ return true;
1070
+ });
1071
+ }
1072
+ /**
1073
+ * Handle entity selection from navigation panel or home screen
1074
+ */
1075
+ onEntitySelected(entity) {
1076
+ this.resetRecordCounts();
1077
+ this.selectedEntity = entity;
1078
+ this.stateService.selectEntity(entity.Name);
1079
+ // Track entity access for recent entities
1080
+ this.stateService.trackEntityAccess(entity.Name, entity.ID);
1081
+ // mj-entity-viewer will automatically load data when entity changes
1082
+ }
1083
+ /**
1084
+ * Handle state changes from external sources
1085
+ */
1086
+ onStateChanged() {
1087
+ if (this.state.selectedEntityName !== this.selectedEntity?.Name) {
1088
+ this.resetRecordCounts();
1089
+ this.selectedEntity = this.entities.find(e => e.Name === this.state.selectedEntityName) || null;
1090
+ }
1091
+ }
1092
+ /**
1093
+ * Reset record counts when entity changes.
1094
+ * The actual counts will be updated when mj-entity-viewer emits dataLoaded event.
1095
+ */
1096
+ resetRecordCounts() {
1097
+ this.totalRecordCount = 0;
1098
+ this.filteredRecordCount = 0;
1099
+ }
1100
+ // ========================================
1101
+ // VIEW MANAGEMENT
1102
+ // ========================================
1103
+ /**
1104
+ * Handle view selection from view selector dropdown
1105
+ */
1106
+ onViewSelected(event) {
1107
+ this.selectedViewEntity = event.view;
1108
+ this.stateService.selectView(event.viewId);
1109
+ // When a view is selected, apply its configuration
1110
+ if (event.view) {
1111
+ // Parse and apply the view's grid state
1112
+ this.currentGridState = this.parseViewGridState(event.view);
1113
+ // Apply the view's filter - for Smart Filter views, use SmartFilterPrompt
1114
+ // For regular filter views, the WhereClause is applied in the entity-viewer
1115
+ if (event.view.SmartFilterEnabled && event.view.SmartFilterPrompt) {
1116
+ this.stateService.setSmartFilterPrompt(event.view.SmartFilterPrompt);
1117
+ this.debouncedFilterText = event.view.SmartFilterPrompt;
1118
+ }
1119
+ else {
1120
+ // Clear the quick filter when switching to a view with regular filters
1121
+ this.stateService.setSmartFilterPrompt('');
1122
+ this.debouncedFilterText = '';
1123
+ }
1124
+ }
1125
+ else {
1126
+ // Switching to default view - clear grid state and filters
1127
+ this.currentGridState = null;
1128
+ this.stateService.setSmartFilterPrompt('');
1129
+ this.debouncedFilterText = '';
1130
+ }
1131
+ }
1132
+ /**
1133
+ * Parse GridState JSON from a UserView entity
1134
+ * Returns null if no valid GridState is present
1135
+ */
1136
+ parseViewGridState(view) {
1137
+ if (!view.GridState) {
1138
+ return null;
1139
+ }
1140
+ try {
1141
+ const parsed = JSON.parse(view.GridState);
1142
+ // Validate structure - expect columnSettings array
1143
+ if (parsed && Array.isArray(parsed.columnSettings)) {
1144
+ return {
1145
+ columnSettings: parsed.columnSettings,
1146
+ sortSettings: parsed.sortSettings || []
1147
+ };
1148
+ }
1149
+ return null;
1150
+ }
1151
+ catch (error) {
1152
+ console.warn('[DataExplorer] Failed to parse GridState:', error);
1153
+ return null;
1154
+ }
1155
+ }
1156
+ /**
1157
+ * Handle grid state changes from entity-viewer (column resize, reorder, etc.)
1158
+ * Updates the local currentGridState and marks view as modified
1159
+ */
1160
+ onGridStateChanged(event) {
1161
+ this.currentGridState = event.gridState;
1162
+ // Mark view as modified if we have a selected view
1163
+ if (this.state.selectedViewId) {
1164
+ this.stateService.setViewModified(true);
1165
+ }
1166
+ }
1167
+ /**
1168
+ * Handle save view request from view selector
1169
+ */
1170
+ onSaveViewRequested(event) {
1171
+ // TODO: Implement in Phase 4 - View CRUD Operations
1172
+ // For now, just open the config panel where save functionality will be
1173
+ this.stateService.openViewConfigPanel();
1174
+ }
1175
+ /**
1176
+ * Handle manage views request - opens view browser
1177
+ */
1178
+ onManageViewsRequested() {
1179
+ // TODO: Implement navigation to view management
1180
+ // For now, just log
1181
+ console.log('[DataExplorer] Manage views requested');
1182
+ }
1183
+ /**
1184
+ * Handle open in tab request
1185
+ */
1186
+ onOpenInTabRequested(viewId) {
1187
+ // Use OpenEntityRecord to open the view as a resource in a new tab
1188
+ // Views are a known resource type in MJ
1189
+ const compositeKey = new CompositeKey();
1190
+ compositeKey.KeyValuePairs = [{ FieldName: 'ID', Value: viewId }];
1191
+ this.OpenEntityRecord.emit({
1192
+ EntityName: 'User Views',
1193
+ RecordPKey: compositeKey
1194
+ });
1195
+ }
1196
+ /**
1197
+ * Handle configure view request - opens the configuration panel
1198
+ */
1199
+ onConfigureViewRequested() {
1200
+ this.stateService.openViewConfigPanel();
1201
+ }
1202
+ /**
1203
+ * Close the view configuration panel
1204
+ */
1205
+ onCloseViewConfigPanel() {
1206
+ this.stateService.closeViewConfigPanel();
1207
+ }
1208
+ /**
1209
+ * Handle save view from config panel
1210
+ */
1211
+ async onSaveView(event) {
1212
+ if (!this.selectedEntity)
1213
+ return;
1214
+ try {
1215
+ const md = new Metadata();
1216
+ // Build GridState in Kendo-compatible format
1217
+ const gridState = this.buildGridState(event);
1218
+ // Build SortState in Kendo-compatible format
1219
+ const sortState = this.buildSortState(event);
1220
+ if (event.saveAsNew || !this.selectedViewEntity) {
1221
+ // Create new view
1222
+ const newView = await md.GetEntityObject('User Views');
1223
+ newView.Name = event.name || 'Custom';
1224
+ newView.Description = event.description;
1225
+ newView.EntityID = this.selectedEntity.ID;
1226
+ newView.UserID = md.CurrentUser.ID;
1227
+ newView.IsShared = event.isShared;
1228
+ newView.IsDefault = false;
1229
+ // Set GridState and SortState
1230
+ if (gridState) {
1231
+ newView.GridState = JSON.stringify(gridState);
1232
+ }
1233
+ if (sortState) {
1234
+ newView.SortState = JSON.stringify(sortState);
1235
+ }
1236
+ // Set Smart Filter settings
1237
+ newView.SmartFilterEnabled = event.smartFilterEnabled;
1238
+ newView.SmartFilterPrompt = event.smartFilterPrompt;
1239
+ const saved = await newView.Save();
1240
+ if (saved) {
1241
+ this.selectedViewEntity = newView;
1242
+ this.stateService.selectView(newView.ID);
1243
+ this.stateService.setViewModified(false);
1244
+ // Update currentGridState from the saved view to refresh the grid
1245
+ this.currentGridState = this.parseViewGridState(newView);
1246
+ // Refresh the view selector dropdown
1247
+ await this.viewSelectorRef?.loadViews();
1248
+ // Note: For new views, ngOnChanges will trigger refresh automatically
1249
+ // because selectedViewEntity reference changes
1250
+ }
1251
+ }
1252
+ else {
1253
+ // Update existing view
1254
+ this.selectedViewEntity.Name = event.name;
1255
+ this.selectedViewEntity.Description = event.description;
1256
+ this.selectedViewEntity.IsShared = event.isShared;
1257
+ // Update GridState and SortState
1258
+ if (gridState) {
1259
+ this.selectedViewEntity.GridState = JSON.stringify(gridState);
1260
+ }
1261
+ if (sortState) {
1262
+ this.selectedViewEntity.SortState = JSON.stringify(sortState);
1263
+ }
1264
+ // Update Smart Filter settings
1265
+ this.selectedViewEntity.SmartFilterEnabled = event.smartFilterEnabled;
1266
+ this.selectedViewEntity.SmartFilterPrompt = event.smartFilterPrompt;
1267
+ const saved = await this.selectedViewEntity.Save();
1268
+ if (saved) {
1269
+ this.stateService.setViewModified(false);
1270
+ // Update currentGridState from the saved view to refresh the grid columns
1271
+ this.currentGridState = this.parseViewGridState(this.selectedViewEntity);
1272
+ // Force change detection to ensure grid picks up the new gridState
1273
+ this.cdr.detectChanges();
1274
+ // Refresh the view selector dropdown
1275
+ await this.viewSelectorRef?.loadViews();
1276
+ // Note: Grid will rebuild columns via ngOnChanges when currentGridState changes
1277
+ }
1278
+ }
1279
+ this.stateService.closeViewConfigPanel();
1280
+ this.cdr.detectChanges();
1281
+ }
1282
+ catch (error) {
1283
+ console.error('[DataExplorer] Error saving view:', error);
1284
+ }
1285
+ }
1286
+ /**
1287
+ * Build GridState in Kendo-compatible format
1288
+ * Format: { columnSettings: [{ID, Name, DisplayName, hidden, width, orderIndex}], sortSettings: [{field, dir}] }
1289
+ *
1290
+ * Priority for column settings:
1291
+ * 1. If event.columns provided (from config panel) - use those
1292
+ * 2. If currentGridState exists (from grid interactions) - use that
1293
+ * 3. Otherwise return null
1294
+ */
1295
+ buildGridState(event) {
1296
+ let columnSettings;
1297
+ // First check if the event has columns configured (from config panel)
1298
+ if (event.columns.length > 0) {
1299
+ columnSettings = event.columns.map((col, idx) => ({
1300
+ ID: col.fieldId,
1301
+ Name: col.fieldName,
1302
+ DisplayName: col.displayName,
1303
+ hidden: false, // Visible columns only
1304
+ width: col.width || null,
1305
+ orderIndex: idx
1306
+ }));
1307
+ }
1308
+ // Otherwise, use the current grid state if available (from grid interactions)
1309
+ else if (this.currentGridState?.columnSettings && this.currentGridState.columnSettings.length > 0) {
1310
+ columnSettings = this.currentGridState.columnSettings;
1311
+ }
1312
+ // No columns to save
1313
+ else {
1314
+ return null;
1315
+ }
1316
+ // Build sort settings - prefer event.sortField, fall back to currentGridState
1317
+ let sortSettings;
1318
+ if (event.sortField) {
1319
+ sortSettings = [{
1320
+ field: event.sortField,
1321
+ dir: event.sortDirection // 'asc' or 'desc'
1322
+ }];
1323
+ }
1324
+ else if (this.currentGridState?.sortSettings && this.currentGridState.sortSettings.length > 0) {
1325
+ sortSettings = this.currentGridState.sortSettings;
1326
+ }
1327
+ return { columnSettings, sortSettings };
1328
+ }
1329
+ /**
1330
+ * Build SortState in Kendo-compatible format
1331
+ * Format: [{field, direction}] where direction is 'asc' or 'desc'
1332
+ */
1333
+ buildSortState(event) {
1334
+ if (!event.sortField)
1335
+ return null;
1336
+ return [{
1337
+ field: event.sortField,
1338
+ direction: event.sortDirection // 'asc' or 'desc'
1339
+ }];
1340
+ }
1341
+ /**
1342
+ * Handle delete view from config panel
1343
+ */
1344
+ async onDeleteView() {
1345
+ if (!this.selectedViewEntity)
1346
+ return;
1347
+ try {
1348
+ const deleted = await this.selectedViewEntity.Delete();
1349
+ if (deleted) {
1350
+ this.selectedViewEntity = null;
1351
+ this.stateService.selectView(null);
1352
+ this.stateService.closeViewConfigPanel();
1353
+ }
1354
+ }
1355
+ catch (error) {
1356
+ console.error('[DataExplorer] Error deleting view:', error);
1357
+ }
1358
+ }
1359
+ // ========================================
1360
+ // VIEW MODE & FILTERING (Dashboard Header)
1361
+ // ========================================
1362
+ /**
1363
+ * Handle view mode toggle from dashboard header
1364
+ */
1365
+ onViewModeChanged(mode) {
1366
+ this.stateService.setViewMode(mode);
1367
+ // Mark view as modified when view mode changes
1368
+ if (this.state.selectedViewId) {
1369
+ this.stateService.setViewModified(true);
1370
+ }
1371
+ }
1372
+ /**
1373
+ * Handle smart filter change from dashboard header
1374
+ */
1375
+ onSmartFilterChanged(prompt) {
1376
+ this.stateService.setSmartFilterPrompt(prompt);
1377
+ this.filterInput$.next(prompt);
1378
+ }
1379
+ /**
1380
+ * Handle filter text change from mj-entity-viewer (two-way binding)
1381
+ */
1382
+ onFilterTextChanged(filterText) {
1383
+ this.stateService.setSmartFilterPrompt(filterText);
1384
+ }
1385
+ // ========================================
1386
+ // ENTITY VIEWER EVENT HANDLERS
1387
+ // ========================================
1388
+ /**
1389
+ * Handle record selection from mj-entity-viewer
1390
+ */
1391
+ onViewerRecordSelected(event) {
1392
+ this.selectedRecord = event.record;
1393
+ // When selecting from grid, detail panel entity matches the grid entity
1394
+ this.detailPanelEntity = this.selectedEntity;
1395
+ const recordName = this.getRecordDisplayName(event.record);
1396
+ this.stateService.selectRecord(event.record.PrimaryKey.ToConcatenatedString(), recordName);
1397
+ // Add to recent items (local state for navigation panel)
1398
+ if (this.selectedEntity) {
1399
+ this.stateService.addRecentItem({
1400
+ entityName: this.selectedEntity.Name,
1401
+ compositeKeyString: event.record.PrimaryKey.ToConcatenatedString(),
1402
+ displayName: recordName
1403
+ });
1404
+ // Update local recent records immediately for instant home screen updates
1405
+ const recordId = event.record.PrimaryKey.KeyValuePairs[0]?.Value?.toString() || '';
1406
+ this.stateService.addLocalRecentRecord(this.selectedEntity.Name, this.selectedEntity.ID, recordId, recordName);
1407
+ // Log to User Record Logs for persistence (fire-and-forget)
1408
+ this.recentAccessService.logAccess(this.selectedEntity.Name, event.record.PrimaryKey.Values(), 'record');
1409
+ }
1410
+ }
1411
+ /**
1412
+ * Handle record opened from mj-entity-viewer (double-click or open button)
1413
+ */
1414
+ onViewerRecordOpened(event) {
1415
+ this.OpenEntityRecord.emit({
1416
+ EntityName: event.entity.Name,
1417
+ RecordPKey: event.compositeKey
1418
+ });
1419
+ }
1420
+ /**
1421
+ * Handle data loaded from mj-entity-viewer
1422
+ */
1423
+ onDataLoaded(event) {
1424
+ this.totalRecordCount = event.totalRowCount;
1425
+ this.filteredRecordCount = event.loadedRowCount;
1426
+ // Store loaded records for back/forward navigation lookup
1427
+ this.loadedRecords = event.records;
1428
+ // Handle pending record selection from deep link
1429
+ if (this.pendingRecordSelection) {
1430
+ const recordId = this.pendingRecordSelection;
1431
+ this.pendingRecordSelection = null; // Clear it so we don't keep trying
1432
+ // Try to find the record by primary key or concatenated string
1433
+ const record = event.records.find(r => {
1434
+ const pkString = r.PrimaryKey.ToConcatenatedString();
1435
+ const pkValue = r.PrimaryKey.KeyValuePairs[0]?.Value?.toString();
1436
+ return pkString === recordId || pkValue === recordId;
1437
+ });
1438
+ if (record) {
1439
+ this.selectedRecord = record;
1440
+ this.detailPanelEntity = this.selectedEntity;
1441
+ const recordName = this.getRecordDisplayName(record);
1442
+ this.stateService.selectRecord(record.PrimaryKey.ToConcatenatedString(), recordName);
1443
+ }
1444
+ else {
1445
+ console.warn(`[DataExplorer] Deep link record not found: ${recordId}`);
1446
+ }
1447
+ }
1448
+ // Restore selected record if we have a persisted selectedRecordId
1449
+ else if (this.state.selectedRecordId && this.state.detailPanelOpen && !this.selectedRecord) {
1450
+ const record = event.records.find(r => r.PrimaryKey.ToConcatenatedString() === this.state.selectedRecordId);
1451
+ if (record) {
1452
+ this.selectedRecord = record;
1453
+ this.detailPanelEntity = this.selectedEntity;
1454
+ }
1455
+ }
1456
+ this.cdr.detectChanges();
1457
+ }
1458
+ /**
1459
+ * Handle filtered count change from mj-entity-viewer
1460
+ */
1461
+ onFilteredCountChanged(event) {
1462
+ this.filteredRecordCount = event.filteredCount;
1463
+ this.totalRecordCount = event.totalCount;
1464
+ this.cdr.detectChanges();
1465
+ }
1466
+ // ========================================
1467
+ // DETAIL PANEL
1468
+ // ========================================
1469
+ /**
1470
+ * Handle detail panel close
1471
+ */
1472
+ onDetailPanelClosed() {
1473
+ this.selectedRecord = null;
1474
+ this.detailPanelEntity = null;
1475
+ this.stateService.closeDetailPanel();
1476
+ }
1477
+ /**
1478
+ * Handle opening a record in full view (from detail panel)
1479
+ * Uses detailPanelEntity since the panel may be showing a different entity than the grid
1480
+ */
1481
+ onOpenRecord(record) {
1482
+ if (!this.detailPanelEntity)
1483
+ return;
1484
+ this.OpenEntityRecord.emit({
1485
+ EntityName: this.detailPanelEntity.Name,
1486
+ RecordPKey: record.PrimaryKey
1487
+ });
1488
+ }
1489
+ /**
1490
+ * Handle navigation to a related entity from detail panel.
1491
+ * Navigates within the explorer and applies filter to show related records.
1492
+ */
1493
+ onNavigateToRelated(event) {
1494
+ const entity = this.entities.find(e => e.Name === event.entityName);
1495
+ if (!entity) {
1496
+ // Entity not in our filtered list - it may exist in the system but not be part of this app
1497
+ console.warn(`Entity not found in explorer: ${event.entityName}`);
1498
+ return;
1499
+ }
1500
+ // Close detail panel and clear current record
1501
+ this.selectedRecord = null;
1502
+ this.detailPanelEntity = null;
1503
+ this.stateService.closeDetailPanel();
1504
+ // Navigate to the entity
1505
+ this.selectedEntity = entity;
1506
+ this.stateService.selectEntity(entity.Name);
1507
+ // Apply the filter to show related records
1508
+ // The filter is in SQL format like "ParentID='xxx'" - we just show it in the filter box
1509
+ // The entity viewer will apply it as a smart filter
1510
+ if (event.filter) {
1511
+ // For now, we'll apply the filter as-is
1512
+ // A future enhancement could parse and display it more user-friendly
1513
+ this.stateService.setSmartFilterPrompt(event.filter);
1514
+ this.debouncedFilterText = event.filter;
1515
+ }
1516
+ }
1517
+ /**
1518
+ * Handle opening a related record - display in detail panel (not new tab)
1519
+ * The record is already loaded, so just update the detail panel
1520
+ */
1521
+ onOpenRelatedRecord(event) {
1522
+ this.showRecordInDetailPanel(event.entityName, event.record);
1523
+ }
1524
+ /**
1525
+ * Handle opening a foreign key record (from FK field link in detail panel)
1526
+ * Loads the record and displays it in the detail panel
1527
+ */
1528
+ async onOpenForeignKeyRecord(event) {
1529
+ await this.loadAndShowRecordInDetailPanel(event.entityName, event.recordId);
1530
+ }
1531
+ /**
1532
+ * Show an already-loaded record in the detail panel
1533
+ * Note: This does NOT change selectedEntity (the main grid's entity)
1534
+ * It only updates detailPanelEntity which is used by the detail panel
1535
+ */
1536
+ showRecordInDetailPanel(entityName, record) {
1537
+ const entityInfo = this.metadata.Entities.find(e => e.Name === entityName);
1538
+ if (!entityInfo) {
1539
+ console.warn(`Entity not found: ${entityName}`);
1540
+ return;
1541
+ }
1542
+ // Update the detail panel entity and record
1543
+ // detailPanelEntity may differ from selectedEntity when viewing FK/related records
1544
+ this.detailPanelEntity = entityInfo;
1545
+ this.selectedRecord = record;
1546
+ // Use selectRecord to open the panel with proper state tracking
1547
+ const recordName = this.getRecordDisplayName(record);
1548
+ this.stateService.selectRecord(record.PrimaryKey.ToConcatenatedString(), recordName);
1549
+ this.cdr.detectChanges();
1550
+ }
1551
+ /**
1552
+ * Load a record by ID and show it in the detail panel
1553
+ */
1554
+ async loadAndShowRecordInDetailPanel(entityName, recordId) {
1555
+ const entityInfo = this.metadata.Entities.find(e => e.Name === entityName);
1556
+ if (!entityInfo) {
1557
+ console.warn(`Entity not found: ${entityName}`);
1558
+ return;
1559
+ }
1560
+ try {
1561
+ // Load the record
1562
+ const rv = new RunView();
1563
+ const result = await rv.RunView({
1564
+ EntityName: entityName,
1565
+ ExtraFilter: `ID='${recordId}'`,
1566
+ ResultType: 'entity_object',
1567
+ MaxRows: 1
1568
+ });
1569
+ if (result.Success && result.Results.length > 0) {
1570
+ this.showRecordInDetailPanel(entityName, result.Results[0]);
1571
+ }
1572
+ else {
1573
+ console.warn(`Record not found: ${entityName} ID=${recordId}`);
1574
+ }
1575
+ }
1576
+ catch (error) {
1577
+ console.error(`Failed to load record: ${entityName} ID=${recordId}`, error);
1578
+ }
1579
+ }
1580
+ // ========================================
1581
+ // NAVIGATION PANEL
1582
+ // ========================================
1583
+ /**
1584
+ * Handle opening a record from navigation panel (recent/favorites)
1585
+ */
1586
+ onOpenRecordFromNav(event) {
1587
+ this.OpenEntityRecord.emit({
1588
+ EntityName: event.entityName,
1589
+ RecordPKey: event.compositeKey
1590
+ });
1591
+ }
1592
+ /**
1593
+ * Handle selecting a record from navigation panel (recent/favorites).
1594
+ * Navigates to the entity within Data Explorer and selects the record
1595
+ * in the detail panel (instead of opening full record view).
1596
+ */
1597
+ onSelectRecordFromNav(event) {
1598
+ const entity = this.entities.find(e => e.Name === event.entityName);
1599
+ if (entity) {
1600
+ // Set pending record selection - will be resolved in onDataLoaded
1601
+ this.pendingRecordSelection = event.recordId;
1602
+ this.onEntitySelected(entity);
1603
+ }
1604
+ }
1605
+ /**
1606
+ * Toggle navigation panel
1607
+ */
1608
+ toggleNavigationPanel() {
1609
+ this.stateService.toggleNavigationPanel();
1610
+ }
1611
+ /**
1612
+ * Handle expand and focus from collapsed nav icon click
1613
+ */
1614
+ onExpandAndFocus(section) {
1615
+ this.stateService.expandAndFocusSection(section);
1616
+ }
1617
+ // ========================================
1618
+ // DEEP LINK HANDLING
1619
+ // ========================================
1620
+ /**
1621
+ * Apply a deep link to navigate to a specific entity/record
1622
+ */
1623
+ async applyDeepLink(deepLink) {
1624
+ // Apply view mode if specified
1625
+ if (deepLink.viewMode) {
1626
+ this.stateService.setViewMode(deepLink.viewMode);
1627
+ }
1628
+ // Navigate to entity if specified
1629
+ if (deepLink.entity) {
1630
+ const entity = this.entities.find(e => e.Name.toLowerCase() === deepLink.entity.toLowerCase());
1631
+ if (entity) {
1632
+ // Reset counts before setting entity to prevent stale data display
1633
+ this.resetRecordCounts();
1634
+ this.selectedEntity = entity;
1635
+ this.stateService.selectEntity(entity.Name);
1636
+ // Apply filter if specified
1637
+ if (deepLink.filter) {
1638
+ this.stateService.setSmartFilterPrompt(deepLink.filter);
1639
+ this.debouncedFilterText = deepLink.filter;
1640
+ }
1641
+ // Note: Record selection is handled after data loads via onDataLoaded
1642
+ // We store the record ID to select once data is available
1643
+ if (deepLink.record) {
1644
+ this.pendingRecordSelection = deepLink.record;
1645
+ }
1646
+ }
1647
+ else {
1648
+ console.warn(`[DataExplorer] Deep link entity not found: ${deepLink.entity}`);
1649
+ }
1650
+ }
1651
+ }
1652
+ /** Record ID to select once data loads (from deep link) */
1653
+ pendingRecordSelection = null;
1654
+ // ========================================
1655
+ // BREADCRUMB NAVIGATION
1656
+ // ========================================
1657
+ /**
1658
+ * Handle breadcrumb click - navigate to that level
1659
+ */
1660
+ onBreadcrumbClick(breadcrumb, index) {
1661
+ // Don't navigate if it's the last (current) breadcrumb
1662
+ if (index === this.breadcrumbs.length - 1) {
1663
+ return;
1664
+ }
1665
+ this.stateService.navigateToBreadcrumb(breadcrumb);
1666
+ // If navigating to application level, clear entity selection
1667
+ if (breadcrumb.type === 'application') {
1668
+ this.selectedEntity = null;
1669
+ this.selectedRecord = null;
1670
+ this.detailPanelEntity = null;
1671
+ }
1672
+ }
1673
+ // ========================================
1674
+ // HELPERS
1675
+ // ========================================
1676
+ /**
1677
+ * Get the set of allowed entity names for filtering favorites/recents.
1678
+ * Returns null if no filter is active (all entities allowed).
1679
+ */
1680
+ get allowedEntityNames() {
1681
+ if (!this.entityFilter) {
1682
+ return null;
1683
+ }
1684
+ return new Set(this.entities.map(e => e.Name));
1685
+ }
1686
+ /**
1687
+ * Get display name for a record
1688
+ */
1689
+ getRecordDisplayName(record) {
1690
+ if (!this.selectedEntity)
1691
+ return 'Unknown';
1692
+ if (this.selectedEntity.NameField) {
1693
+ const nameValue = record.Get(this.selectedEntity.NameField.Name);
1694
+ if (nameValue)
1695
+ return String(nameValue);
1696
+ }
1697
+ return record.PrimaryKey.ToString();
1698
+ }
1699
+ /**
1700
+ * Get the icon class for an entity
1701
+ */
1702
+ getEntityIcon(entity) {
1703
+ if (entity.Icon) {
1704
+ return this.formatEntityIcon(entity.Icon);
1705
+ }
1706
+ return 'fa-solid fa-table';
1707
+ }
1708
+ /**
1709
+ * Format entity icon to ensure proper Font Awesome class format
1710
+ */
1711
+ formatEntityIcon(icon) {
1712
+ if (!icon) {
1713
+ return 'fa-solid fa-table';
1714
+ }
1715
+ if (icon.startsWith('fa-') || icon.startsWith('fa ')) {
1716
+ if (icon.startsWith('fa-solid') || icon.startsWith('fa-regular') ||
1717
+ icon.startsWith('fa-light') || icon.startsWith('fa-brands') ||
1718
+ icon.startsWith('fa ')) {
1719
+ return icon;
1720
+ }
1721
+ return `fa-solid ${icon}`;
1722
+ }
1723
+ return `fa-solid fa-${icon}`;
1724
+ }
1725
+ // ========================================
1726
+ // URL DEEP LINKING
1727
+ // ========================================
1728
+ /**
1729
+ * Parse URL query string and return a deep link object.
1730
+ * Returns null if no relevant params found.
1731
+ *
1732
+ * Query params:
1733
+ * - entity: Selected entity name
1734
+ * - record: Selected record ID (URL segment format via CompositeKey)
1735
+ * - filter: Current filter text
1736
+ * - view: View mode (grid or cards)
1737
+ */
1738
+ parseUrlState() {
1739
+ const url = this.router.url;
1740
+ const queryIndex = url.indexOf('?');
1741
+ if (queryIndex === -1) {
1742
+ return null;
1743
+ }
1744
+ const queryString = url.substring(queryIndex + 1);
1745
+ const params = new URLSearchParams(queryString);
1746
+ const entity = params.get('entity');
1747
+ const record = params.get('record');
1748
+ const filter = params.get('filter');
1749
+ const view = params.get('view');
1750
+ // If no params, return null
1751
+ if (!entity && !record && !filter && !view) {
1752
+ return null;
1753
+ }
1754
+ return {
1755
+ entity: entity || undefined,
1756
+ record: record || undefined,
1757
+ filter: filter || undefined,
1758
+ viewMode: view || undefined
1759
+ };
1760
+ }
1761
+ /**
1762
+ * Apply URL state to the component.
1763
+ * Used both during init and for popstate handling.
1764
+ */
1765
+ applyUrlState(urlState) {
1766
+ // Apply view mode if specified
1767
+ if (urlState.viewMode) {
1768
+ this.stateService.setViewMode(urlState.viewMode);
1769
+ }
1770
+ // Navigate to entity if specified
1771
+ if (urlState.entity) {
1772
+ const entity = this.entities.find(e => e.Name.toLowerCase() === urlState.entity.toLowerCase());
1773
+ if (entity) {
1774
+ const entityChanged = this.selectedEntity?.Name !== entity.Name;
1775
+ if (entityChanged) {
1776
+ // Entity changed - reset counts and select new entity
1777
+ this.resetRecordCounts();
1778
+ this.selectedEntity = entity;
1779
+ this.stateService.selectEntity(entity.Name);
1780
+ }
1781
+ // Apply filter if specified
1782
+ if (urlState.filter) {
1783
+ this.stateService.setSmartFilterPrompt(urlState.filter);
1784
+ this.debouncedFilterText = urlState.filter;
1785
+ }
1786
+ else if (entityChanged) {
1787
+ // Only clear filter if entity changed (selectEntity already handles this)
1788
+ this.stateService.setSmartFilterPrompt('');
1789
+ this.debouncedFilterText = '';
1790
+ }
1791
+ // Handle record selection
1792
+ if (urlState.record) {
1793
+ if (entityChanged) {
1794
+ // Entity changed - need to wait for data to load
1795
+ this.pendingRecordSelection = urlState.record;
1796
+ }
1797
+ else {
1798
+ // Entity is the same - find record from already-loaded data
1799
+ const record = this.loadedRecords.find(r => {
1800
+ const pkString = r.PrimaryKey.ToConcatenatedString();
1801
+ const pkValue = r.PrimaryKey.KeyValuePairs[0]?.Value?.toString();
1802
+ return pkString === urlState.record || pkValue === urlState.record;
1803
+ });
1804
+ if (record) {
1805
+ this.selectedRecord = record;
1806
+ this.detailPanelEntity = this.selectedEntity;
1807
+ const recordName = this.getRecordDisplayName(record);
1808
+ this.stateService.selectRecord(record.PrimaryKey.ToConcatenatedString(), recordName);
1809
+ }
1810
+ else {
1811
+ // Record not in current page - update state but panel won't show
1812
+ this.stateService.selectRecord(urlState.record, this.state.selectedRecordName || undefined);
1813
+ }
1814
+ }
1815
+ }
1816
+ else {
1817
+ // Clear record selection if not in URL
1818
+ this.selectedRecord = null;
1819
+ this.detailPanelEntity = null;
1820
+ this.stateService.closeDetailPanel();
1821
+ }
1822
+ }
1823
+ }
1824
+ else {
1825
+ // No entity in URL - go to home view
1826
+ this.selectedEntity = null;
1827
+ this.selectedRecord = null;
1828
+ this.detailPanelEntity = null;
1829
+ this.stateService.selectEntity(null);
1830
+ this.stateService.closeDetailPanel();
1831
+ }
1832
+ this.cdr.detectChanges();
1833
+ }
1834
+ /**
1835
+ * Update the URL query string to reflect current navigation state.
1836
+ * This enables deep linking - users can bookmark or share URLs to specific views.
1837
+ * Called immediately on state changes (not debounced).
1838
+ * Uses Angular Router for proper browser history integration.
1839
+ */
1840
+ updateUrl() {
1841
+ const params = new URLSearchParams();
1842
+ // Add entity if selected
1843
+ if (this.state.selectedEntityName) {
1844
+ params.set('entity', this.state.selectedEntityName);
1845
+ }
1846
+ // Add record if selected (only if entity is also selected)
1847
+ // Use the stored selectedRecordId which is already in URL segment format
1848
+ if (this.state.selectedRecordId && this.state.selectedEntityName) {
1849
+ // The selectedRecordId is stored using ToConcatenatedString which uses the same format as ToURLSegment
1850
+ params.set('record', this.state.selectedRecordId);
1851
+ }
1852
+ // Add filter if present (only if entity is also selected)
1853
+ if (this.state.smartFilterPrompt && this.state.selectedEntityName) {
1854
+ params.set('filter', this.state.smartFilterPrompt);
1855
+ }
1856
+ // Add view mode if not default (grid is default)
1857
+ if (this.state.viewMode && this.state.viewMode !== 'grid') {
1858
+ params.set('view', this.state.viewMode);
1859
+ }
1860
+ // Get the current path without query string
1861
+ const currentUrl = this.router.url;
1862
+ const currentPath = currentUrl.split('?')[0];
1863
+ // Build the new URL
1864
+ const queryString = params.toString();
1865
+ const newUrl = queryString ? `${currentPath}?${queryString}` : currentPath;
1866
+ // Track this URL so we don't react to our own navigation
1867
+ this.lastNavigatedUrl = newUrl;
1868
+ // Use Angular Router for proper browser history integration
1869
+ // This allows back/forward buttons to work correctly
1870
+ this.router.navigateByUrl(newUrl, { replaceUrl: false });
1871
+ }
1872
+ /**
1873
+ * Handle external navigation (back/forward buttons).
1874
+ * Parses the URL and applies the state without triggering a new navigation.
1875
+ */
1876
+ onExternalNavigation(url) {
1877
+ // Check if this URL is for our component (contains our base path)
1878
+ const currentPath = this.router.url.split('?')[0];
1879
+ const newPath = url.split('?')[0];
1880
+ // Only handle if we're still on the same base path (same dashboard instance)
1881
+ if (currentPath !== newPath) {
1882
+ return; // Different route entirely, shell will handle it
1883
+ }
1884
+ // Parse the new URL state
1885
+ const urlState = this.parseUrlFromString(url);
1886
+ // Apply the state without triggering URL updates
1887
+ this.skipUrlUpdates = true;
1888
+ if (urlState) {
1889
+ this.applyUrlState(urlState);
1890
+ }
1891
+ else {
1892
+ // No params means go to home view
1893
+ this.selectedEntity = null;
1894
+ this.selectedRecord = null;
1895
+ this.detailPanelEntity = null;
1896
+ this.stateService.selectEntity(null);
1897
+ this.stateService.closeDetailPanel();
1898
+ }
1899
+ this.skipUrlUpdates = false;
1900
+ // Update the tracked URL
1901
+ this.lastNavigatedUrl = url;
1902
+ this.cdr.detectChanges();
1903
+ }
1904
+ /**
1905
+ * Parse URL state from a URL string (used for external navigation).
1906
+ */
1907
+ parseUrlFromString(url) {
1908
+ const queryIndex = url.indexOf('?');
1909
+ if (queryIndex === -1) {
1910
+ return null;
1911
+ }
1912
+ const queryString = url.substring(queryIndex + 1);
1913
+ const params = new URLSearchParams(queryString);
1914
+ const entity = params.get('entity');
1915
+ const record = params.get('record');
1916
+ const filterParam = params.get('filter');
1917
+ const view = params.get('view');
1918
+ // If no params, return null
1919
+ if (!entity && !record && !filterParam && !view) {
1920
+ return null;
1921
+ }
1922
+ return {
1923
+ entity: entity || undefined,
1924
+ record: record || undefined,
1925
+ filter: filterParam || undefined,
1926
+ viewMode: view || undefined
1927
+ };
1928
+ }
1929
+ // ========================================
1930
+ // HOME SCREEN ACTIONS
1931
+ // ========================================
1932
+ /**
1933
+ * Toggle entity favorite status
1934
+ */
1935
+ async toggleEntityFavorite(entity, event) {
1936
+ event.stopPropagation(); // Prevent card click
1937
+ if (this.stateService.isEntityFavorited(entity.ID)) {
1938
+ await this.stateService.removeEntityFromFavorites(entity.ID);
1939
+ }
1940
+ else {
1941
+ await this.stateService.addEntityToFavorites(entity.Name, entity.ID);
1942
+ }
1943
+ this.cdr.detectChanges();
1944
+ }
1945
+ /**
1946
+ * Check if entity is favorited (for template)
1947
+ */
1948
+ isEntityFavorited(entity) {
1949
+ return this.stateService.isEntityFavorited(entity.ID);
1950
+ }
1951
+ /**
1952
+ * Toggle show all entities vs common entities
1953
+ */
1954
+ toggleShowAllEntities() {
1955
+ this.stateService.toggleShowAllEntities();
1956
+ }
1957
+ /**
1958
+ * Handle clicking on a recent record from home screen.
1959
+ * Navigates to the entity and sets up pending selection to select the record
1960
+ * and open the detail panel once data loads.
1961
+ */
1962
+ onRecentRecordClick(record) {
1963
+ const entity = this.entities.find(e => e.ID === record.entityId);
1964
+ if (entity) {
1965
+ // Set pending record selection - will be resolved in onDataLoaded
1966
+ this.pendingRecordSelection = record.recordId;
1967
+ this.onEntitySelected(entity);
1968
+ }
1969
+ }
1970
+ /**
1971
+ * Handle clicking on a favorite record from home screen.
1972
+ * Navigates to the entity and sets up pending selection to select the record
1973
+ * and open the detail panel once data loads.
1974
+ */
1975
+ onFavoriteRecordClick(record) {
1976
+ const entity = this.entities.find(e => e.ID === record.entityId);
1977
+ if (entity) {
1978
+ // Set pending record selection - will be resolved in onDataLoaded
1979
+ this.pendingRecordSelection = record.recordId;
1980
+ this.onEntitySelected(entity);
1981
+ }
1982
+ }
1983
+ /**
1984
+ * Get the icon for an entity by ID (for recent records)
1985
+ */
1986
+ getEntityIconById(entityId) {
1987
+ const entity = this.metadata.Entities.find(e => e.ID === entityId);
1988
+ if (entity) {
1989
+ return this.getEntityIcon(entity);
1990
+ }
1991
+ return 'fa-solid fa-table';
1992
+ }
1993
+ /**
1994
+ * Format relative time for display (e.g., "2 hours ago")
1995
+ */
1996
+ formatRelativeTime(date) {
1997
+ const now = new Date();
1998
+ const diff = now.getTime() - new Date(date).getTime();
1999
+ const minutes = Math.floor(diff / 60000);
2000
+ const hours = Math.floor(diff / 3600000);
2001
+ const days = Math.floor(diff / 86400000);
2002
+ if (minutes < 1)
2003
+ return 'Just now';
2004
+ if (minutes < 60)
2005
+ return `${minutes}m ago`;
2006
+ if (hours < 24)
2007
+ return `${hours}h ago`;
2008
+ if (days < 7)
2009
+ return `${days}d ago`;
2010
+ return new Date(date).toLocaleDateString();
2011
+ }
2012
+ /**
2013
+ * Check if we're at the home level (no entity selected)
2014
+ */
2015
+ get isAtHomeLevel() {
2016
+ return !this.selectedEntity;
2017
+ }
2018
+ static ɵfac = function DataExplorerDashboardComponent_Factory(t) { return new (t || DataExplorerDashboardComponent)(i0.ɵɵdirectiveInject(i1.ExplorerStateService), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i2.Router), i0.ɵɵdirectiveInject(i3.RecentAccessService)); };
2019
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: DataExplorerDashboardComponent, selectors: [["mj-data-explorer-dashboard"]], viewQuery: function DataExplorerDashboardComponent_Query(rf, ctx) { if (rf & 1) {
2020
+ i0.ɵɵviewQuery(_c0, 5);
2021
+ i0.ɵɵviewQuery(ViewSelectorComponent, 5);
2022
+ i0.ɵɵviewQuery(EntityViewerComponent, 5);
2023
+ } if (rf & 2) {
2024
+ let _t;
2025
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.filterInputRef = _t.first);
2026
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.viewSelectorRef = _t.first);
2027
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.entityViewerRef = _t.first);
2028
+ } }, hostBindings: function DataExplorerDashboardComponent_HostBindings(rf, ctx) { if (rf & 1) {
2029
+ i0.ɵɵlistener("keydown", function DataExplorerDashboardComponent_keydown_HostBindingHandler($event) { return ctx.handleKeyboardShortcut($event); }, false, i0.ɵɵresolveDocument);
2030
+ } }, inputs: { entityFilter: "entityFilter", deepLink: "deepLink", contextName: "contextName", contextIcon: "contextIcon" }, features: [i0.ɵɵInheritDefinitionFeature, i0.ɵɵNgOnChangesFeature], decls: 18, vars: 15, consts: [["filterInput", ""], [1, "data-explorer-container"], [1, "navigation-panel", 3, "collapsed", "width"], [1, "content-area"], [1, "breadcrumb-bar"], [1, "content-header"], [1, "header-left"], [1, "header-center"], [1, "smart-filter-container"], [1, "header-right"], [1, "view-mode-toggle"], [1, "content-body"], [1, "entity-home-view"], [3, "entity", "viewEntity", "viewMode", "filterText", "selectedRecordId", "config", "gridState"], [1, "detail-panel", 3, "width"], [3, "close", "save", "delete", "entity", "viewEntity", "isOpen", "currentGridState"], [1, "navigation-panel"], [3, "entitySelected", "toggleCollapse", "sectionToggled", "openRecord", "selectRecord", "expandAndFocus", "entities", "selectedEntityName", "favorites", "recentItems", "collapsed", "allowedEntityNames", "favoritesSectionExpanded", "recentSectionExpanded", "entitiesSectionExpanded", "viewsSectionExpanded"], [1, "breadcrumb-item", 3, "click", "title"], [1, "breadcrumb-icon", 3, "class"], [1, "breadcrumb-label"], [1, "fa-solid", "fa-chevron-right", "breadcrumb-separator"], [1, "breadcrumb-icon"], [1, "entity-icon"], [1, "entity-title"], [1, "record-count"], [3, "viewSelected", "saveViewRequested", "manageViewsRequested", "openInTabRequested", "configureViewRequested", "entity", "selectedViewId", "viewModified"], [1, "entity-icon", 3, "class"], [1, "fa-solid", "fa-search", "filter-icon"], ["type", "text", "placeholder", "Filter records... (press / to focus)", 1, "smart-filter-input", 3, "ngModelChange", "ngModel"], [1, "clear-filter-btn"], [1, "clear-filter-btn", 3, "click"], [1, "fa-solid", "fa-times"], ["type", "text", "placeholder", "Filter entities... (press / to focus)", 1, "smart-filter-input", 3, "ngModelChange", "ngModel"], ["title", "Grid View", 1, "toggle-btn", 3, "click"], [1, "fa-solid", "fa-list"], ["title", "Cards View", 1, "toggle-btn", 3, "click"], [1, "fa-solid", "fa-grip"], [1, "loading-state"], [1, "home-section"], ["text", "Loading entities...", "size", "medium"], [1, "home-two-column-layout"], [1, "section-header-with-toggle"], [1, "section-header-left"], [1, "fa-solid", "fa-database", "section-icon"], [1, "section-title"], [1, "section-count"], [1, "inline-toggle"], [1, "entity-card-grid"], [1, "entity-card", 3, "title"], [1, "empty-state", "small"], [1, "empty-state"], [1, "home-column"], [1, "section-header"], [1, "fa-solid", "fa-history", "section-icon"], [1, "entity-filter-strip"], [1, "recent-records-list"], [1, "recent-record-item", 3, "title"], ["title", "Show all entities", 1, "entity-filter-btn", 3, "click"], [1, "fa-solid", "fa-layer-group"], [1, "entity-filter-btn", 3, "active", "title"], [1, "entity-filter-btn", 3, "click", "title"], [1, "recent-record-item", 3, "click", "title"], [1, "recent-record-icon"], [1, "recent-record-content"], [1, "recent-record-name"], [1, "recent-record-entity"], [1, "recent-record-time"], [1, "fa-solid", "fa-star", "section-icon"], [1, "fa-solid", "fa-clock", "section-icon"], [1, "entity-list"], [1, "entity-list-item", 3, "title"], [1, "entity-list-item", 3, "click", "title"], [1, "entity-list-icon"], [1, "entity-list-content"], [1, "entity-list-name"], [1, "favorite-btn", 3, "click", "title"], ["title", "Remove from favorites", 1, "favorite-btn", "favorited", 3, "click"], [1, "fa-solid", "fa-star"], [1, "toggle-btn", 3, "click", "title"], [1, "entity-card", 3, "click", "title"], [1, "entity-card-icon"], [1, "entity-card-content"], [1, "entity-card-title"], [1, "entity-card-description"], [1, "fa-solid", "fa-search", "empty-icon"], [1, "fa-solid", "fa-database", "empty-icon"], [3, "viewModeChange", "filterTextChange", "recordSelected", "recordOpened", "dataLoaded", "filteredCountChanged", "gridStateChanged", "entity", "viewEntity", "viewMode", "filterText", "selectedRecordId", "config", "gridState"], [1, "detail-panel"], [3, "close", "openRecord", "navigateToRelated", "openRelatedRecord", "openForeignKeyRecord", "entity", "record"]], template: function DataExplorerDashboardComponent_Template(rf, ctx) { if (rf & 1) {
2031
+ i0.ɵɵelementStart(0, "div", 1);
2032
+ i0.ɵɵtemplate(1, DataExplorerDashboardComponent_Conditional_1_Template, 2, 15, "div", 2);
2033
+ i0.ɵɵelementStart(2, "div", 3);
2034
+ i0.ɵɵtemplate(3, DataExplorerDashboardComponent_Conditional_3_Template, 3, 0, "div", 4);
2035
+ i0.ɵɵelementStart(4, "div", 5)(5, "div", 6);
2036
+ i0.ɵɵtemplate(6, DataExplorerDashboardComponent_Conditional_6_Template, 6, 7)(7, DataExplorerDashboardComponent_Conditional_7_Template, 5, 3);
2037
+ i0.ɵɵelementEnd();
2038
+ i0.ɵɵelementStart(8, "div", 7);
2039
+ i0.ɵɵtemplate(9, DataExplorerDashboardComponent_Conditional_9_Template, 5, 2, "div", 8)(10, DataExplorerDashboardComponent_Conditional_10_Template, 5, 2, "div", 8);
2040
+ i0.ɵɵelementEnd();
2041
+ i0.ɵɵelementStart(11, "div", 9);
2042
+ i0.ɵɵtemplate(12, DataExplorerDashboardComponent_Conditional_12_Template, 5, 4, "div", 10);
2043
+ i0.ɵɵelementEnd()();
2044
+ i0.ɵɵelementStart(13, "div", 11);
2045
+ i0.ɵɵtemplate(14, DataExplorerDashboardComponent_Conditional_14_Template, 3, 1, "div", 12)(15, DataExplorerDashboardComponent_Conditional_15_Template, 1, 7, "mj-entity-viewer", 13);
2046
+ i0.ɵɵelementEnd()();
2047
+ i0.ɵɵtemplate(16, DataExplorerDashboardComponent_Conditional_16_Template, 2, 4, "div", 14);
2048
+ i0.ɵɵelementStart(17, "mj-view-config-panel", 15);
2049
+ i0.ɵɵlistener("close", function DataExplorerDashboardComponent_Template_mj_view_config_panel_close_17_listener() { return ctx.onCloseViewConfigPanel(); })("save", function DataExplorerDashboardComponent_Template_mj_view_config_panel_save_17_listener($event) { return ctx.onSaveView($event); })("delete", function DataExplorerDashboardComponent_Template_mj_view_config_panel_delete_17_listener() { return ctx.onDeleteView(); });
2050
+ i0.ɵɵelementEnd()();
2051
+ } if (rf & 2) {
2052
+ i0.ɵɵadvance();
2053
+ i0.ɵɵconditional(!ctx.isAtHomeLevel ? 1 : -1);
2054
+ i0.ɵɵadvance(2);
2055
+ i0.ɵɵconditional(!ctx.isAtHomeLevel && ctx.breadcrumbs.length > 0 ? 3 : -1);
2056
+ i0.ɵɵadvance();
2057
+ i0.ɵɵclassProp("home-header", ctx.isAtHomeLevel);
2058
+ i0.ɵɵadvance(2);
2059
+ i0.ɵɵconditional(ctx.selectedEntity ? 6 : 7);
2060
+ i0.ɵɵadvance(3);
2061
+ i0.ɵɵconditional(ctx.selectedEntity ? 9 : 10);
2062
+ i0.ɵɵadvance(3);
2063
+ i0.ɵɵconditional(ctx.selectedEntity ? 12 : -1);
2064
+ i0.ɵɵadvance();
2065
+ i0.ɵɵclassProp("home-content", ctx.isAtHomeLevel);
2066
+ i0.ɵɵadvance();
2067
+ i0.ɵɵconditional(!ctx.selectedEntity ? 14 : 15);
2068
+ i0.ɵɵadvance(2);
2069
+ i0.ɵɵconditional(ctx.state.detailPanelOpen && ctx.selectedRecord ? 16 : -1);
2070
+ i0.ɵɵadvance();
2071
+ i0.ɵɵproperty("entity", ctx.selectedEntity)("viewEntity", ctx.selectedViewEntity)("isOpen", ctx.state.viewConfigPanelOpen)("currentGridState", ctx.currentGridState);
2072
+ } }, dependencies: [i4.DefaultValueAccessor, i4.NgControlStatus, i4.NgModel, i5.EntityViewerComponent, i5.EntityRecordDetailPanelComponent, i3.LoadingComponent, i6.NavigationPanelComponent, i7.ViewSelectorComponent, i8.ViewConfigPanelComponent, i9.DecimalPipe], styles: [".data-explorer-container[_ngcontent-%COMP%] {\n display: flex;\n height: 100%;\n width: 100%;\n background: #f5f7fa;\n overflow: hidden;\n}\n\n.navigation-panel[_ngcontent-%COMP%] {\n flex-shrink: 0;\n height: 100%;\n background: white;\n border-right: 1px solid #e0e0e0;\n transition: width 0.2s ease-in-out;\n overflow: hidden;\n box-shadow: 2px 0 8px rgba(0, 0, 0, 0.04);\n}\n.navigation-panel.collapsed[_ngcontent-%COMP%] {\n width: 48px;\n}\n\n.content-area[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n height: 100%;\n min-width: 0;\n overflow: hidden;\n background: #f5f7fa;\n}\n\n\n\n.breadcrumb-bar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 24px;\n background: white;\n border-bottom: 1px solid #eee;\n flex-shrink: 0;\n font-size: 13px;\n min-height: 40px;\n}\n\n.breadcrumb-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n color: #666;\n padding: 4px 8px;\n border-radius: 4px;\n transition: all 0.15s ease;\n max-width: 200px;\n}\n\n.breadcrumb-item.clickable[_ngcontent-%COMP%] {\n cursor: pointer;\n}\n\n.breadcrumb-item.clickable[_ngcontent-%COMP%]:hover {\n background: #f0f0f0;\n color: #1976d2;\n}\n\n.breadcrumb-item.current[_ngcontent-%COMP%] {\n color: #1a1a1a;\n font-weight: 500;\n cursor: default;\n}\n\n.breadcrumb-icon[_ngcontent-%COMP%] {\n font-size: 12px;\n flex-shrink: 0;\n}\n\n.breadcrumb-label[_ngcontent-%COMP%] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.breadcrumb-separator[_ngcontent-%COMP%] {\n font-size: 10px;\n color: #ccc;\n flex-shrink: 0;\n}\n\n.content-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 24px;\n background: white;\n border-bottom: 1px solid #e0e0e0;\n flex-shrink: 0;\n gap: 24px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);\n}\n\n.header-left[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-shrink: 0;\n flex-wrap: wrap;\n}\n\n\n\n.header-left[_ngcontent-%COMP%] mj-view-selector {\n margin-left: 8px;\n}\n\n.entity-icon[_ngcontent-%COMP%] {\n font-size: 20px;\n color: #1976d2;\n}\n\n.entity-title[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 20px;\n font-weight: 600;\n color: #1a1a1a;\n}\n\n.record-count[_ngcontent-%COMP%] {\n font-size: 13px;\n color: #666;\n font-weight: 500;\n}\n\n.header-center[_ngcontent-%COMP%] {\n flex: 1;\n max-width: 600px;\n}\n\n.smart-filter-container[_ngcontent-%COMP%] {\n position: relative;\n width: 100%;\n}\n\n.smart-filter-input[_ngcontent-%COMP%] {\n width: 100%;\n padding: 10px 40px 10px 16px;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n font-size: 14px;\n background: #fafafa;\n transition: all 0.15s ease;\n}\n.smart-filter-input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: #1976d2;\n background: white;\n box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.1);\n}\n.smart-filter-input[_ngcontent-%COMP%]::placeholder {\n color: #999;\n}\n\n.clear-filter-btn[_ngcontent-%COMP%] {\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #757575;\n transition: all 0.15s ease;\n}\n.clear-filter-btn[_ngcontent-%COMP%]:hover {\n background: #e0e0e0;\n color: #424242;\n}\n\n.header-right[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 16px;\n flex-shrink: 0;\n}\n\n.view-mode-toggle[_ngcontent-%COMP%] {\n display: flex;\n background: #f0f0f0;\n border-radius: 8px;\n padding: 3px;\n}\n\n.toggle-btn[_ngcontent-%COMP%] {\n width: 36px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #666;\n transition: all 0.15s ease;\n}\n.toggle-btn[_ngcontent-%COMP%]:hover {\n color: #333;\n}\n.toggle-btn.active[_ngcontent-%COMP%] {\n background: white;\n color: #1976d2;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n.content-body[_ngcontent-%COMP%] {\n flex: 1;\n overflow: hidden;\n padding: 20px 24px;\n display: flex;\n flex-direction: column;\n}\n\n.loading-container[_ngcontent-%COMP%], \n.loading-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n gap: 16px;\n background: white;\n border-radius: 8px;\n}\n\n.loading-spinner[_ngcontent-%COMP%] {\n font-size: 32px;\n color: #1976d2;\n}\n\n.loading-message[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n color: #666;\n}\n\n.empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n text-align: center;\n color: #757575;\n background: white;\n border-radius: 8px;\n padding: 40px;\n}\n\n.empty-icon[_ngcontent-%COMP%] {\n font-size: 64px;\n color: #e0e0e0;\n margin-bottom: 24px;\n}\n\n.empty-state[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0 0 8px 0;\n font-size: 20px;\n font-weight: 600;\n color: #333;\n}\n\n.empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n max-width: 400px;\n color: #666;\n}\n\n\n\n.entity-home-view[_ngcontent-%COMP%] {\n flex: 1;\n overflow: auto;\n padding: 4px;\n}\n\n.entity-card-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 16px;\n padding: 0;\n}\n\n.entity-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 16px;\n padding: 20px;\n background: white;\n border-radius: 10px;\n cursor: pointer;\n transition: all 0.2s ease;\n border: 1px solid #e8e8e8;\n}\n\n.entity-card[_ngcontent-%COMP%]:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);\n border-color: #1976d2;\n}\n\n.entity-card[_ngcontent-%COMP%]:active {\n transform: translateY(0);\n}\n\n.entity-card-icon[_ngcontent-%COMP%] {\n width: 48px;\n height: 48px;\n border-radius: 10px;\n background: linear-gradient(135deg, #e3f2fd, #bbdefb);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.entity-card-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 20px;\n color: #1976d2;\n}\n\n.entity-card[_ngcontent-%COMP%]:hover .entity-card-icon[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #1976d2, #1565c0);\n}\n\n.entity-card[_ngcontent-%COMP%]:hover .entity-card-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: white;\n}\n\n.entity-card-content[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.entity-card-title[_ngcontent-%COMP%] {\n margin: 0 0 6px 0;\n font-size: 15px;\n font-weight: 600;\n color: #1a1a1a;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.entity-card-description[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 13px;\n color: #666;\n line-height: 1.4;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.detail-panel[_ngcontent-%COMP%] {\n flex-shrink: 0;\n height: 100%;\n background: white;\n border-left: 1px solid #e0e0e0;\n box-shadow: -4px 0 16px rgba(0, 0, 0, 0.08);\n overflow: hidden;\n animation: _ngcontent-%COMP%_slideIn 0.2s ease-out;\n}\n\n@keyframes _ngcontent-%COMP%_slideIn {\n from {\n transform: translateX(100%);\n opacity: 0;\n }\n to {\n transform: translateX(0);\n opacity: 1;\n }\n}\n[_nghost-%COMP%] mj-explorer-grid-view, \n[_nghost-%COMP%] mj-explorer-cards-view {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n\n\n\n\n\n\n\n.content-header.home-header[_ngcontent-%COMP%] {\n border-bottom: none;\n background: transparent;\n box-shadow: none;\n padding: 24px 24px 16px;\n}\n\n.content-body.home-content[_ngcontent-%COMP%] {\n padding-top: 0;\n}\n\n\n\n.filter-icon[_ngcontent-%COMP%] {\n position: absolute;\n left: 14px;\n top: 50%;\n transform: translateY(-50%);\n color: #999;\n font-size: 14px;\n pointer-events: none;\n}\n\n.smart-filter-container[_ngcontent-%COMP%] .smart-filter-input[_ngcontent-%COMP%] {\n padding-left: 40px;\n}\n\n\n\n\n\n\n.home-section[_ngcontent-%COMP%] {\n margin-bottom: 28px;\n}\n\n.section-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n margin-bottom: 14px;\n padding-left: 4px;\n}\n\n.section-icon[_ngcontent-%COMP%] {\n font-size: 14px;\n color: #1976d2;\n width: 20px;\n text-align: center;\n}\n\n.section-title[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n color: #333;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.section-count[_ngcontent-%COMP%] {\n font-size: 12px;\n color: #888;\n font-weight: 500;\n margin-left: 8px;\n}\n\n\n\n\n\n\n.home-two-column-layout[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 24px;\n margin-bottom: 28px;\n}\n\n.home-column[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 20px;\n}\n\n\n\n.section-header-with-toggle[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 14px;\n padding-left: 4px;\n}\n\n.section-header-left[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n\n\n.inline-toggle[_ngcontent-%COMP%] {\n display: flex;\n background: #f0f0f0;\n border-radius: 6px;\n padding: 2px;\n}\n\n.inline-toggle[_ngcontent-%COMP%] .toggle-btn[_ngcontent-%COMP%] {\n width: auto;\n height: 26px;\n padding: 0 10px;\n border: none;\n background: transparent;\n border-radius: 4px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #666;\n font-size: 11px;\n font-weight: 500;\n transition: all 0.15s ease;\n}\n\n.inline-toggle[_ngcontent-%COMP%] .toggle-btn[_ngcontent-%COMP%]:hover {\n color: #333;\n}\n\n.inline-toggle[_ngcontent-%COMP%] .toggle-btn.active[_ngcontent-%COMP%] {\n background: white;\n color: #1976d2;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n\n\n\n\n\n.entity-card-row[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n padding: 0;\n}\n\n.entity-card.compact[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n width: auto;\n min-width: 180px;\n max-width: 280px;\n padding: 12px 16px;\n gap: 12px;\n}\n\n.entity-card.compact[_ngcontent-%COMP%] .entity-card-icon[_ngcontent-%COMP%] {\n width: 36px;\n height: 36px;\n border-radius: 8px;\n}\n\n.entity-card.compact[_ngcontent-%COMP%] .entity-card-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 16px;\n}\n\n.entity-card.compact[_ngcontent-%COMP%] .entity-card-title[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n}\n\n.entity-card.compact[_ngcontent-%COMP%] .entity-card-content[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n}\n\n\n\n\n\n\n.favorite-btn[_ngcontent-%COMP%] {\n flex-shrink: 0;\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #ccc;\n transition: all 0.15s ease;\n margin-left: auto;\n}\n\n.favorite-btn[_ngcontent-%COMP%]:hover {\n background: rgba(255, 193, 7, 0.1);\n color: #ffc107;\n transform: scale(1.1);\n}\n\n.favorite-btn.favorited[_ngcontent-%COMP%] {\n color: #ffc107;\n}\n\n.favorite-btn.favorited[_ngcontent-%COMP%]:hover {\n background: rgba(255, 193, 7, 0.15);\n color: #ffb300;\n}\n\n.entity-card[_ngcontent-%COMP%] .favorite-btn[_ngcontent-%COMP%] {\n opacity: 0;\n transition: opacity 0.15s ease, background 0.15s ease, color 0.15s ease, transform 0.15s ease;\n}\n\n.entity-card[_ngcontent-%COMP%]:hover .favorite-btn[_ngcontent-%COMP%] {\n opacity: 1;\n}\n\n.entity-card[_ngcontent-%COMP%] .favorite-btn.favorited[_ngcontent-%COMP%] {\n opacity: 1;\n}\n\n\n\n\n\n\n.recent-records-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n background: white;\n border-radius: 10px;\n padding: 8px;\n border: 1px solid #e8e8e8;\n}\n\n.recent-record-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 12px;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.recent-record-item[_ngcontent-%COMP%]:hover {\n background: #f5f7fa;\n}\n\n.recent-record-icon[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border-radius: 8px;\n background: linear-gradient(135deg, #e8f5e9, #c8e6c9);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.recent-record-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 14px;\n color: #4caf50;\n}\n\n.recent-record-content[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.recent-record-name[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 500;\n color: #1a1a1a;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.recent-record-entity[_ngcontent-%COMP%] {\n font-size: 12px;\n color: #888;\n}\n\n.recent-record-time[_ngcontent-%COMP%] {\n font-size: 11px;\n color: #aaa;\n flex-shrink: 0;\n}\n\n\n\n\n\n\n.entity-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n background: white;\n border-radius: 10px;\n padding: 8px;\n border: 1px solid #e8e8e8;\n}\n\n.entity-list-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 12px;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.entity-list-item[_ngcontent-%COMP%]:hover {\n background: #f5f7fa;\n}\n\n.entity-list-icon[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border-radius: 8px;\n background: linear-gradient(135deg, #e3f2fd, #bbdefb);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.entity-list-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 14px;\n color: #1976d2;\n}\n\n.entity-list-content[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.entity-list-name[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 500;\n color: #1a1a1a;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.entity-list-item[_ngcontent-%COMP%] .favorite-btn[_ngcontent-%COMP%] {\n opacity: 0;\n transition: opacity 0.15s ease;\n}\n\n.entity-list-item[_ngcontent-%COMP%]:hover .favorite-btn[_ngcontent-%COMP%] {\n opacity: 1;\n}\n\n.entity-list-item[_ngcontent-%COMP%] .favorite-btn.favorited[_ngcontent-%COMP%] {\n opacity: 1;\n}\n\n\n\n\n\n\n.entity-filter-strip[_ngcontent-%COMP%] {\n display: flex;\n gap: 4px;\n margin-left: auto;\n background: #f0f0f0;\n border-radius: 6px;\n padding: 3px;\n}\n\n.entity-filter-btn[_ngcontent-%COMP%] {\n width: 28px;\n height: 26px;\n border: none;\n background: transparent;\n border-radius: 4px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #666;\n font-size: 12px;\n transition: all 0.15s ease;\n}\n\n.entity-filter-btn[_ngcontent-%COMP%]:hover {\n color: #333;\n background: rgba(0, 0, 0, 0.05);\n}\n\n.entity-filter-btn.active[_ngcontent-%COMP%] {\n background: white;\n color: #1976d2;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n\n\n\n\n\n.view-mode-toggle[_ngcontent-%COMP%] .toggle-btn[_ngcontent-%COMP%] {\n width: auto;\n padding: 0 12px;\n gap: 6px;\n}\n\n.toggle-label[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 500;\n}\n\n\n\n\n\n\n.empty-state.small[_ngcontent-%COMP%] {\n height: auto;\n padding: 32px;\n background: #fafafa;\n}\n\n.empty-state.small[_ngcontent-%COMP%] .empty-icon[_ngcontent-%COMP%] {\n font-size: 40px;\n margin-bottom: 16px;\n}\n\n.empty-state.small[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n font-size: 16px;\n}\n\n.empty-state.small[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n font-size: 13px;\n}\n\n\n\n\n\n\n@media (max-width: 1200px) {\n .entity-card-grid[_ngcontent-%COMP%] {\n grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n }\n}\n\n@media (max-width: 900px) {\n .content-header[_ngcontent-%COMP%] {\n flex-wrap: wrap;\n gap: 12px;\n }\n\n .header-center[_ngcontent-%COMP%] {\n order: 3;\n flex-basis: 100%;\n max-width: 100%;\n }\n\n .entity-card.compact[_ngcontent-%COMP%] {\n min-width: 160px;\n }\n\n \n\n .home-two-column-layout[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n gap: 20px;\n }\n}\n\n@media (max-width: 600px) {\n .data-explorer-container[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n\n .navigation-panel[_ngcontent-%COMP%] {\n display: none;\n }\n\n .content-header[_ngcontent-%COMP%] {\n padding: 12px 16px;\n }\n\n .content-body[_ngcontent-%COMP%] {\n padding: 12px 16px;\n }\n\n .entity-card-grid[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n gap: 12px;\n }\n\n .entity-card[_ngcontent-%COMP%] {\n padding: 16px;\n }\n\n .entity-card.compact[_ngcontent-%COMP%] {\n min-width: 100%;\n max-width: 100%;\n }\n\n .entity-card-row[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n\n .view-mode-toggle[_ngcontent-%COMP%] {\n display: none;\n }\n\n .section-title[_ngcontent-%COMP%] {\n font-size: 13px;\n }\n\n .home-section[_ngcontent-%COMP%] {\n margin-bottom: 20px;\n }\n}"], data: { animation: [
2073
+ trigger('slideInLeft', [
2074
+ transition(':enter', [
2075
+ style({ transform: 'translateX(-100%)', opacity: 0 }),
2076
+ animate('200ms ease-out', style({ transform: 'translateX(0)', opacity: 1 }))
2077
+ ]),
2078
+ transition(':leave', [
2079
+ animate('200ms ease-in', style({ transform: 'translateX(-100%)', opacity: 0 }))
2080
+ ])
2081
+ ])
2082
+ ] } });
2083
+ };
2084
+ DataExplorerDashboardComponent = __decorate([
2085
+ RegisterClass(BaseDashboard, 'DataExplorer')
2086
+ ], DataExplorerDashboardComponent);
2087
+ export { DataExplorerDashboardComponent };
2088
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(DataExplorerDashboardComponent, [{
2089
+ type: Component,
2090
+ args: [{ selector: 'mj-data-explorer-dashboard', animations: [
2091
+ trigger('slideInLeft', [
2092
+ transition(':enter', [
2093
+ style({ transform: 'translateX(-100%)', opacity: 0 }),
2094
+ animate('200ms ease-out', style({ transform: 'translateX(0)', opacity: 1 }))
2095
+ ]),
2096
+ transition(':leave', [
2097
+ animate('200ms ease-in', style({ transform: 'translateX(-100%)', opacity: 0 }))
2098
+ ])
2099
+ ])
2100
+ ], template: "<div class=\"data-explorer-container\">\n <!-- Navigation Panel (Left) - Hidden at home level, animated -->\n @if (!isAtHomeLevel) {\n <div\n class=\"navigation-panel\"\n [class.collapsed]=\"state.navigationPanelCollapsed\"\n [style.width.px]=\"state.navigationPanelCollapsed ? 48 : state.navigationPanelWidth\"\n [@slideInLeft]>\n\n <mj-explorer-navigation-panel\n [entities]=\"entities\"\n [selectedEntityName]=\"state.selectedEntityName\"\n [favorites]=\"state.favorites\"\n [recentItems]=\"state.recentItems\"\n [collapsed]=\"state.navigationPanelCollapsed\"\n [allowedEntityNames]=\"allowedEntityNames\"\n [favoritesSectionExpanded]=\"state.favoritesSectionExpanded\"\n [recentSectionExpanded]=\"state.recentSectionExpanded\"\n [entitiesSectionExpanded]=\"state.entitiesSectionExpanded\"\n [viewsSectionExpanded]=\"state.viewsSectionExpanded\"\n (entitySelected)=\"onEntitySelected($event)\"\n (toggleCollapse)=\"toggleNavigationPanel()\"\n (sectionToggled)=\"stateService.toggleSection($event)\"\n (openRecord)=\"onOpenRecordFromNav($event)\"\n (selectRecord)=\"onSelectRecordFromNav($event)\"\n (expandAndFocus)=\"onExpandAndFocus($event)\">\n </mj-explorer-navigation-panel>\n </div>\n }\n\n <!-- Main Content Area -->\n <div class=\"content-area\">\n <!-- Breadcrumb Bar - Hidden at home level -->\n @if (!isAtHomeLevel && breadcrumbs.length > 0) {\n <div class=\"breadcrumb-bar\">\n @for (crumb of breadcrumbs; track crumb.label; let i = $index; let last = $last) {\n <span\n class=\"breadcrumb-item\"\n [class.clickable]=\"!last\"\n [class.current]=\"last\"\n (click)=\"onBreadcrumbClick(crumb, i)\"\n [title]=\"crumb.label\">\n @if (crumb.icon) {\n <i [class]=\"crumb.icon\" class=\"breadcrumb-icon\"></i>\n }\n <span class=\"breadcrumb-label\">{{ crumb.label }}</span>\n </span>\n @if (!last) {\n <i class=\"fa-solid fa-chevron-right breadcrumb-separator\"></i>\n }\n }\n </div>\n }\n\n <!-- Header -->\n <div class=\"content-header\" [class.home-header]=\"isAtHomeLevel\">\n <div class=\"header-left\">\n @if (selectedEntity) {\n <i [class]=\"getEntityIcon(selectedEntity)\" class=\"entity-icon\"></i>\n <h2 class=\"entity-title\">{{ selectedEntity.Name }}</h2>\n @if (debouncedFilterText && filteredRecordCount !== totalRecordCount) {\n <span class=\"record-count\">{{ filteredRecordCount | number }} of {{ totalRecordCount | number }} records</span>\n } @else {\n <span class=\"record-count\">{{ totalRecordCount | number }} records</span>\n }\n\n <!-- View Selector -->\n <mj-view-selector\n [entity]=\"selectedEntity\"\n [selectedViewId]=\"state.selectedViewId\"\n [viewModified]=\"state.viewModified\"\n (viewSelected)=\"onViewSelected($event)\"\n (saveViewRequested)=\"onSaveViewRequested($event)\"\n (manageViewsRequested)=\"onManageViewsRequested()\"\n (openInTabRequested)=\"onOpenInTabRequested($event)\"\n (configureViewRequested)=\"onConfigureViewRequested()\">\n </mj-view-selector>\n } @else {\n @if (displayIcon) {\n <i [class]=\"displayIcon\" class=\"entity-icon\"></i>\n }\n <h2 class=\"entity-title\">{{ displayTitle }}</h2>\n <span class=\"record-count\">{{ entities.length }} entities available</span>\n }\n </div>\n\n <div class=\"header-center\">\n @if (selectedEntity) {\n <div class=\"smart-filter-container\">\n <i class=\"fa-solid fa-search filter-icon\"></i>\n <input\n #filterInput\n type=\"text\"\n class=\"smart-filter-input\"\n placeholder=\"Filter records... (press / to focus)\"\n [ngModel]=\"state.smartFilterPrompt\"\n (ngModelChange)=\"onSmartFilterChanged($event)\"\n />\n @if (state.smartFilterPrompt) {\n <button class=\"clear-filter-btn\" (click)=\"onSmartFilterChanged('')\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n }\n </div>\n } @else {\n <!-- Entity filter for home screen -->\n <div class=\"smart-filter-container\">\n <i class=\"fa-solid fa-search filter-icon\"></i>\n <input\n #filterInput\n type=\"text\"\n class=\"smart-filter-input\"\n placeholder=\"Filter entities... (press / to focus)\"\n [(ngModel)]=\"entityFilterText\"\n />\n @if (entityFilterText) {\n <button class=\"clear-filter-btn\" (click)=\"entityFilterText = ''\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n }\n </div>\n }\n </div>\n\n <div class=\"header-right\">\n @if (selectedEntity) {\n <!-- View Mode Toggle -->\n <div class=\"view-mode-toggle\">\n <button\n class=\"toggle-btn\"\n [class.active]=\"state.viewMode === 'grid'\"\n (click)=\"onViewModeChanged('grid')\"\n title=\"Grid View\">\n <i class=\"fa-solid fa-list\"></i>\n </button>\n <button\n class=\"toggle-btn\"\n [class.active]=\"state.viewMode === 'cards'\"\n (click)=\"onViewModeChanged('cards')\"\n title=\"Cards View\">\n <i class=\"fa-solid fa-grip\"></i>\n </button>\n </div>\n }\n <!-- Common/All toggle moved to inline with All Entities section header -->\n </div>\n </div>\n\n <!-- Content Body - Using mj-entity-viewer composite -->\n <div class=\"content-body\" [class.home-content]=\"isAtHomeLevel\">\n @if (!selectedEntity) {\n <!-- Entity Home View - Two-Column Layout -->\n <div class=\"entity-home-view\">\n @if (isLoadingEntities) {\n <div class=\"loading-state\">\n <mj-loading text=\"Loading entities...\" size=\"medium\"></mj-loading>\n </div>\n } @else {\n <!-- Two-Column Layout: Records (left) | Entities (right) -->\n @if (hasTopSectionContent) {\n <div class=\"home-two-column-layout\">\n <!-- Left Column: Recent & Favorite Records (most actionable) -->\n <div class=\"home-column\">\n <!-- Recent Records Section -->\n @if (recentRecords.length > 0) {\n <div class=\"home-section\">\n <div class=\"section-header\">\n <i class=\"fa-solid fa-history section-icon\"></i>\n <h3 class=\"section-title\">Recent Records</h3>\n <!-- Entity filter strip - only show with 2+ entities -->\n @if (showRecentRecordsEntityFilter) {\n <div class=\"entity-filter-strip\">\n <button\n class=\"entity-filter-btn\"\n [class.active]=\"recentRecordsEntityFilter === null\"\n (click)=\"setRecentRecordsEntityFilter(null)\"\n title=\"Show all entities\">\n <i class=\"fa-solid fa-layer-group\"></i>\n </button>\n @for (entity of uniqueRecentRecordEntities; track entity.entityId) {\n <button\n class=\"entity-filter-btn\"\n [class.active]=\"recentRecordsEntityFilter === entity.entityId\"\n (click)=\"setRecentRecordsEntityFilter(entity.entityId)\"\n [title]=\"entity.entityName\">\n <i [class]=\"entity.icon\"></i>\n </button>\n }\n </div>\n }\n </div>\n <div class=\"recent-records-list\">\n @for (record of filteredRecentRecords; track record.entityId + '|' + record.recordId) {\n <div\n class=\"recent-record-item\"\n (click)=\"onRecentRecordClick(record)\"\n [title]=\"record.entityName + ' - ' + record.recordId\">\n <div class=\"recent-record-icon\">\n <i [class]=\"getEntityIconById(record.entityId)\"></i>\n </div>\n <div class=\"recent-record-content\">\n <span class=\"recent-record-name\">{{ record.recordName || record.recordId }}</span>\n <span class=\"recent-record-entity\">{{ record.entityName }}</span>\n </div>\n <span class=\"recent-record-time\">{{ formatRelativeTime(record.latestAt) }}</span>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Favorite Records Section -->\n @if (favoriteRecords.length > 0) {\n <div class=\"home-section\">\n <div class=\"section-header\">\n <i class=\"fa-solid fa-star section-icon\"></i>\n <h3 class=\"section-title\">Favorite Records</h3>\n </div>\n <div class=\"recent-records-list\">\n @for (record of favoriteRecords; track record.userFavoriteId) {\n <div\n class=\"recent-record-item\"\n (click)=\"onFavoriteRecordClick(record)\"\n [title]=\"record.entityName + ' - ' + record.recordId\">\n <div class=\"recent-record-icon\">\n <i [class]=\"getEntityIconById(record.entityId)\"></i>\n </div>\n <div class=\"recent-record-content\">\n <span class=\"recent-record-name\">{{ record.recordName || record.recordId }}</span>\n <span class=\"recent-record-entity\">{{ record.entityName }}</span>\n </div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- Right Column: Recent & Favorite Entities -->\n <div class=\"home-column\">\n <!-- Recent Entities Section (list format) -->\n @if (recentEntities.length > 0) {\n <div class=\"home-section\">\n <div class=\"section-header\">\n <i class=\"fa-solid fa-clock section-icon\"></i>\n <h3 class=\"section-title\">Recent Entities</h3>\n </div>\n <div class=\"entity-list\">\n @for (entity of recentEntities; track entity.ID) {\n <div\n class=\"entity-list-item\"\n (click)=\"onEntitySelected(entity)\"\n [title]=\"entity.Description || entity.Name\">\n <div class=\"entity-list-icon\">\n <i [class]=\"getEntityIcon(entity)\"></i>\n </div>\n <div class=\"entity-list-content\">\n <span class=\"entity-list-name\">{{ entity.Name }}</span>\n </div>\n <button\n class=\"favorite-btn\"\n [class.favorited]=\"isEntityFavorited(entity)\"\n (click)=\"toggleEntityFavorite(entity, $event)\"\n [title]=\"isEntityFavorited(entity) ? 'Remove from favorites' : 'Add to favorites'\">\n <i [class]=\"isEntityFavorited(entity) ? 'fa-solid fa-star' : 'fa-regular fa-star'\"></i>\n </button>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Favorite Entities Section (list format) -->\n @if (favoriteEntities.length > 0) {\n <div class=\"home-section\">\n <div class=\"section-header\">\n <i class=\"fa-solid fa-star section-icon\"></i>\n <h3 class=\"section-title\">Favorite Entities</h3>\n </div>\n <div class=\"entity-list\">\n @for (entity of favoriteEntities; track entity.ID) {\n <div\n class=\"entity-list-item\"\n (click)=\"onEntitySelected(entity)\"\n [title]=\"entity.Description || entity.Name\">\n <div class=\"entity-list-icon\">\n <i [class]=\"getEntityIcon(entity)\"></i>\n </div>\n <div class=\"entity-list-content\">\n <span class=\"entity-list-name\">{{ entity.Name }}</span>\n </div>\n <button\n class=\"favorite-btn favorited\"\n (click)=\"toggleEntityFavorite(entity, $event)\"\n title=\"Remove from favorites\">\n <i class=\"fa-solid fa-star\"></i>\n </button>\n </div>\n }\n </div>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- All/Common Entities Section - Full Width -->\n <div class=\"home-section\">\n <div class=\"section-header-with-toggle\">\n <div class=\"section-header-left\">\n <i class=\"fa-solid fa-database section-icon\"></i>\n <h3 class=\"section-title\">\n @if (state.showAllEntities || !showCommonAllToggle) {\n All Entities\n } @else {\n Common Entities\n }\n </h3>\n @if (entityFilterText && filteredEntities.length !== entities.length) {\n <span class=\"section-count\">{{ filteredEntities.length }} matching</span>\n }\n </div>\n @if (showCommonAllToggle) {\n <div class=\"inline-toggle\">\n <button\n class=\"toggle-btn\"\n [class.active]=\"!state.showAllEntities\"\n (click)=\"toggleShowAllEntities()\"\n title=\"Show common entities ({{ commonEntitiesCount }})\">\n Common\n </button>\n <button\n class=\"toggle-btn\"\n [class.active]=\"state.showAllEntities\"\n (click)=\"toggleShowAllEntities()\"\n title=\"Show all entities ({{ allEntitiesCount }})\">\n All\n </button>\n </div>\n }\n </div>\n <div class=\"entity-card-grid\">\n @for (entity of filteredEntities; track entity.ID) {\n <div\n class=\"entity-card\"\n (click)=\"onEntitySelected(entity)\"\n [title]=\"entity.Description || entity.Name\">\n <div class=\"entity-card-icon\">\n <i [class]=\"getEntityIcon(entity)\"></i>\n </div>\n <div class=\"entity-card-content\">\n <h4 class=\"entity-card-title\">{{ entity.Name }}</h4>\n @if (entity.Description) {\n <p class=\"entity-card-description\">{{ entity.Description }}</p>\n }\n </div>\n <button\n class=\"favorite-btn\"\n [class.favorited]=\"isEntityFavorited(entity)\"\n (click)=\"toggleEntityFavorite(entity, $event)\"\n [title]=\"isEntityFavorited(entity) ? 'Remove from favorites' : 'Add to favorites'\">\n <i [class]=\"isEntityFavorited(entity) ? 'fa-solid fa-star' : 'fa-regular fa-star'\"></i>\n </button>\n </div>\n }\n </div>\n @if (filteredEntities.length === 0 && entities.length > 0) {\n <div class=\"empty-state small\">\n <i class=\"fa-solid fa-search empty-icon\"></i>\n <h3>No Matching Entities</h3>\n <p>No entities match \"{{ entityFilterText }}\"</p>\n </div>\n }\n @if (entities.length === 0) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-database empty-icon\"></i>\n <h3>No Entities Available</h3>\n <p>There are no entities configured for this application.</p>\n </div>\n }\n </div>\n }\n </div>\n } @else {\n <mj-entity-viewer\n [entity]=\"selectedEntity\"\n [viewEntity]=\"selectedViewEntity\"\n [viewMode]=\"state.viewMode\"\n [filterText]=\"debouncedFilterText\"\n [selectedRecordId]=\"state.selectedRecordId\"\n [config]=\"viewerConfig\"\n [gridState]=\"currentGridState\"\n (viewModeChange)=\"onViewModeChanged($event)\"\n (filterTextChange)=\"onFilterTextChanged($event)\"\n (recordSelected)=\"onViewerRecordSelected($event)\"\n (recordOpened)=\"onViewerRecordOpened($event)\"\n (dataLoaded)=\"onDataLoaded($event)\"\n (filteredCountChanged)=\"onFilteredCountChanged($event)\"\n (gridStateChanged)=\"onGridStateChanged($event)\">\n </mj-entity-viewer>\n }\n </div>\n </div>\n\n <!-- Detail Panel (Right - Slide In) -->\n @if (state.detailPanelOpen && selectedRecord) {\n <div class=\"detail-panel\" [style.width.px]=\"state.detailPanelWidth\">\n <mj-entity-record-detail-panel\n [entity]=\"detailPanelEntity\"\n [record]=\"selectedRecord\"\n (close)=\"onDetailPanelClosed()\"\n (openRecord)=\"onOpenRecord($event)\"\n (navigateToRelated)=\"onNavigateToRelated($event)\"\n (openRelatedRecord)=\"onOpenRelatedRecord($event)\"\n (openForeignKeyRecord)=\"onOpenForeignKeyRecord($event)\">\n </mj-entity-record-detail-panel>\n </div>\n }\n\n <!-- View Configuration Panel -->\n <mj-view-config-panel\n [entity]=\"selectedEntity\"\n [viewEntity]=\"selectedViewEntity\"\n [isOpen]=\"state.viewConfigPanelOpen\"\n [currentGridState]=\"currentGridState\"\n (close)=\"onCloseViewConfigPanel()\"\n (save)=\"onSaveView($event)\"\n (delete)=\"onDeleteView()\">\n </mj-view-config-panel>\n</div>\n", styles: [".data-explorer-container {\n display: flex;\n height: 100%;\n width: 100%;\n background: #f5f7fa;\n overflow: hidden;\n}\n\n.navigation-panel {\n flex-shrink: 0;\n height: 100%;\n background: white;\n border-right: 1px solid #e0e0e0;\n transition: width 0.2s ease-in-out;\n overflow: hidden;\n box-shadow: 2px 0 8px rgba(0, 0, 0, 0.04);\n}\n.navigation-panel.collapsed {\n width: 48px;\n}\n\n.content-area {\n flex: 1;\n display: flex;\n flex-direction: column;\n height: 100%;\n min-width: 0;\n overflow: hidden;\n background: #f5f7fa;\n}\n\n/* Breadcrumb Bar */\n.breadcrumb-bar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 24px;\n background: white;\n border-bottom: 1px solid #eee;\n flex-shrink: 0;\n font-size: 13px;\n min-height: 40px;\n}\n\n.breadcrumb-item {\n display: flex;\n align-items: center;\n gap: 6px;\n color: #666;\n padding: 4px 8px;\n border-radius: 4px;\n transition: all 0.15s ease;\n max-width: 200px;\n}\n\n.breadcrumb-item.clickable {\n cursor: pointer;\n}\n\n.breadcrumb-item.clickable:hover {\n background: #f0f0f0;\n color: #1976d2;\n}\n\n.breadcrumb-item.current {\n color: #1a1a1a;\n font-weight: 500;\n cursor: default;\n}\n\n.breadcrumb-icon {\n font-size: 12px;\n flex-shrink: 0;\n}\n\n.breadcrumb-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.breadcrumb-separator {\n font-size: 10px;\n color: #ccc;\n flex-shrink: 0;\n}\n\n.content-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 24px;\n background: white;\n border-bottom: 1px solid #e0e0e0;\n flex-shrink: 0;\n gap: 24px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);\n}\n\n.header-left {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-shrink: 0;\n flex-wrap: wrap;\n}\n\n/* View Selector within header */\n.header-left ::ng-deep mj-view-selector {\n margin-left: 8px;\n}\n\n.entity-icon {\n font-size: 20px;\n color: #1976d2;\n}\n\n.entity-title {\n margin: 0;\n font-size: 20px;\n font-weight: 600;\n color: #1a1a1a;\n}\n\n.record-count {\n font-size: 13px;\n color: #666;\n font-weight: 500;\n}\n\n.header-center {\n flex: 1;\n max-width: 600px;\n}\n\n.smart-filter-container {\n position: relative;\n width: 100%;\n}\n\n.smart-filter-input {\n width: 100%;\n padding: 10px 40px 10px 16px;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n font-size: 14px;\n background: #fafafa;\n transition: all 0.15s ease;\n}\n.smart-filter-input:focus {\n outline: none;\n border-color: #1976d2;\n background: white;\n box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.1);\n}\n.smart-filter-input::placeholder {\n color: #999;\n}\n\n.clear-filter-btn {\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #757575;\n transition: all 0.15s ease;\n}\n.clear-filter-btn:hover {\n background: #e0e0e0;\n color: #424242;\n}\n\n.header-right {\n display: flex;\n align-items: center;\n gap: 16px;\n flex-shrink: 0;\n}\n\n.view-mode-toggle {\n display: flex;\n background: #f0f0f0;\n border-radius: 8px;\n padding: 3px;\n}\n\n.toggle-btn {\n width: 36px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #666;\n transition: all 0.15s ease;\n}\n.toggle-btn:hover {\n color: #333;\n}\n.toggle-btn.active {\n background: white;\n color: #1976d2;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n.content-body {\n flex: 1;\n overflow: hidden;\n padding: 20px 24px;\n display: flex;\n flex-direction: column;\n}\n\n.loading-container,\n.loading-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n gap: 16px;\n background: white;\n border-radius: 8px;\n}\n\n.loading-spinner {\n font-size: 32px;\n color: #1976d2;\n}\n\n.loading-message {\n margin: 0;\n font-size: 14px;\n color: #666;\n}\n\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n text-align: center;\n color: #757575;\n background: white;\n border-radius: 8px;\n padding: 40px;\n}\n\n.empty-icon {\n font-size: 64px;\n color: #e0e0e0;\n margin-bottom: 24px;\n}\n\n.empty-state h3 {\n margin: 0 0 8px 0;\n font-size: 20px;\n font-weight: 600;\n color: #333;\n}\n\n.empty-state p {\n margin: 0;\n font-size: 14px;\n max-width: 400px;\n color: #666;\n}\n\n/* Entity Home View - Card Grid */\n.entity-home-view {\n flex: 1;\n overflow: auto;\n padding: 4px;\n}\n\n.entity-card-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 16px;\n padding: 0;\n}\n\n.entity-card {\n display: flex;\n align-items: flex-start;\n gap: 16px;\n padding: 20px;\n background: white;\n border-radius: 10px;\n cursor: pointer;\n transition: all 0.2s ease;\n border: 1px solid #e8e8e8;\n}\n\n.entity-card:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);\n border-color: #1976d2;\n}\n\n.entity-card:active {\n transform: translateY(0);\n}\n\n.entity-card-icon {\n width: 48px;\n height: 48px;\n border-radius: 10px;\n background: linear-gradient(135deg, #e3f2fd, #bbdefb);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.entity-card-icon i {\n font-size: 20px;\n color: #1976d2;\n}\n\n.entity-card:hover .entity-card-icon {\n background: linear-gradient(135deg, #1976d2, #1565c0);\n}\n\n.entity-card:hover .entity-card-icon i {\n color: white;\n}\n\n.entity-card-content {\n flex: 1;\n min-width: 0;\n}\n\n.entity-card-title {\n margin: 0 0 6px 0;\n font-size: 15px;\n font-weight: 600;\n color: #1a1a1a;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.entity-card-description {\n margin: 0;\n font-size: 13px;\n color: #666;\n line-height: 1.4;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.detail-panel {\n flex-shrink: 0;\n height: 100%;\n background: white;\n border-left: 1px solid #e0e0e0;\n box-shadow: -4px 0 16px rgba(0, 0, 0, 0.08);\n overflow: hidden;\n animation: slideIn 0.2s ease-out;\n}\n\n@keyframes slideIn {\n from {\n transform: translateX(100%);\n opacity: 0;\n }\n to {\n transform: translateX(0);\n opacity: 1;\n }\n}\n:host ::ng-deep mj-explorer-grid-view,\n:host ::ng-deep mj-explorer-cards-view {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n/* ============================================\n HOME SCREEN STYLES\n ============================================ */\n\n/* Home-level header adjustments */\n.content-header.home-header {\n border-bottom: none;\n background: transparent;\n box-shadow: none;\n padding: 24px 24px 16px;\n}\n\n.content-body.home-content {\n padding-top: 0;\n}\n\n/* Smart filter with search icon */\n.filter-icon {\n position: absolute;\n left: 14px;\n top: 50%;\n transform: translateY(-50%);\n color: #999;\n font-size: 14px;\n pointer-events: none;\n}\n\n.smart-filter-container .smart-filter-input {\n padding-left: 40px;\n}\n\n/* ============================================\n HOME SECTIONS\n ============================================ */\n\n.home-section {\n margin-bottom: 28px;\n}\n\n.section-header {\n display: flex;\n align-items: center;\n gap: 10px;\n margin-bottom: 14px;\n padding-left: 4px;\n}\n\n.section-icon {\n font-size: 14px;\n color: #1976d2;\n width: 20px;\n text-align: center;\n}\n\n.section-title {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n color: #333;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.section-count {\n font-size: 12px;\n color: #888;\n font-weight: 500;\n margin-left: 8px;\n}\n\n/* ============================================\n TWO-COLUMN LAYOUT (Entities left, Records right)\n ============================================ */\n\n.home-two-column-layout {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 24px;\n margin-bottom: 28px;\n}\n\n.home-column {\n display: flex;\n flex-direction: column;\n gap: 20px;\n}\n\n/* Section header with inline toggle */\n.section-header-with-toggle {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 14px;\n padding-left: 4px;\n}\n\n.section-header-left {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n/* Inline Common/All toggle (compact) */\n.inline-toggle {\n display: flex;\n background: #f0f0f0;\n border-radius: 6px;\n padding: 2px;\n}\n\n.inline-toggle .toggle-btn {\n width: auto;\n height: 26px;\n padding: 0 10px;\n border: none;\n background: transparent;\n border-radius: 4px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #666;\n font-size: 11px;\n font-weight: 500;\n transition: all 0.15s ease;\n}\n\n.inline-toggle .toggle-btn:hover {\n color: #333;\n}\n\n.inline-toggle .toggle-btn.active {\n background: white;\n color: #1976d2;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n/* ============================================\n COMPACT ENTITY CARDS (Recent/Favorites rows)\n ============================================ */\n\n.entity-card-row {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n padding: 0;\n}\n\n.entity-card.compact {\n flex: 0 0 auto;\n width: auto;\n min-width: 180px;\n max-width: 280px;\n padding: 12px 16px;\n gap: 12px;\n}\n\n.entity-card.compact .entity-card-icon {\n width: 36px;\n height: 36px;\n border-radius: 8px;\n}\n\n.entity-card.compact .entity-card-icon i {\n font-size: 16px;\n}\n\n.entity-card.compact .entity-card-title {\n margin: 0;\n font-size: 14px;\n}\n\n.entity-card.compact .entity-card-content {\n display: flex;\n align-items: center;\n}\n\n/* ============================================\n FAVORITE BUTTON\n ============================================ */\n\n.favorite-btn {\n flex-shrink: 0;\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #ccc;\n transition: all 0.15s ease;\n margin-left: auto;\n}\n\n.favorite-btn:hover {\n background: rgba(255, 193, 7, 0.1);\n color: #ffc107;\n transform: scale(1.1);\n}\n\n.favorite-btn.favorited {\n color: #ffc107;\n}\n\n.favorite-btn.favorited:hover {\n background: rgba(255, 193, 7, 0.15);\n color: #ffb300;\n}\n\n.entity-card .favorite-btn {\n opacity: 0;\n transition: opacity 0.15s ease, background 0.15s ease, color 0.15s ease, transform 0.15s ease;\n}\n\n.entity-card:hover .favorite-btn {\n opacity: 1;\n}\n\n.entity-card .favorite-btn.favorited {\n opacity: 1;\n}\n\n/* ============================================\n RECENT RECORDS LIST\n ============================================ */\n\n.recent-records-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n background: white;\n border-radius: 10px;\n padding: 8px;\n border: 1px solid #e8e8e8;\n}\n\n.recent-record-item {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 12px;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.recent-record-item:hover {\n background: #f5f7fa;\n}\n\n.recent-record-icon {\n width: 32px;\n height: 32px;\n border-radius: 8px;\n background: linear-gradient(135deg, #e8f5e9, #c8e6c9);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.recent-record-icon i {\n font-size: 14px;\n color: #4caf50;\n}\n\n.recent-record-content {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.recent-record-name {\n font-size: 14px;\n font-weight: 500;\n color: #1a1a1a;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.recent-record-entity {\n font-size: 12px;\n color: #888;\n}\n\n.recent-record-time {\n font-size: 11px;\n color: #aaa;\n flex-shrink: 0;\n}\n\n/* ============================================\n ENTITY LIST (for Recent/Favorite Entities)\n ============================================ */\n\n.entity-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n background: white;\n border-radius: 10px;\n padding: 8px;\n border: 1px solid #e8e8e8;\n}\n\n.entity-list-item {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 12px;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.entity-list-item:hover {\n background: #f5f7fa;\n}\n\n.entity-list-icon {\n width: 32px;\n height: 32px;\n border-radius: 8px;\n background: linear-gradient(135deg, #e3f2fd, #bbdefb);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.entity-list-icon i {\n font-size: 14px;\n color: #1976d2;\n}\n\n.entity-list-content {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.entity-list-name {\n font-size: 14px;\n font-weight: 500;\n color: #1a1a1a;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.entity-list-item .favorite-btn {\n opacity: 0;\n transition: opacity 0.15s ease;\n}\n\n.entity-list-item:hover .favorite-btn {\n opacity: 1;\n}\n\n.entity-list-item .favorite-btn.favorited {\n opacity: 1;\n}\n\n/* ============================================\n ENTITY FILTER STRIP (for Recent Records)\n ============================================ */\n\n.entity-filter-strip {\n display: flex;\n gap: 4px;\n margin-left: auto;\n background: #f0f0f0;\n border-radius: 6px;\n padding: 3px;\n}\n\n.entity-filter-btn {\n width: 28px;\n height: 26px;\n border: none;\n background: transparent;\n border-radius: 4px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #666;\n font-size: 12px;\n transition: all 0.15s ease;\n}\n\n.entity-filter-btn:hover {\n color: #333;\n background: rgba(0, 0, 0, 0.05);\n}\n\n.entity-filter-btn.active {\n background: white;\n color: #1976d2;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n/* ============================================\n COMMON/ALL TOGGLE WITH LABELS\n ============================================ */\n\n.view-mode-toggle .toggle-btn {\n width: auto;\n padding: 0 12px;\n gap: 6px;\n}\n\n.toggle-label {\n font-size: 12px;\n font-weight: 500;\n}\n\n/* ============================================\n EMPTY STATE VARIANTS\n ============================================ */\n\n.empty-state.small {\n height: auto;\n padding: 32px;\n background: #fafafa;\n}\n\n.empty-state.small .empty-icon {\n font-size: 40px;\n margin-bottom: 16px;\n}\n\n.empty-state.small h3 {\n font-size: 16px;\n}\n\n.empty-state.small p {\n font-size: 13px;\n}\n\n/* ============================================\n RESPONSIVE STYLES\n ============================================ */\n\n@media (max-width: 1200px) {\n .entity-card-grid {\n grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n }\n}\n\n@media (max-width: 900px) {\n .content-header {\n flex-wrap: wrap;\n gap: 12px;\n }\n\n .header-center {\n order: 3;\n flex-basis: 100%;\n max-width: 100%;\n }\n\n .entity-card.compact {\n min-width: 160px;\n }\n\n /* Stack two-column layout on smaller screens */\n .home-two-column-layout {\n grid-template-columns: 1fr;\n gap: 20px;\n }\n}\n\n@media (max-width: 600px) {\n .data-explorer-container {\n flex-direction: column;\n }\n\n .navigation-panel {\n display: none;\n }\n\n .content-header {\n padding: 12px 16px;\n }\n\n .content-body {\n padding: 12px 16px;\n }\n\n .entity-card-grid {\n grid-template-columns: 1fr;\n gap: 12px;\n }\n\n .entity-card {\n padding: 16px;\n }\n\n .entity-card.compact {\n min-width: 100%;\n max-width: 100%;\n }\n\n .entity-card-row {\n flex-direction: column;\n }\n\n .view-mode-toggle {\n display: none;\n }\n\n .section-title {\n font-size: 13px;\n }\n\n .home-section {\n margin-bottom: 20px;\n }\n}\n"] }]
2101
+ }], () => [{ type: i1.ExplorerStateService }, { type: i0.ChangeDetectorRef }, { type: i2.Router }, { type: i3.RecentAccessService }], { filterInputRef: [{
2102
+ type: ViewChild,
2103
+ args: ['filterInput']
2104
+ }], viewSelectorRef: [{
2105
+ type: ViewChild,
2106
+ args: [ViewSelectorComponent]
2107
+ }], entityViewerRef: [{
2108
+ type: ViewChild,
2109
+ args: [EntityViewerComponent]
2110
+ }], entityFilter: [{
2111
+ type: Input
2112
+ }], deepLink: [{
2113
+ type: Input
2114
+ }], contextName: [{
2115
+ type: Input
2116
+ }], contextIcon: [{
2117
+ type: Input
2118
+ }], handleKeyboardShortcut: [{
2119
+ type: HostListener,
2120
+ args: ['document:keydown', ['$event']]
2121
+ }] }); })();
2122
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(DataExplorerDashboardComponent, { className: "DataExplorerDashboardComponent", filePath: "src/DataExplorer/data-explorer-dashboard.component.ts", lineNumber: 55 }); })();
2123
+ /**
2124
+ * Tree-shaking prevention
2125
+ */
2126
+ export function LoadDataExplorerDashboard() {
2127
+ // Force inclusion in production builds
2128
+ }
2129
+ //# sourceMappingURL=data-explorer-dashboard.component.js.map