@memberjunction/ng-entity-viewer 0.0.1 → 2.121.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 (42) hide show
  1. package/README.md +224 -43
  2. package/dist/lib/entity-cards/entity-cards.component.d.ts +163 -0
  3. package/dist/lib/entity-cards/entity-cards.component.d.ts.map +1 -0
  4. package/dist/lib/entity-cards/entity-cards.component.js +797 -0
  5. package/dist/lib/entity-cards/entity-cards.component.js.map +1 -0
  6. package/dist/lib/entity-grid/entity-grid.component.d.ts +216 -0
  7. package/dist/lib/entity-grid/entity-grid.component.d.ts.map +1 -0
  8. package/dist/lib/entity-grid/entity-grid.component.js +676 -0
  9. package/dist/lib/entity-grid/entity-grid.component.js.map +1 -0
  10. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.d.ts +182 -0
  11. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.d.ts.map +1 -0
  12. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js +787 -0
  13. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js.map +1 -0
  14. package/dist/lib/entity-viewer/entity-viewer.component.d.ts +252 -0
  15. package/dist/lib/entity-viewer/entity-viewer.component.d.ts.map +1 -0
  16. package/dist/lib/entity-viewer/entity-viewer.component.js +883 -0
  17. package/dist/lib/entity-viewer/entity-viewer.component.js.map +1 -0
  18. package/dist/lib/pagination/pagination.component.d.ts +60 -0
  19. package/dist/lib/pagination/pagination.component.d.ts.map +1 -0
  20. package/dist/lib/pagination/pagination.component.js +199 -0
  21. package/dist/lib/pagination/pagination.component.js.map +1 -0
  22. package/dist/lib/pill/pill.component.d.ts +58 -0
  23. package/dist/lib/pill/pill.component.d.ts.map +1 -0
  24. package/dist/lib/pill/pill.component.js +125 -0
  25. package/dist/lib/pill/pill.component.js.map +1 -0
  26. package/dist/lib/types.d.ts +316 -0
  27. package/dist/lib/types.d.ts.map +1 -0
  28. package/dist/lib/types.js +30 -0
  29. package/dist/lib/types.js.map +1 -0
  30. package/dist/lib/utils/highlight.util.d.ts +69 -0
  31. package/dist/lib/utils/highlight.util.d.ts.map +1 -0
  32. package/dist/lib/utils/highlight.util.js +214 -0
  33. package/dist/lib/utils/highlight.util.js.map +1 -0
  34. package/dist/module.d.ts +38 -0
  35. package/dist/module.d.ts.map +1 -0
  36. package/dist/module.js +83 -0
  37. package/dist/module.js.map +1 -0
  38. package/dist/public-api.d.ts +16 -0
  39. package/dist/public-api.d.ts.map +1 -0
  40. package/dist/public-api.js +20 -0
  41. package/dist/public-api.js.map +1 -0
  42. package/package.json +45 -6
@@ -0,0 +1,797 @@
1
+ import { Component, Input, Output, EventEmitter } from '@angular/core';
2
+ import { EntityFieldValueListType, RunView } from '@memberjunction/core';
3
+ import { PillColorUtil } from '../pill/pill.component';
4
+ import { HighlightUtil } from '../utils/highlight.util';
5
+ import * as i0 from "@angular/core";
6
+ import * as i1 from "@memberjunction/ng-shared-generic";
7
+ import * as i2 from "../pill/pill.component";
8
+ function _forTrack0($index, $item) { return this.getRecordTrackId($item, $index); }
9
+ const _forTrack1 = ($index, $item) => $item.name;
10
+ function EntityCardsComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
11
+ i0.ɵɵelementStart(0, "div", 1);
12
+ i0.ɵɵelement(1, "mj-loading", 3);
13
+ i0.ɵɵelementEnd();
14
+ } }
15
+ function EntityCardsComponent_Conditional_2_For_2_Case_2_Template(rf, ctx) { if (rf & 1) {
16
+ i0.ɵɵelementStart(0, "div", 8);
17
+ i0.ɵɵelement(1, "img", 19);
18
+ i0.ɵɵelementEnd();
19
+ } if (rf & 2) {
20
+ const record_r2 = i0.ɵɵnextContext().$implicit;
21
+ const ctx_r2 = i0.ɵɵnextContext(2);
22
+ i0.ɵɵadvance();
23
+ i0.ɵɵproperty("src", ctx_r2.getThumbnailUrl(record_r2), i0.ɵɵsanitizeUrl);
24
+ } }
25
+ function EntityCardsComponent_Conditional_2_For_2_Case_3_Template(rf, ctx) { if (rf & 1) {
26
+ i0.ɵɵelementStart(0, "div", 20);
27
+ i0.ɵɵelement(1, "i");
28
+ i0.ɵɵelementEnd();
29
+ } if (rf & 2) {
30
+ const record_r2 = i0.ɵɵnextContext().$implicit;
31
+ const ctx_r2 = i0.ɵɵnextContext(2);
32
+ i0.ɵɵstyleProp("background-color", ctx_r2.getRecordColor(record_r2));
33
+ i0.ɵɵadvance();
34
+ i0.ɵɵclassMap(ctx_r2.getThumbnailUrl(record_r2));
35
+ } }
36
+ function EntityCardsComponent_Conditional_2_For_2_Case_4_Template(rf, ctx) { if (rf & 1) {
37
+ i0.ɵɵelementStart(0, "div", 20);
38
+ i0.ɵɵtext(1);
39
+ i0.ɵɵelementEnd();
40
+ } if (rf & 2) {
41
+ const record_r2 = i0.ɵɵnextContext().$implicit;
42
+ const ctx_r2 = i0.ɵɵnextContext(2);
43
+ i0.ɵɵstyleProp("background-color", ctx_r2.getRecordColor(record_r2));
44
+ i0.ɵɵadvance();
45
+ i0.ɵɵtextInterpolate1(" ", ctx_r2.getInitials(record_r2), " ");
46
+ } }
47
+ function EntityCardsComponent_Conditional_2_For_2_Conditional_7_Conditional_0_Template(rf, ctx) { if (rf & 1) {
48
+ i0.ɵɵelement(0, "mj-pill", 21);
49
+ } if (rf & 2) {
50
+ const subField_r4 = i0.ɵɵnextContext();
51
+ const record_r2 = i0.ɵɵnextContext().$implicit;
52
+ const ctx_r2 = i0.ɵɵnextContext(2);
53
+ i0.ɵɵproperty("value", ctx_r2.getFieldValue(record_r2, subField_r4));
54
+ } }
55
+ function EntityCardsComponent_Conditional_2_For_2_Conditional_7_Conditional_1_Template(rf, ctx) { if (rf & 1) {
56
+ i0.ɵɵelement(0, "span", 22);
57
+ } if (rf & 2) {
58
+ const subField_r4 = i0.ɵɵnextContext();
59
+ const record_r2 = i0.ɵɵnextContext().$implicit;
60
+ const ctx_r2 = i0.ɵɵnextContext(2);
61
+ i0.ɵɵproperty("innerHTML", ctx_r2.highlightMatch(ctx_r2.getFieldValue(record_r2, subField_r4)), i0.ɵɵsanitizeHtml);
62
+ } }
63
+ function EntityCardsComponent_Conditional_2_For_2_Conditional_7_Template(rf, ctx) { if (rf & 1) {
64
+ i0.ɵɵtemplate(0, EntityCardsComponent_Conditional_2_For_2_Conditional_7_Conditional_0_Template, 1, 1, "mj-pill", 21)(1, EntityCardsComponent_Conditional_2_For_2_Conditional_7_Conditional_1_Template, 1, 1, "span", 22);
65
+ } if (rf & 2) {
66
+ const ctx_r2 = i0.ɵɵnextContext(3);
67
+ i0.ɵɵconditional(ctx_r2.subtitleIsPill ? 0 : 1);
68
+ } }
69
+ function EntityCardsComponent_Conditional_2_For_2_Conditional_11_Template(rf, ctx) { if (rf & 1) {
70
+ i0.ɵɵelement(0, "p", 15);
71
+ } if (rf & 2) {
72
+ const record_r2 = i0.ɵɵnextContext().$implicit;
73
+ const ctx_r2 = i0.ɵɵnextContext(2);
74
+ i0.ɵɵproperty("innerHTML", ctx_r2.highlightMatch(ctx_r2.getTextValue(record_r2, ctx, 100)), i0.ɵɵsanitizeHtml);
75
+ } }
76
+ function EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Case_1_Template(rf, ctx) { if (rf & 1) {
77
+ i0.ɵɵelement(0, "i", 25);
78
+ i0.ɵɵelementStart(1, "span", 26);
79
+ i0.ɵɵtext(2);
80
+ i0.ɵɵelementEnd();
81
+ } if (rf & 2) {
82
+ const field_r5 = i0.ɵɵnextContext().$implicit;
83
+ const record_r2 = i0.ɵɵnextContext(2).$implicit;
84
+ const ctx_r2 = i0.ɵɵnextContext(2);
85
+ i0.ɵɵclassProp("fa-solid", true)("fa-check", ctx_r2.getBooleanValue(record_r2, field_r5.name))("fa-minus", !ctx_r2.getBooleanValue(record_r2, field_r5.name))("bool-true", ctx_r2.getBooleanValue(record_r2, field_r5.name))("bool-false", !ctx_r2.getBooleanValue(record_r2, field_r5.name));
86
+ i0.ɵɵadvance(2);
87
+ i0.ɵɵtextInterpolate(field_r5.label);
88
+ } }
89
+ function EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Case_2_Template(rf, ctx) { if (rf & 1) {
90
+ i0.ɵɵelementStart(0, "span", 27);
91
+ i0.ɵɵtext(1);
92
+ i0.ɵɵelementEnd();
93
+ i0.ɵɵelementStart(2, "span", 26);
94
+ i0.ɵɵtext(3);
95
+ i0.ɵɵelementEnd();
96
+ } if (rf & 2) {
97
+ const field_r5 = i0.ɵɵnextContext().$implicit;
98
+ const record_r2 = i0.ɵɵnextContext(2).$implicit;
99
+ const ctx_r2 = i0.ɵɵnextContext(2);
100
+ i0.ɵɵadvance();
101
+ i0.ɵɵtextInterpolate(ctx_r2.getNumericValue(record_r2, field_r5.name));
102
+ i0.ɵɵadvance(2);
103
+ i0.ɵɵtextInterpolate(field_r5.label);
104
+ } }
105
+ function EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Case_3_Template(rf, ctx) { if (rf & 1) {
106
+ i0.ɵɵelementStart(0, "span", 28);
107
+ i0.ɵɵtext(1);
108
+ i0.ɵɵelementEnd();
109
+ i0.ɵɵelementStart(2, "span", 26);
110
+ i0.ɵɵtext(3);
111
+ i0.ɵɵelementEnd();
112
+ } if (rf & 2) {
113
+ const field_r5 = i0.ɵɵnextContext().$implicit;
114
+ const record_r2 = i0.ɵɵnextContext(2).$implicit;
115
+ const ctx_r2 = i0.ɵɵnextContext(2);
116
+ i0.ɵɵadvance();
117
+ i0.ɵɵtextInterpolate(ctx_r2.getDateValue(record_r2, field_r5.name));
118
+ i0.ɵɵadvance(2);
119
+ i0.ɵɵtextInterpolate(field_r5.label);
120
+ } }
121
+ function EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Case_4_Template(rf, ctx) { if (rf & 1) {
122
+ i0.ɵɵelementStart(0, "span", 26);
123
+ i0.ɵɵtext(1);
124
+ i0.ɵɵelementEnd();
125
+ i0.ɵɵelement(2, "span", 29);
126
+ } if (rf & 2) {
127
+ const field_r5 = i0.ɵɵnextContext().$implicit;
128
+ const record_r2 = i0.ɵɵnextContext(2).$implicit;
129
+ const ctx_r2 = i0.ɵɵnextContext(2);
130
+ i0.ɵɵadvance();
131
+ i0.ɵɵtextInterpolate(field_r5.label);
132
+ i0.ɵɵadvance();
133
+ i0.ɵɵproperty("innerHTML", ctx_r2.highlightMatch(ctx_r2.getTextValue(record_r2, field_r5.name, 40)), i0.ɵɵsanitizeHtml);
134
+ } }
135
+ function EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Template(rf, ctx) { if (rf & 1) {
136
+ i0.ɵɵelementStart(0, "div", 24);
137
+ i0.ɵɵtemplate(1, EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Case_1_Template, 3, 11)(2, EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Case_2_Template, 4, 2)(3, EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Case_3_Template, 4, 2)(4, EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Case_4_Template, 3, 2);
138
+ i0.ɵɵelementEnd();
139
+ } if (rf & 2) {
140
+ let tmp_24_0;
141
+ const field_r5 = ctx.$implicit;
142
+ i0.ɵɵclassProp("field-boolean", field_r5.type === "boolean")("field-text", field_r5.type === "text");
143
+ i0.ɵɵadvance();
144
+ i0.ɵɵconditional((tmp_24_0 = field_r5.type) === "boolean" ? 1 : tmp_24_0 === "number" ? 2 : tmp_24_0 === "date" ? 3 : 4);
145
+ } }
146
+ function EntityCardsComponent_Conditional_2_For_2_Conditional_12_Template(rf, ctx) { if (rf & 1) {
147
+ i0.ɵɵelementStart(0, "div", 16);
148
+ i0.ɵɵrepeaterCreate(1, EntityCardsComponent_Conditional_2_For_2_Conditional_12_For_2_Template, 5, 5, "div", 23, _forTrack1);
149
+ i0.ɵɵelementEnd();
150
+ } if (rf & 2) {
151
+ const ctx_r2 = i0.ɵɵnextContext(3);
152
+ i0.ɵɵadvance();
153
+ i0.ɵɵrepeater(ctx_r2.effectiveTemplate.displayFields);
154
+ } }
155
+ function EntityCardsComponent_Conditional_2_For_2_Conditional_13_Template(rf, ctx) { if (rf & 1) {
156
+ i0.ɵɵelementStart(0, "div", 17);
157
+ i0.ɵɵelement(1, "mj-pill", 21);
158
+ i0.ɵɵelementEnd();
159
+ } if (rf & 2) {
160
+ const record_r2 = i0.ɵɵnextContext().$implicit;
161
+ const ctx_r2 = i0.ɵɵnextContext(2);
162
+ i0.ɵɵadvance();
163
+ i0.ɵɵproperty("value", ctx_r2.getFieldValue(record_r2, ctx));
164
+ } }
165
+ function EntityCardsComponent_Conditional_2_For_2_Conditional_14_Template(rf, ctx) { if (rf & 1) {
166
+ i0.ɵɵelementStart(0, "div", 18);
167
+ i0.ɵɵelement(1, "i", 30);
168
+ i0.ɵɵelementStart(2, "span");
169
+ i0.ɵɵtext(3);
170
+ i0.ɵɵelementEnd()();
171
+ } if (rf & 2) {
172
+ const record_r2 = i0.ɵɵnextContext().$implicit;
173
+ const ctx_r2 = i0.ɵɵnextContext(2);
174
+ i0.ɵɵproperty("title", "Matched in: " + ctx_r2.getHiddenMatchFieldName(record_r2));
175
+ i0.ɵɵadvance(3);
176
+ i0.ɵɵtextInterpolate(ctx_r2.getHiddenMatchFieldName(record_r2));
177
+ } }
178
+ function EntityCardsComponent_Conditional_2_For_2_Template(rf, ctx) { if (rf & 1) {
179
+ const _r1 = i0.ɵɵgetCurrentView();
180
+ i0.ɵɵelementStart(0, "div", 6);
181
+ i0.ɵɵlistener("click", function EntityCardsComponent_Conditional_2_For_2_Template_div_click_0_listener() { const record_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onCardClick(record_r2)); });
182
+ i0.ɵɵelementStart(1, "div", 7);
183
+ i0.ɵɵtemplate(2, EntityCardsComponent_Conditional_2_For_2_Case_2_Template, 2, 1, "div", 8)(3, EntityCardsComponent_Conditional_2_For_2_Case_3_Template, 2, 4, "div", 9)(4, EntityCardsComponent_Conditional_2_For_2_Case_4_Template, 2, 3, "div", 9);
184
+ i0.ɵɵelementStart(5, "div", 10);
185
+ i0.ɵɵelement(6, "h3", 11);
186
+ i0.ɵɵtemplate(7, EntityCardsComponent_Conditional_2_For_2_Conditional_7_Template, 2, 1);
187
+ i0.ɵɵelementEnd();
188
+ i0.ɵɵelementStart(8, "button", 12);
189
+ i0.ɵɵlistener("click", function EntityCardsComponent_Conditional_2_For_2_Template_button_click_8_listener($event) { const record_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onOpenClick($event, record_r2)); });
190
+ i0.ɵɵelement(9, "i", 13);
191
+ i0.ɵɵelementEnd()();
192
+ i0.ɵɵelementStart(10, "div", 14);
193
+ i0.ɵɵtemplate(11, EntityCardsComponent_Conditional_2_For_2_Conditional_11_Template, 1, 1, "p", 15)(12, EntityCardsComponent_Conditional_2_For_2_Conditional_12_Template, 3, 0, "div", 16);
194
+ i0.ɵɵelementEnd();
195
+ i0.ɵɵtemplate(13, EntityCardsComponent_Conditional_2_For_2_Conditional_13_Template, 2, 1, "div", 17)(14, EntityCardsComponent_Conditional_2_For_2_Conditional_14_Template, 4, 2, "div", 18);
196
+ i0.ɵɵelementEnd();
197
+ } if (rf & 2) {
198
+ let tmp_12_0;
199
+ let tmp_13_0;
200
+ let tmp_14_0;
201
+ let tmp_15_0;
202
+ let tmp_17_0;
203
+ const record_r2 = ctx.$implicit;
204
+ const ctx_r2 = i0.ɵɵnextContext(2);
205
+ i0.ɵɵclassProp("selected", ctx_r2.isSelected(record_r2));
206
+ i0.ɵɵadvance(2);
207
+ i0.ɵɵconditional((tmp_12_0 = ctx_r2.getThumbnailType(record_r2)) === "image" ? 2 : tmp_12_0 === "icon" ? 3 : 4);
208
+ i0.ɵɵadvance(4);
209
+ i0.ɵɵproperty("innerHTML", ctx_r2.highlightMatch(ctx_r2.getFieldValue(record_r2, (tmp_13_0 = ctx_r2.effectiveTemplate == null ? null : ctx_r2.effectiveTemplate.titleField) !== null && tmp_13_0 !== undefined ? tmp_13_0 : null)), i0.ɵɵsanitizeHtml);
210
+ i0.ɵɵadvance();
211
+ i0.ɵɵconditional((tmp_14_0 = ctx_r2.effectiveTemplate == null ? null : ctx_r2.effectiveTemplate.subtitleField) ? 7 : -1, tmp_14_0);
212
+ i0.ɵɵadvance(4);
213
+ i0.ɵɵconditional((tmp_15_0 = ctx_r2.effectiveTemplate == null ? null : ctx_r2.effectiveTemplate.descriptionField) ? 11 : -1, tmp_15_0);
214
+ i0.ɵɵadvance();
215
+ i0.ɵɵconditional(ctx_r2.effectiveTemplate && ctx_r2.effectiveTemplate.displayFields.length > 0 ? 12 : -1);
216
+ i0.ɵɵadvance();
217
+ i0.ɵɵconditional((tmp_17_0 = ctx_r2.effectiveTemplate == null ? null : ctx_r2.effectiveTemplate.badgeField) ? 13 : -1, tmp_17_0);
218
+ i0.ɵɵadvance();
219
+ i0.ɵɵconditional(ctx_r2.hasHiddenFieldMatch(record_r2) ? 14 : -1);
220
+ } }
221
+ function EntityCardsComponent_Conditional_2_Conditional_3_Template(rf, ctx) { if (rf & 1) {
222
+ i0.ɵɵelementStart(0, "div", 5);
223
+ i0.ɵɵelement(1, "i", 31);
224
+ i0.ɵɵelementStart(2, "p");
225
+ i0.ɵɵtext(3, "No records to display");
226
+ i0.ɵɵelementEnd()();
227
+ } }
228
+ function EntityCardsComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
229
+ i0.ɵɵelementStart(0, "div", 2);
230
+ i0.ɵɵrepeaterCreate(1, EntityCardsComponent_Conditional_2_For_2_Template, 15, 9, "div", 4, _forTrack0, true);
231
+ i0.ɵɵtemplate(3, EntityCardsComponent_Conditional_2_Conditional_3_Template, 4, 0, "div", 5);
232
+ i0.ɵɵelementEnd();
233
+ } if (rf & 2) {
234
+ const ctx_r2 = i0.ɵɵnextContext();
235
+ i0.ɵɵadvance();
236
+ i0.ɵɵrepeater(ctx_r2.effectiveRecords);
237
+ i0.ɵɵadvance(2);
238
+ i0.ɵɵconditional(ctx_r2.effectiveRecords.length === 0 ? 3 : -1);
239
+ } }
240
+ /**
241
+ * EntityCardsComponent - Card-based view for entity records
242
+ *
243
+ * This component provides an auto-generated card layout for displaying
244
+ * entity records. Cards are automatically structured based on entity metadata.
245
+ *
246
+ * Supports two modes:
247
+ * 1. Parent-managed data: Records are passed in via [records] input
248
+ * 2. Standalone: Component loads its own data with pagination
249
+ *
250
+ * @example
251
+ * ```html
252
+ * <mj-entity-cards
253
+ * [entity]="selectedEntity"
254
+ * [records]="filteredRecords"
255
+ * [selectedRecordId]="selectedId"
256
+ * (recordSelected)="onRecordSelected($event)"
257
+ * (recordOpened)="onRecordOpened($event)">
258
+ * </mj-entity-cards>
259
+ * ```
260
+ */
261
+ export class EntityCardsComponent {
262
+ elementRef;
263
+ cdr;
264
+ constructor(elementRef, cdr) {
265
+ this.elementRef = elementRef;
266
+ this.cdr = cdr;
267
+ }
268
+ /**
269
+ * The entity metadata for the records being displayed
270
+ */
271
+ entity = null;
272
+ /**
273
+ * The records to display as cards (optional - component can load its own)
274
+ */
275
+ records = null;
276
+ /**
277
+ * The currently selected record's primary key string
278
+ */
279
+ selectedRecordId = null;
280
+ /**
281
+ * Custom card template (optional - auto-generated if not provided)
282
+ */
283
+ cardTemplate = null;
284
+ /**
285
+ * Map of record IDs to hidden field names that matched the filter
286
+ * Used to display an indicator when a match occurred in a non-visible field
287
+ */
288
+ hiddenFieldMatches = new Map();
289
+ /**
290
+ * Current filter text for highlighting matches
291
+ * Supports SQL-style % wildcards
292
+ */
293
+ filterText = '';
294
+ /**
295
+ * Page size for standalone data loading
296
+ * @default 100
297
+ */
298
+ pageSize = 100;
299
+ /**
300
+ * Emitted when a record is selected (single click)
301
+ */
302
+ recordSelected = new EventEmitter();
303
+ /**
304
+ * Emitted when a record should be opened (double click or open button)
305
+ */
306
+ recordOpened = new EventEmitter();
307
+ /** Auto-generated card template */
308
+ autoCardTemplate = null;
309
+ /** Internal records when loading standalone */
310
+ internalRecords = [];
311
+ /** Track if we're in standalone mode */
312
+ standaloneMode = false;
313
+ /** Loading state for standalone mode */
314
+ isLoading = false;
315
+ /** Flag to trigger scroll to selected card after view renders */
316
+ pendingScrollToSelected = false;
317
+ ngOnInit() {
318
+ this.standaloneMode = this.records === null;
319
+ if (this.entity?.Fields && !this.effectiveTemplate) {
320
+ this.autoCardTemplate = this.generateCardTemplate(this.entity);
321
+ }
322
+ if (this.standaloneMode && this.entity) {
323
+ this.loadData();
324
+ }
325
+ }
326
+ ngOnChanges(changes) {
327
+ if (changes['entity'] && this.entity?.Fields) {
328
+ this.autoCardTemplate = this.generateCardTemplate(this.entity);
329
+ }
330
+ else if (changes['entity'] && !this.entity) {
331
+ this.autoCardTemplate = null;
332
+ }
333
+ if (changes['entity'] && this.standaloneMode && this.entity) {
334
+ this.loadData();
335
+ }
336
+ if (changes['records']) {
337
+ this.standaloneMode = this.records === null;
338
+ // When records change and we have a selection, scroll to it
339
+ if (this.selectedRecordId) {
340
+ this.pendingScrollToSelected = true;
341
+ }
342
+ }
343
+ // When selectedRecordId changes programmatically, scroll to the selected card
344
+ if (changes['selectedRecordId'] && this.selectedRecordId) {
345
+ this.pendingScrollToSelected = true;
346
+ }
347
+ }
348
+ ngAfterViewChecked() {
349
+ if (this.pendingScrollToSelected && this.selectedRecordId) {
350
+ this.pendingScrollToSelected = false;
351
+ // Delay scroll to allow detail panel animation to complete and layout to stabilize
352
+ // This ensures the scroll happens after the viewport width has adjusted
353
+ setTimeout(() => this.scrollToSelectedCard(), 350);
354
+ }
355
+ }
356
+ /**
357
+ * Scroll the selected card into view
358
+ */
359
+ scrollToSelectedCard() {
360
+ if (!this.selectedRecordId)
361
+ return;
362
+ // Find the selected card element using the CSS class
363
+ const selectedCard = this.elementRef.nativeElement.querySelector('.data-card.selected');
364
+ if (selectedCard) {
365
+ selectedCard.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
366
+ }
367
+ }
368
+ /**
369
+ * Get effective records (external or internal)
370
+ */
371
+ get effectiveRecords() {
372
+ return this.records ?? this.internalRecords;
373
+ }
374
+ /**
375
+ * Load data in standalone mode
376
+ */
377
+ async loadData() {
378
+ if (!this.entity)
379
+ return;
380
+ this.isLoading = true;
381
+ try {
382
+ const rv = new RunView();
383
+ const result = await rv.RunView({
384
+ EntityName: this.entity.Name,
385
+ ResultType: 'entity_object',
386
+ MaxRows: this.pageSize
387
+ });
388
+ if (result.Success) {
389
+ this.internalRecords = result.Results;
390
+ }
391
+ }
392
+ catch (error) {
393
+ console.error('Error loading cards data:', error);
394
+ }
395
+ finally {
396
+ this.isLoading = false;
397
+ }
398
+ }
399
+ /**
400
+ * Get the effective card template (custom or auto-generated)
401
+ */
402
+ get effectiveTemplate() {
403
+ return this.cardTemplate || this.autoCardTemplate;
404
+ }
405
+ // ========================================
406
+ // TEMPLATE GENERATION
407
+ // ========================================
408
+ /**
409
+ * Generate card template from entity metadata
410
+ */
411
+ generateCardTemplate(entity) {
412
+ const fields = entity.Fields;
413
+ if (!fields || fields.length === 0)
414
+ return null;
415
+ return {
416
+ titleField: this.findTitleField(entity, fields),
417
+ subtitleField: this.findSubtitleField(fields),
418
+ descriptionField: this.findDescriptionField(fields),
419
+ displayFields: this.findDisplayFields(fields),
420
+ thumbnailFields: this.findThumbnailFields(fields),
421
+ badgeField: this.findBadgeField(fields)
422
+ };
423
+ }
424
+ findTitleField(entity, fields) {
425
+ if (entity.NameField)
426
+ return entity.NameField.Name;
427
+ const nameField = fields.find(f => f.Name.toLowerCase() === 'name' || f.Name.toLowerCase() === 'title');
428
+ if (nameField)
429
+ return nameField.Name;
430
+ const endsWithName = fields.find(f => f.Name.toLowerCase().endsWith('name') &&
431
+ f.TSType === 'string' &&
432
+ !f.Name.toLowerCase().includes('file') &&
433
+ !f.IsPrimaryKey);
434
+ if (endsWithName)
435
+ return endsWithName.Name;
436
+ const firstString = fields.find(f => f.TSType === 'string' && !f.IsPrimaryKey && !f.Name.toLowerCase().includes('id'));
437
+ if (firstString)
438
+ return firstString.Name;
439
+ const pk = fields.find(f => f.IsPrimaryKey);
440
+ return pk?.Name || 'ID';
441
+ }
442
+ findSubtitleField(fields) {
443
+ const keywords = ['status', 'type', 'category', 'state', 'stage'];
444
+ for (const keyword of keywords) {
445
+ const field = fields.find(f => f.Name.toLowerCase().includes(keyword) && f.TSType === 'string' && !f.IsPrimaryKey);
446
+ if (field)
447
+ return field.Name;
448
+ }
449
+ return null;
450
+ }
451
+ findDescriptionField(fields) {
452
+ const keywords = ['description', 'desc', 'summary', 'notes', 'comments'];
453
+ for (const keyword of keywords) {
454
+ const field = fields.find(f => f.Name.toLowerCase().includes(keyword) && f.TSType === 'string');
455
+ if (field)
456
+ return field.Name;
457
+ }
458
+ return null;
459
+ }
460
+ findDisplayFields(fields) {
461
+ const displayFields = [];
462
+ const excludePatterns = ['id', 'name', 'title', 'description', 'desc', 'summary', 'notes',
463
+ 'status', 'type', 'category', 'state', 'stage', 'password', 'secret',
464
+ '__mj_', 'createdat', 'updatedat', 'createdby', 'updatedby'];
465
+ const defaultInViewFields = fields.filter(f => f.DefaultInView === true && !f.IsPrimaryKey &&
466
+ !excludePatterns.some(p => f.Name.toLowerCase().includes(p)));
467
+ for (const field of defaultInViewFields) {
468
+ if (displayFields.length >= 4)
469
+ break;
470
+ displayFields.push({
471
+ name: field.Name,
472
+ type: this.getFieldType(field),
473
+ label: this.getFieldLabel(field)
474
+ });
475
+ }
476
+ if (displayFields.length >= 2)
477
+ return displayFields;
478
+ const metricKeywords = ['amount', 'total', 'count', 'value', 'price', 'cost', 'quantity', 'qty', 'balance', 'revenue', 'score'];
479
+ for (const field of fields) {
480
+ if (displayFields.length >= 4)
481
+ break;
482
+ if (displayFields.some(df => df.name === field.Name))
483
+ continue;
484
+ if (metricKeywords.some(kw => field.Name.toLowerCase().includes(kw))) {
485
+ displayFields.push({
486
+ name: field.Name,
487
+ type: this.getFieldType(field),
488
+ label: this.getFieldLabel(field)
489
+ });
490
+ }
491
+ }
492
+ return displayFields;
493
+ }
494
+ /**
495
+ * Find all potential thumbnail fields in priority order
496
+ * Returns an array so we can fall back per-record if one is empty
497
+ */
498
+ findThumbnailFields(fields) {
499
+ const imageKeywords = ['image', 'photo', 'picture', 'thumbnail', 'avatar', 'logo', 'icon'];
500
+ const foundFields = [];
501
+ const foundFieldNames = new Set();
502
+ for (const keyword of imageKeywords) {
503
+ const matchingFields = fields.filter(f => f.Name.toLowerCase().includes(keyword) &&
504
+ f.TSType === 'string' &&
505
+ !foundFieldNames.has(f.Name));
506
+ for (const field of matchingFields) {
507
+ foundFields.push(field.Name);
508
+ foundFieldNames.add(field.Name);
509
+ }
510
+ }
511
+ return foundFields;
512
+ }
513
+ findBadgeField(fields) {
514
+ const keywords = ['priority', 'severity', 'importance', 'rating', 'rank', 'level'];
515
+ for (const keyword of keywords) {
516
+ const field = fields.find(f => f.Name.toLowerCase().includes(keyword));
517
+ if (field)
518
+ return field.Name;
519
+ }
520
+ return null;
521
+ }
522
+ getFieldType(field) {
523
+ if (field.TSType === 'boolean' || field.Type?.toLowerCase() === 'bit')
524
+ return 'boolean';
525
+ if (field.TSType === 'number')
526
+ return 'number';
527
+ if (field.TSType === 'Date' || field.Type?.toLowerCase().includes('date'))
528
+ return 'date';
529
+ return 'text';
530
+ }
531
+ // ========================================
532
+ // VALUE FORMATTING
533
+ // ========================================
534
+ getFieldValue(record, fieldName) {
535
+ if (!fieldName)
536
+ return '';
537
+ const value = record.Get(fieldName);
538
+ if (value === null || value === undefined)
539
+ return '';
540
+ return String(value);
541
+ }
542
+ getNumericValue(record, fieldName) {
543
+ const value = record.Get(fieldName);
544
+ if (value === null || value === undefined)
545
+ return '-';
546
+ const num = Number(value);
547
+ if (isNaN(num))
548
+ return String(value);
549
+ if (num >= 1000000)
550
+ return `${(num / 1000000).toFixed(1)}M`;
551
+ if (num >= 1000)
552
+ return `${(num / 1000).toFixed(1)}K`;
553
+ const fieldNameLower = fieldName.toLowerCase();
554
+ if (['amount', 'price', 'cost', 'value', 'revenue', 'total'].some(k => fieldNameLower.includes(k))) {
555
+ return `$${num.toLocaleString()}`;
556
+ }
557
+ return num.toLocaleString();
558
+ }
559
+ getBooleanValue(record, fieldName) {
560
+ const value = record.Get(fieldName);
561
+ if (value === null || value === undefined)
562
+ return false;
563
+ if (typeof value === 'boolean')
564
+ return value;
565
+ if (typeof value === 'number')
566
+ return value !== 0;
567
+ if (typeof value === 'string')
568
+ return value.toLowerCase() === 'true' || value === '1';
569
+ return Boolean(value);
570
+ }
571
+ getTextValue(record, fieldName, maxLength = 50) {
572
+ const value = this.getFieldValue(record, fieldName);
573
+ if (!value)
574
+ return '-';
575
+ if (value.length <= maxLength)
576
+ return value;
577
+ return value.substring(0, maxLength) + '...';
578
+ }
579
+ getDateValue(record, fieldName) {
580
+ const value = record.Get(fieldName);
581
+ if (value === null || value === undefined)
582
+ return '-';
583
+ try {
584
+ const date = value instanceof Date ? value : new Date(value);
585
+ if (isNaN(date.getTime()))
586
+ return String(value);
587
+ return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
588
+ }
589
+ catch {
590
+ return String(value);
591
+ }
592
+ }
593
+ /**
594
+ * Get display label for a field using EntityFieldInfo's built-in DisplayNameOrName property
595
+ */
596
+ getFieldLabel(field) {
597
+ return field.DisplayNameOrName;
598
+ }
599
+ // ========================================
600
+ // CARD DISPLAY HELPERS
601
+ // ========================================
602
+ getRecordTrackId(record, index) {
603
+ try {
604
+ const pk = record?.PrimaryKey?.ToString();
605
+ if (pk && pk.trim().length > 0)
606
+ return pk;
607
+ }
608
+ catch { /* ignore */ }
609
+ return `record_${index}`;
610
+ }
611
+ isSelected(record) {
612
+ return record.PrimaryKey.ToConcatenatedString() === this.selectedRecordId;
613
+ }
614
+ onCardClick(record) {
615
+ if (!this.entity)
616
+ return;
617
+ this.recordSelected.emit({
618
+ record,
619
+ entity: this.entity,
620
+ compositeKey: record.PrimaryKey
621
+ });
622
+ }
623
+ onOpenClick(event, record) {
624
+ event.stopPropagation();
625
+ if (!this.entity)
626
+ return;
627
+ this.recordOpened.emit({
628
+ record,
629
+ entity: this.entity,
630
+ compositeKey: record.PrimaryKey
631
+ });
632
+ }
633
+ getInitials(record) {
634
+ const template = this.effectiveTemplate;
635
+ if (!template?.titleField)
636
+ return '?';
637
+ const title = this.getFieldValue(record, template.titleField);
638
+ if (!title)
639
+ return '?';
640
+ const words = title.split(/\s+/).filter(w => w.length > 0);
641
+ if (words.length === 1)
642
+ return words[0].substring(0, 2).toUpperCase();
643
+ return (words[0][0] + words[1][0]).toUpperCase();
644
+ }
645
+ /**
646
+ * Get the thumbnail type for a record, with per-record fallback through thumbnailFields
647
+ */
648
+ getThumbnailType(record) {
649
+ const fieldInfo = this.getEffectiveThumbnailField(record);
650
+ if (!fieldInfo)
651
+ return 'none';
652
+ const { fieldName, value } = fieldInfo;
653
+ // Check if value is an image URL
654
+ if (this.isImageValue(value))
655
+ return 'image';
656
+ // Check if value looks like an icon class
657
+ if (this.isIconClass(value))
658
+ return 'icon';
659
+ // If field name suggests it's an icon field, treat non-URL values as icon classes
660
+ const fieldNameLower = fieldName.toLowerCase();
661
+ if (fieldNameLower.includes('icon') || fieldNameLower.includes('class')) {
662
+ return 'icon';
663
+ }
664
+ return 'none';
665
+ }
666
+ /**
667
+ * Get the thumbnail URL/value for a record, with per-record fallback
668
+ */
669
+ getThumbnailUrl(record) {
670
+ const fieldInfo = this.getEffectiveThumbnailField(record);
671
+ return fieldInfo?.value || '';
672
+ }
673
+ /**
674
+ * Find the first thumbnail field that has a value for this record
675
+ * Returns both the field name and value for type determination
676
+ */
677
+ getEffectiveThumbnailField(record) {
678
+ const template = this.effectiveTemplate;
679
+ if (!template?.thumbnailFields || template.thumbnailFields.length === 0)
680
+ return null;
681
+ // Try each field in priority order until we find one with a value
682
+ for (const fieldName of template.thumbnailFields) {
683
+ const value = this.getFieldValue(record, fieldName);
684
+ if (value && value.trim() !== '') {
685
+ return { fieldName, value };
686
+ }
687
+ }
688
+ return null;
689
+ }
690
+ isImageValue(value) {
691
+ if (!value)
692
+ return false;
693
+ const trimmed = value.trim();
694
+ if (trimmed.startsWith('data:image/'))
695
+ return true;
696
+ if (trimmed.startsWith('http://') || trimmed.startsWith('https://'))
697
+ return true;
698
+ return false;
699
+ }
700
+ isIconClass(value) {
701
+ if (!value)
702
+ return false;
703
+ const trimmed = value.trim().toLowerCase();
704
+ if (trimmed.startsWith('fa-') || trimmed.startsWith('fa ') ||
705
+ trimmed.startsWith('fas ') || trimmed.startsWith('far ') ||
706
+ trimmed.startsWith('fal ') || trimmed.startsWith('fab '))
707
+ return true;
708
+ if (trimmed.includes('material-icons') || trimmed.startsWith('mat-icon'))
709
+ return true;
710
+ if (trimmed.startsWith('bi-') || trimmed.startsWith('bi '))
711
+ return true;
712
+ return false;
713
+ }
714
+ getRecordColor(record) {
715
+ const colors = ['#1976d2', '#388e3c', '#f57c00', '#7b1fa2', '#c2185b', '#0097a7', '#5d4037', '#455a64'];
716
+ const pk = record.PrimaryKey.ToString();
717
+ let hash = 0;
718
+ for (let i = 0; i < pk.length; i++) {
719
+ hash = pk.charCodeAt(i) + ((hash << 5) - hash);
720
+ }
721
+ return colors[Math.abs(hash) % colors.length];
722
+ }
723
+ isEnumField(fieldName) {
724
+ if (!this.entity)
725
+ return false;
726
+ const field = this.entity.Fields.find(f => f.Name === fieldName);
727
+ if (!field)
728
+ return false;
729
+ return field.ValueListTypeEnum !== EntityFieldValueListType.None && field.EntityFieldValues.length > 0;
730
+ }
731
+ get subtitleIsPill() {
732
+ const template = this.effectiveTemplate;
733
+ if (!template?.subtitleField || !this.entity)
734
+ return false;
735
+ return this.isEnumField(template.subtitleField);
736
+ }
737
+ getPillColorType(value) {
738
+ return PillColorUtil.getColorType(value);
739
+ }
740
+ /**
741
+ * Check if a record matched on a hidden field
742
+ */
743
+ hasHiddenFieldMatch(record) {
744
+ return this.hiddenFieldMatches.has(record.PrimaryKey.ToConcatenatedString());
745
+ }
746
+ /**
747
+ * Get the display name of the hidden field that matched
748
+ */
749
+ getHiddenMatchFieldName(record) {
750
+ const fieldName = this.hiddenFieldMatches.get(record.PrimaryKey.ToConcatenatedString());
751
+ if (!fieldName || !this.entity)
752
+ return '';
753
+ // Look up the field in entity metadata and use DisplayNameOrName
754
+ const field = this.entity.Fields.find(f => f.Name === fieldName);
755
+ return field ? field.DisplayNameOrName : fieldName;
756
+ }
757
+ /**
758
+ * Highlight matching text in a string based on the filter text
759
+ * Uses HighlightUtil which only highlights if the text actually matches the pattern
760
+ */
761
+ highlightMatch(text) {
762
+ return HighlightUtil.highlight(text, this.filterText, false);
763
+ }
764
+ static ɵfac = function EntityCardsComponent_Factory(t) { return new (t || EntityCardsComponent)(i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
765
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: EntityCardsComponent, selectors: [["mj-entity-cards"]], inputs: { entity: "entity", records: "records", selectedRecordId: "selectedRecordId", cardTemplate: "cardTemplate", hiddenFieldMatches: "hiddenFieldMatches", filterText: "filterText", pageSize: "pageSize" }, outputs: { recordSelected: "recordSelected", recordOpened: "recordOpened" }, features: [i0.ɵɵNgOnChangesFeature], decls: 3, vars: 1, consts: [[1, "cards-view-wrapper"], [1, "loading-container"], [1, "cards-container"], ["text", "Loading records..."], [1, "data-card", 3, "selected"], [1, "no-results"], [1, "data-card", 3, "click"], [1, "card-header"], [1, "card-thumbnail"], [1, "card-avatar", 3, "background-color"], [1, "card-header-content"], [1, "card-title", 3, "innerHTML"], ["title", "Open Record", 1, "card-open-btn", 3, "click"], [1, "fa-solid", "fa-external-link-alt"], [1, "card-body"], [1, "card-description", 3, "innerHTML"], [1, "card-fields"], [1, "card-footer"], [1, "hidden-match-indicator", 3, "title"], ["alt", "", 3, "src"], [1, "card-avatar"], [3, "value"], [1, "card-subtitle", 3, "innerHTML"], [1, "field-item", 3, "field-boolean", "field-text"], [1, "field-item"], [1, "field-icon"], [1, "field-label"], [1, "field-value"], [1, "field-value", "field-date"], [1, "field-text-value", 3, "innerHTML"], [1, "fa-solid", "fa-magnifying-glass"], [1, "fa-solid", "fa-inbox"]], template: function EntityCardsComponent_Template(rf, ctx) { if (rf & 1) {
766
+ i0.ɵɵelementStart(0, "div", 0);
767
+ i0.ɵɵtemplate(1, EntityCardsComponent_Conditional_1_Template, 2, 0, "div", 1)(2, EntityCardsComponent_Conditional_2_Template, 4, 1, "div", 2);
768
+ i0.ɵɵelementEnd();
769
+ } if (rf & 2) {
770
+ i0.ɵɵadvance();
771
+ i0.ɵɵconditional(ctx.isLoading && ctx.effectiveRecords.length === 0 ? 1 : 2);
772
+ } }, dependencies: [i1.LoadingComponent, i2.PillComponent], styles: [".cards-view-wrapper[_ngcontent-%COMP%] {\n height: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n}\n\n.cards-container[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 20px;\n padding: 4px;\n}\n\n.data-card[_ngcontent-%COMP%] {\n background: white;\n border-radius: 12px;\n border: 1px solid #e0e0e0;\n overflow: hidden;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n\n.data-card[_ngcontent-%COMP%]:hover:not(.selected) {\n border-color: #bdbdbd;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\n.data-card[_ngcontent-%COMP%]:hover .card-open-btn[_ngcontent-%COMP%] {\n opacity: 1;\n}\n\n\n\n.data-card.selected[_ngcontent-%COMP%] {\n border-color: #1976d2;\n}\n\n.data-card.selected[_ngcontent-%COMP%] .card-title[_ngcontent-%COMP%] {\n color: #1565c0;\n}\n\n\n\n.data-card.selected[_ngcontent-%COMP%]:hover {\n border-color: #1976d2;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\n\n.card-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n padding: 16px;\n gap: 12px;\n}\n\n.card-thumbnail[_ngcontent-%COMP%] {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n overflow: hidden;\n flex-shrink: 0;\n}\n.card-thumbnail[_ngcontent-%COMP%] img[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n.card-avatar[_ngcontent-%COMP%] {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: 600;\n color: white;\n flex-shrink: 0;\n}\n\n.card-header-content[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.card-title[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: #212121;\n line-height: 1.3;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.card-subtitle[_ngcontent-%COMP%] {\n display: block;\n margin-top: 2px;\n font-size: 12px;\n color: #757575;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.card-open-btn[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #9e9e9e;\n opacity: 0;\n transition: all 0.15s ease;\n flex-shrink: 0;\n}\n.card-open-btn[_ngcontent-%COMP%]:hover {\n background: #f5f5f5;\n color: #1976d2;\n}\n\n.card-body[_ngcontent-%COMP%] {\n padding: 0 16px 16px 16px;\n}\n\n.card-description[_ngcontent-%COMP%] {\n margin: 0 0 12px 0;\n font-size: 13px;\n color: #616161;\n line-height: 1.5;\n}\n\n\n\n.card-fields[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n padding-top: 12px;\n border-top: 1px solid #f0f0f0;\n}\n\n.field-item[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 60px;\n}\n\n.field-item[_ngcontent-%COMP%] .field-value[_ngcontent-%COMP%] {\n font-size: 15px;\n font-weight: 600;\n color: #212121;\n}\n\n.field-item[_ngcontent-%COMP%] .field-label[_ngcontent-%COMP%] {\n font-size: 10px;\n color: #9e9e9e;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n\n\n.field-item.field-boolean[_ngcontent-%COMP%] {\n flex-direction: row;\n align-items: center;\n gap: 6px;\n min-width: auto;\n}\n\n.field-item.field-boolean[_ngcontent-%COMP%] .field-icon[_ngcontent-%COMP%] {\n font-size: 14px;\n}\n\n.field-item.field-boolean[_ngcontent-%COMP%] .field-icon.bool-true[_ngcontent-%COMP%] {\n color: #2e7d32;\n}\n\n.field-item.field-boolean[_ngcontent-%COMP%] .field-icon.bool-false[_ngcontent-%COMP%] {\n color: #bdbdbd;\n}\n\n.field-item.field-boolean[_ngcontent-%COMP%] .field-label[_ngcontent-%COMP%] {\n font-size: 12px;\n text-transform: none;\n color: #616161;\n}\n\n\n\n.field-item.field-text[_ngcontent-%COMP%] {\n flex: 1 1 100%;\n max-width: 100%;\n}\n\n.field-item[_ngcontent-%COMP%] .field-text-value[_ngcontent-%COMP%] {\n font-size: 13px;\n color: #424242;\n line-height: 1.4;\n word-break: break-word;\n}\n\n\n\n.field-item[_ngcontent-%COMP%] .field-date[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 500;\n}\n\n.card-footer[_ngcontent-%COMP%] {\n padding: 12px 16px;\n background: #fafafa;\n border-top: 1px solid #f0f0f0;\n}\n\n\n\n .highlight-match {\n background-color: #fff176;\n border-radius: 2px;\n}\n\n\n\n.card-header-content[_ngcontent-%COMP%] mj-pill[_ngcontent-%COMP%] {\n display: block;\n margin-top: 4px;\n}\n\n\n\n.hidden-match-indicator[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n background: #fff8e1;\n border-top: 1px solid #ffe082;\n font-size: 11px;\n color: #f57c00;\n}\n\n.hidden-match-indicator[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 10px;\n}\n\n.hidden-match-indicator[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-weight: 500;\n}\n\n\n\n.loading-container[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 200px;\n padding: 40px;\n}\n\n\n\n.no-results[_ngcontent-%COMP%] {\n grid-column: 1 / -1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 20px;\n color: #9e9e9e;\n text-align: center;\n}\n.no-results[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n}\n.no-results[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n}"] });
773
+ }
774
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EntityCardsComponent, [{
775
+ type: Component,
776
+ args: [{ selector: 'mj-entity-cards', template: "<div class=\"cards-view-wrapper\">\n @if (isLoading && effectiveRecords.length === 0) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading records...\"></mj-loading>\n </div>\n } @else {\n <div class=\"cards-container\">\n @for (record of effectiveRecords; track getRecordTrackId(record, $index)) {\n <div\n class=\"data-card\"\n [class.selected]=\"isSelected(record)\"\n (click)=\"onCardClick(record)\">\n\n <!-- Card Header with Avatar/Icon -->\n <div class=\"card-header\">\n @switch (getThumbnailType(record)) {\n @case ('image') {\n <div class=\"card-thumbnail\">\n <img [src]=\"getThumbnailUrl(record)\" alt=\"\" />\n </div>\n }\n @case ('icon') {\n <div class=\"card-avatar\" [style.background-color]=\"getRecordColor(record)\">\n <i [class]=\"getThumbnailUrl(record)\"></i>\n </div>\n }\n @default {\n <div class=\"card-avatar\" [style.background-color]=\"getRecordColor(record)\">\n {{ getInitials(record) }}\n </div>\n }\n }\n\n <div class=\"card-header-content\">\n <h3 class=\"card-title\" [innerHTML]=\"highlightMatch(getFieldValue(record, effectiveTemplate?.titleField ?? null))\"></h3>\n @if (effectiveTemplate?.subtitleField; as subField) {\n @if (subtitleIsPill) {\n <mj-pill [value]=\"getFieldValue(record, subField)\"></mj-pill>\n } @else {\n <span class=\"card-subtitle\" [innerHTML]=\"highlightMatch(getFieldValue(record, subField))\"></span>\n }\n }\n </div>\n\n <button class=\"card-open-btn\" (click)=\"onOpenClick($event, record)\" title=\"Open Record\">\n <i class=\"fa-solid fa-external-link-alt\"></i>\n </button>\n </div>\n\n <!-- Card Body -->\n <div class=\"card-body\">\n @if (effectiveTemplate?.descriptionField; as descField) {\n <p class=\"card-description\" [innerHTML]=\"highlightMatch(getTextValue(record, descField, 100))\"></p>\n }\n\n <!-- Display Fields -->\n @if (effectiveTemplate && effectiveTemplate.displayFields.length > 0) {\n <div class=\"card-fields\">\n @for (field of effectiveTemplate.displayFields; track field.name) {\n <div class=\"field-item\" [class.field-boolean]=\"field.type === 'boolean'\" [class.field-text]=\"field.type === 'text'\">\n @switch (field.type) {\n @case ('boolean') {\n <i class=\"field-icon\" [class.fa-solid]=\"true\"\n [class.fa-check]=\"getBooleanValue(record, field.name)\"\n [class.fa-minus]=\"!getBooleanValue(record, field.name)\"\n [class.bool-true]=\"getBooleanValue(record, field.name)\"\n [class.bool-false]=\"!getBooleanValue(record, field.name)\"></i>\n <span class=\"field-label\">{{ field.label }}</span>\n }\n @case ('number') {\n <span class=\"field-value\">{{ getNumericValue(record, field.name) }}</span>\n <span class=\"field-label\">{{ field.label }}</span>\n }\n @case ('date') {\n <span class=\"field-value field-date\">{{ getDateValue(record, field.name) }}</span>\n <span class=\"field-label\">{{ field.label }}</span>\n }\n @default {\n <span class=\"field-label\">{{ field.label }}</span>\n <span class=\"field-text-value\" [innerHTML]=\"highlightMatch(getTextValue(record, field.name, 40))\"></span>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Card Footer with Badge -->\n @if (effectiveTemplate?.badgeField; as badgeField) {\n <div class=\"card-footer\">\n <mj-pill [value]=\"getFieldValue(record, badgeField)\"></mj-pill>\n </div>\n }\n\n <!-- Hidden field match indicator -->\n @if (hasHiddenFieldMatch(record)) {\n <div class=\"hidden-match-indicator\" [title]=\"'Matched in: ' + getHiddenMatchFieldName(record)\">\n <i class=\"fa-solid fa-magnifying-glass\"></i>\n <span>{{ getHiddenMatchFieldName(record) }}</span>\n </div>\n }\n </div>\n }\n\n @if (effectiveRecords.length === 0) {\n <div class=\"no-results\">\n <i class=\"fa-solid fa-inbox\"></i>\n <p>No records to display</p>\n </div>\n }\n </div>\n }\n</div>\n", styles: [".cards-view-wrapper {\n height: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n}\n\n.cards-container {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 20px;\n padding: 4px;\n}\n\n.data-card {\n background: white;\n border-radius: 12px;\n border: 1px solid #e0e0e0;\n overflow: hidden;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n/* Hover state for unselected cards */\n.data-card:hover:not(.selected) {\n border-color: #bdbdbd;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\n.data-card:hover .card-open-btn {\n opacity: 1;\n}\n\n/* Selected state - subtle but clear */\n.data-card.selected {\n border-color: #1976d2;\n}\n\n.data-card.selected .card-title {\n color: #1565c0;\n}\n\n/* Selected + hover state - same as regular hover */\n.data-card.selected:hover {\n border-color: #1976d2;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\n\n.card-header {\n display: flex;\n align-items: flex-start;\n padding: 16px;\n gap: 12px;\n}\n\n.card-thumbnail {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n overflow: hidden;\n flex-shrink: 0;\n}\n.card-thumbnail img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n.card-avatar {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: 600;\n color: white;\n flex-shrink: 0;\n}\n\n.card-header-content {\n flex: 1;\n min-width: 0;\n}\n\n.card-title {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: #212121;\n line-height: 1.3;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.card-subtitle {\n display: block;\n margin-top: 2px;\n font-size: 12px;\n color: #757575;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.card-open-btn {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #9e9e9e;\n opacity: 0;\n transition: all 0.15s ease;\n flex-shrink: 0;\n}\n.card-open-btn:hover {\n background: #f5f5f5;\n color: #1976d2;\n}\n\n.card-body {\n padding: 0 16px 16px 16px;\n}\n\n.card-description {\n margin: 0 0 12px 0;\n font-size: 13px;\n color: #616161;\n line-height: 1.5;\n}\n\n/* Card Display Fields */\n.card-fields {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n padding-top: 12px;\n border-top: 1px solid #f0f0f0;\n}\n\n.field-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 60px;\n}\n\n.field-item .field-value {\n font-size: 15px;\n font-weight: 600;\n color: #212121;\n}\n\n.field-item .field-label {\n font-size: 10px;\n color: #9e9e9e;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n/* Boolean fields */\n.field-item.field-boolean {\n flex-direction: row;\n align-items: center;\n gap: 6px;\n min-width: auto;\n}\n\n.field-item.field-boolean .field-icon {\n font-size: 14px;\n}\n\n.field-item.field-boolean .field-icon.bool-true {\n color: #2e7d32;\n}\n\n.field-item.field-boolean .field-icon.bool-false {\n color: #bdbdbd;\n}\n\n.field-item.field-boolean .field-label {\n font-size: 12px;\n text-transform: none;\n color: #616161;\n}\n\n/* Text fields */\n.field-item.field-text {\n flex: 1 1 100%;\n max-width: 100%;\n}\n\n.field-item .field-text-value {\n font-size: 13px;\n color: #424242;\n line-height: 1.4;\n word-break: break-word;\n}\n\n/* Date fields */\n.field-item .field-date {\n font-size: 13px;\n font-weight: 500;\n}\n\n.card-footer {\n padding: 12px 16px;\n background: #fafafa;\n border-top: 1px solid #f0f0f0;\n}\n\n/* Highlight matches */\n::ng-deep .highlight-match {\n background-color: #fff176;\n border-radius: 2px;\n}\n\n/* Pill spacing when used as subtitle */\n.card-header-content mj-pill {\n display: block;\n margin-top: 4px;\n}\n\n/* Hidden field match indicator */\n.hidden-match-indicator {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n background: #fff8e1;\n border-top: 1px solid #ffe082;\n font-size: 11px;\n color: #f57c00;\n}\n\n.hidden-match-indicator i {\n font-size: 10px;\n}\n\n.hidden-match-indicator span {\n font-weight: 500;\n}\n\n/* Loading state */\n.loading-container {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 200px;\n padding: 40px;\n}\n\n/* No results state */\n.no-results {\n grid-column: 1 / -1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 20px;\n color: #9e9e9e;\n text-align: center;\n}\n.no-results i {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n}\n.no-results p {\n margin: 0;\n font-size: 14px;\n}\n"] }]
777
+ }], () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], { entity: [{
778
+ type: Input
779
+ }], records: [{
780
+ type: Input
781
+ }], selectedRecordId: [{
782
+ type: Input
783
+ }], cardTemplate: [{
784
+ type: Input
785
+ }], hiddenFieldMatches: [{
786
+ type: Input
787
+ }], filterText: [{
788
+ type: Input
789
+ }], pageSize: [{
790
+ type: Input
791
+ }], recordSelected: [{
792
+ type: Output
793
+ }], recordOpened: [{
794
+ type: Output
795
+ }] }); })();
796
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EntityCardsComponent, { className: "EntityCardsComponent" }); })();
797
+ //# sourceMappingURL=entity-cards.component.js.map