@memberjunction/ng-dashboards 5.20.0 → 5.22.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/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts +102 -0
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts.map +1 -0
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js +583 -0
- package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js.map +1 -0
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts +148 -0
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts.map +1 -0
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.js +773 -0
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.js.map +1 -0
- package/dist/AI/components/vectors/vector-management-resource.component.d.ts +241 -0
- package/dist/AI/components/vectors/vector-management-resource.component.d.ts.map +1 -0
- package/dist/AI/components/vectors/vector-management-resource.component.js +1782 -0
- package/dist/AI/components/vectors/vector-management-resource.component.js.map +1 -0
- package/dist/AI/index.d.ts +3 -0
- package/dist/AI/index.d.ts.map +1 -1
- package/dist/AI/index.js +6 -0
- package/dist/AI/index.js.map +1 -1
- package/dist/AI/services/ai-instrumentation.service.d.ts +50 -7
- package/dist/AI/services/ai-instrumentation.service.d.ts.map +1 -1
- package/dist/AI/services/ai-instrumentation.service.js +161 -193
- package/dist/AI/services/ai-instrumentation.service.js.map +1 -1
- package/dist/DashboardBrowser/dashboard-browser-resource.component.d.ts +11 -0
- package/dist/DashboardBrowser/dashboard-browser-resource.component.d.ts.map +1 -1
- package/dist/DashboardBrowser/dashboard-browser-resource.component.js +57 -0
- package/dist/DashboardBrowser/dashboard-browser-resource.component.js.map +1 -1
- package/dist/DataExplorer/components/navigation-panel/navigation-panel.component.d.ts +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts +10 -2
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.js +35 -11
- package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
- package/dist/DataExplorer/data-explorer-resource.component.d.ts +1 -0
- package/dist/DataExplorer/data-explorer-resource.component.d.ts.map +1 -1
- package/dist/DataExplorer/data-explorer-resource.component.js +8 -4
- package/dist/DataExplorer/data-explorer-resource.component.js.map +1 -1
- package/dist/Home/home-dashboard.component.d.ts +181 -1
- package/dist/Home/home-dashboard.component.d.ts.map +1 -1
- package/dist/Home/home-dashboard.component.js +1704 -182
- package/dist/Home/home-dashboard.component.js.map +1 -1
- package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.d.ts +121 -0
- package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.d.ts.map +1 -0
- package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js +1058 -0
- package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js.map +1 -0
- package/dist/KnowledgeHub/components/results-detail/search-result-detail.component.d.ts +56 -0
- package/dist/KnowledgeHub/components/results-detail/search-result-detail.component.d.ts.map +1 -0
- package/dist/KnowledgeHub/components/results-detail/search-result-detail.component.js +291 -0
- package/dist/KnowledgeHub/components/results-detail/search-result-detail.component.js.map +1 -0
- package/dist/KnowledgeHub/components/search/knowledge-search-resource.component.d.ts +85 -0
- package/dist/KnowledgeHub/components/search/knowledge-search-resource.component.d.ts.map +1 -0
- package/dist/KnowledgeHub/components/search/knowledge-search-resource.component.js +461 -0
- package/dist/KnowledgeHub/components/search/knowledge-search-resource.component.js.map +1 -0
- package/dist/KnowledgeHub/index.d.ts +4 -0
- package/dist/KnowledgeHub/index.d.ts.map +1 -0
- package/dist/KnowledgeHub/index.js +5 -0
- package/dist/KnowledgeHub/index.js.map +1 -0
- package/dist/QueryBrowser/query-browser-resource.component.d.ts.map +1 -1
- package/dist/QueryBrowser/query-browser-resource.component.js +5 -1
- package/dist/QueryBrowser/query-browser-resource.component.js.map +1 -1
- package/dist/SystemDiagnostics/system-diagnostics.component.d.ts.map +1 -1
- package/dist/SystemDiagnostics/system-diagnostics.component.js +1 -0
- package/dist/SystemDiagnostics/system-diagnostics.component.js.map +1 -1
- package/dist/Testing/components/testing-dashboard-tab-resource.component.d.ts +7 -1
- package/dist/Testing/components/testing-dashboard-tab-resource.component.d.ts.map +1 -1
- package/dist/Testing/components/testing-dashboard-tab-resource.component.js +63 -8
- package/dist/Testing/components/testing-dashboard-tab-resource.component.js.map +1 -1
- package/dist/Testing/components/testing-dashboard-tab.component.d.ts +9 -1
- package/dist/Testing/components/testing-dashboard-tab.component.d.ts.map +1 -1
- package/dist/Testing/components/testing-dashboard-tab.component.js +109 -62
- package/dist/Testing/components/testing-dashboard-tab.component.js.map +1 -1
- package/dist/Testing/components/testing-explorer.component.d.ts +2 -1
- package/dist/Testing/components/testing-explorer.component.d.ts.map +1 -1
- package/dist/Testing/components/testing-explorer.component.js +241 -200
- package/dist/Testing/components/testing-explorer.component.js.map +1 -1
- package/dist/Testing/components/testing-runs-resource.component.d.ts +7 -1
- package/dist/Testing/components/testing-runs-resource.component.d.ts.map +1 -1
- package/dist/Testing/components/testing-runs-resource.component.js +63 -8
- package/dist/Testing/components/testing-runs-resource.component.js.map +1 -1
- package/dist/Testing/components/testing-runs.component.d.ts.map +1 -1
- package/dist/Testing/components/testing-runs.component.js +7 -5
- package/dist/Testing/components/testing-runs.component.js.map +1 -1
- package/dist/Testing/testing-dashboard.component.d.ts +9 -1
- package/dist/Testing/testing-dashboard.component.d.ts.map +1 -1
- package/dist/Testing/testing-dashboard.component.js +128 -59
- package/dist/Testing/testing-dashboard.component.js.map +1 -1
- package/dist/ai-dashboards.module.d.ts +26 -19
- package/dist/ai-dashboards.module.d.ts.map +1 -1
- package/dist/ai-dashboards.module.js +40 -5
- package/dist/ai-dashboards.module.js.map +1 -1
- package/dist/core-dashboards.module.d.ts +1 -0
- package/dist/core-dashboards.module.d.ts.map +1 -1
- package/dist/core-dashboards.module.js +3 -0
- package/dist/core-dashboards.module.js.map +1 -1
- package/dist/public-api.d.ts +4 -1
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +5 -1
- package/dist/public-api.js.map +1 -1
- package/dist/testing-dashboards.module.d.ts +5 -4
- package/dist/testing-dashboards.module.d.ts.map +1 -1
- package/dist/testing-dashboards.module.js +4 -0
- package/dist/testing-dashboards.module.js.map +1 -1
- package/package.json +44 -42
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* @fileoverview Duplicate Detection Kanban Board Resource Component
|
|
9
|
+
*
|
|
10
|
+
* Dashboard resource for reviewing duplicate detection results in a Kanban-style
|
|
11
|
+
* board with three columns: Pending Review, Approved, and Rejected.
|
|
12
|
+
* Loads data from MJ: Duplicate Runs, MJ: Duplicate Run Details, and
|
|
13
|
+
* MJ: Duplicate Run Detail Matches entities.
|
|
14
|
+
*/
|
|
15
|
+
import { Component, ChangeDetectorRef, Input, inject } from '@angular/core';
|
|
16
|
+
import { Subject } from 'rxjs';
|
|
17
|
+
import { debounceTime, takeUntil } from 'rxjs/operators';
|
|
18
|
+
import { RunView } from '@memberjunction/core';
|
|
19
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
20
|
+
import { BaseResourceComponent } from '@memberjunction/ng-shared';
|
|
21
|
+
import * as i0 from "@angular/core";
|
|
22
|
+
import * as i1 from "@angular/forms";
|
|
23
|
+
import * as i2 from "@memberjunction/ng-shared-generic";
|
|
24
|
+
const _forTrack0 = ($index, $item) => $item.DetailId;
|
|
25
|
+
function DuplicateDetectionResourceComponent_For_33_Template(rf, ctx) { if (rf & 1) {
|
|
26
|
+
i0.ɵɵelementStart(0, "option", 16);
|
|
27
|
+
i0.ɵɵtext(1);
|
|
28
|
+
i0.ɵɵelementEnd();
|
|
29
|
+
} if (rf & 2) {
|
|
30
|
+
const name_r1 = ctx.$implicit;
|
|
31
|
+
i0.ɵɵproperty("value", name_r1);
|
|
32
|
+
i0.ɵɵadvance();
|
|
33
|
+
i0.ɵɵtextInterpolate(name_r1);
|
|
34
|
+
} }
|
|
35
|
+
function DuplicateDetectionResourceComponent_Conditional_50_Template(rf, ctx) { if (rf & 1) {
|
|
36
|
+
const _r2 = i0.ɵɵgetCurrentView();
|
|
37
|
+
i0.ɵɵelementStart(0, "button", 26);
|
|
38
|
+
i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_50_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.ClearFilters()); });
|
|
39
|
+
i0.ɵɵelement(1, "i", 27);
|
|
40
|
+
i0.ɵɵtext(2, " Clear Filters ");
|
|
41
|
+
i0.ɵɵelementEnd();
|
|
42
|
+
} }
|
|
43
|
+
function DuplicateDetectionResourceComponent_Conditional_51_Template(rf, ctx) { if (rf & 1) {
|
|
44
|
+
i0.ɵɵelementStart(0, "div", 22);
|
|
45
|
+
i0.ɵɵelement(1, "mj-loading", 28);
|
|
46
|
+
i0.ɵɵelementEnd();
|
|
47
|
+
} }
|
|
48
|
+
function DuplicateDetectionResourceComponent_Conditional_52_Template(rf, ctx) { if (rf & 1) {
|
|
49
|
+
i0.ɵɵelementStart(0, "div", 23);
|
|
50
|
+
i0.ɵɵelement(1, "i", 29);
|
|
51
|
+
i0.ɵɵelementStart(2, "p", 30);
|
|
52
|
+
i0.ɵɵtext(3, "No duplicate detection results found.");
|
|
53
|
+
i0.ɵɵelementEnd();
|
|
54
|
+
i0.ɵɵelementStart(4, "p", 31);
|
|
55
|
+
i0.ɵɵtext(5, "Run a duplicate detection scan to see results here.");
|
|
56
|
+
i0.ɵɵelementEnd()();
|
|
57
|
+
} }
|
|
58
|
+
function DuplicateDetectionResourceComponent_Conditional_53_For_10_Template(rf, ctx) { if (rf & 1) {
|
|
59
|
+
const _r4 = i0.ɵɵgetCurrentView();
|
|
60
|
+
i0.ɵɵelementStart(0, "div", 38)(1, "div", 44)(2, "span", 45);
|
|
61
|
+
i0.ɵɵtext(3);
|
|
62
|
+
i0.ɵɵelementEnd();
|
|
63
|
+
i0.ɵɵelementStart(4, "span", 46);
|
|
64
|
+
i0.ɵɵtext(5);
|
|
65
|
+
i0.ɵɵelementEnd()();
|
|
66
|
+
i0.ɵɵelementStart(6, "div", 47)(7, "div", 48);
|
|
67
|
+
i0.ɵɵelement(8, "i", 49);
|
|
68
|
+
i0.ɵɵelementStart(9, "span", 50);
|
|
69
|
+
i0.ɵɵtext(10);
|
|
70
|
+
i0.ɵɵelementEnd()();
|
|
71
|
+
i0.ɵɵelementStart(11, "div", 48);
|
|
72
|
+
i0.ɵɵelement(12, "i", 51);
|
|
73
|
+
i0.ɵɵelementStart(13, "span", 52);
|
|
74
|
+
i0.ɵɵtext(14);
|
|
75
|
+
i0.ɵɵelementEnd()();
|
|
76
|
+
i0.ɵɵelementStart(15, "div", 48);
|
|
77
|
+
i0.ɵɵelement(16, "i", 53);
|
|
78
|
+
i0.ɵɵelementStart(17, "span", 52);
|
|
79
|
+
i0.ɵɵtext(18);
|
|
80
|
+
i0.ɵɵelementEnd()()();
|
|
81
|
+
i0.ɵɵelementStart(19, "div", 54)(20, "button", 55);
|
|
82
|
+
i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_53_For_10_Template_button_click_20_listener() { const group_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.ApproveMatch(group_r5)); });
|
|
83
|
+
i0.ɵɵelement(21, "i", 56);
|
|
84
|
+
i0.ɵɵtext(22, " Approve ");
|
|
85
|
+
i0.ɵɵelementEnd();
|
|
86
|
+
i0.ɵɵelementStart(23, "button", 57);
|
|
87
|
+
i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_53_For_10_Template_button_click_23_listener() { const group_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.RejectMatch(group_r5)); });
|
|
88
|
+
i0.ɵɵelement(24, "i", 27);
|
|
89
|
+
i0.ɵɵtext(25, " Reject ");
|
|
90
|
+
i0.ɵɵelementEnd()()();
|
|
91
|
+
} if (rf & 2) {
|
|
92
|
+
const group_r5 = ctx.$implicit;
|
|
93
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
94
|
+
i0.ɵɵadvance(3);
|
|
95
|
+
i0.ɵɵtextInterpolate(group_r5.EntityName);
|
|
96
|
+
i0.ɵɵadvance();
|
|
97
|
+
i0.ɵɵclassMap(ctx_r2.GetScoreClass(group_r5.HighestScore));
|
|
98
|
+
i0.ɵɵadvance();
|
|
99
|
+
i0.ɵɵtextInterpolate1(" ", (group_r5.HighestScore * 100).toFixed(0), "% ");
|
|
100
|
+
i0.ɵɵadvance(4);
|
|
101
|
+
i0.ɵɵproperty("title", group_r5.RecordId);
|
|
102
|
+
i0.ɵɵadvance();
|
|
103
|
+
i0.ɵɵtextInterpolate(group_r5.RecordId);
|
|
104
|
+
i0.ɵɵadvance(4);
|
|
105
|
+
i0.ɵɵtextInterpolate2("", group_r5.MatchCount, " match", group_r5.MatchCount !== 1 ? "es" : "");
|
|
106
|
+
i0.ɵɵadvance(4);
|
|
107
|
+
i0.ɵɵtextInterpolate(ctx_r2.FormatDate(group_r5.MatchedAt));
|
|
108
|
+
i0.ɵɵadvance(2);
|
|
109
|
+
i0.ɵɵproperty("disabled", ctx_r2.IsSaving);
|
|
110
|
+
i0.ɵɵadvance(3);
|
|
111
|
+
i0.ɵɵproperty("disabled", ctx_r2.IsSaving);
|
|
112
|
+
} }
|
|
113
|
+
function DuplicateDetectionResourceComponent_Conditional_53_Conditional_11_Template(rf, ctx) { if (rf & 1) {
|
|
114
|
+
i0.ɵɵelementStart(0, "div", 39);
|
|
115
|
+
i0.ɵɵelement(1, "i", 41);
|
|
116
|
+
i0.ɵɵelementStart(2, "span");
|
|
117
|
+
i0.ɵɵtext(3, "No pending items");
|
|
118
|
+
i0.ɵɵelementEnd()();
|
|
119
|
+
} }
|
|
120
|
+
function DuplicateDetectionResourceComponent_Conditional_53_For_21_Template(rf, ctx) { if (rf & 1) {
|
|
121
|
+
i0.ɵɵelementStart(0, "div", 38)(1, "div", 44)(2, "span", 45);
|
|
122
|
+
i0.ɵɵtext(3);
|
|
123
|
+
i0.ɵɵelementEnd();
|
|
124
|
+
i0.ɵɵelementStart(4, "span", 46);
|
|
125
|
+
i0.ɵɵtext(5);
|
|
126
|
+
i0.ɵɵelementEnd()();
|
|
127
|
+
i0.ɵɵelementStart(6, "div", 47)(7, "div", 48);
|
|
128
|
+
i0.ɵɵelement(8, "i", 49);
|
|
129
|
+
i0.ɵɵelementStart(9, "span", 50);
|
|
130
|
+
i0.ɵɵtext(10);
|
|
131
|
+
i0.ɵɵelementEnd()();
|
|
132
|
+
i0.ɵɵelementStart(11, "div", 48);
|
|
133
|
+
i0.ɵɵelement(12, "i", 51);
|
|
134
|
+
i0.ɵɵelementStart(13, "span", 52);
|
|
135
|
+
i0.ɵɵtext(14);
|
|
136
|
+
i0.ɵɵelementEnd()();
|
|
137
|
+
i0.ɵɵelementStart(15, "div", 48);
|
|
138
|
+
i0.ɵɵelement(16, "i", 53);
|
|
139
|
+
i0.ɵɵelementStart(17, "span", 52);
|
|
140
|
+
i0.ɵɵtext(18);
|
|
141
|
+
i0.ɵɵelementEnd()()()();
|
|
142
|
+
} if (rf & 2) {
|
|
143
|
+
const group_r6 = ctx.$implicit;
|
|
144
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
145
|
+
i0.ɵɵadvance(3);
|
|
146
|
+
i0.ɵɵtextInterpolate(group_r6.EntityName);
|
|
147
|
+
i0.ɵɵadvance();
|
|
148
|
+
i0.ɵɵclassMap(ctx_r2.GetScoreClass(group_r6.HighestScore));
|
|
149
|
+
i0.ɵɵadvance();
|
|
150
|
+
i0.ɵɵtextInterpolate1(" ", (group_r6.HighestScore * 100).toFixed(0), "% ");
|
|
151
|
+
i0.ɵɵadvance(4);
|
|
152
|
+
i0.ɵɵproperty("title", group_r6.RecordId);
|
|
153
|
+
i0.ɵɵadvance();
|
|
154
|
+
i0.ɵɵtextInterpolate(group_r6.RecordId);
|
|
155
|
+
i0.ɵɵadvance(4);
|
|
156
|
+
i0.ɵɵtextInterpolate2("", group_r6.MatchCount, " match", group_r6.MatchCount !== 1 ? "es" : "");
|
|
157
|
+
i0.ɵɵadvance(4);
|
|
158
|
+
i0.ɵɵtextInterpolate(ctx_r2.FormatDate(group_r6.MatchedAt));
|
|
159
|
+
} }
|
|
160
|
+
function DuplicateDetectionResourceComponent_Conditional_53_Conditional_22_Template(rf, ctx) { if (rf & 1) {
|
|
161
|
+
i0.ɵɵelementStart(0, "div", 39);
|
|
162
|
+
i0.ɵɵelement(1, "i", 58);
|
|
163
|
+
i0.ɵɵelementStart(2, "span");
|
|
164
|
+
i0.ɵɵtext(3, "No approved items");
|
|
165
|
+
i0.ɵɵelementEnd()();
|
|
166
|
+
} }
|
|
167
|
+
function DuplicateDetectionResourceComponent_Conditional_53_For_32_Template(rf, ctx) { if (rf & 1) {
|
|
168
|
+
i0.ɵɵelementStart(0, "div", 38)(1, "div", 44)(2, "span", 45);
|
|
169
|
+
i0.ɵɵtext(3);
|
|
170
|
+
i0.ɵɵelementEnd();
|
|
171
|
+
i0.ɵɵelementStart(4, "span", 46);
|
|
172
|
+
i0.ɵɵtext(5);
|
|
173
|
+
i0.ɵɵelementEnd()();
|
|
174
|
+
i0.ɵɵelementStart(6, "div", 47)(7, "div", 48);
|
|
175
|
+
i0.ɵɵelement(8, "i", 49);
|
|
176
|
+
i0.ɵɵelementStart(9, "span", 50);
|
|
177
|
+
i0.ɵɵtext(10);
|
|
178
|
+
i0.ɵɵelementEnd()();
|
|
179
|
+
i0.ɵɵelementStart(11, "div", 48);
|
|
180
|
+
i0.ɵɵelement(12, "i", 51);
|
|
181
|
+
i0.ɵɵelementStart(13, "span", 52);
|
|
182
|
+
i0.ɵɵtext(14);
|
|
183
|
+
i0.ɵɵelementEnd()();
|
|
184
|
+
i0.ɵɵelementStart(15, "div", 48);
|
|
185
|
+
i0.ɵɵelement(16, "i", 53);
|
|
186
|
+
i0.ɵɵelementStart(17, "span", 52);
|
|
187
|
+
i0.ɵɵtext(18);
|
|
188
|
+
i0.ɵɵelementEnd()()()();
|
|
189
|
+
} if (rf & 2) {
|
|
190
|
+
const group_r7 = ctx.$implicit;
|
|
191
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
192
|
+
i0.ɵɵadvance(3);
|
|
193
|
+
i0.ɵɵtextInterpolate(group_r7.EntityName);
|
|
194
|
+
i0.ɵɵadvance();
|
|
195
|
+
i0.ɵɵclassMap(ctx_r2.GetScoreClass(group_r7.HighestScore));
|
|
196
|
+
i0.ɵɵadvance();
|
|
197
|
+
i0.ɵɵtextInterpolate1(" ", (group_r7.HighestScore * 100).toFixed(0), "% ");
|
|
198
|
+
i0.ɵɵadvance(4);
|
|
199
|
+
i0.ɵɵproperty("title", group_r7.RecordId);
|
|
200
|
+
i0.ɵɵadvance();
|
|
201
|
+
i0.ɵɵtextInterpolate(group_r7.RecordId);
|
|
202
|
+
i0.ɵɵadvance(4);
|
|
203
|
+
i0.ɵɵtextInterpolate2("", group_r7.MatchCount, " match", group_r7.MatchCount !== 1 ? "es" : "");
|
|
204
|
+
i0.ɵɵadvance(4);
|
|
205
|
+
i0.ɵɵtextInterpolate(ctx_r2.FormatDate(group_r7.MatchedAt));
|
|
206
|
+
} }
|
|
207
|
+
function DuplicateDetectionResourceComponent_Conditional_53_Conditional_33_Template(rf, ctx) { if (rf & 1) {
|
|
208
|
+
i0.ɵɵelementStart(0, "div", 39);
|
|
209
|
+
i0.ɵɵelement(1, "i", 58);
|
|
210
|
+
i0.ɵɵelementStart(2, "span");
|
|
211
|
+
i0.ɵɵtext(3, "No rejected items");
|
|
212
|
+
i0.ɵɵelementEnd()();
|
|
213
|
+
} }
|
|
214
|
+
function DuplicateDetectionResourceComponent_Conditional_53_Template(rf, ctx) { if (rf & 1) {
|
|
215
|
+
i0.ɵɵelementStart(0, "div", 24)(1, "div", 32)(2, "div", 33);
|
|
216
|
+
i0.ɵɵelement(3, "i", 34);
|
|
217
|
+
i0.ɵɵelementStart(4, "span", 35);
|
|
218
|
+
i0.ɵɵtext(5, "Pending Review");
|
|
219
|
+
i0.ɵɵelementEnd();
|
|
220
|
+
i0.ɵɵelementStart(6, "span", 36);
|
|
221
|
+
i0.ɵɵtext(7);
|
|
222
|
+
i0.ɵɵelementEnd()();
|
|
223
|
+
i0.ɵɵelementStart(8, "div", 37);
|
|
224
|
+
i0.ɵɵrepeaterCreate(9, DuplicateDetectionResourceComponent_Conditional_53_For_10_Template, 26, 11, "div", 38, _forTrack0);
|
|
225
|
+
i0.ɵɵconditionalCreate(11, DuplicateDetectionResourceComponent_Conditional_53_Conditional_11_Template, 4, 0, "div", 39);
|
|
226
|
+
i0.ɵɵelementEnd()();
|
|
227
|
+
i0.ɵɵelementStart(12, "div", 32)(13, "div", 40);
|
|
228
|
+
i0.ɵɵelement(14, "i", 41);
|
|
229
|
+
i0.ɵɵelementStart(15, "span", 35);
|
|
230
|
+
i0.ɵɵtext(16, "Approved");
|
|
231
|
+
i0.ɵɵelementEnd();
|
|
232
|
+
i0.ɵɵelementStart(17, "span", 36);
|
|
233
|
+
i0.ɵɵtext(18);
|
|
234
|
+
i0.ɵɵelementEnd()();
|
|
235
|
+
i0.ɵɵelementStart(19, "div", 37);
|
|
236
|
+
i0.ɵɵrepeaterCreate(20, DuplicateDetectionResourceComponent_Conditional_53_For_21_Template, 19, 9, "div", 38, _forTrack0);
|
|
237
|
+
i0.ɵɵconditionalCreate(22, DuplicateDetectionResourceComponent_Conditional_53_Conditional_22_Template, 4, 0, "div", 39);
|
|
238
|
+
i0.ɵɵelementEnd()();
|
|
239
|
+
i0.ɵɵelementStart(23, "div", 32)(24, "div", 42);
|
|
240
|
+
i0.ɵɵelement(25, "i", 43);
|
|
241
|
+
i0.ɵɵelementStart(26, "span", 35);
|
|
242
|
+
i0.ɵɵtext(27, "Rejected");
|
|
243
|
+
i0.ɵɵelementEnd();
|
|
244
|
+
i0.ɵɵelementStart(28, "span", 36);
|
|
245
|
+
i0.ɵɵtext(29);
|
|
246
|
+
i0.ɵɵelementEnd()();
|
|
247
|
+
i0.ɵɵelementStart(30, "div", 37);
|
|
248
|
+
i0.ɵɵrepeaterCreate(31, DuplicateDetectionResourceComponent_Conditional_53_For_32_Template, 19, 9, "div", 38, _forTrack0);
|
|
249
|
+
i0.ɵɵconditionalCreate(33, DuplicateDetectionResourceComponent_Conditional_53_Conditional_33_Template, 4, 0, "div", 39);
|
|
250
|
+
i0.ɵɵelementEnd()()();
|
|
251
|
+
} if (rf & 2) {
|
|
252
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
253
|
+
i0.ɵɵadvance(7);
|
|
254
|
+
i0.ɵɵtextInterpolate(ctx_r2.PendingCount);
|
|
255
|
+
i0.ɵɵadvance(2);
|
|
256
|
+
i0.ɵɵrepeater(ctx_r2.PendingGroups);
|
|
257
|
+
i0.ɵɵadvance(2);
|
|
258
|
+
i0.ɵɵconditional(ctx_r2.PendingGroups.length === 0 ? 11 : -1);
|
|
259
|
+
i0.ɵɵadvance(7);
|
|
260
|
+
i0.ɵɵtextInterpolate(ctx_r2.ApprovedCount);
|
|
261
|
+
i0.ɵɵadvance(2);
|
|
262
|
+
i0.ɵɵrepeater(ctx_r2.ApprovedGroups);
|
|
263
|
+
i0.ɵɵadvance(2);
|
|
264
|
+
i0.ɵɵconditional(ctx_r2.ApprovedGroups.length === 0 ? 22 : -1);
|
|
265
|
+
i0.ɵɵadvance(7);
|
|
266
|
+
i0.ɵɵtextInterpolate(ctx_r2.RejectedCount);
|
|
267
|
+
i0.ɵɵadvance(2);
|
|
268
|
+
i0.ɵɵrepeater(ctx_r2.RejectedGroups);
|
|
269
|
+
i0.ɵɵadvance(2);
|
|
270
|
+
i0.ɵɵconditional(ctx_r2.RejectedGroups.length === 0 ? 33 : -1);
|
|
271
|
+
} }
|
|
272
|
+
function DuplicateDetectionResourceComponent_Conditional_54_Template(rf, ctx) { if (rf & 1) {
|
|
273
|
+
i0.ɵɵelementStart(0, "div", 25);
|
|
274
|
+
i0.ɵɵelement(1, "mj-loading", 59);
|
|
275
|
+
i0.ɵɵelementEnd();
|
|
276
|
+
} }
|
|
277
|
+
let DuplicateDetectionResourceComponent = class DuplicateDetectionResourceComponent extends BaseResourceComponent {
|
|
278
|
+
cdr = inject(ChangeDetectorRef);
|
|
279
|
+
destroy$ = new Subject();
|
|
280
|
+
filterSubject = new Subject();
|
|
281
|
+
// Loading state
|
|
282
|
+
IsLoading = false;
|
|
283
|
+
IsSaving = false;
|
|
284
|
+
// Raw data
|
|
285
|
+
Runs = [];
|
|
286
|
+
Details = [];
|
|
287
|
+
Matches = [];
|
|
288
|
+
// Aggregated groups for display
|
|
289
|
+
AllGroups = [];
|
|
290
|
+
PendingGroups = [];
|
|
291
|
+
ApprovedGroups = [];
|
|
292
|
+
RejectedGroups = [];
|
|
293
|
+
// Filter state
|
|
294
|
+
Filters = {
|
|
295
|
+
EntityName: '',
|
|
296
|
+
MinScore: 0,
|
|
297
|
+
MaxScore: 1,
|
|
298
|
+
DateFrom: '',
|
|
299
|
+
DateTo: ''
|
|
300
|
+
};
|
|
301
|
+
// Available entity names from loaded runs
|
|
302
|
+
EntityNames = [];
|
|
303
|
+
/** Whether this component is embedded inside the Knowledge Hub shell */
|
|
304
|
+
EmbeddedMode = false;
|
|
305
|
+
/** View mode: 'kanban' (card board) or 'table' (paged grid) */
|
|
306
|
+
DisplayMode = 'kanban';
|
|
307
|
+
/** Page size for table view */
|
|
308
|
+
PageSize = 50;
|
|
309
|
+
/** Current page (0-based) */
|
|
310
|
+
CurrentPage = 0;
|
|
311
|
+
/** Selected entity filter */
|
|
312
|
+
SelectedEntityFilter = '';
|
|
313
|
+
/** Currently selected group for merge panel */
|
|
314
|
+
SelectedMergeGroup = null;
|
|
315
|
+
/** Whether the merge panel is visible */
|
|
316
|
+
ShowMergePanel = false;
|
|
317
|
+
/** Auto-switch to table mode when groups exceed threshold */
|
|
318
|
+
kanbanThreshold = 50;
|
|
319
|
+
/** Toggle display mode */
|
|
320
|
+
ToggleDisplayMode() {
|
|
321
|
+
this.DisplayMode = this.DisplayMode === 'kanban' ? 'table' : 'kanban';
|
|
322
|
+
this.CurrentPage = 0;
|
|
323
|
+
this.cdr.detectChanges();
|
|
324
|
+
}
|
|
325
|
+
/** Filter by entity name */
|
|
326
|
+
FilterByEntity(entityName) {
|
|
327
|
+
this.SelectedEntityFilter = entityName;
|
|
328
|
+
this.Filters.EntityName = entityName;
|
|
329
|
+
this.autoSelectDisplayMode();
|
|
330
|
+
this.cdr.detectChanges();
|
|
331
|
+
}
|
|
332
|
+
/** Get paged groups for table view */
|
|
333
|
+
GetPagedGroups() {
|
|
334
|
+
const start = this.CurrentPage * this.PageSize;
|
|
335
|
+
return this.AllGroups.slice(start, start + this.PageSize);
|
|
336
|
+
}
|
|
337
|
+
/** Get total page count */
|
|
338
|
+
get TotalPages() {
|
|
339
|
+
return Math.ceil(this.AllGroups.length / this.PageSize);
|
|
340
|
+
}
|
|
341
|
+
/** Navigate to next page */
|
|
342
|
+
NextPage() {
|
|
343
|
+
if (this.CurrentPage < this.TotalPages - 1) {
|
|
344
|
+
this.CurrentPage++;
|
|
345
|
+
this.cdr.detectChanges();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/** Navigate to previous page */
|
|
349
|
+
PrevPage() {
|
|
350
|
+
if (this.CurrentPage > 0) {
|
|
351
|
+
this.CurrentPage--;
|
|
352
|
+
this.cdr.detectChanges();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/** Open the merge panel for a duplicate group */
|
|
356
|
+
OpenMergePanel(group) {
|
|
357
|
+
this.SelectedMergeGroup = group;
|
|
358
|
+
this.ShowMergePanel = true;
|
|
359
|
+
this.cdr.detectChanges();
|
|
360
|
+
}
|
|
361
|
+
/** Close the merge panel */
|
|
362
|
+
CloseMergePanel() {
|
|
363
|
+
this.SelectedMergeGroup = null;
|
|
364
|
+
this.ShowMergePanel = false;
|
|
365
|
+
this.cdr.detectChanges();
|
|
366
|
+
}
|
|
367
|
+
/** Auto-select display mode based on group count */
|
|
368
|
+
autoSelectDisplayMode() {
|
|
369
|
+
this.DisplayMode = this.AllGroups.length > this.kanbanThreshold ? 'table' : 'kanban';
|
|
370
|
+
}
|
|
371
|
+
// KPI values
|
|
372
|
+
get TotalGroupCount() {
|
|
373
|
+
return this.AllGroups.length;
|
|
374
|
+
}
|
|
375
|
+
get PendingCount() {
|
|
376
|
+
return this.PendingGroups.length;
|
|
377
|
+
}
|
|
378
|
+
get ApprovedCount() {
|
|
379
|
+
return this.ApprovedGroups.length;
|
|
380
|
+
}
|
|
381
|
+
get RejectedCount() {
|
|
382
|
+
return this.RejectedGroups.length;
|
|
383
|
+
}
|
|
384
|
+
async ngAfterViewInit() {
|
|
385
|
+
this.setupFilterDebounce();
|
|
386
|
+
await this.LoadData();
|
|
387
|
+
this.NotifyLoadComplete();
|
|
388
|
+
}
|
|
389
|
+
ngOnDestroy() {
|
|
390
|
+
this.destroy$.next();
|
|
391
|
+
this.destroy$.complete();
|
|
392
|
+
}
|
|
393
|
+
async GetResourceDisplayName(_data) {
|
|
394
|
+
return 'Duplicate Detection';
|
|
395
|
+
}
|
|
396
|
+
async GetResourceIconClass(_data) {
|
|
397
|
+
return 'fa-solid fa-clone';
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Loads all duplicate run data and builds the Kanban groups.
|
|
401
|
+
*/
|
|
402
|
+
async LoadData() {
|
|
403
|
+
this.IsLoading = true;
|
|
404
|
+
this.cdr.detectChanges();
|
|
405
|
+
try {
|
|
406
|
+
const rv = new RunView();
|
|
407
|
+
const [runsResult, detailsResult, matchesResult] = await rv.RunViews([
|
|
408
|
+
{
|
|
409
|
+
EntityName: 'MJ: Duplicate Runs',
|
|
410
|
+
ExtraFilter: "ProcessingStatus = 'Complete'",
|
|
411
|
+
OrderBy: 'StartedAt DESC',
|
|
412
|
+
ResultType: 'entity_object'
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
EntityName: 'MJ: Duplicate Run Details',
|
|
416
|
+
ExtraFilter: "MatchStatus = 'Complete'",
|
|
417
|
+
OrderBy: '__mj_CreatedAt DESC',
|
|
418
|
+
ResultType: 'entity_object'
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
EntityName: 'MJ: Duplicate Run Detail Matches',
|
|
422
|
+
OrderBy: 'MatchProbability DESC',
|
|
423
|
+
ResultType: 'entity_object'
|
|
424
|
+
}
|
|
425
|
+
]);
|
|
426
|
+
if (runsResult.Success) {
|
|
427
|
+
this.Runs = runsResult.Results;
|
|
428
|
+
}
|
|
429
|
+
if (detailsResult.Success) {
|
|
430
|
+
this.Details = detailsResult.Results;
|
|
431
|
+
}
|
|
432
|
+
if (matchesResult.Success) {
|
|
433
|
+
this.Matches = matchesResult.Results;
|
|
434
|
+
}
|
|
435
|
+
this.extractEntityNames();
|
|
436
|
+
this.buildGroups();
|
|
437
|
+
this.applyFilters();
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
console.error('Error loading duplicate detection data:', error);
|
|
441
|
+
}
|
|
442
|
+
finally {
|
|
443
|
+
this.IsLoading = false;
|
|
444
|
+
this.cdr.detectChanges();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Approves all matches within a duplicate group by updating each match's ApprovalStatus.
|
|
449
|
+
*/
|
|
450
|
+
async ApproveMatch(group) {
|
|
451
|
+
await this.updateGroupApprovalStatus(group, 'Approved');
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Rejects all matches within a duplicate group by updating each match's ApprovalStatus.
|
|
455
|
+
*/
|
|
456
|
+
async RejectMatch(group) {
|
|
457
|
+
await this.updateGroupApprovalStatus(group, 'Rejected');
|
|
458
|
+
}
|
|
459
|
+
/** Handle filter changes with debounce */
|
|
460
|
+
OnFilterChange() {
|
|
461
|
+
this.filterSubject.next();
|
|
462
|
+
}
|
|
463
|
+
/** Clear all filters */
|
|
464
|
+
ClearFilters() {
|
|
465
|
+
this.Filters = {
|
|
466
|
+
EntityName: '',
|
|
467
|
+
MinScore: 0,
|
|
468
|
+
MaxScore: 1,
|
|
469
|
+
DateFrom: '',
|
|
470
|
+
DateTo: ''
|
|
471
|
+
};
|
|
472
|
+
this.applyFilters();
|
|
473
|
+
}
|
|
474
|
+
/** Returns the CSS class for a match score indicator */
|
|
475
|
+
GetScoreClass(score) {
|
|
476
|
+
if (score > 0.9)
|
|
477
|
+
return 'score-high';
|
|
478
|
+
if (score >= 0.7)
|
|
479
|
+
return 'score-medium';
|
|
480
|
+
return 'score-low';
|
|
481
|
+
}
|
|
482
|
+
/** Returns a human-readable label for a match score */
|
|
483
|
+
GetScoreLabel(score) {
|
|
484
|
+
if (score > 0.9)
|
|
485
|
+
return 'High';
|
|
486
|
+
if (score >= 0.7)
|
|
487
|
+
return 'Medium';
|
|
488
|
+
return 'Low';
|
|
489
|
+
}
|
|
490
|
+
/** Format a date for display */
|
|
491
|
+
FormatDate(date) {
|
|
492
|
+
if (!date)
|
|
493
|
+
return '';
|
|
494
|
+
const d = new Date(date);
|
|
495
|
+
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
|
|
496
|
+
}
|
|
497
|
+
/** Whether any filters are active */
|
|
498
|
+
get HasActiveFilters() {
|
|
499
|
+
return this.Filters.EntityName !== '' ||
|
|
500
|
+
this.Filters.MinScore > 0 ||
|
|
501
|
+
this.Filters.MaxScore < 1 ||
|
|
502
|
+
this.Filters.DateFrom !== '' ||
|
|
503
|
+
this.Filters.DateTo !== '';
|
|
504
|
+
}
|
|
505
|
+
// ---- Private Methods ----
|
|
506
|
+
setupFilterDebounce() {
|
|
507
|
+
this.filterSubject.pipe(debounceTime(300), takeUntil(this.destroy$)).subscribe(() => {
|
|
508
|
+
this.applyFilters();
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
/** Extract unique entity names from loaded runs */
|
|
512
|
+
extractEntityNames() {
|
|
513
|
+
const nameSet = new Set();
|
|
514
|
+
for (const run of this.Runs) {
|
|
515
|
+
if (run.Entity) {
|
|
516
|
+
nameSet.add(run.Entity);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
this.EntityNames = Array.from(nameSet).sort();
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Build DuplicateGroup objects by joining details to their matches,
|
|
523
|
+
* and resolving the entity name from the parent run.
|
|
524
|
+
*/
|
|
525
|
+
buildGroups() {
|
|
526
|
+
const runMap = new Map();
|
|
527
|
+
for (const run of this.Runs) {
|
|
528
|
+
runMap.set(run.ID, run);
|
|
529
|
+
}
|
|
530
|
+
const matchesByDetail = new Map();
|
|
531
|
+
for (const match of this.Matches) {
|
|
532
|
+
const key = match.DuplicateRunDetailID;
|
|
533
|
+
const existing = matchesByDetail.get(key);
|
|
534
|
+
if (existing) {
|
|
535
|
+
existing.push(match);
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
matchesByDetail.set(key, [match]);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
this.AllGroups = [];
|
|
542
|
+
for (const detail of this.Details) {
|
|
543
|
+
const detailMatches = matchesByDetail.get(detail.ID);
|
|
544
|
+
if (!detailMatches || detailMatches.length === 0) {
|
|
545
|
+
continue; // Skip details with no matches
|
|
546
|
+
}
|
|
547
|
+
const run = runMap.get(detail.DuplicateRunID);
|
|
548
|
+
const entityName = run?.Entity ?? 'Unknown';
|
|
549
|
+
const highestScore = this.computeHighestScore(detailMatches);
|
|
550
|
+
const dominantStatus = this.computeDominantApprovalStatus(detailMatches);
|
|
551
|
+
const latestMatchDate = this.computeLatestMatchDate(detailMatches);
|
|
552
|
+
this.AllGroups.push({
|
|
553
|
+
DetailId: detail.ID,
|
|
554
|
+
RunId: detail.DuplicateRunID,
|
|
555
|
+
RecordId: detail.RecordID,
|
|
556
|
+
EntityName: entityName,
|
|
557
|
+
ApprovalStatus: dominantStatus,
|
|
558
|
+
MatchCount: detailMatches.length,
|
|
559
|
+
HighestScore: highestScore,
|
|
560
|
+
Matches: detailMatches,
|
|
561
|
+
MatchedAt: latestMatchDate
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
/** Find the highest match probability across a set of matches */
|
|
566
|
+
computeHighestScore(matches) {
|
|
567
|
+
let max = 0;
|
|
568
|
+
for (const m of matches) {
|
|
569
|
+
if (m.MatchProbability > max) {
|
|
570
|
+
max = m.MatchProbability;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return max;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Determine the dominant approval status for a group of matches.
|
|
577
|
+
* If any match is Pending, the group is Pending.
|
|
578
|
+
* Otherwise, if all are Approved, the group is Approved.
|
|
579
|
+
* If all are Rejected, the group is Rejected.
|
|
580
|
+
* Mixed Approved/Rejected defaults to Pending.
|
|
581
|
+
*/
|
|
582
|
+
computeDominantApprovalStatus(matches) {
|
|
583
|
+
let hasApproved = false;
|
|
584
|
+
let hasRejected = false;
|
|
585
|
+
for (const m of matches) {
|
|
586
|
+
if (m.ApprovalStatus === 'Pending')
|
|
587
|
+
return 'Pending';
|
|
588
|
+
if (m.ApprovalStatus === 'Approved')
|
|
589
|
+
hasApproved = true;
|
|
590
|
+
if (m.ApprovalStatus === 'Rejected')
|
|
591
|
+
hasRejected = true;
|
|
592
|
+
}
|
|
593
|
+
if (hasApproved && !hasRejected)
|
|
594
|
+
return 'Approved';
|
|
595
|
+
if (hasRejected && !hasApproved)
|
|
596
|
+
return 'Rejected';
|
|
597
|
+
return 'Pending'; // mixed
|
|
598
|
+
}
|
|
599
|
+
/** Find the latest MatchedAt date across a set of matches */
|
|
600
|
+
computeLatestMatchDate(matches) {
|
|
601
|
+
let latest = new Date(0);
|
|
602
|
+
for (const m of matches) {
|
|
603
|
+
const d = new Date(m.MatchedAt);
|
|
604
|
+
if (d > latest) {
|
|
605
|
+
latest = d;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return latest;
|
|
609
|
+
}
|
|
610
|
+
/** Apply filters and distribute groups into Kanban columns */
|
|
611
|
+
applyFilters() {
|
|
612
|
+
let filtered = [...this.AllGroups];
|
|
613
|
+
if (this.Filters.EntityName) {
|
|
614
|
+
filtered = filtered.filter(g => g.EntityName === this.Filters.EntityName);
|
|
615
|
+
}
|
|
616
|
+
if (this.Filters.MinScore > 0) {
|
|
617
|
+
filtered = filtered.filter(g => g.HighestScore >= this.Filters.MinScore);
|
|
618
|
+
}
|
|
619
|
+
if (this.Filters.MaxScore < 1) {
|
|
620
|
+
filtered = filtered.filter(g => g.HighestScore <= this.Filters.MaxScore);
|
|
621
|
+
}
|
|
622
|
+
if (this.Filters.DateFrom) {
|
|
623
|
+
const from = new Date(this.Filters.DateFrom);
|
|
624
|
+
filtered = filtered.filter(g => new Date(g.MatchedAt) >= from);
|
|
625
|
+
}
|
|
626
|
+
if (this.Filters.DateTo) {
|
|
627
|
+
const to = new Date(this.Filters.DateTo);
|
|
628
|
+
filtered = filtered.filter(g => new Date(g.MatchedAt) <= to);
|
|
629
|
+
}
|
|
630
|
+
this.PendingGroups = filtered.filter(g => g.ApprovalStatus === 'Pending');
|
|
631
|
+
this.ApprovedGroups = filtered.filter(g => g.ApprovalStatus === 'Approved');
|
|
632
|
+
this.RejectedGroups = filtered.filter(g => g.ApprovalStatus === 'Rejected');
|
|
633
|
+
this.cdr.detectChanges();
|
|
634
|
+
}
|
|
635
|
+
/** Update the ApprovalStatus of all matches within a group and re-sort.
|
|
636
|
+
* Since this.Matches already contains entity_object instances, we update and save them directly
|
|
637
|
+
* instead of loading each one individually from the database.
|
|
638
|
+
*/
|
|
639
|
+
async updateGroupApprovalStatus(group, status) {
|
|
640
|
+
this.IsSaving = true;
|
|
641
|
+
this.cdr.detectChanges();
|
|
642
|
+
try {
|
|
643
|
+
for (const match of group.Matches) {
|
|
644
|
+
match.ApprovalStatus = status;
|
|
645
|
+
await match.Save();
|
|
646
|
+
}
|
|
647
|
+
// Update the local group state
|
|
648
|
+
group.ApprovalStatus = status;
|
|
649
|
+
this.applyFilters();
|
|
650
|
+
}
|
|
651
|
+
catch (error) {
|
|
652
|
+
console.error(`Error updating match approval status to ${status}:`, error);
|
|
653
|
+
}
|
|
654
|
+
finally {
|
|
655
|
+
this.IsSaving = false;
|
|
656
|
+
this.cdr.detectChanges();
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
static ɵfac = /*@__PURE__*/ (() => { let ɵDuplicateDetectionResourceComponent_BaseFactory; return function DuplicateDetectionResourceComponent_Factory(__ngFactoryType__) { return (ɵDuplicateDetectionResourceComponent_BaseFactory || (ɵDuplicateDetectionResourceComponent_BaseFactory = i0.ɵɵgetInheritedFactory(DuplicateDetectionResourceComponent)))(__ngFactoryType__ || DuplicateDetectionResourceComponent); }; })();
|
|
660
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: DuplicateDetectionResourceComponent, selectors: [["app-duplicate-detection-resource"]], inputs: { EmbeddedMode: "EmbeddedMode" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 55, vars: 12, consts: [[1, "duplicate-detection-container"], [1, "page-header"], [1, "header-left"], [1, "page-title"], [1, "fa-solid", "fa-clone"], [1, "kpi-strip"], [1, "kpi-card"], [1, "kpi-value"], [1, "kpi-label"], [1, "kpi-card", "kpi-pending"], [1, "kpi-card", "kpi-approved"], [1, "kpi-card", "kpi-rejected"], [1, "filter-bar"], [1, "filter-group"], [1, "filter-select", 3, "ngModelChange", "ngModel"], ["value", ""], [3, "value"], [1, "filter-range"], [1, "filter-label"], ["type", "number", "min", "0", "max", "1", "step", "0.05", 1, "filter-input", 3, "ngModelChange", "ngModel"], ["type", "date", 1, "filter-input", 3, "ngModelChange", "ngModel"], [1, "clear-filters-btn"], [1, "loading-container"], [1, "empty-state"], [1, "kanban-board"], [1, "saving-overlay"], [1, "clear-filters-btn", 3, "click"], [1, "fa-solid", "fa-times"], ["text", "Loading duplicate detection results..."], [1, "fa-solid", "fa-clone", "empty-icon"], [1, "empty-text"], [1, "empty-subtext"], [1, "kanban-column"], [1, "column-header", "column-header-pending"], [1, "fa-solid", "fa-clock"], [1, "column-title"], [1, "column-count"], [1, "column-body"], [1, "kanban-card"], [1, "column-empty"], [1, "column-header", "column-header-approved"], [1, "fa-solid", "fa-check-circle"], [1, "column-header", "column-header-rejected"], [1, "fa-solid", "fa-ban"], [1, "card-header"], [1, "entity-badge"], [1, "score-indicator"], [1, "card-body"], [1, "card-field"], [1, "fa-solid", "fa-fingerprint", "field-icon"], [1, "field-value", "record-id", 3, "title"], [1, "fa-solid", "fa-layer-group", "field-icon"], [1, "field-value"], [1, "fa-solid", "fa-calendar", "field-icon"], [1, "card-actions"], [1, "action-btn", "approve-btn", 3, "click", "disabled"], [1, "fa-solid", "fa-check"], [1, "action-btn", "reject-btn", 3, "click", "disabled"], [1, "fa-solid", "fa-inbox"], ["text", "Saving...", "size", "small"]], template: function DuplicateDetectionResourceComponent_Template(rf, ctx) { if (rf & 1) {
|
|
661
|
+
i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "div", 2)(3, "h2", 3);
|
|
662
|
+
i0.ɵɵelement(4, "i", 4);
|
|
663
|
+
i0.ɵɵtext(5, " Duplicate Detection ");
|
|
664
|
+
i0.ɵɵelementEnd()()();
|
|
665
|
+
i0.ɵɵelementStart(6, "div", 5)(7, "div", 6)(8, "div", 7);
|
|
666
|
+
i0.ɵɵtext(9);
|
|
667
|
+
i0.ɵɵelementEnd();
|
|
668
|
+
i0.ɵɵelementStart(10, "div", 8);
|
|
669
|
+
i0.ɵɵtext(11, "Total Groups");
|
|
670
|
+
i0.ɵɵelementEnd()();
|
|
671
|
+
i0.ɵɵelementStart(12, "div", 9)(13, "div", 7);
|
|
672
|
+
i0.ɵɵtext(14);
|
|
673
|
+
i0.ɵɵelementEnd();
|
|
674
|
+
i0.ɵɵelementStart(15, "div", 8);
|
|
675
|
+
i0.ɵɵtext(16, "Pending");
|
|
676
|
+
i0.ɵɵelementEnd()();
|
|
677
|
+
i0.ɵɵelementStart(17, "div", 10)(18, "div", 7);
|
|
678
|
+
i0.ɵɵtext(19);
|
|
679
|
+
i0.ɵɵelementEnd();
|
|
680
|
+
i0.ɵɵelementStart(20, "div", 8);
|
|
681
|
+
i0.ɵɵtext(21, "Approved");
|
|
682
|
+
i0.ɵɵelementEnd()();
|
|
683
|
+
i0.ɵɵelementStart(22, "div", 11)(23, "div", 7);
|
|
684
|
+
i0.ɵɵtext(24);
|
|
685
|
+
i0.ɵɵelementEnd();
|
|
686
|
+
i0.ɵɵelementStart(25, "div", 8);
|
|
687
|
+
i0.ɵɵtext(26, "Rejected");
|
|
688
|
+
i0.ɵɵelementEnd()()();
|
|
689
|
+
i0.ɵɵelementStart(27, "div", 12)(28, "div", 13)(29, "select", 14);
|
|
690
|
+
i0.ɵɵtwoWayListener("ngModelChange", function DuplicateDetectionResourceComponent_Template_select_ngModelChange_29_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.Filters.EntityName, $event) || (ctx.Filters.EntityName = $event); return $event; });
|
|
691
|
+
i0.ɵɵlistener("ngModelChange", function DuplicateDetectionResourceComponent_Template_select_ngModelChange_29_listener() { return ctx.OnFilterChange(); });
|
|
692
|
+
i0.ɵɵelementStart(30, "option", 15);
|
|
693
|
+
i0.ɵɵtext(31, "All Entities");
|
|
694
|
+
i0.ɵɵelementEnd();
|
|
695
|
+
i0.ɵɵrepeaterCreate(32, DuplicateDetectionResourceComponent_For_33_Template, 2, 2, "option", 16, i0.ɵɵrepeaterTrackByIdentity);
|
|
696
|
+
i0.ɵɵelementEnd();
|
|
697
|
+
i0.ɵɵelementStart(34, "div", 17)(35, "label", 18);
|
|
698
|
+
i0.ɵɵtext(36, "Min Score");
|
|
699
|
+
i0.ɵɵelementEnd();
|
|
700
|
+
i0.ɵɵelementStart(37, "input", 19);
|
|
701
|
+
i0.ɵɵtwoWayListener("ngModelChange", function DuplicateDetectionResourceComponent_Template_input_ngModelChange_37_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.Filters.MinScore, $event) || (ctx.Filters.MinScore = $event); return $event; });
|
|
702
|
+
i0.ɵɵlistener("ngModelChange", function DuplicateDetectionResourceComponent_Template_input_ngModelChange_37_listener() { return ctx.OnFilterChange(); });
|
|
703
|
+
i0.ɵɵelementEnd()();
|
|
704
|
+
i0.ɵɵelementStart(38, "div", 17)(39, "label", 18);
|
|
705
|
+
i0.ɵɵtext(40, "Max Score");
|
|
706
|
+
i0.ɵɵelementEnd();
|
|
707
|
+
i0.ɵɵelementStart(41, "input", 19);
|
|
708
|
+
i0.ɵɵtwoWayListener("ngModelChange", function DuplicateDetectionResourceComponent_Template_input_ngModelChange_41_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.Filters.MaxScore, $event) || (ctx.Filters.MaxScore = $event); return $event; });
|
|
709
|
+
i0.ɵɵlistener("ngModelChange", function DuplicateDetectionResourceComponent_Template_input_ngModelChange_41_listener() { return ctx.OnFilterChange(); });
|
|
710
|
+
i0.ɵɵelementEnd()();
|
|
711
|
+
i0.ɵɵelementStart(42, "div", 17)(43, "label", 18);
|
|
712
|
+
i0.ɵɵtext(44, "From");
|
|
713
|
+
i0.ɵɵelementEnd();
|
|
714
|
+
i0.ɵɵelementStart(45, "input", 20);
|
|
715
|
+
i0.ɵɵtwoWayListener("ngModelChange", function DuplicateDetectionResourceComponent_Template_input_ngModelChange_45_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.Filters.DateFrom, $event) || (ctx.Filters.DateFrom = $event); return $event; });
|
|
716
|
+
i0.ɵɵlistener("ngModelChange", function DuplicateDetectionResourceComponent_Template_input_ngModelChange_45_listener() { return ctx.OnFilterChange(); });
|
|
717
|
+
i0.ɵɵelementEnd()();
|
|
718
|
+
i0.ɵɵelementStart(46, "div", 17)(47, "label", 18);
|
|
719
|
+
i0.ɵɵtext(48, "To");
|
|
720
|
+
i0.ɵɵelementEnd();
|
|
721
|
+
i0.ɵɵelementStart(49, "input", 20);
|
|
722
|
+
i0.ɵɵtwoWayListener("ngModelChange", function DuplicateDetectionResourceComponent_Template_input_ngModelChange_49_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.Filters.DateTo, $event) || (ctx.Filters.DateTo = $event); return $event; });
|
|
723
|
+
i0.ɵɵlistener("ngModelChange", function DuplicateDetectionResourceComponent_Template_input_ngModelChange_49_listener() { return ctx.OnFilterChange(); });
|
|
724
|
+
i0.ɵɵelementEnd()()();
|
|
725
|
+
i0.ɵɵconditionalCreate(50, DuplicateDetectionResourceComponent_Conditional_50_Template, 3, 0, "button", 21);
|
|
726
|
+
i0.ɵɵelementEnd();
|
|
727
|
+
i0.ɵɵconditionalCreate(51, DuplicateDetectionResourceComponent_Conditional_51_Template, 2, 0, "div", 22)(52, DuplicateDetectionResourceComponent_Conditional_52_Template, 6, 0, "div", 23)(53, DuplicateDetectionResourceComponent_Conditional_53_Template, 34, 6, "div", 24);
|
|
728
|
+
i0.ɵɵconditionalCreate(54, DuplicateDetectionResourceComponent_Conditional_54_Template, 2, 0, "div", 25);
|
|
729
|
+
i0.ɵɵelementEnd();
|
|
730
|
+
} if (rf & 2) {
|
|
731
|
+
i0.ɵɵadvance(9);
|
|
732
|
+
i0.ɵɵtextInterpolate(ctx.TotalGroupCount);
|
|
733
|
+
i0.ɵɵadvance(5);
|
|
734
|
+
i0.ɵɵtextInterpolate(ctx.PendingCount);
|
|
735
|
+
i0.ɵɵadvance(5);
|
|
736
|
+
i0.ɵɵtextInterpolate(ctx.ApprovedCount);
|
|
737
|
+
i0.ɵɵadvance(5);
|
|
738
|
+
i0.ɵɵtextInterpolate(ctx.RejectedCount);
|
|
739
|
+
i0.ɵɵadvance(5);
|
|
740
|
+
i0.ɵɵtwoWayProperty("ngModel", ctx.Filters.EntityName);
|
|
741
|
+
i0.ɵɵadvance(3);
|
|
742
|
+
i0.ɵɵrepeater(ctx.EntityNames);
|
|
743
|
+
i0.ɵɵadvance(5);
|
|
744
|
+
i0.ɵɵtwoWayProperty("ngModel", ctx.Filters.MinScore);
|
|
745
|
+
i0.ɵɵadvance(4);
|
|
746
|
+
i0.ɵɵtwoWayProperty("ngModel", ctx.Filters.MaxScore);
|
|
747
|
+
i0.ɵɵadvance(4);
|
|
748
|
+
i0.ɵɵtwoWayProperty("ngModel", ctx.Filters.DateFrom);
|
|
749
|
+
i0.ɵɵadvance(4);
|
|
750
|
+
i0.ɵɵtwoWayProperty("ngModel", ctx.Filters.DateTo);
|
|
751
|
+
i0.ɵɵadvance();
|
|
752
|
+
i0.ɵɵconditional(ctx.HasActiveFilters ? 50 : -1);
|
|
753
|
+
i0.ɵɵadvance();
|
|
754
|
+
i0.ɵɵconditional(ctx.IsLoading ? 51 : ctx.TotalGroupCount === 0 ? 52 : 53);
|
|
755
|
+
i0.ɵɵadvance(3);
|
|
756
|
+
i0.ɵɵconditional(ctx.IsSaving ? 54 : -1);
|
|
757
|
+
} }, dependencies: [i1.NgSelectOption, i1.ɵNgSelectMultipleOption, i1.DefaultValueAccessor, i1.NumberValueAccessor, i1.SelectControlValueAccessor, i1.NgControlStatus, i1.MinValidator, i1.MaxValidator, i1.NgModel, i2.LoadingComponent], styles: ["\n\n\n\n\n\n.duplicate-detection-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n padding: 16px 20px;\n background: var(--mj-bg-page);\n position: relative;\n overflow: hidden;\n}\n\n\n\n\n.page-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 16px;\n flex-shrink: 0;\n}\n\n.page-title[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 20px;\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.page-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n\n\n\n.kpi-strip[_ngcontent-%COMP%] {\n display: flex;\n gap: 12px;\n margin-bottom: 16px;\n flex-shrink: 0;\n}\n\n.kpi-card[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 12px 16px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n}\n\n.kpi-value[_ngcontent-%COMP%] {\n font-size: 28px;\n font-weight: 700;\n color: var(--mj-text-primary);\n line-height: 1.2;\n}\n\n.kpi-label[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-top: 2px;\n}\n\n.kpi-pending[_ngcontent-%COMP%] .kpi-value[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n}\n\n.kpi-approved[_ngcontent-%COMP%] .kpi-value[_ngcontent-%COMP%] {\n color: var(--mj-status-success);\n}\n\n.kpi-rejected[_ngcontent-%COMP%] .kpi-value[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n}\n\n\n\n\n.filter-bar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 16px;\n flex-shrink: 0;\n}\n\n.filter-group[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-end;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.filter-select[_ngcontent-%COMP%] {\n height: 34px;\n padding: 4px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 13px;\n outline: none;\n min-width: 160px;\n}\n\n.filter-select[_ngcontent-%COMP%]:focus {\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.filter-range[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.filter-label[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 500;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n.filter-input[_ngcontent-%COMP%] {\n height: 34px;\n padding: 4px 8px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 13px;\n outline: none;\n width: 100px;\n}\n\n.filter-input[_ngcontent-%COMP%]:focus {\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.clear-filters-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n height: 34px;\n padding: 0 12px;\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: 13px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.clear-filters-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n\n\n\n.loading-container[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 1;\n min-height: 200px;\n}\n\n.empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n flex: 1;\n min-height: 200px;\n gap: 8px;\n}\n\n.empty-icon[_ngcontent-%COMP%] {\n font-size: 48px;\n color: var(--mj-text-disabled);\n}\n\n.empty-text[_ngcontent-%COMP%] {\n font-size: 16px;\n font-weight: 500;\n color: var(--mj-text-secondary);\n margin: 0;\n}\n\n.empty-subtext[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-muted);\n margin: 0;\n}\n\n\n\n\n.kanban-board[_ngcontent-%COMP%] {\n display: flex;\n gap: 16px;\n flex: 1;\n min-height: 0;\n overflow-x: auto;\n}\n\n.kanban-column[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 280px;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n overflow: hidden;\n}\n\n.column-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n border-bottom: 1px solid var(--mj-border-subtle);\n flex-shrink: 0;\n}\n\n.column-header-pending[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-warning) 8%, var(--mj-bg-surface));\n}\n\n.column-header-pending[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n}\n\n.column-header-approved[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-success) 8%, var(--mj-bg-surface));\n}\n\n.column-header-approved[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-success);\n}\n\n.column-header-rejected[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-error) 8%, var(--mj-bg-surface));\n}\n\n.column-header-rejected[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n}\n\n.column-title[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n.column-count[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 24px;\n height: 24px;\n padding: 0 6px;\n border-radius: 12px;\n font-size: 12px;\n font-weight: 600;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n}\n\n.column-body[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n padding: 12px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.column-empty[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 32px 16px;\n color: var(--mj-text-disabled);\n font-size: 13px;\n}\n\n.column-empty[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 24px;\n}\n\n\n\n\n.kanban-card[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n overflow: hidden;\n transition: box-shadow 0.15s ease, border-color 0.15s ease;\n}\n\n.kanban-card[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-border-strong);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n}\n\n.card-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 12px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.entity-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 11px;\n font-weight: 600;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n max-width: 160px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n\n\n\n.score-indicator[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 40px;\n padding: 2px 8px;\n border-radius: 12px;\n font-size: 12px;\n font-weight: 700;\n}\n\n.score-high[_ngcontent-%COMP%] {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n border: 1px solid var(--mj-status-success-border);\n}\n\n.score-medium[_ngcontent-%COMP%] {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n border: 1px solid var(--mj-status-warning-border);\n}\n\n.score-low[_ngcontent-%COMP%] {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n border: 1px solid var(--mj-status-error-border);\n}\n\n.card-body[_ngcontent-%COMP%] {\n padding: 10px 12px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.card-field[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: var(--mj-text-secondary);\n}\n\n.field-icon[_ngcontent-%COMP%] {\n width: 14px;\n text-align: center;\n font-size: 12px;\n color: var(--mj-text-muted);\n flex-shrink: 0;\n}\n\n.field-value[_ngcontent-%COMP%] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.record-id[_ngcontent-%COMP%] {\n font-family: monospace;\n font-size: 12px;\n max-width: 180px;\n}\n\n.card-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n padding: 10px 12px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n.action-btn[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n height: 32px;\n padding: 0 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n\n.action-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.approve-btn[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-success) 10%, var(--mj-bg-surface));\n color: var(--mj-status-success-text);\n border-color: var(--mj-status-success-border);\n}\n\n.approve-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-success) 20%, var(--mj-bg-surface));\n}\n\n.reject-btn[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n color: var(--mj-status-error-text);\n border-color: var(--mj-status-error-border);\n}\n\n.reject-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-error) 20%, var(--mj-bg-surface));\n}\n\n\n\n\n@media (max-width: 768px) {\n .kanban-board[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n\n .kanban-column[_ngcontent-%COMP%] {\n min-width: auto;\n max-height: 400px;\n }\n}\n\n@media (max-width: 480px) {\n .kpi-strip[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n\n .filter-bar[_ngcontent-%COMP%] {\n flex-wrap: wrap;\n }\n\n .filter-group[_ngcontent-%COMP%] {\n flex-direction: column;\n align-items: stretch;\n width: 100%;\n }\n\n .filter-select[_ngcontent-%COMP%] {\n min-width: auto;\n width: 100%;\n }\n\n .filter-input[_ngcontent-%COMP%] {\n width: 100%;\n }\n\n .card-actions[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n\n .action-btn[_ngcontent-%COMP%] {\n width: 100%;\n }\n}\n\n\n\n\n.saving-overlay[_ngcontent-%COMP%] {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--mj-bg-overlay);\n z-index: 10;\n border-radius: 8px;\n}"] });
|
|
758
|
+
};
|
|
759
|
+
DuplicateDetectionResourceComponent = __decorate([
|
|
760
|
+
RegisterClass(BaseResourceComponent, 'DuplicateDetectionResource')
|
|
761
|
+
], DuplicateDetectionResourceComponent);
|
|
762
|
+
export { DuplicateDetectionResourceComponent };
|
|
763
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(DuplicateDetectionResourceComponent, [{
|
|
764
|
+
type: Component,
|
|
765
|
+
args: [{ standalone: false, selector: 'app-duplicate-detection-resource', template: "<div class=\"duplicate-detection-container\">\n <!-- Header -->\n <div class=\"page-header\">\n <div class=\"header-left\">\n <h2 class=\"page-title\">\n <i class=\"fa-solid fa-clone\"></i> Duplicate Detection\n </h2>\n </div>\n </div>\n\n <!-- KPI Strip -->\n <div class=\"kpi-strip\">\n <div class=\"kpi-card\">\n <div class=\"kpi-value\">{{ TotalGroupCount }}</div>\n <div class=\"kpi-label\">Total Groups</div>\n </div>\n <div class=\"kpi-card kpi-pending\">\n <div class=\"kpi-value\">{{ PendingCount }}</div>\n <div class=\"kpi-label\">Pending</div>\n </div>\n <div class=\"kpi-card kpi-approved\">\n <div class=\"kpi-value\">{{ ApprovedCount }}</div>\n <div class=\"kpi-label\">Approved</div>\n </div>\n <div class=\"kpi-card kpi-rejected\">\n <div class=\"kpi-value\">{{ RejectedCount }}</div>\n <div class=\"kpi-label\">Rejected</div>\n </div>\n </div>\n\n <!-- Filter Bar -->\n <div class=\"filter-bar\">\n <div class=\"filter-group\">\n <select class=\"filter-select\"\n [(ngModel)]=\"Filters.EntityName\"\n (ngModelChange)=\"OnFilterChange()\">\n <option value=\"\">All Entities</option>\n @for (name of EntityNames; track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n\n <div class=\"filter-range\">\n <label class=\"filter-label\">Min Score</label>\n <input type=\"number\"\n class=\"filter-input\"\n min=\"0\" max=\"1\" step=\"0.05\"\n [(ngModel)]=\"Filters.MinScore\"\n (ngModelChange)=\"OnFilterChange()\">\n </div>\n\n <div class=\"filter-range\">\n <label class=\"filter-label\">Max Score</label>\n <input type=\"number\"\n class=\"filter-input\"\n min=\"0\" max=\"1\" step=\"0.05\"\n [(ngModel)]=\"Filters.MaxScore\"\n (ngModelChange)=\"OnFilterChange()\">\n </div>\n\n <div class=\"filter-range\">\n <label class=\"filter-label\">From</label>\n <input type=\"date\"\n class=\"filter-input\"\n [(ngModel)]=\"Filters.DateFrom\"\n (ngModelChange)=\"OnFilterChange()\">\n </div>\n\n <div class=\"filter-range\">\n <label class=\"filter-label\">To</label>\n <input type=\"date\"\n class=\"filter-input\"\n [(ngModel)]=\"Filters.DateTo\"\n (ngModelChange)=\"OnFilterChange()\">\n </div>\n </div>\n\n @if (HasActiveFilters) {\n <button class=\"clear-filters-btn\" (click)=\"ClearFilters()\">\n <i class=\"fa-solid fa-times\"></i> Clear Filters\n </button>\n }\n </div>\n\n <!-- Content -->\n @if (IsLoading) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading duplicate detection results...\"></mj-loading>\n </div>\n } @else if (TotalGroupCount === 0) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-clone empty-icon\"></i>\n <p class=\"empty-text\">No duplicate detection results found.</p>\n <p class=\"empty-subtext\">Run a duplicate detection scan to see results here.</p>\n </div>\n } @else {\n <!-- Kanban Board -->\n <div class=\"kanban-board\">\n <!-- Pending Column -->\n <div class=\"kanban-column\">\n <div class=\"column-header column-header-pending\">\n <i class=\"fa-solid fa-clock\"></i>\n <span class=\"column-title\">Pending Review</span>\n <span class=\"column-count\">{{ PendingCount }}</span>\n </div>\n <div class=\"column-body\">\n @for (group of PendingGroups; track group.DetailId) {\n <div class=\"kanban-card\">\n <div class=\"card-header\">\n <span class=\"entity-badge\">{{ group.EntityName }}</span>\n <span class=\"score-indicator\" [class]=\"GetScoreClass(group.HighestScore)\">\n {{ (group.HighestScore * 100).toFixed(0) }}%\n </span>\n </div>\n <div class=\"card-body\">\n <div class=\"card-field\">\n <i class=\"fa-solid fa-fingerprint field-icon\"></i>\n <span class=\"field-value record-id\" [title]=\"group.RecordId\">{{ group.RecordId }}</span>\n </div>\n <div class=\"card-field\">\n <i class=\"fa-solid fa-layer-group field-icon\"></i>\n <span class=\"field-value\">{{ group.MatchCount }} match{{ group.MatchCount !== 1 ? 'es' : '' }}</span>\n </div>\n <div class=\"card-field\">\n <i class=\"fa-solid fa-calendar field-icon\"></i>\n <span class=\"field-value\">{{ FormatDate(group.MatchedAt) }}</span>\n </div>\n </div>\n <div class=\"card-actions\">\n <button class=\"action-btn approve-btn\"\n [disabled]=\"IsSaving\"\n (click)=\"ApproveMatch(group)\">\n <i class=\"fa-solid fa-check\"></i> Approve\n </button>\n <button class=\"action-btn reject-btn\"\n [disabled]=\"IsSaving\"\n (click)=\"RejectMatch(group)\">\n <i class=\"fa-solid fa-times\"></i> Reject\n </button>\n </div>\n </div>\n }\n @if (PendingGroups.length === 0) {\n <div class=\"column-empty\">\n <i class=\"fa-solid fa-check-circle\"></i>\n <span>No pending items</span>\n </div>\n }\n </div>\n </div>\n\n <!-- Approved Column -->\n <div class=\"kanban-column\">\n <div class=\"column-header column-header-approved\">\n <i class=\"fa-solid fa-check-circle\"></i>\n <span class=\"column-title\">Approved</span>\n <span class=\"column-count\">{{ ApprovedCount }}</span>\n </div>\n <div class=\"column-body\">\n @for (group of ApprovedGroups; track group.DetailId) {\n <div class=\"kanban-card\">\n <div class=\"card-header\">\n <span class=\"entity-badge\">{{ group.EntityName }}</span>\n <span class=\"score-indicator\" [class]=\"GetScoreClass(group.HighestScore)\">\n {{ (group.HighestScore * 100).toFixed(0) }}%\n </span>\n </div>\n <div class=\"card-body\">\n <div class=\"card-field\">\n <i class=\"fa-solid fa-fingerprint field-icon\"></i>\n <span class=\"field-value record-id\" [title]=\"group.RecordId\">{{ group.RecordId }}</span>\n </div>\n <div class=\"card-field\">\n <i class=\"fa-solid fa-layer-group field-icon\"></i>\n <span class=\"field-value\">{{ group.MatchCount }} match{{ group.MatchCount !== 1 ? 'es' : '' }}</span>\n </div>\n <div class=\"card-field\">\n <i class=\"fa-solid fa-calendar field-icon\"></i>\n <span class=\"field-value\">{{ FormatDate(group.MatchedAt) }}</span>\n </div>\n </div>\n </div>\n }\n @if (ApprovedGroups.length === 0) {\n <div class=\"column-empty\">\n <i class=\"fa-solid fa-inbox\"></i>\n <span>No approved items</span>\n </div>\n }\n </div>\n </div>\n\n <!-- Rejected Column -->\n <div class=\"kanban-column\">\n <div class=\"column-header column-header-rejected\">\n <i class=\"fa-solid fa-ban\"></i>\n <span class=\"column-title\">Rejected</span>\n <span class=\"column-count\">{{ RejectedCount }}</span>\n </div>\n <div class=\"column-body\">\n @for (group of RejectedGroups; track group.DetailId) {\n <div class=\"kanban-card\">\n <div class=\"card-header\">\n <span class=\"entity-badge\">{{ group.EntityName }}</span>\n <span class=\"score-indicator\" [class]=\"GetScoreClass(group.HighestScore)\">\n {{ (group.HighestScore * 100).toFixed(0) }}%\n </span>\n </div>\n <div class=\"card-body\">\n <div class=\"card-field\">\n <i class=\"fa-solid fa-fingerprint field-icon\"></i>\n <span class=\"field-value record-id\" [title]=\"group.RecordId\">{{ group.RecordId }}</span>\n </div>\n <div class=\"card-field\">\n <i class=\"fa-solid fa-layer-group field-icon\"></i>\n <span class=\"field-value\">{{ group.MatchCount }} match{{ group.MatchCount !== 1 ? 'es' : '' }}</span>\n </div>\n <div class=\"card-field\">\n <i class=\"fa-solid fa-calendar field-icon\"></i>\n <span class=\"field-value\">{{ FormatDate(group.MatchedAt) }}</span>\n </div>\n </div>\n </div>\n }\n @if (RejectedGroups.length === 0) {\n <div class=\"column-empty\">\n <i class=\"fa-solid fa-inbox\"></i>\n <span>No rejected items</span>\n </div>\n }\n </div>\n </div>\n </div>\n }\n\n <!-- Saving overlay -->\n @if (IsSaving) {\n <div class=\"saving-overlay\">\n <mj-loading text=\"Saving...\" size=\"small\"></mj-loading>\n </div>\n }\n</div>\n", styles: ["/* ============================================================\n Duplicate Detection Kanban Board - Resource Component Styles\n All colors use MJ design tokens (--mj-*) exclusively.\n ============================================================ */\n\n.duplicate-detection-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n padding: 16px 20px;\n background: var(--mj-bg-page);\n position: relative;\n overflow: hidden;\n}\n\n/* ---- Page Header ---- */\n\n.page-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 16px;\n flex-shrink: 0;\n}\n\n.page-title {\n margin: 0;\n font-size: 20px;\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.page-title i {\n color: var(--mj-brand-primary);\n}\n\n/* ---- KPI Strip ---- */\n\n.kpi-strip {\n display: flex;\n gap: 12px;\n margin-bottom: 16px;\n flex-shrink: 0;\n}\n\n.kpi-card {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 12px 16px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n}\n\n.kpi-value {\n font-size: 28px;\n font-weight: 700;\n color: var(--mj-text-primary);\n line-height: 1.2;\n}\n\n.kpi-label {\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-top: 2px;\n}\n\n.kpi-pending .kpi-value {\n color: var(--mj-status-warning);\n}\n\n.kpi-approved .kpi-value {\n color: var(--mj-status-success);\n}\n\n.kpi-rejected .kpi-value {\n color: var(--mj-status-error);\n}\n\n/* ---- Filter Bar ---- */\n\n.filter-bar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 16px;\n flex-shrink: 0;\n}\n\n.filter-group {\n display: flex;\n align-items: flex-end;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.filter-select {\n height: 34px;\n padding: 4px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 13px;\n outline: none;\n min-width: 160px;\n}\n\n.filter-select:focus {\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.filter-range {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.filter-label {\n font-size: 11px;\n font-weight: 500;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n.filter-input {\n height: 34px;\n padding: 4px 8px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 13px;\n outline: none;\n width: 100px;\n}\n\n.filter-input:focus {\n border-color: var(--mj-border-focus);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.clear-filters-btn {\n display: flex;\n align-items: center;\n gap: 4px;\n height: 34px;\n padding: 0 12px;\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: 13px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.clear-filters-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n/* ---- Loading & Empty States ---- */\n\n.loading-container {\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 1;\n min-height: 200px;\n}\n\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n flex: 1;\n min-height: 200px;\n gap: 8px;\n}\n\n.empty-icon {\n font-size: 48px;\n color: var(--mj-text-disabled);\n}\n\n.empty-text {\n font-size: 16px;\n font-weight: 500;\n color: var(--mj-text-secondary);\n margin: 0;\n}\n\n.empty-subtext {\n font-size: 13px;\n color: var(--mj-text-muted);\n margin: 0;\n}\n\n/* ---- Kanban Board ---- */\n\n.kanban-board {\n display: flex;\n gap: 16px;\n flex: 1;\n min-height: 0;\n overflow-x: auto;\n}\n\n.kanban-column {\n flex: 1;\n min-width: 280px;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n overflow: hidden;\n}\n\n.column-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n border-bottom: 1px solid var(--mj-border-subtle);\n flex-shrink: 0;\n}\n\n.column-header-pending {\n background: color-mix(in srgb, var(--mj-status-warning) 8%, var(--mj-bg-surface));\n}\n\n.column-header-pending i {\n color: var(--mj-status-warning);\n}\n\n.column-header-approved {\n background: color-mix(in srgb, var(--mj-status-success) 8%, var(--mj-bg-surface));\n}\n\n.column-header-approved i {\n color: var(--mj-status-success);\n}\n\n.column-header-rejected {\n background: color-mix(in srgb, var(--mj-status-error) 8%, var(--mj-bg-surface));\n}\n\n.column-header-rejected i {\n color: var(--mj-status-error);\n}\n\n.column-title {\n flex: 1;\n}\n\n.column-count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 24px;\n height: 24px;\n padding: 0 6px;\n border-radius: 12px;\n font-size: 12px;\n font-weight: 600;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n}\n\n.column-body {\n flex: 1;\n overflow-y: auto;\n padding: 12px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.column-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 32px 16px;\n color: var(--mj-text-disabled);\n font-size: 13px;\n}\n\n.column-empty i {\n font-size: 24px;\n}\n\n/* ---- Kanban Cards ---- */\n\n.kanban-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n overflow: hidden;\n transition: box-shadow 0.15s ease, border-color 0.15s ease;\n}\n\n.kanban-card:hover {\n border-color: var(--mj-border-strong);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n}\n\n.card-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 12px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.entity-badge {\n display: inline-flex;\n align-items: center;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 11px;\n font-weight: 600;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n max-width: 160px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Score indicator colors */\n\n.score-indicator {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 40px;\n padding: 2px 8px;\n border-radius: 12px;\n font-size: 12px;\n font-weight: 700;\n}\n\n.score-high {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n border: 1px solid var(--mj-status-success-border);\n}\n\n.score-medium {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n border: 1px solid var(--mj-status-warning-border);\n}\n\n.score-low {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n border: 1px solid var(--mj-status-error-border);\n}\n\n.card-body {\n padding: 10px 12px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.card-field {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: var(--mj-text-secondary);\n}\n\n.field-icon {\n width: 14px;\n text-align: center;\n font-size: 12px;\n color: var(--mj-text-muted);\n flex-shrink: 0;\n}\n\n.field-value {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.record-id {\n font-family: monospace;\n font-size: 12px;\n max-width: 180px;\n}\n\n.card-actions {\n display: flex;\n gap: 8px;\n padding: 10px 12px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n.action-btn {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n height: 32px;\n padding: 0 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n\n.action-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.approve-btn {\n background: color-mix(in srgb, var(--mj-status-success) 10%, var(--mj-bg-surface));\n color: var(--mj-status-success-text);\n border-color: var(--mj-status-success-border);\n}\n\n.approve-btn:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-success) 20%, var(--mj-bg-surface));\n}\n\n.reject-btn {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n color: var(--mj-status-error-text);\n border-color: var(--mj-status-error-border);\n}\n\n.reject-btn:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-error) 20%, var(--mj-bg-surface));\n}\n\n/* ---- Responsive ---- */\n\n@media (max-width: 768px) {\n .kanban-board {\n flex-direction: column;\n }\n\n .kanban-column {\n min-width: auto;\n max-height: 400px;\n }\n}\n\n@media (max-width: 480px) {\n .kpi-strip {\n flex-direction: column;\n }\n\n .filter-bar {\n flex-wrap: wrap;\n }\n\n .filter-group {\n flex-direction: column;\n align-items: stretch;\n width: 100%;\n }\n\n .filter-select {\n min-width: auto;\n width: 100%;\n }\n\n .filter-input {\n width: 100%;\n }\n\n .card-actions {\n flex-direction: column;\n }\n\n .action-btn {\n width: 100%;\n }\n}\n\n/* ---- Saving Overlay ---- */\n\n.saving-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--mj-bg-overlay);\n z-index: 10;\n border-radius: 8px;\n}\n"] }]
|
|
766
|
+
}], null, { EmbeddedMode: [{
|
|
767
|
+
type: Input
|
|
768
|
+
}] }); })();
|
|
769
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(DuplicateDetectionResourceComponent, { className: "DuplicateDetectionResourceComponent", filePath: "src/AI/components/duplicates/duplicate-detection-resource.component.ts", lineNumber: 54 }); })();
|
|
770
|
+
export function LoadDuplicateDetectionResource() {
|
|
771
|
+
// Prevents tree-shaking
|
|
772
|
+
}
|
|
773
|
+
//# sourceMappingURL=duplicate-detection-resource.component.js.map
|