@memberjunction/ng-explorer-core 5.35.0 → 5.37.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.
- package/dist/generated/lazy-feature-config.d.ts +1 -1
- package/dist/generated/lazy-feature-config.d.ts.map +1 -1
- package/dist/generated/lazy-feature-config.js +5 -3
- package/dist/generated/lazy-feature-config.js.map +1 -1
- package/dist/lib/conversation-feedback/conversation-feedback.d.ts +108 -0
- package/dist/lib/conversation-feedback/conversation-feedback.d.ts.map +1 -0
- package/dist/lib/conversation-feedback/conversation-feedback.js +809 -0
- package/dist/lib/conversation-feedback/conversation-feedback.js.map +1 -0
- package/dist/lib/conversation-feedback/index.d.ts +2 -0
- package/dist/lib/conversation-feedback/index.d.ts.map +1 -0
- package/dist/lib/conversation-feedback/index.js +2 -0
- package/dist/lib/conversation-feedback/index.js.map +1 -0
- package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.js +23 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.js.map +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.d.ts +12 -0
- package/dist/lib/resource-wrappers/view-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/view-resource.component.js +107 -15
- package/dist/lib/resource-wrappers/view-resource.component.js.map +1 -1
- package/dist/lib/single-list-detail/single-list-detail.component.d.ts +153 -3
- package/dist/lib/single-list-detail/single-list-detail.component.d.ts.map +1 -1
- package/dist/lib/single-list-detail/single-list-detail.component.js +1479 -271
- package/dist/lib/single-list-detail/single-list-detail.component.js.map +1 -1
- package/dist/module.d.ts +32 -29
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +19 -6
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +1 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +1 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +45 -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
|
|
44
|
-
i0.ɵɵelement(0, "i",
|
|
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.ɵɵ
|
|
63
|
-
i0.ɵɵ
|
|
64
|
-
i0.ɵɵ
|
|
65
|
-
i0.ɵɵ
|
|
66
|
-
i0.ɵɵ
|
|
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(
|
|
69
|
-
i0.ɵɵlistener("recordOpened", function
|
|
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 ?
|
|
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,
|
|
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:
|
|
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"]}
|
|
@@ -3,6 +3,9 @@ import { MJListEntity, MJUserViewEntityExtended } from '@memberjunction/core-ent
|
|
|
3
3
|
import { SharedService } from '@memberjunction/ng-shared';
|
|
4
4
|
import { ListDetailGridComponent, ListGridRowClickedEvent } from '@memberjunction/ng-list-detail-grid';
|
|
5
5
|
import { GridToolbarConfig } from '@memberjunction/ng-entity-viewer';
|
|
6
|
+
import { type ListCapabilities, type ListDelta, type ListRefreshMode, type SharePermissionLevel } from '@memberjunction/lists-base';
|
|
7
|
+
import { ListSharingService } from '@memberjunction/ng-list-management';
|
|
8
|
+
import { ExportService } from '@memberjunction/ng-export-service';
|
|
6
9
|
import { NewItemOption } from '../../generic/Item.types';
|
|
7
10
|
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
8
11
|
import * as i0 from "@angular/core";
|
|
@@ -19,10 +22,50 @@ export declare class SingleListDetailComponent extends BaseAngularComponent impl
|
|
|
19
22
|
private sharedService;
|
|
20
23
|
private cdr;
|
|
21
24
|
private elementRef;
|
|
25
|
+
private exportService;
|
|
26
|
+
private listSharingService;
|
|
22
27
|
ListID: string;
|
|
28
|
+
/**
|
|
29
|
+
* Bumped on every list-membership mutation so the Usage stats sidebar
|
|
30
|
+
* re-queries member count / growth / last-activity. Without this nudge,
|
|
31
|
+
* those numbers drift from reality until a full page reload because the
|
|
32
|
+
* stats component otherwise only loads on init.
|
|
33
|
+
*/
|
|
34
|
+
statsRefreshTrigger: number;
|
|
35
|
+
private bumpStatsRefresh;
|
|
23
36
|
listDetailGrid: ListDetailGridComponent | undefined;
|
|
24
37
|
listRecord: MJListEntity | null;
|
|
25
38
|
showLoader: boolean;
|
|
39
|
+
capabilities: ListCapabilities;
|
|
40
|
+
currentLevel: SharePermissionLevel | null;
|
|
41
|
+
sourceViewName: string | null;
|
|
42
|
+
bulkStatus: 'Active' | 'Complete' | 'Disabled' | 'Pending' | 'Rejected' | '';
|
|
43
|
+
isApplyingBulkStatus: boolean;
|
|
44
|
+
showMoveCopyDialog: boolean;
|
|
45
|
+
moveCopyMode: 'move' | 'copy';
|
|
46
|
+
moveCopyTargetSearch: string;
|
|
47
|
+
moveCopyTargetCandidates: MJListEntity[];
|
|
48
|
+
moveCopyTargetCandidatesLoading: boolean;
|
|
49
|
+
moveCopySelectedTarget: MJListEntity | null;
|
|
50
|
+
isApplyingMoveCopy: boolean;
|
|
51
|
+
moveCopyProgress: number;
|
|
52
|
+
moveCopyTotal: number;
|
|
53
|
+
moveDeltaConfirmVisible: boolean;
|
|
54
|
+
moveDelta: ListDelta | null;
|
|
55
|
+
showExportDialog: boolean;
|
|
56
|
+
exportFormat: 'excel' | 'csv' | 'json';
|
|
57
|
+
exportFields: Array<{
|
|
58
|
+
Name: string;
|
|
59
|
+
DisplayName: string;
|
|
60
|
+
Selected: boolean;
|
|
61
|
+
}>;
|
|
62
|
+
exportRecordCount: number;
|
|
63
|
+
isExporting: boolean;
|
|
64
|
+
refreshMode: ListRefreshMode;
|
|
65
|
+
isPreviewingRefresh: boolean;
|
|
66
|
+
isApplyingRefresh: boolean;
|
|
67
|
+
refreshDelta: ListDelta | null;
|
|
68
|
+
refreshConfirmVisible: boolean;
|
|
26
69
|
selectedKeys: string[];
|
|
27
70
|
rowCount: number;
|
|
28
71
|
gridToolbarConfig: GridToolbarConfig;
|
|
@@ -45,16 +88,53 @@ export declare class SingleListDetailComponent extends BaseAngularComponent impl
|
|
|
45
88
|
userViewsToAdd: MJUserViewEntityExtended[];
|
|
46
89
|
addFromViewProgress: number;
|
|
47
90
|
addFromViewTotal: number;
|
|
91
|
+
/**
|
|
92
|
+
* Stored percent (0–100) for the progress bar. Backed instead of computed
|
|
93
|
+
* via getter because the original getter recomputed on every Angular CD
|
|
94
|
+
* read and the async save loop mutated addFromViewProgress between the
|
|
95
|
+
* initial check and dev-mode re-check — producing NG0100
|
|
96
|
+
* ExpressionChangedAfterItHasBeenCheckedError floods in the console.
|
|
97
|
+
* Updated via setAddFromViewProgress(...) at controlled points.
|
|
98
|
+
*/
|
|
99
|
+
addFromViewProgressPercent: number;
|
|
48
100
|
fetchingRecordsToSave: boolean;
|
|
49
101
|
showAddDropdown: boolean;
|
|
50
102
|
addOptions: NewItemOption[];
|
|
51
103
|
onDocumentClick(event: MouseEvent): void;
|
|
52
|
-
constructor(sharedService: SharedService, cdr: ChangeDetectorRef, elementRef: ElementRef);
|
|
53
|
-
ngOnInit():
|
|
104
|
+
constructor(sharedService: SharedService, cdr: ChangeDetectorRef, elementRef: ElementRef, exportService: ExportService, listSharingService: ListSharingService);
|
|
105
|
+
ngOnInit(): void;
|
|
54
106
|
/**
|
|
55
107
|
* Load the list entity record
|
|
56
108
|
*/
|
|
57
109
|
private loadListRecord;
|
|
110
|
+
/**
|
|
111
|
+
* Load lineage context for the refresh-from-source UI:
|
|
112
|
+
* - Resolve the source view's display name for the lineage badge.
|
|
113
|
+
* - Initialize the refresh-mode dropdown from per-user last-used
|
|
114
|
+
* preference, falling back to the list's RefreshMode field.
|
|
115
|
+
* Silent on failure — the badge / refresh button just won't render
|
|
116
|
+
* rather than blocking the rest of the detail view.
|
|
117
|
+
*/
|
|
118
|
+
private loadLineageContext;
|
|
119
|
+
/**
|
|
120
|
+
* Resolve the caller's permission level for this list (Owner / Edit /
|
|
121
|
+
* View / null) and derive UI capability flags. Best-effort — if the
|
|
122
|
+
* resolve fails we conservatively default to no-mutation (Viewer-like)
|
|
123
|
+
* so we don't accidentally surface buttons the server will reject.
|
|
124
|
+
*/
|
|
125
|
+
private loadCapabilities;
|
|
126
|
+
get hasLineage(): boolean;
|
|
127
|
+
private loadLastUsedMode;
|
|
128
|
+
private saveLastUsedMode;
|
|
129
|
+
/**
|
|
130
|
+
* Kick off a refresh-from-source preview. Builds the delta server-side
|
|
131
|
+
* and opens the confirm dialog when it returns. The dialog enforces the
|
|
132
|
+
* acknowledgement UX; the server enforces the actual drop guard.
|
|
133
|
+
*/
|
|
134
|
+
onRefreshFromSource(): Promise<void>;
|
|
135
|
+
onRefreshConfirmCancel(): void;
|
|
136
|
+
onRefreshConfirmCommit(deltaToken: string): Promise<void>;
|
|
137
|
+
onRefreshModeChange(mode: ListRefreshMode): void;
|
|
58
138
|
onRowClicked(_event: ListGridRowClickedEvent): void;
|
|
59
139
|
onRowDoubleClicked(_event: ListGridRowClickedEvent): void;
|
|
60
140
|
onSelectionChange(keys: string[]): void;
|
|
@@ -64,11 +144,81 @@ export declare class SingleListDetailComponent extends BaseAngularComponent impl
|
|
|
64
144
|
refreshGrid(): void;
|
|
65
145
|
get removeProgressPercent(): number;
|
|
66
146
|
get addProgressPercent(): number;
|
|
67
|
-
|
|
147
|
+
/** Update progress + recompute the stored percent atomically. Call this
|
|
148
|
+
* from inside the save loop so the bound value is set in lockstep with
|
|
149
|
+
* the underlying counter (avoids NG0100). */
|
|
150
|
+
private setAddFromViewProgress;
|
|
68
151
|
onRefreshClick(): void;
|
|
69
152
|
onExportClick(): void;
|
|
153
|
+
/**
|
|
154
|
+
* Open the format + column picker (mockup 26). Resolves the candidate
|
|
155
|
+
* field list from EntityInfo on the loaded provider — no extra
|
|
156
|
+
* RunView. Default selection is every non-virtual entity field, which
|
|
157
|
+
* matches what the underlying grid's "export all" path produced.
|
|
158
|
+
*/
|
|
159
|
+
openExportDialog(): void;
|
|
160
|
+
closeExportDialog(): void;
|
|
161
|
+
selectAllExportFields(): void;
|
|
162
|
+
selectNoneExportFields(): void;
|
|
163
|
+
get selectedExportFieldCount(): number;
|
|
164
|
+
/**
|
|
165
|
+
* Run the export with the user's chosen format + columns. Resolves
|
|
166
|
+
* the list's member RecordIDs from the in-memory grid when possible
|
|
167
|
+
* (avoids an extra RunView), then bulk-loads the underlying entity
|
|
168
|
+
* rows restricted to the chosen Fields. Output is projected to
|
|
169
|
+
* exactly the user's selected columns + ordering.
|
|
170
|
+
*/
|
|
171
|
+
executeExport(): Promise<void>;
|
|
70
172
|
toggleAddDropdown(): void;
|
|
71
173
|
onDropdownItemClick(item: NewItemOption): void;
|
|
174
|
+
/**
|
|
175
|
+
* Apply the chosen status to all selected list-detail rows. Re-uses
|
|
176
|
+
* the existing extract-record-id-from-composite-key logic to map
|
|
177
|
+
* `selectedKeys` to the actual `MJ: List Detail.RecordID` values.
|
|
178
|
+
*/
|
|
179
|
+
applyBulkStatus(): Promise<void>;
|
|
180
|
+
openMoveDialog(): void;
|
|
181
|
+
openCopyDialog(): void;
|
|
182
|
+
private openMoveCopyDialog;
|
|
183
|
+
closeMoveCopyDialog(): void;
|
|
184
|
+
selectMoveCopyTarget(target: MJListEntity): void;
|
|
185
|
+
get filteredMoveCopyTargets(): MJListEntity[];
|
|
186
|
+
get moveCopyProgressPercent(): number;
|
|
187
|
+
/** Load candidate target lists — same Entity, owned by or shared with
|
|
188
|
+
* the current user, excluding the list we're standing on. One RunView
|
|
189
|
+
* on open; results stay in memory until the dialog closes. */
|
|
190
|
+
private loadMoveCopyTargets;
|
|
191
|
+
/**
|
|
192
|
+
* "Continue" / "Copy" click in the target picker. For Copy we apply
|
|
193
|
+
* immediately. For Move we build a local Delta and show the confirm
|
|
194
|
+
* dialog — the actual mutation fires from onMoveConfirmCommit().
|
|
195
|
+
*/
|
|
196
|
+
confirmMoveCopy(): Promise<void>;
|
|
197
|
+
onMoveConfirmCancel(): void;
|
|
198
|
+
onMoveConfirmCommit(_deltaToken: string): Promise<void>;
|
|
199
|
+
/**
|
|
200
|
+
* Pull RecordIDs out of selectedKeys. selectedKeys arrive from the
|
|
201
|
+
* grid in concatenated-key format ("ID|<value>"). For single-PK entities
|
|
202
|
+
* MJ: List Details stores the raw value; for composite-PK entities it
|
|
203
|
+
* stores the full concatenated string. We normalize based on entity
|
|
204
|
+
* metadata — same logic as confirmRemoveFromList.
|
|
205
|
+
*/
|
|
206
|
+
private extractSelectedRecordIds;
|
|
207
|
+
/**
|
|
208
|
+
* Build a synthetic ListDelta for the Move-confirm dialog. Token is
|
|
209
|
+
* a sentinel — we never call server-side ApplyDelta for this flow,
|
|
210
|
+
* the mutations run client-side inside a transaction group. The
|
|
211
|
+
* delta-confirm component only reads counts + ToAdd/ToRemove + Warnings;
|
|
212
|
+
* the token round-trips through the (Confirm) emit but is unused.
|
|
213
|
+
*/
|
|
214
|
+
private buildMoveDelta;
|
|
215
|
+
/**
|
|
216
|
+
* Execute the move/copy. Insert all records into the target, and for
|
|
217
|
+
* Move also delete the matching MJ: List Details from the source. All
|
|
218
|
+
* mutations run in a single TransactionGroup so partial failures don't
|
|
219
|
+
* leave records duplicated in both lists.
|
|
220
|
+
*/
|
|
221
|
+
private applyMoveCopy;
|
|
72
222
|
openRemoveDialog(): void;
|
|
73
223
|
closeRemoveDialog(): void;
|
|
74
224
|
confirmRemoveFromList(): Promise<void>;
|