@memberjunction/ng-dashboards 5.22.0 → 5.24.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.
Files changed (245) hide show
  1. package/README.md +51 -0
  2. package/dist/AI/components/agents/agent-configuration.component.d.ts.map +1 -1
  3. package/dist/AI/components/agents/agent-configuration.component.js +364 -362
  4. package/dist/AI/components/agents/agent-configuration.component.js.map +1 -1
  5. package/dist/AI/components/agents/agent-editor.component.js +2 -2
  6. package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts +947 -64
  7. package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts.map +1 -1
  8. package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js +7645 -430
  9. package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js.map +1 -1
  10. package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts +285 -6
  11. package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts.map +1 -1
  12. package/dist/AI/components/duplicates/duplicate-detection-resource.component.js +2454 -277
  13. package/dist/AI/components/duplicates/duplicate-detection-resource.component.js.map +1 -1
  14. package/dist/AI/components/execution-monitoring.component.d.ts.map +1 -1
  15. package/dist/AI/components/execution-monitoring.component.js +191 -197
  16. package/dist/AI/components/execution-monitoring.component.js.map +1 -1
  17. package/dist/AI/components/models/model-management.component.js +9 -8
  18. package/dist/AI/components/models/model-management.component.js.map +1 -1
  19. package/dist/AI/components/prompts/prompt-management.component.js +305 -299
  20. package/dist/AI/components/prompts/prompt-management.component.js.map +1 -1
  21. package/dist/AI/components/system/system-configuration.component.js +319 -313
  22. package/dist/AI/components/system/system-configuration.component.js.map +1 -1
  23. package/dist/AI/components/vectors/vector-management-resource.component.d.ts +20 -2
  24. package/dist/AI/components/vectors/vector-management-resource.component.d.ts.map +1 -1
  25. package/dist/AI/components/vectors/vector-management-resource.component.js +419 -232
  26. package/dist/AI/components/vectors/vector-management-resource.component.js.map +1 -1
  27. package/dist/APIKeys/api-applications-panel.component.js +10 -12
  28. package/dist/APIKeys/api-applications-panel.component.js.map +1 -1
  29. package/dist/APIKeys/api-key-create-dialog.component.js +13 -19
  30. package/dist/APIKeys/api-key-create-dialog.component.js.map +1 -1
  31. package/dist/APIKeys/api-key-edit-panel.component.js +12 -14
  32. package/dist/APIKeys/api-key-edit-panel.component.js.map +1 -1
  33. package/dist/APIKeys/api-scopes-panel.component.js +61 -68
  34. package/dist/APIKeys/api-scopes-panel.component.js.map +1 -1
  35. package/dist/APIKeys/api-usage-panel.component.js +10 -11
  36. package/dist/APIKeys/api-usage-panel.component.js.map +1 -1
  37. package/dist/Actions/components/actions-list-view.component.js +82 -96
  38. package/dist/Actions/components/actions-list-view.component.js.map +1 -1
  39. package/dist/Actions/components/actions-overview.component.js +130 -134
  40. package/dist/Actions/components/actions-overview.component.js.map +1 -1
  41. package/dist/Actions/components/categories-list-view.component.d.ts.map +1 -1
  42. package/dist/Actions/components/categories-list-view.component.js +40 -46
  43. package/dist/Actions/components/categories-list-view.component.js.map +1 -1
  44. package/dist/Actions/components/code-management.component.js +2 -2
  45. package/dist/Actions/components/code-management.component.js.map +1 -1
  46. package/dist/Actions/components/entity-integration.component.js +2 -2
  47. package/dist/Actions/components/entity-integration.component.js.map +1 -1
  48. package/dist/Actions/components/execution-monitoring.component.js +127 -132
  49. package/dist/Actions/components/execution-monitoring.component.js.map +1 -1
  50. package/dist/Actions/components/executions-list-view.component.js +2 -2
  51. package/dist/Actions/components/executions-list-view.component.js.map +1 -1
  52. package/dist/Actions/components/explorer/action-card.component.js +11 -17
  53. package/dist/Actions/components/explorer/action-card.component.js.map +1 -1
  54. package/dist/Actions/components/explorer/action-explorer.component.js +5 -11
  55. package/dist/Actions/components/explorer/action-explorer.component.js.map +1 -1
  56. package/dist/Actions/components/explorer/action-list-item.component.js +8 -10
  57. package/dist/Actions/components/explorer/action-list-item.component.js.map +1 -1
  58. package/dist/Actions/components/explorer/action-toolbar.component.js +112 -133
  59. package/dist/Actions/components/explorer/action-toolbar.component.js.map +1 -1
  60. package/dist/Actions/components/explorer/action-tree-panel.component.js +63 -83
  61. package/dist/Actions/components/explorer/action-tree-panel.component.js.map +1 -1
  62. package/dist/Actions/components/explorer/new-action-panel.component.js +17 -21
  63. package/dist/Actions/components/explorer/new-action-panel.component.js.map +1 -1
  64. package/dist/Actions/components/explorer/new-category-panel.component.js +17 -21
  65. package/dist/Actions/components/explorer/new-category-panel.component.js.map +1 -1
  66. package/dist/Actions/components/scheduled-actions.component.js +2 -2
  67. package/dist/Actions/components/scheduled-actions.component.js.map +1 -1
  68. package/dist/Actions/components/security-permissions.component.js +2 -2
  69. package/dist/Actions/components/security-permissions.component.js.map +1 -1
  70. package/dist/ComponentStudio/component-studio-dashboard.component.d.ts +13 -5
  71. package/dist/ComponentStudio/component-studio-dashboard.component.d.ts.map +1 -1
  72. package/dist/ComponentStudio/component-studio-dashboard.component.js +168 -145
  73. package/dist/ComponentStudio/component-studio-dashboard.component.js.map +1 -1
  74. package/dist/ComponentStudio/components/artifact-load-dialog.component.d.ts +4 -5
  75. package/dist/ComponentStudio/components/artifact-load-dialog.component.d.ts.map +1 -1
  76. package/dist/ComponentStudio/components/artifact-load-dialog.component.js +197 -200
  77. package/dist/ComponentStudio/components/artifact-load-dialog.component.js.map +1 -1
  78. package/dist/ComponentStudio/components/artifact-selection-dialog.component.d.ts +5 -7
  79. package/dist/ComponentStudio/components/artifact-selection-dialog.component.d.ts.map +1 -1
  80. package/dist/ComponentStudio/components/artifact-selection-dialog.component.js +142 -148
  81. package/dist/ComponentStudio/components/artifact-selection-dialog.component.js.map +1 -1
  82. package/dist/ComponentStudio/components/browser/component-browser.component.js +153 -166
  83. package/dist/ComponentStudio/components/browser/component-browser.component.js.map +1 -1
  84. package/dist/ComponentStudio/components/editors/code-editor-panel.component.js +15 -20
  85. package/dist/ComponentStudio/components/editors/code-editor-panel.component.js.map +1 -1
  86. package/dist/ComponentStudio/components/editors/data-requirements-editor.component.js +16 -21
  87. package/dist/ComponentStudio/components/editors/data-requirements-editor.component.js.map +1 -1
  88. package/dist/ComponentStudio/components/editors/requirements-editor.component.js +18 -23
  89. package/dist/ComponentStudio/components/editors/requirements-editor.component.js.map +1 -1
  90. package/dist/ComponentStudio/components/editors/spec-editor.component.js +25 -30
  91. package/dist/ComponentStudio/components/editors/spec-editor.component.js.map +1 -1
  92. package/dist/ComponentStudio/components/new-component-dialog/new-component-dialog.component.js +10 -11
  93. package/dist/ComponentStudio/components/new-component-dialog/new-component-dialog.component.js.map +1 -1
  94. package/dist/ComponentStudio/components/save-version-dialog/save-version-dialog.component.d.ts.map +1 -1
  95. package/dist/ComponentStudio/components/save-version-dialog/save-version-dialog.component.js +24 -35
  96. package/dist/ComponentStudio/components/save-version-dialog/save-version-dialog.component.js.map +1 -1
  97. package/dist/ComponentStudio/components/text-import-dialog.component.js +15 -17
  98. package/dist/ComponentStudio/components/text-import-dialog.component.js.map +1 -1
  99. package/dist/Credentials/components/credentials-categories-resource.component.js +7 -6
  100. package/dist/Credentials/components/credentials-categories-resource.component.js.map +1 -1
  101. package/dist/Credentials/components/credentials-list-resource.component.js +6 -5
  102. package/dist/Credentials/components/credentials-list-resource.component.js.map +1 -1
  103. package/dist/Credentials/components/credentials-types-resource.component.js +7 -6
  104. package/dist/Credentials/components/credentials-types-resource.component.js.map +1 -1
  105. package/dist/DashboardBrowser/dashboard-share-dialog.component.js +9 -9
  106. package/dist/DashboardBrowser/dashboard-share-dialog.component.js.map +1 -1
  107. package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
  108. package/dist/DataExplorer/data-explorer-dashboard.component.js +17 -17
  109. package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
  110. package/dist/Home/home-dashboard.component.js +4 -4
  111. package/dist/Home/home-dashboard.component.js.map +1 -1
  112. package/dist/Integration/components/activity/activity.component.d.ts.map +1 -1
  113. package/dist/Integration/components/activity/activity.component.js +1 -0
  114. package/dist/Integration/components/activity/activity.component.js.map +1 -1
  115. package/dist/Integration/components/connections/connections.component.d.ts.map +1 -1
  116. package/dist/Integration/components/connections/connections.component.js +5 -4
  117. package/dist/Integration/components/connections/connections.component.js.map +1 -1
  118. package/dist/Integration/components/mapping-workspace/mapping-workspace.component.d.ts.map +1 -1
  119. package/dist/Integration/components/mapping-workspace/mapping-workspace.component.js +247 -259
  120. package/dist/Integration/components/mapping-workspace/mapping-workspace.component.js.map +1 -1
  121. package/dist/Integration/components/overview/overview.component.d.ts.map +1 -1
  122. package/dist/Integration/components/overview/overview.component.js +1 -0
  123. package/dist/Integration/components/overview/overview.component.js.map +1 -1
  124. package/dist/Integration/components/pipelines/pipelines.component.d.ts.map +1 -1
  125. package/dist/Integration/components/pipelines/pipelines.component.js +1 -0
  126. package/dist/Integration/components/pipelines/pipelines.component.js.map +1 -1
  127. package/dist/Integration/components/schedules/schedules.component.d.ts.map +1 -1
  128. package/dist/Integration/components/schedules/schedules.component.js +1 -0
  129. package/dist/Integration/components/schedules/schedules.component.js.map +1 -1
  130. package/dist/Integration/components/widgets/integration-card.component.js +7 -9
  131. package/dist/Integration/components/widgets/integration-card.component.js.map +1 -1
  132. package/dist/Integration/integration.module.d.ts +6 -10
  133. package/dist/Integration/integration.module.d.ts.map +1 -1
  134. package/dist/Integration/integration.module.js +12 -20
  135. package/dist/Integration/integration.module.js.map +1 -1
  136. package/dist/KnowledgeHub/components/analytics/analytics-resource.component.d.ts +411 -0
  137. package/dist/KnowledgeHub/components/analytics/analytics-resource.component.d.ts.map +1 -0
  138. package/dist/KnowledgeHub/components/analytics/analytics-resource.component.js +4266 -0
  139. package/dist/KnowledgeHub/components/analytics/analytics-resource.component.js.map +1 -0
  140. package/dist/KnowledgeHub/components/clusters/cluster-visualization-resource.component.d.ts +140 -0
  141. package/dist/KnowledgeHub/components/clusters/cluster-visualization-resource.component.d.ts.map +1 -0
  142. package/dist/KnowledgeHub/components/clusters/cluster-visualization-resource.component.js +780 -0
  143. package/dist/KnowledgeHub/components/clusters/cluster-visualization-resource.component.js.map +1 -0
  144. package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.d.ts +8 -2
  145. package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.d.ts.map +1 -1
  146. package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js +246 -195
  147. package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js.map +1 -1
  148. package/dist/KnowledgeHub/components/scheduling/scheduling-resource.component.d.ts +75 -0
  149. package/dist/KnowledgeHub/components/scheduling/scheduling-resource.component.d.ts.map +1 -0
  150. package/dist/KnowledgeHub/components/scheduling/scheduling-resource.component.js +601 -0
  151. package/dist/KnowledgeHub/components/scheduling/scheduling-resource.component.js.map +1 -0
  152. package/dist/KnowledgeHub/components/search/knowledge-search-resource.component.d.ts +93 -12
  153. package/dist/KnowledgeHub/components/search/knowledge-search-resource.component.d.ts.map +1 -1
  154. package/dist/KnowledgeHub/components/search/knowledge-search-resource.component.js +637 -107
  155. package/dist/KnowledgeHub/components/search/knowledge-search-resource.component.js.map +1 -1
  156. package/dist/KnowledgeHub/index.d.ts +3 -0
  157. package/dist/KnowledgeHub/index.d.ts.map +1 -1
  158. package/dist/KnowledgeHub/index.js +3 -0
  159. package/dist/KnowledgeHub/index.js.map +1 -1
  160. package/dist/Lists/components/lists-browse-resource.component.d.ts.map +1 -1
  161. package/dist/Lists/components/lists-browse-resource.component.js +9 -7
  162. package/dist/Lists/components/lists-browse-resource.component.js.map +1 -1
  163. package/dist/Lists/components/lists-my-lists-resource.component.js +5 -4
  164. package/dist/Lists/components/lists-my-lists-resource.component.js.map +1 -1
  165. package/dist/Lists/components/lists-operations-resource.component.js +10 -9
  166. package/dist/Lists/components/lists-operations-resource.component.js.map +1 -1
  167. package/dist/MCP/components/mcp-connection-dialog.component.js +141 -132
  168. package/dist/MCP/components/mcp-connection-dialog.component.js.map +1 -1
  169. package/dist/MCP/components/mcp-log-detail-panel.component.js +4 -4
  170. package/dist/MCP/components/mcp-log-detail-panel.component.js.map +1 -1
  171. package/dist/MCP/components/mcp-server-dialog.component.js +141 -128
  172. package/dist/MCP/components/mcp-server-dialog.component.js.map +1 -1
  173. package/dist/MCP/components/mcp-test-tool-dialog.component.js +210 -218
  174. package/dist/MCP/components/mcp-test-tool-dialog.component.js.map +1 -1
  175. package/dist/MCP/mcp-dashboard.component.js +2 -2
  176. package/dist/MCP/mcp-dashboard.component.js.map +1 -1
  177. package/dist/MCP/mcp.module.d.ts +6 -9
  178. package/dist/MCP/mcp.module.d.ts.map +1 -1
  179. package/dist/MCP/mcp.module.js +20 -22
  180. package/dist/MCP/mcp.module.js.map +1 -1
  181. package/dist/Scheduling/components/scheduling-activity.component.js +5 -4
  182. package/dist/Scheduling/components/scheduling-activity.component.js.map +1 -1
  183. package/dist/Scheduling/components/scheduling-jobs.component.js +6 -5
  184. package/dist/Scheduling/components/scheduling-jobs.component.js.map +1 -1
  185. package/dist/Scheduling/components/scheduling-overview.component.js +93 -92
  186. package/dist/Scheduling/components/scheduling-overview.component.js.map +1 -1
  187. package/dist/Testing/testing-dashboard.component.js +9 -10
  188. package/dist/Testing/testing-dashboard.component.js.map +1 -1
  189. package/dist/__tests__/analytics-resource.test.d.ts +2 -0
  190. package/dist/__tests__/analytics-resource.test.d.ts.map +1 -0
  191. package/dist/__tests__/analytics-resource.test.js +181 -0
  192. package/dist/__tests__/analytics-resource.test.js.map +1 -0
  193. package/dist/__tests__/scheduling.test.d.ts +2 -0
  194. package/dist/__tests__/scheduling.test.d.ts.map +1 -0
  195. package/dist/__tests__/scheduling.test.js +205 -0
  196. package/dist/__tests__/scheduling.test.js.map +1 -0
  197. package/dist/actions-dashboards.module.d.ts +8 -13
  198. package/dist/actions-dashboards.module.d.ts.map +1 -1
  199. package/dist/actions-dashboards.module.js +6 -27
  200. package/dist/actions-dashboards.module.js.map +1 -1
  201. package/dist/ai-dashboards.module.d.ts +20 -20
  202. package/dist/ai-dashboards.module.d.ts.map +1 -1
  203. package/dist/ai-dashboards.module.js +43 -44
  204. package/dist/ai-dashboards.module.js.map +1 -1
  205. package/dist/communication-dashboards.module.d.ts +4 -8
  206. package/dist/communication-dashboards.module.d.ts.map +1 -1
  207. package/dist/communication-dashboards.module.js +0 -19
  208. package/dist/communication-dashboards.module.js.map +1 -1
  209. package/dist/component-studio-dashboards.module.d.ts +7 -11
  210. package/dist/component-studio-dashboards.module.d.ts.map +1 -1
  211. package/dist/component-studio-dashboards.module.js +22 -34
  212. package/dist/component-studio-dashboards.module.js.map +1 -1
  213. package/dist/core-dashboards.module.d.ts +12 -18
  214. package/dist/core-dashboards.module.d.ts.map +1 -1
  215. package/dist/core-dashboards.module.js +15 -31
  216. package/dist/core-dashboards.module.js.map +1 -1
  217. package/dist/credentials-dashboards.module.d.ts +5 -8
  218. package/dist/credentials-dashboards.module.d.ts.map +1 -1
  219. package/dist/credentials-dashboards.module.js +3 -19
  220. package/dist/credentials-dashboards.module.js.map +1 -1
  221. package/dist/data-explorer-dashboards.module.d.ts +7 -13
  222. package/dist/data-explorer-dashboards.module.d.ts.map +1 -1
  223. package/dist/data-explorer-dashboards.module.js +0 -27
  224. package/dist/data-explorer-dashboards.module.js.map +1 -1
  225. package/dist/lists-dashboards.module.d.ts +5 -8
  226. package/dist/lists-dashboards.module.d.ts.map +1 -1
  227. package/dist/lists-dashboards.module.js +3 -19
  228. package/dist/lists-dashboards.module.js.map +1 -1
  229. package/dist/public-api.d.ts +2 -0
  230. package/dist/public-api.d.ts.map +1 -1
  231. package/dist/public-api.js +2 -0
  232. package/dist/public-api.js.map +1 -1
  233. package/dist/scheduling-dashboards.module.d.ts +6 -10
  234. package/dist/scheduling-dashboards.module.d.ts.map +1 -1
  235. package/dist/scheduling-dashboards.module.js +3 -23
  236. package/dist/scheduling-dashboards.module.js.map +1 -1
  237. package/dist/shared/entity-field-display.d.ts +44 -0
  238. package/dist/shared/entity-field-display.d.ts.map +1 -0
  239. package/dist/shared/entity-field-display.js +118 -0
  240. package/dist/shared/entity-field-display.js.map +1 -0
  241. package/dist/testing-dashboards.module.d.ts +7 -13
  242. package/dist/testing-dashboards.module.d.ts.map +1 -1
  243. package/dist/testing-dashboards.module.js +0 -27
  244. package/dist/testing-dashboards.module.js.map +1 -1
  245. package/package.json +48 -55
@@ -9,278 +9,1305 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
9
9
  *
10
10
  * Dashboard resource for reviewing duplicate detection results in a Kanban-style
11
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.
12
+ * Supports triggering new detection runs with real-time progress via
13
+ * GraphQL subscriptions.
14
14
  */
15
- import { Component, ChangeDetectorRef, Input, inject } from '@angular/core';
15
+ import { Component, ChangeDetectorRef, Input, inject, ViewEncapsulation, HostListener } from '@angular/core';
16
16
  import { Subject } from 'rxjs';
17
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';
18
+ import { CompositeKey, LogStatus, Metadata, RecordMergeRequest, RunView } from '@memberjunction/core';
19
+ import { MJNotificationService } from '@memberjunction/ng-notifications';
20
+ import { KnowledgeHubMetadataEngine } from '@memberjunction/core-entities';
21
+ import { RegisterClass, UUIDsEqual } from '@memberjunction/global';
22
+ import { BaseResourceComponent, NavigationService } from '@memberjunction/ng-shared';
21
23
  import * as i0 from "@angular/core";
22
24
  import * as i1 from "@angular/forms";
23
25
  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);
26
+ const _forTrack0 = ($index, $item) => $item.ID;
27
+ const _forTrack1 = ($index, $item) => $item.DetailId;
28
+ const _forTrack2 = ($index, $item) => $item.Name;
29
+ const _forTrack3 = ($index, $item) => $item.Match.ID;
30
+ const _forTrack4 = ($index, $item) => $item.FieldName;
31
+ const _forTrack5 = ($index, $item) => $item.Entity;
32
+ const _forTrack6 = ($index, $item) => $item.ColumnIndex;
33
+ function DuplicateDetectionResourceComponent_For_12_Template(rf, ctx) { if (rf & 1) {
34
+ i0.ɵɵelementStart(0, "option", 9);
27
35
  i0.ɵɵtext(1);
28
36
  i0.ɵɵelementEnd();
29
37
  } if (rf & 2) {
30
- const name_r1 = ctx.$implicit;
31
- i0.ɵɵproperty("value", name_r1);
38
+ const doc_r1 = ctx.$implicit;
39
+ i0.ɵɵproperty("value", doc_r1.ID);
32
40
  i0.ɵɵadvance();
33
- i0.ɵɵtextInterpolate(name_r1);
41
+ i0.ɵɵtextInterpolate2("", doc_r1.Name, " (", doc_r1.EntityName, ")");
34
42
  } }
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);
43
+ function DuplicateDetectionResourceComponent_Conditional_14_Template(rf, ctx) { if (rf & 1) {
44
+ i0.ɵɵelement(0, "i", 34);
45
+ i0.ɵɵtext(1, " Detecting... ");
46
+ } }
47
+ function DuplicateDetectionResourceComponent_Conditional_15_Template(rf, ctx) { if (rf & 1) {
48
+ i0.ɵɵelement(0, "i", 35);
49
+ i0.ɵɵtext(1, " Run Detection ");
50
+ } }
51
+ function DuplicateDetectionResourceComponent_Conditional_16_Conditional_9_Template(rf, ctx) { if (rf & 1) {
52
+ i0.ɵɵelementStart(0, "span", 41);
53
+ i0.ɵɵtext(1);
54
+ i0.ɵɵelementEnd();
55
+ } if (rf & 2) {
56
+ const ctx_r1 = i0.ɵɵnextContext(2);
57
+ i0.ɵɵadvance();
58
+ i0.ɵɵtextInterpolate(ctx_r1.DetectionCurrentItem);
59
+ } }
60
+ function DuplicateDetectionResourceComponent_Conditional_16_Template(rf, ctx) { if (rf & 1) {
61
+ i0.ɵɵelementStart(0, "div", 11)(1, "div", 36)(2, "span", 37);
62
+ i0.ɵɵelement(3, "i", 34);
63
+ i0.ɵɵtext(4);
64
+ i0.ɵɵelementEnd();
65
+ i0.ɵɵelementStart(5, "span", 38);
66
+ i0.ɵɵtext(6);
67
+ i0.ɵɵelementEnd()();
68
+ i0.ɵɵelementStart(7, "div", 39);
69
+ i0.ɵɵelement(8, "div", 40);
70
+ i0.ɵɵelementEnd();
71
+ i0.ɵɵconditionalCreate(9, DuplicateDetectionResourceComponent_Conditional_16_Conditional_9_Template, 2, 1, "span", 41);
72
+ i0.ɵɵelementEnd();
73
+ } if (rf & 2) {
74
+ const ctx_r1 = i0.ɵɵnextContext();
75
+ i0.ɵɵadvance(4);
76
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.DetectionStage, " ");
77
+ i0.ɵɵadvance(2);
78
+ i0.ɵɵtextInterpolate1("", ctx_r1.DetectionProgress, "%");
79
+ i0.ɵɵadvance(2);
80
+ i0.ɵɵstyleProp("width", ctx_r1.DetectionProgress, "%");
81
+ i0.ɵɵadvance();
82
+ i0.ɵɵconditional(ctx_r1.DetectionCurrentItem ? 9 : -1);
83
+ } }
84
+ function DuplicateDetectionResourceComponent_Conditional_17_Template(rf, ctx) { if (rf & 1) {
85
+ const _r3 = i0.ɵɵgetCurrentView();
86
+ i0.ɵɵelementStart(0, "div", 12)(1, "div", 42)(2, "label", 43);
87
+ i0.ɵɵelement(3, "i", 44);
88
+ i0.ɵɵtext(4, " Potential Match ");
89
+ i0.ɵɵelementStart(5, "span", 45);
90
+ i0.ɵɵtext(6);
91
+ i0.ɵɵelementEnd()();
92
+ i0.ɵɵelementStart(7, "input", 46);
93
+ i0.ɵɵlistener("input", function DuplicateDetectionResourceComponent_Conditional_17_Template_input_input_7_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnPotentialThresholdChanged($event.target.value / 100)); });
94
+ i0.ɵɵelementEnd();
95
+ i0.ɵɵelementStart(8, "span", 47);
96
+ i0.ɵɵtext(9, "Score above which duplicates are flagged for review");
97
+ i0.ɵɵelementEnd()();
98
+ i0.ɵɵelementStart(10, "div", 42)(11, "label", 43);
99
+ i0.ɵɵelement(12, "i", 48);
100
+ i0.ɵɵtext(13, " Absolute Match ");
101
+ i0.ɵɵelementStart(14, "span", 45);
102
+ i0.ɵɵtext(15);
103
+ i0.ɵɵelementEnd()();
104
+ i0.ɵɵelementStart(16, "input", 46);
105
+ i0.ɵɵlistener("input", function DuplicateDetectionResourceComponent_Conditional_17_Template_input_input_16_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnAbsoluteThresholdChanged($event.target.value / 100)); });
106
+ i0.ɵɵelementEnd();
107
+ i0.ɵɵelementStart(17, "span", 47);
108
+ i0.ɵɵtext(18, "Score above which duplicates are auto-confirmed");
109
+ i0.ɵɵelementEnd()()();
110
+ } if (rf & 2) {
111
+ const ctx_r1 = i0.ɵɵnextContext();
112
+ i0.ɵɵadvance(6);
113
+ i0.ɵɵtextInterpolate1("", (ctx_r1.RunPotentialThreshold * 100).toFixed(0), "%");
114
+ i0.ɵɵadvance();
115
+ i0.ɵɵproperty("min", 30)("max", 99)("step", 1)("value", ctx_r1.RunPotentialThreshold * 100)("disabled", ctx_r1.IsDetecting);
116
+ i0.ɵɵadvance(8);
117
+ i0.ɵɵtextInterpolate1("", (ctx_r1.RunAbsoluteThreshold * 100).toFixed(0), "%");
118
+ i0.ɵɵadvance();
119
+ i0.ɵɵproperty("min", 50)("max", 100)("step", 1)("value", ctx_r1.RunAbsoluteThreshold * 100)("disabled", ctx_r1.IsDetecting);
120
+ } }
121
+ function DuplicateDetectionResourceComponent_Conditional_42_Template(rf, ctx) { if (rf & 1) {
122
+ i0.ɵɵelementStart(0, "option", 8);
123
+ i0.ɵɵtext(1, "All Entities");
124
+ i0.ɵɵelementEnd();
125
+ } }
126
+ function DuplicateDetectionResourceComponent_For_44_Template(rf, ctx) { if (rf & 1) {
127
+ i0.ɵɵelementStart(0, "option", 9);
128
+ i0.ɵɵtext(1);
129
+ i0.ɵɵelementEnd();
130
+ } if (rf & 2) {
131
+ const name_r4 = ctx.$implicit;
132
+ i0.ɵɵproperty("value", name_r4);
133
+ i0.ɵɵadvance();
134
+ i0.ɵɵtextInterpolate(name_r4);
135
+ } }
136
+ function DuplicateDetectionResourceComponent_Conditional_61_Template(rf, ctx) { if (rf & 1) {
137
+ const _r5 = i0.ɵɵgetCurrentView();
138
+ i0.ɵɵelementStart(0, "button", 49);
139
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_61_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.ClearFilters()); });
140
+ i0.ɵɵelement(1, "i", 50);
40
141
  i0.ɵɵtext(2, " Clear Filters ");
41
142
  i0.ɵɵelementEnd();
42
143
  } }
43
- function DuplicateDetectionResourceComponent_Conditional_51_Template(rf, ctx) { if (rf & 1) {
44
- i0.ɵɵelementStart(0, "div", 22);
45
- i0.ɵɵelement(1, "mj-loading", 28);
144
+ function DuplicateDetectionResourceComponent_Conditional_62_Template(rf, ctx) { if (rf & 1) {
145
+ i0.ɵɵelementStart(0, "div", 28);
146
+ i0.ɵɵelement(1, "i", 51);
147
+ i0.ɵɵtext(2, " Merging is not available for this entity. Detection results are read-only. ");
148
+ i0.ɵɵelementEnd();
149
+ } }
150
+ function DuplicateDetectionResourceComponent_Conditional_63_Template(rf, ctx) { if (rf & 1) {
151
+ i0.ɵɵelementStart(0, "div", 29);
152
+ i0.ɵɵelement(1, "mj-loading", 52);
153
+ i0.ɵɵelementEnd();
154
+ } }
155
+ function DuplicateDetectionResourceComponent_Conditional_64_Template(rf, ctx) { if (rf & 1) {
156
+ i0.ɵɵelementStart(0, "div", 29);
157
+ i0.ɵɵelement(1, "mj-loading", 52);
46
158
  i0.ɵɵelementEnd();
47
159
  } }
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);
160
+ function DuplicateDetectionResourceComponent_Conditional_65_Template(rf, ctx) { if (rf & 1) {
161
+ i0.ɵɵelementStart(0, "div", 30);
162
+ i0.ɵɵelement(1, "i", 53);
163
+ i0.ɵɵelementStart(2, "p", 54);
52
164
  i0.ɵɵtext(3, "No duplicate detection results found.");
53
165
  i0.ɵɵelementEnd();
54
- i0.ɵɵelementStart(4, "p", 31);
55
- i0.ɵɵtext(5, "Run a duplicate detection scan to see results here.");
166
+ i0.ɵɵelementStart(4, "p", 55);
167
+ i0.ɵɵtext(5, "Select an entity document and click \"Run Detection\" to start.");
56
168
  i0.ɵɵelementEnd()();
57
169
  } }
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);
170
+ function DuplicateDetectionResourceComponent_Conditional_66_For_10_Conditional_13_For_2_Template(rf, ctx) { if (rf & 1) {
171
+ i0.ɵɵelementStart(0, "div", 86)(1, "span", 88);
172
+ i0.ɵɵtext(2);
62
173
  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);
174
+ i0.ɵɵelementStart(3, "span", 89);
175
+ i0.ɵɵtext(4);
70
176
  i0.ɵɵelementEnd()();
71
- i0.ɵɵelementStart(11, "div", 48);
72
- i0.ɵɵelement(12, "i", 51);
73
- i0.ɵɵelementStart(13, "span", 52);
74
- i0.ɵɵtext(14);
177
+ } if (rf & 2) {
178
+ const ms_r9 = ctx.$implicit;
179
+ i0.ɵɵadvance(2);
180
+ i0.ɵɵtextInterpolate1("", (ms_r9.Score * 100).toFixed(0), "%");
181
+ i0.ɵɵadvance(2);
182
+ i0.ɵɵtextInterpolate(ms_r9.Name);
183
+ } }
184
+ function DuplicateDetectionResourceComponent_Conditional_66_For_10_Conditional_13_Conditional_3_Template(rf, ctx) { if (rf & 1) {
185
+ i0.ɵɵelementStart(0, "div", 87);
186
+ i0.ɵɵtext(1);
187
+ i0.ɵɵelementEnd();
188
+ } if (rf & 2) {
189
+ const group_r8 = i0.ɵɵnextContext(2).$implicit;
190
+ i0.ɵɵadvance();
191
+ i0.ɵɵtextInterpolate1("+", group_r8.MatchCount - group_r8.TopMatchSummaries.length, " more");
192
+ } }
193
+ function DuplicateDetectionResourceComponent_Conditional_66_For_10_Conditional_13_Template(rf, ctx) { if (rf & 1) {
194
+ i0.ɵɵelementStart(0, "div", 77);
195
+ i0.ɵɵrepeaterCreate(1, DuplicateDetectionResourceComponent_Conditional_66_For_10_Conditional_13_For_2_Template, 5, 2, "div", 86, _forTrack2);
196
+ i0.ɵɵconditionalCreate(3, DuplicateDetectionResourceComponent_Conditional_66_For_10_Conditional_13_Conditional_3_Template, 2, 1, "div", 87);
197
+ i0.ɵɵelementEnd();
198
+ } if (rf & 2) {
199
+ const group_r8 = i0.ɵɵnextContext().$implicit;
200
+ i0.ɵɵadvance();
201
+ i0.ɵɵrepeater(group_r8.TopMatchSummaries);
202
+ i0.ɵɵadvance(2);
203
+ i0.ɵɵconditional(group_r8.MatchCount > group_r8.TopMatchSummaries.length ? 3 : -1);
204
+ } }
205
+ function DuplicateDetectionResourceComponent_Conditional_66_For_10_Template(rf, ctx) { if (rf & 1) {
206
+ const _r7 = i0.ɵɵgetCurrentView();
207
+ i0.ɵɵelementStart(0, "div", 68);
208
+ i0.ɵɵlistener("dragstart", function DuplicateDetectionResourceComponent_Conditional_66_For_10_Template_div_dragstart_0_listener($event) { const group_r8 = i0.ɵɵrestoreView(_r7).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnDragStart($event, group_r8)); })("dragend", function DuplicateDetectionResourceComponent_Conditional_66_For_10_Template_div_dragend_0_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnDragEnd()); })("click", function DuplicateDetectionResourceComponent_Conditional_66_For_10_Template_div_click_0_listener() { const group_r8 = i0.ɵɵrestoreView(_r7).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OpenComparison(group_r8)); });
209
+ i0.ɵɵelementStart(1, "div", 69)(2, "div", 70)(3, "div", 71);
210
+ i0.ɵɵelement(4, "i");
211
+ i0.ɵɵelementEnd();
212
+ i0.ɵɵelementStart(5, "div", 72)(6, "div", 73);
213
+ i0.ɵɵtext(7);
214
+ i0.ɵɵelementEnd();
215
+ i0.ɵɵelementStart(8, "span", 74);
216
+ i0.ɵɵtext(9);
217
+ i0.ɵɵelementEnd()()();
218
+ i0.ɵɵelementStart(10, "span", 75);
219
+ i0.ɵɵtext(11);
75
220
  i0.ɵɵelementEnd()();
76
- i0.ɵɵelementStart(15, "div", 48);
77
- i0.ɵɵelement(16, "i", 53);
78
- i0.ɵɵelementStart(17, "span", 52);
79
- i0.ɵɵtext(18);
221
+ i0.ɵɵelementStart(12, "div", 76);
222
+ i0.ɵɵconditionalCreate(13, DuplicateDetectionResourceComponent_Conditional_66_For_10_Conditional_13_Template, 4, 1, "div", 77);
223
+ i0.ɵɵelementStart(14, "div", 78)(15, "span", 79);
224
+ i0.ɵɵelement(16, "i", 80);
225
+ i0.ɵɵtext(17);
226
+ i0.ɵɵelementEnd();
227
+ i0.ɵɵelementStart(18, "span", 79);
228
+ i0.ɵɵelement(19, "i", 81);
229
+ i0.ɵɵtext(20);
80
230
  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 ");
231
+ i0.ɵɵelementStart(21, "div", 82)(22, "button", 83);
232
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_66_For_10_Template_button_click_22_listener($event) { const group_r8 = i0.ɵɵrestoreView(_r7).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); ctx_r1.ApproveMatch(group_r8); return i0.ɵɵresetView($event.stopPropagation()); });
233
+ i0.ɵɵelement(23, "i", 84);
234
+ i0.ɵɵtext(24, " Approve ");
235
+ i0.ɵɵelementEnd();
236
+ i0.ɵɵelementStart(25, "button", 85);
237
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_66_For_10_Template_button_click_25_listener($event) { const group_r8 = i0.ɵɵrestoreView(_r7).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); ctx_r1.RejectMatch(group_r8); return i0.ɵɵresetView($event.stopPropagation()); });
238
+ i0.ɵɵelement(26, "i", 50);
239
+ i0.ɵɵtext(27, " Reject ");
90
240
  i0.ɵɵelementEnd()()();
91
241
  } 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);
242
+ const group_r8 = ctx.$implicit;
243
+ const ctx_r1 = i0.ɵɵnextContext(2);
244
+ i0.ɵɵadvance(4);
245
+ i0.ɵɵclassMap(group_r8.EntityIcon);
246
+ i0.ɵɵadvance(2);
247
+ i0.ɵɵproperty("title", group_r8.RecordName);
96
248
  i0.ɵɵadvance();
97
- i0.ɵɵclassMap(ctx_r2.GetScoreClass(group_r5.HighestScore));
249
+ i0.ɵɵtextInterpolate(group_r8.RecordName);
250
+ i0.ɵɵadvance(2);
251
+ i0.ɵɵtextInterpolate(group_r8.EntityName);
98
252
  i0.ɵɵadvance();
99
- i0.ɵɵtextInterpolate1(" ", (group_r5.HighestScore * 100).toFixed(0), "% ");
100
- i0.ɵɵadvance(4);
101
- i0.ɵɵproperty("title", group_r5.RecordId);
253
+ i0.ɵɵclassMap(ctx_r1.GetScoreClass(group_r8.HighestScore));
102
254
  i0.ɵɵadvance();
103
- i0.ɵɵtextInterpolate(group_r5.RecordId);
104
- i0.ɵɵadvance(4);
105
- i0.ɵɵtextInterpolate2("", group_r5.MatchCount, " match", group_r5.MatchCount !== 1 ? "es" : "");
255
+ i0.ɵɵtextInterpolate1(" ", (group_r8.HighestScore * 100).toFixed(0), "% ");
256
+ i0.ɵɵadvance(2);
257
+ i0.ɵɵconditional(group_r8.TopMatchSummaries.length > 0 ? 13 : -1);
106
258
  i0.ɵɵadvance(4);
107
- i0.ɵɵtextInterpolate(ctx_r2.FormatDate(group_r5.MatchedAt));
259
+ i0.ɵɵtextInterpolate2(" ", group_r8.MatchCount, " match", group_r8.MatchCount !== 1 ? "es" : "", " ");
260
+ i0.ɵɵadvance(3);
261
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.FormatDate(group_r8.MatchedAt), " ");
108
262
  i0.ɵɵadvance(2);
109
- i0.ɵɵproperty("disabled", ctx_r2.IsSaving);
263
+ i0.ɵɵproperty("disabled", ctx_r1.IsSaving);
110
264
  i0.ɵɵadvance(3);
111
- i0.ɵɵproperty("disabled", ctx_r2.IsSaving);
265
+ i0.ɵɵproperty("disabled", ctx_r1.IsSaving);
112
266
  } }
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);
267
+ function DuplicateDetectionResourceComponent_Conditional_66_Conditional_11_Template(rf, ctx) { if (rf & 1) {
268
+ i0.ɵɵelementStart(0, "div", 63);
269
+ i0.ɵɵelement(1, "i", 65);
116
270
  i0.ɵɵelementStart(2, "span");
117
271
  i0.ɵɵtext(3, "No pending items");
118
272
  i0.ɵɵelementEnd()();
119
273
  } }
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);
274
+ function DuplicateDetectionResourceComponent_Conditional_66_For_21_Conditional_13_For_2_Template(rf, ctx) { if (rf & 1) {
275
+ i0.ɵɵelementStart(0, "div", 86)(1, "span", 88);
276
+ i0.ɵɵtext(2);
123
277
  i0.ɵɵelementEnd();
124
- i0.ɵɵelementStart(4, "span", 46);
125
- i0.ɵɵtext(5);
278
+ i0.ɵɵelementStart(3, "span", 89);
279
+ i0.ɵɵtext(4);
126
280
  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);
281
+ } if (rf & 2) {
282
+ const ms_r12 = ctx.$implicit;
283
+ i0.ɵɵadvance(2);
284
+ i0.ɵɵtextInterpolate1("", (ms_r12.Score * 100).toFixed(0), "%");
285
+ i0.ɵɵadvance(2);
286
+ i0.ɵɵtextInterpolate(ms_r12.Name);
287
+ } }
288
+ function DuplicateDetectionResourceComponent_Conditional_66_For_21_Conditional_13_Conditional_3_Template(rf, ctx) { if (rf & 1) {
289
+ i0.ɵɵelementStart(0, "div", 87);
290
+ i0.ɵɵtext(1);
291
+ i0.ɵɵelementEnd();
292
+ } if (rf & 2) {
293
+ const group_r11 = i0.ɵɵnextContext(2).$implicit;
294
+ i0.ɵɵadvance();
295
+ i0.ɵɵtextInterpolate1("+", group_r11.MatchCount - group_r11.TopMatchSummaries.length, " more");
296
+ } }
297
+ function DuplicateDetectionResourceComponent_Conditional_66_For_21_Conditional_13_Template(rf, ctx) { if (rf & 1) {
298
+ i0.ɵɵelementStart(0, "div", 77);
299
+ i0.ɵɵrepeaterCreate(1, DuplicateDetectionResourceComponent_Conditional_66_For_21_Conditional_13_For_2_Template, 5, 2, "div", 86, _forTrack2);
300
+ i0.ɵɵconditionalCreate(3, DuplicateDetectionResourceComponent_Conditional_66_For_21_Conditional_13_Conditional_3_Template, 2, 1, "div", 87);
301
+ i0.ɵɵelementEnd();
302
+ } if (rf & 2) {
303
+ const group_r11 = i0.ɵɵnextContext().$implicit;
304
+ i0.ɵɵadvance();
305
+ i0.ɵɵrepeater(group_r11.TopMatchSummaries);
306
+ i0.ɵɵadvance(2);
307
+ i0.ɵɵconditional(group_r11.MatchCount > group_r11.TopMatchSummaries.length ? 3 : -1);
308
+ } }
309
+ function DuplicateDetectionResourceComponent_Conditional_66_For_21_Template(rf, ctx) { if (rf & 1) {
310
+ const _r10 = i0.ɵɵgetCurrentView();
311
+ i0.ɵɵelementStart(0, "div", 68);
312
+ i0.ɵɵlistener("dragstart", function DuplicateDetectionResourceComponent_Conditional_66_For_21_Template_div_dragstart_0_listener($event) { const group_r11 = i0.ɵɵrestoreView(_r10).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnDragStart($event, group_r11)); })("dragend", function DuplicateDetectionResourceComponent_Conditional_66_For_21_Template_div_dragend_0_listener() { i0.ɵɵrestoreView(_r10); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnDragEnd()); })("click", function DuplicateDetectionResourceComponent_Conditional_66_For_21_Template_div_click_0_listener() { const group_r11 = i0.ɵɵrestoreView(_r10).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OpenComparison(group_r11)); });
313
+ i0.ɵɵelementStart(1, "div", 69)(2, "div", 70)(3, "div", 71);
314
+ i0.ɵɵelement(4, "i");
315
+ i0.ɵɵelementEnd();
316
+ i0.ɵɵelementStart(5, "div", 72)(6, "div", 73);
317
+ i0.ɵɵtext(7);
318
+ i0.ɵɵelementEnd();
319
+ i0.ɵɵelementStart(8, "span", 74);
320
+ i0.ɵɵtext(9);
321
+ i0.ɵɵelementEnd()()();
322
+ i0.ɵɵelementStart(10, "span", 75);
323
+ i0.ɵɵtext(11);
136
324
  i0.ɵɵelementEnd()();
137
- i0.ɵɵelementStart(15, "div", 48);
138
- i0.ɵɵelement(16, "i", 53);
139
- i0.ɵɵelementStart(17, "span", 52);
140
- i0.ɵɵtext(18);
325
+ i0.ɵɵelementStart(12, "div", 76);
326
+ i0.ɵɵconditionalCreate(13, DuplicateDetectionResourceComponent_Conditional_66_For_21_Conditional_13_Template, 4, 1, "div", 77);
327
+ i0.ɵɵelementStart(14, "div", 78)(15, "span", 79);
328
+ i0.ɵɵelement(16, "i", 80);
329
+ i0.ɵɵtext(17);
330
+ i0.ɵɵelementEnd();
331
+ i0.ɵɵelementStart(18, "span", 79);
332
+ i0.ɵɵelement(19, "i", 81);
333
+ i0.ɵɵtext(20);
141
334
  i0.ɵɵelementEnd()()()();
142
335
  } 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);
336
+ const group_r11 = ctx.$implicit;
337
+ const ctx_r1 = i0.ɵɵnextContext(2);
338
+ i0.ɵɵadvance(4);
339
+ i0.ɵɵclassMap(group_r11.EntityIcon);
340
+ i0.ɵɵadvance(2);
341
+ i0.ɵɵproperty("title", group_r11.RecordName);
147
342
  i0.ɵɵadvance();
148
- i0.ɵɵclassMap(ctx_r2.GetScoreClass(group_r6.HighestScore));
343
+ i0.ɵɵtextInterpolate(group_r11.RecordName);
344
+ i0.ɵɵadvance(2);
345
+ i0.ɵɵtextInterpolate(group_r11.EntityName);
149
346
  i0.ɵɵadvance();
150
- i0.ɵɵtextInterpolate1(" ", (group_r6.HighestScore * 100).toFixed(0), "% ");
151
- i0.ɵɵadvance(4);
152
- i0.ɵɵproperty("title", group_r6.RecordId);
347
+ i0.ɵɵclassMap(ctx_r1.GetScoreClass(group_r11.HighestScore));
153
348
  i0.ɵɵadvance();
154
- i0.ɵɵtextInterpolate(group_r6.RecordId);
155
- i0.ɵɵadvance(4);
156
- i0.ɵɵtextInterpolate2("", group_r6.MatchCount, " match", group_r6.MatchCount !== 1 ? "es" : "");
349
+ i0.ɵɵtextInterpolate1(" ", (group_r11.HighestScore * 100).toFixed(0), "% ");
350
+ i0.ɵɵadvance(2);
351
+ i0.ɵɵconditional(group_r11.TopMatchSummaries.length > 0 ? 13 : -1);
157
352
  i0.ɵɵadvance(4);
158
- i0.ɵɵtextInterpolate(ctx_r2.FormatDate(group_r6.MatchedAt));
353
+ i0.ɵɵtextInterpolate2(" ", group_r11.MatchCount, " match", group_r11.MatchCount !== 1 ? "es" : "", " ");
354
+ i0.ɵɵadvance(3);
355
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.FormatDate(group_r11.MatchedAt), " ");
159
356
  } }
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);
357
+ function DuplicateDetectionResourceComponent_Conditional_66_Conditional_22_Template(rf, ctx) { if (rf & 1) {
358
+ i0.ɵɵelementStart(0, "div", 63);
359
+ i0.ɵɵelement(1, "i", 90);
163
360
  i0.ɵɵelementStart(2, "span");
164
361
  i0.ɵɵtext(3, "No approved items");
165
362
  i0.ɵɵelementEnd()();
166
363
  } }
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);
364
+ function DuplicateDetectionResourceComponent_Conditional_66_For_32_Conditional_13_For_2_Template(rf, ctx) { if (rf & 1) {
365
+ i0.ɵɵelementStart(0, "div", 86)(1, "span", 88);
366
+ i0.ɵɵtext(2);
170
367
  i0.ɵɵelementEnd();
171
- i0.ɵɵelementStart(4, "span", 46);
172
- i0.ɵɵtext(5);
368
+ i0.ɵɵelementStart(3, "span", 89);
369
+ i0.ɵɵtext(4);
173
370
  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);
371
+ } if (rf & 2) {
372
+ const ms_r15 = ctx.$implicit;
373
+ i0.ɵɵadvance(2);
374
+ i0.ɵɵtextInterpolate1("", (ms_r15.Score * 100).toFixed(0), "%");
375
+ i0.ɵɵadvance(2);
376
+ i0.ɵɵtextInterpolate(ms_r15.Name);
377
+ } }
378
+ function DuplicateDetectionResourceComponent_Conditional_66_For_32_Conditional_13_Conditional_3_Template(rf, ctx) { if (rf & 1) {
379
+ i0.ɵɵelementStart(0, "div", 87);
380
+ i0.ɵɵtext(1);
381
+ i0.ɵɵelementEnd();
382
+ } if (rf & 2) {
383
+ const group_r14 = i0.ɵɵnextContext(2).$implicit;
384
+ i0.ɵɵadvance();
385
+ i0.ɵɵtextInterpolate1("+", group_r14.MatchCount - group_r14.TopMatchSummaries.length, " more");
386
+ } }
387
+ function DuplicateDetectionResourceComponent_Conditional_66_For_32_Conditional_13_Template(rf, ctx) { if (rf & 1) {
388
+ i0.ɵɵelementStart(0, "div", 77);
389
+ i0.ɵɵrepeaterCreate(1, DuplicateDetectionResourceComponent_Conditional_66_For_32_Conditional_13_For_2_Template, 5, 2, "div", 86, _forTrack2);
390
+ i0.ɵɵconditionalCreate(3, DuplicateDetectionResourceComponent_Conditional_66_For_32_Conditional_13_Conditional_3_Template, 2, 1, "div", 87);
391
+ i0.ɵɵelementEnd();
392
+ } if (rf & 2) {
393
+ const group_r14 = i0.ɵɵnextContext().$implicit;
394
+ i0.ɵɵadvance();
395
+ i0.ɵɵrepeater(group_r14.TopMatchSummaries);
396
+ i0.ɵɵadvance(2);
397
+ i0.ɵɵconditional(group_r14.MatchCount > group_r14.TopMatchSummaries.length ? 3 : -1);
398
+ } }
399
+ function DuplicateDetectionResourceComponent_Conditional_66_For_32_Template(rf, ctx) { if (rf & 1) {
400
+ const _r13 = i0.ɵɵgetCurrentView();
401
+ i0.ɵɵelementStart(0, "div", 68);
402
+ i0.ɵɵlistener("dragstart", function DuplicateDetectionResourceComponent_Conditional_66_For_32_Template_div_dragstart_0_listener($event) { const group_r14 = i0.ɵɵrestoreView(_r13).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnDragStart($event, group_r14)); })("dragend", function DuplicateDetectionResourceComponent_Conditional_66_For_32_Template_div_dragend_0_listener() { i0.ɵɵrestoreView(_r13); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnDragEnd()); })("click", function DuplicateDetectionResourceComponent_Conditional_66_For_32_Template_div_click_0_listener() { const group_r14 = i0.ɵɵrestoreView(_r13).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OpenComparison(group_r14)); });
403
+ i0.ɵɵelementStart(1, "div", 69)(2, "div", 70)(3, "div", 71);
404
+ i0.ɵɵelement(4, "i");
405
+ i0.ɵɵelementEnd();
406
+ i0.ɵɵelementStart(5, "div", 72)(6, "div", 73);
407
+ i0.ɵɵtext(7);
408
+ i0.ɵɵelementEnd();
409
+ i0.ɵɵelementStart(8, "span", 74);
410
+ i0.ɵɵtext(9);
411
+ i0.ɵɵelementEnd()()();
412
+ i0.ɵɵelementStart(10, "span", 75);
413
+ i0.ɵɵtext(11);
183
414
  i0.ɵɵelementEnd()();
184
- i0.ɵɵelementStart(15, "div", 48);
185
- i0.ɵɵelement(16, "i", 53);
186
- i0.ɵɵelementStart(17, "span", 52);
187
- i0.ɵɵtext(18);
415
+ i0.ɵɵelementStart(12, "div", 76);
416
+ i0.ɵɵconditionalCreate(13, DuplicateDetectionResourceComponent_Conditional_66_For_32_Conditional_13_Template, 4, 1, "div", 77);
417
+ i0.ɵɵelementStart(14, "div", 78)(15, "span", 79);
418
+ i0.ɵɵelement(16, "i", 80);
419
+ i0.ɵɵtext(17);
420
+ i0.ɵɵelementEnd();
421
+ i0.ɵɵelementStart(18, "span", 79);
422
+ i0.ɵɵelement(19, "i", 81);
423
+ i0.ɵɵtext(20);
188
424
  i0.ɵɵelementEnd()()()();
189
425
  } 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);
426
+ const group_r14 = ctx.$implicit;
427
+ const ctx_r1 = i0.ɵɵnextContext(2);
428
+ i0.ɵɵadvance(4);
429
+ i0.ɵɵclassMap(group_r14.EntityIcon);
430
+ i0.ɵɵadvance(2);
431
+ i0.ɵɵproperty("title", group_r14.RecordName);
194
432
  i0.ɵɵadvance();
195
- i0.ɵɵclassMap(ctx_r2.GetScoreClass(group_r7.HighestScore));
433
+ i0.ɵɵtextInterpolate(group_r14.RecordName);
434
+ i0.ɵɵadvance(2);
435
+ i0.ɵɵtextInterpolate(group_r14.EntityName);
196
436
  i0.ɵɵadvance();
197
- i0.ɵɵtextInterpolate1(" ", (group_r7.HighestScore * 100).toFixed(0), "% ");
198
- i0.ɵɵadvance(4);
199
- i0.ɵɵproperty("title", group_r7.RecordId);
437
+ i0.ɵɵclassMap(ctx_r1.GetScoreClass(group_r14.HighestScore));
200
438
  i0.ɵɵadvance();
201
- i0.ɵɵtextInterpolate(group_r7.RecordId);
202
- i0.ɵɵadvance(4);
203
- i0.ɵɵtextInterpolate2("", group_r7.MatchCount, " match", group_r7.MatchCount !== 1 ? "es" : "");
439
+ i0.ɵɵtextInterpolate1(" ", (group_r14.HighestScore * 100).toFixed(0), "% ");
440
+ i0.ɵɵadvance(2);
441
+ i0.ɵɵconditional(group_r14.TopMatchSummaries.length > 0 ? 13 : -1);
204
442
  i0.ɵɵadvance(4);
205
- i0.ɵɵtextInterpolate(ctx_r2.FormatDate(group_r7.MatchedAt));
443
+ i0.ɵɵtextInterpolate2(" ", group_r14.MatchCount, " match", group_r14.MatchCount !== 1 ? "es" : "", " ");
444
+ i0.ɵɵadvance(3);
445
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.FormatDate(group_r14.MatchedAt), " ");
206
446
  } }
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);
447
+ function DuplicateDetectionResourceComponent_Conditional_66_Conditional_33_Template(rf, ctx) { if (rf & 1) {
448
+ i0.ɵɵelementStart(0, "div", 63);
449
+ i0.ɵɵelement(1, "i", 90);
210
450
  i0.ɵɵelementStart(2, "span");
211
451
  i0.ɵɵtext(3, "No rejected items");
212
452
  i0.ɵɵelementEnd()();
213
453
  } }
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);
454
+ function DuplicateDetectionResourceComponent_Conditional_66_Template(rf, ctx) { if (rf & 1) {
455
+ const _r6 = i0.ɵɵgetCurrentView();
456
+ i0.ɵɵelementStart(0, "div", 31)(1, "div", 56)(2, "div", 57);
457
+ i0.ɵɵelement(3, "i", 58);
458
+ i0.ɵɵelementStart(4, "span", 59);
218
459
  i0.ɵɵtext(5, "Pending Review");
219
460
  i0.ɵɵelementEnd();
220
- i0.ɵɵelementStart(6, "span", 36);
461
+ i0.ɵɵelementStart(6, "span", 60);
221
462
  i0.ɵɵtext(7);
222
463
  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);
464
+ i0.ɵɵelementStart(8, "div", 61);
465
+ i0.ɵɵlistener("dragover", function DuplicateDetectionResourceComponent_Conditional_66_Template_div_dragover_8_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnDragOver($event, "Pending")); })("dragleave", function DuplicateDetectionResourceComponent_Conditional_66_Template_div_dragleave_8_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnDragLeave($event, "Pending")); })("drop", function DuplicateDetectionResourceComponent_Conditional_66_Template_div_drop_8_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnDrop($event, "Pending")); });
466
+ i0.ɵɵrepeaterCreate(9, DuplicateDetectionResourceComponent_Conditional_66_For_10_Template, 28, 14, "div", 62, _forTrack1);
467
+ i0.ɵɵconditionalCreate(11, DuplicateDetectionResourceComponent_Conditional_66_Conditional_11_Template, 4, 0, "div", 63);
226
468
  i0.ɵɵelementEnd()();
227
- i0.ɵɵelementStart(12, "div", 32)(13, "div", 40);
228
- i0.ɵɵelement(14, "i", 41);
229
- i0.ɵɵelementStart(15, "span", 35);
469
+ i0.ɵɵelementStart(12, "div", 56)(13, "div", 64);
470
+ i0.ɵɵelement(14, "i", 65);
471
+ i0.ɵɵelementStart(15, "span", 59);
230
472
  i0.ɵɵtext(16, "Approved");
231
473
  i0.ɵɵelementEnd();
232
- i0.ɵɵelementStart(17, "span", 36);
474
+ i0.ɵɵelementStart(17, "span", 60);
233
475
  i0.ɵɵtext(18);
234
476
  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);
477
+ i0.ɵɵelementStart(19, "div", 61);
478
+ i0.ɵɵlistener("dragover", function DuplicateDetectionResourceComponent_Conditional_66_Template_div_dragover_19_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnDragOver($event, "Approved")); })("dragleave", function DuplicateDetectionResourceComponent_Conditional_66_Template_div_dragleave_19_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnDragLeave($event, "Approved")); })("drop", function DuplicateDetectionResourceComponent_Conditional_66_Template_div_drop_19_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnDrop($event, "Approved")); });
479
+ i0.ɵɵrepeaterCreate(20, DuplicateDetectionResourceComponent_Conditional_66_For_21_Template, 21, 12, "div", 62, _forTrack1);
480
+ i0.ɵɵconditionalCreate(22, DuplicateDetectionResourceComponent_Conditional_66_Conditional_22_Template, 4, 0, "div", 63);
238
481
  i0.ɵɵelementEnd()();
239
- i0.ɵɵelementStart(23, "div", 32)(24, "div", 42);
240
- i0.ɵɵelement(25, "i", 43);
241
- i0.ɵɵelementStart(26, "span", 35);
482
+ i0.ɵɵelementStart(23, "div", 56)(24, "div", 66);
483
+ i0.ɵɵelement(25, "i", 67);
484
+ i0.ɵɵelementStart(26, "span", 59);
242
485
  i0.ɵɵtext(27, "Rejected");
243
486
  i0.ɵɵelementEnd();
244
- i0.ɵɵelementStart(28, "span", 36);
487
+ i0.ɵɵelementStart(28, "span", 60);
245
488
  i0.ɵɵtext(29);
246
489
  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);
490
+ i0.ɵɵelementStart(30, "div", 61);
491
+ i0.ɵɵlistener("dragover", function DuplicateDetectionResourceComponent_Conditional_66_Template_div_dragover_30_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnDragOver($event, "Rejected")); })("dragleave", function DuplicateDetectionResourceComponent_Conditional_66_Template_div_dragleave_30_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnDragLeave($event, "Rejected")); })("drop", function DuplicateDetectionResourceComponent_Conditional_66_Template_div_drop_30_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnDrop($event, "Rejected")); });
492
+ i0.ɵɵrepeaterCreate(31, DuplicateDetectionResourceComponent_Conditional_66_For_32_Template, 21, 12, "div", 62, _forTrack1);
493
+ i0.ɵɵconditionalCreate(33, DuplicateDetectionResourceComponent_Conditional_66_Conditional_33_Template, 4, 0, "div", 63);
250
494
  i0.ɵɵelementEnd()()();
251
495
  } if (rf & 2) {
252
- const ctx_r2 = i0.ɵɵnextContext();
253
- i0.ɵɵadvance(7);
254
- i0.ɵɵtextInterpolate(ctx_r2.PendingCount);
496
+ const ctx_r1 = i0.ɵɵnextContext();
497
+ i0.ɵɵadvance();
498
+ i0.ɵɵclassProp("drop-target-active", ctx_r1.DragOverColumn === "Pending" && (ctx_r1.DraggedGroup == null ? null : ctx_r1.DraggedGroup.ApprovalStatus) !== "Pending");
499
+ i0.ɵɵadvance(6);
500
+ i0.ɵɵtextInterpolate(ctx_r1.PendingCount);
255
501
  i0.ɵɵadvance(2);
256
- i0.ɵɵrepeater(ctx_r2.PendingGroups);
502
+ i0.ɵɵrepeater(ctx_r1.PendingGroups);
257
503
  i0.ɵɵadvance(2);
258
- i0.ɵɵconditional(ctx_r2.PendingGroups.length === 0 ? 11 : -1);
259
- i0.ɵɵadvance(7);
260
- i0.ɵɵtextInterpolate(ctx_r2.ApprovedCount);
504
+ i0.ɵɵconditional(ctx_r1.PendingGroups.length === 0 ? 11 : -1);
505
+ i0.ɵɵadvance();
506
+ i0.ɵɵclassProp("drop-target-active", ctx_r1.DragOverColumn === "Approved" && (ctx_r1.DraggedGroup == null ? null : ctx_r1.DraggedGroup.ApprovalStatus) !== "Approved");
507
+ i0.ɵɵadvance(6);
508
+ i0.ɵɵtextInterpolate(ctx_r1.ApprovedCount);
509
+ i0.ɵɵadvance(2);
510
+ i0.ɵɵrepeater(ctx_r1.ApprovedGroups);
511
+ i0.ɵɵadvance(2);
512
+ i0.ɵɵconditional(ctx_r1.ApprovedGroups.length === 0 ? 22 : -1);
513
+ i0.ɵɵadvance();
514
+ i0.ɵɵclassProp("drop-target-active", ctx_r1.DragOverColumn === "Rejected" && (ctx_r1.DraggedGroup == null ? null : ctx_r1.DraggedGroup.ApprovalStatus) !== "Rejected");
515
+ i0.ɵɵadvance(6);
516
+ i0.ɵɵtextInterpolate(ctx_r1.RejectedCount);
517
+ i0.ɵɵadvance(2);
518
+ i0.ɵɵrepeater(ctx_r1.RejectedGroups);
519
+ i0.ɵɵadvance(2);
520
+ i0.ɵɵconditional(ctx_r1.RejectedGroups.length === 0 ? 33 : -1);
521
+ } }
522
+ function DuplicateDetectionResourceComponent_Conditional_67_Template(rf, ctx) { if (rf & 1) {
523
+ i0.ɵɵelementStart(0, "div", 32);
524
+ i0.ɵɵelement(1, "mj-loading", 91);
525
+ i0.ɵɵelementEnd();
526
+ } }
527
+ function DuplicateDetectionResourceComponent_Conditional_68_Conditional_21_Template(rf, ctx) { if (rf & 1) {
528
+ i0.ɵɵelementStart(0, "div", 104);
529
+ i0.ɵɵelement(1, "mj-loading", 130);
530
+ i0.ɵɵelementEnd();
531
+ } }
532
+ function DuplicateDetectionResourceComponent_Conditional_68_Conditional_44_Template(rf, ctx) { if (rf & 1) {
533
+ i0.ɵɵelementStart(0, "span", 120);
534
+ i0.ɵɵelement(1, "i", 131);
535
+ i0.ɵɵtext(2, " Most deps");
536
+ i0.ɵɵelementEnd();
537
+ } }
538
+ function DuplicateDetectionResourceComponent_Conditional_68_Conditional_45_Conditional_4_For_2_Conditional_7_Conditional_1_Template(rf, ctx) { if (rf & 1) {
539
+ i0.ɵɵelementStart(0, "div", 140);
540
+ i0.ɵɵelement(1, "i", 34);
541
+ i0.ɵɵtext(2, " Loading...");
542
+ i0.ɵɵelementEnd();
543
+ } }
544
+ function DuplicateDetectionResourceComponent_Conditional_68_Conditional_45_Conditional_4_For_2_Conditional_7_For_3_Template(rf, ctx) { if (rf & 1) {
545
+ const _r20 = i0.ɵɵgetCurrentView();
546
+ i0.ɵɵelementStart(0, "div", 142);
547
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_Conditional_45_Conditional_4_For_2_Conditional_7_For_3_Template_div_click_0_listener($event) { const record_r21 = i0.ɵɵrestoreView(_r20).$implicit; const ctx_r1 = i0.ɵɵnextContext(6); ctx_r1.OpenDepRecord(record_r21); return i0.ɵɵresetView($event.stopPropagation()); });
548
+ i0.ɵɵelement(1, "i", 143);
549
+ i0.ɵɵelementStart(2, "span", 144);
550
+ i0.ɵɵtext(3);
551
+ i0.ɵɵelementEnd()();
552
+ } if (rf & 2) {
553
+ const record_r21 = ctx.$implicit;
554
+ i0.ɵɵadvance(3);
555
+ i0.ɵɵtextInterpolate(record_r21.Name);
556
+ } }
557
+ function DuplicateDetectionResourceComponent_Conditional_68_Conditional_45_Conditional_4_For_2_Conditional_7_Template(rf, ctx) { if (rf & 1) {
558
+ i0.ɵɵelementStart(0, "div", 139);
559
+ i0.ɵɵconditionalCreate(1, DuplicateDetectionResourceComponent_Conditional_68_Conditional_45_Conditional_4_For_2_Conditional_7_Conditional_1_Template, 3, 0, "div", 140);
560
+ i0.ɵɵrepeaterCreate(2, DuplicateDetectionResourceComponent_Conditional_68_Conditional_45_Conditional_4_For_2_Conditional_7_For_3_Template, 4, 1, "div", 141, i0.ɵɵrepeaterTrackByIndex);
561
+ i0.ɵɵelementEnd();
562
+ } if (rf & 2) {
563
+ const dep_r19 = i0.ɵɵnextContext().$implicit;
564
+ const ctx_r1 = i0.ɵɵnextContext(4);
565
+ i0.ɵɵadvance();
566
+ i0.ɵɵconditional(ctx_r1.IsDepRecordsLoading(0, dep_r19.Entity) ? 1 : -1);
567
+ i0.ɵɵadvance();
568
+ i0.ɵɵrepeater(ctx_r1.GetDepRecords(0, dep_r19.Entity));
569
+ } }
570
+ function DuplicateDetectionResourceComponent_Conditional_68_Conditional_45_Conditional_4_For_2_Template(rf, ctx) { if (rf & 1) {
571
+ const _r18 = i0.ɵɵgetCurrentView();
572
+ i0.ɵɵelementStart(0, "div", 134)(1, "div", 135);
573
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_Conditional_45_Conditional_4_For_2_Template_div_click_1_listener($event) { const dep_r19 = i0.ɵɵrestoreView(_r18).$implicit; const ctx_r1 = i0.ɵɵnextContext(4); ctx_r1.ToggleDepEntityGroup(0, dep_r19.Entity); return i0.ɵɵresetView($event.stopPropagation()); });
574
+ i0.ɵɵelementStart(2, "span", 136);
575
+ i0.ɵɵelement(3, "i", 137);
576
+ i0.ɵɵtext(4);
577
+ i0.ɵɵelementEnd();
578
+ i0.ɵɵelementStart(5, "span", 138);
579
+ i0.ɵɵtext(6);
580
+ i0.ɵɵelementEnd()();
581
+ i0.ɵɵconditionalCreate(7, DuplicateDetectionResourceComponent_Conditional_68_Conditional_45_Conditional_4_For_2_Conditional_7_Template, 4, 1, "div", 139);
582
+ i0.ɵɵelementEnd();
583
+ } if (rf & 2) {
584
+ const dep_r19 = ctx.$implicit;
585
+ const ctx_r1 = i0.ɵɵnextContext(4);
586
+ i0.ɵɵadvance(3);
587
+ i0.ɵɵclassProp("fa-chevron-right", !ctx_r1.IsDepEntityGroupExpanded(0, dep_r19.Entity))("fa-chevron-down", ctx_r1.IsDepEntityGroupExpanded(0, dep_r19.Entity));
588
+ i0.ɵɵadvance();
589
+ i0.ɵɵtextInterpolate1(" ", dep_r19.Entity, " ");
590
+ i0.ɵɵadvance(2);
591
+ i0.ɵɵtextInterpolate(dep_r19.Count);
592
+ i0.ɵɵadvance();
593
+ i0.ɵɵconditional(ctx_r1.IsDepEntityGroupExpanded(0, dep_r19.Entity) ? 7 : -1);
594
+ } }
595
+ function DuplicateDetectionResourceComponent_Conditional_68_Conditional_45_Conditional_4_Template(rf, ctx) { if (rf & 1) {
596
+ i0.ɵɵelementStart(0, "div", 133);
597
+ i0.ɵɵrepeaterCreate(1, DuplicateDetectionResourceComponent_Conditional_68_Conditional_45_Conditional_4_For_2_Template, 8, 7, "div", 134, _forTrack5);
598
+ i0.ɵɵelementEnd();
599
+ } if (rf & 2) {
600
+ const ctx_r1 = i0.ɵɵnextContext(3);
601
+ i0.ɵɵadvance();
602
+ i0.ɵɵrepeater(ctx_r1.GetGroupedDeps(0));
603
+ } }
604
+ function DuplicateDetectionResourceComponent_Conditional_68_Conditional_45_Template(rf, ctx) { if (rf & 1) {
605
+ const _r17 = i0.ɵɵgetCurrentView();
606
+ i0.ɵɵelementStart(0, "div", 132);
607
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_Conditional_45_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.ToggleDepsExpanded(0)); });
608
+ i0.ɵɵelementStart(1, "span");
609
+ i0.ɵɵtext(2);
610
+ i0.ɵɵelementEnd();
611
+ i0.ɵɵelement(3, "i", 115);
612
+ i0.ɵɵelementEnd();
613
+ i0.ɵɵconditionalCreate(4, DuplicateDetectionResourceComponent_Conditional_68_Conditional_45_Conditional_4_Template, 3, 0, "div", 133);
614
+ } if (rf & 2) {
615
+ const ctx_r1 = i0.ɵɵnextContext(2);
616
+ i0.ɵɵadvance(2);
617
+ i0.ɵɵtextInterpolate(ctx_r1.IsDepsExpanded(0) ? "Hide details" : "Show details");
618
+ i0.ɵɵadvance();
619
+ i0.ɵɵclassProp("fa-chevron-down", !ctx_r1.IsDepsExpanded(0))("fa-chevron-up", ctx_r1.IsDepsExpanded(0));
620
+ i0.ɵɵadvance();
621
+ i0.ɵɵconditional(ctx_r1.IsDepsExpanded(0) ? 4 : -1);
622
+ } }
623
+ function DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_21_Template(rf, ctx) { if (rf & 1) {
624
+ i0.ɵɵelementStart(0, "span", 120);
625
+ i0.ɵɵelement(1, "i", 131);
626
+ i0.ɵɵtext(2, " Most deps");
627
+ i0.ɵɵelementEnd();
628
+ } }
629
+ function DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_22_Conditional_4_For_2_Conditional_7_Conditional_1_Template(rf, ctx) { if (rf & 1) {
630
+ i0.ɵɵelementStart(0, "div", 140);
631
+ i0.ɵɵelement(1, "i", 34);
632
+ i0.ɵɵtext(2, " Loading...");
633
+ i0.ɵɵelementEnd();
634
+ } }
635
+ function DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_22_Conditional_4_For_2_Conditional_7_For_3_Template(rf, ctx) { if (rf & 1) {
636
+ const _r27 = i0.ɵɵgetCurrentView();
637
+ i0.ɵɵelementStart(0, "div", 142);
638
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_22_Conditional_4_For_2_Conditional_7_For_3_Template_div_click_0_listener($event) { const record_r28 = i0.ɵɵrestoreView(_r27).$implicit; const ctx_r1 = i0.ɵɵnextContext(7); ctx_r1.OpenDepRecord(record_r28); return i0.ɵɵresetView($event.stopPropagation()); });
639
+ i0.ɵɵelement(1, "i", 143);
640
+ i0.ɵɵelementStart(2, "span", 144);
641
+ i0.ɵɵtext(3);
642
+ i0.ɵɵelementEnd()();
643
+ } if (rf & 2) {
644
+ const record_r28 = ctx.$implicit;
645
+ i0.ɵɵadvance(3);
646
+ i0.ɵɵtextInterpolate(record_r28.Name);
647
+ } }
648
+ function DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_22_Conditional_4_For_2_Conditional_7_Template(rf, ctx) { if (rf & 1) {
649
+ i0.ɵɵelementStart(0, "div", 139);
650
+ i0.ɵɵconditionalCreate(1, DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_22_Conditional_4_For_2_Conditional_7_Conditional_1_Template, 3, 0, "div", 140);
651
+ i0.ɵɵrepeaterCreate(2, DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_22_Conditional_4_For_2_Conditional_7_For_3_Template, 4, 1, "div", 141, i0.ɵɵrepeaterTrackByIndex);
652
+ i0.ɵɵelementEnd();
653
+ } if (rf & 2) {
654
+ const dep_r26 = i0.ɵɵnextContext().$implicit;
655
+ const ɵ$index_567_r23 = i0.ɵɵnextContext(3).$index;
656
+ const ctx_r1 = i0.ɵɵnextContext(2);
657
+ i0.ɵɵadvance();
658
+ i0.ɵɵconditional(ctx_r1.IsDepRecordsLoading(ɵ$index_567_r23 + 1, dep_r26.Entity) ? 1 : -1);
659
+ i0.ɵɵadvance();
660
+ i0.ɵɵrepeater(ctx_r1.GetDepRecords(ɵ$index_567_r23 + 1, dep_r26.Entity));
661
+ } }
662
+ function DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_22_Conditional_4_For_2_Template(rf, ctx) { if (rf & 1) {
663
+ const _r25 = i0.ɵɵgetCurrentView();
664
+ i0.ɵɵelementStart(0, "div", 134)(1, "div", 135);
665
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_22_Conditional_4_For_2_Template_div_click_1_listener($event) { const dep_r26 = i0.ɵɵrestoreView(_r25).$implicit; const ɵ$index_567_r23 = i0.ɵɵnextContext(3).$index; const ctx_r1 = i0.ɵɵnextContext(2); ctx_r1.ToggleDepEntityGroup(ɵ$index_567_r23 + 1, dep_r26.Entity); return i0.ɵɵresetView($event.stopPropagation()); });
666
+ i0.ɵɵelementStart(2, "span", 136);
667
+ i0.ɵɵelement(3, "i", 137);
668
+ i0.ɵɵtext(4);
669
+ i0.ɵɵelementEnd();
670
+ i0.ɵɵelementStart(5, "span", 138);
671
+ i0.ɵɵtext(6);
672
+ i0.ɵɵelementEnd()();
673
+ i0.ɵɵconditionalCreate(7, DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_22_Conditional_4_For_2_Conditional_7_Template, 4, 1, "div", 139);
674
+ i0.ɵɵelementEnd();
675
+ } if (rf & 2) {
676
+ const dep_r26 = ctx.$implicit;
677
+ const ɵ$index_567_r23 = i0.ɵɵnextContext(3).$index;
678
+ const ctx_r1 = i0.ɵɵnextContext(2);
679
+ i0.ɵɵadvance(3);
680
+ i0.ɵɵclassProp("fa-chevron-right", !ctx_r1.IsDepEntityGroupExpanded(ɵ$index_567_r23 + 1, dep_r26.Entity))("fa-chevron-down", ctx_r1.IsDepEntityGroupExpanded(ɵ$index_567_r23 + 1, dep_r26.Entity));
681
+ i0.ɵɵadvance();
682
+ i0.ɵɵtextInterpolate1(" ", dep_r26.Entity, " ");
683
+ i0.ɵɵadvance(2);
684
+ i0.ɵɵtextInterpolate(dep_r26.Count);
685
+ i0.ɵɵadvance();
686
+ i0.ɵɵconditional(ctx_r1.IsDepEntityGroupExpanded(ɵ$index_567_r23 + 1, dep_r26.Entity) ? 7 : -1);
687
+ } }
688
+ function DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_22_Conditional_4_Template(rf, ctx) { if (rf & 1) {
689
+ i0.ɵɵelementStart(0, "div", 133);
690
+ i0.ɵɵrepeaterCreate(1, DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_22_Conditional_4_For_2_Template, 8, 7, "div", 134, _forTrack5);
691
+ i0.ɵɵelementEnd();
692
+ } if (rf & 2) {
693
+ const ɵ$index_567_r23 = i0.ɵɵnextContext(2).$index;
694
+ const ctx_r1 = i0.ɵɵnextContext(2);
695
+ i0.ɵɵadvance();
696
+ i0.ɵɵrepeater(ctx_r1.GetGroupedDeps(ɵ$index_567_r23 + 1));
697
+ } }
698
+ function DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_22_Template(rf, ctx) { if (rf & 1) {
699
+ const _r24 = i0.ɵɵgetCurrentView();
700
+ i0.ɵɵelementStart(0, "div", 132);
701
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_22_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r24); const ɵ$index_567_r23 = i0.ɵɵnextContext().$index; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.ToggleDepsExpanded(ɵ$index_567_r23 + 1)); });
702
+ i0.ɵɵelementStart(1, "span");
703
+ i0.ɵɵtext(2);
704
+ i0.ɵɵelementEnd();
705
+ i0.ɵɵelement(3, "i", 115);
706
+ i0.ɵɵelementEnd();
707
+ i0.ɵɵconditionalCreate(4, DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_22_Conditional_4_Template, 3, 0, "div", 133);
708
+ } if (rf & 2) {
709
+ const ɵ$index_567_r23 = i0.ɵɵnextContext().$index;
710
+ const ctx_r1 = i0.ɵɵnextContext(2);
711
+ i0.ɵɵadvance(2);
712
+ i0.ɵɵtextInterpolate(ctx_r1.IsDepsExpanded(ɵ$index_567_r23 + 1) ? "Hide details" : "Show details");
713
+ i0.ɵɵadvance();
714
+ i0.ɵɵclassProp("fa-chevron-down", !ctx_r1.IsDepsExpanded(ɵ$index_567_r23 + 1))("fa-chevron-up", ctx_r1.IsDepsExpanded(ɵ$index_567_r23 + 1));
715
+ i0.ɵɵadvance();
716
+ i0.ɵɵconditional(ctx_r1.IsDepsExpanded(ɵ$index_567_r23 + 1) ? 4 : -1);
717
+ } }
718
+ function DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_24_Template(rf, ctx) { if (rf & 1) {
719
+ const _r29 = i0.ɵɵgetCurrentView();
720
+ i0.ɵɵelementStart(0, "button", 150);
721
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_24_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r29); const match_r30 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.RejectIndividualMatch(match_r30)); });
722
+ i0.ɵɵelement(1, "i", 67);
723
+ i0.ɵɵtext(2, " Skip ");
724
+ i0.ɵɵelementEnd();
725
+ } }
726
+ function DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_25_Conditional_3_Template(rf, ctx) { if (rf & 1) {
727
+ const _r31 = i0.ɵɵgetCurrentView();
728
+ i0.ɵɵelementStart(0, "button", 153);
729
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_25_Conditional_3_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r31); const match_r30 = i0.ɵɵnextContext(2).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.UndoRejectIndividualMatch(match_r30)); });
730
+ i0.ɵɵelement(1, "i", 154);
731
+ i0.ɵɵtext(2, " Undo ");
732
+ i0.ɵɵelementEnd();
733
+ } }
734
+ function DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_25_Template(rf, ctx) { if (rf & 1) {
735
+ i0.ɵɵelementStart(0, "span", 151);
736
+ i0.ɵɵelement(1, "i", 115);
737
+ i0.ɵɵtext(2);
738
+ i0.ɵɵelementEnd();
739
+ i0.ɵɵconditionalCreate(3, DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_25_Conditional_3_Template, 3, 0, "button", 152);
740
+ } if (rf & 2) {
741
+ const match_r30 = i0.ɵɵnextContext().$implicit;
742
+ i0.ɵɵclassProp("status-approved", match_r30.Match.ApprovalStatus === "Approved")("status-rejected", match_r30.Match.ApprovalStatus === "Rejected");
743
+ i0.ɵɵadvance();
744
+ i0.ɵɵclassProp("fa-check", match_r30.Match.ApprovalStatus === "Approved")("fa-times", match_r30.Match.ApprovalStatus === "Rejected");
745
+ i0.ɵɵadvance();
746
+ i0.ɵɵtextInterpolate1(" ", match_r30.Match.ApprovalStatus === "Rejected" ? "Skipped" : match_r30.Match.ApprovalStatus, " ");
747
+ i0.ɵɵadvance();
748
+ i0.ɵɵconditional(match_r30.Match.ApprovalStatus === "Rejected" ? 3 : -1);
749
+ } }
750
+ function DuplicateDetectionResourceComponent_Conditional_68_For_47_Template(rf, ctx) { if (rf & 1) {
751
+ const _r22 = i0.ɵɵgetCurrentView();
752
+ i0.ɵɵelementStart(0, "div", 145)(1, "div", 146)(2, "span", 110);
753
+ i0.ɵɵtext(3);
754
+ i0.ɵɵelementEnd();
755
+ i0.ɵɵelementStart(4, "span", 75);
756
+ i0.ɵɵtext(5);
757
+ i0.ɵɵelementEnd()();
758
+ i0.ɵɵelementStart(6, "span", 147);
759
+ i0.ɵɵtext(7);
760
+ i0.ɵɵelementEnd();
761
+ i0.ɵɵelementStart(8, "div", 111)(9, "input", 112);
762
+ i0.ɵɵlistener("change", function DuplicateDetectionResourceComponent_Conditional_68_For_47_Template_input_change_9_listener() { const ɵ$index_567_r23 = i0.ɵɵrestoreView(_r22).$index; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.SetSurvivor(ɵ$index_567_r23 + 1)); });
763
+ i0.ɵɵelementEnd();
764
+ i0.ɵɵelementStart(10, "span", 113);
765
+ i0.ɵɵtext(11);
766
+ i0.ɵɵelementEnd()();
767
+ i0.ɵɵelementStart(12, "button", 114);
768
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_For_47_Template_button_click_12_listener() { const ɵ$index_567_r23 = i0.ɵɵrestoreView(_r22).$index; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.UseAllFieldsFrom(ɵ$index_567_r23 + 1)); });
769
+ i0.ɵɵelement(13, "i", 115);
770
+ i0.ɵɵtext(14);
771
+ i0.ɵɵelementEnd();
772
+ i0.ɵɵelementStart(15, "div", 116)(16, "div", 117);
773
+ i0.ɵɵelement(17, "i", 118);
774
+ i0.ɵɵelementStart(18, "span", 119);
775
+ i0.ɵɵtext(19);
776
+ i0.ɵɵelementEnd();
777
+ i0.ɵɵtext(20);
778
+ i0.ɵɵconditionalCreate(21, DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_21_Template, 3, 0, "span", 120);
779
+ i0.ɵɵelementEnd();
780
+ i0.ɵɵconditionalCreate(22, DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_22_Template, 5, 6);
781
+ i0.ɵɵelementEnd();
782
+ i0.ɵɵelementStart(23, "div", 148);
783
+ i0.ɵɵconditionalCreate(24, DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_24_Template, 3, 0, "button", 149)(25, DuplicateDetectionResourceComponent_Conditional_68_For_47_Conditional_25_Template, 4, 10);
784
+ i0.ɵɵelementEnd()();
785
+ } if (rf & 2) {
786
+ const match_r30 = ctx.$implicit;
787
+ const ɵ$index_567_r23 = ctx.$index;
788
+ const ctx_r1 = i0.ɵɵnextContext(2);
789
+ i0.ɵɵclassProp("match-approved", match_r30.Match.ApprovalStatus === "Approved")("match-rejected", match_r30.Match.ApprovalStatus === "Rejected");
790
+ i0.ɵɵadvance(3);
791
+ i0.ɵɵtextInterpolate(match_r30.Name);
792
+ i0.ɵɵadvance();
793
+ i0.ɵɵclassMap(ctx_r1.GetScoreClass(match_r30.Score));
794
+ i0.ɵɵadvance();
795
+ i0.ɵɵtextInterpolate1(" ", (match_r30.Score * 100).toFixed(0), "% ");
796
+ i0.ɵɵadvance(2);
797
+ i0.ɵɵtextInterpolate2("", match_r30.DiffCount, " difference", match_r30.DiffCount !== 1 ? "s" : "");
798
+ i0.ɵɵadvance(2);
799
+ i0.ɵɵproperty("checked", ctx_r1.SurvivorColumnIndex === ɵ$index_567_r23 + 1);
800
+ i0.ɵɵadvance();
801
+ i0.ɵɵclassProp("is-survivor", ctx_r1.SurvivorColumnIndex === ɵ$index_567_r23 + 1);
802
+ i0.ɵɵadvance();
803
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.SurvivorColumnIndex === ɵ$index_567_r23 + 1 ? "Surviving Record" : "Set as survivor", " ");
804
+ i0.ɵɵadvance();
805
+ i0.ɵɵclassProp("all-selected", ctx_r1.AllFieldsSelectedFrom(ɵ$index_567_r23 + 1));
806
+ i0.ɵɵadvance();
807
+ i0.ɵɵclassProp("fa-check-double", ctx_r1.AllFieldsSelectedFrom(ɵ$index_567_r23 + 1))("fa-clone", !ctx_r1.AllFieldsSelectedFrom(ɵ$index_567_r23 + 1));
808
+ i0.ɵɵadvance();
809
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.AllFieldsSelectedFrom(ɵ$index_567_r23 + 1) ? "Using all fields" : "Use all fields", " ");
810
+ i0.ɵɵadvance(5);
811
+ i0.ɵɵtextInterpolate(ctx_r1.GetTotalDeps(ɵ$index_567_r23 + 1));
812
+ i0.ɵɵadvance();
813
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.GetTotalDeps(ɵ$index_567_r23 + 1) === 1 ? "dependency" : "dependencies", " ");
814
+ i0.ɵɵadvance();
815
+ i0.ɵɵconditional(ctx_r1.GetMaxDepsColumnIndex() === ɵ$index_567_r23 + 1 && ctx_r1.GetTotalDeps(ɵ$index_567_r23 + 1) > 0 ? 21 : -1);
816
+ i0.ɵɵadvance();
817
+ i0.ɵɵconditional(ctx_r1.GetGroupedDeps(ɵ$index_567_r23 + 1).length > 0 ? 22 : -1);
261
818
  i0.ɵɵadvance(2);
262
- i0.ɵɵrepeater(ctx_r2.ApprovedGroups);
819
+ i0.ɵɵconditional(match_r30.Match.ApprovalStatus === "Pending" ? 24 : 25);
820
+ } }
821
+ function DuplicateDetectionResourceComponent_Conditional_68_For_49_Conditional_3_Template(rf, ctx) { if (rf & 1) {
822
+ i0.ɵɵtext(0);
823
+ } if (rf & 2) {
824
+ const field_r33 = i0.ɵɵnextContext().$implicit;
825
+ i0.ɵɵtextInterpolate1(" ", field_r33.SourceValue, " ");
826
+ } }
827
+ function DuplicateDetectionResourceComponent_Conditional_68_For_49_Conditional_4_Template(rf, ctx) { if (rf & 1) {
828
+ i0.ɵɵelementStart(0, "span", 157);
829
+ i0.ɵɵtext(1, "(not available)");
830
+ i0.ɵɵelementEnd();
831
+ } }
832
+ function DuplicateDetectionResourceComponent_Conditional_68_For_49_Conditional_5_Template(rf, ctx) { if (rf & 1) {
833
+ const _r34 = i0.ɵɵgetCurrentView();
834
+ i0.ɵɵelementStart(0, "input", 160);
835
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_For_49_Conditional_5_Template_input_click_0_listener($event) { i0.ɵɵrestoreView(_r34); return i0.ɵɵresetView($event.stopPropagation()); })("change", function DuplicateDetectionResourceComponent_Conditional_68_For_49_Conditional_5_Template_input_change_0_listener() { i0.ɵɵrestoreView(_r34); const field_r33 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.SelectFieldValue(field_r33, 0)); });
836
+ i0.ɵɵelementEnd();
837
+ } if (rf & 2) {
838
+ const field_r33 = i0.ɵɵnextContext().$implicit;
839
+ i0.ɵɵproperty("name", "field-" + field_r33.FieldName)("checked", field_r33.SelectedColumnIndex === 0);
840
+ } }
841
+ function DuplicateDetectionResourceComponent_Conditional_68_For_49_For_7_Conditional_1_Conditional_1_Template(rf, ctx) { if (rf & 1) {
842
+ const _r39 = i0.ɵɵgetCurrentView();
843
+ i0.ɵɵelementStart(0, "input", 160);
844
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_For_49_For_7_Conditional_1_Conditional_1_Template_input_click_0_listener($event) { i0.ɵɵrestoreView(_r39); return i0.ɵɵresetView($event.stopPropagation()); })("change", function DuplicateDetectionResourceComponent_Conditional_68_For_49_For_7_Conditional_1_Conditional_1_Template_input_change_0_listener() { i0.ɵɵrestoreView(_r39); const ɵ$index_685_r38 = i0.ɵɵnextContext(2).$index; const field_r33 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.SelectFieldValue(field_r33, ɵ$index_685_r38 + 1)); });
845
+ i0.ɵɵelementEnd();
846
+ } if (rf & 2) {
847
+ const ɵ$index_685_r38 = i0.ɵɵnextContext(2).$index;
848
+ const field_r33 = i0.ɵɵnextContext().$implicit;
849
+ i0.ɵɵproperty("name", "field-" + field_r33.FieldName)("checked", field_r33.SelectedColumnIndex === ɵ$index_685_r38 + 1);
850
+ } }
851
+ function DuplicateDetectionResourceComponent_Conditional_68_For_49_For_7_Conditional_1_Template(rf, ctx) { if (rf & 1) {
852
+ i0.ɵɵtext(0);
853
+ i0.ɵɵconditionalCreate(1, DuplicateDetectionResourceComponent_Conditional_68_For_49_For_7_Conditional_1_Conditional_1_Template, 1, 2, "input", 158);
854
+ } if (rf & 2) {
855
+ const ctx_r39 = i0.ɵɵnextContext();
856
+ const matchVal_r37 = ctx_r39.$implicit;
857
+ const ɵ$index_685_r38 = ctx_r39.$index;
858
+ const field_r33 = i0.ɵɵnextContext().$implicit;
859
+ i0.ɵɵtextInterpolate1(" ", matchVal_r37, " ");
860
+ i0.ɵɵadvance();
861
+ i0.ɵɵconditional(field_r33.HasDifference || field_r33.SelectedColumnIndex === ɵ$index_685_r38 + 1 ? 1 : -1);
862
+ } }
863
+ function DuplicateDetectionResourceComponent_Conditional_68_For_49_For_7_Conditional_2_Template(rf, ctx) { if (rf & 1) {
864
+ i0.ɵɵelementStart(0, "span", 157);
865
+ i0.ɵɵtext(1, "(not available)");
866
+ i0.ɵɵelementEnd();
867
+ } }
868
+ function DuplicateDetectionResourceComponent_Conditional_68_For_49_For_7_Template(rf, ctx) { if (rf & 1) {
869
+ const _r35 = i0.ɵɵgetCurrentView();
870
+ i0.ɵɵelementStart(0, "div", 161);
871
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_For_49_For_7_Template_div_click_0_listener() { const ctx_r35 = i0.ɵɵrestoreView(_r35); const matchVal_r37 = ctx_r35.$implicit; const ɵ$index_685_r38 = ctx_r35.$index; const field_r33 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(matchVal_r37 != null ? ctx_r1.SelectFieldValue(field_r33, ɵ$index_685_r38 + 1) : null); });
872
+ i0.ɵɵconditionalCreate(1, DuplicateDetectionResourceComponent_Conditional_68_For_49_For_7_Conditional_1_Template, 2, 2)(2, DuplicateDetectionResourceComponent_Conditional_68_For_49_For_7_Conditional_2_Template, 2, 0, "span", 157);
873
+ i0.ɵɵelementEnd();
874
+ } if (rf & 2) {
875
+ const matchVal_r37 = ctx.$implicit;
876
+ const ɵ$index_685_r38 = ctx.$index;
877
+ const ctx_r40 = i0.ɵɵnextContext();
878
+ const field_r33 = ctx_r40.$implicit;
879
+ const ɵ$index_670_r42 = ctx_r40.$index;
880
+ const ctx_r1 = i0.ɵɵnextContext(2);
881
+ i0.ɵɵclassProp("grid-row-odd", ɵ$index_670_r42 % 2 !== 0)("value-same", ctx_r1.AreValuesEqual(field_r33.SourceValue, matchVal_r37))("value-different", matchVal_r37 != null && field_r33.SourceValue != null && !ctx_r1.AreValuesEqual(field_r33.SourceValue, matchVal_r37))("field-selected", field_r33.SelectedColumnIndex === ɵ$index_685_r38 + 1);
882
+ i0.ɵɵadvance();
883
+ i0.ɵɵconditional(matchVal_r37 != null ? 1 : 2);
884
+ } }
885
+ function DuplicateDetectionResourceComponent_Conditional_68_For_49_Template(rf, ctx) { if (rf & 1) {
886
+ const _r32 = i0.ɵɵgetCurrentView();
887
+ i0.ɵɵelementStart(0, "div", 155);
888
+ i0.ɵɵtext(1);
889
+ i0.ɵɵelementEnd();
890
+ i0.ɵɵelementStart(2, "div", 156);
891
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_For_49_Template_div_click_2_listener() { const field_r33 = i0.ɵɵrestoreView(_r32).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.SelectFieldValue(field_r33, 0)); });
892
+ i0.ɵɵconditionalCreate(3, DuplicateDetectionResourceComponent_Conditional_68_For_49_Conditional_3_Template, 1, 1)(4, DuplicateDetectionResourceComponent_Conditional_68_For_49_Conditional_4_Template, 2, 0, "span", 157);
893
+ i0.ɵɵconditionalCreate(5, DuplicateDetectionResourceComponent_Conditional_68_For_49_Conditional_5_Template, 1, 2, "input", 158);
894
+ i0.ɵɵelementEnd();
895
+ i0.ɵɵrepeaterCreate(6, DuplicateDetectionResourceComponent_Conditional_68_For_49_For_7_Template, 3, 9, "div", 159, i0.ɵɵrepeaterTrackByIndex);
896
+ } if (rf & 2) {
897
+ const field_r33 = ctx.$implicit;
898
+ const ɵ$index_670_r42 = ctx.$index;
899
+ i0.ɵɵclassProp("grid-row-odd", ɵ$index_670_r42 % 2 !== 0);
900
+ i0.ɵɵadvance();
901
+ i0.ɵɵtextInterpolate1(" ", field_r33.DisplayName, " ");
902
+ i0.ɵɵadvance();
903
+ i0.ɵɵclassProp("grid-row-odd", ɵ$index_670_r42 % 2 !== 0)("has-diff-in-row", field_r33.HasDifference)("field-selected", field_r33.SelectedColumnIndex === 0);
904
+ i0.ɵɵadvance();
905
+ i0.ɵɵconditional(field_r33.SourceValue != null ? 3 : 4);
263
906
  i0.ɵɵadvance(2);
264
- i0.ɵɵconditional(ctx_r2.ApprovedGroups.length === 0 ? 22 : -1);
907
+ i0.ɵɵconditional(field_r33.HasDifference || field_r33.SelectedColumnIndex === 0 ? 5 : -1);
908
+ i0.ɵɵadvance();
909
+ i0.ɵɵrepeater(field_r33.MatchValues);
910
+ } }
911
+ function DuplicateDetectionResourceComponent_Conditional_68_Conditional_59_Template(rf, ctx) { if (rf & 1) {
912
+ i0.ɵɵtext(0);
913
+ } if (rf & 2) {
914
+ const ctx_r1 = i0.ɵɵnextContext(2);
915
+ i0.ɵɵtextInterpolate2(" \u00B7 ", ctx_r1.CherryPickedCount(), " field", ctx_r1.CherryPickedCount() !== 1 ? "s" : "", " cherry-picked ");
916
+ } }
917
+ function DuplicateDetectionResourceComponent_Conditional_68_Conditional_67_Template(rf, ctx) { if (rf & 1) {
918
+ i0.ɵɵelementStart(0, "span", 129);
919
+ i0.ɵɵelement(1, "i", 162);
920
+ i0.ɵɵtext(2, " Merging disabled for this entity");
921
+ i0.ɵɵelementEnd();
922
+ } }
923
+ function DuplicateDetectionResourceComponent_Conditional_68_Template(rf, ctx) { if (rf & 1) {
924
+ const _r16 = i0.ɵɵgetCurrentView();
925
+ i0.ɵɵelementStart(0, "div", 92);
926
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r16); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.CloseComparison()); });
927
+ i0.ɵɵelementEnd();
928
+ i0.ɵɵelementStart(1, "div", 93);
929
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_Template_div_click_1_listener($event) { i0.ɵɵrestoreView(_r16); return i0.ɵɵresetView($event.stopPropagation()); });
930
+ i0.ɵɵelementStart(2, "div", 94)(3, "div", 95)(4, "div", 96);
931
+ i0.ɵɵelement(5, "i");
932
+ i0.ɵɵelementEnd();
933
+ i0.ɵɵelementStart(6, "div")(7, "div", 97);
934
+ i0.ɵɵtext(8);
935
+ i0.ɵɵelementEnd();
936
+ i0.ɵɵelementStart(9, "span", 98);
937
+ i0.ɵɵtext(10);
938
+ i0.ɵɵelementEnd();
939
+ i0.ɵɵelementStart(11, "span", 99);
940
+ i0.ɵɵtext(12);
941
+ i0.ɵɵelementEnd()()();
942
+ i0.ɵɵelementStart(13, "div", 100)(14, "div", 101)(15, "button", 102);
943
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_Template_button_click_15_listener() { i0.ɵɵrestoreView(_r16); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.ComparisonShowAllFields = true); });
944
+ i0.ɵɵtext(16, "All Fields");
945
+ i0.ɵɵelementEnd();
946
+ i0.ɵɵelementStart(17, "button", 102);
947
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_Template_button_click_17_listener() { i0.ɵɵrestoreView(_r16); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.ComparisonShowAllFields = false); });
948
+ i0.ɵɵtext(18, "Differences Only");
949
+ i0.ɵɵelementEnd()();
950
+ i0.ɵɵelementStart(19, "button", 103);
951
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_Template_button_click_19_listener() { i0.ɵɵrestoreView(_r16); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.CloseComparison()); });
952
+ i0.ɵɵelement(20, "i", 50);
953
+ i0.ɵɵelementEnd()()();
954
+ i0.ɵɵconditionalCreate(21, DuplicateDetectionResourceComponent_Conditional_68_Conditional_21_Template, 2, 0, "div", 104);
955
+ i0.ɵɵelementStart(22, "div", 105)(23, "div", 106)(24, "div", 107);
956
+ i0.ɵɵtext(25, "Field");
957
+ i0.ɵɵelementEnd();
958
+ i0.ɵɵelementStart(26, "div", 108)(27, "span", 109);
959
+ i0.ɵɵtext(28, "Source");
960
+ i0.ɵɵelementEnd();
961
+ i0.ɵɵelementStart(29, "span", 110);
962
+ i0.ɵɵtext(30);
963
+ i0.ɵɵelementEnd();
964
+ i0.ɵɵelementStart(31, "div", 111)(32, "input", 112);
965
+ i0.ɵɵlistener("change", function DuplicateDetectionResourceComponent_Conditional_68_Template_input_change_32_listener() { i0.ɵɵrestoreView(_r16); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.SetSurvivor(0)); });
966
+ i0.ɵɵelementEnd();
967
+ i0.ɵɵelementStart(33, "span", 113);
968
+ i0.ɵɵtext(34);
969
+ i0.ɵɵelementEnd()();
970
+ i0.ɵɵelementStart(35, "button", 114);
971
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_Template_button_click_35_listener() { i0.ɵɵrestoreView(_r16); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.UseAllFieldsFrom(0)); });
972
+ i0.ɵɵelement(36, "i", 115);
973
+ i0.ɵɵtext(37);
974
+ i0.ɵɵelementEnd();
975
+ i0.ɵɵelementStart(38, "div", 116)(39, "div", 117);
976
+ i0.ɵɵelement(40, "i", 118);
977
+ i0.ɵɵelementStart(41, "span", 119);
978
+ i0.ɵɵtext(42);
979
+ i0.ɵɵelementEnd();
980
+ i0.ɵɵtext(43);
981
+ i0.ɵɵconditionalCreate(44, DuplicateDetectionResourceComponent_Conditional_68_Conditional_44_Template, 3, 0, "span", 120);
982
+ i0.ɵɵelementEnd();
983
+ i0.ɵɵconditionalCreate(45, DuplicateDetectionResourceComponent_Conditional_68_Conditional_45_Template, 5, 6);
984
+ i0.ɵɵelementEnd()();
985
+ i0.ɵɵrepeaterCreate(46, DuplicateDetectionResourceComponent_Conditional_68_For_47_Template, 26, 26, "div", 121, _forTrack3);
986
+ i0.ɵɵrepeaterCreate(48, DuplicateDetectionResourceComponent_Conditional_68_For_49_Template, 8, 11, null, null, _forTrack4);
987
+ i0.ɵɵelementEnd()();
988
+ i0.ɵɵelementStart(50, "div", 122)(51, "div", 123)(52, "span", 124);
989
+ i0.ɵɵtext(53);
990
+ i0.ɵɵelementEnd();
991
+ i0.ɵɵelementStart(54, "span", 125);
992
+ i0.ɵɵelement(55, "i", 126);
993
+ i0.ɵɵtext(56, " Surviving: ");
994
+ i0.ɵɵelementStart(57, "strong");
995
+ i0.ɵɵtext(58);
996
+ i0.ɵɵelementEnd();
997
+ i0.ɵɵconditionalCreate(59, DuplicateDetectionResourceComponent_Conditional_68_Conditional_59_Template, 1, 2);
998
+ i0.ɵɵelementEnd()();
999
+ i0.ɵɵelementStart(60, "div", 127)(61, "button", 85);
1000
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_Template_button_click_61_listener() { i0.ɵɵrestoreView(_r16); const ctx_r1 = i0.ɵɵnextContext(); ctx_r1.RejectMatch(ctx_r1.ComparisonGroup); return i0.ɵɵresetView(ctx_r1.CloseComparison()); });
1001
+ i0.ɵɵelement(62, "i", 50);
1002
+ i0.ɵɵtext(63, " Reject All ");
1003
+ i0.ɵɵelementEnd();
1004
+ i0.ɵɵelementStart(64, "button", 128);
1005
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_68_Template_button_click_64_listener() { i0.ɵɵrestoreView(_r16); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OpenMergeConfirm()); });
1006
+ i0.ɵɵelement(65, "i", 126);
1007
+ i0.ɵɵtext(66, " Merge Records ");
1008
+ i0.ɵɵelementEnd();
1009
+ i0.ɵɵconditionalCreate(67, DuplicateDetectionResourceComponent_Conditional_68_Conditional_67_Template, 3, 0, "span", 129);
1010
+ i0.ɵɵelementEnd()()();
1011
+ } if (rf & 2) {
1012
+ const ctx_r1 = i0.ɵɵnextContext();
1013
+ i0.ɵɵclassProp("comparison-closing", ctx_r1.ComparisonClosing);
1014
+ i0.ɵɵadvance();
1015
+ i0.ɵɵclassProp("comparison-closing", ctx_r1.ComparisonClosing);
1016
+ i0.ɵɵadvance(4);
1017
+ i0.ɵɵclassMap(ctx_r1.ComparisonGroup.EntityIcon);
1018
+ i0.ɵɵadvance(3);
1019
+ i0.ɵɵtextInterpolate(ctx_r1.ComparisonGroup.RecordName);
1020
+ i0.ɵɵadvance(2);
1021
+ i0.ɵɵtextInterpolate(ctx_r1.ComparisonGroup.EntityName);
1022
+ i0.ɵɵadvance(2);
1023
+ i0.ɵɵtextInterpolate2(" ", ctx_r1.ComparisonGroup.MatchCount, " potential duplicate", ctx_r1.ComparisonGroup.MatchCount !== 1 ? "s" : "", " ");
1024
+ i0.ɵɵadvance(3);
1025
+ i0.ɵɵclassProp("toggle-active", ctx_r1.ComparisonShowAllFields);
1026
+ i0.ɵɵadvance(2);
1027
+ i0.ɵɵclassProp("toggle-active", !ctx_r1.ComparisonShowAllFields);
1028
+ i0.ɵɵadvance(4);
1029
+ i0.ɵɵconditional(ctx_r1.ComparisonLoading ? 21 : -1);
1030
+ i0.ɵɵadvance();
1031
+ i0.ɵɵproperty("hidden", ctx_r1.ComparisonLoading);
1032
+ i0.ɵɵadvance();
1033
+ i0.ɵɵstyleProp("grid-template-columns", "160px repeat(" + (1 + ctx_r1.ComparisonMatches.length) + ", minmax(180px, 1fr))");
265
1034
  i0.ɵɵadvance(7);
266
- i0.ɵɵtextInterpolate(ctx_r2.RejectedCount);
1035
+ i0.ɵɵtextInterpolate(ctx_r1.ComparisonGroup.RecordName);
1036
+ i0.ɵɵadvance(2);
1037
+ i0.ɵɵproperty("checked", ctx_r1.SurvivorColumnIndex === 0);
1038
+ i0.ɵɵadvance();
1039
+ i0.ɵɵclassProp("is-survivor", ctx_r1.SurvivorColumnIndex === 0);
1040
+ i0.ɵɵadvance();
1041
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.SurvivorColumnIndex === 0 ? "Surviving Record" : "Set as survivor", " ");
1042
+ i0.ɵɵadvance();
1043
+ i0.ɵɵclassProp("all-selected", ctx_r1.AllFieldsSelectedFrom(0));
1044
+ i0.ɵɵadvance();
1045
+ i0.ɵɵclassProp("fa-check-double", ctx_r1.AllFieldsSelectedFrom(0))("fa-clone", !ctx_r1.AllFieldsSelectedFrom(0));
1046
+ i0.ɵɵadvance();
1047
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.AllFieldsSelectedFrom(0) ? "Using all fields" : "Use all fields", " ");
1048
+ i0.ɵɵadvance(5);
1049
+ i0.ɵɵtextInterpolate(ctx_r1.GetTotalDeps(0));
1050
+ i0.ɵɵadvance();
1051
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.GetTotalDeps(0) === 1 ? "dependency" : "dependencies", " ");
1052
+ i0.ɵɵadvance();
1053
+ i0.ɵɵconditional(ctx_r1.GetMaxDepsColumnIndex() === 0 && ctx_r1.GetTotalDeps(0) > 0 ? 44 : -1);
1054
+ i0.ɵɵadvance();
1055
+ i0.ɵɵconditional(ctx_r1.GetGroupedDeps(0).length > 0 ? 45 : -1);
1056
+ i0.ɵɵadvance();
1057
+ i0.ɵɵrepeater(ctx_r1.ComparisonMatches);
1058
+ i0.ɵɵadvance(2);
1059
+ i0.ɵɵrepeater(ctx_r1.GetVisibleFields());
1060
+ i0.ɵɵadvance(5);
1061
+ i0.ɵɵtextInterpolate2(" Showing ", ctx_r1.GetVisibleFields().length, " of ", ctx_r1.ComparisonFields.length, " fields ");
1062
+ i0.ɵɵadvance(5);
1063
+ i0.ɵɵtextInterpolate(ctx_r1.SurvivorName());
1064
+ i0.ɵɵadvance();
1065
+ i0.ɵɵconditional(ctx_r1.CherryPickedCount() > 0 ? 59 : -1);
1066
+ i0.ɵɵadvance(2);
1067
+ i0.ɵɵproperty("disabled", ctx_r1.IsSaving);
1068
+ i0.ɵɵadvance(3);
1069
+ i0.ɵɵproperty("disabled", ctx_r1.IsSaving || !ctx_r1.HasMergeableMatches || !ctx_r1.MergeEnabled)("title", !ctx_r1.MergeEnabled ? "Merging is not enabled for this entity" : ctx_r1.HasMergeableMatches ? "Merge non-skipped records" : "All matches have been skipped");
1070
+ i0.ɵɵadvance(3);
1071
+ i0.ɵɵconditional(!ctx_r1.MergeEnabled ? 67 : -1);
1072
+ } }
1073
+ function DuplicateDetectionResourceComponent_Conditional_69_Conditional_22_For_4_Template(rf, ctx) { if (rf & 1) {
1074
+ i0.ɵɵelementStart(0, "div", 187)(1, "span", 188);
1075
+ i0.ɵɵtext(2);
1076
+ i0.ɵɵelementEnd();
1077
+ i0.ɵɵelementStart(3, "span", 189);
1078
+ i0.ɵɵtext(4);
1079
+ i0.ɵɵelementEnd();
1080
+ i0.ɵɵelementStart(5, "span", 190);
1081
+ i0.ɵɵtext(6);
1082
+ i0.ɵɵelementEnd()();
1083
+ } if (rf & 2) {
1084
+ const field_r44 = ctx.$implicit;
1085
+ i0.ɵɵadvance(2);
1086
+ i0.ɵɵtextInterpolate(field_r44.DisplayName);
267
1087
  i0.ɵɵadvance(2);
268
- i0.ɵɵrepeater(ctx_r2.RejectedGroups);
1088
+ i0.ɵɵtextInterpolate1("\"", field_r44.Value, "\"");
269
1089
  i0.ɵɵadvance(2);
270
- i0.ɵɵconditional(ctx_r2.RejectedGroups.length === 0 ? 33 : -1);
1090
+ i0.ɵɵtextInterpolate1("from ", field_r44.SourceName);
271
1091
  } }
272
- function DuplicateDetectionResourceComponent_Conditional_54_Template(rf, ctx) { if (rf & 1) {
273
- i0.ɵɵelementStart(0, "div", 25);
274
- i0.ɵɵelement(1, "mj-loading", 59);
1092
+ function DuplicateDetectionResourceComponent_Conditional_69_Conditional_22_Template(rf, ctx) { if (rf & 1) {
1093
+ i0.ɵɵelementStart(0, "div")(1, "div", 186);
1094
+ i0.ɵɵtext(2);
275
1095
  i0.ɵɵelementEnd();
1096
+ i0.ɵɵrepeaterCreate(3, DuplicateDetectionResourceComponent_Conditional_69_Conditional_22_For_4_Template, 7, 3, "div", 187, _forTrack4);
1097
+ i0.ɵɵelementEnd();
1098
+ } if (rf & 2) {
1099
+ const ctx_r1 = i0.ɵɵnextContext(2);
1100
+ i0.ɵɵadvance(2);
1101
+ i0.ɵɵtextInterpolate2("Field Value Overrides (", ctx_r1.GetCherryPickedFields().length, " field", ctx_r1.GetCherryPickedFields().length !== 1 ? "s" : "", ")");
1102
+ i0.ɵɵadvance();
1103
+ i0.ɵɵrepeater(ctx_r1.GetCherryPickedFields());
1104
+ } }
1105
+ function DuplicateDetectionResourceComponent_Conditional_69_Conditional_23_For_5_Conditional_3_Template(rf, ctx) { if (rf & 1) {
1106
+ i0.ɵɵelementStart(0, "strong");
1107
+ i0.ɵɵtext(1);
1108
+ i0.ɵɵelementEnd();
1109
+ i0.ɵɵtext(2);
1110
+ i0.ɵɵelementStart(3, "strong");
1111
+ i0.ɵɵtext(4);
1112
+ i0.ɵɵelementEnd();
1113
+ i0.ɵɵtext(5);
1114
+ } if (rf & 2) {
1115
+ const col_r45 = i0.ɵɵnextContext().$implicit;
1116
+ const ctx_r1 = i0.ɵɵnextContext(3);
1117
+ i0.ɵɵadvance();
1118
+ i0.ɵɵtextInterpolate(col_r45.DepCount);
1119
+ i0.ɵɵadvance();
1120
+ i0.ɵɵtextInterpolate1(" ", col_r45.DepCount === 1 ? "dependency" : "dependencies", " from ");
1121
+ i0.ɵɵadvance(2);
1122
+ i0.ɵɵtextInterpolate(col_r45.Name);
1123
+ i0.ɵɵadvance();
1124
+ i0.ɵɵtextInterpolate1(" \u2192 ", ctx_r1.SurvivorName(), " ");
1125
+ } }
1126
+ function DuplicateDetectionResourceComponent_Conditional_69_Conditional_23_For_5_Conditional_4_Template(rf, ctx) { if (rf & 1) {
1127
+ i0.ɵɵelementStart(0, "strong");
1128
+ i0.ɵɵtext(1, "0");
1129
+ i0.ɵɵelementEnd();
1130
+ i0.ɵɵtext(2, " dependencies from ");
1131
+ i0.ɵɵelementStart(3, "strong");
1132
+ i0.ɵɵtext(4);
1133
+ i0.ɵɵelementEnd();
1134
+ } if (rf & 2) {
1135
+ const col_r45 = i0.ɵɵnextContext().$implicit;
1136
+ i0.ɵɵadvance(4);
1137
+ i0.ɵɵtextInterpolate(col_r45.Name);
1138
+ } }
1139
+ function DuplicateDetectionResourceComponent_Conditional_69_Conditional_23_For_5_Template(rf, ctx) { if (rf & 1) {
1140
+ i0.ɵɵelementStart(0, "div", 193);
1141
+ i0.ɵɵelement(1, "i", 194);
1142
+ i0.ɵɵelementStart(2, "span");
1143
+ i0.ɵɵconditionalCreate(3, DuplicateDetectionResourceComponent_Conditional_69_Conditional_23_For_5_Conditional_3_Template, 6, 4)(4, DuplicateDetectionResourceComponent_Conditional_69_Conditional_23_For_5_Conditional_4_Template, 5, 1);
1144
+ i0.ɵɵelementEnd()();
1145
+ } if (rf & 2) {
1146
+ const col_r45 = ctx.$implicit;
1147
+ i0.ɵɵadvance(3);
1148
+ i0.ɵɵconditional(col_r45.DepCount > 0 ? 3 : 4);
1149
+ } }
1150
+ function DuplicateDetectionResourceComponent_Conditional_69_Conditional_23_Template(rf, ctx) { if (rf & 1) {
1151
+ i0.ɵɵelementStart(0, "div", 177)(1, "div", 191);
1152
+ i0.ɵɵelement(2, "i", 192);
1153
+ i0.ɵɵtext(3, " Dependencies to Transfer");
1154
+ i0.ɵɵelementEnd();
1155
+ i0.ɵɵrepeaterCreate(4, DuplicateDetectionResourceComponent_Conditional_69_Conditional_23_For_5_Template, 5, 1, "div", 193, _forTrack6);
1156
+ i0.ɵɵelementEnd();
1157
+ } if (rf & 2) {
1158
+ const ctx_r1 = i0.ɵɵnextContext(2);
1159
+ i0.ɵɵadvance(4);
1160
+ i0.ɵɵrepeater(ctx_r1.GetNonSurvivorColumns());
1161
+ } }
1162
+ function DuplicateDetectionResourceComponent_Conditional_69_For_29_Conditional_4_Template(rf, ctx) { if (rf & 1) {
1163
+ i0.ɵɵtext(0);
1164
+ } if (rf & 2) {
1165
+ const col_r46 = i0.ɵɵnextContext().$implicit;
1166
+ i0.ɵɵtextInterpolate2(" ", col_r46.DepCount, " dep", col_r46.DepCount !== 1 ? "s" : "", " transferring ");
1167
+ } }
1168
+ function DuplicateDetectionResourceComponent_Conditional_69_For_29_Conditional_5_Template(rf, ctx) { if (rf & 1) {
1169
+ i0.ɵɵtext(0, " no deps ");
1170
+ } }
1171
+ function DuplicateDetectionResourceComponent_Conditional_69_For_29_Template(rf, ctx) { if (rf & 1) {
1172
+ i0.ɵɵelementStart(0, "div", 181)(1, "span", 195);
1173
+ i0.ɵɵtext(2);
1174
+ i0.ɵɵelementEnd();
1175
+ i0.ɵɵelementStart(3, "span", 196);
1176
+ i0.ɵɵconditionalCreate(4, DuplicateDetectionResourceComponent_Conditional_69_For_29_Conditional_4_Template, 1, 2)(5, DuplicateDetectionResourceComponent_Conditional_69_For_29_Conditional_5_Template, 1, 0);
1177
+ i0.ɵɵelementEnd()();
1178
+ } if (rf & 2) {
1179
+ const col_r46 = ctx.$implicit;
1180
+ i0.ɵɵadvance(2);
1181
+ i0.ɵɵtextInterpolate(col_r46.Name);
1182
+ i0.ɵɵadvance(2);
1183
+ i0.ɵɵconditional(col_r46.DepCount > 0 ? 4 : 5);
1184
+ } }
1185
+ function DuplicateDetectionResourceComponent_Conditional_69_Conditional_35_Template(rf, ctx) { if (rf & 1) {
1186
+ i0.ɵɵelement(0, "i", 34);
1187
+ i0.ɵɵtext(1, " Merging... ");
1188
+ } }
1189
+ function DuplicateDetectionResourceComponent_Conditional_69_Conditional_36_Template(rf, ctx) { if (rf & 1) {
1190
+ i0.ɵɵelement(0, "i", 126);
1191
+ i0.ɵɵtext(1);
1192
+ } if (rf & 2) {
1193
+ const ctx_r1 = i0.ɵɵnextContext(2);
1194
+ i0.ɵɵadvance();
1195
+ i0.ɵɵtextInterpolate1(" Confirm Merge (", ctx_r1.GetNonSurvivorColumns().length + 1, " records \u2192 1) ");
1196
+ } }
1197
+ function DuplicateDetectionResourceComponent_Conditional_69_Template(rf, ctx) { if (rf & 1) {
1198
+ const _r43 = i0.ɵɵgetCurrentView();
1199
+ i0.ɵɵelementStart(0, "div", 163);
1200
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_69_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r43); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.CloseMergeConfirm()); });
1201
+ i0.ɵɵelementStart(1, "div", 164);
1202
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_69_Template_div_click_1_listener($event) { i0.ɵɵrestoreView(_r43); return i0.ɵɵresetView($event.stopPropagation()); });
1203
+ i0.ɵɵelementStart(2, "div", 165)(3, "div", 166);
1204
+ i0.ɵɵelement(4, "i", 126);
1205
+ i0.ɵɵelementEnd();
1206
+ i0.ɵɵelementStart(5, "div")(6, "div", 167);
1207
+ i0.ɵɵtext(7, "Confirm Record Merge");
1208
+ i0.ɵɵelementEnd();
1209
+ i0.ɵɵelementStart(8, "div", 168);
1210
+ i0.ɵɵtext(9, "This action cannot be undone. Please review carefully.");
1211
+ i0.ɵɵelementEnd()()();
1212
+ i0.ɵɵelementStart(10, "div", 169)(11, "div", 170)(12, "div", 171);
1213
+ i0.ɵɵelement(13, "i", 172);
1214
+ i0.ɵɵtext(14, " Surviving Record");
1215
+ i0.ɵɵelementEnd();
1216
+ i0.ɵɵelementStart(15, "div", 173);
1217
+ i0.ɵɵtext(16);
1218
+ i0.ɵɵelementEnd();
1219
+ i0.ɵɵelementStart(17, "div", 174);
1220
+ i0.ɵɵelement(18, "i", 175);
1221
+ i0.ɵɵtext(19);
1222
+ i0.ɵɵelementEnd();
1223
+ i0.ɵɵelementStart(20, "div", 176);
1224
+ i0.ɵɵtext(21, "This record's ID will be retained. All dependencies from merged records will be transferred here.");
1225
+ i0.ɵɵelementEnd()();
1226
+ i0.ɵɵconditionalCreate(22, DuplicateDetectionResourceComponent_Conditional_69_Conditional_22_Template, 5, 2, "div");
1227
+ i0.ɵɵconditionalCreate(23, DuplicateDetectionResourceComponent_Conditional_69_Conditional_23_Template, 6, 0, "div", 177);
1228
+ i0.ɵɵelementStart(24, "div", 178)(25, "div", 179);
1229
+ i0.ɵɵelement(26, "i", 180);
1230
+ i0.ɵɵtext(27, " Records to Delete After Merge");
1231
+ i0.ɵɵelementEnd();
1232
+ i0.ɵɵrepeaterCreate(28, DuplicateDetectionResourceComponent_Conditional_69_For_29_Template, 6, 2, "div", 181, _forTrack6);
1233
+ i0.ɵɵelementEnd()();
1234
+ i0.ɵɵelementStart(30, "div", 182)(31, "button", 183);
1235
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_69_Template_button_click_31_listener() { i0.ɵɵrestoreView(_r43); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.CloseMergeConfirm()); });
1236
+ i0.ɵɵelement(32, "i", 184);
1237
+ i0.ɵɵtext(33, " Back ");
1238
+ i0.ɵɵelementEnd();
1239
+ i0.ɵɵelementStart(34, "button", 185);
1240
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Conditional_69_Template_button_click_34_listener() { i0.ɵɵrestoreView(_r43); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.ExecuteMerge()); });
1241
+ i0.ɵɵconditionalCreate(35, DuplicateDetectionResourceComponent_Conditional_69_Conditional_35_Template, 2, 0)(36, DuplicateDetectionResourceComponent_Conditional_69_Conditional_36_Template, 2, 1);
1242
+ i0.ɵɵelementEnd()()()();
1243
+ } if (rf & 2) {
1244
+ const ctx_r1 = i0.ɵɵnextContext();
1245
+ i0.ɵɵadvance(16);
1246
+ i0.ɵɵtextInterpolate(ctx_r1.SurvivorName());
1247
+ i0.ɵɵadvance(3);
1248
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.SurvivorKeyDisplay(), " ");
1249
+ i0.ɵɵadvance(3);
1250
+ i0.ɵɵconditional(ctx_r1.GetCherryPickedFields().length > 0 ? 22 : -1);
1251
+ i0.ɵɵadvance();
1252
+ i0.ɵɵconditional(ctx_r1.GetNonSurvivorColumns().length > 0 ? 23 : -1);
1253
+ i0.ɵɵadvance(5);
1254
+ i0.ɵɵrepeater(ctx_r1.GetNonSurvivorColumns());
1255
+ i0.ɵɵadvance(3);
1256
+ i0.ɵɵproperty("disabled", ctx_r1.IsMerging);
1257
+ i0.ɵɵadvance(3);
1258
+ i0.ɵɵproperty("disabled", ctx_r1.IsMerging);
1259
+ i0.ɵɵadvance();
1260
+ i0.ɵɵconditional(ctx_r1.IsMerging ? 35 : 36);
276
1261
  } }
277
1262
  let DuplicateDetectionResourceComponent = class DuplicateDetectionResourceComponent extends BaseResourceComponent {
1263
+ /** Close comparison panel on Escape key */
1264
+ OnEscapeKey() {
1265
+ if (this.ComparisonGroup) {
1266
+ this.CloseComparison();
1267
+ }
1268
+ }
278
1269
  cdr = inject(ChangeDetectorRef);
1270
+ navigationService = inject(NavigationService);
279
1271
  destroy$ = new Subject();
280
1272
  filterSubject = new Subject();
281
1273
  // Loading state
282
1274
  IsLoading = false;
1275
+ /** Whether the results area (runs/details/matches) is still loading */
1276
+ IsLoadingResults = false;
283
1277
  IsSaving = false;
1278
+ // ── Comparison Panel State ──
1279
+ /** The group being compared (null = panel closed) */
1280
+ ComparisonGroup = null;
1281
+ /** Whether the comparison panel is loading entity records */
1282
+ ComparisonLoading = false;
1283
+ /** Whether to show all fields or only differences */
1284
+ ComparisonShowAllFields = true;
1285
+ /** Precomputed field rows for the comparison grid */
1286
+ ComparisonFields = [];
1287
+ /** Parsed match info for comparison columns */
1288
+ ComparisonMatches = [];
1289
+ /** Index of the surviving record column (0 = source, 1+ = match index) */
1290
+ SurvivorColumnIndex = 0;
1291
+ /** Whether the panel is animating closed */
1292
+ ComparisonClosing = false;
1293
+ /** Loaded entity records keyed by record ID (populated on panel open via RunView) */
1294
+ comparisonRecords = new Map();
1295
+ // ── Dependencies State ──
1296
+ /** Dependencies per record, keyed by composite key string */
1297
+ ComparisonDependencies = new Map();
1298
+ /** Which column indices have expanded dependency details */
1299
+ DepsExpandedColumns = new Set();
1300
+ /** Tracks which entity groups within deps are expanded (key: "columnIndex::entityName") */
1301
+ depsEntityGroupExpanded = new Set();
1302
+ // ── Merge Confirmation State ──
1303
+ /** Whether the merge confirmation panel is visible */
1304
+ ShowMergeConfirm = false;
1305
+ /** Whether the merge is currently executing */
1306
+ IsMerging = false;
1307
+ /** Whether the current entity allows record merging (controls merge button availability) */
1308
+ MergeEnabled = true;
1309
+ /** Whether merge-not-available inline banner should be shown in the results area */
1310
+ ShowMergeWarningBanner = false;
284
1311
  // Raw data
285
1312
  Runs = [];
286
1313
  Details = [];
@@ -300,6 +1327,29 @@ let DuplicateDetectionResourceComponent = class DuplicateDetectionResourceCompon
300
1327
  };
301
1328
  // Available entity names from loaded runs
302
1329
  EntityNames = [];
1330
+ // Detection run state
1331
+ IsDetecting = false;
1332
+ DetectionProgress = 0;
1333
+ DetectionStage = '';
1334
+ /** Raw stage key from the last progress event — used to detect phase transitions */
1335
+ detectionRawStage = '';
1336
+ /** Runtime threshold overrides — initialized from entity doc, adjustable via sliders */
1337
+ RunPotentialThreshold = 0.70;
1338
+ RunAbsoluteThreshold = 0.95;
1339
+ DetectionCurrentItem = '';
1340
+ // Entity document picker
1341
+ EntityDocuments = [];
1342
+ _selectedEntityDocumentID = '';
1343
+ get SelectedEntityDocumentID() { return this._selectedEntityDocumentID; }
1344
+ set SelectedEntityDocumentID(value) {
1345
+ this._selectedEntityDocumentID = value;
1346
+ // Sync threshold sliders from selected entity document
1347
+ const doc = this.EntityDocuments.find(d => UUIDsEqual(d.ID, value));
1348
+ if (doc) {
1349
+ this.RunPotentialThreshold = doc.PotentialMatchThreshold;
1350
+ this.RunAbsoluteThreshold = doc.AbsoluteMatchThreshold;
1351
+ }
1352
+ }
303
1353
  /** Whether this component is embedded inside the Knowledge Hub shell */
304
1354
  EmbeddedMode = false;
305
1355
  /** View mode: 'kanban' (card board) or 'table' (paged grid) */
@@ -381,9 +1431,22 @@ let DuplicateDetectionResourceComponent = class DuplicateDetectionResourceCompon
381
1431
  get RejectedCount() {
382
1432
  return this.RejectedGroups.length;
383
1433
  }
1434
+ /** Get selected entity document threshold info */
1435
+ get SelectedDocumentThresholds() {
1436
+ if (!this.SelectedEntityDocumentID)
1437
+ return null;
1438
+ return this.EntityDocuments.find(d => UUIDsEqual(d.ID, this.SelectedEntityDocumentID)) ?? null;
1439
+ }
384
1440
  async ngAfterViewInit() {
385
1441
  this.setupFilterDebounce();
386
1442
  await this.LoadData();
1443
+ this.navigationService.SetAgentContext(this, {
1444
+ DetectionStatus: this.IsDetecting ? 'running' : 'idle',
1445
+ PendingCount: this.PendingGroups.length,
1446
+ ApprovedCount: this.ApprovedGroups.length,
1447
+ RejectedCount: this.RejectedGroups.length,
1448
+ SelectedEntityDoc: this.SelectedEntityDocumentID || null,
1449
+ });
387
1450
  this.NotifyLoadComplete();
388
1451
  }
389
1452
  ngOnDestroy() {
@@ -398,49 +1461,147 @@ let DuplicateDetectionResourceComponent = class DuplicateDetectionResourceCompon
398
1461
  }
399
1462
  /**
400
1463
  * Loads all duplicate run data and builds the Kanban groups.
1464
+ * Split into two phases so that controls become interactive immediately:
1465
+ * Phase 1 - entity docs from KH engine cache (instant)
1466
+ * Phase 2 - runs/details/matches via RunViews (heavy)
401
1467
  */
402
1468
  async LoadData() {
403
1469
  this.IsLoading = true;
404
1470
  this.cdr.detectChanges();
405
1471
  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();
1472
+ // Phase 1: Populate entity document picker from cache (instant)
1473
+ await this.loadEntityDocuments();
1474
+ // Controls are now interactive - only the results area is loading
1475
+ this.IsLoading = false;
1476
+ this.IsLoadingResults = true;
1477
+ this.cdr.detectChanges();
1478
+ // Phase 2: Load heavy run/detail/match data
1479
+ await this.loadRunData();
438
1480
  }
439
1481
  catch (error) {
440
1482
  console.error('Error loading duplicate detection data:', error);
441
1483
  }
442
1484
  finally {
443
1485
  this.IsLoading = false;
1486
+ this.IsLoadingResults = false;
1487
+ this.cdr.detectChanges();
1488
+ }
1489
+ }
1490
+ /** Phase 1: Load entity documents from KH engine cache (instant). */
1491
+ async loadEntityDocuments() {
1492
+ const engine = KnowledgeHubMetadataEngine.Instance;
1493
+ await engine.Config(false);
1494
+ this.buildEntityDocumentOptionsFromEngine(engine.GetActiveEntityDocuments());
1495
+ }
1496
+ /** Phase 2: Load runs, details, and matches via RunViews batch. */
1497
+ async loadRunData() {
1498
+ const rv = new RunView();
1499
+ const [runsResult, detailsResult, matchesResult] = await rv.RunViews([
1500
+ {
1501
+ EntityName: 'MJ: Duplicate Runs',
1502
+ ExtraFilter: "ProcessingStatus IN ('Complete', 'Failed', 'In Progress')",
1503
+ OrderBy: 'StartedAt DESC',
1504
+ ResultType: 'entity_object'
1505
+ },
1506
+ {
1507
+ EntityName: 'MJ: Duplicate Run Details',
1508
+ ExtraFilter: "MatchStatus = 'Complete'",
1509
+ OrderBy: '__mj_CreatedAt DESC',
1510
+ ResultType: 'entity_object'
1511
+ },
1512
+ {
1513
+ EntityName: 'MJ: Duplicate Run Detail Matches',
1514
+ OrderBy: 'MatchProbability DESC',
1515
+ ResultType: 'entity_object'
1516
+ }
1517
+ ]);
1518
+ if (runsResult.Success) {
1519
+ this.Runs = runsResult.Results;
1520
+ }
1521
+ if (detailsResult.Success) {
1522
+ this.Details = detailsResult.Results;
1523
+ }
1524
+ if (matchesResult.Success) {
1525
+ this.Matches = matchesResult.Results;
1526
+ }
1527
+ this.buildGroups();
1528
+ this.extractEntityNames();
1529
+ this.computeDataRanges();
1530
+ this.applyFilters();
1531
+ // Reconnect to any in-progress detection run
1532
+ this.reconnectToActiveRun();
1533
+ }
1534
+ /**
1535
+ * Check if there's an in-progress detection run and reconnect to its
1536
+ * progress subscription. This handles the case where the user navigated
1537
+ * away and came back while a run was active.
1538
+ */
1539
+ reconnectToActiveRun() {
1540
+ if (this.IsDetecting)
1541
+ return; // Already tracking a run
1542
+ const activeRun = this.Runs.find(r => r.ProcessingStatus === 'In Progress');
1543
+ if (!activeRun)
1544
+ return;
1545
+ LogStatus(`[DuplicateDetection] Reconnecting to in-progress run ${activeRun.ID}`);
1546
+ this.IsDetecting = true;
1547
+ this.DetectionProgress = 0;
1548
+ this.DetectionStage = 'Reconnecting...';
1549
+ this.cdr.detectChanges();
1550
+ this.subscribeToPipelineProgress(activeRun.ID);
1551
+ }
1552
+ /**
1553
+ * Trigger a new duplicate detection run by creating a DuplicateRun entity.
1554
+ * The server hook auto-triggers detection when a run is saved with EndedAt === null.
1555
+ */
1556
+ async RunDetection() {
1557
+ if (this.IsDetecting || !this.SelectedEntityDocumentID)
1558
+ return;
1559
+ const selectedDoc = this.EntityDocuments.find(d => UUIDsEqual(d.ID, this.SelectedEntityDocumentID));
1560
+ if (!selectedDoc)
1561
+ return;
1562
+ this.IsDetecting = true;
1563
+ this.DetectionProgress = 0;
1564
+ this.DetectionStage = 'Initializing...';
1565
+ this.DetectionCurrentItem = '';
1566
+ this.cdr.detectChanges();
1567
+ try {
1568
+ const md = new Metadata();
1569
+ const dupeRun = await md.GetEntityObject('MJ: Duplicate Runs');
1570
+ dupeRun.NewRecord();
1571
+ // Look up the EntityID from the entity document's entity name
1572
+ const entityInfo = md.Entities.find(e => e.Name === selectedDoc.EntityName);
1573
+ if (!entityInfo) {
1574
+ MJNotificationService.Instance.CreateSimpleNotification(`Entity "${selectedDoc.EntityName}" not found in metadata`, 'error', 5000);
1575
+ this.IsDetecting = false;
1576
+ this.DetectionStage = '';
1577
+ this.cdr.detectChanges();
1578
+ return;
1579
+ }
1580
+ // DD-1: Track whether merging is available for the selected entity
1581
+ this.MergeEnabled = entityInfo.AllowRecordMerge;
1582
+ this.ShowMergeWarningBanner = !entityInfo.AllowRecordMerge;
1583
+ dupeRun.EntityID = entityInfo.ID;
1584
+ dupeRun.StartedByUserID = new Metadata().CurrentUser.ID;
1585
+ dupeRun.StartedAt = new Date();
1586
+ dupeRun.ProcessingStatus = 'In Progress';
1587
+ dupeRun.ApprovalStatus = 'Pending';
1588
+ const saved = await dupeRun.Save();
1589
+ if (!saved) {
1590
+ console.error('Failed to create duplicate run:', dupeRun.LatestResult?.Message || 'unknown error');
1591
+ MJNotificationService.Instance.CreateSimpleNotification(`Failed to start detection: ${dupeRun.LatestResult?.Message || 'unknown error'}`, 'error', 5000);
1592
+ this.IsDetecting = false;
1593
+ this.DetectionStage = '';
1594
+ this.cdr.detectChanges();
1595
+ return;
1596
+ }
1597
+ // Subscribe to progress using the run ID as PipelineRunID
1598
+ this.subscribeToPipelineProgress(dupeRun.ID);
1599
+ }
1600
+ catch (error) {
1601
+ const msg = error instanceof Error ? error.message : String(error);
1602
+ console.error('[DuplicateDetection] Error starting detection:', msg);
1603
+ this.IsDetecting = false;
1604
+ this.DetectionStage = '';
444
1605
  this.cdr.detectChanges();
445
1606
  }
446
1607
  }
@@ -456,18 +1617,124 @@ let DuplicateDetectionResourceComponent = class DuplicateDetectionResourceCompon
456
1617
  async RejectMatch(group) {
457
1618
  await this.updateGroupApprovalStatus(group, 'Rejected');
458
1619
  }
1620
+ // ════════════════════════════════════════════
1621
+ // Drag and Drop
1622
+ // ════════════════════════════════════════════
1623
+ /** The group currently being dragged */
1624
+ DraggedGroup = null;
1625
+ /** Which column is being dragged over (for highlight) */
1626
+ DragOverColumn = null;
1627
+ OnDragStart(event, group) {
1628
+ this.DraggedGroup = group;
1629
+ if (event.dataTransfer) {
1630
+ event.dataTransfer.effectAllowed = 'move';
1631
+ event.dataTransfer.setData('text/plain', group.DetailId);
1632
+ }
1633
+ // Add a slight delay so the drag ghost renders before we add the dragging class
1634
+ setTimeout(() => this.cdr.detectChanges(), 0);
1635
+ }
1636
+ OnDragEnd() {
1637
+ this.DraggedGroup = null;
1638
+ this.DragOverColumn = null;
1639
+ this.cdr.detectChanges();
1640
+ }
1641
+ OnDragOver(event, column) {
1642
+ event.preventDefault(); // Required to allow drop
1643
+ if (event.dataTransfer) {
1644
+ event.dataTransfer.dropEffect = 'move';
1645
+ }
1646
+ if (this.DragOverColumn !== column) {
1647
+ this.DragOverColumn = column;
1648
+ this.cdr.detectChanges();
1649
+ }
1650
+ }
1651
+ OnDragLeave(event, column) {
1652
+ // Only clear if leaving the column (not entering a child element)
1653
+ const related = event.relatedTarget;
1654
+ const columnEl = event.currentTarget;
1655
+ if (!columnEl.contains(related)) {
1656
+ if (this.DragOverColumn === column) {
1657
+ this.DragOverColumn = null;
1658
+ this.cdr.detectChanges();
1659
+ }
1660
+ }
1661
+ }
1662
+ async OnDrop(event, targetStatus) {
1663
+ event.preventDefault();
1664
+ this.DragOverColumn = null;
1665
+ if (!this.DraggedGroup)
1666
+ return;
1667
+ if (this.DraggedGroup.ApprovalStatus === targetStatus) {
1668
+ this.DraggedGroup = null;
1669
+ this.cdr.detectChanges();
1670
+ return; // Already in this column
1671
+ }
1672
+ const group = this.DraggedGroup;
1673
+ this.DraggedGroup = null;
1674
+ await this.updateGroupApprovalStatus(group, targetStatus);
1675
+ }
1676
+ /** Whether a card is currently being dragged */
1677
+ get IsDragging() {
1678
+ return this.DraggedGroup !== null;
1679
+ }
459
1680
  /** Handle filter changes with debounce */
460
1681
  OnFilterChange() {
461
1682
  this.filterSubject.next();
462
1683
  }
1684
+ /** Computed range bounds from actual data — used as min/max/placeholder for filter inputs */
1685
+ DataMinScore = 0;
1686
+ DataMaxScore = 1;
1687
+ DataMinDate = '';
1688
+ DataMaxDate = '';
1689
+ /** Compute the actual data ranges from AllGroups */
1690
+ computeDataRanges() {
1691
+ if (this.AllGroups.length === 0) {
1692
+ this.DataMinScore = 0;
1693
+ this.DataMaxScore = 1;
1694
+ this.DataMinDate = '';
1695
+ this.DataMaxDate = '';
1696
+ return;
1697
+ }
1698
+ let minScore = 1, maxScore = 0;
1699
+ let minDate = null, maxDate = null;
1700
+ for (const group of this.AllGroups) {
1701
+ if (group.HighestScore < minScore)
1702
+ minScore = group.HighestScore;
1703
+ if (group.HighestScore > maxScore)
1704
+ maxScore = group.HighestScore;
1705
+ const d = new Date(group.MatchedAt);
1706
+ if (!isNaN(d.getTime())) {
1707
+ if (!minDate || d < minDate)
1708
+ minDate = d;
1709
+ if (!maxDate || d > maxDate)
1710
+ maxDate = d;
1711
+ }
1712
+ }
1713
+ this.DataMinScore = Math.floor(minScore * 100) / 100;
1714
+ this.DataMaxScore = Math.ceil(maxScore * 100) / 100;
1715
+ this.DataMinDate = minDate ? this.toInputDate(minDate) : '';
1716
+ this.DataMaxDate = maxDate ? this.toInputDate(maxDate) : '';
1717
+ // Leave filters empty by default — no filtering until user explicitly sets values
1718
+ this.Filters.MinScore = 0;
1719
+ this.Filters.MaxScore = 1;
1720
+ this.Filters.DateFrom = '';
1721
+ this.Filters.DateTo = '';
1722
+ }
1723
+ /** Format a Date to YYYY-MM-DD for input[type=date] using local time */
1724
+ toInputDate(d) {
1725
+ const year = d.getFullYear();
1726
+ const month = String(d.getMonth() + 1).padStart(2, '0');
1727
+ const day = String(d.getDate()).padStart(2, '0');
1728
+ return `${year}-${month}-${day}`;
1729
+ }
463
1730
  /** Clear all filters */
464
1731
  ClearFilters() {
465
1732
  this.Filters = {
466
- EntityName: '',
1733
+ EntityName: this.EntityNames.length === 1 ? this.EntityNames[0] : '',
467
1734
  MinScore: 0,
468
1735
  MaxScore: 1,
469
1736
  DateFrom: '',
470
- DateTo: ''
1737
+ DateTo: '',
471
1738
  };
472
1739
  this.applyFilters();
473
1740
  }
@@ -488,6 +1755,39 @@ let DuplicateDetectionResourceComponent = class DuplicateDetectionResourceCompon
488
1755
  return 'Low';
489
1756
  }
490
1757
  /** Format a date for display */
1758
+ /**
1759
+ * Format a composite key string (e.g., "ID|5A07433E-F36B-1410-8AA5-00F1597429B5")
1760
+ * into a readable format. For single-key entities, shows just the value truncated.
1761
+ * For composite keys, shows key: value pairs.
1762
+ */
1763
+ /** Whether there are any non-skipped matches available for merging */
1764
+ get HasMergeableMatches() {
1765
+ return this.ComparisonMatches.some(m => m.Match.ApprovalStatus !== 'Rejected');
1766
+ }
1767
+ FormatRecordID(recordID) {
1768
+ if (!recordID)
1769
+ return '';
1770
+ const pairs = recordID.split('||');
1771
+ if (pairs.length === 1) {
1772
+ // Single key — extract just the value
1773
+ const parts = pairs[0].split('|');
1774
+ if (parts.length === 2) {
1775
+ const val = parts[1];
1776
+ // Truncate long UUIDs
1777
+ return val.length > 12 ? val.substring(0, 8) + '...' : val;
1778
+ }
1779
+ return recordID.length > 12 ? recordID.substring(0, 8) + '...' : recordID;
1780
+ }
1781
+ // Composite key — show key: truncated value pairs
1782
+ return pairs.map(p => {
1783
+ const parts = p.split('|');
1784
+ if (parts.length === 2) {
1785
+ const val = parts[1].length > 8 ? parts[1].substring(0, 8) + '...' : parts[1];
1786
+ return `${parts[0]}: ${val}`;
1787
+ }
1788
+ return p;
1789
+ }).join(', ');
1790
+ }
491
1791
  FormatDate(date) {
492
1792
  if (!date)
493
1793
  return '';
@@ -496,9 +1796,9 @@ let DuplicateDetectionResourceComponent = class DuplicateDetectionResourceCompon
496
1796
  }
497
1797
  /** Whether any filters are active */
498
1798
  get HasActiveFilters() {
499
- return this.Filters.EntityName !== '' ||
1799
+ return (this.EntityNames.length > 1 && this.Filters.EntityName !== '') ||
500
1800
  this.Filters.MinScore > 0 ||
501
- this.Filters.MaxScore < 1 ||
1801
+ (this.Filters.MaxScore > 0 && this.Filters.MaxScore < 1) ||
502
1802
  this.Filters.DateFrom !== '' ||
503
1803
  this.Filters.DateTo !== '';
504
1804
  }
@@ -508,15 +1808,155 @@ let DuplicateDetectionResourceComponent = class DuplicateDetectionResourceCompon
508
1808
  this.applyFilters();
509
1809
  });
510
1810
  }
1811
+ /** Build entity document options from KnowledgeHubMetadataEngine cached entities */
1812
+ buildEntityDocumentOptionsFromEngine(docs) {
1813
+ this.EntityDocuments = docs.map(d => ({
1814
+ ID: d.ID,
1815
+ Name: d.Name ?? 'Unnamed',
1816
+ EntityName: d.Entity ?? '',
1817
+ PotentialMatchThreshold: this.normalizeDupeThreshold(d.PotentialMatchThreshold, 0.70),
1818
+ AbsoluteMatchThreshold: this.normalizeDupeThreshold(d.AbsoluteMatchThreshold, 0.95)
1819
+ }));
1820
+ // Auto-select the first entity document if available
1821
+ if (this.EntityDocuments.length > 0 && !this.SelectedEntityDocumentID) {
1822
+ this.SelectedEntityDocumentID = this.EntityDocuments[0].ID;
1823
+ }
1824
+ }
1825
+ /** Subscribe to PipelineProgress for a specific detection run */
1826
+ /**
1827
+ * Normalizes a duplicate threshold value — treats null, undefined, 0, and 1.0
1828
+ * as "not configured" and falls back to a sensible default.
1829
+ * Thresholds of exactly 1.0 mean "100% match only" which is effectively useless
1830
+ * for real-world duplicate detection.
1831
+ */
1832
+ normalizeDupeThreshold(value, fallback) {
1833
+ if (value == null || value <= 0 || value >= 1.0) {
1834
+ return fallback;
1835
+ }
1836
+ return value;
1837
+ }
1838
+ /** Handle potential threshold slider change */
1839
+ OnPotentialThresholdChanged(value) {
1840
+ this.RunPotentialThreshold = value;
1841
+ }
1842
+ /** Handle absolute threshold slider change */
1843
+ OnAbsoluteThresholdChanged(value) {
1844
+ this.RunAbsoluteThreshold = value;
1845
+ }
1846
+ subscribeToPipelineProgress(pipelineRunID) {
1847
+ const provider = Metadata.Provider;
1848
+ const subscriptionQuery = `
1849
+ subscription PipelineProgress($pipelineRunID: String!) {
1850
+ PipelineProgress(pipelineRunID: $pipelineRunID) {
1851
+ PipelineRunID
1852
+ Stage
1853
+ TotalItems
1854
+ ProcessedItems
1855
+ CurrentItem
1856
+ PercentComplete
1857
+ ElapsedMs
1858
+ }
1859
+ }
1860
+ `;
1861
+ let idleTimer = null;
1862
+ const finishDetection = (success) => {
1863
+ if (idleTimer)
1864
+ clearTimeout(idleTimer);
1865
+ rxSub?.unsubscribe();
1866
+ Promise.resolve().then(async () => {
1867
+ this.IsDetecting = false;
1868
+ this.DetectionStage = success ? 'Complete' : 'Error';
1869
+ this.DetectionProgress = success ? 100 : 0;
1870
+ if (success) {
1871
+ await this.LoadData();
1872
+ }
1873
+ this.cdr.detectChanges();
1874
+ });
1875
+ };
1876
+ const resetIdleTimer = () => {
1877
+ if (idleTimer)
1878
+ clearTimeout(idleTimer);
1879
+ idleTimer = setTimeout(() => {
1880
+ if (this.IsDetecting) {
1881
+ finishDetection(true);
1882
+ }
1883
+ }, 60000); // 60s timeout for duplicate detection (can be very long)
1884
+ };
1885
+ resetIdleTimer();
1886
+ // Reset phase tracking for this new subscription
1887
+ this.detectionRawStage = '';
1888
+ const sub = provider.subscribe(subscriptionQuery, { pipelineRunID });
1889
+ const rxSub = sub.pipe(takeUntil(this.destroy$)).subscribe({
1890
+ next: (data) => {
1891
+ const progress = data['PipelineProgress'];
1892
+ if (!progress)
1893
+ return;
1894
+ const stage = progress['Stage'];
1895
+ const pct = Math.max(0, Math.min(100, progress['PercentComplete']));
1896
+ const currentItem = progress['CurrentItem'];
1897
+ // Detect phase transitions vs. within-phase updates
1898
+ const isNewPhase = stage !== this.detectionRawStage;
1899
+ if (isNewPhase) {
1900
+ // New phase: reset progress and update display
1901
+ this.detectionRawStage = stage;
1902
+ this.DetectionProgress = pct;
1903
+ this.DetectionStage = this.formatDetectionStage(stage);
1904
+ }
1905
+ else {
1906
+ // Same phase: only move forward (never backward)
1907
+ if (pct >= this.DetectionProgress) {
1908
+ this.DetectionProgress = pct;
1909
+ }
1910
+ }
1911
+ this.DetectionCurrentItem = currentItem ?? '';
1912
+ this.cdr.detectChanges();
1913
+ if (stage === 'complete') {
1914
+ finishDetection(true);
1915
+ }
1916
+ else if (stage === 'error') {
1917
+ finishDetection(false);
1918
+ }
1919
+ else {
1920
+ resetIdleTimer();
1921
+ }
1922
+ },
1923
+ error: (err) => {
1924
+ console.error('[DuplicateDetection] Pipeline subscription error:', err);
1925
+ finishDetection(false);
1926
+ }
1927
+ });
1928
+ }
1929
+ /** Format detection stage names for display */
1930
+ formatDetectionStage(stage) {
1931
+ const stageMap = {
1932
+ 'vectorize': 'Vectorizing records',
1933
+ 'autotag': 'Analyzing matches',
1934
+ 'extract': 'Querying vector database',
1935
+ 'complete': 'Complete',
1936
+ 'error': 'Error'
1937
+ };
1938
+ return stageMap[stage] ?? stage;
1939
+ }
511
1940
  /** Extract unique entity names from loaded runs */
512
1941
  extractEntityNames() {
513
1942
  const nameSet = new Set();
1943
+ // Extract from runs first
514
1944
  for (const run of this.Runs) {
515
1945
  if (run.Entity) {
516
1946
  nameSet.add(run.Entity);
517
1947
  }
518
1948
  }
1949
+ // Also extract from groups (covers cases where runs failed but details/matches exist)
1950
+ for (const group of this.AllGroups) {
1951
+ if (group.EntityName && group.EntityName !== 'Unknown') {
1952
+ nameSet.add(group.EntityName);
1953
+ }
1954
+ }
519
1955
  this.EntityNames = Array.from(nameSet).sort();
1956
+ // Auto-select if only one entity — no point showing "All Entities" for a single option
1957
+ if (this.EntityNames.length === 1) {
1958
+ this.Filters.EntityName = this.EntityNames[0];
1959
+ }
520
1960
  }
521
1961
  /**
522
1962
  * Build DuplicateGroup objects by joining details to their matches,
@@ -545,20 +1985,30 @@ let DuplicateDetectionResourceComponent = class DuplicateDetectionResourceCompon
545
1985
  continue; // Skip details with no matches
546
1986
  }
547
1987
  const run = runMap.get(detail.DuplicateRunID);
548
- const entityName = run?.Entity ?? 'Unknown';
549
1988
  const highestScore = this.computeHighestScore(detailMatches);
550
1989
  const dominantStatus = this.computeDominantApprovalStatus(detailMatches);
551
1990
  const latestMatchDate = this.computeLatestMatchDate(detailMatches);
1991
+ // Parse source record metadata (stored as JSON by the detector)
1992
+ const metadata = this.parseRecordMetadata(detail.RecordMetadata);
1993
+ const entityName = metadata.Entity || run?.Entity || 'Unknown';
1994
+ const entityIcon = metadata.EntityIcon || 'fa-solid fa-database';
1995
+ const recordName = this.resolveRecordName(metadata, entityName, detail.RecordID);
1996
+ // Build top match summaries from match metadata
1997
+ const topMatchSummaries = this.buildTopMatchSummaries(detailMatches, 3);
552
1998
  this.AllGroups.push({
553
1999
  DetailId: detail.ID,
554
2000
  RunId: detail.DuplicateRunID,
555
2001
  RecordId: detail.RecordID,
556
2002
  EntityName: entityName,
2003
+ EntityIcon: entityIcon,
2004
+ RecordName: recordName,
557
2005
  ApprovalStatus: dominantStatus,
558
2006
  MatchCount: detailMatches.length,
559
2007
  HighestScore: highestScore,
560
2008
  Matches: detailMatches,
561
- MatchedAt: latestMatchDate
2009
+ MatchedAt: latestMatchDate,
2010
+ Metadata: metadata,
2011
+ TopMatchSummaries: topMatchSummaries,
562
2012
  });
563
2013
  }
564
2014
  }
@@ -572,6 +2022,689 @@ let DuplicateDetectionResourceComponent = class DuplicateDetectionResourceCompon
572
2022
  }
573
2023
  return max;
574
2024
  }
2025
+ // ════════════════════════════════════════════
2026
+ // Comparison Panel
2027
+ // ════════════════════════════════════════════
2028
+ /** Open the comparison slide-in panel for a group — loads real entity records */
2029
+ async OpenComparison(group) {
2030
+ this.ComparisonGroup = group;
2031
+ this.ComparisonShowAllFields = true;
2032
+ this.SurvivorColumnIndex = 0;
2033
+ this.ComparisonClosing = false;
2034
+ this.ComparisonLoading = true;
2035
+ this.ComparisonFields = [];
2036
+ this.ComparisonMatches = [];
2037
+ this.comparisonRecords.clear();
2038
+ this.ComparisonDependencies.clear();
2039
+ this.DepsExpandedColumns.clear();
2040
+ this.depsEntityGroupExpanded.clear();
2041
+ this.ShowMergeConfirm = false;
2042
+ this.cdr.detectChanges();
2043
+ // Load actual entity records and dependencies in parallel
2044
+ await Promise.all([
2045
+ this.loadComparisonRecords(group),
2046
+ this.loadComparisonDependencies(group)
2047
+ ]);
2048
+ this.buildComparisonData();
2049
+ this.ComparisonLoading = false;
2050
+ this.cdr.detectChanges();
2051
+ }
2052
+ /** Close the comparison panel with slide-out animation */
2053
+ CloseComparison() {
2054
+ this.ShowMergeConfirm = false;
2055
+ this.ComparisonClosing = true;
2056
+ this.cdr.detectChanges();
2057
+ setTimeout(() => {
2058
+ this.ComparisonGroup = null;
2059
+ this.ComparisonClosing = false;
2060
+ this.ComparisonFields = [];
2061
+ this.ComparisonMatches = [];
2062
+ this.ComparisonDependencies.clear();
2063
+ this.DepsExpandedColumns.clear();
2064
+ this.depsEntityGroupExpanded.clear();
2065
+ this.cdr.detectChanges();
2066
+ }, 250);
2067
+ }
2068
+ /** Get visible fields based on the toggle state */
2069
+ GetVisibleFields() {
2070
+ return this.ComparisonShowAllFields
2071
+ ? this.ComparisonFields
2072
+ : this.ComparisonFields.filter(f => f.HasDifference);
2073
+ }
2074
+ /** Case-insensitive, trimmed comparison of two field values */
2075
+ AreValuesEqual(a, b) {
2076
+ if (a == null && b == null)
2077
+ return true;
2078
+ if (a == null || b == null)
2079
+ return false;
2080
+ return a.trim().toLowerCase() === b.trim().toLowerCase();
2081
+ }
2082
+ /** Set the surviving record column index */
2083
+ SetSurvivor(columnIndex) {
2084
+ this.SurvivorColumnIndex = columnIndex;
2085
+ // When switching survivor, reset all field selections to the new survivor
2086
+ for (const field of this.ComparisonFields) {
2087
+ field.SelectedColumnIndex = columnIndex;
2088
+ }
2089
+ this.cdr.detectChanges();
2090
+ }
2091
+ /** Select all field values from a specific column */
2092
+ UseAllFieldsFrom(columnIndex) {
2093
+ for (const field of this.ComparisonFields) {
2094
+ // Only select if the column has a value for this field
2095
+ const val = columnIndex === 0 ? field.SourceValue : field.MatchValues[columnIndex - 1];
2096
+ if (val != null) {
2097
+ field.SelectedColumnIndex = columnIndex;
2098
+ }
2099
+ }
2100
+ this.cdr.detectChanges();
2101
+ }
2102
+ /** Select a specific field value from a column */
2103
+ SelectFieldValue(field, columnIndex) {
2104
+ field.SelectedColumnIndex = columnIndex;
2105
+ this.cdr.detectChanges();
2106
+ }
2107
+ /** Check if all fields are selected from a given column */
2108
+ AllFieldsSelectedFrom(columnIndex) {
2109
+ return this.ComparisonFields.every(f => f.SelectedColumnIndex === columnIndex);
2110
+ }
2111
+ /** Count how many fields are cherry-picked from non-survivor columns */
2112
+ CherryPickedCount() {
2113
+ return this.ComparisonFields.filter(f => f.SelectedColumnIndex !== this.SurvivorColumnIndex).length;
2114
+ }
2115
+ /** Get the name of the surviving record */
2116
+ SurvivorName() {
2117
+ if (!this.ComparisonGroup)
2118
+ return '';
2119
+ if (this.SurvivorColumnIndex === 0)
2120
+ return this.ComparisonGroup.RecordName;
2121
+ const match = this.ComparisonMatches[this.SurvivorColumnIndex - 1];
2122
+ return match?.Name || 'Unknown';
2123
+ }
2124
+ /** Get the primary key display string for the surviving record */
2125
+ SurvivorKeyDisplay() {
2126
+ if (!this.ComparisonGroup)
2127
+ return '';
2128
+ const keyStr = this.getCompositeKeyStringForColumn(this.SurvivorColumnIndex);
2129
+ if (!keyStr)
2130
+ return '';
2131
+ const ck = new CompositeKey();
2132
+ ck.LoadFromConcatenatedString(keyStr);
2133
+ return ck.KeyValuePairs.map(kv => `${kv.FieldName}: ${kv.Value}`).join(', ');
2134
+ }
2135
+ // ════════════════════════════════════════════
2136
+ // Dependencies
2137
+ // ════════════════════════════════════════════
2138
+ /** Get dependencies for a column (0 = source, 1+ = match index) */
2139
+ GetDepsForColumn(columnIndex) {
2140
+ const keyStr = this.getCompositeKeyStringForColumn(columnIndex);
2141
+ return keyStr ? (this.ComparisonDependencies.get(keyStr) ?? []) : [];
2142
+ }
2143
+ /** Get deps grouped by related entity for a column */
2144
+ GetGroupedDeps(columnIndex) {
2145
+ const deps = this.GetDepsForColumn(columnIndex);
2146
+ const grouped = new Map();
2147
+ for (const dep of deps) {
2148
+ const name = dep.RelatedEntityName;
2149
+ grouped.set(name, (grouped.get(name) ?? 0) + 1);
2150
+ }
2151
+ return Array.from(grouped.entries())
2152
+ .map(([Entity, Count]) => ({ Entity, Count }))
2153
+ .sort((a, b) => b.Count - a.Count);
2154
+ }
2155
+ /** Cached dependent records loaded on demand, keyed by "columnIndex::entityName" */
2156
+ depRecordsCache = new Map();
2157
+ /** Tracks which entity groups are currently loading */
2158
+ depRecordsLoading = new Set();
2159
+ /** Get cached dependent records for an entity group (empty until expanded and loaded) */
2160
+ GetDepRecords(columnIndex, entityName) {
2161
+ return this.depRecordsCache.get(`${columnIndex}::${entityName}`) ?? [];
2162
+ }
2163
+ /** Check if dep records are loading for an entity group */
2164
+ IsDepRecordsLoading(columnIndex, entityName) {
2165
+ return this.depRecordsLoading.has(`${columnIndex}::${entityName}`);
2166
+ }
2167
+ /** Navigate to a dependent record */
2168
+ OpenDepRecord(record) {
2169
+ if (record.PrimaryKey) {
2170
+ this.navigationService.OpenEntityRecord(record.EntityName, record.PrimaryKey);
2171
+ }
2172
+ }
2173
+ /** Toggle expanded state for a specific entity group — lazy-loads records on first expand */
2174
+ async ToggleDepEntityGroup(columnIndex, entityName) {
2175
+ const key = `${columnIndex}::${entityName}`;
2176
+ if (this.depsEntityGroupExpanded.has(key)) {
2177
+ this.depsEntityGroupExpanded.delete(key);
2178
+ this.cdr.detectChanges();
2179
+ return;
2180
+ }
2181
+ this.depsEntityGroupExpanded.add(key);
2182
+ this.cdr.detectChanges();
2183
+ // Lazy-load actual dependent records on first expand
2184
+ if (!this.depRecordsCache.has(key)) {
2185
+ await this.loadDepRecordsForGroup(columnIndex, entityName);
2186
+ }
2187
+ }
2188
+ /** Load dependent records for an entity group via RunView */
2189
+ async loadDepRecordsForGroup(columnIndex, relatedEntityName) {
2190
+ const key = `${columnIndex}::${relatedEntityName}`;
2191
+ this.depRecordsLoading.add(key);
2192
+ this.cdr.detectChanges();
2193
+ try {
2194
+ // Get the FK field name from the dependency info
2195
+ const deps = this.GetDepsForColumn(columnIndex)
2196
+ .filter(d => d.RelatedEntityName === relatedEntityName);
2197
+ if (deps.length === 0)
2198
+ return;
2199
+ const dep = deps[0];
2200
+ const fkFieldName = dep.FieldName;
2201
+ // Get the parent record's primary key value from the column's composite key string
2202
+ const keyStr = this.getCompositeKeyStringForColumn(columnIndex);
2203
+ if (!keyStr)
2204
+ return;
2205
+ const parentCK = new CompositeKey();
2206
+ parentCK.LoadFromConcatenatedString(keyStr);
2207
+ const parentKeyValue = parentCK.KeyValuePairs[0]?.Value;
2208
+ if (!parentKeyValue)
2209
+ return;
2210
+ // Query the related entity for records pointing at this parent via the FK field
2211
+ const rv = new RunView();
2212
+ const md = new Metadata();
2213
+ const relatedEntityInfo = md.Entities.find(e => e.Name === relatedEntityName);
2214
+ const nameField = relatedEntityInfo?.NameField;
2215
+ const pkFieldName = relatedEntityInfo?.FirstPrimaryKey?.Name || 'ID';
2216
+ const result = await rv.RunView({
2217
+ EntityName: relatedEntityName,
2218
+ ExtraFilter: `${fkFieldName}='${parentKeyValue}'`,
2219
+ Fields: nameField ? [pkFieldName, nameField.Name] : [pkFieldName],
2220
+ MaxRows: 50,
2221
+ ResultType: 'simple',
2222
+ });
2223
+ const records = [];
2224
+ if (result.Success && result.Results) {
2225
+ for (const row of result.Results) {
2226
+ const pk = new CompositeKey([{ FieldName: pkFieldName, Value: String(row[pkFieldName] || '') }]);
2227
+ const name = nameField ? String(row[nameField.Name] || '') : String(row[pkFieldName] || '');
2228
+ records.push({ Name: name || pk.Values(), PrimaryKey: pk, EntityName: relatedEntityName });
2229
+ }
2230
+ }
2231
+ this.depRecordsCache.set(key, records);
2232
+ }
2233
+ catch (error) {
2234
+ console.warn('[DuplicateDetection] Error loading dep records:', error);
2235
+ this.depRecordsCache.set(key, []);
2236
+ }
2237
+ finally {
2238
+ this.depRecordsLoading.delete(key);
2239
+ this.cdr.detectChanges();
2240
+ }
2241
+ }
2242
+ /** Check if a specific entity group is expanded */
2243
+ IsDepEntityGroupExpanded(columnIndex, entityName) {
2244
+ return this.depsEntityGroupExpanded.has(`${columnIndex}::${entityName}`);
2245
+ }
2246
+ /** Get total dependency count for a column */
2247
+ GetTotalDeps(columnIndex) {
2248
+ return this.GetDepsForColumn(columnIndex).length;
2249
+ }
2250
+ /** Toggle expanded state for dependency details in a column */
2251
+ ToggleDepsExpanded(columnIndex) {
2252
+ if (this.DepsExpandedColumns.has(columnIndex)) {
2253
+ this.DepsExpandedColumns.delete(columnIndex);
2254
+ }
2255
+ else {
2256
+ this.DepsExpandedColumns.add(columnIndex);
2257
+ }
2258
+ this.cdr.detectChanges();
2259
+ }
2260
+ /** Check if dependency details are expanded for a column */
2261
+ IsDepsExpanded(columnIndex) {
2262
+ return this.DepsExpandedColumns.has(columnIndex);
2263
+ }
2264
+ /** Get the column index with the most dependencies */
2265
+ GetMaxDepsColumnIndex() {
2266
+ let maxDeps = -1;
2267
+ let maxIndex = 0;
2268
+ const totalColumns = 1 + this.ComparisonMatches.length;
2269
+ for (let i = 0; i < totalColumns; i++) {
2270
+ const count = this.GetTotalDeps(i);
2271
+ if (count > maxDeps) {
2272
+ maxDeps = count;
2273
+ maxIndex = i;
2274
+ }
2275
+ }
2276
+ return maxIndex;
2277
+ }
2278
+ /** Get the composite key string for a column index */
2279
+ getCompositeKeyStringForColumn(columnIndex) {
2280
+ if (!this.ComparisonGroup)
2281
+ return null;
2282
+ if (columnIndex === 0)
2283
+ return this.ComparisonGroup.RecordId;
2284
+ const match = this.ComparisonMatches[columnIndex - 1];
2285
+ return match?.Match.MatchRecordID ?? null;
2286
+ }
2287
+ /** Get the name for a column index */
2288
+ GetColumnName(columnIndex) {
2289
+ if (!this.ComparisonGroup)
2290
+ return '';
2291
+ if (columnIndex === 0)
2292
+ return this.ComparisonGroup.RecordName;
2293
+ const match = this.ComparisonMatches[columnIndex - 1];
2294
+ return match?.Name ?? 'Unknown';
2295
+ }
2296
+ // ════════════════════════════════════════════
2297
+ // ════════════════════════════════════════════
2298
+ // Merge Confirmation
2299
+ // ════════════════════════════════════════════
2300
+ /** Open the merge confirmation panel */
2301
+ OpenMergeConfirm() {
2302
+ this.ShowMergeConfirm = true;
2303
+ this.cdr.detectChanges();
2304
+ }
2305
+ /** Close the merge confirmation panel */
2306
+ CloseMergeConfirm() {
2307
+ this.ShowMergeConfirm = false;
2308
+ this.cdr.detectChanges();
2309
+ }
2310
+ /** Get the list of cherry-picked field overrides (fields picked from non-survivor columns) */
2311
+ GetCherryPickedFields() {
2312
+ return this.ComparisonFields
2313
+ .filter(f => f.SelectedColumnIndex !== this.SurvivorColumnIndex)
2314
+ .map(f => {
2315
+ const value = f.SelectedColumnIndex === 0
2316
+ ? f.SourceValue
2317
+ : f.MatchValues[f.SelectedColumnIndex - 1];
2318
+ return {
2319
+ FieldName: f.FieldName,
2320
+ DisplayName: f.DisplayName,
2321
+ Value: value ?? '(empty)',
2322
+ SourceName: this.GetColumnName(f.SelectedColumnIndex)
2323
+ };
2324
+ });
2325
+ }
2326
+ /** Get non-surviving columns with their dependency counts */
2327
+ /** Get non-surviving columns excluding skipped (Rejected) matches for merge confirmation display */
2328
+ GetNonSurvivorColumns() {
2329
+ const result = [];
2330
+ const totalColumns = 1 + this.ComparisonMatches.length;
2331
+ for (let i = 0; i < totalColumns; i++) {
2332
+ if (i === this.SurvivorColumnIndex)
2333
+ continue;
2334
+ // Skip records marked as Rejected/Skipped
2335
+ if (i > 0) {
2336
+ const matchInfo = this.ComparisonMatches[i - 1];
2337
+ if (matchInfo?.Match?.ApprovalStatus === 'Rejected')
2338
+ continue;
2339
+ }
2340
+ result.push({
2341
+ ColumnIndex: i,
2342
+ Name: this.GetColumnName(i),
2343
+ DepCount: this.GetTotalDeps(i)
2344
+ });
2345
+ }
2346
+ return result;
2347
+ }
2348
+ /** Execute the merge operation */
2349
+ async ExecuteMerge() {
2350
+ if (!this.ComparisonGroup || this.IsMerging)
2351
+ return;
2352
+ this.IsMerging = true;
2353
+ this.cdr.detectChanges();
2354
+ try {
2355
+ const request = new RecordMergeRequest();
2356
+ request.EntityName = this.ComparisonGroup.EntityName;
2357
+ // Build surviving record composite key
2358
+ const survivorKeyStr = this.getCompositeKeyStringForColumn(this.SurvivorColumnIndex);
2359
+ if (!survivorKeyStr)
2360
+ return;
2361
+ const survivorKey = new CompositeKey();
2362
+ survivorKey.SimpleLoadFromURLSegment(survivorKeyStr);
2363
+ request.SurvivingRecordCompositeKey = survivorKey;
2364
+ // Build records to merge (non-survivors)
2365
+ request.RecordsToMerge = this.buildNonSurvivorKeys();
2366
+ // Build field map for cherry-picked fields
2367
+ const cherryPicked = this.GetCherryPickedFields();
2368
+ if (cherryPicked.length > 0) {
2369
+ request.FieldMap = cherryPicked.map(f => ({
2370
+ FieldName: f.FieldName,
2371
+ Value: f.Value
2372
+ }));
2373
+ }
2374
+ const result = await Metadata.Provider.MergeRecords(request);
2375
+ if (result.Success) {
2376
+ MJNotificationService.Instance.CreateSimpleNotification(`Successfully merged ${request.RecordsToMerge.length + 1} records into one.`, 'success', 5000);
2377
+ // Close both panels and reload
2378
+ this.ShowMergeConfirm = false;
2379
+ this.ComparisonGroup = null;
2380
+ this.ComparisonFields = [];
2381
+ this.ComparisonMatches = [];
2382
+ this.ComparisonDependencies.clear();
2383
+ this.DepsExpandedColumns.clear();
2384
+ this.depsEntityGroupExpanded.clear();
2385
+ await this.LoadData();
2386
+ }
2387
+ else {
2388
+ MJNotificationService.Instance.CreateSimpleNotification(`Merge failed: ${result.OverallStatus}`, 'error', 5000);
2389
+ }
2390
+ }
2391
+ catch (error) {
2392
+ const msg = error instanceof Error ? error.message : String(error);
2393
+ console.error('[DuplicateDetection] Merge error:', msg);
2394
+ MJNotificationService.Instance.CreateSimpleNotification(`Merge error: ${msg}`, 'error', 5000);
2395
+ }
2396
+ finally {
2397
+ this.IsMerging = false;
2398
+ this.cdr.detectChanges();
2399
+ }
2400
+ }
2401
+ /** Build CompositeKey array for non-surviving records */
2402
+ buildNonSurvivorKeys() {
2403
+ const keys = [];
2404
+ const totalColumns = 1 + this.ComparisonMatches.length;
2405
+ for (let i = 0; i < totalColumns; i++) {
2406
+ if (i === this.SurvivorColumnIndex)
2407
+ continue;
2408
+ // Skip records that the user has marked as "Skipped" (Rejected)
2409
+ if (i > 0) {
2410
+ const matchInfo = this.ComparisonMatches[i - 1];
2411
+ if (matchInfo?.Match?.ApprovalStatus === 'Rejected')
2412
+ continue;
2413
+ }
2414
+ const keyStr = this.getCompositeKeyStringForColumn(i);
2415
+ if (keyStr) {
2416
+ const ck = new CompositeKey();
2417
+ ck.SimpleLoadFromURLSegment(keyStr);
2418
+ keys.push(ck);
2419
+ }
2420
+ }
2421
+ return keys;
2422
+ }
2423
+ /** Approve an individual match */
2424
+ async ApproveIndividualMatch(matchInfo) {
2425
+ this.IsSaving = true;
2426
+ matchInfo.Match.ApprovalStatus = 'Approved';
2427
+ await matchInfo.Match.Save();
2428
+ this.IsSaving = false;
2429
+ this.cdr.detectChanges();
2430
+ }
2431
+ /** Reject an individual match (skip it from merge) */
2432
+ async RejectIndividualMatch(matchInfo) {
2433
+ this.IsSaving = true;
2434
+ matchInfo.Match.ApprovalStatus = 'Rejected';
2435
+ await matchInfo.Match.Save();
2436
+ this.IsSaving = false;
2437
+ this.cdr.detectChanges();
2438
+ }
2439
+ /** Undo a rejected individual match (restore to Pending) */
2440
+ async UndoRejectIndividualMatch(matchInfo) {
2441
+ this.IsSaving = true;
2442
+ matchInfo.Match.ApprovalStatus = 'Pending';
2443
+ await matchInfo.Match.Save();
2444
+ this.IsSaving = false;
2445
+ this.cdr.detectChanges();
2446
+ }
2447
+ /**
2448
+ * Load the actual entity records for the source + all matches in one RunView call.
2449
+ * Record IDs are stored as composite key strings (e.g., "ID|uuid") — we parse each
2450
+ * into a CompositeKey, generate WHERE clauses, and OR them together for one query.
2451
+ * Results stored in comparisonRecords map keyed by the composite key string.
2452
+ */
2453
+ async loadComparisonRecords(group) {
2454
+ this.comparisonRecords.clear();
2455
+ // Collect all composite key strings (source + matches)
2456
+ const keyStrings = [group.RecordId];
2457
+ for (const m of group.Matches) {
2458
+ if (m.MatchRecordID) {
2459
+ keyStrings.push(m.MatchRecordID);
2460
+ }
2461
+ }
2462
+ // Parse each into a CompositeKey and build WHERE clauses
2463
+ const whereClauses = [];
2464
+ for (const keyStr of keyStrings) {
2465
+ const ck = new CompositeKey();
2466
+ ck.SimpleLoadFromURLSegment(keyStr);
2467
+ if (ck.KeyValuePairs.length > 0) {
2468
+ whereClauses.push(`(${ck.ToWhereClause()})`);
2469
+ }
2470
+ }
2471
+ if (whereClauses.length === 0)
2472
+ return;
2473
+ // Single RunView with all records OR'd together
2474
+ const rv = new RunView();
2475
+ const result = await rv.RunView({
2476
+ EntityName: group.EntityName,
2477
+ ExtraFilter: whereClauses.join(' OR '),
2478
+ ResultType: 'simple',
2479
+ });
2480
+ if (result.Success && result.Results) {
2481
+ // Get entity info to know primary key field name
2482
+ const md = new Metadata();
2483
+ const entityInfo = md.Entities.find(e => e.Name === group.EntityName);
2484
+ const pkFieldName = entityInfo?.FirstPrimaryKey?.Name || 'ID';
2485
+ for (const record of result.Results) {
2486
+ const pkValue = String(record[pkFieldName] || '');
2487
+ // Store keyed by both raw PK value and the composite key string format
2488
+ this.comparisonRecords.set(pkValue, record);
2489
+ this.comparisonRecords.set(`${pkFieldName}|${pkValue}`, record);
2490
+ }
2491
+ }
2492
+ }
2493
+ /**
2494
+ * Load dependencies for all records (source + matches) in parallel.
2495
+ * Each record's deps are stored in ComparisonDependencies keyed by composite key string.
2496
+ */
2497
+ async loadComparisonDependencies(group) {
2498
+ const provider = Metadata.Provider;
2499
+ const keyStrings = [group.RecordId];
2500
+ for (const m of group.Matches) {
2501
+ if (m.MatchRecordID) {
2502
+ keyStrings.push(m.MatchRecordID);
2503
+ }
2504
+ }
2505
+ const promises = keyStrings.map(async (keyStr) => {
2506
+ const ck = new CompositeKey();
2507
+ ck.SimpleLoadFromURLSegment(keyStr);
2508
+ if (ck.KeyValuePairs.length === 0)
2509
+ return;
2510
+ try {
2511
+ const deps = await provider.GetRecordDependencies(group.EntityName, ck);
2512
+ this.ComparisonDependencies.set(keyStr, deps);
2513
+ }
2514
+ catch (error) {
2515
+ console.warn(`[DuplicateDetection] Failed to load deps for ${keyStr}:`, error);
2516
+ this.ComparisonDependencies.set(keyStr, []);
2517
+ }
2518
+ });
2519
+ await Promise.all(promises);
2520
+ }
2521
+ /** Get the field value for a record from the loaded entity records map */
2522
+ getRecordFieldValue(recordId, fieldName) {
2523
+ // Try the composite key string directly (e.g., "ID|uuid")
2524
+ let record = this.comparisonRecords.get(recordId);
2525
+ if (!record) {
2526
+ // Try extracting just the value from "FieldName|Value" format
2527
+ const parts = recordId.split('|');
2528
+ const id = parts.length >= 2 ? parts[1] : parts[0];
2529
+ record = this.comparisonRecords.get(id);
2530
+ }
2531
+ if (!record) {
2532
+ // Case-insensitive search as a fallback (UUIDs may differ in case)
2533
+ const lower = recordId.toLowerCase();
2534
+ for (const [key, val] of this.comparisonRecords.entries()) {
2535
+ if (key.toLowerCase() === lower || key.toLowerCase().endsWith(lower)) {
2536
+ record = val;
2537
+ break;
2538
+ }
2539
+ }
2540
+ }
2541
+ if (!record)
2542
+ return undefined;
2543
+ const val = record[fieldName];
2544
+ return val != null ? String(val) : undefined;
2545
+ }
2546
+ /** Build comparison data from loaded entity records */
2547
+ buildComparisonData() {
2548
+ if (!this.ComparisonGroup)
2549
+ return;
2550
+ // Build match infos (use loaded record data for names)
2551
+ this.ComparisonMatches = this.ComparisonGroup.Matches
2552
+ .sort((a, b) => b.MatchProbability - a.MatchProbability)
2553
+ .map(m => {
2554
+ const meta = this.parseRecordMetadata(m.RecordMetadata);
2555
+ // Resolve display name using IsNameField fields from entity metadata
2556
+ const entityName = this.ComparisonGroup.EntityName;
2557
+ const resolvedName = this.resolveMatchName(entityName, m.MatchRecordID, meta);
2558
+ return {
2559
+ Match: m,
2560
+ Name: resolvedName,
2561
+ Score: m.MatchProbability,
2562
+ Metadata: meta,
2563
+ DiffCount: 0,
2564
+ };
2565
+ });
2566
+ // Get entity field info for display names and ordering
2567
+ const md = new Metadata();
2568
+ const entityInfo = md.Entities.find(e => e.Name === this.ComparisonGroup.EntityName);
2569
+ const entityFields = entityInfo?.Fields ?? [];
2570
+ const skip = new Set(['ID', '__mj_CreatedAt', '__mj_UpdatedAt']);
2571
+ const rows = [];
2572
+ // Use entity fields in sequence order — values come from loaded entity records
2573
+ const sourceId = this.ComparisonGroup.RecordId;
2574
+ const matchRecordIds = this.ComparisonGroup.Matches
2575
+ .sort((a, b) => b.MatchProbability - a.MatchProbability)
2576
+ .map(m => m.MatchRecordID);
2577
+ // Sort fields: IsNameField first, then DefaultInView, then by Sequence
2578
+ const sortedFields = [...entityFields].sort((a, b) => {
2579
+ if (a.IsNameField !== b.IsNameField)
2580
+ return a.IsNameField ? -1 : 1;
2581
+ if (a.DefaultInView !== b.DefaultInView)
2582
+ return a.DefaultInView ? -1 : 1;
2583
+ return (a.Sequence ?? 9999) - (b.Sequence ?? 9999);
2584
+ });
2585
+ for (const field of sortedFields) {
2586
+ if (skip.has(field.Name) || field.IsPrimaryKey)
2587
+ continue;
2588
+ const sourceVal = this.getRecordFieldValue(sourceId, field.Name);
2589
+ const matchVals = matchRecordIds.map(rid => this.getRecordFieldValue(rid, field.Name));
2590
+ const hasDiff = matchVals.some(mv => !this.AreValuesEqual(sourceVal, mv));
2591
+ const hasData = sourceVal != null || matchVals.some(v => v != null);
2592
+ if (!hasData)
2593
+ continue;
2594
+ rows.push({
2595
+ FieldName: field.Name,
2596
+ DisplayName: field.DisplayName || field.Name,
2597
+ Category: field.Category || null,
2598
+ SourceValue: sourceVal,
2599
+ MatchValues: matchVals,
2600
+ HasDifference: hasDiff,
2601
+ SelectedColumnIndex: 0,
2602
+ });
2603
+ }
2604
+ this.ComparisonFields = rows;
2605
+ // Compute diff counts per match
2606
+ for (let mi = 0; mi < this.ComparisonMatches.length; mi++) {
2607
+ this.ComparisonMatches[mi].DiffCount = rows.filter(r => !this.AreValuesEqual(r.SourceValue, r.MatchValues[mi])).length;
2608
+ }
2609
+ }
2610
+ /** Parse RecordMetadata JSON from a detail or match entity */
2611
+ parseRecordMetadata(json) {
2612
+ if (!json)
2613
+ return {};
2614
+ try {
2615
+ return JSON.parse(json);
2616
+ }
2617
+ catch {
2618
+ return {};
2619
+ }
2620
+ }
2621
+ /** Build top N match summaries with parsed names and scores */
2622
+ buildTopMatchSummaries(matches, limit) {
2623
+ return matches
2624
+ .slice(0, limit)
2625
+ .map(m => {
2626
+ const meta = this.parseRecordMetadata(m.RecordMetadata);
2627
+ return {
2628
+ Name: this.resolveRecordName(meta, this.SelectedEntityFilter || 'Unknown', m.MatchRecordID ?? ''),
2629
+ Score: m.MatchProbability,
2630
+ };
2631
+ });
2632
+ }
2633
+ /**
2634
+ * Resolve match record name from loaded entity records using IsNameField fields.
2635
+ * Falls back to metadata, then truncated record ID.
2636
+ */
2637
+ resolveMatchName(entityName, matchRecordID, meta) {
2638
+ try {
2639
+ const md = new Metadata();
2640
+ const entityInfo = md.Entities.find(e => e.Name === entityName);
2641
+ if (entityInfo) {
2642
+ const nameFields = entityInfo.Fields
2643
+ .filter(f => f.IsNameField)
2644
+ .sort((a, b) => (a.Sequence ?? 9999) - (b.Sequence ?? 9999));
2645
+ if (nameFields.length > 0) {
2646
+ // Try loaded entity record data first
2647
+ const parts = nameFields
2648
+ .map(f => this.getRecordFieldValue(matchRecordID, f.Name))
2649
+ .filter(v => v != null && String(v).trim() !== '')
2650
+ .map(v => String(v));
2651
+ if (parts.length > 0)
2652
+ return parts.join(' ');
2653
+ // Fall back to vector metadata
2654
+ const metaParts = nameFields
2655
+ .map(f => meta[f.Name])
2656
+ .filter(v => v != null && String(v).trim() !== '')
2657
+ .map(v => String(v));
2658
+ if (metaParts.length > 0)
2659
+ return metaParts.join(' ');
2660
+ }
2661
+ }
2662
+ }
2663
+ catch { /* fall through */ }
2664
+ return meta.Name || this.FormatRecordID(matchRecordID ?? '');
2665
+ }
2666
+ /**
2667
+ * Resolve record display name from metadata using entity IsNameField fields.
2668
+ * Combines multiple name fields (e.g., FirstName + LastName → "Sarah Chen").
2669
+ * Falls back to metadata.Name, then recordID.
2670
+ */
2671
+ resolveRecordName(metadata, entityName, recordID) {
2672
+ try {
2673
+ const md = new Metadata();
2674
+ const entityInfo = md.Entities.find(e => e.Name === entityName);
2675
+ if (entityInfo) {
2676
+ const nameFields = entityInfo.Fields
2677
+ .filter(f => f.IsNameField)
2678
+ .sort((a, b) => (a.Sequence ?? 9999) - (b.Sequence ?? 9999));
2679
+ if (nameFields.length > 0) {
2680
+ // 1. Try individual name fields from metadata (new rich metadata)
2681
+ const metaParts = nameFields
2682
+ .map(f => metadata[f.Name])
2683
+ .filter(v => v != null && String(v).trim() !== '')
2684
+ .map(v => String(v));
2685
+ if (metaParts.length > 0)
2686
+ return metaParts.join(' ');
2687
+ // 2. Try loaded entity records (available in comparison panel)
2688
+ const recordParts = nameFields
2689
+ .map(f => this.getRecordFieldValue(recordID, f.Name))
2690
+ .filter(v => v != null && String(v).trim() !== '')
2691
+ .map(v => String(v));
2692
+ if (recordParts.length > 0)
2693
+ return recordParts.join(' ');
2694
+ }
2695
+ // 3. Single NameField fallback
2696
+ if (entityInfo.NameField && metadata[entityInfo.NameField.Name]) {
2697
+ return String(metadata[entityInfo.NameField.Name]);
2698
+ }
2699
+ }
2700
+ }
2701
+ catch { /* fall through */ }
2702
+ // 4. Heuristic — skip single-char or initial-only names from old sparse metadata
2703
+ const metaName = metadata.Name;
2704
+ if (metaName && metaName.length > 2)
2705
+ return metaName;
2706
+ return metadata.Title || this.FormatRecordID(recordID);
2707
+ }
575
2708
  /**
576
2709
  * Determine the dominant approval status for a group of matches.
577
2710
  * If any match is Pending, the group is Pending.
@@ -616,15 +2749,17 @@ let DuplicateDetectionResourceComponent = class DuplicateDetectionResourceCompon
616
2749
  if (this.Filters.MinScore > 0) {
617
2750
  filtered = filtered.filter(g => g.HighestScore >= this.Filters.MinScore);
618
2751
  }
619
- if (this.Filters.MaxScore < 1) {
2752
+ if (this.Filters.MaxScore > 0 && this.Filters.MaxScore < 1) {
620
2753
  filtered = filtered.filter(g => g.HighestScore <= this.Filters.MaxScore);
621
2754
  }
622
2755
  if (this.Filters.DateFrom) {
623
- const from = new Date(this.Filters.DateFrom);
2756
+ const parts = this.Filters.DateFrom.split('-');
2757
+ const from = new Date(+parts[0], +parts[1] - 1, +parts[2], 0, 0, 0, 0);
624
2758
  filtered = filtered.filter(g => new Date(g.MatchedAt) >= from);
625
2759
  }
626
2760
  if (this.Filters.DateTo) {
627
- const to = new Date(this.Filters.DateTo);
2761
+ const parts = this.Filters.DateTo.split('-');
2762
+ const to = new Date(+parts[0], +parts[1] - 1, +parts[2], 23, 59, 59, 999);
628
2763
  filtered = filtered.filter(g => new Date(g.MatchedAt) <= to);
629
2764
  }
630
2765
  this.PendingGroups = filtered.filter(g => g.ApprovalStatus === 'Pending');
@@ -632,10 +2767,7 @@ let DuplicateDetectionResourceComponent = class DuplicateDetectionResourceCompon
632
2767
  this.RejectedGroups = filtered.filter(g => g.ApprovalStatus === 'Rejected');
633
2768
  this.cdr.detectChanges();
634
2769
  }
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
- */
2770
+ /** Update the ApprovalStatus of all matches within a group and re-sort. */
639
2771
  async updateGroupApprovalStatus(group, status) {
640
2772
  this.IsSaving = true;
641
2773
  this.cdr.detectChanges();
@@ -657,78 +2789,107 @@ let DuplicateDetectionResourceComponent = class DuplicateDetectionResourceCompon
657
2789
  }
658
2790
  }
659
2791
  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) {
2792
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: DuplicateDetectionResourceComponent, selectors: [["app-duplicate-detection-resource"]], hostBindings: function DuplicateDetectionResourceComponent_HostBindings(rf, ctx) { if (rf & 1) {
2793
+ i0.ɵɵlistener("keydown.escape", function DuplicateDetectionResourceComponent_keydown_escape_HostBindingHandler() { return ctx.OnEscapeKey(); }, i0.ɵɵresolveDocument);
2794
+ } }, inputs: { EmbeddedMode: "EmbeddedMode" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 70, vars: 29, consts: [[1, "duplicate-detection-container"], [1, "page-header"], [1, "header-left"], [1, "page-title"], [1, "fa-solid", "fa-clone"], [1, "header-actions"], [1, "run-detection-controls"], [1, "entity-doc-select", 3, "ngModelChange", "ngModel", "disabled"], ["value", ""], [3, "value"], [1, "run-detection-btn", 3, "click", "disabled"], [1, "detection-progress-section"], [1, "threshold-controls"], [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", "disabled"], [1, "filter-range"], [1, "filter-label"], ["type", "number", "min", "0", "max", "1", "step", "0.05", 1, "filter-input", "filter-input-score", 3, "ngModelChange", "placeholder", "ngModel"], ["type", "date", 1, "filter-input", "filter-input-date", 3, "ngModelChange", "min", "max", "ngModel"], [1, "clear-filters-btn"], [1, "merge-warning-banner"], [1, "loading-container"], [1, "empty-state"], [1, "kanban-board"], [1, "saving-overlay"], [1, "merge-confirm-backdrop"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "fa-solid", "fa-magnifying-glass"], [1, "progress-header"], [1, "progress-stage"], [1, "progress-percent"], [1, "progress-bar-track"], [1, "progress-bar-fill"], [1, "progress-current-item"], [1, "threshold-slider-group"], [1, "threshold-label"], [1, "fa-solid", "fa-adjust"], [1, "threshold-value"], ["type", "range", 1, "threshold-slider", 3, "input", "min", "max", "step", "value", "disabled"], [1, "threshold-hint"], [1, "fa-solid", "fa-bullseye"], [1, "clear-filters-btn", 3, "click"], [1, "fa-solid", "fa-times"], [1, "fa-solid", "fa-triangle-exclamation"], ["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", 3, "dragover", "dragleave", "drop"], ["draggable", "true", 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"], ["draggable", "true", 1, "kanban-card", 3, "dragstart", "dragend", "click"], [1, "card-header"], [1, "card-header-left"], [1, "card-icon"], [1, "card-title-block"], [1, "card-record-name", 3, "title"], [1, "entity-badge"], [1, "score-indicator"], [1, "card-body"], [1, "match-summaries"], [1, "card-meta-row"], [1, "card-meta-item"], [1, "fa-solid", "fa-layer-group"], [1, "fa-solid", "fa-calendar"], [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, "match-summary-row"], [1, "match-summary-more"], [1, "match-score"], [1, "match-name"], [1, "fa-solid", "fa-inbox"], ["text", "Saving...", "size", "small"], [1, "slide-backdrop", 3, "click"], [1, "comparison-panel", 3, "click"], [1, "comparison-header"], [1, "comparison-header-left"], [1, "comparison-entity-icon"], [1, "comparison-title"], [1, "comparison-entity-badge"], [1, "comparison-match-count"], [1, "comparison-header-right"], [1, "comparison-toggle"], [1, "toggle-btn", 3, "click"], ["title", "Close (Esc)", 1, "comparison-close-btn", 3, "click"], [1, "comparison-loading"], [1, "comparison-grid-wrapper", 3, "hidden"], [1, "comparison-grid"], [1, "grid-corner-cell"], [1, "grid-col-header", "grid-col-source"], [1, "col-header-label"], [1, "col-header-name"], [1, "surviving-selector"], ["type", "radio", "name", "survivor", 1, "surviving-radio", 3, "change", "checked"], [1, "surviving-label"], [1, "use-all-btn", 3, "click"], [1, "fa-solid"], [1, "deps-summary"], [1, "deps-total"], [1, "fa-solid", "fa-link"], [1, "deps-total-number"], [1, "deps-total-recommended"], [1, "grid-col-header", 3, "match-approved", "match-rejected"], [1, "comparison-footer"], [1, "comparison-footer-left"], [1, "comparison-summary"], [1, "merge-summary"], [1, "fa-solid", "fa-code-merge"], [1, "comparison-footer-right"], [1, "action-btn", "merge-btn", 3, "click", "disabled", "title"], [1, "merge-disabled-hint"], ["text", "Loading records for comparison...", "size", "medium"], [1, "fa-solid", "fa-star"], [1, "deps-header", 3, "click"], [1, "deps-detail-list"], [1, "deps-detail-group"], [1, "deps-detail-row", 3, "click"], [1, "deps-detail-entity"], [1, "fa-solid", "deps-expand-icon"], [1, "deps-detail-count"], [1, "deps-records-list"], [1, "deps-record-loading"], [1, "deps-record-row"], [1, "deps-record-row", 3, "click"], [1, "fa-solid", "fa-arrow-up-right-from-square", "deps-record-icon"], [1, "deps-record-name"], [1, "grid-col-header"], [1, "col-header-top"], [1, "col-header-diff-count"], [1, "match-approval-actions"], ["title", "Skip this match (exclude from merge)", 1, "match-action-btn", "match-skip-btn"], ["title", "Skip this match (exclude from merge)", 1, "match-action-btn", "match-skip-btn", 3, "click"], [1, "match-status-badge"], ["title", "Undo skip", 1, "match-action-btn", "match-undo-btn"], ["title", "Undo skip", 1, "match-action-btn", "match-undo-btn", 3, "click"], [1, "fa-solid", "fa-undo"], [1, "grid-label-cell"], [1, "grid-value-cell", "grid-source-cell", 3, "click"], [1, "field-not-available"], ["type", "radio", 1, "field-select-radio", 3, "name", "checked"], [1, "grid-value-cell", 3, "grid-row-odd", "value-same", "value-different", "field-selected"], ["type", "radio", 1, "field-select-radio", 3, "click", "change", "name", "checked"], [1, "grid-value-cell", 3, "click"], [1, "fa-solid", "fa-info-circle"], [1, "merge-confirm-backdrop", 3, "click"], [1, "merge-confirm-panel", 3, "click"], [1, "merge-confirm-header"], [1, "merge-confirm-icon"], [1, "merge-confirm-title"], [1, "merge-confirm-subtitle"], [1, "merge-confirm-body"], [1, "merge-survivor-card"], [1, "merge-survivor-label"], [1, "fa-solid", "fa-shield-halved"], [1, "merge-survivor-name"], [1, "merge-survivor-pk"], [1, "fa-solid", "fa-key"], [1, "merge-survivor-detail"], [1, "merge-deps-transfer"], [1, "merge-delete-card"], [1, "merge-delete-label"], [1, "fa-solid", "fa-trash"], [1, "merge-delete-item"], [1, "merge-confirm-footer"], [1, "action-btn", "cancel-btn", 3, "click", "disabled"], [1, "fa-solid", "fa-arrow-left"], [1, "action-btn", "confirm-merge-btn", 3, "click", "disabled"], [1, "merge-section-label"], [1, "merge-field-override"], [1, "merge-field-name"], [1, "merge-field-value"], [1, "merge-field-source"], [1, "merge-deps-transfer-label"], [1, "fa-solid", "fa-arrow-right-arrow-left"], [1, "merge-deps-transfer-row"], [1, "fa-solid", "fa-arrow-right"], [1, "merge-delete-name"], [1, "merge-delete-deps"]], template: function DuplicateDetectionResourceComponent_Template(rf, ctx) { if (rf & 1) {
661
2795
  i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "div", 2)(3, "h2", 3);
662
2796
  i0.ɵɵelement(4, "i", 4);
663
2797
  i0.ɵɵtext(5, " Duplicate Detection ");
664
- i0.ɵɵelementEnd()()();
665
- i0.ɵɵelementStart(6, "div", 5)(7, "div", 6)(8, "div", 7);
666
- i0.ɵɵtext(9);
2798
+ i0.ɵɵelementEnd()();
2799
+ i0.ɵɵelementStart(6, "div", 5)(7, "div", 6)(8, "select", 7);
2800
+ i0.ɵɵtwoWayListener("ngModelChange", function DuplicateDetectionResourceComponent_Template_select_ngModelChange_8_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.SelectedEntityDocumentID, $event) || (ctx.SelectedEntityDocumentID = $event); return $event; });
2801
+ i0.ɵɵelementStart(9, "option", 8);
2802
+ i0.ɵɵtext(10, "Select entity document...");
2803
+ i0.ɵɵelementEnd();
2804
+ i0.ɵɵrepeaterCreate(11, DuplicateDetectionResourceComponent_For_12_Template, 2, 3, "option", 9, _forTrack0);
667
2805
  i0.ɵɵelementEnd();
668
- i0.ɵɵelementStart(10, "div", 8);
669
- i0.ɵɵtext(11, "Total Groups");
2806
+ i0.ɵɵelementStart(13, "button", 10);
2807
+ i0.ɵɵlistener("click", function DuplicateDetectionResourceComponent_Template_button_click_13_listener() { return ctx.RunDetection(); });
2808
+ i0.ɵɵconditionalCreate(14, DuplicateDetectionResourceComponent_Conditional_14_Template, 2, 0)(15, DuplicateDetectionResourceComponent_Conditional_15_Template, 2, 0);
2809
+ i0.ɵɵelementEnd()()()();
2810
+ i0.ɵɵconditionalCreate(16, DuplicateDetectionResourceComponent_Conditional_16_Template, 10, 5, "div", 11);
2811
+ i0.ɵɵconditionalCreate(17, DuplicateDetectionResourceComponent_Conditional_17_Template, 19, 12, "div", 12);
2812
+ i0.ɵɵelementStart(18, "div", 13)(19, "div", 14)(20, "div", 15);
2813
+ i0.ɵɵtext(21);
2814
+ i0.ɵɵelementEnd();
2815
+ i0.ɵɵelementStart(22, "div", 16);
2816
+ i0.ɵɵtext(23, "Total Groups");
670
2817
  i0.ɵɵelementEnd()();
671
- i0.ɵɵelementStart(12, "div", 9)(13, "div", 7);
672
- i0.ɵɵtext(14);
2818
+ i0.ɵɵelementStart(24, "div", 17)(25, "div", 15);
2819
+ i0.ɵɵtext(26);
673
2820
  i0.ɵɵelementEnd();
674
- i0.ɵɵelementStart(15, "div", 8);
675
- i0.ɵɵtext(16, "Pending");
2821
+ i0.ɵɵelementStart(27, "div", 16);
2822
+ i0.ɵɵtext(28, "Pending");
676
2823
  i0.ɵɵelementEnd()();
677
- i0.ɵɵelementStart(17, "div", 10)(18, "div", 7);
678
- i0.ɵɵtext(19);
2824
+ i0.ɵɵelementStart(29, "div", 18)(30, "div", 15);
2825
+ i0.ɵɵtext(31);
679
2826
  i0.ɵɵelementEnd();
680
- i0.ɵɵelementStart(20, "div", 8);
681
- i0.ɵɵtext(21, "Approved");
2827
+ i0.ɵɵelementStart(32, "div", 16);
2828
+ i0.ɵɵtext(33, "Approved");
682
2829
  i0.ɵɵelementEnd()();
683
- i0.ɵɵelementStart(22, "div", 11)(23, "div", 7);
684
- i0.ɵɵtext(24);
2830
+ i0.ɵɵelementStart(34, "div", 19)(35, "div", 15);
2831
+ i0.ɵɵtext(36);
685
2832
  i0.ɵɵelementEnd();
686
- i0.ɵɵelementStart(25, "div", 8);
687
- i0.ɵɵtext(26, "Rejected");
2833
+ i0.ɵɵelementStart(37, "div", 16);
2834
+ i0.ɵɵtext(38, "Rejected");
688
2835
  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);
2836
+ i0.ɵɵelementStart(39, "div", 20)(40, "div", 21)(41, "select", 22);
2837
+ i0.ɵɵtwoWayListener("ngModelChange", function DuplicateDetectionResourceComponent_Template_select_ngModelChange_41_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.Filters.EntityName, $event) || (ctx.Filters.EntityName = $event); return $event; });
2838
+ i0.ɵɵlistener("ngModelChange", function DuplicateDetectionResourceComponent_Template_select_ngModelChange_41_listener() { return ctx.OnFilterChange(); });
2839
+ i0.ɵɵconditionalCreate(42, DuplicateDetectionResourceComponent_Conditional_42_Template, 2, 0, "option", 8);
2840
+ i0.ɵɵrepeaterCreate(43, DuplicateDetectionResourceComponent_For_44_Template, 2, 2, "option", 9, i0.ɵɵrepeaterTrackByIdentity);
696
2841
  i0.ɵɵelementEnd();
697
- i0.ɵɵelementStart(34, "div", 17)(35, "label", 18);
698
- i0.ɵɵtext(36, "Min Score");
2842
+ i0.ɵɵelementStart(45, "div", 23)(46, "label", 24);
2843
+ i0.ɵɵtext(47, "Min Score");
699
2844
  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(); });
2845
+ i0.ɵɵelementStart(48, "input", 25);
2846
+ i0.ɵɵtwoWayListener("ngModelChange", function DuplicateDetectionResourceComponent_Template_input_ngModelChange_48_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.Filters.MinScore, $event) || (ctx.Filters.MinScore = $event); return $event; });
2847
+ i0.ɵɵlistener("ngModelChange", function DuplicateDetectionResourceComponent_Template_input_ngModelChange_48_listener() { return ctx.OnFilterChange(); });
703
2848
  i0.ɵɵelementEnd()();
704
- i0.ɵɵelementStart(38, "div", 17)(39, "label", 18);
705
- i0.ɵɵtext(40, "Max Score");
2849
+ i0.ɵɵelementStart(49, "div", 23)(50, "label", 24);
2850
+ i0.ɵɵtext(51, "Max Score");
706
2851
  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(); });
2852
+ i0.ɵɵelementStart(52, "input", 25);
2853
+ i0.ɵɵtwoWayListener("ngModelChange", function DuplicateDetectionResourceComponent_Template_input_ngModelChange_52_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.Filters.MaxScore, $event) || (ctx.Filters.MaxScore = $event); return $event; });
2854
+ i0.ɵɵlistener("ngModelChange", function DuplicateDetectionResourceComponent_Template_input_ngModelChange_52_listener() { return ctx.OnFilterChange(); });
710
2855
  i0.ɵɵelementEnd()();
711
- i0.ɵɵelementStart(42, "div", 17)(43, "label", 18);
712
- i0.ɵɵtext(44, "From");
2856
+ i0.ɵɵelementStart(53, "div", 23)(54, "label", 24);
2857
+ i0.ɵɵtext(55, "From");
713
2858
  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(); });
2859
+ i0.ɵɵelementStart(56, "input", 26);
2860
+ i0.ɵɵtwoWayListener("ngModelChange", function DuplicateDetectionResourceComponent_Template_input_ngModelChange_56_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.Filters.DateFrom, $event) || (ctx.Filters.DateFrom = $event); return $event; });
2861
+ i0.ɵɵlistener("ngModelChange", function DuplicateDetectionResourceComponent_Template_input_ngModelChange_56_listener() { return ctx.OnFilterChange(); });
717
2862
  i0.ɵɵelementEnd()();
718
- i0.ɵɵelementStart(46, "div", 17)(47, "label", 18);
719
- i0.ɵɵtext(48, "To");
2863
+ i0.ɵɵelementStart(57, "div", 23)(58, "label", 24);
2864
+ i0.ɵɵtext(59, "To");
720
2865
  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(); });
2866
+ i0.ɵɵelementStart(60, "input", 26);
2867
+ i0.ɵɵtwoWayListener("ngModelChange", function DuplicateDetectionResourceComponent_Template_input_ngModelChange_60_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.Filters.DateTo, $event) || (ctx.Filters.DateTo = $event); return $event; });
2868
+ i0.ɵɵlistener("ngModelChange", function DuplicateDetectionResourceComponent_Template_input_ngModelChange_60_listener() { return ctx.OnFilterChange(); });
724
2869
  i0.ɵɵelementEnd()()();
725
- i0.ɵɵconditionalCreate(50, DuplicateDetectionResourceComponent_Conditional_50_Template, 3, 0, "button", 21);
2870
+ i0.ɵɵconditionalCreate(61, DuplicateDetectionResourceComponent_Conditional_61_Template, 3, 0, "button", 27);
726
2871
  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);
2872
+ i0.ɵɵconditionalCreate(62, DuplicateDetectionResourceComponent_Conditional_62_Template, 3, 0, "div", 28);
2873
+ i0.ɵɵconditionalCreate(63, DuplicateDetectionResourceComponent_Conditional_63_Template, 2, 0, "div", 29)(64, DuplicateDetectionResourceComponent_Conditional_64_Template, 2, 0, "div", 29)(65, DuplicateDetectionResourceComponent_Conditional_65_Template, 6, 0, "div", 30)(66, DuplicateDetectionResourceComponent_Conditional_66_Template, 34, 12, "div", 31);
2874
+ i0.ɵɵconditionalCreate(67, DuplicateDetectionResourceComponent_Conditional_67_Template, 2, 0, "div", 32);
2875
+ i0.ɵɵconditionalCreate(68, DuplicateDetectionResourceComponent_Conditional_68_Template, 68, 42);
2876
+ i0.ɵɵconditionalCreate(69, DuplicateDetectionResourceComponent_Conditional_69_Template, 37, 7, "div", 33);
729
2877
  i0.ɵɵelementEnd();
730
2878
  } if (rf & 2) {
731
- i0.ɵɵadvance(9);
2879
+ i0.ɵɵadvance(8);
2880
+ i0.ɵɵtwoWayProperty("ngModel", ctx.SelectedEntityDocumentID);
2881
+ i0.ɵɵproperty("disabled", ctx.IsDetecting);
2882
+ i0.ɵɵadvance(3);
2883
+ i0.ɵɵrepeater(ctx.EntityDocuments);
2884
+ i0.ɵɵadvance(2);
2885
+ i0.ɵɵproperty("disabled", ctx.IsDetecting || !ctx.SelectedEntityDocumentID);
2886
+ i0.ɵɵadvance();
2887
+ i0.ɵɵconditional(ctx.IsDetecting ? 14 : 15);
2888
+ i0.ɵɵadvance(2);
2889
+ i0.ɵɵconditional(ctx.IsDetecting ? 16 : -1);
2890
+ i0.ɵɵadvance();
2891
+ i0.ɵɵconditional(ctx.SelectedDocumentThresholds ? 17 : -1);
2892
+ i0.ɵɵadvance(4);
732
2893
  i0.ɵɵtextInterpolate(ctx.TotalGroupCount);
733
2894
  i0.ɵɵadvance(5);
734
2895
  i0.ɵɵtextInterpolate(ctx.PendingCount);
@@ -738,23 +2899,36 @@ let DuplicateDetectionResourceComponent = class DuplicateDetectionResourceCompon
738
2899
  i0.ɵɵtextInterpolate(ctx.RejectedCount);
739
2900
  i0.ɵɵadvance(5);
740
2901
  i0.ɵɵtwoWayProperty("ngModel", ctx.Filters.EntityName);
741
- i0.ɵɵadvance(3);
2902
+ i0.ɵɵproperty("disabled", ctx.EntityNames.length <= 1);
2903
+ i0.ɵɵadvance();
2904
+ i0.ɵɵconditional(ctx.EntityNames.length > 1 ? 42 : -1);
2905
+ i0.ɵɵadvance();
742
2906
  i0.ɵɵrepeater(ctx.EntityNames);
743
2907
  i0.ɵɵadvance(5);
2908
+ i0.ɵɵproperty("placeholder", ctx.DataMinScore);
744
2909
  i0.ɵɵtwoWayProperty("ngModel", ctx.Filters.MinScore);
745
2910
  i0.ɵɵadvance(4);
2911
+ i0.ɵɵproperty("placeholder", ctx.DataMaxScore);
746
2912
  i0.ɵɵtwoWayProperty("ngModel", ctx.Filters.MaxScore);
747
2913
  i0.ɵɵadvance(4);
2914
+ i0.ɵɵproperty("min", ctx.DataMinDate)("max", ctx.DataMaxDate);
748
2915
  i0.ɵɵtwoWayProperty("ngModel", ctx.Filters.DateFrom);
749
2916
  i0.ɵɵadvance(4);
2917
+ i0.ɵɵproperty("min", ctx.DataMinDate)("max", ctx.DataMaxDate);
750
2918
  i0.ɵɵtwoWayProperty("ngModel", ctx.Filters.DateTo);
751
2919
  i0.ɵɵadvance();
752
- i0.ɵɵconditional(ctx.HasActiveFilters ? 50 : -1);
2920
+ i0.ɵɵconditional(ctx.HasActiveFilters ? 61 : -1);
753
2921
  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}"] });
2922
+ i0.ɵɵconditional(ctx.ShowMergeWarningBanner ? 62 : -1);
2923
+ i0.ɵɵadvance();
2924
+ i0.ɵɵconditional(ctx.IsLoading ? 63 : ctx.IsLoadingResults ? 64 : ctx.TotalGroupCount === 0 ? 65 : 66);
2925
+ i0.ɵɵadvance(4);
2926
+ i0.ɵɵconditional(ctx.IsSaving ? 67 : -1);
2927
+ i0.ɵɵadvance();
2928
+ i0.ɵɵconditional(ctx.ComparisonGroup ? 68 : -1);
2929
+ i0.ɵɵadvance();
2930
+ i0.ɵɵconditional(ctx.ShowMergeConfirm && ctx.ComparisonGroup ? 69 : -1);
2931
+ } }, dependencies: [i1.NgSelectOption, i1.ɵNgSelectMultipleOption, i1.DefaultValueAccessor, i1.NumberValueAccessor, i1.SelectControlValueAccessor, i1.NgControlStatus, i1.MinValidator, i1.MaxValidator, i1.NgModel, i2.LoadingComponent], styles: ["/* ============================================================\n Duplicate Detection Kanban Board - Resource Component Styles\n All colors use MJ design tokens (--mj-*) exclusively.\n ============================================================ */\n\napp-duplicate-detection-resource {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\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/* ---- Header Actions ---- */\n\n.header-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.run-detection-controls {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.entity-doc-select {\n padding: 8px 12px;\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 min-width: 200px;\n}\n\n.entity-doc-select:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.run-detection-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n cursor: pointer;\n font-size: 13px;\n font-weight: 600;\n transition: background 0.15s;\n white-space: nowrap;\n}\n\n.run-detection-btn:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.run-detection-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n/* ---- Detection Progress ---- */\n\n.detection-progress-section {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-brand-primary);\n border-radius: 8px;\n padding: 14px 18px;\n margin-bottom: 16px;\n flex-shrink: 0;\n}\n\n.progress-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 8px;\n}\n\n.progress-stage {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-brand-primary);\n}\n\n.progress-percent {\n font-size: 13px;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.progress-bar-track {\n height: 6px;\n border-radius: 3px;\n background: var(--mj-bg-surface-sunken);\n overflow: hidden;\n}\n\n.progress-bar-fill {\n height: 100%;\n border-radius: 3px;\n background: var(--mj-brand-primary);\n transition: width 0.3s ease;\n}\n\n.progress-current-item {\n display: block;\n margin-top: 6px;\n font-size: 11px;\n color: var(--mj-text-muted);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ---- Threshold Controls ---- */\n\n.threshold-controls {\n display: flex;\n gap: 24px;\n padding: 12px 16px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-status-info) 6%, var(--mj-bg-surface));\n border: 1px solid var(--mj-border-subtle);\n margin-bottom: 16px;\n flex-shrink: 0;\n}\n\n.threshold-slider-group {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.threshold-label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 0.82rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n\n.threshold-value {\n margin-left: auto;\n font-weight: 700;\n color: var(--mj-brand-primary);\n font-size: 0.85rem;\n}\n\n.threshold-slider {\n width: 100%;\n height: 4px;\n appearance: none;\n -webkit-appearance: none;\n background: var(--mj-border-default);\n border-radius: 2px;\n outline: none;\n cursor: pointer;\n}\n\n.threshold-slider::-webkit-slider-thumb {\n -webkit-appearance: none;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: var(--mj-brand-primary);\n border: 2px solid var(--mj-bg-surface);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n cursor: pointer;\n}\n\n.threshold-slider::-moz-range-thumb {\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: var(--mj-brand-primary);\n border: 2px solid var(--mj-bg-surface);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n cursor: pointer;\n}\n\n.threshold-slider:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n.threshold-hint {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n}\n\n/* ---- Threshold Info (legacy) ---- */\n\n.threshold-info {\n display: flex;\n gap: 16px;\n padding: 8px 14px;\n border-radius: 6px;\n background: color-mix(in srgb, var(--mj-status-info) 8%, var(--mj-bg-surface));\n border: 1px solid var(--mj-status-info-border);\n margin-bottom: 16px;\n flex-shrink: 0;\n}\n\n.threshold-item {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--mj-status-info-text);\n font-weight: 500;\n}\n\n.threshold-item i {\n font-size: 11px;\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-score {\n width: 80px;\n}\n\n.filter-input-date {\n width: 140px;\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 flex-shrink: 0; /* Prevent cards from shrinking \u2014 column scrolls instead */\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/* Drag and Drop */\n.kanban-card[draggable=\"true\"] {\n cursor: grab;\n}\n\n.kanban-card[draggable=\"true\"]:active {\n cursor: grabbing;\n}\n\n.drop-target-active {\n outline: 2px dashed var(--mj-brand-primary);\n outline-offset: -2px;\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface-card)) !important;\n}\n\n.drop-target-active .column-body {\n min-height: 100px;\n}\n\n.card-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n padding: 10px 12px;\n border-bottom: 1px solid var(--mj-border-subtle);\n gap: 8px;\n}\n\n.card-header-left {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n flex: 1;\n}\n\n.card-icon {\n width: 2rem;\n height: 2rem;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 6px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.85rem;\n flex-shrink: 0;\n}\n\n.card-title-block {\n min-width: 0;\n flex: 1;\n}\n\n.card-record-name {\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n margin-bottom: 2px;\n}\n\n.entity-badge {\n display: inline-flex;\n align-items: center;\n padding: 1px 6px;\n border-radius: 3px;\n font-size: 10px;\n font-weight: 500;\n color: var(--mj-text-muted);\n letter-spacing: 0.2px;\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: 8px;\n}\n\n.match-summaries {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.match-summary-row {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n}\n\n.match-score {\n flex-shrink: 0;\n font-weight: 600;\n font-size: 11px;\n color: var(--mj-text-muted);\n min-width: 30px;\n}\n\n.match-name {\n color: var(--mj-text-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.match-summary-more {\n font-size: 11px;\n color: var(--mj-text-muted);\n font-style: italic;\n padding-left: 38px;\n}\n\n.card-meta-row {\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 11px;\n color: var(--mj-text-muted);\n border-top: 1px solid var(--mj-border-subtle);\n padding-top: 6px;\n}\n\n.card-meta-item {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.card-meta-item i {\n font-size: 10px;\n}\n font-size: 12px;\n max-width: 180px;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Comparison Slide-In Panel\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.slide-backdrop {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n z-index: 9999;\n animation: fadeIn 0.2s ease;\n}\n\n.slide-backdrop.comparison-closing {\n animation: fadeOut 0.25s ease forwards;\n}\n\n@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }\n@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }\n\n.comparison-panel {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 82vw;\n max-width: 1300px;\n background: var(--mj-bg-surface);\n border-left: 1px solid var(--mj-border-default);\n box-shadow: -8px 0 40px rgba(0,0,0,0.4);\n z-index: 10000;\n display: flex;\n flex-direction: column;\n animation: slideIn 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n overflow: hidden;\n}\n\n.comparison-panel.comparison-closing {\n animation: slideOut 0.25s cubic-bezier(0.4, 0, 0.2, 1) forwards;\n}\n\n@keyframes slideIn { from { transform: translateX(100%); } to { transform: translateX(0); } }\n@keyframes slideOut { from { transform: translateX(0); } to { transform: translateX(100%); } }\n\n/* Header */\n.comparison-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 20px;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n gap: 12px;\n}\n\n.comparison-header-left {\n display: flex;\n align-items: center;\n gap: 12px;\n min-width: 0;\n}\n\n.comparison-entity-icon {\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 1rem;\n flex-shrink: 0;\n}\n\n.comparison-title {\n font-size: 1rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.comparison-entity-badge {\n display: inline-block;\n padding: 1px 7px;\n border-radius: 10px;\n font-size: 0.65rem;\n font-weight: 500;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n margin-right: 6px;\n}\n\n.comparison-match-count {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n}\n\n.comparison-header-right {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-shrink: 0;\n}\n\n/* Toggle */\n.comparison-toggle {\n display: flex;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n overflow: hidden;\n}\n\n.toggle-btn {\n padding: 5px 12px;\n font-size: 0.7rem;\n font-weight: 500;\n border: none;\n cursor: pointer;\n transition: all 0.15s ease;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n}\n\n.toggle-btn:first-child {\n border-right: 1px solid var(--mj-border-default);\n}\n\n.toggle-btn.toggle-active {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.comparison-close-btn {\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n font-size: 0.85rem;\n transition: all 0.15s ease;\n}\n\n.comparison-close-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n/* Loading state */\n.comparison-loading {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n/* Grid wrapper */\n.comparison-grid-wrapper {\n flex: 1;\n overflow: auto;\n min-height: 0;\n}\n\n/* Grid */\n.comparison-grid {\n display: grid;\n min-width: 100%;\n}\n\n.comparison-grid > div {\n padding: 8px 12px;\n font-size: 0.78rem;\n border-bottom: 1px solid var(--mj-border-subtle);\n border-right: 1px solid var(--mj-border-subtle);\n}\n\n/* Corner cell */\n.grid-corner-cell {\n position: sticky;\n top: 0;\n left: 0;\n z-index: 4;\n background: var(--mj-bg-surface-elevated);\n font-size: 0.65rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--mj-text-muted);\n display: flex;\n align-items: flex-end;\n padding-bottom: 6px;\n}\n\n/* Column headers */\n.grid-col-header {\n position: sticky;\n top: 0;\n z-index: 3;\n background: var(--mj-bg-surface-elevated);\n padding: 10px 12px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.grid-col-source {\n border-left: 3px solid var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 6%, var(--mj-bg-surface-elevated));\n}\n\n.col-header-label {\n font-size: 0.6rem;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--mj-brand-primary);\n}\n\n.col-header-top {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n}\n\n.col-header-name {\n font-size: 0.82rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.col-header-diff-count {\n font-size: 0.65rem;\n color: var(--mj-status-warning-text);\n}\n\n/* Surviving record selector */\n.surviving-selector {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-top: 4px;\n}\n\n.surviving-radio {\n appearance: none;\n width: 14px;\n height: 14px;\n border: 2px solid var(--mj-border-strong);\n border-radius: 50%;\n cursor: pointer;\n transition: all 0.15s ease;\n flex-shrink: 0;\n}\n\n.surviving-radio:checked {\n border-color: var(--mj-brand-primary);\n background: var(--mj-brand-primary);\n box-shadow: inset 0 0 0 2px var(--mj-bg-surface-elevated);\n}\n\n.surviving-label {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n cursor: pointer;\n}\n\n.surviving-label.is-survivor {\n color: var(--mj-brand-primary);\n font-weight: 600;\n}\n\n/* Use all fields button */\n.use-all-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 8px;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 0.62rem;\n cursor: pointer;\n transition: all 0.12s ease;\n margin-top: 2px;\n}\n\n.use-all-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n border-color: var(--mj-brand-primary);\n}\n\n.use-all-btn.all-selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n}\n\n/* Per-match actions */\n.comparison-match-actions {\n display: flex;\n gap: 4px;\n margin-top: 3px;\n}\n\n.action-btn-sm {\n width: 24px;\n height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid;\n border-radius: 5px;\n cursor: pointer;\n font-size: 0.65rem;\n transition: all 0.15s ease;\n}\n\n.approve-btn-sm {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n border-color: var(--mj-status-success-border);\n}\n.approve-btn-sm:hover { background: rgba(34,197,94,0.25); }\n\n.reject-btn-sm {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n border-color: var(--mj-status-error-border);\n}\n.reject-btn-sm:hover { background: rgba(239,68,68,0.25); }\n\n.match-status-badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 0.68rem;\n font-weight: 500;\n padding: 2px 8px;\n border-radius: 10px;\n}\n\n.status-approved {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n\n.status-rejected {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.match-approval-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-top: 6px;\n}\n\n.match-action-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 10px;\n border-radius: 6px;\n font-size: 0.7rem;\n font-weight: 500;\n border: 1px solid var(--mj-border-default);\n cursor: pointer;\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n\n.match-skip-btn {\n background: var(--mj-bg-surface);\n color: var(--mj-status-error);\n border-color: color-mix(in srgb, var(--mj-status-error) 30%, var(--mj-border-default));\n}\n\n.match-skip-btn:hover {\n background: var(--mj-status-error-bg);\n border-color: var(--mj-status-error);\n}\n\n.match-undo-btn {\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n}\n\n.match-undo-btn:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n}\n\n.match-approved {\n border-top: 3px solid var(--mj-status-success);\n}\n\n.match-rejected {\n border-top: 3px solid var(--mj-status-error);\n opacity: 0.6;\n}\n\n/* Label cells */\n.grid-label-cell {\n position: sticky;\n left: 0;\n z-index: 2;\n background: var(--mj-bg-surface);\n font-size: 0.72rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n display: flex;\n align-items: center;\n}\n\n.grid-row-odd {\n background: color-mix(in srgb, var(--mj-bg-surface-sunken) 50%, var(--mj-bg-surface));\n}\n\n.grid-label-cell.grid-row-odd {\n background: color-mix(in srgb, var(--mj-bg-surface-sunken) 50%, var(--mj-bg-surface));\n}\n\n/* Value cells */\n.grid-value-cell {\n font-size: 0.78rem;\n color: var(--mj-text-primary);\n line-height: 1.45;\n word-break: break-word;\n position: relative;\n cursor: pointer;\n}\n\n.grid-source-cell {\n background: color-mix(in srgb, var(--mj-brand-primary) 3%, var(--mj-bg-surface));\n border-left: 3px solid transparent;\n}\n\n.grid-source-cell.grid-row-odd {\n background: color-mix(in srgb, var(--mj-brand-primary) 3%, color-mix(in srgb, var(--mj-bg-surface-sunken) 50%, var(--mj-bg-surface)));\n}\n\n.grid-source-cell.has-diff-in-row {\n font-weight: 600;\n}\n\n/* Diff highlighting */\n.value-same {\n color: var(--mj-text-muted);\n}\n\n.value-different {\n background: color-mix(in srgb, var(--mj-status-warning) 8%, transparent) !important;\n border-left: 3px solid var(--mj-status-warning);\n color: var(--mj-text-primary);\n}\n\n.field-not-available {\n font-style: italic;\n font-size: 0.72rem;\n color: var(--mj-text-disabled);\n}\n\n/* Field selection radio */\n.field-select-radio {\n position: absolute;\n top: 50%;\n right: 8px;\n transform: translateY(-50%);\n appearance: none;\n width: 16px;\n height: 16px;\n border: 2px solid var(--mj-border-strong);\n border-radius: 50%;\n cursor: pointer;\n transition: all 0.12s ease;\n opacity: 0.4;\n}\n\n.field-select-radio:hover {\n opacity: 0.8;\n}\n\n.field-select-radio:checked {\n border-color: var(--mj-brand-primary);\n background: var(--mj-brand-primary);\n box-shadow: inset 0 0 0 2.5px var(--mj-bg-surface);\n opacity: 1;\n}\n\n.grid-value-cell.field-selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface)) !important;\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n.grid-value-cell.field-selected .field-select-radio {\n opacity: 1;\n}\n\n/* Footer */\n.comparison-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 20px;\n border-top: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.comparison-footer-left {\n display: flex;\n align-items: center;\n gap: 16px;\n}\n\n.comparison-summary {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n}\n\n.merge-summary {\n font-size: 0.72rem;\n color: var(--mj-brand-primary);\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.merge-summary i {\n font-size: 0.65rem;\n}\n\n.comparison-footer-right {\n display: flex;\n gap: 8px;\n}\n\n.comparison-footer-right .action-btn {\n flex: none;\n padding: 7px 14px;\n}\n\n.merge-btn {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary);\n}\n\n.merge-btn:hover {\n background: var(--mj-brand-primary-hover);\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\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Dependencies Summary (in column headers)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.deps-summary {\n margin-top: 6px;\n padding: 6px 8px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 6px;\n font-size: 0.68rem;\n}\n\n.deps-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n cursor: pointer;\n color: var(--mj-text-secondary);\n font-weight: 600;\n}\n\n.deps-header i {\n font-size: 0.6rem;\n color: var(--mj-text-muted);\n}\n\n.deps-header:hover {\n color: var(--mj-text-primary);\n}\n\n.deps-total {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 0.72rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n margin-bottom: 2px;\n}\n\n.deps-total i {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n}\n\n.deps-total-number {\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n padding: 0 6px;\n border-radius: 8px;\n font-size: 0.7rem;\n font-weight: 700;\n}\n\n.deps-total-recommended {\n font-size: 0.58rem;\n color: var(--mj-status-success-text);\n font-weight: 600;\n margin-left: 4px;\n}\n\n.deps-total-recommended i {\n color: var(--mj-status-success-text);\n}\n\n.deps-detail-list {\n margin-top: 4px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.deps-detail-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 3px 0;\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n cursor: pointer;\n border-radius: 3px;\n transition: background 0.1s ease;\n}\n\n.deps-detail-row:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.deps-detail-entity {\n display: flex;\n align-items: center;\n gap: 4px;\n color: var(--mj-text-secondary);\n}\n\n.deps-expand-icon {\n font-size: 0.5rem;\n width: 10px;\n text-align: center;\n color: var(--mj-text-muted);\n}\n\n/* Individual dependency records list */\n.deps-records-list {\n padding-left: 14px;\n margin-bottom: 2px;\n}\n\n.deps-record-row {\n display: flex;\n align-items: center;\n gap: 5px;\n padding: 2px 4px;\n font-size: 0.6rem;\n color: var(--mj-text-muted);\n cursor: pointer;\n border-radius: 3px;\n transition: color 0.1s ease, background 0.1s ease;\n}\n\n.deps-record-row:hover {\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.deps-record-icon {\n font-size: 0.5rem;\n flex-shrink: 0;\n}\n\n.deps-record-name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 0.6rem;\n}\n\n.deps-record-loading {\n padding: 3px 4px;\n font-size: 0.6rem;\n color: var(--mj-text-muted);\n}\n\n.deps-record-loading i {\n margin-right: 4px;\n}\n\n.deps-detail-count {\n color: var(--mj-text-primary);\n font-weight: 600;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Merge Confirmation Panel\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.merge-confirm-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: 20000;\n animation: fadeIn 0.2s ease;\n}\n\n.merge-confirm-panel {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 520px;\n background: var(--mj-bg-surface);\n border-left: 1px solid var(--mj-border-default);\n box-shadow: -8px 0 40px rgba(0, 0, 0, 0.4);\n z-index: 20001;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n animation: slideIn 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.merge-confirm-header {\n padding: 18px 24px;\n border-bottom: 1px solid var(--mj-border-default);\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.merge-confirm-icon {\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 10px;\n background: color-mix(in srgb, var(--mj-status-warning) 12%, var(--mj-bg-surface));\n color: var(--mj-status-warning-text);\n font-size: 1.1rem;\n flex-shrink: 0;\n}\n\n.merge-confirm-title {\n font-size: 1.05rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.merge-confirm-subtitle {\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n margin-top: 2px;\n}\n\n.merge-confirm-body {\n padding: 20px 24px;\n overflow-y: auto;\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n/* Surviving record card */\n\n.merge-survivor-card {\n padding: 14px;\n background: color-mix(in srgb, var(--mj-brand-primary) 6%, var(--mj-bg-surface));\n border: 1px solid var(--mj-brand-primary);\n border-radius: 8px;\n}\n\n.merge-survivor-label {\n font-size: 0.65rem;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--mj-brand-primary);\n margin-bottom: 4px;\n}\n\n.merge-survivor-label i {\n margin-right: 4px;\n}\n\n.merge-survivor-name {\n font-size: 0.95rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.merge-survivor-pk {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-top: 4px;\n padding: 4px 8px;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border-radius: 4px;\n font-size: 0.68rem;\n font-family: monospace;\n color: var(--mj-text-secondary);\n}\n\n.merge-survivor-pk i {\n font-size: 0.6rem;\n color: var(--mj-brand-primary);\n}\n\n.merge-survivor-detail {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n margin-top: 4px;\n}\n\n/* Cherry-picked fields section */\n\n.merge-section-label {\n font-size: 0.7rem;\n font-weight: 700;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n margin-bottom: 6px;\n}\n\n.merge-field-override {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 6px 0;\n border-bottom: 1px solid var(--mj-border-subtle);\n font-size: 0.78rem;\n}\n\n.merge-field-name {\n font-weight: 600;\n color: var(--mj-text-secondary);\n min-width: 110px;\n flex-shrink: 0;\n}\n\n.merge-field-value {\n color: var(--mj-text-primary);\n}\n\n.merge-field-source {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n margin-left: auto;\n white-space: nowrap;\n}\n\n/* Dependency transfer summary */\n\n.merge-deps-transfer {\n padding: 10px 14px;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n}\n\n.merge-deps-transfer-label {\n font-size: 0.65rem;\n font-weight: 700;\n text-transform: uppercase;\n color: var(--mj-brand-primary);\n margin-bottom: 6px;\n}\n\n.merge-deps-transfer-label i {\n margin-right: 4px;\n}\n\n.merge-deps-transfer-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 3px 0;\n font-size: 0.75rem;\n color: var(--mj-text-secondary);\n}\n\n.merge-deps-transfer-row i {\n color: var(--mj-brand-primary);\n font-size: 0.65rem;\n width: 14px;\n text-align: center;\n}\n\n/* Records to delete */\n\n.merge-delete-card {\n padding: 10px 14px;\n background: color-mix(in srgb, var(--mj-status-error) 5%, var(--mj-bg-surface));\n border: 1px solid var(--mj-status-error-border);\n border-radius: 8px;\n}\n\n.merge-delete-label {\n font-size: 0.65rem;\n font-weight: 700;\n text-transform: uppercase;\n color: var(--mj-status-error-text);\n margin-bottom: 6px;\n}\n\n.merge-delete-label i {\n margin-right: 4px;\n}\n\n.merge-delete-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 5px 0;\n font-size: 0.78rem;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.merge-delete-item:last-child {\n border-bottom: none;\n}\n\n.merge-delete-name {\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.merge-delete-deps {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n}\n\n/* Confirm footer */\n\n.merge-confirm-footer {\n padding: 14px 24px;\n border-top: 1px solid var(--mj-border-default);\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 10px;\n}\n\n.cancel-btn {\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n border-color: var(--mj-border-default);\n}\n\n.cancel-btn:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.confirm-merge-btn {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary);\n font-weight: 600;\n}\n\n.confirm-merge-btn:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.confirm-merge-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n/* ---- Responsive: Merge Confirm ---- */\n\n@media (max-width: 600px) {\n .merge-confirm-panel {\n width: 100%;\n }\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 Merge Disabled Hint \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.merge-disabled-hint {\n font-size: 11.5px;\n color: var(--mj-status-warning-text, #e65100);\n display: inline-flex;\n align-items: center;\n gap: 4px;\n margin-left: 4px;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 Merge Warning Banner (inline, non-blocking) \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.merge-warning-banner {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 16px;\n margin-bottom: 12px;\n border-radius: 6px;\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n border: 1px solid var(--mj-status-warning-border);\n font-size: 13px;\n font-weight: 500;\n flex-shrink: 0;\n}\n\n.merge-warning-banner i {\n font-size: 14px;\n flex-shrink: 0;\n}\n"], encapsulation: 2 });
758
2932
  };
759
2933
  DuplicateDetectionResourceComponent = __decorate([
760
2934
  RegisterClass(BaseResourceComponent, 'DuplicateDetectionResource')
@@ -762,11 +2936,14 @@ DuplicateDetectionResourceComponent = __decorate([
762
2936
  export { DuplicateDetectionResourceComponent };
763
2937
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(DuplicateDetectionResourceComponent, [{
764
2938
  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: [{
2939
+ args: [{ standalone: false, selector: 'app-duplicate-detection-resource', encapsulation: ViewEncapsulation.None, 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 class=\"header-actions\">\n <div class=\"run-detection-controls\">\n <select class=\"entity-doc-select\"\n [(ngModel)]=\"SelectedEntityDocumentID\"\n [disabled]=\"IsDetecting\">\n <option value=\"\">Select entity document...</option>\n @for (doc of EntityDocuments; track doc.ID) {\n <option [value]=\"doc.ID\">{{ doc.Name }} ({{ doc.EntityName }})</option>\n }\n </select>\n <button class=\"run-detection-btn\"\n (click)=\"RunDetection()\"\n [disabled]=\"IsDetecting || !SelectedEntityDocumentID\">\n @if (IsDetecting) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Detecting...\n } @else {\n <i class=\"fa-solid fa-magnifying-glass\"></i> Run Detection\n }\n </button>\n </div>\n </div>\n </div>\n\n <!-- Detection Progress (visible during run) -->\n @if (IsDetecting) {\n <div class=\"detection-progress-section\">\n <div class=\"progress-header\">\n <span class=\"progress-stage\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n {{ DetectionStage }}\n </span>\n <span class=\"progress-percent\">{{ DetectionProgress }}%</span>\n </div>\n <div class=\"progress-bar-track\">\n <div class=\"progress-bar-fill\" [style.width.%]=\"DetectionProgress\"></div>\n </div>\n @if (DetectionCurrentItem) {\n <span class=\"progress-current-item\">{{ DetectionCurrentItem }}</span>\n }\n </div>\n }\n\n <!-- Threshold Controls (when entity doc selected) -->\n @if (SelectedDocumentThresholds) {\n <div class=\"threshold-controls\">\n <div class=\"threshold-slider-group\">\n <label class=\"threshold-label\">\n <i class=\"fa-solid fa-adjust\"></i>\n Potential Match\n <span class=\"threshold-value\">{{ (RunPotentialThreshold * 100).toFixed(0) }}%</span>\n </label>\n <input type=\"range\" class=\"threshold-slider\"\n [min]=\"30\" [max]=\"99\" [step]=\"1\"\n [value]=\"RunPotentialThreshold * 100\"\n (input)=\"OnPotentialThresholdChanged($any($event.target).value / 100)\"\n [disabled]=\"IsDetecting\" />\n <span class=\"threshold-hint\">Score above which duplicates are flagged for review</span>\n </div>\n <div class=\"threshold-slider-group\">\n <label class=\"threshold-label\">\n <i class=\"fa-solid fa-bullseye\"></i>\n Absolute Match\n <span class=\"threshold-value\">{{ (RunAbsoluteThreshold * 100).toFixed(0) }}%</span>\n </label>\n <input type=\"range\" class=\"threshold-slider\"\n [min]=\"50\" [max]=\"100\" [step]=\"1\"\n [value]=\"RunAbsoluteThreshold * 100\"\n (input)=\"OnAbsoluteThresholdChanged($any($event.target).value / 100)\"\n [disabled]=\"IsDetecting\" />\n <span class=\"threshold-hint\">Score above which duplicates are auto-confirmed</span>\n </div>\n </div>\n }\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 [disabled]=\"EntityNames.length <= 1\">\n @if (EntityNames.length > 1) {\n <option value=\"\">All Entities</option>\n }\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 filter-input-score\"\n min=\"0\" max=\"1\" step=\"0.05\"\n [placeholder]=\"DataMinScore\"\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 filter-input-score\"\n min=\"0\" max=\"1\" step=\"0.05\"\n [placeholder]=\"DataMaxScore\"\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 filter-input-date\"\n [min]=\"DataMinDate\" [max]=\"DataMaxDate\"\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 filter-input-date\"\n [min]=\"DataMinDate\" [max]=\"DataMaxDate\"\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 <!-- Merge Warning Banner (non-blocking, shown when entity lacks AllowRecordMerge) -->\n @if (ShowMergeWarningBanner) {\n <div class=\"merge-warning-banner\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n Merging is not available for this entity. Detection results are read-only.\n </div>\n }\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 (IsLoadingResults) {\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\">Select an entity document and click \"Run Detection\" to start.</p>\n </div>\n } @else {\n <!-- Kanban Board -->\n <div class=\"kanban-board\">\n <!-- Pending Column -->\n <div class=\"kanban-column\" [class.drop-target-active]=\"DragOverColumn === 'Pending' && DraggedGroup?.ApprovalStatus !== 'Pending'\">\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 (dragover)=\"OnDragOver($event, 'Pending')\"\n (dragleave)=\"OnDragLeave($event, 'Pending')\"\n (drop)=\"OnDrop($event, 'Pending')\">\n @for (group of PendingGroups; track group.DetailId) {\n <div class=\"kanban-card\"\n draggable=\"true\"\n (dragstart)=\"OnDragStart($event, group)\"\n (dragend)=\"OnDragEnd()\"\n (click)=\"OpenComparison(group)\">\n <div class=\"card-header\">\n <div class=\"card-header-left\">\n <div class=\"card-icon\">\n <i [class]=\"group.EntityIcon\"></i>\n </div>\n <div class=\"card-title-block\">\n <div class=\"card-record-name\" [title]=\"group.RecordName\">{{ group.RecordName }}</div>\n <span class=\"entity-badge\">{{ group.EntityName }}</span>\n </div>\n </div>\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 @if (group.TopMatchSummaries.length > 0) {\n <div class=\"match-summaries\">\n @for (ms of group.TopMatchSummaries; track ms.Name) {\n <div class=\"match-summary-row\">\n <span class=\"match-score\">{{ (ms.Score * 100).toFixed(0) }}%</span>\n <span class=\"match-name\">{{ ms.Name }}</span>\n </div>\n }\n @if (group.MatchCount > group.TopMatchSummaries.length) {\n <div class=\"match-summary-more\">+{{ group.MatchCount - group.TopMatchSummaries.length }} more</div>\n }\n </div>\n }\n <div class=\"card-meta-row\">\n <span class=\"card-meta-item\">\n <i class=\"fa-solid fa-layer-group\"></i>\n {{ group.MatchCount }} match{{ group.MatchCount !== 1 ? 'es' : '' }}\n </span>\n <span class=\"card-meta-item\">\n <i class=\"fa-solid fa-calendar\"></i>\n {{ FormatDate(group.MatchedAt) }}\n </span>\n </div>\n </div>\n <div class=\"card-actions\">\n <button class=\"action-btn approve-btn\"\n [disabled]=\"IsSaving\"\n (click)=\"ApproveMatch(group); $event.stopPropagation()\">\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); $event.stopPropagation()\">\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\" [class.drop-target-active]=\"DragOverColumn === 'Approved' && DraggedGroup?.ApprovalStatus !== 'Approved'\">\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 (dragover)=\"OnDragOver($event, 'Approved')\"\n (dragleave)=\"OnDragLeave($event, 'Approved')\"\n (drop)=\"OnDrop($event, 'Approved')\">\n @for (group of ApprovedGroups; track group.DetailId) {\n <div class=\"kanban-card\"\n draggable=\"true\"\n (dragstart)=\"OnDragStart($event, group)\"\n (dragend)=\"OnDragEnd()\"\n (click)=\"OpenComparison(group)\">\n <div class=\"card-header\">\n <div class=\"card-header-left\">\n <div class=\"card-icon\">\n <i [class]=\"group.EntityIcon\"></i>\n </div>\n <div class=\"card-title-block\">\n <div class=\"card-record-name\" [title]=\"group.RecordName\">{{ group.RecordName }}</div>\n <span class=\"entity-badge\">{{ group.EntityName }}</span>\n </div>\n </div>\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 @if (group.TopMatchSummaries.length > 0) {\n <div class=\"match-summaries\">\n @for (ms of group.TopMatchSummaries; track ms.Name) {\n <div class=\"match-summary-row\">\n <span class=\"match-score\">{{ (ms.Score * 100).toFixed(0) }}%</span>\n <span class=\"match-name\">{{ ms.Name }}</span>\n </div>\n }\n @if (group.MatchCount > group.TopMatchSummaries.length) {\n <div class=\"match-summary-more\">+{{ group.MatchCount - group.TopMatchSummaries.length }} more</div>\n }\n </div>\n }\n <div class=\"card-meta-row\">\n <span class=\"card-meta-item\">\n <i class=\"fa-solid fa-layer-group\"></i>\n {{ group.MatchCount }} match{{ group.MatchCount !== 1 ? 'es' : '' }}\n </span>\n <span class=\"card-meta-item\">\n <i class=\"fa-solid fa-calendar\"></i>\n {{ FormatDate(group.MatchedAt) }}\n </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\" [class.drop-target-active]=\"DragOverColumn === 'Rejected' && DraggedGroup?.ApprovalStatus !== 'Rejected'\">\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 (dragover)=\"OnDragOver($event, 'Rejected')\"\n (dragleave)=\"OnDragLeave($event, 'Rejected')\"\n (drop)=\"OnDrop($event, 'Rejected')\">\n @for (group of RejectedGroups; track group.DetailId) {\n <div class=\"kanban-card\"\n draggable=\"true\"\n (dragstart)=\"OnDragStart($event, group)\"\n (dragend)=\"OnDragEnd()\"\n (click)=\"OpenComparison(group)\">\n <div class=\"card-header\">\n <div class=\"card-header-left\">\n <div class=\"card-icon\">\n <i [class]=\"group.EntityIcon\"></i>\n </div>\n <div class=\"card-title-block\">\n <div class=\"card-record-name\" [title]=\"group.RecordName\">{{ group.RecordName }}</div>\n <span class=\"entity-badge\">{{ group.EntityName }}</span>\n </div>\n </div>\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 @if (group.TopMatchSummaries.length > 0) {\n <div class=\"match-summaries\">\n @for (ms of group.TopMatchSummaries; track ms.Name) {\n <div class=\"match-summary-row\">\n <span class=\"match-score\">{{ (ms.Score * 100).toFixed(0) }}%</span>\n <span class=\"match-name\">{{ ms.Name }}</span>\n </div>\n }\n @if (group.MatchCount > group.TopMatchSummaries.length) {\n <div class=\"match-summary-more\">+{{ group.MatchCount - group.TopMatchSummaries.length }} more</div>\n }\n </div>\n }\n <div class=\"card-meta-row\">\n <span class=\"card-meta-item\">\n <i class=\"fa-solid fa-layer-group\"></i>\n {{ group.MatchCount }} match{{ group.MatchCount !== 1 ? 'es' : '' }}\n </span>\n <span class=\"card-meta-item\">\n <i class=\"fa-solid fa-calendar\"></i>\n {{ FormatDate(group.MatchedAt) }}\n </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\n <!-- \u2550\u2550\u2550 Comparison Slide-In Panel \u2550\u2550\u2550 -->\n @if (ComparisonGroup) {\n <div class=\"slide-backdrop\" [class.comparison-closing]=\"ComparisonClosing\" (click)=\"CloseComparison()\"></div>\n <div class=\"comparison-panel\" [class.comparison-closing]=\"ComparisonClosing\" (click)=\"$event.stopPropagation()\">\n <!-- Header -->\n <div class=\"comparison-header\">\n <div class=\"comparison-header-left\">\n <div class=\"comparison-entity-icon\">\n <i [class]=\"ComparisonGroup.EntityIcon\"></i>\n </div>\n <div>\n <div class=\"comparison-title\">{{ ComparisonGroup.RecordName }}</div>\n <span class=\"comparison-entity-badge\">{{ ComparisonGroup.EntityName }}</span>\n <span class=\"comparison-match-count\">\n {{ ComparisonGroup.MatchCount }} potential duplicate{{ ComparisonGroup.MatchCount !== 1 ? 's' : '' }}\n </span>\n </div>\n </div>\n <div class=\"comparison-header-right\">\n <div class=\"comparison-toggle\">\n <button class=\"toggle-btn\" [class.toggle-active]=\"ComparisonShowAllFields\"\n (click)=\"ComparisonShowAllFields = true\">All Fields</button>\n <button class=\"toggle-btn\" [class.toggle-active]=\"!ComparisonShowAllFields\"\n (click)=\"ComparisonShowAllFields = false\">Differences Only</button>\n </div>\n <button class=\"comparison-close-btn\" (click)=\"CloseComparison()\" title=\"Close (Esc)\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n </div>\n\n <!-- Grid -->\n @if (ComparisonLoading) {\n <div class=\"comparison-loading\">\n <mj-loading text=\"Loading records for comparison...\" size=\"medium\"></mj-loading>\n </div>\n }\n <div class=\"comparison-grid-wrapper\" [hidden]=\"ComparisonLoading\">\n <div class=\"comparison-grid\"\n [style.grid-template-columns]=\"'160px repeat(' + (1 + ComparisonMatches.length) + ', minmax(180px, 1fr))'\">\n\n <!-- Corner cell -->\n <div class=\"grid-corner-cell\">Field</div>\n\n <!-- Source column header -->\n <div class=\"grid-col-header grid-col-source\">\n <span class=\"col-header-label\">Source</span>\n <span class=\"col-header-name\">{{ ComparisonGroup.RecordName }}</span>\n <div class=\"surviving-selector\">\n <input type=\"radio\" name=\"survivor\" class=\"surviving-radio\"\n [checked]=\"SurvivorColumnIndex === 0\"\n (change)=\"SetSurvivor(0)\">\n <span class=\"surviving-label\" [class.is-survivor]=\"SurvivorColumnIndex === 0\">\n {{ SurvivorColumnIndex === 0 ? 'Surviving Record' : 'Set as survivor' }}\n </span>\n </div>\n <button class=\"use-all-btn\" [class.all-selected]=\"AllFieldsSelectedFrom(0)\"\n (click)=\"UseAllFieldsFrom(0)\">\n <i class=\"fa-solid\" [class.fa-check-double]=\"AllFieldsSelectedFrom(0)\" [class.fa-clone]=\"!AllFieldsSelectedFrom(0)\"></i>\n {{ AllFieldsSelectedFrom(0) ? 'Using all fields' : 'Use all fields' }}\n </button>\n <!-- Dependencies Summary -->\n <div class=\"deps-summary\">\n <div class=\"deps-total\">\n <i class=\"fa-solid fa-link\"></i>\n <span class=\"deps-total-number\">{{ GetTotalDeps(0) }}</span>\n {{ GetTotalDeps(0) === 1 ? 'dependency' : 'dependencies' }}\n @if (GetMaxDepsColumnIndex() === 0 && GetTotalDeps(0) > 0) {\n <span class=\"deps-total-recommended\"><i class=\"fa-solid fa-star\"></i> Most deps</span>\n }\n </div>\n @if (GetGroupedDeps(0).length > 0) {\n <div class=\"deps-header\" (click)=\"ToggleDepsExpanded(0)\">\n <span>{{ IsDepsExpanded(0) ? 'Hide details' : 'Show details' }}</span>\n <i class=\"fa-solid\" [class.fa-chevron-down]=\"!IsDepsExpanded(0)\" [class.fa-chevron-up]=\"IsDepsExpanded(0)\"></i>\n </div>\n @if (IsDepsExpanded(0)) {\n <div class=\"deps-detail-list\">\n @for (dep of GetGroupedDeps(0); track dep.Entity) {\n <div class=\"deps-detail-group\">\n <div class=\"deps-detail-row\" (click)=\"ToggleDepEntityGroup(0, dep.Entity); $event.stopPropagation()\">\n <span class=\"deps-detail-entity\">\n <i class=\"fa-solid deps-expand-icon\"\n [class.fa-chevron-right]=\"!IsDepEntityGroupExpanded(0, dep.Entity)\"\n [class.fa-chevron-down]=\"IsDepEntityGroupExpanded(0, dep.Entity)\"></i>\n {{ dep.Entity }}\n </span>\n <span class=\"deps-detail-count\">{{ dep.Count }}</span>\n </div>\n @if (IsDepEntityGroupExpanded(0, dep.Entity)) {\n <div class=\"deps-records-list\">\n @if (IsDepRecordsLoading(0, dep.Entity)) {\n <div class=\"deps-record-loading\"><i class=\"fa-solid fa-spinner fa-spin\"></i> Loading...</div>\n }\n @for (record of GetDepRecords(0, dep.Entity); track $index) {\n <div class=\"deps-record-row\" (click)=\"OpenDepRecord(record); $event.stopPropagation()\">\n <i class=\"fa-solid fa-arrow-up-right-from-square deps-record-icon\"></i>\n <span class=\"deps-record-name\">{{ record.Name }}</span>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n }\n </div>\n </div>\n\n <!-- Match column headers -->\n @for (match of ComparisonMatches; track match.Match.ID; let mi = $index) {\n <div class=\"grid-col-header\"\n [class.match-approved]=\"match.Match.ApprovalStatus === 'Approved'\"\n [class.match-rejected]=\"match.Match.ApprovalStatus === 'Rejected'\">\n <div class=\"col-header-top\">\n <span class=\"col-header-name\">{{ match.Name }}</span>\n <span class=\"score-indicator\" [class]=\"GetScoreClass(match.Score)\">\n {{ (match.Score * 100).toFixed(0) }}%\n </span>\n </div>\n <span class=\"col-header-diff-count\">{{ match.DiffCount }} difference{{ match.DiffCount !== 1 ? 's' : '' }}</span>\n <div class=\"surviving-selector\">\n <input type=\"radio\" name=\"survivor\" class=\"surviving-radio\"\n [checked]=\"SurvivorColumnIndex === mi + 1\"\n (change)=\"SetSurvivor(mi + 1)\">\n <span class=\"surviving-label\" [class.is-survivor]=\"SurvivorColumnIndex === mi + 1\">\n {{ SurvivorColumnIndex === mi + 1 ? 'Surviving Record' : 'Set as survivor' }}\n </span>\n </div>\n <button class=\"use-all-btn\" [class.all-selected]=\"AllFieldsSelectedFrom(mi + 1)\"\n (click)=\"UseAllFieldsFrom(mi + 1)\">\n <i class=\"fa-solid\" [class.fa-check-double]=\"AllFieldsSelectedFrom(mi + 1)\" [class.fa-clone]=\"!AllFieldsSelectedFrom(mi + 1)\"></i>\n {{ AllFieldsSelectedFrom(mi + 1) ? 'Using all fields' : 'Use all fields' }}\n </button>\n <!-- Dependencies Summary -->\n <div class=\"deps-summary\">\n <div class=\"deps-total\">\n <i class=\"fa-solid fa-link\"></i>\n <span class=\"deps-total-number\">{{ GetTotalDeps(mi + 1) }}</span>\n {{ GetTotalDeps(mi + 1) === 1 ? 'dependency' : 'dependencies' }}\n @if (GetMaxDepsColumnIndex() === mi + 1 && GetTotalDeps(mi + 1) > 0) {\n <span class=\"deps-total-recommended\"><i class=\"fa-solid fa-star\"></i> Most deps</span>\n }\n </div>\n @if (GetGroupedDeps(mi + 1).length > 0) {\n <div class=\"deps-header\" (click)=\"ToggleDepsExpanded(mi + 1)\">\n <span>{{ IsDepsExpanded(mi + 1) ? 'Hide details' : 'Show details' }}</span>\n <i class=\"fa-solid\" [class.fa-chevron-down]=\"!IsDepsExpanded(mi + 1)\" [class.fa-chevron-up]=\"IsDepsExpanded(mi + 1)\"></i>\n </div>\n @if (IsDepsExpanded(mi + 1)) {\n <div class=\"deps-detail-list\">\n @for (dep of GetGroupedDeps(mi + 1); track dep.Entity) {\n <div class=\"deps-detail-group\">\n <div class=\"deps-detail-row\" (click)=\"ToggleDepEntityGroup(mi + 1, dep.Entity); $event.stopPropagation()\">\n <span class=\"deps-detail-entity\">\n <i class=\"fa-solid deps-expand-icon\"\n [class.fa-chevron-right]=\"!IsDepEntityGroupExpanded(mi + 1, dep.Entity)\"\n [class.fa-chevron-down]=\"IsDepEntityGroupExpanded(mi + 1, dep.Entity)\"></i>\n {{ dep.Entity }}\n </span>\n <span class=\"deps-detail-count\">{{ dep.Count }}</span>\n </div>\n @if (IsDepEntityGroupExpanded(mi + 1, dep.Entity)) {\n <div class=\"deps-records-list\">\n @if (IsDepRecordsLoading(mi + 1, dep.Entity)) {\n <div class=\"deps-record-loading\"><i class=\"fa-solid fa-spinner fa-spin\"></i> Loading...</div>\n }\n @for (record of GetDepRecords(mi + 1, dep.Entity); track $index) {\n <div class=\"deps-record-row\" (click)=\"OpenDepRecord(record); $event.stopPropagation()\">\n <i class=\"fa-solid fa-arrow-up-right-from-square deps-record-icon\"></i>\n <span class=\"deps-record-name\">{{ record.Name }}</span>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n }\n </div>\n <!-- Per-match approval actions -->\n <div class=\"match-approval-actions\">\n @if (match.Match.ApprovalStatus === 'Pending') {\n <button class=\"match-action-btn match-skip-btn\"\n title=\"Skip this match (exclude from merge)\"\n (click)=\"RejectIndividualMatch(match)\">\n <i class=\"fa-solid fa-ban\"></i> Skip\n </button>\n } @else {\n <span class=\"match-status-badge\"\n [class.status-approved]=\"match.Match.ApprovalStatus === 'Approved'\"\n [class.status-rejected]=\"match.Match.ApprovalStatus === 'Rejected'\">\n <i class=\"fa-solid\" [class.fa-check]=\"match.Match.ApprovalStatus === 'Approved'\"\n [class.fa-times]=\"match.Match.ApprovalStatus === 'Rejected'\"></i>\n {{ match.Match.ApprovalStatus === 'Rejected' ? 'Skipped' : match.Match.ApprovalStatus }}\n </span>\n @if (match.Match.ApprovalStatus === 'Rejected') {\n <button class=\"match-action-btn match-undo-btn\"\n title=\"Undo skip\"\n (click)=\"UndoRejectIndividualMatch(match)\">\n <i class=\"fa-solid fa-undo\"></i> Undo\n </button>\n }\n }\n </div>\n </div>\n }\n\n <!-- Field rows -->\n @for (field of GetVisibleFields(); track field.FieldName; let odd = $odd) {\n <!-- Label cell -->\n <div class=\"grid-label-cell\" [class.grid-row-odd]=\"odd\">\n {{ field.DisplayName }}\n </div>\n <!-- Source value cell -->\n <div class=\"grid-value-cell grid-source-cell\"\n [class.grid-row-odd]=\"odd\"\n [class.has-diff-in-row]=\"field.HasDifference\"\n [class.field-selected]=\"field.SelectedColumnIndex === 0\"\n (click)=\"SelectFieldValue(field, 0)\">\n @if (field.SourceValue != null) {\n {{ field.SourceValue }}\n } @else {\n <span class=\"field-not-available\">(not available)</span>\n }\n @if (field.HasDifference || field.SelectedColumnIndex === 0) {\n <input type=\"radio\" [name]=\"'field-' + field.FieldName\" class=\"field-select-radio\"\n [checked]=\"field.SelectedColumnIndex === 0\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"SelectFieldValue(field, 0)\">\n }\n </div>\n <!-- Match value cells -->\n @for (matchVal of field.MatchValues; track $index; let mi = $index) {\n <div class=\"grid-value-cell\"\n [class.grid-row-odd]=\"odd\"\n [class.value-same]=\"AreValuesEqual(field.SourceValue, matchVal)\"\n [class.value-different]=\"matchVal != null && field.SourceValue != null && !AreValuesEqual(field.SourceValue, matchVal)\"\n [class.field-selected]=\"field.SelectedColumnIndex === mi + 1\"\n (click)=\"matchVal != null ? SelectFieldValue(field, mi + 1) : null\">\n @if (matchVal != null) {\n {{ matchVal }}\n @if (field.HasDifference || field.SelectedColumnIndex === mi + 1) {\n <input type=\"radio\" [name]=\"'field-' + field.FieldName\" class=\"field-select-radio\"\n [checked]=\"field.SelectedColumnIndex === mi + 1\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"SelectFieldValue(field, mi + 1)\">\n }\n } @else {\n <span class=\"field-not-available\">(not available)</span>\n }\n </div>\n }\n }\n </div>\n </div>\n\n <!-- Footer -->\n <div class=\"comparison-footer\">\n <div class=\"comparison-footer-left\">\n <span class=\"comparison-summary\">\n Showing {{ GetVisibleFields().length }} of {{ ComparisonFields.length }} fields\n </span>\n <span class=\"merge-summary\">\n <i class=\"fa-solid fa-code-merge\"></i>\n Surviving: <strong>{{ SurvivorName() }}</strong>\n @if (CherryPickedCount() > 0) {\n &middot; {{ CherryPickedCount() }} field{{ CherryPickedCount() !== 1 ? 's' : '' }} cherry-picked\n }\n </span>\n </div>\n <div class=\"comparison-footer-right\">\n <button class=\"action-btn reject-btn\" [disabled]=\"IsSaving\"\n (click)=\"RejectMatch(ComparisonGroup!); CloseComparison()\">\n <i class=\"fa-solid fa-times\"></i> Reject All\n </button>\n <button class=\"action-btn merge-btn\" [disabled]=\"IsSaving || !HasMergeableMatches || !MergeEnabled\"\n (click)=\"OpenMergeConfirm()\"\n [title]=\"!MergeEnabled ? 'Merging is not enabled for this entity' : HasMergeableMatches ? 'Merge non-skipped records' : 'All matches have been skipped'\">\n <i class=\"fa-solid fa-code-merge\"></i> Merge Records\n </button>\n @if (!MergeEnabled) {\n <span class=\"merge-disabled-hint\"><i class=\"fa-solid fa-info-circle\"></i> Merging disabled for this entity</span>\n }\n </div>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550 Merge Confirmation Panel \u2550\u2550\u2550 -->\n @if (ShowMergeConfirm && ComparisonGroup) {\n <div class=\"merge-confirm-backdrop\" (click)=\"CloseMergeConfirm()\">\n <div class=\"merge-confirm-panel\" (click)=\"$event.stopPropagation()\">\n <div class=\"merge-confirm-header\">\n <div class=\"merge-confirm-icon\"><i class=\"fa-solid fa-code-merge\"></i></div>\n <div>\n <div class=\"merge-confirm-title\">Confirm Record Merge</div>\n <div class=\"merge-confirm-subtitle\">This action cannot be undone. Please review carefully.</div>\n </div>\n </div>\n\n <div class=\"merge-confirm-body\">\n <!-- Surviving Record -->\n <div class=\"merge-survivor-card\">\n <div class=\"merge-survivor-label\"><i class=\"fa-solid fa-shield-halved\"></i> Surviving Record</div>\n <div class=\"merge-survivor-name\">{{ SurvivorName() }}</div>\n <div class=\"merge-survivor-pk\">\n <i class=\"fa-solid fa-key\"></i>\n {{ SurvivorKeyDisplay() }}\n </div>\n <div class=\"merge-survivor-detail\">This record's ID will be retained. All dependencies from merged records will be transferred here.</div>\n </div>\n\n <!-- Cherry-picked field overrides -->\n @if (GetCherryPickedFields().length > 0) {\n <div>\n <div class=\"merge-section-label\">Field Value Overrides ({{ GetCherryPickedFields().length }} field{{ GetCherryPickedFields().length !== 1 ? 's' : '' }})</div>\n @for (field of GetCherryPickedFields(); track field.FieldName) {\n <div class=\"merge-field-override\">\n <span class=\"merge-field-name\">{{ field.DisplayName }}</span>\n <span class=\"merge-field-value\">\"{{ field.Value }}\"</span>\n <span class=\"merge-field-source\">from {{ field.SourceName }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Dependency transfer -->\n @if (GetNonSurvivorColumns().length > 0) {\n <div class=\"merge-deps-transfer\">\n <div class=\"merge-deps-transfer-label\"><i class=\"fa-solid fa-arrow-right-arrow-left\"></i> Dependencies to Transfer</div>\n @for (col of GetNonSurvivorColumns(); track col.ColumnIndex) {\n <div class=\"merge-deps-transfer-row\">\n <i class=\"fa-solid fa-arrow-right\"></i>\n <span>\n @if (col.DepCount > 0) {\n <strong>{{ col.DepCount }}</strong> {{ col.DepCount === 1 ? 'dependency' : 'dependencies' }} from <strong>{{ col.Name }}</strong> &rarr; {{ SurvivorName() }}\n } @else {\n <strong>0</strong> dependencies from <strong>{{ col.Name }}</strong>\n }\n </span>\n </div>\n }\n </div>\n }\n\n <!-- Records to be deleted -->\n <div class=\"merge-delete-card\">\n <div class=\"merge-delete-label\"><i class=\"fa-solid fa-trash\"></i> Records to Delete After Merge</div>\n @for (col of GetNonSurvivorColumns(); track col.ColumnIndex) {\n <div class=\"merge-delete-item\">\n <span class=\"merge-delete-name\">{{ col.Name }}</span>\n <span class=\"merge-delete-deps\">\n @if (col.DepCount > 0) {\n {{ col.DepCount }} dep{{ col.DepCount !== 1 ? 's' : '' }} transferring\n } @else {\n no deps\n }\n </span>\n </div>\n }\n </div>\n </div>\n\n <div class=\"merge-confirm-footer\">\n <button class=\"action-btn cancel-btn\" (click)=\"CloseMergeConfirm()\" [disabled]=\"IsMerging\">\n <i class=\"fa-solid fa-arrow-left\"></i> Back\n </button>\n <button class=\"action-btn confirm-merge-btn\" (click)=\"ExecuteMerge()\" [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> Confirm Merge ({{ GetNonSurvivorColumns().length + 1 }} records &rarr; 1)\n }\n </button>\n </div>\n </div>\n </div>\n }\n\n</div>\n", styles: ["/* ============================================================\n Duplicate Detection Kanban Board - Resource Component Styles\n All colors use MJ design tokens (--mj-*) exclusively.\n ============================================================ */\n\napp-duplicate-detection-resource {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\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/* ---- Header Actions ---- */\n\n.header-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.run-detection-controls {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.entity-doc-select {\n padding: 8px 12px;\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 min-width: 200px;\n}\n\n.entity-doc-select:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.run-detection-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n cursor: pointer;\n font-size: 13px;\n font-weight: 600;\n transition: background 0.15s;\n white-space: nowrap;\n}\n\n.run-detection-btn:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.run-detection-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n/* ---- Detection Progress ---- */\n\n.detection-progress-section {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-brand-primary);\n border-radius: 8px;\n padding: 14px 18px;\n margin-bottom: 16px;\n flex-shrink: 0;\n}\n\n.progress-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 8px;\n}\n\n.progress-stage {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-brand-primary);\n}\n\n.progress-percent {\n font-size: 13px;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.progress-bar-track {\n height: 6px;\n border-radius: 3px;\n background: var(--mj-bg-surface-sunken);\n overflow: hidden;\n}\n\n.progress-bar-fill {\n height: 100%;\n border-radius: 3px;\n background: var(--mj-brand-primary);\n transition: width 0.3s ease;\n}\n\n.progress-current-item {\n display: block;\n margin-top: 6px;\n font-size: 11px;\n color: var(--mj-text-muted);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ---- Threshold Controls ---- */\n\n.threshold-controls {\n display: flex;\n gap: 24px;\n padding: 12px 16px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-status-info) 6%, var(--mj-bg-surface));\n border: 1px solid var(--mj-border-subtle);\n margin-bottom: 16px;\n flex-shrink: 0;\n}\n\n.threshold-slider-group {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.threshold-label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 0.82rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n\n.threshold-value {\n margin-left: auto;\n font-weight: 700;\n color: var(--mj-brand-primary);\n font-size: 0.85rem;\n}\n\n.threshold-slider {\n width: 100%;\n height: 4px;\n appearance: none;\n -webkit-appearance: none;\n background: var(--mj-border-default);\n border-radius: 2px;\n outline: none;\n cursor: pointer;\n}\n\n.threshold-slider::-webkit-slider-thumb {\n -webkit-appearance: none;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: var(--mj-brand-primary);\n border: 2px solid var(--mj-bg-surface);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n cursor: pointer;\n}\n\n.threshold-slider::-moz-range-thumb {\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: var(--mj-brand-primary);\n border: 2px solid var(--mj-bg-surface);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n cursor: pointer;\n}\n\n.threshold-slider:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n.threshold-hint {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n}\n\n/* ---- Threshold Info (legacy) ---- */\n\n.threshold-info {\n display: flex;\n gap: 16px;\n padding: 8px 14px;\n border-radius: 6px;\n background: color-mix(in srgb, var(--mj-status-info) 8%, var(--mj-bg-surface));\n border: 1px solid var(--mj-status-info-border);\n margin-bottom: 16px;\n flex-shrink: 0;\n}\n\n.threshold-item {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--mj-status-info-text);\n font-weight: 500;\n}\n\n.threshold-item i {\n font-size: 11px;\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-score {\n width: 80px;\n}\n\n.filter-input-date {\n width: 140px;\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 flex-shrink: 0; /* Prevent cards from shrinking \u2014 column scrolls instead */\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/* Drag and Drop */\n.kanban-card[draggable=\"true\"] {\n cursor: grab;\n}\n\n.kanban-card[draggable=\"true\"]:active {\n cursor: grabbing;\n}\n\n.drop-target-active {\n outline: 2px dashed var(--mj-brand-primary);\n outline-offset: -2px;\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface-card)) !important;\n}\n\n.drop-target-active .column-body {\n min-height: 100px;\n}\n\n.card-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n padding: 10px 12px;\n border-bottom: 1px solid var(--mj-border-subtle);\n gap: 8px;\n}\n\n.card-header-left {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n flex: 1;\n}\n\n.card-icon {\n width: 2rem;\n height: 2rem;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 6px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.85rem;\n flex-shrink: 0;\n}\n\n.card-title-block {\n min-width: 0;\n flex: 1;\n}\n\n.card-record-name {\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n margin-bottom: 2px;\n}\n\n.entity-badge {\n display: inline-flex;\n align-items: center;\n padding: 1px 6px;\n border-radius: 3px;\n font-size: 10px;\n font-weight: 500;\n color: var(--mj-text-muted);\n letter-spacing: 0.2px;\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: 8px;\n}\n\n.match-summaries {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.match-summary-row {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n}\n\n.match-score {\n flex-shrink: 0;\n font-weight: 600;\n font-size: 11px;\n color: var(--mj-text-muted);\n min-width: 30px;\n}\n\n.match-name {\n color: var(--mj-text-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.match-summary-more {\n font-size: 11px;\n color: var(--mj-text-muted);\n font-style: italic;\n padding-left: 38px;\n}\n\n.card-meta-row {\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 11px;\n color: var(--mj-text-muted);\n border-top: 1px solid var(--mj-border-subtle);\n padding-top: 6px;\n}\n\n.card-meta-item {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.card-meta-item i {\n font-size: 10px;\n}\n font-size: 12px;\n max-width: 180px;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Comparison Slide-In Panel\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.slide-backdrop {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n z-index: 9999;\n animation: fadeIn 0.2s ease;\n}\n\n.slide-backdrop.comparison-closing {\n animation: fadeOut 0.25s ease forwards;\n}\n\n@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }\n@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }\n\n.comparison-panel {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 82vw;\n max-width: 1300px;\n background: var(--mj-bg-surface);\n border-left: 1px solid var(--mj-border-default);\n box-shadow: -8px 0 40px rgba(0,0,0,0.4);\n z-index: 10000;\n display: flex;\n flex-direction: column;\n animation: slideIn 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n overflow: hidden;\n}\n\n.comparison-panel.comparison-closing {\n animation: slideOut 0.25s cubic-bezier(0.4, 0, 0.2, 1) forwards;\n}\n\n@keyframes slideIn { from { transform: translateX(100%); } to { transform: translateX(0); } }\n@keyframes slideOut { from { transform: translateX(0); } to { transform: translateX(100%); } }\n\n/* Header */\n.comparison-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 20px;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n gap: 12px;\n}\n\n.comparison-header-left {\n display: flex;\n align-items: center;\n gap: 12px;\n min-width: 0;\n}\n\n.comparison-entity-icon {\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 1rem;\n flex-shrink: 0;\n}\n\n.comparison-title {\n font-size: 1rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.comparison-entity-badge {\n display: inline-block;\n padding: 1px 7px;\n border-radius: 10px;\n font-size: 0.65rem;\n font-weight: 500;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n margin-right: 6px;\n}\n\n.comparison-match-count {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n}\n\n.comparison-header-right {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-shrink: 0;\n}\n\n/* Toggle */\n.comparison-toggle {\n display: flex;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n overflow: hidden;\n}\n\n.toggle-btn {\n padding: 5px 12px;\n font-size: 0.7rem;\n font-weight: 500;\n border: none;\n cursor: pointer;\n transition: all 0.15s ease;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n}\n\n.toggle-btn:first-child {\n border-right: 1px solid var(--mj-border-default);\n}\n\n.toggle-btn.toggle-active {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.comparison-close-btn {\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n font-size: 0.85rem;\n transition: all 0.15s ease;\n}\n\n.comparison-close-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n/* Loading state */\n.comparison-loading {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n/* Grid wrapper */\n.comparison-grid-wrapper {\n flex: 1;\n overflow: auto;\n min-height: 0;\n}\n\n/* Grid */\n.comparison-grid {\n display: grid;\n min-width: 100%;\n}\n\n.comparison-grid > div {\n padding: 8px 12px;\n font-size: 0.78rem;\n border-bottom: 1px solid var(--mj-border-subtle);\n border-right: 1px solid var(--mj-border-subtle);\n}\n\n/* Corner cell */\n.grid-corner-cell {\n position: sticky;\n top: 0;\n left: 0;\n z-index: 4;\n background: var(--mj-bg-surface-elevated);\n font-size: 0.65rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--mj-text-muted);\n display: flex;\n align-items: flex-end;\n padding-bottom: 6px;\n}\n\n/* Column headers */\n.grid-col-header {\n position: sticky;\n top: 0;\n z-index: 3;\n background: var(--mj-bg-surface-elevated);\n padding: 10px 12px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.grid-col-source {\n border-left: 3px solid var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 6%, var(--mj-bg-surface-elevated));\n}\n\n.col-header-label {\n font-size: 0.6rem;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--mj-brand-primary);\n}\n\n.col-header-top {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n}\n\n.col-header-name {\n font-size: 0.82rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.col-header-diff-count {\n font-size: 0.65rem;\n color: var(--mj-status-warning-text);\n}\n\n/* Surviving record selector */\n.surviving-selector {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-top: 4px;\n}\n\n.surviving-radio {\n appearance: none;\n width: 14px;\n height: 14px;\n border: 2px solid var(--mj-border-strong);\n border-radius: 50%;\n cursor: pointer;\n transition: all 0.15s ease;\n flex-shrink: 0;\n}\n\n.surviving-radio:checked {\n border-color: var(--mj-brand-primary);\n background: var(--mj-brand-primary);\n box-shadow: inset 0 0 0 2px var(--mj-bg-surface-elevated);\n}\n\n.surviving-label {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n cursor: pointer;\n}\n\n.surviving-label.is-survivor {\n color: var(--mj-brand-primary);\n font-weight: 600;\n}\n\n/* Use all fields button */\n.use-all-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 8px;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 0.62rem;\n cursor: pointer;\n transition: all 0.12s ease;\n margin-top: 2px;\n}\n\n.use-all-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n border-color: var(--mj-brand-primary);\n}\n\n.use-all-btn.all-selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n}\n\n/* Per-match actions */\n.comparison-match-actions {\n display: flex;\n gap: 4px;\n margin-top: 3px;\n}\n\n.action-btn-sm {\n width: 24px;\n height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid;\n border-radius: 5px;\n cursor: pointer;\n font-size: 0.65rem;\n transition: all 0.15s ease;\n}\n\n.approve-btn-sm {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n border-color: var(--mj-status-success-border);\n}\n.approve-btn-sm:hover { background: rgba(34,197,94,0.25); }\n\n.reject-btn-sm {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n border-color: var(--mj-status-error-border);\n}\n.reject-btn-sm:hover { background: rgba(239,68,68,0.25); }\n\n.match-status-badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 0.68rem;\n font-weight: 500;\n padding: 2px 8px;\n border-radius: 10px;\n}\n\n.status-approved {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n\n.status-rejected {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.match-approval-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-top: 6px;\n}\n\n.match-action-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 10px;\n border-radius: 6px;\n font-size: 0.7rem;\n font-weight: 500;\n border: 1px solid var(--mj-border-default);\n cursor: pointer;\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n\n.match-skip-btn {\n background: var(--mj-bg-surface);\n color: var(--mj-status-error);\n border-color: color-mix(in srgb, var(--mj-status-error) 30%, var(--mj-border-default));\n}\n\n.match-skip-btn:hover {\n background: var(--mj-status-error-bg);\n border-color: var(--mj-status-error);\n}\n\n.match-undo-btn {\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n}\n\n.match-undo-btn:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n}\n\n.match-approved {\n border-top: 3px solid var(--mj-status-success);\n}\n\n.match-rejected {\n border-top: 3px solid var(--mj-status-error);\n opacity: 0.6;\n}\n\n/* Label cells */\n.grid-label-cell {\n position: sticky;\n left: 0;\n z-index: 2;\n background: var(--mj-bg-surface);\n font-size: 0.72rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n display: flex;\n align-items: center;\n}\n\n.grid-row-odd {\n background: color-mix(in srgb, var(--mj-bg-surface-sunken) 50%, var(--mj-bg-surface));\n}\n\n.grid-label-cell.grid-row-odd {\n background: color-mix(in srgb, var(--mj-bg-surface-sunken) 50%, var(--mj-bg-surface));\n}\n\n/* Value cells */\n.grid-value-cell {\n font-size: 0.78rem;\n color: var(--mj-text-primary);\n line-height: 1.45;\n word-break: break-word;\n position: relative;\n cursor: pointer;\n}\n\n.grid-source-cell {\n background: color-mix(in srgb, var(--mj-brand-primary) 3%, var(--mj-bg-surface));\n border-left: 3px solid transparent;\n}\n\n.grid-source-cell.grid-row-odd {\n background: color-mix(in srgb, var(--mj-brand-primary) 3%, color-mix(in srgb, var(--mj-bg-surface-sunken) 50%, var(--mj-bg-surface)));\n}\n\n.grid-source-cell.has-diff-in-row {\n font-weight: 600;\n}\n\n/* Diff highlighting */\n.value-same {\n color: var(--mj-text-muted);\n}\n\n.value-different {\n background: color-mix(in srgb, var(--mj-status-warning) 8%, transparent) !important;\n border-left: 3px solid var(--mj-status-warning);\n color: var(--mj-text-primary);\n}\n\n.field-not-available {\n font-style: italic;\n font-size: 0.72rem;\n color: var(--mj-text-disabled);\n}\n\n/* Field selection radio */\n.field-select-radio {\n position: absolute;\n top: 50%;\n right: 8px;\n transform: translateY(-50%);\n appearance: none;\n width: 16px;\n height: 16px;\n border: 2px solid var(--mj-border-strong);\n border-radius: 50%;\n cursor: pointer;\n transition: all 0.12s ease;\n opacity: 0.4;\n}\n\n.field-select-radio:hover {\n opacity: 0.8;\n}\n\n.field-select-radio:checked {\n border-color: var(--mj-brand-primary);\n background: var(--mj-brand-primary);\n box-shadow: inset 0 0 0 2.5px var(--mj-bg-surface);\n opacity: 1;\n}\n\n.grid-value-cell.field-selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface)) !important;\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n.grid-value-cell.field-selected .field-select-radio {\n opacity: 1;\n}\n\n/* Footer */\n.comparison-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 20px;\n border-top: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.comparison-footer-left {\n display: flex;\n align-items: center;\n gap: 16px;\n}\n\n.comparison-summary {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n}\n\n.merge-summary {\n font-size: 0.72rem;\n color: var(--mj-brand-primary);\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.merge-summary i {\n font-size: 0.65rem;\n}\n\n.comparison-footer-right {\n display: flex;\n gap: 8px;\n}\n\n.comparison-footer-right .action-btn {\n flex: none;\n padding: 7px 14px;\n}\n\n.merge-btn {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary);\n}\n\n.merge-btn:hover {\n background: var(--mj-brand-primary-hover);\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\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Dependencies Summary (in column headers)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.deps-summary {\n margin-top: 6px;\n padding: 6px 8px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 6px;\n font-size: 0.68rem;\n}\n\n.deps-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n cursor: pointer;\n color: var(--mj-text-secondary);\n font-weight: 600;\n}\n\n.deps-header i {\n font-size: 0.6rem;\n color: var(--mj-text-muted);\n}\n\n.deps-header:hover {\n color: var(--mj-text-primary);\n}\n\n.deps-total {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 0.72rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n margin-bottom: 2px;\n}\n\n.deps-total i {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n}\n\n.deps-total-number {\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n padding: 0 6px;\n border-radius: 8px;\n font-size: 0.7rem;\n font-weight: 700;\n}\n\n.deps-total-recommended {\n font-size: 0.58rem;\n color: var(--mj-status-success-text);\n font-weight: 600;\n margin-left: 4px;\n}\n\n.deps-total-recommended i {\n color: var(--mj-status-success-text);\n}\n\n.deps-detail-list {\n margin-top: 4px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.deps-detail-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 3px 0;\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n cursor: pointer;\n border-radius: 3px;\n transition: background 0.1s ease;\n}\n\n.deps-detail-row:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.deps-detail-entity {\n display: flex;\n align-items: center;\n gap: 4px;\n color: var(--mj-text-secondary);\n}\n\n.deps-expand-icon {\n font-size: 0.5rem;\n width: 10px;\n text-align: center;\n color: var(--mj-text-muted);\n}\n\n/* Individual dependency records list */\n.deps-records-list {\n padding-left: 14px;\n margin-bottom: 2px;\n}\n\n.deps-record-row {\n display: flex;\n align-items: center;\n gap: 5px;\n padding: 2px 4px;\n font-size: 0.6rem;\n color: var(--mj-text-muted);\n cursor: pointer;\n border-radius: 3px;\n transition: color 0.1s ease, background 0.1s ease;\n}\n\n.deps-record-row:hover {\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.deps-record-icon {\n font-size: 0.5rem;\n flex-shrink: 0;\n}\n\n.deps-record-name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 0.6rem;\n}\n\n.deps-record-loading {\n padding: 3px 4px;\n font-size: 0.6rem;\n color: var(--mj-text-muted);\n}\n\n.deps-record-loading i {\n margin-right: 4px;\n}\n\n.deps-detail-count {\n color: var(--mj-text-primary);\n font-weight: 600;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Merge Confirmation Panel\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.merge-confirm-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: 20000;\n animation: fadeIn 0.2s ease;\n}\n\n.merge-confirm-panel {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 520px;\n background: var(--mj-bg-surface);\n border-left: 1px solid var(--mj-border-default);\n box-shadow: -8px 0 40px rgba(0, 0, 0, 0.4);\n z-index: 20001;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n animation: slideIn 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.merge-confirm-header {\n padding: 18px 24px;\n border-bottom: 1px solid var(--mj-border-default);\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.merge-confirm-icon {\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 10px;\n background: color-mix(in srgb, var(--mj-status-warning) 12%, var(--mj-bg-surface));\n color: var(--mj-status-warning-text);\n font-size: 1.1rem;\n flex-shrink: 0;\n}\n\n.merge-confirm-title {\n font-size: 1.05rem;\n font-weight: 700;\n color: var(--mj-text-primary);\n}\n\n.merge-confirm-subtitle {\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n margin-top: 2px;\n}\n\n.merge-confirm-body {\n padding: 20px 24px;\n overflow-y: auto;\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n/* Surviving record card */\n\n.merge-survivor-card {\n padding: 14px;\n background: color-mix(in srgb, var(--mj-brand-primary) 6%, var(--mj-bg-surface));\n border: 1px solid var(--mj-brand-primary);\n border-radius: 8px;\n}\n\n.merge-survivor-label {\n font-size: 0.65rem;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--mj-brand-primary);\n margin-bottom: 4px;\n}\n\n.merge-survivor-label i {\n margin-right: 4px;\n}\n\n.merge-survivor-name {\n font-size: 0.95rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.merge-survivor-pk {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-top: 4px;\n padding: 4px 8px;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border-radius: 4px;\n font-size: 0.68rem;\n font-family: monospace;\n color: var(--mj-text-secondary);\n}\n\n.merge-survivor-pk i {\n font-size: 0.6rem;\n color: var(--mj-brand-primary);\n}\n\n.merge-survivor-detail {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n margin-top: 4px;\n}\n\n/* Cherry-picked fields section */\n\n.merge-section-label {\n font-size: 0.7rem;\n font-weight: 700;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n margin-bottom: 6px;\n}\n\n.merge-field-override {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 6px 0;\n border-bottom: 1px solid var(--mj-border-subtle);\n font-size: 0.78rem;\n}\n\n.merge-field-name {\n font-weight: 600;\n color: var(--mj-text-secondary);\n min-width: 110px;\n flex-shrink: 0;\n}\n\n.merge-field-value {\n color: var(--mj-text-primary);\n}\n\n.merge-field-source {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n margin-left: auto;\n white-space: nowrap;\n}\n\n/* Dependency transfer summary */\n\n.merge-deps-transfer {\n padding: 10px 14px;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n}\n\n.merge-deps-transfer-label {\n font-size: 0.65rem;\n font-weight: 700;\n text-transform: uppercase;\n color: var(--mj-brand-primary);\n margin-bottom: 6px;\n}\n\n.merge-deps-transfer-label i {\n margin-right: 4px;\n}\n\n.merge-deps-transfer-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 3px 0;\n font-size: 0.75rem;\n color: var(--mj-text-secondary);\n}\n\n.merge-deps-transfer-row i {\n color: var(--mj-brand-primary);\n font-size: 0.65rem;\n width: 14px;\n text-align: center;\n}\n\n/* Records to delete */\n\n.merge-delete-card {\n padding: 10px 14px;\n background: color-mix(in srgb, var(--mj-status-error) 5%, var(--mj-bg-surface));\n border: 1px solid var(--mj-status-error-border);\n border-radius: 8px;\n}\n\n.merge-delete-label {\n font-size: 0.65rem;\n font-weight: 700;\n text-transform: uppercase;\n color: var(--mj-status-error-text);\n margin-bottom: 6px;\n}\n\n.merge-delete-label i {\n margin-right: 4px;\n}\n\n.merge-delete-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 5px 0;\n font-size: 0.78rem;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.merge-delete-item:last-child {\n border-bottom: none;\n}\n\n.merge-delete-name {\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.merge-delete-deps {\n font-size: 0.65rem;\n color: var(--mj-text-muted);\n}\n\n/* Confirm footer */\n\n.merge-confirm-footer {\n padding: 14px 24px;\n border-top: 1px solid var(--mj-border-default);\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 10px;\n}\n\n.cancel-btn {\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n border-color: var(--mj-border-default);\n}\n\n.cancel-btn:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.confirm-merge-btn {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary);\n font-weight: 600;\n}\n\n.confirm-merge-btn:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.confirm-merge-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n/* ---- Responsive: Merge Confirm ---- */\n\n@media (max-width: 600px) {\n .merge-confirm-panel {\n width: 100%;\n }\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 Merge Disabled Hint \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.merge-disabled-hint {\n font-size: 11.5px;\n color: var(--mj-status-warning-text, #e65100);\n display: inline-flex;\n align-items: center;\n gap: 4px;\n margin-left: 4px;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550 Merge Warning Banner (inline, non-blocking) \u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.merge-warning-banner {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 16px;\n margin-bottom: 12px;\n border-radius: 6px;\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n border: 1px solid var(--mj-status-warning-border);\n font-size: 13px;\n font-weight: 500;\n flex-shrink: 0;\n}\n\n.merge-warning-banner i {\n font-size: 14px;\n flex-shrink: 0;\n}\n"] }]
2940
+ }], null, { OnEscapeKey: [{
2941
+ type: HostListener,
2942
+ args: ['document:keydown.escape']
2943
+ }], EmbeddedMode: [{
767
2944
  type: Input
768
2945
  }] }); })();
769
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(DuplicateDetectionResourceComponent, { className: "DuplicateDetectionResourceComponent", filePath: "src/AI/components/duplicates/duplicate-detection-resource.component.ts", lineNumber: 54 }); })();
2946
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(DuplicateDetectionResourceComponent, { className: "DuplicateDetectionResourceComponent", filePath: "src/AI/components/duplicates/duplicate-detection-resource.component.ts", lineNumber: 105 }); })();
770
2947
  export function LoadDuplicateDetectionResource() {
771
2948
  // Prevents tree-shaking
772
2949
  }