@memberjunction/ng-dashboards 2.120.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,912 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { BehaviorSubject } from 'rxjs';
3
+ import { Metadata, RunView, CompositeKey } from '@memberjunction/core';
4
+ import { DEFAULT_EXPLORER_STATE } from '../models/explorer-state.interface';
5
+ import * as i0 from "@angular/core";
6
+ const BASE_SETTING_KEY = 'DataExplorer.State';
7
+ const MAX_RECENT_ITEMS = 20;
8
+ const MAX_RECENT_ENTITIES = 10;
9
+ const MAX_RECENT_RECORDS = 10;
10
+ const MAX_ENTITY_CACHE_SIZE = 50; // LRU cache limit
11
+ export class ExplorerStateService {
12
+ state$ = new BehaviorSubject(DEFAULT_EXPLORER_STATE);
13
+ metadata = new Metadata();
14
+ saveTimeout = null;
15
+ /** Current context filter (determines which settings key to use) */
16
+ currentFilter = null;
17
+ /** Computed breadcrumbs based on current state */
18
+ breadcrumbs$ = new BehaviorSubject([]);
19
+ /** Recent records loaded from User Record Logs */
20
+ recentRecords$ = new BehaviorSubject([]);
21
+ /** Favorite records loaded from User Favorites (non-entity favorites) */
22
+ favoriteRecords$ = new BehaviorSubject([]);
23
+ /** Application entities with DefaultForNewUser info (loaded once per context) */
24
+ applicationEntities = [];
25
+ /** Set of entity IDs that have DefaultForNewUser=true */
26
+ defaultEntityIds = new Set();
27
+ constructor() {
28
+ // Don't load state in constructor - wait for setContext to be called
29
+ }
30
+ /**
31
+ * Set the context filter and load context-specific state.
32
+ * Call this before using the service to ensure proper context isolation.
33
+ */
34
+ async setContext(filter) {
35
+ this.currentFilter = filter;
36
+ await this.loadState();
37
+ // Load application entities first (needed for filtering recent/favorite records)
38
+ await this.loadApplicationEntities();
39
+ // Then load the rest in parallel
40
+ await Promise.all([
41
+ this.loadFavoriteEntities(),
42
+ this.loadFavoriteRecords(),
43
+ this.loadRecentRecords()
44
+ ]);
45
+ this.updateBreadcrumbs();
46
+ }
47
+ /**
48
+ * Get the storage key based on current context.
49
+ * Uses application ID if available for context-specific storage.
50
+ */
51
+ getSettingKey() {
52
+ if (this.currentFilter?.applicationId) {
53
+ return `${BASE_SETTING_KEY}.${this.currentFilter.applicationId}`;
54
+ }
55
+ return BASE_SETTING_KEY;
56
+ }
57
+ /**
58
+ * Get observable of current state
59
+ */
60
+ get State() {
61
+ return this.state$.asObservable();
62
+ }
63
+ /**
64
+ * Get current state value
65
+ */
66
+ get CurrentState() {
67
+ return this.state$.value;
68
+ }
69
+ /**
70
+ * Observable of current breadcrumbs
71
+ */
72
+ get Breadcrumbs() {
73
+ return this.breadcrumbs$.asObservable();
74
+ }
75
+ /**
76
+ * Get current breadcrumbs value
77
+ */
78
+ get CurrentBreadcrumbs() {
79
+ return this.breadcrumbs$.value;
80
+ }
81
+ /**
82
+ * Get the current context filter
83
+ */
84
+ get CurrentFilter() {
85
+ return this.currentFilter;
86
+ }
87
+ /**
88
+ * Observable of recent records (from User Record Logs)
89
+ */
90
+ get RecentRecords() {
91
+ return this.recentRecords$.asObservable();
92
+ }
93
+ /**
94
+ * Get current recent records value
95
+ */
96
+ get CurrentRecentRecords() {
97
+ return this.recentRecords$.value;
98
+ }
99
+ /**
100
+ * Observable of favorite records (from User Favorites, non-entity)
101
+ */
102
+ get FavoriteRecords() {
103
+ return this.favoriteRecords$.asObservable();
104
+ }
105
+ /**
106
+ * Get current favorite records value
107
+ */
108
+ get CurrentFavoriteRecords() {
109
+ return this.favoriteRecords$.value;
110
+ }
111
+ /**
112
+ * Get the set of entity IDs with DefaultForNewUser=true
113
+ */
114
+ get DefaultEntityIds() {
115
+ return this.defaultEntityIds;
116
+ }
117
+ /**
118
+ * Update state partially
119
+ */
120
+ updateState(partial) {
121
+ const newState = { ...this.state$.value, ...partial };
122
+ this.state$.next(newState);
123
+ this.updateBreadcrumbs();
124
+ this.debouncedSave();
125
+ }
126
+ /**
127
+ * Set selected entity
128
+ * Caches current entity's filter before switching, restores new entity's cached filter
129
+ */
130
+ selectEntity(entityName) {
131
+ const currentState = this.state$.value;
132
+ const entityCache = { ...currentState.entityCache };
133
+ // Cache current entity's filter before switching (if we have a current entity)
134
+ if (currentState.selectedEntityName && currentState.smartFilterPrompt) {
135
+ entityCache[currentState.selectedEntityName] = {
136
+ filterText: currentState.smartFilterPrompt,
137
+ lastAccessed: Date.now()
138
+ };
139
+ }
140
+ // Restore filter for new entity (if cached) or clear it
141
+ let restoredFilter = '';
142
+ if (entityName && entityCache[entityName]) {
143
+ restoredFilter = entityCache[entityName].filterText;
144
+ entityCache[entityName].lastAccessed = Date.now();
145
+ }
146
+ // Evict old entries if cache is too large (LRU)
147
+ this.evictOldCacheEntries(entityCache);
148
+ this.updateState({
149
+ selectedEntityName: entityName,
150
+ selectedViewId: null, // Reset view when entity changes
151
+ smartFilterPrompt: restoredFilter, // Restore cached filter or empty
152
+ selectedRecordId: null,
153
+ detailPanelOpen: false,
154
+ entityCache
155
+ });
156
+ }
157
+ /**
158
+ * Evict oldest cache entries if over limit (LRU eviction)
159
+ */
160
+ evictOldCacheEntries(cache) {
161
+ const entries = Object.entries(cache);
162
+ if (entries.length <= MAX_ENTITY_CACHE_SIZE)
163
+ return;
164
+ // Sort by lastAccessed (oldest first) and remove excess
165
+ entries.sort((a, b) => a[1].lastAccessed - b[1].lastAccessed);
166
+ const toRemove = entries.length - MAX_ENTITY_CACHE_SIZE;
167
+ for (let i = 0; i < toRemove; i++) {
168
+ delete cache[entries[i][0]];
169
+ }
170
+ }
171
+ /**
172
+ * Set selected view
173
+ */
174
+ selectView(viewId) {
175
+ this.updateState({
176
+ selectedViewId: viewId,
177
+ viewModified: false // Reset modified state when selecting a different view
178
+ });
179
+ }
180
+ /**
181
+ * Mark the current view as modified (has unsaved changes)
182
+ */
183
+ setViewModified(modified) {
184
+ this.updateState({ viewModified: modified });
185
+ }
186
+ /**
187
+ * Toggle the view configuration panel
188
+ */
189
+ toggleViewConfigPanel() {
190
+ this.updateState({ viewConfigPanelOpen: !this.state$.value.viewConfigPanelOpen });
191
+ }
192
+ /**
193
+ * Open the view configuration panel
194
+ */
195
+ openViewConfigPanel() {
196
+ this.updateState({ viewConfigPanelOpen: true });
197
+ }
198
+ /**
199
+ * Close the view configuration panel
200
+ */
201
+ closeViewConfigPanel() {
202
+ this.updateState({ viewConfigPanelOpen: false });
203
+ }
204
+ /**
205
+ * Set view mode
206
+ */
207
+ setViewMode(mode) {
208
+ this.updateState({ viewMode: mode });
209
+ }
210
+ /**
211
+ * Set smart filter prompt
212
+ * Also updates the entity cache for the current entity
213
+ */
214
+ setSmartFilterPrompt(prompt) {
215
+ const currentState = this.state$.value;
216
+ const entityCache = { ...currentState.entityCache };
217
+ // Update cache for current entity
218
+ if (currentState.selectedEntityName) {
219
+ if (prompt) {
220
+ entityCache[currentState.selectedEntityName] = {
221
+ filterText: prompt,
222
+ lastAccessed: Date.now()
223
+ };
224
+ }
225
+ else {
226
+ // Clear cache entry if filter is empty
227
+ delete entityCache[currentState.selectedEntityName];
228
+ }
229
+ }
230
+ this.updateState({ smartFilterPrompt: prompt, entityCache });
231
+ }
232
+ /**
233
+ * Select a record and open detail panel
234
+ * @param recordId The composite key string for the record
235
+ * @param recordName Optional display name for the record (for breadcrumbs)
236
+ */
237
+ selectRecord(recordId, recordName) {
238
+ this.updateState({
239
+ selectedRecordId: recordId,
240
+ selectedRecordName: recordName || null,
241
+ detailPanelOpen: recordId !== null
242
+ });
243
+ }
244
+ /**
245
+ * Close detail panel
246
+ */
247
+ closeDetailPanel() {
248
+ this.updateState({
249
+ detailPanelOpen: false,
250
+ selectedRecordId: null,
251
+ selectedRecordName: null
252
+ });
253
+ }
254
+ /**
255
+ * Toggle navigation panel collapsed state
256
+ */
257
+ toggleNavigationPanel() {
258
+ this.updateState({
259
+ navigationPanelCollapsed: !this.state$.value.navigationPanelCollapsed
260
+ });
261
+ }
262
+ /**
263
+ * Expand navigation panel and ensure a specific section is expanded
264
+ */
265
+ expandAndFocusSection(section) {
266
+ const updates = {
267
+ navigationPanelCollapsed: false
268
+ };
269
+ // Ensure the target section is expanded
270
+ switch (section) {
271
+ case 'favorites':
272
+ updates.favoritesSectionExpanded = true;
273
+ break;
274
+ case 'recent':
275
+ updates.recentSectionExpanded = true;
276
+ break;
277
+ case 'entities':
278
+ updates.entitiesSectionExpanded = true;
279
+ break;
280
+ }
281
+ this.updateState(updates);
282
+ }
283
+ /**
284
+ * Add item to recent history
285
+ */
286
+ addRecentItem(item) {
287
+ const recentItems = [...this.state$.value.recentItems];
288
+ // Remove existing entry if present (match by entity and composite key)
289
+ const existingIndex = recentItems.findIndex(r => r.entityName === item.entityName && r.compositeKeyString === item.compositeKeyString);
290
+ if (existingIndex >= 0) {
291
+ recentItems.splice(existingIndex, 1);
292
+ }
293
+ // Add to front with timestamp
294
+ recentItems.unshift({
295
+ ...item,
296
+ timestamp: new Date()
297
+ });
298
+ // Trim to max size
299
+ if (recentItems.length > MAX_RECENT_ITEMS) {
300
+ recentItems.length = MAX_RECENT_ITEMS;
301
+ }
302
+ this.updateState({ recentItems });
303
+ }
304
+ /**
305
+ * Add item to favorites
306
+ */
307
+ addFavorite(item) {
308
+ const favorites = [...this.state$.value.favorites];
309
+ // Check if already exists
310
+ const exists = favorites.some(f => f.type === item.type &&
311
+ f.entityName === item.entityName &&
312
+ f.compositeKeyString === item.compositeKeyString &&
313
+ f.viewId === item.viewId);
314
+ if (!exists) {
315
+ favorites.push(item);
316
+ this.updateState({ favorites });
317
+ }
318
+ }
319
+ /**
320
+ * Remove item from favorites
321
+ */
322
+ removeFavorite(item) {
323
+ const favorites = this.state$.value.favorites.filter(f => !(f.type === item.type &&
324
+ f.entityName === item.entityName &&
325
+ f.compositeKeyString === item.compositeKeyString &&
326
+ f.viewId === item.viewId));
327
+ this.updateState({ favorites });
328
+ }
329
+ /**
330
+ * Toggle section expanded state
331
+ */
332
+ toggleSection(section) {
333
+ const key = `${section}SectionExpanded`;
334
+ this.updateState({ [key]: !this.state$.value[key] });
335
+ }
336
+ /**
337
+ * Load state from UserSetting
338
+ */
339
+ async loadState() {
340
+ try {
341
+ const userId = this.metadata.CurrentUser?.ID;
342
+ if (!userId)
343
+ return;
344
+ const settingKey = this.getSettingKey();
345
+ const rv = new RunView();
346
+ const result = await rv.RunView({
347
+ EntityName: 'MJ: User Settings',
348
+ ExtraFilter: `UserID='${userId}' AND Setting='${settingKey}'`,
349
+ ResultType: 'entity_object'
350
+ });
351
+ if (result.Success && result.Results.length > 0) {
352
+ const setting = result.Results[0];
353
+ if (setting.Value) {
354
+ const savedState = JSON.parse(setting.Value);
355
+ // Merge with defaults to handle new properties
356
+ this.state$.next({ ...DEFAULT_EXPLORER_STATE, ...savedState });
357
+ }
358
+ }
359
+ else {
360
+ // No saved state, use defaults
361
+ this.state$.next({ ...DEFAULT_EXPLORER_STATE });
362
+ }
363
+ }
364
+ catch (error) {
365
+ console.warn('Failed to load Data Explorer state:', error);
366
+ this.state$.next({ ...DEFAULT_EXPLORER_STATE });
367
+ }
368
+ }
369
+ /**
370
+ * Update breadcrumbs based on current state and filter
371
+ */
372
+ updateBreadcrumbs() {
373
+ const breadcrumbs = [];
374
+ const state = this.state$.value;
375
+ // Always add a root/home breadcrumb - use application name if available, otherwise "All"
376
+ const rootLabel = this.currentFilter?.applicationName || 'All';
377
+ breadcrumbs.push({
378
+ label: rootLabel,
379
+ type: 'application',
380
+ icon: 'fa-solid fa-th-large'
381
+ });
382
+ // Add entity breadcrumb if selected
383
+ if (state.selectedEntityName) {
384
+ const entityInfo = this.metadata.Entities.find(e => e.Name === state.selectedEntityName);
385
+ breadcrumbs.push({
386
+ label: state.selectedEntityName,
387
+ type: 'entity',
388
+ entityName: state.selectedEntityName,
389
+ icon: entityInfo?.Icon ? this.formatEntityIcon(entityInfo.Icon) : 'fa-solid fa-table'
390
+ });
391
+ }
392
+ // Add record breadcrumb if selected
393
+ if (state.selectedRecordId && state.selectedRecordName) {
394
+ breadcrumbs.push({
395
+ label: state.selectedRecordName,
396
+ type: 'record',
397
+ entityName: state.selectedEntityName || undefined,
398
+ compositeKeyString: state.selectedRecordId,
399
+ icon: 'fa-solid fa-file-alt'
400
+ });
401
+ }
402
+ this.breadcrumbs$.next(breadcrumbs);
403
+ }
404
+ /**
405
+ * Format entity icon to ensure proper Font Awesome class format
406
+ */
407
+ formatEntityIcon(icon) {
408
+ if (!icon)
409
+ return 'fa-solid fa-table';
410
+ if (icon.startsWith('fa-') || icon.startsWith('fa ')) {
411
+ if (icon.startsWith('fa-solid') || icon.startsWith('fa-regular') ||
412
+ icon.startsWith('fa-light') || icon.startsWith('fa-brands') ||
413
+ icon.startsWith('fa ')) {
414
+ return icon;
415
+ }
416
+ return `fa-solid ${icon}`;
417
+ }
418
+ return `fa-solid fa-${icon}`;
419
+ }
420
+ /**
421
+ * Debounced save to avoid too many writes
422
+ */
423
+ debouncedSave() {
424
+ if (this.saveTimeout) {
425
+ clearTimeout(this.saveTimeout);
426
+ }
427
+ this.saveTimeout = setTimeout(() => {
428
+ this.saveState();
429
+ }, 1000);
430
+ }
431
+ /**
432
+ * Save state to UserSetting
433
+ */
434
+ async saveState() {
435
+ try {
436
+ const userId = this.metadata.CurrentUser?.ID;
437
+ if (!userId)
438
+ return;
439
+ const settingKey = this.getSettingKey();
440
+ const md = new Metadata();
441
+ const rv = new RunView();
442
+ // Check if setting exists
443
+ const result = await rv.RunView({
444
+ EntityName: 'MJ: User Settings',
445
+ ExtraFilter: `UserID='${userId}' AND Setting='${settingKey}'`,
446
+ ResultType: 'entity_object'
447
+ });
448
+ let setting;
449
+ if (result.Success && result.Results.length > 0) {
450
+ setting = result.Results[0];
451
+ }
452
+ else {
453
+ setting = await md.GetEntityObject('MJ: User Settings');
454
+ setting.UserID = userId;
455
+ setting.Setting = settingKey;
456
+ }
457
+ setting.Value = JSON.stringify(this.state$.value);
458
+ await setting.Save();
459
+ }
460
+ catch (error) {
461
+ console.warn('Failed to save Data Explorer state:', error);
462
+ }
463
+ }
464
+ /**
465
+ * Navigate to a specific breadcrumb level.
466
+ * Clears any selection deeper than the clicked breadcrumb.
467
+ */
468
+ navigateToBreadcrumb(breadcrumb) {
469
+ switch (breadcrumb.type) {
470
+ case 'application':
471
+ // Clear entity and record selection (show home view)
472
+ this.updateState({
473
+ selectedEntityName: null,
474
+ selectedViewId: null,
475
+ selectedRecordId: null,
476
+ selectedRecordName: null,
477
+ smartFilterPrompt: '',
478
+ detailPanelOpen: false
479
+ });
480
+ break;
481
+ case 'entity':
482
+ // Keep entity selected, clear record selection
483
+ if (breadcrumb.entityName) {
484
+ this.updateState({
485
+ selectedRecordId: null,
486
+ selectedRecordName: null,
487
+ detailPanelOpen: false
488
+ });
489
+ }
490
+ break;
491
+ case 'record':
492
+ // Already at record level, do nothing
493
+ break;
494
+ }
495
+ }
496
+ // ========================================
497
+ // HOME SCREEN ENTITY MANAGEMENT
498
+ // ========================================
499
+ /**
500
+ * Toggle between showing all entities or just common (DefaultForNewUser) entities
501
+ */
502
+ toggleShowAllEntities() {
503
+ this.updateState({ showAllEntities: !this.state$.value.showAllEntities });
504
+ }
505
+ /**
506
+ * Track entity access - called when user navigates to an entity
507
+ */
508
+ trackEntityAccess(entityName, entityId) {
509
+ const recentEntityAccesses = [...this.state$.value.recentEntityAccesses];
510
+ // Find existing entry
511
+ const existingIndex = recentEntityAccesses.findIndex(r => r.entityId === entityId);
512
+ if (existingIndex >= 0) {
513
+ // Update existing entry
514
+ recentEntityAccesses[existingIndex] = {
515
+ ...recentEntityAccesses[existingIndex],
516
+ lastAccessed: new Date(),
517
+ accessCount: recentEntityAccesses[existingIndex].accessCount + 1
518
+ };
519
+ // Move to front
520
+ const [item] = recentEntityAccesses.splice(existingIndex, 1);
521
+ recentEntityAccesses.unshift(item);
522
+ }
523
+ else {
524
+ // Add new entry at front
525
+ recentEntityAccesses.unshift({
526
+ entityName,
527
+ entityId,
528
+ lastAccessed: new Date(),
529
+ accessCount: 1
530
+ });
531
+ }
532
+ // Trim to max size
533
+ if (recentEntityAccesses.length > MAX_RECENT_ENTITIES) {
534
+ recentEntityAccesses.length = MAX_RECENT_ENTITIES;
535
+ }
536
+ this.updateState({ recentEntityAccesses });
537
+ }
538
+ /**
539
+ * Add entity to favorites using User Favorites entity
540
+ */
541
+ async addEntityToFavorites(entityName, entityId) {
542
+ try {
543
+ const userId = this.metadata.CurrentUser?.ID;
544
+ if (!userId)
545
+ return false;
546
+ // Check if already favorited
547
+ const favorites = this.state$.value.favoriteEntities;
548
+ if (favorites.some(f => f.entityId === entityId)) {
549
+ return true; // Already favorited
550
+ }
551
+ // Get the Entities entity ID for favoriting
552
+ const entitiesEntity = this.metadata.Entities.find(e => e.Name === 'Entities');
553
+ if (!entitiesEntity)
554
+ return false;
555
+ // Create User Favorite record
556
+ const md = new Metadata();
557
+ const favorite = await md.GetEntityObject('User Favorites');
558
+ favorite.UserID = userId;
559
+ favorite.EntityID = entitiesEntity.ID;
560
+ favorite.RecordID = entityId;
561
+ const saved = await favorite.Save();
562
+ if (saved) {
563
+ // Update local state
564
+ const favoriteEntities = [...favorites, {
565
+ userFavoriteId: favorite.ID,
566
+ entityName,
567
+ entityId
568
+ }];
569
+ this.updateState({ favoriteEntities });
570
+ }
571
+ return saved;
572
+ }
573
+ catch (error) {
574
+ console.warn('Failed to add entity to favorites:', error);
575
+ return false;
576
+ }
577
+ }
578
+ /**
579
+ * Remove entity from favorites
580
+ */
581
+ async removeEntityFromFavorites(entityId) {
582
+ try {
583
+ const favorites = this.state$.value.favoriteEntities;
584
+ const favorite = favorites.find(f => f.entityId === entityId);
585
+ if (!favorite)
586
+ return true; // Already not favorited
587
+ // Delete User Favorite record
588
+ const md = new Metadata();
589
+ const favoriteEntity = await md.GetEntityObject('User Favorites');
590
+ await favoriteEntity.Load(favorite.userFavoriteId);
591
+ const deleted = await favoriteEntity.Delete();
592
+ if (deleted) {
593
+ // Update local state
594
+ const favoriteEntities = favorites.filter(f => f.entityId !== entityId);
595
+ this.updateState({ favoriteEntities });
596
+ }
597
+ return deleted;
598
+ }
599
+ catch (error) {
600
+ console.warn('Failed to remove entity from favorites:', error);
601
+ return false;
602
+ }
603
+ }
604
+ /**
605
+ * Check if an entity is favorited
606
+ */
607
+ isEntityFavorited(entityId) {
608
+ return this.state$.value.favoriteEntities.some(f => f.entityId === entityId);
609
+ }
610
+ /**
611
+ * Load application entities with DefaultForNewUser information
612
+ */
613
+ async loadApplicationEntities() {
614
+ try {
615
+ if (!this.currentFilter?.applicationId) {
616
+ this.applicationEntities = [];
617
+ this.defaultEntityIds.clear();
618
+ return;
619
+ }
620
+ const rv = new RunView();
621
+ const result = await rv.RunView({
622
+ EntityName: 'Application Entities',
623
+ ExtraFilter: `ApplicationID = '${this.currentFilter.applicationId}'`,
624
+ ResultType: 'entity_object'
625
+ });
626
+ if (result.Success && result.Results) {
627
+ this.applicationEntities = result.Results;
628
+ this.defaultEntityIds.clear();
629
+ for (const appEntity of result.Results) {
630
+ if (appEntity.DefaultForNewUser) {
631
+ this.defaultEntityIds.add(appEntity.EntityID);
632
+ }
633
+ }
634
+ }
635
+ }
636
+ catch (error) {
637
+ console.warn('Failed to load application entities:', error);
638
+ }
639
+ }
640
+ /**
641
+ * Load favorite entities from User Favorites
642
+ */
643
+ async loadFavoriteEntities() {
644
+ try {
645
+ const userId = this.metadata.CurrentUser?.ID;
646
+ if (!userId)
647
+ return;
648
+ // Get the Entities entity to filter favorites
649
+ const entitiesEntity = this.metadata.Entities.find(e => e.Name === 'Entities');
650
+ if (!entitiesEntity)
651
+ return;
652
+ const rv = new RunView();
653
+ const result = await rv.RunView({
654
+ EntityName: 'User Favorites',
655
+ ExtraFilter: `UserID='${userId}' AND EntityID='${entitiesEntity.ID}'`,
656
+ ResultType: 'entity_object'
657
+ });
658
+ if (result.Success && result.Results) {
659
+ const favoriteEntities = [];
660
+ for (const fav of result.Results) {
661
+ // Look up entity name from RecordID (which is the Entity.ID)
662
+ const entity = this.metadata.Entities.find(e => e.ID === fav.RecordID);
663
+ if (entity) {
664
+ favoriteEntities.push({
665
+ userFavoriteId: fav.ID,
666
+ entityName: entity.Name,
667
+ entityId: fav.RecordID
668
+ });
669
+ }
670
+ }
671
+ this.updateState({ favoriteEntities });
672
+ }
673
+ }
674
+ catch (error) {
675
+ console.warn('Failed to load favorite entities:', error);
676
+ }
677
+ }
678
+ /**
679
+ * Load recent records from User Record Logs with batch record name lookup
680
+ */
681
+ async loadRecentRecords() {
682
+ try {
683
+ const userId = this.metadata.CurrentUser?.ID;
684
+ if (!userId)
685
+ return;
686
+ const rv = new RunView();
687
+ const result = await rv.RunView({
688
+ EntityName: 'User Record Logs',
689
+ ExtraFilter: `UserID='${userId}'`,
690
+ OrderBy: 'LatestAt DESC',
691
+ MaxRows: MAX_RECENT_RECORDS,
692
+ ResultType: 'entity_object'
693
+ });
694
+ if (result.Success && result.Results) {
695
+ const recentRecords = [];
696
+ const recordNameInputs = [];
697
+ const recordIndexMap = new Map(); // Map composite key to array index
698
+ for (const log of result.Results) {
699
+ // Look up entity name from EntityID
700
+ const entity = this.metadata.Entities.find(e => e.ID === log.EntityID);
701
+ if (entity) {
702
+ // Filter by application context if applicable
703
+ if (this.currentFilter?.applicationId && !this.applicationEntities.some(ae => ae.EntityID === log.EntityID)) {
704
+ continue; // Skip records from entities not in this application
705
+ }
706
+ const recordAccess = {
707
+ entityName: entity.Name,
708
+ entityId: log.EntityID,
709
+ recordId: log.RecordID,
710
+ latestAt: log.LatestAt,
711
+ totalCount: log.TotalCount
712
+ };
713
+ const index = recentRecords.length;
714
+ recentRecords.push(recordAccess);
715
+ // Build composite key for batch name lookup
716
+ const compositeKey = this.buildCompositeKeyForEntity(entity, log.RecordID);
717
+ if (compositeKey) {
718
+ recordNameInputs.push({ EntityName: entity.Name, CompositeKey: compositeKey });
719
+ recordIndexMap.set(`${entity.Name}|${log.RecordID}`, index);
720
+ }
721
+ }
722
+ }
723
+ // Batch fetch record names
724
+ if (recordNameInputs.length > 0) {
725
+ try {
726
+ const nameResults = await this.metadata.GetEntityRecordNames(recordNameInputs);
727
+ for (const nameResult of nameResults) {
728
+ if (nameResult.Success && nameResult.RecordName) {
729
+ const pkValue = nameResult.CompositeKey.KeyValuePairs[0]?.Value?.toString();
730
+ const key = `${nameResult.EntityName}|${pkValue}`;
731
+ const index = recordIndexMap.get(key);
732
+ if (index !== undefined) {
733
+ recentRecords[index].recordName = nameResult.RecordName;
734
+ }
735
+ }
736
+ }
737
+ }
738
+ catch (nameError) {
739
+ console.warn('Failed to load record names:', nameError);
740
+ // Continue without names - they'll show record IDs
741
+ }
742
+ }
743
+ this.recentRecords$.next(recentRecords);
744
+ }
745
+ }
746
+ catch (error) {
747
+ console.warn('Failed to load recent records:', error);
748
+ }
749
+ }
750
+ /**
751
+ * Build a CompositeKey for a given entity and record ID.
752
+ * Assumes single primary key field named ID for most entities.
753
+ */
754
+ buildCompositeKeyForEntity(entity, recordId) {
755
+ try {
756
+ const entityInfo = this.metadata.Entities.find(e => e.Name === entity.Name);
757
+ if (!entityInfo)
758
+ return null;
759
+ const pkFields = entityInfo.PrimaryKeys;
760
+ if (pkFields.length === 0)
761
+ return null;
762
+ const compositeKey = new CompositeKey();
763
+ // For single PK, use the recordId directly
764
+ if (pkFields.length === 1) {
765
+ compositeKey.KeyValuePairs = [{
766
+ FieldName: pkFields[0].Name,
767
+ Value: recordId
768
+ }];
769
+ }
770
+ else {
771
+ // For composite keys, we'd need to parse the recordId
772
+ // For now, assume the recordId is already in the correct format
773
+ // This is a simplified approach - composite keys would need more handling
774
+ compositeKey.KeyValuePairs = [{
775
+ FieldName: pkFields[0].Name,
776
+ Value: recordId
777
+ }];
778
+ }
779
+ return compositeKey;
780
+ }
781
+ catch {
782
+ return null;
783
+ }
784
+ }
785
+ /**
786
+ * Refresh recent records (call after navigating to a record)
787
+ */
788
+ async refreshRecentRecords() {
789
+ await this.loadRecentRecords();
790
+ }
791
+ /**
792
+ * Add or update a recent record locally (without DB refresh).
793
+ * Call this when a record is selected to provide instant UI feedback.
794
+ * The DB write happens separately via RecentAccessService.logAccess().
795
+ *
796
+ * @param entityName The entity name
797
+ * @param entityId The entity ID
798
+ * @param recordId The record ID (primary key value)
799
+ * @param recordName Optional display name for the record
800
+ */
801
+ addLocalRecentRecord(entityName, entityId, recordId, recordName) {
802
+ // Filter by application context if applicable
803
+ if (this.currentFilter?.applicationId && !this.applicationEntities.some(ae => ae.EntityID === entityId)) {
804
+ return; // Don't add records from entities not in this application
805
+ }
806
+ const currentRecords = [...this.recentRecords$.value];
807
+ // Find existing entry
808
+ const existingIndex = currentRecords.findIndex(r => r.entityId === entityId && r.recordId === recordId);
809
+ const newRecord = {
810
+ entityName,
811
+ entityId,
812
+ recordId,
813
+ recordName,
814
+ latestAt: new Date(),
815
+ totalCount: existingIndex >= 0 ? currentRecords[existingIndex].totalCount + 1 : 1
816
+ };
817
+ if (existingIndex >= 0) {
818
+ // Remove existing entry (will be re-added at front)
819
+ currentRecords.splice(existingIndex, 1);
820
+ }
821
+ // Add to front
822
+ currentRecords.unshift(newRecord);
823
+ // Trim to max size
824
+ if (currentRecords.length > MAX_RECENT_RECORDS) {
825
+ currentRecords.length = MAX_RECENT_RECORDS;
826
+ }
827
+ this.recentRecords$.next(currentRecords);
828
+ }
829
+ /**
830
+ * Load favorite records from User Favorites (non-entity favorites) with batch record name lookup.
831
+ * Filters to only include records from entities in the current application context.
832
+ */
833
+ async loadFavoriteRecords() {
834
+ try {
835
+ const userId = this.metadata.CurrentUser?.ID;
836
+ if (!userId)
837
+ return;
838
+ // Get the Entities entity to exclude entity favorites
839
+ const entitiesEntity = this.metadata.Entities.find(e => e.Name === 'Entities');
840
+ const entitiesEntityId = entitiesEntity?.ID || '';
841
+ const rv = new RunView();
842
+ const result = await rv.RunView({
843
+ EntityName: 'User Favorites',
844
+ ExtraFilter: `UserID='${userId}' AND EntityID <> '${entitiesEntityId}'`,
845
+ ResultType: 'entity_object'
846
+ });
847
+ if (result.Success && result.Results) {
848
+ const favoriteRecords = [];
849
+ const recordNameInputs = [];
850
+ const recordIndexMap = new Map();
851
+ for (const fav of result.Results) {
852
+ // Look up entity info from the EntityID
853
+ const entity = this.metadata.Entities.find(e => e.ID === fav.EntityID);
854
+ if (entity) {
855
+ // Filter by application context if applicable
856
+ if (this.currentFilter?.applicationId && !this.applicationEntities.some(ae => ae.EntityID === fav.EntityID)) {
857
+ continue; // Skip records from entities not in this application
858
+ }
859
+ const favoriteRecord = {
860
+ userFavoriteId: fav.ID,
861
+ entityName: entity.Name,
862
+ entityId: fav.EntityID,
863
+ recordId: fav.RecordID
864
+ };
865
+ const index = favoriteRecords.length;
866
+ favoriteRecords.push(favoriteRecord);
867
+ // Build composite key for batch name lookup
868
+ const compositeKey = this.buildCompositeKeyForEntity(entity, fav.RecordID);
869
+ if (compositeKey) {
870
+ recordNameInputs.push({ EntityName: entity.Name, CompositeKey: compositeKey });
871
+ recordIndexMap.set(`${entity.Name}|${fav.RecordID}`, index);
872
+ }
873
+ }
874
+ }
875
+ // Batch fetch record names
876
+ if (recordNameInputs.length > 0) {
877
+ try {
878
+ const nameResults = await this.metadata.GetEntityRecordNames(recordNameInputs);
879
+ for (const nameResult of nameResults) {
880
+ if (nameResult.Success && nameResult.RecordName) {
881
+ const pkValue = nameResult.CompositeKey.KeyValuePairs[0]?.Value?.toString();
882
+ const key = `${nameResult.EntityName}|${pkValue}`;
883
+ const index = recordIndexMap.get(key);
884
+ if (index !== undefined) {
885
+ favoriteRecords[index].recordName = nameResult.RecordName;
886
+ }
887
+ }
888
+ }
889
+ }
890
+ catch (nameError) {
891
+ console.warn('Failed to load record names for favorites:', nameError);
892
+ // Continue without names - they'll show record IDs
893
+ }
894
+ }
895
+ this.favoriteRecords$.next(favoriteRecords);
896
+ this.updateState({ favoriteRecords });
897
+ }
898
+ }
899
+ catch (error) {
900
+ console.warn('Failed to load favorite records:', error);
901
+ }
902
+ }
903
+ static ɵfac = function ExplorerStateService_Factory(t) { return new (t || ExplorerStateService)(); };
904
+ static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: ExplorerStateService, factory: ExplorerStateService.ɵfac, providedIn: 'root' });
905
+ }
906
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ExplorerStateService, [{
907
+ type: Injectable,
908
+ args: [{
909
+ providedIn: 'root'
910
+ }]
911
+ }], () => [], null); })();
912
+ //# sourceMappingURL=explorer-state.service.js.map