@memberjunction/ng-artifacts 5.1.0 → 5.2.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/lib/artifacts.module.d.ts +13 -8
- package/dist/lib/artifacts.module.d.ts.map +1 -1
- package/dist/lib/artifacts.module.js +27 -6
- package/dist/lib/artifacts.module.js.map +1 -1
- package/dist/lib/components/artifact-type-plugin-viewer.component.d.ts +4 -2
- package/dist/lib/components/artifact-type-plugin-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/artifact-type-plugin-viewer.component.js +19 -1
- package/dist/lib/components/artifact-type-plugin-viewer.component.js.map +1 -1
- package/dist/lib/components/artifact-viewer-panel.component.d.ts +14 -2
- package/dist/lib/components/artifact-viewer-panel.component.d.ts.map +1 -1
- package/dist/lib/components/artifact-viewer-panel.component.js +33 -3
- package/dist/lib/components/artifact-viewer-panel.component.js.map +1 -1
- package/dist/lib/components/base-artifact-viewer.component.d.ts +34 -1
- package/dist/lib/components/base-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/base-artifact-viewer.component.js +16 -1
- package/dist/lib/components/base-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/component-artifact-viewer.component.d.ts +6 -0
- package/dist/lib/components/plugins/component-artifact-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/plugins/component-artifact-viewer.component.js +14 -3
- package/dist/lib/components/plugins/component-artifact-viewer.component.js.map +1 -1
- package/dist/lib/components/plugins/data-artifact-viewer.component.d.ts +131 -0
- package/dist/lib/components/plugins/data-artifact-viewer.component.d.ts.map +1 -0
- package/dist/lib/components/plugins/data-artifact-viewer.component.js +590 -0
- package/dist/lib/components/plugins/data-artifact-viewer.component.js.map +1 -0
- package/dist/lib/components/plugins/save-query-dialog.component.d.ts +57 -0
- package/dist/lib/components/plugins/save-query-dialog.component.d.ts.map +1 -0
- package/dist/lib/components/plugins/save-query-dialog.component.js +561 -0
- package/dist/lib/components/plugins/save-query-dialog.component.js.map +1 -0
- 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 +18 -12
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { EventEmitter, ChangeDetectorRef } from '@angular/core';
|
|
2
|
+
import { CompositeKey } from '@memberjunction/core';
|
|
3
|
+
import { TreeBranchConfig, TreeNode } from '@memberjunction/ng-trees';
|
|
4
|
+
import { TreeDropdownComponent } from '@memberjunction/ng-trees';
|
|
5
|
+
import * as i0 from "@angular/core";
|
|
6
|
+
/**
|
|
7
|
+
* Result emitted when a query is successfully saved
|
|
8
|
+
*/
|
|
9
|
+
export interface SaveQueryResult {
|
|
10
|
+
queryId: string;
|
|
11
|
+
queryName: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Slide-in panel for saving an ad-hoc SQL query as a reusable MJ Query record.
|
|
15
|
+
* Pre-populates name from the artifact title and provides a tree dropdown
|
|
16
|
+
* for selecting a query category, with inline category creation.
|
|
17
|
+
*
|
|
18
|
+
* Follows the CreateAgentSlideInComponent pattern for slide-in UX.
|
|
19
|
+
*/
|
|
20
|
+
export declare class SaveQueryPanelComponent {
|
|
21
|
+
private cdr;
|
|
22
|
+
QueryName: string;
|
|
23
|
+
QueryDescription: string;
|
|
24
|
+
SQL: string;
|
|
25
|
+
Saved: EventEmitter<SaveQueryResult>;
|
|
26
|
+
Cancelled: EventEmitter<void>;
|
|
27
|
+
categoryTree: TreeDropdownComponent;
|
|
28
|
+
Name: string;
|
|
29
|
+
Description: string;
|
|
30
|
+
IsSaving: boolean;
|
|
31
|
+
ErrorMessage: string;
|
|
32
|
+
IsVisible: boolean;
|
|
33
|
+
private selectedCategoryId;
|
|
34
|
+
SelectedCategoryKey: CompositeKey | null;
|
|
35
|
+
IsCreatingCategory: boolean;
|
|
36
|
+
IsCreatingCategorySaving: boolean;
|
|
37
|
+
NewCategoryName: string;
|
|
38
|
+
CreateCategoryError: string;
|
|
39
|
+
private newCategoryParentId;
|
|
40
|
+
/** Tree config for query category hierarchy */
|
|
41
|
+
CategoryBranchConfig: TreeBranchConfig;
|
|
42
|
+
constructor(cdr: ChangeDetectorRef);
|
|
43
|
+
get CanSave(): boolean;
|
|
44
|
+
ngOnInit(): void;
|
|
45
|
+
OnEscapeKey(): void;
|
|
46
|
+
OnCategoryChanged(selection: TreeNode | TreeNode[] | null): void;
|
|
47
|
+
StartCreateCategory(): void;
|
|
48
|
+
CancelCreateCategory(): void;
|
|
49
|
+
OnNewCategoryParentChanged(selection: TreeNode | TreeNode[] | null): void;
|
|
50
|
+
CreateCategory(): Promise<void>;
|
|
51
|
+
OnSave(): Promise<void>;
|
|
52
|
+
OnCancel(): void;
|
|
53
|
+
private getEntityErrorMessage;
|
|
54
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SaveQueryPanelComponent, never>;
|
|
55
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SaveQueryPanelComponent, "mj-save-query-panel", never, { "QueryName": { "alias": "QueryName"; "required": false; }; "QueryDescription": { "alias": "QueryDescription"; "required": false; }; "SQL": { "alias": "SQL"; "required": false; }; }, { "Saved": "Saved"; "Cancelled": "Cancelled"; }, never, never, false, never>;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=save-query-dialog.component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"save-query-dialog.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/plugins/save-query-dialog.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAsC,iBAAiB,EAAgB,MAAM,eAAe,CAAC;AAC5I,OAAO,EAAY,YAAY,EAA4B,MAAM,sBAAsB,CAAC;AAGxF,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;;AAEjE;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,qBAwca,uBAAuB;IAsCtB,OAAO,CAAC,GAAG;IArCd,SAAS,SAAM;IACf,gBAAgB,SAAM;IACtB,GAAG,SAAM;IAER,KAAK,gCAAuC;IAC5C,SAAS,qBAA4B;IAEpB,YAAY,EAAG,qBAAqB,CAAC;IAGzD,IAAI,SAAM;IACV,WAAW,SAAM;IACjB,QAAQ,UAAS;IACjB,YAAY,SAAM;IAClB,SAAS,UAAS;IAGzB,OAAO,CAAC,kBAAkB,CAAuB;IAC1C,mBAAmB,EAAE,YAAY,GAAG,IAAI,CAAQ;IAGhD,kBAAkB,UAAS;IAC3B,wBAAwB,UAAS;IACjC,eAAe,SAAM;IACrB,mBAAmB,SAAM;IAChC,OAAO,CAAC,mBAAmB,CAAuB;IAElD,+CAA+C;IACxC,oBAAoB,EAAE,gBAAgB,CAO3C;gBAEkB,GAAG,EAAE,iBAAiB;IAE1C,IAAW,OAAO,IAAI,OAAO,CAE5B;IAED,QAAQ,IAAI,IAAI;IAaT,WAAW,IAAI,IAAI;IAYnB,iBAAiB,CAAC,SAAS,EAAE,QAAQ,GAAG,QAAQ,EAAE,GAAG,IAAI,GAAG,IAAI;IAchE,mBAAmB,IAAI,IAAI;IAQ3B,oBAAoB,IAAI,IAAI;IAQ5B,0BAA0B,CAAC,SAAS,EAAE,QAAQ,GAAG,QAAQ,EAAE,GAAG,IAAI,GAAG,IAAI;IAUnE,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IA0D/B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAoD7B,QAAQ,IAAI,IAAI;IAWvB,OAAO,CAAC,qBAAqB;yCAtOlB,uBAAuB;2CAAvB,uBAAuB;CA6OnC"}
|
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter, ViewChild, ChangeDetectionStrategy, HostListener } from '@angular/core';
|
|
2
|
+
import { Metadata, CompositeKey, KeyValuePair } from '@memberjunction/core';
|
|
3
|
+
import { MJNotificationService } from '@memberjunction/ng-notifications';
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
import * as i1 from "@angular/forms";
|
|
6
|
+
import * as i2 from "@memberjunction/ng-trees";
|
|
7
|
+
const _c0 = ["categoryTree"];
|
|
8
|
+
function SaveQueryPanelComponent_Conditional_20_Template(rf, ctx) { if (rf & 1) {
|
|
9
|
+
const _r2 = i0.ɵɵgetCurrentView();
|
|
10
|
+
i0.ɵɵelementStart(0, "button", 23);
|
|
11
|
+
i0.ɵɵlistener("click", function SaveQueryPanelComponent_Conditional_20_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.StartCreateCategory()); });
|
|
12
|
+
i0.ɵɵelement(1, "i", 24);
|
|
13
|
+
i0.ɵɵtext(2, " New ");
|
|
14
|
+
i0.ɵɵelementEnd();
|
|
15
|
+
} }
|
|
16
|
+
function SaveQueryPanelComponent_Conditional_23_Conditional_13_Template(rf, ctx) { if (rf & 1) {
|
|
17
|
+
i0.ɵɵelement(0, "i", 34);
|
|
18
|
+
} }
|
|
19
|
+
function SaveQueryPanelComponent_Conditional_23_Conditional_14_Template(rf, ctx) { if (rf & 1) {
|
|
20
|
+
i0.ɵɵelement(0, "i", 35);
|
|
21
|
+
} }
|
|
22
|
+
function SaveQueryPanelComponent_Conditional_23_Conditional_18_Template(rf, ctx) { if (rf & 1) {
|
|
23
|
+
i0.ɵɵelementStart(0, "div", 37);
|
|
24
|
+
i0.ɵɵtext(1);
|
|
25
|
+
i0.ɵɵelementEnd();
|
|
26
|
+
} if (rf & 2) {
|
|
27
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
28
|
+
i0.ɵɵadvance();
|
|
29
|
+
i0.ɵɵtextInterpolate(ctx_r2.CreateCategoryError);
|
|
30
|
+
} }
|
|
31
|
+
function SaveQueryPanelComponent_Conditional_23_Template(rf, ctx) { if (rf & 1) {
|
|
32
|
+
const _r4 = i0.ɵɵgetCurrentView();
|
|
33
|
+
i0.ɵɵelementStart(0, "div", 17)(1, "div", 25);
|
|
34
|
+
i0.ɵɵelement(2, "i", 26);
|
|
35
|
+
i0.ɵɵelementStart(3, "span");
|
|
36
|
+
i0.ɵɵtext(4, "New Category");
|
|
37
|
+
i0.ɵɵelementEnd()();
|
|
38
|
+
i0.ɵɵelementStart(5, "div", 27)(6, "input", 28);
|
|
39
|
+
i0.ɵɵtwoWayListener("ngModelChange", function SaveQueryPanelComponent_Conditional_23_Template_input_ngModelChange_6_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r2.NewCategoryName, $event) || (ctx_r2.NewCategoryName = $event); return i0.ɵɵresetView($event); });
|
|
40
|
+
i0.ɵɵlistener("keydown.enter", function SaveQueryPanelComponent_Conditional_23_Template_input_keydown_enter_6_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.CreateCategory()); })("keydown.escape", function SaveQueryPanelComponent_Conditional_23_Template_input_keydown_escape_6_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.CancelCreateCategory()); });
|
|
41
|
+
i0.ɵɵelementEnd();
|
|
42
|
+
i0.ɵɵelementStart(7, "div", 29)(8, "label", 30);
|
|
43
|
+
i0.ɵɵtext(9, "Parent (optional)");
|
|
44
|
+
i0.ɵɵelementEnd();
|
|
45
|
+
i0.ɵɵelementStart(10, "mj-tree-dropdown", 31);
|
|
46
|
+
i0.ɵɵlistener("SelectionChange", function SaveQueryPanelComponent_Conditional_23_Template_mj_tree_dropdown_SelectionChange_10_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnNewCategoryParentChanged($event)); });
|
|
47
|
+
i0.ɵɵelementEnd()()();
|
|
48
|
+
i0.ɵɵelementStart(11, "div", 32)(12, "button", 33);
|
|
49
|
+
i0.ɵɵlistener("click", function SaveQueryPanelComponent_Conditional_23_Template_button_click_12_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.CreateCategory()); });
|
|
50
|
+
i0.ɵɵconditionalCreate(13, SaveQueryPanelComponent_Conditional_23_Conditional_13_Template, 1, 0, "i", 34)(14, SaveQueryPanelComponent_Conditional_23_Conditional_14_Template, 1, 0, "i", 35);
|
|
51
|
+
i0.ɵɵtext(15);
|
|
52
|
+
i0.ɵɵelementEnd();
|
|
53
|
+
i0.ɵɵelementStart(16, "button", 36);
|
|
54
|
+
i0.ɵɵlistener("click", function SaveQueryPanelComponent_Conditional_23_Template_button_click_16_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.CancelCreateCategory()); });
|
|
55
|
+
i0.ɵɵtext(17, " Cancel ");
|
|
56
|
+
i0.ɵɵelementEnd()();
|
|
57
|
+
i0.ɵɵconditionalCreate(18, SaveQueryPanelComponent_Conditional_23_Conditional_18_Template, 2, 1, "div", 37);
|
|
58
|
+
i0.ɵɵelementEnd();
|
|
59
|
+
} if (rf & 2) {
|
|
60
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
61
|
+
i0.ɵɵadvance(6);
|
|
62
|
+
i0.ɵɵtwoWayProperty("ngModel", ctx_r2.NewCategoryName);
|
|
63
|
+
i0.ɵɵadvance(4);
|
|
64
|
+
i0.ɵɵproperty("BranchConfig", ctx_r2.CategoryBranchConfig)("SelectionMode", "single")("SelectableTypes", "branch")("Placeholder", "None (top level)");
|
|
65
|
+
i0.ɵɵadvance(2);
|
|
66
|
+
i0.ɵɵproperty("disabled", !ctx_r2.NewCategoryName.trim() || ctx_r2.IsCreatingCategorySaving);
|
|
67
|
+
i0.ɵɵadvance();
|
|
68
|
+
i0.ɵɵconditional(ctx_r2.IsCreatingCategorySaving ? 13 : 14);
|
|
69
|
+
i0.ɵɵadvance(2);
|
|
70
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r2.IsCreatingCategorySaving ? "Creating..." : "Create", " ");
|
|
71
|
+
i0.ɵɵadvance();
|
|
72
|
+
i0.ɵɵproperty("disabled", ctx_r2.IsCreatingCategorySaving);
|
|
73
|
+
i0.ɵɵadvance(2);
|
|
74
|
+
i0.ɵɵconditional(ctx_r2.CreateCategoryError ? 18 : -1);
|
|
75
|
+
} }
|
|
76
|
+
function SaveQueryPanelComponent_Conditional_29_Template(rf, ctx) { if (rf & 1) {
|
|
77
|
+
i0.ɵɵelementStart(0, "div", 19);
|
|
78
|
+
i0.ɵɵelement(1, "i", 38);
|
|
79
|
+
i0.ɵɵtext(2);
|
|
80
|
+
i0.ɵɵelementEnd();
|
|
81
|
+
} if (rf & 2) {
|
|
82
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
83
|
+
i0.ɵɵadvance(2);
|
|
84
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r2.ErrorMessage, " ");
|
|
85
|
+
} }
|
|
86
|
+
function SaveQueryPanelComponent_Conditional_32_Template(rf, ctx) { if (rf & 1) {
|
|
87
|
+
i0.ɵɵelement(0, "i", 34);
|
|
88
|
+
i0.ɵɵtext(1, " Saving... ");
|
|
89
|
+
} }
|
|
90
|
+
function SaveQueryPanelComponent_Conditional_33_Template(rf, ctx) { if (rf & 1) {
|
|
91
|
+
i0.ɵɵelement(0, "i", 39);
|
|
92
|
+
i0.ɵɵtext(1, " Save Query ");
|
|
93
|
+
} }
|
|
94
|
+
/**
|
|
95
|
+
* Slide-in panel for saving an ad-hoc SQL query as a reusable MJ Query record.
|
|
96
|
+
* Pre-populates name from the artifact title and provides a tree dropdown
|
|
97
|
+
* for selecting a query category, with inline category creation.
|
|
98
|
+
*
|
|
99
|
+
* Follows the CreateAgentSlideInComponent pattern for slide-in UX.
|
|
100
|
+
*/
|
|
101
|
+
export class SaveQueryPanelComponent {
|
|
102
|
+
cdr;
|
|
103
|
+
QueryName = '';
|
|
104
|
+
QueryDescription = '';
|
|
105
|
+
SQL = '';
|
|
106
|
+
Saved = new EventEmitter();
|
|
107
|
+
Cancelled = new EventEmitter();
|
|
108
|
+
categoryTree;
|
|
109
|
+
// Form state
|
|
110
|
+
Name = '';
|
|
111
|
+
Description = '';
|
|
112
|
+
IsSaving = false;
|
|
113
|
+
ErrorMessage = '';
|
|
114
|
+
IsVisible = false;
|
|
115
|
+
// Category selection
|
|
116
|
+
selectedCategoryId = null;
|
|
117
|
+
SelectedCategoryKey = null;
|
|
118
|
+
// Inline category creation
|
|
119
|
+
IsCreatingCategory = false;
|
|
120
|
+
IsCreatingCategorySaving = false;
|
|
121
|
+
NewCategoryName = '';
|
|
122
|
+
CreateCategoryError = '';
|
|
123
|
+
newCategoryParentId = null;
|
|
124
|
+
/** Tree config for query category hierarchy */
|
|
125
|
+
CategoryBranchConfig = {
|
|
126
|
+
EntityName: 'MJ: Query Categories',
|
|
127
|
+
DisplayField: 'Name',
|
|
128
|
+
IDField: 'ID',
|
|
129
|
+
ParentIDField: 'ParentID',
|
|
130
|
+
DefaultIcon: 'fa-solid fa-folder',
|
|
131
|
+
OrderBy: 'Name ASC'
|
|
132
|
+
};
|
|
133
|
+
constructor(cdr) {
|
|
134
|
+
this.cdr = cdr;
|
|
135
|
+
}
|
|
136
|
+
get CanSave() {
|
|
137
|
+
return !!this.Name?.trim();
|
|
138
|
+
}
|
|
139
|
+
ngOnInit() {
|
|
140
|
+
// Sync input values
|
|
141
|
+
this.Name = this.QueryName;
|
|
142
|
+
this.Description = this.QueryDescription;
|
|
143
|
+
// Trigger slide-in on next microtask (ensures DOM is ready)
|
|
144
|
+
Promise.resolve().then(() => {
|
|
145
|
+
this.IsVisible = true;
|
|
146
|
+
this.cdr.markForCheck();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
OnEscapeKey() {
|
|
150
|
+
if (this.IsCreatingCategory) {
|
|
151
|
+
this.CancelCreateCategory();
|
|
152
|
+
}
|
|
153
|
+
else if (!this.IsSaving) {
|
|
154
|
+
this.OnCancel();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// =========================================================================
|
|
158
|
+
// Category Selection
|
|
159
|
+
// =========================================================================
|
|
160
|
+
OnCategoryChanged(selection) {
|
|
161
|
+
if (!selection) {
|
|
162
|
+
this.selectedCategoryId = null;
|
|
163
|
+
}
|
|
164
|
+
else if (Array.isArray(selection)) {
|
|
165
|
+
this.selectedCategoryId = selection.length > 0 ? selection[0].ID : null;
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
this.selectedCategoryId = selection.ID;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// =========================================================================
|
|
172
|
+
// Inline Category Creation
|
|
173
|
+
// =========================================================================
|
|
174
|
+
StartCreateCategory() {
|
|
175
|
+
this.IsCreatingCategory = true;
|
|
176
|
+
this.NewCategoryName = '';
|
|
177
|
+
this.newCategoryParentId = null;
|
|
178
|
+
this.CreateCategoryError = '';
|
|
179
|
+
this.cdr.markForCheck();
|
|
180
|
+
}
|
|
181
|
+
CancelCreateCategory() {
|
|
182
|
+
this.IsCreatingCategory = false;
|
|
183
|
+
this.NewCategoryName = '';
|
|
184
|
+
this.newCategoryParentId = null;
|
|
185
|
+
this.CreateCategoryError = '';
|
|
186
|
+
this.cdr.markForCheck();
|
|
187
|
+
}
|
|
188
|
+
OnNewCategoryParentChanged(selection) {
|
|
189
|
+
if (!selection) {
|
|
190
|
+
this.newCategoryParentId = null;
|
|
191
|
+
}
|
|
192
|
+
else if (Array.isArray(selection)) {
|
|
193
|
+
this.newCategoryParentId = selection.length > 0 ? selection[0].ID : null;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
this.newCategoryParentId = selection.ID;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async CreateCategory() {
|
|
200
|
+
const name = this.NewCategoryName.trim();
|
|
201
|
+
if (!name || this.IsCreatingCategorySaving)
|
|
202
|
+
return;
|
|
203
|
+
this.IsCreatingCategorySaving = true;
|
|
204
|
+
this.CreateCategoryError = '';
|
|
205
|
+
this.cdr.markForCheck();
|
|
206
|
+
try {
|
|
207
|
+
const md = new Metadata();
|
|
208
|
+
const category = await md.GetEntityObject('MJ: Query Categories');
|
|
209
|
+
category.Name = name;
|
|
210
|
+
category.UserID = md.CurrentUser.ID;
|
|
211
|
+
if (this.newCategoryParentId) {
|
|
212
|
+
category.ParentID = this.newCategoryParentId;
|
|
213
|
+
}
|
|
214
|
+
const saved = await category.Save();
|
|
215
|
+
if (saved) {
|
|
216
|
+
MJNotificationService.Instance.CreateSimpleNotification(`Category "${name}" created`, 'success', 2500);
|
|
217
|
+
// Refresh tree to pick up the new category
|
|
218
|
+
await this.categoryTree.Refresh();
|
|
219
|
+
// Auto-select the newly created category.
|
|
220
|
+
// Must use detectChanges() (not markForCheck) to immediately propagate
|
|
221
|
+
// the new Value input to the tree dropdown, since this panel uses OnPush.
|
|
222
|
+
const newKey = new CompositeKey([new KeyValuePair('ID', category.ID)]);
|
|
223
|
+
this.SelectedCategoryKey = newKey;
|
|
224
|
+
this.selectedCategoryId = category.ID;
|
|
225
|
+
this.cdr.detectChanges();
|
|
226
|
+
// Collapse the create form
|
|
227
|
+
this.IsCreatingCategory = false;
|
|
228
|
+
this.NewCategoryName = '';
|
|
229
|
+
this.newCategoryParentId = null;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
const errorMsg = this.getEntityErrorMessage(category, 'create category');
|
|
233
|
+
this.CreateCategoryError = errorMsg;
|
|
234
|
+
MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);
|
|
235
|
+
console.error('Failed to create category:', category.LatestResult);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
const errorMsg = error instanceof Error ? error.message : 'Failed to create category';
|
|
240
|
+
this.CreateCategoryError = errorMsg;
|
|
241
|
+
MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);
|
|
242
|
+
console.error('Error creating category:', error);
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
this.IsCreatingCategorySaving = false;
|
|
246
|
+
this.cdr.markForCheck();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// =========================================================================
|
|
250
|
+
// Save / Cancel
|
|
251
|
+
// =========================================================================
|
|
252
|
+
async OnSave() {
|
|
253
|
+
if (!this.CanSave || this.IsSaving)
|
|
254
|
+
return;
|
|
255
|
+
this.IsSaving = true;
|
|
256
|
+
this.ErrorMessage = '';
|
|
257
|
+
this.cdr.markForCheck();
|
|
258
|
+
try {
|
|
259
|
+
const md = new Metadata();
|
|
260
|
+
const query = await md.GetEntityObject('MJ: Queries');
|
|
261
|
+
query.Name = this.Name.trim();
|
|
262
|
+
query.SQL = this.SQL;
|
|
263
|
+
query.Status = 'Approved';
|
|
264
|
+
if (this.Description?.trim()) {
|
|
265
|
+
query.Description = this.Description.trim();
|
|
266
|
+
}
|
|
267
|
+
if (this.selectedCategoryId) {
|
|
268
|
+
query.CategoryID = this.selectedCategoryId;
|
|
269
|
+
}
|
|
270
|
+
const saved = await query.Save();
|
|
271
|
+
if (saved) {
|
|
272
|
+
MJNotificationService.Instance.CreateSimpleNotification(`Query "${query.Name}" saved`, 'success', 2500);
|
|
273
|
+
// Animate out, then emit
|
|
274
|
+
this.IsVisible = false;
|
|
275
|
+
this.cdr.markForCheck();
|
|
276
|
+
setTimeout(() => {
|
|
277
|
+
this.Saved.emit({
|
|
278
|
+
queryId: query.ID,
|
|
279
|
+
queryName: query.Name
|
|
280
|
+
});
|
|
281
|
+
}, 300);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
const errorMsg = this.getEntityErrorMessage(query, 'save query');
|
|
285
|
+
this.ErrorMessage = errorMsg;
|
|
286
|
+
MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);
|
|
287
|
+
console.error('Failed to save query:', query.LatestResult);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
const errorMsg = error instanceof Error ? error.message : 'An unexpected error occurred';
|
|
292
|
+
this.ErrorMessage = errorMsg;
|
|
293
|
+
MJNotificationService.Instance.CreateSimpleNotification(errorMsg, 'error', 5000);
|
|
294
|
+
console.error('Error saving query:', error);
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
this.IsSaving = false;
|
|
298
|
+
this.cdr.markForCheck();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
OnCancel() {
|
|
302
|
+
if (this.IsSaving)
|
|
303
|
+
return;
|
|
304
|
+
this.IsVisible = false;
|
|
305
|
+
this.cdr.markForCheck();
|
|
306
|
+
setTimeout(() => this.Cancelled.emit(), 300);
|
|
307
|
+
}
|
|
308
|
+
// =========================================================================
|
|
309
|
+
// Helpers
|
|
310
|
+
// =========================================================================
|
|
311
|
+
getEntityErrorMessage(entity, operation) {
|
|
312
|
+
const result = entity.LatestResult;
|
|
313
|
+
if (result?.Message) {
|
|
314
|
+
return result.Message;
|
|
315
|
+
}
|
|
316
|
+
return `Failed to ${operation}. Please try again.`;
|
|
317
|
+
}
|
|
318
|
+
static ɵfac = function SaveQueryPanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || SaveQueryPanelComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
|
|
319
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: SaveQueryPanelComponent, selectors: [["mj-save-query-panel"]], viewQuery: function SaveQueryPanelComponent_Query(rf, ctx) { if (rf & 1) {
|
|
320
|
+
i0.ɵɵviewQuery(_c0, 5);
|
|
321
|
+
} if (rf & 2) {
|
|
322
|
+
let _t;
|
|
323
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.categoryTree = _t.first);
|
|
324
|
+
} }, hostBindings: function SaveQueryPanelComponent_HostBindings(rf, ctx) { if (rf & 1) {
|
|
325
|
+
i0.ɵɵlistener("keydown.escape", function SaveQueryPanelComponent_keydown_escape_HostBindingHandler() { return ctx.OnEscapeKey(); }, i0.ɵɵresolveDocument);
|
|
326
|
+
} }, inputs: { QueryName: "QueryName", QueryDescription: "QueryDescription", SQL: "SQL" }, outputs: { Saved: "Saved", Cancelled: "Cancelled" }, standalone: false, decls: 36, vars: 17, consts: [["categoryTree", ""], [1, "sqp-backdrop", 3, "click"], [1, "sqp-panel"], [1, "sqp-header"], [1, "sqp-title-group"], [1, "fa-solid", "fa-save", "sqp-title-icon"], [1, "sqp-title"], [1, "sqp-close-btn", 3, "click"], [1, "fa-solid", "fa-xmark"], [1, "sqp-body"], [1, "sqp-field"], [1, "sqp-label"], [1, "sqp-required"], ["type", "text", "placeholder", "Query name", 1, "sqp-input", 3, "ngModelChange", "keydown.enter", "ngModel"], [1, "sqp-label-row"], ["title", "Create new category", 1, "sqp-add-btn"], [3, "SelectionChange", "BranchConfig", "SelectionMode", "SelectableTypes", "Placeholder", "Value"], [1, "sqp-create-category"], ["placeholder", "Optional description", "rows", "3", 1, "sqp-textarea", 3, "ngModelChange", "ngModel"], [1, "sqp-error"], [1, "sqp-footer"], [1, "sqp-btn", "sqp-btn-save", 3, "click", "disabled"], [1, "sqp-btn", "sqp-btn-cancel", 3, "click", "disabled"], ["title", "Create new category", 1, "sqp-add-btn", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "sqp-create-header"], [1, "fa-solid", "fa-folder-plus"], [1, "sqp-create-fields"], ["type", "text", "placeholder", "Category name", 1, "sqp-input", 3, "ngModelChange", "keydown.enter", "keydown.escape", "ngModel"], [1, "sqp-create-parent"], [1, "sqp-sublabel"], [3, "SelectionChange", "BranchConfig", "SelectionMode", "SelectableTypes", "Placeholder"], [1, "sqp-create-actions"], [1, "sqp-btn", "sqp-btn-create", 3, "click", "disabled"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "fa-solid", "fa-check"], [1, "sqp-btn", "sqp-btn-cancel-create", 3, "click", "disabled"], [1, "sqp-inline-error"], [1, "fa-solid", "fa-exclamation-circle"], [1, "fa-solid", "fa-save"]], template: function SaveQueryPanelComponent_Template(rf, ctx) { if (rf & 1) {
|
|
327
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
328
|
+
i0.ɵɵelementStart(0, "div", 1);
|
|
329
|
+
i0.ɵɵlistener("click", function SaveQueryPanelComponent_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnCancel()); });
|
|
330
|
+
i0.ɵɵelementEnd();
|
|
331
|
+
i0.ɵɵelementStart(1, "div", 2)(2, "div", 3)(3, "div", 4);
|
|
332
|
+
i0.ɵɵelement(4, "i", 5);
|
|
333
|
+
i0.ɵɵelementStart(5, "h2", 6);
|
|
334
|
+
i0.ɵɵtext(6, "Save Query");
|
|
335
|
+
i0.ɵɵelementEnd()();
|
|
336
|
+
i0.ɵɵelementStart(7, "button", 7);
|
|
337
|
+
i0.ɵɵlistener("click", function SaveQueryPanelComponent_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnCancel()); });
|
|
338
|
+
i0.ɵɵelement(8, "i", 8);
|
|
339
|
+
i0.ɵɵelementEnd()();
|
|
340
|
+
i0.ɵɵelementStart(9, "div", 9)(10, "div", 10)(11, "label", 11);
|
|
341
|
+
i0.ɵɵtext(12, " Name ");
|
|
342
|
+
i0.ɵɵelementStart(13, "span", 12);
|
|
343
|
+
i0.ɵɵtext(14, "*");
|
|
344
|
+
i0.ɵɵelementEnd()();
|
|
345
|
+
i0.ɵɵelementStart(15, "input", 13);
|
|
346
|
+
i0.ɵɵtwoWayListener("ngModelChange", function SaveQueryPanelComponent_Template_input_ngModelChange_15_listener($event) { i0.ɵɵrestoreView(_r1); i0.ɵɵtwoWayBindingSet(ctx.Name, $event) || (ctx.Name = $event); return i0.ɵɵresetView($event); });
|
|
347
|
+
i0.ɵɵlistener("keydown.enter", function SaveQueryPanelComponent_Template_input_keydown_enter_15_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnSave()); });
|
|
348
|
+
i0.ɵɵelementEnd()();
|
|
349
|
+
i0.ɵɵelementStart(16, "div", 10)(17, "div", 14)(18, "label", 11);
|
|
350
|
+
i0.ɵɵtext(19, "Category");
|
|
351
|
+
i0.ɵɵelementEnd();
|
|
352
|
+
i0.ɵɵconditionalCreate(20, SaveQueryPanelComponent_Conditional_20_Template, 3, 0, "button", 15);
|
|
353
|
+
i0.ɵɵelementEnd();
|
|
354
|
+
i0.ɵɵelementStart(21, "mj-tree-dropdown", 16, 0);
|
|
355
|
+
i0.ɵɵlistener("SelectionChange", function SaveQueryPanelComponent_Template_mj_tree_dropdown_SelectionChange_21_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnCategoryChanged($event)); });
|
|
356
|
+
i0.ɵɵelementEnd();
|
|
357
|
+
i0.ɵɵconditionalCreate(23, SaveQueryPanelComponent_Conditional_23_Template, 19, 10, "div", 17);
|
|
358
|
+
i0.ɵɵelementEnd();
|
|
359
|
+
i0.ɵɵelementStart(24, "div", 10)(25, "label", 11);
|
|
360
|
+
i0.ɵɵtext(26, "Description");
|
|
361
|
+
i0.ɵɵelementEnd();
|
|
362
|
+
i0.ɵɵelementStart(27, "textarea", 18);
|
|
363
|
+
i0.ɵɵtwoWayListener("ngModelChange", function SaveQueryPanelComponent_Template_textarea_ngModelChange_27_listener($event) { i0.ɵɵrestoreView(_r1); i0.ɵɵtwoWayBindingSet(ctx.Description, $event) || (ctx.Description = $event); return i0.ɵɵresetView($event); });
|
|
364
|
+
i0.ɵɵtext(28, " ");
|
|
365
|
+
i0.ɵɵelementEnd()();
|
|
366
|
+
i0.ɵɵconditionalCreate(29, SaveQueryPanelComponent_Conditional_29_Template, 3, 1, "div", 19);
|
|
367
|
+
i0.ɵɵelementEnd();
|
|
368
|
+
i0.ɵɵelementStart(30, "div", 20)(31, "button", 21);
|
|
369
|
+
i0.ɵɵlistener("click", function SaveQueryPanelComponent_Template_button_click_31_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnSave()); });
|
|
370
|
+
i0.ɵɵconditionalCreate(32, SaveQueryPanelComponent_Conditional_32_Template, 2, 0)(33, SaveQueryPanelComponent_Conditional_33_Template, 2, 0);
|
|
371
|
+
i0.ɵɵelementEnd();
|
|
372
|
+
i0.ɵɵelementStart(34, "button", 22);
|
|
373
|
+
i0.ɵɵlistener("click", function SaveQueryPanelComponent_Template_button_click_34_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnCancel()); });
|
|
374
|
+
i0.ɵɵtext(35, " Cancel ");
|
|
375
|
+
i0.ɵɵelementEnd()()();
|
|
376
|
+
} if (rf & 2) {
|
|
377
|
+
i0.ɵɵclassProp("sqp-visible", ctx.IsVisible);
|
|
378
|
+
i0.ɵɵadvance();
|
|
379
|
+
i0.ɵɵclassProp("sqp-visible", ctx.IsVisible);
|
|
380
|
+
i0.ɵɵadvance(14);
|
|
381
|
+
i0.ɵɵtwoWayProperty("ngModel", ctx.Name);
|
|
382
|
+
i0.ɵɵadvance(5);
|
|
383
|
+
i0.ɵɵconditional(!ctx.IsCreatingCategory ? 20 : -1);
|
|
384
|
+
i0.ɵɵadvance();
|
|
385
|
+
i0.ɵɵproperty("BranchConfig", ctx.CategoryBranchConfig)("SelectionMode", "single")("SelectableTypes", "branch")("Placeholder", "Select a category (optional)")("Value", ctx.SelectedCategoryKey);
|
|
386
|
+
i0.ɵɵadvance(2);
|
|
387
|
+
i0.ɵɵconditional(ctx.IsCreatingCategory ? 23 : -1);
|
|
388
|
+
i0.ɵɵadvance(4);
|
|
389
|
+
i0.ɵɵtwoWayProperty("ngModel", ctx.Description);
|
|
390
|
+
i0.ɵɵadvance(2);
|
|
391
|
+
i0.ɵɵconditional(ctx.ErrorMessage ? 29 : -1);
|
|
392
|
+
i0.ɵɵadvance(2);
|
|
393
|
+
i0.ɵɵproperty("disabled", !ctx.CanSave || ctx.IsSaving);
|
|
394
|
+
i0.ɵɵadvance();
|
|
395
|
+
i0.ɵɵconditional(ctx.IsSaving ? 32 : 33);
|
|
396
|
+
i0.ɵɵadvance(2);
|
|
397
|
+
i0.ɵɵproperty("disabled", ctx.IsSaving);
|
|
398
|
+
} }, dependencies: [i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgModel, i2.TreeDropdownComponent], styles: ["\n\n .sqp-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0);\n z-index: 1000;\n transition: background 0.3s ease;\n pointer-events: none;\n }\n .sqp-backdrop.sqp-visible[_ngcontent-%COMP%] {\n background: rgba(0, 0, 0, 0.3);\n pointer-events: auto;\n }\n\n \n\n\n\n .sqp-panel[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n right: -440px;\n height: 100vh;\n width: 440px;\n background: var(--card-background, #ffffff);\n box-shadow: -8px 0 32px rgba(0, 0, 0, 0.12);\n z-index: 1001;\n display: flex;\n flex-direction: column;\n transition: right 0.3s cubic-bezier(0.16, 1, 0.3, 1);\n }\n .sqp-panel.sqp-visible[_ngcontent-%COMP%] {\n right: 0;\n }\n\n \n\n .sqp-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px 16px;\n border-bottom: 1px solid var(--border-color, #e5e7eb);\n flex-shrink: 0;\n }\n .sqp-title-group[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n .sqp-title-icon[_ngcontent-%COMP%] {\n font-size: 20px;\n color: #5c6bc0;\n }\n .sqp-title[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 18px;\n font-weight: 700;\n color: var(--text-primary, #1f2937);\n }\n .sqp-close-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n background: none;\n border: none;\n border-radius: 8px;\n color: var(--text-secondary, #6b7280);\n cursor: pointer;\n transition: all 0.15s ease;\n font-size: 16px;\n flex-shrink: 0;\n }\n .sqp-close-btn[_ngcontent-%COMP%]:hover {\n background: var(--hover-background, #f3f4f6);\n color: var(--text-primary, #1f2937);\n }\n\n \n\n .sqp-body[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n padding: 24px;\n }\n\n \n\n .sqp-field[_ngcontent-%COMP%] {\n margin-bottom: 20px;\n }\n .sqp-label-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 6px;\n }\n .sqp-label[_ngcontent-%COMP%] {\n display: block;\n margin-bottom: 6px;\n font-weight: 600;\n font-size: 13px;\n color: var(--text-primary, #374151);\n }\n .sqp-label-row[_ngcontent-%COMP%] .sqp-label[_ngcontent-%COMP%] {\n margin-bottom: 0;\n }\n .sqp-sublabel[_ngcontent-%COMP%] {\n display: block;\n margin-bottom: 4px;\n font-weight: 500;\n font-size: 12px;\n color: #6b7280;\n }\n .sqp-required[_ngcontent-%COMP%] {\n color: #dc2626;\n }\n .sqp-input[_ngcontent-%COMP%] {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid #d1d5db;\n border-radius: 6px;\n font-size: 14px;\n color: #1f2937;\n background: #fff;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n }\n .sqp-input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: #5c6bc0;\n box-shadow: 0 0 0 3px rgba(92, 107, 192, 0.15);\n }\n .sqp-input[_ngcontent-%COMP%]::placeholder {\n color: #9ca3af;\n }\n .sqp-textarea[_ngcontent-%COMP%] {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid #d1d5db;\n border-radius: 6px;\n font-size: 14px;\n color: #1f2937;\n background: #fff;\n resize: vertical;\n font-family: inherit;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n }\n .sqp-textarea[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: #5c6bc0;\n box-shadow: 0 0 0 3px rgba(92, 107, 192, 0.15);\n }\n .sqp-textarea[_ngcontent-%COMP%]::placeholder {\n color: #9ca3af;\n }\n\n \n\n .sqp-add-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 10px;\n background: none;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n font-size: 12px;\n color: #5c6bc0;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .sqp-add-btn[_ngcontent-%COMP%]:hover {\n background: #eef2ff;\n border-color: #5c6bc0;\n }\n\n \n\n .sqp-create-category[_ngcontent-%COMP%] {\n margin-top: 12px;\n padding: 14px;\n background: #f8fafc;\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n }\n .sqp-create-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n font-weight: 600;\n color: #475569;\n margin-bottom: 10px;\n }\n .sqp-create-header[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #5c6bc0;\n }\n .sqp-create-fields[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n .sqp-create-parent[_ngcontent-%COMP%] {\n margin-top: 2px;\n }\n .sqp-create-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n margin-top: 12px;\n }\n .sqp-inline-error[_ngcontent-%COMP%] {\n margin-top: 8px;\n font-size: 12px;\n color: #dc2626;\n }\n\n \n\n .sqp-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .sqp-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n .sqp-btn-save[_ngcontent-%COMP%] {\n background: #5c6bc0;\n color: #fff;\n padding: 10px 20px;\n font-size: 14px;\n }\n .sqp-btn-save[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: #3f51b5;\n }\n .sqp-btn-cancel[_ngcontent-%COMP%] {\n background: none;\n border: 1px solid #d1d5db;\n color: #6b7280;\n padding: 10px 20px;\n font-size: 14px;\n }\n .sqp-btn-cancel[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: #f3f4f6;\n color: #374151;\n }\n .sqp-btn-create[_ngcontent-%COMP%] {\n background: #16a34a;\n color: #fff;\n }\n .sqp-btn-create[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: #15803d;\n }\n .sqp-btn-cancel-create[_ngcontent-%COMP%] {\n background: none;\n color: #6b7280;\n }\n .sqp-btn-cancel-create[_ngcontent-%COMP%]:hover:not(:disabled) {\n color: #374151;\n background: #f1f5f9;\n }\n\n \n\n .sqp-footer[_ngcontent-%COMP%] {\n display: flex;\n gap: 10px;\n padding: 16px 24px;\n border-top: 1px solid var(--border-color, #e5e7eb);\n flex-shrink: 0;\n }\n\n \n\n .sqp-error[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 14px;\n background: #fef2f2;\n border: 1px solid #fecaca;\n border-radius: 6px;\n color: #dc2626;\n font-size: 13px;\n }\n\n \n\n @media (max-width: 768px) {\n .sqp-panel[_ngcontent-%COMP%] {\n width: 100% !important;\n right: -100%;\n }\n .sqp-panel.sqp-visible[_ngcontent-%COMP%] {\n right: 0;\n }\n .sqp-header[_ngcontent-%COMP%] {\n padding: 16px 20px 12px;\n }\n .sqp-body[_ngcontent-%COMP%] {\n padding: 16px;\n }\n .sqp-footer[_ngcontent-%COMP%] {\n padding: 12px 16px;\n }\n }"], changeDetection: 0 });
|
|
399
|
+
}
|
|
400
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SaveQueryPanelComponent, [{
|
|
401
|
+
type: Component,
|
|
402
|
+
args: [{ standalone: false, selector: 'mj-save-query-panel', template: `
|
|
403
|
+
<!-- Backdrop -->
|
|
404
|
+
<div class="sqp-backdrop" [class.sqp-visible]="IsVisible" (click)="OnCancel()"></div>
|
|
405
|
+
|
|
406
|
+
<!-- Slide panel -->
|
|
407
|
+
<div class="sqp-panel" [class.sqp-visible]="IsVisible">
|
|
408
|
+
<!-- Header -->
|
|
409
|
+
<div class="sqp-header">
|
|
410
|
+
<div class="sqp-title-group">
|
|
411
|
+
<i class="fa-solid fa-save sqp-title-icon"></i>
|
|
412
|
+
<h2 class="sqp-title">Save Query</h2>
|
|
413
|
+
</div>
|
|
414
|
+
<button class="sqp-close-btn" (click)="OnCancel()">
|
|
415
|
+
<i class="fa-solid fa-xmark"></i>
|
|
416
|
+
</button>
|
|
417
|
+
</div>
|
|
418
|
+
|
|
419
|
+
<!-- Body -->
|
|
420
|
+
<div class="sqp-body">
|
|
421
|
+
<!-- Name -->
|
|
422
|
+
<div class="sqp-field">
|
|
423
|
+
<label class="sqp-label">
|
|
424
|
+
Name <span class="sqp-required">*</span>
|
|
425
|
+
</label>
|
|
426
|
+
<input
|
|
427
|
+
type="text"
|
|
428
|
+
class="sqp-input"
|
|
429
|
+
[(ngModel)]="Name"
|
|
430
|
+
placeholder="Query name"
|
|
431
|
+
(keydown.enter)="OnSave()">
|
|
432
|
+
</div>
|
|
433
|
+
|
|
434
|
+
<!-- Category -->
|
|
435
|
+
<div class="sqp-field">
|
|
436
|
+
<div class="sqp-label-row">
|
|
437
|
+
<label class="sqp-label">Category</label>
|
|
438
|
+
@if (!IsCreatingCategory) {
|
|
439
|
+
<button class="sqp-add-btn" (click)="StartCreateCategory()" title="Create new category">
|
|
440
|
+
<i class="fa-solid fa-plus"></i> New
|
|
441
|
+
</button>
|
|
442
|
+
}
|
|
443
|
+
</div>
|
|
444
|
+
|
|
445
|
+
<mj-tree-dropdown
|
|
446
|
+
#categoryTree
|
|
447
|
+
[BranchConfig]="CategoryBranchConfig"
|
|
448
|
+
[SelectionMode]="'single'"
|
|
449
|
+
[SelectableTypes]="'branch'"
|
|
450
|
+
[Placeholder]="'Select a category (optional)'"
|
|
451
|
+
[Value]="SelectedCategoryKey"
|
|
452
|
+
(SelectionChange)="OnCategoryChanged($event)">
|
|
453
|
+
</mj-tree-dropdown>
|
|
454
|
+
|
|
455
|
+
<!-- Inline create category form -->
|
|
456
|
+
@if (IsCreatingCategory) {
|
|
457
|
+
<div class="sqp-create-category">
|
|
458
|
+
<div class="sqp-create-header">
|
|
459
|
+
<i class="fa-solid fa-folder-plus"></i>
|
|
460
|
+
<span>New Category</span>
|
|
461
|
+
</div>
|
|
462
|
+
<div class="sqp-create-fields">
|
|
463
|
+
<input
|
|
464
|
+
type="text"
|
|
465
|
+
class="sqp-input"
|
|
466
|
+
[(ngModel)]="NewCategoryName"
|
|
467
|
+
placeholder="Category name"
|
|
468
|
+
(keydown.enter)="CreateCategory()"
|
|
469
|
+
(keydown.escape)="CancelCreateCategory()">
|
|
470
|
+
<div class="sqp-create-parent">
|
|
471
|
+
<label class="sqp-sublabel">Parent (optional)</label>
|
|
472
|
+
<mj-tree-dropdown
|
|
473
|
+
[BranchConfig]="CategoryBranchConfig"
|
|
474
|
+
[SelectionMode]="'single'"
|
|
475
|
+
[SelectableTypes]="'branch'"
|
|
476
|
+
[Placeholder]="'None (top level)'"
|
|
477
|
+
(SelectionChange)="OnNewCategoryParentChanged($event)">
|
|
478
|
+
</mj-tree-dropdown>
|
|
479
|
+
</div>
|
|
480
|
+
</div>
|
|
481
|
+
<div class="sqp-create-actions">
|
|
482
|
+
<button class="sqp-btn sqp-btn-create"
|
|
483
|
+
(click)="CreateCategory()"
|
|
484
|
+
[disabled]="!NewCategoryName.trim() || IsCreatingCategorySaving">
|
|
485
|
+
@if (IsCreatingCategorySaving) {
|
|
486
|
+
<i class="fa-solid fa-spinner fa-spin"></i>
|
|
487
|
+
} @else {
|
|
488
|
+
<i class="fa-solid fa-check"></i>
|
|
489
|
+
}
|
|
490
|
+
{{ IsCreatingCategorySaving ? 'Creating...' : 'Create' }}
|
|
491
|
+
</button>
|
|
492
|
+
<button class="sqp-btn sqp-btn-cancel-create"
|
|
493
|
+
(click)="CancelCreateCategory()"
|
|
494
|
+
[disabled]="IsCreatingCategorySaving">
|
|
495
|
+
Cancel
|
|
496
|
+
</button>
|
|
497
|
+
</div>
|
|
498
|
+
@if (CreateCategoryError) {
|
|
499
|
+
<div class="sqp-inline-error">{{ CreateCategoryError }}</div>
|
|
500
|
+
}
|
|
501
|
+
</div>
|
|
502
|
+
}
|
|
503
|
+
</div>
|
|
504
|
+
|
|
505
|
+
<!-- Description -->
|
|
506
|
+
<div class="sqp-field">
|
|
507
|
+
<label class="sqp-label">Description</label>
|
|
508
|
+
<textarea
|
|
509
|
+
class="sqp-textarea"
|
|
510
|
+
[(ngModel)]="Description"
|
|
511
|
+
placeholder="Optional description"
|
|
512
|
+
rows="3">
|
|
513
|
+
</textarea>
|
|
514
|
+
</div>
|
|
515
|
+
|
|
516
|
+
@if (ErrorMessage) {
|
|
517
|
+
<div class="sqp-error">
|
|
518
|
+
<i class="fa-solid fa-exclamation-circle"></i>
|
|
519
|
+
{{ ErrorMessage }}
|
|
520
|
+
</div>
|
|
521
|
+
}
|
|
522
|
+
</div>
|
|
523
|
+
|
|
524
|
+
<!-- Footer -->
|
|
525
|
+
<div class="sqp-footer">
|
|
526
|
+
<button class="sqp-btn sqp-btn-save"
|
|
527
|
+
(click)="OnSave()"
|
|
528
|
+
[disabled]="!CanSave || IsSaving">
|
|
529
|
+
@if (IsSaving) {
|
|
530
|
+
<i class="fa-solid fa-spinner fa-spin"></i> Saving...
|
|
531
|
+
} @else {
|
|
532
|
+
<i class="fa-solid fa-save"></i> Save Query
|
|
533
|
+
}
|
|
534
|
+
</button>
|
|
535
|
+
<button class="sqp-btn sqp-btn-cancel"
|
|
536
|
+
(click)="OnCancel()"
|
|
537
|
+
[disabled]="IsSaving">
|
|
538
|
+
Cancel
|
|
539
|
+
</button>
|
|
540
|
+
</div>
|
|
541
|
+
</div>
|
|
542
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["\n /* Backdrop */\n .sqp-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0);\n z-index: 1000;\n transition: background 0.3s ease;\n pointer-events: none;\n }\n .sqp-backdrop.sqp-visible {\n background: rgba(0, 0, 0, 0.3);\n pointer-events: auto;\n }\n\n /* Panel \u2014 uses right offset instead of transform to avoid creating\n a new containing block that breaks position:fixed children\n (e.g., the tree-dropdown popup). */\n .sqp-panel {\n position: fixed;\n top: 0;\n right: -440px;\n height: 100vh;\n width: 440px;\n background: var(--card-background, #ffffff);\n box-shadow: -8px 0 32px rgba(0, 0, 0, 0.12);\n z-index: 1001;\n display: flex;\n flex-direction: column;\n transition: right 0.3s cubic-bezier(0.16, 1, 0.3, 1);\n }\n .sqp-panel.sqp-visible {\n right: 0;\n }\n\n /* Header */\n .sqp-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px 16px;\n border-bottom: 1px solid var(--border-color, #e5e7eb);\n flex-shrink: 0;\n }\n .sqp-title-group {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n .sqp-title-icon {\n font-size: 20px;\n color: #5c6bc0;\n }\n .sqp-title {\n margin: 0;\n font-size: 18px;\n font-weight: 700;\n color: var(--text-primary, #1f2937);\n }\n .sqp-close-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n background: none;\n border: none;\n border-radius: 8px;\n color: var(--text-secondary, #6b7280);\n cursor: pointer;\n transition: all 0.15s ease;\n font-size: 16px;\n flex-shrink: 0;\n }\n .sqp-close-btn:hover {\n background: var(--hover-background, #f3f4f6);\n color: var(--text-primary, #1f2937);\n }\n\n /* Body */\n .sqp-body {\n flex: 1;\n overflow-y: auto;\n padding: 24px;\n }\n\n /* Fields */\n .sqp-field {\n margin-bottom: 20px;\n }\n .sqp-label-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 6px;\n }\n .sqp-label {\n display: block;\n margin-bottom: 6px;\n font-weight: 600;\n font-size: 13px;\n color: var(--text-primary, #374151);\n }\n .sqp-label-row .sqp-label {\n margin-bottom: 0;\n }\n .sqp-sublabel {\n display: block;\n margin-bottom: 4px;\n font-weight: 500;\n font-size: 12px;\n color: #6b7280;\n }\n .sqp-required {\n color: #dc2626;\n }\n .sqp-input {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid #d1d5db;\n border-radius: 6px;\n font-size: 14px;\n color: #1f2937;\n background: #fff;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n }\n .sqp-input:focus {\n outline: none;\n border-color: #5c6bc0;\n box-shadow: 0 0 0 3px rgba(92, 107, 192, 0.15);\n }\n .sqp-input::placeholder {\n color: #9ca3af;\n }\n .sqp-textarea {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid #d1d5db;\n border-radius: 6px;\n font-size: 14px;\n color: #1f2937;\n background: #fff;\n resize: vertical;\n font-family: inherit;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n box-sizing: border-box;\n }\n .sqp-textarea:focus {\n outline: none;\n border-color: #5c6bc0;\n box-shadow: 0 0 0 3px rgba(92, 107, 192, 0.15);\n }\n .sqp-textarea::placeholder {\n color: #9ca3af;\n }\n\n /* Add category button */\n .sqp-add-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 10px;\n background: none;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n font-size: 12px;\n color: #5c6bc0;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .sqp-add-btn:hover {\n background: #eef2ff;\n border-color: #5c6bc0;\n }\n\n /* Inline create category */\n .sqp-create-category {\n margin-top: 12px;\n padding: 14px;\n background: #f8fafc;\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n }\n .sqp-create-header {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n font-weight: 600;\n color: #475569;\n margin-bottom: 10px;\n }\n .sqp-create-header i {\n color: #5c6bc0;\n }\n .sqp-create-fields {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n .sqp-create-parent {\n margin-top: 2px;\n }\n .sqp-create-actions {\n display: flex;\n gap: 8px;\n margin-top: 12px;\n }\n .sqp-inline-error {\n margin-top: 8px;\n font-size: 12px;\n color: #dc2626;\n }\n\n /* Buttons */\n .sqp-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .sqp-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n .sqp-btn-save {\n background: #5c6bc0;\n color: #fff;\n padding: 10px 20px;\n font-size: 14px;\n }\n .sqp-btn-save:hover:not(:disabled) {\n background: #3f51b5;\n }\n .sqp-btn-cancel {\n background: none;\n border: 1px solid #d1d5db;\n color: #6b7280;\n padding: 10px 20px;\n font-size: 14px;\n }\n .sqp-btn-cancel:hover:not(:disabled) {\n background: #f3f4f6;\n color: #374151;\n }\n .sqp-btn-create {\n background: #16a34a;\n color: #fff;\n }\n .sqp-btn-create:hover:not(:disabled) {\n background: #15803d;\n }\n .sqp-btn-cancel-create {\n background: none;\n color: #6b7280;\n }\n .sqp-btn-cancel-create:hover:not(:disabled) {\n color: #374151;\n background: #f1f5f9;\n }\n\n /* Footer */\n .sqp-footer {\n display: flex;\n gap: 10px;\n padding: 16px 24px;\n border-top: 1px solid var(--border-color, #e5e7eb);\n flex-shrink: 0;\n }\n\n /* Error */\n .sqp-error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 14px;\n background: #fef2f2;\n border: 1px solid #fecaca;\n border-radius: 6px;\n color: #dc2626;\n font-size: 13px;\n }\n\n /* Responsive */\n @media (max-width: 768px) {\n .sqp-panel {\n width: 100% !important;\n right: -100%;\n }\n .sqp-panel.sqp-visible {\n right: 0;\n }\n .sqp-header {\n padding: 16px 20px 12px;\n }\n .sqp-body {\n padding: 16px;\n }\n .sqp-footer {\n padding: 12px 16px;\n }\n }\n "] }]
|
|
543
|
+
}], () => [{ type: i0.ChangeDetectorRef }], { QueryName: [{
|
|
544
|
+
type: Input
|
|
545
|
+
}], QueryDescription: [{
|
|
546
|
+
type: Input
|
|
547
|
+
}], SQL: [{
|
|
548
|
+
type: Input
|
|
549
|
+
}], Saved: [{
|
|
550
|
+
type: Output
|
|
551
|
+
}], Cancelled: [{
|
|
552
|
+
type: Output
|
|
553
|
+
}], categoryTree: [{
|
|
554
|
+
type: ViewChild,
|
|
555
|
+
args: ['categoryTree']
|
|
556
|
+
}], OnEscapeKey: [{
|
|
557
|
+
type: HostListener,
|
|
558
|
+
args: ['document:keydown.escape']
|
|
559
|
+
}] }); })();
|
|
560
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SaveQueryPanelComponent, { className: "SaveQueryPanelComponent", filePath: "src/lib/components/plugins/save-query-dialog.component.ts", lineNumber: 479 }); })();
|
|
561
|
+
//# sourceMappingURL=save-query-dialog.component.js.map
|