@memberjunction/ng-entity-viewer 6.1.4 → 6.2.0-edge.1
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/aggregate-panel/aggregate-panel.component.d.ts +18 -2
- package/dist/lib/aggregate-panel/aggregate-panel.component.d.ts.map +1 -1
- package/dist/lib/aggregate-panel/aggregate-panel.component.js +47 -18
- package/dist/lib/aggregate-panel/aggregate-panel.component.js.map +1 -1
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.d.ts +22 -0
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.d.ts.map +1 -1
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.js +57 -13
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.js.map +1 -1
- package/dist/lib/entity-cards/entity-cards.component.d.ts +68 -5
- package/dist/lib/entity-cards/entity-cards.component.d.ts.map +1 -1
- package/dist/lib/entity-cards/entity-cards.component.js +213 -73
- package/dist/lib/entity-cards/entity-cards.component.js.map +1 -1
- package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts +156 -14
- package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts.map +1 -1
- package/dist/lib/entity-data-grid/entity-data-grid.component.js +432 -138
- package/dist/lib/entity-data-grid/entity-data-grid.component.js.map +1 -1
- package/dist/lib/entity-data-grid/events/grid-events.js +67 -67
- package/dist/lib/entity-data-grid/events/grid-events.js.map +1 -1
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.d.ts +112 -8
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.d.ts.map +1 -1
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js +255 -69
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js.map +1 -1
- package/dist/lib/entity-viewer/entity-viewer.component.d.ts +11 -0
- package/dist/lib/entity-viewer/entity-viewer.component.d.ts.map +1 -1
- package/dist/lib/entity-viewer/entity-viewer.component.js +45 -8
- package/dist/lib/entity-viewer/entity-viewer.component.js.map +1 -1
- package/dist/lib/pill/pill.component.d.ts +13 -3
- package/dist/lib/pill/pill.component.d.ts.map +1 -1
- package/dist/lib/pill/pill.component.js +34 -10
- package/dist/lib/pill/pill.component.js.map +1 -1
- package/dist/lib/quick-save-dialog/quick-save-dialog.component.d.ts +29 -3
- package/dist/lib/quick-save-dialog/quick-save-dialog.component.d.ts.map +1 -1
- package/dist/lib/quick-save-dialog/quick-save-dialog.component.js +173 -114
- package/dist/lib/quick-save-dialog/quick-save-dialog.component.js.map +1 -1
- package/dist/lib/recycle-bin/recycle-bin.component.d.ts +4 -0
- package/dist/lib/recycle-bin/recycle-bin.component.d.ts.map +1 -1
- package/dist/lib/recycle-bin/recycle-bin.component.js +10 -2
- package/dist/lib/recycle-bin/recycle-bin.component.js.map +1 -1
- package/dist/lib/utils/aggregate-field.util.d.ts +21 -0
- package/dist/lib/utils/aggregate-field.util.d.ts.map +1 -0
- package/dist/lib/utils/aggregate-field.util.js +40 -0
- package/dist/lib/utils/aggregate-field.util.js.map +1 -0
- package/dist/lib/utils/highlight.util.d.ts +6 -0
- package/dist/lib/utils/highlight.util.d.ts.map +1 -1
- package/dist/lib/utils/highlight.util.js +23 -11
- package/dist/lib/utils/highlight.util.js.map +1 -1
- package/dist/lib/utils/record.util.d.ts +10 -0
- package/dist/lib/utils/record.util.d.ts.map +1 -1
- package/dist/lib/utils/record.util.js +20 -4
- package/dist/lib/utils/record.util.js.map +1 -1
- package/dist/lib/view-config-panel/view-config-panel.component.d.ts +22 -2
- package/dist/lib/view-config-panel/view-config-panel.component.d.ts.map +1 -1
- package/dist/lib/view-config-panel/view-config-panel.component.js +54 -14
- package/dist/lib/view-config-panel/view-config-panel.component.js.map +1 -1
- package/dist/lib/view-selector/view-selector.component.js +2 -2
- package/dist/lib/view-types/renderers/cards-view-renderer.component.d.ts +15 -3
- package/dist/lib/view-types/renderers/cards-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-types/renderers/cards-view-renderer.component.js +33 -5
- package/dist/lib/view-types/renderers/cards-view-renderer.component.js.map +1 -1
- package/dist/lib/view-types/renderers/grid-view-renderer.component.d.ts +160 -5
- package/dist/lib/view-types/renderers/grid-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-types/renderers/grid-view-renderer.component.js +576 -36
- package/dist/lib/view-types/renderers/grid-view-renderer.component.js.map +1 -1
- package/dist/lib/view-types/renderers/map-view-renderer.component.d.ts +16 -2
- package/dist/lib/view-types/renderers/map-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-types/renderers/map-view-renderer.component.js +37 -9
- package/dist/lib/view-types/renderers/map-view-renderer.component.js.map +1 -1
- package/dist/lib/view-types/view-type.contracts.js.map +1 -1
- package/dist/lib/view-workspace/view-workspace.component.d.ts +235 -24
- package/dist/lib/view-workspace/view-workspace.component.d.ts.map +1 -1
- package/dist/lib/view-workspace/view-workspace.component.js +600 -174
- package/dist/lib/view-workspace/view-workspace.component.js.map +1 -1
- package/dist/module.d.ts +5 -4
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +8 -1
- 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 +21 -20
|
@@ -1,23 +1,92 @@
|
|
|
1
1
|
import { Component, Input, Output, EventEmitter, ViewEncapsulation, ViewChild, ChangeDetectorRef, inject } from '@angular/core';
|
|
2
|
-
import { LogError, CompositeKey } from '@memberjunction/core';
|
|
2
|
+
import { LogError, CompositeKey, RunView, RecordMergeRequest } from '@memberjunction/core';
|
|
3
|
+
import { RecordComparisonService, } from '@memberjunction/ng-record-merge';
|
|
3
4
|
import { UUIDsEqual } from '@memberjunction/global';
|
|
4
5
|
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
5
|
-
import {
|
|
6
|
+
import { BuildPkString, BuildCompositeKey } from '../../utils/record.util';
|
|
6
7
|
import * as i0 from "@angular/core";
|
|
7
8
|
import * as i1 from "@memberjunction/ng-list-management";
|
|
8
|
-
import * as i2 from "
|
|
9
|
-
import * as i3 from "
|
|
9
|
+
import * as i2 from "@memberjunction/ng-record-merge";
|
|
10
|
+
import * as i3 from "@memberjunction/ng-ui-components";
|
|
11
|
+
import * as i4 from "../../entity-data-grid/entity-data-grid.component";
|
|
12
|
+
import * as i5 from "../../confirm-dialog/confirm-dialog.component";
|
|
10
13
|
const _c0 = ["grid"];
|
|
11
14
|
const _c1 = () => ({});
|
|
12
15
|
function GridViewRendererComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
13
16
|
const _r1 = i0.ɵɵgetCurrentView();
|
|
14
|
-
i0.ɵɵelementStart(0, "mj-list-management-dialog",
|
|
17
|
+
i0.ɵɵelementStart(0, "mj-list-management-dialog", 6);
|
|
15
18
|
i0.ɵɵlistener("complete", function GridViewRendererComponent_Conditional_2_Template_mj_list_management_dialog_complete_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onListManagementComplete($event)); })("cancel", function GridViewRendererComponent_Conditional_2_Template_mj_list_management_dialog_cancel_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onListManagementCancel()); });
|
|
16
19
|
i0.ɵɵelementEnd();
|
|
17
20
|
} if (rf & 2) {
|
|
18
21
|
const ctx_r1 = i0.ɵɵnextContext();
|
|
19
22
|
i0.ɵɵproperty("Provider", ctx_r1.Provider)("visible", ctx_r1.showListManagementDialog)("config", ctx_r1.listManagementConfig);
|
|
20
23
|
} }
|
|
24
|
+
function GridViewRendererComponent_Conditional_4_Conditional_11_Template(rf, ctx) { if (rf & 1) {
|
|
25
|
+
i0.ɵɵelementStart(0, "p", 12);
|
|
26
|
+
i0.ɵɵtext(1);
|
|
27
|
+
i0.ɵɵelementEnd();
|
|
28
|
+
} if (rf & 2) {
|
|
29
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
30
|
+
i0.ɵɵadvance();
|
|
31
|
+
i0.ɵɵtextInterpolate(ctx_r1.mergeState.DependencyNote);
|
|
32
|
+
} }
|
|
33
|
+
function GridViewRendererComponent_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
34
|
+
const _r3 = i0.ɵɵgetCurrentView();
|
|
35
|
+
i0.ɵɵelementStart(0, "mj-dialog", 7);
|
|
36
|
+
i0.ɵɵlistener("Close", function GridViewRendererComponent_Conditional_4_Template_mj_dialog_Close_0_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onMergeCancelled()); });
|
|
37
|
+
i0.ɵɵelementStart(1, "div", 8)(2, "span");
|
|
38
|
+
i0.ɵɵtext(3, "Keep");
|
|
39
|
+
i0.ɵɵelementEnd();
|
|
40
|
+
i0.ɵɵelementStart(4, "button", 9);
|
|
41
|
+
i0.ɵɵlistener("click", function GridViewRendererComponent_Conditional_4_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onSurvivorChange("left")); });
|
|
42
|
+
i0.ɵɵtext(5);
|
|
43
|
+
i0.ɵɵelementEnd();
|
|
44
|
+
i0.ɵɵelementStart(6, "button", 9);
|
|
45
|
+
i0.ɵɵlistener("click", function GridViewRendererComponent_Conditional_4_Template_button_click_6_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onSurvivorChange("right")); });
|
|
46
|
+
i0.ɵɵtext(7);
|
|
47
|
+
i0.ɵɵelementEnd();
|
|
48
|
+
i0.ɵɵelementStart(8, "span", 10);
|
|
49
|
+
i0.ɵɵtext(9);
|
|
50
|
+
i0.ɵɵelementEnd()();
|
|
51
|
+
i0.ɵɵelementStart(10, "mj-record-merge-panel", 11);
|
|
52
|
+
i0.ɵɵlistener("MergeConfirmed", function GridViewRendererComponent_Conditional_4_Template_mj_record_merge_panel_MergeConfirmed_10_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onMergeConfirmed($event)); })("MergeCancelled", function GridViewRendererComponent_Conditional_4_Template_mj_record_merge_panel_MergeCancelled_10_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onMergeCancelled()); });
|
|
53
|
+
i0.ɵɵelementEnd();
|
|
54
|
+
i0.ɵɵconditionalCreate(11, GridViewRendererComponent_Conditional_4_Conditional_11_Template, 2, 1, "p", 12);
|
|
55
|
+
i0.ɵɵelementEnd();
|
|
56
|
+
} if (rf & 2) {
|
|
57
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
58
|
+
i0.ɵɵproperty("Visible", true)("Closeable", !ctx_r1.mergeState.IsMerging);
|
|
59
|
+
i0.ɵɵadvance(4);
|
|
60
|
+
i0.ɵɵproperty("variant", ctx_r1.mergeState.Config.SurvivorSide === "left" ? "primary" : "secondary")("disabled", ctx_r1.mergeState.IsMerging);
|
|
61
|
+
i0.ɵɵattribute("aria-checked", ctx_r1.mergeState.Config.SurvivorSide === "left");
|
|
62
|
+
i0.ɵɵadvance();
|
|
63
|
+
i0.ɵɵtextInterpolate(ctx_r1.mergeState.Config.LeftLabel);
|
|
64
|
+
i0.ɵɵadvance();
|
|
65
|
+
i0.ɵɵproperty("variant", ctx_r1.mergeState.Config.SurvivorSide === "right" ? "primary" : "secondary")("disabled", ctx_r1.mergeState.IsMerging);
|
|
66
|
+
i0.ɵɵattribute("aria-checked", ctx_r1.mergeState.Config.SurvivorSide === "right");
|
|
67
|
+
i0.ɵɵadvance();
|
|
68
|
+
i0.ɵɵtextInterpolate(ctx_r1.mergeState.Config.RightLabel);
|
|
69
|
+
i0.ɵɵadvance(2);
|
|
70
|
+
i0.ɵɵtextInterpolate1("", ctx_r1.mergeLoserLabel, " will be deleted; its linked records move to the record you keep.");
|
|
71
|
+
i0.ɵɵadvance();
|
|
72
|
+
i0.ɵɵproperty("Fields", ctx_r1.mergeState.Fields)("Config", ctx_r1.mergeState.Config)("MergeEnabled", !ctx_r1.mergeState.IsMerging)("IsMerging", ctx_r1.mergeState.IsMerging);
|
|
73
|
+
i0.ɵɵadvance();
|
|
74
|
+
i0.ɵɵconditional(ctx_r1.mergeState.DependencyNote ? 11 : -1);
|
|
75
|
+
} }
|
|
76
|
+
function GridViewRendererComponent_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
77
|
+
const _r4 = i0.ɵɵgetCurrentView();
|
|
78
|
+
i0.ɵɵelementStart(0, "div", 5)(1, "span", 13);
|
|
79
|
+
i0.ɵɵtext(2);
|
|
80
|
+
i0.ɵɵelementEnd();
|
|
81
|
+
i0.ɵɵelementStart(3, "button", 14);
|
|
82
|
+
i0.ɵɵlistener("click", function GridViewRendererComponent_Conditional_5_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onDismissMergeNotice()); });
|
|
83
|
+
i0.ɵɵelement(4, "i", 15);
|
|
84
|
+
i0.ɵɵelementEnd()();
|
|
85
|
+
} if (rf & 2) {
|
|
86
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
87
|
+
i0.ɵɵadvance(2);
|
|
88
|
+
i0.ɵɵtextInterpolate(ctx_r1.mergeNotice);
|
|
89
|
+
} }
|
|
21
90
|
/**
|
|
22
91
|
* GridViewRendererComponent
|
|
23
92
|
* -------------------------
|
|
@@ -87,7 +156,15 @@ export class GridViewRendererComponent extends BaseAngularComponent {
|
|
|
87
156
|
* Optional full-result-set fetcher supplied by the host, forwarded to the grid's ExportDataProvider
|
|
88
157
|
* so exports cover every matching row rather than just the loaded page (bug C1).
|
|
89
158
|
*/
|
|
90
|
-
|
|
159
|
+
ExportDataProvider = null;
|
|
160
|
+
/** @deprecated Use {@link ExportDataProvider}. */
|
|
161
|
+
set exportDataProvider(value) {
|
|
162
|
+
this.ExportDataProvider = value;
|
|
163
|
+
}
|
|
164
|
+
/** @deprecated Use {@link ExportDataProvider}. */
|
|
165
|
+
get exportDataProvider() {
|
|
166
|
+
return this.ExportDataProvider;
|
|
167
|
+
}
|
|
91
168
|
/** Primary-key string of the currently selected record, if any. */
|
|
92
169
|
selectedRecordId = null;
|
|
93
170
|
/** Active filter text — passed through for the grid's cell highlighting. */
|
|
@@ -175,38 +252,62 @@ export class GridViewRendererComponent extends BaseAngularComponent {
|
|
|
175
252
|
// Effective binding accessors — seed grid inputs from config + defaults
|
|
176
253
|
// ================================================================
|
|
177
254
|
/** Effective toolbar visibility — defaults to `true` when not set in config. */
|
|
178
|
-
get
|
|
255
|
+
get EffectiveShowToolbar() {
|
|
179
256
|
return this.config.showToolbar ?? true;
|
|
180
257
|
}
|
|
258
|
+
/** @deprecated Use {@link EffectiveShowToolbar}. */
|
|
259
|
+
get effectiveShowToolbar() {
|
|
260
|
+
return this.EffectiveShowToolbar;
|
|
261
|
+
}
|
|
181
262
|
/**
|
|
182
263
|
* Grid search defaults OFF when hosted in entity-viewer. The container already owns
|
|
183
264
|
* `filterText` ("Filter records..."). Opt in with `toolbarConfig.showSearch: true`.
|
|
184
265
|
*/
|
|
185
|
-
get
|
|
266
|
+
get EffectiveShowSearch() {
|
|
186
267
|
return this.config.toolbarConfig?.showSearch ?? false;
|
|
187
268
|
}
|
|
269
|
+
/** @deprecated Use {@link EffectiveShowSearch}. */
|
|
270
|
+
get effectiveShowSearch() {
|
|
271
|
+
return this.EffectiveShowSearch;
|
|
272
|
+
}
|
|
188
273
|
/** Effective selection mode — defaults to `'checkbox'` when not set in config. */
|
|
189
|
-
get
|
|
274
|
+
get EffectiveSelectionMode() {
|
|
190
275
|
return this.config.selectionMode ?? 'checkbox';
|
|
191
276
|
}
|
|
277
|
+
/** @deprecated Use {@link EffectiveSelectionMode}. */
|
|
278
|
+
get effectiveSelectionMode() {
|
|
279
|
+
return this.EffectiveSelectionMode;
|
|
280
|
+
}
|
|
192
281
|
/** Effective add-to-list button visibility — defaults to `true` when not set in config. */
|
|
193
|
-
get
|
|
282
|
+
get EffectiveShowAddToListButton() {
|
|
194
283
|
return this.config.showAddToListButton ?? true;
|
|
195
284
|
}
|
|
285
|
+
/** @deprecated Use {@link EffectiveShowAddToListButton}. */
|
|
286
|
+
get effectiveShowAddToListButton() {
|
|
287
|
+
return this.EffectiveShowAddToListButton;
|
|
288
|
+
}
|
|
196
289
|
/** Effective pager visibility — defaults to `true` when not set in config. */
|
|
197
|
-
get
|
|
290
|
+
get EffectiveShowPager() {
|
|
198
291
|
return this.config.showPager ?? true;
|
|
199
292
|
}
|
|
293
|
+
/** @deprecated Use {@link EffectiveShowPager}. */
|
|
294
|
+
get effectiveShowPager() {
|
|
295
|
+
return this.EffectiveShowPager;
|
|
296
|
+
}
|
|
200
297
|
/** Effective page size — config wins, else the generic `pageSize` input, else the grid's own default. */
|
|
201
|
-
get
|
|
298
|
+
get EffectivePageSize() {
|
|
202
299
|
return this.config.pageSize ?? this.pageSize ?? 100;
|
|
203
300
|
}
|
|
301
|
+
/** @deprecated Use {@link EffectivePageSize}. */
|
|
302
|
+
get effectivePageSize() {
|
|
303
|
+
return this.EffectivePageSize;
|
|
304
|
+
}
|
|
204
305
|
/**
|
|
205
306
|
* Effective RunViewParams for the grid. Uses `config.params` when supplied; otherwise builds a
|
|
206
307
|
* minimal dynamic-view params object from {@link entity}. Returns `null` when no entity is known
|
|
207
308
|
* yet (the grid renders empty until the host provides one).
|
|
208
309
|
*/
|
|
209
|
-
get
|
|
310
|
+
get EffectiveParams() {
|
|
210
311
|
if (this.config.params) {
|
|
211
312
|
return this.config.params;
|
|
212
313
|
}
|
|
@@ -215,6 +316,10 @@ export class GridViewRendererComponent extends BaseAngularComponent {
|
|
|
215
316
|
}
|
|
216
317
|
return null;
|
|
217
318
|
}
|
|
319
|
+
/** @deprecated Use {@link EffectiveParams}. */
|
|
320
|
+
get effectiveParams() {
|
|
321
|
+
return this.EffectiveParams;
|
|
322
|
+
}
|
|
218
323
|
// ================================================================
|
|
219
324
|
// Navigation + generic coordination output mapping
|
|
220
325
|
// ================================================================
|
|
@@ -222,24 +327,32 @@ export class GridViewRendererComponent extends BaseAngularComponent {
|
|
|
222
327
|
* Row single-click → {@link recordSelected}. Extracts the raw record (`event.row`) so the host
|
|
223
328
|
* builds the composite key itself, matching the other renderers.
|
|
224
329
|
*/
|
|
225
|
-
|
|
330
|
+
OnAfterRowClick(event) {
|
|
226
331
|
if (event.row) {
|
|
227
332
|
this.recordSelected.emit(event.row);
|
|
228
333
|
}
|
|
229
334
|
}
|
|
335
|
+
/** @deprecated Use {@link OnAfterRowClick}. */
|
|
336
|
+
onAfterRowClick(event) {
|
|
337
|
+
return this.OnAfterRowClick(event);
|
|
338
|
+
}
|
|
230
339
|
/** Row double-click → {@link recordOpened} (NAVIGATION), emitting the raw record object. */
|
|
231
|
-
|
|
340
|
+
OnAfterRowDoubleClick(event) {
|
|
232
341
|
if (event.row) {
|
|
233
342
|
this.recordOpened.emit(event.row);
|
|
234
343
|
}
|
|
235
344
|
}
|
|
345
|
+
/** @deprecated Use {@link OnAfterRowDoubleClick}. */
|
|
346
|
+
onAfterRowDoubleClick(event) {
|
|
347
|
+
return this.OnAfterRowDoubleClick(event);
|
|
348
|
+
}
|
|
236
349
|
/**
|
|
237
350
|
* Sort change → both generic coordination channels:
|
|
238
351
|
* 1. {@link dataRequest} with the generic `sort` shape so the host re-loads with a new OrderBy.
|
|
239
352
|
* 2. persists the sort into `config.gridState.sortSettings` and emits {@link configChanged} so
|
|
240
353
|
* the grid's sort survives view reloads via the opaque config channel.
|
|
241
354
|
*/
|
|
242
|
-
|
|
355
|
+
OnAfterSort(event) {
|
|
243
356
|
const sort = (event.newSortState ?? []).map((s) => ({ field: s.field, direction: s.direction }));
|
|
244
357
|
this.dataRequest.emit({ sort });
|
|
245
358
|
const gridState = { ...(this.config.gridState ?? {}) };
|
|
@@ -247,47 +360,71 @@ export class GridViewRendererComponent extends BaseAngularComponent {
|
|
|
247
360
|
this.config = { ...this.config, gridState };
|
|
248
361
|
this.configChanged.emit(this.config);
|
|
249
362
|
}
|
|
363
|
+
/** @deprecated Use {@link OnAfterSort}. */
|
|
364
|
+
onAfterSort(event) {
|
|
365
|
+
return this.OnAfterSort(event);
|
|
366
|
+
}
|
|
250
367
|
/**
|
|
251
368
|
* Generic grid-state change (column resize / reorder / visibility) → merge the grid's updated
|
|
252
369
|
* {@link ViewGridState} into the opaque config and emit {@link configChanged}; the host persists
|
|
253
370
|
* the blob verbatim. No `dataRequest` here — column changes don't alter the loaded record set.
|
|
254
371
|
*/
|
|
255
|
-
|
|
372
|
+
OnGridStateChanged(event) {
|
|
256
373
|
this.config = { ...this.config, gridState: event.gridState };
|
|
257
374
|
this.configChanged.emit(this.config);
|
|
258
375
|
}
|
|
376
|
+
/** @deprecated Use {@link OnGridStateChanged}. */
|
|
377
|
+
onGridStateChanged(event) {
|
|
378
|
+
return this.OnGridStateChanged(event);
|
|
379
|
+
}
|
|
259
380
|
/**
|
|
260
381
|
* Selection change → kept INTERNAL. The grid drives its own add-to-list from the selection, so the
|
|
261
382
|
* wrapper has no need to surface it. We intentionally do not bubble selection anywhere.
|
|
262
383
|
*/
|
|
263
|
-
|
|
384
|
+
OnSelectionChange(_selectedKeys) {
|
|
264
385
|
// No-op: selection is internal to the grid + this wrapper's add-to-list. It never bubbles up.
|
|
265
386
|
}
|
|
387
|
+
/** @deprecated Use {@link OnSelectionChange}. */
|
|
388
|
+
onSelectionChange(_selectedKeys) {
|
|
389
|
+
return this.OnSelectionChange(_selectedKeys);
|
|
390
|
+
}
|
|
266
391
|
/**
|
|
267
392
|
* Foreign-key link click → {@link openRelatedRecordRequested} (NAVIGATION). Maps the grid's
|
|
268
393
|
* {@link ForeignKeyClickEvent} (which carries the related entity + the FK value) onto the generic
|
|
269
394
|
* {@link ViewRelatedRecordNavigation} the container forwards to the outer app for routing. The
|
|
270
395
|
* related entity name is taken from the event when present, else resolved from the metadata by ID.
|
|
271
396
|
*/
|
|
272
|
-
|
|
397
|
+
OnForeignKeyClick(event) {
|
|
273
398
|
const entityName = event.relatedEntityName ?? this.resolveEntityNameById(event.relatedEntityId);
|
|
274
399
|
if (!entityName) {
|
|
275
400
|
return;
|
|
276
401
|
}
|
|
277
402
|
this.openRelatedRecordRequested.emit({ entityName, recordKey: event.recordId });
|
|
278
403
|
}
|
|
404
|
+
/** @deprecated Use {@link OnForeignKeyClick}. */
|
|
405
|
+
onForeignKeyClick(event) {
|
|
406
|
+
return this.OnForeignKeyClick(event);
|
|
407
|
+
}
|
|
279
408
|
/** New button → {@link createRecordRequested} (NAVIGATION) — opening the create form is routing. */
|
|
280
|
-
|
|
409
|
+
OnNewButtonClick() {
|
|
281
410
|
this.createRecordRequested.emit();
|
|
282
411
|
}
|
|
412
|
+
/** @deprecated Use {@link OnNewButtonClick}. */
|
|
413
|
+
onNewButtonClick() {
|
|
414
|
+
return this.OnNewButtonClick();
|
|
415
|
+
}
|
|
283
416
|
/**
|
|
284
417
|
* Pager navigation → generic {@link dataRequest}. Maps the grid's {@link PageChangeEvent}
|
|
285
418
|
* (`PageNumber` is already 1-based, `PageSize`) onto the generic `{ page, pageSize }` shape the
|
|
286
419
|
* host honors against its own RunView.
|
|
287
420
|
*/
|
|
288
|
-
|
|
421
|
+
OnPageChange(event) {
|
|
289
422
|
this.dataRequest.emit({ page: event.PageNumber, pageSize: event.PageSize });
|
|
290
423
|
}
|
|
424
|
+
/** @deprecated Use {@link OnPageChange}. */
|
|
425
|
+
onPageChange(event) {
|
|
426
|
+
return this.OnPageChange(event);
|
|
427
|
+
}
|
|
291
428
|
// ================================================================
|
|
292
429
|
// Self-contained: Refresh
|
|
293
430
|
// ================================================================
|
|
@@ -296,9 +433,13 @@ export class GridViewRendererComponent extends BaseAngularComponent {
|
|
|
296
433
|
* generic container ↔ plug-in coordination (a data-access request), NOT a feature event surfaced
|
|
297
434
|
* to the outer app.
|
|
298
435
|
*/
|
|
299
|
-
|
|
436
|
+
OnRefreshButtonClick() {
|
|
300
437
|
this.dataRequest.emit(this.currentPageDataRequest());
|
|
301
438
|
}
|
|
439
|
+
/** @deprecated Use {@link OnRefreshButtonClick}. */
|
|
440
|
+
onRefreshButtonClick() {
|
|
441
|
+
return this.OnRefreshButtonClick();
|
|
442
|
+
}
|
|
302
443
|
// ================================================================
|
|
303
444
|
// Export: handled entirely by mj-entity-data-grid (its onExportClick opens its own export dialog
|
|
304
445
|
// via ExportService). This wrapper intentionally does NOT host an export dialog — doing so produced
|
|
@@ -313,7 +454,7 @@ export class GridViewRendererComponent extends BaseAngularComponent {
|
|
|
313
454
|
* changes itself — nothing bubbles up. Mirrors the config construction the legacy host did in
|
|
314
455
|
* `data-explorer-dashboard`'s `onAddToListRequested()`.
|
|
315
456
|
*/
|
|
316
|
-
|
|
457
|
+
OnAddToListRequested(event) {
|
|
317
458
|
const entity = event.entityInfo ?? this.entity;
|
|
318
459
|
if (!entity || !event.records || event.records.length === 0) {
|
|
319
460
|
return;
|
|
@@ -339,18 +480,30 @@ export class GridViewRendererComponent extends BaseAngularComponent {
|
|
|
339
480
|
this.showListManagementDialog = true;
|
|
340
481
|
this.cdr.detectChanges();
|
|
341
482
|
}
|
|
483
|
+
/** @deprecated Use {@link OnAddToListRequested}. */
|
|
484
|
+
onAddToListRequested(event) {
|
|
485
|
+
return this.OnAddToListRequested(event);
|
|
486
|
+
}
|
|
342
487
|
/** List-management dialog completed (membership changes applied) → tear down state. */
|
|
343
|
-
|
|
488
|
+
OnListManagementComplete(_result) {
|
|
344
489
|
this.showListManagementDialog = false;
|
|
345
490
|
this.listManagementConfig = null;
|
|
346
491
|
this.cdr.detectChanges();
|
|
347
492
|
}
|
|
493
|
+
/** @deprecated Use {@link OnListManagementComplete}. */
|
|
494
|
+
onListManagementComplete(_result) {
|
|
495
|
+
return this.OnListManagementComplete(_result);
|
|
496
|
+
}
|
|
348
497
|
/** List-management dialog cancelled → tear down state. */
|
|
349
|
-
|
|
498
|
+
OnListManagementCancel() {
|
|
350
499
|
this.showListManagementDialog = false;
|
|
351
500
|
this.listManagementConfig = null;
|
|
352
501
|
this.cdr.detectChanges();
|
|
353
502
|
}
|
|
503
|
+
/** @deprecated Use {@link OnListManagementCancel}. */
|
|
504
|
+
onListManagementCancel() {
|
|
505
|
+
return this.OnListManagementCancel();
|
|
506
|
+
}
|
|
354
507
|
/**
|
|
355
508
|
* Resolve a human-friendly display name for a record using the entity's NameField, falling back to
|
|
356
509
|
* the composite-key string. Mirrors the legacy host's display-name resolution.
|
|
@@ -362,7 +515,7 @@ export class GridViewRendererComponent extends BaseAngularComponent {
|
|
|
362
515
|
return String(nameValue);
|
|
363
516
|
}
|
|
364
517
|
}
|
|
365
|
-
return
|
|
518
|
+
return BuildPkString(record, entity) || 'Unknown';
|
|
366
519
|
}
|
|
367
520
|
/**
|
|
368
521
|
* Build the record-id strings used by list membership matching, in the compact form `MJ: List Details`
|
|
@@ -385,7 +538,7 @@ export class GridViewRendererComponent extends BaseAngularComponent {
|
|
|
385
538
|
* Delete button → stage the records and open the Generic confirm dialog. The actual delete happens
|
|
386
539
|
* in {@link onDeleteConfirmed} after the user confirms — nothing bubbles up.
|
|
387
540
|
*/
|
|
388
|
-
|
|
541
|
+
OnDeleteButtonClick(records) {
|
|
389
542
|
if (!records || records.length === 0) {
|
|
390
543
|
return;
|
|
391
544
|
}
|
|
@@ -395,13 +548,17 @@ export class GridViewRendererComponent extends BaseAngularComponent {
|
|
|
395
548
|
this.showDeleteConfirm = true;
|
|
396
549
|
this.cdr.detectChanges();
|
|
397
550
|
}
|
|
551
|
+
/** @deprecated Use {@link OnDeleteButtonClick}. */
|
|
552
|
+
onDeleteButtonClick(records) {
|
|
553
|
+
return this.OnDeleteButtonClick(records);
|
|
554
|
+
}
|
|
398
555
|
/**
|
|
399
556
|
* Delete confirmed → delete each staged record through the MJ entity layer
|
|
400
557
|
* (`ProviderToUse.GetEntityObject(name, compositeKey, user)` → `Delete`), checking the boolean
|
|
401
558
|
* result and surfacing failures via {@link LogError}. After deletion, re-request the current page
|
|
402
559
|
* so the host reloads. Self-contained — no feature event leaves the wrapper.
|
|
403
560
|
*/
|
|
404
|
-
async
|
|
561
|
+
async OnDeleteConfirmed() {
|
|
405
562
|
this.showDeleteConfirm = false;
|
|
406
563
|
const entity = this.entity;
|
|
407
564
|
const records = this.pendingDeleteRecords;
|
|
@@ -413,7 +570,7 @@ export class GridViewRendererComponent extends BaseAngularComponent {
|
|
|
413
570
|
const provider = this.ProviderToUse;
|
|
414
571
|
const user = provider.CurrentUser;
|
|
415
572
|
for (const record of records) {
|
|
416
|
-
const key =
|
|
573
|
+
const key = BuildCompositeKey(record, entity);
|
|
417
574
|
try {
|
|
418
575
|
// The (name, key, user) overload instantiates AND loads in one call (throws if it can't load).
|
|
419
576
|
const obj = await provider.GetEntityObject(entity.Name, key, user);
|
|
@@ -435,12 +592,344 @@ export class GridViewRendererComponent extends BaseAngularComponent {
|
|
|
435
592
|
}
|
|
436
593
|
this.cdr.detectChanges();
|
|
437
594
|
}
|
|
595
|
+
/** @deprecated Use {@link OnDeleteConfirmed}. */
|
|
596
|
+
async onDeleteConfirmed() {
|
|
597
|
+
return this.OnDeleteConfirmed();
|
|
598
|
+
}
|
|
438
599
|
/** Delete cancelled → discard the staged records and close the confirm dialog. */
|
|
439
|
-
|
|
600
|
+
OnDeleteCancelled() {
|
|
440
601
|
this.showDeleteConfirm = false;
|
|
441
602
|
this.pendingDeleteRecords = [];
|
|
442
603
|
this.cdr.detectChanges();
|
|
443
604
|
}
|
|
605
|
+
/** @deprecated Use {@link OnDeleteCancelled}. */
|
|
606
|
+
onDeleteCancelled() {
|
|
607
|
+
return this.OnDeleteCancelled();
|
|
608
|
+
}
|
|
609
|
+
// ================================================================
|
|
610
|
+
// Record merge
|
|
611
|
+
// ================================================================
|
|
612
|
+
/**
|
|
613
|
+
* The in-flight merge, or null when the panel is closed. Holds everything the panel needs
|
|
614
|
+
* plus the dependency preview, so the panel itself stays a pure presentation component.
|
|
615
|
+
*/
|
|
616
|
+
mergeState = null;
|
|
617
|
+
/** A one-line status shown under the grid — why a merge was refused, or how one ended. */
|
|
618
|
+
mergeNotice = null;
|
|
619
|
+
/** Auto-dismiss timer for {@link mergeNotice}; cleared whenever the notice is replaced. */
|
|
620
|
+
mergeNoticeTimer = null;
|
|
621
|
+
/**
|
|
622
|
+
* How long a merge notice stays up before dismissing itself. The banner is fixed-position over
|
|
623
|
+
* the whole application, so leaving it until the next merge pins it across navigation and every
|
|
624
|
+
* other record the user visits afterwards.
|
|
625
|
+
*/
|
|
626
|
+
static MERGE_NOTICE_TIMEOUT_MS = 8000;
|
|
627
|
+
comparison = inject(RecordComparisonService);
|
|
628
|
+
ngOnDestroy() {
|
|
629
|
+
// The notice's auto-dismiss outlives the view otherwise, and fires detectChanges on a
|
|
630
|
+
// destroyed component.
|
|
631
|
+
if (this.mergeNoticeTimer) {
|
|
632
|
+
clearTimeout(this.mergeNoticeTimer);
|
|
633
|
+
this.mergeNoticeTimer = null;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
/** Dismiss the merge notice from its own close button. */
|
|
637
|
+
OnDismissMergeNotice() {
|
|
638
|
+
this.setMergeNotice(null);
|
|
639
|
+
this.cdr.detectChanges();
|
|
640
|
+
}
|
|
641
|
+
/** @deprecated Use {@link OnDismissMergeNotice}. */
|
|
642
|
+
onDismissMergeNotice() {
|
|
643
|
+
return this.OnDismissMergeNotice();
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Set (or clear) the merge notice, replacing any pending auto-dismiss. Every write to
|
|
647
|
+
* `mergeNotice` goes through here so a notice cannot outlive its timer or strand an old one.
|
|
648
|
+
*/
|
|
649
|
+
setMergeNotice(message) {
|
|
650
|
+
if (this.mergeNoticeTimer) {
|
|
651
|
+
clearTimeout(this.mergeNoticeTimer);
|
|
652
|
+
this.mergeNoticeTimer = null;
|
|
653
|
+
}
|
|
654
|
+
this.mergeNotice = message;
|
|
655
|
+
if (message) {
|
|
656
|
+
this.mergeNoticeTimer = setTimeout(() => {
|
|
657
|
+
this.mergeNoticeTimer = null;
|
|
658
|
+
this.mergeNotice = null;
|
|
659
|
+
this.cdr.detectChanges();
|
|
660
|
+
}, GridViewRendererComponent.MERGE_NOTICE_TIMEOUT_MS);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Show the Merge button only where merging can actually succeed: the entity opts in and the
|
|
665
|
+
* user can both update the survivor and delete the loser.
|
|
666
|
+
*/
|
|
667
|
+
get effectiveShowMergeButton() {
|
|
668
|
+
const entity = this.entity;
|
|
669
|
+
const user = this.ProviderToUse?.CurrentUser;
|
|
670
|
+
if (!entity?.AllowRecordMerge || !user)
|
|
671
|
+
return false;
|
|
672
|
+
const permissions = entity.GetUserPermisions(user);
|
|
673
|
+
return !!permissions && permissions.CanUpdate && permissions.CanDelete;
|
|
674
|
+
}
|
|
675
|
+
/** The record that will be deleted, named so the user sees the choice before confirming. */
|
|
676
|
+
get mergeLoserLabel() {
|
|
677
|
+
const config = this.mergeState?.Config;
|
|
678
|
+
if (!config)
|
|
679
|
+
return '';
|
|
680
|
+
return config.SurvivorSide === 'left' ? config.RightLabel : config.LeftLabel;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* The grid asked to merge the selected rows. Compare them, preview what will move, and open
|
|
684
|
+
* the panel — nothing is written until the user confirms.
|
|
685
|
+
*/
|
|
686
|
+
async OnMergeRequested(event) {
|
|
687
|
+
this.setMergeNotice(null);
|
|
688
|
+
try {
|
|
689
|
+
await this.prepareMerge(event);
|
|
690
|
+
}
|
|
691
|
+
catch (err) {
|
|
692
|
+
// A transport failure in any pre-flight query must not leave the user with nothing.
|
|
693
|
+
this.setMergeNotice(`Could not prepare the merge: ${err instanceof Error ? err.message : String(err)}`);
|
|
694
|
+
this.cdr.detectChanges();
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
/** @deprecated Use {@link OnMergeRequested}. */
|
|
698
|
+
async onMergeRequested(event) {
|
|
699
|
+
return this.OnMergeRequested(event);
|
|
700
|
+
}
|
|
701
|
+
/** The user chose which record survives; differing fields default to that record's values. */
|
|
702
|
+
OnSurvivorChange(side) {
|
|
703
|
+
if (!this.mergeState || this.mergeState.IsMerging)
|
|
704
|
+
return;
|
|
705
|
+
this.mergeState.Config = { ...this.mergeState.Config, SurvivorSide: side };
|
|
706
|
+
for (const field of this.mergeState.Fields) {
|
|
707
|
+
if (!field.IsReadOnly)
|
|
708
|
+
field.SelectedSide = side;
|
|
709
|
+
}
|
|
710
|
+
this.cdr.detectChanges();
|
|
711
|
+
}
|
|
712
|
+
/** @deprecated Use {@link OnSurvivorChange}. */
|
|
713
|
+
onSurvivorChange(side) {
|
|
714
|
+
return this.OnSurvivorChange(side);
|
|
715
|
+
}
|
|
716
|
+
async prepareMerge(event) {
|
|
717
|
+
const entity = event.entityInfo ?? this.entity;
|
|
718
|
+
if (!entity)
|
|
719
|
+
return;
|
|
720
|
+
// The merge panel is two-sided by construction, so n > 2 has nowhere to render.
|
|
721
|
+
if (event.records.length !== 2) {
|
|
722
|
+
this.setMergeNotice('Select exactly two records to merge.');
|
|
723
|
+
this.cdr.detectChanges();
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
// Composite-key safe: the grid renders arbitrary entities, so never assume one key column.
|
|
727
|
+
const keys = event.records.map(r => CompositeKey.FromEntityRecord(entity, r));
|
|
728
|
+
if (keys.some(k => k.KeyValuePairs.length === 0 || k.KeyValuePairs.some(kv => kv.Value == null || kv.Value === ''))) {
|
|
729
|
+
this.setMergeNotice('Could not read the primary key of the selected records.');
|
|
730
|
+
this.cdr.detectChanges();
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
// The merge panel's config is string-keyed, so keys travel through it as compact segments.
|
|
734
|
+
const [left, right] = keys.map(k => k.ToCompactURLSegment());
|
|
735
|
+
// Subtype records: the merge re-points only the keys that target this entity, then the loser's
|
|
736
|
+
// delete follows the shared key into the parent row, whose own references never moved.
|
|
737
|
+
if (entity.ParentID) {
|
|
738
|
+
this.setMergeNotice(`${entity.DisplayName} records extend a parent type; merging subtype records is not supported yet.`);
|
|
739
|
+
this.cdr.detectChanges();
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
const childRows = await this.hasIsAChildRows(entity, keys);
|
|
743
|
+
if (childRows === null) {
|
|
744
|
+
this.setMergeNotice('Could not verify whether the selected records have subtype rows, so the merge was not started.');
|
|
745
|
+
this.cdr.detectChanges();
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (childRows) {
|
|
749
|
+
this.setMergeNotice(`${entity.DisplayName} records that another app extends (shared-key subtype rows) cannot be merged yet — ` +
|
|
750
|
+
'the merge would collide on the shared key. Merge them from the extending app instead.');
|
|
751
|
+
this.cdr.detectChanges();
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
const result = await this.comparison.GetRecordComparison({
|
|
755
|
+
EntityName: entity.Name,
|
|
756
|
+
Keys: keys.map(k => ({
|
|
757
|
+
KeyValuePairs: k.KeyValuePairs.map(kv => ({ FieldName: kv.FieldName, Value: String(kv.Value) })),
|
|
758
|
+
})),
|
|
759
|
+
}, this.ProviderToUse);
|
|
760
|
+
if (!result.Success || !result.Output) {
|
|
761
|
+
this.setMergeNotice(result.ErrorMessage ?? 'Could not compare the selected records.');
|
|
762
|
+
this.cdr.detectChanges();
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
this.mergeState = {
|
|
766
|
+
Config: {
|
|
767
|
+
EntityName: entity.Name,
|
|
768
|
+
LeftRecordID: left,
|
|
769
|
+
RightRecordID: right,
|
|
770
|
+
SurvivorSide: 'left',
|
|
771
|
+
LeftLabel: this.labelFor(event.records[0], entity, keys[0]),
|
|
772
|
+
RightLabel: this.labelFor(event.records[1], entity, keys[1]),
|
|
773
|
+
},
|
|
774
|
+
Fields: result.Output.Fields.map(delta => ({
|
|
775
|
+
FieldName: delta.FieldName,
|
|
776
|
+
DisplayLabel: delta.DisplayName,
|
|
777
|
+
LeftValue: delta.Cells[0]?.Value,
|
|
778
|
+
RightValue: delta.Cells[1]?.Value,
|
|
779
|
+
HasConflict: delta.Differs,
|
|
780
|
+
SelectedSide: 'left',
|
|
781
|
+
// What the ORM will refuse to write — keys, AllowUpdateAPI = 0, timestamps. A field the
|
|
782
|
+
// metadata does not know cannot be written either.
|
|
783
|
+
IsReadOnly: entity.FieldByName(delta.FieldName)?.ReadOnly ?? true,
|
|
784
|
+
DataType: 'string',
|
|
785
|
+
})),
|
|
786
|
+
IsMerging: false,
|
|
787
|
+
DependencyNote: await this.describeDependencies(entity, keys),
|
|
788
|
+
Entity: entity,
|
|
789
|
+
Keys: keys,
|
|
790
|
+
};
|
|
791
|
+
this.cdr.detectChanges();
|
|
792
|
+
}
|
|
793
|
+
/** The user confirmed: merge, then reload the page so the loser disappears from the grid. */
|
|
794
|
+
async OnMergeConfirmed(event) {
|
|
795
|
+
if (!this.mergeState)
|
|
796
|
+
return;
|
|
797
|
+
this.mergeState.IsMerging = true;
|
|
798
|
+
this.cdr.detectChanges();
|
|
799
|
+
const survivorIsLeft = event.Config.SurvivorSide === 'left';
|
|
800
|
+
const [leftKey, rightKey] = this.mergeState.Keys;
|
|
801
|
+
const survivor = survivorIsLeft ? leftKey : rightKey;
|
|
802
|
+
const loser = survivorIsLeft ? rightKey : leftKey;
|
|
803
|
+
const request = new RecordMergeRequest();
|
|
804
|
+
request.EntityName = event.Config.EntityName;
|
|
805
|
+
request.SurvivingRecordCompositeKey = survivor;
|
|
806
|
+
request.RecordsToMerge = [loser];
|
|
807
|
+
request.FieldMap = this.buildFieldMap(event, survivorIsLeft);
|
|
808
|
+
try {
|
|
809
|
+
const result = await this.ProviderToUse.MergeRecords(request);
|
|
810
|
+
this.mergeState = null;
|
|
811
|
+
this.setMergeNotice(result.Success ? 'Records merged.' : `Merge failed: ${result.OverallStatus}`);
|
|
812
|
+
if (result.Success) {
|
|
813
|
+
this.dataRequest.emit(this.currentPageDataRequest());
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
catch (err) {
|
|
817
|
+
this.mergeState = null;
|
|
818
|
+
this.setMergeNotice(`Merge failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
819
|
+
}
|
|
820
|
+
this.cdr.detectChanges();
|
|
821
|
+
}
|
|
822
|
+
/** @deprecated Use {@link OnMergeConfirmed}. */
|
|
823
|
+
async onMergeConfirmed(event) {
|
|
824
|
+
return this.OnMergeConfirmed(event);
|
|
825
|
+
}
|
|
826
|
+
/** Merge cancelled → close the panel, write nothing. */
|
|
827
|
+
OnMergeCancelled() {
|
|
828
|
+
this.mergeState = null;
|
|
829
|
+
this.cdr.detectChanges();
|
|
830
|
+
}
|
|
831
|
+
/** @deprecated Use {@link OnMergeCancelled}. */
|
|
832
|
+
onMergeCancelled() {
|
|
833
|
+
return this.OnMergeCancelled();
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Fields whose resolution has to travel, and only those: the survivor already holds the value
|
|
837
|
+
* for any field resolved to its own side.
|
|
838
|
+
*
|
|
839
|
+
* The distinction that matters is "the user did not change this" versus "the user chose the
|
|
840
|
+
* blank side". Both arrive as an absent value, so deciding from the value alone drops a real
|
|
841
|
+
* resolution — the survivor silently keeps its old value on the one operation where someone is
|
|
842
|
+
* reconciling two records field by field, and the loser is gone afterwards. So the skip is
|
|
843
|
+
* decided from WHICH SIDE was chosen, and a chosen blank is written through as null.
|
|
844
|
+
*/
|
|
845
|
+
buildFieldMap(event, survivorIsLeft) {
|
|
846
|
+
const map = [];
|
|
847
|
+
for (const field of event.ResolvedFields) {
|
|
848
|
+
if (field.IsReadOnly || !field.HasConflict)
|
|
849
|
+
continue;
|
|
850
|
+
// Resolved to the survivor's own side — nothing to write, whatever the value is.
|
|
851
|
+
if (field.SelectedSide !== 'custom' && (field.SelectedSide === 'left') === survivorIsLeft)
|
|
852
|
+
continue;
|
|
853
|
+
const chosen = field.SelectedSide === 'custom'
|
|
854
|
+
? field.CustomValue
|
|
855
|
+
: (survivorIsLeft ? field.RightValue : field.LeftValue);
|
|
856
|
+
// A chosen blank is a clear, not a no-op. Normalize undefined to null so the transport
|
|
857
|
+
// carries an explicit value rather than an absent one.
|
|
858
|
+
map.push({ FieldName: field.FieldName, Value: chosen === undefined ? null : chosen });
|
|
859
|
+
}
|
|
860
|
+
return map;
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* True when any of these records has a row in an IS-A child entity, null when that could not be
|
|
864
|
+
* determined (a child view the user cannot read answers `Success: false`, which must not pass
|
|
865
|
+
* for "no rows"). `MergeRecords` has no subtype handling: the child rows share the parent's key,
|
|
866
|
+
* so merging would collide. Refuse rather than half-merge.
|
|
867
|
+
*/
|
|
868
|
+
async hasIsAChildRows(entity, keys) {
|
|
869
|
+
const children = entity.ChildEntities;
|
|
870
|
+
if (children.length === 0)
|
|
871
|
+
return false;
|
|
872
|
+
// An IS-A child shares its parent's key SEMANTICALLY; nothing requires it to share the column
|
|
873
|
+
// NAME. `CompositeKey.ToWhereClause()` emits the parent's names, so filtering a child view
|
|
874
|
+
// with it fails wherever the names differ — and a failed probe refuses the merge, which would
|
|
875
|
+
// disable merge for that entity permanently. Pair each key's values onto the child's own PK
|
|
876
|
+
// columns instead, the way the platform's own child discovery does
|
|
877
|
+
// (SQLServerDataProvider.BuildChildDiscoverySQL).
|
|
878
|
+
const probes = children
|
|
879
|
+
.map(child => ({ child, filter: this.buildChildKeyFilter(child, keys) }))
|
|
880
|
+
.filter((probe) => probe.filter !== null);
|
|
881
|
+
if (probes.length === 0)
|
|
882
|
+
return false;
|
|
883
|
+
const results = await RunView.FromMetadataProvider(this.ProviderToUse).RunViews(probes.map(({ child, filter }) => ({
|
|
884
|
+
EntityName: child.Name,
|
|
885
|
+
ExtraFilter: filter,
|
|
886
|
+
Fields: child.PrimaryKeys.map(f => f.Name),
|
|
887
|
+
ResultType: 'simple',
|
|
888
|
+
MaxRows: 1,
|
|
889
|
+
})));
|
|
890
|
+
if (results.some(r => !r.Success))
|
|
891
|
+
return null;
|
|
892
|
+
return results.some(r => r.Results.length > 0);
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* `(childPk = v1) OR (childPk = v2)` against one child entity, built from the parent's key
|
|
896
|
+
* VALUES and the child's own key COLUMN NAMES, paired positionally because an IS-A child shares
|
|
897
|
+
* its parent's key by design. Null when the child's key arity differs, which means it does not
|
|
898
|
+
* share the key and cannot hold a subtype row for these records.
|
|
899
|
+
*/
|
|
900
|
+
buildChildKeyFilter(child, keys) {
|
|
901
|
+
const childPkFields = child.PrimaryKeys;
|
|
902
|
+
if (childPkFields.length === 0 || keys.some(k => k.KeyValuePairs.length !== childPkFields.length)) {
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
const clauses = keys.map(key => childPkFields
|
|
906
|
+
.map((field, i) => `[${field.Name}] = '${String(key.KeyValuePairs[i].Value ?? '').replace(/'/g, "''")}'`)
|
|
907
|
+
.join(' AND '));
|
|
908
|
+
return clauses.map(c => `(${c})`).join(' OR ');
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* How many linked records will move to the survivor — the thing users most want to know.
|
|
912
|
+
*
|
|
913
|
+
* Indexes `counts[0]` / `counts[1]` directly: `onMergeRequested` refuses anything other than
|
|
914
|
+
* exactly two records before reaching here, and the merge panel's config is two-sided by
|
|
915
|
+
* construction, so this is only ever called with a pair. Widen both together.
|
|
916
|
+
*/
|
|
917
|
+
async describeDependencies(entity, keys) {
|
|
918
|
+
try {
|
|
919
|
+
const counts = await Promise.all(keys.map(key => this.ProviderToUse.GetRecordDependencies(entity.Name, key)));
|
|
920
|
+
return `Linked records that would move to the survivor: ${counts[0].length} on the left, ${counts[1].length} on the right.`;
|
|
921
|
+
}
|
|
922
|
+
catch (err) {
|
|
923
|
+
LogError(`Could not load merge dependencies for ${entity.Name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
924
|
+
return null;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
/** A human label for a row — its name field when it has one, else its key. */
|
|
928
|
+
labelFor(row, entity, key) {
|
|
929
|
+
const nameField = entity.NameField?.Name;
|
|
930
|
+
const name = nameField ? String(row[nameField] ?? '') : '';
|
|
931
|
+
return name || key.ToCompactURLSegment();
|
|
932
|
+
}
|
|
444
933
|
// ================================================================
|
|
445
934
|
// Helpers
|
|
446
935
|
// ================================================================
|
|
@@ -473,21 +962,27 @@ export class GridViewRendererComponent extends BaseAngularComponent {
|
|
|
473
962
|
} if (rf & 2) {
|
|
474
963
|
let _t;
|
|
475
964
|
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.grid = _t.first);
|
|
476
|
-
} }, inputs: { entity: "entity", records: "records", exportDataProvider: "exportDataProvider", selectedRecordId: "selectedRecordId", filterText: "filterText", config: "config", AutoLoadEntityActions: "AutoLoadEntityActions", totalRecordCount: "totalRecordCount", page: "page", pageSize: "pageSize", isLoading: "isLoading" }, outputs: { recordSelected: "recordSelected", recordOpened: "recordOpened", configChanged: "configChanged", dataRequest: "dataRequest", configureRequested: "configureRequested", openRelatedRecordRequested: "openRelatedRecordRequested", createRecordRequested: "createRecordRequested" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls:
|
|
965
|
+
} }, inputs: { entity: "entity", records: "records", ExportDataProvider: "ExportDataProvider", exportDataProvider: "exportDataProvider", selectedRecordId: "selectedRecordId", filterText: "filterText", config: "config", AutoLoadEntityActions: "AutoLoadEntityActions", totalRecordCount: "totalRecordCount", page: "page", pageSize: "pageSize", isLoading: "isLoading" }, outputs: { recordSelected: "recordSelected", recordOpened: "recordOpened", configChanged: "configChanged", dataRequest: "dataRequest", configureRequested: "configureRequested", openRelatedRecordRequested: "openRelatedRecordRequested", createRecordRequested: "createRecordRequested" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 6, vars: 25, consts: [["grid", ""], [3, "AfterRowClick", "AfterRowDoubleClick", "AfterSort", "GridStateChanged", "SelectionChange", "NewButtonClick", "RefreshButtonClick", "DeleteButtonClick", "AddToListRequested", "ForeignKeyClick", "PageChange", "ManageColumnsRequested", "MergeRecordsRequested", "Provider", "Data", "ExportDataProvider", "Params", "FilterText", "GridState", "Height", "AllowLoad", "AutoLoadEntityActions", "ShowToolbar", "ShowSearch", "ToolbarConfig", "SelectionMode", "ShowAddToListButton", "ShowPager", "PageSize", "TotalRowCount", "PagerPageNumber", "ShowMergeButton"], [3, "Provider", "visible", "config"], ["Title", "Delete Records", "DetailMessage", "This action cannot be undone.", "ConfirmText", "Delete", "ConfirmStyle", "danger", "Icon", "fa-solid fa-trash", 3, "Confirmed", "Cancelled", "IsOpen", "Message"], ["Size", "lg", 3, "Visible", "Closeable"], ["role", "status", 1, "mj-ev-merge-notice"], [3, "complete", "cancel", "Provider", "visible", "config"], ["Size", "lg", 3, "Close", "Visible", "Closeable"], ["role", "radiogroup", "aria-label", "Record to keep", 1, "mj-ev-merge-survivor"], ["type", "button", "mjButton", "", "size", "sm", "role", "radio", 3, "click", "variant", "disabled"], [1, "mj-ev-merge-survivor-note"], [3, "MergeConfirmed", "MergeCancelled", "Fields", "Config", "MergeEnabled", "IsMerging"], [1, "mj-ev-merge-deps"], [1, "mj-ev-merge-notice-text"], ["type", "button", "title", "Dismiss", "aria-label", "Dismiss", 1, "mj-ev-merge-notice-dismiss", 3, "click"], [1, "fa-solid", "fa-xmark"]], template: function GridViewRendererComponent_Template(rf, ctx) { if (rf & 1) {
|
|
477
966
|
i0.ɵɵelementStart(0, "mj-entity-data-grid", 1, 0);
|
|
478
|
-
i0.ɵɵlistener("AfterRowClick", function GridViewRendererComponent_Template_mj_entity_data_grid_AfterRowClick_0_listener($event) { return ctx.onAfterRowClick($event); })("AfterRowDoubleClick", function GridViewRendererComponent_Template_mj_entity_data_grid_AfterRowDoubleClick_0_listener($event) { return ctx.onAfterRowDoubleClick($event); })("AfterSort", function GridViewRendererComponent_Template_mj_entity_data_grid_AfterSort_0_listener($event) { return ctx.onAfterSort($event); })("GridStateChanged", function GridViewRendererComponent_Template_mj_entity_data_grid_GridStateChanged_0_listener($event) { return ctx.onGridStateChanged($event); })("SelectionChange", function GridViewRendererComponent_Template_mj_entity_data_grid_SelectionChange_0_listener($event) { return ctx.onSelectionChange($event); })("NewButtonClick", function GridViewRendererComponent_Template_mj_entity_data_grid_NewButtonClick_0_listener() { return ctx.onNewButtonClick(); })("RefreshButtonClick", function GridViewRendererComponent_Template_mj_entity_data_grid_RefreshButtonClick_0_listener() { return ctx.onRefreshButtonClick(); })("DeleteButtonClick", function GridViewRendererComponent_Template_mj_entity_data_grid_DeleteButtonClick_0_listener($event) { return ctx.onDeleteButtonClick($event); })("AddToListRequested", function GridViewRendererComponent_Template_mj_entity_data_grid_AddToListRequested_0_listener($event) { return ctx.onAddToListRequested($event); })("ForeignKeyClick", function GridViewRendererComponent_Template_mj_entity_data_grid_ForeignKeyClick_0_listener($event) { return ctx.onForeignKeyClick($event); })("PageChange", function GridViewRendererComponent_Template_mj_entity_data_grid_PageChange_0_listener($event) { return ctx.onPageChange($event); })("ManageColumnsRequested", function GridViewRendererComponent_Template_mj_entity_data_grid_ManageColumnsRequested_0_listener() { return ctx.configureRequested.emit(); });
|
|
967
|
+
i0.ɵɵlistener("AfterRowClick", function GridViewRendererComponent_Template_mj_entity_data_grid_AfterRowClick_0_listener($event) { return ctx.onAfterRowClick($event); })("AfterRowDoubleClick", function GridViewRendererComponent_Template_mj_entity_data_grid_AfterRowDoubleClick_0_listener($event) { return ctx.onAfterRowDoubleClick($event); })("AfterSort", function GridViewRendererComponent_Template_mj_entity_data_grid_AfterSort_0_listener($event) { return ctx.onAfterSort($event); })("GridStateChanged", function GridViewRendererComponent_Template_mj_entity_data_grid_GridStateChanged_0_listener($event) { return ctx.onGridStateChanged($event); })("SelectionChange", function GridViewRendererComponent_Template_mj_entity_data_grid_SelectionChange_0_listener($event) { return ctx.onSelectionChange($event); })("NewButtonClick", function GridViewRendererComponent_Template_mj_entity_data_grid_NewButtonClick_0_listener() { return ctx.onNewButtonClick(); })("RefreshButtonClick", function GridViewRendererComponent_Template_mj_entity_data_grid_RefreshButtonClick_0_listener() { return ctx.onRefreshButtonClick(); })("DeleteButtonClick", function GridViewRendererComponent_Template_mj_entity_data_grid_DeleteButtonClick_0_listener($event) { return ctx.onDeleteButtonClick($event); })("AddToListRequested", function GridViewRendererComponent_Template_mj_entity_data_grid_AddToListRequested_0_listener($event) { return ctx.onAddToListRequested($event); })("ForeignKeyClick", function GridViewRendererComponent_Template_mj_entity_data_grid_ForeignKeyClick_0_listener($event) { return ctx.onForeignKeyClick($event); })("PageChange", function GridViewRendererComponent_Template_mj_entity_data_grid_PageChange_0_listener($event) { return ctx.onPageChange($event); })("ManageColumnsRequested", function GridViewRendererComponent_Template_mj_entity_data_grid_ManageColumnsRequested_0_listener() { return ctx.configureRequested.emit(); })("MergeRecordsRequested", function GridViewRendererComponent_Template_mj_entity_data_grid_MergeRecordsRequested_0_listener($event) { return ctx.onMergeRequested($event); });
|
|
479
968
|
i0.ɵɵelementEnd();
|
|
480
969
|
i0.ɵɵconditionalCreate(2, GridViewRendererComponent_Conditional_2_Template, 1, 3, "mj-list-management-dialog", 2);
|
|
481
970
|
i0.ɵɵelementStart(3, "mj-ev-confirm-dialog", 3);
|
|
482
971
|
i0.ɵɵlistener("Confirmed", function GridViewRendererComponent_Template_mj_ev_confirm_dialog_Confirmed_3_listener() { return ctx.onDeleteConfirmed(); })("Cancelled", function GridViewRendererComponent_Template_mj_ev_confirm_dialog_Cancelled_3_listener() { return ctx.onDeleteCancelled(); });
|
|
483
972
|
i0.ɵɵelementEnd();
|
|
973
|
+
i0.ɵɵconditionalCreate(4, GridViewRendererComponent_Conditional_4_Template, 12, 16, "mj-dialog", 4);
|
|
974
|
+
i0.ɵɵconditionalCreate(5, GridViewRendererComponent_Conditional_5_Template, 5, 1, "div", 5);
|
|
484
975
|
} if (rf & 2) {
|
|
485
|
-
i0.ɵɵproperty("Provider", ctx.Provider)("Data", ctx.records)("ExportDataProvider", ctx.exportDataProvider)("Params", ctx.effectiveParams)("FilterText", ctx.filterText ?? "")("GridState", ctx.config.gridState ?? null)("Height", "auto")("AllowLoad", false)("AutoLoadEntityActions", ctx.AutoLoadEntityActions)("ShowToolbar", ctx.effectiveShowToolbar)("ShowSearch", ctx.effectiveShowSearch)("ToolbarConfig", ctx.config.toolbarConfig ?? i0.ɵɵpureFunction0(
|
|
976
|
+
i0.ɵɵproperty("Provider", ctx.Provider)("Data", ctx.records)("ExportDataProvider", ctx.exportDataProvider)("Params", ctx.effectiveParams)("FilterText", ctx.filterText ?? "")("GridState", ctx.config.gridState ?? null)("Height", "auto")("AllowLoad", false)("AutoLoadEntityActions", ctx.AutoLoadEntityActions)("ShowToolbar", ctx.effectiveShowToolbar)("ShowSearch", ctx.effectiveShowSearch)("ToolbarConfig", ctx.config.toolbarConfig ?? i0.ɵɵpureFunction0(24, _c1))("SelectionMode", ctx.effectiveSelectionMode)("ShowAddToListButton", ctx.effectiveShowAddToListButton)("ShowPager", ctx.effectiveShowPager)("PageSize", ctx.effectivePageSize)("TotalRowCount", ctx.totalRecordCount ?? 0)("PagerPageNumber", ctx.page ?? 1)("ShowMergeButton", ctx.effectiveShowMergeButton);
|
|
486
977
|
i0.ɵɵadvance(2);
|
|
487
978
|
i0.ɵɵconditional(ctx.listManagementConfig ? 2 : -1);
|
|
488
979
|
i0.ɵɵadvance();
|
|
489
980
|
i0.ɵɵproperty("IsOpen", ctx.showDeleteConfirm)("Message", ctx.deleteConfirmMessage);
|
|
490
|
-
|
|
981
|
+
i0.ɵɵadvance();
|
|
982
|
+
i0.ɵɵconditional(ctx.mergeState ? 4 : -1);
|
|
983
|
+
i0.ɵɵadvance();
|
|
984
|
+
i0.ɵɵconditional(ctx.mergeNotice ? 5 : -1);
|
|
985
|
+
} }, dependencies: [i1.ListManagementDialogComponent, i2.RecordMergePanelComponent, i3.MJDialogComponent, i3.MJButtonDirective, i4.EntityDataGridComponent, i5.ConfirmDialogComponent], styles: ["\n :host {\n display: block;\n height: 100%;\n }\n\n .mj-ev-merge-survivor {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n }\n\n .mj-ev-merge-survivor-note {\n color: var(--mj-text-muted);\n }\n\n .mj-ev-merge-deps {\n margin: 0;\n font-size: var(--mj-text-xs);\n color: var(--mj-text-muted);\n }\n\n .mj-ev-merge-notice {\n position: fixed;\n bottom: 16px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 1001;\n padding: 8px 14px;\n border: 1px solid var(--mj-border-subtle);\n border-radius: var(--mj-radius-md);\n background: var(--mj-bg-surface-elevated);\n color: var(--mj-text-primary);\n font-size: var(--mj-text-sm);\n box-shadow: var(--mj-shadow-md);\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .mj-ev-merge-notice-dismiss {\n flex-shrink: 0;\n background: none;\n border: none;\n padding: 0 2px;\n cursor: pointer;\n color: var(--mj-text-muted);\n font-size: var(--mj-text-sm);\n line-height: 1;\n }\n\n .mj-ev-merge-notice-dismiss:hover {\n color: var(--mj-text-primary);\n }\n "], encapsulation: 2 });
|
|
491
986
|
}
|
|
492
987
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(GridViewRendererComponent, [{
|
|
493
988
|
type: Component,
|
|
@@ -524,6 +1019,8 @@ export class GridViewRendererComponent extends BaseAngularComponent {
|
|
|
524
1019
|
(ForeignKeyClick)="onForeignKeyClick($event)"
|
|
525
1020
|
(PageChange)="onPageChange($event)"
|
|
526
1021
|
(ManageColumnsRequested)="configureRequested.emit()"
|
|
1022
|
+
[ShowMergeButton]="effectiveShowMergeButton"
|
|
1023
|
+
(MergeRecordsRequested)="onMergeRequested($event)"
|
|
527
1024
|
>
|
|
528
1025
|
</mj-entity-data-grid>
|
|
529
1026
|
|
|
@@ -556,11 +1053,54 @@ export class GridViewRendererComponent extends BaseAngularComponent {
|
|
|
556
1053
|
(Cancelled)="onDeleteCancelled()"
|
|
557
1054
|
>
|
|
558
1055
|
</mj-ev-confirm-dialog>
|
|
559
|
-
|
|
1056
|
+
|
|
1057
|
+
<!-- Self-contained merge panel (Generic) — owned by this wrapper, never bubbles up.
|
|
1058
|
+
Lives inside the @if so the panel rebuilds its comparison (ngOnInit) per request. -->
|
|
1059
|
+
@if (mergeState) {
|
|
1060
|
+
<mj-dialog [Visible]="true" Size="lg" [Closeable]="!mergeState.IsMerging" (Close)="onMergeCancelled()">
|
|
1061
|
+
<div class="mj-ev-merge-survivor" role="radiogroup" aria-label="Record to keep">
|
|
1062
|
+
<span>Keep</span>
|
|
1063
|
+
<button type="button" mjButton size="sm" role="radio"
|
|
1064
|
+
[variant]="mergeState.Config.SurvivorSide === 'left' ? 'primary' : 'secondary'"
|
|
1065
|
+
[attr.aria-checked]="mergeState.Config.SurvivorSide === 'left'"
|
|
1066
|
+
[disabled]="mergeState.IsMerging"
|
|
1067
|
+
(click)="onSurvivorChange('left')">{{ mergeState.Config.LeftLabel }}</button>
|
|
1068
|
+
<button type="button" mjButton size="sm" role="radio"
|
|
1069
|
+
[variant]="mergeState.Config.SurvivorSide === 'right' ? 'primary' : 'secondary'"
|
|
1070
|
+
[attr.aria-checked]="mergeState.Config.SurvivorSide === 'right'"
|
|
1071
|
+
[disabled]="mergeState.IsMerging"
|
|
1072
|
+
(click)="onSurvivorChange('right')">{{ mergeState.Config.RightLabel }}</button>
|
|
1073
|
+
<span class="mj-ev-merge-survivor-note">{{ mergeLoserLabel }} will be deleted; its linked records move to the record you keep.</span>
|
|
1074
|
+
</div>
|
|
1075
|
+
<mj-record-merge-panel
|
|
1076
|
+
[Fields]="mergeState.Fields"
|
|
1077
|
+
[Config]="mergeState.Config"
|
|
1078
|
+
[MergeEnabled]="!mergeState.IsMerging"
|
|
1079
|
+
[IsMerging]="mergeState.IsMerging"
|
|
1080
|
+
(MergeConfirmed)="onMergeConfirmed($event)"
|
|
1081
|
+
(MergeCancelled)="onMergeCancelled()"
|
|
1082
|
+
>
|
|
1083
|
+
</mj-record-merge-panel>
|
|
1084
|
+
@if (mergeState.DependencyNote) {
|
|
1085
|
+
<p class="mj-ev-merge-deps">{{ mergeState.DependencyNote }}</p>
|
|
1086
|
+
}
|
|
1087
|
+
</mj-dialog>
|
|
1088
|
+
}
|
|
1089
|
+
@if (mergeNotice) {
|
|
1090
|
+
<div class="mj-ev-merge-notice" role="status">
|
|
1091
|
+
<span class="mj-ev-merge-notice-text">{{ mergeNotice }}</span>
|
|
1092
|
+
<button type="button" class="mj-ev-merge-notice-dismiss" (click)="onDismissMergeNotice()" title="Dismiss" aria-label="Dismiss">
|
|
1093
|
+
<i class="fa-solid fa-xmark"></i>
|
|
1094
|
+
</button>
|
|
1095
|
+
</div>
|
|
1096
|
+
}
|
|
1097
|
+
`, styles: ["\n :host {\n display: block;\n height: 100%;\n }\n\n .mj-ev-merge-survivor {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n }\n\n .mj-ev-merge-survivor-note {\n color: var(--mj-text-muted);\n }\n\n .mj-ev-merge-deps {\n margin: 0;\n font-size: var(--mj-text-xs);\n color: var(--mj-text-muted);\n }\n\n .mj-ev-merge-notice {\n position: fixed;\n bottom: 16px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 1001;\n padding: 8px 14px;\n border: 1px solid var(--mj-border-subtle);\n border-radius: var(--mj-radius-md);\n background: var(--mj-bg-surface-elevated);\n color: var(--mj-text-primary);\n font-size: var(--mj-text-sm);\n box-shadow: var(--mj-shadow-md);\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .mj-ev-merge-notice-dismiss {\n flex-shrink: 0;\n background: none;\n border: none;\n padding: 0 2px;\n cursor: pointer;\n color: var(--mj-text-muted);\n font-size: var(--mj-text-sm);\n line-height: 1;\n }\n\n .mj-ev-merge-notice-dismiss:hover {\n color: var(--mj-text-primary);\n }\n "] }]
|
|
560
1098
|
}], null, { entity: [{
|
|
561
1099
|
type: Input
|
|
562
1100
|
}], records: [{
|
|
563
1101
|
type: Input
|
|
1102
|
+
}], ExportDataProvider: [{
|
|
1103
|
+
type: Input
|
|
564
1104
|
}], exportDataProvider: [{
|
|
565
1105
|
type: Input
|
|
566
1106
|
}], selectedRecordId: [{
|
|
@@ -597,7 +1137,7 @@ export class GridViewRendererComponent extends BaseAngularComponent {
|
|
|
597
1137
|
type: ViewChild,
|
|
598
1138
|
args: ['grid']
|
|
599
1139
|
}] }); })();
|
|
600
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(GridViewRendererComponent, { className: "GridViewRendererComponent", filePath: "src/lib/view-types/renderers/grid-view-renderer.component.ts", lineNumber:
|
|
1140
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(GridViewRendererComponent, { className: "GridViewRendererComponent", filePath: "src/lib/view-types/renderers/grid-view-renderer.component.ts", lineNumber: 289 }); })();
|
|
601
1141
|
/**
|
|
602
1142
|
* Tree-shaking guard. Force-references this renderer so bundlers (ESBuild/Vite) don't drop the
|
|
603
1143
|
* component in builds that only mount it dynamically via the ClassFactory/descriptor. Mirrors the
|