@memberjunction/ng-explorer-core 5.34.1 → 5.36.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 (42) hide show
  1. package/dist/generated/lazy-feature-config.d.ts +1 -1
  2. package/dist/generated/lazy-feature-config.d.ts.map +1 -1
  3. package/dist/generated/lazy-feature-config.js +5 -3
  4. package/dist/generated/lazy-feature-config.js.map +1 -1
  5. package/dist/lib/resource-wrappers/chat-collections-resource.component.d.ts.map +1 -1
  6. package/dist/lib/resource-wrappers/chat-collections-resource.component.js +9 -2
  7. package/dist/lib/resource-wrappers/chat-collections-resource.component.js.map +1 -1
  8. package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts +8 -0
  9. package/dist/lib/resource-wrappers/chat-conversations-resource.component.d.ts.map +1 -1
  10. package/dist/lib/resource-wrappers/chat-conversations-resource.component.js +25 -0
  11. package/dist/lib/resource-wrappers/chat-conversations-resource.component.js.map +1 -1
  12. package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts.map +1 -1
  13. package/dist/lib/resource-wrappers/dashboard-resource.component.js +23 -1
  14. package/dist/lib/resource-wrappers/dashboard-resource.component.js.map +1 -1
  15. package/dist/lib/resource-wrappers/search-results-resource.component.d.ts +6 -0
  16. package/dist/lib/resource-wrappers/search-results-resource.component.d.ts.map +1 -1
  17. package/dist/lib/resource-wrappers/search-results-resource.component.js +21 -0
  18. package/dist/lib/resource-wrappers/search-results-resource.component.js.map +1 -1
  19. package/dist/lib/resource-wrappers/view-resource.component.d.ts +12 -0
  20. package/dist/lib/resource-wrappers/view-resource.component.d.ts.map +1 -1
  21. package/dist/lib/resource-wrappers/view-resource.component.js +107 -15
  22. package/dist/lib/resource-wrappers/view-resource.component.js.map +1 -1
  23. package/dist/lib/shell/components/tabs/component-cache-manager.d.ts +11 -0
  24. package/dist/lib/shell/components/tabs/component-cache-manager.d.ts.map +1 -1
  25. package/dist/lib/shell/components/tabs/component-cache-manager.js +26 -0
  26. package/dist/lib/shell/components/tabs/component-cache-manager.js.map +1 -1
  27. package/dist/lib/shell/components/tabs/tab-container.component.d.ts +26 -0
  28. package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
  29. package/dist/lib/shell/components/tabs/tab-container.component.js +75 -3
  30. package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
  31. package/dist/lib/shell/shell.component.d.ts.map +1 -1
  32. package/dist/lib/shell/shell.component.js +42 -7
  33. package/dist/lib/shell/shell.component.js.map +1 -1
  34. package/dist/lib/single-list-detail/single-list-detail.component.d.ts +153 -3
  35. package/dist/lib/single-list-detail/single-list-detail.component.d.ts.map +1 -1
  36. package/dist/lib/single-list-detail/single-list-detail.component.js +1479 -271
  37. package/dist/lib/single-list-detail/single-list-detail.component.js.map +1 -1
  38. package/dist/module.d.ts +4 -3
  39. package/dist/module.d.ts.map +1 -1
  40. package/dist/module.js +4 -0
  41. package/dist/module.js.map +1 -1
  42. package/package.json +44 -42
@@ -9,10 +9,12 @@ import { BaseResourceComponent } from '@memberjunction/ng-shared';
9
9
  import { ViewInfo } from '@memberjunction/core-entities';
10
10
  import { RegisterClass, MJGlobal, MJEventType, UUIDsEqual } from '@memberjunction/global';
11
11
  import { CompositeKey, RunView } from '@memberjunction/core';
12
+ import { GraphQLListsClient } from '@memberjunction/graphql-dataprovider';
12
13
  import * as i0 from "@angular/core";
13
14
  import * as i1 from "@memberjunction/ng-export-service";
14
15
  import * as i2 from "@memberjunction/ng-shared-generic";
15
16
  import * as i3 from "@memberjunction/ng-entity-viewer";
17
+ import * as i4 from "@memberjunction/ng-list-management";
16
18
  const _c0 = ["container"];
17
19
  const _c1 = ["entityViewer"];
18
20
  function UserViewResource_Conditional_2_Template(rf, ctx) { if (rf & 1) {
@@ -40,12 +42,34 @@ function UserViewResource_Conditional_4_Conditional_4_Template(rf, ctx) { if (rf
40
42
  i0.ɵɵadvance();
41
43
  i0.ɵɵtextInterpolate(ctx_r0.viewEntity.Description);
42
44
  } }
43
- function UserViewResource_Conditional_4_Conditional_11_Template(rf, ctx) { if (rf & 1) {
44
- i0.ɵɵelement(0, "i", 15);
45
+ function UserViewResource_Conditional_4_Conditional_10_Conditional_1_Template(rf, ctx) { if (rf & 1) {
46
+ i0.ɵɵelement(0, "i", 16);
47
+ } }
48
+ function UserViewResource_Conditional_4_Conditional_10_Conditional_2_Template(rf, ctx) { if (rf & 1) {
49
+ i0.ɵɵelement(0, "i", 21);
50
+ } }
51
+ function UserViewResource_Conditional_4_Conditional_10_Template(rf, ctx) { if (rf & 1) {
52
+ const _r3 = i0.ɵɵgetCurrentView();
53
+ i0.ɵɵelementStart(0, "button", 20);
54
+ i0.ɵɵlistener("click", function UserViewResource_Conditional_4_Conditional_10_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r3); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.onSaveAsList()); });
55
+ i0.ɵɵconditionalCreate(1, UserViewResource_Conditional_4_Conditional_10_Conditional_1_Template, 1, 0, "i", 16)(2, UserViewResource_Conditional_4_Conditional_10_Conditional_2_Template, 1, 0, "i", 21);
56
+ i0.ɵɵelementStart(3, "span");
57
+ i0.ɵɵtext(4);
58
+ i0.ɵɵelementEnd()();
59
+ } if (rf & 2) {
60
+ const ctx_r0 = i0.ɵɵnextContext(2);
61
+ i0.ɵɵproperty("disabled", ctx_r0.isSavingAsList);
62
+ i0.ɵɵadvance();
63
+ i0.ɵɵconditional(ctx_r0.isSavingAsList ? 1 : 2);
64
+ i0.ɵɵadvance(3);
65
+ i0.ɵɵtextInterpolate(ctx_r0.isSavingAsList ? "Saving..." : "Save as List");
45
66
  } }
46
67
  function UserViewResource_Conditional_4_Conditional_12_Template(rf, ctx) { if (rf & 1) {
47
68
  i0.ɵɵelement(0, "i", 16);
48
69
  } }
70
+ function UserViewResource_Conditional_4_Conditional_13_Template(rf, ctx) { if (rf & 1) {
71
+ i0.ɵɵelement(0, "i", 17);
72
+ } }
49
73
  function UserViewResource_Conditional_4_Template(rf, ctx) { if (rf & 1) {
50
74
  const _r2 = i0.ɵɵgetCurrentView();
51
75
  i0.ɵɵelementStart(0, "div", 7)(1, "div", 8)(2, "h2", 9);
@@ -59,14 +83,18 @@ function UserViewResource_Conditional_4_Template(rf, ctx) { if (rf & 1) {
59
83
  i0.ɵɵelementStart(8, "span");
60
84
  i0.ɵɵtext(9, "New Record");
61
85
  i0.ɵɵelementEnd()();
62
- i0.ɵɵelementStart(10, "button", 14);
63
- i0.ɵɵlistener("click", function UserViewResource_Conditional_4_Template_button_click_10_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.onExport()); });
64
- i0.ɵɵconditionalCreate(11, UserViewResource_Conditional_4_Conditional_11_Template, 1, 0, "i", 15)(12, UserViewResource_Conditional_4_Conditional_12_Template, 1, 0, "i", 16);
65
- i0.ɵɵelementStart(13, "span");
66
- i0.ɵɵtext(14);
86
+ i0.ɵɵconditionalCreate(10, UserViewResource_Conditional_4_Conditional_10_Template, 5, 3, "button", 14);
87
+ i0.ɵɵelementStart(11, "button", 15);
88
+ i0.ɵɵlistener("click", function UserViewResource_Conditional_4_Template_button_click_11_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.onExport()); });
89
+ i0.ɵɵconditionalCreate(12, UserViewResource_Conditional_4_Conditional_12_Template, 1, 0, "i", 16)(13, UserViewResource_Conditional_4_Conditional_13_Template, 1, 0, "i", 17);
90
+ i0.ɵɵelementStart(14, "span");
91
+ i0.ɵɵtext(15);
67
92
  i0.ɵɵelementEnd()()()();
68
- i0.ɵɵelementStart(15, "mj-entity-viewer", 17, 1);
69
- i0.ɵɵlistener("recordOpened", function UserViewResource_Conditional_4_Template_mj_entity_viewer_recordOpened_15_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.onRecordOpened($event)); })("dataLoaded", function UserViewResource_Conditional_4_Template_mj_entity_viewer_dataLoaded_15_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.onDataLoaded()); });
93
+ i0.ɵɵelementStart(16, "mj-entity-viewer", 18, 1);
94
+ i0.ɵɵlistener("recordOpened", function UserViewResource_Conditional_4_Template_mj_entity_viewer_recordOpened_16_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.onRecordOpened($event)); })("dataLoaded", function UserViewResource_Conditional_4_Template_mj_entity_viewer_dataLoaded_16_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.onDataLoaded()); });
95
+ i0.ɵɵelementEnd();
96
+ i0.ɵɵelementStart(18, "mj-save-view-as-list-dialog", 19);
97
+ i0.ɵɵlistener("Save", function UserViewResource_Conditional_4_Template_mj_save_view_as_list_dialog_Save_18_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.onSaveAsListSubmit($event)); })("Cancel", function UserViewResource_Conditional_4_Template_mj_save_view_as_list_dialog_Cancel_18_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.onSaveAsListCancelled()); });
70
98
  i0.ɵɵelementEnd();
71
99
  } if (rf & 2) {
72
100
  const ctx_r0 = i0.ɵɵnextContext();
@@ -77,13 +105,17 @@ function UserViewResource_Conditional_4_Template(rf, ctx) { if (rf & 1) {
77
105
  i0.ɵɵadvance(2);
78
106
  i0.ɵɵproperty("title", i0.ɵɵinterpolate1("Create new ", ctx_r0.entityInfo.Name, " record"));
79
107
  i0.ɵɵadvance(4);
108
+ i0.ɵɵconditional((ctx_r0.viewEntity == null ? null : ctx_r0.viewEntity.ID) ? 10 : -1);
109
+ i0.ɵɵadvance();
80
110
  i0.ɵɵproperty("disabled", ctx_r0.isExporting);
81
111
  i0.ɵɵadvance();
82
- i0.ɵɵconditional(ctx_r0.isExporting ? 11 : 12);
112
+ i0.ɵɵconditional(ctx_r0.isExporting ? 12 : 13);
83
113
  i0.ɵɵadvance(3);
84
114
  i0.ɵɵtextInterpolate(ctx_r0.isExporting ? "Exporting..." : "Export");
85
115
  i0.ɵɵadvance();
86
116
  i0.ɵɵproperty("entity", ctx_r0.entityInfo)("viewEntity", ctx_r0.viewEntity)("gridState", ctx_r0.gridState)("viewMode", ctx_r0.configuredViewMode)("mapRenderMode", ctx_r0.configuredMapRenderMode);
117
+ i0.ɵɵadvance(2);
118
+ i0.ɵɵproperty("Provider", ctx_r0.ProviderToUse)("Visible", ctx_r0.saveAsListDialogVisible)("ViewId", (ctx_r0.viewEntity == null ? null : ctx_r0.viewEntity.ID) ?? null)("ViewName", (ctx_r0.viewEntity == null ? null : ctx_r0.viewEntity.Name) ?? null)("RecordCount", ctx_r0.saveAsListRecordCount);
87
119
  } }
88
120
  /**
89
121
  * UserViewResource - Resource wrapper for displaying User Views in tabs
@@ -113,6 +145,10 @@ let UserViewResource = class UserViewResource extends BaseResourceComponent {
113
145
  configuredMapRenderMode = 'point';
114
146
  // Export state
115
147
  isExporting = false;
148
+ // Save-as-list dialog state
149
+ saveAsListDialogVisible = false;
150
+ saveAsListRecordCount = null;
151
+ isSavingAsList = false;
116
152
  dataLoaded = false;
117
153
  get metadata() { return this.ProviderToUse; }
118
154
  constructor(cdr, exportService) {
@@ -322,6 +358,62 @@ let UserViewResource = class UserViewResource extends BaseResourceComponent {
322
358
  this.cdr.detectChanges();
323
359
  }
324
360
  }
361
+ /**
362
+ * Open the Save-as-List dialog. Only meaningful for saved views (a
363
+ * ViewID is required to materialize). Dynamic views fall back to a
364
+ * user-visible notification rather than silently doing nothing.
365
+ */
366
+ onSaveAsList() {
367
+ if (!this.viewEntity?.ID) {
368
+ this.showNotification('Save as List requires a saved View. Save this view first.', 'info', 4000);
369
+ return;
370
+ }
371
+ // Best-effort record-count hint — the entity-viewer exposes the
372
+ // grid's row count on its gridState; we surface it so the dialog's
373
+ // confirm button can say "Save List (476 records)".
374
+ this.saveAsListRecordCount = this.entityViewerRef?.totalRecordCount ?? null;
375
+ this.saveAsListDialogVisible = true;
376
+ this.cdr.detectChanges();
377
+ }
378
+ onSaveAsListCancelled() {
379
+ this.saveAsListDialogVisible = false;
380
+ this.cdr.detectChanges();
381
+ }
382
+ async onSaveAsListSubmit(payload) {
383
+ const viewId = this.viewEntity?.ID;
384
+ if (!viewId)
385
+ return;
386
+ this.isSavingAsList = true;
387
+ this.cdr.detectChanges();
388
+ try {
389
+ const provider = this.ProviderToUse;
390
+ const client = new GraphQLListsClient(provider);
391
+ const result = await client.MaterializeFromView(viewId, {
392
+ ListName: payload.ListName,
393
+ Description: payload.Description,
394
+ CategoryId: payload.CategoryId,
395
+ RememberLineage: payload.RememberLineage,
396
+ UseSnapshot: payload.UseSnapshot,
397
+ RefreshMode: payload.RefreshMode,
398
+ });
399
+ if (result.Success && result.CreatedListId) {
400
+ this.saveAsListDialogVisible = false;
401
+ this.showNotification(`List created with ${result.Counts?.Added ?? 0} record(s).`, 'success', 3000);
402
+ this.navigationService.OpenEntityRecord('MJ: Lists', new CompositeKey([{ FieldName: 'ID', Value: result.CreatedListId }]));
403
+ }
404
+ else {
405
+ this.showNotification(`Save failed: ${result.Message}`, 'error', 5000);
406
+ }
407
+ }
408
+ catch (e) {
409
+ const message = e instanceof Error ? e.message : String(e);
410
+ this.showNotification(`Save failed: ${message}`, 'error', 5000);
411
+ }
412
+ finally {
413
+ this.isSavingAsList = false;
414
+ this.cdr.detectChanges();
415
+ }
416
+ }
325
417
  /**
326
418
  * Load all records for the current view/entity for export
327
419
  */
@@ -400,14 +492,14 @@ let UserViewResource = class UserViewResource extends BaseResourceComponent {
400
492
  let _t;
401
493
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.containerElement = _t.first);
402
494
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.entityViewerRef = _t.first);
403
- } }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 5, vars: 1, consts: [["container", ""], ["entityViewer", ""], [1, "view-resource-container"], [1, "view-loading-state"], [1, "view-error-state"], ["text", "Loading view...", "size", "large"], [1, "fas", "fa-exclamation-triangle"], [1, "view-header"], [1, "header-left"], [1, "view-title"], [1, "view-description"], [1, "header-right"], [1, "action-button", "create-button", 3, "click", "title"], [1, "fa-solid", "fa-plus"], ["title", "Export to Excel", 1, "action-button", "export-button", 3, "click", "disabled"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "fa-solid", "fa-file-excel"], [3, "recordOpened", "dataLoaded", "entity", "viewEntity", "gridState", "viewMode", "mapRenderMode"]], template: function UserViewResource_Template(rf, ctx) { if (rf & 1) {
495
+ } }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 5, vars: 1, consts: [["container", ""], ["entityViewer", ""], [1, "view-resource-container"], [1, "view-loading-state"], [1, "view-error-state"], ["text", "Loading view...", "size", "large"], [1, "fas", "fa-exclamation-triangle"], [1, "view-header"], [1, "header-left"], [1, "view-title"], [1, "view-description"], [1, "header-right"], [1, "action-button", "create-button", 3, "click", "title"], [1, "fa-solid", "fa-plus"], ["title", "Materialize these results as a static List", 1, "action-button", "save-as-list-button", 3, "disabled"], ["title", "Export to Excel", 1, "action-button", "export-button", 3, "click", "disabled"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "fa-solid", "fa-file-excel"], [3, "recordOpened", "dataLoaded", "entity", "viewEntity", "gridState", "viewMode", "mapRenderMode"], [3, "Save", "Cancel", "Provider", "Visible", "ViewId", "ViewName", "RecordCount"], ["title", "Materialize these results as a static List", 1, "action-button", "save-as-list-button", 3, "click", "disabled"], [1, "fa-solid", "fa-floppy-disk"]], template: function UserViewResource_Template(rf, ctx) { if (rf & 1) {
404
496
  i0.ɵɵelementStart(0, "div", 2, 0);
405
- i0.ɵɵconditionalCreate(2, UserViewResource_Conditional_2_Template, 2, 0, "div", 3)(3, UserViewResource_Conditional_3_Template, 4, 1, "div", 4)(4, UserViewResource_Conditional_4_Template, 17, 12);
497
+ i0.ɵɵconditionalCreate(2, UserViewResource_Conditional_2_Template, 2, 0, "div", 3)(3, UserViewResource_Conditional_3_Template, 4, 1, "div", 4)(4, UserViewResource_Conditional_4_Template, 19, 18);
406
498
  i0.ɵɵelementEnd();
407
499
  } if (rf & 2) {
408
500
  i0.ɵɵadvance(2);
409
501
  i0.ɵɵconditional(ctx.isLoading ? 2 : ctx.errorMessage ? 3 : ctx.entityInfo ? 4 : -1);
410
- } }, dependencies: [i2.LoadingComponent, i3.EntityViewerComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n }\n .view-resource-container[_ngcontent-%COMP%] {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n .view-header[_ngcontent-%COMP%] {\n padding: 16px 20px 8px 20px;\n flex-shrink: 0;\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: 16px;\n }\n .header-left[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n }\n .header-right[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n flex-shrink: 0;\n }\n .view-title[_ngcontent-%COMP%] {\n margin: 0 0 4px 0;\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--text-primary, #1a1a1a);\n }\n .view-description[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 0.875rem;\n color: var(--text-secondary, #666);\n }\n .action-button[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-primary);\n font-size: 0.875rem;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n white-space: nowrap;\n }\n .action-button[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-bg-surface-sunken);\n border-color: var(--mj-border-default);\n }\n .action-button[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n .action-button[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.875rem;\n }\n .create-button[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: white;\n border-color: var(--mj-brand-primary);\n }\n .create-button[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n border-color: var(--mj-brand-primary-hover);\n }\n .export-button[_ngcontent-%COMP%]:hover:not(:disabled) {\n color: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n }\n .view-loading-state[_ngcontent-%COMP%], \n .view-error-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 }\n .view-error-state[_ngcontent-%COMP%] {\n color: var(--danger-color, #dc3545);\n }\n .view-error-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 2rem;\n }\n .view-error-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 1rem;\n }\n mj-entity-viewer[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n }"] });
502
+ } }, dependencies: [i2.LoadingComponent, i3.EntityViewerComponent, i4.SaveViewAsListDialogComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n }\n .view-resource-container[_ngcontent-%COMP%] {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n .view-header[_ngcontent-%COMP%] {\n padding: 16px 20px 8px 20px;\n flex-shrink: 0;\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: 16px;\n }\n .header-left[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n }\n .header-right[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n flex-shrink: 0;\n }\n .view-title[_ngcontent-%COMP%] {\n margin: 0 0 4px 0;\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--text-primary, #1a1a1a);\n }\n .view-description[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 0.875rem;\n color: var(--text-secondary, #666);\n }\n .action-button[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-primary);\n font-size: 0.875rem;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n white-space: nowrap;\n }\n .action-button[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-bg-surface-sunken);\n border-color: var(--mj-border-default);\n }\n .action-button[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n .action-button[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.875rem;\n }\n .create-button[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: white;\n border-color: var(--mj-brand-primary);\n }\n .create-button[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n border-color: var(--mj-brand-primary-hover);\n }\n .export-button[_ngcontent-%COMP%]:hover:not(:disabled) {\n color: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n }\n .view-loading-state[_ngcontent-%COMP%], \n .view-error-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 }\n .view-error-state[_ngcontent-%COMP%] {\n color: var(--danger-color, #dc3545);\n }\n .view-error-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 2rem;\n }\n .view-error-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 1rem;\n }\n mj-entity-viewer[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n }"] });
411
503
  };
412
504
  UserViewResource = __decorate([
413
505
  RegisterClass(BaseResourceComponent, 'ViewResource')
@@ -415,7 +507,7 @@ UserViewResource = __decorate([
415
507
  export { UserViewResource };
416
508
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(UserViewResource, [{
417
509
  type: Component,
418
- args: [{ standalone: false, selector: 'mj-userview-resource', template: "<div #container class=\"view-resource-container\">\n @if (isLoading) {\n <div class=\"view-loading-state\">\n <mj-loading text=\"Loading view...\" size=\"large\"></mj-loading>\n </div>\n } @else if (errorMessage) {\n <div class=\"view-error-state\">\n <i class=\"fas fa-exclamation-triangle\"></i>\n <p>{{ errorMessage }}</p>\n </div>\n } @else if (entityInfo) {\n <!-- Header with title and action buttons -->\n <div class=\"view-header\">\n <div class=\"header-left\">\n <h2 class=\"view-title\">{{ viewEntity?.Name || entityInfo.Name }}</h2>\n @if (viewEntity?.Description) {\n <p class=\"view-description\">{{ viewEntity!.Description }}</p>\n }\n </div>\n <div class=\"header-right\">\n <!-- Create New Record Button -->\n <button\n class=\"action-button create-button\"\n (click)=\"onCreateNewRecord()\"\n title=\"Create new {{ entityInfo.Name }} record\">\n <i class=\"fa-solid fa-plus\"></i>\n <span>New Record</span>\n </button>\n\n <!-- Export Button -->\n <button\n class=\"action-button export-button\"\n (click)=\"onExport()\"\n [disabled]=\"isExporting\"\n title=\"Export to Excel\">\n @if (isExporting) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n } @else {\n <i class=\"fa-solid fa-file-excel\"></i>\n }\n <span>{{ isExporting ? 'Exporting...' : 'Export' }}</span>\n </button>\n </div>\n </div>\n\n <!-- Entity Viewer -->\n <mj-entity-viewer\n #entityViewer\n [entity]=\"entityInfo\"\n [viewEntity]=\"viewEntity\"\n [gridState]=\"gridState\"\n [viewMode]=\"configuredViewMode\"\n [mapRenderMode]=\"configuredMapRenderMode\"\n (recordOpened)=\"onRecordOpened($any($event))\"\n (dataLoaded)=\"onDataLoaded()\">\n </mj-entity-viewer>\n }\n</div>\n", styles: ["\n :host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n }\n .view-resource-container {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n .view-header {\n padding: 16px 20px 8px 20px;\n flex-shrink: 0;\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: 16px;\n }\n .header-left {\n flex: 1;\n min-width: 0;\n }\n .header-right {\n display: flex;\n gap: 8px;\n flex-shrink: 0;\n }\n .view-title {\n margin: 0 0 4px 0;\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--text-primary, #1a1a1a);\n }\n .view-description {\n margin: 0;\n font-size: 0.875rem;\n color: var(--text-secondary, #666);\n }\n .action-button {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-primary);\n font-size: 0.875rem;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n white-space: nowrap;\n }\n .action-button:hover:not(:disabled) {\n background: var(--mj-bg-surface-sunken);\n border-color: var(--mj-border-default);\n }\n .action-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n .action-button i {\n font-size: 0.875rem;\n }\n .create-button {\n background: var(--mj-brand-primary);\n color: white;\n border-color: var(--mj-brand-primary);\n }\n .create-button:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n border-color: var(--mj-brand-primary-hover);\n }\n .export-button:hover:not(:disabled) {\n color: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n }\n .view-loading-state,\n .view-error-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n gap: 16px;\n }\n .view-error-state {\n color: var(--danger-color, #dc3545);\n }\n .view-error-state i {\n font-size: 2rem;\n }\n .view-error-state p {\n margin: 0;\n font-size: 1rem;\n }\n mj-entity-viewer {\n flex: 1;\n min-height: 0;\n }\n "] }]
510
+ args: [{ standalone: false, selector: 'mj-userview-resource', template: "<div #container class=\"view-resource-container\">\n @if (isLoading) {\n <div class=\"view-loading-state\">\n <mj-loading text=\"Loading view...\" size=\"large\"></mj-loading>\n </div>\n } @else if (errorMessage) {\n <div class=\"view-error-state\">\n <i class=\"fas fa-exclamation-triangle\"></i>\n <p>{{ errorMessage }}</p>\n </div>\n } @else if (entityInfo) {\n <!-- Header with title and action buttons -->\n <div class=\"view-header\">\n <div class=\"header-left\">\n <h2 class=\"view-title\">{{ viewEntity?.Name || entityInfo.Name }}</h2>\n @if (viewEntity?.Description) {\n <p class=\"view-description\">{{ viewEntity!.Description }}</p>\n }\n </div>\n <div class=\"header-right\">\n <!-- Create New Record Button -->\n <button\n class=\"action-button create-button\"\n (click)=\"onCreateNewRecord()\"\n title=\"Create new {{ entityInfo.Name }} record\">\n <i class=\"fa-solid fa-plus\"></i>\n <span>New Record</span>\n </button>\n\n <!-- Save as List Button \u2014 only meaningful for saved (non-dynamic) views. -->\n @if (viewEntity?.ID) {\n <button\n class=\"action-button save-as-list-button\"\n (click)=\"onSaveAsList()\"\n [disabled]=\"isSavingAsList\"\n title=\"Materialize these results as a static List\">\n @if (isSavingAsList) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n } @else {\n <i class=\"fa-solid fa-floppy-disk\"></i>\n }\n <span>{{ isSavingAsList ? 'Saving...' : 'Save as List' }}</span>\n </button>\n }\n\n <!-- Export Button -->\n <button\n class=\"action-button export-button\"\n (click)=\"onExport()\"\n [disabled]=\"isExporting\"\n title=\"Export to Excel\">\n @if (isExporting) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n } @else {\n <i class=\"fa-solid fa-file-excel\"></i>\n }\n <span>{{ isExporting ? 'Exporting...' : 'Export' }}</span>\n </button>\n </div>\n </div>\n\n <!-- Entity Viewer -->\n <mj-entity-viewer\n #entityViewer\n [entity]=\"entityInfo\"\n [viewEntity]=\"viewEntity\"\n [gridState]=\"gridState\"\n [viewMode]=\"configuredViewMode\"\n [mapRenderMode]=\"configuredMapRenderMode\"\n (recordOpened)=\"onRecordOpened($any($event))\"\n (dataLoaded)=\"onDataLoaded()\">\n </mj-entity-viewer>\n\n <!-- Save-as-List dialog -->\n <mj-save-view-as-list-dialog\n [Provider]=\"ProviderToUse\"\n [Visible]=\"saveAsListDialogVisible\"\n [ViewId]=\"viewEntity?.ID ?? null\"\n [ViewName]=\"viewEntity?.Name ?? null\"\n [RecordCount]=\"saveAsListRecordCount\"\n (Save)=\"onSaveAsListSubmit($event)\"\n (Cancel)=\"onSaveAsListCancelled()\">\n </mj-save-view-as-list-dialog>\n }\n</div>\n", styles: ["\n :host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n }\n .view-resource-container {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n .view-header {\n padding: 16px 20px 8px 20px;\n flex-shrink: 0;\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: 16px;\n }\n .header-left {\n flex: 1;\n min-width: 0;\n }\n .header-right {\n display: flex;\n gap: 8px;\n flex-shrink: 0;\n }\n .view-title {\n margin: 0 0 4px 0;\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--text-primary, #1a1a1a);\n }\n .view-description {\n margin: 0;\n font-size: 0.875rem;\n color: var(--text-secondary, #666);\n }\n .action-button {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-primary);\n font-size: 0.875rem;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n white-space: nowrap;\n }\n .action-button:hover:not(:disabled) {\n background: var(--mj-bg-surface-sunken);\n border-color: var(--mj-border-default);\n }\n .action-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n .action-button i {\n font-size: 0.875rem;\n }\n .create-button {\n background: var(--mj-brand-primary);\n color: white;\n border-color: var(--mj-brand-primary);\n }\n .create-button:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n border-color: var(--mj-brand-primary-hover);\n }\n .export-button:hover:not(:disabled) {\n color: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n }\n .view-loading-state,\n .view-error-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n gap: 16px;\n }\n .view-error-state {\n color: var(--danger-color, #dc3545);\n }\n .view-error-state i {\n font-size: 2rem;\n }\n .view-error-state p {\n margin: 0;\n font-size: 1rem;\n }\n mj-entity-viewer {\n flex: 1;\n min-height: 0;\n }\n "] }]
419
511
  }], () => [{ type: i0.ChangeDetectorRef }, { type: i1.ExportService }], { containerElement: [{
420
512
  type: ViewChild,
421
513
  args: ['container', { static: true }]
@@ -423,5 +515,5 @@ export { UserViewResource };
423
515
  type: ViewChild,
424
516
  args: ['entityViewer']
425
517
  }] }); })();
426
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(UserViewResource, { className: "UserViewResource", filePath: "src/lib/resource-wrappers/view-resource.component.ts", lineNumber: 136 }); })();
518
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(UserViewResource, { className: "UserViewResource", filePath: "src/lib/resource-wrappers/view-resource.component.ts", lineNumber: 138 }); })();
427
519
  //# sourceMappingURL=view-resource.component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"view-resource.component.js","sourceRoot":"","sources":["../../../src/lib/resource-wrappers/view-resource.component.ts","../../../src/lib/resource-wrappers/view-resource.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';\nimport { BaseResourceComponent, NavigationService } from '@memberjunction/ng-shared';\nimport { ResourceData, MJUserViewEntityExtended, ViewInfo } from '@memberjunction/core-entities';\nimport { RegisterClass, MJGlobal, MJEventType , UUIDsEqual } from '@memberjunction/global';\nimport { CompositeKey, Metadata, EntityInfo, RunView } from '@memberjunction/core';\nimport { RecordOpenedEvent, ViewGridState, EntityViewerComponent, EntityViewMode } from '@memberjunction/ng-entity-viewer';\nimport { ExportService } from '@memberjunction/ng-export-service';\nimport { ExportColumn } from '@memberjunction/export-engine';\n/**\n * UserViewResource - Resource wrapper for displaying User Views in tabs\n *\n * This component wraps the EntityViewerComponent to display view data.\n * It loads the view configuration and entity, then renders the data grid/cards.\n *\n * Key features:\n * - Loads view by ID from ResourceRecordID\n * - Supports dynamic views by entity name + extra filter\n * - Applies view's WhereClause, GridState, and SortState\n * - Opens records in new tabs via NavigationService\n */\n@RegisterClass(BaseResourceComponent, 'ViewResource')\n@Component({\n standalone: false,\n selector: 'mj-userview-resource',\n templateUrl: './view-resource.component.html',\n styles: [`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n }\n .view-resource-container {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n .view-header {\n padding: 16px 20px 8px 20px;\n flex-shrink: 0;\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: 16px;\n }\n .header-left {\n flex: 1;\n min-width: 0;\n }\n .header-right {\n display: flex;\n gap: 8px;\n flex-shrink: 0;\n }\n .view-title {\n margin: 0 0 4px 0;\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--text-primary, #1a1a1a);\n }\n .view-description {\n margin: 0;\n font-size: 0.875rem;\n color: var(--text-secondary, #666);\n }\n .action-button {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-primary);\n font-size: 0.875rem;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n white-space: nowrap;\n }\n .action-button:hover:not(:disabled) {\n background: var(--mj-bg-surface-sunken);\n border-color: var(--mj-border-default);\n }\n .action-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n .action-button i {\n font-size: 0.875rem;\n }\n .create-button {\n background: var(--mj-brand-primary);\n color: white;\n border-color: var(--mj-brand-primary);\n }\n .create-button:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n border-color: var(--mj-brand-primary-hover);\n }\n .export-button:hover:not(:disabled) {\n color: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n }\n .view-loading-state,\n .view-error-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n gap: 16px;\n }\n .view-error-state {\n color: var(--danger-color, #dc3545);\n }\n .view-error-state i {\n font-size: 2rem;\n }\n .view-error-state p {\n margin: 0;\n font-size: 1rem;\n }\n mj-entity-viewer {\n flex: 1;\n min-height: 0;\n }\n `]\n})\nexport class UserViewResource extends BaseResourceComponent {\n @ViewChild('container', { static: true }) containerElement!: ElementRef<HTMLDivElement>;\n @ViewChild('entityViewer') entityViewerRef?: EntityViewerComponent;\n\n public isLoading: boolean = false;\n public errorMessage: string | null = null;\n public entityInfo: EntityInfo | null = null;\n public viewEntity: MJUserViewEntityExtended | null = null;\n public gridState: ViewGridState | null = null;\n\n /** View mode from dashboard configuration (grid/cards/timeline/map) */\n public configuredViewMode: EntityViewMode | null = null;\n\n /** Map render mode from dashboard configuration */\n public configuredMapRenderMode: 'point' | 'choropleth' | 'heatmap' = 'point';\n\n // Export state\n public isExporting: boolean = false;\n\n private dataLoaded = false;\n private get metadata() { return this.ProviderToUse; }\n constructor(\n private cdr: ChangeDetectorRef,\n private exportService: ExportService\n ) {\n super();\n }\n\n override set Data(value: ResourceData) {\n const previousRecordId = super.Data?.ResourceRecordID;\n const previousEntity = super.Data?.Configuration?.Entity;\n super.Data = value;\n\n const newRecordId = value?.ResourceRecordID;\n const newEntity = value?.Configuration?.Entity;\n\n // Read view mode and map render mode from configuration if provided\n const viewMode = value?.Configuration?.['viewMode'] as string | undefined;\n if (viewMode && ['grid', 'cards', 'timeline', 'map'].includes(viewMode)) {\n this.configuredViewMode = viewMode as EntityViewMode;\n } else {\n this.configuredViewMode = null;\n }\n\n const mapMode = value?.Configuration?.['mapRenderMode'] as string | undefined;\n if (mapMode && ['point', 'choropleth', 'heatmap'].includes(mapMode)) {\n this.configuredMapRenderMode = mapMode as 'point' | 'choropleth' | 'heatmap';\n } else {\n this.configuredMapRenderMode = 'point';\n }\n\n // Load on first set, or when the view/entity has changed\n if (!this.dataLoaded || newRecordId !== previousRecordId || newEntity !== previousEntity) {\n this.dataLoaded = true;\n // Reset state before loading new view\n this.entityInfo = null;\n this.viewEntity = null;\n this.gridState = null;\n this.errorMessage = null;\n this.loadView();\n }\n }\n\n override get Data(): ResourceData {\n return super.Data;\n }\n\n /**\n * Load the view and entity based on ResourceData\n */\n private async loadView(): Promise<void> {\n const data = this.Data;\n\n if (!data) {\n this.NotifyLoadComplete();\n return;\n }\n\n this.isLoading = true;\n this.errorMessage = null;\n this.NotifyLoadStarted();\n this.cdr.detectChanges();\n\n try {\n // Case 1: Load view by ID\n if (data.ResourceRecordID) {\n await this.loadViewById(data.ResourceRecordID);\n }\n // Case 2: Load dynamic view by entity name\n else if (data.Configuration?.Entity) {\n await this.loadDynamicView(\n data.Configuration.Entity as string,\n data.Configuration.ExtraFilter as string | undefined\n );\n }\n else {\n this.errorMessage = 'No view ID or entity specified';\n }\n } catch (error) {\n console.error('Error loading view:', error);\n this.errorMessage = error instanceof Error ? error.message : 'Failed to load view';\n } finally {\n this.isLoading = false;\n this.cdr.detectChanges();\n\n // If there was an error, notify load complete now\n if (this.errorMessage) {\n this.NotifyLoadComplete();\n }\n // Otherwise, wait for dataLoaded event from entity-viewer\n }\n }\n\n /**\n * Load a saved view by its ID\n */\n private async loadViewById(viewId: string): Promise<void> {\n // Load the view entity\n const view = await ViewInfo.GetViewEntity(viewId);\n\n if (!view) {\n throw new Error(`View with ID ${viewId} not found`);\n }\n\n this.viewEntity = view as MJUserViewEntityExtended;\n\n // Check permissions\n if (!this.viewEntity.UserCanView) {\n throw new Error('You do not have permission to view this view');\n }\n\n // Load the entity info\n const entity = this.metadata.Entities.find(e => UUIDsEqual(e.ID, this.viewEntity!.EntityID));\n\n if (!entity) {\n throw new Error(`Entity for view not found`);\n }\n\n this.entityInfo = entity;\n\n // Parse grid state if available\n if (this.viewEntity.GridState) {\n try {\n this.gridState = JSON.parse(this.viewEntity.GridState) as ViewGridState;\n } catch (e) {\n console.warn('Failed to parse GridState:', e);\n this.gridState = null;\n }\n }\n }\n\n /**\n * Load a dynamic view (no saved view, just entity + filter)\n */\n private async loadDynamicView(entityName: string, _extraFilter?: string): Promise<void> {\n const entity = this.metadata.Entities.find(\n e => e.Name.trim().toLowerCase() === entityName.trim().toLowerCase()\n );\n\n if (!entity) {\n throw new Error(`Entity '${entityName}' not found`);\n }\n\n this.entityInfo = entity;\n this.viewEntity = null;\n this.gridState = null;\n\n // For dynamic views, we could create a synthetic viewEntity with just the WhereClause\n // but for now, we'll rely on the entity-viewer's default behavior\n }\n\n /**\n * Handle record opened event - open in new tab\n */\n public onRecordOpened(event: RecordOpenedEvent): void {\n if (event && event.entity && event.compositeKey) {\n this.navigationService.OpenEntityRecord(event.entity.Name, event.compositeKey);\n }\n }\n\n /**\n * Handle data loaded event from entity-viewer\n */\n public onDataLoaded(): void {\n this.NotifyLoadComplete();\n }\n\n /**\n * Get display name for the resource tab\n */\n override async GetResourceDisplayName(data: ResourceData): Promise<string> {\n if (data.ResourceRecordID) {\n const compositeKey = new CompositeKey([{ FieldName: 'ID', Value: data.ResourceRecordID }]);\n const name = await this.metadata.GetEntityRecordName('MJ: User Views', compositeKey);\n return name ? name : `View: ${data.ResourceRecordID}`;\n }\n else if (data.Configuration?.Entity) {\n const entityName = data.Configuration.Entity as string;\n const hasFilter = data.Configuration.ExtraFilter;\n return `${entityName} [Dynamic${hasFilter ? ' - Filtered' : ' - All'}]`;\n }\n return 'User Views [Error]';\n }\n\n /**\n * Get icon class for the resource tab\n */\n override async GetResourceIconClass(_data: ResourceData): Promise<string> {\n return 'fa-solid fa-table-list';\n }\n\n /**\n * Handle creating a new record for the current entity\n */\n public onCreateNewRecord(): void {\n if (!this.entityInfo) return;\n\n // Use NavigationService to open a new record form\n this.navigationService.OpenNewEntityRecord(this.entityInfo.Name);\n }\n\n /**\n * Handle export to Excel request\n */\n public async onExport(): Promise<void> {\n if (!this.entityInfo) {\n console.error('Cannot export: entity not available');\n return;\n }\n\n this.isExporting = true;\n this.cdr.detectChanges();\n\n try {\n this.showNotification('Working on the export, will notify you when it is complete...', 'info', 2000);\n\n const rows = await this.loadExportRows();\n const columns = this.buildExportColumns();\n const fileName = this.buildExportFileName();\n\n const result = await this.exportService.toExcel(rows, {\n fileName,\n columns,\n includeHeaders: true\n });\n\n if (result.success) {\n this.exportService.downloadResult(result);\n this.showNotification('Excel Export Complete', 'success', 2000);\n } else {\n this.showNotification('Export failed', 'error', 5000);\n }\n }\n catch (e) {\n this.showNotification('Error exporting data', 'error', 5000);\n console.error('Export error:', e);\n }\n finally {\n this.isExporting = false;\n this.cdr.detectChanges();\n }\n }\n\n /**\n * Load all records for the current view/entity for export\n */\n private async loadExportRows(): Promise<Record<string, unknown>[]> {\n if (!this.entityInfo) {\n throw new Error('No entity selected for export');\n }\n\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n let filter = '';\n if (this.viewEntity?.WhereClause) {\n filter = this.viewEntity.WhereClause;\n }\n\n const result = await rv.RunView<Record<string, unknown>>({\n EntityName: this.entityInfo.Name,\n ExtraFilter: filter,\n OrderBy: '',\n ResultType: 'simple'\n });\n\n if (!result.Success) {\n throw new Error(result.ErrorMessage || 'Failed to load data for export');\n }\n\n return result.Results || [];\n }\n\n /**\n * Determine which columns to export based on grid state, view entity, or entity fields\n */\n private buildExportColumns(): ExportColumn[] {\n if (!this.entityInfo) return [];\n\n if (this.gridState?.columnSettings && this.gridState.columnSettings.length > 0) {\n const visibleColumns = this.gridState.columnSettings.filter(col => col.hidden !== true);\n return visibleColumns.map(col => ({\n name: col.Name,\n displayName: col.DisplayName || col.Name\n }));\n }\n\n if (this.viewEntity?.Columns) {\n const visibleColumns = this.viewEntity.Columns.filter(col => !col.hidden);\n return visibleColumns.map(col => ({\n name: col.Name,\n displayName: col.DisplayName || col.Name\n }));\n }\n\n const visibleFields = this.entityInfo.Fields.filter(f => !f.IsVirtual);\n return visibleFields.map(f => ({\n name: f.Name,\n displayName: f.DisplayNameOrName\n }));\n }\n\n /**\n * Build the export file name based on entity and view\n */\n private buildExportFileName(): string {\n const viewName = this.viewEntity?.Name || 'Data';\n return `${this.entityInfo!.Name}_${viewName}_${new Date().toISOString().split('T')[0]}`;\n }\n\n /**\n * Show a notification to the user\n */\n private showNotification(message: string, style: 'info' | 'success' | 'error' | 'warning', duration: number): void {\n MJGlobal.Instance.RaiseEvent({\n component: this,\n event: MJEventType.DisplaySimpleNotificationRequest,\n eventCode: '',\n args: {\n message,\n style,\n DisplayDuration: duration\n }\n });\n }\n}\n","<div #container class=\"view-resource-container\">\n @if (isLoading) {\n <div class=\"view-loading-state\">\n <mj-loading text=\"Loading view...\" size=\"large\"></mj-loading>\n </div>\n } @else if (errorMessage) {\n <div class=\"view-error-state\">\n <i class=\"fas fa-exclamation-triangle\"></i>\n <p>{{ errorMessage }}</p>\n </div>\n } @else if (entityInfo) {\n <!-- Header with title and action buttons -->\n <div class=\"view-header\">\n <div class=\"header-left\">\n <h2 class=\"view-title\">{{ viewEntity?.Name || entityInfo.Name }}</h2>\n @if (viewEntity?.Description) {\n <p class=\"view-description\">{{ viewEntity!.Description }}</p>\n }\n </div>\n <div class=\"header-right\">\n <!-- Create New Record Button -->\n <button\n class=\"action-button create-button\"\n (click)=\"onCreateNewRecord()\"\n title=\"Create new {{ entityInfo.Name }} record\">\n <i class=\"fa-solid fa-plus\"></i>\n <span>New Record</span>\n </button>\n\n <!-- Export Button -->\n <button\n class=\"action-button export-button\"\n (click)=\"onExport()\"\n [disabled]=\"isExporting\"\n title=\"Export to Excel\">\n @if (isExporting) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n } @else {\n <i class=\"fa-solid fa-file-excel\"></i>\n }\n <span>{{ isExporting ? 'Exporting...' : 'Export' }}</span>\n </button>\n </div>\n </div>\n\n <!-- Entity Viewer -->\n <mj-entity-viewer\n #entityViewer\n [entity]=\"entityInfo\"\n [viewEntity]=\"viewEntity\"\n [gridState]=\"gridState\"\n [viewMode]=\"configuredViewMode\"\n [mapRenderMode]=\"configuredMapRenderMode\"\n (recordOpened)=\"onRecordOpened($any($event))\"\n (dataLoaded)=\"onDataLoaded()\">\n </mj-entity-viewer>\n }\n</div>\n"]}
1
+ {"version":3,"file":"view-resource.component.js","sourceRoot":"","sources":["../../../src/lib/resource-wrappers/view-resource.component.ts","../../../src/lib/resource-wrappers/view-resource.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';\nimport { BaseResourceComponent, NavigationService } from '@memberjunction/ng-shared';\nimport { ResourceData, MJUserViewEntityExtended, ViewInfo } from '@memberjunction/core-entities';\nimport { RegisterClass, MJGlobal, MJEventType , UUIDsEqual } from '@memberjunction/global';\nimport { CompositeKey, Metadata, EntityInfo, RunView } from '@memberjunction/core';\nimport { RecordOpenedEvent, ViewGridState, EntityViewerComponent, EntityViewMode } from '@memberjunction/ng-entity-viewer';\nimport { ExportService } from '@memberjunction/ng-export-service';\nimport { ExportColumn } from '@memberjunction/export-engine';\nimport { GraphQLDataProvider, GraphQLListsClient } from '@memberjunction/graphql-dataprovider';\nimport type { SaveViewAsListResult } from '@memberjunction/ng-list-management';\n/**\n * UserViewResource - Resource wrapper for displaying User Views in tabs\n *\n * This component wraps the EntityViewerComponent to display view data.\n * It loads the view configuration and entity, then renders the data grid/cards.\n *\n * Key features:\n * - Loads view by ID from ResourceRecordID\n * - Supports dynamic views by entity name + extra filter\n * - Applies view's WhereClause, GridState, and SortState\n * - Opens records in new tabs via NavigationService\n */\n@RegisterClass(BaseResourceComponent, 'ViewResource')\n@Component({\n standalone: false,\n selector: 'mj-userview-resource',\n templateUrl: './view-resource.component.html',\n styles: [`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n }\n .view-resource-container {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n .view-header {\n padding: 16px 20px 8px 20px;\n flex-shrink: 0;\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: 16px;\n }\n .header-left {\n flex: 1;\n min-width: 0;\n }\n .header-right {\n display: flex;\n gap: 8px;\n flex-shrink: 0;\n }\n .view-title {\n margin: 0 0 4px 0;\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--text-primary, #1a1a1a);\n }\n .view-description {\n margin: 0;\n font-size: 0.875rem;\n color: var(--text-secondary, #666);\n }\n .action-button {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-primary);\n font-size: 0.875rem;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n white-space: nowrap;\n }\n .action-button:hover:not(:disabled) {\n background: var(--mj-bg-surface-sunken);\n border-color: var(--mj-border-default);\n }\n .action-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n .action-button i {\n font-size: 0.875rem;\n }\n .create-button {\n background: var(--mj-brand-primary);\n color: white;\n border-color: var(--mj-brand-primary);\n }\n .create-button:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n border-color: var(--mj-brand-primary-hover);\n }\n .export-button:hover:not(:disabled) {\n color: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n }\n .view-loading-state,\n .view-error-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n gap: 16px;\n }\n .view-error-state {\n color: var(--danger-color, #dc3545);\n }\n .view-error-state i {\n font-size: 2rem;\n }\n .view-error-state p {\n margin: 0;\n font-size: 1rem;\n }\n mj-entity-viewer {\n flex: 1;\n min-height: 0;\n }\n `]\n})\nexport class UserViewResource extends BaseResourceComponent {\n @ViewChild('container', { static: true }) containerElement!: ElementRef<HTMLDivElement>;\n @ViewChild('entityViewer') entityViewerRef?: EntityViewerComponent;\n\n public isLoading: boolean = false;\n public errorMessage: string | null = null;\n public entityInfo: EntityInfo | null = null;\n public viewEntity: MJUserViewEntityExtended | null = null;\n public gridState: ViewGridState | null = null;\n\n /** View mode from dashboard configuration (grid/cards/timeline/map) */\n public configuredViewMode: EntityViewMode | null = null;\n\n /** Map render mode from dashboard configuration */\n public configuredMapRenderMode: 'point' | 'choropleth' | 'heatmap' = 'point';\n\n // Export state\n public isExporting: boolean = false;\n\n // Save-as-list dialog state\n public saveAsListDialogVisible = false;\n public saveAsListRecordCount: number | null = null;\n public isSavingAsList = false;\n\n private dataLoaded = false;\n private get metadata() { return this.ProviderToUse; }\n constructor(\n private cdr: ChangeDetectorRef,\n private exportService: ExportService\n ) {\n super();\n }\n\n override set Data(value: ResourceData) {\n const previousRecordId = super.Data?.ResourceRecordID;\n const previousEntity = super.Data?.Configuration?.Entity;\n super.Data = value;\n\n const newRecordId = value?.ResourceRecordID;\n const newEntity = value?.Configuration?.Entity;\n\n // Read view mode and map render mode from configuration if provided\n const viewMode = value?.Configuration?.['viewMode'] as string | undefined;\n if (viewMode && ['grid', 'cards', 'timeline', 'map'].includes(viewMode)) {\n this.configuredViewMode = viewMode as EntityViewMode;\n } else {\n this.configuredViewMode = null;\n }\n\n const mapMode = value?.Configuration?.['mapRenderMode'] as string | undefined;\n if (mapMode && ['point', 'choropleth', 'heatmap'].includes(mapMode)) {\n this.configuredMapRenderMode = mapMode as 'point' | 'choropleth' | 'heatmap';\n } else {\n this.configuredMapRenderMode = 'point';\n }\n\n // Load on first set, or when the view/entity has changed\n if (!this.dataLoaded || newRecordId !== previousRecordId || newEntity !== previousEntity) {\n this.dataLoaded = true;\n // Reset state before loading new view\n this.entityInfo = null;\n this.viewEntity = null;\n this.gridState = null;\n this.errorMessage = null;\n this.loadView();\n }\n }\n\n override get Data(): ResourceData {\n return super.Data;\n }\n\n /**\n * Load the view and entity based on ResourceData\n */\n private async loadView(): Promise<void> {\n const data = this.Data;\n\n if (!data) {\n this.NotifyLoadComplete();\n return;\n }\n\n this.isLoading = true;\n this.errorMessage = null;\n this.NotifyLoadStarted();\n this.cdr.detectChanges();\n\n try {\n // Case 1: Load view by ID\n if (data.ResourceRecordID) {\n await this.loadViewById(data.ResourceRecordID);\n }\n // Case 2: Load dynamic view by entity name\n else if (data.Configuration?.Entity) {\n await this.loadDynamicView(\n data.Configuration.Entity as string,\n data.Configuration.ExtraFilter as string | undefined\n );\n }\n else {\n this.errorMessage = 'No view ID or entity specified';\n }\n } catch (error) {\n console.error('Error loading view:', error);\n this.errorMessage = error instanceof Error ? error.message : 'Failed to load view';\n } finally {\n this.isLoading = false;\n this.cdr.detectChanges();\n\n // If there was an error, notify load complete now\n if (this.errorMessage) {\n this.NotifyLoadComplete();\n }\n // Otherwise, wait for dataLoaded event from entity-viewer\n }\n }\n\n /**\n * Load a saved view by its ID\n */\n private async loadViewById(viewId: string): Promise<void> {\n // Load the view entity\n const view = await ViewInfo.GetViewEntity(viewId);\n\n if (!view) {\n throw new Error(`View with ID ${viewId} not found`);\n }\n\n this.viewEntity = view as MJUserViewEntityExtended;\n\n // Check permissions\n if (!this.viewEntity.UserCanView) {\n throw new Error('You do not have permission to view this view');\n }\n\n // Load the entity info\n const entity = this.metadata.Entities.find(e => UUIDsEqual(e.ID, this.viewEntity!.EntityID));\n\n if (!entity) {\n throw new Error(`Entity for view not found`);\n }\n\n this.entityInfo = entity;\n\n // Parse grid state if available\n if (this.viewEntity.GridState) {\n try {\n this.gridState = JSON.parse(this.viewEntity.GridState) as ViewGridState;\n } catch (e) {\n console.warn('Failed to parse GridState:', e);\n this.gridState = null;\n }\n }\n }\n\n /**\n * Load a dynamic view (no saved view, just entity + filter)\n */\n private async loadDynamicView(entityName: string, _extraFilter?: string): Promise<void> {\n const entity = this.metadata.Entities.find(\n e => e.Name.trim().toLowerCase() === entityName.trim().toLowerCase()\n );\n\n if (!entity) {\n throw new Error(`Entity '${entityName}' not found`);\n }\n\n this.entityInfo = entity;\n this.viewEntity = null;\n this.gridState = null;\n\n // For dynamic views, we could create a synthetic viewEntity with just the WhereClause\n // but for now, we'll rely on the entity-viewer's default behavior\n }\n\n /**\n * Handle record opened event - open in new tab\n */\n public onRecordOpened(event: RecordOpenedEvent): void {\n if (event && event.entity && event.compositeKey) {\n this.navigationService.OpenEntityRecord(event.entity.Name, event.compositeKey);\n }\n }\n\n /**\n * Handle data loaded event from entity-viewer\n */\n public onDataLoaded(): void {\n this.NotifyLoadComplete();\n }\n\n /**\n * Get display name for the resource tab\n */\n override async GetResourceDisplayName(data: ResourceData): Promise<string> {\n if (data.ResourceRecordID) {\n const compositeKey = new CompositeKey([{ FieldName: 'ID', Value: data.ResourceRecordID }]);\n const name = await this.metadata.GetEntityRecordName('MJ: User Views', compositeKey);\n return name ? name : `View: ${data.ResourceRecordID}`;\n }\n else if (data.Configuration?.Entity) {\n const entityName = data.Configuration.Entity as string;\n const hasFilter = data.Configuration.ExtraFilter;\n return `${entityName} [Dynamic${hasFilter ? ' - Filtered' : ' - All'}]`;\n }\n return 'User Views [Error]';\n }\n\n /**\n * Get icon class for the resource tab\n */\n override async GetResourceIconClass(_data: ResourceData): Promise<string> {\n return 'fa-solid fa-table-list';\n }\n\n /**\n * Handle creating a new record for the current entity\n */\n public onCreateNewRecord(): void {\n if (!this.entityInfo) return;\n\n // Use NavigationService to open a new record form\n this.navigationService.OpenNewEntityRecord(this.entityInfo.Name);\n }\n\n /**\n * Handle export to Excel request\n */\n public async onExport(): Promise<void> {\n if (!this.entityInfo) {\n console.error('Cannot export: entity not available');\n return;\n }\n\n this.isExporting = true;\n this.cdr.detectChanges();\n\n try {\n this.showNotification('Working on the export, will notify you when it is complete...', 'info', 2000);\n\n const rows = await this.loadExportRows();\n const columns = this.buildExportColumns();\n const fileName = this.buildExportFileName();\n\n const result = await this.exportService.toExcel(rows, {\n fileName,\n columns,\n includeHeaders: true\n });\n\n if (result.success) {\n this.exportService.downloadResult(result);\n this.showNotification('Excel Export Complete', 'success', 2000);\n } else {\n this.showNotification('Export failed', 'error', 5000);\n }\n }\n catch (e) {\n this.showNotification('Error exporting data', 'error', 5000);\n console.error('Export error:', e);\n }\n finally {\n this.isExporting = false;\n this.cdr.detectChanges();\n }\n }\n\n /**\n * Open the Save-as-List dialog. Only meaningful for saved views (a\n * ViewID is required to materialize). Dynamic views fall back to a\n * user-visible notification rather than silently doing nothing.\n */\n public onSaveAsList(): void {\n if (!this.viewEntity?.ID) {\n this.showNotification('Save as List requires a saved View. Save this view first.', 'info', 4000);\n return;\n }\n // Best-effort record-count hint — the entity-viewer exposes the\n // grid's row count on its gridState; we surface it so the dialog's\n // confirm button can say \"Save List (476 records)\".\n this.saveAsListRecordCount = this.entityViewerRef?.totalRecordCount ?? null;\n this.saveAsListDialogVisible = true;\n this.cdr.detectChanges();\n }\n\n public onSaveAsListCancelled(): void {\n this.saveAsListDialogVisible = false;\n this.cdr.detectChanges();\n }\n\n public async onSaveAsListSubmit(payload: SaveViewAsListResult): Promise<void> {\n const viewId = this.viewEntity?.ID;\n if (!viewId) return;\n this.isSavingAsList = true;\n this.cdr.detectChanges();\n try {\n const provider = this.ProviderToUse as unknown as GraphQLDataProvider;\n const client = new GraphQLListsClient(provider);\n const result = await client.MaterializeFromView(viewId, {\n ListName: payload.ListName,\n Description: payload.Description,\n CategoryId: payload.CategoryId,\n RememberLineage: payload.RememberLineage,\n UseSnapshot: payload.UseSnapshot,\n RefreshMode: payload.RefreshMode,\n });\n if (result.Success && result.CreatedListId) {\n this.saveAsListDialogVisible = false;\n this.showNotification(\n `List created with ${result.Counts?.Added ?? 0} record(s).`,\n 'success',\n 3000,\n );\n this.navigationService.OpenEntityRecord('MJ: Lists', new CompositeKey([{ FieldName: 'ID', Value: result.CreatedListId }]));\n } else {\n this.showNotification(`Save failed: ${result.Message}`, 'error', 5000);\n }\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e);\n this.showNotification(`Save failed: ${message}`, 'error', 5000);\n } finally {\n this.isSavingAsList = false;\n this.cdr.detectChanges();\n }\n }\n\n /**\n * Load all records for the current view/entity for export\n */\n private async loadExportRows(): Promise<Record<string, unknown>[]> {\n if (!this.entityInfo) {\n throw new Error('No entity selected for export');\n }\n\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n let filter = '';\n if (this.viewEntity?.WhereClause) {\n filter = this.viewEntity.WhereClause;\n }\n\n const result = await rv.RunView<Record<string, unknown>>({\n EntityName: this.entityInfo.Name,\n ExtraFilter: filter,\n OrderBy: '',\n ResultType: 'simple'\n });\n\n if (!result.Success) {\n throw new Error(result.ErrorMessage || 'Failed to load data for export');\n }\n\n return result.Results || [];\n }\n\n /**\n * Determine which columns to export based on grid state, view entity, or entity fields\n */\n private buildExportColumns(): ExportColumn[] {\n if (!this.entityInfo) return [];\n\n if (this.gridState?.columnSettings && this.gridState.columnSettings.length > 0) {\n const visibleColumns = this.gridState.columnSettings.filter(col => col.hidden !== true);\n return visibleColumns.map(col => ({\n name: col.Name,\n displayName: col.DisplayName || col.Name\n }));\n }\n\n if (this.viewEntity?.Columns) {\n const visibleColumns = this.viewEntity.Columns.filter(col => !col.hidden);\n return visibleColumns.map(col => ({\n name: col.Name,\n displayName: col.DisplayName || col.Name\n }));\n }\n\n const visibleFields = this.entityInfo.Fields.filter(f => !f.IsVirtual);\n return visibleFields.map(f => ({\n name: f.Name,\n displayName: f.DisplayNameOrName\n }));\n }\n\n /**\n * Build the export file name based on entity and view\n */\n private buildExportFileName(): string {\n const viewName = this.viewEntity?.Name || 'Data';\n return `${this.entityInfo!.Name}_${viewName}_${new Date().toISOString().split('T')[0]}`;\n }\n\n /**\n * Show a notification to the user\n */\n private showNotification(message: string, style: 'info' | 'success' | 'error' | 'warning', duration: number): void {\n MJGlobal.Instance.RaiseEvent({\n component: this,\n event: MJEventType.DisplaySimpleNotificationRequest,\n eventCode: '',\n args: {\n message,\n style,\n DisplayDuration: duration\n }\n });\n }\n}\n","<div #container class=\"view-resource-container\">\n @if (isLoading) {\n <div class=\"view-loading-state\">\n <mj-loading text=\"Loading view...\" size=\"large\"></mj-loading>\n </div>\n } @else if (errorMessage) {\n <div class=\"view-error-state\">\n <i class=\"fas fa-exclamation-triangle\"></i>\n <p>{{ errorMessage }}</p>\n </div>\n } @else if (entityInfo) {\n <!-- Header with title and action buttons -->\n <div class=\"view-header\">\n <div class=\"header-left\">\n <h2 class=\"view-title\">{{ viewEntity?.Name || entityInfo.Name }}</h2>\n @if (viewEntity?.Description) {\n <p class=\"view-description\">{{ viewEntity!.Description }}</p>\n }\n </div>\n <div class=\"header-right\">\n <!-- Create New Record Button -->\n <button\n class=\"action-button create-button\"\n (click)=\"onCreateNewRecord()\"\n title=\"Create new {{ entityInfo.Name }} record\">\n <i class=\"fa-solid fa-plus\"></i>\n <span>New Record</span>\n </button>\n\n <!-- Save as List Button — only meaningful for saved (non-dynamic) views. -->\n @if (viewEntity?.ID) {\n <button\n class=\"action-button save-as-list-button\"\n (click)=\"onSaveAsList()\"\n [disabled]=\"isSavingAsList\"\n title=\"Materialize these results as a static List\">\n @if (isSavingAsList) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n } @else {\n <i class=\"fa-solid fa-floppy-disk\"></i>\n }\n <span>{{ isSavingAsList ? 'Saving...' : 'Save as List' }}</span>\n </button>\n }\n\n <!-- Export Button -->\n <button\n class=\"action-button export-button\"\n (click)=\"onExport()\"\n [disabled]=\"isExporting\"\n title=\"Export to Excel\">\n @if (isExporting) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n } @else {\n <i class=\"fa-solid fa-file-excel\"></i>\n }\n <span>{{ isExporting ? 'Exporting...' : 'Export' }}</span>\n </button>\n </div>\n </div>\n\n <!-- Entity Viewer -->\n <mj-entity-viewer\n #entityViewer\n [entity]=\"entityInfo\"\n [viewEntity]=\"viewEntity\"\n [gridState]=\"gridState\"\n [viewMode]=\"configuredViewMode\"\n [mapRenderMode]=\"configuredMapRenderMode\"\n (recordOpened)=\"onRecordOpened($any($event))\"\n (dataLoaded)=\"onDataLoaded()\">\n </mj-entity-viewer>\n\n <!-- Save-as-List dialog -->\n <mj-save-view-as-list-dialog\n [Provider]=\"ProviderToUse\"\n [Visible]=\"saveAsListDialogVisible\"\n [ViewId]=\"viewEntity?.ID ?? null\"\n [ViewName]=\"viewEntity?.Name ?? null\"\n [RecordCount]=\"saveAsListRecordCount\"\n (Save)=\"onSaveAsListSubmit($event)\"\n (Cancel)=\"onSaveAsListCancelled()\">\n </mj-save-view-as-list-dialog>\n }\n</div>\n"]}
@@ -110,6 +110,17 @@ export declare class ComponentCacheManager {
110
110
  * Call this on user logout or app shutdown.
111
111
  */
112
112
  clearCache(): void;
113
+ /**
114
+ * Selectively clear cached components matching a predicate.
115
+ * Components that match are destroyed; those that don't are kept.
116
+ *
117
+ * Use this for tenant switching: clear org-scoped components while
118
+ * keeping system/global components alive.
119
+ *
120
+ * @param predicate Return true for components that should be destroyed.
121
+ * @returns Number of components destroyed.
122
+ */
123
+ ClearCacheByPredicate(predicate: (info: CachedComponentInfo) => boolean): number;
113
124
  /**
114
125
  * Get cache statistics for debugging.
115
126
  */
@@ -1 +1 @@
1
- {"version":3,"file":"component-cache-manager.d.ts","sourceRoot":"","sources":["../../../../../src/lib/shell/components/tabs/component-cache-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"component-cache-manager.d.ts","sourceRoot":"","sources":["../../../../../src/lib/shell/components/tabs/component-cache-manager.ts"],"names":[],"mappings":"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"}
@@ -189,6 +189,32 @@ export class ComponentCacheManager {
189
189
  });
190
190
  this.cache.clear();
191
191
  }
192
+ /**
193
+ * Selectively clear cached components matching a predicate.
194
+ * Components that match are destroyed; those that don't are kept.
195
+ *
196
+ * Use this for tenant switching: clear org-scoped components while
197
+ * keeping system/global components alive.
198
+ *
199
+ * @param predicate Return true for components that should be destroyed.
200
+ * @returns Number of components destroyed.
201
+ */
202
+ ClearCacheByPredicate(predicate) {
203
+ let destroyed = 0;
204
+ const toRemove = [];
205
+ this.cache.forEach((info, key) => {
206
+ if (predicate(info)) {
207
+ this.appRef.detachView(info.componentRef.hostView);
208
+ info.componentRef.destroy();
209
+ toRemove.push(key);
210
+ destroyed++;
211
+ }
212
+ });
213
+ for (const key of toRemove) {
214
+ this.cache.delete(key);
215
+ }
216
+ return destroyed;
217
+ }
192
218
  /**
193
219
  * Get cache statistics for debugging.
194
220
  */
@@ -1 +1 @@
1
- {"version":3,"file":"component-cache-manager.js","sourceRoot":"","sources":["../../../../../src/lib/shell/components/tabs/component-cache-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import { ComponentRef, ApplicationRef } from '@angular/core';\nimport { BaseResourceComponent } from '@memberjunction/ng-shared';\nimport { ResourceData } from '@memberjunction/core-entities';\n\n/**\n * Metadata about a cached component\n */\nexport interface CachedComponentInfo {\n // The Angular component reference\n componentRef: ComponentRef<BaseResourceComponent>;\n\n // The wrapper DOM element (for detaching/reattaching)\n wrapperElement: HTMLElement;\n\n // Resource identity (the ONLY key used for cache operations)\n resourceType: string;\n resourceRecordId: string;\n applicationId: string;\n\n // Usage tracking\n isAttached: boolean; // Currently attached to a tab/container?\n attachedToTabId: string | null; // Which tab is it attached to? (metadata only, NOT used for lookup)\n\n // Lifecycle tracking\n lastUsed: Date;\n createdAt: Date;\n\n // Resource data snapshot (for comparison)\n resourceData: ResourceData;\n\n // Saved query params from the tab config at detach time.\n // Restored to the tab config when the component is reattached,\n // so the URL reflects the component's preserved state.\n savedQueryParams?: Record<string, string>;\n\n // Agent context reported by this component via NavigationService.SetAgentContext()\n // Cached so it can be restored when the component becomes active again.\n AgentContext?: Record<string, unknown>;\n\n // Agent client tools registered by this component via NavigationService.SetAgentClientTools()\n // Cached so they can be re-registered when the component becomes active again.\n AgentClientTools?: { Name: string; Description: string; ParameterSchema: Record<string, unknown>; Handler: (params: Record<string, unknown>) => Promise<unknown> }[];\n}\n\n/**\n * Smart component cache manager that preserves component state across tab switches.\n *\n * ALL cache operations use a consistent identity key: `appId::resourceType::recordId`.\n * This key is the same regardless of whether the component is in Golden Layout (tabbed)\n * mode or Single Resource mode, ensuring components are reusable across both modes.\n *\n * The `attachedToTabId` field is metadata for debugging/display — it is NEVER used\n * as a lookup key. This prevents bugs where multiple resources sharing the same tab ID\n * (e.g., nav items within a single-resource app) interfere with each other's cache state.\n *\n * Features:\n * - Caches components by resource identity (appId + resourceType + recordId)\n * - Tracks component usage to prevent double-attachment\n * - Detaches/reattaches DOM elements without destroying Angular components\n * - LRU eviction when detached component count exceeds MaxDetachedComponents\n */\nexport class ComponentCacheManager {\n private cache = new Map<string, CachedComponentInfo>();\n\n /**\n * Maximum number of detached (not currently visible) components to keep\n * cached. When exceeded, least-recently-used detached components are\n * evicted. Set to 0 to disable eviction (legacy behavior). Default: 20.\n */\n public static MaxDetachedComponents: number = 20;\n\n constructor(private appRef: ApplicationRef) {}\n\n /**\n * Generate a unique cache key from resource identity.\n * This is the ONE canonical key format used by ALL cache operations.\n */\n private getCacheKey(resourceType: string, recordId: string, appId: string): string {\n const normalizedRecordId = recordId || '__no_record__';\n return `${appId}::${resourceType}::${normalizedRecordId}`;\n }\n\n /**\n * Check if a component exists in cache and is available for reuse.\n */\n hasAvailableComponent(resourceType: string, recordId: string, appId: string): boolean {\n const key = this.getCacheKey(resourceType, recordId, appId);\n const info = this.cache.get(key);\n return info !== undefined && !info.isAttached;\n }\n\n /**\n * Get a cached component if available (not currently attached).\n * Lookup is by resource identity, not tab ID.\n */\n getCachedComponent(resourceType: string, recordId: string, appId: string): CachedComponentInfo | null {\n const key = this.getCacheKey(resourceType, recordId, appId);\n const info = this.cache.get(key);\n\n if (!info) {\n return null;\n }\n\n // Can only reuse if not currently attached elsewhere\n if (info.isAttached) {\n return null;\n }\n\n return info;\n }\n\n /**\n * Store a component in the cache and mark as attached.\n */\n cacheComponent(\n componentRef: ComponentRef<BaseResourceComponent>,\n wrapperElement: HTMLElement,\n resourceData: ResourceData,\n tabId: string\n ): void {\n // Use driverClass (the actual component class name) as the resourceType for the cache key,\n // NOT resourceData.ResourceType (which is often just \"Custom\" for dashboard resources).\n // This must match the lookup key used in getCachedComponent/markAsAttached/markAsDetached.\n const resolvedResourceType = resourceData.Configuration?.resourceTypeDriverClass\n || resourceData.Configuration?.driverClass\n || resourceData.ResourceType;\n const key = this.getCacheKey(\n resolvedResourceType,\n resourceData.ResourceRecordID || '',\n resourceData.Configuration?.applicationId || ''\n );\n\n const info: CachedComponentInfo = {\n componentRef,\n wrapperElement,\n resourceType: resolvedResourceType,\n resourceRecordId: resourceData.ResourceRecordID || '',\n applicationId: resourceData.Configuration?.applicationId || '',\n isAttached: true,\n attachedToTabId: tabId,\n lastUsed: new Date(),\n createdAt: new Date(),\n resourceData\n };\n\n this.cache.set(key, info);\n }\n\n /**\n * Mark a component as attached. Lookup by resource identity.\n */\n markAsAttached(resourceType: string, recordId: string, appId: string, tabId: string): void {\n const key = this.getCacheKey(resourceType, recordId, appId);\n const info = this.cache.get(key);\n\n if (info) {\n info.isAttached = true;\n info.attachedToTabId = tabId;\n info.lastUsed = new Date();\n }\n }\n\n /**\n * Mark a component as detached (available for reuse). Lookup by resource identity.\n *\n * This is the ONLY way to detach a component. Both single-resource mode and\n * Golden Layout mode use this same method to ensure consistent cache behavior.\n */\n markAsDetached(resourceType: string, recordId: string, appId: string): CachedComponentInfo | null {\n const key = this.getCacheKey(resourceType, recordId, appId);\n const info = this.cache.get(key);\n if (!info) return null;\n\n info.isAttached = false;\n info.attachedToTabId = null;\n info.lastUsed = new Date();\n this.EvictIfNeeded();\n return info;\n }\n\n /**\n * Find a cached component by tab ID and detach it.\n * This is a convenience wrapper for callers that only know the tab ID\n * (e.g., Golden Layout tab close events). It resolves the tab ID to\n * resource identity, then delegates to the identity-based markAsDetached.\n */\n findAndDetachByTabId(tabId: string): CachedComponentInfo | null {\n const entry = Array.from(this.cache.entries())\n .find(([_, info]) => info.attachedToTabId === tabId);\n\n if (!entry) return null;\n\n const [_, info] = entry;\n return this.markAsDetached(info.resourceType, info.resourceRecordId, info.applicationId);\n }\n\n /**\n * Evict least-recently-used detached components when over the limit.\n * Only evicts components that are not currently attached.\n */\n private EvictIfNeeded(): void {\n if (ComponentCacheManager.MaxDetachedComponents <= 0) return;\n\n const detached = Array.from(this.cache.entries())\n .filter(([_, info]) => !info.isAttached)\n .sort((a, b) => a[1].lastUsed.getTime() - b[1].lastUsed.getTime());\n\n while (detached.length > ComponentCacheManager.MaxDetachedComponents) {\n const [key, info] = detached.shift()!;\n this.appRef.detachView(info.componentRef.hostView);\n info.componentRef.destroy();\n this.cache.delete(key);\n }\n }\n\n /**\n * Get component info by tab ID (for finding what's attached to a tab).\n * Uses linear scan since tabId is metadata, not a key.\n */\n getComponentByTabId(tabId: string): CachedComponentInfo | null {\n const entry = Array.from(this.cache.entries())\n .find(([_, info]) => info.attachedToTabId === tabId);\n\n return entry ? entry[1] : null;\n }\n\n /**\n * Remove and destroy a specific component from cache by resource identity.\n */\n destroyComponent(resourceType: string, recordId: string, appId: string): void {\n const key = this.getCacheKey(resourceType, recordId, appId);\n const info = this.cache.get(key);\n\n if (!info) return;\n\n this.appRef.detachView(info.componentRef.hostView);\n info.componentRef.destroy();\n this.cache.delete(key);\n }\n\n /**\n * Remove and destroy component by tab ID (convenience for Golden Layout tab close).\n */\n destroyComponentByTabId(tabId: string): void {\n const entry = Array.from(this.cache.entries())\n .find(([_, info]) => info.attachedToTabId === tabId);\n\n if (!entry) return;\n\n const [key, info] = entry;\n this.appRef.detachView(info.componentRef.hostView);\n info.componentRef.destroy();\n this.cache.delete(key);\n }\n\n /**\n * Clear the entire cache, destroying all components.\n * Call this on user logout or app shutdown.\n */\n clearCache(): void {\n this.cache.forEach(info => {\n this.appRef.detachView(info.componentRef.hostView);\n info.componentRef.destroy();\n });\n this.cache.clear();\n }\n\n /**\n * Get cache statistics for debugging.\n */\n getCacheStats(): {\n total: number;\n attached: number;\n detached: number;\n byResourceType: Map<string, number>;\n } {\n const stats = {\n total: this.cache.size,\n attached: 0,\n detached: 0,\n byResourceType: new Map<string, number>()\n };\n\n this.cache.forEach(info => {\n if (info.isAttached) {\n stats.attached++;\n } else {\n stats.detached++;\n }\n\n const count = stats.byResourceType.get(info.resourceType) || 0;\n stats.byResourceType.set(info.resourceType, count + 1);\n });\n\n return stats;\n }\n}\n"]}
1
+ {"version":3,"file":"component-cache-manager.js","sourceRoot":"","sources":["../../../../../src/lib/shell/components/tabs/component-cache-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import { ComponentRef, ApplicationRef } from '@angular/core';\nimport { BaseResourceComponent } from '@memberjunction/ng-shared';\nimport { ResourceData } from '@memberjunction/core-entities';\n\n/**\n * Metadata about a cached component\n */\nexport interface CachedComponentInfo {\n // The Angular component reference\n componentRef: ComponentRef<BaseResourceComponent>;\n\n // The wrapper DOM element (for detaching/reattaching)\n wrapperElement: HTMLElement;\n\n // Resource identity (the ONLY key used for cache operations)\n resourceType: string;\n resourceRecordId: string;\n applicationId: string;\n\n // Usage tracking\n isAttached: boolean; // Currently attached to a tab/container?\n attachedToTabId: string | null; // Which tab is it attached to? (metadata only, NOT used for lookup)\n\n // Lifecycle tracking\n lastUsed: Date;\n createdAt: Date;\n\n // Resource data snapshot (for comparison)\n resourceData: ResourceData;\n\n // Saved query params from the tab config at detach time.\n // Restored to the tab config when the component is reattached,\n // so the URL reflects the component's preserved state.\n savedQueryParams?: Record<string, string>;\n\n // Agent context reported by this component via NavigationService.SetAgentContext()\n // Cached so it can be restored when the component becomes active again.\n AgentContext?: Record<string, unknown>;\n\n // Agent client tools registered by this component via NavigationService.SetAgentClientTools()\n // Cached so they can be re-registered when the component becomes active again.\n AgentClientTools?: { Name: string; Description: string; ParameterSchema: Record<string, unknown>; Handler: (params: Record<string, unknown>) => Promise<unknown> }[];\n}\n\n/**\n * Smart component cache manager that preserves component state across tab switches.\n *\n * ALL cache operations use a consistent identity key: `appId::resourceType::recordId`.\n * This key is the same regardless of whether the component is in Golden Layout (tabbed)\n * mode or Single Resource mode, ensuring components are reusable across both modes.\n *\n * The `attachedToTabId` field is metadata for debugging/display — it is NEVER used\n * as a lookup key. This prevents bugs where multiple resources sharing the same tab ID\n * (e.g., nav items within a single-resource app) interfere with each other's cache state.\n *\n * Features:\n * - Caches components by resource identity (appId + resourceType + recordId)\n * - Tracks component usage to prevent double-attachment\n * - Detaches/reattaches DOM elements without destroying Angular components\n * - LRU eviction when detached component count exceeds MaxDetachedComponents\n */\nexport class ComponentCacheManager {\n private cache = new Map<string, CachedComponentInfo>();\n\n /**\n * Maximum number of detached (not currently visible) components to keep\n * cached. When exceeded, least-recently-used detached components are\n * evicted. Set to 0 to disable eviction (legacy behavior). Default: 20.\n */\n public static MaxDetachedComponents: number = 20;\n\n constructor(private appRef: ApplicationRef) {}\n\n /**\n * Generate a unique cache key from resource identity.\n * This is the ONE canonical key format used by ALL cache operations.\n */\n private getCacheKey(resourceType: string, recordId: string, appId: string): string {\n const normalizedRecordId = recordId || '__no_record__';\n return `${appId}::${resourceType}::${normalizedRecordId}`;\n }\n\n /**\n * Check if a component exists in cache and is available for reuse.\n */\n hasAvailableComponent(resourceType: string, recordId: string, appId: string): boolean {\n const key = this.getCacheKey(resourceType, recordId, appId);\n const info = this.cache.get(key);\n return info !== undefined && !info.isAttached;\n }\n\n /**\n * Get a cached component if available (not currently attached).\n * Lookup is by resource identity, not tab ID.\n */\n getCachedComponent(resourceType: string, recordId: string, appId: string): CachedComponentInfo | null {\n const key = this.getCacheKey(resourceType, recordId, appId);\n const info = this.cache.get(key);\n\n if (!info) {\n return null;\n }\n\n // Can only reuse if not currently attached elsewhere\n if (info.isAttached) {\n return null;\n }\n\n return info;\n }\n\n /**\n * Store a component in the cache and mark as attached.\n */\n cacheComponent(\n componentRef: ComponentRef<BaseResourceComponent>,\n wrapperElement: HTMLElement,\n resourceData: ResourceData,\n tabId: string\n ): void {\n // Use driverClass (the actual component class name) as the resourceType for the cache key,\n // NOT resourceData.ResourceType (which is often just \"Custom\" for dashboard resources).\n // This must match the lookup key used in getCachedComponent/markAsAttached/markAsDetached.\n const resolvedResourceType = resourceData.Configuration?.resourceTypeDriverClass\n || resourceData.Configuration?.driverClass\n || resourceData.ResourceType;\n const key = this.getCacheKey(\n resolvedResourceType,\n resourceData.ResourceRecordID || '',\n resourceData.Configuration?.applicationId || ''\n );\n\n const info: CachedComponentInfo = {\n componentRef,\n wrapperElement,\n resourceType: resolvedResourceType,\n resourceRecordId: resourceData.ResourceRecordID || '',\n applicationId: resourceData.Configuration?.applicationId || '',\n isAttached: true,\n attachedToTabId: tabId,\n lastUsed: new Date(),\n createdAt: new Date(),\n resourceData\n };\n\n this.cache.set(key, info);\n }\n\n /**\n * Mark a component as attached. Lookup by resource identity.\n */\n markAsAttached(resourceType: string, recordId: string, appId: string, tabId: string): void {\n const key = this.getCacheKey(resourceType, recordId, appId);\n const info = this.cache.get(key);\n\n if (info) {\n info.isAttached = true;\n info.attachedToTabId = tabId;\n info.lastUsed = new Date();\n }\n }\n\n /**\n * Mark a component as detached (available for reuse). Lookup by resource identity.\n *\n * This is the ONLY way to detach a component. Both single-resource mode and\n * Golden Layout mode use this same method to ensure consistent cache behavior.\n */\n markAsDetached(resourceType: string, recordId: string, appId: string): CachedComponentInfo | null {\n const key = this.getCacheKey(resourceType, recordId, appId);\n const info = this.cache.get(key);\n if (!info) return null;\n\n info.isAttached = false;\n info.attachedToTabId = null;\n info.lastUsed = new Date();\n this.EvictIfNeeded();\n return info;\n }\n\n /**\n * Find a cached component by tab ID and detach it.\n * This is a convenience wrapper for callers that only know the tab ID\n * (e.g., Golden Layout tab close events). It resolves the tab ID to\n * resource identity, then delegates to the identity-based markAsDetached.\n */\n findAndDetachByTabId(tabId: string): CachedComponentInfo | null {\n const entry = Array.from(this.cache.entries())\n .find(([_, info]) => info.attachedToTabId === tabId);\n\n if (!entry) return null;\n\n const [_, info] = entry;\n return this.markAsDetached(info.resourceType, info.resourceRecordId, info.applicationId);\n }\n\n /**\n * Evict least-recently-used detached components when over the limit.\n * Only evicts components that are not currently attached.\n */\n private EvictIfNeeded(): void {\n if (ComponentCacheManager.MaxDetachedComponents <= 0) return;\n\n const detached = Array.from(this.cache.entries())\n .filter(([_, info]) => !info.isAttached)\n .sort((a, b) => a[1].lastUsed.getTime() - b[1].lastUsed.getTime());\n\n while (detached.length > ComponentCacheManager.MaxDetachedComponents) {\n const [key, info] = detached.shift()!;\n this.appRef.detachView(info.componentRef.hostView);\n info.componentRef.destroy();\n this.cache.delete(key);\n }\n }\n\n /**\n * Get component info by tab ID (for finding what's attached to a tab).\n * Uses linear scan since tabId is metadata, not a key.\n */\n getComponentByTabId(tabId: string): CachedComponentInfo | null {\n const entry = Array.from(this.cache.entries())\n .find(([_, info]) => info.attachedToTabId === tabId);\n\n return entry ? entry[1] : null;\n }\n\n /**\n * Remove and destroy a specific component from cache by resource identity.\n */\n destroyComponent(resourceType: string, recordId: string, appId: string): void {\n const key = this.getCacheKey(resourceType, recordId, appId);\n const info = this.cache.get(key);\n\n if (!info) return;\n\n this.appRef.detachView(info.componentRef.hostView);\n info.componentRef.destroy();\n this.cache.delete(key);\n }\n\n /**\n * Remove and destroy component by tab ID (convenience for Golden Layout tab close).\n */\n destroyComponentByTabId(tabId: string): void {\n const entry = Array.from(this.cache.entries())\n .find(([_, info]) => info.attachedToTabId === tabId);\n\n if (!entry) return;\n\n const [key, info] = entry;\n this.appRef.detachView(info.componentRef.hostView);\n info.componentRef.destroy();\n this.cache.delete(key);\n }\n\n /**\n * Clear the entire cache, destroying all components.\n * Call this on user logout or app shutdown.\n */\n clearCache(): void {\n this.cache.forEach(info => {\n this.appRef.detachView(info.componentRef.hostView);\n info.componentRef.destroy();\n });\n this.cache.clear();\n }\n\n /**\n * Selectively clear cached components matching a predicate.\n * Components that match are destroyed; those that don't are kept.\n *\n * Use this for tenant switching: clear org-scoped components while\n * keeping system/global components alive.\n *\n * @param predicate Return true for components that should be destroyed.\n * @returns Number of components destroyed.\n */\n ClearCacheByPredicate(predicate: (info: CachedComponentInfo) => boolean): number {\n let destroyed = 0;\n const toRemove: string[] = [];\n\n this.cache.forEach((info, key) => {\n if (predicate(info)) {\n this.appRef.detachView(info.componentRef.hostView);\n info.componentRef.destroy();\n toRemove.push(key);\n destroyed++;\n }\n });\n\n for (const key of toRemove) {\n this.cache.delete(key);\n }\n\n return destroyed;\n }\n\n /**\n * Get cache statistics for debugging.\n */\n getCacheStats(): {\n total: number;\n attached: number;\n detached: number;\n byResourceType: Map<string, number>;\n } {\n const stats = {\n total: this.cache.size,\n attached: 0,\n detached: 0,\n byResourceType: new Map<string, number>()\n };\n\n this.cache.forEach(info => {\n if (info.isAttached) {\n stats.attached++;\n } else {\n stats.detached++;\n }\n\n const count = stats.byResourceType.get(info.resourceType) || 0;\n stats.byResourceType.set(info.resourceType, count + 1);\n });\n\n return stats;\n }\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { OnInit, OnDestroy, AfterViewInit, ElementRef, ApplicationRef, EnvironmentInjector, ChangeDetectorRef, EventEmitter } from '@angular/core';
2
2
  import { GoldenLayoutManager, WorkspaceStateManager, ApplicationManager } from '@memberjunction/ng-base-application';
3
+ import { CachedComponentInfo } from './component-cache-manager';
3
4
  import { BaseAngularComponent } from '@memberjunction/ng-base-types';
4
5
  import * as i0 from "@angular/core";
5
6
  /**
@@ -60,6 +61,31 @@ export declare class TabContainerComponent extends BaseAngularComponent implemen
60
61
  * @param forceCreateTabs - If true, always creates tabs fresh from config.tabs instead of restoring saved layout
61
62
  */
62
63
  private initializeGoldenLayout;
64
+ /**
65
+ * Clear cached components matching a predicate. Components that match are
66
+ * destroyed; others are kept. Use for tenant switching — clear org-scoped
67
+ * components while keeping system/global components alive.
68
+ *
69
+ * @param predicate Return true for components that should be destroyed.
70
+ * If omitted, clears ALL cached components.
71
+ * @returns Number of components destroyed.
72
+ */
73
+ ClearComponentCache(predicate?: (info: CachedComponentInfo) => boolean): number;
74
+ /**
75
+ * Destroy all cached components and reload open tabs.
76
+ *
77
+ * In single-resource mode: clears the signature to force a reload, then
78
+ * re-invokes loadSingleResourceContent().
79
+ *
80
+ * In multi-tab mode: destroys all cached components, marks all tabs as
81
+ * not-loaded, then reloads the currently active tab immediately. Other
82
+ * tabs reload lazily when the user switches to them (onTabShown fires
83
+ * with isFirstShow=true after MarkTabNotLoaded).
84
+ *
85
+ * Use this for tenant switching — tabs stay open but their components
86
+ * are recreated fresh with the new org context.
87
+ */
88
+ ReloadAllTabs(): Promise<void>;
63
89
  ngOnDestroy(): void;
64
90
  /**
65
91
  * Handle window resize events as a fallback safety mechanism.