@memberjunction/ng-record-merge 5.43.0 → 5.44.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/module.d.ts +2 -1
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/module.js +7 -3
- package/dist/lib/module.js.map +1 -1
- package/dist/lib/record-comparison.service.d.ts +21 -0
- package/dist/lib/record-comparison.service.d.ts.map +1 -0
- package/dist/lib/record-comparison.service.js +39 -0
- package/dist/lib/record-comparison.service.js.map +1 -0
- package/dist/lib/record-merge-panel.component.js +6 -9
- package/dist/lib/record-merge-panel.component.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 +7 -4
- package/dist/__tests__/record-merge-types.test.d.ts +0 -2
- package/dist/__tests__/record-merge-types.test.d.ts.map +0 -1
- package/dist/__tests__/record-merge-types.test.js +0 -132
- package/dist/__tests__/record-merge-types.test.js.map +0 -1
package/dist/lib/module.d.ts
CHANGED
|
@@ -2,9 +2,10 @@ import * as i0 from "@angular/core";
|
|
|
2
2
|
import * as i1 from "./record-merge-panel.component";
|
|
3
3
|
import * as i2 from "@angular/common";
|
|
4
4
|
import * as i3 from "@memberjunction/ng-shared-generic";
|
|
5
|
+
import * as i4 from "@memberjunction/ng-ui-components";
|
|
5
6
|
export declare class RecordMergeModule {
|
|
6
7
|
static ɵfac: i0.ɵɵFactoryDeclaration<RecordMergeModule, never>;
|
|
7
|
-
static ɵmod: i0.ɵɵNgModuleDeclaration<RecordMergeModule, [typeof i1.RecordMergePanelComponent], [typeof i2.CommonModule, typeof i3.SharedGenericModule], [typeof i1.RecordMergePanelComponent]>;
|
|
8
|
+
static ɵmod: i0.ɵɵNgModuleDeclaration<RecordMergeModule, [typeof i1.RecordMergePanelComponent], [typeof i2.CommonModule, typeof i3.SharedGenericModule, typeof i4.MJEmptyStateComponent], [typeof i1.RecordMergePanelComponent]>;
|
|
8
9
|
static ɵinj: i0.ɵɵInjectorDeclaration<RecordMergeModule>;
|
|
9
10
|
}
|
|
10
11
|
//# sourceMappingURL=module.d.ts.map
|
package/dist/lib/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/lib/module.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/lib/module.ts"],"names":[],"mappings":";;;;;AAOA,qBAaa,iBAAiB;yCAAjB,iBAAiB;0CAAjB,iBAAiB;0CAAjB,iBAAiB;CAAI"}
|
package/dist/lib/module.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { NgModule } from '@angular/core';
|
|
2
2
|
import { CommonModule } from '@angular/common';
|
|
3
3
|
import { SharedGenericModule } from '@memberjunction/ng-shared-generic';
|
|
4
|
+
import { MJEmptyStateComponent } from '@memberjunction/ng-ui-components';
|
|
4
5
|
import { RecordMergePanelComponent } from './record-merge-panel.component';
|
|
5
6
|
import * as i0 from "@angular/core";
|
|
6
7
|
export class RecordMergeModule {
|
|
7
8
|
static ɵfac = function RecordMergeModule_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || RecordMergeModule)(); };
|
|
8
9
|
static ɵmod = /*@__PURE__*/ i0.ɵɵdefineNgModule({ type: RecordMergeModule });
|
|
9
10
|
static ɵinj = /*@__PURE__*/ i0.ɵɵdefineInjector({ imports: [CommonModule,
|
|
10
|
-
SharedGenericModule
|
|
11
|
+
SharedGenericModule,
|
|
12
|
+
MJEmptyStateComponent] });
|
|
11
13
|
}
|
|
12
14
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RecordMergeModule, [{
|
|
13
15
|
type: NgModule,
|
|
@@ -17,7 +19,8 @@ export class RecordMergeModule {
|
|
|
17
19
|
],
|
|
18
20
|
imports: [
|
|
19
21
|
CommonModule,
|
|
20
|
-
SharedGenericModule
|
|
22
|
+
SharedGenericModule,
|
|
23
|
+
MJEmptyStateComponent
|
|
21
24
|
],
|
|
22
25
|
exports: [
|
|
23
26
|
RecordMergePanelComponent
|
|
@@ -25,5 +28,6 @@ export class RecordMergeModule {
|
|
|
25
28
|
}]
|
|
26
29
|
}], null, null); })();
|
|
27
30
|
(function () { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵɵsetNgModuleScope(RecordMergeModule, { declarations: [RecordMergePanelComponent], imports: [CommonModule,
|
|
28
|
-
SharedGenericModule
|
|
31
|
+
SharedGenericModule,
|
|
32
|
+
MJEmptyStateComponent], exports: [RecordMergePanelComponent] }); })();
|
|
29
33
|
//# sourceMappingURL=module.js.map
|
package/dist/lib/module.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/lib/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/lib/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAEzE,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;;AAe3E,MAAM,OAAO,iBAAiB;2GAAjB,iBAAiB;4DAAjB,iBAAiB;gEARtB,YAAY;YACZ,mBAAmB;YACnB,qBAAqB;;iFAMhB,iBAAiB;cAb7B,QAAQ;eAAC;gBACN,YAAY,EAAE;oBACV,yBAAyB;iBAC5B;gBACD,OAAO,EAAE;oBACL,YAAY;oBACZ,mBAAmB;oBACnB,qBAAqB;iBACxB;gBACD,OAAO,EAAE;oBACL,yBAAyB;iBAC5B;aACJ;;wFACY,iBAAiB,mBAXtB,yBAAyB,aAGzB,YAAY;QACZ,mBAAmB;QACnB,qBAAqB,aAGrB,yBAAyB","sourcesContent":["import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { SharedGenericModule } from '@memberjunction/ng-shared-generic';\nimport { MJEmptyStateComponent } from '@memberjunction/ng-ui-components';\n\nimport { RecordMergePanelComponent } from './record-merge-panel.component';\n\n@NgModule({\n declarations: [\n RecordMergePanelComponent\n ],\n imports: [\n CommonModule,\n SharedGenericModule,\n MJEmptyStateComponent\n ],\n exports: [\n RecordMergePanelComponent\n ]\n})\nexport class RecordMergeModule { }\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { IMetadataProvider, RemoteOpResult } from '@memberjunction/core';
|
|
2
|
+
import { type RecordComparisonCompareInput, type RecordComparisonCompareOutput } from '@memberjunction/core-entities';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
export declare class RecordComparisonService {
|
|
5
|
+
/**
|
|
6
|
+
* Compute a field-level comparison of a set of records on the server.
|
|
7
|
+
*
|
|
8
|
+
* The provider is resolved per multi-provider rules: the caller may pass an explicit
|
|
9
|
+
* {@link IMetadataProvider}; otherwise the global `Metadata.Provider` is used. The operation
|
|
10
|
+
* never throws for logical failures — inspect `result.Success` / `result.ErrorMessage`, and
|
|
11
|
+
* read the typed payload from `result.Output`.
|
|
12
|
+
*
|
|
13
|
+
* @param input The entity, keys (column 0 = survivor/reference), and optional field include-list.
|
|
14
|
+
* @param provider Optional metadata provider to scope the request to a specific server.
|
|
15
|
+
* @returns The wrapped typed comparison result.
|
|
16
|
+
*/
|
|
17
|
+
GetRecordComparison(input: RecordComparisonCompareInput, provider?: IMetadataProvider | null): Promise<RemoteOpResult<RecordComparisonCompareOutput>>;
|
|
18
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<RecordComparisonService, never>;
|
|
19
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<RecordComparisonService>;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=record-comparison.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record-comparison.service.d.ts","sourceRoot":"","sources":["../../src/lib/record-comparison.service.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,iBAAiB,EAAY,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAEH,KAAK,4BAA4B,EACjC,KAAK,6BAA6B,EACrC,MAAM,+BAA+B,CAAC;;AAEvC,qBACa,uBAAuB;IAChC;;;;;;;;;;;OAWG;IACU,mBAAmB,CAC5B,KAAK,EAAE,4BAA4B,EACnC,QAAQ,CAAC,EAAE,iBAAiB,GAAG,IAAI,GACpC,OAAO,CAAC,cAAc,CAAC,6BAA6B,CAAC,CAAC;yCAhBhD,uBAAuB;6CAAvB,uBAAuB;CAoBnC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Thin Angular wrapper over the server-side record-comparison capability.
|
|
3
|
+
*
|
|
4
|
+
* The field-delta computation runs server-side via `@memberjunction/record-comparison`'s
|
|
5
|
+
* `RecordComparisonEngine`, reached through the `RecordComparison.Compare` **Remote Operation**.
|
|
6
|
+
* This service does nothing but resolve the provider (multi-provider aware) and invoke the typed
|
|
7
|
+
* operation — it never inlines `gql`, never touches the engine, and holds no state. The Remote
|
|
8
|
+
* Operation marshals over the generic `ExecuteRemoteOperation` transport on the client and runs
|
|
9
|
+
* in-process on the server, so there is no bespoke resolver/client to maintain.
|
|
10
|
+
*/
|
|
11
|
+
import { Injectable } from '@angular/core';
|
|
12
|
+
import { Metadata } from '@memberjunction/core';
|
|
13
|
+
import { RecordComparisonCompareOperation, } from '@memberjunction/core-entities';
|
|
14
|
+
import * as i0 from "@angular/core";
|
|
15
|
+
export class RecordComparisonService {
|
|
16
|
+
/**
|
|
17
|
+
* Compute a field-level comparison of a set of records on the server.
|
|
18
|
+
*
|
|
19
|
+
* The provider is resolved per multi-provider rules: the caller may pass an explicit
|
|
20
|
+
* {@link IMetadataProvider}; otherwise the global `Metadata.Provider` is used. The operation
|
|
21
|
+
* never throws for logical failures — inspect `result.Success` / `result.ErrorMessage`, and
|
|
22
|
+
* read the typed payload from `result.Output`.
|
|
23
|
+
*
|
|
24
|
+
* @param input The entity, keys (column 0 = survivor/reference), and optional field include-list.
|
|
25
|
+
* @param provider Optional metadata provider to scope the request to a specific server.
|
|
26
|
+
* @returns The wrapped typed comparison result.
|
|
27
|
+
*/
|
|
28
|
+
async GetRecordComparison(input, provider) {
|
|
29
|
+
const p = provider ?? Metadata.Provider;
|
|
30
|
+
return new RecordComparisonCompareOperation().Execute(input, { provider: p ?? undefined });
|
|
31
|
+
}
|
|
32
|
+
static ɵfac = function RecordComparisonService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || RecordComparisonService)(); };
|
|
33
|
+
static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: RecordComparisonService, factory: RecordComparisonService.ɵfac, providedIn: 'root' });
|
|
34
|
+
}
|
|
35
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RecordComparisonService, [{
|
|
36
|
+
type: Injectable,
|
|
37
|
+
args: [{ providedIn: 'root' }]
|
|
38
|
+
}], null, null); })();
|
|
39
|
+
//# sourceMappingURL=record-comparison.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record-comparison.service.js","sourceRoot":"","sources":["../../src/lib/record-comparison.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAqB,QAAQ,EAAkB,MAAM,sBAAsB,CAAC;AACnF,OAAO,EACH,gCAAgC,GAGnC,MAAM,+BAA+B,CAAC;;AAGvC,MAAM,OAAO,uBAAuB;IAChC;;;;;;;;;;;OAWG;IACI,KAAK,CAAC,mBAAmB,CAC5B,KAAmC,EACnC,QAAmC;QAEnC,MAAM,CAAC,GAAG,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC;QACxC,OAAO,IAAI,gCAAgC,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC,CAAC;IAC/F,CAAC;iHAnBQ,uBAAuB;gEAAvB,uBAAuB,WAAvB,uBAAuB,mBADV,MAAM;;iFACnB,uBAAuB;cADnC,UAAU;eAAC,EAAE,UAAU,EAAE,MAAM,EAAE","sourcesContent":["/**\n * @fileoverview Thin Angular wrapper over the server-side record-comparison capability.\n *\n * The field-delta computation runs server-side via `@memberjunction/record-comparison`'s\n * `RecordComparisonEngine`, reached through the `RecordComparison.Compare` **Remote Operation**.\n * This service does nothing but resolve the provider (multi-provider aware) and invoke the typed\n * operation — it never inlines `gql`, never touches the engine, and holds no state. The Remote\n * Operation marshals over the generic `ExecuteRemoteOperation` transport on the client and runs\n * in-process on the server, so there is no bespoke resolver/client to maintain.\n */\n\nimport { Injectable } from '@angular/core';\nimport { IMetadataProvider, Metadata, RemoteOpResult } from '@memberjunction/core';\nimport {\n RecordComparisonCompareOperation,\n type RecordComparisonCompareInput,\n type RecordComparisonCompareOutput,\n} from '@memberjunction/core-entities';\n\n@Injectable({ providedIn: 'root' })\nexport class RecordComparisonService {\n /**\n * Compute a field-level comparison of a set of records on the server.\n *\n * The provider is resolved per multi-provider rules: the caller may pass an explicit\n * {@link IMetadataProvider}; otherwise the global `Metadata.Provider` is used. The operation\n * never throws for logical failures — inspect `result.Success` / `result.ErrorMessage`, and\n * read the typed payload from `result.Output`.\n *\n * @param input The entity, keys (column 0 = survivor/reference), and optional field include-list.\n * @param provider Optional metadata provider to scope the request to a specific server.\n * @returns The wrapped typed comparison result.\n */\n public async GetRecordComparison(\n input: RecordComparisonCompareInput,\n provider?: IMetadataProvider | null,\n ): Promise<RemoteOpResult<RecordComparisonCompareOutput>> {\n const p = provider ?? Metadata.Provider;\n return new RecordComparisonCompareOperation().Execute(input, { provider: p ?? undefined });\n }\n}\n"]}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Component, EventEmitter, Input, Output, ChangeDetectorRef, inject } from '@angular/core';
|
|
9
9
|
import * as i0 from "@angular/core";
|
|
10
|
+
import * as i1 from "@memberjunction/ng-ui-components";
|
|
10
11
|
const _forTrack0 = ($index, $item) => $item.FieldName;
|
|
11
12
|
function RecordMergePanelComponent_Conditional_0_Conditional_11_Template(rf, ctx) { if (rf & 1) {
|
|
12
13
|
i0.ɵɵelement(0, "i", 20);
|
|
@@ -180,11 +181,7 @@ function RecordMergePanelComponent_Conditional_0_Template(rf, ctx) { if (rf & 1)
|
|
|
180
181
|
i0.ɵɵconditional(ctx_r1.ConfirmDialogVisible ? 30 : -1);
|
|
181
182
|
} }
|
|
182
183
|
function RecordMergePanelComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
183
|
-
i0.ɵɵ
|
|
184
|
-
i0.ɵɵelement(1, "i", 42);
|
|
185
|
-
i0.ɵɵelementStart(2, "p");
|
|
186
|
-
i0.ɵɵtext(3, "Select two records to compare");
|
|
187
|
-
i0.ɵɵelementEnd()();
|
|
184
|
+
i0.ɵɵelement(0, "mj-empty-state", 0);
|
|
188
185
|
} }
|
|
189
186
|
export class RecordMergePanelComponent {
|
|
190
187
|
cdr = inject(ChangeDetectorRef);
|
|
@@ -318,15 +315,15 @@ export class RecordMergePanelComponent {
|
|
|
318
315
|
this.cdr.detectChanges();
|
|
319
316
|
}
|
|
320
317
|
static ɵfac = function RecordMergePanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || RecordMergePanelComponent)(); };
|
|
321
|
-
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RecordMergePanelComponent, selectors: [["mj-record-merge-panel"]], inputs: { Fields: "Fields", Config: "Config", ShowConflictsOnly: "ShowConflictsOnly", MergeEnabled: "MergeEnabled", IsMerging: "IsMerging" }, outputs: { MergeConfirmed: "MergeConfirmed", MergeCancelled: "MergeCancelled", FieldSelected: "FieldSelected" }, standalone: false, decls: 2, vars: 1, consts: [[
|
|
322
|
-
i0.ɵɵconditionalCreate(0, RecordMergePanelComponent_Conditional_0_Template, 31, 8)(1, RecordMergePanelComponent_Conditional_1_Template,
|
|
318
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RecordMergePanelComponent, selectors: [["mj-record-merge-panel"]], inputs: { Fields: "Fields", Config: "Config", ShowConflictsOnly: "ShowConflictsOnly", MergeEnabled: "MergeEnabled", IsMerging: "IsMerging" }, outputs: { MergeConfirmed: "MergeConfirmed", MergeCancelled: "MergeCancelled", FieldSelected: "FieldSelected" }, standalone: false, decls: 2, vars: 1, consts: [["Icon", "fa-solid fa-code-compare", "Title", "Select two records to compare"], [1, "merge-panel"], [1, "merge-header"], [1, "merge-header-info"], [1, "merge-title"], [1, "merge-entity-name"], [1, "merge-conflict-badge"], [1, "merge-header-actions"], [1, "merge-toggle-btn", 3, "click"], [1, "merge-columns"], [1, "merge-col-label", "merge-col-field"], [1, "merge-col-label", "merge-col-left"], ["title", "Select all from left", 1, "merge-select-all-btn", 3, "click"], [1, "fa-solid", "fa-check-double"], [1, "merge-col-label", "merge-col-right"], ["title", "Select all from right", 1, "merge-select-all-btn", 3, "click"], [1, "merge-fields"], [1, "merge-row", 3, "merge-row-conflict", "merge-row-readonly"], [1, "merge-footer"], [1, "merge-dialog-backdrop"], [1, "fa-solid", "fa-filter"], [1, "fa-solid", "fa-list"], [1, "merge-row"], [1, "merge-cell", "merge-cell-field"], [1, "merge-field-name"], ["title", "Read-only field", 1, "fa-solid", "fa-lock", "merge-readonly-icon"], [1, "merge-cell", "merge-cell-value", "merge-cell-left", 3, "click"], [1, "merge-value"], [1, "fa-solid", "fa-check", "merge-check-icon"], [1, "merge-cell", "merge-cell-value", "merge-cell-right", 3, "click"], [1, "merge-btn", "merge-btn-primary", 3, "click", "disabled"], [1, "merge-btn", "merge-btn-secondary", 3, "click", "disabled"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "fa-solid", "fa-code-merge"], [1, "merge-dialog-backdrop", 3, "click"], [1, "merge-dialog", 3, "click"], [1, "merge-dialog-header"], [1, "fa-solid", "fa-exclamation-triangle", "merge-dialog-warning-icon"], [1, "merge-dialog-text"], [1, "merge-dialog-actions"], [1, "merge-btn", "merge-btn-primary", 3, "click"], [1, "merge-btn", "merge-btn-secondary", 3, "click"]], template: function RecordMergePanelComponent_Template(rf, ctx) { if (rf & 1) {
|
|
319
|
+
i0.ɵɵconditionalCreate(0, RecordMergePanelComponent_Conditional_0_Template, 31, 8)(1, RecordMergePanelComponent_Conditional_1_Template, 1, 0, "mj-empty-state", 0);
|
|
323
320
|
} if (rf & 2) {
|
|
324
321
|
i0.ɵɵconditional(ctx.Config && ctx.Comparison ? 0 : 1);
|
|
325
|
-
} }, styles: ["\n\n\n.merge-panel[_ngcontent-%COMP%] {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n}\n\n
|
|
322
|
+
} }, dependencies: [i1.MJEmptyStateComponent], styles: ["\n\n\n.merge-panel[_ngcontent-%COMP%] {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n}\n\n\n\n.merge-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.75rem 1rem;\n border-bottom: 1px solid var(--mj-border-default);\n flex-wrap: wrap;\n gap: 0.5rem;\n}\n\n.merge-header-info[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n flex-wrap: wrap;\n}\n\n.merge-title[_ngcontent-%COMP%] {\n font-size: 1rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n}\n\n.merge-entity-name[_ngcontent-%COMP%] {\n font-size: 0.82rem;\n color: var(--mj-text-secondary);\n padding: 0.1rem 0.5rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n}\n\n.merge-conflict-badge[_ngcontent-%COMP%] {\n font-size: 0.78rem;\n color: var(--mj-status-warning-text);\n background: var(--mj-status-warning-bg);\n padding: 0.15rem 0.5rem;\n border-radius: 9999px;\n}\n\n.merge-toggle-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.35rem 0.75rem;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 0.78rem;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.merge-toggle-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n\n\n.merge-columns[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 1fr 1fr 1fr;\n border-bottom: 2px solid var(--mj-border-default);\n}\n\n.merge-col-label[_ngcontent-%COMP%] {\n padding: 0.5rem 0.75rem;\n font-weight: 600;\n font-size: 0.78rem;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.03em;\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n.merge-select-all-btn[_ngcontent-%COMP%] {\n border: none;\n background: none;\n color: var(--mj-text-muted);\n font-size: 0.72rem;\n cursor: pointer;\n padding: 0.2rem;\n border-radius: 3px;\n}\n\n.merge-select-all-btn[_ngcontent-%COMP%]:hover {\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n\n\n.merge-fields[_ngcontent-%COMP%] {\n overflow-y: auto;\n max-height: 60vh;\n}\n\n.merge-row[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 1fr 1fr 1fr;\n border-bottom: 1px solid var(--mj-border-subtle);\n transition: background 0.1s ease;\n}\n\n.merge-row[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.merge-row-conflict[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-warning) 5%, var(--mj-bg-surface));\n}\n\n.merge-row-readonly[_ngcontent-%COMP%] {\n opacity: 0.7;\n}\n\n.merge-cell[_ngcontent-%COMP%] {\n padding: 0.5rem 0.75rem;\n font-size: 0.85rem;\n display: flex;\n align-items: center;\n gap: 0.4rem;\n min-height: 2.5rem;\n}\n\n.merge-cell-field[_ngcontent-%COMP%] {\n color: var(--mj-text-primary);\n font-weight: 500;\n border-right: 1px solid var(--mj-border-subtle);\n}\n\n.merge-field-name[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n.merge-readonly-icon[_ngcontent-%COMP%] {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n}\n\n.merge-cell-value[_ngcontent-%COMP%] {\n position: relative;\n}\n\n.merge-cell-selectable[_ngcontent-%COMP%] {\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.merge-cell-selectable[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.merge-cell-selected[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n.merge-value[_ngcontent-%COMP%] {\n color: var(--mj-text-primary);\n flex: 1;\n word-break: break-word;\n}\n\n.merge-value-empty[_ngcontent-%COMP%] {\n color: var(--mj-text-disabled);\n font-style: italic;\n}\n\n.merge-check-icon[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 0.75rem;\n flex-shrink: 0;\n}\n\n\n\n.merge-footer[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1rem;\n border-top: 1px solid var(--mj-border-default);\n}\n\n.merge-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.5rem 1rem;\n border-radius: 6px;\n font-size: 0.85rem;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n border: none;\n}\n\n.merge-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.merge-btn-primary[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.merge-btn-primary[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.merge-btn-secondary[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n border: 1px solid var(--mj-border-default);\n}\n\n.merge-btn-secondary[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n}\n\n\n\n.merge-dialog-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n}\n\n.merge-dialog[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 1.5rem;\n max-width: 420px;\n width: 100%;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n}\n\n.merge-dialog-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n margin-bottom: 0.75rem;\n}\n\n.merge-dialog-header[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 1.1rem;\n color: var(--mj-text-primary);\n}\n\n.merge-dialog-warning-icon[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n font-size: 1.2rem;\n}\n\n.merge-dialog-text[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n font-size: 0.9rem;\n line-height: 1.5;\n margin-bottom: 1.25rem;\n}\n\n.merge-dialog-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 0.5rem;\n}\n\n\n\n@media (max-width: 768px) {\n .merge-columns[_ngcontent-%COMP%], \n .merge-row[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n\n .merge-cell-field[_ngcontent-%COMP%] {\n border-right: none;\n border-bottom: 1px solid var(--mj-border-subtle);\n font-size: 0.78rem;\n }\n\n .merge-col-left[_ngcontent-%COMP%], \n .merge-col-right[_ngcontent-%COMP%] {\n padding-left: 1.5rem;\n }\n}"] });
|
|
326
323
|
}
|
|
327
324
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RecordMergePanelComponent, [{
|
|
328
325
|
type: Component,
|
|
329
|
-
args: [{ standalone: false, selector: 'mj-record-merge-panel', template: "@if (Config && Comparison) {\n <div class=\"merge-panel\">\n <!-- Header -->\n <div class=\"merge-header\">\n <div class=\"merge-header-info\">\n <h3 class=\"merge-title\">Record Comparison</h3>\n <span class=\"merge-entity-name\">{{ Config.EntityName }}</span>\n <span class=\"merge-conflict-badge\">\n {{ Comparison.ConflictCount }} conflicts / {{ Comparison.TotalFieldCount }} fields\n </span>\n </div>\n <div class=\"merge-header-actions\">\n <button class=\"merge-toggle-btn\" (click)=\"ToggleShowAll()\">\n @if (ShowAllFields) {\n <i class=\"fa-solid fa-filter\"></i> Show Conflicts Only\n } @else {\n <i class=\"fa-solid fa-list\"></i> Show All Fields\n }\n </button>\n </div>\n </div>\n\n <!-- Column Headers -->\n <div class=\"merge-columns\">\n <div class=\"merge-col-label merge-col-field\">Field</div>\n <div class=\"merge-col-label merge-col-left\">\n <span>{{ Config.LeftLabel }}</span>\n <button class=\"merge-select-all-btn\" (click)=\"SelectAllLeft()\" title=\"Select all from left\">\n <i class=\"fa-solid fa-check-double\"></i>\n </button>\n </div>\n <div class=\"merge-col-label merge-col-right\">\n <span>{{ Config.RightLabel }}</span>\n <button class=\"merge-select-all-btn\" (click)=\"SelectAllRight()\" title=\"Select all from right\">\n <i class=\"fa-solid fa-check-double\"></i>\n </button>\n </div>\n </div>\n\n <!-- Field Rows -->\n <div class=\"merge-fields\">\n @for (field of GetVisibleFields(); track field.FieldName) {\n <div class=\"merge-row\" [class.merge-row-conflict]=\"field.HasConflict\" [class.merge-row-readonly]=\"field.IsReadOnly\">\n <div class=\"merge-cell merge-cell-field\">\n <span class=\"merge-field-name\">{{ field.DisplayLabel }}</span>\n @if (field.IsReadOnly) {\n <i class=\"fa-solid fa-lock merge-readonly-icon\" title=\"Read-only field\"></i>\n }\n </div>\n <div\n class=\"merge-cell merge-cell-value merge-cell-left\"\n [class.merge-cell-selected]=\"field.SelectedSide === 'left'\"\n [class.merge-cell-selectable]=\"!field.IsReadOnly\"\n (click)=\"SelectLeft(field)\"\n >\n <span class=\"merge-value\" [class.merge-value-empty]=\"field.LeftValue === null || field.LeftValue === undefined\">\n {{ FormatValue(field.LeftValue) }}\n </span>\n @if (field.SelectedSide === 'left' && !field.IsReadOnly) {\n <i class=\"fa-solid fa-check merge-check-icon\"></i>\n }\n </div>\n <div\n class=\"merge-cell merge-cell-value merge-cell-right\"\n [class.merge-cell-selected]=\"field.SelectedSide === 'right'\"\n [class.merge-cell-selectable]=\"!field.IsReadOnly\"\n (click)=\"SelectRight(field)\"\n >\n <span class=\"merge-value\" [class.merge-value-empty]=\"field.RightValue === null || field.RightValue === undefined\">\n {{ FormatValue(field.RightValue) }}\n </span>\n @if (field.SelectedSide === 'right' && !field.IsReadOnly) {\n <i class=\"fa-solid fa-check merge-check-icon\"></i>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- Footer Actions -->\n @if (MergeEnabled) {\n <div class=\"merge-footer\">\n <button class=\"merge-btn merge-btn-primary\" (click)=\"ShowConfirmDialog()\" [disabled]=\"IsMerging\">\n @if (IsMerging) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Merging...\n } @else {\n <i class=\"fa-solid fa-code-merge\"></i> Merge Records\n }\n </button>\n <button class=\"merge-btn merge-btn-secondary\" (click)=\"CancelMerge()\" [disabled]=\"IsMerging\">\n Cancel\n </button>\n </div>\n }\n </div>\n\n <!-- Confirmation Dialog -->\n @if (ConfirmDialogVisible) {\n <div class=\"merge-dialog-backdrop\" (click)=\"ConfirmDialogVisible = false\">\n <div class=\"merge-dialog\" (click)=\"$event.stopPropagation()\">\n <div class=\"merge-dialog-header\">\n <i class=\"fa-solid fa-exclamation-triangle merge-dialog-warning-icon\"></i>\n <h4>Confirm Merge</h4>\n </div>\n <p class=\"merge-dialog-text\">\n This will merge the selected field values and create a merge log.\n This action cannot be undone. Are you sure?\n </p>\n <div class=\"merge-dialog-actions\">\n <button class=\"merge-btn merge-btn-primary\" (click)=\"ConfirmMerge()\">\n <i class=\"fa-solid fa-code-merge\"></i> Confirm Merge\n </button>\n <button class=\"merge-btn merge-btn-secondary\" (click)=\"ConfirmDialogVisible = false\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n }\n} @else {\n <div class=\"merge-panel merge-panel-empty\">\n <i class=\"fa-solid fa-code-compare merge-empty-icon\"></i>\n <p>Select two records to compare</p>\n </div>\n}\n", styles: ["/* Record Merge Panel */\n\n.merge-panel {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n}\n\n.merge-panel-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 3rem 1rem;\n gap: 0.75rem;\n color: var(--mj-text-muted);\n}\n\n.merge-empty-icon {\n font-size: 2.5rem;\n margin-bottom: 0.5rem;\n}\n\n/* Header */\n.merge-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.75rem 1rem;\n border-bottom: 1px solid var(--mj-border-default);\n flex-wrap: wrap;\n gap: 0.5rem;\n}\n\n.merge-header-info {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n flex-wrap: wrap;\n}\n\n.merge-title {\n font-size: 1rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n}\n\n.merge-entity-name {\n font-size: 0.82rem;\n color: var(--mj-text-secondary);\n padding: 0.1rem 0.5rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n}\n\n.merge-conflict-badge {\n font-size: 0.78rem;\n color: var(--mj-status-warning-text);\n background: var(--mj-status-warning-bg);\n padding: 0.15rem 0.5rem;\n border-radius: 9999px;\n}\n\n.merge-toggle-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.35rem 0.75rem;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 0.78rem;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.merge-toggle-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n/* Column Headers */\n.merge-columns {\n display: grid;\n grid-template-columns: 1fr 1fr 1fr;\n border-bottom: 2px solid var(--mj-border-default);\n}\n\n.merge-col-label {\n padding: 0.5rem 0.75rem;\n font-weight: 600;\n font-size: 0.78rem;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.03em;\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n.merge-select-all-btn {\n border: none;\n background: none;\n color: var(--mj-text-muted);\n font-size: 0.72rem;\n cursor: pointer;\n padding: 0.2rem;\n border-radius: 3px;\n}\n\n.merge-select-all-btn:hover {\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n/* Field Rows */\n.merge-fields {\n overflow-y: auto;\n max-height: 60vh;\n}\n\n.merge-row {\n display: grid;\n grid-template-columns: 1fr 1fr 1fr;\n border-bottom: 1px solid var(--mj-border-subtle);\n transition: background 0.1s ease;\n}\n\n.merge-row:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.merge-row-conflict {\n background: color-mix(in srgb, var(--mj-status-warning) 5%, var(--mj-bg-surface));\n}\n\n.merge-row-readonly {\n opacity: 0.7;\n}\n\n.merge-cell {\n padding: 0.5rem 0.75rem;\n font-size: 0.85rem;\n display: flex;\n align-items: center;\n gap: 0.4rem;\n min-height: 2.5rem;\n}\n\n.merge-cell-field {\n color: var(--mj-text-primary);\n font-weight: 500;\n border-right: 1px solid var(--mj-border-subtle);\n}\n\n.merge-field-name {\n flex: 1;\n}\n\n.merge-readonly-icon {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n}\n\n.merge-cell-value {\n position: relative;\n}\n\n.merge-cell-selectable {\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.merge-cell-selectable:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.merge-cell-selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n.merge-value {\n color: var(--mj-text-primary);\n flex: 1;\n word-break: break-word;\n}\n\n.merge-value-empty {\n color: var(--mj-text-disabled);\n font-style: italic;\n}\n\n.merge-check-icon {\n color: var(--mj-brand-primary);\n font-size: 0.75rem;\n flex-shrink: 0;\n}\n\n/* Footer */\n.merge-footer {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1rem;\n border-top: 1px solid var(--mj-border-default);\n}\n\n.merge-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.5rem 1rem;\n border-radius: 6px;\n font-size: 0.85rem;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n border: none;\n}\n\n.merge-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.merge-btn-primary {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.merge-btn-primary:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.merge-btn-secondary {\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n border: 1px solid var(--mj-border-default);\n}\n\n.merge-btn-secondary:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n}\n\n/* Confirmation Dialog */\n.merge-dialog-backdrop {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n}\n\n.merge-dialog {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 1.5rem;\n max-width: 420px;\n width: 100%;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n}\n\n.merge-dialog-header {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n margin-bottom: 0.75rem;\n}\n\n.merge-dialog-header h4 {\n margin: 0;\n font-size: 1.1rem;\n color: var(--mj-text-primary);\n}\n\n.merge-dialog-warning-icon {\n color: var(--mj-status-warning);\n font-size: 1.2rem;\n}\n\n.merge-dialog-text {\n color: var(--mj-text-secondary);\n font-size: 0.9rem;\n line-height: 1.5;\n margin-bottom: 1.25rem;\n}\n\n.merge-dialog-actions {\n display: flex;\n gap: 0.5rem;\n}\n\n/* Mobile responsive */\n@media (max-width: 768px) {\n .merge-columns,\n .merge-row {\n grid-template-columns: 1fr;\n }\n\n .merge-cell-field {\n border-right: none;\n border-bottom: 1px solid var(--mj-border-subtle);\n font-size: 0.78rem;\n }\n\n .merge-col-left,\n .merge-col-right {\n padding-left: 1.5rem;\n }\n}\n"] }]
|
|
326
|
+
args: [{ standalone: false, selector: 'mj-record-merge-panel', template: "@if (Config && Comparison) {\n <div class=\"merge-panel\">\n <!-- Header -->\n <div class=\"merge-header\">\n <div class=\"merge-header-info\">\n <h3 class=\"merge-title\">Record Comparison</h3>\n <span class=\"merge-entity-name\">{{ Config.EntityName }}</span>\n <span class=\"merge-conflict-badge\">\n {{ Comparison.ConflictCount }} conflicts / {{ Comparison.TotalFieldCount }} fields\n </span>\n </div>\n <div class=\"merge-header-actions\">\n <button class=\"merge-toggle-btn\" (click)=\"ToggleShowAll()\">\n @if (ShowAllFields) {\n <i class=\"fa-solid fa-filter\"></i> Show Conflicts Only\n } @else {\n <i class=\"fa-solid fa-list\"></i> Show All Fields\n }\n </button>\n </div>\n </div>\n\n <!-- Column Headers -->\n <div class=\"merge-columns\">\n <div class=\"merge-col-label merge-col-field\">Field</div>\n <div class=\"merge-col-label merge-col-left\">\n <span>{{ Config.LeftLabel }}</span>\n <button class=\"merge-select-all-btn\" (click)=\"SelectAllLeft()\" title=\"Select all from left\">\n <i class=\"fa-solid fa-check-double\"></i>\n </button>\n </div>\n <div class=\"merge-col-label merge-col-right\">\n <span>{{ Config.RightLabel }}</span>\n <button class=\"merge-select-all-btn\" (click)=\"SelectAllRight()\" title=\"Select all from right\">\n <i class=\"fa-solid fa-check-double\"></i>\n </button>\n </div>\n </div>\n\n <!-- Field Rows -->\n <div class=\"merge-fields\">\n @for (field of GetVisibleFields(); track field.FieldName) {\n <div class=\"merge-row\" [class.merge-row-conflict]=\"field.HasConflict\" [class.merge-row-readonly]=\"field.IsReadOnly\">\n <div class=\"merge-cell merge-cell-field\">\n <span class=\"merge-field-name\">{{ field.DisplayLabel }}</span>\n @if (field.IsReadOnly) {\n <i class=\"fa-solid fa-lock merge-readonly-icon\" title=\"Read-only field\"></i>\n }\n </div>\n <div\n class=\"merge-cell merge-cell-value merge-cell-left\"\n [class.merge-cell-selected]=\"field.SelectedSide === 'left'\"\n [class.merge-cell-selectable]=\"!field.IsReadOnly\"\n (click)=\"SelectLeft(field)\"\n >\n <span class=\"merge-value\" [class.merge-value-empty]=\"field.LeftValue === null || field.LeftValue === undefined\">\n {{ FormatValue(field.LeftValue) }}\n </span>\n @if (field.SelectedSide === 'left' && !field.IsReadOnly) {\n <i class=\"fa-solid fa-check merge-check-icon\"></i>\n }\n </div>\n <div\n class=\"merge-cell merge-cell-value merge-cell-right\"\n [class.merge-cell-selected]=\"field.SelectedSide === 'right'\"\n [class.merge-cell-selectable]=\"!field.IsReadOnly\"\n (click)=\"SelectRight(field)\"\n >\n <span class=\"merge-value\" [class.merge-value-empty]=\"field.RightValue === null || field.RightValue === undefined\">\n {{ FormatValue(field.RightValue) }}\n </span>\n @if (field.SelectedSide === 'right' && !field.IsReadOnly) {\n <i class=\"fa-solid fa-check merge-check-icon\"></i>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- Footer Actions -->\n @if (MergeEnabled) {\n <div class=\"merge-footer\">\n <button class=\"merge-btn merge-btn-primary\" (click)=\"ShowConfirmDialog()\" [disabled]=\"IsMerging\">\n @if (IsMerging) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Merging...\n } @else {\n <i class=\"fa-solid fa-code-merge\"></i> Merge Records\n }\n </button>\n <button class=\"merge-btn merge-btn-secondary\" (click)=\"CancelMerge()\" [disabled]=\"IsMerging\">\n Cancel\n </button>\n </div>\n }\n </div>\n\n <!-- Confirmation Dialog -->\n @if (ConfirmDialogVisible) {\n <div class=\"merge-dialog-backdrop\" (click)=\"ConfirmDialogVisible = false\">\n <div class=\"merge-dialog\" (click)=\"$event.stopPropagation()\">\n <div class=\"merge-dialog-header\">\n <i class=\"fa-solid fa-exclamation-triangle merge-dialog-warning-icon\"></i>\n <h4>Confirm Merge</h4>\n </div>\n <p class=\"merge-dialog-text\">\n This will merge the selected field values and create a merge log.\n This action cannot be undone. Are you sure?\n </p>\n <div class=\"merge-dialog-actions\">\n <button class=\"merge-btn merge-btn-primary\" (click)=\"ConfirmMerge()\">\n <i class=\"fa-solid fa-code-merge\"></i> Confirm Merge\n </button>\n <button class=\"merge-btn merge-btn-secondary\" (click)=\"ConfirmDialogVisible = false\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n }\n} @else {\n <mj-empty-state\n Icon=\"fa-solid fa-code-compare\"\n Title=\"Select two records to compare\" />\n}\n", styles: ["/* Record Merge Panel */\n\n.merge-panel {\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n}\n\n/* Header */\n.merge-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.75rem 1rem;\n border-bottom: 1px solid var(--mj-border-default);\n flex-wrap: wrap;\n gap: 0.5rem;\n}\n\n.merge-header-info {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n flex-wrap: wrap;\n}\n\n.merge-title {\n font-size: 1rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n}\n\n.merge-entity-name {\n font-size: 0.82rem;\n color: var(--mj-text-secondary);\n padding: 0.1rem 0.5rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n}\n\n.merge-conflict-badge {\n font-size: 0.78rem;\n color: var(--mj-status-warning-text);\n background: var(--mj-status-warning-bg);\n padding: 0.15rem 0.5rem;\n border-radius: 9999px;\n}\n\n.merge-toggle-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.35rem 0.75rem;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 0.78rem;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.merge-toggle-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n/* Column Headers */\n.merge-columns {\n display: grid;\n grid-template-columns: 1fr 1fr 1fr;\n border-bottom: 2px solid var(--mj-border-default);\n}\n\n.merge-col-label {\n padding: 0.5rem 0.75rem;\n font-weight: 600;\n font-size: 0.78rem;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.03em;\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n.merge-select-all-btn {\n border: none;\n background: none;\n color: var(--mj-text-muted);\n font-size: 0.72rem;\n cursor: pointer;\n padding: 0.2rem;\n border-radius: 3px;\n}\n\n.merge-select-all-btn:hover {\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n/* Field Rows */\n.merge-fields {\n overflow-y: auto;\n max-height: 60vh;\n}\n\n.merge-row {\n display: grid;\n grid-template-columns: 1fr 1fr 1fr;\n border-bottom: 1px solid var(--mj-border-subtle);\n transition: background 0.1s ease;\n}\n\n.merge-row:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.merge-row-conflict {\n background: color-mix(in srgb, var(--mj-status-warning) 5%, var(--mj-bg-surface));\n}\n\n.merge-row-readonly {\n opacity: 0.7;\n}\n\n.merge-cell {\n padding: 0.5rem 0.75rem;\n font-size: 0.85rem;\n display: flex;\n align-items: center;\n gap: 0.4rem;\n min-height: 2.5rem;\n}\n\n.merge-cell-field {\n color: var(--mj-text-primary);\n font-weight: 500;\n border-right: 1px solid var(--mj-border-subtle);\n}\n\n.merge-field-name {\n flex: 1;\n}\n\n.merge-readonly-icon {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n}\n\n.merge-cell-value {\n position: relative;\n}\n\n.merge-cell-selectable {\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.merge-cell-selectable:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.merge-cell-selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n.merge-value {\n color: var(--mj-text-primary);\n flex: 1;\n word-break: break-word;\n}\n\n.merge-value-empty {\n color: var(--mj-text-disabled);\n font-style: italic;\n}\n\n.merge-check-icon {\n color: var(--mj-brand-primary);\n font-size: 0.75rem;\n flex-shrink: 0;\n}\n\n/* Footer */\n.merge-footer {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1rem;\n border-top: 1px solid var(--mj-border-default);\n}\n\n.merge-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.5rem 1rem;\n border-radius: 6px;\n font-size: 0.85rem;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n border: none;\n}\n\n.merge-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.merge-btn-primary {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.merge-btn-primary:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.merge-btn-secondary {\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n border: 1px solid var(--mj-border-default);\n}\n\n.merge-btn-secondary:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n}\n\n/* Confirmation Dialog */\n.merge-dialog-backdrop {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n}\n\n.merge-dialog {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 1.5rem;\n max-width: 420px;\n width: 100%;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n}\n\n.merge-dialog-header {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n margin-bottom: 0.75rem;\n}\n\n.merge-dialog-header h4 {\n margin: 0;\n font-size: 1.1rem;\n color: var(--mj-text-primary);\n}\n\n.merge-dialog-warning-icon {\n color: var(--mj-status-warning);\n font-size: 1.2rem;\n}\n\n.merge-dialog-text {\n color: var(--mj-text-secondary);\n font-size: 0.9rem;\n line-height: 1.5;\n margin-bottom: 1.25rem;\n}\n\n.merge-dialog-actions {\n display: flex;\n gap: 0.5rem;\n}\n\n/* Mobile responsive */\n@media (max-width: 768px) {\n .merge-columns,\n .merge-row {\n grid-template-columns: 1fr;\n }\n\n .merge-cell-field {\n border-right: none;\n border-bottom: 1px solid var(--mj-border-subtle);\n font-size: 0.78rem;\n }\n\n .merge-col-left,\n .merge-col-right {\n padding-left: 1.5rem;\n }\n}\n"] }]
|
|
330
327
|
}], null, { Fields: [{
|
|
331
328
|
type: Input
|
|
332
329
|
}], Config: [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record-merge-panel.component.js","sourceRoot":"","sources":["../../src/lib/record-merge-panel.component.ts","../../src/lib/record-merge-panel.component.html"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Record Merge Panel Component\n *\n * Side-by-side field comparison panel for entity records.\n * Highlights conflicting values and allows field-by-field selection\n * of which value to keep in the merge result.\n */\n\nimport {\n Component,\n EventEmitter,\n Input,\n Output,\n OnInit,\n ChangeDetectorRef,\n inject\n} from '@angular/core';\nimport {\n FieldComparison,\n MergeConfig,\n MergeComparisonResult,\n MergeConfirmedEvent,\n FieldSelectionEvent\n} from './record-merge-types';\n\n@Component({\n standalone: false,\n selector: 'mj-record-merge-panel',\n templateUrl: './record-merge-panel.component.html',\n styleUrls: ['./record-merge-panel.component.css']\n})\nexport class RecordMergePanelComponent implements OnInit {\n private cdr = inject(ChangeDetectorRef);\n\n // --- Inputs ---\n\n /** Field comparisons to display */\n @Input() Fields: FieldComparison[] = [];\n\n /** Merge configuration */\n @Input() Config: MergeConfig | null = null;\n\n /** Whether to show only conflicting fields initially */\n @Input() ShowConflictsOnly = false;\n\n /** Whether the merge action is enabled */\n @Input() MergeEnabled = true;\n\n /** Whether a merge is in progress */\n @Input() IsMerging = false;\n\n // --- Outputs ---\n\n @Output() MergeConfirmed = new EventEmitter<MergeConfirmedEvent>();\n @Output() MergeCancelled = new EventEmitter<void>();\n @Output() FieldSelected = new EventEmitter<FieldSelectionEvent>();\n\n // --- Component State ---\n\n public ShowAllFields = true;\n public Comparison: MergeComparisonResult | null = null;\n public ConfirmDialogVisible = false;\n\n ngOnInit(): void {\n this.ShowAllFields = !this.ShowConflictsOnly;\n this.buildComparison();\n }\n\n /** Toggle between showing all fields and conflicts only */\n public ToggleShowAll(): void {\n this.ShowAllFields = !this.ShowAllFields;\n this.cdr.detectChanges();\n }\n\n /** Select the left value for a field */\n public SelectLeft(field: FieldComparison): void {\n if (field.IsReadOnly) return;\n field.SelectedSide = 'left';\n this.emitFieldSelection(field);\n }\n\n /** Select the right value for a field */\n public SelectRight(field: FieldComparison): void {\n if (field.IsReadOnly) return;\n field.SelectedSide = 'right';\n this.emitFieldSelection(field);\n }\n\n /** Select all fields from the left side */\n public SelectAllLeft(): void {\n for (const field of this.Fields) {\n if (!field.IsReadOnly) {\n field.SelectedSide = 'left';\n }\n }\n this.cdr.detectChanges();\n }\n\n /** Select all fields from the right side */\n public SelectAllRight(): void {\n for (const field of this.Fields) {\n if (!field.IsReadOnly) {\n field.SelectedSide = 'right';\n }\n }\n this.cdr.detectChanges();\n }\n\n /** Get fields to display based on current filter */\n public GetVisibleFields(): FieldComparison[] {\n if (this.ShowAllFields) {\n return this.Fields;\n }\n return this.Fields.filter(f => f.HasConflict);\n }\n\n /** Show the merge confirmation dialog */\n public ShowConfirmDialog(): void {\n this.ConfirmDialogVisible = true;\n this.cdr.detectChanges();\n }\n\n /** Confirm the merge */\n public ConfirmMerge(): void {\n if (!this.Config) return;\n this.ConfirmDialogVisible = false;\n this.MergeConfirmed.emit({\n Config: this.Config,\n ResolvedFields: this.Fields\n });\n }\n\n /** Cancel the merge */\n public CancelMerge(): void {\n this.ConfirmDialogVisible = false;\n this.MergeCancelled.emit();\n }\n\n /** Get the display value for a field value */\n public FormatValue(value: unknown): string {\n if (value === null || value === undefined) {\n return '(empty)';\n }\n if (value instanceof Date) {\n return value.toLocaleString();\n }\n if (typeof value === 'boolean') {\n return value ? 'Yes' : 'No';\n }\n return String(value);\n }\n\n /** Check if all conflict fields have been resolved */\n public AllConflictsResolved(): boolean {\n if (!this.Comparison) return true;\n return this.Comparison.ConflictFields.every(f => f.SelectedSide === 'left' || f.SelectedSide === 'right');\n }\n\n // --- Private Methods ---\n\n private buildComparison(): void {\n const conflictFields = this.Fields.filter(f => f.HasConflict);\n const matchingFields = this.Fields.filter(f => !f.HasConflict);\n this.Comparison = {\n Fields: this.Fields,\n ConflictFields: conflictFields,\n MatchingFields: matchingFields,\n TotalFieldCount: this.Fields.length,\n ConflictCount: conflictFields.length\n };\n }\n\n private emitFieldSelection(field: FieldComparison): void {\n const value = field.SelectedSide === 'left' ? field.LeftValue :\n field.SelectedSide === 'right' ? field.RightValue :\n field.CustomValue;\n this.FieldSelected.emit({\n FieldName: field.FieldName,\n SelectedSide: field.SelectedSide,\n Value: value\n });\n this.cdr.detectChanges();\n }\n}\n","@if (Config && Comparison) {\n <div class=\"merge-panel\">\n <!-- Header -->\n <div class=\"merge-header\">\n <div class=\"merge-header-info\">\n <h3 class=\"merge-title\">Record Comparison</h3>\n <span class=\"merge-entity-name\">{{ Config.EntityName }}</span>\n <span class=\"merge-conflict-badge\">\n {{ Comparison.ConflictCount }} conflicts / {{ Comparison.TotalFieldCount }} fields\n </span>\n </div>\n <div class=\"merge-header-actions\">\n <button class=\"merge-toggle-btn\" (click)=\"ToggleShowAll()\">\n @if (ShowAllFields) {\n <i class=\"fa-solid fa-filter\"></i> Show Conflicts Only\n } @else {\n <i class=\"fa-solid fa-list\"></i> Show All Fields\n }\n </button>\n </div>\n </div>\n\n <!-- Column Headers -->\n <div class=\"merge-columns\">\n <div class=\"merge-col-label merge-col-field\">Field</div>\n <div class=\"merge-col-label merge-col-left\">\n <span>{{ Config.LeftLabel }}</span>\n <button class=\"merge-select-all-btn\" (click)=\"SelectAllLeft()\" title=\"Select all from left\">\n <i class=\"fa-solid fa-check-double\"></i>\n </button>\n </div>\n <div class=\"merge-col-label merge-col-right\">\n <span>{{ Config.RightLabel }}</span>\n <button class=\"merge-select-all-btn\" (click)=\"SelectAllRight()\" title=\"Select all from right\">\n <i class=\"fa-solid fa-check-double\"></i>\n </button>\n </div>\n </div>\n\n <!-- Field Rows -->\n <div class=\"merge-fields\">\n @for (field of GetVisibleFields(); track field.FieldName) {\n <div class=\"merge-row\" [class.merge-row-conflict]=\"field.HasConflict\" [class.merge-row-readonly]=\"field.IsReadOnly\">\n <div class=\"merge-cell merge-cell-field\">\n <span class=\"merge-field-name\">{{ field.DisplayLabel }}</span>\n @if (field.IsReadOnly) {\n <i class=\"fa-solid fa-lock merge-readonly-icon\" title=\"Read-only field\"></i>\n }\n </div>\n <div\n class=\"merge-cell merge-cell-value merge-cell-left\"\n [class.merge-cell-selected]=\"field.SelectedSide === 'left'\"\n [class.merge-cell-selectable]=\"!field.IsReadOnly\"\n (click)=\"SelectLeft(field)\"\n >\n <span class=\"merge-value\" [class.merge-value-empty]=\"field.LeftValue === null || field.LeftValue === undefined\">\n {{ FormatValue(field.LeftValue) }}\n </span>\n @if (field.SelectedSide === 'left' && !field.IsReadOnly) {\n <i class=\"fa-solid fa-check merge-check-icon\"></i>\n }\n </div>\n <div\n class=\"merge-cell merge-cell-value merge-cell-right\"\n [class.merge-cell-selected]=\"field.SelectedSide === 'right'\"\n [class.merge-cell-selectable]=\"!field.IsReadOnly\"\n (click)=\"SelectRight(field)\"\n >\n <span class=\"merge-value\" [class.merge-value-empty]=\"field.RightValue === null || field.RightValue === undefined\">\n {{ FormatValue(field.RightValue) }}\n </span>\n @if (field.SelectedSide === 'right' && !field.IsReadOnly) {\n <i class=\"fa-solid fa-check merge-check-icon\"></i>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- Footer Actions -->\n @if (MergeEnabled) {\n <div class=\"merge-footer\">\n <button class=\"merge-btn merge-btn-primary\" (click)=\"ShowConfirmDialog()\" [disabled]=\"IsMerging\">\n @if (IsMerging) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Merging...\n } @else {\n <i class=\"fa-solid fa-code-merge\"></i> Merge Records\n }\n </button>\n <button class=\"merge-btn merge-btn-secondary\" (click)=\"CancelMerge()\" [disabled]=\"IsMerging\">\n Cancel\n </button>\n </div>\n }\n </div>\n\n <!-- Confirmation Dialog -->\n @if (ConfirmDialogVisible) {\n <div class=\"merge-dialog-backdrop\" (click)=\"ConfirmDialogVisible = false\">\n <div class=\"merge-dialog\" (click)=\"$event.stopPropagation()\">\n <div class=\"merge-dialog-header\">\n <i class=\"fa-solid fa-exclamation-triangle merge-dialog-warning-icon\"></i>\n <h4>Confirm Merge</h4>\n </div>\n <p class=\"merge-dialog-text\">\n This will merge the selected field values and create a merge log.\n This action cannot be undone. Are you sure?\n </p>\n <div class=\"merge-dialog-actions\">\n <button class=\"merge-btn merge-btn-primary\" (click)=\"ConfirmMerge()\">\n <i class=\"fa-solid fa-code-merge\"></i> Confirm Merge\n </button>\n <button class=\"merge-btn merge-btn-secondary\" (click)=\"ConfirmDialogVisible = false\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n }\n} @else {\n <div class=\"merge-panel merge-panel-empty\">\n <i class=\"fa-solid fa-code-compare merge-empty-icon\"></i>\n <p>Select two records to compare</p>\n </div>\n}\n"]}
|
|
1
|
+
{"version":3,"file":"record-merge-panel.component.js","sourceRoot":"","sources":["../../src/lib/record-merge-panel.component.ts","../../src/lib/record-merge-panel.component.html"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Record Merge Panel Component\n *\n * Side-by-side field comparison panel for entity records.\n * Highlights conflicting values and allows field-by-field selection\n * of which value to keep in the merge result.\n */\n\nimport {\n Component,\n EventEmitter,\n Input,\n Output,\n OnInit,\n ChangeDetectorRef,\n inject\n} from '@angular/core';\nimport {\n FieldComparison,\n MergeConfig,\n MergeComparisonResult,\n MergeConfirmedEvent,\n FieldSelectionEvent\n} from './record-merge-types';\n\n@Component({\n standalone: false,\n selector: 'mj-record-merge-panel',\n templateUrl: './record-merge-panel.component.html',\n styleUrls: ['./record-merge-panel.component.css']\n})\nexport class RecordMergePanelComponent implements OnInit {\n private cdr = inject(ChangeDetectorRef);\n\n // --- Inputs ---\n\n /** Field comparisons to display */\n @Input() Fields: FieldComparison[] = [];\n\n /** Merge configuration */\n @Input() Config: MergeConfig | null = null;\n\n /** Whether to show only conflicting fields initially */\n @Input() ShowConflictsOnly = false;\n\n /** Whether the merge action is enabled */\n @Input() MergeEnabled = true;\n\n /** Whether a merge is in progress */\n @Input() IsMerging = false;\n\n // --- Outputs ---\n\n @Output() MergeConfirmed = new EventEmitter<MergeConfirmedEvent>();\n @Output() MergeCancelled = new EventEmitter<void>();\n @Output() FieldSelected = new EventEmitter<FieldSelectionEvent>();\n\n // --- Component State ---\n\n public ShowAllFields = true;\n public Comparison: MergeComparisonResult | null = null;\n public ConfirmDialogVisible = false;\n\n ngOnInit(): void {\n this.ShowAllFields = !this.ShowConflictsOnly;\n this.buildComparison();\n }\n\n /** Toggle between showing all fields and conflicts only */\n public ToggleShowAll(): void {\n this.ShowAllFields = !this.ShowAllFields;\n this.cdr.detectChanges();\n }\n\n /** Select the left value for a field */\n public SelectLeft(field: FieldComparison): void {\n if (field.IsReadOnly) return;\n field.SelectedSide = 'left';\n this.emitFieldSelection(field);\n }\n\n /** Select the right value for a field */\n public SelectRight(field: FieldComparison): void {\n if (field.IsReadOnly) return;\n field.SelectedSide = 'right';\n this.emitFieldSelection(field);\n }\n\n /** Select all fields from the left side */\n public SelectAllLeft(): void {\n for (const field of this.Fields) {\n if (!field.IsReadOnly) {\n field.SelectedSide = 'left';\n }\n }\n this.cdr.detectChanges();\n }\n\n /** Select all fields from the right side */\n public SelectAllRight(): void {\n for (const field of this.Fields) {\n if (!field.IsReadOnly) {\n field.SelectedSide = 'right';\n }\n }\n this.cdr.detectChanges();\n }\n\n /** Get fields to display based on current filter */\n public GetVisibleFields(): FieldComparison[] {\n if (this.ShowAllFields) {\n return this.Fields;\n }\n return this.Fields.filter(f => f.HasConflict);\n }\n\n /** Show the merge confirmation dialog */\n public ShowConfirmDialog(): void {\n this.ConfirmDialogVisible = true;\n this.cdr.detectChanges();\n }\n\n /** Confirm the merge */\n public ConfirmMerge(): void {\n if (!this.Config) return;\n this.ConfirmDialogVisible = false;\n this.MergeConfirmed.emit({\n Config: this.Config,\n ResolvedFields: this.Fields\n });\n }\n\n /** Cancel the merge */\n public CancelMerge(): void {\n this.ConfirmDialogVisible = false;\n this.MergeCancelled.emit();\n }\n\n /** Get the display value for a field value */\n public FormatValue(value: unknown): string {\n if (value === null || value === undefined) {\n return '(empty)';\n }\n if (value instanceof Date) {\n return value.toLocaleString();\n }\n if (typeof value === 'boolean') {\n return value ? 'Yes' : 'No';\n }\n return String(value);\n }\n\n /** Check if all conflict fields have been resolved */\n public AllConflictsResolved(): boolean {\n if (!this.Comparison) return true;\n return this.Comparison.ConflictFields.every(f => f.SelectedSide === 'left' || f.SelectedSide === 'right');\n }\n\n // --- Private Methods ---\n\n private buildComparison(): void {\n const conflictFields = this.Fields.filter(f => f.HasConflict);\n const matchingFields = this.Fields.filter(f => !f.HasConflict);\n this.Comparison = {\n Fields: this.Fields,\n ConflictFields: conflictFields,\n MatchingFields: matchingFields,\n TotalFieldCount: this.Fields.length,\n ConflictCount: conflictFields.length\n };\n }\n\n private emitFieldSelection(field: FieldComparison): void {\n const value = field.SelectedSide === 'left' ? field.LeftValue :\n field.SelectedSide === 'right' ? field.RightValue :\n field.CustomValue;\n this.FieldSelected.emit({\n FieldName: field.FieldName,\n SelectedSide: field.SelectedSide,\n Value: value\n });\n this.cdr.detectChanges();\n }\n}\n","@if (Config && Comparison) {\n <div class=\"merge-panel\">\n <!-- Header -->\n <div class=\"merge-header\">\n <div class=\"merge-header-info\">\n <h3 class=\"merge-title\">Record Comparison</h3>\n <span class=\"merge-entity-name\">{{ Config.EntityName }}</span>\n <span class=\"merge-conflict-badge\">\n {{ Comparison.ConflictCount }} conflicts / {{ Comparison.TotalFieldCount }} fields\n </span>\n </div>\n <div class=\"merge-header-actions\">\n <button class=\"merge-toggle-btn\" (click)=\"ToggleShowAll()\">\n @if (ShowAllFields) {\n <i class=\"fa-solid fa-filter\"></i> Show Conflicts Only\n } @else {\n <i class=\"fa-solid fa-list\"></i> Show All Fields\n }\n </button>\n </div>\n </div>\n\n <!-- Column Headers -->\n <div class=\"merge-columns\">\n <div class=\"merge-col-label merge-col-field\">Field</div>\n <div class=\"merge-col-label merge-col-left\">\n <span>{{ Config.LeftLabel }}</span>\n <button class=\"merge-select-all-btn\" (click)=\"SelectAllLeft()\" title=\"Select all from left\">\n <i class=\"fa-solid fa-check-double\"></i>\n </button>\n </div>\n <div class=\"merge-col-label merge-col-right\">\n <span>{{ Config.RightLabel }}</span>\n <button class=\"merge-select-all-btn\" (click)=\"SelectAllRight()\" title=\"Select all from right\">\n <i class=\"fa-solid fa-check-double\"></i>\n </button>\n </div>\n </div>\n\n <!-- Field Rows -->\n <div class=\"merge-fields\">\n @for (field of GetVisibleFields(); track field.FieldName) {\n <div class=\"merge-row\" [class.merge-row-conflict]=\"field.HasConflict\" [class.merge-row-readonly]=\"field.IsReadOnly\">\n <div class=\"merge-cell merge-cell-field\">\n <span class=\"merge-field-name\">{{ field.DisplayLabel }}</span>\n @if (field.IsReadOnly) {\n <i class=\"fa-solid fa-lock merge-readonly-icon\" title=\"Read-only field\"></i>\n }\n </div>\n <div\n class=\"merge-cell merge-cell-value merge-cell-left\"\n [class.merge-cell-selected]=\"field.SelectedSide === 'left'\"\n [class.merge-cell-selectable]=\"!field.IsReadOnly\"\n (click)=\"SelectLeft(field)\"\n >\n <span class=\"merge-value\" [class.merge-value-empty]=\"field.LeftValue === null || field.LeftValue === undefined\">\n {{ FormatValue(field.LeftValue) }}\n </span>\n @if (field.SelectedSide === 'left' && !field.IsReadOnly) {\n <i class=\"fa-solid fa-check merge-check-icon\"></i>\n }\n </div>\n <div\n class=\"merge-cell merge-cell-value merge-cell-right\"\n [class.merge-cell-selected]=\"field.SelectedSide === 'right'\"\n [class.merge-cell-selectable]=\"!field.IsReadOnly\"\n (click)=\"SelectRight(field)\"\n >\n <span class=\"merge-value\" [class.merge-value-empty]=\"field.RightValue === null || field.RightValue === undefined\">\n {{ FormatValue(field.RightValue) }}\n </span>\n @if (field.SelectedSide === 'right' && !field.IsReadOnly) {\n <i class=\"fa-solid fa-check merge-check-icon\"></i>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- Footer Actions -->\n @if (MergeEnabled) {\n <div class=\"merge-footer\">\n <button class=\"merge-btn merge-btn-primary\" (click)=\"ShowConfirmDialog()\" [disabled]=\"IsMerging\">\n @if (IsMerging) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Merging...\n } @else {\n <i class=\"fa-solid fa-code-merge\"></i> Merge Records\n }\n </button>\n <button class=\"merge-btn merge-btn-secondary\" (click)=\"CancelMerge()\" [disabled]=\"IsMerging\">\n Cancel\n </button>\n </div>\n }\n </div>\n\n <!-- Confirmation Dialog -->\n @if (ConfirmDialogVisible) {\n <div class=\"merge-dialog-backdrop\" (click)=\"ConfirmDialogVisible = false\">\n <div class=\"merge-dialog\" (click)=\"$event.stopPropagation()\">\n <div class=\"merge-dialog-header\">\n <i class=\"fa-solid fa-exclamation-triangle merge-dialog-warning-icon\"></i>\n <h4>Confirm Merge</h4>\n </div>\n <p class=\"merge-dialog-text\">\n This will merge the selected field values and create a merge log.\n This action cannot be undone. Are you sure?\n </p>\n <div class=\"merge-dialog-actions\">\n <button class=\"merge-btn merge-btn-primary\" (click)=\"ConfirmMerge()\">\n <i class=\"fa-solid fa-code-merge\"></i> Confirm Merge\n </button>\n <button class=\"merge-btn merge-btn-secondary\" (click)=\"ConfirmDialogVisible = false\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n }\n} @else {\n <mj-empty-state\n Icon=\"fa-solid fa-code-compare\"\n Title=\"Select two records to compare\" />\n}\n"]}
|
package/dist/public-api.d.ts
CHANGED
package/dist/public-api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAIA,cAAc,0BAA0B,CAAC;AACzC,cAAc,oCAAoC,CAAC;AACnD,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAIA,cAAc,0BAA0B,CAAC;AACzC,cAAc,oCAAoC,CAAC;AACnD,cAAc,iCAAiC,CAAC;AAChD,cAAc,cAAc,CAAC"}
|
package/dist/public-api.js
CHANGED
package/dist/public-api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,0BAA0B,CAAC;AACzC,cAAc,oCAAoC,CAAC;AACnD,cAAc,cAAc,CAAC","sourcesContent":["/*\n * Public API Surface of @memberjunction/ng-record-merge\n */\n\nexport * from './lib/record-merge-types';\nexport * from './lib/record-merge-panel.component';\nexport * from './lib/module';\n"]}
|
|
1
|
+
{"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,0BAA0B,CAAC;AACzC,cAAc,oCAAoC,CAAC;AACnD,cAAc,iCAAiC,CAAC;AAChD,cAAc,cAAc,CAAC","sourcesContent":["/*\n * Public API Surface of @memberjunction/ng-record-merge\n */\n\nexport * from './lib/record-merge-types';\nexport * from './lib/record-merge-panel.component';\nexport * from './lib/record-comparison.service';\nexport * from './lib/module';\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-record-merge",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.44.0",
|
|
4
4
|
"description": "MemberJunction: Reusable record merge/comparison panel for side-by-side entity record comparison and field-by-field merge selection",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@angular/compiler": "21.1.3",
|
|
20
20
|
"@angular/compiler-cli": "21.1.3",
|
|
21
|
+
"@memberjunction/ng-test-utils": "5.44.0",
|
|
21
22
|
"vitest": "^4.0.18"
|
|
22
23
|
},
|
|
23
24
|
"peerDependencies": {
|
|
@@ -25,9 +26,11 @@
|
|
|
25
26
|
"@angular/core": "21.1.3"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
28
|
-
"@memberjunction/
|
|
29
|
-
"@memberjunction/
|
|
30
|
-
"@memberjunction/
|
|
29
|
+
"@memberjunction/ng-ui-components": "5.44.0",
|
|
30
|
+
"@memberjunction/core": "5.44.0",
|
|
31
|
+
"@memberjunction/core-entities": "5.44.0",
|
|
32
|
+
"@memberjunction/global": "5.44.0",
|
|
33
|
+
"@memberjunction/ng-shared-generic": "5.44.0",
|
|
31
34
|
"rxjs": "^7.8.2",
|
|
32
35
|
"tslib": "^2.8.1"
|
|
33
36
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"record-merge-types.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/record-merge-types.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
describe('Record Merge Types', () => {
|
|
3
|
-
describe('FieldComparison', () => {
|
|
4
|
-
it('should represent a matching field', () => {
|
|
5
|
-
const field = {
|
|
6
|
-
FieldName: 'FirstName',
|
|
7
|
-
DisplayLabel: 'First Name',
|
|
8
|
-
LeftValue: 'John',
|
|
9
|
-
RightValue: 'John',
|
|
10
|
-
HasConflict: false,
|
|
11
|
-
SelectedSide: 'left',
|
|
12
|
-
IsReadOnly: false,
|
|
13
|
-
DataType: 'string'
|
|
14
|
-
};
|
|
15
|
-
expect(field.HasConflict).toBe(false);
|
|
16
|
-
expect(field.LeftValue).toBe(field.RightValue);
|
|
17
|
-
});
|
|
18
|
-
it('should represent a conflicting field', () => {
|
|
19
|
-
const field = {
|
|
20
|
-
FieldName: 'Email',
|
|
21
|
-
DisplayLabel: 'Email',
|
|
22
|
-
LeftValue: 'john@a.com',
|
|
23
|
-
RightValue: 'john@b.com',
|
|
24
|
-
HasConflict: true,
|
|
25
|
-
SelectedSide: 'left',
|
|
26
|
-
IsReadOnly: false,
|
|
27
|
-
DataType: 'string'
|
|
28
|
-
};
|
|
29
|
-
expect(field.HasConflict).toBe(true);
|
|
30
|
-
expect(field.LeftValue).not.toBe(field.RightValue);
|
|
31
|
-
});
|
|
32
|
-
it('should support read-only fields', () => {
|
|
33
|
-
const field = {
|
|
34
|
-
FieldName: 'ID',
|
|
35
|
-
DisplayLabel: 'ID',
|
|
36
|
-
LeftValue: 'abc-123',
|
|
37
|
-
RightValue: 'def-456',
|
|
38
|
-
HasConflict: true,
|
|
39
|
-
SelectedSide: 'left',
|
|
40
|
-
IsReadOnly: true,
|
|
41
|
-
DataType: 'string'
|
|
42
|
-
};
|
|
43
|
-
expect(field.IsReadOnly).toBe(true);
|
|
44
|
-
});
|
|
45
|
-
it('should support custom value selection', () => {
|
|
46
|
-
const field = {
|
|
47
|
-
FieldName: 'Phone',
|
|
48
|
-
DisplayLabel: 'Phone',
|
|
49
|
-
LeftValue: '555-0100',
|
|
50
|
-
RightValue: '555-0200',
|
|
51
|
-
HasConflict: true,
|
|
52
|
-
SelectedSide: 'custom',
|
|
53
|
-
CustomValue: '555-0150',
|
|
54
|
-
IsReadOnly: false,
|
|
55
|
-
DataType: 'string'
|
|
56
|
-
};
|
|
57
|
-
expect(field.SelectedSide).toBe('custom');
|
|
58
|
-
expect(field.CustomValue).toBe('555-0150');
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
describe('MergeConfig', () => {
|
|
62
|
-
it('should define merge parameters', () => {
|
|
63
|
-
const config = {
|
|
64
|
-
EntityName: 'Contacts',
|
|
65
|
-
LeftRecordID: 'rec-1',
|
|
66
|
-
RightRecordID: 'rec-2',
|
|
67
|
-
SurvivorSide: 'left',
|
|
68
|
-
LeftLabel: 'Record A',
|
|
69
|
-
RightLabel: 'Record B'
|
|
70
|
-
};
|
|
71
|
-
expect(config.SurvivorSide).toBe('left');
|
|
72
|
-
expect(config.EntityName).toBe('Contacts');
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
describe('MergeComparisonResult', () => {
|
|
76
|
-
it('should separate conflict and matching fields', () => {
|
|
77
|
-
const result = {
|
|
78
|
-
Fields: [],
|
|
79
|
-
ConflictFields: [
|
|
80
|
-
createField('Email', true),
|
|
81
|
-
createField('Phone', true)
|
|
82
|
-
],
|
|
83
|
-
MatchingFields: [
|
|
84
|
-
createField('FirstName', false),
|
|
85
|
-
createField('LastName', false),
|
|
86
|
-
createField('City', false)
|
|
87
|
-
],
|
|
88
|
-
TotalFieldCount: 5,
|
|
89
|
-
ConflictCount: 2
|
|
90
|
-
};
|
|
91
|
-
expect(result.ConflictCount).toBe(2);
|
|
92
|
-
expect(result.MatchingFields).toHaveLength(3);
|
|
93
|
-
expect(result.TotalFieldCount).toBe(5);
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
describe('MergeActionResult', () => {
|
|
97
|
-
it('should represent successful merge', () => {
|
|
98
|
-
const result = {
|
|
99
|
-
Success: true,
|
|
100
|
-
SurvivorRecordID: 'rec-1',
|
|
101
|
-
MergedRecordID: 'rec-2',
|
|
102
|
-
UpdatedFieldCount: 3
|
|
103
|
-
};
|
|
104
|
-
expect(result.Success).toBe(true);
|
|
105
|
-
expect(result.UpdatedFieldCount).toBe(3);
|
|
106
|
-
});
|
|
107
|
-
it('should represent failed merge', () => {
|
|
108
|
-
const result = {
|
|
109
|
-
Success: false,
|
|
110
|
-
SurvivorRecordID: 'rec-1',
|
|
111
|
-
MergedRecordID: 'rec-2',
|
|
112
|
-
UpdatedFieldCount: 0,
|
|
113
|
-
ErrorMessage: 'Record locked by another user'
|
|
114
|
-
};
|
|
115
|
-
expect(result.Success).toBe(false);
|
|
116
|
-
expect(result.ErrorMessage).toBeDefined();
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
function createField(name, hasConflict) {
|
|
121
|
-
return {
|
|
122
|
-
FieldName: name,
|
|
123
|
-
DisplayLabel: name,
|
|
124
|
-
LeftValue: `left-${name}`,
|
|
125
|
-
RightValue: hasConflict ? `right-${name}` : `left-${name}`,
|
|
126
|
-
HasConflict: hasConflict,
|
|
127
|
-
SelectedSide: 'left',
|
|
128
|
-
IsReadOnly: false,
|
|
129
|
-
DataType: 'string'
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
//# sourceMappingURL=record-merge-types.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"record-merge-types.test.js","sourceRoot":"","sources":["../../src/__tests__/record-merge-types.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect } from 'vitest';\nimport {\n FieldComparison,\n MergeConfig,\n MergeComparisonResult,\n MergeActionResult,\n MergeConfirmedEvent,\n FieldSelectionEvent\n} from '../lib/record-merge-types';\n\ndescribe('Record Merge Types', () => {\n describe('FieldComparison', () => {\n it('should represent a matching field', () => {\n const field: FieldComparison = {\n FieldName: 'FirstName',\n DisplayLabel: 'First Name',\n LeftValue: 'John',\n RightValue: 'John',\n HasConflict: false,\n SelectedSide: 'left',\n IsReadOnly: false,\n DataType: 'string'\n };\n\n expect(field.HasConflict).toBe(false);\n expect(field.LeftValue).toBe(field.RightValue);\n });\n\n it('should represent a conflicting field', () => {\n const field: FieldComparison = {\n FieldName: 'Email',\n DisplayLabel: 'Email',\n LeftValue: 'john@a.com',\n RightValue: 'john@b.com',\n HasConflict: true,\n SelectedSide: 'left',\n IsReadOnly: false,\n DataType: 'string'\n };\n\n expect(field.HasConflict).toBe(true);\n expect(field.LeftValue).not.toBe(field.RightValue);\n });\n\n it('should support read-only fields', () => {\n const field: FieldComparison = {\n FieldName: 'ID',\n DisplayLabel: 'ID',\n LeftValue: 'abc-123',\n RightValue: 'def-456',\n HasConflict: true,\n SelectedSide: 'left',\n IsReadOnly: true,\n DataType: 'string'\n };\n\n expect(field.IsReadOnly).toBe(true);\n });\n\n it('should support custom value selection', () => {\n const field: FieldComparison = {\n FieldName: 'Phone',\n DisplayLabel: 'Phone',\n LeftValue: '555-0100',\n RightValue: '555-0200',\n HasConflict: true,\n SelectedSide: 'custom',\n CustomValue: '555-0150',\n IsReadOnly: false,\n DataType: 'string'\n };\n\n expect(field.SelectedSide).toBe('custom');\n expect(field.CustomValue).toBe('555-0150');\n });\n });\n\n describe('MergeConfig', () => {\n it('should define merge parameters', () => {\n const config: MergeConfig = {\n EntityName: 'Contacts',\n LeftRecordID: 'rec-1',\n RightRecordID: 'rec-2',\n SurvivorSide: 'left',\n LeftLabel: 'Record A',\n RightLabel: 'Record B'\n };\n\n expect(config.SurvivorSide).toBe('left');\n expect(config.EntityName).toBe('Contacts');\n });\n });\n\n describe('MergeComparisonResult', () => {\n it('should separate conflict and matching fields', () => {\n const result: MergeComparisonResult = {\n Fields: [],\n ConflictFields: [\n createField('Email', true),\n createField('Phone', true)\n ],\n MatchingFields: [\n createField('FirstName', false),\n createField('LastName', false),\n createField('City', false)\n ],\n TotalFieldCount: 5,\n ConflictCount: 2\n };\n\n expect(result.ConflictCount).toBe(2);\n expect(result.MatchingFields).toHaveLength(3);\n expect(result.TotalFieldCount).toBe(5);\n });\n });\n\n describe('MergeActionResult', () => {\n it('should represent successful merge', () => {\n const result: MergeActionResult = {\n Success: true,\n SurvivorRecordID: 'rec-1',\n MergedRecordID: 'rec-2',\n UpdatedFieldCount: 3\n };\n\n expect(result.Success).toBe(true);\n expect(result.UpdatedFieldCount).toBe(3);\n });\n\n it('should represent failed merge', () => {\n const result: MergeActionResult = {\n Success: false,\n SurvivorRecordID: 'rec-1',\n MergedRecordID: 'rec-2',\n UpdatedFieldCount: 0,\n ErrorMessage: 'Record locked by another user'\n };\n\n expect(result.Success).toBe(false);\n expect(result.ErrorMessage).toBeDefined();\n });\n });\n});\n\nfunction createField(name: string, hasConflict: boolean): FieldComparison {\n return {\n FieldName: name,\n DisplayLabel: name,\n LeftValue: `left-${name}`,\n RightValue: hasConflict ? `right-${name}` : `left-${name}`,\n HasConflict: hasConflict,\n SelectedSide: 'left',\n IsReadOnly: false,\n DataType: 'string'\n };\n}\n"]}
|